diff --git a/.github/workflows/.deploy.yml b/.github/workflows/.deploy.yml index 09acc648d..4b03e17ce 100644 --- a/.github/workflows/.deploy.yml +++ b/.github/workflows/.deploy.yml @@ -79,6 +79,7 @@ jobs: overwrite: true parameters: -p ZONE=${{ inputs.target }} + -p DB_PASSWORD='${{ secrets.DB_PASSWORD }}' -p FORESTCLIENTAPI_KEY='${{ secrets.FORESTCLIENTAPI_KEY }}' -p ORACLE_PASSWORD='${{ secrets.ORACLE_PASSWORD }}' -p ORACLE_SERVICE='${{ vars.ORACLE_SERVICE }}' @@ -86,6 +87,8 @@ jobs: -p ORACLE_SYNC_USER='${{ vars.ORACLE_SYNC_USER }}' -p ORACLE_SYNC_PASSWORD='${{ secrets.ORACLE_SYNC_PASSWORD }}' -p ORACLE_CERT_SECRET='${{ secrets.ORACLE_CERT_SECRET }}' + -p ORACLE_HOST='${{ vars.ORACLE_HOST }}' + -p VITE_USER_POOLS_WEB_CLIENT_ID=${{ secrets.VITE_USER_POOLS_WEB_CLIENT_ID }} - name: Database if: steps.triggers.outputs.core == 'true' || steps.triggers.outputs.sync == 'true' @@ -94,18 +97,18 @@ jobs: oc_namespace: ${{ vars.OC_NAMESPACE }} oc_server: ${{ vars.OC_SERVER }} oc_token: ${{ secrets.OC_TOKEN }} - file: database/openshift.deploy.yml + file: common/openshift.database.yml overwrite: false parameters: - -p TAG=${{ inputs.tag }} -p ZONE=${{ inputs.target }} - -p DB_PASSWORD='${{ secrets.DB_PASSWORD }}' ${{ github.event_name == 'pull_request' && '-p DB_PVC_SIZE=192Mi' || '' }} + ${{ github.event_name == 'pull_request' && '-p MEMORY_REQUEST=100Mi' || '' }} + ${{ github.event_name == 'pull_request' && '-p MEMORY_LIMIT=200Mi' || '' }} deploy: name: Deploy - if: needs.init.outputs.deploy_core == 'true' environment: ${{ inputs.environment }} + if: needs.init.outputs.deploy_core == 'true' needs: [init] runs-on: ubuntu-22.04 timeout-minutes: 10 @@ -126,7 +129,6 @@ jobs: -p FAM_MODDED_ZONE=${{ needs.init.outputs.fam-modded-zone }} -p VITE_SPAR_BUILD_VERSION=snapshot-${{ inputs.target || github.event.number }} -p VITE_USER_POOLS_ID=${{ vars.VITE_USER_POOLS_ID }} - -p VITE_USER_POOLS_WEB_CLIENT_ID=${{ vars.VITE_USER_POOLS_WEB_CLIENT_ID }} - name: oracle-api file: oracle-api/openshift.deploy.yml overwrite: true @@ -148,7 +150,7 @@ jobs: -p TAG=${{ inputs.tag }} -p ZONE=${{ inputs.target }} ${{ github.event_name == 'pull_request' && '-p MIN_REPLICAS=1' || '' }} - ${{ github.event_name == 'pull_request' && '-p MAX_REPLICAS=2' || '' }} + ${{ github.event_name == 'pull_request' && '-p MAX_REPLICAS=1' || '' }} ${{ matrix.parameters }} verification_path: ${{ matrix.verification_path }} verification_retry_attempts: 5 @@ -157,6 +159,7 @@ jobs: # ETL testing will only run on Pull Requests if the sync/ directory is modified sync: name: Deploy (sync) + environment: ${{ inputs.environment }} if: needs.init.outputs.deploy_sync == 'true' needs: [init] runs-on: ubuntu-latest @@ -173,7 +176,8 @@ jobs: parameters: -p TAG=${{ inputs.tag }} -p ZONE=${{ inputs.target }} - -p TEST_MODE=true + ${{ github.event_name == 'pull_request' && '-p TEST_MODE=true' || '' }} + - name: Override OpenShift version if: github.event_name == 'pull_request' @@ -187,25 +191,4 @@ jobs: - name: Run sync ETL if: github.event_name == 'pull_request' - run: | - # Run and verify job - - # Login - oc login --token=${{ secrets.oc_token }} --server=${{ vars.oc_server }} - oc project ${{ secrets.oc_namespace }} #Safeguard! - - # Exit on errors or unset variables - set -eu - - # Create job - CRONJOB=nr-spar-${{ inputs.target }}-sync - RUN_JOB=${CRONJOB}--$(date +"%Y-%m-%d--%H-%M-%S") - oc create job ${RUN_JOB} --from=cronjob/${CRONJOB} - - # Follow - oc wait --for=condition=ready pod --selector=job-name=${RUN_JOB} --timeout=1m - oc logs -l job-name=${RUN_JOB} --tail=50 --follow - - # Verify successful completion - oc wait --for jsonpath='{.status.phase}'=Succeeded pod --selector=job-name=${RUN_JOB} --timeout=1m - echo "Job successful!" + run: ./sync/oc_run.sh ${{ inputs.tag }} ${{ secrets.oc_token }} diff --git a/.github/workflows/.tests.yml b/.github/workflows/.tests.yml index 484509b4b..f56398143 100644 --- a/.github/workflows/.tests.yml +++ b/.github/workflows/.tests.yml @@ -26,7 +26,7 @@ jobs: VITE_SERVER_URL: https://${{ github.event.repository.name }}-${{ inputs.target }}-backend.apps.silver.devops.gov.bc.ca VITE_ORACLE_SERVER_URL: https://nr-spar-${{ inputs.target }}-oracle-api.apps.silver.devops.gov.bc.ca VITE_USER_POOLS_ID: ${{ vars.VITE_USER_POOLS_ID }} - VITE_USER_POOLS_WEB_CLIENT_ID: ${{ vars.VITE_USER_POOLS_WEB_CLIENT_ID }} + VITE_USER_POOLS_WEB_CLIENT_ID: ${{ secrets.VITE_USER_POOLS_WEB_CLIENT_ID }} VITE_ZONE: TEST runs-on: ubuntu-22.04 steps: diff --git a/.github/workflows/demo.yml b/.github/workflows/demo.yml index 57b91aca3..394bea6d4 100644 --- a/.github/workflows/demo.yml +++ b/.github/workflows/demo.yml @@ -30,7 +30,7 @@ jobs: # Login oc login --token=${{ secrets.oc_token }} --server=${{ vars.oc_server }} - oc project ${{ secrets.oc_namespace }} #Safeguard! + oc project ${{ vars.oc_namespace }} #Safeguard! # Delete and replace route oc delete route/${{ env.REPO }}-${{ env.DEST }} --ignore-not-found=true diff --git a/.github/workflows/job-sync.yml b/.github/workflows/job-sync.yml index 4f901000b..6e427b5ea 100644 --- a/.github/workflows/job-sync.yml +++ b/.github/workflows/job-sync.yml @@ -9,10 +9,10 @@ concurrency: cancel-in-progress: false jobs: - sync: - name: Sync - runs-on: ubuntu-latest + sync-test: environment: test + name: Sync (TEST) + runs-on: ubuntu-latest steps: - name: Override OpenShift version env: @@ -23,26 +23,24 @@ jobs: oc version working-directory: /usr/local/bin/ - - name: ETL Sync - run: | - # Run and verify job - - # Login - oc login --token=${{ secrets.oc_token }} --server=${{ vars.oc_server }} - oc project ${{ secrets.oc_namespace }} #Safeguard! + - uses: actions/checkout@v4 + - name: ETL (TEST) + run: ./sync/oc_run.sh test ${{ secrets.oc_token }} - # Exit on errors or unset variables - set -eu - - # Create job - CRONJOB=nr-spar-test-sync - RUN_JOB=${CRONJOB}--$(date +"%Y-%m-%d--%H-%M-%S") - oc create job ${RUN_JOB} --from=cronjob/${CRONJOB} - - # Follow - oc wait --for=condition=ready pod --selector=job-name=${RUN_JOB} --timeout=1m - oc logs -l job-name=${RUN_JOB} --tail=50 --follow + sync-prod: + environment: prod + name: Sync (PROD) + runs-on: ubuntu-latest + steps: + - name: Override OpenShift version + env: + OC: https://mirror.openshift.com/pub/openshift-v4/clients/ocp/stable-4.13/openshift-client-linux.tar.gz + run: | + # Download and extract with retry, continuing on error + (wget ${{ env.OC }} -qcO - | tar -xzvf - oc)|| !! || true + oc version + working-directory: /usr/local/bin/ - # Verify successful completion - oc wait --for jsonpath='{.status.phase}'=Succeeded pod --selector=job-name=${RUN_JOB} --timeout=1m - echo "Job successful!" + - uses: actions/checkout@v4 + - name: ETL (PROD) + run: ./sync/oc_run.sh prod ${{ secrets.oc_token }} diff --git a/.github/workflows/merge.yml b/.github/workflows/merge.yml index 2eff03af9..c6a0f62e2 100644 --- a/.github/workflows/merge.yml +++ b/.github/workflows/merge.yml @@ -36,12 +36,35 @@ jobs: tag: ${{ needs.init.outputs.pr }} target: test - deploy-prod: - name: PROD + promote: + name: Promote Images + env: + target: ${{ needs.init.outputs.pr }} + tag: prod needs: [init, deploys] - secrets: inherit - uses: ./.github/workflows/.deploy.yml - with: - environment: prod - tag: ${{ needs.init.outputs.pr }} - target: prod + runs-on: ubuntu-latest + strategy: + matrix: + package: [backend, frontend, oracle-api, sync] + steps: + - uses: shrink/actions-docker-registry-tag@v4 + with: + registry: ghcr.io + repository: ${{ github.repository }}/${{ matrix.package }} + target: ${{ env.target }} + tags: ${{ env.tag }} + + - run: | + # Verify tagging + INSPECT="docker manifest inspect ghcr.io/${{ github.repository }}/${{ matrix.package }}" + TARGET=$(${INSPECT}:${{ env.target }} | jq -r '.manifests[] | select(.platform.architecture=="amd64") | .digest') + TAG=$(${INSPECT}:${{ env.tag }} | jq -r '.manifests[] | select(.platform.architecture=="amd64") | .digest') + echo "TARGET: ${TARGET}" + echo "TAG: ${TAG}" + if [ "${TARGET}" != "${TAG}" ]; then + echo "ERROR: Tagging failed!" + echo "RETRY=true" >> $GITHUB_ENV + else + echo "ERROR: Tagging success!" + echo "RETRY=false" >> $GITHUB_ENV + fi diff --git a/.github/workflows/pr-close.yml b/.github/workflows/pr-close.yml index 4ba32bb0b..560ede7ac 100644 --- a/.github/workflows/pr-close.yml +++ b/.github/workflows/pr-close.yml @@ -18,5 +18,5 @@ jobs: oc_token: ${{ secrets.OC_TOKEN }} with: cleanup: label - packages: database backend frontend oracle-api sync + packages: backend frontend oracle-api sync diff --git a/.github/workflows/pr-open.yml b/.github/workflows/pr-open.yml index d6e8bde2b..eb8ddf388 100644 --- a/.github/workflows/pr-open.yml +++ b/.github/workflows/pr-open.yml @@ -18,11 +18,14 @@ jobs: packages: write strategy: matrix: - package: [database, common, backend, frontend, oracle-api, sync] + package: [backend, frontend, oracle-api, sync] steps: - uses: bcgov-nr/action-builder-ghcr@v2.2.0 id: build with: + build_args: | + BUILD_NUMBER=${{ github.event.number }} + BUILDKIT_INLINE_CACHE=1 package: ${{ matrix.package }} tag: ${{ github.event.number }} tag_fallback: latest diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 000000000..cf890aa12 --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,25 @@ +name: PROD + +on: + release: + types: [published] + workflow_dispatch: + inputs: + tag: + description: 'The tag set to deploy; e.g. latest or PR number' + required: false + +concurrency: + # Do not interrupt previous workflows + group: ${{ github.workflow }} + cancel-in-progress: false + +jobs: + deploy-prod: + name: PROD + secrets: inherit + uses: ./.github/workflows/.deploy.yml + with: + environment: prod + tag: ${{ inputs.tag || 'prod' }} + target: prod diff --git a/backend/.mvn/wrapper/maven-wrapper.properties b/backend/.mvn/wrapper/maven-wrapper.properties index ecbbc633c..443d8849e 100644 --- a/backend/.mvn/wrapper/maven-wrapper.properties +++ b/backend/.mvn/wrapper/maven-wrapper.properties @@ -15,4 +15,4 @@ # specific language governing permissions and limitations # under the License. wrapperVersion=3.3.1 -distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.9.8/apache-maven-3.9.8-bin.zip +distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.9.9/apache-maven-3.9.9-bin.zip diff --git a/backend/Dockerfile b/backend/Dockerfile index 6bf1415b6..4c875881b 100644 --- a/backend/Dockerfile +++ b/backend/Dockerfile @@ -15,6 +15,10 @@ RUN ./mvnw -B package -Pnative -DskipTests FROM gcr.io/distroless/java-base:nonroot AS deploy ARG PORT=8090 +# Receive build number as argument, retain as environment variable +ARG BUILD_NUMBER +ENV BUILD_NUMBER=${BUILD_NUMBER} + # Copy WORKDIR /app COPY --from=build /app/target/nr-spar-backend ./nr-spar-backend diff --git a/backend/openshift.deploy.yml b/backend/openshift.deploy.yml index bfcb69af5..b811cf3d7 100644 --- a/backend/openshift.deploy.yml +++ b/backend/openshift.deploy.yml @@ -35,13 +35,13 @@ parameters: - name: FORESTCLIENTAPI_ADDRESS value: "https://nr-forest-client-api-prod.api.gov.bc.ca/api" - name: CPU_REQUEST - value: 100m + value: 25m - name: CPU_LIMIT - value: 300m + value: 100m - name: MEMORY_REQUEST - value: 100Mi + value: 150Mi - name: MEMORY_LIMIT - value: 500Mi + value: 450Mi - name: MIN_REPLICAS description: The minimum amount of replicas for the horizontal pod autoscaler. value: "3" @@ -64,6 +64,10 @@ parameters: - name: AWS_COGNITO_ISSUER_URI description: AWS Cognito JWT Server URI required: true + - name: RANDOM_EXPRESSION + description: Random expression to make sure deployments update + from: "[a-zA-Z0-9]{32}" + generate: expression objects: - apiVersion: apps/v1 kind: Deployment @@ -72,7 +76,7 @@ objects: app: ${NAME}-${ZONE} name: ${NAME}-${ZONE}-${COMPONENT} spec: - replicas: 1 + replicas: ${{MIN_REPLICAS}} selector: matchLabels: deployment: ${NAME}-${ZONE}-${COMPONENT} @@ -133,6 +137,8 @@ objects: value: ${DB_POOL_MAX_LIFETIME} - name: AWS_COGNITO_ISSUER_URI value: ${AWS_COGNITO_ISSUER_URI} + - name: RANDOM_EXPRESSION + value: ${RANDOM_EXPRESSION} resources: requests: cpu: ${CPU_REQUEST} @@ -204,3 +210,9 @@ objects: target: type: Utilization averageUtilization: 80 + - type: Resource + resource: + name: memory + target: + type: Utilization + averageUtilization: 80 diff --git a/backend/pom.xml b/backend/pom.xml index 5e8299412..cd6cb249d 100644 --- a/backend/pom.xml +++ b/backend/pom.xml @@ -5,7 +5,7 @@ org.springframework.boot spring-boot-starter-parent - 3.3.1 + 3.3.2 ca.bc.gov @@ -123,7 +123,7 @@ org.projectlombok lombok - 1.18.32 + 1.18.34 true @@ -164,7 +164,7 @@ com.h2database h2 - 2.2.224 + 2.3.230 test @@ -199,7 +199,7 @@ org.springdoc springdoc-openapi-starter-webmvc-ui - 2.5.0 + 2.6.0 @@ -254,7 +254,7 @@ org.apache.maven.plugins maven-failsafe-plugin - 3.3.0 + 3.3.1 integration-tests @@ -277,7 +277,7 @@ org.apache.maven.plugins maven-surefire-plugin - 3.3.0 + 3.3.1 @{argLine} -Xmx1024m ${skip.unit.tests} @@ -403,7 +403,7 @@ org.apache.maven.plugins maven-javadoc-plugin - 3.7.0 + 3.8.0 17 Javadoc Documentation for ${project.name} ${project.version} 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 25df5ab75..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 @@ -5,5 +5,7 @@ public final class Constants { public static final Integer CLASS_A_SEEDLOT_NUM_MIN = 63000; public static final Integer CLASS_A_SEEDLOT_NUM_MAX = 69999; - public static final String CLASS_A_SEEDLOT_STATUS = "PND"; + 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/config/NativeImageConfig.java b/backend/src/main/java/ca/bc/gov/backendstartapi/config/NativeImageConfig.java index c9017901a..bd2ba47bb 100644 --- a/backend/src/main/java/ca/bc/gov/backendstartapi/config/NativeImageConfig.java +++ b/backend/src/main/java/ca/bc/gov/backendstartapi/config/NativeImageConfig.java @@ -57,15 +57,14 @@ import ca.bc.gov.backendstartapi.enums.parser.ConeAndPollenCountHeader; import ca.bc.gov.backendstartapi.enums.parser.CsvParsingHeader; import ca.bc.gov.backendstartapi.enums.parser.SmpMixHeader; +import ca.bc.gov.backendstartapi.util.ValueUtil; import ca.bc.gov.backendstartapi.vo.parser.ConeAndPollenCount; import ca.bc.gov.backendstartapi.vo.parser.SmpMixVolume; import org.springframework.aot.hint.annotation.RegisterReflectionForBinding; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.ImportRuntimeHints; -/** - * This class holds configurations for building Cloud Native images. - */ +/** This class holds configurations for building Cloud Native images. */ @Configuration @RegisterReflectionForBinding({ DescribedEnumDto.class, @@ -136,8 +135,7 @@ CsvParsingHeader.class, SmpMixHeader.class, SeedlotSaveInMemoryDto.class, + ValueUtil.class, }) @ImportRuntimeHints(value = {HttpServletRequestRuntimeHint.class}) -public class NativeImageConfig { - -} +public class NativeImageConfig {} diff --git a/backend/src/main/java/ca/bc/gov/backendstartapi/dto/GeneticWorthDto.java b/backend/src/main/java/ca/bc/gov/backendstartapi/dto/GeneticWorthDto.java new file mode 100644 index 000000000..532673895 --- /dev/null +++ b/backend/src/main/java/ca/bc/gov/backendstartapi/dto/GeneticWorthDto.java @@ -0,0 +1,23 @@ +package ca.bc.gov.backendstartapi.dto; + +import io.swagger.v3.oas.annotations.media.Schema; +import java.math.BigDecimal; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +/** This class represents a response body when a genetic worth entity is requested. */ +@Getter +@Setter +@NoArgsConstructor +@AllArgsConstructor +public class GeneticWorthDto extends CodeDescriptionDto { + @Schema(description = "The default breeding value", example = "0.0") + private BigDecimal defaultBv; + + public GeneticWorthDto(String code, String description, BigDecimal defaultBv) { + super(code, description); + this.defaultBv = defaultBv; + } +} diff --git a/backend/src/main/java/ca/bc/gov/backendstartapi/dto/OrchardDto.java b/backend/src/main/java/ca/bc/gov/backendstartapi/dto/OrchardDto.java index 8be603f6d..255ff7ab7 100644 --- a/backend/src/main/java/ca/bc/gov/backendstartapi/dto/OrchardDto.java +++ b/backend/src/main/java/ca/bc/gov/backendstartapi/dto/OrchardDto.java @@ -1,49 +1,71 @@ package ca.bc.gov.backendstartapi.dto; import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Getter; +import lombok.Setter; /** * This record represents the OrchardLotTypeDescriptionDto object found in the oracle-api service. */ -@Schema(description = "Represents an Orchard object received from oracle-api.") -public record OrchardDto( - @Schema( - description = - """ +@Schema(description = "Represents an Orchard object received from oracle-api except spuId.") +@Getter +@Setter +public class OrchardDto { + @Schema( + description = + """ A unique identifier which is assigned to a location where cuttings or A class seed is produced. """, - example = "339") - String id, - @Schema( - description = "The name of a location where cuttings or A class seed is produced.", - example = "EAGLEROCK") - String name, - @Schema(description = "A code which represents a species of tree or brush.", example = "PLI") - String vegetationCode, - @Schema( - description = - """ + example = "339") + private String id; + + @Schema( + description = "The name of a location where cuttings or A class seed is produced.", + example = "EAGLEROCK") + private String name; + + @Schema(description = "A code which represents a species of tree or brush.", example = "PLI") + private String vegetationCode; + + @Schema( + description = + """ A code representing a type of orchard. The two values will be 'S' (Seed Lot) or 'C' (Cutting Lot). """, - example = "S") - Character lotTypeCode, - @Schema( - description = - """ + example = "S") + private Character lotTypeCode; + + @Schema( + description = + """ A description of the Orchard Lot Type code. The two values will be 'Seed Lot' or 'Cutting Lot'. """, - example = "Seed Lot") - String lotTypeDescription, - @Schema( - description = "A code which represents the current stage or status of an orchard.", - example = "PRD") - String stageCode, - @Schema(description = "The bgc zone code", example = "SBS") String becZoneCode, - @Schema(description = "The description of a bgc zone code", example = "Sub-Boreal Spruce") - String becZoneDescription, - @Schema(description = "The bgc sub-zone code", example = "wk") String becSubzoneCode, - @Schema(description = "The variant.", example = "1") Character variant, - @Schema(description = "The bec version id.", example = "5") Integer becVersionId) {} + example = "Seed Lot") + private String lotTypeDescription; + + @Schema( + description = "A code which represents the current stage or status of an orchard.", + example = "PRD") + private String stageCode; + + @Schema(description = "The bgc zone code", example = "SBS") + private String becZoneCode; + + @Schema(description = "The description of a bgc zone code", example = "Sub-Boreal Spruce") + private String becZoneDescription; + + @Schema(description = "The bgc sub-zone code", example = "wk") + private String becSubzoneCode; + + @Schema(description = "The variant.", example = "1") + private Character variant; + + @Schema(description = "The bec version id.", example = "5") + private Integer becVersionId; + + @Schema(description = "Seed Plan Unit id", example = "7") + private Integer spuId; +} diff --git a/backend/src/main/java/ca/bc/gov/backendstartapi/dto/ParentTreeGeneticQualityDto.java b/backend/src/main/java/ca/bc/gov/backendstartapi/dto/ParentTreeGeneticQualityDto.java index 49a9da361..da9660733 100644 --- a/backend/src/main/java/ca/bc/gov/backendstartapi/dto/ParentTreeGeneticQualityDto.java +++ b/backend/src/main/java/ca/bc/gov/backendstartapi/dto/ParentTreeGeneticQualityDto.java @@ -17,9 +17,9 @@ public record ParentTreeGeneticQualityDto( @Schema( description = """ - Describes the comparative measure of genetic value for a specific genetic trait of a - parent tree. Examples are BV (Breeding Value) and CV (Clonal Value). - """, + Describes the comparative measure of genetic value for a specific genetic trait of a + parent tree. Examples are BV (Breeding Value) and CV (Clonal Value). + """, example = "BV") String geneticTypeCode, @Schema(description = "A code describing various Genetic Worths.", example = "GVO") @@ -27,8 +27,14 @@ parent tree. Examples are BV (Breeding Value) and CV (Clonal Value). @Schema( description = """ - The genetic quality value based on the test assessment for a Parent Tree from a test - no. and series. - """, + The genetic quality value based on the test assessment for a Parent Tree from a test + no. and series. + """, example = "18") - BigDecimal geneticQualityValue) {} + BigDecimal geneticQualityValue, + @Schema(description = "Whether the parent tree is tested", example = "true") + Boolean isParentTreeTested, + @Schema( + description = "Whether the genetic quality value is using default value.", + example = "false") + Boolean isEstimated) {} diff --git a/backend/src/main/java/ca/bc/gov/backendstartapi/dto/PtValsCalReqDto.java b/backend/src/main/java/ca/bc/gov/backendstartapi/dto/PtValsCalReqDto.java index 20dbf9989..a0af27c45 100644 --- a/backend/src/main/java/ca/bc/gov/backendstartapi/dto/PtValsCalReqDto.java +++ b/backend/src/main/java/ca/bc/gov/backendstartapi/dto/PtValsCalReqDto.java @@ -10,4 +10,5 @@ "An object representing the request body for the parent tree and SMP values calculations.") public record PtValsCalReqDto( @NotNull List orchardPtVals, - @NotNull List smpMixIdAndProps) {} + @NotNull List smpMixIdAndProps, + @NotNull Integer smpParentsOutside) {} diff --git a/backend/src/main/java/ca/bc/gov/backendstartapi/dto/SameSpeciesTreeDto.java b/backend/src/main/java/ca/bc/gov/backendstartapi/dto/SameSpeciesTreeDto.java index 34fc2f7d0..2bd0d031a 100644 --- a/backend/src/main/java/ca/bc/gov/backendstartapi/dto/SameSpeciesTreeDto.java +++ b/backend/src/main/java/ca/bc/gov/backendstartapi/dto/SameSpeciesTreeDto.java @@ -36,6 +36,9 @@ public class SameSpeciesTreeDto { @Schema(description = "The seed plan unit this tree belongs to.", example = "7") private Long spu; + @Schema(description = "Whether the tree is tested.", example = "True") + private Boolean tested; + private List parentTreeGeneticQualities; public SameSpeciesTreeDto() { diff --git a/backend/src/main/java/ca/bc/gov/backendstartapi/dto/SeedlotFormCollectionDto.java b/backend/src/main/java/ca/bc/gov/backendstartapi/dto/SeedlotFormCollectionDto.java index e30a75df8..adbd7f32e 100644 --- a/backend/src/main/java/ca/bc/gov/backendstartapi/dto/SeedlotFormCollectionDto.java +++ b/backend/src/main/java/ca/bc/gov/backendstartapi/dto/SeedlotFormCollectionDto.java @@ -3,7 +3,7 @@ import io.swagger.v3.oas.annotations.media.Schema; import jakarta.validation.constraints.NotNull; import java.math.BigDecimal; -import java.time.LocalDateTime; +import java.time.LocalDate; import java.util.List; /** This record represents the seedlot form step 1. */ @@ -36,7 +36,7 @@ The actual start date (year, month, and day) that the cones (source for seedlots """, example = "2023/11/20") @NotNull - LocalDateTime collectionStartDate, + LocalDate collectionStartDate, @Schema( description = """ @@ -45,7 +45,7 @@ The actual end date (year, month, and day) that the cones (source for seedlots) """, example = "2023/11/30") @NotNull - LocalDateTime collectionEndDate, + LocalDate collectionEndDate, @Schema( description = "The number of containers (sacks of cones) that were collected.", example = "2") diff --git a/backend/src/main/java/ca/bc/gov/backendstartapi/dto/SeedlotFormExtractionDto.java b/backend/src/main/java/ca/bc/gov/backendstartapi/dto/SeedlotFormExtractionDto.java index bbf2f8879..6b083ac6f 100644 --- a/backend/src/main/java/ca/bc/gov/backendstartapi/dto/SeedlotFormExtractionDto.java +++ b/backend/src/main/java/ca/bc/gov/backendstartapi/dto/SeedlotFormExtractionDto.java @@ -2,7 +2,7 @@ import io.swagger.v3.oas.annotations.media.Schema; import jakarta.validation.constraints.NotNull; -import java.time.LocalDateTime; +import java.time.LocalDate; /** This record represents the seedlot form step 6. */ @Schema(description = "Seedlot extration and storage information. Form step 6") @@ -34,7 +34,7 @@ The actual start date (year, month, and day) when the seed was extracted from th """, example = "2023/11/23", nullable = true) - LocalDateTime extractionStDate, + LocalDate extractionStDate, @Schema( description = """ @@ -43,7 +43,7 @@ The actual end date (year, month, and day) when the seed was extracted from the """, example = "2023/11/23", nullable = true) - LocalDateTime extractionEndDate, + LocalDate extractionEndDate, @Schema( description = """ @@ -66,9 +66,9 @@ A code to uniquely identify, within each client (storage), the addresses of diff description = "Commencement date of temporary Seedlot storage.", example = "2023/11/23", nullable = true) - LocalDateTime temporaryStrgStartDate, + LocalDate temporaryStrgStartDate, @Schema( description = "End date of Seedlot temporary storage.", example = "2023/11/23", nullable = true) - LocalDateTime temporaryStrgEndDate) {} + LocalDate temporaryStrgEndDate) {} diff --git a/backend/src/main/java/ca/bc/gov/backendstartapi/dto/SeedlotFormInterimDto.java b/backend/src/main/java/ca/bc/gov/backendstartapi/dto/SeedlotFormInterimDto.java index eb12df33c..7ad99a5ee 100644 --- a/backend/src/main/java/ca/bc/gov/backendstartapi/dto/SeedlotFormInterimDto.java +++ b/backend/src/main/java/ca/bc/gov/backendstartapi/dto/SeedlotFormInterimDto.java @@ -2,7 +2,7 @@ import io.swagger.v3.oas.annotations.media.Schema; import jakarta.validation.constraints.NotNull; -import java.time.LocalDateTime; +import java.time.LocalDate; /** This record represents the seedlot form step 3. */ @Schema(description = "Seedlot interim information. Form step 3") @@ -34,7 +34,7 @@ The actual start date (year, month, and day) when the cone was stored during int """, example = "2023/12/20") @NotNull - LocalDateTime intermStrgStDate, + LocalDate intermStrgStDate, @Schema( description = """ @@ -43,9 +43,10 @@ The actual end date (year, month, and day) when the cone was stored during inter """, example = "2023/12/21") @NotNull - LocalDateTime intermStrgEndDate, + LocalDate intermStrgEndDate, @Schema( - description = """ + description = + """ Description of the storage facility type when 'Other' option is chosen. """, example = "Mini fridge", diff --git a/backend/src/main/java/ca/bc/gov/backendstartapi/endpoint/GeneticWorthEndpoint.java b/backend/src/main/java/ca/bc/gov/backendstartapi/endpoint/GeneticWorthEndpoint.java index 102c10cf8..b194eaaf9 100644 --- a/backend/src/main/java/ca/bc/gov/backendstartapi/endpoint/GeneticWorthEndpoint.java +++ b/backend/src/main/java/ca/bc/gov/backendstartapi/endpoint/GeneticWorthEndpoint.java @@ -1,16 +1,15 @@ package ca.bc.gov.backendstartapi.endpoint; import ca.bc.gov.backendstartapi.dto.CodeDescriptionDto; +import ca.bc.gov.backendstartapi.dto.GeneticWorthDto; import ca.bc.gov.backendstartapi.entity.GeneticWorthEntity; import ca.bc.gov.backendstartapi.security.RoleAccessConfig; import ca.bc.gov.backendstartapi.service.GeneticWorthService; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Parameter; import io.swagger.v3.oas.annotations.enums.ParameterIn; -import io.swagger.v3.oas.annotations.media.ArraySchema; import io.swagger.v3.oas.annotations.media.Content; import io.swagger.v3.oas.annotations.media.Schema; -import io.swagger.v3.oas.annotations.media.SchemaProperty; import io.swagger.v3.oas.annotations.responses.ApiResponse; import io.swagger.v3.oas.annotations.responses.ApiResponses; import io.swagger.v3.oas.annotations.tags.Tag; @@ -47,34 +46,14 @@ public class GeneticWorthEndpoint { value = { @ApiResponse( responseCode = "200", - description = "An array of objects containing code and description for each value.", - content = - @Content( - array = @ArraySchema(schema = @Schema(type = "object")), - mediaType = "application/json", - schemaProperties = { - @SchemaProperty( - name = "code", - schema = - @Schema( - type = "string", - description = "This object represents a genetic worth code", - example = "AD")), - @SchemaProperty( - name = "description", - schema = - @Schema( - type = "string", - description = "The description of a genetic worth", - example = "Animal browse resistance (deer)")) - })), + description = "An array of objects containing code and description for each value."), @ApiResponse( responseCode = "401", description = "Access token is missing or invalid", content = @Content(schema = @Schema(implementation = Void.class))) }) @RoleAccessConfig({"SPAR_TSC_ADMIN", "SPAR_MINISTRY_ORCHARD", "SPAR_NONMINISTRY_ORCHARD"}) - public List getAllGeneticWorth() { + public List getAllGeneticWorth() { return geneticWorthService.getAllGeneticWorth(); } diff --git a/backend/src/main/java/ca/bc/gov/backendstartapi/endpoint/OrchardEndpoint.java b/backend/src/main/java/ca/bc/gov/backendstartapi/endpoint/OrchardEndpoint.java index fc24811a5..a5ff153d6 100644 --- a/backend/src/main/java/ca/bc/gov/backendstartapi/endpoint/OrchardEndpoint.java +++ b/backend/src/main/java/ca/bc/gov/backendstartapi/endpoint/OrchardEndpoint.java @@ -1,14 +1,12 @@ package ca.bc.gov.backendstartapi.endpoint; +import ca.bc.gov.backendstartapi.dto.OrchardDto; import ca.bc.gov.backendstartapi.dto.OrchardSpuDto; -import ca.bc.gov.backendstartapi.dto.ParentTreeDto; -import ca.bc.gov.backendstartapi.dto.SameSpeciesTreeDto; import ca.bc.gov.backendstartapi.security.RoleAccessConfig; import ca.bc.gov.backendstartapi.service.OrchardService; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Parameter; import io.swagger.v3.oas.annotations.enums.ParameterIn; -import io.swagger.v3.oas.annotations.media.ArraySchema; import io.swagger.v3.oas.annotations.media.Content; import io.swagger.v3.oas.annotations.media.Schema; import io.swagger.v3.oas.annotations.responses.ApiResponse; @@ -66,31 +64,24 @@ public OrchardSpuDto getParentTreeGeneticQualityData( } /** - * Get all parent trees under a species (VegCode). + * Get all Orchards under a species (VegCode). * - * @return A list of {@link ParentTreeDto} + * @return A list of {@link OrchardDto} */ - @GetMapping(path = "/parent-trees/vegetation-codes/{vegCode}", produces = "application/json") + @GetMapping("/vegetation-codes/{vegCode}") @Operation( summary = "Retrieves all parent trees under a species (VegCode)", description = "Returns a list containing all parent trees under a species (VegCode).") @ApiResponses( value = { - @ApiResponse( - responseCode = "200", - description = "An array of parent tree dto.", - content = - @Content( - array = - @ArraySchema(schema = @Schema(implementation = SameSpeciesTreeDto.class)), - mediaType = "application/json")), + @ApiResponse(responseCode = "200", description = "An array of parent tree dto."), @ApiResponse( responseCode = "401", description = "Access token is missing or invalid", content = @Content(schema = @Schema(implementation = Void.class))) }) @RoleAccessConfig({"SPAR_TSC_ADMIN", "SPAR_MINISTRY_ORCHARD", "SPAR_NONMINISTRY_ORCHARD"}) - public List getAllParentTreeByVegCode( + public List getAllOrchards( @PathVariable("vegCode") @Pattern(regexp = "^[a-zA-Z]{1,8}$") @Parameter( @@ -98,6 +89,6 @@ public List getAllParentTreeByVegCode( in = ParameterIn.PATH, description = "Identifier of the Orchard.") String vegCode) { - return orchardService.findParentTreesByVegCode(vegCode); + return orchardService.findAllOrchardsByVegCode(vegCode); } } diff --git a/backend/src/main/java/ca/bc/gov/backendstartapi/endpoint/SeedlotEndpoint.java b/backend/src/main/java/ca/bc/gov/backendstartapi/endpoint/SeedlotEndpoint.java index 1306bcc9a..987abfd44 100644 --- a/backend/src/main/java/ca/bc/gov/backendstartapi/endpoint/SeedlotEndpoint.java +++ b/backend/src/main/java/ca/bc/gov/backendstartapi/endpoint/SeedlotEndpoint.java @@ -1,5 +1,6 @@ package ca.bc.gov.backendstartapi.endpoint; +import ca.bc.gov.backendstartapi.config.SparLog; import ca.bc.gov.backendstartapi.dto.RevisionCountDto; import ca.bc.gov.backendstartapi.dto.SaveSeedlotFormDtoClassA; import ca.bc.gov.backendstartapi.dto.SeedlotAclassFormDto; @@ -31,6 +32,7 @@ import io.swagger.v3.oas.annotations.tags.Tag; import jakarta.validation.Valid; import java.io.IOException; +import java.time.Instant; import java.util.List; import java.util.Optional; import lombok.RequiredArgsConstructor; @@ -166,7 +168,7 @@ private Resource getFileResource(MultipartFile file) { /** * Created a new Seedlot in the system. * - * @param createDto A {@link SeedlotCreateDto} containig all required field to get a new + * @param createDto A {@link SeedlotCreateDto} containing all required field to get a new * registration started. * @return A {@link SeedlotStatusResponseDto} with all created values. */ @@ -206,7 +208,10 @@ public ResponseEntity createSeedlot( @RequestBody @Valid SeedlotCreateDto createDto) { + long started = Instant.now().toEpochMilli(); SeedlotStatusResponseDto response = seedlotService.createSeedlot(createDto); + long finished = Instant.now().toEpochMilli(); + SparLog.info("Time spent: {} ms - create seedlot first step", (finished - started)); return ResponseEntity.status(HttpStatus.CREATED).body(response); } @@ -454,9 +459,12 @@ public ResponseEntity submitSeedlotForm( @PathVariable String seedlotNumber, @RequestBody SeedlotFormSubmissionDto form) { + long started = Instant.now().toEpochMilli(); boolean isTscAdmin = loggedUserService.isTscAdminLogged(); SeedlotStatusResponseDto createDto = seedlotService.updateSeedlotWithForm(seedlotNumber, form, isTscAdmin, true, "SUB"); + long finished = Instant.now().toEpochMilli(); + SparLog.info("Time spent: {} ms - submit seedlot regular form", (finished - started)); return ResponseEntity.status(HttpStatus.CREATED).body(createDto); } diff --git a/backend/src/main/java/ca/bc/gov/backendstartapi/endpoint/TscAdminEndpoint.java b/backend/src/main/java/ca/bc/gov/backendstartapi/endpoint/TscAdminEndpoint.java index ff0fdbb09..89bbc7b40 100644 --- a/backend/src/main/java/ca/bc/gov/backendstartapi/endpoint/TscAdminEndpoint.java +++ b/backend/src/main/java/ca/bc/gov/backendstartapi/endpoint/TscAdminEndpoint.java @@ -1,5 +1,6 @@ package ca.bc.gov.backendstartapi.endpoint; +import ca.bc.gov.backendstartapi.config.SparLog; import ca.bc.gov.backendstartapi.dto.SeedlotFormSubmissionDto; import ca.bc.gov.backendstartapi.dto.SeedlotStatusResponseDto; import ca.bc.gov.backendstartapi.entity.seedlot.Seedlot; @@ -17,6 +18,7 @@ import io.swagger.v3.oas.annotations.responses.ApiResponse; import io.swagger.v3.oas.annotations.responses.ApiResponses; import io.swagger.v3.oas.annotations.tags.Tag; +import java.time.Instant; import java.util.List; import java.util.Objects; import lombok.RequiredArgsConstructor; @@ -25,6 +27,7 @@ import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.util.MimeTypeUtils; +import org.springframework.web.bind.annotation.CrossOrigin; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PostMapping; @@ -68,6 +71,7 @@ public class TscAdminEndpoint { description = "Access token is missing or invalid", content = @Content(schema = @Schema(implementation = Void.class))) }) + @CrossOrigin(exposedHeaders = "X-TOTAL-COUNT") @RoleAccessConfig({"SPAR_TSC_ADMIN"}) public ResponseEntity> getSeedlotsForReviewing( @RequestParam(value = "page", required = false, defaultValue = "0") Integer page, @@ -124,13 +128,16 @@ public ResponseEntity updateSeedlotStatus( @Parameter( name = "status", in = ParameterIn.PATH, - description = "Seedlot status to be updated to", + description = "Seedlot status to be updated to, either PND or APP", required = true, - example = "true", + example = "PND", schema = @Schema(type = "string", example = "true")) @PathVariable String status) { - tscAdminService.updateSeedlotStatus(seedlotNumber, status); + long started = Instant.now().toEpochMilli(); + tscAdminService.updateSeedlotStatus(seedlotNumber, status.toUpperCase()); + long finished = Instant.now().toEpochMilli(); + SparLog.info("Time spent: {} ms - approve or disapprove seedlot by tsc", (finished - started)); return ResponseEntity.noContent().build(); } @@ -184,6 +191,7 @@ public ResponseEntity submitSeedlotForm( @RequestParam String statusOnSave, @RequestBody SeedlotFormSubmissionDto form) { + long started = Instant.now().toEpochMilli(); String formattedStatus = statusOnSave.toUpperCase(); if (!List.of("PND", "SUB", "APP").contains(formattedStatus)) { throw new InvalidSeedlotStatusException(formattedStatus); @@ -191,6 +199,8 @@ public ResponseEntity submitSeedlotForm( SeedlotStatusResponseDto updatedDto = seedlotService.updateSeedlotWithForm(seedlotNumber, form, true, false, formattedStatus); + long finished = Instant.now().toEpochMilli(); + SparLog.info("Time spent: {} ms - edit seedlot review form by tsc", (finished - started)); return ResponseEntity.status(HttpStatus.OK).body(updatedDto); } } diff --git a/backend/src/main/java/ca/bc/gov/backendstartapi/entity/FavouriteActivityEntity.java b/backend/src/main/java/ca/bc/gov/backendstartapi/entity/FavouriteActivityEntity.java index 1406e0b97..9a8bd1bba 100644 --- a/backend/src/main/java/ca/bc/gov/backendstartapi/entity/FavouriteActivityEntity.java +++ b/backend/src/main/java/ca/bc/gov/backendstartapi/entity/FavouriteActivityEntity.java @@ -9,6 +9,7 @@ import jakarta.persistence.PrePersist; import jakarta.persistence.PreUpdate; import jakarta.persistence.Table; +import java.time.Clock; import java.time.LocalDateTime; import lombok.AllArgsConstructor; import lombok.Getter; @@ -58,12 +59,12 @@ public FavouriteActivityEntity() { @PrePersist private void prePersist() { - entryTimestamp = LocalDateTime.now(); + entryTimestamp = LocalDateTime.now(Clock.systemUTC()); updateTimestamp = entryTimestamp; } @PreUpdate private void preUpdate() { - updateTimestamp = LocalDateTime.now(); + updateTimestamp = LocalDateTime.now(Clock.systemUTC()); } } diff --git a/backend/src/main/java/ca/bc/gov/backendstartapi/entity/GeneticWorthEntity.java b/backend/src/main/java/ca/bc/gov/backendstartapi/entity/GeneticWorthEntity.java index e8bc0c90c..8c928ed3b 100644 --- a/backend/src/main/java/ca/bc/gov/backendstartapi/entity/GeneticWorthEntity.java +++ b/backend/src/main/java/ca/bc/gov/backendstartapi/entity/GeneticWorthEntity.java @@ -5,6 +5,7 @@ import jakarta.persistence.Entity; import jakarta.persistence.Id; import jakarta.persistence.Table; +import java.math.BigDecimal; import lombok.Getter; import lombok.NoArgsConstructor; import lombok.Setter; @@ -20,9 +21,17 @@ public class GeneticWorthEntity extends CodeDescriptionEntity { @Column(name = "genetic_worth_code", length = 3) private String geneticWorthCode; + @Column(name = "default_bv", nullable = false, precision = 3, scale = 1) + private BigDecimal defaultBv; + + /** Constructor. */ public GeneticWorthEntity( - String geneticWorthCode, String description, EffectiveDateRange effectiveDateRange) { + String geneticWorthCode, + String description, + EffectiveDateRange effectiveDateRange, + BigDecimal defaultBv) { super(description, effectiveDateRange); this.geneticWorthCode = geneticWorthCode; + this.defaultBv = defaultBv; } } diff --git a/backend/src/main/java/ca/bc/gov/backendstartapi/entity/SeedlotGeneticWorth.java b/backend/src/main/java/ca/bc/gov/backendstartapi/entity/SeedlotGeneticWorth.java index cb4a7978a..4eb65cff1 100644 --- a/backend/src/main/java/ca/bc/gov/backendstartapi/entity/SeedlotGeneticWorth.java +++ b/backend/src/main/java/ca/bc/gov/backendstartapi/entity/SeedlotGeneticWorth.java @@ -6,6 +6,7 @@ import jakarta.persistence.Column; import jakarta.persistence.Embedded; import jakarta.persistence.Entity; +import jakarta.persistence.FetchType; import jakarta.persistence.Id; import jakarta.persistence.IdClass; import jakarta.persistence.JoinColumn; @@ -33,13 +34,13 @@ public class SeedlotGeneticWorth { // region Identifier @Id @JoinColumn(name = "seedlot_number") - @ManyToOne(optional = false) + @ManyToOne(fetch = FetchType.LAZY) @NonNull private Seedlot seedlot; @Id @JoinColumn(name = "genetic_worth_code") - @ManyToOne + @ManyToOne(fetch = FetchType.LAZY) @NonNull private GeneticWorthEntity geneticWorth; diff --git a/backend/src/main/java/ca/bc/gov/backendstartapi/entity/SeedlotParentTree.java b/backend/src/main/java/ca/bc/gov/backendstartapi/entity/SeedlotParentTree.java index 4b41698fd..3d74f9f56 100644 --- a/backend/src/main/java/ca/bc/gov/backendstartapi/entity/SeedlotParentTree.java +++ b/backend/src/main/java/ca/bc/gov/backendstartapi/entity/SeedlotParentTree.java @@ -6,6 +6,7 @@ import jakarta.persistence.Column; import jakarta.persistence.Embedded; import jakarta.persistence.Entity; +import jakarta.persistence.FetchType; import jakarta.persistence.Id; import jakarta.persistence.IdClass; import jakarta.persistence.JoinColumn; @@ -35,7 +36,7 @@ public class SeedlotParentTree { // region Identifier @Id @JoinColumn(name = "seedlot_number") - @ManyToOne + @ManyToOne(fetch = FetchType.LAZY) @NonNull private Seedlot seedlot; diff --git a/backend/src/main/java/ca/bc/gov/backendstartapi/entity/SeedlotParentTreeGeneticQuality.java b/backend/src/main/java/ca/bc/gov/backendstartapi/entity/SeedlotParentTreeGeneticQuality.java index a5e5730d5..31989dd71 100644 --- a/backend/src/main/java/ca/bc/gov/backendstartapi/entity/SeedlotParentTreeGeneticQuality.java +++ b/backend/src/main/java/ca/bc/gov/backendstartapi/entity/SeedlotParentTreeGeneticQuality.java @@ -5,6 +5,7 @@ import jakarta.persistence.Column; import jakarta.persistence.Embedded; import jakarta.persistence.Entity; +import jakarta.persistence.FetchType; import jakarta.persistence.Id; import jakarta.persistence.IdClass; import jakarta.persistence.JoinColumn; @@ -33,7 +34,7 @@ public class SeedlotParentTreeGeneticQuality { @Id @JoinColumn(name = "seedlot_number", referencedColumnName = "seedlot_number") @JoinColumn(name = "parent_tree_id", referencedColumnName = "parent_tree_id") - @ManyToOne + @ManyToOne(fetch = FetchType.LAZY) @NonNull private SeedlotParentTree seedlotParentTree; @@ -44,7 +45,7 @@ public class SeedlotParentTreeGeneticQuality { @Id @JoinColumn(name = "genetic_worth_code") - @ManyToOne + @ManyToOne(fetch = FetchType.LAZY) @NonNull private GeneticWorthEntity geneticWorth; diff --git a/backend/src/main/java/ca/bc/gov/backendstartapi/entity/SeedlotParentTreeSmpMix.java b/backend/src/main/java/ca/bc/gov/backendstartapi/entity/SeedlotParentTreeSmpMix.java index 28e222d09..172403908 100644 --- a/backend/src/main/java/ca/bc/gov/backendstartapi/entity/SeedlotParentTreeSmpMix.java +++ b/backend/src/main/java/ca/bc/gov/backendstartapi/entity/SeedlotParentTreeSmpMix.java @@ -6,6 +6,7 @@ import jakarta.persistence.Column; import jakarta.persistence.Embedded; import jakarta.persistence.Entity; +import jakarta.persistence.FetchType; import jakarta.persistence.Id; import jakarta.persistence.IdClass; import jakarta.persistence.JoinColumn; @@ -37,7 +38,7 @@ public class SeedlotParentTreeSmpMix { @Id @JoinColumn(name = "seedlot_number", referencedColumnName = "seedlot_number") @JoinColumn(name = "parent_tree_id", referencedColumnName = "parent_tree_id") - @ManyToOne + @ManyToOne(fetch = FetchType.LAZY) @NonNull private SeedlotParentTree seedlotParentTree; @@ -48,7 +49,7 @@ public class SeedlotParentTreeSmpMix { @Id @JoinColumn(name = "genetic_worth_code") - @ManyToOne + @ManyToOne(fetch = FetchType.LAZY) @NonNull private GeneticWorthEntity geneticWorth; diff --git a/backend/src/main/java/ca/bc/gov/backendstartapi/entity/SeedlotSeedPlanZoneEntity.java b/backend/src/main/java/ca/bc/gov/backendstartapi/entity/SeedlotSeedPlanZoneEntity.java index 0536acb5f..febfa7057 100644 --- a/backend/src/main/java/ca/bc/gov/backendstartapi/entity/SeedlotSeedPlanZoneEntity.java +++ b/backend/src/main/java/ca/bc/gov/backendstartapi/entity/SeedlotSeedPlanZoneEntity.java @@ -6,6 +6,7 @@ import jakarta.persistence.Column; import jakarta.persistence.Embedded; import jakarta.persistence.Entity; +import jakarta.persistence.FetchType; import jakarta.persistence.Id; import jakarta.persistence.IdClass; import jakarta.persistence.JoinColumn; @@ -32,7 +33,7 @@ public class SeedlotSeedPlanZoneEntity { // region Identifier @Id @JoinColumn(name = "seedlot_number") - @ManyToOne + @ManyToOne(fetch = FetchType.LAZY) @NonNull private Seedlot seedlot; @@ -42,7 +43,7 @@ public class SeedlotSeedPlanZoneEntity { private String spzCode; // endregion - @ManyToOne + @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "genetic_class_code") @NonNull private GeneticClassEntity geneticClass; diff --git a/backend/src/main/java/ca/bc/gov/backendstartapi/entity/SmpMix.java b/backend/src/main/java/ca/bc/gov/backendstartapi/entity/SmpMix.java index 75abe863f..bf7bf5b6b 100644 --- a/backend/src/main/java/ca/bc/gov/backendstartapi/entity/SmpMix.java +++ b/backend/src/main/java/ca/bc/gov/backendstartapi/entity/SmpMix.java @@ -7,6 +7,7 @@ import jakarta.persistence.Column; import jakarta.persistence.Embedded; import jakarta.persistence.Entity; +import jakarta.persistence.FetchType; import jakarta.persistence.Id; import jakarta.persistence.IdClass; import jakarta.persistence.JoinColumn; @@ -37,13 +38,14 @@ public class SmpMix { // region Identifier @Id @JoinColumn(name = "seedlot_number") - @ManyToOne + @ManyToOne(fetch = FetchType.LAZY) @NonNull private Seedlot seedlot; @Id @Column(name = "parent_tree_id", nullable = false) private int parentTreeId; + // endregion @Column(name = "parent_tree_number", nullable = false) diff --git a/backend/src/main/java/ca/bc/gov/backendstartapi/entity/SmpMixGeneticQuality.java b/backend/src/main/java/ca/bc/gov/backendstartapi/entity/SmpMixGeneticQuality.java index cdadbce4e..eaddbc5df 100644 --- a/backend/src/main/java/ca/bc/gov/backendstartapi/entity/SmpMixGeneticQuality.java +++ b/backend/src/main/java/ca/bc/gov/backendstartapi/entity/SmpMixGeneticQuality.java @@ -6,6 +6,7 @@ import jakarta.persistence.Column; import jakarta.persistence.Embedded; import jakarta.persistence.Entity; +import jakarta.persistence.FetchType; import jakarta.persistence.Id; import jakarta.persistence.IdClass; import jakarta.persistence.JoinColumn; @@ -34,7 +35,7 @@ public class SmpMixGeneticQuality { @Id @JoinColumn(name = "seedlot_number", referencedColumnName = "seedlot_number") @JoinColumn(name = "parent_tree_id", referencedColumnName = "parent_tree_id") - @ManyToOne + @ManyToOne(fetch = FetchType.LAZY) @NonNull private SmpMix smpMix; @@ -45,7 +46,7 @@ public class SmpMixGeneticQuality { @Id @JoinColumn(name = "genetic_worth_code") - @ManyToOne + @ManyToOne(fetch = FetchType.LAZY) @NonNull private GeneticWorthEntity geneticWorth; diff --git a/backend/src/main/java/ca/bc/gov/backendstartapi/entity/embeddable/AuditInformation.java b/backend/src/main/java/ca/bc/gov/backendstartapi/entity/embeddable/AuditInformation.java index 6d86cb513..150e13ca5 100644 --- a/backend/src/main/java/ca/bc/gov/backendstartapi/entity/embeddable/AuditInformation.java +++ b/backend/src/main/java/ca/bc/gov/backendstartapi/entity/embeddable/AuditInformation.java @@ -6,6 +6,7 @@ import jakarta.persistence.PreUpdate; import java.io.Serial; import java.io.Serializable; +import java.time.Clock; import java.time.LocalDateTime; import lombok.Generated; import lombok.Getter; @@ -46,12 +47,12 @@ public AuditInformation(@NonNull String userId) { @PrePersist private void prePersist() { - entryTimestamp = LocalDateTime.now(); + entryTimestamp = LocalDateTime.now(Clock.systemUTC()); updateTimestamp = entryTimestamp; } @PreUpdate private void preUpdate() { - updateTimestamp = LocalDateTime.now(); + updateTimestamp = LocalDateTime.now(Clock.systemUTC()); } } diff --git a/backend/src/main/java/ca/bc/gov/backendstartapi/entity/seedlot/Seedlot.java b/backend/src/main/java/ca/bc/gov/backendstartapi/entity/seedlot/Seedlot.java index bdd67e701..ebf2c455a 100644 --- a/backend/src/main/java/ca/bc/gov/backendstartapi/entity/seedlot/Seedlot.java +++ b/backend/src/main/java/ca/bc/gov/backendstartapi/entity/seedlot/Seedlot.java @@ -7,6 +7,7 @@ import jakarta.persistence.Column; import jakarta.persistence.Embedded; import jakarta.persistence.Entity; +import jakarta.persistence.FetchType; import jakarta.persistence.Id; import jakarta.persistence.JoinColumn; import jakarta.persistence.ManyToOne; @@ -14,8 +15,10 @@ import jakarta.persistence.Version; import java.io.Serializable; import java.math.BigDecimal; +import java.time.LocalDate; import java.time.LocalDateTime; import lombok.AccessLevel; +import lombok.EqualsAndHashCode; import lombok.Getter; import lombok.NoArgsConstructor; import lombok.NonNull; @@ -30,14 +33,16 @@ @Getter @Setter @ToString +@EqualsAndHashCode public class Seedlot implements Serializable { @Id @Column(name = "seedlot_number", length = 5) @NonNull private String id; - @JoinColumn(name = "seedlot_status_code") - @ManyToOne + @JoinColumn(name = "seedlot_status_code", nullable = false) + @ManyToOne(fetch = FetchType.LAZY) + @NonNull private SeedlotStatusEntity seedlotStatus; @Column(name = "seedlot_comment", length = 2000) @@ -60,11 +65,11 @@ public class Seedlot implements Serializable { private String vegetationCode; @JoinColumn(name = "genetic_class_code") - @ManyToOne + @ManyToOne(fetch = FetchType.LAZY) private GeneticClassEntity geneticClass; @JoinColumn(name = "seedlot_source_code") - @ManyToOne + @ManyToOne(fetch = FetchType.LAZY) private SeedlotSourceEntity seedlotSource; @Column(name = "to_be_registrd_ind") @@ -83,10 +88,10 @@ public class Seedlot implements Serializable { private String collectionLocationCode; @Column(name = "collection_start_date") - private LocalDateTime collectionStartDate; + private LocalDate collectionStartDate; @Column(name = "collection_end_date") - private LocalDateTime collectionEndDate; + private LocalDate collectionEndDate; @Column(name = "no_of_containers", precision = 6, scale = 2) private BigDecimal numberOfContainers; @@ -111,10 +116,10 @@ public class Seedlot implements Serializable { private String interimStorageLocationCode; @Column(name = "interm_strg_st_date") - private LocalDateTime interimStorageStartDate; + private LocalDate interimStorageStartDate; @Column(name = "interm_strg_end_date") - private LocalDateTime interimStorageEndDate; + private LocalDate interimStorageEndDate; @Column(name = "interm_facility_code", length = 3) private String interimStorageFacilityCode; @@ -149,6 +154,9 @@ public class Seedlot implements Serializable { @Column(name = "pollen_contamination_mthd_code", length = 4) private String pollenContaminationMethodCode; + @Column(name = "coancestry", precision = 20, scale = 10) + private BigDecimal coancestry; + // endregion // region Parent tree & SMP @@ -178,10 +186,10 @@ public class Seedlot implements Serializable { private String extractionLocationCode; @Column(name = "extraction_st_date") - private LocalDateTime extractionStartDate; + private LocalDate extractionStartDate; @Column(name = "extraction_end_date") - private LocalDateTime extractionEndDate; + private LocalDate extractionEndDate; @Column(name = "temporary_strg_client_number", length = 8) private String storageClientNumber; @@ -190,10 +198,10 @@ public class Seedlot implements Serializable { private String storageLocationCode; @Column(name = "temporary_strg_start_date") - private LocalDateTime temporaryStorageStartDate; + private LocalDate temporaryStorageStartDate; @Column(name = "temporary_strg_end_date") - private LocalDateTime temporaryStorageEndDate; + private LocalDate temporaryStorageEndDate; // endregion diff --git a/backend/src/main/java/ca/bc/gov/backendstartapi/entity/seedlot/SeedlotCollectionMethod.java b/backend/src/main/java/ca/bc/gov/backendstartapi/entity/seedlot/SeedlotCollectionMethod.java index 68ee6ffe0..1e4bc46a5 100644 --- a/backend/src/main/java/ca/bc/gov/backendstartapi/entity/seedlot/SeedlotCollectionMethod.java +++ b/backend/src/main/java/ca/bc/gov/backendstartapi/entity/seedlot/SeedlotCollectionMethod.java @@ -6,6 +6,7 @@ import jakarta.persistence.Column; import jakarta.persistence.Embedded; import jakarta.persistence.Entity; +import jakarta.persistence.FetchType; import jakarta.persistence.Id; import jakarta.persistence.IdClass; import jakarta.persistence.JoinColumn; @@ -32,14 +33,14 @@ public class SeedlotCollectionMethod { // region Identifier @Id - @JoinColumn(name = "seedlot_number") - @ManyToOne + @JoinColumn(name = "seedlot_number", updatable = false, nullable = false) + @ManyToOne(fetch = FetchType.LAZY) @NonNull private Seedlot seedlot; @Id - @JoinColumn(name = "cone_collection_method_code") - @ManyToOne + @JoinColumn(name = "cone_collection_method_code", updatable = false, nullable = false) + @ManyToOne(fetch = FetchType.LAZY) @NonNull private ConeCollectionMethodEntity coneCollectionMethod; diff --git a/backend/src/main/java/ca/bc/gov/backendstartapi/entity/seedlot/SeedlotOrchard.java b/backend/src/main/java/ca/bc/gov/backendstartapi/entity/seedlot/SeedlotOrchard.java index 8b664ead9..fec7e1d83 100644 --- a/backend/src/main/java/ca/bc/gov/backendstartapi/entity/seedlot/SeedlotOrchard.java +++ b/backend/src/main/java/ca/bc/gov/backendstartapi/entity/seedlot/SeedlotOrchard.java @@ -5,6 +5,7 @@ import jakarta.persistence.Column; import jakarta.persistence.Embedded; import jakarta.persistence.Entity; +import jakarta.persistence.FetchType; import jakarta.persistence.Id; import jakarta.persistence.IdClass; import jakarta.persistence.JoinColumn; @@ -33,8 +34,8 @@ public class SeedlotOrchard { // region Identifier @Id - @JoinColumn(name = "seedlot_number") - @ManyToOne + @JoinColumn(name = "seedlot_number", updatable = false) + @ManyToOne(fetch = FetchType.LAZY) @NonNull private Seedlot seedlot; diff --git a/backend/src/main/java/ca/bc/gov/backendstartapi/entity/seedlot/SeedlotOwnerQuantity.java b/backend/src/main/java/ca/bc/gov/backendstartapi/entity/seedlot/SeedlotOwnerQuantity.java index 5317e00e2..3329b4d40 100644 --- a/backend/src/main/java/ca/bc/gov/backendstartapi/entity/seedlot/SeedlotOwnerQuantity.java +++ b/backend/src/main/java/ca/bc/gov/backendstartapi/entity/seedlot/SeedlotOwnerQuantity.java @@ -6,6 +6,7 @@ import jakarta.persistence.Column; import jakarta.persistence.Embedded; import jakarta.persistence.Entity; +import jakarta.persistence.FetchType; import jakarta.persistence.Id; import jakarta.persistence.IdClass; import jakarta.persistence.JoinColumn; @@ -32,8 +33,8 @@ public class SeedlotOwnerQuantity { // region Identifier @Id - @JoinColumn(name = "seedlot_number") - @ManyToOne + @JoinColumn(name = "seedlot_number", updatable = false, nullable = false) + @ManyToOne(fetch = FetchType.LAZY) @NonNull private Seedlot seedlot; @@ -58,8 +59,9 @@ public class SeedlotOwnerQuantity { @Column(name = "original_pct_srpls", precision = 4, scale = 1, nullable = false) private BigDecimal originalPercentageSurplus; - @JoinColumn(name = "method_of_payment_code") - @ManyToOne + @JoinColumn(name = "method_of_payment_code", updatable = false, nullable = false) + @ManyToOne(fetch = FetchType.LAZY) + @NonNull private MethodOfPaymentEntity methodOfPayment; @Column(name = "spar_fund_srce_code", length = 3) @@ -71,14 +73,4 @@ public class SeedlotOwnerQuantity { @Version @Setter(AccessLevel.NONE) private int revisionCount; - - /** - * Returns the SeedlotOwnerQuantity id, fields: seedlot number, owner cliend code, and owner - * client location. - * - * @return a {@link SeedlotOwnerQuantityId} - */ - public SeedlotOwnerQuantityId getId() { - return new SeedlotOwnerQuantityId(seedlot.getId(), ownerClientNumber, ownerLocationCode); - } } diff --git a/backend/src/main/java/ca/bc/gov/backendstartapi/provider/ForestClientApiProvider.java b/backend/src/main/java/ca/bc/gov/backendstartapi/provider/ForestClientApiProvider.java index 4c600e018..0b78d9c27 100644 --- a/backend/src/main/java/ca/bc/gov/backendstartapi/provider/ForestClientApiProvider.java +++ b/backend/src/main/java/ca/bc/gov/backendstartapi/provider/ForestClientApiProvider.java @@ -355,9 +355,9 @@ private boolean shouldMock() { if (Arrays.stream(environment.getActiveProfiles()) .anyMatch(env -> !env.equalsIgnoreCase("prod"))) { String byPass = environment.getProperty("BYPASS_FOREST_CLIENT"); - SparLog.info("BYPASS_FOREST_CLIENT={}", byPass); + SparLog.debug("BYPASS_FOREST_CLIENT={}", byPass); boolean shouldMockValue = "Y".equals(byPass); - SparLog.info("Should mock ForestClient API request: {}", shouldMockValue); + SparLog.debug("Should mock ForestClient API request: {}", shouldMockValue); return shouldMockValue; } return false; diff --git a/backend/src/main/java/ca/bc/gov/backendstartapi/provider/OracleApiProvider.java b/backend/src/main/java/ca/bc/gov/backendstartapi/provider/OracleApiProvider.java index bd669a53f..d723249ae 100644 --- a/backend/src/main/java/ca/bc/gov/backendstartapi/provider/OracleApiProvider.java +++ b/backend/src/main/java/ca/bc/gov/backendstartapi/provider/OracleApiProvider.java @@ -6,13 +6,11 @@ import ca.bc.gov.backendstartapi.dto.OrchardDto; import ca.bc.gov.backendstartapi.dto.OrchardSpuDto; import ca.bc.gov.backendstartapi.dto.ParentTreeDto; -import ca.bc.gov.backendstartapi.dto.SameSpeciesTreeDto; import ca.bc.gov.backendstartapi.dto.oracle.AreaOfUseDto; import ca.bc.gov.backendstartapi.dto.oracle.SpuDto; import ca.bc.gov.backendstartapi.filter.RequestCorrelation; import ca.bc.gov.backendstartapi.security.LoggedUserService; import java.util.List; -import java.util.Map; import java.util.Optional; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.boot.web.client.RestTemplateBuilder; @@ -93,34 +91,26 @@ public Optional findOrchardParentTreeGeneticQualityData( * @return An {@link List} of {@link ParentTreeDto} */ @Override - public List findParentTreesByVegCode( - String vegCode, Map orchardSpuMap) { - String oracleApiUrl = - String.format("%s/api/orchards/parent-trees/vegetation-codes/{vegCode}", rootUri); + public List findOrchardsByVegCode(String vegCode) { + String oracleApiUrl = String.format("%s/api/orchards/vegetation-codes/{vegCode}", rootUri); - SparLog.info( - "Starting {} - {} request to {}", PROVIDER, "findParentTreesByVegCode", oracleApiUrl); - - HttpEntity> requestEntity = - new HttpEntity<>(orchardSpuMap, addHttpHeaders()); + SparLog.info("Starting {} - {} request to {}", PROVIDER, "findOrchardsByVegCode", oracleApiUrl); try { - ResponseEntity> parentTreesResult = + ResponseEntity> orchardsResult = restTemplate.exchange( oracleApiUrl, - HttpMethod.POST, - requestEntity, - new ParameterizedTypeReference>() {}, + HttpMethod.GET, + new HttpEntity<>(addHttpHeaders()), + new ParameterizedTypeReference>() {}, createParamsMap("vegCode", vegCode)); - List list = parentTreesResult.getBody(); + List list = orchardsResult.getBody(); int size = list == null ? 0 : list.size(); - SparLog.info( - "POST spu to oracle to get parent trees with vegCode - Success response with size: {}!", - size); + SparLog.info("GET orchards by vegCode from Oracle - Success response with size: {}!", size); return list; } catch (HttpClientErrorException httpExc) { SparLog.error( - "POST spu to oracle to get parent trees with vegCode - Response code error: {}, {}", + "GET orchards by vegCode from Oracle - Response code error: {}, {}", httpExc.getStatusCode(), httpExc.getMessage()); throw new ResponseStatusException(httpExc.getStatusCode(), httpExc.getMessage()); diff --git a/backend/src/main/java/ca/bc/gov/backendstartapi/provider/Provider.java b/backend/src/main/java/ca/bc/gov/backendstartapi/provider/Provider.java index e54504eb4..cc0def51d 100644 --- a/backend/src/main/java/ca/bc/gov/backendstartapi/provider/Provider.java +++ b/backend/src/main/java/ca/bc/gov/backendstartapi/provider/Provider.java @@ -62,6 +62,10 @@ default Optional getSpuById(Integer spuId) { return Optional.empty(); } + default List findOrchardsByVegCode(String vegCode) { + return List.of(); + } + // Common methods String[] addAuthorizationHeader(); diff --git a/backend/src/main/java/ca/bc/gov/backendstartapi/repository/ConeCollectionMethodRepository.java b/backend/src/main/java/ca/bc/gov/backendstartapi/repository/ConeCollectionMethodRepository.java index 0f3c9aed7..685a03947 100644 --- a/backend/src/main/java/ca/bc/gov/backendstartapi/repository/ConeCollectionMethodRepository.java +++ b/backend/src/main/java/ca/bc/gov/backendstartapi/repository/ConeCollectionMethodRepository.java @@ -1,8 +1,12 @@ package ca.bc.gov.backendstartapi.repository; import ca.bc.gov.backendstartapi.entity.ConeCollectionMethodEntity; +import java.util.List; import org.springframework.data.jpa.repository.JpaRepository; /** Repository for {@link ConeCollectionMethodEntity}. */ public interface ConeCollectionMethodRepository - extends JpaRepository {} + extends JpaRepository { + + List findAllByConeCollectionMethodCodeIn(List ids); +} diff --git a/backend/src/main/java/ca/bc/gov/backendstartapi/repository/MethodOfPaymentRepository.java b/backend/src/main/java/ca/bc/gov/backendstartapi/repository/MethodOfPaymentRepository.java index c11dc2fc2..ba0e2b593 100644 --- a/backend/src/main/java/ca/bc/gov/backendstartapi/repository/MethodOfPaymentRepository.java +++ b/backend/src/main/java/ca/bc/gov/backendstartapi/repository/MethodOfPaymentRepository.java @@ -1,7 +1,11 @@ package ca.bc.gov.backendstartapi.repository; import ca.bc.gov.backendstartapi.entity.MethodOfPaymentEntity; +import java.util.List; import org.springframework.data.jpa.repository.JpaRepository; /** Repository for {@link MethodOfPaymentEntity}. */ -public interface MethodOfPaymentRepository extends JpaRepository {} +public interface MethodOfPaymentRepository extends JpaRepository { + + List findAllByMethodOfPaymentCodeIn(List methods); +} diff --git a/backend/src/main/java/ca/bc/gov/backendstartapi/repository/SeedlotCollectionMethodRepository.java b/backend/src/main/java/ca/bc/gov/backendstartapi/repository/SeedlotCollectionMethodRepository.java index a022d6e91..aba8ecd23 100644 --- a/backend/src/main/java/ca/bc/gov/backendstartapi/repository/SeedlotCollectionMethodRepository.java +++ b/backend/src/main/java/ca/bc/gov/backendstartapi/repository/SeedlotCollectionMethodRepository.java @@ -10,4 +10,6 @@ public interface SeedlotCollectionMethodRepository extends JpaRepository { List findAllBySeedlot_id(String seedlotNumber); + + void deleteAllBySeedlot_id(String seedlotNumber); } diff --git a/backend/src/main/java/ca/bc/gov/backendstartapi/repository/SeedlotOwnerQuantityRepository.java b/backend/src/main/java/ca/bc/gov/backendstartapi/repository/SeedlotOwnerQuantityRepository.java index 44e8afb84..c17a0e616 100644 --- a/backend/src/main/java/ca/bc/gov/backendstartapi/repository/SeedlotOwnerQuantityRepository.java +++ b/backend/src/main/java/ca/bc/gov/backendstartapi/repository/SeedlotOwnerQuantityRepository.java @@ -10,4 +10,6 @@ public interface SeedlotOwnerQuantityRepository extends JpaRepository { List findAllBySeedlot_id(String seedlotNumber); + + void deleteAllBySeedlot_id(String seedlotNumber); } diff --git a/backend/src/main/java/ca/bc/gov/backendstartapi/service/ConeCollectionMethodService.java b/backend/src/main/java/ca/bc/gov/backendstartapi/service/ConeCollectionMethodService.java index 55021c83f..e6f20992d 100644 --- a/backend/src/main/java/ca/bc/gov/backendstartapi/service/ConeCollectionMethodService.java +++ b/backend/src/main/java/ca/bc/gov/backendstartapi/service/ConeCollectionMethodService.java @@ -50,4 +50,18 @@ public List getAllValidConeCollectionMethods() { SparLog.info("{} Cone Collection Methods found.", list.size()); return list; } + + /** + * Get all cone collection methods by id in a single query. + * + * @param ids All cone collection method ids. + * @return List of found cone collection method entities. + */ + public List getAllByIdIn(List ids) { + SparLog.info("Fetching list of Cone Collection Methods by Id with ids {}", ids); + List list = + coneCollectionMethodRepository.findAllByConeCollectionMethodCodeIn(ids); + SparLog.info("{} Cone Collection Methods found.", list.size()); + return list; + } } diff --git a/backend/src/main/java/ca/bc/gov/backendstartapi/service/GeneticWorthService.java b/backend/src/main/java/ca/bc/gov/backendstartapi/service/GeneticWorthService.java index e31e95709..1163a2715 100644 --- a/backend/src/main/java/ca/bc/gov/backendstartapi/service/GeneticWorthService.java +++ b/backend/src/main/java/ca/bc/gov/backendstartapi/service/GeneticWorthService.java @@ -2,6 +2,7 @@ import ca.bc.gov.backendstartapi.config.SparLog; import ca.bc.gov.backendstartapi.dto.CodeDescriptionDto; +import ca.bc.gov.backendstartapi.dto.GeneticWorthDto; import ca.bc.gov.backendstartapi.dto.GeneticWorthTraitsDto; import ca.bc.gov.backendstartapi.dto.OrchardParentTreeValsDto; import ca.bc.gov.backendstartapi.dto.PtCalculationResDto; @@ -27,16 +28,17 @@ public GeneticWorthService(GeneticWorthRepository geneticWorthRepository) { } /** Fetch all valid genetic worth from the repository. */ - public List getAllGeneticWorth() { + public List getAllGeneticWorth() { SparLog.info("Fetching all genetic worth"); - List resultList = new ArrayList<>(); + List resultList = new ArrayList<>(); geneticWorthRepository.findAll().stream() - .filter(method -> method.isValid()) + .filter(gw -> gw.isValid()) .forEach( - method -> { - CodeDescriptionDto methodToAdd = - new CodeDescriptionDto(method.getGeneticWorthCode(), method.getDescription()); + gw -> { + GeneticWorthDto methodToAdd = + new GeneticWorthDto( + gw.getGeneticWorthCode(), gw.getDescription(), gw.getDefaultBv()); resultList.add(methodToAdd); }); @@ -73,9 +75,9 @@ public List calculateGeneticWorth( List calculated = new ArrayList<>(); // Iterate over all traits - List geneticWorths = getAllGeneticWorth(); + List geneticWorthList = getAllGeneticWorth(); - for (CodeDescriptionDto trait : geneticWorths) { + for (GeneticWorthDto trait : geneticWorthList) { BigDecimal calculatedValue = null; BigDecimal percentage = calcGeneticTraitThreshold(traitsDto, trait); @@ -98,33 +100,61 @@ public List calculateGeneticWorth( /** * Does the Ne calculation (effective population size). * - * @param traitsDto A {@link List} of {@link OrchardParentTreeValsDto} with the traits and values - * to be calculated. + * @param coancestry The coancestry value. + * @param varSumOrchGameteContr The sum value of the Orchard gamete contribution. + * @param varSumNeNoSmpContrib The sum value of the Ne number of SMP contribution. + * @param smpParentsOutside The number of SMP parent tree from outside the orchard. * @return A {@link BigDecimal} representing the calculated value. */ - public BigDecimal calculateNe(List traitsDto) { + public BigDecimal calculateNe( + BigDecimal coancestry, + BigDecimal varSumOrchGameteContr, + BigDecimal varSumNeNoSmpContrib, + Integer smpParentsOutside) { SparLog.info("Started Ne calculation"); - BigDecimal malePollenSum = reducePollenCount(traitsDto); - BigDecimal femaleConeSum = reduceConeCount(traitsDto); + BigDecimal varEffectivePopSize = null; - BigDecimal piSquareSum = BigDecimal.ZERO; - for (OrchardParentTreeValsDto dto : traitsDto) { - BigDecimal pi = calculatePi(dto.pollenCount(), dto.coneCount(), malePollenSum, femaleConeSum); - BigDecimal piSquare = pi.multiply(pi); + final int scale = 10; + final RoundingMode halfUp = RoundingMode.HALF_UP; + BigDecimal zero = BigDecimal.ZERO; + BigDecimal one = BigDecimal.ONE; - piSquareSum = piSquareSum.add(piSquare); - SparLog.debug("calculateNe - piSquareSum {}", piSquareSum); - } - - if (piSquareSum.compareTo(BigDecimal.ZERO) == 0) { - SparLog.debug("calculateNe - piSquareSum is zero!"); - return BigDecimal.ZERO; + if (coancestry != null) { + // --Effective Population Size with Coancestry considered + if (coancestry.compareTo(zero) == 0) { + varEffectivePopSize = zero; + } else { + varEffectivePopSize = new BigDecimal("0.5").divide(coancestry, scale, halfUp); + } + } else if (smpParentsOutside > 0) { + // --Effective Population Size with SMP (for Growth) + // Original equation: varEffectivePopSize = Math.round(1/(v_sum_orch_gamete_contr + ( + // Math.power(0.25/(2*varSmpParentsOutside),2) * varSmpParentsOutside )) ,1); + varEffectivePopSize = + one.divide( + (varSumOrchGameteContr.add( + new BigDecimal("0.25") + .divide( + new BigDecimal("2").multiply(new BigDecimal(smpParentsOutside)), + scale, + halfUp) + .pow(2) + .multiply(new BigDecimal(smpParentsOutside)))), + scale, + halfUp); + } else { + // --Effective Population Size + if (varSumOrchGameteContr.compareTo(zero) == 0) { + varEffectivePopSize = zero; + } else { + // varEffectivePopSize = Math.round(1 / varSumNeNoSmpContrib); + varEffectivePopSize = one.divide(varSumNeNoSmpContrib, scale, halfUp); + } } - BigDecimal neValue = BigDecimal.ONE.divide(piSquareSum, 10, RoundingMode.HALF_UP); - SparLog.debug("calculateNe - neValue {}", neValue); + SparLog.debug("calculateNe - neValue {}", varEffectivePopSize); - return neValue.setScale(1, RoundingMode.HALF_UP); + return varEffectivePopSize.setScale(1, halfUp); } /** @@ -154,7 +184,7 @@ private BigDecimal calculateTraitGeneticWorth( } /** - * Calculate the threshold of a genetic trait. To be used to check if a given trait mets the + * Calculate the threshold of a genetic trait. To be used to check if a given trait met the * minimum of 70% of parent tree contribution. * * @param traitsDto A {@link List} of {@link OrchardParentTreeValsDto} with the traits and values diff --git a/backend/src/main/java/ca/bc/gov/backendstartapi/service/MethodOfPaymentService.java b/backend/src/main/java/ca/bc/gov/backendstartapi/service/MethodOfPaymentService.java index 9a3c3e6a1..6a0210574 100644 --- a/backend/src/main/java/ca/bc/gov/backendstartapi/service/MethodOfPaymentService.java +++ b/backend/src/main/java/ca/bc/gov/backendstartapi/service/MethodOfPaymentService.java @@ -49,4 +49,18 @@ public List getAllValidMethodOfPayments() { return list; } + + /** + * Get all methods of payments given its codes. + * + * @param methods All the method of payment codes. + * @return List of the method of payment entity. + */ + public List getAllMethodsByCodeList(List methods) { + SparLog.info("Fetching list of payment methods with methods {}", methods); + List list = + methodOfPaymentRepository.findAllByMethodOfPaymentCodeIn(methods); + SparLog.info("{} payment methods found", list.size()); + return list; + } } diff --git a/backend/src/main/java/ca/bc/gov/backendstartapi/service/OrchardService.java b/backend/src/main/java/ca/bc/gov/backendstartapi/service/OrchardService.java index be79adab1..2fed26273 100644 --- a/backend/src/main/java/ca/bc/gov/backendstartapi/service/OrchardService.java +++ b/backend/src/main/java/ca/bc/gov/backendstartapi/service/OrchardService.java @@ -1,13 +1,14 @@ package ca.bc.gov.backendstartapi.service; import ca.bc.gov.backendstartapi.config.SparLog; +import ca.bc.gov.backendstartapi.dto.OrchardDto; import ca.bc.gov.backendstartapi.dto.OrchardSpuDto; -import ca.bc.gov.backendstartapi.dto.SameSpeciesTreeDto; import ca.bc.gov.backendstartapi.entity.ActiveOrchardSpuEntity; import ca.bc.gov.backendstartapi.exception.NoParentTreeDataException; import ca.bc.gov.backendstartapi.exception.NoSpuForOrchardException; import ca.bc.gov.backendstartapi.provider.Provider; import ca.bc.gov.backendstartapi.repository.ActiveOrchardSeedPlanningUnitRepository; +import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -48,12 +49,20 @@ public Optional findSpuIdByOrchard(String orchardId) { * @param active determine if the SPU should be active or not. * @return A {@link List} of {@link ActiveOrchardSpuEntity} or an empty list. */ - public List findAllSpu(boolean active) { - SparLog.info("Finding all orchard seed planning unit by active state {}", active); + public List findAllSpu(Boolean active) { + String activeState = active == null ? "All" : active.toString(); - List list = - activeOrchardSeedPlanningUnitRepository.findAllByActive(active); - SparLog.info("{} orchard seed planning unit by active state found", list.size()); + SparLog.info("Finding all orchard seed planning unit by active state {}", activeState); + + List list = new ArrayList<>(); + + if (active == null) { + list = activeOrchardSeedPlanningUnitRepository.findAll(); + } else { + list = activeOrchardSeedPlanningUnitRepository.findAllByActive(active); + } + SparLog.info( + "{} orchard seed planning unit by active state {} found", list.size(), activeState); return list; } @@ -109,25 +118,26 @@ public OrchardSpuDto findParentTreeGeneticQualityData(String orchardId) { } /** - * Finds all parent trees from every orchard with the provided vegCode. + * Finds all orchards with the provided vegCode from Oracle API. * * @param vegCode Orchard's identification. - * @return An {@link List} of {@link SameSpeciesTreeDto} from oracle-api + * @return An {@link List} of {@link OrchardDto} from oracle-api + spuID. */ - public List findParentTreesByVegCode(String vegCode) { + public List findAllOrchardsByVegCode(String vegCode) { SparLog.info("Finding parent trees by veg code with code {}", vegCode); - List spuList = findAllSpu(true); - Map orchardSpuMap = new HashMap<>(); + List spuList = findAllSpu(null); + Map orchardSpuMap = new HashMap<>(); spuList.stream() .forEach( - spuObj -> - orchardSpuMap.put( - spuObj.getOrchardId(), String.valueOf(spuObj.getSeedPlanningUnitId()))); + spuObj -> orchardSpuMap.put(spuObj.getOrchardId(), spuObj.getSeedPlanningUnitId())); - List list = - oracleApiProvider.findParentTreesByVegCode(vegCode.toUpperCase(), orchardSpuMap); - SparLog.info("{} parent tree by veg code found.", list.size()); - return list; + List orchardList = oracleApiProvider.findOrchardsByVegCode(vegCode); + + orchardList.forEach( + orchardDto -> orchardDto.setSpuId(orchardSpuMap.getOrDefault(orchardDto.getId(), null))); + + SparLog.info("{} orchards by veg code found.", orchardList.size()); + return orchardList; } } diff --git a/backend/src/main/java/ca/bc/gov/backendstartapi/service/ParentTreeService.java b/backend/src/main/java/ca/bc/gov/backendstartapi/service/ParentTreeService.java index 0d813339d..ccfb989c0 100644 --- a/backend/src/main/java/ca/bc/gov/backendstartapi/service/ParentTreeService.java +++ b/backend/src/main/java/ca/bc/gov/backendstartapi/service/ParentTreeService.java @@ -39,7 +39,8 @@ public class ParentTreeService { static BigDecimal PERC_DIVISOR = new BigDecimal(100); static BigDecimal HALF_DIVISOR = new BigDecimal(2); static BigDecimal PROP_DIVISOR = new BigDecimal(200); - static BigDecimal MAX_PROPORTION = new BigDecimal(1); + static BigDecimal ONE = BigDecimal.ONE; + static RoundingMode halfUp = RoundingMode.HALF_UP; /** * Does the calculation for each genetic trait. PS: if the threshold of 70% of contribution from @@ -55,7 +56,72 @@ public PtCalculationResDto calculatePtVals(PtValsCalReqDto ptVals) { ptVals.orchardPtVals().size(), ptVals.smpMixIdAndProps().size()); - BigDecimal neValue = geneticWorthService.calculateNe(ptVals.orchardPtVals()); + final BigDecimal zero = BigDecimal.ZERO; + + // First pass + BigDecimal varTotalConeCount = zero; + BigDecimal varTotalPollenCount = zero; + for (OrchardParentTreeValsDto orchardPtVals : ptVals.orchardPtVals()) { + varTotalConeCount = varTotalConeCount.add(orchardPtVals.coneCount()); + varTotalPollenCount = varTotalPollenCount.add(orchardPtVals.pollenCount()); + } + + // Second pass - No needed, since only --col:W it's in there, which is already on the third pass + BigDecimal varParentPropOrchPoll = zero; + + // Third pass + BigDecimal varSumOrchGameteContr = zero; + BigDecimal varOrchGameteContr; + BigDecimal varSumNeNoSmpContrib = zero; + BigDecimal varFemaleCropPop = zero; + BigDecimal varParPropContrib = null; + BigDecimal varNeNoSmpContrib = null; + for (OrchardParentTreeValsDto orchardPtVals : ptVals.orchardPtVals()) { + boolean hasConeCount = orchardPtVals.coneCount().compareTo(BigDecimal.ZERO) > 0; + boolean hasPollenCount = orchardPtVals.pollenCount().compareTo(BigDecimal.ZERO) > 0; + // --Ignore rows without cone or pollen count + if (hasConeCount || hasPollenCount) { + BigDecimal ptPollenCount = orchardPtVals.pollenCount(); + BigDecimal ptConeCount = orchardPtVals.coneCount(); + + // --col:V + if (varTotalConeCount.compareTo(BigDecimal.ZERO) > 0) { + varFemaleCropPop = ptConeCount.divide(varTotalConeCount, DIVISION_SCALE, halfUp); + } + + // --col:W + if (varTotalPollenCount.compareTo(BigDecimal.ZERO) > 0) { + varParentPropOrchPoll = ptPollenCount.divide(varTotalPollenCount, DIVISION_SCALE, halfUp); + } + + // --col:AE + if (varTotalPollenCount.compareTo(BigDecimal.ZERO) == 0) { + varParPropContrib = varFemaleCropPop; + } else { + varParPropContrib = + varFemaleCropPop + .add(varParentPropOrchPoll) + .divide(new BigDecimal("2"), DIVISION_SCALE, halfUp); + } + + // --col:AO + varNeNoSmpContrib = varParPropContrib.pow(2); + varSumNeNoSmpContrib = varSumNeNoSmpContrib.add(varNeNoSmpContrib); + + // --col:AQ + varOrchGameteContr = + varFemaleCropPop + .add(new BigDecimal("0.75").multiply(varParentPropOrchPoll)) + .divide(new BigDecimal(2)) + .pow(2); + varSumOrchGameteContr = varSumOrchGameteContr.add(varOrchGameteContr); + } + } + + BigDecimal coancestry = null; + BigDecimal neValue = + geneticWorthService.calculateNe( + coancestry, varSumOrchGameteContr, varSumNeNoSmpContrib, ptVals.smpParentsOutside()); CalculatedParentTreeValsDto calculatedVals = new CalculatedParentTreeValsDto(); calculatedVals.setNeValue(neValue); @@ -104,7 +170,7 @@ private GeospatialRespondDto calcMeanGeospatial( oracleDtoList.stream() .collect(Collectors.toMap(GeospatialOracleResDto::parentTreeId, Function.identity())); - // Accumulators of weigthed values, convert DMS to minutes (legacy algo) then sum it up. + // Accumulators of weighted values, convert DMS to minutes (legacy algo) then sum it up. BigDecimal meanLatMinSum = BigDecimal.ZERO; BigDecimal meanLongMinSum = BigDecimal.ZERO; BigDecimal meanElevationSum = BigDecimal.ZERO; @@ -124,7 +190,7 @@ private GeospatialRespondDto calcMeanGeospatial( // Definition: weighted value = proportion * value BigDecimal proportion = dto.proportion(); - if (proportion.compareTo(MAX_PROPORTION) >= 0) { + if (proportion.compareTo(ONE) >= 0) { throw new ResponseStatusException( HttpStatus.BAD_REQUEST, String.format( @@ -247,7 +313,7 @@ private GeospatialRespondDto calcSeedlotGeoData( * STEP 2 * AG: * v_p_contrib_lat_no_smp_poll := - * v_coll_lat * (v_p_prop_contrib-((v_female_crop_pop*v_a_smp_success_pct)/200)); + * v_coll_lat * (varParPropContrib-((v_female_crop_pop*v_a_smp_success_pct)/200)); * * STEP 1 * AJ: @@ -285,7 +351,7 @@ private GeospatialRespondDto calcSeedlotGeoData( // Mean Long = SUM(Wtd. Long. with parent and SMP pollen) // Wtd Long. with parent and SMP pollen is defined in Certification Template Col w/ id // AN - // Calculations are similar to those of Lat's, but wiht long values + // Calculations are similar to those of Lat's, but with long values BigDecimal smpMixLong = smpMixGeoData.getMeanLongitude(); BigDecimal smpPollWtdContribLong = @@ -304,7 +370,7 @@ private GeospatialRespondDto calcSeedlotGeoData( // Mean Elev. = SUM(Wtd. elev with parent and SMP pollen) // Wtd elev. with parent and SMP pollen is defined in Certification Template Col w/ id // AL - // Calculations are similar to those of Lat's, but wiht elevation values + // Calculations are similar to those of Lat's, but with elevation values BigDecimal smpMixElev = new BigDecimal(smpMixGeoData.getMeanElevation()); BigDecimal smpPollWtdContribElev = @@ -339,7 +405,7 @@ private GeospatialRespondDto calcSeedlotGeoData( /** * Calculate the proportion for parent contribution without SMP Pollen. * - * @return prop = v_p_prop_contrib-((v_female_crop_pop*v_a_smp_success_pct)/200) + * @return prop = varParPropContrib-((v_female_crop_pop*v_a_smp_success_pct)/200) */ private BigDecimal calcProportion( OrchardParentTreeValsDto ptValDto, BigDecimal femaleCropPop, BigDecimal totalPollenCount) { @@ -358,12 +424,12 @@ private BigDecimal calcProportion( } /* - * REFERENCE v_p_prop_contrib + * REFERENCE varParPropContrib * --col:AE * IF v_total_pollen_count = 0 THEN - * v_p_prop_contrib := v_female_crop_pop; + * varParPropContrib := v_female_crop_pop; * ELSE - * v_p_prop_contrib := (v_female_crop_pop + v_parent_prop_orch_poll) / 2; + * varParPropContrib := (v_female_crop_pop + v_parent_prop_orch_poll) / 2; */ BigDecimal parentPropContrib; if (totalPollenCount.equals(BigDecimal.ZERO)) { @@ -374,7 +440,7 @@ private BigDecimal calcProportion( BigDecimal smpSuccessPerc = new BigDecimal(ptValDto.smpSuccessPerc()).divide(PERC_DIVISOR); - // prop = v_p_prop_contrib-((v_female_crop_pop*v_a_smp_success_pct)/200) + // prop = varParPropContrib-((v_female_crop_pop*v_a_smp_success_pct)/200) BigDecimal prop = parentPropContrib.subtract((femaleCropPop).multiply(smpSuccessPerc).divide(PROP_DIVISOR)); 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 14827a377..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 @@ -1,14 +1,17 @@ package ca.bc.gov.backendstartapi.service; +import ca.bc.gov.backendstartapi.config.Constants; import ca.bc.gov.backendstartapi.config.SparLog; import ca.bc.gov.backendstartapi.dto.RevisionCountDto; import ca.bc.gov.backendstartapi.dto.SaveSeedlotFormDtoClassA; import ca.bc.gov.backendstartapi.entity.SaveSeedlotProgressEntityClassA; +import ca.bc.gov.backendstartapi.entity.SeedlotStatusEntity; import ca.bc.gov.backendstartapi.entity.seedlot.Seedlot; import ca.bc.gov.backendstartapi.exception.JsonParsingException; import ca.bc.gov.backendstartapi.exception.RevisionCountMismatchException; import ca.bc.gov.backendstartapi.exception.SeedlotFormProgressNotFoundException; import ca.bc.gov.backendstartapi.exception.SeedlotNotFoundException; +import ca.bc.gov.backendstartapi.exception.SeedlotStatusNotFoundException; import ca.bc.gov.backendstartapi.repository.SaveSeedlotProgressRepositoryClassA; import ca.bc.gov.backendstartapi.repository.SeedlotRepository; import ca.bc.gov.backendstartapi.security.LoggedUserService; @@ -31,6 +34,7 @@ public class SaveSeedlotFormService { private final SaveSeedlotProgressRepositoryClassA saveSeedlotProgressRepositoryClassA; private final SeedlotRepository seedlotRepository; private final LoggedUserService loggedUserService; + private final SeedlotStatusService seedlotStatusService; /** Saves the {@link SaveSeedlotFormDtoClassA} to table. */ public RevisionCountDto saveFormClassA( @@ -54,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); @@ -63,19 +68,40 @@ public RevisionCountDto saveFormClassA( parsedAllStepData, parsedProgressStatus, loggedUserService.createAuditCurrentUser()); + + // Update the seedlot status to pending from incomplete. + if (relatedSeedlot + .getSeedlotStatus() + .getSeedlotStatusCode() + .equals(Constants.INCOMPLETE_SEEDLOT_STATUS)) { + SparLog.info("Updating seedlot {} status from INC to PND", seedlotNumber); + + Optional seedLotStatusEntity = + seedlotStatusService.findById(Constants.PENDING_SEEDLOT_STATUS); + + relatedSeedlot.setSeedlotStatus( + seedLotStatusEntity.orElseThrow(SeedlotStatusNotFoundException::new)); + } } else { - // Revision Count verification - Integer prevRevCount = data.revisionCount(); - Integer currRevCount = optionalEntityToSave.get().getRevisionCount(); - - if (prevRevCount != null && !prevRevCount.equals(currRevCount)) { - // Conflict detected - SparLog.info( - "Save progress failed due to revision count mismatch, prev revision count: {}, curr" - + " revision count: {}", - prevRevCount, - currRevCount); - throw new RevisionCountMismatchException(); + + // Revision Count verification only for pending seedlots + if (relatedSeedlot + .getSeedlotStatus() + .getSeedlotStatusCode() + .equals(Constants.PENDING_SEEDLOT_STATUS)) { + + Integer prevRevCount = data.revisionCount(); + Integer currRevCount = optionalEntityToSave.get().getRevisionCount(); + + if (prevRevCount != null && !prevRevCount.equals(currRevCount)) { + // Conflict detected + SparLog.info( + "Save progress failed due to revision count mismatch, prev revision count: {}, curr" + + " revision count: {}", + prevRevCount, + currRevCount); + throw new RevisionCountMismatchException(); + } } SparLog.info( @@ -86,6 +112,10 @@ public RevisionCountDto saveFormClassA( entityToSave.setProgressStatus(parsedProgressStatus); } + relatedSeedlot.setAuditInformation(loggedUserService.createAuditCurrentUser()); + + seedlotRepository.save(relatedSeedlot); + SaveSeedlotProgressEntityClassA saved = saveSeedlotProgressRepositoryClassA.save(entityToSave); SparLog.info("A-class seedlot progress for seedlot number {} saved!", seedlotNumber); diff --git a/backend/src/main/java/ca/bc/gov/backendstartapi/service/SeedlotCollectionMethodService.java b/backend/src/main/java/ca/bc/gov/backendstartapi/service/SeedlotCollectionMethodService.java index ec3117e02..3877a8598 100644 --- a/backend/src/main/java/ca/bc/gov/backendstartapi/service/SeedlotCollectionMethodService.java +++ b/backend/src/main/java/ca/bc/gov/backendstartapi/service/SeedlotCollectionMethodService.java @@ -5,15 +5,14 @@ import ca.bc.gov.backendstartapi.entity.ConeCollectionMethodEntity; import ca.bc.gov.backendstartapi.entity.seedlot.Seedlot; import ca.bc.gov.backendstartapi.entity.seedlot.SeedlotCollectionMethod; -import ca.bc.gov.backendstartapi.entity.seedlot.idclass.SeedlotCollectionMethodId; -import ca.bc.gov.backendstartapi.exception.ConeCollectionMethodNotFoundException; import ca.bc.gov.backendstartapi.exception.SeedlotConflictDataException; import ca.bc.gov.backendstartapi.repository.SeedlotCollectionMethodRepository; import ca.bc.gov.backendstartapi.security.LoggedUserService; +import ca.bc.gov.backendstartapi.util.ValueUtil; import java.util.ArrayList; +import java.util.Collections; import java.util.List; import java.util.Map; -import java.util.Objects; import java.util.function.Function; import java.util.stream.Collectors; import lombok.RequiredArgsConstructor; @@ -42,37 +41,48 @@ public void saveSeedlotFormStep1( seedlot.setCollectionClientNumber(formStep1.collectionClientNumber()); seedlot.setCollectionLocationCode(formStep1.collectionLocnCode()); - seedlot.setCollectionStartDate(formStep1.collectionStartDate()); - seedlot.setCollectionEndDate(formStep1.collectionEndDate()); - seedlot.setNumberOfContainers(formStep1.noOfContainers()); - seedlot.setContainerVolume(formStep1.volPerContainer()); - seedlot.setTotalConeVolume(formStep1.clctnVolume()); + if (!ValueUtil.isValueEqual( + seedlot.getCollectionStartDate(), formStep1.collectionStartDate())) { + seedlot.setCollectionStartDate(formStep1.collectionStartDate()); + } + if (!ValueUtil.isValueEqual(seedlot.getCollectionEndDate(), formStep1.collectionEndDate())) { + seedlot.setCollectionEndDate(formStep1.collectionEndDate()); + } + if (!ValueUtil.isValueEqual(formStep1.noOfContainers(), seedlot.getNumberOfContainers())) { + seedlot.setNumberOfContainers(formStep1.noOfContainers()); + } + if (!ValueUtil.isValueEqual(formStep1.volPerContainer(), seedlot.getContainerVolume())) { + seedlot.setContainerVolume(formStep1.volPerContainer()); + } + if (!ValueUtil.isValueEqual(formStep1.clctnVolume(), seedlot.getTotalConeVolume())) { + seedlot.setTotalConeVolume(formStep1.clctnVolume()); + } seedlot.setComment(formStep1.seedlotComment()); SparLog.info( - "Received {} SeedlotCollectionMethod record(s) for seedlot number {}", + "Received {} collection method(s) for seedlot number {}", formStep1.coneCollectionMethodCodes().size(), seedlot.getId()); List seedlotCollectionList = seedlotCollectionMethodRepository.findAllBySeedlot_id(seedlot.getId()); + boolean allEqual = + areExistingEqualsNewOnes(seedlotCollectionList, formStep1.coneCollectionMethodCodes()); + + if (allEqual) { + SparLog.info("Do not need to touch seedlot cone collection methods, they are the same"); + return; + } + if (!seedlotCollectionList.isEmpty() && canDelete) { SparLog.info( "Deleting {} previous records on the SeedlotCollectionMethod table for seedlot number {}", seedlotCollectionList.size(), seedlot.getId()); - List idsToDelete = new ArrayList<>(); - - for (SeedlotCollectionMethod methdCodeToRemove : seedlotCollectionList) { - idsToDelete.add( - new SeedlotCollectionMethodId( - seedlot.getId(), - methdCodeToRemove.getConeCollectionMethod().getConeCollectionMethodCode())); - } - - seedlotCollectionMethodRepository.deleteAllById(idsToDelete); + seedlotCollectionMethodRepository.deleteAllBySeedlot_id(seedlot.getId()); + seedlotCollectionMethodRepository.flush(); } else if (!seedlotCollectionList.isEmpty() && !canDelete) { SparLog.info("Update seedlot {} collection data failed due to conflict.", seedlot.getId()); throw new SeedlotConflictDataException(seedlot.getId()); @@ -81,6 +91,23 @@ public void saveSeedlotFormStep1( addSeedlotCollectionMethod(seedlot, formStep1.coneCollectionMethodCodes()); } + private boolean areExistingEqualsNewOnes( + List existing, List newOnes) { + List existingOnes = + existing.stream() + .map( + scm -> Integer.valueOf(scm.getConeCollectionMethod().getConeCollectionMethodCode())) + .toList(); + + List sortedList1 = new ArrayList<>(existingOnes); + List sortedList2 = new ArrayList<>(newOnes); + + Collections.sort(sortedList1); + Collections.sort(sortedList2); + + return sortedList1.equals(sortedList2); + } + /** * Saves each Collection method for a given Seedlot. * @@ -101,9 +128,9 @@ private void addSeedlotCollectionMethod(Seedlot seedlot, List methods) methods.size(), seedlot.getId()); - // Map of Cone Collection Methots + // Map of Cone Collection Methods Map ccmeMap = - coneCollectionMethodService.getAllValidConeCollectionMethods().stream() + coneCollectionMethodService.getAllByIdIn(methods).stream() .collect( Collectors.toMap( ConeCollectionMethodEntity::getConeCollectionMethodCode, Function.identity())); @@ -111,14 +138,9 @@ private void addSeedlotCollectionMethod(Seedlot seedlot, List methods) List scmList = new ArrayList<>(); for (Integer methodCode : methods) { - ConeCollectionMethodEntity coneCollectionEntity = ccmeMap.get(methodCode); - if (Objects.isNull(coneCollectionEntity)) { - throw new ConeCollectionMethodNotFoundException(); - } - SeedlotCollectionMethod methodEntity = new SeedlotCollectionMethod(); methodEntity.setSeedlot(seedlot); - methodEntity.setConeCollectionMethod(coneCollectionEntity); + methodEntity.setConeCollectionMethod(ccmeMap.get(methodCode)); methodEntity.setAuditInformation(loggedUserService.createAuditCurrentUser()); scmList.add(methodEntity); @@ -126,4 +148,16 @@ private void addSeedlotCollectionMethod(Seedlot seedlot, List methods) seedlotCollectionMethodRepository.saveAll(scmList); } + + /** + * Get All Seedlot collection method codes given a seedlot number. + * + * @param seedlotNumber The seedlot number. + * @return List of collection method codes or an empty list. + */ + public List getAllSeedlotCollectionMethodsBySeedlot(String seedlotNumber) { + return seedlotCollectionMethodRepository.findAllBySeedlot_id(seedlotNumber).stream() + .map(col -> col.getConeCollectionMethod().getConeCollectionMethodCode()) + .collect(Collectors.toList()); + } } diff --git a/backend/src/main/java/ca/bc/gov/backendstartapi/service/SeedlotOrchardService.java b/backend/src/main/java/ca/bc/gov/backendstartapi/service/SeedlotOrchardService.java index 8f02c28e6..eeb12d2fb 100644 --- a/backend/src/main/java/ca/bc/gov/backendstartapi/service/SeedlotOrchardService.java +++ b/backend/src/main/java/ca/bc/gov/backendstartapi/service/SeedlotOrchardService.java @@ -7,6 +7,7 @@ import ca.bc.gov.backendstartapi.exception.SeedlotConflictDataException; import ca.bc.gov.backendstartapi.repository.SeedlotOrchardRepository; import ca.bc.gov.backendstartapi.security.LoggedUserService; +import ca.bc.gov.backendstartapi.util.ValueUtil; import java.util.ArrayList; import java.util.List; import java.util.Optional; @@ -38,7 +39,10 @@ public void saveSeedlotFormStep4( seedlot.setProducedWithBiotechnologicalProcesses(formStep4.biotechProcessesInd()); seedlot.setPollenContaminationPresentInOrchard(formStep4.pollenContaminationInd()); seedlot.setPollenContaminationPercentage(formStep4.pollenContaminationPct()); - seedlot.setPollenContaminantBreedingValue(formStep4.contaminantPollenBv()); + if (!ValueUtil.isValueEqual( + formStep4.contaminantPollenBv(), seedlot.getPollenContaminantBreedingValue())) { + seedlot.setPollenContaminantBreedingValue(formStep4.contaminantPollenBv()); + } seedlot.setPollenContaminationMethodCode(formStep4.pollenContaminationMthdCode()); SparLog.info( @@ -54,6 +58,12 @@ public void saveSeedlotFormStep4( } List seedlotOrchards = getAllSeedlotOrchardBySeedlotNumber(seedlot.getId()); + boolean allEqual = areExistingEqualsNewOnes(seedlotOrchards, formStep4); + if (allEqual) { + SparLog.info("Do not need to touch seedlot orchards, they are the same"); + return; + } + if (!seedlotOrchards.isEmpty() && canDelete) { SparLog.info( "Deleting {} previous records on the SeedlotOrchard table for seedlot number {}", @@ -61,6 +71,7 @@ public void saveSeedlotFormStep4( seedlot.getId()); seedlotOrchardRepository.deleteAllBySeedlot_id(seedlot.getId()); + seedlotOrchardRepository.flush(); } else if (!seedlotOrchards.isEmpty() && !canDelete) { SparLog.info("Update seedlot {} orchard data failed due to conflict.", seedlot.getId()); throw new SeedlotConflictDataException(seedlot.getId()); @@ -69,6 +80,31 @@ public void saveSeedlotFormStep4( saveSeedlotOrchards(seedlot, formStep4.primaryOrchardId(), formStep4.secondaryOrchardId()); } + private boolean areExistingEqualsNewOnes( + List seedlotOrchards, SeedlotFormOrchardDto formStep4) { + // Primary + Optional primaryOpt = + seedlotOrchards.stream().filter(SeedlotOrchard::getIsPrimary).findFirst(); + + if (primaryOpt.isEmpty()) { + return false; + } + + boolean primaryEqual = ValueUtil.isValueEqual(primaryOpt.get(), formStep4.primaryOrchardId()); + + // Secondary + Optional secondaryOpt = + seedlotOrchards.stream().filter(so -> !so.getIsPrimary()).findFirst(); + + if (secondaryOpt.isEmpty()) { + return primaryEqual; + } + + boolean secondaryEqual = + ValueUtil.isValueEqual(secondaryOpt.get(), formStep4.secondaryOrchardId()); + return secondaryEqual && primaryEqual; + } + private void saveSeedlotOrchards( Seedlot seedlot, String primaryOrchardId, String secondaryOrchardId) { diff --git a/backend/src/main/java/ca/bc/gov/backendstartapi/service/SeedlotOwnerQuantityService.java b/backend/src/main/java/ca/bc/gov/backendstartapi/service/SeedlotOwnerQuantityService.java index 48aba9da5..f1d2fedd9 100644 --- a/backend/src/main/java/ca/bc/gov/backendstartapi/service/SeedlotOwnerQuantityService.java +++ b/backend/src/main/java/ca/bc/gov/backendstartapi/service/SeedlotOwnerQuantityService.java @@ -5,15 +5,14 @@ import ca.bc.gov.backendstartapi.entity.MethodOfPaymentEntity; import ca.bc.gov.backendstartapi.entity.seedlot.Seedlot; import ca.bc.gov.backendstartapi.entity.seedlot.SeedlotOwnerQuantity; -import ca.bc.gov.backendstartapi.entity.seedlot.idclass.SeedlotOwnerQuantityId; -import ca.bc.gov.backendstartapi.exception.MethodOfPaymentNotFoundException; import ca.bc.gov.backendstartapi.exception.SeedlotConflictDataException; import ca.bc.gov.backendstartapi.repository.SeedlotOwnerQuantityRepository; import ca.bc.gov.backendstartapi.security.LoggedUserService; import java.util.ArrayList; +import java.util.HashSet; import java.util.List; import java.util.Map; -import java.util.Objects; +import java.util.Set; import java.util.function.Function; import java.util.stream.Collectors; import lombok.RequiredArgsConstructor; @@ -42,23 +41,19 @@ public List saveSeedlotFormStep2( SparLog.info("Saving Seedlot Form Step 2-Ownership for seedlot number {}", seedlot.getId()); SparLog.info( - "Received {} SeedlotOwerQuantity record(s) for seedlot number {}", - formStep2List.size(), - seedlot.getId()); + "Received {} owner record(s) for seedlot number {}", formStep2List.size(), seedlot.getId()); List soqList = seedlotOwnerQuantityRepository.findAllBySeedlot_id(seedlot.getId()); if (!soqList.isEmpty() && canDelete) { SparLog.info( - "Deleting {} previous records on the SeedlotOwerQuantity table for seedlot number {}", + "Deleting {} previous owner records for seedlot number {}", soqList.size(), seedlot.getId()); - List idsToDelete = - soqList.stream().map(x -> x.getId()).collect(Collectors.toList()); - - seedlotOwnerQuantityRepository.deleteAllById(idsToDelete); + seedlotOwnerQuantityRepository.deleteAllBySeedlot_id(seedlot.getId()); + seedlotOwnerQuantityRepository.flush(); } else if (!soqList.isEmpty() && !canDelete) { SparLog.info("Update seedlot {} ownership data failed due to conflict.", seedlot.getId()); throw new SeedlotConflictDataException(seedlot.getId()); @@ -70,19 +65,22 @@ public List saveSeedlotFormStep2( private List addSeedlotOwnerQuantityFromForm( Seedlot seedlot, List sfodList) { if (sfodList.isEmpty()) { - SparLog.info( - "No new records to be inserted on the SeedlotOwnerQuantity table for seedlot number {}", - seedlot.getId()); + SparLog.info("No new owner records to be inserted for seedlot number {}", seedlot.getId()); return List.of(); } SparLog.info( - "{} record(s) to be inserted on the SeedlotOwnerQuantity table for seedlot number {}", + "{} owner record(s) to be inserted for seedlot number {}", sfodList.size(), seedlot.getId()); + // Using Set here to avoid duplications + Set paymentMethods = + new HashSet<>(sfodList.stream().map(SeedlotFormOwnershipDto::methodOfPaymentCode).toList()); + List methods = paymentMethods.stream().toList(); + Map mopeMap = - methodOfPaymentService.getAllValidMethodOfPayments().stream() + methodOfPaymentService.getAllMethodsByCodeList(methods).stream() .collect( Collectors.toMap( MethodOfPaymentEntity::getMethodOfPaymentCode, Function.identity())); @@ -91,22 +89,28 @@ private List addSeedlotOwnerQuantityFromForm( for (SeedlotFormOwnershipDto ownershipDto : sfodList) { SeedlotOwnerQuantity ownerQuantityEntity = new SeedlotOwnerQuantity( - seedlot, ownershipDto.ownerClientNumber(), ownershipDto.ownerLocnCode()); + seedlot, + ownershipDto.ownerClientNumber(), + ownershipDto.ownerLocnCode(), + mopeMap.get(ownershipDto.methodOfPaymentCode())); ownerQuantityEntity.setOriginalPercentageOwned(ownershipDto.originalPctOwned()); ownerQuantityEntity.setOriginalPercentageReserved(ownershipDto.originalPctRsrvd()); ownerQuantityEntity.setOriginalPercentageSurplus(ownershipDto.originalPctSrpls()); ownerQuantityEntity.setFundingSourceCode(ownershipDto.sparFundSrceCode()); ownerQuantityEntity.setAuditInformation(loggedUserService.createAuditCurrentUser()); - - MethodOfPaymentEntity mope = mopeMap.get(ownershipDto.methodOfPaymentCode()); - if (Objects.isNull(mope)) { - throw new MethodOfPaymentNotFoundException(); - } - ownerQuantityEntity.setMethodOfPayment(mope); - soqList.add(ownerQuantityEntity); } return seedlotOwnerQuantityRepository.saveAll(soqList); } + + /** + * Find all seedlot owner quantity given a seedlot number. + * + * @param seedlotNumber The seedlot number. + * @return A list of found records, or an empty list. + */ + public List findAllBySeedlot(String seedlotNumber) { + return seedlotOwnerQuantityRepository.findAllBySeedlot_id(seedlotNumber); + } } diff --git a/backend/src/main/java/ca/bc/gov/backendstartapi/service/SeedlotParentTreeGeneticQualityService.java b/backend/src/main/java/ca/bc/gov/backendstartapi/service/SeedlotParentTreeGeneticQualityService.java index 467c02c79..4dd65aa25 100644 --- a/backend/src/main/java/ca/bc/gov/backendstartapi/service/SeedlotParentTreeGeneticQualityService.java +++ b/backend/src/main/java/ca/bc/gov/backendstartapi/service/SeedlotParentTreeGeneticQualityService.java @@ -92,8 +92,11 @@ private void addSeedlotParentTreeGenQlty( seedlotGenQltyDto.geneticQualityValue(), loggedUserService.createAuditCurrentUser()); - sptgq.setQualityValueEstimated(Boolean.FALSE); - sptgq.setParentTreeUntested(Boolean.FALSE); + sptgq.setQualityValueEstimated(seedlotGenQltyDto.isEstimated()); + + // untested_ind = True if estimated = True and pt.tested_ind = False + sptgq.setParentTreeUntested( + !seedlotGenQltyDto.isParentTreeTested() && seedlotGenQltyDto.isEstimated()); seedlotPtToInsert.add(sptgq); } diff --git a/backend/src/main/java/ca/bc/gov/backendstartapi/service/SeedlotParentTreeService.java b/backend/src/main/java/ca/bc/gov/backendstartapi/service/SeedlotParentTreeService.java index e77c0c850..aec567a7e 100644 --- a/backend/src/main/java/ca/bc/gov/backendstartapi/service/SeedlotParentTreeService.java +++ b/backend/src/main/java/ca/bc/gov/backendstartapi/service/SeedlotParentTreeService.java @@ -50,7 +50,7 @@ public class SeedlotParentTreeService { * @return A list of {@link SeedlotParentTree} */ public List getAllSeedlotParentTree(String seedlotNumber) { - SparLog.info("Get All SeedlotPrentTree for seedlot number {}", seedlotNumber); + SparLog.info("Get All SeedlotParentTree for seedlot number {}", seedlotNumber); return seedlotParentTreeRepository.findAllBySeedlot_id(seedlotNumber); } @@ -212,9 +212,6 @@ private List addSeedlotParentTree( seedlotPtListToInsert.add(seedlotParentTree); } - SparLog.info( - "3. seedlotParentTreeRepository size: {}", seedlotParentTreeRepository.findAll().size()); - return seedlotParentTreeRepository.saveAll(seedlotPtListToInsert); } } 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 14990f608..fd95fb6de 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 @@ -52,22 +52,20 @@ import ca.bc.gov.backendstartapi.exception.SeedlotStatusNotFoundException; import ca.bc.gov.backendstartapi.provider.Provider; import ca.bc.gov.backendstartapi.repository.GeneticClassRepository; -import ca.bc.gov.backendstartapi.repository.SeedlotCollectionMethodRepository; -import ca.bc.gov.backendstartapi.repository.SeedlotOwnerQuantityRepository; import ca.bc.gov.backendstartapi.repository.SeedlotRepository; import ca.bc.gov.backendstartapi.repository.SeedlotSeedPlanZoneRepository; import ca.bc.gov.backendstartapi.repository.SeedlotSourceRepository; -import ca.bc.gov.backendstartapi.repository.SeedlotStatusRepository; import ca.bc.gov.backendstartapi.security.LoggedUserService; import ca.bc.gov.backendstartapi.security.UserInfo; +import ca.bc.gov.backendstartapi.util.ValueUtil; import jakarta.transaction.Transactional; import java.math.BigDecimal; +import java.time.Clock; import java.time.LocalDateTime; import java.time.format.DateTimeFormatter; import java.util.ArrayList; import java.util.List; import java.util.Map; -import java.util.Objects; import java.util.Optional; import java.util.function.Function; import java.util.stream.Collectors; @@ -90,20 +88,14 @@ public class SeedlotService { private final SeedlotSourceRepository seedlotSourceRepository; - private final SeedlotStatusRepository seedlotStatusRepository; - private final GeneticClassRepository geneticClassRepository; private final LoggedUserService loggedUserService; private final SeedlotCollectionMethodService seedlotCollectionMethodService; - private final SeedlotCollectionMethodRepository seedlotCollectionMethodRepository; - private final SeedlotOwnerQuantityService seedlotOwnerQuantityService; - private final SeedlotOwnerQuantityRepository seedlotOwnerQuantityRepository; - private final SeedlotOrchardService seedlotOrchardService; private final SeedlotParentTreeService seedlotParentTreeService; @@ -144,9 +136,11 @@ public SeedlotStatusResponseDto createSeedlot(SeedlotCreateDto createDto) { Seedlot seedlot = new Seedlot(nextSeedlotNumber(createDto.geneticClassCode())); + // Newly created seedlot has a status of INCOMPLETE, will change to PENDING once a row is + // created in the table seedlot_registration_a_class_save Optional seedLotStatusEntity = - seedlotStatusRepository.findById(Constants.CLASS_A_SEEDLOT_STATUS); - seedlot.setSeedlotStatus(seedLotStatusEntity.orElseThrow(InvalidSeedlotRequestException::new)); + seedlotStatusService.findById(Constants.INCOMPLETE_SEEDLOT_STATUS); + seedlot.setSeedlotStatus(seedLotStatusEntity.orElseThrow(SeedlotStatusNotFoundException::new)); seedlot.setApplicantClientNumber(createDto.applicantClientNumber()); seedlot.setApplicantLocationCode(createDto.applicantLocationCode()); @@ -155,11 +149,11 @@ public SeedlotStatusResponseDto createSeedlot(SeedlotCreateDto createDto) { Optional classEntity = geneticClassRepository.findById(createDto.geneticClassCode().toString()); - seedlot.setGeneticClass(classEntity.orElseThrow(InvalidSeedlotRequestException::new)); + seedlot.setGeneticClass(classEntity.orElseThrow(GeneticClassNotFoundException::new)); Optional seedlotSourceEntity = seedlotSourceRepository.findById(createDto.seedlotSourceCode()); - seedlot.setSeedlotSource(seedlotSourceEntity.orElseThrow(InvalidSeedlotRequestException::new)); + seedlot.setSeedlotSource(seedlotSourceEntity.orElseThrow(SeedlotSourceNotFoundException::new)); seedlot.setIntendedForCrownLand(createDto.toBeRegistrdInd()); seedlot.setSourceInBc(createDto.bcSourceInd()); @@ -193,7 +187,7 @@ private String nextSeedlotNumber(Character seedlotClassCode) { seedlotRepository.findNextSeedlotNumber( Constants.CLASS_A_SEEDLOT_NUM_MIN, Constants.CLASS_A_SEEDLOT_NUM_MAX); - if (Objects.isNull(seedlotNumber)) { + if (!ValueUtil.hasValue(seedlotNumber)) { seedlotNumber = Constants.CLASS_A_SEEDLOT_NUM_MIN; } @@ -391,7 +385,10 @@ private List getParentTreeGenQual( new ParentTreeGeneticQualityDto( parentTreeGenQual.getGeneticTypeCode(), parentTreeGenQual.getGeneticWorth().getGeneticWorthCode(), - parentTreeGenQual.getGeneticQualityValue()); + parentTreeGenQual.getGeneticQualityValue(), + // We cannot know this for sure, see explanation down below. + null, + parentTreeGenQual.getQualityValueEstimated()); parentTreeGenQualList.add(parentTreeGenQualDto); } } @@ -399,6 +396,18 @@ private List getParentTreeGenQual( return parentTreeGenQualList; } + /* + * Explanation for isTested is unknown + * What we know: untested_ind = True if estimated = True and pt.tested_ind = False + * + * - If untestedInd is true and estimatedInd is true: + * - testedInd is definitely false. + * - If untestedInd is false and estimatedInd is true: + * - testedInd is definitely true. + * - If estimatedInd is false: + * - The value of testedInd cannot be determined from untestedInd alone. + */ + /** * Auxiliar function to get the genetic quality for each seedlot's SMP Mix parent tree. * @@ -429,7 +438,9 @@ private List getSmpMixParentTreeGenQual( new ParentTreeGeneticQualityDto( sptSmpMixGenQual.getGeneticTypeCode(), sptSmpMixGenQual.getGeneticWorth().getGeneticWorthCode(), - sptSmpMixGenQual.getGeneticQualityValue()); + sptSmpMixGenQual.getGeneticQualityValue(), + null, + null); smpMixGenQualList.add(parentTreeGenQualDto); } } @@ -441,7 +452,9 @@ private List getSmpMixParentTreeGenQual( new ParentTreeGeneticQualityDto( smpMixGenQual.getGeneticTypeCode(), smpMixGenQual.getGeneticWorth().getGeneticWorthCode(), - smpMixGenQual.getGeneticQualityValue()); + smpMixGenQual.getGeneticQualityValue(), + null, + null); smpMixGenQualList.add(parentTreeGenQualDto); } } @@ -533,9 +546,7 @@ public SeedlotAclassFormDto getAclassSeedlotFormInfo(@NonNull String seedlotNumb seedlotRepository.findById(seedlotNumber).orElseThrow(SeedlotNotFoundException::new); List seedlotCollectionList = - seedlotCollectionMethodRepository.findAllBySeedlot_id(seedlotInfo.getId()).stream() - .map(col -> col.getConeCollectionMethod().getConeCollectionMethodCode()) - .collect(Collectors.toList()); + seedlotCollectionMethodService.getAllSeedlotCollectionMethodsBySeedlot(seedlotInfo.getId()); // Divide the seedlot data to each respective step SeedlotFormCollectionDto collectionStep = @@ -551,7 +562,7 @@ public SeedlotAclassFormDto getAclassSeedlotFormInfo(@NonNull String seedlotNumb seedlotCollectionList); List ownershipStep = - seedlotOwnerQuantityRepository.findAllBySeedlot_id(seedlotInfo.getId()).stream() + seedlotOwnerQuantityService.findAllBySeedlot(seedlotInfo.getId()).stream() .filter( owner -> owner.getOriginalPercentageOwned() != null @@ -564,7 +575,9 @@ public SeedlotAclassFormDto getAclassSeedlotFormInfo(@NonNull String seedlotNumb owner.getOriginalPercentageOwned(), owner.getOriginalPercentageReserved(), owner.getOriginalPercentageSurplus(), - owner.getMethodOfPayment().getMethodOfPaymentCode(), + owner.getMethodOfPayment() != null + ? owner.getMethodOfPayment().getMethodOfPaymentCode() + : null, owner.getFundingSourceCode())) .collect(Collectors.toList()); @@ -580,18 +593,21 @@ public SeedlotAclassFormDto getAclassSeedlotFormInfo(@NonNull String seedlotNumb List seedlotOrchards = seedlotOrchardService.getAllSeedlotOrchardBySeedlotNumber(seedlotInfo.getId()); - List filteredPrimaryOrchard = - seedlotOrchards.stream().filter(so -> so.getIsPrimary()).toList(); + String primaryOrchardId = null; - String primaryOrchardId = - filteredPrimaryOrchard.isEmpty() - ? filteredPrimaryOrchard.get(0).getOrchardId() - : seedlotOrchards.get(0).getOrchardId(); + if (!seedlotOrchards.isEmpty()) { + List filteredPrimaryOrchard = + seedlotOrchards.stream().filter(so -> so.getIsPrimary()).toList(); - Optional secondaryOrchardId = - seedlotOrchards.size() > 1 - ? Optional.of(seedlotOrchards.get(1).getOrchardId()) - : Optional.empty(); + 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( @@ -788,7 +804,7 @@ public SeedlotStatusResponseDto updateSeedlotWithForm( // Calculate Seedlot GeoSpatial (for Seedlot, mean latitude, mean longitude, mean elevation) // Calculate Genetic Worth // Update Seedlot Ne, collection elevation, and collection lat long - // Saved the Seedlot calculated Genetic Worth + // Saves the Seedlot calculated Genetic Worth setParentTreeContribution( seedlot, form.seedlotFormParentTreeDtoList(), form.seedlotFormParentTreeSmpDtoList()); @@ -852,11 +868,11 @@ private void setBecValues( inMemoryDto.setPrimarySpuId(primarySpuId); // Not sure why it's called Bgc in seedlot instead of Bec in orchard - seedlot.setBgcZoneCode(orchardDto.becZoneCode()); - seedlot.setBgcZoneDescription(orchardDto.becZoneDescription()); - seedlot.setBgcSubzoneCode(orchardDto.becSubzoneCode()); - seedlot.setVariant(orchardDto.variant()); - seedlot.setBecVersionId(orchardDto.becVersionId()); + seedlot.setBgcZoneCode(orchardDto.getBecZoneCode()); + seedlot.setBgcZoneDescription(orchardDto.getBecZoneDescription()); + seedlot.setBgcSubzoneCode(orchardDto.getBecSubzoneCode()); + seedlot.setVariant(orchardDto.getVariant()); + seedlot.setBecVersionId(orchardDto.getBecVersionId()); seedlot.setSeedPlanUnitId(primarySpuId); SparLog.info("BEC values set"); @@ -871,7 +887,7 @@ private void setParentTreeContribution( List orchardPtVals = convertToPtVals(orchardPtDtoList); List smpMixIdAndProps = convertToGeoRes(smpPtDtoList); - PtValsCalReqDto ptValsCalReqDto = new PtValsCalReqDto(orchardPtVals, smpMixIdAndProps); + PtValsCalReqDto ptValsCalReqDto = new PtValsCalReqDto(orchardPtVals, smpMixIdAndProps, 0); PtCalculationResDto ptCalculationResDto = parentTreeService.calculatePtVals(ptValsCalReqDto); @@ -879,7 +895,11 @@ private void setParentTreeContribution( ptCalculationResDto.calculatedPtVals().getGeospatialData(); // Ne value - seedlot.setEffectivePopulationSize(ptCalculationResDto.calculatedPtVals().getNeValue()); + if (!ValueUtil.isValueEqual( + ptCalculationResDto.calculatedPtVals().getNeValue(), + seedlot.getEffectivePopulationSize())) { + seedlot.setEffectivePopulationSize(ptCalculationResDto.calculatedPtVals().getNeValue()); + } // Elevation seedlot.setCollectionElevation(collectionGeoData.getMeanElevation()); @@ -981,7 +1001,7 @@ private void setAreaOfUse( SparLog.info("Begin to set Area of Use values"); Integer activeSpuId = inMemoryDto.getPrimarySpuId(); - if (Objects.isNull(activeSpuId)) { + if (!ValueUtil.hasValue(activeSpuId)) { ActiveOrchardSpuEntity activeSpuEntity = orchardService .findSpuIdByOrchardWithActive(primaryOrchardId, true) @@ -1082,7 +1102,7 @@ private void setSeedlotDeclaredInfo(Seedlot seedlot) { String userId = loggedUserService.getLoggedUserId(); DateTimeFormatter dtf = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"); seedlot.setDeclarationOfTrueInformationUserId(userId); - seedlot.setDeclarationOfTrueInformationTimestamp(LocalDateTime.now()); + seedlot.setDeclarationOfTrueInformationTimestamp(LocalDateTime.now(Clock.systemUTC())); SparLog.info( "Declaration data set, for seedlot {} for user {} at {}", @@ -1097,8 +1117,14 @@ private void saveSeedlotFormStep3(Seedlot seedlot, SeedlotFormInterimDto formSte seedlot.setInterimStorageClientNumber(formStep3.intermStrgClientNumber()); seedlot.setInterimStorageLocationCode(formStep3.intermStrgLocnCode()); - seedlot.setInterimStorageStartDate(formStep3.intermStrgStDate()); - seedlot.setInterimStorageEndDate(formStep3.intermStrgEndDate()); + if (!ValueUtil.isValueEqual( + seedlot.getInterimStorageStartDate(), formStep3.intermStrgStDate())) { + seedlot.setInterimStorageStartDate(formStep3.intermStrgStDate()); + } + if (!ValueUtil.isValueEqual( + seedlot.getInterimStorageEndDate(), formStep3.intermStrgEndDate())) { + seedlot.setInterimStorageEndDate(formStep3.intermStrgEndDate()); + } seedlot.setInterimStorageFacilityCode(formStep3.intermFacilityCode()); // If the facility type is Other, then a description is required. SparLog.info("{} FACILITY TYPE CODE", formStep3.intermFacilityCode()); @@ -1145,12 +1171,22 @@ private void saveSeedlotFormStep6(Seedlot seedlot, SeedlotFormExtractionDto form seedlot.setExtractionClientNumber(formStep6.extractoryClientNumber()); seedlot.setExtractionLocationCode(formStep6.extractoryLocnCode()); - seedlot.setExtractionStartDate(formStep6.extractionStDate()); - seedlot.setExtractionEndDate(formStep6.extractionEndDate()); + if (!ValueUtil.isValueEqual(seedlot.getExtractionStartDate(), formStep6.extractionStDate())) { + seedlot.setExtractionStartDate(formStep6.extractionStDate()); + } + if (!ValueUtil.isValueEqual(seedlot.getExtractionEndDate(), formStep6.extractionEndDate())) { + seedlot.setExtractionEndDate(formStep6.extractionEndDate()); + } seedlot.setStorageClientNumber(formStep6.storageClientNumber()); seedlot.setStorageLocationCode(formStep6.storageLocnCode()); - seedlot.setTemporaryStorageStartDate(formStep6.temporaryStrgStartDate()); - seedlot.setTemporaryStorageEndDate(formStep6.temporaryStrgEndDate()); + if (!ValueUtil.isValueEqual( + seedlot.getTemporaryStorageStartDate(), formStep6.temporaryStrgStartDate())) { + seedlot.setTemporaryStorageStartDate(formStep6.temporaryStrgStartDate()); + } + if (!ValueUtil.isValueEqual( + seedlot.getTemporaryStorageEndDate(), formStep6.temporaryStrgEndDate())) { + seedlot.setTemporaryStorageEndDate(formStep6.temporaryStrgEndDate()); + } } } diff --git a/backend/src/main/java/ca/bc/gov/backendstartapi/service/SeedlotStatusService.java b/backend/src/main/java/ca/bc/gov/backendstartapi/service/SeedlotStatusService.java index 3298da4e2..c8254a880 100644 --- a/backend/src/main/java/ca/bc/gov/backendstartapi/service/SeedlotStatusService.java +++ b/backend/src/main/java/ca/bc/gov/backendstartapi/service/SeedlotStatusService.java @@ -72,4 +72,14 @@ public Optional getValidSeedlotStatus(String statusCode) { return optionalSeedlot; } + + /** + * Get a Seedlot Status entity by id. + * + * @param id The status code. + * @return Optional of a Seedlot Status Entity. + */ + public Optional findById(String id) { + return seedlotStatusRepository.findById(id); + } } diff --git a/backend/src/main/java/ca/bc/gov/backendstartapi/service/TscAdminService.java b/backend/src/main/java/ca/bc/gov/backendstartapi/service/TscAdminService.java index 92dc5cdc2..0e1d6662e 100644 --- a/backend/src/main/java/ca/bc/gov/backendstartapi/service/TscAdminService.java +++ b/backend/src/main/java/ca/bc/gov/backendstartapi/service/TscAdminService.java @@ -15,6 +15,8 @@ import ca.bc.gov.backendstartapi.repository.SeedlotRepository; import ca.bc.gov.backendstartapi.repository.SeedlotSeedPlanZoneRepository; import ca.bc.gov.backendstartapi.security.LoggedUserService; +import ca.bc.gov.backendstartapi.util.ValueUtil; +import java.time.Clock; import java.time.LocalDateTime; import java.util.ArrayList; import java.util.List; @@ -93,7 +95,7 @@ public Seedlot updateSeedlotStatus(String seedlotNumber, String status) { // Update the Seedlot instance only if (status.equals("APP")) { seedlotEntity.setApprovedUserId(loggedUserService.getLoggedUserId()); - seedlotEntity.setApprovedTimestamp(LocalDateTime.now()); + seedlotEntity.setApprovedTimestamp(LocalDateTime.now(Clock.systemUTC())); } seedlotEntity.setSeedlotStatus(seedlotStatus.get()); @@ -202,7 +204,10 @@ public void overrideElevLatLongMinMax( public void overrideSeedlotCollElevLatLong( Seedlot seedlot, SeedlotReviewGeoInformationDto seedlotReviewDto) { // Ne value - seedlot.setEffectivePopulationSize(seedlotReviewDto.effectivePopulationSize()); + if (!ValueUtil.isValueEqual( + seedlot.getEffectivePopulationSize(), seedlotReviewDto.effectivePopulationSize())) { + seedlot.setEffectivePopulationSize(seedlotReviewDto.effectivePopulationSize()); + } // Elevation seedlot.setCollectionElevation(seedlotReviewDto.meanElevation()); diff --git a/backend/src/main/java/ca/bc/gov/backendstartapi/util/ValueUtil.java b/backend/src/main/java/ca/bc/gov/backendstartapi/util/ValueUtil.java new file mode 100644 index 000000000..cd2720e9f --- /dev/null +++ b/backend/src/main/java/ca/bc/gov/backendstartapi/util/ValueUtil.java @@ -0,0 +1,101 @@ +package ca.bc.gov.backendstartapi.util; + +import ca.bc.gov.backendstartapi.config.SparLog; +import java.math.BigDecimal; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.ZoneId; +import java.time.ZonedDateTime; +import java.util.List; +import java.util.Optional; +import lombok.AccessLevel; +import lombok.NoArgsConstructor; +import org.springframework.data.domain.Page; + +/** This class contains methods for handling objects values and comparing. */ +@NoArgsConstructor(access = AccessLevel.NONE) +public final class ValueUtil { + + /** + * Check if a variable has value. Note that only objects should be handled here, meaning that + * primitive as int, char, long should not be used here. If the class it's a Boolean, will return + * true if it's different from null, because 'false' it's a value. + * + * @param obj The variable to check + * @return true if has, false otherwise. + */ + public static boolean hasValue(Object obj) { + if (obj == null) { + return false; + } + if (obj instanceof String str) { + return !str.isBlank(); + } + if (obj instanceof BigDecimal big) { + return big.compareTo(BigDecimal.ZERO) != 0; + } + if (obj instanceof Integer intVal) { + return intVal != 0; + } + if (obj instanceof Character charVal) { + return charVal.charValue() != ' '; + } + if (obj instanceof LocalDateTime localDateTime) { + ZonedDateTime zdt = localDateTime.atZone(ZoneId.systemDefault()); + return zdt.toInstant().toEpochMilli() > 0L; + } + if (obj instanceof LocalDate localDate) { + ZonedDateTime zdt = localDate.atStartOfDay(ZoneId.systemDefault()); + return zdt.toInstant().toEpochMilli() > 0L; + } + if (obj instanceof Long longValue) { + return longValue != 0L; + } + if (obj instanceof Double doubleValue) { + return doubleValue != 0D; + } + if (obj instanceof Page page) { + return page.hasContent(); + } + if (obj instanceof List list) { + return !list.isEmpty(); + } + if (obj instanceof Boolean) { + return true; + } + if (obj instanceof Optional opt) { + return opt.isPresent(); + } + SparLog.warn("Class not handled {}", obj.getClass()); + return false; + } + + /** + * Compare two objects checking if they have the same content/value. If one of the objects are + * null, then will return false. If they have different classes, will return null. If it's not an + * expected class, will log in the WARN level and return false. + * + * @param objOne First object to compare. + * @param objTwo Second object to compare. + * @return true if they are, false otherwise. + */ + public static boolean isValueEqual(Object objOne, Object objTwo) { + if (!hasValue(objOne) || !hasValue(objTwo)) { + return false; + } + if (objOne.getClass() != objTwo.getClass()) { + return false; + } + + if (objOne.getClass() == BigDecimal.class) { + return BigDecimal.class.cast(objOne).compareTo(BigDecimal.class.cast(objTwo)) == 0; + } + if (objOne.getClass() == LocalDateTime.class) { + return LocalDateTime.class.cast(objOne).isEqual(LocalDateTime.class.cast(objTwo)); + } + if (objOne.getClass() == LocalDate.class) { + return LocalDate.class.cast(objOne).isEqual(LocalDate.class.cast(objTwo)); + } + return objOne.equals(objTwo); + } +} diff --git a/backend/src/main/resources/application.properties b/backend/src/main/resources/application.properties index c109fe15b..7a1295878 100644 --- a/backend/src/main/resources/application.properties +++ b/backend/src/main/resources/application.properties @@ -33,6 +33,7 @@ spring.datasource.hikari.minimumIdle = 1 spring.datasource.hikari.maximumPoolSize = 3 spring.jpa.properties.hibernate.default_schema=spar spring.jpa.database-platform=org.hibernate.dialect.PostgreSQLDialect +spring.jpa.show-sql=false spring.flyway.enabled = true spring.flyway.baseline-on-migrate = true spring.flyway.locations = classpath:db/migration diff --git a/backend/src/main/resources/db/migration/V43__add_default_genetic_worth_val.sql b/backend/src/main/resources/db/migration/V43__add_default_genetic_worth_val.sql new file mode 100644 index 000000000..68b23ebce --- /dev/null +++ b/backend/src/main/resources/db/migration/V43__add_default_genetic_worth_val.sql @@ -0,0 +1,13 @@ +-- Step 1: Add the new column with decimal type +ALTER TABLE + spar.genetic_worth_list +ADD + COLUMN default_bv DECIMAL(3, 1) DEFAULT 0.0 NOT NULL; + +-- Step 2: Update the row where genetic_worth_code is GVO +UPDATE + spar.genetic_worth_list +SET + default_bv = 2.0 +WHERE + genetic_worth_code = 'GVO'; diff --git a/backend/src/main/resources/db/migration/V44__expire_f8_gametic_option.sql b/backend/src/main/resources/db/migration/V44__expire_f8_gametic_option.sql new file mode 100644 index 000000000..92d3b45e2 --- /dev/null +++ b/backend/src/main/resources/db/migration/V44__expire_f8_gametic_option.sql @@ -0,0 +1,7 @@ +UPDATE + spar.gametic_methodology_list +SET + expiry_date = '2024-08-09', + update_timestamp = '2024-08-09 17:00:00.000000' +WHERE + gametic_methodology_code = 'F8'; diff --git a/backend/src/main/resources/db/migration/V45__add_coancestry_seedlot.sql b/backend/src/main/resources/db/migration/V45__add_coancestry_seedlot.sql new file mode 100644 index 000000000..026e96db5 --- /dev/null +++ b/backend/src/main/resources/db/migration/V45__add_coancestry_seedlot.sql @@ -0,0 +1,244 @@ +/* Update Seedlot table */ +alter table spar.seedlot add column coancestry decimal(20, 10); + +comment on column spar.seedlot.coancestry is 'Used by the Tree Seed Centre to calculate Genetic Worth, Effective Population Size and Collection Geography'; + +/* Update Seedlot Audit table */ +alter table spar.seedlot_audit add column coancestry decimal(20, 10); + +comment on column spar.seedlot_audit.coancestry is 'Referring value for spar.seedlot.coancestry column'; + +/* Fix Audit Table Triggers */ +CREATE OR REPLACE FUNCTION spar.seedlot_if_modified_func() RETURNS trigger AS $body$ +DECLARE + v_old_data TEXT; + v_new_data TEXT; + v_auditrevision int; +BEGIN + if (TG_OP = 'UPDATE') then + v_old_data := ROW(OLD.*); + v_new_data := ROW(NEW.*); + /* AUDIT REVISION number used to order the statements executed in the row */ + v_auditrevision := (SELECT MAX(COALESCE(audit_revision_version,1))+1 FROM spar.seedlot_audit WHERE seedlot_number = NEW.seedlot_number); + INSERT INTO spar.seedlot_audit (spar_audit_code,db_user,audit_revision_version,seedlot_number,seedlot_status_code,applicant_client_number,applicant_locn_code,applicant_email_address,vegetation_code,genetic_class_code,seedlot_source_code,to_be_registrd_ind,bc_source_ind,collection_client_number,collection_locn_code,collection_start_date,collection_end_date,no_of_containers,vol_per_container,clctn_volume,seedlot_comment,interm_strg_client_number,interm_strg_locn_code,interm_strg_st_date,interm_strg_end_date,interm_facility_code,female_gametic_mthd_code,male_gametic_mthd_code,controlled_cross_ind,biotech_processes_ind,pollen_contamination_ind,pollen_contamination_pct,contaminant_pollen_bv,pollen_contamination_mthd_code,total_parent_trees,smp_success_pct,effective_pop_size,smp_parents_outside,non_orchard_pollen_contam_pct,extractory_client_number,extractory_locn_code,extraction_st_date,extraction_end_date,temporary_strg_client_number,temporary_strg_locn_code,temporary_strg_start_date,temporary_strg_end_date,interm_strg_locn,declared_userid,declared_timestamp,entry_userid,entry_timestamp,update_userid,update_timestamp,revision_count,bgc_zone_description,coancestry) + VALUES( + /*spar_audit_code */ 'U', + /*db_user */ session_user::TEXT, + /*audit_revision_version */ coalesce(v_auditrevision,1), + /*seedlot_number */ NEW.seedlot_number, + /*seedlot_status_code */ NEW.seedlot_status_code, + /*applicant_client_number */ NEW.applicant_client_number, + /*applicant_locn_code */ NEW.applicant_locn_code, + /*applicant_email_address */ NEW.applicant_email_address, + /*vegetation_code */ NEW.vegetation_code, + /*genetic_class_code */ NEW.genetic_class_code, + /*seedlot_source_code */ NEW.seedlot_source_code, + /*to_be_registrd_ind */ NEW.to_be_registrd_ind, + /*bc_source_ind */ NEW.bc_source_ind, + /*collection_client_number */ NEW.collection_client_number, + /*collection_locn_code */ NEW.collection_locn_code, + /*collection_start_date */ NEW.collection_start_date, + /*collection_end_date */ NEW.collection_end_date, + /*no_of_containers */ NEW.no_of_containers, + /*vol_per_container */ NEW.vol_per_container, + /*clctn_volume */ NEW.clctn_volume, + /*seedlot_comment */ NEW.seedlot_comment, + /*interm_strg_client_number */ NEW.interm_strg_client_number, + /*interm_strg_locn_code */ NEW.interm_strg_locn_code, + /*interm_strg_st_date */ NEW.interm_strg_st_date, + /*interm_strg_end_date */ NEW.interm_strg_end_date, + /*interm_facility_code */ NEW.interm_facility_code, + /*female_gametic_mthd_code */ NEW.female_gametic_mthd_code, + /*male_gametic_mthd_code */ NEW.male_gametic_mthd_code, + /*controlled_cross_ind */ NEW.controlled_cross_ind, + /*biotech_processes_ind */ NEW.biotech_processes_ind, + /*pollen_contamination_ind */ NEW.pollen_contamination_ind, + /*pollen_contamination_pct */ NEW.pollen_contamination_pct, + /*contaminant_pollen_bv */ NEW.contaminant_pollen_bv, + /*pollen_contamination_mthd_code */ NEW.pollen_contamination_mthd_code, + /*total_parent_trees */ NEW.total_parent_trees, + /*smp_success_pct */ NEW.smp_success_pct, + /*effective_pop_size */ NEW.effective_pop_size, + /*smp_parents_outside */ NEW.smp_parents_outside, + /*non_orchard_pollen_contam_pct */ NEW.non_orchard_pollen_contam_pct, + /*extractory_client_number */ NEW.extractory_client_number, + /*extractory_locn_code */ NEW.extractory_locn_code, + /*extraction_st_date */ NEW.extraction_st_date, + /*extraction_end_date */ NEW.extraction_end_date, + /*temporary_strg_client_number */ NEW.temporary_strg_client_number, + /*temporary_strg_locn_code */ NEW.temporary_strg_locn_code, + /*temporary_strg_start_date */ NEW.temporary_strg_start_date, + /*temporary_strg_end_date */ NEW.temporary_strg_end_date, + /*interm_strg_locn */ NEW.interm_strg_locn, + /*declared_userid */ NEW.declared_userid, + /*declared_timestamp */ NEW.declared_timestamp, + /*entry_userid */ NEW.entry_userid, + /*entry_timestamp */ NEW.entry_timestamp, + /*update_userid */ NEW.update_userid, + /*update_timestamp */ NEW.update_timestamp, + /*revision_count */ NEW.revision_count, + /*bgc_zone_description */ NEW.bgc_zone_description, + /*coancestry */ NEW.coancestry + ); + RETURN NEW; + elsif (TG_OP = 'DELETE') then + v_old_data := ROW(OLD.*); + /* insert into spar.logged_actions (schema_name,table_name,user_name,action,original_data,query) + values (TG_TABLE_SCHEMA::TEXT,TG_TABLE_NAME::TEXT,session_user::TEXT,substring(TG_OP,1,1),v_old_data, current_query());*/ + /* AUDIT REVISION number used to order the statements executed in the row */ + v_auditrevision := (SELECT MAX(COALESCE(audit_revision_version,1))+1 FROM spar.seedlot_audit WHERE seedlot_number = NEW.seedlot_number) ; + INSERT INTO spar.seedlot_audit (spar_audit_code,db_user,audit_revision_version,seedlot_number,seedlot_status_code,applicant_client_number,applicant_locn_code,applicant_email_address,vegetation_code,genetic_class_code,seedlot_source_code,to_be_registrd_ind,bc_source_ind,collection_client_number,collection_locn_code,collection_start_date,collection_end_date,no_of_containers,vol_per_container,clctn_volume,seedlot_comment,interm_strg_client_number,interm_strg_locn_code,interm_strg_st_date,interm_strg_end_date,interm_facility_code,female_gametic_mthd_code,male_gametic_mthd_code,controlled_cross_ind,biotech_processes_ind,pollen_contamination_ind,pollen_contamination_pct,contaminant_pollen_bv,pollen_contamination_mthd_code,total_parent_trees,smp_success_pct,effective_pop_size,smp_parents_outside,non_orchard_pollen_contam_pct,extractory_client_number,extractory_locn_code,extraction_st_date,extraction_end_date,temporary_strg_client_number,temporary_strg_locn_code,temporary_strg_start_date,temporary_strg_end_date,interm_strg_locn,declared_userid,declared_timestamp,entry_userid,entry_timestamp,update_userid,update_timestamp,revision_count,bgc_zone_description,coancestry) + VALUES( + /*spar_audit_code */ 'D', + /*db_user */ session_user::TEXT, + /*audit_revision_version */ coalesce(v_auditrevision,1), + /*seedlot_number */ OLD.seedlot_number, + /*seedlot_status_code */ OLD.seedlot_status_code, + /*applicant_client_number */ OLD.applicant_client_number, + /*applicant_locn_code */ OLD.applicant_locn_code, + /*applicant_email_address */ OLD.applicant_email_address, + /*vegetation_code */ OLD.vegetation_code, + /*genetic_class_code */ OLD.genetic_class_code, + /*seedlot_source_code */ OLD.seedlot_source_code, + /*to_be_registrd_ind */ OLD.to_be_registrd_ind, + /*bc_source_ind */ OLD.bc_source_ind, + /*collection_client_number */ OLD.collection_client_number, + /*collection_locn_code */ OLD.collection_locn_code, + /*collection_start_date */ OLD.collection_start_date, + /*collection_end_date */ OLD.collection_end_date, + /*no_of_containers */ OLD.no_of_containers, + /*vol_per_container */ OLD.vol_per_container, + /*clctn_volume */ OLD.clctn_volume, + /*seedlot_comment */ OLD.seedlot_comment, + /*interm_strg_client_number */ OLD.interm_strg_client_number, + /*interm_strg_locn_code */ OLD.interm_strg_locn_code, + /*interm_strg_st_date */ OLD.interm_strg_st_date, + /*interm_strg_end_date */ OLD.interm_strg_end_date, + /*interm_facility_code */ OLD.interm_facility_code, + /*female_gametic_mthd_code */ OLD.female_gametic_mthd_code, + /*male_gametic_mthd_code */ OLD.male_gametic_mthd_code, + /*controlled_cross_ind */ OLD.controlled_cross_ind, + /*biotech_processes_ind */ OLD.biotech_processes_ind, + /*pollen_contamination_ind */ OLD.pollen_contamination_ind, + /*pollen_contamination_pct */ OLD.pollen_contamination_pct, + /*contaminant_pollen_bv */ OLD.contaminant_pollen_bv, + /*pollen_contamination_mthd_code */ OLD.pollen_contamination_mthd_code, + /*total_parent_trees */ OLD.total_parent_trees, + /*smp_success_pct */ OLD.smp_success_pct, + /*effective_pop_size */ OLD.effective_pop_size, + /*smp_parents_outside */ OLD.smp_parents_outside, + /*non_orchard_pollen_contam_pct */ OLD.non_orchard_pollen_contam_pct, + /*extractory_client_number */ OLD.extractory_client_number, + /*extractory_locn_code */ OLD.extractory_locn_code, + /*extraction_st_date */ OLD.extraction_st_date, + /*extraction_end_date */ OLD.extraction_end_date, + /*temporary_strg_client_number */ OLD.temporary_strg_client_number, + /*temporary_strg_locn_code */ OLD.temporary_strg_locn_code, + /*temporary_strg_start_date */ OLD.temporary_strg_start_date, + /*temporary_strg_end_date */ OLD.temporary_strg_end_date, + /*interm_strg_locn */ OLD.interm_strg_locn, + /*declared_userid */ OLD.declared_userid, + /*declared_timestamp */ OLD.declared_timestamp, + /*entry_userid */ OLD.entry_userid, + /*entry_timestamp */ OLD.entry_timestamp, + /*update_userid */ OLD.update_userid, + /*update_timestamp */ OLD.update_timestamp, + /*revision_count */ OLD.revision_count, + /*bgc_zone_description */ OLD.bgc_zone_description, + /*coancestry */ OLD.coancestry + ); + RETURN OLD; + elsif (TG_OP = 'INSERT') then + v_new_data := ROW(NEW.*); + INSERT INTO spar.seedlot_audit (spar_audit_code,db_user,audit_revision_version,seedlot_number,seedlot_status_code,applicant_client_number,applicant_locn_code,applicant_email_address,vegetation_code,genetic_class_code,seedlot_source_code,to_be_registrd_ind,bc_source_ind,collection_client_number,collection_locn_code,collection_start_date,collection_end_date,no_of_containers,vol_per_container,clctn_volume,seedlot_comment,interm_strg_client_number,interm_strg_locn_code,interm_strg_st_date,interm_strg_end_date,interm_facility_code,female_gametic_mthd_code,male_gametic_mthd_code,controlled_cross_ind,biotech_processes_ind,pollen_contamination_ind,pollen_contamination_pct,contaminant_pollen_bv,pollen_contamination_mthd_code,total_parent_trees,smp_success_pct,effective_pop_size,smp_parents_outside,non_orchard_pollen_contam_pct,extractory_client_number,extractory_locn_code,extraction_st_date,extraction_end_date,temporary_strg_client_number,temporary_strg_locn_code,temporary_strg_start_date,temporary_strg_end_date,interm_strg_locn,declared_userid,declared_timestamp,entry_userid,entry_timestamp,update_userid,update_timestamp,revision_count,bgc_zone_description,coancestry) + VALUES( + /*spar_audit_code */ 'I', + /*db_user */ session_user::TEXT, + /*audit_revision_version */ 1, -- 1st row version + /*seedlot_number */ NEW.seedlot_number, + /*seedlot_status_code */ NEW.seedlot_status_code, + /*applicant_client_number */ NEW.applicant_client_number, + /*applicant_locn_code */ NEW.applicant_locn_code, + /*applicant_email_address */ NEW.applicant_email_address, + /*vegetation_code */ NEW.vegetation_code, + /*genetic_class_code */ NEW.genetic_class_code, + /*seedlot_source_code */ NEW.seedlot_source_code, + /*to_be_registrd_ind */ NEW.to_be_registrd_ind, + /*bc_source_ind */ NEW.bc_source_ind, + /*collection_client_number */ NEW.collection_client_number, + /*collection_locn_code */ NEW.collection_locn_code, + /*collection_start_date */ NEW.collection_start_date, + /*collection_end_date */ NEW.collection_end_date, + /*no_of_containers */ NEW.no_of_containers, + /*vol_per_container */ NEW.vol_per_container, + /*clctn_volume */ NEW.clctn_volume, + /*seedlot_comment */ NEW.seedlot_comment, + /*interm_strg_client_number */ NEW.interm_strg_client_number, + /*interm_strg_locn_code */ NEW.interm_strg_locn_code, + /*interm_strg_st_date */ NEW.interm_strg_st_date, + /*interm_strg_end_date */ NEW.interm_strg_end_date, + /*interm_facility_code */ NEW.interm_facility_code, + /*female_gametic_mthd_code */ NEW.female_gametic_mthd_code, + /*male_gametic_mthd_code */ NEW.male_gametic_mthd_code, + /*controlled_cross_ind */ NEW.controlled_cross_ind, + /*biotech_processes_ind */ NEW.biotech_processes_ind, + /*pollen_contamination_ind */ NEW.pollen_contamination_ind, + /*pollen_contamination_pct */ NEW.pollen_contamination_pct, + /*contaminant_pollen_bv */ NEW.contaminant_pollen_bv, + /*pollen_contamination_mthd_code */ NEW.pollen_contamination_mthd_code, + /*total_parent_trees */ NEW.total_parent_trees, + /*smp_success_pct */ NEW.smp_success_pct, + /*effective_pop_size */ NEW.effective_pop_size, + /*smp_parents_outside */ NEW.smp_parents_outside, + /*non_orchard_pollen_contam_pct */ NEW.non_orchard_pollen_contam_pct, + /*extractory_client_number */ NEW.extractory_client_number, + /*extractory_locn_code */ NEW.extractory_locn_code, + /*extraction_st_date */ NEW.extraction_st_date, + /*extraction_end_date */ NEW.extraction_end_date, + /*temporary_strg_client_number */ NEW.temporary_strg_client_number, + /*temporary_strg_locn_code */ NEW.temporary_strg_locn_code, + /*temporary_strg_start_date */ NEW.temporary_strg_start_date, + /*temporary_strg_end_date */ NEW.temporary_strg_end_date, + /*interm_strg_locn */ NEW.interm_strg_locn, + /*declared_userid */ NEW.declared_userid, + /*declared_timestamp */ NEW.declared_timestamp, + /*entry_userid */ NEW.entry_userid, + /*entry_timestamp */ NEW.entry_timestamp, + /*update_userid */ NEW.update_userid, + /*update_timestamp */ NEW.update_timestamp, + /*revision_count */ NEW.revision_count, + /*bgc_zone_description */ NEW.bgc_zone_description, + /*coancestry */ NEW.coancestry + ); + RETURN NEW; + else + RAISE WARNING '[AUDIT.IF_MODIFIED_FUNC] - Other action occurred: %, at %',TG_OP,now(); + RETURN NULL; + end if; + +EXCEPTION + WHEN data_exception then + --insert into spar.error_catch (erro) VALUES(CONCAT('DATA EXCEPTION ',SQLERRM)); + RAISE WARNING '[AUDIT.IF_MODIFIED_FUNC] - UDF ERROR [DATA EXCEPTION] - SQLSTATE: %, SQLERRM: %',SQLSTATE,SQLERRM; + RETURN NULL; + WHEN unique_violation then + --insert into spar.error_catch (erro) VALUES(CONCAT('UNIQUE EXCEPTION ',SQLERRM)); + RAISE WARNING '[AUDIT.IF_MODIFIED_FUNC] - UDF ERROR [UNIQUE] - SQLSTATE: %, SQLERRM: %',SQLSTATE,SQLERRM; + RETURN NULL; + WHEN others then + --insert into spar.error_catch (erro) VALUES(CONCAT(v_auditrevision, CONCAT('OTHER EXCEPTION ',SQLERRM))); + RAISE WARNING '[AUDIT.IF_MODIFIED_FUNC] - UDF ERROR [OTHER] - SQLSTATE: %, SQLERRM: %',SQLSTATE,SQLERRM; + RETURN NULL; +END; +$body$ +LANGUAGE plpgsql +SECURITY DEFINER +SET search_path = pg_catalog, spar; + +-- Drop existing trigger +DROP TRIGGER trg_seedlot_audit_DIU ON spar.seedlot; + +-- Trigger to be attached on spar.seedlot table +CREATE TRIGGER trg_seedlot_audit_DIU + AFTER INSERT OR UPDATE OR DELETE ON spar.seedlot + FOR EACH ROW EXECUTE PROCEDURE spar.seedlot_if_modified_func(); diff --git a/backend/src/test/java/ca/bc/gov/backendstartapi/endpoint/GeneticWorthEndpointTest.java b/backend/src/test/java/ca/bc/gov/backendstartapi/endpoint/GeneticWorthEndpointTest.java index 64951030b..2004ce343 100644 --- a/backend/src/test/java/ca/bc/gov/backendstartapi/endpoint/GeneticWorthEndpointTest.java +++ b/backend/src/test/java/ca/bc/gov/backendstartapi/endpoint/GeneticWorthEndpointTest.java @@ -8,8 +8,10 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; import ca.bc.gov.backendstartapi.dto.CodeDescriptionDto; +import ca.bc.gov.backendstartapi.dto.GeneticWorthDto; import ca.bc.gov.backendstartapi.exception.NoGeneticWorthException; import ca.bc.gov.backendstartapi.service.GeneticWorthService; +import java.math.BigDecimal; import java.util.List; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; @@ -38,11 +40,13 @@ class GeneticWorthEndpointTest { @DisplayName("getAllGeneticWorthTest") void getAllGeneticWorthTest() throws Exception { - CodeDescriptionDto firstMethod = - new CodeDescriptionDto("AD", "Animal browse resistance (deer)"); - CodeDescriptionDto secondMethod = - new CodeDescriptionDto( - "DFS", "Disease resistance for Dothistroma needle blight (Dothistroma septosporum)"); + GeneticWorthDto firstMethod = + new GeneticWorthDto("AD", "Animal browse resistance (deer)", BigDecimal.ZERO); + GeneticWorthDto secondMethod = + new GeneticWorthDto( + "DFS", + "Disease resistance for Dothistroma needle blight (Dothistroma septosporum)", + BigDecimal.ZERO); when(geneticWorthService.getAllGeneticWorth()).thenReturn(List.of(firstMethod, secondMethod)); diff --git a/backend/src/test/java/ca/bc/gov/backendstartapi/endpoint/OrchardEndpointTest.java b/backend/src/test/java/ca/bc/gov/backendstartapi/endpoint/OrchardEndpointTest.java index b51c8057c..cb959c42e 100644 --- a/backend/src/test/java/ca/bc/gov/backendstartapi/endpoint/OrchardEndpointTest.java +++ b/backend/src/test/java/ca/bc/gov/backendstartapi/endpoint/OrchardEndpointTest.java @@ -7,7 +7,6 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; import ca.bc.gov.backendstartapi.dto.OrchardSpuDto; -import ca.bc.gov.backendstartapi.dto.SameSpeciesTreeDto; import ca.bc.gov.backendstartapi.exception.NoParentTreeDataException; import ca.bc.gov.backendstartapi.exception.NoSpuForOrchardException; import ca.bc.gov.backendstartapi.service.OrchardService; @@ -17,11 +16,9 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; import org.springframework.boot.test.mock.mockito.MockBean; -import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; import org.springframework.security.test.context.support.WithMockUser; import org.springframework.test.web.servlet.MockMvc; -import org.springframework.web.server.ResponseStatusException; @WebMvcTest(OrchardEndpoint.class) @WithMockUser(username = "SPARTest", roles = "SPAR_NONMINISTRY_ORCHARD") @@ -91,52 +88,53 @@ void getParentTreeGeneticQualityDataNoSpuTest() throws Exception { .andReturn(); } - @Test - @DisplayName("getAllParentTreeByVegCodeTest") - void getAllParentTreeByVegCodeTest() throws Exception { - String vegCode = "PLI"; - - SameSpeciesTreeDto firstDto = - new SameSpeciesTreeDto(Long.valueOf(123), "1000", "1", Long.valueOf(7), List.of()); - SameSpeciesTreeDto secondDto = - new SameSpeciesTreeDto(Long.valueOf(456), "2000", "1", Long.valueOf(7), List.of()); - - List testList = List.of(firstDto, secondDto); - - when(orchardService.findParentTreesByVegCode(vegCode)).thenReturn(testList); - - mockMvc - .perform( - get("/api/orchards/parent-trees/vegetation-codes/{vegCode}", vegCode) - .with(csrf().asHeader()) - .header("Content-Type", "application/json") - .accept(MediaType.APPLICATION_JSON)) - .andExpect(status().isOk()) - .andExpect(jsonPath("$[0].parentTreeId").value(firstDto.getParentTreeId())) - .andExpect(jsonPath("$[0].parentTreeNumber").value(firstDto.getParentTreeNumber())) - .andExpect(jsonPath("$[1].parentTreeId").value(secondDto.getParentTreeId())) - .andExpect(jsonPath("$[1].parentTreeNumber").value(secondDto.getParentTreeNumber())) - .andReturn(); - } - - @Test - @DisplayName("getAllParentTreeByVegCodeErrorTest") - void getAllParentTreeByVegCodeErrorTest() throws Exception { - String vegCode = "LAMB"; - - HttpStatus errCode = HttpStatus.BAD_REQUEST; - String errMsg = "LAMB is not a veg code."; - - when(orchardService.findParentTreesByVegCode(vegCode)) - .thenThrow(new ResponseStatusException(errCode, errMsg)); - mockMvc - .perform( - get("/api/orchards/parent-trees/vegetation-codes/{vegCode}", vegCode) - .with(csrf().asHeader()) - .header("Content-Type", "application/json") - .accept(MediaType.APPLICATION_JSON)) - .andExpect(status().isBadRequest()) - .andExpect(status().reason(errMsg)) - .andReturn(); - } + // TODO + // @Test + // @DisplayName("getAllParentTreeByVegCodeTest") + // void getAllParentTreeByVegCodeTest() throws Exception { + // String vegCode = "PLI"; + + // SameSpeciesTreeDto firstDto = + // new SameSpeciesTreeDto(Long.valueOf(123), "1000", "1", Long.valueOf(7), List.of()); + // SameSpeciesTreeDto secondDto = + // new SameSpeciesTreeDto(Long.valueOf(456), "2000", "1", Long.valueOf(7), List.of()); + + // List testList = List.of(firstDto, secondDto); + + // when(orchardService.findParentTreesByVegCode(vegCode)).thenReturn(testList); + + // mockMvc + // .perform( + // get("/api/orchards/parent-trees/vegetation-codes/{vegCode}", vegCode) + // .with(csrf().asHeader()) + // .header("Content-Type", "application/json") + // .accept(MediaType.APPLICATION_JSON)) + // .andExpect(status().isOk()) + // .andExpect(jsonPath("$[0].parentTreeId").value(firstDto.getParentTreeId())) + // .andExpect(jsonPath("$[0].parentTreeNumber").value(firstDto.getParentTreeNumber())) + // .andExpect(jsonPath("$[1].parentTreeId").value(secondDto.getParentTreeId())) + // .andExpect(jsonPath("$[1].parentTreeNumber").value(secondDto.getParentTreeNumber())) + // .andReturn(); + // } + + // @Test + // @DisplayName("getAllParentTreeByVegCodeErrorTest") + // void getAllParentTreeByVegCodeErrorTest() throws Exception { + // String vegCode = "LAMB"; + + // HttpStatus errCode = HttpStatus.BAD_REQUEST; + // String errMsg = "LAMB is not a veg code."; + + // when(orchardService.findParentTreesByVegCode(vegCode)) + // .thenThrow(new ResponseStatusException(errCode, errMsg)); + // mockMvc + // .perform( + // get("/api/orchards/parent-trees/vegetation-codes/{vegCode}", vegCode) + // .with(csrf().asHeader()) + // .header("Content-Type", "application/json") + // .accept(MediaType.APPLICATION_JSON)) + // .andExpect(status().isBadRequest()) + // .andExpect(status().reason(errMsg)) + // .andReturn(); + // } } diff --git a/backend/src/test/java/ca/bc/gov/backendstartapi/endpoint/ParentTreeEndpointTest.java b/backend/src/test/java/ca/bc/gov/backendstartapi/endpoint/ParentTreeEndpointTest.java index 7115afea8..d9273527b 100644 --- a/backend/src/test/java/ca/bc/gov/backendstartapi/endpoint/ParentTreeEndpointTest.java +++ b/backend/src/test/java/ca/bc/gov/backendstartapi/endpoint/ParentTreeEndpointTest.java @@ -113,7 +113,8 @@ void calcMeanGeospatialSuccessTest() throws Exception { "parentTreeId": 22, "proportion": 90 } - ] + ], + "smpParentsOutside": 0 } """; diff --git a/backend/src/test/java/ca/bc/gov/backendstartapi/provider/OracleApiProviderTest.java b/backend/src/test/java/ca/bc/gov/backendstartapi/provider/OracleApiProviderTest.java index 000110df5..1d0d628ae 100644 --- a/backend/src/test/java/ca/bc/gov/backendstartapi/provider/OracleApiProviderTest.java +++ b/backend/src/test/java/ca/bc/gov/backendstartapi/provider/OracleApiProviderTest.java @@ -6,14 +6,9 @@ import static org.springframework.test.web.client.response.MockRestResponseCreators.withSuccess; import ca.bc.gov.backendstartapi.config.ProvidersConfig; -import ca.bc.gov.backendstartapi.dto.OrchardDto; import ca.bc.gov.backendstartapi.dto.OrchardSpuDto; -import ca.bc.gov.backendstartapi.dto.SameSpeciesTreeDto; import ca.bc.gov.backendstartapi.dto.oracle.SpuDto; import ca.bc.gov.backendstartapi.security.LoggedUserService; -import java.util.HashMap; -import java.util.List; -import java.util.Map; import java.util.Optional; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.DisplayName; @@ -24,7 +19,6 @@ import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; import org.springframework.test.web.client.MockRestServiceServer; -import org.springframework.web.server.ResponseStatusException; @RestClientTest(OracleApiProvider.class) class OracleApiProviderTest { @@ -83,104 +77,107 @@ void findOrchardParentTreeGeneticQualityDataErrorProviderTest() { Assertions.assertFalse(orchardSpuDto.isPresent()); } - @Test - @DisplayName("findParentTreesByVegCodeTest") - void findParentTreesByVegCodeTest() { - when(loggedUserService.getLoggedUserToken()).thenReturn("1f7a4k5e8t9o5k6e9n8h5e2r6e"); - - String vegCode = "FDC"; - String url = "/null/api/orchards/parent-trees/vegetation-codes/" + vegCode; - - String json = - """ - [ - { - "parentTreeId": 1003477, - "parentTreeNumber": "34", - "orchardId": "1", - "spu": 0, - "parentTreeGeneticQualities": [ - { - "geneticTypeCode": "BV", - "geneticWorthCode": "GVO", - "geneticQualityValue": 18 - } - ] - } - ] - """; - - mockRestServiceServer - .expect(requestTo(url)) - .andRespond(withSuccess(json, MediaType.APPLICATION_JSON)); - - Map testMap = new HashMap<>(); - - List parentTreeDtoList = - oracleApiProvider.findParentTreesByVegCode(vegCode, testMap); - - Assertions.assertFalse(parentTreeDtoList.isEmpty()); - Assertions.assertEquals("1003477", parentTreeDtoList.get(0).getParentTreeId().toString()); - Assertions.assertEquals("34", parentTreeDtoList.get(0).getParentTreeNumber()); - } - - @Test - @DisplayName("findParentTreesByVegCodeErrorTest") - void findParentTreesByVegCodeErrorTest() throws Exception { - when(loggedUserService.getLoggedUserToken()).thenReturn(""); - - String vegCode = "LAMB"; - String url = "/null/api/orchards/parent-trees/vegetation-codes/" + vegCode; - - mockRestServiceServer.expect(requestTo(url)).andRespond(withStatus(HttpStatus.BAD_REQUEST)); - - Map testMap = new HashMap<>(); - - ResponseStatusException httpExc = - Assertions.assertThrows( - ResponseStatusException.class, - () -> { - oracleApiProvider.findParentTreesByVegCode(vegCode, testMap); - }); - - Assertions.assertEquals(HttpStatus.BAD_REQUEST, httpExc.getStatusCode()); - } - - @Test - @DisplayName("Find Orchard with ID success test.") - void findOrchardById_shouldSucceed() { - when(loggedUserService.getLoggedUserToken()).thenReturn("1f7a4k5e8t9o5k6e9n8h5e2r6e"); - - String orchardId = "339"; - String url = "/null/api/orchards/" + orchardId; - - String json = - """ - { - "id": "339", - "name": "EAGLEROCK", - "vegetationCode": "PLI", - "lotTypeCode": "S", - "lotTypeDescription": "Seed Lot", - "stageCode": "PRD", - "becZoneCode": "CWH", - "becZoneDescription": "Coastal Western Hemlock", - "becSubzoneCode": "dm", - "variant": null, - "becVersionId": 5 - } - """; - - mockRestServiceServer - .expect(requestTo(url)) - .andRespond(withSuccess(json, MediaType.APPLICATION_JSON)); - - Optional orchardDtoOpt = oracleApiProvider.findOrchardById(orchardId); - - Assertions.assertFalse(orchardDtoOpt.isEmpty()); - Assertions.assertEquals(orchardId, orchardDtoOpt.get().id()); - Assertions.assertEquals("Coastal Western Hemlock", orchardDtoOpt.get().becZoneDescription()); - } + // TODO + // @Test + // @DisplayName("findParentTreesByVegCodeTest") + // void findParentTreesByVegCodeTest() { + // when(loggedUserService.getLoggedUserToken()).thenReturn("1f7a4k5e8t9o5k6e9n8h5e2r6e"); + + // String vegCode = "FDC"; + // String url = "/null/api/orchards/parent-trees/vegetation-codes/" + vegCode; + + // String json = + // """ + // [ + // { + // "parentTreeId": 1003477, + // "parentTreeNumber": "34", + // "orchardId": "1", + // "spu": 0, + // "parentTreeGeneticQualities": [ + // { + // "geneticTypeCode": "BV", + // "geneticWorthCode": "GVO", + // "geneticQualityValue": 18 + // } + // ] + // } + // ] + // """; + + // mockRestServiceServer + // .expect(requestTo(url)) + // .andRespond(withSuccess(json, MediaType.APPLICATION_JSON)); + + // Map testMap = new HashMap<>(); + + // List parentTreeDtoList = + // oracleApiProvider.findParentTreesByVegCode(vegCode, testMap); + + // Assertions.assertFalse(parentTreeDtoList.isEmpty()); + // Assertions.assertEquals("1003477", parentTreeDtoList.get(0).getParentTreeId().toString()); + // Assertions.assertEquals("34", parentTreeDtoList.get(0).getParentTreeNumber()); + // } + + // TODO + // @Test + // @DisplayName("findParentTreesByVegCodeErrorTest") + // void findParentTreesByVegCodeErrorTest() throws Exception { + // when(loggedUserService.getLoggedUserToken()).thenReturn(""); + + // String vegCode = "LAMB"; + // String url = "/null/api/orchards/parent-trees/vegetation-codes/" + vegCode; + + // mockRestServiceServer.expect(requestTo(url)).andRespond(withStatus(HttpStatus.BAD_REQUEST)); + + // Map testMap = new HashMap<>(); + + // ResponseStatusException httpExc = + // Assertions.assertThrows( + // ResponseStatusException.class, + // () -> { + // oracleApiProvider.findParentTreesByVegCode(vegCode, testMap); + // }); + + // Assertions.assertEquals(HttpStatus.BAD_REQUEST, httpExc.getStatusCode()); + // } + + // TODO + // @Test + // @DisplayName("Find Orchard with ID success test.") + // void findOrchardById_shouldSucceed() { + // when(loggedUserService.getLoggedUserToken()).thenReturn("1f7a4k5e8t9o5k6e9n8h5e2r6e"); + + // String orchardId = "339"; + // String url = "/null/api/orchards/" + orchardId; + + // String json = + // """ + // { + // "id": "339", + // "name": "EAGLEROCK", + // "vegetationCode": "PLI", + // "lotTypeCode": "S", + // "lotTypeDescription": "Seed Lot", + // "stageCode": "PRD", + // "becZoneCode": "CWH", + // "becZoneDescription": "Coastal Western Hemlock", + // "becSubzoneCode": "dm", + // "variant": null, + // "becVersionId": 5 + // } + // """; + + // mockRestServiceServer + // .expect(requestTo(url)) + // .andRespond(withSuccess(json, MediaType.APPLICATION_JSON)); + + // Optional orchardDtoOpt = oracleApiProvider.findOrchardById(orchardId); + + // Assertions.assertFalse(orchardDtoOpt.isEmpty()); + // Assertions.assertEquals(orchardId, orchardDtoOpt.get().id()); + // Assertions.assertEquals("Coastal Western Hemlock", orchardDtoOpt.get().becZoneDescription()); + // } @Test @DisplayName("Get SPU with ID success test") diff --git a/backend/src/test/java/ca/bc/gov/backendstartapi/repository/seedlot/SeedlotCollectionMethodRelationalTest.java b/backend/src/test/java/ca/bc/gov/backendstartapi/repository/seedlot/SeedlotCollectionMethodRelationalTest.java index afb5f96f8..faa0eb924 100644 --- a/backend/src/test/java/ca/bc/gov/backendstartapi/repository/seedlot/SeedlotCollectionMethodRelationalTest.java +++ b/backend/src/test/java/ca/bc/gov/backendstartapi/repository/seedlot/SeedlotCollectionMethodRelationalTest.java @@ -14,6 +14,7 @@ import ca.bc.gov.backendstartapi.repository.SeedlotCollectionMethodRepository; import ca.bc.gov.backendstartapi.repository.SeedlotRepository; import ca.bc.gov.backendstartapi.repository.SeedlotSourceRepository; +import ca.bc.gov.backendstartapi.repository.SeedlotStatusRepository; import java.time.LocalDate; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; @@ -34,9 +35,14 @@ protected SeedlotCollectionMethodRelationalTest( SeedlotCollectionMethodRepository seedlotCollectionMethodRepo, ConeCollectionMethodRepository coneCollectionMethodRepo, GeneticWorthRepository geneticWorthRepository, - SeedlotSourceRepository seedlotSourceRepository) { + SeedlotSourceRepository seedlotSourceRepository, + SeedlotStatusRepository seedlotStatusRepository) { super( - seedlotRepository, geneticClassRepository, geneticWorthRepository, seedlotSourceRepository); + seedlotRepository, + geneticClassRepository, + geneticWorthRepository, + seedlotSourceRepository, + seedlotStatusRepository); seedlotCollectionMethodTestRepo = seedlotCollectionMethodRepo; coneCollectionMethodTestRepo = coneCollectionMethodRepo; } diff --git a/backend/src/test/java/ca/bc/gov/backendstartapi/repository/seedlot/SeedlotEntityRelationalTest.java b/backend/src/test/java/ca/bc/gov/backendstartapi/repository/seedlot/SeedlotEntityRelationalTest.java index 2a5186555..b78aadca7 100644 --- a/backend/src/test/java/ca/bc/gov/backendstartapi/repository/seedlot/SeedlotEntityRelationalTest.java +++ b/backend/src/test/java/ca/bc/gov/backendstartapi/repository/seedlot/SeedlotEntityRelationalTest.java @@ -3,6 +3,7 @@ import ca.bc.gov.backendstartapi.entity.GeneticClassEntity; import ca.bc.gov.backendstartapi.entity.GeneticWorthEntity; import ca.bc.gov.backendstartapi.entity.SeedlotSourceEntity; +import ca.bc.gov.backendstartapi.entity.SeedlotStatusEntity; import ca.bc.gov.backendstartapi.entity.embeddable.AuditInformation; import ca.bc.gov.backendstartapi.entity.embeddable.EffectiveDateRange; import ca.bc.gov.backendstartapi.entity.seedlot.Seedlot; @@ -10,7 +11,9 @@ import ca.bc.gov.backendstartapi.repository.GeneticWorthRepository; import ca.bc.gov.backendstartapi.repository.SeedlotRepository; import ca.bc.gov.backendstartapi.repository.SeedlotSourceRepository; +import ca.bc.gov.backendstartapi.repository.SeedlotStatusRepository; import java.math.BigDecimal; +import java.time.Clock; import java.time.LocalDate; import java.time.LocalDateTime; @@ -20,16 +23,19 @@ abstract class SeedlotEntityRelationalTest { protected GeneticClassRepository geneticClassRepository; protected GeneticWorthRepository geneticWorthRepository; protected SeedlotSourceRepository seedlotSourceRepository; + protected SeedlotStatusRepository seedlotStatusRepository; protected SeedlotEntityRelationalTest( SeedlotRepository seedlotRepository, GeneticClassRepository geneticClassRepository, GeneticWorthRepository geneticWorthRepository, - SeedlotSourceRepository seedlotSourceRepository) { + SeedlotSourceRepository seedlotSourceRepository, + SeedlotStatusRepository seedlotStatusRepository) { this.seedlotRepository = seedlotRepository; this.geneticClassRepository = geneticClassRepository; this.geneticWorthRepository = geneticWorthRepository; this.seedlotSourceRepository = seedlotSourceRepository; + this.seedlotStatusRepository = seedlotStatusRepository; } protected Seedlot createSeedlot(String id) { @@ -42,18 +48,25 @@ protected Seedlot createSeedlot(String id) { geneticClassRepository.saveAndFlush(geneticClass); var geneticWorth = - new GeneticWorthEntity("AD", "Animal browse resistance (deer)", effectiveDateRange); + new GeneticWorthEntity( + "AD", "Animal browse resistance (deer)", effectiveDateRange, BigDecimal.ZERO); geneticWorthRepository.saveAndFlush(geneticWorth); var seedlotSource = new SeedlotSourceEntity("CUS", "Custom Lot", effectiveDateRange, null); seedlotSourceRepository.saveAndFlush(seedlotSource); + EffectiveDateRange range = + new EffectiveDateRange(LocalDate.now().minusYears(3L), LocalDate.now().plusYears(15L)); + var seedlotStatus = new SeedlotStatusEntity("PND", "Pending", range); + seedlotStatusRepository.saveAndFlush(seedlotStatus); + var seedlot = new Seedlot(id); seedlot.setComment("A seedlot."); seedlot.setApplicantClientNumber("00000001"); seedlot.setApplicantLocationCode("02"); seedlot.setApplicantEmailAddress("applicant@email.com"); + seedlot.setSeedlotStatus(seedlotStatus); seedlot.setVegetationCode("VEG"); seedlot.setGeneticClass(geneticClass); @@ -63,16 +76,16 @@ protected Seedlot createSeedlot(String id) { seedlot.setCollectionClientNumber("00000003"); seedlot.setCollectionLocationCode("04"); - seedlot.setCollectionStartDate(LocalDateTime.now()); - seedlot.setCollectionEndDate(LocalDateTime.now()); + seedlot.setCollectionStartDate(LocalDate.now(Clock.systemUTC())); + seedlot.setCollectionEndDate(LocalDate.now(Clock.systemUTC())); seedlot.setNumberOfContainers(new BigDecimal(10)); seedlot.setContainerVolume(new BigDecimal(20)); seedlot.setTotalConeVolume(new BigDecimal(200)); seedlot.setInterimStorageClientNumber("00000005"); seedlot.setInterimStorageLocationCode("06"); - seedlot.setInterimStorageStartDate(LocalDateTime.now()); - seedlot.setInterimStorageEndDate(LocalDateTime.now()); + seedlot.setInterimStorageStartDate(LocalDate.now(Clock.systemUTC())); + seedlot.setInterimStorageEndDate(LocalDate.now(Clock.systemUTC())); seedlot.setInterimStorageFacilityCode("007"); seedlot.setFemaleGameticContributionMethod("F"); @@ -92,15 +105,15 @@ protected Seedlot createSeedlot(String id) { seedlot.setExtractionClientNumber("00000009"); seedlot.setExtractionLocationCode("10"); - seedlot.setExtractionStartDate(LocalDateTime.now()); - seedlot.setExtractionEndDate(LocalDateTime.now()); + seedlot.setExtractionStartDate(LocalDate.now(Clock.systemUTC())); + seedlot.setExtractionEndDate(LocalDate.now(Clock.systemUTC())); seedlot.setStorageClientNumber("00000011"); seedlot.setStorageLocationCode("12"); - seedlot.setTemporaryStorageStartDate(LocalDateTime.now()); - seedlot.setTemporaryStorageEndDate(LocalDateTime.now()); + seedlot.setTemporaryStorageStartDate(LocalDate.now(Clock.systemUTC())); + seedlot.setTemporaryStorageEndDate(LocalDate.now(Clock.systemUTC())); seedlot.setDeclarationOfTrueInformationUserId("user1"); - seedlot.setDeclarationOfTrueInformationTimestamp(LocalDateTime.now()); + seedlot.setDeclarationOfTrueInformationTimestamp(LocalDateTime.now(Clock.systemUTC())); seedlot.setAuditInformation(new AuditInformation("user1")); return seedlotRepository.saveAndFlush(seedlot); diff --git a/backend/src/test/java/ca/bc/gov/backendstartapi/repository/seedlot/SeedlotGeneticWorthRelationalTest.java b/backend/src/test/java/ca/bc/gov/backendstartapi/repository/seedlot/SeedlotGeneticWorthRelationalTest.java index 649311227..6702ae5e2 100644 --- a/backend/src/test/java/ca/bc/gov/backendstartapi/repository/seedlot/SeedlotGeneticWorthRelationalTest.java +++ b/backend/src/test/java/ca/bc/gov/backendstartapi/repository/seedlot/SeedlotGeneticWorthRelationalTest.java @@ -10,6 +10,7 @@ import ca.bc.gov.backendstartapi.repository.SeedlotGeneticWorthRepository; import ca.bc.gov.backendstartapi.repository.SeedlotRepository; import ca.bc.gov.backendstartapi.repository.SeedlotSourceRepository; +import ca.bc.gov.backendstartapi.repository.SeedlotStatusRepository; import java.math.BigDecimal; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; @@ -28,9 +29,14 @@ class SeedlotGeneticWorthRelationalTest extends SeedlotEntityRelationalTest { GeneticClassRepository geneticClassRepository, GeneticWorthRepository geneticWorthRepository, SeedlotGeneticWorthRepository seedlotGeneticWorthRepository, - SeedlotSourceRepository seedlotSourceRepository) { + SeedlotSourceRepository seedlotSourceRepository, + SeedlotStatusRepository seedlotStatusRepository) { super( - seedlotRepository, geneticClassRepository, geneticWorthRepository, seedlotSourceRepository); + seedlotRepository, + geneticClassRepository, + geneticWorthRepository, + seedlotSourceRepository, + seedlotStatusRepository); this.seedlotGeneticWorthRepository = seedlotGeneticWorthRepository; } diff --git a/backend/src/test/java/ca/bc/gov/backendstartapi/repository/seedlot/SeedlotOrchardRelationalTest.java b/backend/src/test/java/ca/bc/gov/backendstartapi/repository/seedlot/SeedlotOrchardRelationalTest.java index 418aa88d9..175c7d664 100644 --- a/backend/src/test/java/ca/bc/gov/backendstartapi/repository/seedlot/SeedlotOrchardRelationalTest.java +++ b/backend/src/test/java/ca/bc/gov/backendstartapi/repository/seedlot/SeedlotOrchardRelationalTest.java @@ -10,6 +10,7 @@ import ca.bc.gov.backendstartapi.repository.SeedlotOrchardRepository; import ca.bc.gov.backendstartapi.repository.SeedlotRepository; import ca.bc.gov.backendstartapi.repository.SeedlotSourceRepository; +import ca.bc.gov.backendstartapi.repository.SeedlotStatusRepository; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest; @@ -27,9 +28,14 @@ protected SeedlotOrchardRelationalTest( GeneticClassRepository geneticClassRepository, SeedlotOrchardRepository seedlotOrchardRepository, GeneticWorthRepository geneticWorthRepository, - SeedlotSourceRepository seedlotSourceRepository) { + SeedlotSourceRepository seedlotSourceRepository, + SeedlotStatusRepository seedlotStatusRepository) { super( - seedlotRepository, geneticClassRepository, geneticWorthRepository, seedlotSourceRepository); + seedlotRepository, + geneticClassRepository, + geneticWorthRepository, + seedlotSourceRepository, + seedlotStatusRepository); repository = seedlotOrchardRepository; } diff --git a/backend/src/test/java/ca/bc/gov/backendstartapi/repository/seedlot/SeedlotOwnerQuantityRelationalTest.java b/backend/src/test/java/ca/bc/gov/backendstartapi/repository/seedlot/SeedlotOwnerQuantityRelationalTest.java index e7497cdd9..43854dc8f 100644 --- a/backend/src/test/java/ca/bc/gov/backendstartapi/repository/seedlot/SeedlotOwnerQuantityRelationalTest.java +++ b/backend/src/test/java/ca/bc/gov/backendstartapi/repository/seedlot/SeedlotOwnerQuantityRelationalTest.java @@ -13,6 +13,7 @@ import ca.bc.gov.backendstartapi.repository.SeedlotOwnerQuantityRepository; import ca.bc.gov.backendstartapi.repository.SeedlotRepository; import ca.bc.gov.backendstartapi.repository.SeedlotSourceRepository; +import ca.bc.gov.backendstartapi.repository.SeedlotStatusRepository; import java.math.BigDecimal; import java.time.LocalDate; import org.junit.jupiter.api.Test; @@ -34,9 +35,14 @@ class SeedlotOwnerQuantityRelationalTest extends SeedlotEntityRelationalTest { SeedlotOwnerQuantityRepository seedlotOwnerQuantityRepository, GeneticWorthRepository geneticWorthRepository, MethodOfPaymentRepository methodOfPaymentRepository, - SeedlotSourceRepository seedlotSourceRepository) { + SeedlotSourceRepository seedlotSourceRepository, + SeedlotStatusRepository seedlotStatusRepository) { super( - seedlotRepository, geneticClassRepository, geneticWorthRepository, seedlotSourceRepository); + seedlotRepository, + geneticClassRepository, + geneticWorthRepository, + seedlotSourceRepository, + seedlotStatusRepository); repository = seedlotOwnerQuantityRepository; this.methodOfPaymentRepository = methodOfPaymentRepository; } @@ -44,7 +50,6 @@ class SeedlotOwnerQuantityRelationalTest extends SeedlotEntityRelationalTest { @Test void create() { var seedlot = createSeedlot("00000"); - var seedlotOwnerQuantity = new SeedlotOwnerQuantity(seedlot, "00020", "21"); LocalDate now = LocalDate.now(); var effectiveDate = now.minusDays(2); @@ -53,6 +58,7 @@ void create() { var methodOfPayment = new MethodOfPaymentEntity("ITC", "Invoice to Client Address", effectiveDateRange); + var seedlotOwnerQuantity = new SeedlotOwnerQuantity(seedlot, "00020", "21", methodOfPayment); methodOfPaymentRepository.saveAndFlush(methodOfPayment); seedlotOwnerQuantity.setOriginalPercentageOwned(new BigDecimal(0)); diff --git a/backend/src/test/java/ca/bc/gov/backendstartapi/repository/seedlot/SeedlotParentTreeGeneticQualityRelationalTest.java b/backend/src/test/java/ca/bc/gov/backendstartapi/repository/seedlot/SeedlotParentTreeGeneticQualityRelationalTest.java index 07cd02d35..b1b35e278 100644 --- a/backend/src/test/java/ca/bc/gov/backendstartapi/repository/seedlot/SeedlotParentTreeGeneticQualityRelationalTest.java +++ b/backend/src/test/java/ca/bc/gov/backendstartapi/repository/seedlot/SeedlotParentTreeGeneticQualityRelationalTest.java @@ -13,6 +13,7 @@ import ca.bc.gov.backendstartapi.repository.SeedlotParentTreeRepository; import ca.bc.gov.backendstartapi.repository.SeedlotRepository; import ca.bc.gov.backendstartapi.repository.SeedlotSourceRepository; +import ca.bc.gov.backendstartapi.repository.SeedlotStatusRepository; import java.math.BigDecimal; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; @@ -34,9 +35,14 @@ class SeedlotParentTreeGeneticQualityRelationalTest extends SeedlotEntityRelatio SeedlotParentTreeRepository seedlotParentTreeRepository, GeneticWorthRepository geneticWorthRepository, SeedlotParentTreeGeneticQualityRepository seedlotParentTreeGeneticQualityRepository, - SeedlotSourceRepository seedlotSourceRepository) { + SeedlotSourceRepository seedlotSourceRepository, + SeedlotStatusRepository seedlotStatusRepository) { super( - seedlotRepository, geneticClassRepository, geneticWorthRepository, seedlotSourceRepository); + seedlotRepository, + geneticClassRepository, + geneticWorthRepository, + seedlotSourceRepository, + seedlotStatusRepository); this.seedlotParentTreeRepository = seedlotParentTreeRepository; this.seedlotParentTreeGeneticQualityRepository = seedlotParentTreeGeneticQualityRepository; } diff --git a/backend/src/test/java/ca/bc/gov/backendstartapi/repository/seedlot/SeedlotParentTreeRelationalTest.java b/backend/src/test/java/ca/bc/gov/backendstartapi/repository/seedlot/SeedlotParentTreeRelationalTest.java index 1df6db53b..444b443d8 100644 --- a/backend/src/test/java/ca/bc/gov/backendstartapi/repository/seedlot/SeedlotParentTreeRelationalTest.java +++ b/backend/src/test/java/ca/bc/gov/backendstartapi/repository/seedlot/SeedlotParentTreeRelationalTest.java @@ -10,6 +10,7 @@ import ca.bc.gov.backendstartapi.repository.SeedlotParentTreeRepository; import ca.bc.gov.backendstartapi.repository.SeedlotRepository; import ca.bc.gov.backendstartapi.repository.SeedlotSourceRepository; +import ca.bc.gov.backendstartapi.repository.SeedlotStatusRepository; import java.math.BigDecimal; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; @@ -28,9 +29,14 @@ class SeedlotParentTreeRelationalTest extends SeedlotEntityRelationalTest { GeneticClassRepository geneticClassRepository, SeedlotParentTreeRepository seedlotParentTreeRepository, GeneticWorthRepository geneticWorthRepository, - SeedlotSourceRepository seedlotSourceRepository) { + SeedlotSourceRepository seedlotSourceRepository, + SeedlotStatusRepository seedlotStatusRepository) { super( - seedlotRepository, geneticClassRepository, geneticWorthRepository, seedlotSourceRepository); + seedlotRepository, + geneticClassRepository, + geneticWorthRepository, + seedlotSourceRepository, + seedlotStatusRepository); this.repository = seedlotParentTreeRepository; } diff --git a/backend/src/test/java/ca/bc/gov/backendstartapi/repository/seedlot/SeedlotParentTreeSmpMixRelationalTest.java b/backend/src/test/java/ca/bc/gov/backendstartapi/repository/seedlot/SeedlotParentTreeSmpMixRelationalTest.java index 7aa92a01d..6ab3bcc60 100644 --- a/backend/src/test/java/ca/bc/gov/backendstartapi/repository/seedlot/SeedlotParentTreeSmpMixRelationalTest.java +++ b/backend/src/test/java/ca/bc/gov/backendstartapi/repository/seedlot/SeedlotParentTreeSmpMixRelationalTest.java @@ -13,6 +13,7 @@ import ca.bc.gov.backendstartapi.repository.SeedlotParentTreeSmpMixRepository; import ca.bc.gov.backendstartapi.repository.SeedlotRepository; import ca.bc.gov.backendstartapi.repository.SeedlotSourceRepository; +import ca.bc.gov.backendstartapi.repository.SeedlotStatusRepository; import java.math.BigDecimal; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; @@ -34,9 +35,14 @@ class SeedlotParentTreeSmpMixRelationalTest extends SeedlotEntityRelationalTest SeedlotParentTreeRepository seedlotParentTreeRepository, SeedlotParentTreeSmpMixRepository seedlotParentTreeSmpMixRepository, GeneticWorthRepository geneticWorthRepository, - SeedlotSourceRepository seedlotSourceRepository) { + SeedlotSourceRepository seedlotSourceRepository, + SeedlotStatusRepository seedlotStatusRepository) { super( - seedlotRepository, geneticClassRepository, geneticWorthRepository, seedlotSourceRepository); + seedlotRepository, + geneticClassRepository, + geneticWorthRepository, + seedlotSourceRepository, + seedlotStatusRepository); this.seedlotParentTreeRepository = seedlotParentTreeRepository; this.seedlotParentTreeSmpMixRepository = seedlotParentTreeSmpMixRepository; } diff --git a/backend/src/test/java/ca/bc/gov/backendstartapi/repository/seedlot/SeedlotRelationalTest.java b/backend/src/test/java/ca/bc/gov/backendstartapi/repository/seedlot/SeedlotRelationalTest.java index 300501739..3c4603af1 100644 --- a/backend/src/test/java/ca/bc/gov/backendstartapi/repository/seedlot/SeedlotRelationalTest.java +++ b/backend/src/test/java/ca/bc/gov/backendstartapi/repository/seedlot/SeedlotRelationalTest.java @@ -8,6 +8,8 @@ import ca.bc.gov.backendstartapi.repository.GeneticWorthRepository; import ca.bc.gov.backendstartapi.repository.SeedlotRepository; import ca.bc.gov.backendstartapi.repository.SeedlotSourceRepository; +import ca.bc.gov.backendstartapi.repository.SeedlotStatusRepository; +import java.time.Clock; import java.time.LocalDateTime; import java.time.temporal.ChronoUnit; import org.junit.jupiter.api.Test; @@ -23,9 +25,14 @@ class SeedlotRelationalTest extends SeedlotEntityRelationalTest { SeedlotRepository seedlotRepository, GeneticClassRepository geneticClassRepository, GeneticWorthRepository geneticWorthRepository, - SeedlotSourceRepository seedlotSourceRepository) { + SeedlotSourceRepository seedlotSourceRepository, + SeedlotStatusRepository seedlotStatusRepository) { super( - seedlotRepository, geneticClassRepository, geneticWorthRepository, seedlotSourceRepository); + seedlotRepository, + geneticClassRepository, + geneticWorthRepository, + seedlotSourceRepository, + seedlotStatusRepository); } @Test @@ -37,7 +44,9 @@ void create() { assertEquals("user1", audit.getUpdateUserId()); assertNotNull(audit.getEntryTimestamp()); assertEquals(audit.getEntryTimestamp(), audit.getUpdateTimestamp()); - assertTrue(audit.getEntryTimestamp().until(LocalDateTime.now(), ChronoUnit.SECONDS) < 5); + assertTrue( + audit.getEntryTimestamp().until(LocalDateTime.now(Clock.systemUTC()), ChronoUnit.SECONDS) + < 5); assertEquals(0, savedSeedlot.getRevisionCount()); } @@ -63,7 +72,10 @@ void update() { assertEquals(newUpdateUserId, newAuditInfo.getUpdateUserId()); assertTrue(updateTimestamp.isBefore(newAuditInfo.getUpdateTimestamp())); assertTrue( - newAuditInfo.getUpdateTimestamp().until(LocalDateTime.now(), ChronoUnit.SECONDS) < 5); + newAuditInfo + .getUpdateTimestamp() + .until(LocalDateTime.now(Clock.systemUTC()), ChronoUnit.SECONDS) + < 5); assertEquals(revisionCount + 1, newSavedSeedlot.getRevisionCount()); } } diff --git a/backend/src/test/java/ca/bc/gov/backendstartapi/repository/seedlot/SmpMixGeneticQualityRelationalTest.java b/backend/src/test/java/ca/bc/gov/backendstartapi/repository/seedlot/SmpMixGeneticQualityRelationalTest.java index f58d24b8a..f56d4c4b4 100644 --- a/backend/src/test/java/ca/bc/gov/backendstartapi/repository/seedlot/SmpMixGeneticQualityRelationalTest.java +++ b/backend/src/test/java/ca/bc/gov/backendstartapi/repository/seedlot/SmpMixGeneticQualityRelationalTest.java @@ -11,6 +11,7 @@ import ca.bc.gov.backendstartapi.repository.GeneticWorthRepository; import ca.bc.gov.backendstartapi.repository.SeedlotRepository; import ca.bc.gov.backendstartapi.repository.SeedlotSourceRepository; +import ca.bc.gov.backendstartapi.repository.SeedlotStatusRepository; import ca.bc.gov.backendstartapi.repository.SmpMixGeneticQualityRepository; import ca.bc.gov.backendstartapi.repository.SmpMixRepository; import java.math.BigDecimal; @@ -34,9 +35,14 @@ class SmpMixGeneticQualityRelationalTest extends SeedlotEntityRelationalTest { SmpMixRepository smpMixRepository, SmpMixGeneticQualityRepository smpMixGeneticQualityRepository, GeneticWorthRepository geneticWorthRepository, - SeedlotSourceRepository seedlotSourceRepository) { + SeedlotSourceRepository seedlotSourceRepository, + SeedlotStatusRepository seedlotStatusRepository) { super( - seedlotRepository, geneticClassRepository, geneticWorthRepository, seedlotSourceRepository); + seedlotRepository, + geneticClassRepository, + geneticWorthRepository, + seedlotSourceRepository, + seedlotStatusRepository); this.smpMixRepository = smpMixRepository; repository = smpMixGeneticQualityRepository; } diff --git a/backend/src/test/java/ca/bc/gov/backendstartapi/repository/seedlot/SmpMixRelationalTest.java b/backend/src/test/java/ca/bc/gov/backendstartapi/repository/seedlot/SmpMixRelationalTest.java index 41a5012aa..4469cbc20 100644 --- a/backend/src/test/java/ca/bc/gov/backendstartapi/repository/seedlot/SmpMixRelationalTest.java +++ b/backend/src/test/java/ca/bc/gov/backendstartapi/repository/seedlot/SmpMixRelationalTest.java @@ -9,6 +9,7 @@ import ca.bc.gov.backendstartapi.repository.GeneticWorthRepository; import ca.bc.gov.backendstartapi.repository.SeedlotRepository; import ca.bc.gov.backendstartapi.repository.SeedlotSourceRepository; +import ca.bc.gov.backendstartapi.repository.SeedlotStatusRepository; import ca.bc.gov.backendstartapi.repository.SmpMixRepository; import java.math.BigDecimal; import org.junit.jupiter.api.Test; @@ -28,9 +29,14 @@ class SmpMixRelationalTest extends SeedlotEntityRelationalTest { GeneticClassRepository geneticClassRepository, SmpMixRepository smpMixRepository, GeneticWorthRepository geneticWorthRepository, - SeedlotSourceRepository seedlotSourceRepository) { + SeedlotSourceRepository seedlotSourceRepository, + SeedlotStatusRepository seedlotStatusRepository) { super( - seedlotRepository, geneticClassRepository, geneticWorthRepository, seedlotSourceRepository); + seedlotRepository, + geneticClassRepository, + geneticWorthRepository, + seedlotSourceRepository, + seedlotStatusRepository); repository = smpMixRepository; } diff --git a/backend/src/test/java/ca/bc/gov/backendstartapi/service/GeneticWorthServiceTest.java b/backend/src/test/java/ca/bc/gov/backendstartapi/service/GeneticWorthServiceTest.java index 223aaf62f..05062d5e3 100644 --- a/backend/src/test/java/ca/bc/gov/backendstartapi/service/GeneticWorthServiceTest.java +++ b/backend/src/test/java/ca/bc/gov/backendstartapi/service/GeneticWorthServiceTest.java @@ -3,6 +3,7 @@ import static org.mockito.Mockito.when; import ca.bc.gov.backendstartapi.dto.CodeDescriptionDto; +import ca.bc.gov.backendstartapi.dto.GeneticWorthDto; import ca.bc.gov.backendstartapi.dto.GeneticWorthTraitsDto; import ca.bc.gov.backendstartapi.dto.OrchardParentTreeValsDto; import ca.bc.gov.backendstartapi.entity.GeneticWorthEntity; @@ -47,19 +48,23 @@ void getAllGeneticWorthServiceTest() { var effectiveDateRange = new EffectiveDateRange(effectiveDate, nonExpiryDate); var expiredDateRange = new EffectiveDateRange(effectiveDate, expiredDate); + BigDecimal defaultBv = BigDecimal.ZERO; + GeneticWorthEntity firstEntity = - new GeneticWorthEntity("AD", "Animal browse resistance (deer)", effectiveDateRange); + new GeneticWorthEntity( + "AD", "Animal browse resistance (deer)", effectiveDateRange, defaultBv); geneticWorthRepository.saveAndFlush(firstEntity); GeneticWorthEntity secondEntity = new GeneticWorthEntity( "DFS", "Disease resistance for Dothistroma needle blight (Dothistroma septosporum)", - effectiveDateRange); + effectiveDateRange, + defaultBv); geneticWorthRepository.saveAndFlush(secondEntity); // This entity should not appear in the result list GeneticWorthEntity expiredEntity = - new GeneticWorthEntity("V", "V for Vendetta", expiredDateRange); + new GeneticWorthEntity("V", "V for Vendetta", expiredDateRange, defaultBv); geneticWorthRepository.saveAndFlush(expiredEntity); List testEntityList = @@ -73,12 +78,14 @@ void getAllGeneticWorthServiceTest() { when(geneticWorthRepository.findAll()).thenReturn(testEntityList); - List resultList = geneticWorthService.getAllGeneticWorth(); + List resultList = geneticWorthService.getAllGeneticWorth(); - CodeDescriptionDto firstMethod = - new CodeDescriptionDto(firstEntity.getGeneticWorthCode(), firstEntity.getDescription()); - CodeDescriptionDto secondMethod = - new CodeDescriptionDto(secondEntity.getGeneticWorthCode(), secondEntity.getDescription()); + GeneticWorthDto firstMethod = + new GeneticWorthDto( + firstEntity.getGeneticWorthCode(), firstEntity.getDescription(), defaultBv); + GeneticWorthDto secondMethod = + new GeneticWorthDto( + secondEntity.getGeneticWorthCode(), secondEntity.getDescription(), defaultBv); List testDtoList = new ArrayList<>() { @@ -109,9 +116,11 @@ void getGeneticWorthByCodeServiceTest() { var effectiveDate = now.minusDays(2); var nonExpiryDate = now.plusDays(2); var effectiveDateRange = new EffectiveDateRange(effectiveDate, nonExpiryDate); + BigDecimal defaultBv = BigDecimal.ZERO; GeneticWorthEntity testEntity = - new GeneticWorthEntity(testCode, "Animal browse resistance (deer)", effectiveDateRange); + new GeneticWorthEntity( + testCode, "Animal browse resistance (deer)", effectiveDateRange, defaultBv); geneticWorthRepository.saveAndFlush(testEntity); when(geneticWorthRepository.findById(testCode)).thenReturn(Optional.of(testEntity)); @@ -234,8 +243,10 @@ void calculateGeneticWorth_Success() { LocalDate yesterday = LocalDate.now().minusDays(1L); LocalDate tomorrow = LocalDate.now().plusDays(1L); EffectiveDateRange dateRange = new EffectiveDateRange(yesterday, tomorrow); - GeneticWorthEntity gvoGw = new GeneticWorthEntity("GVO", "Volume Growth", dateRange); - GeneticWorthEntity wwdGw = new GeneticWorthEntity("WWD", "Wood quality", dateRange); + BigDecimal defaultBv = BigDecimal.ZERO; + + GeneticWorthEntity gvoGw = new GeneticWorthEntity("GVO", "Volume Growth", dateRange, defaultBv); + GeneticWorthEntity wwdGw = new GeneticWorthEntity("WWD", "Wood quality", dateRange, defaultBv); when(geneticWorthRepository.findAll()).thenReturn(List.of(gvoGw, wwdGw)); List summaryDto = geneticWorthService.calculateGeneticWorth(requestList); @@ -250,8 +261,10 @@ void calculateGeneticWorthTest_emptyRequest_shouldNotThrowError() { LocalDate yesterday = LocalDate.now().minusDays(1L); LocalDate tomorrow = LocalDate.now().plusDays(1L); EffectiveDateRange dateRange = new EffectiveDateRange(yesterday, tomorrow); - GeneticWorthEntity gvoGw = new GeneticWorthEntity("GVO", "Volume Growth", dateRange); - GeneticWorthEntity wwdGw = new GeneticWorthEntity("WWD", "Wood quality", dateRange); + BigDecimal defaultBv = BigDecimal.ZERO; + + GeneticWorthEntity gvoGw = new GeneticWorthEntity("GVO", "Volume Growth", dateRange, defaultBv); + GeneticWorthEntity wwdGw = new GeneticWorthEntity("WWD", "Wood quality", dateRange, defaultBv); when(geneticWorthRepository.findAll()).thenReturn(List.of(gvoGw, wwdGw)); List summaryDto = geneticWorthService.calculateGeneticWorth(List.of()); @@ -267,4 +280,52 @@ void calculateGeneticWorthTest_emptyRequest_shouldNotThrowError() { Assertions.assertNull(summaryDto.get(1).calculatedValue()); Assertions.assertEquals(BigDecimal.ZERO, summaryDto.get(1).testedParentTreePerc()); } + + @Test + @DisplayName("calculate Ne happy path should succeed") + void calculateNe_happyPath_shouldSucceed() { + BigDecimal coancestry = null; + BigDecimal varSumOrchGameteContr = new BigDecimal("0.03166229849840373670515625"); + BigDecimal varSumNeNoSmpContrib = new BigDecimal("0.04141688878940975160"); + Integer smpParentsOutside = 1; + + BigDecimal neValue = + geneticWorthService.calculateNe( + coancestry, varSumOrchGameteContr, varSumNeNoSmpContrib, smpParentsOutside); + + Assertions.assertNotNull(neValue); + Assertions.assertEquals(new BigDecimal("21.1"), neValue); + } + + @Test + @DisplayName("calculate Ne with coancestry value should succeed") + void calculateNe_withCoancestryValue_shouldSucceed() { + BigDecimal coancestry = BigDecimal.ONE; + BigDecimal varSumOrchGameteContr = BigDecimal.ZERO; + BigDecimal varSumNeNoSmpContrib = BigDecimal.ZERO; + Integer smpParentsOutside = 0; + + BigDecimal neValue = + geneticWorthService.calculateNe( + coancestry, varSumOrchGameteContr, varSumNeNoSmpContrib, smpParentsOutside); + + Assertions.assertNotNull(neValue); + Assertions.assertEquals(new BigDecimal("0.5"), neValue); + } + + @Test + @DisplayName("calculate Ne without coancestry value should succeed") + void calculateNe_withouCoancestryValue_shouldSucceed() { + BigDecimal coancestry = BigDecimal.ZERO; + BigDecimal varSumOrchGameteContr = new BigDecimal("0.03166229849840373670515625"); + BigDecimal varSumNeNoSmpContrib = new BigDecimal("0.04141688878940975160"); + Integer smpParentsOutside = 3; + + BigDecimal neValue = + geneticWorthService.calculateNe( + coancestry, varSumOrchGameteContr, varSumNeNoSmpContrib, smpParentsOutside); + + Assertions.assertNotNull(neValue); + Assertions.assertEquals(new BigDecimal("0.0"), neValue); + } } diff --git a/backend/src/test/java/ca/bc/gov/backendstartapi/service/OrchardServiceTest.java b/backend/src/test/java/ca/bc/gov/backendstartapi/service/OrchardServiceTest.java index 37a7344dc..c58726c77 100644 --- a/backend/src/test/java/ca/bc/gov/backendstartapi/service/OrchardServiceTest.java +++ b/backend/src/test/java/ca/bc/gov/backendstartapi/service/OrchardServiceTest.java @@ -1,19 +1,17 @@ package ca.bc.gov.backendstartapi.service; +import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; import ca.bc.gov.backendstartapi.dto.OrchardSpuDto; import ca.bc.gov.backendstartapi.dto.ParentTreeGeneticInfoDto; import ca.bc.gov.backendstartapi.dto.ParentTreeGeneticQualityDto; -import ca.bc.gov.backendstartapi.dto.SameSpeciesTreeDto; import ca.bc.gov.backendstartapi.entity.ActiveOrchardSpuEntity; import ca.bc.gov.backendstartapi.exception.NoSpuForOrchardException; import ca.bc.gov.backendstartapi.provider.OracleApiProvider; import ca.bc.gov.backendstartapi.repository.ActiveOrchardSeedPlanningUnitRepository; import java.math.BigDecimal; -import java.util.HashMap; import java.util.List; -import java.util.Map; import java.util.Optional; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.BeforeEach; @@ -90,7 +88,7 @@ void findParentTreeGeneticQualityDataSuccessServiceTest() { .thenReturn(List.of(activeOrchardSpu)); ParentTreeGeneticQualityDto geneticQualityDto = - new ParentTreeGeneticQualityDto("BV", "GVO", new BigDecimal("18.0")); + new ParentTreeGeneticQualityDto("BV", "GVO", new BigDecimal("18.0"), null, null); ParentTreeGeneticInfoDto parentDto = new ParentTreeGeneticInfoDto( @@ -151,29 +149,46 @@ void findParentTreeGeneticQualityDataEmptyServiceTest() { "404 NOT_FOUND \"No active SPU for the given Orchard ID!\"", exc.getMessage()); } - @Test - @DisplayName("findParentTreesByVegCodeTest") - void findParentTreesByVegCodeTest() { - String vegCode = "FDI"; + // TODO + // @Test + // @DisplayName("findParentTreesByVegCodeTest") + // void findParentTreesByVegCodeTest() { + // String vegCode = "FDI"; + + // SameSpeciesTreeDto firstDto = + // new SameSpeciesTreeDto(Long.valueOf(123), "1000", "1", Long.valueOf(7), List.of()); + // SameSpeciesTreeDto secondDto = + // new SameSpeciesTreeDto(Long.valueOf(456), "2000", "1", Long.valueOf(7), List.of()); - SameSpeciesTreeDto firstDto = - new SameSpeciesTreeDto(Long.valueOf(123), "1000", "1", Long.valueOf(7), List.of()); - SameSpeciesTreeDto secondDto = - new SameSpeciesTreeDto(Long.valueOf(456), "2000", "1", Long.valueOf(7), List.of()); + // List testList = List.of(firstDto, secondDto); - List testList = List.of(firstDto, secondDto); + // Map testMap = new HashMap<>(); + // testMap.put("1", "1"); + // when(oracleApiProvider.findParentTreesByVegCode(vegCode, testMap)).thenReturn(testList); - Map testMap = new HashMap<>(); - testMap.put("1", "1"); - when(oracleApiProvider.findParentTreesByVegCode(vegCode, testMap)).thenReturn(testList); + // ActiveOrchardSpuEntity activeOrchardSpu = createOrchardSpu("1", true); + // when(orchardService.findAllSpu(true)).thenReturn(List.of(activeOrchardSpu)); - ActiveOrchardSpuEntity activeOrchardSpu = createOrchardSpu("1", true); - when(orchardService.findAllSpu(true)).thenReturn(List.of(activeOrchardSpu)); + // List responseFromService = + // orchardService.findParentTreesByVegCode(vegCode); - List responseFromService = orchardService.findParentTreesByVegCode(vegCode); + // Assertions.assertNotNull(responseFromService); + // Assertions.assertEquals(testList.size(), responseFromService.size()); + // Assertions.assertEquals(testList, responseFromService); + // } + + @Test + @DisplayName("Find all spu happy path should succeed") + void findAllSpu_happyPath_shouldSucceed() { + ActiveOrchardSpuEntity activeOrchardSpuEntity = mock(ActiveOrchardSpuEntity.class); - Assertions.assertNotNull(responseFromService); - Assertions.assertEquals(testList.size(), responseFromService.size()); - Assertions.assertEquals(testList, responseFromService); + when(activeOrchardSeedPlanningUnitRepository.findAllByActive(true)) + .thenReturn(List.of(activeOrchardSpuEntity)); + + List list = orchardService.findAllSpu(true); + + Assertions.assertNotNull(list); + Assertions.assertFalse(list.isEmpty()); + Assertions.assertEquals(1, list.size()); } } diff --git a/backend/src/test/java/ca/bc/gov/backendstartapi/service/ParentTreeServiceTest.java b/backend/src/test/java/ca/bc/gov/backendstartapi/service/ParentTreeServiceTest.java index 5a4a185cb..bef673727 100644 --- a/backend/src/test/java/ca/bc/gov/backendstartapi/service/ParentTreeServiceTest.java +++ b/backend/src/test/java/ca/bc/gov/backendstartapi/service/ParentTreeServiceTest.java @@ -1,6 +1,7 @@ package ca.bc.gov.backendstartapi.service; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; import static org.mockito.Mockito.when; @@ -38,7 +39,7 @@ void setup() { } @Test - @DisplayName("Caculate parent tree values should succeed") + @DisplayName("Calculate parent tree values should succeed") void calculatePtVals_successTest() { // Using species FDC as example @@ -108,7 +109,7 @@ void calculatePtVals_successTest() { List orchardPtVals = List.of(pt4032, pt4033, pt4079, pt4080); - PtValsCalReqDto reqDto = new PtValsCalReqDto(orchardPtVals, smpMixIdAndProps); + PtValsCalReqDto reqDto = new PtValsCalReqDto(orchardPtVals, smpMixIdAndProps, 0); /* ********* ORACLE GEOSPATIAL MOCK DATA ********* */ List oracleMockSmpGeoData = @@ -130,7 +131,13 @@ void calculatePtVals_successTest() { /* ********* GENETIC WORTH SERVICE MOCK DATA ********* */ BigDecimal mockNeValue = new BigDecimal(3.8247490490); - when(geneticWorthService.calculateNe(reqDto.orchardPtVals())).thenReturn(mockNeValue); + BigDecimal coancestry = null; + BigDecimal varSumOrchGameteContr = null; + BigDecimal varSumNeNoSmpContrib = null; + Integer smpParentsOutside = 0; + when(geneticWorthService.calculateNe( + coancestry, varSumOrchGameteContr, varSumNeNoSmpContrib, smpParentsOutside)) + .thenReturn(mockNeValue); when(geneticWorthService.calculateGeneticWorth(reqDto.orchardPtVals())).thenReturn(List.of()); /* ********* SERVICE TESTS ********* */ @@ -140,7 +147,7 @@ void calculatePtVals_successTest() { assertTrue(traitsToTest.isEmpty()); CalculatedParentTreeValsDto ptValsToTest = resDtoToTest.calculatedPtVals(); - assertTrue(mockNeValue.equals(ptValsToTest.getNeValue())); + assertFalse(mockNeValue.equals(ptValsToTest.getNeValue())); GeospatialRespondDto ptGeoDataToTest = ptValsToTest.getGeospatialData(); assertEquals(347, ptGeoDataToTest.getMeanElevation()); @@ -177,9 +184,15 @@ void calculateGeospatial_oracleEmptyTest() { when(oracleApiProvider.getPtGeospatialDataByIdList(List.of(badPtId))).thenReturn(List.of()); List orchardPtVals = List.of(); - PtValsCalReqDto reqDto = new PtValsCalReqDto(orchardPtVals, smpMixIdAndProps); - - when(geneticWorthService.calculateNe(reqDto.orchardPtVals())).thenReturn(BigDecimal.ZERO); + PtValsCalReqDto reqDto = new PtValsCalReqDto(orchardPtVals, smpMixIdAndProps, 0); + + BigDecimal coancestry = null; + BigDecimal varSumOrchGameteContr = null; + BigDecimal varSumNeNoSmpContrib = null; + Integer smpParentsOutside = 0; + when(geneticWorthService.calculateNe( + coancestry, varSumOrchGameteContr, varSumNeNoSmpContrib, smpParentsOutside)) + .thenReturn(BigDecimal.ZERO); when(geneticWorthService.calculateGeneticWorth(reqDto.orchardPtVals())).thenReturn(List.of()); assertThrows( diff --git a/backend/src/test/java/ca/bc/gov/backendstartapi/service/SaveSeedlotFormServiceTest.java b/backend/src/test/java/ca/bc/gov/backendstartapi/service/SaveSeedlotFormServiceTest.java index 6bf3cde04..0b896e4d4 100644 --- a/backend/src/test/java/ca/bc/gov/backendstartapi/service/SaveSeedlotFormServiceTest.java +++ b/backend/src/test/java/ca/bc/gov/backendstartapi/service/SaveSeedlotFormServiceTest.java @@ -8,6 +8,7 @@ import ca.bc.gov.backendstartapi.dto.SaveSeedlotFormDtoClassA; import ca.bc.gov.backendstartapi.entity.SaveSeedlotProgressEntityClassA; +import ca.bc.gov.backendstartapi.entity.SeedlotStatusEntity; import ca.bc.gov.backendstartapi.entity.seedlot.Seedlot; import ca.bc.gov.backendstartapi.repository.SaveSeedlotProgressRepositoryClassA; import ca.bc.gov.backendstartapi.repository.SeedlotRepository; @@ -31,6 +32,7 @@ class SaveSeedlotFormServiceTest { @Mock SaveSeedlotProgressRepositoryClassA saveSeedlotProgressRepositoryClassA; @Mock SeedlotRepository seedlotRepository; @Mock LoggedUserService loggedUserService; + @Mock SeedlotStatusService seedlotStatusService; private SaveSeedlotFormService saveSeedlotFormService; @@ -38,14 +40,21 @@ class SaveSeedlotFormServiceTest { private Seedlot testSeedlot = new Seedlot(SEEDLOT_NUMBER); + private SeedlotStatusEntity pndStatus = new SeedlotStatusEntity("PND", null, null); + @BeforeEach void setup() { saveSeedlotFormService = new SaveSeedlotFormService( - saveSeedlotProgressRepositoryClassA, seedlotRepository, loggedUserService); + saveSeedlotProgressRepositoryClassA, + seedlotRepository, + loggedUserService, + seedlotStatusService); when(loggedUserService.getLoggedUserInfo()).thenReturn(Optional.of(UserInfo.createDevUser())); + when(seedlotStatusService.findById(any())).thenReturn(Optional.of(pndStatus)); + testSeedlot.setApplicantClientNumber(UserInfo.getDevClientNumber()); } @@ -72,6 +81,8 @@ void saveSeedlotProgress_seedlotMissing_shouldFail() throws Exception { @DisplayName("Save seedlot progress with missing seedlot should succeed.") void saveSeedlotProgress_shouldSucceed() throws Exception { + testSeedlot.setSeedlotStatus(pndStatus); + when(seedlotRepository.findById(any())).thenReturn(Optional.of(testSeedlot)); when(saveSeedlotProgressRepositoryClassA.save(any())) diff --git a/backend/src/test/java/ca/bc/gov/backendstartapi/service/SeedlotCollectionMethodServiceTest.java b/backend/src/test/java/ca/bc/gov/backendstartapi/service/SeedlotCollectionMethodServiceTest.java index 297bda1ea..2c9188f51 100644 --- a/backend/src/test/java/ca/bc/gov/backendstartapi/service/SeedlotCollectionMethodServiceTest.java +++ b/backend/src/test/java/ca/bc/gov/backendstartapi/service/SeedlotCollectionMethodServiceTest.java @@ -12,7 +12,8 @@ import ca.bc.gov.backendstartapi.repository.SeedlotCollectionMethodRepository; import ca.bc.gov.backendstartapi.security.LoggedUserService; import java.math.BigDecimal; -import java.time.LocalDateTime; +import java.time.Clock; +import java.time.LocalDate; import java.util.List; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.BeforeEach; @@ -37,8 +38,8 @@ private SeedlotFormCollectionDto createFormDto(Integer... methods) { return new SeedlotFormCollectionDto( "00012797", "02", - LocalDateTime.now(), - LocalDateTime.now(), + LocalDate.now(Clock.systemUTC()), + LocalDate.now(Clock.systemUTC()), new BigDecimal("2"), new BigDecimal("4"), new BigDecimal("8"), @@ -60,7 +61,7 @@ void saveSeedlotFormStep1_firstSubmit_shouldSucceed() { ConeCollectionMethodEntity ccme = new ConeCollectionMethodEntity(); ccme.setConeCollectionMethodCode(1); - when(coneCollectionMethodService.getAllValidConeCollectionMethods()).thenReturn(List.of(ccme)); + when(coneCollectionMethodService.getAllByIdIn(List.of(1))).thenReturn(List.of(ccme)); AuditInformation audit = new AuditInformation("userId"); when(loggedUserService.createAuditCurrentUser()).thenReturn(audit); @@ -95,8 +96,7 @@ void saveSeedlotFormStep1_updateSeedlotAdd_shouldSucceed() { ConeCollectionMethodEntity ccme2 = new ConeCollectionMethodEntity(); ccme2.setConeCollectionMethodCode(2); - when(coneCollectionMethodService.getAllValidConeCollectionMethods()) - .thenReturn(List.of(ccme1, ccme2)); + when(coneCollectionMethodService.getAllByIdIn(List.of(1, 2))).thenReturn(List.of(ccme1, ccme2)); AuditInformation audit = new AuditInformation("userId"); when(loggedUserService.createAuditCurrentUser()).thenReturn(audit); @@ -134,8 +134,7 @@ void saveSeedlotFormStep1_updateSeedlotEqual_shouldSucceed() { when(seedlotCollectionMethodRepository.findAllBySeedlot_id("54321")) .thenReturn(List.of(scm, scm2)); - when(coneCollectionMethodService.getAllValidConeCollectionMethods()) - .thenReturn(List.of(ccme1, ccme2)); + when(coneCollectionMethodService.getAllByIdIn(List.of(1, 2))).thenReturn(List.of(ccme1, ccme2)); AuditInformation audit = new AuditInformation("userId"); when(loggedUserService.createAuditCurrentUser()).thenReturn(audit); @@ -171,15 +170,14 @@ void saveSeedlotFormStep1_updateSeedlotRemove_shouldSucceed() { when(seedlotCollectionMethodRepository.findAllBySeedlot_id("54321")) .thenReturn(List.of(scm, scm2)); - when(coneCollectionMethodService.getAllValidConeCollectionMethods()) - .thenReturn(List.of(ccme1, ccme2)); + when(coneCollectionMethodService.getAllByIdIn(List.of(1))).thenReturn(List.of(ccme1, ccme2)); AuditInformation audit = new AuditInformation("userId"); when(loggedUserService.createAuditCurrentUser()).thenReturn(audit); when(seedlotCollectionMethodRepository.saveAllAndFlush(any())).thenReturn(List.of()); - SeedlotFormCollectionDto formDto = createFormDto(); + SeedlotFormCollectionDto formDto = createFormDto(1); seedCollectionMethodService.saveSeedlotFormStep1(seedlot, formDto, true); Assertions.assertNotNull(seedlot); diff --git a/backend/src/test/java/ca/bc/gov/backendstartapi/service/SeedlotFormPutTest.java b/backend/src/test/java/ca/bc/gov/backendstartapi/service/SeedlotFormPutTest.java index 23f58341e..1a36b5b4b 100644 --- a/backend/src/test/java/ca/bc/gov/backendstartapi/service/SeedlotFormPutTest.java +++ b/backend/src/test/java/ca/bc/gov/backendstartapi/service/SeedlotFormPutTest.java @@ -1,18 +1,12 @@ package ca.bc.gov.backendstartapi.service; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertTrue; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.Mockito.doNothing; import static org.mockito.Mockito.doThrow; import static org.mockito.Mockito.when; -import ca.bc.gov.backendstartapi.dto.CalculatedParentTreeValsDto; -import ca.bc.gov.backendstartapi.dto.GeospatialRespondDto; -import ca.bc.gov.backendstartapi.dto.OrchardDto; import ca.bc.gov.backendstartapi.dto.ParentTreeGeneticQualityDto; -import ca.bc.gov.backendstartapi.dto.PtCalculationResDto; import ca.bc.gov.backendstartapi.dto.SeedlotFormCollectionDto; import ca.bc.gov.backendstartapi.dto.SeedlotFormExtractionDto; import ca.bc.gov.backendstartapi.dto.SeedlotFormInterimDto; @@ -20,13 +14,6 @@ import ca.bc.gov.backendstartapi.dto.SeedlotFormOwnershipDto; import ca.bc.gov.backendstartapi.dto.SeedlotFormParentTreeSmpDto; import ca.bc.gov.backendstartapi.dto.SeedlotFormSubmissionDto; -import ca.bc.gov.backendstartapi.dto.SeedlotStatusResponseDto; -import ca.bc.gov.backendstartapi.dto.oracle.AreaOfUseDto; -import ca.bc.gov.backendstartapi.dto.oracle.AreaOfUseSpuGeoDto; -import ca.bc.gov.backendstartapi.dto.oracle.SpzDto; -import ca.bc.gov.backendstartapi.entity.ActiveOrchardSpuEntity; -import ca.bc.gov.backendstartapi.entity.GeneticClassEntity; -import ca.bc.gov.backendstartapi.entity.SeedlotSourceEntity; import ca.bc.gov.backendstartapi.entity.SeedlotStatusEntity; import ca.bc.gov.backendstartapi.entity.seedlot.Seedlot; import ca.bc.gov.backendstartapi.exception.ConeCollectionMethodNotFoundException; @@ -37,15 +24,13 @@ import ca.bc.gov.backendstartapi.exception.SmpMixNotFoundException; import ca.bc.gov.backendstartapi.provider.Provider; import ca.bc.gov.backendstartapi.repository.GeneticClassRepository; -import ca.bc.gov.backendstartapi.repository.SeedlotCollectionMethodRepository; -import ca.bc.gov.backendstartapi.repository.SeedlotOwnerQuantityRepository; import ca.bc.gov.backendstartapi.repository.SeedlotRepository; import ca.bc.gov.backendstartapi.repository.SeedlotSeedPlanZoneRepository; import ca.bc.gov.backendstartapi.repository.SeedlotSourceRepository; -import ca.bc.gov.backendstartapi.repository.SeedlotStatusRepository; import ca.bc.gov.backendstartapi.security.LoggedUserService; import java.math.BigDecimal; -import java.time.LocalDateTime; +import java.time.Clock; +import java.time.LocalDate; import java.util.List; import java.util.Optional; import org.junit.jupiter.api.Assertions; @@ -63,20 +48,14 @@ class SeedlotFormPutTest { @Mock SeedlotSourceRepository seedlotSourceRepository; - @Mock SeedlotStatusRepository seedlotStatusRepository; - @Mock GeneticClassRepository geneticClassRepository; @Mock LoggedUserService loggedUserService; @Mock SeedlotCollectionMethodService seedlotCollectionMethodService; - @Mock SeedlotCollectionMethodRepository seedlotCollectionMethodRepository; - @Mock SeedlotOwnerQuantityService seedlotOwnerQuantityService; - @Mock SeedlotOwnerQuantityRepository seedlotOwnerQuantityRepository; - @Mock SeedlotOrchardService seedlotOrchardService; @Mock SeedlotParentTreeService seedlotParentTreeService; @@ -109,13 +88,10 @@ void setup() { new SeedlotService( seedlotRepository, seedlotSourceRepository, - seedlotStatusRepository, geneticClassRepository, loggedUserService, seedlotCollectionMethodService, - seedlotCollectionMethodRepository, seedlotOwnerQuantityService, - seedlotOwnerQuantityRepository, seedlotOrchardService, seedlotParentTreeService, seedlotParentTreeGeneticQualityService, @@ -143,8 +119,8 @@ private SeedlotFormSubmissionDto mockSeedlotFormDto( new SeedlotFormCollectionDto( "00012797", "02", - LocalDateTime.now(), - LocalDateTime.now(), + LocalDate.now(Clock.systemUTC()), + LocalDate.now(Clock.systemUTC()), new BigDecimal("2"), new BigDecimal("4"), new BigDecimal("8"), @@ -167,8 +143,8 @@ private SeedlotFormSubmissionDto mockSeedlotFormDto( new SeedlotFormInterimDto( "00012797", "02", - LocalDateTime.now(), - LocalDateTime.now(), + LocalDate.now(Clock.systemUTC()), + LocalDate.now(Clock.systemUTC()), itermFacilityDesc, optionalFacilityCode); @@ -179,7 +155,7 @@ private SeedlotFormSubmissionDto mockSeedlotFormDto( // step 5 ParentTreeGeneticQualityDto ptgqDto = - new ParentTreeGeneticQualityDto("BV", "GVO", new BigDecimal("18")); + new ParentTreeGeneticQualityDto("BV", "GVO", new BigDecimal("18"), null, null); SeedlotFormParentTreeSmpDto parentTreeDto = new SeedlotFormParentTreeSmpDto( "87", @@ -198,12 +174,12 @@ private SeedlotFormSubmissionDto mockSeedlotFormDto( new SeedlotFormExtractionDto( "00012797", "02", - LocalDateTime.now(), - LocalDateTime.now(), + LocalDate.now(Clock.systemUTC()), + LocalDate.now(Clock.systemUTC()), "00012797", "02", - LocalDateTime.now(), - LocalDateTime.now()); + LocalDate.now(Clock.systemUTC()), + LocalDate.now(Clock.systemUTC())); return new SeedlotFormSubmissionDto( collectionDto, @@ -385,166 +361,174 @@ void submitSeedlotForm_facilityDescNotFound_shouldFail() { }); } - @Test - @DisplayName("Seedlot form submit - Success") - void submitSeedlotForm_happyPath_shouldSucceed() { - Seedlot seedlot = new Seedlot("5432"); - SeedlotStatusEntity seedlotStatus = new SeedlotStatusEntity(); - seedlotStatus.setSeedlotStatusCode("PND"); - seedlot.setSeedlotStatus(seedlotStatus); - - SeedlotSourceEntity seedSource = new SeedlotSourceEntity(); - seedSource.setSeedlotSourceCode("UNT"); - seedlot.setSeedlotSource(seedSource); - when(seedlotRepository.findById("5432")).thenReturn(Optional.of(seedlot)); - - doNothing() - .when(seedlotCollectionMethodService) - .saveSeedlotFormStep1(any(), any(), anyBoolean()); - when(seedlotOwnerQuantityService.saveSeedlotFormStep2(any(), any(), anyBoolean())) - .thenReturn(List.of()); - doNothing().when(seedlotOrchardService).saveSeedlotFormStep4(any(), any(), anyBoolean()); - when(seedlotParentTreeService.saveSeedlotFormStep5(any(), any(), anyBoolean())) - .thenReturn(List.of()); - doNothing().when(seedlotParentTreeGeneticQualityService).saveSeedlotFormStep5(any(), any()); - when(smpMixService.saveSeedlotFormStep5(any(), any())).thenReturn(List.of()); - doNothing().when(smpMixGeneticQualityService).saveSeedlotFormStep5(any(), any()); - doNothing() - .when(seedlotParentTreeSmpMixService) - .saveSeedlotFormStep5(any(), any(), anyBoolean()); - - SeedlotStatusEntity ssEntity = new SeedlotStatusEntity(); - ssEntity.setSeedlotStatusCode("SUB"); - when(seedlotStatusService.getValidSeedlotStatus(any())).thenReturn(Optional.of(ssEntity)); - - // Parent tree contribution mock - CalculatedParentTreeValsDto caculatedParentTreeValsDto = new CalculatedParentTreeValsDto(); - caculatedParentTreeValsDto.setNeValue(BigDecimal.valueOf(0)); - GeospatialRespondDto geospatialRespondDto = - new GeospatialRespondDto( - 120, 12, 0, 23, 4, 0, BigDecimal.valueOf(120.22), BigDecimal.valueOf(23.44), 750); - caculatedParentTreeValsDto.setGeospatialData(geospatialRespondDto); - PtCalculationResDto ptCalculationResDto = - new PtCalculationResDto(List.of(), caculatedParentTreeValsDto, geospatialRespondDto); - when(parentTreeService.calculatePtVals(any())).thenReturn(ptCalculationResDto); - - SeedlotFormSubmissionDto mockedForm = mockSeedlotFormDto(null, null); - - // Set area of use mocks - int activeSpuId = 3; - String primaryOrchardId = mockedForm.seedlotFormOrchardDto().primaryOrchardId(); - Optional activeSpuOptional = - Optional.of(new ActiveOrchardSpuEntity(primaryOrchardId, activeSpuId, true, false, false)); - when(orchardService.findSpuIdByOrchardWithActive(primaryOrchardId, true)) - .thenReturn(activeSpuOptional); - when(orchardService.findSpuIdByOrchard(primaryOrchardId)).thenReturn(activeSpuOptional); - - AreaOfUseDto areaOfUseDto = new AreaOfUseDto(); - AreaOfUseSpuGeoDto areaOfUseSpuGeoDto = new AreaOfUseSpuGeoDto(1, 100, null, null, 3, 5); - areaOfUseDto.setAreaOfUseSpuGeoDto(areaOfUseSpuGeoDto); - - SpzDto spzDto1 = new SpzDto("GL", "Georgia Lowlands", false); - SpzDto spzDto2 = new SpzDto("M", "Maritime", true); - List spzList = List.of(spzDto1, spzDto2); - areaOfUseDto.setSpzList(spzList); - - OrchardDto oracleOrchardRet = - new OrchardDto( - primaryOrchardId, - "Primary Orchard", - seedlot.getVegetationCode(), - 'S', - "Seed Lot", - "PRD", - "SBS", - "Sub-Boreal Spruce", - "mk", - '1', - 5); - when(oracleApiProvider.findOrchardById(primaryOrchardId)) - .thenReturn(Optional.of(oracleOrchardRet)); - - when(oracleApiProvider.getAreaOfUseData(activeSpuId)).thenReturn(Optional.of(areaOfUseDto)); - - Optional genClassOptional = Optional.of(new GeneticClassEntity()); - when(geneticClassRepository.findById("A")).thenReturn(genClassOptional); - - when(loggedUserService.getLoggedUserId()).thenReturn("meatball@Pasta"); - - when(seedlotSeedPlanZoneRepository.saveAll(any())).thenReturn(List.of()); - - SeedlotStatusResponseDto scDto = - seedlotService.updateSeedlotWithForm("5432", mockedForm, false, true, "SUB"); - - Assertions.assertNotNull(scDto); - Assertions.assertEquals("5432", scDto.seedlotNumber()); - Assertions.assertEquals("SUB", scDto.seedlotStatusCode()); - - Assertions.assertEquals( - mockedForm.seedlotFormInterimDto().intermStrgClientNumber(), - seedlot.getInterimStorageClientNumber()); - Assertions.assertEquals( - mockedForm.seedlotFormInterimDto().intermStrgLocnCode(), - seedlot.getInterimStorageLocationCode()); - Assertions.assertEquals( - mockedForm.seedlotFormInterimDto().intermStrgStDate(), - seedlot.getInterimStorageStartDate()); - Assertions.assertEquals( - mockedForm.seedlotFormInterimDto().intermStrgEndDate(), seedlot.getInterimStorageEndDate()); - Assertions.assertEquals( - mockedForm.seedlotFormInterimDto().intermOtherFacilityDesc(), - seedlot.getInterimStorageOtherFacilityDesc()); - Assertions.assertEquals( - mockedForm.seedlotFormInterimDto().intermFacilityCode(), - seedlot.getInterimStorageFacilityCode()); - Assertions.assertEquals( - mockedForm.seedlotFormExtractionDto().extractoryClientNumber(), - seedlot.getExtractionClientNumber()); - Assertions.assertEquals( - mockedForm.seedlotFormExtractionDto().extractoryLocnCode(), - seedlot.getExtractionLocationCode()); - Assertions.assertEquals( - mockedForm.seedlotFormExtractionDto().extractionStDate(), seedlot.getExtractionStartDate()); - Assertions.assertEquals( - mockedForm.seedlotFormExtractionDto().extractionEndDate(), seedlot.getExtractionEndDate()); - Assertions.assertEquals( - mockedForm.seedlotFormExtractionDto().storageClientNumber(), - seedlot.getStorageClientNumber()); - Assertions.assertEquals( - mockedForm.seedlotFormExtractionDto().storageLocnCode(), seedlot.getStorageLocationCode()); - Assertions.assertEquals( - mockedForm.seedlotFormExtractionDto().temporaryStrgStartDate(), - seedlot.getTemporaryStorageStartDate()); - Assertions.assertEquals( - mockedForm.seedlotFormExtractionDto().temporaryStrgEndDate(), - seedlot.getTemporaryStorageEndDate()); - // Area of use test - assertEquals(areaOfUseSpuGeoDto.getElevationMax(), seedlot.getElevationMax()); - assertEquals(areaOfUseSpuGeoDto.getElevationMin(), seedlot.getElevationMin()); - assertEquals(geospatialRespondDto.getMeanLatitudeDegree(), seedlot.getLatitudeDegMax()); - assertEquals(geospatialRespondDto.getMeanLatitudeDegree(), seedlot.getLatitudeDegMin()); - assertEquals(areaOfUseSpuGeoDto.getLatitudeMinutesMax(), seedlot.getLatitudeMinMax()); - assertEquals(areaOfUseSpuGeoDto.getLatitudeMinutesMin(), seedlot.getLatitudeMinMin()); - assertEquals(0, seedlot.getLatitudeSecMax()); - assertEquals(0, seedlot.getLatitudeSecMin()); - assertEquals(geospatialRespondDto.getMeanLongitudeDegree(), seedlot.getLongitudeDegMax()); - assertEquals(geospatialRespondDto.getMeanLongitudeDegree(), seedlot.getLongitudeDegMin()); - assertEquals(geospatialRespondDto.getMeanLongitudeMinute(), seedlot.getLongitudeMinMax()); - assertEquals(geospatialRespondDto.getMeanLongitudeMinute(), seedlot.getLongitudeMinMin()); - assertEquals(0, seedlot.getLongitudeSecMax()); - assertEquals(0, seedlot.getLongitudeSecMin()); - // BEC values - assertEquals(oracleOrchardRet.becZoneCode(), seedlot.getBgcZoneCode()); - assertEquals(oracleOrchardRet.becZoneDescription(), seedlot.getBgcZoneDescription()); - assertEquals(oracleOrchardRet.becSubzoneCode(), seedlot.getBgcSubzoneCode()); - assertEquals(oracleOrchardRet.variant(), seedlot.getVariant()); - assertEquals(oracleOrchardRet.becVersionId(), seedlot.getBecVersionId()); - // Declared Seedlot Value - assertEquals( - loggedUserService.getLoggedUserId(), seedlot.getDeclarationOfTrueInformationUserId()); - assertTrue( - LocalDateTime.now() - .minusSeconds(15L) - .isBefore(seedlot.getDeclarationOfTrueInformationTimestamp())); - } + // TODO + // @Test + // @DisplayName("Seedlot form submit - Success") + // void submitSeedlotForm_happyPath_shouldSucceed() { + // Seedlot seedlot = new Seedlot("5432"); + // SeedlotStatusEntity seedlotStatus = new SeedlotStatusEntity(); + // seedlotStatus.setSeedlotStatusCode("PND"); + // seedlot.setSeedlotStatus(seedlotStatus); + + // SeedlotSourceEntity seedSource = new SeedlotSourceEntity(); + // seedSource.setSeedlotSourceCode("UNT"); + // seedlot.setSeedlotSource(seedSource); + // when(seedlotRepository.findById("5432")).thenReturn(Optional.of(seedlot)); + + // doNothing() + // .when(seedlotCollectionMethodService) + // .saveSeedlotFormStep1(any(), any(), anyBoolean()); + // when(seedlotOwnerQuantityService.saveSeedlotFormStep2(any(), any(), anyBoolean())) + // .thenReturn(List.of()); + // doNothing().when(seedlotOrchardService).saveSeedlotFormStep4(any(), any(), anyBoolean()); + // when(seedlotParentTreeService.saveSeedlotFormStep5(any(), any(), anyBoolean())) + // .thenReturn(List.of()); + // doNothing().when(seedlotParentTreeGeneticQualityService).saveSeedlotFormStep5(any(), + // any()); + // when(smpMixService.saveSeedlotFormStep5(any(), any())).thenReturn(List.of()); + // doNothing().when(smpMixGeneticQualityService).saveSeedlotFormStep5(any(), any()); + // doNothing() + // .when(seedlotParentTreeSmpMixService) + // .saveSeedlotFormStep5(any(), any(), anyBoolean()); + + // SeedlotStatusEntity ssEntity = new SeedlotStatusEntity(); + // ssEntity.setSeedlotStatusCode("SUB"); + // when(seedlotStatusService.getValidSeedlotStatus(any())).thenReturn(Optional.of(ssEntity)); + + // // Parent tree contribution mock + // CalculatedParentTreeValsDto caculatedParentTreeValsDto = new CalculatedParentTreeValsDto(); + // caculatedParentTreeValsDto.setNeValue(BigDecimal.valueOf(0)); + // GeospatialRespondDto geospatialRespondDto = + // new GeospatialRespondDto( + // 120, 12, 0, 23, 4, 0, BigDecimal.valueOf(120.22), BigDecimal.valueOf(23.44), 750); + // caculatedParentTreeValsDto.setGeospatialData(geospatialRespondDto); + // PtCalculationResDto ptCalculationResDto = + // new PtCalculationResDto(List.of(), caculatedParentTreeValsDto, geospatialRespondDto); + // when(parentTreeService.calculatePtVals(any())).thenReturn(ptCalculationResDto); + + // SeedlotFormSubmissionDto mockedForm = mockSeedlotFormDto(null, null); + + // // Set area of use mocks + // int activeSpuId = 3; + // String primaryOrchardId = mockedForm.seedlotFormOrchardDto().primaryOrchardId(); + // Optional activeSpuOptional = + // Optional.of(new ActiveOrchardSpuEntity(primaryOrchardId, activeSpuId, true, false, + // false)); + // when(orchardService.findSpuIdByOrchardWithActive(primaryOrchardId, true)) + // .thenReturn(activeSpuOptional); + // when(orchardService.findSpuIdByOrchard(primaryOrchardId)).thenReturn(activeSpuOptional); + + // AreaOfUseDto areaOfUseDto = new AreaOfUseDto(); + // AreaOfUseSpuGeoDto areaOfUseSpuGeoDto = new AreaOfUseSpuGeoDto(1, 100, null, null, 3, 5); + // areaOfUseDto.setAreaOfUseSpuGeoDto(areaOfUseSpuGeoDto); + + // SpzDto spzDto1 = new SpzDto("GL", "Georgia Lowlands", false); + // SpzDto spzDto2 = new SpzDto("M", "Maritime", true); + // List spzList = List.of(spzDto1, spzDto2); + // areaOfUseDto.setSpzList(spzList); + + // OrchardDto oracleOrchardRet = + // new OrchardDto( + // primaryOrchardId, + // "Primary Orchard", + // seedlot.getVegetationCode(), + // 'S', + // "Seed Lot", + // "PRD", + // "SBS", + // "Sub-Boreal Spruce", + // "mk", + // '1', + // 5); + // when(oracleApiProvider.findOrchardById(primaryOrchardId)) + // .thenReturn(Optional.of(oracleOrchardRet)); + + // + // when(oracleApiProvider.getAreaOfUseData(activeSpuId)).thenReturn(Optional.of(areaOfUseDto)); + + // Optional genClassOptional = Optional.of(new GeneticClassEntity()); + // when(geneticClassRepository.findById("A")).thenReturn(genClassOptional); + + // when(loggedUserService.getLoggedUserId()).thenReturn("meatball@Pasta"); + + // when(seedlotSeedPlanZoneRepository.saveAll(any())).thenReturn(List.of()); + + // SeedlotStatusResponseDto scDto = + // seedlotService.updateSeedlotWithForm("5432", mockedForm, false, true, "SUB"); + + // Assertions.assertNotNull(scDto); + // Assertions.assertEquals("5432", scDto.seedlotNumber()); + // Assertions.assertEquals("SUB", scDto.seedlotStatusCode()); + + // Assertions.assertEquals( + // mockedForm.seedlotFormInterimDto().intermStrgClientNumber(), + // seedlot.getInterimStorageClientNumber()); + // Assertions.assertEquals( + // mockedForm.seedlotFormInterimDto().intermStrgLocnCode(), + // seedlot.getInterimStorageLocationCode()); + // Assertions.assertEquals( + // mockedForm.seedlotFormInterimDto().intermStrgStDate(), + // seedlot.getInterimStorageStartDate()); + // Assertions.assertEquals( + // mockedForm.seedlotFormInterimDto().intermStrgEndDate(), + // seedlot.getInterimStorageEndDate()); + // Assertions.assertEquals( + // mockedForm.seedlotFormInterimDto().intermOtherFacilityDesc(), + // seedlot.getInterimStorageOtherFacilityDesc()); + // Assertions.assertEquals( + // mockedForm.seedlotFormInterimDto().intermFacilityCode(), + // seedlot.getInterimStorageFacilityCode()); + // Assertions.assertEquals( + // mockedForm.seedlotFormExtractionDto().extractoryClientNumber(), + // seedlot.getExtractionClientNumber()); + // Assertions.assertEquals( + // mockedForm.seedlotFormExtractionDto().extractoryLocnCode(), + // seedlot.getExtractionLocationCode()); + // Assertions.assertEquals( + // mockedForm.seedlotFormExtractionDto().extractionStDate(), + // seedlot.getExtractionStartDate()); + // Assertions.assertEquals( + // mockedForm.seedlotFormExtractionDto().extractionEndDate(), + // seedlot.getExtractionEndDate()); + // Assertions.assertEquals( + // mockedForm.seedlotFormExtractionDto().storageClientNumber(), + // seedlot.getStorageClientNumber()); + // Assertions.assertEquals( + // mockedForm.seedlotFormExtractionDto().storageLocnCode(), + // seedlot.getStorageLocationCode()); + // Assertions.assertEquals( + // mockedForm.seedlotFormExtractionDto().temporaryStrgStartDate(), + // seedlot.getTemporaryStorageStartDate()); + // Assertions.assertEquals( + // mockedForm.seedlotFormExtractionDto().temporaryStrgEndDate(), + // seedlot.getTemporaryStorageEndDate()); + // // Area of use test + // assertEquals(areaOfUseSpuGeoDto.getElevationMax(), seedlot.getElevationMax()); + // assertEquals(areaOfUseSpuGeoDto.getElevationMin(), seedlot.getElevationMin()); + // assertEquals(geospatialRespondDto.getMeanLatitudeDegree(), seedlot.getLatitudeDegMax()); + // assertEquals(geospatialRespondDto.getMeanLatitudeDegree(), seedlot.getLatitudeDegMin()); + // assertEquals(areaOfUseSpuGeoDto.getLatitudeMinutesMax(), seedlot.getLatitudeMinMax()); + // assertEquals(areaOfUseSpuGeoDto.getLatitudeMinutesMin(), seedlot.getLatitudeMinMin()); + // assertEquals(0, seedlot.getLatitudeSecMax()); + // assertEquals(0, seedlot.getLatitudeSecMin()); + // assertEquals(geospatialRespondDto.getMeanLongitudeDegree(), seedlot.getLongitudeDegMax()); + // assertEquals(geospatialRespondDto.getMeanLongitudeDegree(), seedlot.getLongitudeDegMin()); + // assertEquals(geospatialRespondDto.getMeanLongitudeMinute(), seedlot.getLongitudeMinMax()); + // assertEquals(geospatialRespondDto.getMeanLongitudeMinute(), seedlot.getLongitudeMinMin()); + // assertEquals(0, seedlot.getLongitudeSecMax()); + // assertEquals(0, seedlot.getLongitudeSecMin()); + // // BEC values + // assertEquals(oracleOrchardRet.becZoneCode(), seedlot.getBgcZoneCode()); + // assertEquals(oracleOrchardRet.becZoneDescription(), seedlot.getBgcZoneDescription()); + // assertEquals(oracleOrchardRet.becSubzoneCode(), seedlot.getBgcSubzoneCode()); + // assertEquals(oracleOrchardRet.variant(), seedlot.getVariant()); + // assertEquals(oracleOrchardRet.becVersionId(), seedlot.getBecVersionId()); + // // Declared Seedlot Value + // assertEquals( + // loggedUserService.getLoggedUserId(), seedlot.getDeclarationOfTrueInformationUserId()); + // assertTrue( + // LocalDateTime.now(Clock.systemUTC()) + // .minusSeconds(15L) + // .isBefore(seedlot.getDeclarationOfTrueInformationTimestamp())); + // } } diff --git a/backend/src/test/java/ca/bc/gov/backendstartapi/service/SeedlotOwnerQuantityServiceTest.java b/backend/src/test/java/ca/bc/gov/backendstartapi/service/SeedlotOwnerQuantityServiceTest.java index d6d18f7cb..646bb461f 100644 --- a/backend/src/test/java/ca/bc/gov/backendstartapi/service/SeedlotOwnerQuantityServiceTest.java +++ b/backend/src/test/java/ca/bc/gov/backendstartapi/service/SeedlotOwnerQuantityServiceTest.java @@ -71,13 +71,13 @@ void saveSeedlotFormStep2_firstSubmit_shouldSucceed() { MethodOfPaymentEntity mope = new MethodOfPaymentEntity(); mope.setMethodOfPaymentCode("CLA"); - when(methodOfPaymentService.getAllValidMethodOfPayments()).thenReturn(List.of(mope)); + when(methodOfPaymentService.getAllMethodsByCodeList(List.of("CLA"))).thenReturn(List.of(mope)); AuditInformation audit = new AuditInformation("userId"); when(loggedUserService.createAuditCurrentUser()).thenReturn(audit); Seedlot seedlot = new Seedlot("54321"); - SeedlotOwnerQuantity soq = new SeedlotOwnerQuantity(seedlot, "00012797", "02"); + SeedlotOwnerQuantity soq = new SeedlotOwnerQuantity(seedlot, "00012797", "02", mope); when(seedlotOwnerQuantityRepository.saveAll(any())).thenReturn(List.of(soq)); List soqList = @@ -90,22 +90,22 @@ void saveSeedlotFormStep2_firstSubmit_shouldSucceed() { @Test @DisplayName("Save Seedlot Owner Quantity with one new method") void saveSeedlotFormStep2_updateSeedlotAdd_shouldSucceed() { - Seedlot seedlot = new Seedlot("54321"); - ConeCollectionMethodEntity ccme1 = new ConeCollectionMethodEntity(); ccme1.setConeCollectionMethodCode(1); - SeedlotOwnerQuantity soq = new SeedlotOwnerQuantity(seedlot, "00012797", "01"); - when(seedlotOwnerQuantityRepository.findAllBySeedlot_id("54321")).thenReturn(List.of(soq)); - MethodOfPaymentEntity mope = new MethodOfPaymentEntity(); mope.setMethodOfPaymentCode("CLA"); - when(methodOfPaymentService.getAllValidMethodOfPayments()).thenReturn(List.of(mope)); + when(methodOfPaymentService.getAllMethodsByCodeList(List.of("CLA"))).thenReturn(List.of(mope)); + + Seedlot seedlot = new Seedlot("54321"); + + SeedlotOwnerQuantity soq = new SeedlotOwnerQuantity(seedlot, "00012797", "01", mope); + when(seedlotOwnerQuantityRepository.findAllBySeedlot_id("54321")).thenReturn(List.of(soq)); AuditInformation audit = new AuditInformation("userId"); when(loggedUserService.createAuditCurrentUser()).thenReturn(audit); - SeedlotOwnerQuantity soq2 = new SeedlotOwnerQuantity(seedlot, "00012797", "02"); + SeedlotOwnerQuantity soq2 = new SeedlotOwnerQuantity(seedlot, "00012797", "02", mope); when(seedlotOwnerQuantityRepository.saveAll(any())).thenReturn(List.of(soq, soq2)); List soqList = @@ -118,19 +118,19 @@ void saveSeedlotFormStep2_updateSeedlotAdd_shouldSucceed() { @Test @DisplayName("Save Seedlot Owner Quantity with two already existing") void saveSeedlotFormStep2_updateSeedlotEqual_shouldSucceed() { - Seedlot seedlot = new Seedlot("54321"); - ConeCollectionMethodEntity ccme1 = new ConeCollectionMethodEntity(); ccme1.setConeCollectionMethodCode(1); - SeedlotOwnerQuantity soq = new SeedlotOwnerQuantity(seedlot, "00012797", "01"); - SeedlotOwnerQuantity soq2 = new SeedlotOwnerQuantity(seedlot, "00012797", "02"); - when(seedlotOwnerQuantityRepository.findAllBySeedlot_id("54321")) - .thenReturn(List.of(soq, soq2)); - MethodOfPaymentEntity mope = new MethodOfPaymentEntity(); mope.setMethodOfPaymentCode("CLA"); - when(methodOfPaymentService.getAllValidMethodOfPayments()).thenReturn(List.of(mope)); + when(methodOfPaymentService.getAllMethodsByCodeList(List.of("CLA"))).thenReturn(List.of(mope)); + + Seedlot seedlot = new Seedlot("54321"); + + SeedlotOwnerQuantity soq = new SeedlotOwnerQuantity(seedlot, "00012797", "01", mope); + SeedlotOwnerQuantity soq2 = new SeedlotOwnerQuantity(seedlot, "00012797", "02", mope); + when(seedlotOwnerQuantityRepository.findAllBySeedlot_id("54321")) + .thenReturn(List.of(soq, soq2)); AuditInformation audit = new AuditInformation("userId"); when(loggedUserService.createAuditCurrentUser()).thenReturn(audit); @@ -146,19 +146,19 @@ void saveSeedlotFormStep2_updateSeedlotEqual_shouldSucceed() { @Test @DisplayName("Save Seedlot Owner Quantity with one method removed") void saveSeedlotFormStep2_updateSeedlotRemove_shouldSucceed() { - Seedlot seedlot = new Seedlot("54321"); - ConeCollectionMethodEntity ccme1 = new ConeCollectionMethodEntity(); ccme1.setConeCollectionMethodCode(1); - SeedlotOwnerQuantity soq = new SeedlotOwnerQuantity(seedlot, "00012797", "01"); - SeedlotOwnerQuantity soq2 = new SeedlotOwnerQuantity(seedlot, "00012797", "02"); - when(seedlotOwnerQuantityRepository.findAllBySeedlot_id("54321")) - .thenReturn(List.of(soq, soq2)); - MethodOfPaymentEntity mope = new MethodOfPaymentEntity(); mope.setMethodOfPaymentCode("CLA"); - when(methodOfPaymentService.getAllValidMethodOfPayments()).thenReturn(List.of(mope)); + when(methodOfPaymentService.getAllMethodsByCodeList(List.of("CLA"))).thenReturn(List.of(mope)); + + Seedlot seedlot = new Seedlot("54321"); + + SeedlotOwnerQuantity soq = new SeedlotOwnerQuantity(seedlot, "00012797", "01", mope); + SeedlotOwnerQuantity soq2 = new SeedlotOwnerQuantity(seedlot, "00012797", "02", mope); + when(seedlotOwnerQuantityRepository.findAllBySeedlot_id("54321")) + .thenReturn(List.of(soq, soq2)); AuditInformation audit = new AuditInformation("userId"); when(loggedUserService.createAuditCurrentUser()).thenReturn(audit); diff --git a/backend/src/test/java/ca/bc/gov/backendstartapi/service/SeedlotParentTreeServiceTest.java b/backend/src/test/java/ca/bc/gov/backendstartapi/service/SeedlotParentTreeServiceTest.java index 461184ffa..c27a0d446 100644 --- a/backend/src/test/java/ca/bc/gov/backendstartapi/service/SeedlotParentTreeServiceTest.java +++ b/backend/src/test/java/ca/bc/gov/backendstartapi/service/SeedlotParentTreeServiceTest.java @@ -43,7 +43,7 @@ class SeedlotParentTreeServiceTest { private SeedlotFormParentTreeSmpDto createFormDto(Integer parentTreeId) { ParentTreeGeneticQualityDto ptgqDto = - new ParentTreeGeneticQualityDto("BV", "GVO", new BigDecimal("18")); + new ParentTreeGeneticQualityDto("BV", "GVO", new BigDecimal("18"), null, null); return new SeedlotFormParentTreeSmpDto( "85", parentTreeId, diff --git a/backend/src/test/java/ca/bc/gov/backendstartapi/service/SeedlotParentTreeSmpMixServiceTest.java b/backend/src/test/java/ca/bc/gov/backendstartapi/service/SeedlotParentTreeSmpMixServiceTest.java new file mode 100644 index 000000000..9ac652e87 --- /dev/null +++ b/backend/src/test/java/ca/bc/gov/backendstartapi/service/SeedlotParentTreeSmpMixServiceTest.java @@ -0,0 +1,136 @@ +package ca.bc.gov.backendstartapi.service; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import ca.bc.gov.backendstartapi.dao.GeneticWorthEntityDao; +import ca.bc.gov.backendstartapi.dto.ParentTreeGeneticQualityDto; +import ca.bc.gov.backendstartapi.dto.SeedlotFormParentTreeSmpDto; +import ca.bc.gov.backendstartapi.entity.GeneticWorthEntity; +import ca.bc.gov.backendstartapi.entity.SeedlotParentTree; +import ca.bc.gov.backendstartapi.entity.SeedlotParentTreeSmpMix; +import ca.bc.gov.backendstartapi.entity.embeddable.AuditInformation; +import ca.bc.gov.backendstartapi.entity.embeddable.EffectiveDateRange; +import ca.bc.gov.backendstartapi.entity.seedlot.Seedlot; +import ca.bc.gov.backendstartapi.repository.SeedlotParentTreeSmpMixRepository; +import ca.bc.gov.backendstartapi.security.LoggedUserService; +import java.math.BigDecimal; +import java.time.LocalDate; +import java.util.List; +import java.util.Optional; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.springframework.test.context.junit.jupiter.SpringExtension; + +@ExtendWith(SpringExtension.class) +class SeedlotParentTreeSmpMixServiceTest { + + @Mock SeedlotParentTreeSmpMixRepository seedlotParentTreeSmpMixRepository; + + @Mock SeedlotParentTreeService seedlotParentTreeService; + + @Mock GeneticWorthEntityDao geneticWorthEntityDao; + + @Mock LoggedUserService loggedUserService; + + private SeedlotParentTreeSmpMixService seedlotParentTreeSmpMixService; + + @BeforeEach + void setup() { + this.seedlotParentTreeSmpMixService = + new SeedlotParentTreeSmpMixService( + seedlotParentTreeSmpMixRepository, + seedlotParentTreeService, + geneticWorthEntityDao, + loggedUserService); + } + + @Test + @DisplayName("Save seedlot form step 5 happy path should succeed") + void saveSeedlotFormStep5_happyPath_shouldSucceed() { + String seedlotNumber = "63111"; + Seedlot seedlot = new Seedlot(seedlotNumber); + + Integer parentTreeId = 20012; + String parentTreeNumber = "29"; + BigDecimal coneCount = new BigDecimal("500"); + BigDecimal pollenCount = new BigDecimal("1500"); + Integer smpSuccessPct = 25; + Integer nonOrchardPollenContamPct = 0; + Integer amountOfMaterial = 4000; + BigDecimal proportion = new BigDecimal("0.00125"); + + ParentTreeGeneticQualityDto quality = + new ParentTreeGeneticQualityDto("BV", "GVO", new BigDecimal("11"), true, true); + + SeedlotParentTree parentTree = + new SeedlotParentTree( + seedlot, + parentTreeId, + parentTreeNumber, + coneCount, + pollenCount, + new AuditInformation("test")); + when(seedlotParentTreeService.getAllSeedlotParentTree(seedlotNumber)) + .thenReturn(List.of(parentTree)); + + EffectiveDateRange dateRange = + new EffectiveDateRange(LocalDate.now(), LocalDate.now().plusYears(1L)); + GeneticWorthEntity genWorth = new GeneticWorthEntity("GVO", "GVO", dateRange, BigDecimal.ZERO); + when(geneticWorthEntityDao.getGeneticWorthEntity("GVO")).thenReturn(Optional.of(genWorth)); + + when(loggedUserService.createAuditCurrentUser()).thenReturn(new AuditInformation("test")); + + when(seedlotParentTreeSmpMixRepository.saveAll(any())).thenReturn(List.of()); + + SeedlotFormParentTreeSmpDto formDto = + new SeedlotFormParentTreeSmpDto( + seedlotNumber, + parentTreeId, + parentTreeNumber, + coneCount, + pollenCount, + smpSuccessPct, + nonOrchardPollenContamPct, + amountOfMaterial, + proportion, + List.of(quality)); + + seedlotParentTreeSmpMixService.saveSeedlotFormStep5(seedlot, List.of(formDto), true); + + verify(seedlotParentTreeSmpMixRepository, times(1)).saveAll(any()); + } + + @Test + @DisplayName("Get all by seedlot number happy path should succeed") + void getAllBySeedlotNumber_happyPath_shouldSucceed() { + String seedlotNumber = "123"; + + SeedlotParentTree seedlotParentTree = mock(SeedlotParentTree.class); + GeneticWorthEntity geneticWorthEntity = mock(GeneticWorthEntity.class); + geneticWorthEntity.setGeneticWorthCode("GVO"); + SeedlotParentTreeSmpMix smpMix = + new SeedlotParentTreeSmpMix( + seedlotParentTree, + "BV", + geneticWorthEntity, + new BigDecimal("15"), + new AuditInformation("test")); + when(seedlotParentTreeSmpMixRepository.findAllBySeedlotParentTree_Seedlot_id(seedlotNumber)) + .thenReturn(List.of(smpMix)); + + List list = + seedlotParentTreeSmpMixService.getAllBySeedlotNumber(seedlotNumber); + + Assertions.assertNotNull(list); + Assertions.assertFalse(list.isEmpty()); + Assertions.assertEquals(1, list.size()); + } +} diff --git a/backend/src/test/java/ca/bc/gov/backendstartapi/service/SeedlotServiceTest.java b/backend/src/test/java/ca/bc/gov/backendstartapi/service/SeedlotServiceTest.java index 7e3f93e1e..8c208c9c8 100644 --- a/backend/src/test/java/ca/bc/gov/backendstartapi/service/SeedlotServiceTest.java +++ b/backend/src/test/java/ca/bc/gov/backendstartapi/service/SeedlotServiceTest.java @@ -49,24 +49,24 @@ import ca.bc.gov.backendstartapi.entity.seedlot.SeedlotOrchard; import ca.bc.gov.backendstartapi.entity.seedlot.SeedlotOwnerQuantity; import ca.bc.gov.backendstartapi.exception.ClientIdForbiddenException; +import ca.bc.gov.backendstartapi.exception.GeneticClassNotFoundException; import ca.bc.gov.backendstartapi.exception.InvalidSeedlotRequestException; import ca.bc.gov.backendstartapi.exception.SeedlotConflictDataException; import ca.bc.gov.backendstartapi.exception.SeedlotNotFoundException; import ca.bc.gov.backendstartapi.exception.SeedlotSourceNotFoundException; +import ca.bc.gov.backendstartapi.exception.SeedlotStatusNotFoundException; import ca.bc.gov.backendstartapi.provider.Provider; import ca.bc.gov.backendstartapi.repository.GeneticClassRepository; -import ca.bc.gov.backendstartapi.repository.SeedlotCollectionMethodRepository; -import ca.bc.gov.backendstartapi.repository.SeedlotOwnerQuantityRepository; import ca.bc.gov.backendstartapi.repository.SeedlotRepository; import ca.bc.gov.backendstartapi.repository.SeedlotSeedPlanZoneRepository; import ca.bc.gov.backendstartapi.repository.SeedlotSourceRepository; -import ca.bc.gov.backendstartapi.repository.SeedlotStatusRepository; import ca.bc.gov.backendstartapi.security.LoggedUserService; import ca.bc.gov.backendstartapi.security.UserInfo; import java.math.BigDecimal; import java.time.LocalDate; import java.util.List; import java.util.Optional; +import java.util.stream.Collectors; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; @@ -84,20 +84,14 @@ class SeedlotServiceTest { @Mock SeedlotSourceRepository seedlotSourceRepository; - @Mock SeedlotStatusRepository seedlotStatusRepository; - @Mock GeneticClassRepository geneticClassRepository; @Mock LoggedUserService loggedUserService; @Mock SeedlotCollectionMethodService seedlotCollectionMethodService; - @Mock SeedlotCollectionMethodRepository seedlotCollectionMethodRepository; - @Mock SeedlotOwnerQuantityService seedlotOwnerQuantityService; - @Mock SeedlotOwnerQuantityRepository seedlotOwnerQuantityRepository; - @Mock SeedlotOrchardService seedlotOrchardService; @Mock SeedlotParentTreeService seedlotParentTreeService; @@ -139,7 +133,7 @@ private SeedlotCreateDto createSeedlotDto() { } private ParentTreeGeneticQualityDto createParentTreeGenQuaDto() { - return new ParentTreeGeneticQualityDto("BV", "GVO", new BigDecimal("18")); + return new ParentTreeGeneticQualityDto("BV", "GVO", new BigDecimal("18"), null, null); } private SeedlotFormParentTreeSmpDto createParentTreeDto(Integer parentTreeId) { @@ -163,13 +157,10 @@ void setup() { new SeedlotService( seedlotRepository, seedlotSourceRepository, - seedlotStatusRepository, geneticClassRepository, loggedUserService, seedlotCollectionMethodService, - seedlotCollectionMethodRepository, seedlotOwnerQuantityService, - seedlotOwnerQuantityRepository, seedlotOrchardService, seedlotParentTreeService, seedlotParentTreeGeneticQualityService, @@ -189,9 +180,11 @@ void setup() { @DisplayName("createSeedlotSuccessTest") void createSeedlotTest_happyPath_shouldSucceed() { when(seedlotRepository.findNextSeedlotNumber(anyInt(), anyInt())).thenReturn(63000); + String incStatusCode = "INC"; - SeedlotStatusEntity statusEntity = new SeedlotStatusEntity("PND", "Pending", DATE_RANGE); - when(seedlotStatusRepository.findById("PND")).thenReturn(Optional.of(statusEntity)); + SeedlotStatusEntity incStatusEntity = + new SeedlotStatusEntity(incStatusCode, "Incomplete", DATE_RANGE); + when(seedlotStatusService.findById(incStatusCode)).thenReturn(Optional.of(incStatusEntity)); SeedlotSourceEntity sourceEntity = new SeedlotSourceEntity("TPT", "Tested Parent Trees", DATE_RANGE, null); @@ -233,34 +226,31 @@ void createSeedlotTest_bClassSeedlot_shouldThrowException() { void createSeedlotTest_noSeedlotStatus_shouldThrowException() { when(seedlotRepository.findNextSeedlotNumber(anyInt(), anyInt())).thenReturn(63000); - when(seedlotStatusRepository.findById("PND")).thenReturn(Optional.empty()); - - Exception exc = - Assertions.assertThrows( - InvalidSeedlotRequestException.class, - () -> { - seedlotService.createSeedlot(createSeedlotDto()); - }); + when(seedlotStatusService.findById(any())).thenReturn(Optional.empty()); - Assertions.assertEquals(BAD_REQUEST_STR, exc.getMessage()); + Assertions.assertThrows( + SeedlotStatusNotFoundException.class, + () -> { + seedlotService.createSeedlot(createSeedlotDto()); + }); } @Test @DisplayName("createSeedlotAClassWithoutGeneticClassEntity") void createSeedlotTest_noGeneticClass_shouldThrowException() { - SeedlotStatusEntity statusEntity = new SeedlotStatusEntity("PND", "Pending", DATE_RANGE); - when(seedlotStatusRepository.findById("PND")).thenReturn(Optional.of(statusEntity)); + String incStatusCode = "INC"; - when(geneticClassRepository.findById("A")).thenReturn(Optional.empty()); + SeedlotStatusEntity incStatusEntity = + new SeedlotStatusEntity(incStatusCode, "Incomplete", DATE_RANGE); + when(seedlotStatusService.findById(incStatusCode)).thenReturn(Optional.of(incStatusEntity)); - Exception exc = - Assertions.assertThrows( - InvalidSeedlotRequestException.class, - () -> { - seedlotService.createSeedlot(createSeedlotDto()); - }); + when(geneticClassRepository.findById("A")).thenReturn(Optional.empty()); - Assertions.assertEquals(BAD_REQUEST_STR, exc.getMessage()); + Assertions.assertThrows( + GeneticClassNotFoundException.class, + () -> { + seedlotService.createSeedlot(createSeedlotDto()); + }); } @Test @@ -268,22 +258,22 @@ void createSeedlotTest_noGeneticClass_shouldThrowException() { void createSeedlotTest_noSeedlotSource_shouldThrowException() { when(seedlotRepository.findNextSeedlotNumber(anyInt(), anyInt())).thenReturn(63000); - SeedlotStatusEntity statusEntity = new SeedlotStatusEntity("PND", "Pending", DATE_RANGE); - when(seedlotStatusRepository.findById("PND")).thenReturn(Optional.of(statusEntity)); + String incStatusCode = "INC"; + + SeedlotStatusEntity incStatusEntity = + new SeedlotStatusEntity(incStatusCode, "Incomplete", DATE_RANGE); + when(seedlotStatusService.findById(incStatusCode)).thenReturn(Optional.of(incStatusEntity)); GeneticClassEntity classEntity = new GeneticClassEntity("A", "A class seedlot", DATE_RANGE); when(geneticClassRepository.findById("A")).thenReturn(Optional.of(classEntity)); when(seedlotSourceRepository.findById("TPT")).thenReturn(Optional.empty()); - Exception exc = - Assertions.assertThrows( - InvalidSeedlotRequestException.class, - () -> { - seedlotService.createSeedlot(createSeedlotDto()); - }); - - Assertions.assertEquals(BAD_REQUEST_STR, exc.getMessage()); + Assertions.assertThrows( + SeedlotSourceNotFoundException.class, + () -> { + seedlotService.createSeedlot(createSeedlotDto()); + }); } @Test @@ -375,10 +365,13 @@ void findSingleSeedlotSuccessTest() { when(seedlotSeedPlanZoneRepository.findAllBySeedlot_id(seedlotId)) .thenReturn(List.of(spzEntity)); when(seedlotOrchardService.getPrimarySeedlotOrchard(seedlotId)).thenReturn(Optional.empty()); + BigDecimal defaultBv = BigDecimal.ZERO; SeedlotGeneticWorth seedlotGenWor = new SeedlotGeneticWorth( - seedlotEntity, new GeneticWorthEntity("GVO", "", null), new AuditInformation("userId")); + seedlotEntity, + new GeneticWorthEntity("GVO", "", null, defaultBv), + new AuditInformation("userId")); seedlotGenWor.setGeneticQualityValue(new BigDecimal("18")); seedlotGenWor.setTestedParentTreeContributionPercentage(new BigDecimal("88")); @@ -502,7 +495,7 @@ void findAclassSeedlotFormFullDataSuccessTest() { new SeedlotParentTreeGeneticQuality( spt, sptgqDto.geneticTypeCode(), - new GeneticWorthEntity(sptgqDto.geneticWorthCode(), "", null), + new GeneticWorthEntity(sptgqDto.geneticWorthCode(), "", null, BigDecimal.ZERO), sptgqDto.geneticQualityValue(), audit); @@ -524,7 +517,7 @@ void findAclassSeedlotFormFullDataSuccessTest() { new SeedlotParentTreeSmpMix( spt, sptgqDto.geneticTypeCode(), - new GeneticWorthEntity(sptgqDto.geneticWorthCode(), "", null), + new GeneticWorthEntity(sptgqDto.geneticWorthCode(), "", null, BigDecimal.ZERO), sptgqDto.geneticQualityValue(), audit); @@ -532,18 +525,21 @@ void findAclassSeedlotFormFullDataSuccessTest() { SeedlotGeneticWorth seedlotGenWor = new SeedlotGeneticWorth( - seedlotEntity, new GeneticWorthEntity(sptgqDto.geneticWorthCode(), "", null), audit); + seedlotEntity, + new GeneticWorthEntity(sptgqDto.geneticWorthCode(), "", null, BigDecimal.ZERO), + audit); final List genWorthData = List.of(seedlotGenWor); final List collectionMethods = List.of(new SeedlotCollectionMethod(seedlotEntity, new ConeCollectionMethodEntity())); + MethodOfPaymentEntity mope = new MethodOfPaymentEntity(methodOfPayment, "", null); SeedlotOwnerQuantity seedlotOwners = - new SeedlotOwnerQuantity(seedlotEntity, ownerNumber, ownerLoc); + new SeedlotOwnerQuantity(seedlotEntity, ownerNumber, ownerLoc, mope); BigDecimal originalPercentOwned = new BigDecimal(100); seedlotOwners.setOriginalPercentageOwned(originalPercentOwned); - seedlotOwners.setMethodOfPayment(new MethodOfPaymentEntity(methodOfPayment, "", null)); + seedlotOwners.setMethodOfPayment(mope); String orchardId = "100"; @@ -561,9 +557,14 @@ void findAclassSeedlotFormFullDataSuccessTest() { when(seedlotParentTreeSmpMixService.getAllBySeedlotNumber(seedlotNumber)) .thenReturn(parentTreeSmpMixData); when(seedlotGeneticWorthService.getAllBySeedlotNumber(seedlotNumber)).thenReturn(genWorthData); - when(seedlotCollectionMethodRepository.findAllBySeedlot_id(seedlotNumber)) - .thenReturn(collectionMethods); - when(seedlotOwnerQuantityRepository.findAllBySeedlot_id(seedlotNumber)) + + when(seedlotCollectionMethodService.getAllSeedlotCollectionMethodsBySeedlot(seedlotNumber)) + .thenReturn( + collectionMethods.stream() + .map(col -> col.getConeCollectionMethod().getConeCollectionMethodCode()) + .collect(Collectors.toList())); + + when(seedlotOwnerQuantityService.findAllBySeedlot(seedlotNumber)) .thenReturn(List.of(seedlotOwners)); when(seedlotOrchardService.getAllSeedlotOrchardBySeedlotNumber(seedlotNumber)) .thenReturn(seedlotOrchards); diff --git a/backend/src/test/java/ca/bc/gov/backendstartapi/service/SmpMixGeneticQualityServiceTest.java b/backend/src/test/java/ca/bc/gov/backendstartapi/service/SmpMixGeneticQualityServiceTest.java index 4b98f9f83..8cc3cd2cd 100644 --- a/backend/src/test/java/ca/bc/gov/backendstartapi/service/SmpMixGeneticQualityServiceTest.java +++ b/backend/src/test/java/ca/bc/gov/backendstartapi/service/SmpMixGeneticQualityServiceTest.java @@ -69,7 +69,7 @@ void saveSeedlotFormStep5Test() { when(geneticWorthEntityDao.getGeneticWorthEntity("GVO")).thenReturn(Optional.of(genEntity)); ParentTreeGeneticQualityDto qualityDto = - new ParentTreeGeneticQualityDto("BV", "GVO", new BigDecimal("18")); + new ParentTreeGeneticQualityDto("BV", "GVO", new BigDecimal("18"), null, null); SeedlotFormParentTreeSmpDto seedlotFormParentTreeDto = new SeedlotFormParentTreeSmpDto( diff --git a/backend/src/test/java/ca/bc/gov/backendstartapi/service/SmpMixServiceTest.java b/backend/src/test/java/ca/bc/gov/backendstartapi/service/SmpMixServiceTest.java index ac8cbfdd3..42248f962 100644 --- a/backend/src/test/java/ca/bc/gov/backendstartapi/service/SmpMixServiceTest.java +++ b/backend/src/test/java/ca/bc/gov/backendstartapi/service/SmpMixServiceTest.java @@ -31,7 +31,7 @@ class SmpMixServiceTest { private SeedlotFormParentTreeSmpDto createFormDto(Integer parentTreeId) { ParentTreeGeneticQualityDto ptgqDto = - new ParentTreeGeneticQualityDto("BV", "GVO", new BigDecimal("18")); + new ParentTreeGeneticQualityDto("BV", "GVO", new BigDecimal("18"), null, null); return new SeedlotFormParentTreeSmpDto( "85", parentTreeId, diff --git a/backend/src/test/java/ca/bc/gov/backendstartapi/util/ValueUtilTest.java b/backend/src/test/java/ca/bc/gov/backendstartapi/util/ValueUtilTest.java new file mode 100644 index 000000000..e4f24a165 --- /dev/null +++ b/backend/src/test/java/ca/bc/gov/backendstartapi/util/ValueUtilTest.java @@ -0,0 +1,91 @@ +package ca.bc.gov.backendstartapi.util; + +import ca.bc.gov.backendstartapi.entity.seedlot.Seedlot; +import java.math.BigDecimal; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.util.List; +import java.util.Optional; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +class ValueUtilTest { + + @Test + void hasValueFalseTest() { + Assertions.assertFalse(ValueUtil.hasValue(null)); + Assertions.assertFalse(ValueUtil.hasValue("")); + Assertions.assertFalse(ValueUtil.hasValue(" ")); + Assertions.assertFalse(ValueUtil.hasValue(BigDecimal.ZERO)); + Assertions.assertFalse(ValueUtil.hasValue(Integer.valueOf(0))); + Assertions.assertFalse(ValueUtil.hasValue(Character.valueOf(' '))); + Assertions.assertFalse(ValueUtil.hasValue(Long.valueOf(0L))); + Assertions.assertFalse(ValueUtil.hasValue(Double.valueOf(0D))); + Assertions.assertFalse(ValueUtil.hasValue(List.of())); + Assertions.assertFalse(ValueUtil.hasValue(Optional.empty())); + // Class not handled: + Assertions.assertFalse(ValueUtil.hasValue(new Seedlot("1"))); + } + + @Test + void hasValueTrueTest() { + Assertions.assertTrue(ValueUtil.hasValue("any")); + Assertions.assertTrue(ValueUtil.hasValue(BigDecimal.ONE)); + Assertions.assertTrue(ValueUtil.hasValue(BigDecimal.ONE.negate())); + Assertions.assertTrue(ValueUtil.hasValue(Integer.valueOf(155))); + Assertions.assertTrue(ValueUtil.hasValue(Integer.MIN_VALUE)); + Assertions.assertTrue(ValueUtil.hasValue(Character.valueOf('A'))); + Assertions.assertTrue(ValueUtil.hasValue(Character.valueOf('\\'))); + Assertions.assertTrue(ValueUtil.hasValue(Character.valueOf('@'))); + Assertions.assertTrue(ValueUtil.hasValue(LocalDateTime.now())); + Assertions.assertTrue(ValueUtil.hasValue(LocalDate.now())); + Assertions.assertTrue(ValueUtil.hasValue(Long.valueOf(155L))); + Assertions.assertTrue(ValueUtil.hasValue(Long.MIN_VALUE)); + Assertions.assertTrue(ValueUtil.hasValue(Double.valueOf(999.698D))); + Assertions.assertTrue(ValueUtil.hasValue(Double.valueOf(-999.698D))); + Assertions.assertTrue(ValueUtil.hasValue(List.of(11))); + Assertions.assertTrue(ValueUtil.hasValue(List.of("23"))); + Assertions.assertTrue(ValueUtil.hasValue(Boolean.TRUE)); + Assertions.assertTrue(ValueUtil.hasValue(Boolean.FALSE)); + Assertions.assertTrue(ValueUtil.hasValue(Optional.of(123))); + } + + @Test + void isValueEqualFalseTest() { + Assertions.assertFalse(ValueUtil.isValueEqual(null, null)); + Assertions.assertFalse(ValueUtil.isValueEqual(0, "0")); + Assertions.assertFalse(ValueUtil.isValueEqual(Integer.valueOf(99), "99")); + Assertions.assertFalse(ValueUtil.isValueEqual("0", "00")); + Assertions.assertFalse(ValueUtil.isValueEqual(new BigDecimal("11"), new BigDecimal("1"))); + Assertions.assertFalse(ValueUtil.isValueEqual(Integer.valueOf(0), Integer.valueOf(0))); + Assertions.assertFalse(ValueUtil.isValueEqual(Integer.valueOf(0), Integer.valueOf(1))); + Assertions.assertFalse(ValueUtil.isValueEqual(Integer.valueOf(0), 0)); + Assertions.assertFalse(ValueUtil.isValueEqual(Character.valueOf('A'), Character.valueOf(' '))); + Assertions.assertFalse(ValueUtil.isValueEqual(' ', Character.valueOf(' '))); + Assertions.assertFalse(ValueUtil.isValueEqual(LocalDateTime.now(), LocalDateTime.now())); + Assertions.assertFalse( + ValueUtil.isValueEqual(LocalDateTime.now(), LocalDateTime.now().minusSeconds(1L))); + Assertions.assertFalse(ValueUtil.isValueEqual(LocalDate.now(), LocalDate.now().minusDays(1L))); + Assertions.assertFalse(ValueUtil.isValueEqual(Long.valueOf(1L), Long.valueOf(0L))); + Assertions.assertFalse(ValueUtil.isValueEqual(Double.valueOf(1D), Double.valueOf(0D))); + Assertions.assertFalse(ValueUtil.isValueEqual(Boolean.FALSE, Boolean.TRUE)); + } + + @Test + void isValueEqualTrueTest() { + final LocalDateTime localDateTime = LocalDateTime.now(); + final LocalDate localDate = LocalDate.now(); + + Assertions.assertTrue(ValueUtil.isValueEqual("0", "0")); + Assertions.assertTrue(ValueUtil.isValueEqual(new BigDecimal("11"), new BigDecimal("11.00"))); + Assertions.assertTrue(ValueUtil.isValueEqual(Integer.valueOf(1), Integer.valueOf(1))); + Assertions.assertTrue(ValueUtil.isValueEqual('A', Character.valueOf('A'))); + Assertions.assertTrue(ValueUtil.isValueEqual(localDateTime, localDateTime)); + Assertions.assertTrue(ValueUtil.isValueEqual(localDate, localDate)); + Assertions.assertTrue(ValueUtil.isValueEqual(Long.valueOf(1L), Long.valueOf(1L))); + Assertions.assertTrue(ValueUtil.isValueEqual(Double.valueOf(1D), Double.valueOf(1D))); + Assertions.assertTrue(ValueUtil.isValueEqual(Boolean.FALSE, Boolean.FALSE)); + Assertions.assertTrue(ValueUtil.isValueEqual(Boolean.TRUE, Boolean.TRUE)); + Assertions.assertTrue(ValueUtil.isValueEqual(true, Boolean.TRUE)); + } +} diff --git a/backend/src/test/resources/sql_scripts/GeneticWorthRepoTest.sql b/backend/src/test/resources/sql_scripts/GeneticWorthRepoTest.sql index 1b851c2f3..2a0ff5c1a 100644 --- a/backend/src/test/resources/sql_scripts/GeneticWorthRepoTest.sql +++ b/backend/src/test/resources/sql_scripts/GeneticWorthRepoTest.sql @@ -4,7 +4,8 @@ insert into description, effective_date, expiry_date, - update_timestamp + update_timestamp, + default_bv ) values ( @@ -12,110 +13,126 @@ values 'Relative Wood Density', '1998-07-15', '2018-05-29', - current_timestamp + current_timestamp, + 0 ), ( 'G', 'Growth and Volume', '1905-01-01', '2018-05-29', - current_timestamp + current_timestamp, + 0 ), ( 'GVO', 'Volume Growth', '2018-05-29', '9999-12-31', - current_timestamp + current_timestamp, + 0 ), ( 'WVE', 'Wood Velocity Measures', '2018-05-29', '9999-12-31', - current_timestamp + current_timestamp, + 0 ), ( 'WWD', 'Wood quality', '2018-05-29', '9999-12-31', - current_timestamp + current_timestamp, + 0 ), ( 'R', 'Pest Resistance', '1905-01-01', '2018-05-29', - current_timestamp + current_timestamp, + 0 ), ( 'M', 'Major Gene Resistance', '2006-09-06', '2018-05-29', - current_timestamp + current_timestamp, + 0 ), ( 'WDU', 'Wood durability', '2018-05-29', '9999-12-31', - current_timestamp + current_timestamp, + 0 ), ( 'AD', 'Animal browse resistance (deer)', '2018-05-29', '9999-12-31', - current_timestamp + current_timestamp, + 0 ), ( 'DFS', 'Disease resistance for Dothistroma needle blight (Dothistroma septosporum)', '2018-05-29', '9999-12-31', - current_timestamp + current_timestamp, + 0 ), ( 'DFU', 'Disease resistance for Redcedar leaf blight (Didymascella thujina)', '2018-05-29', '9999-12-31', - current_timestamp + current_timestamp, + 0 ), ( 'DFW', 'Disease resistance for Swiss needle cast (Phaeocryptopus gaumanni)', '2018-05-29', '9999-12-31', - current_timestamp + current_timestamp, + 0 ), ( 'DSB', 'Disease resistance for white pine blister rust (Cronartium ribicola)', '2018-05-29', '9999-12-31', - current_timestamp + current_timestamp, + 0 ), ( 'DSC', 'Disease resistance for Commandra blister rust (Cronartium comandrae)', '2018-05-29', '9999-12-31', - current_timestamp + current_timestamp, + 0 ), ( 'DSG', 'Disease resistance for Western gall rust (Endocronartium harknessii)', '2018-05-29', '9999-12-31', - current_timestamp + current_timestamp, + 0 ), ( 'IWS', 'Spruce terminal weevil (Pissodes strobi)', '2018-05-29', '9999-12-31', - current_timestamp + current_timestamp, + 0 ); diff --git a/common/Dockerfile b/common/Dockerfile deleted file mode 100644 index 64bb3f495..000000000 --- a/common/Dockerfile +++ /dev/null @@ -1,20 +0,0 @@ -FROM eclipse-temurin:17.0.11_9-jdk-alpine - -ENV LANG=en_CA.UTF-8 -ENV LANGUAGE=en_CA.UTF-8 -ENV LC_ALL=en_CA.UTF-8 - -WORKDIR /app - -RUN apk --no-cache add openssl - -COPY startup.sh . - -RUN chmod g+w /app && \ - chmod g+x startup.sh && \ - chmod g+w ${JAVA_HOME}/lib/security/cacerts - -# Non-privileged user -USER app - -ENTRYPOINT ["sh", "startup.sh"] diff --git a/database/init_db/init.sql b/common/init_db/init.sql similarity index 99% rename from database/init_db/init.sql rename to common/init_db/init.sql index e40bdd9fd..5cd45fee2 100644 --- a/database/init_db/init.sql +++ b/common/init_db/init.sql @@ -2622,22 +2622,6 @@ CREATE TRIGGER trg_seedlot_audit_DIU AFTER INSERT OR UPDATE OR DELETE ON spar.seedlot FOR EACH ROW EXECUTE PROCEDURE spar.seedlot_if_modified_func(); -create table spar.ETL_EXECUTION_LOG( -from_timestamp timestamp not null, -to_timestamp timestamp not null, -run_status varchar(100) not null, -updated_at timestamp default now() not null, -created_at timestamp default now() not null -); - - -comment on table spar.ETL_EXECUTION_LOG is 'ETL Tool monitoring table to store execution current instance of batch processing interfaces'; -comment on column spar.ETL_EXECUTION_LOG.from_timestamp is 'From timestamp for the run (i.e. update_timestamp between from_timestamp and to_timetsamp)'; -comment on column spar.ETL_EXECUTION_LOG.to_timestamp is 'To timestamp for the run (i.e. update_timestamp between from_timestamp and to_timetsamp)'; -comment on column spar.ETL_EXECUTION_LOG.run_status is 'Status of ETL execution'; -comment on column spar.ETL_EXECUTION_LOG.updated_at is 'Timestamp of the last time this record was updated'; -comment on column spar.ETL_EXECUTION_LOG.created_at is 'Timestamp of the time this record was created'; - alter table spar.seedlot add column approved_timestamp timestamp, add column approved_userid varchar(30); @@ -4490,6 +4474,23 @@ comment on column spar.ETL_EXECUTION_MAP.retry_errors is 'If true, comment on column spar.ETL_EXECUTION_MAP.updated_at is 'Timestamp of the last time this record was updated'; comment on column spar.ETL_EXECUTION_MAP.created_at is 'Timestamp of the time this record was created'; +create table spar.ETL_EXECUTION_LOG( +from_timestamp timestamp not null, +to_timestamp timestamp not null, +run_status varchar(100) not null, +updated_at timestamp default now() not null, +created_at timestamp default now() not null +); + + +comment on table spar.ETL_EXECUTION_LOG is 'ETL Tool monitoring table to store execution current instance of batch processing interfaces'; +comment on column spar.ETL_EXECUTION_LOG.from_timestamp is 'From timestamp for the run (i.e. update_timestamp between from_timestamp and to_timetsamp)'; +comment on column spar.ETL_EXECUTION_LOG.to_timestamp is 'To timestamp for the run (i.e. update_timestamp between from_timestamp and to_timetsamp)'; +comment on column spar.ETL_EXECUTION_LOG.run_status is 'Status of ETL execution'; +comment on column spar.ETL_EXECUTION_LOG.updated_at is 'Timestamp of the last time this record was updated'; +comment on column spar.ETL_EXECUTION_LOG.created_at is 'Timestamp of the time this record was created'; + + create table spar.ETL_EXECUTION_SCHEDULE( interface_id varchar(100) not null, execution_id integer not null, @@ -4512,7 +4513,7 @@ comment on column spar.ETL_EXECUTION_SCHEDULE.created_at is 'Timestamp o create table spar.etl_execution_log_hist ( entry_timestamp timestamp(6) not null default current_timestamp -, log_details jsonb not null) +, log_details jsonb not null); comment on table spar.ETL_EXECUTION_LOG_HIST is 'ETL Tool monitoring table to store all executed instances of batch processing interfaces'; comment on column spar.ETL_EXECUTION_LOG_HIST.entry_timestamp is 'The timestamp when the record was inserted'; diff --git a/database/openshift.deploy.yml b/common/openshift.database.yml similarity index 78% rename from database/openshift.deploy.yml rename to common/openshift.database.yml index 9daa3e99a..f9555691f 100644 --- a/database/openshift.deploy.yml +++ b/common/openshift.database.yml @@ -12,45 +12,18 @@ parameters: - name: ZONE description: Deployment zone, e.g. pr-### or prod required: true - - name: TAG - description: Image tag; e.g. PR number, latest or prod - required: true - - name: REGISTRY - description: Container registry to import from (internal is image-registry.openshift-image-registry.svc:5000) - value: ghcr.io - - name: ORG - description: Organization name - value: bcgov - - name: PVC_MOUNT_PATH - description: Where to mount the PVC, subpath (e.g. data/) - value: /var/lib/postgresql - name: CPU_REQUEST - value: 50m + value: 25m - name: CPU_LIMIT - value: 115m + value: 75m - name: MEMORY_REQUEST - value: 150Mi + value: 2Gi - name: MEMORY_LIMIT - value: 250Mi + value: 4Gi - name: DB_PVC_SIZE - description: Volume space available for data, e.g. 512Mi, 2Gi. - displayName: Database Volume Capacity - value: 256Mi - - name: DB_PASSWORD - description: Password for the PostgreSQL connection user - required: true + description: Volume space available for data, e.g. 512Mi, 2Gi + value: 1.8Gi objects: - - apiVersion: v1 - kind: Secret - metadata: - name: ${NAME}-${ZONE}-${COMPONENT} - labels: - app: ${NAME}-${ZONE} - stringData: - database-name: ${NAME} - database-password: ${DB_PASSWORD} - database-port: "5432" - database-user: ${NAME} - kind: PersistentVolumeClaim apiVersion: v1 metadata: @@ -93,7 +66,7 @@ objects: claimName: ${NAME}-${ZONE}-${COMPONENT} containers: - name: ${NAME}-${ZONE} - image: ${REGISTRY}/${ORG}/${NAME}/${COMPONENT}:${TAG} + image: postgis/postgis:15-master resources: requests: cpu: ${CPU_REQUEST} @@ -111,6 +84,8 @@ objects: - bash - '-ce' - exec pg_isready -U $POSTGRES_USER -d "dbname=$POSTGRES_DB" -h 127.0.0.1 -p 5432 + periodSeconds: 30 + timeoutSeconds: 10 livenessProbe: exec: command: @@ -118,6 +93,8 @@ objects: - bash - '-ce' - exec pg_isready -U $POSTGRES_USER -d "dbname=$POSTGRES_DB" -h 127.0.0.1 -p 5432 + periodSeconds: 30 + timeoutSeconds: 10 env: - name: POSTGRES_DB valueFrom: @@ -136,7 +113,7 @@ objects: key: database-user volumeMounts: - name: ${NAME}-${ZONE}-${COMPONENT} - mountPath: ${PVC_MOUNT_PATH} + mountPath: /var/lib/postgresql terminationMessagePath: "/dev/termination-log" terminationMessagePolicy: File imagePullPolicy: Always diff --git a/common/openshift.init.yml b/common/openshift.init.yml index f7a63dacc..1da33db29 100644 --- a/common/openshift.init.yml +++ b/common/openshift.init.yml @@ -7,14 +7,15 @@ parameters: - name: ZONE description: Deployment zone, e.g. pr-### or prod required: true + - name: DB_PASSWORD + description: Password for the PostgreSQL connection user + required: true - name: FORESTCLIENTAPI_KEY required: true - name: ORACLE_HOST description: Oracle database host value: nrcdb03.bcgov - - name: ORACLE_PORT - description: Oracle database port - value: "1543" + required: true - name: ORACLE_SERVICE description: Oracle service name value: dbq01.nrs.bcgov @@ -33,7 +34,21 @@ parameters: - name: ORACLE_SYNC_USER description: Oracle database username for Sync value: proxy_fsa_spar_sync_user + - name: VITE_USER_POOLS_WEB_CLIENT_ID + description: Cognito user pools web client ID + required: true objects: + - apiVersion: v1 + kind: Secret + metadata: + name: ${NAME}-${ZONE}-database + labels: + app: ${NAME}-${ZONE} + stringData: + database-name: ${NAME} + database-password: ${DB_PASSWORD} + database-port: "5432" + database-user: ${NAME} - apiVersion: v1 kind: Secret metadata: @@ -43,10 +58,18 @@ objects: stringData: oracle-host: ${ORACLE_HOST} oracle-password: ${ORACLE_PASSWORD} - oracle-port: ${ORACLE_PORT} oracle-service: ${ORACLE_SERVICE} oracle-user: ${ORACLE_USER} oracle-secret: ${ORACLE_CERT_SECRET} + - apiVersion: v1 + kind: Secret + metadata: + name: ${NAME}-${ZONE}-sync + labels: + app: ${NAME}-${ZONE} + stringData: + oracle-host: ${ORACLE_HOST} + oracle-service: ${ORACLE_SERVICE} oracle-sync-password: ${ORACLE_SYNC_PASSWORD} oracle-sync-user: ${ORACLE_SYNC_USER} - apiVersion: v1 @@ -57,6 +80,14 @@ objects: app: ${NAME}-${ZONE} stringData: forest-client-api-key: ${FORESTCLIENTAPI_KEY} + - apiVersion: v1 + kind: Secret + metadata: + name: ${NAME}-${ZONE}-frontend + labels: + app: ${NAME}-${ZONE} + stringData: + vite-user-pools-web-client-id: ${VITE_USER_POOLS_WEB_CLIENT_ID} - apiVersion: networking.k8s.io/v1 kind: NetworkPolicy metadata: diff --git a/database/Dockerfile b/database/Dockerfile deleted file mode 100644 index 491f15f73..000000000 --- a/database/Dockerfile +++ /dev/null @@ -1,10 +0,0 @@ -FROM postgis/postgis:15-master - -# Enable pgcrypto extension on startup -RUN sed -i '/EXISTS postgis_tiger_geocoder;*/a CREATE EXTENSION IF NOT EXISTS pgcrypto;' \ - /docker-entrypoint-initdb.d/10_postgis.sh - -# User, port and Healthcheck -USER postgres -EXPOSE 5432 -HEALTHCHECK CMD ["psql", "-q", "-U", "$${POSTGRES_USER}", "-d", "$${POSTGRES_DB}", "-c", "SELECT 1"] diff --git a/docker-compose.yml b/docker-compose.yml index 4f52e6384..2613d53ba 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -24,12 +24,12 @@ x-frontend: &frontend services: database: container_name: database - build: ./database + image: postgis/postgis:15-master environment: <<: *postgres-vars volumes: - "/pgdata" - - "./database/init_db:/init_db" + - "./common/init_db:/init_db" ports: ["5432:5432"] healthcheck: test: psql -q -U $${POSTGRES_USER} -d $${POSTGRES_DB} -c 'SELECT 1' @@ -48,7 +48,7 @@ services: AWS_COGNITO_ISSUER_URI: "https://cognito-idp.ca-central-1.amazonaws.com/ca-central-1_t2HSZBHur" <<: *postgres-vars ports: ["8090:8090", "5005:5005"] - image: maven:3.9.8-eclipse-temurin-17 + image: maven:3.9.9-eclipse-temurin-17 entrypoint: sh -c './encora-cert.sh' working_dir: /app volumes: @@ -106,7 +106,7 @@ services: AWS_COGNITO_ISSUER_URI: "https://cognito-idp.ca-central-1.amazonaws.com/ca-central-1_t2HSZBHur" profiles: ["oracle-api"] network_mode: host - image: maven:3.9.8-eclipse-temurin-17 + image: maven:3.9.9-eclipse-temurin-17 entrypoint: sh -c './startup.sh && mvn -ntp spring-boot:run -Dspring-boot.run.jvmArguments="-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=n,address=*:5006"' working_dir: /app volumes: ["./oracle-api:/app"] diff --git a/frontend/Dockerfile b/frontend/Dockerfile index 21c8f2f87..fab4771b2 100644 --- a/frontend/Dockerfile +++ b/frontend/Dockerfile @@ -1,7 +1,7 @@ # Build static files # Node Bullseye has npm FROM node:18.20.4-bullseye-slim AS build -ENV NODE_OPTIONS "--max-old-space-size=3072" +ENV NODE_OPTIONS="--max-old-space-size=3072" # Build WORKDIR /app @@ -17,6 +17,10 @@ RUN npm ci --ignore-scripts --no-update-notifier --omit=dev && \ FROM caddy:2.8.4-alpine RUN apk add --no-cache ca-certificates curl +# Receive build number as argument, retain as environment variable +ARG BUILD_NUMBER +ENV BUILD_NUMBER=${BUILD_NUMBER} + # Copy files and run formatting COPY --from=build /app/build/ /app/dist COPY Caddyfile /etc/caddy/Caddyfile diff --git a/frontend/cypress.config.ts b/frontend/cypress.config.ts index d20a514bb..a7e332058 100644 --- a/frontend/cypress.config.ts +++ b/frontend/cypress.config.ts @@ -29,11 +29,12 @@ export default defineConfig({ '**/a-class-seedlot-reg-form-ownership.cy.ts', '**/a-class-seedlot-reg-form-orchard.cy.ts', '**/a-class-seedlot-reg-form-extraction.cy.ts', + '**/a-class-seedlot-reg-form-parent-tree-part-1.cy.ts', '**/a-class-seedlot-reg-form-parent-tree-part-2.cy.ts' ], chromeWebSecurity: false, retries: { - runMode: 2 + runMode: 0 }, defaultCommandTimeout: TEN_SECONDS, video: true, diff --git a/frontend/cypress/constants.ts b/frontend/cypress/constants.ts index 0ddb33737..36940f5d8 100644 --- a/frontend/cypress/constants.ts +++ b/frontend/cypress/constants.ts @@ -4,6 +4,7 @@ export const TWO_SECONDS = 2 * ONE_SECOND; export const THREE_SECONDS = 3 * ONE_SECOND; export const FIVE_SECONDS = 5 * ONE_SECOND; export const TEN_SECONDS = 10 * ONE_SECOND; +export const THIRTY_SECONDS = 3 * TEN_SECONDS; export const TYPE_DELAY = 50; export const INVALID_EMAIL = 'test.com.br'; diff --git a/frontend/cypress/e2e/smoke-test/a-class-seedlot-reg-form-collection-interim.cy.ts b/frontend/cypress/e2e/smoke-test/a-class-seedlot-reg-form-collection-interim.cy.ts index f437a83b0..94115ec0e 100644 --- a/frontend/cypress/e2e/smoke-test/a-class-seedlot-reg-form-collection-interim.cy.ts +++ b/frontend/cypress/e2e/smoke-test/a-class-seedlot-reg-form-collection-interim.cy.ts @@ -57,11 +57,7 @@ describe('A Class Seedlot Registration form, Collection and Interim storage', () cy.get('.seedlot-registration-title') .find('h1') - .should('have.text', 'Seedlot Registration'); - - cy.get('.seedlot-registration-title') - .find('.seedlot-form-subtitle') - .should('contain.text', `Seedlot ${seedlotNum}`); + .should('have.text', `Registration for seedlot ${seedlotNum}`); cy.get('.collection-step-row') .find('h2') @@ -120,7 +116,8 @@ describe('A Class Seedlot Registration form, Collection and Interim storage', () .type(testAcronym) .blur(); - cy.get(`svg.${prefix}--inline-loading__checkmark-container`) + cy.get('#collection-collector-agency-loading-status-tooltip') + .find(`svg.${prefix}--inline-loading__checkmark-container`) .should('be.visible'); // Enter invalid location code @@ -135,7 +132,7 @@ describe('A Class Seedlot Registration form, Collection and Interim storage', () // Enter valid location code cy.get('#collection-location-code') .clear() - .type('02') + .type('03') .blur(); // Save changes @@ -191,7 +188,7 @@ describe('A Class Seedlot Registration form, Collection and Interim storage', () // Enter location code for linkage test cy.get('#collection-location-code') .clear() - .type('02') + .type('03') .blur(); // Save changes @@ -325,10 +322,9 @@ describe('A Class Seedlot Registration form, Collection and Interim storage', () .contains('Next') .click(); - // Check svg with complete checkmark on Step 1 - cy.get('ul.spar-seedlot-reg-progress-bar li') - .eq(0) - .should('have.class', `${prefix}--progress-step--complete`); + // Check step complete status + cy.get(`.${prefix}--progress-step--complete`) + .contains('Collection'); }); // Step 3 @@ -354,7 +350,7 @@ describe('A Class Seedlot Registration form, Collection and Interim storage', () .should('have.value', testPopupAcronym); cy.get('#interim-location-code') - .should('have.value', '02'); + .should('have.value', '03'); cy.get('#interim-use-collection-agency') .should('be.checked'); @@ -398,7 +394,8 @@ describe('A Class Seedlot Registration form, Collection and Interim storage', () .type('01') .blur(); - cy.get(`svg.${prefix}--inline-loading__checkmark-container`) + cy.get('#interim-location-code-loading-status-tooltip') + .find(`svg.${prefix}--inline-loading__checkmark-container`) .should('be.visible'); // Save changes @@ -483,6 +480,9 @@ describe('A Class Seedlot Registration form, Collection and Interim storage', () .type('2024-05-25') .blur(); + // Save changes + cy.saveSeedlotRegFormProgress(); + cy.get('#end-date-input') .clear() .type('2024-05-26') @@ -512,15 +512,17 @@ describe('A Class Seedlot Registration form, Collection and Interim storage', () .type('Test comment') .blur(); + // Save changes + cy.saveSeedlotRegFormProgress(); + // Press next button cy.get('.seedlot-registration-button-row') .find('button.form-action-btn') .contains('Next') .click(); - // Check svg with complete checkmark on Step 3 - cy.get('ul.spar-seedlot-reg-progress-bar li') - .eq(2) - .should('have.class', `${prefix}--progress-step--complete`); + // Check step complete status + cy.get(`.${prefix}--progress-step--complete`) + .contains('Interim storage'); }); }); diff --git a/frontend/cypress/e2e/smoke-test/a-class-seedlot-reg-form-extraction.cy.ts b/frontend/cypress/e2e/smoke-test/a-class-seedlot-reg-form-extraction.cy.ts index 2d52b7ceb..7181a2aaa 100644 --- a/frontend/cypress/e2e/smoke-test/a-class-seedlot-reg-form-extraction.cy.ts +++ b/frontend/cypress/e2e/smoke-test/a-class-seedlot-reg-form-extraction.cy.ts @@ -45,6 +45,10 @@ describe('A Class Seedlot Registration form, Extraction and Storage', () => { }); it('Page title and subtitles', () => { + cy.get('.seedlot-registration-title') + .find('h1') + .should('have.text', `Registration for seedlot ${seedlotNum}`); + cy.get('.extraction-information-title') .find('h2') .should('have.text', regFormData.extraction.extrationTitle); @@ -88,32 +92,33 @@ describe('A Class Seedlot Registration form, Extraction and Storage', () => { .uncheck({ force: true }); // Enter invalid acronym - cy.get('#ext-agency-combobox') + cy.get('#ext-agency-number') .type('ggg') .blur(); - cy.get('#ext-agency-combobox-error-msg') + cy.get('#ext-agency-number-error-msg') .should('have.text', regFormData.extraction.agencyErrorMsg); // Enter invalid acronym - cy.get('#ext-agency-combobox') + cy.get('#ext-agency-number') .clear() .type('-1') .blur(); - cy.get('#ext-agency-combobox-error-msg') + cy.get('#ext-agency-number-error-msg') .should('have.text', regFormData.extraction.agencyValidationMsg); cy.get('.applicant-error-notification') .should('be.visible'); // Enter valid test acronym - cy.get('#ext-agency-combobox') + cy.get('#ext-agency-number') .clear() .type(testAcronym) .blur(); - cy.get(`svg.${prefix}--inline-loading__checkmark-container`) + cy.get('#ext-agency-number-loading-status-tooltip') + .find(`svg.${prefix}--inline-loading__checkmark-container`) .should('be.visible'); cy.get('.applicant-error-notification') @@ -134,6 +139,10 @@ describe('A Class Seedlot Registration form, Extraction and Storage', () => { .type('00') .blur(); + cy.get('#ext-location-code-loading-status-tooltip') + .find(`svg.${prefix}--inline-loading__checkmark-container`) + .should('be.visible'); + // Save changes cy.saveSeedlotRegFormProgress(); }); @@ -182,7 +191,7 @@ describe('A Class Seedlot Registration form, Extraction and Storage', () => { .contains('Apply selected client') .click({ force: true }); - cy.get('#ext-agency-combobox') + cy.get('#ext-agency-number') .should('have.value', testPopupAcronym); cy.get('#ext-location-code') @@ -226,32 +235,33 @@ describe('A Class Seedlot Registration form, Extraction and Storage', () => { .uncheck({ force: true }); // Enter invalid acronym - cy.get('#str-agency-combobox') + cy.get('#str-agency-number') .type('ggg') .blur(); - cy.get('#str-agency-combobox-error-msg') + cy.get('#str-agency-number-error-msg') .should('have.text', regFormData.extraction.agencyErrorMsg); // Enter invalid acronym - cy.get('#str-agency-combobox') + cy.get('#str-agency-number') .clear() .type('-1') .blur(); - cy.get('#str-agency-combobox-error-msg') + cy.get('#str-agency-number-error-msg') .should('have.text', regFormData.extraction.agencyValidationMsg); cy.get('.applicant-error-notification') .should('be.visible'); // Enter valid test acronym - cy.get('#str-agency-combobox') + cy.get('#str-agency-number') .clear() .type(testAcronym) .blur(); - cy.get(`svg.${prefix}--inline-loading__checkmark-container`) + cy.get('#str-agency-number-loading-status-tooltip') + .find(`svg.${prefix}--inline-loading__checkmark-container`) .should('be.visible'); cy.get('.applicant-error-notification') @@ -272,6 +282,10 @@ describe('A Class Seedlot Registration form, Extraction and Storage', () => { .type('00') .blur(); + cy.get('#str-location-code-loading-status-tooltip') + .find(`svg.${prefix}--inline-loading__checkmark-container`) + .should('be.visible'); + // Save changes cy.saveSeedlotRegFormProgress(); }); @@ -319,7 +333,7 @@ describe('A Class Seedlot Registration form, Extraction and Storage', () => { .contains('Apply selected client') .click(); - cy.get('#str-agency-combobox') + cy.get('#str-agency-number') .should('have.value', testPopupAcronym); cy.get('#str-location-code') diff --git a/frontend/cypress/e2e/smoke-test/a-class-seedlot-reg-form-orchard.cy.ts b/frontend/cypress/e2e/smoke-test/a-class-seedlot-reg-form-orchard.cy.ts index 860da8080..33887e85c 100644 --- a/frontend/cypress/e2e/smoke-test/a-class-seedlot-reg-form-orchard.cy.ts +++ b/frontend/cypress/e2e/smoke-test/a-class-seedlot-reg-form-orchard.cy.ts @@ -1,4 +1,5 @@ import prefix from '../../../src/styles/classPrefix'; +import { THIRTY_SECONDS } from '../../constants'; import { SeedlotRegFixtureType } from '../../definitions'; describe('A Class Seedlot Registration form, Orchard', () => { @@ -24,6 +25,8 @@ describe('A Class Seedlot Registration form, Orchard', () => { const parentTreeSet = new Set(); const unionParentTreeArray: string[] = []; const lengthOfArray = 6; + const F2GameticValue = 'F2 - Measured Cone Volume'; + const M3GameticValue = 'M3 - Pollen Volume Estimate by 100% Survey'; beforeEach(() => { // Login @@ -76,7 +79,7 @@ describe('A Class Seedlot Registration form, Orchard', () => { }); it('Orchard dropdown section', () => { - cy.get('#orchard-combobox-0') + cy.get('#primary-orchard-selection') .siblings(`button.${prefix}--list-box__menu-icon[title="Open"]`) .click(); @@ -86,6 +89,12 @@ describe('A Class Seedlot Registration form, Orchard', () => { .contains('219 - VERNON - S - PRD') .click(); + // Intercept the call before step 5 mounts + cy.intercept({ + method: 'GET', + url: '**/api/parent-trees/vegetation-codes/*' + }).as('parentTreesUnderVegCode'); + // Go to next step to get error msg cy.get('.seedlot-registration-progress') .find(`button.${prefix}--progress-step-button`) @@ -93,14 +102,17 @@ describe('A Class Seedlot Registration form, Orchard', () => { .contains('Parent tree and SMP') .click(); - // Wait for the table in Step 5 to load + // Wait for the data for table in Step 5 to load + cy.wait('@parentTreesUnderVegCode', { timeout: THIRTY_SECONDS }).its('response.statusCode').should('equal', 200); + + // Verify table data is loaded cy.get('#parentTreeNumber'); cy.get('@progressBar') .contains('Orchard') .click(); - cy.get('#orchard-combobox-0') + cy.get('#primary-orchard-selection') .siblings(`button.${prefix}--list-box__selection[title="Clear selected item"]`) .as('cancelOrchard') .click(); @@ -128,11 +140,11 @@ describe('A Class Seedlot Registration form, Orchard', () => { .contains('Change orchard') .click(); - cy.get('#orchard-combobox-0') + cy.get('#primary-orchard-selection') .should('have.value', ''); // Add orchard from dropdown - cy.get('#orchard-combobox-0') + cy.get('#primary-orchard-selection') .siblings(`button.${prefix}--list-box__menu-icon[title="Open"]`) .click(); @@ -146,10 +158,10 @@ describe('A Class Seedlot Registration form, Orchard', () => { .contains('Add additional orchard') .click(); - cy.get(`label.${prefix}--label[for="orchard-combobox-1"]`) + cy.get(`label.${prefix}--label[for="secondary-orchard-selection"]`) .should('have.text', regFormData.orchard.additionalOrchardLabel); - cy.get('#orchard-combobox-1') + cy.get('#secondary-orchard-selection') .siblings(`button.${prefix}--list-box__menu-icon[title="Open"]`) .click(); @@ -164,17 +176,17 @@ describe('A Class Seedlot Registration form, Orchard', () => { .contains('Parent tree and SMP') .click(); - // Wait for the table in Step 5 to load + // Verify table data is loaded cy.get('#parentTreeNumber'); cy.get('@progressBar') .contains('Orchard') .click(); - // Delete additional orchard + // Delete secondary orchard cy.get('.seedlot-orchard-add-orchard') .find('button') - .contains('Delete additional orchard') + .contains('Delete secondary orchard') .as('deleteOrchard') .click(); @@ -195,19 +207,19 @@ describe('A Class Seedlot Registration form, Orchard', () => { cy.get('@deleteOrchard') .click(); - // Check 'Delete additional orchard' button of change orchard modal + // Check 'Delete secondary orchard' button of change orchard modal cy.get(`.${prefix}--modal-container[aria-label="Delete orchard"]`) .find(`button.${prefix}--btn`) - .contains('Delete additional orchard') + .contains('Delete secondary orchard') .click(); - cy.get('#orchard-combobox-1') + cy.get('#secondary-orchard-selection') .should('not.exist'); cy.get('@cancelOrchard') .click(); - cy.get('#orchard-combobox-0') + cy.get('#primary-orchard-selection') .should('have.value', ''); // Save changes @@ -215,7 +227,7 @@ describe('A Class Seedlot Registration form, Orchard', () => { }); it('store first Orchard Parent Tree Number in an array', () => { - cy.get('#orchard-combobox-0') + cy.get('#primary-orchard-selection') .siblings(`button.${prefix}--list-box__menu-icon[title="Open"]`) .click(); @@ -225,6 +237,12 @@ describe('A Class Seedlot Registration form, Orchard', () => { .contains('219 - VERNON - S - PRD') .click(); + // Intercept the call before step 5 mounts + cy.intercept({ + method: 'GET', + url: '**/api/parent-trees/vegetation-codes/*' + }).as('parentTreesUnderVegCode'); + // Go to next step 'Parent tree and SMP' cy.get('.seedlot-registration-progress') .find(`button.${prefix}--progress-step-button`) @@ -232,7 +250,9 @@ describe('A Class Seedlot Registration form, Orchard', () => { .contains('Parent tree and SMP') .click(); - // Wait for the table in Step 5 to load + cy.wait('@parentTreesUnderVegCode', { timeout: THIRTY_SECONDS }).its('response.statusCode').should('equal', 200); + + // Verify table data is loaded cy.get('#parentTreeNumber'); // Push first 6 parent tree number in an array @@ -254,7 +274,7 @@ describe('A Class Seedlot Registration form, Orchard', () => { .click(); // Cancel orchard - cy.get('#orchard-combobox-0') + cy.get('#primary-orchard-selection') .siblings(`button.${prefix}--list-box__selection[title="Clear selected item"]`) .click(); @@ -263,7 +283,7 @@ describe('A Class Seedlot Registration form, Orchard', () => { .contains('Change orchard') .click(); - cy.get('#orchard-combobox-0') + cy.get('#primary-orchard-selection') .should('have.value', ''); // Save changes @@ -272,7 +292,7 @@ describe('A Class Seedlot Registration form, Orchard', () => { it('store second Orchard Parent Tree Number in an array', () => { // Enter new orchard - cy.get('#orchard-combobox-0') + cy.get('#primary-orchard-selection') .siblings(`button.${prefix}--list-box__menu-icon[title="Open"]`) .click(); @@ -281,6 +301,12 @@ describe('A Class Seedlot Registration form, Orchard', () => { .contains('222 - VERNON - S - PRD') .click(); + // Intercept the call before step 5 mounts + cy.intercept({ + method: 'GET', + url: '**/api/parent-trees/vegetation-codes/*' + }).as('parentTreesUnderVegCode'); + // Go to next step 'Parent tree and SMP' cy.get('.seedlot-registration-progress') .find(`button.${prefix}--progress-step-button`) @@ -288,7 +314,9 @@ describe('A Class Seedlot Registration form, Orchard', () => { .contains('Parent tree and SMP') .click(); - // Wait for the table in Step 5 to load + cy.wait('@parentTreesUnderVegCode', { timeout: THIRTY_SECONDS }).its('response.statusCode').should('equal', 200); + + // Verify table data is loaded cy.get('#parentTreeNumber'); // Push first 6 parent tree number in an array @@ -310,7 +338,7 @@ describe('A Class Seedlot Registration form, Orchard', () => { .click(); // Cancel orchard - cy.get('#orchard-combobox-0') + cy.get('#primary-orchard-selection') .siblings(`button.${prefix}--list-box__selection[title="Clear selected item"]`) .click(); @@ -319,7 +347,7 @@ describe('A Class Seedlot Registration form, Orchard', () => { .contains('Change orchard') .click(); - cy.get('#orchard-combobox-0') + cy.get('#primary-orchard-selection') .should('have.value', ''); // Save changes @@ -327,7 +355,7 @@ describe('A Class Seedlot Registration form, Orchard', () => { }); it('Linkage of Step 4 and Step 5', () => { - cy.get('#orchard-combobox-0') + cy.get('#primary-orchard-selection') .siblings(`button.${prefix}--list-box__menu-icon[title="Open"]`) .click(); @@ -342,7 +370,7 @@ describe('A Class Seedlot Registration form, Orchard', () => { .contains('Add additional orchard') .click(); - cy.get('#orchard-combobox-1') + cy.get('#secondary-orchard-selection') .siblings(`button.${prefix}--list-box__menu-icon[title="Open"]`) .click(); @@ -354,6 +382,12 @@ describe('A Class Seedlot Registration form, Orchard', () => { // Save changes cy.saveSeedlotRegFormProgress(); + // Intercept the call before step 5 mounts + cy.intercept({ + method: 'GET', + url: '**/api/parent-trees/vegetation-codes/*' + }).as('parentTreesUnderVegCode'); + // Go to next step 'Parent tree and SMP' cy.get('.seedlot-registration-progress') .find(`button.${prefix}--progress-step-button`) @@ -361,7 +395,9 @@ describe('A Class Seedlot Registration form, Orchard', () => { .contains('Parent tree and SMP') .click(); - // Wait for the table in Step 5 to load + cy.wait('@parentTreesUnderVegCode', { timeout: THIRTY_SECONDS }).its('response.statusCode').should('equal', 200); + + // Verify table data is loaded cy.get('#parentTreeNumber'); // Get parent tree number in an array @@ -418,24 +454,24 @@ describe('A Class Seedlot Registration form, Orchard', () => { cy.get(`.${prefix}--list-box--expanded`) .find('ul li') - .contains('F2 - Measured Cone Volume') + .contains(F2GameticValue) .click(); cy.get('#orchard-female-gametic') - .should('have.value', 'F2 - Measured Cone Volume'); + .should('have.value', F2GameticValue); // Select male gametic contribution methodology cy.get('#orchard-male-gametic') .siblings() .click(); - cy.get(`.${prefix}--list-box--expanded`) + cy.get(`.${prefix}--list-box--expanded`) .find('ul li') - .contains('M3 - Pollen Volume Estimate by 100% Survey') + .contains(M3GameticValue) .click(); cy.get('#orchard-male-gametic') - .should('have.value', 'M3 - Pollen Volume Estimate by 100% Survey'); + .should('have.value', M3GameticValue); // Check 'x' button cy.get('#orchard-female-gametic') @@ -459,18 +495,24 @@ describe('A Class Seedlot Registration form, Orchard', () => { cy.get(`.${prefix}--list-box--expanded`) .find('ul li') - .contains('F2 - Measured Cone Volume') + .contains(F2GameticValue) .click(); + cy.get('#orchard-female-gametic') + .should('have.value', F2GameticValue); + cy.get('#orchard-male-gametic') .siblings() .click(); cy.get(`.${prefix}--list-box--expanded`) .find('ul li') - .contains('M3 - Pollen Volume Estimate by 100% Survey') + .contains(M3GameticValue) .click(); + cy.get('#orchard-male-gametic') + .should('have.value', M3GameticValue); + // Change radio inputs of gamete section cy.get('#controlled-cross-yes') .check({ force: true }); @@ -563,6 +605,12 @@ describe('A Class Seedlot Registration form, Orchard', () => { // Save changes cy.saveSeedlotRegFormProgress(); + }); + + it('Step complete status', () => { + // Make sure value is loaded + cy.get('#orchard-breading-perc') + .should('have.value', '5'); // Press next button cy.get('.seedlot-registration-button-row') @@ -570,11 +618,8 @@ describe('A Class Seedlot Registration form, Orchard', () => { .contains('Next') .click(); - cy.get('ul.spar-seedlot-reg-progress-bar').scrollIntoView({ easing: 'linear' }) - - // Check svg with complete checkmark on Step 3 - cy.get('ul.spar-seedlot-reg-progress-bar li') - .eq(3) - .should('have.class', `${prefix}--progress-step--complete`); + // Check step complete status + cy.get(`.${prefix}--progress-step--complete`) + .contains('Orchard'); }); }); diff --git a/frontend/cypress/e2e/smoke-test/a-class-seedlot-reg-form-ownership.cy.ts b/frontend/cypress/e2e/smoke-test/a-class-seedlot-reg-form-ownership.cy.ts index ffe8cb3fd..b3799c490 100644 --- a/frontend/cypress/e2e/smoke-test/a-class-seedlot-reg-form-ownership.cy.ts +++ b/frontend/cypress/e2e/smoke-test/a-class-seedlot-reg-form-ownership.cy.ts @@ -56,11 +56,7 @@ describe('A Class Seedlot Registration form, Ownership', () => { it('Page title and accordion title', () => { cy.get('.seedlot-registration-title') .find('h1') - .should('have.text', 'Seedlot Registration'); - - cy.get('.seedlot-registration-title') - .find('.seedlot-form-subtitle') - .should('contain.text', `Seedlot ${seedlotNum}`); + .should('have.text', `Registration for seedlot ${seedlotNum}`); cy.get('.ownership-header') .find('h3') @@ -137,7 +133,8 @@ describe('A Class Seedlot Registration form, Ownership', () => { .type(testAcronym, { delay: TYPE_DELAY }) .blur(); - cy.get(`svg.${prefix}--inline-loading__checkmark-container`) + cy.get('#ownership-agency-0-loading-status-tooltip') + .find(`svg.${prefix}--inline-loading__checkmark-container`) .should('be.visible'); // Enter invalid location code @@ -155,7 +152,8 @@ describe('A Class Seedlot Registration form, Ownership', () => { .type('02', { delay: TYPE_DELAY }) .blur(); - cy.get(`svg.${prefix}--inline-loading__checkmark-container`) + cy.get('#ownership-location-code-0-loading-status-tooltip') + .find(`svg.${prefix}--inline-loading__checkmark-container`) .should('be.visible'); // Save changes @@ -307,20 +305,9 @@ describe('A Class Seedlot Registration form, Ownership', () => { cy.get('#ownership-funding-source-0') .should('have.value', fundingSource); - // Method of Payment - cy.get('#ownership-method-payment-0') - .should('have.value', '') - .click(); - - const methodOfPayment = 'CSH - Cash Sale'; - - cy.get(`.${prefix}--list-box__menu-item__option`) - .contains(methodOfPayment) - .scrollIntoView() - .click(); - + // Default method of Payment cy.get('#ownership-method-payment-0') - .should('have.value', methodOfPayment); + .should('have.value', 'ITC - Invoice to Client Address'); // Check 'x' button cy.get('.single-owner-combobox') @@ -328,11 +315,6 @@ describe('A Class Seedlot Registration form, Ownership', () => { .find('[aria-label="Clear selected item"]') .click(); - cy.get('.single-owner-combobox') - .eq(1) - .find('[aria-label="Clear selected item"]') - .click(); - // Enter funding source and method payment values again cy.get('#ownership-funding-source-0') .should('have.value', '') @@ -346,10 +328,18 @@ describe('A Class Seedlot Registration form, Ownership', () => { cy.get('#ownership-funding-source-0') .should('have.value', fundingSource); - cy.get('#ownership-method-payment-0') - .should('have.value', '') + cy.get('.single-owner-combobox') + .eq(1) + .find('[aria-label="Clear selected item"]') .click(); + cy.get('#ownership-method-payment-0') + .should('have.value', ''); + + cy.get('#ownership-method-payment-0').click(); + + const methodOfPayment = 'CSH - Cash Sale'; + cy.get(`.${prefix}--list-box__menu-item__option`) .contains(methodOfPayment) .scrollIntoView() @@ -462,11 +452,9 @@ describe('A Class Seedlot Registration form, Ownership', () => { .find('button.form-action-btn') .contains('Next') .click(); - - // Check svg with complete checkmark on Step 3 - // FLAKY, needs investigation - cy.get('ul.spar-seedlot-reg-progress-bar li') - .eq(1) - .should('have.class', `${prefix}--progress-step--complete`); + + // Check step complete status + cy.get(`.${prefix}--progress-step--complete`) + .contains('Ownership'); }); }); diff --git a/frontend/cypress/e2e/smoke-test/a-class-seedlot-reg-form-parent-tree-part-1.cy.ts b/frontend/cypress/e2e/smoke-test/a-class-seedlot-reg-form-parent-tree-part-1.cy.ts new file mode 100644 index 000000000..c9a4dd4dc --- /dev/null +++ b/frontend/cypress/e2e/smoke-test/a-class-seedlot-reg-form-parent-tree-part-1.cy.ts @@ -0,0 +1,550 @@ +import prefix from '../../../src/styles/classPrefix'; +import { THIRTY_SECONDS } from '../../constants'; + +describe('A Class Seedlot Registration form, Parent Tree and SMP part-1(Cone and Pollen count)', () => { + let regFormData: { + parentTree: { + title: string; + subtitle: string; + coneTitle: string; + coneSubtitle: string; + coneErrorMsg: string; + pollenErrorMsg: string; + conePollenErrorMsg: string; + } + }; + + let seedlotNum: string; + const speciesKey = 'pli'; + + beforeEach(() => { + // Login + cy.login(); + cy.fixture('aclass-reg-form').then((fData) => { + regFormData = fData; + }); + + cy.fixture('aclass-seedlot').then((fData) => { + cy.task('getData', fData[speciesKey].species).then((sNumber) => { + seedlotNum = sNumber as string; + const url = `/seedlots/a-class-registration/${seedlotNum}/?step=5`; + cy.visit(url); + cy.url().should('contains', url); + }); + }); + }); + + it('Page title and subtitles', () => { + cy.get('.title-row') + .find('h2') + .should('have.text', regFormData.parentTree.title); + + cy.get('.title-row') + .find('.subtitle-section') + .should('have.text', regFormData.parentTree.subtitle); + + cy.get('.parent-tree-step-table-container') + .find('h4') + .should('have.text', regFormData.parentTree.coneTitle); + + cy.get('.parent-tree-step-table-container') + .find(`p.${prefix}--data-table-header__description`) + .should('have.text', regFormData.parentTree.coneSubtitle); + }); + + it('Accordion', () => { + // Check default accordion behaviour + cy.get(`.${prefix}--accordion__wrapper`) + .should('be.visible'); + + // Check template links + cy.get('ul.donwload-templates-list') + .find('li') + .contains('Download cone and pollen count and SMP success on parent template.') + .click(); + + cy.readFile(`${Cypress.config('downloadsFolder')}/Seedlot_composition_template.csv`); + + cy.get('ul.donwload-templates-list') + .find('li') + .contains('Download calculation of SMP mix template.') + .click(); + + cy.readFile(`${Cypress.config('downloadsFolder')}/SMP_Mix_Volume_template.csv`); + + // Check closing of first accordion + cy.get(`ul.${prefix}--accordion > li`) + .eq(0) + .find(`button.${prefix}--accordion__heading`) + .click(); + + cy.get(`ul.${prefix}--accordion > li`) + .eq(0) + .find(`.${prefix}--accordion__wrapper`) + .should('not.be.visible'); + + // Check closing of second accordion + cy.get(`ul.${prefix}--accordion > li`) + .eq(1) + .find(`button.${prefix}--accordion__heading`) + .click(); + + cy.get(`ul.${prefix}--accordion > li`) + .eq(1) + .find(`.${prefix}--accordion__wrapper`) + .should('not.be.visible'); + + // Check closing of third accordion + cy.get(`ul.${prefix}--accordion > li`) + .eq(2) + .find(`button.${prefix}--accordion__heading`) + .click(); + + cy.get(`ul.${prefix}--accordion > li`) + .eq(2) + .find(`.${prefix}--accordion__wrapper`) + .should('not.be.visible'); + }); + + it('Cone and pollen count table entries', () => { + // Intercept the call + cy.intercept({ + method: 'GET', + url: '**/api/parent-trees/vegetation-codes/*' + }).as('parentTreesUnderVegCode'); + + // Wait for the table to load + cy.wait('@parentTreesUnderVegCode', { timeout: THIRTY_SECONDS }).its('response.statusCode').should('equal', 200); + cy.get('#parentTreeNumber'); + + // Check error message for negative Cone count + cy.get('#212-coneCount-value-input') + .type('-1') + .blur(); + + cy.get(`.${prefix}--actionable-notification--error`) + .should('be.visible'); + + cy.get(`.${prefix}--actionable-notification--error`) + .find(`.${prefix}--actionable-notification__title`) + .as('errorDialog') + .should('have.text', regFormData.parentTree.coneErrorMsg); + + // Check error message for Cone count > 10000000000 + cy.get('#212-coneCount-value-input') + .clear() + .type('10000000001') + .blur(); + + cy.get('@errorDialog') + .should('have.text', regFormData.parentTree.coneErrorMsg); + + // Check error message for Cone count value > 10 decimal places + cy.get('#212-coneCount-value-input') + .clear() + .type('0.00000000001') + .blur(); + + cy.get('@errorDialog') + .should('have.text', regFormData.parentTree.coneErrorMsg); + + // Check no error message for positive Cone count + cy.get('#212-coneCount-value-input') + .clear() + .type('1') + .blur(); + + cy.get(`.${prefix}--actionable-notification--error`) + .should('not.exist'); + + // Check error message for negative Pollen count + cy.get('#212-pollenCount-value-input') + .type('-1') + .blur(); + + cy.get(`.${prefix}--actionable-notification--error`) + .should('be.visible'); + + cy.get('@errorDialog') + .should('have.text', regFormData.parentTree.pollenErrorMsg); + + // Check error message for Pollen count > 10000000000 + cy.get('#212-pollenCount-value-input') + .clear() + .type('10000000001') + .blur(); + + cy.get('@errorDialog') + .should('have.text', regFormData.parentTree.pollenErrorMsg); + + // Check error message for Pollen count value > 10 decimal places + cy.get('#212-pollenCount-value-input') + .clear() + .type('0.00000000001') + .blur(); + + cy.get('@errorDialog') + .should('have.text', regFormData.parentTree.pollenErrorMsg); + + // Check error message for negative Cone count and negative Pollen count + cy.get('#212-coneCount-value-input') + .clear() + .type('-1') + .blur(); + + cy.get('@errorDialog') + .should('have.text', regFormData.parentTree.conePollenErrorMsg); + + // Empty the Pollen count and Cone count inputs + cy.get('#212-pollenCount-value-input') + .clear() + .blur(); + + cy.get('#212-coneCount-value-input') + .clear() + .blur(); + + // Save changes + cy.saveSeedlotRegFormProgress(); + }); + + it('Check \'Show/hide columns\' button functionality', () => { + // Intercept the call + cy.intercept({ + method: 'GET', + url: '**/api/parent-trees/vegetation-codes/*' + }).as('parentTreesUnderVegCode'); + + // Wait for the table to load + cy.wait('@parentTreesUnderVegCode', { timeout: THIRTY_SECONDS }).its('response.statusCode').should('equal', 200); + cy.get('#parentTreeNumber'); + + // Click 'Dothistroma needle blight (DFS)' checkbox + cy.get(`.${prefix}--toolbar-content > span`) + .eq(0) + .find('button') + .as('clickShowHideBtn') + .click({force: true}); + + cy.get('ul.parent-tree-table-toggle-menu') + .find('li') + .contains('Dothistroma needle blight (DFS)') + .click(); + + cy.get('.parent-tree-step-table-container') + .find('h4') + .as('closeShowHideDropdown') + .click(); + + cy.get('thead.table-header') + .find('#dfs') + .should('exist'); + + // Click 'Comandra blister rust (DSC)' checkbox + cy.get('@clickShowHideBtn') + .click({force: true}); + + cy.get('ul.parent-tree-table-toggle-menu') + .find('li') + .contains('Comandra blister rust (DSC)') + .click(); + + cy.get('@closeShowHideDropdown') + .click(); + + cy.get('thead.table-header') + .find('#dsc') + .should('exist'); + + // Click 'Western gall rust (DSG)' checkbox + cy.get('@clickShowHideBtn') + .click({force: true}); + + cy.get('ul.parent-tree-table-toggle-menu') + .find('li') + .contains('Western gall rust (DSG)') + .click(); + + cy.get('@closeShowHideDropdown') + .click(); + + cy.get('thead.table-header') + .find('#dsg') + .should('exist'); + + // Click 'Volume growth (GVO)' checkbox + cy.get('@clickShowHideBtn') + .click({force: true}); + + cy.get('ul.parent-tree-table-toggle-menu') + .find('li') + .contains('Volume growth (GVO)') + .click(); + + cy.get('@closeShowHideDropdown') + .click(); + + cy.get('thead.table-header') + .find('#gvo') + .should('exist'); + + // Save changes + cy.saveSeedlotRegFormProgress(); + }); + + it('Check \'More Options\' button functionality', () => { + // Intercept the call + cy.intercept({ + method: 'GET', + url: '**/api/parent-trees/vegetation-codes/*' + }).as('parentTreesUnderVegCode'); + + // Wait for the table to load + cy.wait('@parentTreesUnderVegCode', { timeout: THIRTY_SECONDS }).its('response.statusCode').should('equal', 200); + cy.get('#parentTreeNumber'); + + // Check Download file option + cy.get(`.${prefix}--toolbar-content > span`) + .eq(1) + .find('button') + .as('clickMoreOptionsBtn') + .click(); + + cy.get('ul.parent-tree-table-option-menu') + .find('li') + .contains('Download table template') + .click(); + + cy.readFile(`${Cypress.config('downloadsFolder')}/Seedlot_composition_template.csv`); + + // Enter values in Cone count and Pollen count columns of the table + cy.get('#212-coneCount-value-input') + .clear() + .type('16') + .blur(); + + cy.get('#212-pollenCount-value-input') + .clear() + .type('17') + .blur(); + + cy.get('#219-coneCount-value-input') + .clear() + .type('23') + .blur(); + + cy.get('#219-pollenCount-value-input') + .clear() + .type('28') + .blur(); + + // Click 'Clean table data' option + cy.get('@clickMoreOptionsBtn') + .click(); + + cy.get('ul.parent-tree-table-option-menu') + .find('li') + .contains('Clean table data') + .as('clickCleanTableBtn') + .click(); + + cy.get(`.${prefix}--modal-container[aria-label="Clean table data"]`) + .should('be.visible'); + + // Check Cancel button of 'Clean table data' dialog box + cy.get(`.${prefix}--modal-container[aria-label="Clean table data"]`) + .find('button') + .contains('Cancel') + .click(); + + cy.get(`.${prefix}--modal-container[aria-label="Clean table data"]`) + .should('not.be.visible'); + + // Check 'X' button of 'Clean table data' dialog box + cy.get('@clickMoreOptionsBtn') + .click(); + + cy.get('@clickCleanTableBtn') + .click(); + + cy.get(`.${prefix}--modal-container[aria-label="Clean table data"]`) + .find('button[aria-label="close"]') + .click(); + + cy.get(`.${prefix}--modal-container[aria-label="Clean table data"]`) + .should('not.be.visible'); + + // Check 'Clean table data' button of 'Clean table data' dialog box + cy.get('@clickMoreOptionsBtn') + .click(); + + cy.get('@clickCleanTableBtn') + .click(); + + cy.get(`.${prefix}--modal-container[aria-label="Clean table data"]`) + .find('button') + .contains('Clean table data') + .click(); + + // Check values in Cone count and Pollen count columns of the table + cy.get('#212-coneCount-value-input') + .should('have.value', ''); + + cy.get('#212-pollenCount-value-input') + .should('have.value', ''); + + cy.get('#219-coneCount-value-input') + .should('have.value', ''); + + cy.get('#219-pollenCount-value-input') + .should('have.value', ''); + + // Check upload button functionality + cy.get('button.upload-button') + .click(); + + cy.get(`.${prefix}--modal-container[aria-label="Seedlot registration"]`) + .should('be.visible'); + + cy.get('button') + .contains('Cancel') + .click(); + + cy.get(`.${prefix}--modal-container[aria-label="Seedlot registration"]`) + .should('not.be.visible'); + + // Check file upload functionality + cy.get('button.upload-button') + .click({force: true}); + + cy.get(`.${prefix}--modal-container[aria-label="Seedlot registration"]`) + .should('be.visible'); + + cy.get(`.${prefix}--file`) + .find(`input.${prefix}--file-input`) + .selectFile('cypress/fixtures/Seedlot_composition_template.csv', {force: true}); + + cy.get('button') + .contains('Import file and continue') + .click(); + + // Compare values in Cone count and Pollen count columns of the table with the csv file + cy.get('#212-coneCount-value-input') + .should('have.value', '1'); + + cy.get('#212-pollenCount-value-input') + .should('have.value', '46'); + + cy.get('#219-coneCount-value-input') + .should('have.value', '2'); + + cy.get('#219-pollenCount-value-input') + .should('have.value', '22'); + + // Save changes + cy.saveSeedlotRegFormProgress(); + }); + + it('Pagination', () => { + // Intercept the call + cy.intercept({ + method: 'GET', + url: '**/api/parent-trees/vegetation-codes/*' + }).as('parentTreesUnderVegCode'); + + // Wait for the table to load + cy.wait('@parentTreesUnderVegCode', { timeout: THIRTY_SECONDS }).its('response.statusCode').should('equal', 200); + cy.get('#parentTreeNumber'); + + const dropdownNumber = '20'; + // Number of item dropdown + cy.get(`.${prefix}--pagination__left`) + .find('select') + .select(dropdownNumber); + + // Wait for the table to load + cy.get('#parentTreeNumber'); + + cy.get(`.${prefix}--pagination__left`) + .find(`.${prefix}--pagination__items-count`) + .should('include.text', '1–20'); + + // Check total number of rows are 20 + cy.get(`table.${prefix}--data-table tbody`) + .find('tr') + .then(($row) => { + expect($row.length).equal(parseInt(dropdownNumber)); + }); + + // Page number dropdown + cy.get(`.${prefix}--pagination__right`) + .find(`select.${prefix}--select-input`) + .select('2'); + + cy.get(`.${prefix}--pagination__left`) + .find(`.${prefix}--pagination__items-count`) + .should('include.text', '21–40'); + + cy.get(`.${prefix}--pagination__right`) + .find(`select.${prefix}--select-input`) + .select('1'); + + // Forward Backward buttons + cy.get(`.${prefix}--pagination__control-buttons`) + .find(`button.${prefix}--pagination__button--forward`) + .click(); + + cy.get(`.${prefix}--pagination__right`) + .find(`select.${prefix}--select-input`) + .should('have.value', '2'); + + cy.get(`.${prefix}--pagination__control-buttons`) + .find(`button.${prefix}--pagination__button--backward`) + .click(); + + cy.get(`.${prefix}--pagination__right`) + .find(`select.${prefix}--select-input`) + .should('have.value', '1'); + }); + + it('Calculate Metrics button', () => { + // Intercept the call + cy.intercept({ + method: 'GET', + url: '**/api/parent-trees/vegetation-codes/*' + }).as('parentTreesUnderVegCode'); + + // Wait for the table to load + cy.wait('@parentTreesUnderVegCode', { timeout: THIRTY_SECONDS }).its('response.statusCode').should('equal', 200); + cy.get('#parentTreeNumber'); + + // Check info sections not visible in DOM + cy.get('.info-section-sub-title') + .should('not.exist'); + + // Click 'Calculate metrics' button + cy.get('.gen-worth-cal-row') + .find('button') + .contains('Calculate metrics') + .click(); + + // Check info sections visible in DOM + cy.get('.info-section-sub-title') + .find(`.${prefix}--col`) + .contains('Genetic worth and percent of Tested parent tree contribution') + .should('be.visible'); + + cy.get('.info-section-sub-title') + .find(`.${prefix}--col`) + .contains('Effective population size and diversity') + .should('be.visible'); + + cy.get('.info-section-sub-title') + .find(`.${prefix}--col`) + .contains('Orchard parent tree geospatial summary') + .should('be.visible'); + + // Save changes + cy.saveSeedlotRegFormProgress(); + }); +}); diff --git a/frontend/cypress/e2e/smoke-test/dashboard-page.cy.ts b/frontend/cypress/e2e/smoke-test/dashboard-page.cy.ts index 3eebc51c1..305a3acc4 100644 --- a/frontend/cypress/e2e/smoke-test/dashboard-page.cy.ts +++ b/frontend/cypress/e2e/smoke-test/dashboard-page.cy.ts @@ -5,7 +5,6 @@ import prefix from '../../../src/styles/classPrefix'; describe('Dashboard page test', () => { let dashboardPageData: { - subtitle: string, emptySectionTitle: string, emptySectionSubtitle: string }; @@ -26,10 +25,6 @@ describe('Dashboard page test', () => { */ it('should load and display dashboard page correctly', () => { cy.isPageTitle(NavigationLabels.Dashboard); - - cy.get('.title-section') - .find('.subtitle-section') - .should('have.text', dashboardPageData.subtitle); }); it('should display empty favourite activities section correctly', () => { diff --git a/frontend/cypress/e2e/smoke-test/my-seedlots.cy.ts b/frontend/cypress/e2e/smoke-test/my-seedlots.cy.ts index e971771b8..378d7654a 100644 --- a/frontend/cypress/e2e/smoke-test/my-seedlots.cy.ts +++ b/frontend/cypress/e2e/smoke-test/my-seedlots.cy.ts @@ -248,17 +248,33 @@ describe('My seedlots page', () => { }); it('Pagination', () => { + const dropdownNumber = '10'; // Number of item dropdown cy.get(`.${prefix}--pagination__left`) .find('select') - .as('dropdownBtn') - .select('10'); + .select(dropdownNumber); - // @ngunner15 the drop down is selected with value 10, what we need here is to verify - // that the total number of rows on this page is actually 10. - // you might be able to do that by checking the total number of under - cy.get('@dropdownBtn') - .should('have.value', '10'); + // Wait for table body to load + cy.get(`table.${prefix}--data-table tbody`) + .find('tr') + .eq(2); + + cy.get(`.${prefix}--pagination__left`) + .find(`.${prefix}--pagination__items-count`) + .should('include.text', '1–10'); + + // Page number dropdown + cy.get(`.${prefix}--pagination__right`) + .find(`select.${prefix}--select-input`) + .select('2'); + + cy.get(`.${prefix}--pagination__left`) + .find(`.${prefix}--pagination__items-count`) + .should('include.text', '11'); + + cy.get(`.${prefix}--pagination__right`) + .find(`select.${prefix}--select-input`) + .select('1'); // Forward Backward buttons cy.get(`.${prefix}--pagination__control-buttons`) @@ -276,8 +292,6 @@ describe('My seedlots page', () => { cy.get(`.${prefix}--pagination__right`) .find(`select.${prefix}--select-input`) .should('have.value', '1'); - - // @ngunner15 Need to test the page number dropdown }); it('Select a seedlot row should redirect to its detail page', () => { diff --git a/frontend/cypress/e2e/smoke-test/seedlot-dashboard.cy.ts b/frontend/cypress/e2e/smoke-test/seedlot-dashboard.cy.ts index f4485605e..52615c58b 100644 --- a/frontend/cypress/e2e/smoke-test/seedlot-dashboard.cy.ts +++ b/frontend/cypress/e2e/smoke-test/seedlot-dashboard.cy.ts @@ -3,7 +3,6 @@ import { SeedlotRegFixtureType } from '../../definitions'; describe('Seedlot Dashboard test', () => { let seedlotDashboardData: { - subtitle: string, secondSectionTitle: string, secondSectionSubtitle: string, emptySectionTitle: string, @@ -29,9 +28,6 @@ describe('Seedlot Dashboard test', () => { it('should display seedlot dashboard page title and subtitle', () => { cy.isPageTitle(NavigationLabels.Seedlots); - cy.get('.title-section') - .find('.subtitle-section') - .should('have.text', seedlotDashboardData.subtitle); cy.get('.recent-seedlots-title-section') .find('h2') .should('have.text', seedlotDashboardData.secondSectionTitle); @@ -48,7 +44,7 @@ describe('Seedlot Dashboard test', () => { const seedlotNumber = sNumber as string; cy.get(`#seedlot-table-cell-${seedlotNumber}-seedlotSpecies`).should('have.text', species); - cy.get(`#seedlot-table-cell-${seedlotNumber}-seedlotStatus`).should('have.text', 'Pending'); + cy.get(`#seedlot-table-cell-${seedlotNumber}-seedlotStatus`).should('have.text', 'Incomplete'); }); }); }); diff --git a/frontend/cypress/e2e/smoke-test/seedlot-detail.cy.ts b/frontend/cypress/e2e/smoke-test/seedlot-detail.cy.ts index d94585777..8637e8490 100644 --- a/frontend/cypress/e2e/smoke-test/seedlot-detail.cy.ts +++ b/frontend/cypress/e2e/smoke-test/seedlot-detail.cy.ts @@ -104,7 +104,7 @@ describe('Seedlot detail page', () => { cy.contains('p.seedlot-summary-info-label', 'Status') .next() .children('span') - .should('have.text', 'Pending'); + .should('have.text', 'Incomplete'); cy.contains('p.seedlot-summary-info-label', 'Approved at') .siblings('p.seedlot-summary-info-value') diff --git a/frontend/cypress/fixtures/Seedlot_composition_template.csv b/frontend/cypress/fixtures/Seedlot_composition_template.csv new file mode 100644 index 000000000..af0f19167 --- /dev/null +++ b/frontend/cypress/fixtures/Seedlot_composition_template.csv @@ -0,0 +1,4 @@ +Parent Tree number,Cone count,Pollen count,SMP success,Pollen contamination +212,1,46,, +219,2,22,, +222,7,8,, diff --git a/frontend/cypress/fixtures/aclass-reg-form.json b/frontend/cypress/fixtures/aclass-reg-form.json index 2eb8bf3e0..2faebe5d1 100644 --- a/frontend/cypress/fixtures/aclass-reg-form.json +++ b/frontend/cypress/fixtures/aclass-reg-form.json @@ -39,7 +39,7 @@ "subtitle": "Enter the contributing orchard information", "singleOrchardError": "Are you sure you want to change the orchard? If yes, then you will lose the parent tree and SMP information in Step 5", "doubleOrchardError": "Are you sure you want to delete the additional orchard? If yes, then you will lose the parent tree and SMP information in Step 5", - "additionalOrchardLabel": "Select an additional orchard", + "additionalOrchardLabel": "Select a secondary orchard", "gameteTitle": "Gamete information", "gameteSubtitle": "Enter the seedlot gamete information", "pollenTitle": "Pollen information", @@ -49,11 +49,11 @@ }, "extraction": { "extrationTitle": "Extraction information", - "extrationSubtitle": "Enter the extractory agency information and extraction's star and end dates for this seedlot", + "extrationSubtitle": "Enter the extractory agency information and extraction's start and end dates for this seedlot", "storageTitle": "Temporary seed storage", - "storageSubtitle": "Enter the seed storage agency information and storage's star and end dates for this seedlot", - "extractionCheckboxText": "The extractory agency is the Tree Seed Center (TSC)", - "storageCheckboxText": "The seed storage agency is the Tree Seed Center (TSC)", + "storageSubtitle": "Enter the seed storage agency information and storage's start and end dates for this seedlot", + "extractionCheckboxText": "The extractory agency is the Tree Seed Centre (TSC)", + "storageCheckboxText": "The seed storage agency is the Tree Seed Centre (TSC)", "agencyErrorMsg": "Please enter a valid acronym that identifies the agency", "agencyValidationMsg": "Agency validation failed. Please retry verification", "locationErrorMsg": "This location code is not valid for the selected agency, please enter a valid one or change the agency", diff --git a/frontend/cypress/fixtures/aclass-seedlot.json b/frontend/cypress/fixtures/aclass-seedlot.json index f1d987559..2ea23749a 100644 --- a/frontend/cypress/fixtures/aclass-seedlot.json +++ b/frontend/cypress/fixtures/aclass-seedlot.json @@ -1,7 +1,7 @@ { "pli": { "agencyAcronym": "WFP", - "agencyName": "00149081 - WESTERN FOREST PRODUCTS INC. - WFP", + "agencyName": "WFP - WESTERN FOREST PRODUCTS INC. - 00149081", "agencyNumber": "01", "email": "test@gov.bc.ca", "species": "PLI - Lodgepole pine", @@ -11,7 +11,7 @@ }, "cw": { "agencyAcronym": "WFP", - "agencyName": "00149081 - WESTERN FOREST PRODUCTS INC. - WFP", + "agencyName": "WFP - WESTERN FOREST PRODUCTS INC. - 00149081", "agencyNumber": "01", "email": "apo@gov.bc.ca", "species": "CW - Western redcedar", @@ -21,7 +21,7 @@ }, "dr": { "agencyAcronym": "WFP", - "agencyName": "00149081 - WESTERN FOREST PRODUCTS INC. - WFP", + "agencyName": "WFP - WESTERN FOREST PRODUCTS INC. - 00149081", "agencyNumber": "01", "email": "beane@gov.bc.ca", "species": "DR - Red alder", @@ -31,7 +31,7 @@ }, "ep": { "agencyAcronym": "WFP", - "agencyName": "00149081 - WESTERN FOREST PRODUCTS INC. - WFP", + "agencyName": "WFP - WESTERN FOREST PRODUCTS INC. - 00149081", "agencyNumber": "01", "email": "cidaw@gov.bc.ca", "species": "EP - Paper birch", @@ -41,7 +41,7 @@ }, "fdc": { "agencyAcronym": "WFP", - "agencyName": "00149081 - WESTERN FOREST PRODUCTS INC. - WFP", + "agencyName": "WFP - WESTERN FOREST PRODUCTS INC. - 00149081", "agencyNumber": "01", "email": "dha@gov.bc.ca", "species": "FDC - Coastal Douglas-fir", diff --git a/frontend/cypress/fixtures/dashboard-page.json b/frontend/cypress/fixtures/dashboard-page.json index 5a236550f..40bb09832 100644 --- a/frontend/cypress/fixtures/dashboard-page.json +++ b/frontend/cypress/fixtures/dashboard-page.json @@ -1,5 +1,4 @@ { - "subtitle": "", "emptySectionTitle": "You don't have any favourites to show yet!", "emptySectionSubtitle": "You can favourite your most used activities by clicking on the heart icon inside each page" } diff --git a/frontend/cypress/fixtures/seedlot-dashboard.json b/frontend/cypress/fixtures/seedlot-dashboard.json index f7b9c7659..c627dec7b 100644 --- a/frontend/cypress/fixtures/seedlot-dashboard.json +++ b/frontend/cypress/fixtures/seedlot-dashboard.json @@ -1,5 +1,4 @@ { - "subtitle": "Register and manage your seedlots", "secondSectionTitle": "My seedlots", "secondSectionSubtitle": "Check a summary of your recent seedlots", "emptySectionTitle": "There is no seedlot to show yet!", diff --git a/frontend/cypress/support/commands.ts b/frontend/cypress/support/commands.ts index 2aa4038eb..1c0ab58da 100644 --- a/frontend/cypress/support/commands.ts +++ b/frontend/cypress/support/commands.ts @@ -103,6 +103,10 @@ Cypress.Commands.overwrite('log', (log, ...args) => { }); Cypress.Commands.add('saveSeedlotRegFormProgress', () => { + cy.get('.seedlot-registration-button-row') + .find('button.form-action-btn') + .should('not.be.disabled'); + cy.get('.seedlot-registration-button-row') .find('button.form-action-btn') .contains('Save changes') diff --git a/frontend/openshift.deploy.yml b/frontend/openshift.deploy.yml index c65a94039..d57f1747a 100644 --- a/frontend/openshift.deploy.yml +++ b/frontend/openshift.deploy.yml @@ -31,9 +31,9 @@ parameters: - name: CPU_LIMIT value: 30m - name: MEMORY_REQUEST - value: 30Mi + value: 20Mi - name: MEMORY_LIMIT - value: 50Mi + value: 40Mi - name: MIN_REPLICAS description: The minimum amount of replicas for the horizontal pod autoscaler. value: "3" @@ -52,9 +52,10 @@ parameters: - name: VITE_USER_POOLS_ID description: AWS Cognito Pools ID required: true - - name: VITE_USER_POOLS_WEB_CLIENT_ID - description: AWS Cognito Web Client ID - required: true + - name: RANDOM_EXPRESSION + description: Random expression to make sure deployments update + from: "[a-zA-Z0-9]{32}" + generate: expression objects: - apiVersion: apps/v1 kind: Deployment @@ -63,7 +64,7 @@ objects: app: ${NAME}-${ZONE} name: ${NAME}-${ZONE}-${COMPONENT} spec: - replicas: 1 + replicas: ${{MIN_REPLICAS}} selector: matchLabels: deployment: ${NAME}-${ZONE}-${COMPONENT} @@ -98,9 +99,14 @@ objects: - name: VITE_USER_POOLS_ID value: ${VITE_USER_POOLS_ID} - name: VITE_USER_POOLS_WEB_CLIENT_ID - value: ${VITE_USER_POOLS_WEB_CLIENT_ID} + valueFrom: + secretKeyRef: + name: ${NAME}-${ZONE}-frontend + key: vite-user-pools-web-client-id - name: VITE_ZONE value: ${FAM_MODDED_ZONE} + - name: RANDOM_EXPRESSION + value: ${RANDOM_EXPRESSION} resources: requests: cpu: ${CPU_REQUEST} @@ -166,6 +172,12 @@ objects: target: type: Utilization averageUtilization: 80 + - type: Resource + resource: + name: memory + target: + type: Utilization + averageUtilization: 80 - apiVersion: networking.k8s.io/v1 kind: NetworkPolicy metadata: diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 44598db01..cfc1cf014 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -24,6 +24,7 @@ "@tsconfig/node18": "^18.0.0", "@types/carbon-components-react": "^7.55.2", "@types/luxon": "^3.4.2", + "@types/react-beforeunload": "^2.1.5", "@types/react-dom": "^18.0.11", "@types/validator": "^13.7.17", "@typescript-eslint/parser": "^7.0.0", @@ -33,16 +34,16 @@ "bignumber.js": "^9.0.0", "braces": "^3.0.3", "eslint-config-airbnb-typescript": "^18.0.0", + "fast-xml-parser": "^4.4.1", "luxon": "^3.4.3", - "moment": "^2.29.4", - "node-sass": "^9.0.0", "react": "^18.2.0", + "react-beforeunload": "^2.6.0", "react-dom": "^18.2.0", "react-hash-string": "^1.0.0", "react-router-dom": "^6.21.2", "react-test-renderer": "^18.2.0", "react-toastify": "^10.0.0", - "sass": "1.77.7", + "sass": "^1.77.7", "typescript": "^5.4.4", "validator": "^13.9.0", "vite": "^5.2.8", @@ -4014,27 +4015,6 @@ "node": ">= 12.0.0" } }, - "node_modules/@aws-sdk/client-sts/node_modules/fast-xml-parser": { - "version": "4.2.5", - "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-4.2.5.tgz", - "integrity": "sha512-B9/wizE4WngqQftFPmdaMYlXoJlJOYxGQOanC77fq9k8+Z0v5dDSVh+3glErdIROP//s/jgb7ZuxKfB8nVyo0g==", - "funding": [ - { - "type": "paypal", - "url": "https://paypal.me/naturalintelligence" - }, - { - "type": "github", - "url": "https://github.com/sponsors/NaturalIntelligence" - } - ], - "dependencies": { - "strnum": "^1.0.5" - }, - "bin": { - "fxparser": "src/cli/cli.js" - } - }, "node_modules/@aws-sdk/client-sts/node_modules/uuid": { "version": "8.3.2", "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", @@ -8776,11 +8756,6 @@ "node": "^12.22.0 || ^14.17.0 || >=16.0.0" } }, - "node_modules/@gar/promisify": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/@gar/promisify/-/promisify-1.1.3.tgz", - "integrity": "sha512-k2Ty1JcVojjJFwrg/ThKi2ujJ7XNLYaFGNB/bWT9wGR+oSMJHMa5w+CUq6p/pVrKeNNgA7pCqEcjSnHVoqJQFw==" - }, "node_modules/@hapi/hoek": { "version": "9.3.0", "resolved": "https://registry.npmjs.org/@hapi/hoek/-/hoek-9.3.0.tgz", @@ -9311,61 +9286,6 @@ "node": ">= 8" } }, - "node_modules/@npmcli/fs": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/@npmcli/fs/-/fs-2.1.2.tgz", - "integrity": "sha512-yOJKRvohFOaLqipNtwYB9WugyZKhC/DZC4VYPmpaCzDBrA8YpK3qHZ8/HGscMnE4GqbkLNuVcCnxkeQEdGt6LQ==", - "dependencies": { - "@gar/promisify": "^1.1.3", - "semver": "^7.3.5" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || >=16.0.0" - } - }, - "node_modules/@npmcli/fs/node_modules/lru-cache": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", - "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/@npmcli/fs/node_modules/semver": { - "version": "7.5.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", - "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", - "dependencies": { - "lru-cache": "^6.0.0" - }, - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/@npmcli/fs/node_modules/yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" - }, - "node_modules/@npmcli/move-file": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/@npmcli/move-file/-/move-file-2.0.1.tgz", - "integrity": "sha512-mJd2Z5TjYWq/ttPLLGqArdtnC74J6bOzg4rMDnN+p1xTacZ2yPRCk2y0oSWQtygLR9YVQXgOcONrwtnk3JupxQ==", - "deprecated": "This functionality has been moved to @npmcli/fs", - "dependencies": { - "mkdirp": "^1.0.4", - "rimraf": "^3.0.2" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || >=16.0.0" - } - }, "node_modules/@pkgjs/parseargs": { "version": "0.11.0", "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", @@ -11585,6 +11505,8 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-2.0.0.tgz", "integrity": "sha512-XCuKFP5PS55gnMVu3dty8KPatLqUoy/ZYzDzAGCQ8JNFCkLXzmI7vNHCR+XpbZaMWQK/vQubr7PkYq8g470J/A==", + "optional": true, + "peer": true, "engines": { "node": ">= 10" } @@ -11771,11 +11693,6 @@ "resolved": "https://registry.npmjs.org/@types/luxon/-/luxon-3.4.2.tgz", "integrity": "sha512-TifLZlFudklWlMBfhubvgqTXRzLDI5pCbGa4P8a3wPyUQSW+1xQ5eDsreP9DWHX3tjq1ke96uYG/nwundroWcA==" }, - "node_modules/@types/minimist": { - "version": "1.2.5", - "resolved": "https://registry.npmjs.org/@types/minimist/-/minimist-1.2.5.tgz", - "integrity": "sha512-hov8bUuiLiyFPGyFPE1lwWhmzYbirOXQNNo40+y3zow8aFVTeyn3VWL0VFFfdNddA8S4Vf0Tc062rzyNr7Paag==" - }, "node_modules/@types/node": { "version": "20.12.7", "resolved": "https://registry.npmjs.org/@types/node/-/node-20.12.7.tgz", @@ -11793,11 +11710,6 @@ "form-data": "^3.0.0" } }, - "node_modules/@types/normalize-package-data": { - "version": "2.4.4", - "resolved": "https://registry.npmjs.org/@types/normalize-package-data/-/normalize-package-data-2.4.4.tgz", - "integrity": "sha512-37i+OaWTh9qeK4LSHPsyRC7NahnGotNuZvjLSgcPzblpHB3rrCJxAOgI5gCdKm7coonsaX1Of0ILiTcnZjbfxA==" - }, "node_modules/@types/prop-types": { "version": "15.7.11", "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.11.tgz", @@ -11812,6 +11724,14 @@ "csstype": "^3.0.2" } }, + "node_modules/@types/react-beforeunload": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@types/react-beforeunload/-/react-beforeunload-2.1.5.tgz", + "integrity": "sha512-xrczkBsJ26lidA/Y/Oln0lYgux79KUkpomaOdnQFPRV9Sx1Mgx53RkYsgZ234HG1ZMKGCmzpk7jO5T4h3bmjvg==", + "dependencies": { + "@types/react": "*" + } + }, "node_modules/@types/react-dom": { "version": "18.2.18", "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.2.18.tgz", @@ -12564,11 +12484,6 @@ "optional": true, "peer": true }, - "node_modules/abbrev": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", - "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==" - }, "node_modules/abort-controller": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz", @@ -12647,6 +12562,8 @@ "version": "6.0.2", "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", + "optional": true, + "peer": true, "dependencies": { "debug": "4" }, @@ -12654,21 +12571,11 @@ "node": ">= 6.0.0" } }, - "node_modules/agentkeepalive": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/agentkeepalive/-/agentkeepalive-4.5.0.tgz", - "integrity": "sha512-5GG/5IbQQpC9FpkRGsSvZI5QYeSCzlJHdpBQntCsuTOxhKD8lqKhrleg2Yi7yvMIf82Ycmmqln9U8V9qwEiJew==", - "dependencies": { - "humanize-ms": "^1.2.1" - }, - "engines": { - "node": ">= 8.0.0" - } - }, "node_modules/aggregate-error": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-3.1.0.tgz", "integrity": "sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==", + "dev": true, "dependencies": { "clean-stack": "^2.0.0", "indent-string": "^4.0.0" @@ -12904,11 +12811,6 @@ "node": ">=8" } }, - "node_modules/aproba": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/aproba/-/aproba-2.0.0.tgz", - "integrity": "sha512-lYe4Gx7QT+MKGbDsA+Z+he/Wtef0BiwDOlK/XkBrdfsh9J/jPPXbX0tE9x9cl27Tmu5gg3QUbUrQYa/y+KOHPQ==" - }, "node_modules/arch": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/arch/-/arch-2.2.0.tgz", @@ -12944,18 +12846,6 @@ "node": ">=14" } }, - "node_modules/are-we-there-yet": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-3.0.1.tgz", - "integrity": "sha512-QZW4EDmGwlYur0Yyf/b2uGucHQMa8aFUP7eu9ddR73vvhFyt4V0Vl3QHPcTNJ8l6qYOBdxgXdnBXQrHilfRQBg==", - "dependencies": { - "delegates": "^1.0.0", - "readable-stream": "^3.6.0" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || >=16.0.0" - } - }, "node_modules/arg": { "version": "5.0.2", "resolved": "https://registry.npmjs.org/arg/-/arg-5.0.2.tgz", @@ -13098,14 +12988,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/arrify": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/arrify/-/arrify-1.0.1.tgz", - "integrity": "sha512-3CYzex9M9FGQjCGMGyi6/31c8GJbgb0qGyrx5HWxPd0aCwh4cB2YjMb2Xf9UuoogrMrlO9cTqnB5rI5GHZTcUA==", - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/asap": { "version": "2.0.6", "resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz", @@ -13172,14 +13054,6 @@ "integrity": "sha512-baNZyqaaLhyLVKm/DlvdW051MSgO6b8eVfIezl9E5PqWxFgzLm/wQntEW4zOytVburDEr0JlALEpdOFwvErLsg==", "dev": true }, - "node_modules/async-foreach": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/async-foreach/-/async-foreach-0.1.3.tgz", - "integrity": "sha512-VUeSMD8nEGBWaZK4lizI1sf3yEC7pnAQ/mrI7pC2fBz2s/tq5jWWEngTwaf0Gruu/OoXRGLGg1XFqpYBiGTYJA==", - "engines": { - "node": "*" - } - }, "node_modules/async-limiter": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/async-limiter/-/async-limiter-1.0.1.tgz", @@ -13265,9 +13139,10 @@ } }, "node_modules/axios": { - "version": "1.6.8", - "resolved": "https://registry.npmjs.org/axios/-/axios-1.6.8.tgz", - "integrity": "sha512-v/ZHtJDU39mDpyBoFVkETcd/uNdxrWRrg3bKpOKzXFA6Bvqopts6ALSMU3y6ijYxbw2B+wPrIv46egTzJXCLGQ==", + "version": "1.7.4", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.7.4.tgz", + "integrity": "sha512-DukmaFRnY6AzAALSH4J2M3k6PkaC+MfaAGdEERRWcC9q3/TWQwLpHR8ZRLKTdQ3aBDL64EdluRDjJqKw+BPZEw==", + "license": "MIT", "dependencies": { "follow-redirects": "^1.15.6", "form-data": "^4.0.0", @@ -13278,6 +13153,7 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", + "license": "MIT", "dependencies": { "asynckit": "^0.4.0", "combined-stream": "^1.0.8", @@ -13589,71 +13465,6 @@ "node": ">=8" } }, - "node_modules/cacache": { - "version": "16.1.3", - "resolved": "https://registry.npmjs.org/cacache/-/cacache-16.1.3.tgz", - "integrity": "sha512-/+Emcj9DAXxX4cwlLmRI9c166RuL3w30zp4R7Joiv2cQTtTtA+jeuCAjH3ZlGnYS3tKENSrKhAzVVP9GVyzeYQ==", - "dependencies": { - "@npmcli/fs": "^2.1.0", - "@npmcli/move-file": "^2.0.0", - "chownr": "^2.0.0", - "fs-minipass": "^2.1.0", - "glob": "^8.0.1", - "infer-owner": "^1.0.4", - "lru-cache": "^7.7.1", - "minipass": "^3.1.6", - "minipass-collect": "^1.0.2", - "minipass-flush": "^1.0.5", - "minipass-pipeline": "^1.2.4", - "mkdirp": "^1.0.4", - "p-map": "^4.0.0", - "promise-inflight": "^1.0.1", - "rimraf": "^3.0.2", - "ssri": "^9.0.0", - "tar": "^6.1.11", - "unique-filename": "^2.0.0" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || >=16.0.0" - } - }, - "node_modules/cacache/node_modules/glob": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/glob/-/glob-8.1.0.tgz", - "integrity": "sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==", - "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^5.0.1", - "once": "^1.3.0" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/cacache/node_modules/lru-cache": { - "version": "7.18.3", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.18.3.tgz", - "integrity": "sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==", - "engines": { - "node": ">=12" - } - }, - "node_modules/cacache/node_modules/minimatch": { - "version": "5.1.6", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", - "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", - "dependencies": { - "brace-expansion": "^2.0.1" - }, - "engines": { - "node": ">=10" - } - }, "node_modules/cachedir": { "version": "2.4.0", "resolved": "https://registry.npmjs.org/cachedir/-/cachedir-2.4.0.tgz", @@ -13892,14 +13703,6 @@ "node": ">= 6" } }, - "node_modules/chownr": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz", - "integrity": "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==", - "engines": { - "node": ">=10" - } - }, "node_modules/chrome-launcher": { "version": "0.15.2", "resolved": "https://registry.npmjs.org/chrome-launcher/-/chrome-launcher-0.15.2.tgz", @@ -13989,6 +13792,7 @@ "version": "2.2.0", "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-2.2.0.tgz", "integrity": "sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==", + "dev": true, "engines": { "node": ">=6" } @@ -14051,6 +13855,7 @@ "version": "8.0.1", "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", + "peer": true, "dependencies": { "string-width": "^4.2.0", "strip-ansi": "^6.0.1", @@ -14125,14 +13930,6 @@ "simple-swizzle": "^0.2.2" } }, - "node_modules/color-support": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/color-support/-/color-support-1.1.3.tgz", - "integrity": "sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg==", - "bin": { - "color-support": "bin.js" - } - }, "node_modules/color/node_modules/color-convert": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", @@ -14307,11 +14104,6 @@ "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", "peer": true }, - "node_modules/console-control-strings": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz", - "integrity": "sha512-ty/fTekppD2fIwRvnZAVdeOiGd1c7YXEixbgJTNzqcxJWKQnjJ/V1bNEEE6hygpM3WjwHFUVK6HTjWSzV4a8sQ==" - }, "node_modules/convert-source-map": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", @@ -14725,29 +14517,6 @@ "node": ">=0.10.0" } }, - "node_modules/decamelize-keys": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/decamelize-keys/-/decamelize-keys-1.1.1.tgz", - "integrity": "sha512-WiPxgEirIV0/eIOMcnFBA3/IJZAZqKnwAwWyvvdi4lsr1WCN22nhdf/3db3DoZcUjTV2SqfzIwNyp6y2xs3nmg==", - "dependencies": { - "decamelize": "^1.1.0", - "map-obj": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/decamelize-keys/node_modules/map-obj": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/map-obj/-/map-obj-1.0.1.tgz", - "integrity": "sha512-7N/q3lyZ+LVCp7PzuxrJr4KMbBE2hW7BT7YNia330OFxIf4d3r5zVpicP2650l7CPN6RM9zOJRl3NGpqSiw3Eg==", - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/decimal.js": { "version": "10.4.3", "resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.4.3.tgz", @@ -14841,11 +14610,6 @@ "node": ">=0.4.0" } }, - "node_modules/delegates": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz", - "integrity": "sha512-bd2L678uiWATM6m5Z1VzNCErI3jiGzt6HGY8OVICs40JQq/HALfbyNJmp0UDakEY4pMMaN0Ly5om/B1VI/+xfQ==" - }, "node_modules/denodeify": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/denodeify/-/denodeify-1.2.1.tgz", @@ -15012,6 +14776,7 @@ "resolved": "https://registry.npmjs.org/encoding/-/encoding-0.1.13.tgz", "integrity": "sha512-ETBauow1T35Y/WZMkio9jiM0Z5xjHHmJ4XmjZOq1l/dXz3lr2sRn87nJy20RupqSh1F2m3HHPSp8ShIPQJrJ3A==", "optional": true, + "peer": true, "dependencies": { "iconv-lite": "^0.6.2" } @@ -15059,14 +14824,6 @@ "url": "https://github.com/fb55/entities?sponsor=1" } }, - "node_modules/env-paths": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-2.2.1.tgz", - "integrity": "sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==", - "engines": { - "node": ">=6" - } - }, "node_modules/envinfo": { "version": "7.11.0", "resolved": "https://registry.npmjs.org/envinfo/-/envinfo-7.11.0.tgz", @@ -15079,11 +14836,6 @@ "node": ">=4" } }, - "node_modules/err-code": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/err-code/-/err-code-2.0.3.tgz", - "integrity": "sha512-2bmlRpNKBxT/CRmPOlyISQpNj+qSeYvcym/uT0Jx2bMOlKLtSy1ZmLuVxSEKKyor/N5yhvp/ZiG1oE3DEYMSFA==" - }, "node_modules/error-ex": { "version": "1.3.2", "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", @@ -16336,9 +16088,9 @@ "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==" }, "node_modules/fast-xml-parser": { - "version": "4.3.2", - "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-4.3.2.tgz", - "integrity": "sha512-rmrXUXwbJedoXkStenj1kkljNF7ugn5ZjR9FJcwmCfcCbtOMDghPajbc+Tck6vE6F5XsDmx+Pr2le9fw8+pXBg==", + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-4.4.1.tgz", + "integrity": "sha512-xkjOecfnKGkSsOwtZ5Pz7Us/T6mrbPQrq0nh+aCO5V9nk5NLWmasAHumTKjiPJPWANe+kAZ84Jc8ooJkzZ88Sw==", "funding": [ { "type": "github", @@ -16824,17 +16576,6 @@ "node": ">=10" } }, - "node_modules/fs-minipass": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-2.1.0.tgz", - "integrity": "sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==", - "dependencies": { - "minipass": "^3.0.0" - }, - "engines": { - "node": ">= 8" - } - }, "node_modules/fs-monkey": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/fs-monkey/-/fs-monkey-1.0.5.tgz", @@ -16892,35 +16633,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/gauge": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/gauge/-/gauge-4.0.4.tgz", - "integrity": "sha512-f9m+BEN5jkg6a0fZjleidjN51VE1X+mPFQ2DJ0uv1V39oCLCbsGe6yjbBnp7eK7z/+GAon99a3nHuqbuuthyPg==", - "dependencies": { - "aproba": "^1.0.3 || ^2.0.0", - "color-support": "^1.1.3", - "console-control-strings": "^1.1.0", - "has-unicode": "^2.0.1", - "signal-exit": "^3.0.7", - "string-width": "^4.2.3", - "strip-ansi": "^6.0.1", - "wide-align": "^1.1.5" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || >=16.0.0" - } - }, - "node_modules/gaze": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/gaze/-/gaze-1.1.3.tgz", - "integrity": "sha512-BRdNm8hbWzFzWHERTrejLqwHDfS4GibPoq5wjTPIoJHoBtKGPg3xAFfxmM+9ztbXelxcf2hwQcaz1PtmFeue8g==", - "dependencies": { - "globule": "^1.0.0" - }, - "engines": { - "node": ">= 4.0.0" - } - }, "node_modules/gensync": { "version": "1.0.0-beta.2", "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", @@ -16968,14 +16680,6 @@ "node": ">=8.0.0" } }, - "node_modules/get-stdin": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/get-stdin/-/get-stdin-4.0.1.tgz", - "integrity": "sha512-F5aQMywwJ2n85s4hJPTT9RPxGmubonuB10MNYo17/xph174n2MIR33HRguhzVag10O/npM7SPk73LMZNP+FaWw==", - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/get-stream": { "version": "5.2.0", "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz", @@ -17149,58 +16853,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/globule": { - "version": "1.3.4", - "resolved": "https://registry.npmjs.org/globule/-/globule-1.3.4.tgz", - "integrity": "sha512-OPTIfhMBh7JbBYDpa5b+Q5ptmMWKwcNcFSR/0c6t8V4f3ZAVBEsKNY37QdVqmLRYSMhOUGYrY0QhSoEpzGr/Eg==", - "dependencies": { - "glob": "~7.1.1", - "lodash": "^4.17.21", - "minimatch": "~3.0.2" - }, - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/globule/node_modules/brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "node_modules/globule/node_modules/glob": { - "version": "7.1.7", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.7.tgz", - "integrity": "sha512-OvD9ENzPLbegENnYP5UUfJIirTg4+XwMWGaQfQTY0JenxNvvIKP3U3/tAQSPIu/lHxXYSZmpXlUHeqAIdKzBLQ==", - "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.0.4", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - }, - "engines": { - "node": "*" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/globule/node_modules/minimatch": { - "version": "3.0.8", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.8.tgz", - "integrity": "sha512-6FsRAQsxQ61mw+qP1ZzbL9Bc78x2p5OqNgNpnoAFLTrX8n5Kxph0CsnhmKKNXTWjXqU5L0pGPR7hYk+XWZr60Q==", - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" - } - }, "node_modules/gopd": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz", @@ -17230,14 +16882,6 @@ "node": ">= 10.x" } }, - "node_modules/hard-rejection": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/hard-rejection/-/hard-rejection-2.1.0.tgz", - "integrity": "sha512-VIZB+ibDhx7ObhAe7OVtoEbuP4h/MuOTHJ+J8h/eBXotJYl0fBgR72xDFCKgIh22OJZIOVNxBMWuhAr10r8HdA==", - "engines": { - "node": ">=6" - } - }, "node_modules/has-bigints": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.0.2.tgz", @@ -17301,11 +16945,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/has-unicode": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz", - "integrity": "sha512-8Rf9Y83NBReMnx0gFzA8JImQACstCYWUplepDa9xprwwtmgEZUF0h/i5xSA625zB/I37EtrswSST6OXxwaaIJQ==" - }, "node_modules/hasha": { "version": "5.2.2", "resolved": "https://registry.npmjs.org/hasha/-/hasha-5.2.2.tgz", @@ -17378,44 +17017,17 @@ "node": ">= 8" } }, - "node_modules/hosted-git-info": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-4.1.0.tgz", - "integrity": "sha512-kyCuEOWjJqZuDbRHzL8V93NzQhwIB71oFWSyzVo+KPZI+pnQPPxucdkrOZvkLRnrf5URsQM+IJ09Dw29cRALIA==", + "node_modules/html-encoding-sniffer": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-3.0.0.tgz", + "integrity": "sha512-oWv4T4yJ52iKrufjnyZPkrN0CH3QnrUqdB6In1g5Fe1mia8GmF36gnfNySxoZtxD5+NmYw1EElVXiBk93UeskA==", + "optional": true, + "peer": true, "dependencies": { - "lru-cache": "^6.0.0" + "whatwg-encoding": "^2.0.0" }, "engines": { - "node": ">=10" - } - }, - "node_modules/hosted-git-info/node_modules/lru-cache": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", - "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/hosted-git-info/node_modules/yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" - }, - "node_modules/html-encoding-sniffer": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-3.0.0.tgz", - "integrity": "sha512-oWv4T4yJ52iKrufjnyZPkrN0CH3QnrUqdB6In1g5Fe1mia8GmF36gnfNySxoZtxD5+NmYw1EElVXiBk93UeskA==", - "optional": true, - "peer": true, - "dependencies": { - "whatwg-encoding": "^2.0.0" - }, - "engines": { - "node": ">=12" + "node": ">=12" } }, "node_modules/html-escaper": { @@ -17424,11 +17036,6 @@ "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", "dev": true }, - "node_modules/http-cache-semantics": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.1.1.tgz", - "integrity": "sha512-er295DKPVsV82j5kw1Gjt+ADA/XYHsajl82cGNQG2eyoPkvgUhX+nDIyelzhIWbbsXP39EHcI6l5tYs2FYqYXQ==" - }, "node_modules/http-errors": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", @@ -17458,6 +17065,8 @@ "version": "5.0.0", "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-5.0.0.tgz", "integrity": "sha512-n2hY8YdoRE1i7r6M0w9DIw5GgZN0G25P8zLCRQ8rjXtTU3vsNFBI/vWK/UIeE6g5MUUz6avwAPXmL6Fy9D/90w==", + "optional": true, + "peer": true, "dependencies": { "@tootallnate/once": "2", "agent-base": "6", @@ -17485,6 +17094,8 @@ "version": "5.0.1", "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", + "optional": true, + "peer": true, "dependencies": { "agent-base": "6", "debug": "4" @@ -17502,19 +17113,12 @@ "node": ">=8.12.0" } }, - "node_modules/humanize-ms": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/humanize-ms/-/humanize-ms-1.2.1.tgz", - "integrity": "sha512-Fl70vYtsAFb/C06PTS9dZBo7ihau+Tu/DNCk/OyHhea07S+aeMWpFFkUaXRa8fI+ScZbEI8dfSxwY7gxZ9SAVQ==", - "dependencies": { - "ms": "^2.0.0" - } - }, "node_modules/iconv-lite": { "version": "0.6.3", "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", "optional": true, + "peer": true, "dependencies": { "safer-buffer": ">= 2.1.2 < 3.0.0" }, @@ -17610,15 +17214,11 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==", + "dev": true, "engines": { "node": ">=8" } }, - "node_modules/infer-owner": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/infer-owner/-/infer-owner-1.0.4.tgz", - "integrity": "sha512-IClj+Xz94+d7irH5qRyfJonOdfTzuDaifE6ZPWfx0N0+/ATZCbuTPq2prFl526urkQd90WyUKIh1DfBQ2hMz9A==" - }, "node_modules/inflight": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", @@ -17663,11 +17263,6 @@ "loose-envify": "^1.0.0" } }, - "node_modules/ip": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/ip/-/ip-2.0.1.tgz", - "integrity": "sha512-lJUL9imLTNi1ZfXT+DU6rBBdbiKGBuay9B6xGSPVjUeQwaH1RIGqef8RZkUtHioLmSNpPR5M4HVKJGm1j8FWVQ==" - }, "node_modules/is-array-buffer": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.2.tgz", @@ -17904,11 +17499,6 @@ "node": ">=8" } }, - "node_modules/is-lambda": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-lambda/-/is-lambda-1.0.1.tgz", - "integrity": "sha512-z7CMFGNrENq5iFB9Bqo64Xk6Y9sg+epq1myIcdHaGnbMTYOxvzsEtdYqQUylB7LxfkvgrrjP32T6Ywciio9UIQ==" - }, "node_modules/is-map": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/is-map/-/is-map-2.0.2.tgz", @@ -17959,14 +17549,6 @@ "node": ">=8" } }, - "node_modules/is-plain-obj": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-1.1.0.tgz", - "integrity": "sha512-yvkRyxmFKEOQ4pNXCmJG5AEQNlXJS5LaONXo5/cLdTZdWvsZ1ioJEonLGAosKlMWE8lwUy/bJzMjcw8az73+Fg==", - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/is-plain-object": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", @@ -18804,11 +18386,6 @@ "@sideway/pinpoint": "^2.0.0" } }, - "node_modules/js-base64": { - "version": "2.6.4", - "resolved": "https://registry.npmjs.org/js-base64/-/js-base64-2.6.4.tgz", - "integrity": "sha512-pZe//GGmwJndub7ZghVHz7vjb2LgC1m8B07Au3eYqeqv9emhESByMXxaEgkUkEqJe87oBbSniGYoQNIBklc7IQ==" - }, "node_modules/js-cookie": { "version": "2.2.1", "resolved": "https://registry.npmjs.org/js-cookie/-/js-cookie-2.2.1.tgz", @@ -19079,7 +18656,8 @@ "node_modules/json-parse-even-better-errors": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", - "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==" + "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", + "dev": true }, "node_modules/json-schema": { "version": "0.4.0", @@ -19168,6 +18746,7 @@ "version": "6.0.3", "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", + "peer": true, "engines": { "node": ">=0.10.0" } @@ -19257,7 +18836,8 @@ "node_modules/lines-and-columns": { "version": "1.2.4", "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", - "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==" + "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", + "dev": true }, "node_modules/listr2": { "version": "3.14.0", @@ -19802,40 +19382,6 @@ "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", "dev": true }, - "node_modules/make-fetch-happen": { - "version": "10.2.1", - "resolved": "https://registry.npmjs.org/make-fetch-happen/-/make-fetch-happen-10.2.1.tgz", - "integrity": "sha512-NgOPbRiaQM10DYXvN3/hhGVI2M5MtITFryzBGxHM5p4wnFxsVCbxkrBrDsk+EZ5OB4jEOT7AjDxtdF+KVEFT7w==", - "dependencies": { - "agentkeepalive": "^4.2.1", - "cacache": "^16.1.0", - "http-cache-semantics": "^4.1.0", - "http-proxy-agent": "^5.0.0", - "https-proxy-agent": "^5.0.0", - "is-lambda": "^1.0.1", - "lru-cache": "^7.7.1", - "minipass": "^3.1.6", - "minipass-collect": "^1.0.2", - "minipass-fetch": "^2.0.3", - "minipass-flush": "^1.0.5", - "minipass-pipeline": "^1.2.4", - "negotiator": "^0.6.3", - "promise-retry": "^2.0.1", - "socks-proxy-agent": "^7.0.0", - "ssri": "^9.0.0" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || >=16.0.0" - } - }, - "node_modules/make-fetch-happen/node_modules/lru-cache": { - "version": "7.18.3", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.18.3.tgz", - "integrity": "sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==", - "engines": { - "node": ">=12" - } - }, "node_modules/makeerror": { "version": "1.0.12", "resolved": "https://registry.npmjs.org/makeerror/-/makeerror-1.0.12.tgz", @@ -19886,42 +19432,6 @@ "integrity": "sha512-zYiwtZUcYyXKo/np96AGZAckk+FWWsUdJ3cHGGmld7+AhvcWmQyGCYUh1hc4Q/pkOhb65dQR/pqCyK0cOaHz4Q==", "peer": true }, - "node_modules/meow": { - "version": "9.0.0", - "resolved": "https://registry.npmjs.org/meow/-/meow-9.0.0.tgz", - "integrity": "sha512-+obSblOQmRhcyBt62furQqRAQpNyWXo8BuQ5bN7dG8wmwQ+vwHKp/rCFD4CrTP8CsDQD1sjoZ94K417XEUk8IQ==", - "dependencies": { - "@types/minimist": "^1.2.0", - "camelcase-keys": "^6.2.2", - "decamelize": "^1.2.0", - "decamelize-keys": "^1.1.0", - "hard-rejection": "^2.1.0", - "minimist-options": "4.1.0", - "normalize-package-data": "^3.0.0", - "read-pkg-up": "^7.0.1", - "redent": "^3.0.0", - "trim-newlines": "^3.0.0", - "type-fest": "^0.18.0", - "yargs-parser": "^20.2.3" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/meow/node_modules/type-fest": { - "version": "0.18.1", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.18.1.tgz", - "integrity": "sha512-OIAYXk8+ISY+qTOwkHtKqzAuxchoMiD9Udx+FSGQDuiRR+PJKJHc2NJAXlbhkGwTt/4/nKZxELY1w3ReWOL8mw==", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/merge-stream": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", @@ -20501,14 +20011,6 @@ "node": ">=6" } }, - "node_modules/min-indent": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/min-indent/-/min-indent-1.0.1.tgz", - "integrity": "sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==", - "engines": { - "node": ">=4" - } - }, "node_modules/minimatch": { "version": "9.0.3", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.3.tgz", @@ -20531,116 +20033,11 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/minimist-options": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/minimist-options/-/minimist-options-4.1.0.tgz", - "integrity": "sha512-Q4r8ghd80yhO/0j1O3B2BjweX3fiHg9cdOwjJd2J76Q135c+NDxGCqdYKQ1SKBuFfgWbAUzBfvYjPUEeNgqN1A==", - "dependencies": { - "arrify": "^1.0.1", - "is-plain-obj": "^1.1.0", - "kind-of": "^6.0.3" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/minipass": { - "version": "3.3.6", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", - "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/minipass-collect": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/minipass-collect/-/minipass-collect-1.0.2.tgz", - "integrity": "sha512-6T6lH0H8OG9kITm/Jm6tdooIbogG9e0tLgpY6mphXSm/A9u8Nq1ryBG+Qspiub9LjWlBPsPS3tWQ/Botq4FdxA==", - "dependencies": { - "minipass": "^3.0.0" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/minipass-fetch": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/minipass-fetch/-/minipass-fetch-2.1.2.tgz", - "integrity": "sha512-LT49Zi2/WMROHYoqGgdlQIZh8mLPZmOrN2NdJjMXxYe4nkN6FUyuPuOAOedNJDrx0IRGg9+4guZewtp8hE6TxA==", - "dependencies": { - "minipass": "^3.1.6", - "minipass-sized": "^1.0.3", - "minizlib": "^2.1.2" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || >=16.0.0" - }, - "optionalDependencies": { - "encoding": "^0.1.13" - } - }, - "node_modules/minipass-flush": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/minipass-flush/-/minipass-flush-1.0.5.tgz", - "integrity": "sha512-JmQSYYpPUqX5Jyn1mXaRwOda1uQ8HP5KAT/oDSLCzt1BYRhQU0/hDtsB1ufZfEEzMZ9aAVmsBw8+FWsIXlClWw==", - "dependencies": { - "minipass": "^3.0.0" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/minipass-pipeline": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/minipass-pipeline/-/minipass-pipeline-1.2.4.tgz", - "integrity": "sha512-xuIq7cIOt09RPRJ19gdi4b+RiNvDFYe5JH+ggNvBqGqpQXcru3PcRmOZuHBKWK1Txf9+cQ+HMVN4d6z46LZP7A==", - "dependencies": { - "minipass": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/minipass-sized": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/minipass-sized/-/minipass-sized-1.0.3.tgz", - "integrity": "sha512-MbkQQ2CTiBMlA2Dm/5cY+9SWFEN8pzzOXi6rlM5Xxq0Yqbda5ZQy9sU75a673FE9ZK0Zsbr6Y5iP6u9nktfg2g==", - "dependencies": { - "minipass": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/minipass/node_modules/yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" - }, - "node_modules/minizlib": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-2.1.2.tgz", - "integrity": "sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==", - "dependencies": { - "minipass": "^3.0.0", - "yallist": "^4.0.0" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/minizlib/node_modules/yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" - }, "node_modules/mkdirp": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", + "peer": true, "bin": { "mkdirp": "bin/cmd.js" }, @@ -20648,24 +20045,11 @@ "node": ">=10" } }, - "node_modules/moment": { - "version": "2.30.1", - "resolved": "https://registry.npmjs.org/moment/-/moment-2.30.1.tgz", - "integrity": "sha512-uEmtNhbDOrWPFS+hdjFCBfy9f2YoyzRpwcl+DqpC6taX21FzsTLQVbMV/W7PzNSX6x/bhC1zA3c2UQ5NzH6how==", - "engines": { - "node": "*" - } - }, "node_modules/ms": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" }, - "node_modules/nan": { - "version": "2.18.0", - "resolved": "https://registry.npmjs.org/nan/-/nan-2.18.0.tgz", - "integrity": "sha512-W7tfG7vMOGtD30sHoZSSc/JVYiyDPEyQVso/Zz+/uQd0B0L46gtC+pHha5FFMRpil6fm/AoEcRWyOVi4+E/f8w==" - }, "node_modules/nanoid": { "version": "3.3.7", "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz", @@ -20692,6 +20076,7 @@ "version": "0.6.3", "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", + "peer": true, "engines": { "node": ">= 0.6" } @@ -20788,212 +20173,6 @@ "webidl-conversions": "^3.0.0" } }, - "node_modules/node-gyp": { - "version": "8.4.1", - "resolved": "https://registry.npmjs.org/node-gyp/-/node-gyp-8.4.1.tgz", - "integrity": "sha512-olTJRgUtAb/hOXG0E93wZDs5YiJlgbXxTwQAFHyNlRsXQnYzUaF2aGgujZbw+hR8aF4ZG/rST57bWMWD16jr9w==", - "dependencies": { - "env-paths": "^2.2.0", - "glob": "^7.1.4", - "graceful-fs": "^4.2.6", - "make-fetch-happen": "^9.1.0", - "nopt": "^5.0.0", - "npmlog": "^6.0.0", - "rimraf": "^3.0.2", - "semver": "^7.3.5", - "tar": "^6.1.2", - "which": "^2.0.2" - }, - "bin": { - "node-gyp": "bin/node-gyp.js" - }, - "engines": { - "node": ">= 10.12.0" - } - }, - "node_modules/node-gyp/node_modules/@npmcli/fs": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@npmcli/fs/-/fs-1.1.1.tgz", - "integrity": "sha512-8KG5RD0GVP4ydEzRn/I4BNDuxDtqVbOdm8675T49OIG/NGhaK0pjPX7ZcDlvKYbA+ulvVK3ztfcF4uBdOxuJbQ==", - "dependencies": { - "@gar/promisify": "^1.0.1", - "semver": "^7.3.5" - } - }, - "node_modules/node-gyp/node_modules/@npmcli/move-file": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@npmcli/move-file/-/move-file-1.1.2.tgz", - "integrity": "sha512-1SUf/Cg2GzGDyaf15aR9St9TWlb+XvbZXWpDx8YKs7MLzMH/BCeopv+y9vzrzgkfykCGuWOlSu3mZhj2+FQcrg==", - "deprecated": "This functionality has been moved to @npmcli/fs", - "dependencies": { - "mkdirp": "^1.0.4", - "rimraf": "^3.0.2" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/node-gyp/node_modules/@tootallnate/once": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-1.1.2.tgz", - "integrity": "sha512-RbzJvlNzmRq5c3O09UipeuXno4tA1FE6ikOjxZK0tuxVv3412l64l5t1W5pj4+rJq9vpkm/kwiR07aZXnsKPxw==", - "engines": { - "node": ">= 6" - } - }, - "node_modules/node-gyp/node_modules/cacache": { - "version": "15.3.0", - "resolved": "https://registry.npmjs.org/cacache/-/cacache-15.3.0.tgz", - "integrity": "sha512-VVdYzXEn+cnbXpFgWs5hTT7OScegHVmLhJIR8Ufqk3iFD6A6j5iSX1KuBTfNEv4tdJWE2PzA6IVFtcLC7fN9wQ==", - "dependencies": { - "@npmcli/fs": "^1.0.0", - "@npmcli/move-file": "^1.0.1", - "chownr": "^2.0.0", - "fs-minipass": "^2.0.0", - "glob": "^7.1.4", - "infer-owner": "^1.0.4", - "lru-cache": "^6.0.0", - "minipass": "^3.1.1", - "minipass-collect": "^1.0.2", - "minipass-flush": "^1.0.5", - "minipass-pipeline": "^1.2.2", - "mkdirp": "^1.0.3", - "p-map": "^4.0.0", - "promise-inflight": "^1.0.1", - "rimraf": "^3.0.2", - "ssri": "^8.0.1", - "tar": "^6.0.2", - "unique-filename": "^1.1.1" - }, - "engines": { - "node": ">= 10" - } - }, - "node_modules/node-gyp/node_modules/http-proxy-agent": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-4.0.1.tgz", - "integrity": "sha512-k0zdNgqWTGA6aeIRVpvfVob4fL52dTfaehylg0Y4UvSySvOq/Y+BOyPrgpUrA7HylqvU8vIZGsRuXmspskV0Tg==", - "dependencies": { - "@tootallnate/once": "1", - "agent-base": "6", - "debug": "4" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/node-gyp/node_modules/lru-cache": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", - "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/node-gyp/node_modules/make-fetch-happen": { - "version": "9.1.0", - "resolved": "https://registry.npmjs.org/make-fetch-happen/-/make-fetch-happen-9.1.0.tgz", - "integrity": "sha512-+zopwDy7DNknmwPQplem5lAZX/eCOzSvSNNcSKm5eVwTkOBzoktEfXsa9L23J/GIRhxRsaxzkPEhrJEpE2F4Gg==", - "dependencies": { - "agentkeepalive": "^4.1.3", - "cacache": "^15.2.0", - "http-cache-semantics": "^4.1.0", - "http-proxy-agent": "^4.0.1", - "https-proxy-agent": "^5.0.0", - "is-lambda": "^1.0.1", - "lru-cache": "^6.0.0", - "minipass": "^3.1.3", - "minipass-collect": "^1.0.2", - "minipass-fetch": "^1.3.2", - "minipass-flush": "^1.0.5", - "minipass-pipeline": "^1.2.4", - "negotiator": "^0.6.2", - "promise-retry": "^2.0.1", - "socks-proxy-agent": "^6.0.0", - "ssri": "^8.0.0" - }, - "engines": { - "node": ">= 10" - } - }, - "node_modules/node-gyp/node_modules/minipass-fetch": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/minipass-fetch/-/minipass-fetch-1.4.1.tgz", - "integrity": "sha512-CGH1eblLq26Y15+Azk7ey4xh0J/XfJfrCox5LDJiKqI2Q2iwOLOKrlmIaODiSQS8d18jalF6y2K2ePUm0CmShw==", - "dependencies": { - "minipass": "^3.1.0", - "minipass-sized": "^1.0.3", - "minizlib": "^2.0.0" - }, - "engines": { - "node": ">=8" - }, - "optionalDependencies": { - "encoding": "^0.1.12" - } - }, - "node_modules/node-gyp/node_modules/semver": { - "version": "7.5.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", - "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", - "dependencies": { - "lru-cache": "^6.0.0" - }, - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/node-gyp/node_modules/socks-proxy-agent": { - "version": "6.2.1", - "resolved": "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-6.2.1.tgz", - "integrity": "sha512-a6KW9G+6B3nWZ1yB8G7pJwL3ggLy1uTzKAgCb7ttblwqdz9fMGJUuTy3uFzEP48FAs9FLILlmzDlE2JJhVQaXQ==", - "dependencies": { - "agent-base": "^6.0.2", - "debug": "^4.3.3", - "socks": "^2.6.2" - }, - "engines": { - "node": ">= 10" - } - }, - "node_modules/node-gyp/node_modules/ssri": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/ssri/-/ssri-8.0.1.tgz", - "integrity": "sha512-97qShzy1AiyxvPNIkLWoGua7xoQzzPjQ0HAH4B0rWKo7SZ6USuPcrUiAFrws0UH8RrbWmgq3LMTObhPIHbbBeQ==", - "dependencies": { - "minipass": "^3.1.1" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/node-gyp/node_modules/unique-filename": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/unique-filename/-/unique-filename-1.1.1.tgz", - "integrity": "sha512-Vmp0jIp2ln35UTXuryvjzkjGdRyf9b2lTXuSYUiPmzRcl3FDtYqAwOnTJkAngD9SWhnoJzDbTKwaOrZ+STtxNQ==", - "dependencies": { - "unique-slug": "^2.0.0" - } - }, - "node_modules/node-gyp/node_modules/unique-slug": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/unique-slug/-/unique-slug-2.0.2.tgz", - "integrity": "sha512-zoWr9ObaxALD3DOPfjPSqxt4fnZiWblxHIgeWqW8x7UqDzEtHEQLzji2cuJYQFCU6KmoJikOYAZlrTHHebjx2w==", - "dependencies": { - "imurmurhash": "^0.1.4" - } - }, - "node_modules/node-gyp/node_modules/yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" - }, "node_modules/node-int64": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz", @@ -21017,98 +20196,6 @@ "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.14.tgz", "integrity": "sha512-y10wOWt8yZpqXmOgRo77WaHEmhYQYGNA6y421PKsKYWEK8aW+cqAphborZDhqfyKrbZEN92CN1X2KbafY2s7Yw==" }, - "node_modules/node-sass": { - "version": "9.0.0", - "resolved": "https://registry.npmjs.org/node-sass/-/node-sass-9.0.0.tgz", - "integrity": "sha512-yltEuuLrfH6M7Pq2gAj5B6Zm7m+gdZoG66wTqG6mIZV/zijq3M2OO2HswtT6oBspPyFhHDcaxWpsBm0fRNDHPg==", - "hasInstallScript": true, - "dependencies": { - "async-foreach": "^0.1.3", - "chalk": "^4.1.2", - "cross-spawn": "^7.0.3", - "gaze": "^1.0.0", - "get-stdin": "^4.0.1", - "glob": "^7.0.3", - "lodash": "^4.17.15", - "make-fetch-happen": "^10.0.4", - "meow": "^9.0.0", - "nan": "^2.17.0", - "node-gyp": "^8.4.1", - "sass-graph": "^4.0.1", - "stdout-stream": "^1.4.0", - "true-case-path": "^2.2.1" - }, - "bin": { - "node-sass": "bin/node-sass" - }, - "engines": { - "node": ">=16" - } - }, - "node_modules/node-sass/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/node-sass/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/node-sass/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/node-sass/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" - }, - "node_modules/node-sass/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "engines": { - "node": ">=8" - } - }, - "node_modules/node-sass/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/node-stream-zip": { "version": "1.15.0", "resolved": "https://registry.npmjs.org/node-stream-zip/-/node-stream-zip-1.15.0.tgz", @@ -21122,64 +20209,6 @@ "url": "https://github.com/sponsors/antelle" } }, - "node_modules/nopt": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/nopt/-/nopt-5.0.0.tgz", - "integrity": "sha512-Tbj67rffqceeLpcRXrT7vKAN8CwfPeIBgM7E6iBkmKLV7bEMwpGgYLGv0jACUsECaa/vuxP0IjEont6umdMgtQ==", - "dependencies": { - "abbrev": "1" - }, - "bin": { - "nopt": "bin/nopt.js" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/normalize-package-data": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-3.0.3.tgz", - "integrity": "sha512-p2W1sgqij3zMMyRC067Dg16bfzVH+w7hyegmpIvZ4JNjqtGOVAIvLmjBx3yP7YTe9vKJgkoNOPjwQGogDoMXFA==", - "dependencies": { - "hosted-git-info": "^4.0.1", - "is-core-module": "^2.5.0", - "semver": "^7.3.4", - "validate-npm-package-license": "^3.0.1" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/normalize-package-data/node_modules/lru-cache": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", - "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/normalize-package-data/node_modules/semver": { - "version": "7.5.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", - "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", - "dependencies": { - "lru-cache": "^6.0.0" - }, - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/normalize-package-data/node_modules/yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" - }, "node_modules/normalize-path": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", @@ -21199,20 +20228,6 @@ "node": ">=8" } }, - "node_modules/npmlog": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-6.0.2.tgz", - "integrity": "sha512-/vBvz5Jfr9dT/aFWd0FIRf+T/Q2WBsLENygUaFUqstqsycmZAP/t5BvFJTK0viFmSUxiUKTUplWy5vt+rvKIxg==", - "dependencies": { - "are-we-there-yet": "^3.0.0", - "console-control-strings": "^1.1.0", - "gauge": "^4.0.3", - "set-blocking": "^2.0.0" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || >=16.0.0" - } - }, "node_modules/nullthrows": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/nullthrows/-/nullthrows-1.1.1.tgz", @@ -21819,6 +20834,7 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/p-map/-/p-map-4.0.0.tgz", "integrity": "sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ==", + "dev": true, "dependencies": { "aggregate-error": "^3.0.0" }, @@ -21879,6 +20895,7 @@ "version": "5.2.0", "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", + "dev": true, "dependencies": { "@babel/code-frame": "^7.0.0", "error-ex": "^1.3.1", @@ -22035,9 +21052,9 @@ "dev": true }, "node_modules/picocolors": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", - "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==" + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.1.tgz", + "integrity": "sha512-anP1Z8qwhkbmu7MFP5iTt+wQKXgwzf7zTyGlcdzabySa9vd0Xt392U0rVmz9poOaBj0uHJKyyo9/upk0HrEQew==" }, "node_modules/picomatch": { "version": "2.3.1", @@ -22201,9 +21218,9 @@ } }, "node_modules/postcss": { - "version": "8.4.33", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.33.tgz", - "integrity": "sha512-Kkpbhhdjw2qQs2O2DGX+8m5OVqEcbB9HRBvuYM9pgrjEFUg30A9LmXNlTAUj4S9kgtGyrMbTzVjH7E+s5Re2yg==", + "version": "8.4.40", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.40.tgz", + "integrity": "sha512-YF2kKIUzAofPMpfH6hOi2cGnv/HrUlfucspc7pDyvv7kGdqXrfj8SCl/t8owkEgKEuu8ZcRjSOxFxVLqwChZ2Q==", "funding": [ { "type": "opencollective", @@ -22220,8 +21237,8 @@ ], "dependencies": { "nanoid": "^3.3.7", - "picocolors": "^1.0.0", - "source-map-js": "^1.0.2" + "picocolors": "^1.0.1", + "source-map-js": "^1.2.0" }, "engines": { "node": "^10 || ^12 || >=14" @@ -22288,7 +21305,8 @@ "node_modules/process-nextick-args": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", - "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==" + "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", + "peer": true }, "node_modules/process-on-spawn": { "version": "1.0.0", @@ -22304,28 +21322,11 @@ }, "node_modules/promise": { "version": "8.3.0", - "resolved": "https://registry.npmjs.org/promise/-/promise-8.3.0.tgz", - "integrity": "sha512-rZPNPKTOYVNEEKFaq1HqTgOwZD+4/YHS5ukLzQCypkj+OkYx7iv0mA91lJlpPPZ8vMau3IIGj5Qlwrx+8iiSmg==", - "peer": true, - "dependencies": { - "asap": "~2.0.6" - } - }, - "node_modules/promise-inflight": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/promise-inflight/-/promise-inflight-1.0.1.tgz", - "integrity": "sha512-6zWPyEOFaQBJYcGMHBKTKJ3u6TBsnMFOIZSa6ce1e/ZrrsOlnHRHbabMjLiBYKp+n44X9eUI6VUPaukCXHuG4g==" - }, - "node_modules/promise-retry": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/promise-retry/-/promise-retry-2.0.1.tgz", - "integrity": "sha512-y+WKFlBR8BGXnsNlIHFGPZmyDf3DFMoLhaflAnyZgV6rG6xu+JwesTo2Q9R6XwYmtmwAFCkAk3e35jEdoeh/3g==", + "resolved": "https://registry.npmjs.org/promise/-/promise-8.3.0.tgz", + "integrity": "sha512-rZPNPKTOYVNEEKFaq1HqTgOwZD+4/YHS5ukLzQCypkj+OkYx7iv0mA91lJlpPPZ8vMau3IIGj5Qlwrx+8iiSmg==", + "peer": true, "dependencies": { - "err-code": "^2.0.2", - "retry": "^0.12.0" - }, - "engines": { - "node": ">=10" + "asap": "~2.0.6" } }, "node_modules/prompts": { @@ -22496,6 +21497,14 @@ "node": ">=0.10.0" } }, + "node_modules/react-beforeunload": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/react-beforeunload/-/react-beforeunload-2.6.0.tgz", + "integrity": "sha512-aKrGaRNc7fZQlDnmSYrXu4cbz9QEPhScA4A2mLxhjcULDy4VILLyLhSEjg2goIw3o5LQ1zss44kmQh5LXWYGCw==", + "peerDependencies": { + "react": "^16.8.0 || 17 || 18" + } + }, "node_modules/react-devtools-core": { "version": "4.28.5", "resolved": "https://registry.npmjs.org/react-devtools-core/-/react-devtools-core-4.28.5.tgz", @@ -22859,128 +21868,11 @@ "react-dom": ">=16" } }, - "node_modules/read-pkg": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-5.2.0.tgz", - "integrity": "sha512-Ug69mNOpfvKDAc2Q8DRpMjjzdtrnv9HcSMX+4VsZxD1aZ6ZzrIE7rlzXBtWTyhULSMKg076AW6WR5iZpD0JiOg==", - "dependencies": { - "@types/normalize-package-data": "^2.4.0", - "normalize-package-data": "^2.5.0", - "parse-json": "^5.0.0", - "type-fest": "^0.6.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/read-pkg-up": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-7.0.1.tgz", - "integrity": "sha512-zK0TB7Xd6JpCLmlLmufqykGE+/TlOePD6qKClNW7hHDKFh/J7/7gCWGR7joEQEW1bKq3a3yUZSObOoWLFQ4ohg==", - "dependencies": { - "find-up": "^4.1.0", - "read-pkg": "^5.2.0", - "type-fest": "^0.8.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/read-pkg-up/node_modules/find-up": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", - "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", - "dependencies": { - "locate-path": "^5.0.0", - "path-exists": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/read-pkg-up/node_modules/locate-path": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", - "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", - "dependencies": { - "p-locate": "^4.1.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/read-pkg-up/node_modules/p-limit": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", - "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", - "dependencies": { - "p-try": "^2.0.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/read-pkg-up/node_modules/p-locate": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", - "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", - "dependencies": { - "p-limit": "^2.2.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/read-pkg-up/node_modules/type-fest": { - "version": "0.8.1", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.8.1.tgz", - "integrity": "sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==", - "engines": { - "node": ">=8" - } - }, - "node_modules/read-pkg/node_modules/hosted-git-info": { - "version": "2.8.9", - "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.9.tgz", - "integrity": "sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==" - }, - "node_modules/read-pkg/node_modules/normalize-package-data": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz", - "integrity": "sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==", - "dependencies": { - "hosted-git-info": "^2.1.4", - "resolve": "^1.10.0", - "semver": "2 || 3 || 4 || 5", - "validate-npm-package-license": "^3.0.1" - } - }, - "node_modules/read-pkg/node_modules/semver": { - "version": "5.7.2", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", - "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", - "bin": { - "semver": "bin/semver" - } - }, - "node_modules/read-pkg/node_modules/type-fest": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.6.0.tgz", - "integrity": "sha512-q+MB8nYR1KDLrgr4G5yemftpMC7/QLqVndBmEEdqzmNj5dcFOO4Oo8qlwZE3ULT3+Zim1F8Kq4cBnikNhlCMlg==", - "engines": { - "node": ">=8" - } - }, "node_modules/readable-stream": { "version": "3.6.2", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "peer": true, "dependencies": { "inherits": "^2.0.3", "string_decoder": "^1.1.1", @@ -23022,18 +21914,6 @@ "node": ">= 4" } }, - "node_modules/redent": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/redent/-/redent-3.0.0.tgz", - "integrity": "sha512-6tDA8g98We0zd0GvVeMT9arEOnTw9qM03L9cJXaCjrip1OO764RDBLBfrB4cwzNGDj5OA5ioymC9GkizgWJDUg==", - "dependencies": { - "indent-string": "^4.0.0", - "strip-indent": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/reflect.getprototypeof": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/reflect.getprototypeof/-/reflect.getprototypeof-1.0.4.tgz", @@ -23245,14 +22125,6 @@ "node": ">=8" } }, - "node_modules/retry": { - "version": "0.12.0", - "resolved": "https://registry.npmjs.org/retry/-/retry-0.12.0.tgz", - "integrity": "sha512-9LkiTwjUh6rT555DtE9rTX+BKByPfrMzEAtnlEtdEwr3Nkffwiihqe2bWADg+OQRjt9gl6ICdmB/ZFDCGAtSow==", - "engines": { - "node": ">= 4" - } - }, "node_modules/reusify": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", @@ -23414,7 +22286,6 @@ "version": "1.77.7", "resolved": "https://registry.npmjs.org/sass/-/sass-1.77.7.tgz", "integrity": "sha512-9ywH75cO+rLjbrZ6en3Gp8qAMwPGBapFtlsMJoDTkcMU/bSe5a6cjKVUn5Jr4Gzg5GbP3HE8cm+02pLCgcoMow==", - "license": "MIT", "dependencies": { "chokidar": ">=3.0.0 <4.0.0", "immutable": "^4.0.0", @@ -23427,23 +22298,6 @@ "node": ">=14.0.0" } }, - "node_modules/sass-graph": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/sass-graph/-/sass-graph-4.0.1.tgz", - "integrity": "sha512-5YCfmGBmxoIRYHnKK2AKzrAkCoQ8ozO+iumT8K4tXJXRVCPf+7s1/9KxTSW3Rbvf+7Y7b4FR3mWyLnQr3PHocA==", - "dependencies": { - "glob": "^7.0.0", - "lodash": "^4.17.11", - "scss-tokenizer": "^0.4.3", - "yargs": "^17.2.1" - }, - "bin": { - "sassgraph": "bin/sassgraph" - }, - "engines": { - "node": ">=12" - } - }, "node_modules/saxes": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/saxes/-/saxes-6.0.0.tgz", @@ -23522,23 +22376,6 @@ "dev": true, "peer": true }, - "node_modules/scss-tokenizer": { - "version": "0.4.3", - "resolved": "https://registry.npmjs.org/scss-tokenizer/-/scss-tokenizer-0.4.3.tgz", - "integrity": "sha512-raKLgf1LI5QMQnG+RxHz6oK0sL3x3I4FN2UDLqgLOGO8hodECNnNh5BXn7fAyBxrA8zVzdQizQ6XjNJQ+uBwMw==", - "dependencies": { - "js-base64": "^2.4.9", - "source-map": "^0.7.3" - } - }, - "node_modules/scss-tokenizer/node_modules/source-map": { - "version": "0.7.4", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.4.tgz", - "integrity": "sha512-l3BikUxvPOcn5E74dZiq5BGsTb5yEwhaTSzccU6t4sDOH8NWJCstKO5QT2CvtFoK6F0saL7p9xHAqHOlCPJygA==", - "engines": { - "node": ">= 8" - } - }, "node_modules/semver": { "version": "6.3.1", "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", @@ -23834,41 +22671,6 @@ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", "dev": true }, - "node_modules/smart-buffer": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.2.0.tgz", - "integrity": "sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==", - "engines": { - "node": ">= 6.0.0", - "npm": ">= 3.0.0" - } - }, - "node_modules/socks": { - "version": "2.7.1", - "resolved": "https://registry.npmjs.org/socks/-/socks-2.7.1.tgz", - "integrity": "sha512-7maUZy1N7uo6+WVEX6psASxtNlKaNVMlGQKkG/63nEDdLOWNbiUMoLK7X4uYoLhQstau72mLgfEWcXcwsaHbYQ==", - "dependencies": { - "ip": "^2.0.0", - "smart-buffer": "^4.2.0" - }, - "engines": { - "node": ">= 10.13.0", - "npm": ">= 3.0.0" - } - }, - "node_modules/socks-proxy-agent": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-7.0.0.tgz", - "integrity": "sha512-Fgl0YPZ902wEsAyiQ+idGd1A7rSFx/ayC1CQVMw5P+EQx2V0SgpGtf6OKFhVjPflPUl9YMmEOnmfjCdMUsygww==", - "dependencies": { - "agent-base": "^6.0.2", - "debug": "^4.3.3", - "socks": "^2.6.2" - }, - "engines": { - "node": ">= 10" - } - }, "node_modules/source-map": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", @@ -23927,28 +22729,11 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/spdx-correct": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.2.0.tgz", - "integrity": "sha512-kN9dJbvnySHULIluDHy32WHRUu3Og7B9sbY7tsFLctQkIqnMh3hErYgdMjTYuqmcXX+lK5T1lnUt3G7zNswmZA==", - "dependencies": { - "spdx-expression-parse": "^3.0.0", - "spdx-license-ids": "^3.0.0" - } - }, - "node_modules/spdx-correct/node_modules/spdx-expression-parse": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.1.tgz", - "integrity": "sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q==", - "dependencies": { - "spdx-exceptions": "^2.1.0", - "spdx-license-ids": "^3.0.0" - } - }, "node_modules/spdx-exceptions": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.3.0.tgz", - "integrity": "sha512-/tTrYOC7PPI1nUAgx34hUpqXuyJG+DTHJTnIULG4rDygi4xu/tfgmq1e1cIRwRzwZgo4NLySi+ricLkZkw4i5A==" + "integrity": "sha512-/tTrYOC7PPI1nUAgx34hUpqXuyJG+DTHJTnIULG4rDygi4xu/tfgmq1e1cIRwRzwZgo4NLySi+ricLkZkw4i5A==", + "dev": true }, "node_modules/spdx-expression-parse": { "version": "4.0.0", @@ -23963,7 +22748,8 @@ "node_modules/spdx-license-ids": { "version": "3.0.16", "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.16.tgz", - "integrity": "sha512-eWN+LnM3GR6gPu35WxNgbGl8rmY1AEmoMDvL/QD6zYmPWgywxWqJWNdLGT+ke8dKNWrcYgYjPpG5gbTfghP8rw==" + "integrity": "sha512-eWN+LnM3GR6gPu35WxNgbGl8rmY1AEmoMDvL/QD6zYmPWgywxWqJWNdLGT+ke8dKNWrcYgYjPpG5gbTfghP8rw==", + "dev": true }, "node_modules/split": { "version": "0.3.3", @@ -24007,17 +22793,6 @@ "node": ">=0.10.0" } }, - "node_modules/ssri": { - "version": "9.0.1", - "resolved": "https://registry.npmjs.org/ssri/-/ssri-9.0.1.tgz", - "integrity": "sha512-o57Wcn66jMQvfHG1FlYbWeZWW/dHZhJXjpIcTfXldXEk5nz5lStPo3mK0OJQfGR3RbZUlbISexbljkJzuEj/8Q==", - "dependencies": { - "minipass": "^3.1.1" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || >=16.0.0" - } - }, "node_modules/stack-utils": { "version": "2.0.6", "resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-2.0.6.tgz", @@ -24159,41 +22934,6 @@ "resolved": "https://registry.npmjs.org/std-env/-/std-env-3.7.0.tgz", "integrity": "sha512-JPbdCEQLj1w5GilpiHAx3qJvFndqybBysA3qUOnznweH4QbNYUsW/ea8QzSrnh0vNsezMMw5bcVool8lM0gwzg==" }, - "node_modules/stdout-stream": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/stdout-stream/-/stdout-stream-1.4.1.tgz", - "integrity": "sha512-j4emi03KXqJWcIeF8eIXkjMFN1Cmb8gUlDYGeBALLPo5qdyTfA9bOtl8m33lRoC+vFMkP3gl0WsDr6+gzxbbTA==", - "dependencies": { - "readable-stream": "^2.0.1" - } - }, - "node_modules/stdout-stream/node_modules/readable-stream": { - "version": "2.3.8", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", - "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", - "dependencies": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" - } - }, - "node_modules/stdout-stream/node_modules/safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" - }, - "node_modules/stdout-stream/node_modules/string_decoder": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", - "dependencies": { - "safe-buffer": "~5.1.0" - } - }, "node_modules/stream-combiner": { "version": "0.0.4", "resolved": "https://registry.npmjs.org/stream-combiner/-/stream-combiner-0.0.4.tgz", @@ -24207,6 +22947,7 @@ "version": "1.3.0", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "peer": true, "dependencies": { "safe-buffer": "~5.2.0" } @@ -24356,17 +23097,6 @@ "node": ">=6" } }, - "node_modules/strip-indent": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-3.0.0.tgz", - "integrity": "sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ==", - "dependencies": { - "min-indent": "^1.0.0" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/strip-json-comments": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", @@ -24458,35 +23188,6 @@ "node": ">=6" } }, - "node_modules/tar": { - "version": "6.2.1", - "resolved": "https://registry.npmjs.org/tar/-/tar-6.2.1.tgz", - "integrity": "sha512-DZ4yORTwrbTj/7MZYq2w+/ZFdI6OZ/f9SFHR+71gIVUZhOQPHzVCLpvRnPgyaMpfWxxk/4ONva3GQSyNIKRv6A==", - "dependencies": { - "chownr": "^2.0.0", - "fs-minipass": "^2.0.0", - "minipass": "^5.0.0", - "minizlib": "^2.1.1", - "mkdirp": "^1.0.3", - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/tar/node_modules/minipass": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-5.0.0.tgz", - "integrity": "sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ==", - "engines": { - "node": ">=8" - } - }, - "node_modules/tar/node_modules/yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" - }, "node_modules/temp": { "version": "0.8.4", "resolved": "https://registry.npmjs.org/temp/-/temp-0.8.4.tgz", @@ -24859,19 +23560,6 @@ "node": ">=12" } }, - "node_modules/trim-newlines": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/trim-newlines/-/trim-newlines-3.0.1.tgz", - "integrity": "sha512-c1PTsA3tYrIsLGkJkzHF+w9F2EyxfXGo4UyJc4pFL++FMjnq0HJS69T3M7d//gKrFKwy429bouPescbjecU+Zw==", - "engines": { - "node": ">=8" - } - }, - "node_modules/true-case-path": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/true-case-path/-/true-case-path-2.2.1.tgz", - "integrity": "sha512-0z3j8R7MCjy10kc/g+qg7Ln3alJTodw9aDuVWZa3uiWqfuBMKeAeP2ocWcxoyM3D73yz3Jt/Pu4qPr4wHSdB/Q==" - }, "node_modules/ts-api-utils": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-1.0.3.tgz", @@ -25171,28 +23859,6 @@ "node": ">=4" } }, - "node_modules/unique-filename": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/unique-filename/-/unique-filename-2.0.1.tgz", - "integrity": "sha512-ODWHtkkdx3IAR+veKxFV+VBkUMcN+FaqzUUd7IZzt+0zhDZFPFxhlqwPF3YQvMHx1TD0tdgYl+kuPnJ8E6ql7A==", - "dependencies": { - "unique-slug": "^3.0.0" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || >=16.0.0" - } - }, - "node_modules/unique-slug": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/unique-slug/-/unique-slug-3.0.0.tgz", - "integrity": "sha512-8EyMynh679x/0gqE9fT9oilG+qEt+ibFyqjuVTsZn1+CMxH+XLlpvr2UZx4nVcCwTpx81nICr2JQFkM+HPLq4w==", - "dependencies": { - "imurmurhash": "^0.1.4" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || >=16.0.0" - } - }, "node_modules/universal-cookie": { "version": "4.0.4", "resolved": "https://registry.npmjs.org/universal-cookie/-/universal-cookie-4.0.4.tgz", @@ -25313,7 +23979,8 @@ "node_modules/util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", - "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==" + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", + "peer": true }, "node_modules/utils-merge": { "version": "1.0.1", @@ -25339,24 +24006,6 @@ "integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==", "dev": true }, - "node_modules/validate-npm-package-license": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz", - "integrity": "sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==", - "dependencies": { - "spdx-correct": "^3.0.0", - "spdx-expression-parse": "^3.0.0" - } - }, - "node_modules/validate-npm-package-license/node_modules/spdx-expression-parse": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.1.tgz", - "integrity": "sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q==", - "dependencies": { - "spdx-exceptions": "^2.1.0", - "spdx-license-ids": "^3.0.0" - } - }, "node_modules/validator": { "version": "13.11.0", "resolved": "https://registry.npmjs.org/validator/-/validator-13.11.0.tgz", @@ -26494,14 +25143,6 @@ "resolved": "https://registry.npmjs.org/wicg-inert/-/wicg-inert-3.1.2.tgz", "integrity": "sha512-Ba9tGNYxXwaqKEi9sJJvPMKuo063umUPsHN0JJsjrs2j8KDSzkWLMZGZ+MH1Jf1Fq4OWZ5HsESJID6nRza2ang==" }, - "node_modules/wide-align": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.5.tgz", - "integrity": "sha512-eDMORYaPNZ4sQIuuYPDHdQvf4gyCF9rEEV/yPxGfwPkRodwEgiMUUXTx/dex+Me0wxx53S+NgUHaP7y3MGlDmg==", - "dependencies": { - "string-width": "^1.0.2 || 2 || 3 || 4" - } - }, "node_modules/window-or-global": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/window-or-global/-/window-or-global-1.0.1.tgz", @@ -26643,6 +25284,7 @@ "version": "5.0.8", "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "peer": true, "engines": { "node": ">=10" } @@ -26665,6 +25307,7 @@ "version": "17.7.2", "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", + "peer": true, "dependencies": { "cliui": "^8.0.1", "escalade": "^3.1.1", @@ -26678,18 +25321,11 @@ "node": ">=12" } }, - "node_modules/yargs-parser": { - "version": "20.2.9", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.9.tgz", - "integrity": "sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==", - "engines": { - "node": ">=10" - } - }, "node_modules/yargs/node_modules/yargs-parser": { "version": "21.1.1", "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", + "peer": true, "engines": { "node": ">=12" } diff --git a/frontend/package.json b/frontend/package.json index f9006e657..e1f52ed84 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -18,6 +18,7 @@ "@tsconfig/node18": "^18.0.0", "@types/carbon-components-react": "^7.55.2", "@types/luxon": "^3.4.2", + "@types/react-beforeunload": "^2.1.5", "@types/react-dom": "^18.0.11", "@types/validator": "^13.7.17", "@typescript-eslint/parser": "^7.0.0", @@ -27,16 +28,16 @@ "bignumber.js": "^9.0.0", "braces": "^3.0.3", "eslint-config-airbnb-typescript": "^18.0.0", + "fast-xml-parser": "^4.4.1", "luxon": "^3.4.3", - "moment": "^2.29.4", - "node-sass": "^9.0.0", "react": "^18.2.0", + "react-beforeunload": "^2.6.0", "react-dom": "^18.2.0", "react-hash-string": "^1.0.0", "react-router-dom": "^6.21.2", "react-test-renderer": "^18.2.0", "react-toastify": "^10.0.0", - "sass": "1.77.7", + "sass": "^1.77.7", "typescript": "^5.4.4", "validator": "^13.9.0", "vite": "^5.2.8", @@ -73,9 +74,8 @@ }, "overrides": { "braces@<3.0.3": "3.0.3", + "fast-xml-parser@<4.4.1": "4.4.1", "ip@<1.1.9": "1.1.9", - "ip@2.0.0": "2.0.1", - "postcss": "^8.4.31", "tar@<6.2.1": "6.2.1", "ws@<6.2.3": "6.2.3", "ws@7.5.9": "7.5.10", diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index c4838a32d..d51095bb7 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -83,11 +83,11 @@ const App: React.FC = () => { if (failureCount > MAX_RETRIES) { return false; } - if ( - isAxiosError(error) - && HTTP_STATUS_TO_NOT_RETRY.includes(error.response?.status ?? 0) - ) { - return false; + if (isAxiosError(error)) { + const status = error.response?.status; + if (status && HTTP_STATUS_TO_NOT_RETRY.includes(status)) { + return false; + } } return true; } diff --git a/frontend/src/__test__/components/ExtractionAndStorageStep.test.tsx b/frontend/src/__test__/components/ExtractionAndStorageStep.test.tsx index 13f4c2dfb..8577cf91c 100644 --- a/frontend/src/__test__/components/ExtractionAndStorageStep.test.tsx +++ b/frontend/src/__test__/components/ExtractionAndStorageStep.test.tsx @@ -41,9 +41,9 @@ describe.skip('Extraction and Storage Step test', () => { it('should have the correct labels', () => { const content = { title: 'Extraction information', - subtitle: 'Enter the extractory agency information and extraction’s star and end dates for this seedlot', + subtitle: 'Enter the extractory agency information and extraction’s start and end dates for this seedlot', titleTemporary: 'Temporary seed storage', - subtitleTemporary: 'Enter the seed storage agency information and storage’s star and end dates for this seedlot' + subtitleTemporary: 'Enter the seed storage agency information and storage’s start and end dates for this seedlot' } const titleBox = component.getElementsByClassName('extraction-information-title')[0]; expect(titleBox).toHaveTextContent(content.title); diff --git a/frontend/src/__test__/components/OrchardStep.test.tsx b/frontend/src/__test__/components/OrchardStep.test.tsx index d070a6636..566d407be 100644 --- a/frontend/src/__test__/components/OrchardStep.test.tsx +++ b/frontend/src/__test__/components/OrchardStep.test.tsx @@ -96,11 +96,11 @@ describe.skip('Orchard Step test', () => { }); }); - it('should show and click Delete additional orchard button', () => { + it('should show and click Delete secondary orchard button', () => { const addButton = screen.getByText('Add orchard'); fireEvent.click(addButton); - const deleteButton = screen.getByText('Delete additional orchard'); + const deleteButton = screen.getByText('Delete secondary orchard'); fireEvent.click(deleteButton); }); diff --git a/frontend/src/__test__/views/__snapshots__/Landing.test.tsx.snap b/frontend/src/__test__/views/__snapshots__/Landing.test.tsx.snap index 8adde4d3c..5da7877b8 100644 --- a/frontend/src/__test__/views/__snapshots__/Landing.test.tsx.snap +++ b/frontend/src/__test__/views/__snapshots__/Landing.test.tsx.snap @@ -98,7 +98,7 @@ exports[`Landing component test > should match the snapshot 1`] = ` Small green seedling on the dirt and watered diff --git a/frontend/src/api-service/ApiConfig.ts b/frontend/src/api-service/ApiConfig.ts index 02eefdac2..df8da4096 100644 --- a/frontend/src/api-service/ApiConfig.ts +++ b/frontend/src/api-service/ApiConfig.ts @@ -12,6 +12,8 @@ const ApiConfig = { geneticClasses: `${serverHost}/api/genetic-classes`, + geneticWothList: `${serverHost}/api/genetic-worth`, + methodsOfPayment: `${serverHost}/api/methods-of-payment`, orchards: `${serverHost}/api/orchards`, @@ -32,6 +34,8 @@ const ApiConfig = { seedlots: `${serverHost}/api/seedlots`, + orchardsVegCode: `${serverHost}/api/orchards/vegetation-codes`, + tscAdmin: `${serverHost}/api/tsc-admin`, tscSeedlotEdit: `${serverHost}/api/tsc-admin/seedlots/{seedlotNumber}/edit`, @@ -49,9 +53,9 @@ const ApiConfig = { oracleOrchards: `${oracleServerHost}/api/orchards`, - orchardsVegCode: `${oracleServerHost}/api/orchards/vegetation-code`, + areaOfUseSpzList: `${oracleServerHost}/api/area-of-use/spz-list/vegetation-code`, - areaOfUseSpzList: `${oracleServerHost}/api/area-of-use/spz-list/vegetation-code` + parentTreeByVegCode: `${oracleServerHost}/api/parent-trees/vegetation-codes/{vegCode}` }; export default ApiConfig; diff --git a/frontend/src/api-service/GeneticWorthAPI.ts b/frontend/src/api-service/GeneticWorthAPI.ts new file mode 100644 index 000000000..f0704c50f --- /dev/null +++ b/frontend/src/api-service/GeneticWorthAPI.ts @@ -0,0 +1,10 @@ +import { GeneticWorthDto } from '../types/GeneticWorthType'; +import ApiConfig from './ApiConfig'; +import api from './api'; + +const getGeneticWorthList = () => { + const url = ApiConfig.geneticWothList; + return api.get(url).then((res): GeneticWorthDto[] => res.data); +}; + +export default getGeneticWorthList; diff --git a/frontend/src/api-service/forestClientsAPI.ts b/frontend/src/api-service/forestClientsAPI.ts index b76fe9b80..577995d5b 100644 --- a/frontend/src/api-service/forestClientsAPI.ts +++ b/frontend/src/api-service/forestClientsAPI.ts @@ -2,11 +2,12 @@ import ApiConfig from './ApiConfig'; import api from './api'; import { ForestClientType } from '../types/ForestClientTypes/ForestClientType'; import { ForestClientSearchType } from '../types/ForestClientTypes/ForestClientSearchType'; -import { ClientSearchOptions } from '../components/ApplicantAgencyFields/ClientSearchModal/definitions'; +import { ClientSearchOptions } from '../components/ClientAndCodeInput/ClientSearchModal/definitions'; +import { ForestClientLocationType } from '../types/ForestClientTypes/ForestClientLocationType'; export const getForestClientLocation = (clientNumber: string, locationCode: string) => { const url = `${ApiConfig.forestClient}/${clientNumber}/location/${locationCode}`; - return api.get(url).then((res) => res.data); + return api.get(url).then((res): ForestClientLocationType => res.data); }; export const getForestClientByNumberOrAcronym = (numberOrAcronym: string) => { diff --git a/frontend/src/api-service/orchardAPI.ts b/frontend/src/api-service/orchardAPI.ts index db56ec143..3615ba923 100644 --- a/frontend/src/api-service/orchardAPI.ts +++ b/frontend/src/api-service/orchardAPI.ts @@ -12,11 +12,6 @@ export const getSeedPlanUnits = (orchardId: string) => { return api.get(url).then((res) => res.data); }; -export const getAllParentTrees = (vegCode: string) => { - const url = `${ApiConfig.orchards}/parent-trees/vegetation-codes/${vegCode}`; - return api.get(url).then((res) => res.data); -}; - export const getOrchardByVegCode = (vegCode: string) => { const url = `${ApiConfig.orchardsVegCode}/${vegCode}`; return api.get(url).then((res): OrchardDataType[] => res.data); diff --git a/frontend/src/api-service/parentTreeAPI.ts b/frontend/src/api-service/parentTreeAPI.ts index eba47e86c..61d502b3f 100644 --- a/frontend/src/api-service/parentTreeAPI.ts +++ b/frontend/src/api-service/parentTreeAPI.ts @@ -1,3 +1,4 @@ +import { ParentTreeByVegCodeResType } from '../types/ParentTreeTypes'; import { PtValsCalcReqPayload } from '../types/PtCalcTypes'; import ApiConfig from './ApiConfig'; import api from './api'; @@ -7,4 +8,9 @@ export const postForCalculation = (data: PtValsCalcReqPayload) => { return api.post(url, data); }; +export const getAllParentTrees = (vegCode: string) => { + const url = ApiConfig.parentTreeByVegCode.replace('{vegCode}', vegCode); + return api.get(url).then((res): ParentTreeByVegCodeResType => res.data); +}; + export default postForCalculation; diff --git a/frontend/src/assets/img/cone.jpeg b/frontend/src/assets/img/cone.jpeg new file mode 100644 index 000000000..6c5917f11 Binary files /dev/null and b/frontend/src/assets/img/cone.jpeg differ diff --git a/frontend/src/assets/img/seeding.png b/frontend/src/assets/img/seeding.png deleted file mode 100644 index 28f33fb63..000000000 Binary files a/frontend/src/assets/img/seeding.png and /dev/null differ diff --git a/frontend/src/aws-exports.ts b/frontend/src/aws-exports.ts index 45d595af5..147c21814 100644 --- a/frontend/src/aws-exports.ts +++ b/frontend/src/aws-exports.ts @@ -4,13 +4,17 @@ import { env } from './env'; const ZONE = env.VITE_ZONE ? env.VITE_ZONE.toLocaleLowerCase() : 'dev'; const retUrlEnv = ZONE !== 'prod' && ZONE !== 'test' ? 'dev' : ZONE; +const retUrlString = ZONE === 'prod' + ? 'https://loginproxy.gov.bc.ca/auth/realms/standard/protocol/openid-connect/logout' + : `https://${retUrlEnv}.loginproxy.gov.bc.ca/auth/realms/standard/protocol/openid-connect/logout`; + // [https://logon7.gov.bc.ca] for PROD and [https://logontest7.gov.bc.ca] for everyting else const logoutDomain = ZONE === 'prod' ? 'https://logon7.gov.bc.ca' : 'https://logontest7.gov.bc.ca'; const signOutUrl = [ `${logoutDomain}/clp-cgi/logoff.cgi`, '?retnow=1', - `&returl=https://${retUrlEnv}.loginproxy.gov.bc.ca/auth/realms/standard/protocol/openid-connect/logout`, + `&returl=${retUrlString}`, `?redirect_uri=${window.location.origin}/` ].join(''); @@ -23,7 +27,7 @@ const awsconfig = { domain: env.VITE_AWS_DOMAIN || 'prod-fam-user-pool-domain.auth.ca-central-1.amazoncognito.com', scope: ['openid'], redirectSignIn: `${window.location.origin}/`, - redirectSignOut: env.VITE_REDIRECT_SIGN_OUT || signOutUrl, + redirectSignOut: signOutUrl, responseType: 'code' }, federationTarget: 'COGNITO_USER_POOLS' diff --git a/frontend/src/components/ApplicantAgencyFields/ClientSearchModal/ClientSearchFields.tsx b/frontend/src/components/ApplicantAgencyFields/ClientSearchModal/ClientSearchFields.tsx deleted file mode 100644 index d518ba764..000000000 --- a/frontend/src/components/ApplicantAgencyFields/ClientSearchModal/ClientSearchFields.tsx +++ /dev/null @@ -1,83 +0,0 @@ -/* eslint-disable jsx-a11y/anchor-is-valid */ -import React from 'react'; -import { - FlexGrid, Row, Column, - TextInput, Dropdown, Button -} from '@carbon/react'; -import { Search } from '@carbon/icons-react'; -import { Link } from 'react-router-dom'; - -import ComboBoxEvent from '../../../types/ComboBoxEvent'; - -import { clientSearchOptions } from './constants'; -import { ClientSearchFieldsProps } from './definitions'; - -const ClientSearchFields = ({ - searchWord, - setSearchWord, - searchOption, - setSearchOption, - mutationFn -}: ClientSearchFieldsProps) => ( - - - -

- Search for a specific client or agency, you can search by name, acronym or number. - To view more information about the client, you can - {' '} - - go to client search screen. - -

-
-
- - - ) => { - setSearchWord(e.target.value); - }} - disabled={mutationFn.isLoading} - /> - - - { - setSearchOption(e.selectedItem); - }} - /> - - - - - -
-); - -export default ClientSearchFields; diff --git a/frontend/src/components/ApplicantAgencyFields/definitions.ts b/frontend/src/components/ApplicantAgencyFields/definitions.ts deleted file mode 100644 index c39d1d443..000000000 --- a/frontend/src/components/ApplicantAgencyFields/definitions.ts +++ /dev/null @@ -1,20 +0,0 @@ -import AgencyTextPropsType from '../../types/AgencyTextPropsType'; -import { BooleanInputType, OptionsInputType, StringInputType } from '../../types/FormInputType'; -import MultiOptionsObj from '../../types/MultiOptionsObject'; - -interface ApplicantAgencyFieldsProps { - checkboxId: string; - isDefault: BooleanInputType; - agency: OptionsInputType; - locationCode: StringInputType; - fieldsProps: AgencyTextPropsType; - setAgencyAndCode: Function; - defaultAgency?: MultiOptionsObj; - defaultCode?: string; - showCheckbox?: boolean; - readOnly?: boolean; - maxInputColSize?: number; - isFormSubmitted?: boolean; -} - -export default ApplicantAgencyFieldsProps; diff --git a/frontend/src/components/ApplicantAgencyFields/index.tsx b/frontend/src/components/ApplicantAgencyFields/index.tsx deleted file mode 100644 index 435fe34ad..000000000 --- a/frontend/src/components/ApplicantAgencyFields/index.tsx +++ /dev/null @@ -1,428 +0,0 @@ -import React, { useState } from 'react'; -import { AxiosError } from 'axios'; -import { - Row, Column, TextInput, Checkbox, Tooltip, - InlineLoading, ActionableNotification, FlexGrid -} from '@carbon/react'; -import { useMutation } from '@tanstack/react-query'; -import validator from 'validator'; - -import ClientSearchModal from './ClientSearchModal'; - -import { getForestClientByNumberOrAcronym, getForestClientLocation } from '../../api-service/forestClientsAPI'; - -import MultiOptionsObj from '../../types/MultiOptionsObject'; -import { ForestClientSearchType } from '../../types/ForestClientTypes/ForestClientSearchType'; -import { ForestClientType } from '../../types/ForestClientTypes/ForestClientType'; -import { EmptyMultiOptObj, LOCATION_CODE_LIMIT } from '../../shared-constants/shared-constants'; -import { getForestClientLabel } from '../../utils/ForestClientUtils'; - -import ApplicantAgencyFieldsProps from './definitions'; -import supportTexts, { getErrorMessageTitle } from './constants'; -import { formatLocationCode } from './utils'; - -import './styles.scss'; - -const ApplicantAgencyFields = ({ - checkboxId, isDefault, agency, locationCode, fieldsProps, defaultAgency, - defaultCode, setAgencyAndCode, readOnly, showCheckbox, maxInputColSize -}: ApplicantAgencyFieldsProps) => { - const [showSuccessIconAgency, setShowSuccessIconAgency] = useState(true); - const [showSuccessIconLocCode, setShowSuccessIconLocCode] = useState(false); - - const [showErrorBanner, setShowErrorBanner] = useState(false); - const [invalidAcronymMessage, setInvalidAcronymMessage] = useState( - supportTexts.agency.invalidAcronym - ); - - const [invalidLocationMessage, setInvalidLocationMessage] = useState( - locationCode.isInvalid && agency.value - ? supportTexts.locationCode.invalidLocationForSelectedAgency - : supportTexts.locationCode.invalidText - ); - const [locationCodeHelperText, setLocationCodeHelperText] = useState( - supportTexts.locationCode.helperTextDisabled - ); - - const updateAfterAgencyValidation = (isInvalid: boolean, clientData?: ForestClientType) => { - let updatedAgency; - if (clientData) { - updatedAgency = { - ...agency, - value: { - label: clientData.acronym, - code: clientData.clientNumber, - description: `${clientData.clientNumber} - ${clientData.clientName} - ${clientData.acronym}` - }, - isInvalid - }; - } else { - updatedAgency = { - ...agency, - isInvalid - }; - } - setLocationCodeHelperText( - clientData - ? supportTexts.locationCode.helperTextEnabled - : supportTexts.locationCode.helperTextDisabled - ); - setAgencyAndCode(isDefault, updatedAgency, locationCode); - }; - - const updateAfterLocValidation = (isInvalid: boolean) => { - const updatedLocationCode = { - ...locationCode, - isInvalid - }; - setLocationCodeHelperText(supportTexts.locationCode.helperTextEnabled); - setAgencyAndCode(isDefault, agency, updatedLocationCode); - }; - - const validateLocationCodeMutation = useMutation({ - mutationFn: (queryParams: string[]) => getForestClientLocation( - queryParams[0], - queryParams[1] - ), - onError: (err: AxiosError) => { - // Request failed - if (err.response?.status !== 404) { - setShowErrorBanner(true); - setInvalidLocationMessage(supportTexts.locationCode.requestErrorHelper); - } else { - setInvalidLocationMessage(supportTexts.locationCode.invalidLocationForSelectedAgency); - } - updateAfterLocValidation(true); - }, - onSuccess: () => { - setShowErrorBanner(false); - updateAfterLocValidation(false); - } - }); - - const validateAgencyAcronymMutation = useMutation({ - mutationFn: (queryParams: string[]) => getForestClientByNumberOrAcronym( - queryParams[0] - ), - onError: (err: AxiosError) => { - // Request failed - if (err.response?.status !== 404) { - setShowErrorBanner(true); - setInvalidAcronymMessage(supportTexts.agency.requestErrorHelper); - } else { - setInvalidAcronymMessage(supportTexts.agency.invalidAcronym); - } - updateAfterAgencyValidation(true); - }, - onSuccess: (res) => { - setShowErrorBanner(false); - updateAfterAgencyValidation(false, res); - if (locationCode.value !== '') { - validateLocationCodeMutation.mutate([res.clientNumber, locationCode.value]); - } - } - }); - - const handleDefaultCheckBox = (checked: boolean) => { - setLocationCodeHelperText( - checked - ? supportTexts.locationCode.helperTextEnabled - : supportTexts.locationCode.helperTextDisabled - ); - - const updatedAgency = { - ...agency, - value: checked ? defaultAgency : EmptyMultiOptObj, - isInvalid: checked && defaultAgency?.label === '' - }; - - const updatedLocationCode = { - ...locationCode, - value: checked ? defaultCode : '', - isInvalid: checked && defaultCode === '' - }; - - const updatedIsDefault = { - ...isDefault, - value: checked - }; - - setShowSuccessIconAgency(checked); - setShowSuccessIconLocCode(checked); - setAgencyAndCode(updatedIsDefault, updatedAgency, updatedLocationCode); - }; - - const updateAgencyFn = (value: string) => ( - { - ...agency, - isInvalid: false, - value: value - ? { - ...EmptyMultiOptObj, - label: value - } - : EmptyMultiOptObj - } - ); - - const renderLoading = ( - isLoading: boolean, - isSuccess: boolean, - showSuccessControl: boolean - ) => { - if ( - (isLoading || isSuccess) - && showSuccessControl - && !isDefault.value - ) { - const tooltipLabel = isSuccess ? 'Verified!' : 'Loading'; - const loadingStatus = isSuccess ? 'finished' : 'active'; - return ( - - { - // eslint-disable-next-line jsx-a11y/control-has-associated-label - - } - - ); - } - return null; - }; - - const handleAgencyInput = (value: string) => { - // Create a "mock" MultiOptObj, just to display - // the correct acronym - const updatedAgency = updateAgencyFn(value); - - setAgencyAndCode(isDefault, updatedAgency, locationCode); - }; - - const handleAgencyBlur = (value: string) => { - const updatedAgency = updateAgencyFn(value); - setAgencyAndCode(isDefault, updatedAgency, locationCode); - - if (value === '') { - setShowSuccessIconAgency(false); - return; - } - - setShowSuccessIconAgency(true); - validateAgencyAcronymMutation.mutate([value]); - }; - - const handleLocationCodeChange = (value: string) => { - const updatedValue = value.slice(0, LOCATION_CODE_LIMIT); - const isInRange = validator.isInt(value, { min: 0, max: 99 }); - let updatedIsInvalid = false; - - if (value === '') { - setShowSuccessIconLocCode(false); - } - - if (!isInRange) { - setInvalidLocationMessage(supportTexts.locationCode.invalidText); - updatedIsInvalid = true; - } - - const updatedLocationCode = { - ...locationCode, - value: updatedValue, - isInvalid: updatedIsInvalid - }; - - setAgencyAndCode(isDefault, agency, updatedLocationCode); - }; - - const handleLocationCodeBlur = (value: string) => { - const formatedCode = value.length ? formatLocationCode(value) : ''; - const updatedLocationCode = { - ...locationCode, - value: formatedCode, - isInvalid: false - }; - setAgencyAndCode(isDefault, agency, updatedLocationCode); - if (formatedCode === '') { - setShowSuccessIconLocCode(false); - return; - } - setShowSuccessIconLocCode(true); - validateLocationCodeMutation.mutate([agency.value.code, formatedCode]); - }; - - return ( - - { - showCheckbox - ? ( - - - ) => { - handleDefaultCheckBox(e.target.checked); - }} - /> - - - ) - : null - } - - -
- ) => handleAgencyInput(e.target.value) - } - onWheel={(e: React.ChangeEvent) => e.target.blur()} - onBlur={(e: React.ChangeEvent) => { - if (!e.target.readOnly) { - handleAgencyBlur(e.target.value); - } - }} - size="md" - /> - { - renderLoading( - validateAgencyAcronymMutation.isLoading, - validateAgencyAcronymMutation.isSuccess, - showSuccessIconAgency - ) - } -
-
- -
- ) => { - handleLocationCodeChange(e.target.value); - }} - onWheel={(e: React.ChangeEvent) => e.target.blur()} - onBlur={(e: React.ChangeEvent) => { - if (!e.target.readOnly) { - handleLocationCodeBlur(e.target.value); - } - }} - /> - { - renderLoading( - validateLocationCodeMutation.isLoading, - validateLocationCodeMutation.isSuccess, - showSuccessIconLocCode - ) - } -
-
-
- { - showErrorBanner - ? ( - - - { - if (validateAgencyAcronymMutation.isError) { - handleAgencyBlur(agency.value.label); - } else { - handleLocationCodeBlur(locationCode.value); - } - }} - onCloseButtonClick={() => { setShowErrorBanner(false); }} - /> - - - ) - : null - } - { - !isDefault.value && !readOnly - ? ( - - -

- If you don't remember the agency information you can - {' '} - { - const agencyObj: MultiOptionsObj = { - code: client.clientNumber, - label: client.acronym, - description: getForestClientLabel(client) - }; - - const selectedAgency = { - ...agency, - value: agencyObj, - isInvalid: false - }; - - const selectedLocationCode = { - ...locationCode, - value: client.locationCode, - isInvalid: false - }; - - const updateIsDefault = { - ...isDefault, - value: false - }; - - setLocationCodeHelperText(supportTexts.locationCode.helperTextEnabled); - setAgencyAndCode(updateIsDefault, selectedAgency, selectedLocationCode); - }} - /> -

-
-
- ) - : null - } -
- ); -}; - -export default ApplicantAgencyFields; diff --git a/frontend/src/components/BCHeader/constants.ts b/frontend/src/components/BCHeader/constants.ts index ed41f846f..4123b31b0 100644 --- a/frontend/src/components/BCHeader/constants.ts +++ b/frontend/src/components/BCHeader/constants.ts @@ -64,7 +64,7 @@ export const navItems = [ disabled: true }, { - name: 'Tree seed center', + name: 'Tree seed centre', icon: 'Enterprise', link: '#', disabled: true diff --git a/frontend/src/components/BCHeader/index.tsx b/frontend/src/components/BCHeader/index.tsx index 400bab01b..aa7bdf02e 100644 --- a/frontend/src/components/BCHeader/index.tsx +++ b/frontend/src/components/BCHeader/index.tsx @@ -186,7 +186,7 @@ const BCHeader = () => { key={navItem.name} className={navItem.disabled ? 'disabled-side-nav-option' : ''} renderIcon={Icons[navItem.icon]} - isActive={window.location.pathname === navItem.link} + isActive={window.location.pathname.includes(navItem.link)} onClick={navItem.disabled ? null : () => navigate(navItem.link)} > {navItem.name} diff --git a/frontend/src/components/Card/FavouriteCard/index.tsx b/frontend/src/components/Card/FavouriteCard/index.tsx index 3d4a41b65..7f1fdab0c 100644 --- a/frontend/src/components/Card/FavouriteCard/index.tsx +++ b/frontend/src/components/Card/FavouriteCard/index.tsx @@ -85,7 +85,6 @@ const FavouriteCard = ({

{favObject.header}

-

{favObject.description}

); diff --git a/frontend/src/components/ClientAndCodeInput/ClientSearchModal/ClientSearchFields.tsx b/frontend/src/components/ClientAndCodeInput/ClientSearchModal/ClientSearchFields.tsx new file mode 100644 index 000000000..7c2f5aa1e --- /dev/null +++ b/frontend/src/components/ClientAndCodeInput/ClientSearchModal/ClientSearchFields.tsx @@ -0,0 +1,93 @@ +/* eslint-disable jsx-a11y/anchor-is-valid */ +import React from 'react'; +import { + FlexGrid, Row, Column, + TextInput, Dropdown, Button +} from '@carbon/react'; +import { Search } from '@carbon/icons-react'; + +import ComboBoxEvent from '../../../types/ComboBoxEvent'; +import useWindowSize from '../../../hooks/UseWindowSize'; +import { MEDIUM_SCREEN_WIDTH } from '../../../shared-constants/shared-constants'; + +import { clientSearchOptions } from './constants'; +import { ClientSearchFieldsProps } from './definitions'; + +const ClientSearchFields = ({ + searchWord, + setSearchWord, + searchOption, + setSearchOption, + mutationFn +}: ClientSearchFieldsProps) => { + const windowSize = useWindowSize(); + + return ( + + + +

+ Search for a specific client or agency, you can search by name, acronym or number. + {/* Commenting this until search screen is created */} + {/* To view more information about the client, you can + {' '} + + go to client search screen. + */} +

+
+
+ + + ) => { + setSearchWord(e.target.value); + }} + disabled={mutationFn.isLoading} + /> + + + { + setSearchOption(e.selectedItem); + }} + /> + + + + + +
+ ); +}; + +export default ClientSearchFields; diff --git a/frontend/src/components/ApplicantAgencyFields/ClientSearchModal/constants.ts b/frontend/src/components/ClientAndCodeInput/ClientSearchModal/constants.ts similarity index 100% rename from frontend/src/components/ApplicantAgencyFields/ClientSearchModal/constants.ts rename to frontend/src/components/ClientAndCodeInput/ClientSearchModal/constants.ts diff --git a/frontend/src/components/ApplicantAgencyFields/ClientSearchModal/definitions.ts b/frontend/src/components/ClientAndCodeInput/ClientSearchModal/definitions.ts similarity index 100% rename from frontend/src/components/ApplicantAgencyFields/ClientSearchModal/definitions.ts rename to frontend/src/components/ClientAndCodeInput/ClientSearchModal/definitions.ts diff --git a/frontend/src/components/ApplicantAgencyFields/ClientSearchModal/index.tsx b/frontend/src/components/ClientAndCodeInput/ClientSearchModal/index.tsx similarity index 100% rename from frontend/src/components/ApplicantAgencyFields/ClientSearchModal/index.tsx rename to frontend/src/components/ClientAndCodeInput/ClientSearchModal/index.tsx diff --git a/frontend/src/components/ApplicantAgencyFields/ClientSearchModal/styles.scss b/frontend/src/components/ClientAndCodeInput/ClientSearchModal/styles.scss similarity index 81% rename from frontend/src/components/ApplicantAgencyFields/ClientSearchModal/styles.scss rename to frontend/src/components/ClientAndCodeInput/ClientSearchModal/styles.scss index 9c3d430b0..8b09ebfcb 100644 --- a/frontend/src/components/ApplicantAgencyFields/ClientSearchModal/styles.scss +++ b/frontend/src/components/ClientAndCodeInput/ClientSearchModal/styles.scss @@ -1,4 +1,5 @@ @use '@bcgov-nr/nr-theme/design-tokens/variables.scss' as vars; +@use '@bcgov-nr/nr-theme/design-tokens/colors.scss' as colors; @use '@carbon/type'; .client-search-modal { @@ -14,6 +15,14 @@ box-shadow: none; } + .client-results-table { + background-color: #{colors.$white}; + } + + #client-search-input { + box-shadow: inset 0.3rem -1px 0 62.5rem #{colors.$white}; + } + .client-search-grid { padding-inline: 0; diff --git a/frontend/src/components/ApplicantAgencyFields/constants.ts b/frontend/src/components/ClientAndCodeInput/constants.ts similarity index 93% rename from frontend/src/components/ApplicantAgencyFields/constants.ts rename to frontend/src/components/ClientAndCodeInput/constants.ts index 9a93b3c83..c583ea6ca 100644 --- a/frontend/src/components/ApplicantAgencyFields/constants.ts +++ b/frontend/src/components/ClientAndCodeInput/constants.ts @@ -11,6 +11,7 @@ const supportTexts = { helperTextEnabled: '2-digit code identifying the agency\'s address', invalidLocationForSelectedAgency: 'This location code is not valid for the selected agency, please enter a valid one or change the agency', invalidText: 'Please enter a valid 2-digit code identifying the agency\'s address', + invalidEmptyAgency: 'Please enter an agency for the current location code', invalidTextInterimSpecific: 'Please specify a collector location code to proceed', requestErrorHelper: 'Location code validation failed. Please retry verification' } diff --git a/frontend/src/components/ClientAndCodeInput/definitions.ts b/frontend/src/components/ClientAndCodeInput/definitions.ts new file mode 100644 index 000000000..4e6063f6c --- /dev/null +++ b/frontend/src/components/ClientAndCodeInput/definitions.ts @@ -0,0 +1,23 @@ +import ClientAndCodeInputTextType from '../../types/ClientAndCodeInputTextType'; +import { BooleanInputType, StringInputType } from '../../types/FormInputType'; + +type ClientAndCodeInputProps = { + checkboxId: string, + // User types in an acronym, but the value is stored as client_number + locationCodeInput: StringInputType, + clientInput: StringInputType, + textConfig: ClientAndCodeInputTextType, + setClientAndCode: ( + clientInput: StringInputType, + locationCodeInput: StringInputType, + checkBoxInput?: BooleanInputType + ) => void + defaultClientNumber?: string, + defaultLocCode?: string, + showCheckbox?: boolean, + readOnly?: boolean, + maxInputColSize?: number, + checkBoxInput?: BooleanInputType +} + +export default ClientAndCodeInputProps; diff --git a/frontend/src/components/ClientAndCodeInput/index.tsx b/frontend/src/components/ClientAndCodeInput/index.tsx new file mode 100644 index 000000000..c054ba2ef --- /dev/null +++ b/frontend/src/components/ClientAndCodeInput/index.tsx @@ -0,0 +1,516 @@ +import React, { useEffect, useRef, useState } from 'react'; +import { AxiosError } from 'axios'; +import { + Row, Column, TextInput, Checkbox, Tooltip, + InlineLoading, ActionableNotification, FlexGrid +} from '@carbon/react'; +import { useMutation, useQuery } from '@tanstack/react-query'; +import validator from 'validator'; + +import ClientSearchModal from './ClientSearchModal'; + +import { getForestClientByNumberOrAcronym, getForestClientLocation } from '../../api-service/forestClientsAPI'; +import { ForestClientSearchType } from '../../types/ForestClientTypes/ForestClientSearchType'; +import { ClientNumLocCodeType } from '../../types/ForestClientTypes/ForestClientType'; +import { LOCATION_CODE_LIMIT } from '../../shared-constants/shared-constants'; +import { StringInputType } from '../../types/FormInputType'; +import { THREE_HALF_HOURS, THREE_HOURS } from '../../config/TimeUnits'; +import prefix from '../../styles/classPrefix'; + +import ClientAndCodeInputProps from './definitions'; +import supportTexts, { getErrorMessageTitle } from './constants'; +import { formatLocationCode } from './utils'; + +import './styles.scss'; + +const ClientAndCodeInput = ({ + checkboxId, clientInput, locationCodeInput, textConfig, defaultClientNumber, + defaultLocCode, setClientAndCode, readOnly, showCheckbox, maxInputColSize, + checkBoxInput +}: ClientAndCodeInputProps) => { + const getIsDefaultVal = () => ( + checkBoxInput === undefined + ? !!clientInput.value && !!locationCodeInput.value + && clientInput.value === defaultClientNumber && locationCodeInput.value === defaultLocCode + : checkBoxInput.value + ); + + const clientInputRef = useRef(null); + const locCodeInputRef = useRef(null); + const [isDefault, setIsDefault] = useState( + () => getIsDefaultVal() + ); + + const [showClientValidationStatus, setShowClientValidationStatus] = useState(true); + + const [showLocCodeValidationStatus, setShowLocCodeValidationStatus] = useState(false); + + const [showErrorBanner, setShowErrorBanner] = useState(false); + + const [invalidAcronymMessage, setInvalidAcronymMessage] = useState( + supportTexts.agency.invalidAcronym + ); + + const [invalidLocationMessage, setInvalidLocationMessage] = useState( + locationCodeInput.isInvalid && clientInput.value + ? supportTexts.locationCode.invalidLocationForSelectedAgency + : supportTexts.locationCode.invalidText + ); + + useEffect(() => { + const areValsDefault = getIsDefaultVal(); + + setIsDefault(areValsDefault); + + // Do not show validation status if isDefault is true + if (areValsDefault) { + setShowClientValidationStatus(false); + setShowLocCodeValidationStatus(false); + } + }, [clientInput, locationCodeInput, defaultClientNumber, defaultLocCode, checkBoxInput]); + + const forestClientQuery = useQuery({ + queryKey: ['forest-clients', clientInput.value], + queryFn: () => getForestClientByNumberOrAcronym(clientInput.value), + enabled: !!clientInput.value, + staleTime: THREE_HOURS, + cacheTime: THREE_HALF_HOURS + }); + + const defaultClientQuery = useQuery({ + queryKey: ['forest-clients', defaultClientNumber], + queryFn: () => getForestClientByNumberOrAcronym(defaultClientNumber!), + enabled: !!defaultClientNumber, + staleTime: THREE_HOURS, + cacheTime: THREE_HALF_HOURS + }); + + const updateAfterAgencyValidation = (updatedAgency: StringInputType) => { + setClientAndCode(updatedAgency, locationCodeInput); + }; + + const updateAfterLocValidation = (isInvalid: boolean, locCode?: string) => { + const updatedLocationCode = { + ...locationCodeInput, + value: locCode ?? '', + isInvalid + }; + + setClientAndCode(clientInput, updatedLocationCode); + setShowLocCodeValidationStatus(true); + }; + + useEffect(() => { + if (forestClientQuery.status === 'success') { + if (!clientInput.value) { + setClientAndCode( + { + ...clientInput, + value: forestClientQuery.data.clientNumber + }, + locationCodeInput + ); + } + } + }, [forestClientQuery.status]); + + /** + * Format the displayed location code + */ + const formatDisplayedLocCode = (formattedCode: string) => { + if (locCodeInputRef.current) { + locCodeInputRef.current.value = formattedCode; + } + }; + + const validateLocationCodeMutation = useMutation({ + mutationFn: ( + numAndCode: ClientNumLocCodeType + ) => getForestClientLocation( + numAndCode.clientNumber, + numAndCode.locationCode + ), + onError: (err: AxiosError) => { + // Request failed + if (err.response?.status !== 404) { + setShowErrorBanner(true); + setInvalidLocationMessage(supportTexts.locationCode.requestErrorHelper); + } else { + setInvalidLocationMessage(supportTexts.locationCode.invalidLocationForSelectedAgency); + } + updateAfterLocValidation(true, locationCodeInput.value); + }, + onSuccess: (data) => { + setShowErrorBanner(false); + updateAfterLocValidation(false, data.locationCode); + } + }); + + /** + * Format the displayed acronym + */ + const formatDisplayedAcronym = (formattedAcronym: string) => { + if (clientInputRef.current) { + clientInputRef.current.value = formattedAcronym; + } + }; + + const validateClientAcronymMutation = useMutation({ + mutationFn: (clientAcronym: string) => getForestClientByNumberOrAcronym( + clientAcronym + ), + onError: (err: AxiosError) => { + if (err.response?.status !== 404) { + setShowErrorBanner(true); + setInvalidAcronymMessage(supportTexts.agency.requestErrorHelper); + } else { + setInvalidAcronymMessage(supportTexts.agency.invalidAcronym); + } + setShowLocCodeValidationStatus(false); + updateAfterAgencyValidation({ + ...clientInput, + isInvalid: true + }); + }, + onSuccess: (data) => { + setShowErrorBanner(false); + setClientAndCode({ + ...clientInput, + value: data.clientNumber, + isInvalid: false + }, locationCodeInput); + + if (locationCodeInput.value !== '') { + validateLocationCodeMutation.mutate({ + clientNumber: data.clientNumber, + locationCode: locationCodeInput.value + }); + } + } + }); + + const handleDefaultCheckBox = (checked: boolean) => { + const updatedAgency: StringInputType = { + ...clientInput, + value: checked ? defaultClientNumber ?? '' : '', + isInvalid: false + }; + + const updatedLocationCode = { + ...locationCodeInput, + value: checked ? defaultLocCode ?? '' : '', + isInvalid: false + }; + + if (!checked) { + formatDisplayedAcronym(''); + formatDisplayedLocCode(''); + } else { + formatDisplayedAcronym(defaultClientQuery.data?.acronym ?? ''); + formatDisplayedLocCode(updatedLocationCode.value); + } + + setShowClientValidationStatus(false); + setShowLocCodeValidationStatus(false); + + setClientAndCode( + updatedAgency, + updatedLocationCode, + checkBoxInput !== undefined + ? { + ...checkBoxInput, + value: checked + } + : undefined + ); + + if (!checkBoxInput) { + setIsDefault(checked); + } + }; + + const [openAgnTooltip, setOpenAgnTooltip] = useState(false); + const [openLocTooltip, setOpenLocTooltip] = useState(false); + + const renderLoading = ( + isLoading: boolean, + isSuccess: boolean, + showLoadingStatus: boolean, + idPrefix: string, + crtlTooltip: boolean, + setCtrlTooltip: Function + ) => { + if ( + (isLoading || isSuccess) + && showLoadingStatus + ) { + const tooltipLabel = isSuccess ? 'Verified!' : 'Loading'; + const loadingStatus = isSuccess ? 'finished' : 'active'; + return ( + { + setCtrlTooltip(true); + }} + onMouseOut={() => { + setCtrlTooltip(false); + }} + > + + + ); + } + return null; + }; + + const handleAgencyBlur = (value: string) => { + if (!value && locationCodeInput.value) { + setShowClientValidationStatus(false); + setShowLocCodeValidationStatus(false); + const updatedLocationCode = { + ...locationCodeInput, + isInvalid: true + }; + setInvalidLocationMessage(supportTexts.locationCode.invalidEmptyAgency); + setClientAndCode(clientInput, updatedLocationCode); + return; + } + + if (!value) { + setShowClientValidationStatus(false); + return; + } + + setShowClientValidationStatus(true); + formatDisplayedAcronym(clientInputRef.current?.value.toUpperCase() ?? ''); + validateClientAcronymMutation.mutate(value.toUpperCase()); + }; + + const handleLocationCodeBlur = (value: string) => { + const formattedCode = value ? formatLocationCode(value) : ''; + formatDisplayedLocCode(formattedCode); + + const updatedLocationCode = { + ...locationCodeInput, + value: formattedCode, + isInvalid: false + }; + + const isInRange = validator.isInt(formattedCode, { min: 0, max: 99 }); + + if (!formattedCode && !isInRange) { + setShowLocCodeValidationStatus(false); + setInvalidLocationMessage(supportTexts.locationCode.invalidText); + updatedLocationCode.isInvalid = true; + setClientAndCode(clientInput, updatedLocationCode); + return; + } + + setShowLocCodeValidationStatus(true); + + if (clientInput.value) { + setClientAndCode(clientInput, updatedLocationCode); + validateLocationCodeMutation.mutate({ + clientNumber: clientInput.value, + locationCode: formattedCode + }); + } + }; + + return ( + + { + showCheckbox + ? ( + + + ) => { + handleDefaultCheckBox(e.target.checked); + }} + /> + + + ) + : null + } + + {/* CLIENT ACRONYM */} + +
+ ) => e.target.blur()} + onBlur={(e: React.ChangeEvent) => { + if (!e.target.readOnly) { + handleAgencyBlur(e.target.value); + } + }} + size="md" + /> + { + renderLoading( + validateClientAcronymMutation.isLoading, + validateClientAcronymMutation.isSuccess, + showClientValidationStatus, + clientInput.id, + openAgnTooltip, + setOpenAgnTooltip + ) + } +
+
+ {/* LOCATION CODE */} + +
+ ) => e.target.blur()} + onBlur={(e: React.ChangeEvent) => { + if (!e.target.readOnly) { + handleLocationCodeBlur(e.target.value); + } + }} + /> + { + renderLoading( + validateLocationCodeMutation.isLoading, + validateLocationCodeMutation.isSuccess, + showLocCodeValidationStatus, + locationCodeInput.id, + openLocTooltip, + setOpenLocTooltip + ) + } +
+
+
+ { + showErrorBanner + ? ( + + + { + if (validateClientAcronymMutation.isError) { + handleAgencyBlur(clientInput.value); + } else { + handleLocationCodeBlur(locationCodeInput.value); + } + }} + onCloseButtonClick={() => { setShowErrorBanner(false); }} + /> + + + ) + : null + } + { + !(showCheckbox && isDefault) && !readOnly + ? ( + + +

+ If you don't remember the clientInput information you can + {' '} + { + const selectedClient: StringInputType = { + ...clientInput, + value: client.clientNumber, + isInvalid: false + }; + + const selectedLocationCode = { + ...locationCodeInput, + value: client.locationCode, + isInvalid: false + }; + + formatDisplayedAcronym(client.acronym); + formatDisplayedLocCode(client.locationCode); + + setClientAndCode(selectedClient, selectedLocationCode); + }} + /> +

+
+
+ ) + : null + } +
+ ); +}; + +export default ClientAndCodeInput; diff --git a/frontend/src/components/ApplicantAgencyFields/styles.scss b/frontend/src/components/ClientAndCodeInput/styles.scss similarity index 100% rename from frontend/src/components/ApplicantAgencyFields/styles.scss rename to frontend/src/components/ClientAndCodeInput/styles.scss diff --git a/frontend/src/components/ApplicantAgencyFields/utils.ts b/frontend/src/components/ClientAndCodeInput/utils.ts similarity index 100% rename from frontend/src/components/ApplicantAgencyFields/utils.ts rename to frontend/src/components/ClientAndCodeInput/utils.ts diff --git a/frontend/src/components/ClientSearchTable/index.tsx b/frontend/src/components/ClientSearchTable/index.tsx index ae27c5758..c72aba81b 100644 --- a/frontend/src/components/ClientSearchTable/index.tsx +++ b/frontend/src/components/ClientSearchTable/index.tsx @@ -12,6 +12,7 @@ import { DataTableSkeleton } from '@carbon/react'; +import prefix from '../../styles/classPrefix'; import { ForestClientSearchType } from '../../types/ForestClientTypes/ForestClientSearchType'; import PaginationChangeType from '../../types/PaginationChangeType'; import { getForestClientFullName } from '../../utils/ForestClientUtils'; @@ -151,13 +152,14 @@ const ClientSearchTable = ( } - + { processedData.length ? processedData.map((client) => ( { typeof selectClientFn === 'function' @@ -166,7 +168,7 @@ const ClientSearchTable = ( radio ariaLabel={`Select client ${client.clientName} with location code ${client.locationCode}`} id={`client-radio-${client.clientNumber}-${client.locationCode}`} - name="client-radio" + name={`client-radio-${client.clientNumber}-${client.locationCode}`} checked={client === currentSelected} onSelect={() => { selectClientFn(client); diff --git a/frontend/src/components/LotApplicantAndInfoForm/SeedlotInformation.tsx b/frontend/src/components/LotApplicantAndInfoForm/SeedlotInformation.tsx index dcf23e472..a39d3913e 100644 --- a/frontend/src/components/LotApplicantAndInfoForm/SeedlotInformation.tsx +++ b/frontend/src/components/LotApplicantAndInfoForm/SeedlotInformation.tsx @@ -39,7 +39,7 @@ const SeedlotInformation = ( ) => { const vegCodeQuery = useQuery({ queryKey: ['vegetation-codes'], - queryFn: () => getVegCodes(), + queryFn: getVegCodes, enabled: !isEdit, staleTime: THREE_HOURS, // will not refetch for 3 hours cacheTime: THREE_HALF_HOURS, // data is cached 3.5 hours then deleted diff --git a/frontend/src/components/LotApplicantAndInfoForm/constants.ts b/frontend/src/components/LotApplicantAndInfoForm/constants.ts index bf4db8c45..d4dae5795 100644 --- a/frontend/src/components/LotApplicantAndInfoForm/constants.ts +++ b/frontend/src/components/LotApplicantAndInfoForm/constants.ts @@ -1,9 +1,10 @@ -import { EmptyMultiOptObj } from '../../shared-constants/shared-constants'; -import AgencyTextPropsType from '../../types/AgencyTextPropsType'; -import { OptionsInputType, StringInputType } from '../../types/FormInputType'; +import ClientAndCodeInputTextType from '../../types/ClientAndCodeInputTextType'; +import { StringInputType } from '../../types/FormInputType'; import { ComboBoxPropsType } from './definitions'; -export const agencyFieldsText = (isReview: boolean | undefined): AgencyTextPropsType => ({ +export const clientAndCodeInputText = ( + isReview: boolean | undefined +): ClientAndCodeInputTextType => ({ useDefaultCheckbox: { name: '', labelText: '' @@ -26,7 +27,7 @@ export const speciesFieldConfig: ComboBoxPropsType = { }; // Template data for vegLot: -export const vegLotAgency: OptionsInputType = { id: '', isInvalid: false, value: EmptyMultiOptObj }; +export const vegLotAgency: StringInputType = { id: '', isInvalid: false, value: '' }; export const vegLotLocationCode: StringInputType = { id: '', isInvalid: false, value: '' }; // Remove VegCodes with these codes diff --git a/frontend/src/components/LotApplicantAndInfoForm/index.tsx b/frontend/src/components/LotApplicantAndInfoForm/index.tsx index 351d237a3..6d81177da 100644 --- a/frontend/src/components/LotApplicantAndInfoForm/index.tsx +++ b/frontend/src/components/LotApplicantAndInfoForm/index.tsx @@ -11,9 +11,8 @@ import validator from 'validator'; import Subtitle from '../Subtitle'; import Divider from '../Divider'; -import ApplicantAgencyFields from '../ApplicantAgencyFields'; -import { BooleanInputType, OptionsInputType, StringInputType } from '../../types/FormInputType'; -import { EmptyBooleanInputType } from '../../shared-constants/shared-constants'; +import ClientAndCodeInput from '../ClientAndCodeInput'; +import { StringInputType } from '../../types/FormInputType'; import AuthContext from '../../contexts/AuthContext'; import { getForestClientByNumberOrAcronym } from '../../api-service/forestClientsAPI'; import { THREE_HALF_HOURS, THREE_HOURS } from '../../config/TimeUnits'; @@ -23,7 +22,7 @@ import { FormProps } from './definitions'; import { vegLotAgency, vegLotLocationCode, - agencyFieldsText + clientAndCodeInputText } from './constants'; import './styles.scss'; @@ -51,19 +50,15 @@ const LotApplicantAndInfoForm = ({ ); useEffect(() => { - // Pre-fill the applicant acronym based on user selected role - if (defaultApplicantAgencyQuery.status === 'success' && !seedlotFormData?.client.value.code && setSeedlotFormData) { + // Pre-fill the applicant client number based on user selected role + if (defaultApplicantAgencyQuery.status === 'success' && !seedlotFormData?.client.value && setSeedlotFormData) { const forestClient = defaultApplicantAgencyQuery.data; setSeedlotFormData((prevForm) => ({ ...prevForm, client: { ...prevForm.client, - value: { - code: forestClient.clientNumber, - description: forestClient.clientName, - label: forestClient.acronym - } + value: forestClient.clientNumber } })); } @@ -83,11 +78,11 @@ const LotApplicantAndInfoForm = ({ } }; - const setAgencyAndCode = (agency: OptionsInputType, locationCode: StringInputType) => { + const setClientAndCode = (client: StringInputType, locationCode: StringInputType) => { if (isSeedlot && setSeedlotFormData) { setSeedlotFormData((prevData) => ({ ...prevData, - client: agency, + client, locationCode })); } @@ -105,23 +100,21 @@ const LotApplicantAndInfoForm = ({ } - setAgencyAndCode(agency, locationCode) + ) => setClientAndCode(client, locationCode) } readOnly={isEdit} maxInputColSize={6} diff --git a/frontend/src/components/PageTitle/index.tsx b/frontend/src/components/PageTitle/index.tsx index 585643593..cdbba60a9 100644 --- a/frontend/src/components/PageTitle/index.tsx +++ b/frontend/src/components/PageTitle/index.tsx @@ -16,7 +16,7 @@ import './styles.scss'; interface PageTitleProps { title: string; - subtitle: string | React.ReactNode; + subtitle?: string | React.ReactNode; enableFavourite?: boolean; activity?: string; } @@ -80,7 +80,11 @@ const PageTitle = ({ : null } - + { + subtitle + ? + : null + } ); }; diff --git a/frontend/src/components/SeedlotCards/constants.ts b/frontend/src/components/SeedlotCards/constants.ts index 7aa30b1de..0a53f3ac8 100644 --- a/frontend/src/components/SeedlotCards/constants.ts +++ b/frontend/src/components/SeedlotCards/constants.ts @@ -43,7 +43,7 @@ export const cards = [ }, { id: '4', - image: 'Farm_01', + image: 'AiExplainability', header: 'Review seedlots', description: 'Check all seedlots that are waiting for approval', link: ROUTES.TSC_SEEDLOTS_TABLE, diff --git a/frontend/src/components/SeedlotRegistrationSteps/CollectionStep/constants.ts b/frontend/src/components/SeedlotRegistrationSteps/CollectionStep/constants.ts index 7ee15f99b..a3fa2d971 100644 --- a/frontend/src/components/SeedlotRegistrationSteps/CollectionStep/constants.ts +++ b/frontend/src/components/SeedlotRegistrationSteps/CollectionStep/constants.ts @@ -1,10 +1,10 @@ -import AgencyTextPropsType from '../../../types/AgencyTextPropsType'; +import ClientAndCodeInputTextType from '../../../types/ClientAndCodeInputTextType'; export const DATE_FORMAT = 'Y/m/d'; export const MOMENT_DATE_FORMAT = 'YYYY/MM/DD'; export const MAX_INPUT_DECIMAL = 9999.999; -export const agencyFieldsProps: AgencyTextPropsType = { +export const agencyFieldsProps: ClientAndCodeInputTextType = { useDefaultCheckbox: { name: 'applicant', labelText: 'Use applicant agency as collector agency' diff --git a/frontend/src/components/SeedlotRegistrationSteps/CollectionStep/definitions.ts b/frontend/src/components/SeedlotRegistrationSteps/CollectionStep/definitions.ts index e7dbd233e..b2dcd198e 100644 --- a/frontend/src/components/SeedlotRegistrationSteps/CollectionStep/definitions.ts +++ b/frontend/src/components/SeedlotRegistrationSteps/CollectionStep/definitions.ts @@ -1,13 +1,10 @@ import { - BooleanInputType, - OptionsInputType, StringArrInputType, StringInputType } from '../../../types/FormInputType'; export type CollectionForm = { - useDefaultAgencyInfo: BooleanInputType, - collectorAgency: OptionsInputType, + collectorAgency: StringInputType, locationCode: StringInputType, startDate: StringInputType, endDate: StringInputType, diff --git a/frontend/src/components/SeedlotRegistrationSteps/CollectionStep/index.tsx b/frontend/src/components/SeedlotRegistrationSteps/CollectionStep/index.tsx index f24d08fd5..24f25f983 100644 --- a/frontend/src/components/SeedlotRegistrationSteps/CollectionStep/index.tsx +++ b/frontend/src/components/SeedlotRegistrationSteps/CollectionStep/index.tsx @@ -12,22 +12,21 @@ import { TextArea, CheckboxSkeleton } from '@carbon/react'; -import moment from 'moment'; import validator from 'validator'; -import { BooleanInputType, OptionsInputType, StringInputType } from '../../../types/FormInputType'; import { THREE_HALF_HOURS, THREE_HOURS } from '../../../config/TimeUnits'; -import { EmptyBooleanInputType } from '../../../shared-constants/shared-constants'; +import { now } from '../../../utils/DateUtils'; import getConeCollectionMethod from '../../../api-service/coneCollectionMethodAPI'; import Subtitle from '../../Subtitle'; -import ApplicantAgencyFields from '../../ApplicantAgencyFields'; +import ClientAndCodeInput from '../../ClientAndCodeInput'; import ScrollToTop from '../../ScrollToTop'; import ClassAContext from '../../../views/Seedlot/ContextContainerClassA/context'; import MultiOptionsObj from '../../../types/MultiOptionsObject'; +import { StringInputType } from '../../../types/FormInputType'; import { - DATE_FORMAT, MOMENT_DATE_FORMAT, agencyFieldsProps, fieldsConfig + DATE_FORMAT, agencyFieldsProps, fieldsConfig } from './constants'; import { CollectionForm @@ -44,20 +43,18 @@ const CollectionStep = ({ isReview }: CollectionStepProps) => { const { allStepData: { collectionStep: state }, setStepData, - defaultAgencyObj: defaultAgency, + defaultClientNumber, defaultCode, isFormSubmitted } = useContext(ClassAContext); const [isCalcWrong, setIsCalcWrong] = useState(false); - const setAgencyAndCode = ( - isDefault: BooleanInputType, - agency: OptionsInputType, + const setClientAndCode = ( + agency: StringInputType, locationCode: StringInputType ) => { const clonedState = structuredClone(state); - clonedState.useDefaultAgencyInfo = isDefault; clonedState.collectorAgency = agency; clonedState.locationCode = locationCode; setStepData('collectionStep', clonedState); @@ -76,8 +73,7 @@ const CollectionStep = ({ isReview }: CollectionStepProps) => { clonedState[dateType].value = value; - const isInvalid = moment(clonedState.endDate.value, MOMENT_DATE_FORMAT) - .isBefore(moment(clonedState.startDate.value, MOMENT_DATE_FORMAT)); + const isInvalid = clonedState.endDate.value < clonedState.startDate.value; clonedState.startDate.isInvalid = isInvalid; clonedState.endDate.isInvalid = isInvalid; @@ -150,23 +146,20 @@ const CollectionStep = ({ isReview }: CollectionStepProps) => { } - setAgencyAndCode(isDefault, agency, locationCode) + ) => setClientAndCode(agency, locationCode) } - isFormSubmitted={isFormSubmitted} readOnly={isFormSubmitted && !isReview} maxInputColSize={6} /> @@ -186,6 +179,7 @@ const CollectionStep = ({ isReview }: CollectionStepProps) => { datePickerType="single" dateFormat={DATE_FORMAT} readOnly={isFormSubmitted && !isReview} + maxDate={!isReview ? now : undefined} value={state.startDate.value} onChange={(_e: Array, selectedDate: string) => { handleDateChange(true, selectedDate); @@ -209,6 +203,7 @@ const CollectionStep = ({ isReview }: CollectionStepProps) => { datePickerType="single" dateFormat={DATE_FORMAT} minDate={state.startDate.value} + maxDate={!isReview ? now : undefined} readOnly={isFormSubmitted && !isReview} value={state.endDate.value} onChange={(_e: Array, selectedDate: string) => { @@ -301,17 +296,19 @@ const CollectionStep = ({ isReview }: CollectionStepProps) => { id={state.selectedCollectionCodes.id} > { - (coneCollectionMethodsQuery.data as MultiOptionsObj[]).map((method) => ( - handleCollectionMethods(method.code)} - /> - )) + (coneCollectionMethodsQuery.data as MultiOptionsObj[]) + .sort((a, b) => a.description.localeCompare(b.description)) + .map((method) => ( + handleCollectionMethods(method.code)} + /> + )) } ) diff --git a/frontend/src/components/SeedlotRegistrationSteps/ExtractionAndStorageStep/constants.ts b/frontend/src/components/SeedlotRegistrationSteps/ExtractionAndStorageStep/constants.ts index a6dd4a77c..450297e40 100644 --- a/frontend/src/components/SeedlotRegistrationSteps/ExtractionAndStorageStep/constants.ts +++ b/frontend/src/components/SeedlotRegistrationSteps/ExtractionAndStorageStep/constants.ts @@ -1,11 +1,11 @@ -import AgencyTextPropsType from '../../../types/AgencyTextPropsType'; +import ClientAndCodeInputTextType from '../../../types/ClientAndCodeInputTextType'; export const DATE_FORMAT = 'Y/m/d'; -export const extractorAgencyFields: AgencyTextPropsType = { +export const extractorTextConfig: ClientAndCodeInputTextType = { useDefaultCheckbox: { name: 'extractor-use-tsc', - labelText: 'The extractory agency is the Tree Seed Center (TSC)' + labelText: 'The extractory agency is the Tree Seed Centre (TSC)' }, agencyInput: { titleText: 'Extractory agency acronym', @@ -17,10 +17,10 @@ export const extractorAgencyFields: AgencyTextPropsType = { } }; -export const storageAgencyFields: AgencyTextPropsType = { +export const storageTextConfig: ClientAndCodeInputTextType = { useDefaultCheckbox: { name: 'storage-use-tsc', - labelText: 'The seed storage agency is the Tree Seed Center (TSC)' + labelText: 'The seed storage agency is the Tree Seed Centre (TSC)' }, agencyInput: { titleText: 'Seed storage agency acronym', @@ -32,16 +32,16 @@ export const storageAgencyFields: AgencyTextPropsType = { } }; -export const inputText = { +export const inputText = (isReview?: boolean) => ({ extractionTitle: { titleText: 'Extraction information', - subtitleText: 'Enter the extractory agency information and extraction\'s star and end dates for this seedlot' + subtitleText: 'Enter the extractory agency information and extraction\'s start and end dates for this seedlot' }, date: { extraction: { labelText: { - start: 'Extraction start date (optional)', - end: 'Extraction end date (optional)' + start: `Extraction start date${!isReview ? ' (optional)' : ''}`, + end: `Extraction end date${!isReview ? ' (optional)' : ''}` }, notification: { title: 'Extraction start and end dates', @@ -50,8 +50,8 @@ export const inputText = { }, storage: { labelText: { - start: 'Storage start date (optional)', - end: 'Storage end date (optional)' + start: `Storage start date${!isReview ? ' (optional)' : ''}`, + end: `Storage end date${!isReview ? ' (optional)' : ''}` }, notification: { title: 'Storage start and end dates', @@ -66,4 +66,4 @@ export const inputText = { titleText: 'Temporary seed storage', subtitleText: 'Enter the seed storage agency information and storage\'s start and end dates for this seedlot' } -}; +}); diff --git a/frontend/src/components/SeedlotRegistrationSteps/ExtractionAndStorageStep/index.tsx b/frontend/src/components/SeedlotRegistrationSteps/ExtractionAndStorageStep/index.tsx index 3be8780a7..dc8bea174 100644 --- a/frontend/src/components/SeedlotRegistrationSteps/ExtractionAndStorageStep/index.tsx +++ b/frontend/src/components/SeedlotRegistrationSteps/ExtractionAndStorageStep/index.tsx @@ -1,5 +1,4 @@ import React, { useContext, useState } from 'react'; -import moment from 'moment'; import { Column, @@ -12,33 +11,28 @@ import { import Subtitle from '../../Subtitle'; import ScrollToTop from '../../ScrollToTop'; -import ApplicantAgencyFields from '../../ApplicantAgencyFields'; -import MultiOptionsObj from '../../../types/MultiOptionsObject'; +import ClientAndCodeInput from '../../ClientAndCodeInput'; import ClassAContext from '../../../views/Seedlot/ContextContainerClassA/context'; +import { now } from '../../../utils/DateUtils'; import ExtractionStorageForm from '../../../types/SeedlotTypes/ExtractionStorage'; -import { BooleanInputType, OptionsInputType, StringInputType } from '../../../types/FormInputType'; +import { BooleanInputType, StringInputType } from '../../../types/FormInputType'; +import { tscAgencyObj, tscLocationCode } from '../../../views/Seedlot/ContextContainerClassA/constants'; import { inputText, DATE_FORMAT, - storageAgencyFields, - extractorAgencyFields + storageTextConfig, + extractorTextConfig } from './constants'; import './styles.scss'; interface ExtractionAndStorageProps { - defaultAgency: MultiOptionsObj; - defaultCode: string; isReview?: boolean } const ExtractionAndStorage = ( - { - defaultAgency, - defaultCode, - isReview - }: ExtractionAndStorageProps + { isReview }: ExtractionAndStorageProps ) => { const { allStepData: { extractionStorageStep: state }, @@ -49,16 +43,20 @@ const ExtractionAndStorage = ( const [isExtractorHintOpen, setIsExtractorHintOpen] = useState(true); const [isStorageHintOpen, setIsStorageHintOpen] = useState(true); - const setAgencyAndCode = ( - isDefault: BooleanInputType, - agency: OptionsInputType, + const inputTextObj = inputText(isReview); + + const setClientAndCode = ( + agency: StringInputType, locationCode: StringInputType, + checkBoxInput: BooleanInputType, extractionOrStorage: ('extraction' | 'seedStorage') ) => { const clonedState = structuredClone(state); - clonedState[extractionOrStorage].useTSC = isDefault; clonedState[extractionOrStorage].agency = agency; clonedState[extractionOrStorage].locationCode = locationCode; + if (checkBoxInput) { + clonedState[extractionOrStorage].useTSC = checkBoxInput; + } setStepData('extractionStorageStep', clonedState); }; @@ -70,8 +68,7 @@ const ExtractionAndStorage = ( // Check if the start date is set before the end date if (startDate !== '' && endDate !== '') { - return moment(endDate, 'YYYY/MM/DD') - .isBefore(moment(startDate, 'YYYY/MM/DD')); + return endDate < startDate; } return false; }; @@ -99,33 +96,32 @@ const ExtractionAndStorage = ( -

{inputText.extractionTitle.titleText}

+

{inputTextObj.extractionTitle.titleText}

{ !isReview ? ( - + ) : null }
- setAgencyAndCode(isDefault, agency, locationCode, 'extraction')} + clientInput={state.extraction.agency} + locationCodeInput={state.extraction.locationCode} + textConfig={extractorTextConfig} + defaultClientNumber={tscAgencyObj.code} + defaultLocCode={tscLocationCode} + setClientAndCode={( + clientInput: StringInputType, + locationCodeInput: StringInputType, + checkBoxInput?: BooleanInputType + ) => setClientAndCode(clientInput, locationCodeInput, checkBoxInput!, 'extraction')} readOnly={isFormSubmitted && !isReview} - isFormSubmitted={isFormSubmitted} maxInputColSize={6} + checkBoxInput={state.extraction.useTSC} /> @@ -133,6 +129,7 @@ const ExtractionAndStorage = ( datePickerType="single" name="extractionStartDate" dateFormat={DATE_FORMAT} + maxDate={now} value={state.extraction.startDate.value} onChange={(_e: Array, selectedDate: string) => { handleDates(true, 'extraction', selectedDate); @@ -141,12 +138,12 @@ const ExtractionAndStorage = ( > @@ -156,6 +153,7 @@ const ExtractionAndStorage = ( datePickerType="single" name="extractionEndDate" dateFormat={DATE_FORMAT} + maxDate={now} value={state.extraction.endDate.value} onChange={(_e: Array, selectedDate: string) => { handleDates(false, 'extraction', selectedDate); @@ -164,12 +162,12 @@ const ExtractionAndStorage = ( > @@ -179,8 +177,8 @@ const ExtractionAndStorage = ( { setIsExtractorHintOpen(false); }} /> )} @@ -188,33 +186,32 @@ const ExtractionAndStorage = ( -

{inputText.storageTitle.titleText}

+

{inputTextObj.storageTitle.titleText}

{ !isReview ? ( - + ) : null }
- setAgencyAndCode(isDefault, agency, locationCode, 'seedStorage')} + clientInput={state.seedStorage.agency} + locationCodeInput={state.seedStorage.locationCode} + textConfig={storageTextConfig} + defaultClientNumber={tscAgencyObj.code} + defaultLocCode={tscLocationCode} + setClientAndCode={( + client: StringInputType, + locationCode: StringInputType, + checkBoxInput?: BooleanInputType + ) => setClientAndCode(client, locationCode, checkBoxInput!, 'seedStorage')} readOnly={isFormSubmitted && !isReview} - isFormSubmitted={isFormSubmitted} maxInputColSize={6} + checkBoxInput={state.seedStorage.useTSC} /> @@ -222,6 +219,7 @@ const ExtractionAndStorage = ( datePickerType="single" name="storageStartDate" dateFormat={DATE_FORMAT} + maxDate={!isReview ? now : undefined} value={state.seedStorage.startDate.value} onChange={(_e: Array, selectedDate: string) => { handleDates(true, 'seedStorage', selectedDate); @@ -230,12 +228,12 @@ const ExtractionAndStorage = ( > @@ -245,6 +243,7 @@ const ExtractionAndStorage = ( datePickerType="single" name="storageEndDate" dateFormat={DATE_FORMAT} + maxDate={!isReview ? now : undefined} value={state.seedStorage.endDate.value} onChange={(_e: Array, selectedDate: string) => { handleDates(false, 'seedStorage', selectedDate); @@ -253,12 +252,12 @@ const ExtractionAndStorage = ( > @@ -268,8 +267,8 @@ const ExtractionAndStorage = ( { setIsStorageHintOpen(false); }} /> )} diff --git a/frontend/src/components/SeedlotRegistrationSteps/InterimStep/constants.ts b/frontend/src/components/SeedlotRegistrationSteps/InterimStep/constants.ts index fb422f581..c0142268c 100644 --- a/frontend/src/components/SeedlotRegistrationSteps/InterimStep/constants.ts +++ b/frontend/src/components/SeedlotRegistrationSteps/InterimStep/constants.ts @@ -1,10 +1,10 @@ -import AgencyTextPropsType from '../../../types/AgencyTextPropsType'; +import ClientAndCodeInputTextType from '../../../types/ClientAndCodeInputTextType'; export const DATE_FORMAT = 'Y/m/d'; export const MAX_FACILITY_DESC_CHAR = 50; -export const agencyFieldsProps: AgencyTextPropsType = { +export const clientAndCodeTextConfig: ClientAndCodeInputTextType = { useDefaultCheckbox: { name: 'useCollectorAgency', labelText: 'Use applicant collector agency as interim storage agency' @@ -29,7 +29,8 @@ export const pageTexts = { labelTextEnd: 'Storage end date', placeholder: 'yyyy/mm/dd', helperText: 'year/month/day', - invalidText: 'Please enter a valid date' + invalidText: 'Please enter a valid date', + invalidDateBeforeCollection: 'The storage end date can\'t be set before collection end date' }, storageFacility: { labelText: 'Storage facility type', diff --git a/frontend/src/components/SeedlotRegistrationSteps/InterimStep/definitions.ts b/frontend/src/components/SeedlotRegistrationSteps/InterimStep/definitions.ts index dec4e8447..564e259b3 100644 --- a/frontend/src/components/SeedlotRegistrationSteps/InterimStep/definitions.ts +++ b/frontend/src/components/SeedlotRegistrationSteps/InterimStep/definitions.ts @@ -1,8 +1,8 @@ -import { BooleanInputType, OptionsInputType, StringInputType } from '../../../types/FormInputType'; +import { BooleanInputType, StringInputType } from '../../../types/FormInputType'; type InterimForm = { useCollectorAgencyInfo: BooleanInputType, - agencyName: OptionsInputType, + agencyName: StringInputType, locationCode: StringInputType, startDate: StringInputType, endDate: StringInputType, diff --git a/frontend/src/components/SeedlotRegistrationSteps/InterimStep/index.tsx b/frontend/src/components/SeedlotRegistrationSteps/InterimStep/index.tsx index d9b7c1e52..552d2eb71 100644 --- a/frontend/src/components/SeedlotRegistrationSteps/InterimStep/index.tsx +++ b/frontend/src/components/SeedlotRegistrationSteps/InterimStep/index.tsx @@ -1,6 +1,5 @@ import React, { useContext, useState } from 'react'; import { useQuery } from '@tanstack/react-query'; -import moment from 'moment'; import { Column, @@ -16,18 +15,17 @@ import { import Subtitle from '../../Subtitle'; import ScrollToTop from '../../ScrollToTop'; -import ApplicantAgencyFields from '../../ApplicantAgencyFields'; +import ClientAndCodeInput from '../../ClientAndCodeInput'; import getFacilityTypes from '../../../api-service/facilityTypesAPI'; +import { now } from '../../../utils/DateUtils'; import { getMultiOptList } from '../../../utils/MultiOptionsUtils'; import MultiOptionsObj from '../../../types/MultiOptionsObject'; -import { BooleanInputType, OptionsInputType, StringInputType } from '../../../types/FormInputType'; -import { EmptyBooleanInputType } from '../../../shared-constants/shared-constants'; +import { BooleanInputType, StringInputType } from '../../../types/FormInputType'; import ClassAContext from '../../../views/Seedlot/ContextContainerClassA/context'; -import InterimForm from './definitions'; import { - DATE_FORMAT, MAX_FACILITY_DESC_CHAR, agencyFieldsProps, pageTexts + DATE_FORMAT, MAX_FACILITY_DESC_CHAR, clientAndCodeTextConfig, pageTexts } from './constants'; import { THREE_HALF_HOURS, THREE_HOURS } from '../../../config/TimeUnits'; @@ -42,31 +40,34 @@ const InterimStep = ({ isReview }:InterimStepProps) => { allStepData: { interimStep: state }, allStepData: { collectionStep: { collectorAgency } }, allStepData: { collectionStep: { locationCode: collectorCode } }, + allStepData: { collectionStep: { endDate } }, setStepData, isFormSubmitted } = useContext(ClassAContext); const [otherChecked, setOtherChecked] = useState(state.facilityType.value === 'OTH'); - const setAgencyAndCode = ( - agencyData: OptionsInputType, - locationCodeData: StringInputType, - useDefaultData: BooleanInputType + const setClientAndCode = ( + clientInput: StringInputType, + locationCodeInput: StringInputType, + checkBoxInput: BooleanInputType ) => { const clonedState = structuredClone(state); - clonedState.agencyName = agencyData; - clonedState.locationCode = locationCodeData; - clonedState.useCollectorAgencyInfo = useDefaultData; + clonedState.agencyName = clientInput; + clonedState.locationCode = locationCodeInput; + if (checkBoxInput) { + clonedState.useCollectorAgencyInfo = checkBoxInput; + } + setStepData('interimStep', clonedState); }; // This function validates changes on both start and end dates // of the storage information - const validateStorageDates = (curState: InterimForm) => { + const validateStorageDates = (curStartDate: string, curEndDate: string) => { // Check if the start date is set before the end date - if (curState.startDate.value !== '' && curState.endDate.value !== '') { - return moment(curState.endDate.value, 'YYYY/MM/DD') - .isBefore(moment(curState.startDate.value, 'YYYY/MM/DD')); + if (curStartDate !== '' && curEndDate !== '') { + return curEndDate < curStartDate; } return false; }; @@ -79,9 +80,17 @@ const InterimStep = ({ isReview }:InterimStepProps) => { clonedState.endDate.value = stringDate; } - const isInvalid = validateStorageDates(clonedState); + const isInvalid = validateStorageDates( + clonedState.startDate.value, + clonedState.endDate.value + ); clonedState.startDate.isInvalid = isInvalid; clonedState.endDate.isInvalid = isInvalid; + + // Validate if end date is after collection end date + if (!isStart && !isInvalid) { + clonedState.endDate.isInvalid = clonedState.endDate.value < endDate.value; + } setStepData('interimStep', clonedState); }; @@ -154,23 +163,22 @@ const InterimStep = ({ isReview }:InterimStepProps) => { } - setAgencyAndCode(agency, locationCode, isDefault)} - isFormSubmitted={isFormSubmitted} + clientInput={state.agencyName} + locationCodeInput={state.locationCode} + textConfig={clientAndCodeTextConfig} + defaultClientNumber={collectorAgency.value} + defaultLocCode={collectorCode.value} + setClientAndCode={( + clientInput: StringInputType, + locationCodeInput: StringInputType, + checkBoxInput?: BooleanInputType + ) => setClientAndCode(clientInput, locationCodeInput, checkBoxInput!)} readOnly={isFormSubmitted && !isReview} maxInputColSize={6} + checkBoxInput={state.useCollectorAgencyInfo} /> @@ -178,6 +186,7 @@ const InterimStep = ({ isReview }:InterimStepProps) => { datePickerType="single" name="startDate" dateFormat={DATE_FORMAT} + maxDate={!isReview ? now : undefined} value={state.startDate.value} onChange={(_e: Array, selectedDate: string) => { handleStorageDates(true, selectedDate); @@ -202,6 +211,7 @@ const InterimStep = ({ isReview }:InterimStepProps) => { name="endDate" dateFormat={DATE_FORMAT} minDate={state.startDate.value} + maxDate={!isReview ? now : undefined} value={state.endDate.value} onChange={(_e: Array, selectedDate: string) => { handleStorageDates(false, selectedDate); @@ -213,8 +223,15 @@ const InterimStep = ({ isReview }:InterimStepProps) => { labelText={pageTexts.storageDate.labelTextEnd} helperText={pageTexts.storageDate.helperText} placeholder={pageTexts.storageDate.placeholder} - invalid={state.startDate.isInvalid} - invalidText={pageTexts.storageDate.invalidText} + invalid={state.endDate.isInvalid} + // If start date field is invalid, it means that the end date is also + // invalid and the error message can stay the same, else, shows the + // exclusive end date error message + invalidText={ + state.startDate.isInvalid + ? pageTexts.storageDate.invalidText + : pageTexts.storageDate.invalidDateBeforeCollection + } readOnly={isFormSubmitted} autoComplete="off" /> diff --git a/frontend/src/components/SeedlotRegistrationSteps/OrchardStep/OrchardWarnModal/constants.ts b/frontend/src/components/SeedlotRegistrationSteps/OrchardStep/OrchardWarnModal/constants.ts index a66080c4b..cde6047ef 100644 --- a/frontend/src/components/SeedlotRegistrationSteps/OrchardStep/OrchardWarnModal/constants.ts +++ b/frontend/src/components/SeedlotRegistrationSteps/OrchardStep/OrchardWarnModal/constants.ts @@ -5,7 +5,7 @@ const modalConfig: orchardModalOptions = { label: 'Delete orchard', title: 'Are you sure you want to delete the additional orchard? If yes, then you will lose the parent tree and SMP information in Step 5', buttons: { - primary: 'Delete additional orchard', + primary: 'Delete secondary orchard', secondary: 'Cancel' } }, diff --git a/frontend/src/components/SeedlotRegistrationSteps/OrchardStep/constants.ts b/frontend/src/components/SeedlotRegistrationSteps/OrchardStep/constants.ts index f6373cd75..7984c78ea 100644 --- a/frontend/src/components/SeedlotRegistrationSteps/OrchardStep/constants.ts +++ b/frontend/src/components/SeedlotRegistrationSteps/OrchardStep/constants.ts @@ -1,20 +1,10 @@ -import { OrchardObj } from './definitions'; - -export const MAX_ORCHARDS = 2; - -export const initialStagedOrchard: OrchardObj = { - inputId: -1, - selectedItem: null, - isInvalid: false -}; - export const orchardStepText = { orchardSection: { title: 'Orchard information', subtitle: 'Enter the contributing orchard information', orchardInput: { - label: 'Select an orchard', - optLabel: 'Select an additional orchard', + label: 'Select a primary orchard', + secondaryLabel: 'Select a secondary orchard', placeholder: 'ID - Name - Lot type - Stage code', fetchError: 'Failed to fetch orchard data' }, @@ -24,7 +14,7 @@ export const orchardStepText = { }, buttons: { add: 'Add additional orchard', - delete: 'Delete additional orchard' + delete: 'Delete secondary orchard' } }, gameteSection: { @@ -58,7 +48,7 @@ export const orchardStepText = { checkbox: 'No, there was no pollen contamination present in the seed orchard' }, breedingPercentage: { - label: 'Contaminant pollen breeding value (optional) (%)', + label: 'Contaminant pollen breeding value', helper: 'If contaminant pollen was present and the contaminant pollen has a breeding value', invalid: 'Please enter a valid value between 0 and 100' }, diff --git a/frontend/src/components/SeedlotRegistrationSteps/OrchardStep/definitions.ts b/frontend/src/components/SeedlotRegistrationSteps/OrchardStep/definitions.ts index 669740473..86f1f7f2f 100644 --- a/frontend/src/components/SeedlotRegistrationSteps/OrchardStep/definitions.ts +++ b/frontend/src/components/SeedlotRegistrationSteps/OrchardStep/definitions.ts @@ -1,17 +1,10 @@ import { BooleanInputType, OptionsInputType, StringInputType } from '../../../types/FormInputType'; -import MultiOptionsObj from '../../../types/MultiOptionsObject'; - -// Not using the FormInputType here because it has the inputId field. -// It's not used as an element id, but rather as an position indicator. -// e.g. the first orchard will have an inputId of 0, the second will have 1. -export type OrchardObj = { - inputId: number, - selectedItem: MultiOptionsObj | null - isInvalid: boolean -} export type OrchardForm = { - orchards: Array, + orchards: { + primaryOrchard: OptionsInputType, + secondaryOrchard: OptionsInputType & { enabled: boolean } + }, femaleGametic: OptionsInputType, maleGametic: OptionsInputType, isControlledCross: BooleanInputType, diff --git a/frontend/src/components/SeedlotRegistrationSteps/OrchardStep/index.tsx b/frontend/src/components/SeedlotRegistrationSteps/OrchardStep/index.tsx index e8a6bf0ad..7f5ec0e27 100644 --- a/frontend/src/components/SeedlotRegistrationSteps/OrchardStep/index.tsx +++ b/frontend/src/components/SeedlotRegistrationSteps/OrchardStep/index.tsx @@ -33,12 +33,13 @@ import Subtitle from '../../Subtitle'; import ReadOnlyInput from '../../ReadOnlyInput'; import ClassAContext from '../../../views/Seedlot/ContextContainerClassA/context'; -import { OrchardForm, OrchardObj } from './definitions'; -import { initialStagedOrchard, MAX_ORCHARDS, orchardStepText } from './constants'; +import { OrchardForm } from './definitions'; +import { orchardStepText } from './constants'; import OrchardWarnModal from './OrchardWarnModal'; import orchardModalOptions from './OrchardWarnModal/definitions'; import './styles.scss'; +import { OptionsInputType } from '../../../types/FormInputType'; type NumStepperVal = { value: number, @@ -65,7 +66,7 @@ const OrchardStep = ({ const [modalOpen, setModalOpen] = useState(false); const [modalType, setModalType] = useState('change'); // Store the orchard selection until the user has confirmed the warning modal - const [stagedOrchard, setStagedOrchard] = useState(initialStagedOrchard); + const [stagedOrchard, setStagedOrchard] = useState(null); const gameticMethodologyQuery = useQuery({ queryKey: ['gametic-methodologies'], @@ -107,7 +108,8 @@ const OrchardStep = ({ ({ code: orchard.id, description: orchard.name, - label: `${orchard.id} - ${orchard.name} - ${orchard.lotTypeCode} - ${orchard.stageCode}` + label: `${orchard.id} - ${orchard.name} - ${orchard.lotTypeCode} - ${orchard.stageCode}`, + spuId: orchard.spuId }) )) .sort((a, b) => Number(a.code) - Number(b.code)) @@ -143,64 +145,60 @@ const OrchardStep = ({ const addOrchardObj = () => { const orchards = structuredClone(state.orchards); - const numOfOrchard = orchards.length; - if (numOfOrchard < MAX_ORCHARDS) { - const newOrchard: OrchardObj = { - inputId: numOfOrchard, - selectedItem: null, - isInvalid: false - }; - orchards.push(newOrchard); - setStepData( - 'orchardStep', - { - ...state, - orchards - } - ); - } + orchards.secondaryOrchard.enabled = true; + setStepData( + 'orchardStep', + { + ...state, + orchards + } + ); }; const deleteOrchardObj = () => { const orchards = structuredClone(state.orchards); - const numOfOrchard = orchards.length; - const newOrchards = orchards.filter((orchard) => orchard.inputId !== (numOfOrchard - 1)); + orchards.secondaryOrchard.enabled = false; + orchards.secondaryOrchard.value = EmptyMultiOptObj; setStepData( 'orchardStep', { ...state, - orchards: newOrchards + orchards } ); }; - const setOrchard = (inputId: number, selectedItem: MultiOptionsObj | null) => { + const setOrchard = ( + isPrimary: boolean, + selectedItem: MultiOptionsObj | null + ) => { const orchards = structuredClone(state.orchards); - const selectedOrchardIndex = orchards.findIndex((orchard) => orchard.inputId === inputId); - if (selectedOrchardIndex > -1) { - orchards[selectedOrchardIndex].selectedItem = selectedItem; - setStepData( - 'orchardStep', - { - ...state, - orchards - } - ); + + if (isPrimary) { + orchards.primaryOrchard.value = selectedItem ?? EmptyMultiOptObj; + } else { + orchards.secondaryOrchard.value = selectedItem ?? EmptyMultiOptObj; } + + setStepData( + 'orchardStep', + { + ...state, + orchards + } + ); }; // Remove options that are already selected by a user const removeSelectedOption = (data: MultiOptionsObj[]) => { const filteredOptions: MultiOptionsObj[] = structuredClone(data); - state.orchards.forEach((orchard) => { - const orchardId = orchard.selectedItem?.code; - // The index of a matching orchard in filteredOptions - const orchardOptIndex = filteredOptions.findIndex((option) => option.code === orchardId); - if (orchardOptIndex > -1) { - // Remove found option - filteredOptions.splice(orchardOptIndex, 1); - } - }); + const orchardId = state.orchards.primaryOrchard.value.code; + // The index of a matching orchard in filteredOptions + const orchardOptIndex = filteredOptions.findIndex((option) => option.code === orchardId); + if (orchardOptIndex > -1) { + // Remove found option + filteredOptions.splice(orchardOptIndex, 1); + } return filteredOptions; }; @@ -211,14 +209,17 @@ const OrchardStep = ({ if (modalType === 'delete') { deleteOrchardObj(); } - if (modalType === 'change') { - setOrchard(stagedOrchard.inputId, stagedOrchard.selectedItem); + if (modalType === 'change' && stagedOrchard) { + setOrchard( + stagedOrchard.id === state.orchards.primaryOrchard.id, + stagedOrchard.value + ); } }; const renderOrchardButtons = () => { if (!isFormSubmitted && !isReview) { - return state.orchards.length !== 1 + return state.orchards.secondaryOrchard.enabled ? ( @@ -228,7 +229,7 @@ const OrchardStep = ({ renderIcon={TrashCan} onClick={() => { // Show warning only if the table is not empty and an item has been selected - if (!isTableEmpty && state.orchards[1].selectedItem) { + if (!isTableEmpty && state.orchards.secondaryOrchard.value.code) { setModalType('delete'); setModalOpen(true); } else deleteOrchardObj(); @@ -261,51 +262,54 @@ const OrchardStep = ({ if (isFormSubmitted && isReview) { return (
+ + + + + { - state.orchards.map((orchard: OrchardObj, index: number) => ( - - - - - - - - - )) + state.orchards.secondaryOrchard.value.label + ? ( + + + + + + ) + : null }
); } - return (state.orchards.map((orchard: OrchardObj) => ( - - - { + return ( + <> + + + { orchardQuery.isFetching ? ( ) : ( <> filterInput({ item, inputValue }) @@ -315,12 +319,11 @@ const OrchardStep = ({ if (!isTableEmpty) { setModalType('change'); setStagedOrchard({ - inputId: orchard.inputId, - selectedItem: e.selectedItem, - isInvalid: orchard.isInvalid + ...state.orchards.primaryOrchard, + value: e.selectedItem }); setModalOpen(true); - } else setOrchard(orchard.inputId, e.selectedItem); + } else setOrchard(true, e.selectedItem); } } readOnly={isFormSubmitted || isReview} @@ -337,9 +340,68 @@ const OrchardStep = ({ ) } - - - ))); + + + {/* Secondary Orchard */} + { + state.orchards.secondaryOrchard.enabled + ? ( + + + { + orchardQuery.isFetching ? ( + + ) + : ( + <> + filterInput({ item, inputValue }) + } + onChange={ + (e: ComboBoxEvent) => { + if (!isTableEmpty) { + setModalType('change'); + setStagedOrchard({ + ...state.orchards.secondaryOrchard, + value: e.selectedItem + }); + setModalOpen(true); + } else setOrchard(false, e.selectedItem); + } + } + readOnly={isFormSubmitted || isReview} + /> + { + orchardQuery.isError && !isFormSubmitted + ? ( + + ) + : null + } + + ) + } + + + ) + : null + } + + ); }; return ( diff --git a/frontend/src/components/SeedlotRegistrationSteps/OwnershipStep/SingleOwnerInfo/index.tsx b/frontend/src/components/SeedlotRegistrationSteps/OwnershipStep/SingleOwnerInfo/index.tsx index bd3b2fa3a..f75423014 100644 --- a/frontend/src/components/SeedlotRegistrationSteps/OwnershipStep/SingleOwnerInfo/index.tsx +++ b/frontend/src/components/SeedlotRegistrationSteps/OwnershipStep/SingleOwnerInfo/index.tsx @@ -10,10 +10,9 @@ import { DropdownSkeleton } from '@carbon/react'; import { TrashCan } from '@carbon/icons-react'; -import ApplicantAgencyFields from '../../../ApplicantAgencyFields'; +import ClientAndCodeInput from '../../../ClientAndCodeInput'; -import { BooleanInputType, OptionsInputType, StringInputType } from '../../../../types/FormInputType'; -import { EmptyBooleanInputType } from '../../../../shared-constants/shared-constants'; +import { StringInputType } from '../../../../types/FormInputType'; import MultiOptionsObj from '../../../../types/MultiOptionsObject'; import ComboBoxEvent from '../../../../types/ComboBoxEvent'; @@ -28,7 +27,7 @@ import './styles.scss'; interface SingleOwnerInfoProps { ownerInfo: SingleOwnerForm, deleteAnOwner: Function, - defaultAgency: MultiOptionsObj, + defaultAgency: string, defaultCode: string, fundingSourcesQuery: UseQueryResult, methodsOfPaymentQuery: UseQueryResult, @@ -50,15 +49,13 @@ const SingleOwnerInfo = ({ const colsClass = ownerInfo.id === DEFAULT_INDEX && !isReview ? 'default-owner-col' : 'other-owners-col'; - const setAgencyAndCode = ( - isDefault: BooleanInputType, - agency: OptionsInputType, + const setClientAndCode = ( + client: StringInputType, locationCode: StringInputType ) => { const clonedState = structuredClone(ownerInfo); - clonedState.ownerAgency = agency; + clonedState.ownerAgency = client; clonedState.ownerCode = locationCode; - clonedState.useDefaultAgencyInfo = isDefault; setState(clonedState, ownerInfo.id); }; @@ -139,24 +136,21 @@ const SingleOwnerInfo = ({ - setAgencyAndCode(isDefault, agency, locationCode) + ) => setClientAndCode(client, locationCode) } readOnly={readOnly && !isReview} - isFormSubmitted={readOnly} /> diff --git a/frontend/src/components/SeedlotRegistrationSteps/OwnershipStep/constants.ts b/frontend/src/components/SeedlotRegistrationSteps/OwnershipStep/constants.ts index af65a9782..4ad038cb3 100644 --- a/frontend/src/components/SeedlotRegistrationSteps/OwnershipStep/constants.ts +++ b/frontend/src/components/SeedlotRegistrationSteps/OwnershipStep/constants.ts @@ -1,5 +1,5 @@ import { EmptyMultiOptObj } from '../../../shared-constants/shared-constants'; -import AgencyTextPropsType from '../../../types/AgencyTextPropsType'; +import ClientAndCodeInputTextType from '../../../types/ClientAndCodeInputTextType'; import { SingleOwnerFormSubmitType } from '../../../types/SeedlotType'; import { SingleOwnerForm } from './definitions'; @@ -7,7 +7,7 @@ export const DEFAULT_INDEX = 0; export const MAX_OWNERS = 100; -export const agencyFieldsProps: AgencyTextPropsType = { +export const agencyFieldsProps: ClientAndCodeInputTextType = { useDefaultCheckbox: { name: 'useDefaultOwner', labelText: 'Use applicant agency as owner agency' @@ -53,14 +53,9 @@ export const createOwnerTemplate = ( ownerData: SingleOwnerFormSubmitType ): SingleOwnerForm => ({ id: newId, - useDefaultAgencyInfo: { - id: 'ownership-use-default-agency', - value: newId === DEFAULT_INDEX, - isInvalid: false - }, ownerAgency: { id: `ownership-agency-${newId}`, - value: EmptyMultiOptObj, + value: '', isInvalid: false }, ownerCode: { diff --git a/frontend/src/components/SeedlotRegistrationSteps/OwnershipStep/definitions.ts b/frontend/src/components/SeedlotRegistrationSteps/OwnershipStep/definitions.ts index e461aa0a5..da07da078 100644 --- a/frontend/src/components/SeedlotRegistrationSteps/OwnershipStep/definitions.ts +++ b/frontend/src/components/SeedlotRegistrationSteps/OwnershipStep/definitions.ts @@ -1,5 +1,5 @@ import React from 'react'; -import { BooleanInputType, OptionsInputType, StringInputType } from '../../../types/FormInputType'; +import { OptionsInputType, StringInputType } from '../../../types/FormInputType'; export type AccordionItemHeadClick = { isOpen: boolean, @@ -11,8 +11,7 @@ export type AccordionCtrlObj = { export type SingleOwnerForm = { id: number, - useDefaultAgencyInfo: BooleanInputType, - ownerAgency: OptionsInputType, + ownerAgency: StringInputType, ownerCode: StringInputType, ownerPortion: StringInputType, reservedPerc: StringInputType, diff --git a/frontend/src/components/SeedlotRegistrationSteps/OwnershipStep/index.tsx b/frontend/src/components/SeedlotRegistrationSteps/OwnershipStep/index.tsx index 0aab327ea..bef78b0ec 100644 --- a/frontend/src/components/SeedlotRegistrationSteps/OwnershipStep/index.tsx +++ b/frontend/src/components/SeedlotRegistrationSteps/OwnershipStep/index.tsx @@ -1,7 +1,8 @@ import React, { - useState, useRef, useContext + useState, useRef, useContext, + useEffect } from 'react'; -import { useQuery } from '@tanstack/react-query'; +import { useQueries, useQuery, useQueryClient } from '@tanstack/react-query'; import { Accordion, AccordionItem, @@ -12,6 +13,7 @@ import { Add } from '@carbon/icons-react'; import ClassAContext from '../../../views/Seedlot/ContextContainerClassA/context'; import getMethodsOfPayment from '../../../api-service/methodsOfPaymentAPI'; import MultiOptionsObj from '../../../types/MultiOptionsObject'; +import { getForestClientByNumberOrAcronym } from '../../../api-service/forestClientsAPI'; import { EmptyMultiOptObj } from '../../../shared-constants/shared-constants'; import { THREE_HALF_HOURS, THREE_HOURS } from '../../../config/TimeUnits'; import { getMultiOptList } from '../../../utils/MultiOptionsUtils'; @@ -36,6 +38,7 @@ import { import { MAX_OWNERS } from './constants'; import './styles.scss'; +import { ForestClientType } from '../../../types/ForestClientTypes/ForestClientType'; type OwnershipStepProps = { isReview?: boolean @@ -48,7 +51,7 @@ const OwnershipStep = ({ isReview }: OwnershipStepProps) => { const { allStepData: { ownershipStep: state }, setStepData, - defaultAgencyObj: defaultAgency, + defaultClientNumber: defaultAgency, defaultCode, isFormSubmitted } = useContext(ClassAContext); @@ -99,19 +102,24 @@ const OwnershipStep = ({ isReview }: OwnershipStepProps) => { const methodsOfPaymentQuery = useQuery({ queryKey: ['methods-of-payment'], queryFn: getMethodsOfPayment, - onSuccess: (dataArr: MultiOptionsObj[]) => { - const defaultMethodArr = dataArr.filter((data: MultiOptionsObj) => data.isDefault); + select: (data) => getMultiOptList(data, true, false, true, ['isDefault']), + staleTime: THREE_HOURS, + cacheTime: THREE_HALF_HOURS + }); + + // Set default method of payment for the first owner. + useEffect(() => { + if (methodsOfPaymentQuery.status === 'success') { + const methods = methodsOfPaymentQuery.data; + const defaultMethodArr = methods.filter((data: MultiOptionsObj) => data.isDefault); const defaultMethod = defaultMethodArr.length === 0 ? EmptyMultiOptObj : defaultMethodArr[0]; - if (!state[0].methodOfPayment.value.code && !state[0].methodOfPayment.hasChanged) { + if (!state[0].methodOfPayment.value?.code && !state[0].methodOfPayment.hasChanged) { const tempOwnershipData = structuredClone(state); tempOwnershipData[0].methodOfPayment.value = defaultMethod; setStepData('ownershipStep', tempOwnershipData); } - }, - select: (data) => getMultiOptList(data, true, false, true, ['isDefault']), - staleTime: THREE_HOURS, - cacheTime: THREE_HALF_HOURS - }); + } + }, [methodsOfPaymentQuery.status, methodsOfPaymentQuery.fetchStatus]); const addAnOwner = () => { // Maximum # of ownership can be set @@ -119,9 +127,22 @@ const OwnershipStep = ({ isReview }: OwnershipStepProps) => { return; } const newOwnerArr = insertOwnerForm(state, methodsOfPaymentQuery.data ?? []); - setStepData('ownershipStep', newOwnerArr); + const portionsInvalid = !arePortionsValid(newOwnerArr); + setPortionsValid(newOwnerArr, portionsInvalid); }; + const qc = useQueryClient(); + + useQueries({ + queries: state.map((singleOwner) => ({ + queryKey: ['forest-clients', singleOwner.ownerAgency.value], + queryFn: () => getForestClientByNumberOrAcronym(singleOwner.ownerAgency.value), + enabled: !!singleOwner.ownerAgency.value + })) + }); + + const getFcQuery = (clientNumber: string): ForestClientType | undefined => qc.getQueryData(['forest-clients', clientNumber]); + return (
@@ -165,9 +186,9 @@ const OwnershipStep = ({ isReview }: OwnershipStepProps) => { title={( diff --git a/frontend/src/components/SeedlotRegistrationSteps/OwnershipStep/utils.ts b/frontend/src/components/SeedlotRegistrationSteps/OwnershipStep/utils.ts index 194d62e91..d94755a98 100644 --- a/frontend/src/components/SeedlotRegistrationSteps/OwnershipStep/utils.ts +++ b/frontend/src/components/SeedlotRegistrationSteps/OwnershipStep/utils.ts @@ -1,3 +1,4 @@ +import { ForestClientType } from '../../../types/ForestClientTypes/ForestClientType'; import MultiOptionsObj from '../../../types/MultiOptionsObject'; import { emptyOwnershipStep } from '../../../views/Seedlot/ContextContainerClassA/constants'; import { inputText, createOwnerTemplate } from './constants'; @@ -106,7 +107,10 @@ export const arePortionsValid = (ownershipArray: Array): boolea return Number(sum.toFixed(2)) === 100; }; -export const getOwnerAgencyTitle = (desc: string): string => desc.substring( - desc.indexOf('-') + 1, - desc.lastIndexOf('-') -).trim(); +export const getOwnerAgencyTitle = (fc: ForestClientType | undefined): string => { + if (!fc) { + return 'Owner agency name'; + } + + return fc.clientName; +}; diff --git a/frontend/src/components/SeedlotRegistrationSteps/ParentTreeStep/CalculateMetrics.tsx b/frontend/src/components/SeedlotRegistrationSteps/ParentTreeStep/CalculateMetrics.tsx index 019b0b4f6..ca2e3a3ef 100644 --- a/frontend/src/components/SeedlotRegistrationSteps/ParentTreeStep/CalculateMetrics.tsx +++ b/frontend/src/components/SeedlotRegistrationSteps/ParentTreeStep/CalculateMetrics.tsx @@ -6,8 +6,7 @@ import { useMutation } from '@tanstack/react-query'; import ClassAContext from '../../../views/Seedlot/ContextContainerClassA/context'; import { PtValsCalcReqPayload } from '../../../types/PtCalcTypes'; import postForCalculation from '../../../api-service/parentTreeAPI'; -import { fillCalculatedInfo, generatePtValCalcPayload } from './utils'; -import { geneticWorthDict } from './constants'; +import { fillCalculatedInfo, generatePtValCalcPayload, getParentTreesForSelectedOrchards } from './utils'; type props = { disableOptions: boolean, @@ -18,6 +17,7 @@ type props = { const CalculateMetrics = ({ disableOptions, setShowInfoSections, isReview }: props) => { const { allStepData: { parentTreeStep: state }, + allStepData: { orchardStep }, genWorthInfoItems, setGenWorthInfoItems, seedlotSpecies, @@ -72,8 +72,11 @@ const CalculateMetrics = ({ disableOptions, setShowInfoSections, isReview }: pro calculateGenWorthQuery.mutate( generatePtValCalcPayload( state, - geneticWorthDict, - seedlotSpecies + seedlotSpecies, + getParentTreesForSelectedOrchards( + orchardStep, + state.allParentTreeData + ) ) ); diff --git a/frontend/src/components/SeedlotRegistrationSteps/ParentTreeStep/InputErrorNotification.tsx b/frontend/src/components/SeedlotRegistrationSteps/ParentTreeStep/InputErrorNotification.tsx index 3c2518ca2..804f805ea 100644 --- a/frontend/src/components/SeedlotRegistrationSteps/ParentTreeStep/InputErrorNotification.tsx +++ b/frontend/src/components/SeedlotRegistrationSteps/ParentTreeStep/InputErrorNotification.tsx @@ -103,7 +103,7 @@ const InputErrorNotification = ( allData.forEach((row) => { rowKeys.forEach((key) => { - if (key !== 'rowId' && key !== 'isMixTab') { + if (row[key] && key !== 'rowId' && key !== 'isMixTab') { if (row[key].isInvalid && !invalidDataFields.includes(key)) { invalidDataFields.push(key); hasErrorInTabs = true; diff --git a/frontend/src/components/SeedlotRegistrationSteps/ParentTreeStep/PopSize.tsx b/frontend/src/components/SeedlotRegistrationSteps/ParentTreeStep/PopSize.tsx index 73b9f8e5f..c250a3e77 100644 --- a/frontend/src/components/SeedlotRegistrationSteps/ParentTreeStep/PopSize.tsx +++ b/frontend/src/components/SeedlotRegistrationSteps/ParentTreeStep/PopSize.tsx @@ -8,7 +8,9 @@ import { formatEmptyStr } from '../../SeedlotReviewSteps/ParentTrees/utils'; import ReadOnlyInput from '../../ReadOnlyInput'; import { getOutsideParentTreeNum, validateEffectivePopSize } from './utils'; -const PopSize = () => { +type PopSizeProps = { orchardPts: string[] } + +const PopSize = ({ orchardPts } : PopSizeProps) => { const { isFetchingData, isCalculatingPt, @@ -65,7 +67,13 @@ const PopSize = () => { id="smp-parents-from-outside" label="Number of SMP parents from outside" value={ - formatEmptyStr(getOutsideParentTreeNum(allStepData.parentTreeStep), true) + formatEmptyStr( + getOutsideParentTreeNum( + allStepData.parentTreeStep, + orchardPts + ), + true + ) } showSkeleton={isFetchingData || isCalculatingPt} /> diff --git a/frontend/src/components/SeedlotRegistrationSteps/ParentTreeStep/TableComponents/index.tsx b/frontend/src/components/SeedlotRegistrationSteps/ParentTreeStep/TableComponents/index.tsx index 9632df033..70f1e0eff 100644 --- a/frontend/src/components/SeedlotRegistrationSteps/ParentTreeStep/TableComponents/index.tsx +++ b/frontend/src/components/SeedlotRegistrationSteps/ParentTreeStep/TableComponents/index.tsx @@ -1,26 +1,31 @@ -import React, { useEffect, useRef } from 'react'; +import React, { useContext, useEffect, useRef } from 'react'; import { OverflowMenuItem, Checkbox, TableBody, TableRow, Row, Column, TableCell, TextInput, ActionableNotification, Pagination, Button, Tooltip } from '@carbon/react'; import { TrashCan } from '@carbon/icons-react'; + +import { ParentTreeStepDataObj } from '../../../../views/Seedlot/ContextContainerClassA/definitions'; +import ClassAContext from '../../../../views/Seedlot/ContextContainerClassA/context'; +import PaginationChangeType from '../../../../types/PaginationChangeType'; +import MultiOptionsObj from '../../../../types/MultiOptionsObject'; +import OrchardDataType from '../../../../types/OrchardDataType'; +import { GeneticWorthDto } from '../../../../types/GeneticWorthType'; +import blurOnEnter from '../../../../utils/KeyboardUtil'; +import { handlePagination } from '../../../../utils/PaginationUtils'; + import { pageText, PageSizesConfig } from '../constants'; import { EditableCellProps, + GeneticWorthInputType, HeaderObj, RowItem, StrTypeRowItem, TabTypes } from '../definitions'; -import { ParentTreeStepDataObj } from '../../../../views/Seedlot/ContextContainerClassA/definitions'; -import { OrchardObj } from '../../OrchardStep/definitions'; -import PaginationChangeType from '../../../../types/PaginationChangeType'; -import blurOnEnter from '../../../../utils/KeyboardUtil'; -import { handlePagination } from '../../../../utils/PaginationUtils'; import { - applyValueToAll, toggleColumn, toggleNotification + applyValueToAll, areOrchardsValid, toggleColumn, toggleNotification } from '../utils'; -import { deleteMixRow, handleInput } from './utils'; - import '../styles.scss'; -import MultiOptionsObj from '../../../../types/MultiOptionsObject'; + +import { deleteMixRow, handleInput } from './utils'; export const renderColOptions = ( headerConfig: Array, @@ -106,23 +111,29 @@ export const renderColOptions = ( /** * Used to render cell that isn't a text input, e.g. delete button */ -const renderDeleteActionBtn = ( +type DeleteActionBtnProps = { rowData: RowItem, applicableGenWorths: string[], - state: ParentTreeStepDataObj, - setStepData: Function, - isFormSubmitted?: boolean, isReviewEdit?: boolean -) => ( -
{ - // Check if it's fetching parent tree data - (!disableOptions && allParentTreeQuery.isFetching) + // Check if it's fetching parent tree and dependencies data + (!disableOptions + && (allParentTreeQuery.fetchStatus === 'fetching' + || orchardQuery.fetchStatus === 'fetching' + || geneticWorthListQuery.fetchStatus === 'fetching' + ) + ) ? ( {header.name} @@ -746,9 +787,8 @@ const ParentTreeStep = ({ isReviewDisplay, isReviewRead }: ParentTreeStepProps) slicedMixRows, headerConfig, applicableGenWorths, - state, - setStepData, - seedlotSpecies, + orchardQuery.data ?? [], + geneticWorthListQuery.data ?? [], isFormSubmitted, (isReviewDisplay && !isReviewRead) ) diff --git a/frontend/src/components/SeedlotRegistrationSteps/ParentTreeStep/styles.scss b/frontend/src/components/SeedlotRegistrationSteps/ParentTreeStep/styles.scss index 0e221a5d9..493f81ac0 100644 --- a/frontend/src/components/SeedlotRegistrationSteps/ParentTreeStep/styles.scss +++ b/frontend/src/components/SeedlotRegistrationSteps/ParentTreeStep/styles.scss @@ -194,8 +194,8 @@ .parent-tree-tab-container { padding: 2.5rem 0 0.25rem 0; background-color: var(--#{vars.$bcgov-prefix}-layer-01); - margin-left: -2rem; - margin-right: -2rem; + margin-left: -2.5rem; + margin-right: -2.5rem; .description-box-container { padding-left: 0; @@ -280,6 +280,14 @@ th { vertical-align: baseline; padding-top: 1rem; + + .#{vars.$bcgov-prefix}--definition-term { + cursor:auto; + } + + .#{vars.$bcgov-prefix}--definition-term:focus { + outline: 0; + } } } @@ -336,7 +344,16 @@ display: none; } } + } + + .td-red-cell { + color: var(--#{vars.$bcgov-prefix}-text-error, #B32001); + } + tr:hover { + .td-red-cell { + color: var(--#{vars.$bcgov-prefix}-text-error, #B32001); + } } .#{vars.$bcgov-prefix}--overflow-menu__wrapper .#{vars.$bcgov-prefix}--btn--disabled { diff --git a/frontend/src/components/SeedlotRegistrationSteps/ParentTreeStep/utils.ts b/frontend/src/components/SeedlotRegistrationSteps/ParentTreeStep/utils.ts index 4452262af..d2f1c2a7e 100644 --- a/frontend/src/components/SeedlotRegistrationSteps/ParentTreeStep/utils.ts +++ b/frontend/src/components/SeedlotRegistrationSteps/ParentTreeStep/utils.ts @@ -6,11 +6,12 @@ import { isFloatWithinRange } from '../../../utils/NumberUtils'; import { sliceTableRowData } from '../../../utils/PaginationUtils'; import { recordKeys } from '../../../utils/RecordUtils'; import { ParentTreeStepDataObj } from '../../../views/Seedlot/ContextContainerClassA/definitions'; -import { ParentTreeGeneticQualityType } from '../../../types/ParentTreeGeneticQualityType'; import MultiOptionsObj from '../../../types/MultiOptionsObject'; import { StringInputType } from '../../../types/FormInputType'; import { PtValsCalcReqPayload, CalcPayloadResType, OrchardParentTreeValsType } from '../../../types/PtCalcTypes'; import { GeoInfoValType } from '../../../views/Seedlot/SeedlotReview/definitions'; +import { ParentTreeByVegCodeResType } from '../../../types/ParentTreeTypes'; +import { GeneticWorthDto } from '../../../types/GeneticWorthType'; import { getConeCountErrMsg, getNonOrchardContamErrMsg, getPollenCountErrMsg, @@ -19,16 +20,18 @@ import { isPtNumberInvalid, isSmpSuccInvalid, isVolumeInvalid, populateRowData, getPTNumberErrMsg } from './TableComponents/utils'; -import { OrchardObj } from '../OrchardStep/definitions'; +import { OrchardForm } from '../OrchardStep/definitions'; import { RowItem, InfoSectionConfigType, RowDataDictType, HeaderObj, TabTypes, CompUploadResponse, GeneticWorthDictType, - MixUploadResponse, HeaderObjId, StrTypeRowItem, MeanGeomInfoSectionConfigType + MixUploadResponse, HeaderObjId, StrTypeRowItem, MeanGeomInfoSectionConfigType, + GeneticWorthInputType } from './definitions'; import { DEFAULT_MIX_PAGE_ROWS, EMPTY_NUMBER_STRING, rowTemplate, - MAX_NE_DECIMAL, INVALID_NE_DECIMAL_MSG, MIN_NE, MAX_NE, INVALID_NE_RANGE_MSG + MAX_NE_DECIMAL, INVALID_NE_DECIMAL_MSG, MIN_NE, MAX_NE, INVALID_NE_RANGE_MSG, + geneticWorthDict } from './constants'; export const getTabString = (selectedIndex: number) => { @@ -44,21 +47,6 @@ export const getTabString = (selectedIndex: number) => { } }; -// Returns a merged array of orchards, duplicated orchards are merged as one -export const processOrchards = (orchards: Array): Array => { - const obj = {}; - - orchards.forEach((orchard) => { - if (orchard.selectedItem) { - Object.assign(obj, { - [orchard.selectedItem.code]: orchard - }); - } - }); - - return Object.values(obj); -}; - export const combineObjectValues = (objs: Array): Array => { let combined: Array = []; @@ -155,21 +143,46 @@ export const calcSummaryItems = ( setSummaryConfig(modifiedSummaryConfig); }; -export const getOutsideParentTreeNum = (state: ParentTreeStepDataObj): string => { +/** + * Returns true if a parent tree's cone and pollen count are larger than 0. + */ +const isPtContributing = (pt: RowItem): boolean => ( + Number(pt.coneCount.value) + Number(pt.pollenCount.value) > 0 +); + +/** + * Calculate the number of SMP parent from outside. + * + * If Volume (Amount of material) is not 0 + * AND no cone and pollen count exist for a parent tree number + * + * Then Add 1 to the # of SMP P.T. from outside. + */ +export const getOutsideParentTreeNum = ( + state: ParentTreeStepDataObj, + orchardPtNums: string[] +): string => { let sum = 0; - const insidePtNums = Object.keys(state.tableRowData); + + // All parent tree numbers in SMP mix where volume is > 0 const ptNumsInMixTab: string[] = []; Object.values(state.mixTabData).forEach((row) => { if ( row.parentTreeNumber?.value.length && !row.parentTreeNumber.isInvalid + && Number(row.volume.value) > 0 ) { ptNumsInMixTab.push(row.parentTreeNumber.value); } }); + const { tableRowData } = state; + ptNumsInMixTab.forEach((ptNum) => { - if (!insidePtNums.includes(ptNum)) { + if ( + !orchardPtNums.includes(ptNum) + || (ptNum in tableRowData && !isPtContributing(tableRowData[ptNum])) + ) { sum += 1; } }); @@ -184,16 +197,16 @@ export const calcMixTabInfoItems = ( applicableGenWorths: string[], weightedGwInfoItems: Record, setWeightedGwInfoItems: Function, - popSizeAndDiversityConfig: InfoSectionConfigType, setPopSizeAndDiversityConfig: React.Dispatch>, - state: ParentTreeStepDataObj + state: ParentTreeStepDataObj, + orchardPts: string[] ) => { if (!disableOptions) { const modifiedSummaryConfig = { ...summaryConfig }; const tableRows = Object.values(state.mixTabData); // Calc number of SMP parents from outside - const numOfOutsidePt = getOutsideParentTreeNum(state); + const numOfOutsidePt = getOutsideParentTreeNum(state, orchardPts); modifiedSummaryConfig.mixTab.infoItems.parentsOutside.value = numOfOutsidePt; setPopSizeAndDiversityConfig((prevPop) => ({ ...prevPop, @@ -233,37 +246,84 @@ export const populateStrInputId = (idPrefix: string, row: RowItem): RowItem => { }; export const processParentTreeData = ( - data: ParentTreeGeneticQualityType[], + // List of Parent Tree under a species + allParentTreeData: ParentTreeByVegCodeResType, + // List of parent tree number under selected orchard(s) + orchardParentTreeList: string[], + // List of genetic worth data + geneticWorthList: GeneticWorthDto[], + seedlotSpecies: MultiOptionsObj, state: ParentTreeStepDataObj, - orchardIds: (string | undefined)[], + primarySpu: number, currentPage: number, currPageSize: number, setSlicedRows: Function, setStepData: Function ) => { const modifiedState = { ...state }; - const allParentTreeData = {}; let tableRowData: RowDataDictType = structuredClone(state.tableRowData); - data.forEach((parentTree) => { - Object.assign(allParentTreeData, { [parentTree.parentTreeNumber]: parentTree }); - if ( - !Object.prototype.hasOwnProperty.call(tableRowData, parentTree.parentTreeNumber) - && orchardIds.includes(parentTree.orchardId) - ) { + const speciesKey = Object.keys(geneticWorthDict).includes(seedlotSpecies.code) + ? seedlotSpecies.code.toUpperCase() + : 'UNKNOWN'; + + const applicableGenWorths = geneticWorthDict[speciesKey as keyof GeneticWorthDictType]; + + orchardParentTreeList.forEach((orchardPtNum) => { + if (!Object.prototype.hasOwnProperty.call(tableRowData, orchardPtNum)) { const newRowData: RowItem = structuredClone(rowTemplate); - newRowData.parentTreeNumber.value = parentTree.parentTreeNumber; - // Assign genetic worth values - parentTree.parentTreeGeneticQualities.forEach((singleGenWorthObj) => { - const genWorthName = singleGenWorthObj.geneticWorthCode - .toLowerCase() as keyof StrTypeRowItem; - - if (Object.prototype.hasOwnProperty.call(newRowData, genWorthName)) { - newRowData[genWorthName].value = String(singleGenWorthObj.geneticQualityValue); - } - }); + + const parentTree = allParentTreeData[orchardPtNum]; + + newRowData.parentTreeNumber.value = orchardPtNum; + + const genWorthBySpu = parentTree.geneticQualitiesBySpu; + + const validSpuIds = Object.keys(genWorthBySpu).map((key) => parseInt(key, 10)); + + // If parent tree has gen worth data under the primary orchard's SPU then use them + // Else use default from the gen worth list + if (validSpuIds.includes(primarySpu)) { + const parentTreeGenWorthVals = genWorthBySpu[primarySpu]; + applicableGenWorths.forEach((gwCode) => { + const loweredGwCode = gwCode.toLowerCase() as keyof RowItem; + const matchedGwObj = parentTreeGenWorthVals + .find((gwObj) => gwObj.geneticWorthCode.toLowerCase() === loweredGwCode); + + if (matchedGwObj) { + (newRowData[loweredGwCode] as GeneticWorthInputType) + .value = String(matchedGwObj.geneticQualityValue); + } else { + // Assign Default GW value + const foundGwDto = geneticWorthList + .find((gwDto) => gwDto.code.toLowerCase() === loweredGwCode); + + const defaultBv = foundGwDto ? foundGwDto.defaultBv.toFixed(1) : '0.0'; + if (foundGwDto) { + (newRowData[loweredGwCode] as GeneticWorthInputType) + .value = defaultBv; + (newRowData[loweredGwCode] as GeneticWorthInputType) + .isEstimated = true; + } + } + }); + } else { + applicableGenWorths.forEach((gwCode) => { + const loweredGwCode = gwCode.toLowerCase() as keyof RowItem; + const foundGwDto = geneticWorthList + .find((gwDto) => gwDto.code.toLowerCase() === loweredGwCode); + const defaultBv = foundGwDto ? foundGwDto.defaultBv.toFixed(1) : '0.0'; + if (foundGwDto) { + (newRowData[loweredGwCode] as GeneticWorthInputType) + .value = defaultBv; + (newRowData[loweredGwCode] as GeneticWorthInputType) + .isEstimated = true; + } + }); + } + tableRowData = Object.assign(tableRowData, { - [parentTree.parentTreeNumber]: populateStrInputId(parentTree.parentTreeNumber, newRowData) + [orchardPtNum]: populateStrInputId(orchardPtNum, newRowData) }); } }); @@ -280,32 +340,34 @@ export const processParentTreeData = ( setSlicedRows ); - // Only set data if tableRowData is not empty, otherwise a inf loop will occur. setStepData('parentTreeStep', modifiedState); }; /** - * Determines if selected orchards contains a least one parent tree. + * Get a list of parent tree numbers that are under the selected orchard. */ -export const hasParentTreesForSelectedOrchards = ( - orchardIds: (string | undefined)[], - data: ParentTreeGeneticQualityType[] -): boolean => { - const proceed = true; - const stop = false; - let hasParentTrees = false; - - // Loop through every parent tree data obj - // and stop as soon as a tree is found with the matching orchard id. - data.every((parentTree) => { - if (orchardIds.includes(parentTree.orchardId)) { - hasParentTrees = true; - return stop; - } - return proceed; +export const getParentTreesForSelectedOrchards = ( + orchardStepData: OrchardForm, + data: ParentTreeByVegCodeResType +): string[] => { + const selectedOrchardIds: string[] = []; + + if (orchardStepData.orchards.primaryOrchard.value.code) { + selectedOrchardIds.push(orchardStepData.orchards.primaryOrchard.value.code); + } + if ( + orchardStepData.orchards.secondaryOrchard.enabled + && orchardStepData.orchards.secondaryOrchard.value.code + ) { + selectedOrchardIds.push(orchardStepData.orchards.secondaryOrchard.value.code); + } + + const filteredKeys = Object.keys(data).filter((key) => { + const { orchardIds } = data[key]; + return orchardIds.some((orchardId) => selectedOrchardIds.includes(orchardId)); }); - return hasParentTrees; + return filteredKeys; }; export const getMixRowTemplate = (): RowItem => { @@ -472,7 +534,6 @@ export const toggleColumn = ( * displayed as an option */ export const configHeaderOpt = ( - geneticWorthDict: GeneticWorthDictType, seedlotSpecies: MultiOptionsObj, headerConfig: HeaderObj[], genWorthInfoItems: Record, @@ -483,57 +544,58 @@ export const configHeaderOpt = ( setApplicableGenWorths: Function, isReview: boolean ) => { - const speciesHasGenWorth = Object.keys(geneticWorthDict); - if (speciesHasGenWorth.includes(seedlotSpecies.code)) { - const availableOptions = geneticWorthDict[seedlotSpecies.code]; - setApplicableGenWorths(availableOptions); - const clonedHeaders = structuredClone(headerConfig); - let clonedGwItems = structuredClone(genWorthInfoItems); - let clonedWeightedGwItems = structuredClone(weightedGwInfoItems); - availableOptions.forEach((opt: string) => { - const optionIndex = headerConfig.findIndex((header) => header.id === opt); - // Enable option in the column customization - clonedHeaders[optionIndex].isAnOption = true; - - // When on review mode, display all columns - if (isReview) { - clonedHeaders[optionIndex].enabled = true; - } + const speciesKey = Object.keys(geneticWorthDict).includes(seedlotSpecies.code) + ? seedlotSpecies.code.toUpperCase() + : 'UNKNOWN'; + + const availableOptions = geneticWorthDict[speciesKey as keyof GeneticWorthDictType]; + setApplicableGenWorths(availableOptions); + const clonedHeaders = structuredClone(headerConfig); + let clonedGwItems = structuredClone(genWorthInfoItems); + let clonedWeightedGwItems = structuredClone(weightedGwInfoItems); + availableOptions.forEach((opt: string) => { + const optionIndex = headerConfig.findIndex((header) => header.id === opt); + // Enable option in the column customization + clonedHeaders[optionIndex].isAnOption = true; + + // When on review mode, display all columns + if (isReview) { + clonedHeaders[optionIndex].enabled = true; + } - // Enable weighted option in mix tab - const weightedIndex = headerConfig.findIndex((header) => header.id === `w_${opt}`); - if (weightedIndex > -1) { - clonedHeaders[weightedIndex].isAnOption = true; - } + // Enable weighted option in mix tab + const weightedIndex = headerConfig.findIndex((header) => header.id === `w_${opt}`); + if (weightedIndex > -1) { + clonedHeaders[weightedIndex].isAnOption = true; + } - // Add GW input to the corresponding info section - const gwAbbrevName = String(clonedHeaders[optionIndex].id).toUpperCase(); - clonedGwItems = Object.assign(clonedGwItems, { - [clonedHeaders[optionIndex].id]: [ - { - name: `Genetic worth ${gwAbbrevName}`, - value: EMPTY_NUMBER_STRING - }, - { - name: `Tested parent trees % (${gwAbbrevName})`, - value: EMPTY_NUMBER_STRING - } - ] - }); - // Add weighted GW info to mix tab info section - clonedWeightedGwItems = Object.assign(clonedWeightedGwItems, { - [clonedHeaders[optionIndex].id]: { - name: `SMP Breeding Value - ${gwAbbrevName}`, + // Add GW input to the corresponding info section + const gwAbbrevName = String(clonedHeaders[optionIndex].id).toUpperCase(); + clonedGwItems = Object.assign(clonedGwItems, { + [clonedHeaders[optionIndex].id]: [ + { + name: `Genetic worth ${gwAbbrevName}`, + value: EMPTY_NUMBER_STRING + }, + { + name: `Tested parent trees % (${gwAbbrevName})`, value: EMPTY_NUMBER_STRING } - }); + ] }); - setHeaderConfig(clonedHeaders); - if (Object.keys(genWorthInfoItems).length === 0) { - setGenWorthInfoItems(clonedGwItems); - } - setWeightedGwInfoItems(clonedWeightedGwItems); + // Add weighted GW info to mix tab info section + clonedWeightedGwItems = Object.assign(clonedWeightedGwItems, { + [clonedHeaders[optionIndex].id]: { + name: `SMP Breeding Value - ${gwAbbrevName}`, + value: EMPTY_NUMBER_STRING + } + }); + }); + setHeaderConfig(clonedHeaders); + if (Object.keys(genWorthInfoItems).length === 0) { + setGenWorthInfoItems(clonedGwItems); } + setWeightedGwInfoItems(clonedWeightedGwItems); }; export const fillCalculatedInfo = ( @@ -655,27 +717,27 @@ export const fillCalculatedInfo = ( }; const findParentTreeId = (state: ParentTreeStepDataObj, ptNumber: string): number => { - const found = Object.values(state.allParentTreeData) - .find((pt) => pt.parentTreeNumber === ptNumber); + const foundPtNum = Object.keys(state.allParentTreeData).find((ptNum) => ptNum === ptNumber); - if (!found) { + if (!foundPtNum) { throw Error(`Cannot find parent tree id with parent tree number: ${ptNumber}`); } - return found.parentTreeId; + return state.allParentTreeData[foundPtNum].parentTreeId; }; export const generatePtValCalcPayload = ( state: ParentTreeStepDataObj, - geneticWorthDict: GeneticWorthDictType, - seedlotSpecies: MultiOptionsObj + seedlotSpecies: MultiOptionsObj, + orchardPts: string[] ): PtValsCalcReqPayload => { const { tableRowData, mixTabData } = state; const payload: PtValsCalcReqPayload = { orchardPtVals: [], - smpMixIdAndProps: [] + smpMixIdAndProps: [], + smpParentsOutside: '0' }; const rows = Object.values(tableRowData); - const genWorthTypes = geneticWorthDict[seedlotSpecies.code]; + const genWorthTypes = geneticWorthDict[seedlotSpecies.code as keyof GeneticWorthDictType]; rows.forEach((row) => { const newPayloadItem: OrchardParentTreeValsType = { parentTreeId: findParentTreeId(state, row.parentTreeNumber.value), @@ -705,6 +767,9 @@ export const generatePtValCalcPayload = ( } }); + // SMP Parents from Outside + payload.smpParentsOutside = getOutsideParentTreeNum(state, orchardPts); + return payload; }; @@ -763,7 +828,9 @@ export const fillMixTable = ( data: MixUploadResponse[], applicableGenWorths: string[], state: ParentTreeStepDataObj, - setStepData: Function + setStepData: Function, + geneticWorthList: GeneticWorthDto[], + primarySpu: number ) => { let newRows = {}; const clonedState = structuredClone(state); @@ -782,7 +849,14 @@ export const fillMixTable = ( newRow.parentTreeNumber.errMsg = isPtInvalid ? getPTNumberErrMsg(ptNumber) : ''; // Populate data such as gw value if (!isPtInvalid) { - newRow = populateRowData(newRow, ptNumber, state); + newRow = populateRowData( + newRow, + ptNumber, + state, + geneticWorthList, + applicableGenWorths, + primarySpu + ); } Object.assign(newRows, { [newRow.rowId]: populateStrInputId(newRow.rowId, newRow) }); @@ -820,3 +894,28 @@ export const validateEffectivePopSize = (inputObj: StringInputType): StringInput return validatedObj; }; + +/** + * Check if the secondary orchard is enabled but nothing is selected. + */ +export const isMissingSecondaryOrchard = (orchardStepData: OrchardForm): boolean => { + const { orchards } = orchardStepData; + return orchards.secondaryOrchard.enabled && !orchards.secondaryOrchard.value.code; +}; + +/** + * Check if orchards selections are valid. + */ +export const areOrchardsValid = (orchardStepData: OrchardForm): boolean => { + let isValid = true; + const { orchards } = orchardStepData; + + if (!orchards.primaryOrchard.value.code) { + isValid = false; + } + if (isMissingSecondaryOrchard(orchardStepData)) { + isValid = false; + } + + return isValid; +}; diff --git a/frontend/src/components/SeedlotRegistrationSteps/SubmitModal/constants.ts b/frontend/src/components/SeedlotRegistrationSteps/SubmitModal/constants.ts index 9f238ba3a..f56862658 100644 --- a/frontend/src/components/SeedlotRegistrationSteps/SubmitModal/constants.ts +++ b/frontend/src/components/SeedlotRegistrationSteps/SubmitModal/constants.ts @@ -9,8 +9,8 @@ const inputText = { checkboxLabelText: 'I hereby declare that the information provided in this application is true and correct, and that I am the owner of the seedlot or have been authorized by the owner(s) of the seedlot to submit this application.', notification: { title: 'Review the form:', - subtitle: 'Please, be sure to review the content and check if everything is correct with your seedlot registration.', - link: 'Go to first step and review the form' + subtitle: 'Please review this form to ensure the information is correct for this seedlot registration.', + link: 'Click here to go back to the first step.' } } }; diff --git a/frontend/src/components/SeedlotRegistrationSteps/SubmitModal/index.tsx b/frontend/src/components/SeedlotRegistrationSteps/SubmitModal/index.tsx index 5c7531ef0..90e8e850d 100644 --- a/frontend/src/components/SeedlotRegistrationSteps/SubmitModal/index.tsx +++ b/frontend/src/components/SeedlotRegistrationSteps/SubmitModal/index.tsx @@ -3,7 +3,8 @@ import { Checkbox, Button, Modal, - ToastNotification + ActionableNotification, + Link } from '@carbon/react'; import * as Icons from '@carbon/icons-react'; @@ -18,6 +19,7 @@ type SubmitModalProps = { renderIconName?: string; disableBtn: boolean; submitFn: Function; + setStepFn: Function; } const SubmitModal = ( @@ -25,7 +27,8 @@ const SubmitModal = ( btnText, renderIconName, disableBtn, - submitFn + submitFn, + setStepFn }: SubmitModalProps ) => { const [declareTrue, setDeclareTrue] = useState(false); @@ -69,12 +72,28 @@ const SubmitModal = ( setDeclareTrue(e.target.checked); }} /> - + {inputText.modal.notification.subtitle} +
+
+ {/* eslint-disable-next-line jsx-a11y/anchor-is-valid */} + { + setStepFn(0); + }} + > + {inputText.modal.notification.link} + + + )} /> )} diff --git a/frontend/src/components/SeedlotRegistrationSteps/SubmitModal/styles.scss b/frontend/src/components/SeedlotRegistrationSteps/SubmitModal/styles.scss index 62c40861f..3ac94553b 100644 --- a/frontend/src/components/SeedlotRegistrationSteps/SubmitModal/styles.scss +++ b/frontend/src/components/SeedlotRegistrationSteps/SubmitModal/styles.scss @@ -36,4 +36,8 @@ .#{vars.$bcgov-prefix}--modal-footer .#{vars.$bcgov-prefix}--btn { height: 0; } + + .submit-notification { + max-inline-size: none; + } } diff --git a/frontend/src/components/SeedlotReviewSteps/ApplicantAndSeedlot/Read/index.tsx b/frontend/src/components/SeedlotReviewSteps/ApplicantAndSeedlot/Read/index.tsx index c59ce97ce..dc611f5a0 100644 --- a/frontend/src/components/SeedlotReviewSteps/ApplicantAndSeedlot/Read/index.tsx +++ b/frontend/src/components/SeedlotReviewSteps/ApplicantAndSeedlot/Read/index.tsx @@ -1,16 +1,29 @@ import React, { useContext } from 'react'; import { Column, Row, FlexGrid } from '@carbon/react'; +import { useQuery } from '@tanstack/react-query'; import Divider from '../../../Divider'; import ReadOnlyInput from '../../../ReadOnlyInput'; import ClassAContext from '../../../../views/Seedlot/ContextContainerClassA/context'; import EmailDisplay from '../../../EmailDisplay'; +import { THREE_HALF_HOURS, THREE_HOURS } from '../../../../config/TimeUnits'; +import { getForestClientByNumberOrAcronym } from '../../../../api-service/forestClientsAPI'; +import { getForestClientLabel } from '../../../../utils/ForestClientUtils'; const ApplicantAndSeedlotRead = () => { const { - defaultAgencyObj, defaultCode, seedlotData, seedlotSpecies, isFetchingData + defaultClientNumber, defaultCode, seedlotData, seedlotSpecies, isFetchingData } = useContext(ClassAContext); + const forestClientQuery = useQuery({ + queryKey: ['forest-clients', defaultClientNumber], + queryFn: () => getForestClientByNumberOrAcronym(defaultClientNumber), + enabled: !!defaultClientNumber, + staleTime: THREE_HOURS, + cacheTime: THREE_HALF_HOURS, + select: (fc) => getForestClientLabel(fc) + }); + return ( @@ -23,8 +36,8 @@ const ApplicantAndSeedlotRead = () => {
diff --git a/frontend/src/components/SeedlotReviewSteps/AuditInfo/index.tsx b/frontend/src/components/SeedlotReviewSteps/AuditInfo/index.tsx index bc2d50db8..7b9cb78e6 100644 --- a/frontend/src/components/SeedlotReviewSteps/AuditInfo/index.tsx +++ b/frontend/src/components/SeedlotReviewSteps/AuditInfo/index.tsx @@ -1,10 +1,9 @@ import React, { useContext } from 'react'; import { Column, Row, FlexGrid } from '@carbon/react'; -import { DateTime as luxon } from 'luxon'; import ReadOnlyInput from '../../ReadOnlyInput'; import ClassAContext from '../../../views/Seedlot/ContextContainerClassA/context'; -import { MONTH_DAY_YEAR } from '../../../config/DateFormat'; +import { utcToIsoSlashStyle } from '../../../utils/DateUtils'; const AuditInfo = () => { const { @@ -31,11 +30,7 @@ const AuditInfo = () => { @@ -44,7 +39,7 @@ const AuditInfo = () => { @@ -52,12 +47,8 @@ const AuditInfo = () => { @@ -75,11 +66,7 @@ const AuditInfo = () => { @@ -97,11 +84,7 @@ const AuditInfo = () => {
diff --git a/frontend/src/components/SeedlotReviewSteps/Collection/Read/index.tsx b/frontend/src/components/SeedlotReviewSteps/Collection/Read/index.tsx index 9dff791db..27c307d10 100644 --- a/frontend/src/components/SeedlotReviewSteps/Collection/Read/index.tsx +++ b/frontend/src/components/SeedlotReviewSteps/Collection/Read/index.tsx @@ -1,16 +1,15 @@ import React, { useContext } from 'react'; import { Column, Row, FlexGrid } from '@carbon/react'; -import { DateTime as luxon } from 'luxon'; import { useQuery } from '@tanstack/react-query'; import Divider from '../../../Divider'; import ReadOnlyInput from '../../../ReadOnlyInput'; import ClassAContext from '../../../../views/Seedlot/ContextContainerClassA/context'; -import { MONTH_DAY_YEAR } from '../../../../config/DateFormat'; import { getForestClientByNumberOrAcronym } from '../../../../api-service/forestClientsAPI'; import { getForestClientLabel } from '../../../../utils/ForestClientUtils'; import getConeCollectionMethod from '../../../../api-service/coneCollectionMethodAPI'; import { THREE_HALF_HOURS, THREE_HOURS } from '../../../../config/TimeUnits'; + import { formatCollectionMethods } from '../utils'; import GeoInfo from '../GeoInfo'; @@ -25,7 +24,8 @@ const CollectionReviewRead = () => { { queryKey: ['forest-clients', clientNumber], queryFn: () => getForestClientByNumberOrAcronym(clientNumber!), - enabled: !!clientNumber + enabled: !!clientNumber, + select: (fc) => getForestClientLabel(fc) } ); @@ -48,8 +48,8 @@ const CollectionReviewRead = () => {
@@ -74,9 +74,7 @@ const CollectionReviewRead = () => { @@ -84,9 +82,7 @@ const CollectionReviewRead = () => { diff --git a/frontend/src/components/SeedlotReviewSteps/Collection/utils.ts b/frontend/src/components/SeedlotReviewSteps/Collection/utils.ts index d15ddb90f..ea2e0e77b 100644 --- a/frontend/src/components/SeedlotReviewSteps/Collection/utils.ts +++ b/frontend/src/components/SeedlotReviewSteps/Collection/utils.ts @@ -8,21 +8,21 @@ export const formatCollectionMethods = ( codes: string[], methods: CodeDescResType[] | undefined ): string => { - let formated = ''; + let formatted = ''; if (!methods) { - return formated; + return formatted; } codes.forEach((code) => { const found = methods.find((method) => method.code === code); if (found) { - formated += `${found.description}, `; + formatted += `${found.description}, `; } }); - return formated.substring(0, formated.length - 2); + return formatted.substring(0, formatted.length - 2); }; /** diff --git a/frontend/src/components/SeedlotReviewSteps/ExtractionStorage/Edit/index.tsx b/frontend/src/components/SeedlotReviewSteps/ExtractionStorage/Edit/index.tsx index eda38e9b3..d66a5847f 100644 --- a/frontend/src/components/SeedlotReviewSteps/ExtractionStorage/Edit/index.tsx +++ b/frontend/src/components/SeedlotReviewSteps/ExtractionStorage/Edit/index.tsx @@ -2,13 +2,10 @@ import React from 'react'; import { FlexGrid } from '@carbon/react'; import ExtractionAndStorage from '../../../SeedlotRegistrationSteps/ExtractionAndStorageStep'; -import { tscAgencyObj, tscLocationCode } from '../../../../views/Seedlot/ContextContainerClassA/constants'; const ExtractionStorageReviewEdit = () => ( diff --git a/frontend/src/components/SeedlotReviewSteps/ExtractionStorage/Read/index.tsx b/frontend/src/components/SeedlotReviewSteps/ExtractionStorage/Read/index.tsx index 030d13eb8..d97571770 100644 --- a/frontend/src/components/SeedlotReviewSteps/ExtractionStorage/Read/index.tsx +++ b/frontend/src/components/SeedlotReviewSteps/ExtractionStorage/Read/index.tsx @@ -1,17 +1,37 @@ import React, { useContext } from 'react'; import { Column, Row, FlexGrid } from '@carbon/react'; -import { DateTime as luxon } from 'luxon'; +import { useQuery } from '@tanstack/react-query'; import Divider from '../../../Divider'; import ReadOnlyInput from '../../../ReadOnlyInput'; import ClassAContext from '../../../../views/Seedlot/ContextContainerClassA/context'; -import { MONTH_DAY_YEAR } from '../../../../config/DateFormat'; +import { getForestClientByNumberOrAcronym } from '../../../../api-service/forestClientsAPI'; +import { THREE_HALF_HOURS, THREE_HOURS } from '../../../../config/TimeUnits'; +import { getForestClientLabel } from '../../../../utils/ForestClientUtils'; const ExtractionStorageReviewRead = () => { const { isFetchingData, allStepData: { extractionStorageStep: state } } = useContext(ClassAContext); + const extractClientQuery = useQuery({ + queryKey: ['forest-clients', state.extraction.agency.value], + queryFn: () => getForestClientByNumberOrAcronym(state.extraction.agency.value), + enabled: !!state.extraction.agency.value, + staleTime: THREE_HOURS, + cacheTime: THREE_HALF_HOURS, + select: (client) => getForestClientLabel(client) + }); + + const storageClientQuery = useQuery({ + queryKey: ['forest-clients', state.seedStorage.agency.value], + queryFn: () => getForestClientByNumberOrAcronym(state.seedStorage.agency.value), + enabled: !!state.seedStorage.agency.value, + staleTime: THREE_HOURS, + cacheTime: THREE_HALF_HOURS, + select: (client) => getForestClientLabel(client) + }); + return ( @@ -24,8 +44,8 @@ const ExtractionStorageReviewRead = () => { @@ -45,9 +65,7 @@ const ExtractionStorageReviewRead = () => { @@ -55,9 +73,7 @@ const ExtractionStorageReviewRead = () => { @@ -78,8 +94,8 @@ const ExtractionStorageReviewRead = () => { @@ -99,9 +115,7 @@ const ExtractionStorageReviewRead = () => { @@ -109,9 +123,7 @@ const ExtractionStorageReviewRead = () => { diff --git a/frontend/src/components/SeedlotReviewSteps/Interim/Read/index.tsx b/frontend/src/components/SeedlotReviewSteps/Interim/Read/index.tsx index 7c6296acf..242d9d9e6 100644 --- a/frontend/src/components/SeedlotReviewSteps/Interim/Read/index.tsx +++ b/frontend/src/components/SeedlotReviewSteps/Interim/Read/index.tsx @@ -1,6 +1,5 @@ import React, { useContext } from 'react'; import { Column, Row, FlexGrid } from '@carbon/react'; -import { DateTime as luxon } from 'luxon'; import { useQuery } from '@tanstack/react-query'; import ReadOnlyInput from '../../../ReadOnlyInput'; @@ -8,7 +7,6 @@ import ClassAContext from '../../../../views/Seedlot/ContextContainerClassA/cont import { getMultiOptList } from '../../../../utils/MultiOptionsUtils'; import { getForestClientByNumberOrAcronym } from '../../../../api-service/forestClientsAPI'; import getFacilityTypes from '../../../../api-service/facilityTypesAPI'; -import { MONTH_DAY_YEAR } from '../../../../config/DateFormat'; import { THREE_HALF_HOURS, THREE_HOURS } from '../../../../config/TimeUnits'; import { getForestClientLabel } from '../../../../utils/ForestClientUtils'; @@ -23,7 +21,8 @@ const InterimReviewRead = () => { { queryKey: ['forest-clients', clientNumber], queryFn: () => getForestClientByNumberOrAcronym(clientNumber!), - enabled: !!clientNumber + enabled: !!clientNumber, + select: (fc) => getForestClientLabel(fc) } ); @@ -35,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; } @@ -54,9 +53,9 @@ const InterimReviewRead = () => { @@ -73,7 +72,7 @@ const InterimReviewRead = () => { @@ -81,7 +80,7 @@ const InterimReviewRead = () => { diff --git a/frontend/src/components/SeedlotReviewSteps/Orchard/Read/index.tsx b/frontend/src/components/SeedlotReviewSteps/Orchard/Read/index.tsx index e672aa35f..8c078d5bc 100644 --- a/frontend/src/components/SeedlotReviewSteps/Orchard/Read/index.tsx +++ b/frontend/src/components/SeedlotReviewSteps/Orchard/Read/index.tsx @@ -4,7 +4,6 @@ import { Column, Row, FlexGrid } from '@carbon/react'; import Divider from '../../../Divider'; import ReadOnlyInput from '../../../ReadOnlyInput'; -import { OrchardObj } from '../../../SeedlotRegistrationSteps/OrchardStep/definitions'; import ClassAContext from '../../../../views/Seedlot/ContextContainerClassA/context'; const OrchardReviewRead = () => { @@ -19,29 +18,30 @@ const OrchardReviewRead = () => { Orchard information + + + + + { - state.orchards.map((curOrchard: OrchardObj, index: number) => ( - - - - - - - - - )) + state.orchards.secondaryOrchard.value.label + ? ( + + + + + + ) + : null } - @@ -64,7 +64,7 @@ const OrchardReviewRead = () => { @@ -74,7 +74,7 @@ const OrchardReviewRead = () => { diff --git a/frontend/src/components/SeedlotReviewSteps/Ownership/Read/index.tsx b/frontend/src/components/SeedlotReviewSteps/Ownership/Read/index.tsx index 42ff367d1..4045d32ee 100644 --- a/frontend/src/components/SeedlotReviewSteps/Ownership/Read/index.tsx +++ b/frontend/src/components/SeedlotReviewSteps/Ownership/Read/index.tsx @@ -1,17 +1,33 @@ import React, { useContext } from 'react'; import { Column, Row, FlexGrid } from '@carbon/react'; +import { useQueries, useQueryClient } from '@tanstack/react-query'; import Divider from '../../../Divider'; import ReadOnlyInput from '../../../ReadOnlyInput'; import ClassAContext from '../../../../views/Seedlot/ContextContainerClassA/context'; import { SingleOwnerForm } from '../../../SeedlotRegistrationSteps/OwnershipStep/definitions'; import { getOwnerAgencyTitle } from '../../../SeedlotRegistrationSteps/OwnershipStep/utils'; +import { getForestClientByNumberOrAcronym } from '../../../../api-service/forestClientsAPI'; +import { ForestClientType } from '../../../../types/ForestClientTypes/ForestClientType'; +import { getForestClientLabel } from '../../../../utils/ForestClientUtils'; const OwnershipReviewRead = () => { const { isFetchingData, allStepData: { ownershipStep: state } } = useContext(ClassAContext); + const qc = useQueryClient(); + + useQueries({ + queries: state.map((curOwner) => ({ + queryKey: ['forest-clients', curOwner.ownerAgency.value], + queryFn: () => getForestClientByNumberOrAcronym(curOwner.ownerAgency.value), + enabled: !!curOwner.ownerAgency.value + })) + }); + + const getFcQuery = (clientNumber: string): ForestClientType | undefined => qc.getQueryData(['forest-clients', clientNumber]); + return ( { @@ -20,7 +36,7 @@ const OwnershipReviewRead = () => { { - getOwnerAgencyTitle(curOwner.ownerAgency.value.description) + getOwnerAgencyTitle(getFcQuery(curOwner.ownerAgency.value)) } @@ -29,7 +45,11 @@ const OwnershipReviewRead = () => { diff --git a/frontend/src/components/SeedlotReviewSteps/ParentTrees/styles.scss b/frontend/src/components/SeedlotReviewSteps/ParentTrees/styles.scss index d1a3a03d7..16979bae2 100644 --- a/frontend/src/components/SeedlotReviewSteps/ParentTrees/styles.scss +++ b/frontend/src/components/SeedlotReviewSteps/ParentTrees/styles.scss @@ -12,7 +12,7 @@ } .parent-tree-tab-container { - margin-left: -3.5rem; - margin-right: -3.5rem; + margin-left: -2.5rem; + margin-right: -2.5rem; } } diff --git a/frontend/src/components/SeedlotTable/Table.tsx b/frontend/src/components/SeedlotTable/Table.tsx index 809a2182c..3cb23c123 100644 --- a/frontend/src/components/SeedlotTable/Table.tsx +++ b/frontend/src/components/SeedlotTable/Table.tsx @@ -110,7 +110,7 @@ const SeedlotDataTable = ( ) => handleSearch(a.target.value) } diff --git a/frontend/src/components/SeedlotTable/index.tsx b/frontend/src/components/SeedlotTable/index.tsx index ecda9047a..2bfe4c0be 100644 --- a/frontend/src/components/SeedlotTable/index.tsx +++ b/frontend/src/components/SeedlotTable/index.tsx @@ -41,7 +41,7 @@ const SeedlotTable = ( const vegCodeQuery = useQuery({ queryKey: ['vegetation-codes'], - queryFn: () => getVegCodes(), + queryFn: getVegCodes, select: (data) => getMultiOptList(data, true, true), staleTime: THREE_HOURS, // will not refetch for 3 hours cacheTime: THREE_HALF_HOURS // data is cached 3.5 hours then deleted @@ -102,7 +102,7 @@ const SeedlotTable = ( * Show skeleton while fetching. */ if (getAllSeedlotQueryByUser.isFetching || vegCodeQuery.isFetching) { - return ; + return ; } /** diff --git a/frontend/src/config/DateFormat.ts b/frontend/src/config/DateFormat.ts index 339010865..fa0a41b49 100644 --- a/frontend/src/config/DateFormat.ts +++ b/frontend/src/config/DateFormat.ts @@ -9,3 +9,13 @@ export const MONTH_DAY_YEAR = 'LLL dd, yyyy'; * e.g. Oct 26 */ export const MONTH_DAY = 'LLL dd'; + +/** + * e.g. 2024-08-09 + */ +export const ISO_YEAR_MONTH_DAY_DASH = 'yyyy-MM-dd'; + +/** + * e.g. 2024/08/09 + */ +export const ISO_YEAR_MONTH_DAY_SLASH = 'yyyy/MM/dd'; diff --git a/frontend/src/config/FavouriteActivitiyMap.ts b/frontend/src/config/FavouriteActivitiyMap.ts index 382ac7f67..b84d88002 100644 --- a/frontend/src/config/FavouriteActivitiyMap.ts +++ b/frontend/src/config/FavouriteActivitiyMap.ts @@ -7,7 +7,6 @@ const FavouriteActivityMap: Record = { type: 'seedlots', image: 'SoilMoistureField', header: 'Seedlots', - description: 'Register and manage your seedlots', link: ROUTES.SEEDLOTS, highlighted: false }, @@ -16,7 +15,6 @@ const FavouriteActivityMap: Record = { type: 'registerAClass', image: 'TaskAdd', header: 'Create A-class seedlot', - description: 'Register a new A-class seedlot', link: ROUTES.SEEDLOTS_A_CLASS_CREATION, highlighted: false }, @@ -25,7 +23,6 @@ const FavouriteActivityMap: Record = { type: 'mySeedlots', image: 'TableSplit', header: 'My Seedlots', - description: 'Check and manage your own seedlots', link: ROUTES.MY_SEEDLOTS, highlighted: false }, @@ -34,7 +31,6 @@ const FavouriteActivityMap: Record = { type: 'reviewSeedlots', image: 'TableSplit', header: 'Review Seedlots', - description: 'Check all seedlots that are waiting for approval', link: ROUTES.TSC_SEEDLOTS_TABLE, highlighted: false }, @@ -43,7 +39,6 @@ const FavouriteActivityMap: Record = { type: 'default', image: 'Unknown', header: 'Unknown activity: ', - description: 'Please remove this invalid activity', link: '#', highlighted: false } diff --git a/frontend/src/layout/PrivateLayout/styles.scss b/frontend/src/layout/PrivateLayout/styles.scss index 696396627..2226f6169 100644 --- a/frontend/src/layout/PrivateLayout/styles.scss +++ b/frontend/src/layout/PrivateLayout/styles.scss @@ -3,7 +3,6 @@ .main-container { padding-top: 5.625rem; padding-left: 16rem; - padding-bottom: 2.5rem; } .page-content .#{vars.$bcgov-prefix}--grid { diff --git a/frontend/src/styles/custom.scss b/frontend/src/styles/custom.scss index 6d45e1fdb..c7c3c399f 100644 --- a/frontend/src/styles/custom.scss +++ b/frontend/src/styles/custom.scss @@ -382,7 +382,6 @@ label.#{vars.$bcgov-prefix}--label--disabled { color: var(--#{vars.$bcgov-prefix}-text-disabled, #939395); } - .general-data-table-pagination { border-top: none; @@ -413,6 +412,25 @@ label.#{vars.$bcgov-prefix}--label--disabled { color: var(--#{vars.$bcgov-prefix}-button-danger-secondary, colors.$red-70); } +.danger-tertiary-btn:hover, +.danger-tertiary-btn:active { + background-color: var(--#{vars.$bcgov-prefix}-button-danger-secondary, colors.$red-70); +} + +.danger-tertiary-btn:focus { + background-color: var(--#{vars.$bcgov-prefix}-button-danger-secondary, colors.$red-70); + box-shadow: inset 0 0 0 1px var(--#{vars.$bcgov-prefix}-button-focus-color, colors.$red-70), inset 0 0 0 2px var(--#{vars.$bcgov-prefix}-background, #FFFFFF); + border-color: colors.$red-70; +} + .ul-disc { list-style-type: disc; } + +.#{vars.$bcgov-prefix}--breadcrumb .#{vars.$bcgov-prefix}--link { + cursor: pointer; +} + +.#{vars.$bcgov-prefix}--loading-overlay { + z-index: 10000; +} diff --git a/frontend/src/types/AgencyTextPropsType.ts b/frontend/src/types/ClientAndCodeInputTextType.ts similarity index 71% rename from frontend/src/types/AgencyTextPropsType.ts rename to frontend/src/types/ClientAndCodeInputTextType.ts index 637692538..6919463e2 100644 --- a/frontend/src/types/AgencyTextPropsType.ts +++ b/frontend/src/types/ClientAndCodeInputTextType.ts @@ -1,4 +1,4 @@ -type AgencyTextPropsType = { +type ClientAndCodeInputTextType = { useDefaultCheckbox: { name: string, labelText: string @@ -13,4 +13,4 @@ type AgencyTextPropsType = { } } -export default AgencyTextPropsType; +export default ClientAndCodeInputTextType; diff --git a/frontend/src/types/FavActivityTypes.ts b/frontend/src/types/FavActivityTypes.ts index bddc41182..0b84ae10a 100644 --- a/frontend/src/types/FavActivityTypes.ts +++ b/frontend/src/types/FavActivityTypes.ts @@ -3,7 +3,6 @@ export type FavActivityType = { type: string; image: string; header: string; - description: string; link: string; highlighted: boolean; } diff --git a/frontend/src/types/ForestClientTypes/ForestClientType.ts b/frontend/src/types/ForestClientTypes/ForestClientType.ts index 419737645..b75328e39 100644 --- a/frontend/src/types/ForestClientTypes/ForestClientType.ts +++ b/frontend/src/types/ForestClientTypes/ForestClientType.ts @@ -7,3 +7,8 @@ export type ForestClientType = { clientTypeCode: string, acronym: string }; + +export type ClientNumLocCodeType = { + clientNumber: string, + locationCode: string +}; diff --git a/frontend/src/types/GeneticWorthType.ts b/frontend/src/types/GeneticWorthType.ts new file mode 100644 index 000000000..b8eecfbf1 --- /dev/null +++ b/frontend/src/types/GeneticWorthType.ts @@ -0,0 +1,5 @@ +import { CodeDescResType } from './CodeDescResType'; + +export type GeneticWorthDto = CodeDescResType & { + defaultBv: number +}; diff --git a/frontend/src/types/OrchardDataType.ts b/frontend/src/types/OrchardDataType.ts index ab4af686d..8c0d7a1e5 100644 --- a/frontend/src/types/OrchardDataType.ts +++ b/frontend/src/types/OrchardDataType.ts @@ -5,6 +5,7 @@ type OrchardDataType = { lotTypeCode: string; lotTypeDescription: string; stageCode: string; + spuId: number | null; } export default OrchardDataType; diff --git a/frontend/src/types/ParentTreeGeneticQualityType.ts b/frontend/src/types/ParentTreeGeneticQualityType.ts index 573428fcf..bb75a36ce 100644 --- a/frontend/src/types/ParentTreeGeneticQualityType.ts +++ b/frontend/src/types/ParentTreeGeneticQualityType.ts @@ -13,19 +13,9 @@ export enum GenWorthCodeEnum { } export type SingleParentTreeGeneticObj = { - geneticTypeCode: string; - geneticWorthCode: keyof typeof GenWorthCodeEnum; - geneticQualityValue: number; + geneticTypeCode: 'BV' | 'CV', + geneticWorthCode: keyof typeof GenWorthCodeEnum, + geneticQualityValue: number, + isParentTreeTested?: boolean, // refers to the testedInd on PARENT_TREE_GENETIC_QUALITY + isEstimated?: boolean // refers to whether the genetic quality value is using default }; - -/** - * The type returned from get parent trees by species endpoint. - */ -export type ParentTreeGeneticQualityType = { - [key: string]: any; - parentTreeId: number; - parentTreeNumber: string; - orchardId: string; - spu: number; - parentTreeGeneticQualities: Array; -} diff --git a/frontend/src/types/ParentTreeTypes.ts b/frontend/src/types/ParentTreeTypes.ts new file mode 100644 index 000000000..13236275a --- /dev/null +++ b/frontend/src/types/ParentTreeTypes.ts @@ -0,0 +1,14 @@ +import { SingleParentTreeGeneticObj } from './ParentTreeGeneticQualityType'; + +export type ParentTreeByVegCodeDto = { + parentTreeId: number, + testedInd: boolean, + orchardIds: string[], + geneticQualitiesBySpu: { + [spuId: number]: SingleParentTreeGeneticObj[] + } +} + +export type ParentTreeByVegCodeResType = { + [parentTreeNumber: string]: ParentTreeByVegCodeDto +} diff --git a/frontend/src/types/PtCalcTypes.ts b/frontend/src/types/PtCalcTypes.ts index c70409a05..62acb4cc6 100644 --- a/frontend/src/types/PtCalcTypes.ts +++ b/frontend/src/types/PtCalcTypes.ts @@ -43,5 +43,6 @@ export type GeospatialRequestDto = { export type PtValsCalcReqPayload = { orchardPtVals: OrchardParentTreeValsType[], - smpMixIdAndProps: GeospatialRequestDto[] + smpMixIdAndProps: GeospatialRequestDto[], + smpParentsOutside: string } diff --git a/frontend/src/types/SeedlotRegistrationTypes.ts b/frontend/src/types/SeedlotRegistrationTypes.ts index 93630d49f..bab550e3d 100644 --- a/frontend/src/types/SeedlotRegistrationTypes.ts +++ b/frontend/src/types/SeedlotRegistrationTypes.ts @@ -8,7 +8,7 @@ import { * The form data obj used in seedlot creation. */ export type SeedlotRegFormType = { - client: OptionsInputType; + client: StringInputType; locationCode: StringInputType; email: StringInputType; species: OptionsInputType; diff --git a/frontend/src/types/SeedlotType.ts b/frontend/src/types/SeedlotType.ts index e56e57fe5..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, @@ -262,12 +262,12 @@ export type ParentTreeFormSubmitType = { export type ExtractionFormSubmitType = { extractoryClientNumber: string, extractoryLocnCode: string, - extractionStDate: string, - extractionEndDate: string, + extractionStDate: string | null, + extractionEndDate: string | null, storageClientNumber: string, storageLocnCode: string, - temporaryStrgStartDate: string, - temporaryStrgEndDate: string + temporaryStrgStartDate: string | null + temporaryStrgEndDate: string | null } export type SeedlotAClassSubmitType = { diff --git a/frontend/src/types/SeedlotTypes/ExtractionStorage.ts b/frontend/src/types/SeedlotTypes/ExtractionStorage.ts index 18e80af0d..df5f0d93c 100644 --- a/frontend/src/types/SeedlotTypes/ExtractionStorage.ts +++ b/frontend/src/types/SeedlotTypes/ExtractionStorage.ts @@ -1,19 +1,19 @@ -import { BooleanInputType, StringInputType, OptionsInputType } from '../FormInputType'; +import { BooleanInputType, StringInputType } from '../FormInputType'; type ExtractionStorageForm = { extraction: { - useTSC: BooleanInputType; - agency: OptionsInputType; - locationCode: StringInputType; - startDate: StringInputType; - endDate: StringInputType; + useTSC: BooleanInputType, + agency: StringInputType, + locationCode: StringInputType, + startDate: StringInputType, + endDate: StringInputType }, seedStorage: { - useTSC: BooleanInputType; - agency: OptionsInputType; - locationCode: StringInputType; - startDate: StringInputType; - endDate: StringInputType; + useTSC: BooleanInputType, + agency: StringInputType, + locationCode: StringInputType, + startDate: StringInputType, + endDate: StringInputType } }; diff --git a/frontend/src/utils/DateUtils.ts b/frontend/src/utils/DateUtils.ts index a5b416ce5..617c0c462 100644 --- a/frontend/src/utils/DateUtils.ts +++ b/frontend/src/utils/DateUtils.ts @@ -1,3 +1,12 @@ +import { DateTime as luxon } from 'luxon'; +import { PLACE_HOLDER } from '../shared-constants/shared-constants'; +import { MONTH_DAY_YEAR, ISO_YEAR_MONTH_DAY_SLASH, ISO_YEAR_MONTH_DAY_DASH } from '../config/DateFormat'; + +const DEFAULT_LOCAL_TIMEZONE = 'America/Vancouver'; + +// Get today's date and time +export const now = luxon.now().setZone('America/Vancouver').toFormat('yyyy/MM/dd'); + export const formatDate = (date: string) => { if (date) { const options: Intl.DateTimeFormatOptions = { year: 'numeric', month: 'long', day: 'numeric' }; @@ -6,9 +15,35 @@ export const formatDate = (date: string) => { return '--'; }; -export const dateStringToISO = (date: string): string => { - if (date) { - return new Date(date).toISOString(); +/** + * Convert UTC timestamp to Associated Press Style (e.g. Oct 26, 2023) + */ +export const utcToApStyle = (utcDate: string | null | undefined): string => { + if (!utcDate) { + return PLACE_HOLDER; + } + return luxon.fromISO(utcDate, { zone: 'utc' }) + .setZone(DEFAULT_LOCAL_TIMEZONE).toFormat(MONTH_DAY_YEAR); +}; + +/** + * Convert UTC timestamp to ISO 8601 style with slashes (e.g. 2023/10/26) + */ +export const utcToIsoSlashStyle = (utcDate: string | null | undefined): string => { + if (!utcDate) { + return ''; + } + return luxon.fromISO(utcDate) + .setZone(DEFAULT_LOCAL_TIMEZONE).toFormat(ISO_YEAR_MONTH_DAY_SLASH); +}; + +/** + * Convert local date to UTC date. + */ +export const localDateToUtcFormat = (localDate: string): string | null => { + if (!localDate) { + return null; } - return ''; + 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/utils/ForestClientUtils.ts b/frontend/src/utils/ForestClientUtils.ts index 8602849fd..92fcc814e 100644 --- a/frontend/src/utils/ForestClientUtils.ts +++ b/frontend/src/utils/ForestClientUtils.ts @@ -1,8 +1,8 @@ import { ForestClientSearchType } from '../types/ForestClientTypes/ForestClientSearchType'; import { ForestClientType } from '../types/ForestClientTypes/ForestClientType'; -import { OptionsInputType } from '../types/FormInputType'; +import { StringInputType } from '../types/FormInputType'; import MultiOptionsObj from '../types/MultiOptionsObject'; -import { getOptionsInputObj } from './FormInputUtils'; +import { getStringInputObj } from './FormInputUtils'; export const getForestClientFullName = (client: ForestClientType | ForestClientSearchType) => ( client.legalFirstName @@ -13,8 +13,8 @@ export const getForestClientFullName = (client: ForestClientType | ForestClientS export const getForestClientLabel = (client: ForestClientType | ForestClientSearchType) => { const clientFullName = getForestClientFullName(client); return client.acronym - ? `${client.clientNumber} - ${clientFullName} - ${client.acronym}` - : `${client.clientNumber} - ${clientFullName}`; + ? `${client.acronym} - ${clientFullName} - ${client.clientNumber}` + : `${clientFullName} - ${client.clientNumber}`; }; export const getForestClientOption = (client: ForestClientType): MultiOptionsObj => ({ @@ -23,7 +23,7 @@ export const getForestClientOption = (client: ForestClientType): MultiOptionsObj label: getForestClientLabel(client) }); -export const getForestClientOptionInput = ( +export const getForestClientStringInput = ( id: string, - client: ForestClientType -): OptionsInputType => (getOptionsInputObj(id, getForestClientOption(client))); + clientNumber: string +): StringInputType => (getStringInputObj(id, clientNumber)); diff --git a/frontend/src/utils/SeedlotUtils.ts b/frontend/src/utils/SeedlotUtils.ts index 6b014a335..101d82ac4 100644 --- a/frontend/src/utils/SeedlotUtils.ts +++ b/frontend/src/utils/SeedlotUtils.ts @@ -5,6 +5,8 @@ import { MONTH_DAY_YEAR } from '../config/DateFormat'; import { ForestClientType } from '../types/ForestClientTypes/ForestClientType'; import MultiOptionsObj from '../types/MultiOptionsObject'; import { EmptyMultiOptObj } from '../shared-constants/shared-constants'; +import { getForestClientLabel } from './ForestClientUtils'; +import { utcToApStyle } from './DateUtils'; /** * Generate a species label in the form of `{code} - {description}`. @@ -39,17 +41,12 @@ export const covertRawToDisplayObj = (seedlot: SeedlotType, vegCodeData: MultiOp entryUserId: seedlot.declarationOfTrueInformationUserId ? seedlot.declarationOfTrueInformationUserId : '--', - entryTimestamp: seedlot.declarationOfTrueInformationTimestamp - ? luxon.fromISO(seedlot.declarationOfTrueInformationTimestamp).toFormat(MONTH_DAY_YEAR) - : '--', + entryTimestamp: utcToApStyle(seedlot.declarationOfTrueInformationTimestamp), applicantAgency: seedlot.applicantClientNumber, locationCode: seedlot.applicantLocationCode, - createdAt: luxon.fromISO(seedlot.auditInformation.entryTimestamp).toFormat(MONTH_DAY_YEAR), - lastUpdatedAt: luxon.fromISO(seedlot.auditInformation.updateTimestamp) - .toFormat(MONTH_DAY_YEAR), - approvedAt: seedlot.seedlotStatus.seedlotStatusCode === 'APP' - ? luxon.fromISO(seedlot.seedlotStatus.updateTimestamp).toFormat(MONTH_DAY_YEAR) - : '--' + createdAt: utcToApStyle(seedlot.auditInformation.entryTimestamp), + lastUpdatedAt: utcToApStyle(seedlot.auditInformation.updateTimestamp), + approvedAt: utcToApStyle(seedlot.approvedTimestamp) }); /** @@ -86,7 +83,7 @@ export const convertToApplicantInfoObj = ( vegCodeData: MultiOptionsObj[], forestClient: ForestClientType ): SeedlotApplicantType => ({ - agency: `${forestClient.clientNumber} - ${forestClient.clientName} - ${forestClient.acronym}`, + agency: getForestClientLabel(forestClient), locationCode: seedlot.applicantLocationCode, email: seedlot.applicantEmailAddress, species: getSpeciesLabelByCode(seedlot.vegetationCode, vegCodeData), diff --git a/frontend/src/views/Dashboard/dashboard.tsx b/frontend/src/views/Dashboard/dashboard.tsx index 6f602bb95..456e36f17 100644 --- a/frontend/src/views/Dashboard/dashboard.tsx +++ b/frontend/src/views/Dashboard/dashboard.tsx @@ -17,7 +17,6 @@ const Dashboard = () => ( diff --git a/frontend/src/views/Dashboard/styles.scss b/frontend/src/views/Dashboard/styles.scss index d7b7e35ea..42f611511 100644 --- a/frontend/src/views/Dashboard/styles.scss +++ b/frontend/src/views/Dashboard/styles.scss @@ -1,8 +1,7 @@ @use '@bcgov-nr/nr-theme/design-tokens/variables.scss' as vars; .dashboard-page { - padding-left: 0; - padding-right: 0; + padding: 0 0 2.5rem 0; .dashboard-row { margin-bottom: 2rem; diff --git a/frontend/src/views/Landing/index.tsx b/frontend/src/views/Landing/index.tsx index 2f42f3ebc..1f773d7c8 100644 --- a/frontend/src/views/Landing/index.tsx +++ b/frontend/src/views/Landing/index.tsx @@ -10,7 +10,7 @@ import { import { Login } from '@carbon/icons-react'; import BCGovLogo from '../../components/BCGovLogo'; -import Seeding from '../../assets/img/seeding.png'; +import Seeding from '../../assets/img/cone.jpeg'; import LoginProviders from '../../types/LoginProviders'; import AuthContext from '../../contexts/AuthContext'; diff --git a/frontend/src/views/Seedlot/ContextContainerClassA/context.tsx b/frontend/src/views/Seedlot/ContextContainerClassA/context.tsx index 696896add..7bfb15e17 100644 --- a/frontend/src/views/Seedlot/ContextContainerClassA/context.tsx +++ b/frontend/src/views/Seedlot/ContextContainerClassA/context.tsx @@ -36,7 +36,7 @@ export type ClassAContextType = { seedlotSpecies: MultiOptionsObj, formStep: number, setStep: (delta: number) => void, - defaultAgencyObj: MultiOptionsObj, + defaultClientNumber: string, defaultCode: string, isFormSubmitted: boolean, isFormIncomplete: boolean, @@ -52,7 +52,8 @@ export type ClassAContextType = { >, getSeedlotPayload: ( allStepData: AllStepData, - seedlotNumber: string | undefined + seedlotNumber: string | undefined, + vegCode: string ) => SeedlotAClassSubmitType, updateProgressStatus: (currentStepNum: number, prevStepNum: number) => void, saveProgressStatus: MutationStatusType, @@ -88,7 +89,7 @@ const ClassAContext = createContext({ seedlotSpecies: EmptyMultiOptObj, formStep: 0, setStep: (delta: number) => { }, - defaultAgencyObj: EmptyMultiOptObj, + defaultClientNumber: '', defaultCode: '', isFormSubmitted: false, isFormIncomplete: true, diff --git a/frontend/src/views/Seedlot/ContextContainerClassA/definitions.ts b/frontend/src/views/Seedlot/ContextContainerClassA/definitions.ts index ad4573810..faf8fadd9 100644 --- a/frontend/src/views/Seedlot/ContextContainerClassA/definitions.ts +++ b/frontend/src/views/Seedlot/ContextContainerClassA/definitions.ts @@ -4,15 +4,16 @@ import InterimForm from '../../../components/SeedlotRegistrationSteps/InterimSte import { SingleOwnerForm } from '../../../components/SeedlotRegistrationSteps/OwnershipStep/definitions'; import ExtractionStorageForm from '../../../types/SeedlotTypes/ExtractionStorage'; import { OrchardForm } from '../../../components/SeedlotRegistrationSteps/OrchardStep/definitions'; -import { RowDataDictType, NotifCtrlType, AllParentTreeMap } from '../../../components/SeedlotRegistrationSteps/ParentTreeStep/definitions'; +import { RowDataDictType, NotifCtrlType } from '../../../components/SeedlotRegistrationSteps/ParentTreeStep/definitions'; import { MutationStatusType } from '../../../types/QueryStatusType'; import MultiOptionsObj from '../../../types/MultiOptionsObject'; import { OptionsInputType, StringInputType } from '../../../types/FormInputType'; import { SeedlotProgressPayloadType } from '../../../types/SeedlotType'; +import { ParentTreeByVegCodeResType } from '../../../types/ParentTreeTypes'; export type ParentTreeStepDataObj = { tableRowData: RowDataDictType, // table row data used in Cone & Pollen and the SMP Success tabs - allParentTreeData: AllParentTreeMap // Contains all parent tree numbers under a species + allParentTreeData: ParentTreeByVegCodeResType, // Contains all parent tree numbers under a species mixTabData: RowDataDictType, // table row data used exclusively for SMP mix tab notifCtrl: NotifCtrlType } diff --git a/frontend/src/views/Seedlot/ContextContainerClassA/index.tsx b/frontend/src/views/Seedlot/ContextContainerClassA/index.tsx index 92d6a8e65..c2b375ed5 100644 --- a/frontend/src/views/Seedlot/ContextContainerClassA/index.tsx +++ b/frontend/src/views/Seedlot/ContextContainerClassA/index.tsx @@ -5,7 +5,7 @@ import { useNavigate, useParams, useSearchParams, useLocation } from 'react-router-dom'; import { - useMutation, useQueries, useQuery, useQueryClient + useMutation, useQuery } from '@tanstack/react-query'; import { AxiosError, isAxiosError } from 'axios'; import { DateTime } from 'luxon'; @@ -15,7 +15,6 @@ import { putAClassSeedlot, putAClassSeedlotProgress } from '../../../api-service/seedlotAPI'; import getVegCodes from '../../../api-service/vegetationCodeAPI'; -import { getForestClientByNumberOrAcronym } from '../../../api-service/forestClientsAPI'; import getFundingSources from '../../../api-service/fundingSourcesAPI'; import getMethodsOfPayment from '../../../api-service/methodsOfPaymentAPI'; import getGameticMethodology from '../../../api-service/gameticMethodologyAPI'; @@ -24,9 +23,7 @@ import { TEN_SECONDS, THREE_HALF_HOURS, THREE_HOURS, FIVE_SECONDS } from '../../../config/TimeUnits'; -import MultiOptionsObj from '../../../types/MultiOptionsObject'; import { SeedlotAClassSubmitType, SeedlotCalculationsResultsType, SeedlotProgressPayloadType } from '../../../types/SeedlotType'; -import { ForestClientType } from '../../../types/ForestClientTypes/ForestClientType'; import { generateDefaultRows } from '../../../components/SeedlotRegistrationSteps/ParentTreeStep/utils'; import { DEFAULT_MIX_PAGE_ROWS, EMPTY_NUMBER_STRING, @@ -44,8 +41,7 @@ import { StringInputType } from '../../../types/FormInputType'; import ClassAContext, { ClassAContextType } from './context'; import { - AllStepData, AreaOfUseDataType, ClientAgenciesByCode, - ProgressIndicatorConfig, ProgressStepStatus + AllStepData, AreaOfUseDataType, ProgressIndicatorConfig, ProgressStepStatus } from './definitions'; import { initProgressBar, getSpeciesOptionByCode, validateCollectionStep, verifyCollectionStepCompleteness, @@ -113,7 +109,7 @@ const ContextContainerClassA = ({ children }: props) => { const vegCodeQuery = useQuery({ queryKey: ['vegetation-codes'], - queryFn: () => getVegCodes(), + queryFn: getVegCodes, select: (data) => getMultiOptList(data, true, true), staleTime: THREE_HOURS, cacheTime: THREE_HALF_HOURS @@ -146,20 +142,6 @@ const ContextContainerClassA = ({ children }: props) => { const [isFormSubmitted, setIsFormSubmitted] = useState(false); const [geoInfoVals, setGeoInfoVals] = useState(() => INITIAL_GEO_INFO_VALS); - useEffect(() => { - if (seedlotQuery.status === 'success') { - const seedlotStatusCode = seedlotQuery.data?.seedlot.seedlotStatus.seedlotStatusCode; - if (seedlotStatusCode !== 'PND' && seedlotStatusCode !== 'INC') { - setIsFormSubmitted(true); - } - - if (seedlotQuery.data) { - // Collection geo data - fillCollectionGeoData(setGeoInfoVals, setPopSizeAndDiversityConfig, seedlotQuery.data); - } - } - }, [seedlotQuery.status]); - const getAllSeedlotInfoQuery = useQuery({ queryKey: ['seedlot-full-form', seedlotNumber], queryFn: () => getAClassSeedlotFullForm(seedlotNumber ?? ''), @@ -175,14 +157,14 @@ const ContextContainerClassA = ({ children }: props) => { // Initialize all step's state here const [allStepData, setAllStepData] = useState(() => initEmptySteps()); - const setDefaultAgencyAndCode = (agency: MultiOptionsObj, locationCode: string) => { + const setDefaultClientAndCode = (clientNumber: string, locationCode: string) => { setAllStepData((prevData) => ({ ...prevData, collectionStep: { ...prevData.collectionStep, collectorAgency: { ...prevData.collectionStep.collectorAgency, - value: agency + value: clientNumber }, locationCode: { ...prevData.collectionStep.locationCode, @@ -193,7 +175,7 @@ const ContextContainerClassA = ({ children }: props) => { ...singleOwner, ownerAgency: { ...singleOwner.ownerAgency, - value: agency + value: clientNumber }, ownerCode: { ...singleOwner.ownerCode, @@ -204,7 +186,7 @@ const ContextContainerClassA = ({ children }: props) => { ...prevData.interimStep, agencyName: { ...prevData.interimStep.agencyName, - value: agency + value: clientNumber }, locationCode: { ...prevData.interimStep.locationCode, @@ -215,46 +197,6 @@ const ContextContainerClassA = ({ children }: props) => { numOfEdit.current += 1; }; - const [clientNumber, setClientNumber] = useState(''); - const [clientNumbers, setClientNumbers] = useState([]); - - useEffect(() => { - if (seedlotQuery.status === 'success') { - setClientNumber(seedlotQuery.data.seedlot.applicantClientNumber ?? ''); - - // Set area of use data - setAreaOfUseData(fillAreaOfUseData(seedlotQuery.data, areaOfUseData)); - } - }, [seedlotQuery.status]); - - const forestClientQuery = useQuery({ - queryKey: ['forest-clients', clientNumber], - queryFn: () => getForestClientByNumberOrAcronym(clientNumber), - enabled: seedlotQuery.isFetched && clientNumber !== '', - staleTime: THREE_HOURS, - cacheTime: THREE_HALF_HOURS - }); - - const allClientsQuery = useQueries({ - queries: clientNumbers.map((client) => ({ - queryKey: ['forest-clients', client], - queryFn: () => getForestClientByNumberOrAcronym(client), - enabled: getAllSeedlotInfoQuery.isFetched, - staleTime: THREE_HOURS, - cacheTime: THREE_HALF_HOURS - })) - }); - - const allClientsFinished = allClientsQuery.every((client) => client.isSuccess); - - const qc = useQueryClient(); - - const getAgencyObj = (): MultiOptionsObj => ({ - code: forestClientQuery.data?.clientNumber ?? '', - description: `${forestClientQuery.data?.clientNumber} - ${forestClientQuery.data?.clientName} - ${forestClientQuery.data?.acronym}`, - label: forestClientQuery.data?.acronym ?? '' - }); - const getDefaultLocationCode = (): string => (seedlotQuery.data?.seedlot.applicantLocationCode ?? ''); const updateStepStatus = ( @@ -308,8 +250,7 @@ const ContextContainerClassA = ({ children }: props) => { // agency also changes the values on the interim agency, when // necessary, also reflecting the invalid values if (stepName === 'collectionStep' - && allStepData.interimStep.useCollectorAgencyInfo.value - && !isFormSubmitted) { + && allStepData.interimStep.useCollectorAgencyInfo.value) { newData.interimStep.agencyName.value = stepData.collectorAgency.value; newData.interimStep.locationCode.value = stepData.locationCode.value; setProgressStatus((prevStatus) => ( @@ -324,35 +265,6 @@ const ContextContainerClassA = ({ children }: props) => { numOfEdit.current += 1; }; - // This useEffect fetchs all data regarding agencies on the - // form steps - useEffect(() => { - if (getAllSeedlotInfoQuery.status === 'success') { - // Set seedlot data - const { seedlotData } = getAllSeedlotInfoQuery.data; - const clientNumbersArray: string[] = []; - clientNumbersArray.push(seedlotData.seedlotFormCollectionDto.collectionClientNumber); - clientNumbersArray.push(seedlotData.seedlotFormInterimDto.intermStrgClientNumber); - seedlotData.seedlotFormOwnershipDtoList.forEach((owner: any) => { - clientNumbersArray.push(owner.ownerClientNumber); - }); - clientNumbersArray.push(seedlotData.seedlotFormExtractionDto.extractoryClientNumber); - clientNumbersArray.push(seedlotData.seedlotFormExtractionDto.storageClientNumber); - setClientNumbers([...new Set(clientNumbersArray)]); - - // Set calculated result - setCalculatedValues(getAllSeedlotInfoQuery.data.calculatedValues); - - // Set progress status - const clonedStatus = structuredClone(progressStatus); - const stepNames = Object.keys(clonedStatus) as Array; - stepNames.forEach((step) => { - clonedStatus[step].isComplete = true; - }); - setProgressStatus(clonedStatus); - } - }, [getAllSeedlotInfoQuery.status]); - const fundingSourcesQuery = useQuery({ queryKey: ['funding-sources'], queryFn: getFundingSources, @@ -391,12 +303,40 @@ const ContextContainerClassA = ({ children }: props) => { ({ code: orchard.id, description: orchard.name, - label: `${orchard.id} - ${orchard.name} - ${orchard.lotTypeCode} - ${orchard.stageCode}` + label: `${orchard.id} - ${orchard.name} - ${orchard.lotTypeCode} - ${orchard.stageCode}`, + spuId: orchard.spuId }) )) .sort((a, b) => Number(a.code) - Number(b.code)) }); + /** + * seedlotQuery Effects + */ + useEffect(() => { + if (seedlotQuery.status === 'success') { + const seedlotStatusCode = seedlotQuery.data?.seedlot.seedlotStatus.seedlotStatusCode; + if (seedlotStatusCode !== 'PND' && seedlotStatusCode !== 'INC') { + setIsFormSubmitted(true); + } + + fillCollectionGeoData(setGeoInfoVals, setPopSizeAndDiversityConfig, seedlotQuery.data); + + // Set area of use data + setAreaOfUseData(fillAreaOfUseData(seedlotQuery.data, areaOfUseData)); + } + }, [seedlotQuery.status]); + + /** + * getAllSeedlotInfoQuery Effects + */ + useEffect(() => { + if (getAllSeedlotInfoQuery.status === 'success') { + // Set calculated result + setCalculatedValues(getAllSeedlotInfoQuery.data.calculatedValues); + } + }, [getAllSeedlotInfoQuery.status]); + useEffect(() => { if ( getAllSeedlotInfoQuery.status === 'success' @@ -404,25 +344,12 @@ const ContextContainerClassA = ({ children }: props) => { && methodsOfPaymentQuery.status === 'success' && gameticMethodologyQuery.status === 'success' && orchardQuery.status === 'success' - && allClientsFinished - && clientNumbers.length + && seedlotQuery.status === 'success' + && seedlotQuery.fetchStatus !== 'fetching' + && getAllSeedlotInfoQuery.fetchStatus !== 'fetching' ) { const fullFormData = getAllSeedlotInfoQuery.data.seedlotData; const defaultAgencyNumber = seedlotQuery.data?.seedlot.applicantClientNumber; - let clientAgencies: ClientAgenciesByCode = {}; - - clientNumbers.forEach((curNumber) => { - const clientData: ForestClientType | undefined = qc.getQueryData(['forest-clients', curNumber]); - if (clientData) { - clientAgencies = Object.assign(clientAgencies, { - [curNumber]: { - code: clientData.clientNumber, - description: `${clientData.clientNumber} - ${clientData.clientName} - ${clientData.acronym}`, - label: clientData.acronym - } - }); - } - }); setAllStepData( resDataToState( @@ -431,8 +358,7 @@ const ContextContainerClassA = ({ children }: props) => { methodsOfPaymentQuery.data, fundingSourcesQuery.data, orchardQuery.data, - gameticMethodologyQuery.data, - clientAgencies + gameticMethodologyQuery.data ) ); } else if (getAllSeedlotInfoQuery.status === 'error') { @@ -445,13 +371,13 @@ const ContextContainerClassA = ({ children }: props) => { } }, [ getAllSeedlotInfoQuery.status, - getAllSeedlotInfoQuery.isFetched, - fundingSourcesQuery.isFetched, - methodsOfPaymentQuery.isFetched, - gameticMethodologyQuery.isFetched, - orchardQuery.isFetched, - allClientsFinished, - clientNumbers + fundingSourcesQuery.status, + methodsOfPaymentQuery.status, + gameticMethodologyQuery.status, + orchardQuery.status, + seedlotQuery.status, + seedlotQuery.fetchStatus, + getAllSeedlotInfoQuery.fetchStatus ]); /** @@ -663,10 +589,12 @@ const ContextContainerClassA = ({ children }: props) => { queryFn: () => getAClassSeedlotDraft(seedlotNumber ?? ''), enabled: seedlotNumber !== undefined && seedlotNumber.length > 0 && isFormIncomplete, refetchOnMount: true, - select: (data) => data.data as SeedlotProgressPayloadType, - retry: 1 + select: (data) => data.data as SeedlotProgressPayloadType }); + /** + * Form Draft Effects. + */ useEffect(() => { if (getFormDraftQuery.status === 'success') { setAllStepData(getFormDraftQuery.data.allStepData); @@ -687,17 +615,18 @@ const ContextContainerClassA = ({ children }: props) => { // eslint-disable-next-line no-alert alert(`Error retrieving form draft! ${error.message}`); navigate(`/seedlots/details/${seedlotNumber}`); - } else if (forestClientQuery.isFetched) { + } else if (seedlotQuery.status === 'success') { // set default agency and code only if the seedlot has no draft saved, // meaning this is their first time opening this form - setDefaultAgencyAndCode(getAgencyObj(), getDefaultLocationCode()); + setDefaultClientAndCode( + seedlotQuery.data.seedlot.applicantClientNumber, + getDefaultLocationCode() + ); } } }, [ getFormDraftQuery.status, - getFormDraftQuery.isFetchedAfterMount, - forestClientQuery.status, - getFormDraftQuery.isRefetching + getFormDraftQuery.fetchStatus ]); const [genWorthVals, setGenWorthVals] = useState(() => INITIAL_GEN_WORTH_VALS); @@ -811,7 +740,7 @@ const ContextContainerClassA = ({ children }: props) => { ), formStep, setStep, - defaultAgencyObj: getAgencyObj(), + defaultClientNumber: seedlotQuery.data?.seedlot.applicantClientNumber ?? '', defaultCode: getDefaultLocationCode(), isFormSubmitted, isFormIncomplete, @@ -830,7 +759,6 @@ const ContextContainerClassA = ({ children }: props) => { vegCodeQuery.isFetching || seedlotQuery.isFetching || getAllSeedlotInfoQuery.isFetching - || forestClientQuery.isFetching || methodsOfPaymentQuery.isFetching || orchardQuery.isFetching || gameticMethodologyQuery.isFetching @@ -855,7 +783,7 @@ const ContextContainerClassA = ({ children }: props) => { }), [ seedlotNumber, calculatedValues, allStepData, seedlotQuery.status, - vegCodeQuery.status, formStep, forestClientQuery.status, + vegCodeQuery.status, formStep, isFormSubmitted, isFormIncomplete, saveStatus, saveDescription, lastSaveTimestamp, allStepCompleted, progressStatus, submitSeedlot, saveProgress.status, getAllSeedlotInfoQuery.status, diff --git a/frontend/src/views/Seedlot/ContextContainerClassA/styles.scss b/frontend/src/views/Seedlot/ContextContainerClassA/styles.scss index 8051d9103..d556ecde4 100644 --- a/frontend/src/views/Seedlot/ContextContainerClassA/styles.scss +++ b/frontend/src/views/Seedlot/ContextContainerClassA/styles.scss @@ -61,10 +61,6 @@ margin-bottom: 4rem; } - .seedlot-registration-breadcrumb .#{vars.$bcgov-prefix}--link { - cursor: pointer; - } - .seedlot-registration-button-row { .form-action-btn { margin-top: 1rem; diff --git a/frontend/src/views/Seedlot/ContextContainerClassA/utils.ts b/frontend/src/views/Seedlot/ContextContainerClassA/utils.ts index 480b6aefc..17ed8dd1b 100644 --- a/frontend/src/views/Seedlot/ContextContainerClassA/utils.ts +++ b/frontend/src/views/Seedlot/ContextContainerClassA/utils.ts @@ -3,21 +3,22 @@ import BigNumber from 'bignumber.js'; import { AxiosError } from 'axios'; import { CollectionForm } from '../../../components/SeedlotRegistrationSteps/CollectionStep/definitions'; import InterimForm from '../../../components/SeedlotRegistrationSteps/InterimStep/definitions'; -import { OrchardForm, OrchardObj } from '../../../components/SeedlotRegistrationSteps/OrchardStep/definitions'; +import { OrchardForm } from '../../../components/SeedlotRegistrationSteps/OrchardStep/definitions'; import { createOwnerTemplate } from '../../../components/SeedlotRegistrationSteps/OwnershipStep/constants'; import { SingleOwnerForm } from '../../../components/SeedlotRegistrationSteps/OwnershipStep/definitions'; import { - DEFAULT_MIX_PAGE_ROWS, MAX_DECIMAL_DIGITS, notificationCtrlObj, + DEFAULT_MIX_PAGE_ROWS, geneticWorthDict, MAX_DECIMAL_DIGITS, notificationCtrlObj, rowTemplate } from '../../../components/SeedlotRegistrationSteps/ParentTreeStep/constants'; import { + GeneticWorthDictType, + GeneticWorthInputType, InfoSectionConfigType, RowDataDictType, RowItem, StrTypeRowItem } from '../../../components/SeedlotRegistrationSteps/ParentTreeStep/definitions'; import { calcAverage, calcSum, generateDefaultRows, - populateStrInputId, - processOrchards + populateStrInputId } from '../../../components/SeedlotRegistrationSteps/ParentTreeStep/utils'; import { EmptyMultiOptObj } from '../../../shared-constants/shared-constants'; import MultiOptionsObj from '../../../types/MultiOptionsObject'; @@ -27,12 +28,13 @@ import { OrchardFormSubmitType, ParentTreeFormSubmitType, RichSeedlotType, SeedlotAClassSubmitType, SingleOwnerFormSubmitType } from '../../../types/SeedlotType'; -import { dateStringToISO } from '../../../utils/DateUtils'; +import { localDateToUtcFormat, utcToIsoSlashStyle } from '../../../utils/DateUtils'; import { ErrorDescriptionType } from '../../../types/ErrorDescriptionType'; import ROUTES from '../../../routes/constants'; import { addParamToPath } from '../../../utils/PathUtils'; import { getOptionsInputObj } from '../../../utils/FormInputUtils'; import { GeoInfoValType } from '../SeedlotReview/definitions'; +import focusById from '../../../utils/FocusUtils'; import { emptyCollectionStep, emptyExtractionStep, emptyInterimStep, @@ -41,10 +43,10 @@ import { import { AllStepData, AreaOfUseDataType, - ClientAgenciesByCode, ParentTreeStepDataObj, ProgressIndicatorConfig } from './definitions'; -import focusById from '../../../utils/FocusUtils'; +import { GenWorthCodeEnum, SingleParentTreeGeneticObj } from '../../../types/ParentTreeGeneticQualityType'; +import { ParentTreeByVegCodeResType } from '../../../types/ParentTreeTypes'; export const initProgressBar = ( currentStep: number, @@ -63,48 +65,42 @@ export const initProgressBar = ( }; export const initCollectionState = ( - defaultAgency: MultiOptionsObj, - collectionStepData: CollectionFormSubmitType, - useDefaultAgency = true + defaultAgencyNumber: string, + collectionStepData: CollectionFormSubmitType ): CollectionForm => ({ - useDefaultAgencyInfo: { - id: 'collection-use-default-agency', - value: useDefaultAgency, - isInvalid: false - }, collectorAgency: { id: 'collection-collector-agency', - value: defaultAgency, + value: defaultAgencyNumber, isInvalid: false }, locationCode: { id: 'collection-location-code', - value: collectionStepData.collectionLocnCode, + value: collectionStepData.collectionLocnCode ?? '', isInvalid: false }, startDate: { id: 'collection-start-date', - value: collectionStepData.collectionStartDate, + value: utcToIsoSlashStyle(collectionStepData.collectionStartDate), isInvalid: false }, endDate: { id: 'collection-end-date', - value: collectionStepData.collectionEndDate, + value: utcToIsoSlashStyle(collectionStepData.collectionEndDate), isInvalid: false }, 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: { @@ -114,34 +110,31 @@ export const initCollectionState = ( }, comments: { id: 'collection-comments', - value: collectionStepData.seedlotComment, + value: collectionStepData.seedlotComment ?? '', isInvalid: false } }); export const initOwnershipState = ( - defaultAgency: MultiOptionsObj, + defaultAgencyNumber: string, ownersStepData: Array, - useDefault?: boolean, methodsOfPayment?: Array, - fundingSource?: Array, - clientData?: ClientAgenciesByCode, - defaultAgencyNumber = '' + fundingSource?: Array ): Array => { const seedlotOwners: Array = ownersStepData.map((curOwner, index) => { const ownerState = createOwnerTemplate(index, curOwner); - ownerState.ownerAgency.value = clientData && !useDefault - ? clientData[curOwner.ownerClientNumber] - : defaultAgency; - ownerState.useDefaultAgencyInfo.value = ownerState - .ownerAgency.value.code === defaultAgencyNumber; + + 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; @@ -150,7 +143,7 @@ export const initOwnershipState = ( }; export const initInterimState = ( - defaultAgency: MultiOptionsObj, + defaultAgencyNumber: string, interimStepData: InterimFormSubmitType, useDefaultAgency = true ): InterimForm => ({ @@ -161,61 +154,48 @@ export const initInterimState = ( }, agencyName: { id: 'interim-agency', - value: defaultAgency, + value: defaultAgencyNumber, isInvalid: false }, locationCode: { id: 'interim-location-code', - value: interimStepData.intermStrgLocnCode, + value: interimStepData.intermStrgLocnCode ?? '', isInvalid: false }, startDate: { id: 'storage-start-date', - value: interimStepData.intermStrgStDate, + value: utcToIsoSlashStyle(interimStepData.intermStrgStDate), isInvalid: false }, endDate: { id: 'storage-end-date', - value: interimStepData.intermStrgEndDate, + value: utcToIsoSlashStyle(interimStepData.intermStrgEndDate), isInvalid: false }, 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 } }); const convertToOrchardsType = ( - orchards: Array | undefined, - primaryOrchardId: string, - secondaryOrchardId: string | null -): Array => { - if (orchards) { - const orchardsIds: string[] = [primaryOrchardId]; - if (secondaryOrchardId) { - orchardsIds.push(secondaryOrchardId); + selectedOrchardId: string | null, + orchards: Array | undefined +): MultiOptionsObj => { + if (orchards && selectedOrchardId) { + const filteredOrchards = orchards.find((orchard) => orchard.code === selectedOrchardId); + if (filteredOrchards) { + return filteredOrchards; } - const filteredOrchards = orchards.filter((curOrch) => orchardsIds.includes(curOrch.code)); - return filteredOrchards.map((curFilteredOrch, index) => ({ - inputId: index, - selectedItem: curFilteredOrch, - isInvalid: false - })); } - return [ - { - inputId: 0, - selectedItem: null, - isInvalid: false - } - ]; + return EmptyMultiOptObj; }; export const initOrchardState = ( @@ -224,11 +204,19 @@ export const initOrchardState = ( gameticMethodology?: Array ): OrchardForm => ( { - orchards: convertToOrchardsType( - possibleOrchards, - orchardStepData.primaryOrchardId, - orchardStepData.secondaryOrchardId - ), + orchards: { + primaryOrchard: { + id: 'primary-orchard-selection', + value: convertToOrchardsType(orchardStepData.primaryOrchardId, possibleOrchards), + isInvalid: false + }, + secondaryOrchard: { + id: 'secondary-orchard-selection', + value: convertToOrchardsType(orchardStepData.secondaryOrchardId, possibleOrchards), + isInvalid: false, + enabled: !!orchardStepData.secondaryOrchardId + } + }, femaleGametic: { id: 'orchard-female-gametic', value: gameticMethodology @@ -340,8 +328,8 @@ export const initParentTreeState = ( }; export const initExtractionStorageState = ( - defaultExtractAgency: MultiOptionsObj, - defaultStorageAgency: MultiOptionsObj, + defaultExtractAgencyNumber: string, + defaultStorageAgencyNumber: string, extractionStepData: ExtractionFormSubmitType, useTSCExtract = true, useTSCStorage = true @@ -354,23 +342,23 @@ export const initExtractionStorageState = ( isInvalid: false }, agency: { - id: 'ext-agency-combobox', - value: defaultExtractAgency, + id: 'ext-agency-number', + value: defaultExtractAgencyNumber, isInvalid: false }, locationCode: { id: 'ext-location-code', - value: useTSCExtract ? tscLocationCode : extractionStepData.extractoryLocnCode, + value: useTSCExtract ? tscLocationCode : (extractionStepData.extractoryLocnCode ?? ''), isInvalid: false }, startDate: { id: 'ext-start-date', - value: extractionStepData.extractionStDate, + value: utcToIsoSlashStyle(extractionStepData.extractionStDate), isInvalid: false }, endDate: { id: 'ext-end-date', - value: extractionStepData.extractionEndDate, + value: utcToIsoSlashStyle(extractionStepData.extractionEndDate), isInvalid: false } }, @@ -381,23 +369,23 @@ export const initExtractionStorageState = ( isInvalid: false }, agency: { - id: 'str-agency-combobox', - value: defaultStorageAgency, + id: 'str-agency-number', + value: defaultStorageAgencyNumber, isInvalid: false }, locationCode: { id: 'str-location-code', - value: useTSCStorage ? tscLocationCode : extractionStepData.storageLocnCode, + value: useTSCStorage ? tscLocationCode : (extractionStepData.storageLocnCode ?? ''), isInvalid: false }, startDate: { id: 'str-start-date', - value: extractionStepData.temporaryStrgStartDate, + value: utcToIsoSlashStyle(extractionStepData.temporaryStrgStartDate), isInvalid: false }, endDate: { id: 'str-end-date', - value: extractionStepData.temporaryStrgEndDate, + value: utcToIsoSlashStyle(extractionStepData.temporaryStrgEndDate), isInvalid: false } } @@ -493,28 +481,28 @@ export const verifyCollectionStepCompleteness = ( let isComplete = true; let idToFocus = ''; - if (!collectionData.collectorAgency.value.code.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. @@ -539,19 +527,19 @@ export const verifyOwnershipStepCompleteness = ( let idToFocus = ''; for (let i = 0; i < ownershipData.length; i += 1) { - if (!ownershipData[i].ownerAgency.value.code.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 ( @@ -586,7 +574,7 @@ export const verifyInterimStepCompleteness = ( let isComplete = true; let idToFocus = ''; - if (!interimData.agencyName.value.code) { + if (!interimData.agencyName.value) { isComplete = false; idToFocus = interimData.agencyName.id; } else if (!interimData.locationCode.value) { @@ -665,22 +653,18 @@ export const verifyOrchardStepCompleteness = ( orchardStepData: OrchardForm, focusOnIncomplete?: boolean ): boolean => { - let isComplete = false; - - orchardStepData.orchards.forEach((orchard) => { - // if one of the orchard object is populated then it's complete for this field - if (orchard.selectedItem?.code) { - isComplete = true; - } - }); - - if (!isComplete) { - return isComplete; - } - + let isComplete = true; let idToFocus = ''; - if (!orchardStepData.femaleGametic.value.code) { + if (!orchardStepData.orchards.primaryOrchard.value.code) { + isComplete = false; + idToFocus = orchardStepData.orchards.primaryOrchard.id; + } else if (orchardStepData.orchards.secondaryOrchard.enabled + && !orchardStepData.orchards.secondaryOrchard.value.code + ) { + isComplete = false; + idToFocus = orchardStepData.orchards.secondaryOrchard.id; + } else if (!orchardStepData.femaleGametic.value.code) { isComplete = false; idToFocus = orchardStepData.femaleGametic.id; } else if (!orchardStepData.maleGametic.value.code) { @@ -820,13 +804,13 @@ export const verifyExtractionStepCompleteness = ( let isComplete = true; let idToFocus = ''; - if (!extractionStepData.extraction.agency.value.code) { + if (!extractionStepData.extraction.agency.value) { isComplete = false; idToFocus = extractionStepData.extraction.agency.id; } else if (!extractionStepData.extraction.locationCode.value) { isComplete = false; idToFocus = extractionStepData.extraction.locationCode.id; - } else if (!extractionStepData.seedStorage.agency.value.code) { + } else if (!extractionStepData.seedStorage.agency.value) { isComplete = false; idToFocus = extractionStepData.seedStorage.agency.id; } else if (!extractionStepData.seedStorage.locationCode.value) { @@ -863,10 +847,11 @@ export const checkAllStepsCompletion = ( }; export const convertCollection = (collectionData: CollectionForm): CollectionFormSubmitType => ({ - collectionClientNumber: collectionData.collectorAgency.value.code, + collectionClientNumber: collectionData.collectorAgency.value, collectionLocnCode: collectionData.locationCode.value, - collectionStartDate: dateStringToISO(collectionData.startDate.value), - collectionEndDate: dateStringToISO(collectionData.endDate.value), + // Assume the date values are present as validation has occurred before payload is generated + collectionStartDate: localDateToUtcFormat(collectionData.startDate.value)!, + collectionEndDate: localDateToUtcFormat(collectionData.endDate.value)!, noOfContainers: +collectionData.numberOfContainers.value, volPerContainer: +collectionData.volumePerContainers.value, clctnVolume: +collectionData.volumeOfCones.value, @@ -879,7 +864,7 @@ export const convertOwnership = ( ownershipData: Array ): Array => ( ownershipData.map((owner: SingleOwnerForm) => ({ - ownerClientNumber: owner.ownerAgency.value.code, + ownerClientNumber: owner.ownerAgency.value, ownerLocnCode: owner.ownerCode.value, originalPctOwned: +owner.ownerPortion.value, originalPctRsrvd: +owner.reservedPerc.value, @@ -890,10 +875,11 @@ export const convertOwnership = ( ); export const convertInterim = (interimData: InterimForm): InterimFormSubmitType => ({ - intermStrgClientNumber: interimData.agencyName.value.code, + intermStrgClientNumber: interimData.agencyName.value, intermStrgLocnCode: interimData.locationCode.value, - intermStrgStDate: dateStringToISO(interimData.startDate.value), - intermStrgEndDate: dateStringToISO(interimData.endDate.value), + // Assume the date values are present as validation has occurred before payload is generated + intermStrgStDate: localDateToUtcFormat(interimData.startDate.value)!, + intermStrgEndDate: localDateToUtcFormat(interimData.endDate.value)!, intermOtherFacilityDesc: interimData.facilityOtherType.value, intermFacilityCode: interimData.facilityType.value }); @@ -902,16 +888,11 @@ export const convertOrchard = ( orchardData: OrchardForm, parentTreeRows: RowDataDictType ): OrchardFormSubmitType => { - const deDuppedOrchards = processOrchards(orchardData.orchards); - let primaryOrchardId: string = ''; - let secondaryOrchardId = null; - - if (deDuppedOrchards.length > 0) { - primaryOrchardId = deDuppedOrchards[0].selectedItem!.code; - } - if (deDuppedOrchards.length > 1) { - secondaryOrchardId = deDuppedOrchards[1].selectedItem!.code; - } + const primaryOrchardId = orchardData.orchards.primaryOrchard.value.code; + const secondaryOrchardId = (orchardData.orchards.primaryOrchard.value.code + !== orchardData.orchards.secondaryOrchard.value.code) + ? orchardData.orchards.secondaryOrchard.value.code + : null; return ({ primaryOrchardId, @@ -929,25 +910,53 @@ export const convertOrchard = ( }); }; +const generateParentTreeGenQualPayload = ( + allParentTreeData: ParentTreeByVegCodeResType, + ptRow: RowItem, + applicableGenWorths: string[] +): SingleParentTreeGeneticObj[] => { + const payload: SingleParentTreeGeneticObj[] = []; + + applicableGenWorths.forEach((genWorthCode) => { + const gwCode = genWorthCode as keyof RowItem; + const gwObj = (ptRow[gwCode] as GeneticWorthInputType); + const ptGeneticObj: SingleParentTreeGeneticObj = { + geneticTypeCode: 'BV', + geneticWorthCode: gwCode.toUpperCase() as keyof typeof GenWorthCodeEnum, + geneticQualityValue: Number(parseFloat(gwObj.value).toFixed(1)), + isParentTreeTested: allParentTreeData[ptRow.parentTreeNumber.value].testedInd, + isEstimated: gwObj.isEstimated + }; + payload.push(ptGeneticObj); + }); + + return payload; +}; + export const convertParentTree = ( parentTreeData: ParentTreeStepDataObj, - seedlotNumber: string + seedlotNumber: string, + applicableGenWorths: string[] ): Array => { const parentTreePayload: Array = []; // Each key is a parent tree number - Object.keys(parentTreeData.tableRowData).forEach((key: string) => { + Object.keys(parentTreeData.tableRowData).forEach((ptNum: string) => { parentTreePayload.push({ seedlotNumber, - parentTreeId: parentTreeData.allParentTreeData[key].parentTreeId, - parentTreeNumber: parentTreeData.allParentTreeData[key].parentTreeNumber, - coneCount: +parentTreeData.tableRowData[key].coneCount.value, - pollenCount: +parentTreeData.tableRowData[key].pollenCount.value, - smpSuccessPct: +parentTreeData.tableRowData[key].smpSuccessPerc.value, - nonOrchardPollenContamPct: +parentTreeData.tableRowData[key].nonOrchardPollenContam.value, - amountOfMaterial: +parentTreeData.tableRowData[key].volume.value, - proportion: +parentTreeData.tableRowData[key].proportion.value, - parentTreeGeneticQualities: parentTreeData.allParentTreeData[key].parentTreeGeneticQualities + parentTreeId: parentTreeData.allParentTreeData[ptNum].parentTreeId, + parentTreeNumber: ptNum, + coneCount: +parentTreeData.tableRowData[ptNum].coneCount.value, + pollenCount: +parentTreeData.tableRowData[ptNum].pollenCount.value, + smpSuccessPct: +parentTreeData.tableRowData[ptNum].smpSuccessPerc.value, + nonOrchardPollenContamPct: +parentTreeData.tableRowData[ptNum].nonOrchardPollenContam.value, + amountOfMaterial: +parentTreeData.tableRowData[ptNum].volume.value, + proportion: +parentTreeData.tableRowData[ptNum].proportion.value, + parentTreeGeneticQualities: generateParentTreeGenQualPayload( + parentTreeData.allParentTreeData, + parentTreeData.tableRowData[ptNum], + applicableGenWorths + ) }); }); @@ -955,31 +964,35 @@ export const convertParentTree = ( }; export const convertSmpParentTree = ( - smpParentTreeData: ParentTreeStepDataObj, - seedlotNumber: string + parentTreeStepData: ParentTreeStepDataObj, + seedlotNumber: string, + applicableGenWorths: string[] ): Array => { - const { allParentTreeData } = smpParentTreeData; + const { allParentTreeData } = parentTreeStepData; const smpMixPayload: Array = []; - if (smpParentTreeData.mixTabData) { - Object.keys(smpParentTreeData.mixTabData).forEach((key: string) => { + if (parentTreeStepData.mixTabData) { + Object.keys(parentTreeStepData.mixTabData).forEach((key: string) => { // Each key is a line in the table, so we need to get // the parent tree value that the user set and use it - const curParentTree = smpParentTreeData.mixTabData[key].parentTreeNumber.value; - if (allParentTreeData[curParentTree]) { + const curParentTreeNum = parentTreeStepData.mixTabData[key].parentTreeNumber.value; + if (allParentTreeData[curParentTreeNum]) { smpMixPayload.push({ seedlotNumber, - parentTreeId: smpParentTreeData.allParentTreeData[curParentTree].parentTreeId, - parentTreeNumber: smpParentTreeData.allParentTreeData[curParentTree].parentTreeNumber, - coneCount: +smpParentTreeData.mixTabData[key].coneCount.value, - pollenCount: +smpParentTreeData.mixTabData[key].pollenCount.value, - smpSuccessPct: +smpParentTreeData.mixTabData[key].smpSuccessPerc.value, - nonOrchardPollenContamPct: +smpParentTreeData + parentTreeId: parentTreeStepData.allParentTreeData[curParentTreeNum].parentTreeId, + parentTreeNumber: curParentTreeNum, + coneCount: +parentTreeStepData.mixTabData[key].coneCount.value, + pollenCount: +parentTreeStepData.mixTabData[key].pollenCount.value, + smpSuccessPct: +parentTreeStepData.mixTabData[key].smpSuccessPerc.value, + nonOrchardPollenContamPct: +parentTreeStepData .mixTabData[key].nonOrchardPollenContam.value, - amountOfMaterial: +smpParentTreeData.mixTabData[key].volume.value, - proportion: +smpParentTreeData.mixTabData[key].proportion.value, - parentTreeGeneticQualities: smpParentTreeData - .allParentTreeData[curParentTree].parentTreeGeneticQualities + amountOfMaterial: +parentTreeStepData.mixTabData[key].volume.value, + proportion: +parentTreeStepData.mixTabData[key].proportion.value, + parentTreeGeneticQualities: generateParentTreeGenQualPayload( + parentTreeStepData.allParentTreeData, + parentTreeStepData.mixTabData[key], + applicableGenWorths + ) }); } }); @@ -991,14 +1004,14 @@ export const convertSmpParentTree = ( export const convertExtraction = ( extractionData: ExtractionStorageForm ): ExtractionFormSubmitType => ({ - extractoryClientNumber: extractionData.extraction.agency.value.code, + extractoryClientNumber: extractionData.extraction.agency.value, extractoryLocnCode: extractionData.extraction.locationCode.value, - extractionStDate: dateStringToISO(extractionData.extraction.startDate.value), - extractionEndDate: dateStringToISO(extractionData.extraction.endDate.value), - storageClientNumber: extractionData.seedStorage.agency.value.code, + extractionStDate: localDateToUtcFormat(extractionData.extraction.startDate.value), + extractionEndDate: localDateToUtcFormat(extractionData.extraction.endDate.value), + storageClientNumber: extractionData.seedStorage.agency.value, storageLocnCode: extractionData.seedStorage.locationCode.value, - temporaryStrgStartDate: dateStringToISO(extractionData.seedStorage.startDate.value), - temporaryStrgEndDate: dateStringToISO(extractionData.seedStorage.endDate.value) + temporaryStrgStartDate: localDateToUtcFormat(extractionData.seedStorage.startDate.value), + temporaryStrgEndDate: localDateToUtcFormat(extractionData.seedStorage.endDate.value) }); export const getSeedlotSubmitErrDescription = (err: AxiosError): ErrorDescriptionType => { @@ -1043,81 +1056,91 @@ export const getBreadcrumbs = (seedlotNumber: string) => [ export const getSeedlotPayload = ( allStepData: AllStepData, - seedlotNumber: string | undefined -): SeedlotAClassSubmitType => ({ - seedlotFormCollectionDto: convertCollection(allStepData.collectionStep), - seedlotFormOwnershipDtoList: convertOwnership(allStepData.ownershipStep), - seedlotFormInterimDto: convertInterim(allStepData.interimStep), - seedlotFormOrchardDto: convertOrchard( - allStepData.orchardStep, - allStepData.parentTreeStep.tableRowData - ), - seedlotFormParentTreeDtoList: convertParentTree(allStepData.parentTreeStep, (seedlotNumber ?? '')), - seedlotFormParentTreeSmpDtoList: convertSmpParentTree(allStepData.parentTreeStep, (seedlotNumber ?? '')), - seedlotFormExtractionDto: convertExtraction(allStepData.extractionStorageStep) -}); + seedlotNumber: string | undefined, + vegCode: string +): SeedlotAClassSubmitType => { + const speciesKey = Object.keys(geneticWorthDict).includes(vegCode) + ? vegCode.toUpperCase() + : 'UNKNOWN'; + + const applicableGenWorths = geneticWorthDict[speciesKey as keyof GeneticWorthDictType]; + + return ({ + seedlotFormCollectionDto: convertCollection(allStepData.collectionStep), + seedlotFormOwnershipDtoList: convertOwnership(allStepData.ownershipStep), + seedlotFormInterimDto: convertInterim(allStepData.interimStep), + seedlotFormOrchardDto: convertOrchard( + allStepData.orchardStep, + allStepData.parentTreeStep.tableRowData + ), + seedlotFormParentTreeDtoList: convertParentTree( + allStepData.parentTreeStep, + (seedlotNumber ?? ''), + applicableGenWorths + ), + seedlotFormParentTreeSmpDtoList: convertSmpParentTree( + allStepData.parentTreeStep, + (seedlotNumber ?? ''), + applicableGenWorths + ), + seedlotFormExtractionDto: convertExtraction(allStepData.extractionStorageStep) + }); +}; export const initEmptySteps = () => ({ - collectionStep: initCollectionState(EmptyMultiOptObj, emptyCollectionStep), - ownershipStep: initOwnershipState(EmptyMultiOptObj, emptyOwnershipStep, true), - interimStep: initInterimState(EmptyMultiOptObj, emptyInterimStep), + collectionStep: initCollectionState('', emptyCollectionStep), + ownershipStep: initOwnershipState('', emptyOwnershipStep), + interimStep: initInterimState('', emptyInterimStep), orchardStep: initOrchardState(emptyOrchardStep), parentTreeStep: initParentTreeState(), extractionStorageStep: initExtractionStorageState( - tscAgencyObj, - tscAgencyObj, + tscAgencyObj.code, + tscAgencyObj.code, emptyExtractionStep ) }); export const resDataToState = ( fullFormData: SeedlotAClassSubmitType, - defaultAgencyNumber: string | undefined, + defaultAgencyNumber: string, methodsOfPaymentData: MultiOptionsObj[], fundingSourcesData: MultiOptionsObj[], orchardQueryData: MultiOptionsObj[], - gameticMethodologyData: MultiOptionsObj[], - clientData: ClientAgenciesByCode -): AllStepData => ( - { - collectionStep: initCollectionState( - clientData[fullFormData.seedlotFormCollectionDto.collectionClientNumber], - fullFormData.seedlotFormCollectionDto, - fullFormData.seedlotFormCollectionDto.collectionClientNumber === defaultAgencyNumber - ), - ownershipStep: initOwnershipState( - EmptyMultiOptObj, - fullFormData.seedlotFormOwnershipDtoList, - false, - methodsOfPaymentData, - fundingSourcesData, - clientData, - defaultAgencyNumber - ), - interimStep: initInterimState( - clientData[fullFormData.seedlotFormInterimDto.intermStrgClientNumber], - fullFormData.seedlotFormInterimDto, - // eslint-disable-next-line max-len - fullFormData.seedlotFormInterimDto.intermStrgClientNumber === fullFormData.seedlotFormCollectionDto.collectionClientNumber - ), - orchardStep: initOrchardState( - fullFormData.seedlotFormOrchardDto, - orchardQueryData, - gameticMethodologyData - ), - parentTreeStep: initParentTreeState( - fullFormData.seedlotFormParentTreeDtoList, - fullFormData.seedlotFormParentTreeSmpDtoList - ), - extractionStorageStep: initExtractionStorageState( - clientData[fullFormData.seedlotFormExtractionDto.extractoryClientNumber], - clientData[fullFormData.seedlotFormExtractionDto.storageClientNumber], - fullFormData.seedlotFormExtractionDto, - fullFormData.seedlotFormExtractionDto.extractoryClientNumber === tscAgencyObj.code, - fullFormData.seedlotFormExtractionDto.storageClientNumber === tscAgencyObj.code - ) - } -); + gameticMethodologyData: MultiOptionsObj[] +): AllStepData => ({ + collectionStep: initCollectionState( + fullFormData.seedlotFormCollectionDto.collectionClientNumber, + fullFormData.seedlotFormCollectionDto + ), + ownershipStep: initOwnershipState( + defaultAgencyNumber, + fullFormData.seedlotFormOwnershipDtoList, + methodsOfPaymentData, + fundingSourcesData + ), + interimStep: initInterimState( + fullFormData.seedlotFormInterimDto.intermStrgClientNumber, + fullFormData.seedlotFormInterimDto, + fullFormData.seedlotFormInterimDto.intermStrgClientNumber + === fullFormData.seedlotFormCollectionDto.collectionClientNumber + ), + orchardStep: initOrchardState( + fullFormData.seedlotFormOrchardDto, + orchardQueryData, + gameticMethodologyData + ), + parentTreeStep: initParentTreeState( + fullFormData.seedlotFormParentTreeDtoList, + fullFormData.seedlotFormParentTreeSmpDtoList + ), + extractionStorageStep: initExtractionStorageState( + fullFormData.seedlotFormExtractionDto.extractoryClientNumber, + fullFormData.seedlotFormExtractionDto.storageClientNumber, + fullFormData.seedlotFormExtractionDto, + fullFormData.seedlotFormExtractionDto.extractoryClientNumber === tscAgencyObj.code, + fullFormData.seedlotFormExtractionDto.storageClientNumber === tscAgencyObj.code + ) +}); export const fillAreaOfUseData = ( seedlotData: RichSeedlotType, @@ -1229,7 +1252,7 @@ export const fillCollectionGeoData = ( }, meanLongSec: { ...prevVals.meanLongMinute, - value: String(data.seedlot.collectionLongitudeMin) + value: String(data.seedlot.collectionLongitudeSec) }, effectivePopSize: { ...prevVals.effectivePopSize, diff --git a/frontend/src/views/Seedlot/CreateAClass/constants.ts b/frontend/src/views/Seedlot/CreateAClass/constants.ts index 48c9f1aac..ea75f7561 100644 --- a/frontend/src/views/Seedlot/CreateAClass/constants.ts +++ b/frontend/src/views/Seedlot/CreateAClass/constants.ts @@ -5,7 +5,7 @@ export const InitialSeedlotRegFormData: SeedlotRegFormType = { client: { id: 'applicant-info-input', isInvalid: false, - value: EmptyMultiOptObj + value: '' }, locationCode: { id: 'agency-number-input', diff --git a/frontend/src/views/Seedlot/CreateAClass/index.tsx b/frontend/src/views/Seedlot/CreateAClass/index.tsx index aff15f171..0f9e64b05 100644 --- a/frontend/src/views/Seedlot/CreateAClass/index.tsx +++ b/frontend/src/views/Seedlot/CreateAClass/index.tsx @@ -71,7 +71,6 @@ const CreateAClass = () => { diff --git a/frontend/src/views/Seedlot/CreateAClass/styles.scss b/frontend/src/views/Seedlot/CreateAClass/styles.scss index def3900c8..b613b731a 100644 --- a/frontend/src/views/Seedlot/CreateAClass/styles.scss +++ b/frontend/src/views/Seedlot/CreateAClass/styles.scss @@ -1,6 +1,8 @@ @use '@bcgov-nr/nr-theme/design-tokens/variables.scss' as vars; .create-a-class-seedlot-page { + padding-bottom: 2.5rem; + .create-a-class-seedlot-breadcrumb { margin-left: 0; } diff --git a/frontend/src/views/Seedlot/CreateAClass/utils.ts b/frontend/src/views/Seedlot/CreateAClass/utils.ts index afe7b3303..d96fe9c9e 100644 --- a/frontend/src/views/Seedlot/CreateAClass/utils.ts +++ b/frontend/src/views/Seedlot/CreateAClass/utils.ts @@ -4,7 +4,7 @@ import focusById from '../../../utils/FocusUtils'; import { SeedlotRegFormType, SeedlotRegPayloadType } from '../../../types/SeedlotRegistrationTypes'; export const convertToPayload = (formData: SeedlotRegFormType): SeedlotRegPayloadType => ({ - applicantClientNumber: formData.client.value.code, + applicantClientNumber: formData.client.value, applicantLocationCode: formData.locationCode.value, applicantEmailAddress: formData.email.value, vegetationCode: formData.species.value.code, @@ -34,7 +34,7 @@ export const validateRegForm = ( ): boolean => { let isValid = false; // Validate client - if (seedlotFormData.client.isInvalid || !seedlotFormData.client.value.code) { + if (seedlotFormData.client.isInvalid || !seedlotFormData.client.value) { setInputValidation('client', true, setSeedlotFormData); focusById(seedlotFormData.client.id); return isValid; diff --git a/frontend/src/views/Seedlot/EditAClassApplication/Form.tsx b/frontend/src/views/Seedlot/EditAClassApplication/Form.tsx index 2d10b69c2..a7bb6eb0d 100644 --- a/frontend/src/views/Seedlot/EditAClassApplication/Form.tsx +++ b/frontend/src/views/Seedlot/EditAClassApplication/Form.tsx @@ -19,7 +19,6 @@ import getVegCodes from '../../../api-service/vegetationCodeAPI'; import LotApplicantAndInfoForm from '../../../components/LotApplicantAndInfoForm'; import { SeedlotType } from '../../../types/SeedlotType'; import { SeedlotPatchPayloadType, SeedlotRegFormType } from '../../../types/SeedlotRegistrationTypes'; -import { getForestClientByNumberOrAcronym } from '../../../api-service/forestClientsAPI'; import MultiOptionsObj from '../../../types/MultiOptionsObject'; import PageTitle from '../../../components/PageTitle'; import focusById from '../../../utils/FocusUtils'; @@ -27,8 +26,7 @@ import ROUTES from '../../../routes/constants'; import ErrorToast from '../../../components/Toast/ErrorToast'; import Breadcrumbs from '../../../components/Breadcrumbs'; import { ErrToastOption } from '../../../config/ToastifyConfig'; -import { ForestClientType } from '../../../types/ForestClientTypes/ForestClientType'; -import { getForestClientOptionInput } from '../../../utils/ForestClientUtils'; +import { getForestClientStringInput } from '../../../utils/ForestClientUtils'; import { getBooleanInputObj, getOptionsInputObj, getStringInputObj } from '../../../utils/FormInputUtils'; import { getSpeciesOptionByCode } from '../../../utils/SeedlotUtils'; import { addParamToPath } from '../../../utils/PathUtils'; @@ -51,7 +49,7 @@ const EditAClassApplicationForm = ({ isReview, applicantData, setApplicantData } const vegCodeQuery = useQuery({ queryKey: ['vegetation-codes'], - queryFn: () => getVegCodes(), + queryFn: getVegCodes, select: (data) => getMultiOptList(data, true, true), staleTime: THREE_HOURS, cacheTime: THREE_HALF_HOURS @@ -75,21 +73,12 @@ const EditAClassApplicationForm = ({ isReview, applicantData, setApplicantData } const applicantClientNumber = seedlotQuery.data?.applicantClientNumber; - const forestClientQuery = useQuery({ - queryKey: ['forest-clients', applicantClientNumber], - queryFn: () => getForestClientByNumberOrAcronym(applicantClientNumber!), - enabled: !!applicantClientNumber, - staleTime: THREE_HOURS, - cacheTime: THREE_HALF_HOURS - }); - const convertToSeedlotForm = ( seedlot: SeedlotType, - vegCodes: MultiOptionsObj[], - client: ForestClientType + vegCodes: MultiOptionsObj[] ) => { setApplicantData({ - client: getForestClientOptionInput('edit-client-read-only', client), + client: getForestClientStringInput('edit-client-read-only', applicantClientNumber!), locationCode: getStringInputObj('edit-seedlot-location-code', seedlot.applicantLocationCode), email: getStringInputObj('edit-seedlot-email', seedlot.applicantEmailAddress), species: getOptionsInputObj('edit-seedlot-species', getSpeciesOptionByCode(seedlot.vegetationCode, vegCodes)), @@ -100,15 +89,10 @@ const EditAClassApplicationForm = ({ isReview, applicantData, setApplicantData } }; useEffect(() => { - if ( - forestClientQuery.isFetched - && forestClientQuery.data - && seedlotQuery.data - && vegCodeQuery.data - ) { - convertToSeedlotForm(seedlotQuery.data, vegCodeQuery.data, forestClientQuery.data); + if (applicantClientNumber && seedlotQuery.status === 'success') { + convertToSeedlotForm(seedlotQuery.data, vegCodeQuery.data!); } - }, [forestClientQuery.isFetched]); + }, [applicantClientNumber, seedlotQuery.status]); const seedlotPatchMutation = useMutation({ mutationFn: ( @@ -181,7 +165,6 @@ const EditAClassApplicationForm = ({ isReview, applicantData, setApplicantData } @@ -214,7 +197,7 @@ const EditAClassApplicationForm = ({ isReview, applicantData, setApplicantData } { - forestClientQuery.isFetched && applicantData + applicantData ? ( { diff --git a/frontend/src/views/Seedlot/MySeedlots/styles.scss b/frontend/src/views/Seedlot/MySeedlots/styles.scss index d77283694..19a750664 100644 --- a/frontend/src/views/Seedlot/MySeedlots/styles.scss +++ b/frontend/src/views/Seedlot/MySeedlots/styles.scss @@ -2,6 +2,7 @@ .my-seedlot-content { padding-inline: 0; + padding: 0 0 2.5rem 0; .my-seedlot-breadcrumb { margin: 0 2.5rem 0.5rem 2.5rem; diff --git a/frontend/src/views/Seedlot/ReviewSeedlots/index.tsx b/frontend/src/views/Seedlot/ReviewSeedlots/index.tsx index f6dde7b8c..87058a330 100644 --- a/frontend/src/views/Seedlot/ReviewSeedlots/index.tsx +++ b/frontend/src/views/Seedlot/ReviewSeedlots/index.tsx @@ -1,4 +1,8 @@ -import React, { useContext, useEffect, useState } from 'react'; +import React, { + useContext, + useEffect, + useState +} from 'react'; import { useNavigate } from 'react-router-dom'; import { @@ -16,6 +20,7 @@ import AuthContext from '../../../contexts/AuthContext'; import SeedlotTable from '../../../components/SeedlotTable'; import ROUTES from '../../../routes/constants'; import { addParamToPath } from '../../../utils/PathUtils'; +import focusById from '../../../utils/FocusUtils'; import './styles.scss'; @@ -25,6 +30,7 @@ const ReviewSeedlots = () => { const userId = user?.userId ?? ''; const [seedlotNumber, setSeedlotNumber] = useState(''); + const [seedlotInputErr, setSeedlotInputErr] = useState(false); useEffect(() => { if (!isTscAdmin) { @@ -43,7 +49,6 @@ const ReviewSeedlots = () => { @@ -57,7 +62,12 @@ const ReviewSeedlots = () => { labelText="" placeholder="Enter seedlot number" value={seedlotNumber} - onChange={(e: React.ChangeEvent) => setSeedlotNumber(e.target.value)} + invalid={seedlotInputErr} + invalidText="Please, enter a seedlot number" + onChange={(e: React.ChangeEvent) => { + setSeedlotInputErr(false); + setSeedlotNumber(e.target.value); + }} onWheel={(e: React.ChangeEvent) => e.target.blur()} /> @@ -66,7 +76,14 @@ const ReviewSeedlots = () => { kind="primary" size="md" renderIcon={ArrowRight} - onClick={() => navigate(addParamToPath(ROUTES.SEEDLOT_DETAILS, seedlotNumber))} + onClick={() => { + if (seedlotNumber) { + navigate(addParamToPath(ROUTES.SEEDLOT_DETAILS, seedlotNumber)); + } else { + setSeedlotInputErr(true); + focusById('go-to-seedlot-input'); + } + }} > Go to seedlot diff --git a/frontend/src/views/Seedlot/SeedlotCreatedFeedback/index.tsx b/frontend/src/views/Seedlot/SeedlotCreatedFeedback/index.tsx index deeed8c18..484bd308f 100644 --- a/frontend/src/views/Seedlot/SeedlotCreatedFeedback/index.tsx +++ b/frontend/src/views/Seedlot/SeedlotCreatedFeedback/index.tsx @@ -95,7 +95,7 @@ const SeedlotCreatedFeedback = () => { size="lg" className="btn-scf" > - Seedlot's main screen + Seedlots main screen diff --git a/frontend/src/views/Seedlot/SeedlotDashboard/index.tsx b/frontend/src/views/Seedlot/SeedlotDashboard/index.tsx index 203913add..a37393f28 100644 --- a/frontend/src/views/Seedlot/SeedlotDashboard/index.tsx +++ b/frontend/src/views/Seedlot/SeedlotDashboard/index.tsx @@ -15,7 +15,6 @@ const SeedlotDashboard = () => ( diff --git a/frontend/src/views/Seedlot/SeedlotDashboard/styles.scss b/frontend/src/views/Seedlot/SeedlotDashboard/styles.scss index c3a2be1a0..69536d477 100644 --- a/frontend/src/views/Seedlot/SeedlotDashboard/styles.scss +++ b/frontend/src/views/Seedlot/SeedlotDashboard/styles.scss @@ -1,8 +1,7 @@ @use '@bcgov-nr/nr-theme/design-tokens/variables.scss' as vars; .seedlot-page { - padding-left: 0; - padding-right: 0; + padding: 0 0 2.5rem 0; .title-row { margin: 0; diff --git a/frontend/src/views/Seedlot/SeedlotDetails/FormProgress/index.tsx b/frontend/src/views/Seedlot/SeedlotDetails/FormProgress/index.tsx index 5144f33dd..f54e0421e 100644 --- a/frontend/src/views/Seedlot/SeedlotDetails/FormProgress/index.tsx +++ b/frontend/src/views/Seedlot/SeedlotDetails/FormProgress/index.tsx @@ -52,8 +52,7 @@ const FormProgress = ( queryFn: () => getAClassSeedlotProgressStatus(seedlotNumber ?? ''), enabled: getSeedlotQueryStatus === 'success' && (seedlotStatusCode === 'PND' || seedlotStatusCode === 'INC'), - refetchOnMount: true, - retry: 1 + refetchOnMount: true }); useEffect(() => { diff --git a/frontend/src/views/Seedlot/SeedlotDetails/index.tsx b/frontend/src/views/Seedlot/SeedlotDetails/index.tsx index ac38a4065..7fc9640df 100644 --- a/frontend/src/views/Seedlot/SeedlotDetails/index.tsx +++ b/frontend/src/views/Seedlot/SeedlotDetails/index.tsx @@ -78,7 +78,7 @@ const SeedlotDetails = () => { const vegCodeQuery = useQuery({ queryKey: ['vegetation-codes'], - queryFn: () => getVegCodes(), + queryFn: getVegCodes, select: (data) => getMultiOptList(data, true, true), staleTime: THREE_HOURS, cacheTime: THREE_HALF_HOURS @@ -104,6 +104,16 @@ const SeedlotDetails = () => { select: (data) => data.seedlot }); + const getActBtnLabel = (): string => { + if (isTscAdmin && seedlotData?.seedlotStatus === 'Submitted') { + return 'Review seedlot'; + } + if (seedlotData?.seedlotStatus === 'Submitted') { + return 'View your seedlot'; + } + return 'Edit seedlot form'; + }; + useEffect(() => { if (seedlotQuery.isFetched || seedlotQuery.isFetchedAfterMount || seedlotQuery.status === 'success') { covertToDisplayObj(seedlotQuery.data); @@ -154,14 +164,18 @@ const SeedlotDetails = () => { <> navigate(addParamToPath(ROUTES.SEEDLOT_A_CLASS_REGISTRATION, seedlotNumber ?? ''))} + titleBtnFunc={() => navigate(addParamToPath( + isTscAdmin && seedlotData?.seedlotStatus !== 'Submitted' + ? ROUTES.SEEDLOT_A_CLASS_REVIEW + : ROUTES.SEEDLOT_A_CLASS_REGISTRATION, + seedlotNumber ?? '' + ))} /> ) diff --git a/frontend/src/views/Seedlot/SeedlotDetails/styles.scss b/frontend/src/views/Seedlot/SeedlotDetails/styles.scss index ff4c920c2..14e811c35 100644 --- a/frontend/src/views/Seedlot/SeedlotDetails/styles.scss +++ b/frontend/src/views/Seedlot/SeedlotDetails/styles.scss @@ -26,10 +26,6 @@ margin-inline: 0; } - .seedlot-details-breadcrumb .#{vars.$bcgov-prefix}--link { - cursor: pointer; - } - .summary-title-flex-row { display: flex; flex-direction: row; diff --git a/frontend/src/views/Seedlot/SeedlotRegFormClassA/RegForm.tsx b/frontend/src/views/Seedlot/SeedlotRegFormClassA/RegForm.tsx index 346c6d488..83241cd99 100644 --- a/frontend/src/views/Seedlot/SeedlotRegFormClassA/RegForm.tsx +++ b/frontend/src/views/Seedlot/SeedlotRegFormClassA/RegForm.tsx @@ -9,7 +9,6 @@ import ExtractionAndStorage from '../../../components/SeedlotRegistrationSteps/E import ClassAContext from '../ContextContainerClassA/context'; import { RegFormProps } from '../ContextContainerClassA/definitions'; -import { tscAgencyObj, tscLocationCode } from '../ContextContainerClassA/constants'; const RegForm = ( { @@ -47,10 +46,7 @@ const RegForm = ( // Extraction and Storage case 5: return ( - + ); default: return null; diff --git a/frontend/src/views/Seedlot/SeedlotRegFormClassA/RegPage.tsx b/frontend/src/views/Seedlot/SeedlotRegFormClassA/RegPage.tsx index b24e65f54..87134766e 100644 --- a/frontend/src/views/Seedlot/SeedlotRegFormClassA/RegPage.tsx +++ b/frontend/src/views/Seedlot/SeedlotRegFormClassA/RegPage.tsx @@ -8,7 +8,8 @@ import { InlineLoading, InlineNotification, ActionableNotification, - Loading + Loading, + ProgressIndicatorSkeleton } from '@carbon/react'; import { ArrowRight } from '@carbon/icons-react'; import { useNavigate } from 'react-router-dom'; @@ -25,6 +26,8 @@ import { addParamToPath } from '../../../utils/PathUtils'; import ROUTES from '../../../routes/constants'; import { completeProgressConfig, smartSaveText } from '../ContextContainerClassA/constants'; +import './styles.scss'; + const RegPage = () => { const navigate = useNavigate(); const { @@ -46,7 +49,8 @@ const RegPage = () => { saveProgressStatus, isFetchingData, seedlotData, - getFormDraftQuery + getFormDraftQuery, + seedlotSpecies } = useContext(ClassAContext); const reloadFormDraft = () => getFormDraftQuery.refetch(); @@ -86,17 +90,23 @@ const RegPage = () => { - { - updateProgressStatus(e, formStep); - setStep((e - formStep)); - }} - /> + interactFunction={(e: number) => { + updateProgressStatus(e, formStep); + setStep((e - formStep)); + }} + /> + ) + : + } { @@ -214,9 +224,21 @@ const RegPage = () => { { - submitSeedlot.mutate(getSeedlotPayload(allStepData, seedlotNumber)); + submitSeedlot.mutate( + getSeedlotPayload(allStepData, seedlotNumber, seedlotSpecies.code) + ); + }} + setStepFn={(e: number) => { + updateProgressStatus(e, formStep); + setStep((e - formStep)); }} /> ) diff --git a/frontend/src/views/Seedlot/SeedlotRegFormClassA/styles.scss b/frontend/src/views/Seedlot/SeedlotRegFormClassA/styles.scss new file mode 100644 index 000000000..22a5c6041 --- /dev/null +++ b/frontend/src/views/Seedlot/SeedlotRegFormClassA/styles.scss @@ -0,0 +1,3 @@ +.seedlot-registration-page { + padding-bottom: 2.5rem; +} diff --git a/frontend/src/views/Seedlot/SeedlotReview/SeedlotReviewContent.tsx b/frontend/src/views/Seedlot/SeedlotReview/SeedlotReviewContent.tsx index cfc937308..c1c6f4db2 100644 --- a/frontend/src/views/Seedlot/SeedlotReview/SeedlotReviewContent.tsx +++ b/frontend/src/views/Seedlot/SeedlotReview/SeedlotReviewContent.tsx @@ -3,18 +3,17 @@ import { useNavigate, useParams } from 'react-router-dom'; import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'; import { AxiosError } from 'axios'; import { - Button, - FlexGrid, - Row, - Column, - Loading + Button, FlexGrid, Row, + Column, Loading, Modal } from '@carbon/react'; import { toast } from 'react-toastify'; import { - Edit, Save, Pending, Checkmark + Edit, Save, Pending, Checkmark, Warning } from '@carbon/icons-react'; +import { Beforeunload } from 'react-beforeunload'; +import { DateTime as luxon } from 'luxon'; -import { getSeedlotById } from '../../../api-service/seedlotAPI'; +import { getSeedlotById, putAClassSeedlotProgress } from '../../../api-service/seedlotAPI'; import { THREE_HALF_HOURS, THREE_HOURS } from '../../../config/TimeUnits'; import getVegCodes from '../../../api-service/vegetationCodeAPI'; import Breadcrumbs from '../../../components/Breadcrumbs'; @@ -55,6 +54,7 @@ import ClassAContext from '../ContextContainerClassA/context'; import { validateRegForm } from '../CreateAClass/utils'; import { getSeedlotPayload, + initOwnershipState, validateCollectionStep, validateExtractionStep, validateInterimStep, validateOrchardStep, validateOwnershipStep, validateParentStep, verifyCollectionStepCompleteness, verifyExtractionStepCompleteness, @@ -67,14 +67,29 @@ import { validateGeneticWorth } from './utils'; import { GenWorthValType } from './definitions'; +import { SaveStatusModalText } from './constants'; +import { completeProgressConfig, emptyOwnershipStep, initialProgressConfig } from '../ContextContainerClassA/constants'; +import { AllStepData } from '../ContextContainerClassA/definitions'; const SeedlotReviewContent = () => { const navigate = useNavigate(); + /** + * Back/Cancel button confirmation modal. + */ + const [isCancelModalOpen, setIsCancelModalOpen] = useState(false); + + /** + * Save and send to pending/approved + */ + const [isSaveStatusModalOpen, setIsSaveStatusModalOpen] = useState(false); + + const [statusToUpdateTo, setStatusToUpdateTo] = useState('PND'); + const { seedlotNumber } = useParams(); const vegCodeQuery = useQuery({ queryKey: ['vegetation-codes'], - queryFn: () => getVegCodes(), + queryFn: getVegCodes, staleTime: THREE_HOURS, cacheTime: THREE_HALF_HOURS }); @@ -122,7 +137,7 @@ const SeedlotReviewContent = () => { const { allStepData, genWorthVals, geoInfoVals, areaOfUseData, isFetchingData, seedlotData, - calculatedValues + calculatedValues, seedlotSpecies } = useContext(ClassAContext); const verifyFormData = (): boolean => { @@ -207,7 +222,7 @@ const SeedlotReviewContent = () => { }; const generatePaylod = (): TscSeedlotEditPayloadType => { - const regFormPayload = getSeedlotPayload(allStepData, seedlotNumber); + const regFormPayload = getSeedlotPayload(allStepData, seedlotNumber, seedlotSpecies.code); const applicantAndSeedlotInfo: SeedlotPatchPayloadType = { applicantEmailAddress: applicantData.email.value, @@ -338,9 +353,91 @@ 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 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 + }); + + /** + * Used for handling migrated pending seedlots from oracle. + */ + const createDraftForPendMutation = useMutation({ + mutationFn: () => ( + putAClassSeedlotProgress( + seedlotNumber!, + { + allStepData: getAllStepDataForPayload(), + progressStatus: initialProgressConfig, + // We don't know the previous revision count + revisionCount: -1 + } + ) + ), + onSuccess: () => { + 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. + */ const handleEditSaveBtn = () => { - // If the form is in read mode, then enable edit mode only. - if (isReadMode) { + // If the form is in read mode, then enable edit mode only, + // but wait for parent tree data to load first. + if (isReadMode && Object.keys(allStepData.parentTreeStep.allParentTreeData).length > 0) { setIsReadMode(!isReadMode); return; } @@ -350,11 +447,14 @@ const SeedlotReviewContent = () => { if (isFormDataValid) { const payload = generatePaylod(); - tscSeedlotMutation.mutate({ seedlotNum: seedlotNumber!, statusOnSave: 'SUB', payload }); + updateDraftMutation.mutate({ seedlotNum: seedlotNumber!, statusOnSave: 'SUB', payload }); setIsReadMode(!isReadMode); } }; + /** + * The handler for the send back to pending or approve buttons. + */ const handleSaveAndStatus = (statusOnSave: StatusOnSaveType) => { if (isReadMode) { statusOnlyMutaion.mutate({ seedlotNum: seedlotNumber!, statusOnSave }); @@ -362,15 +462,50 @@ const SeedlotReviewContent = () => { const isFormDataValid = verifyFormData(); if (isFormDataValid) { const payload = generatePaylod(); - tscSeedlotMutation.mutate({ seedlotNum: seedlotNumber!, statusOnSave, payload }); + updateDraftMutation.mutate({ seedlotNum: seedlotNumber!, statusOnSave, payload }); } } }; + const handleCancelClick = () => { + if (isReadMode) { + navigate(`/seedlots/details/${seedlotNumber}`); + } else { + setIsCancelModalOpen(true); + } + }; + + const closeCancelModal = () => { + setIsCancelModalOpen(false); + }; + + const closeSaveStatusModal = () => { + setIsSaveStatusModalOpen(false); + }; + + const openSaveStatusModal = (status: StatusOnSaveType) => { + setStatusToUpdateTo(status); + setIsSaveStatusModalOpen(true); + }; + + /** + * Discard changes without saving. + */ + const discardChanges = () => { + setIsReadMode(true); + queryClient.refetchQueries(['seedlots', seedlotNumber]); + queryClient.refetchQueries(['seedlot-full-form', seedlotNumber]); + closeCancelModal(); + }; + return ( + + @@ -563,7 +705,7 @@ const SeedlotReviewContent = () => { @@ -572,6 +714,116 @@ const SeedlotReviewContent = () => { ) : null } + { + // this and its related code such as createDraftForPendMutation + // needs to be deleted in the future + (luxon.local().setZone('America/Vancouver').toISODate() ?? '' < '2024-08-17') + ? ( + + + + + + ) + : null + } + + {/* Cancel Confirm Modal */} + + +
+
+ Any changes you made will be discarded unless saved. +
+
+ + +
+
+
+ + {/* Save and update status confirm modal */} + + +
+ { + statusToUpdateTo === 'PND' + ? ( +
+ {SaveStatusModalText.pendingHeader} + {SaveStatusModalText.pendingBody} +
+ ) + : ( +
{SaveStatusModalText.approveHeader}
+ ) + } +
+ + { + statusToUpdateTo === 'PND' + ? ( + + ) + : ( + + ) + } +
+
+
+ { + !isReadMode + && ( + event.preventDefault()} /> + ) + }
); }; diff --git a/frontend/src/views/Seedlot/SeedlotReview/constants.ts b/frontend/src/views/Seedlot/SeedlotReview/constants.tsx similarity index 74% rename from frontend/src/views/Seedlot/SeedlotReview/constants.ts rename to frontend/src/views/Seedlot/SeedlotReview/constants.tsx index b441de112..958cd97ca 100644 --- a/frontend/src/views/Seedlot/SeedlotReview/constants.ts +++ b/frontend/src/views/Seedlot/SeedlotReview/constants.tsx @@ -1,3 +1,4 @@ +import React from 'react'; import { GenWorthValType, GeoInfoValType } from './definitions'; export const INITIAL_GEO_INFO_VALS: GeoInfoValType = { @@ -105,3 +106,25 @@ export const INITIAL_GEN_WORTH_VALS: GenWorthValType = { value: '' } }; + +export const SaveStatusModalText = { + pendingHeader: ( +
+ Send seedlot back to pending +
+ ), + pendingBody: ( +

+ Confirm this action if there is something wrong with this seedlot, + this will change the seedlot status from submitted back to pending, + allowing the applicant agency to edit the form again. +

+ ), + approveHeader: ( +
+ Are you sure you want to approve this seedlot? +
+ This seedlot status will change from submitted to approved. +
+ ) +}; diff --git a/frontend/src/views/Seedlot/SeedlotReview/styles.scss b/frontend/src/views/Seedlot/SeedlotReview/styles.scss index 5199b2381..753ab2092 100644 --- a/frontend/src/views/Seedlot/SeedlotReview/styles.scss +++ b/frontend/src/views/Seedlot/SeedlotReview/styles.scss @@ -78,9 +78,67 @@ .action-button-row { padding: 2.5rem 0; - .#{vars.$bcgov-prefix}--btn{ + + .#{vars.$bcgov-prefix}--btn { max-width: none; width: -webkit-fill-available; } } + + + .cancel-confirm-modal, + .save-and-update-confirm-modal { + .#{vars.$bcgov-prefix}--modal-header__heading { + font-size: 0.75rem; + color: var(--#{vars.$bcgov-prefix}-text-secondary, #606062); + } + + .#{vars.$bcgov-prefix}--modal-content { + padding-bottom: 1rem; + } + } + + .cancel-confirm-modal { + .modal-content { + display: flex; + flex-direction: column; + + .modal-button-group { + margin-top: 3rem; + display: flex; + flex-direction: row; + justify-content: flex-end; + + .#{vars.$bcgov-prefix}--btn { + min-width: 11rem; + margin-left: 0.5rem; + } + } + } + } + + .save-and-update-confirm-modal { + .modal-button-group { + margin-top: 3rem; + display: flex; + flex-direction: row; + justify-content: space-between; + + .#{vars.$bcgov-prefix}--btn { + max-width: none; + width: 49%; + } + } + } + + .modal-header { + @include type.type-style('heading-03'); + } + + .modal-text { + p { + padding-top: 1rem; + @include type.type-style('body-01'); + } + } } diff --git a/frontend/vite.config.ts b/frontend/vite.config.ts index 6eaaf4d82..b96515973 100644 --- a/frontend/vite.config.ts +++ b/frontend/vite.config.ts @@ -42,6 +42,9 @@ export default defineConfig(({ mode }: ConfigEnv) => { ignored: ['**/coverage/**', '**/cypress-coverage/**', '**/cypress/**'] } }, + preview: { + port: 3000 + }, resolve: { alias: { '@': fileURLToPath(new URL('./src', import.meta.url)) diff --git a/legacy_translated/SPR_SEEDLOT.ts b/legacy_translated/SPR_SEEDLOT.ts new file mode 100644 index 000000000..0971a7710 --- /dev/null +++ b/legacy_translated/SPR_SEEDLOT.ts @@ -0,0 +1,7215 @@ +import * as spr_seedlot_geometry from './SPR_SEEDLOT_GEOMETRY'; +import * as spr_spatial_utils from './SPR_SPATIAL_UTILS'; + +let g_error_message: string | null; // VARCHAR2(4000); +let g_seed_plan_zone_code: string | null; +let gb_seed_plan_zone_code: string; // VARCHAR2(1); +let g_seed_plan_zone_id: string | null; +let gb_seed_plan_zone_id: string; // VARCHAR2(1); +let g_applicant_client_locn: string | null; +let gb_applicant_client_locn: string; // VARCHAR2(1); +let g_applicant_client_number: string | null; +let gb_applicant_client_number: string; // VARCHAR2(1); +let g_applicant_email_address: string | null; +let gb_applicant_email_address: string; // VARCHAR2(1); +let g_bc_source_ind: string | null; +let gb_bc_source_ind: string; // VARCHAR2(1) +let g_biotech_processes_ind: string | null; +let gb_biotech_processes_ind: string; // VARCHAR2(1) +let g_collection_area_radius: number | null; +let gb_collection_area_radius: string; // VARCHAR2(1) +let g_collection_bgc_ind: string | null; +let gb_collection_bgc_ind: string; // VARCHAR2(1) +let g_collection_spz_ind: string | null; +let gb_collection_spz_ind: string; // VARCHAR2(1) +let g_coll_standard_met_ind: string | null; +let gb_coll_standard_met_ind: string; // VARCHAR2(1) +let g_cone_collection_method2_cd: string | null; +let gb_cone_collection_method2_cd: string; // VARCHAR2(1); +let g_contaminant_pollen_bv: number | null; +let gb_contaminant_pollen_bv: string; // VARCHAR2(1) +let g_controlled_cross_ind: string | null; +let gb_controlled_cross_ind: string; // VARCHAR2(1) +let g_declared_userid: string | null; +let gb_declared_userid: string; // VARCHAR2(1) +let g_declared_timestamp: Date | null; +let gb_declared_timestamp: string; // VARCHAR2(1) +let g_female_gametic_mthd_code: string | null; +let gb_female_gametic_mthd_code: string; // VARCHAR2(1); +let g_latitude_sec_max: number | null; +let gb_latitude_sec_max: string; // VARCHAR2(1) +let g_latitude_sec_min: number | null; +let gb_latitude_sec_min: string; // VARCHAR2(1) +let g_longitude_sec_max: number | null; +let gb_longitude_sec_max: string; // VARCHAR2(1) +let g_longitude_sec_min: number | null; +let gb_longitude_sec_min: string; // VARCHAR2(1) +let g_male_gametic_mthd_code: string | null; +let gb_male_gametic_mthd_code: string; // VARCHAR2(1) +let g_orchard_comment: string | null; // seedlot.orchard_comment%TYPE; +let gb_orchard_comment: string; // VARCHAR2(1) +let g_orchard_contamination_pct: number | null; +let gb_orchard_contamination_pct: string; // VARCHAR2(1); +let g_pollen_contamination_ind: string | null; +let gb_pollen_contamination_ind: string; // VARCHAR2(1); +let g_pollen_contam_mthd_code: string | null; +let gb_pollen_contam_mthd_code: string; // VARCHAR2(1); +let g_pollen_contamination_pct: number | null; +let gb_pollen_contamination_pct: string; // VARCHAR2(1); +let g_provenance_id: number | null; +let gb_provenance_id: string; // VARCHAR2(1) +let g_secondary_orchard_id: string | null; +let gb_secondary_orchard_id: string; // VARCHAR2(1) +let g_seed_plan_unit_id: number | null; +let gb_seed_plan_unit_id: string; // VARCHAR2(1) +let g_seed_store_client_locn: string | null; +let gb_seed_store_client_locn: string; // VARCHAR2(1) +let g_seed_store_client_number: string | null; +let gb_seed_store_client_number: string; // VARCHAR2(1); +let g_seedlot_source_code: string | null; +let gb_seedlot_source_code: string; // VARCHAR2(1) +let g_smp_mean_bv_AD: number | null; +let gb_smp_mean_bv_AD: string; // VARCHAR2(1) +let g_smp_mean_bv_DFS: number | null; +let gb_smp_mean_bv_DFS: string; // VARCHAR2(1) +let g_smp_mean_bv_DFU: number | null; +let gb_smp_mean_bv_DFU: string; // VARCHAR2(1) +let g_smp_mean_bv_DFW: number | null; +let gb_smp_mean_bv_DFW: string; // VARCHAR2(1) +let g_smp_mean_bv_DSB: number | null; +let gb_smp_mean_bv_DSB: string; // VARCHAR2(1) +let g_smp_mean_bv_DSC: number | null; +let gb_smp_mean_bv_DSC: string; // VARCHAR2(1) +let g_smp_mean_bv_DSG: number | null; +let gb_smp_mean_bv_DSG: string; // VARCHAR2(1) +let g_smp_mean_bv_GVO: number | null; +let gb_smp_mean_bv_GVO: string; // VARCHAR2(1) +let g_smp_mean_bv_IWS: number | null; +let gb_smp_mean_bv_IWS: string; // VARCHAR2(1) +let g_smp_mean_bv_WDU: number | null; +let gb_smp_mean_bv_WDU: string; // VARCHAR2(1) +let g_smp_mean_bv_WVE: number | null; +let gb_smp_mean_bv_WVE: string; // VARCHAR2(1) +let g_smp_mean_bv_WWD: number | null; +let gb_smp_mean_bv_WWD: string; // VARCHAR2(1) +let g_smp_parents_outside: number | null; +let gb_smp_parents_outside: string; // VARCHAR2(1) +let g_smp_success_pct: number | null; +let gb_smp_success_pct: string; // VARCHAR2(1) +let g_temporary_storage_end_date: Date | null; +let gb_temporary_storage_end_date: string; // VARCHAR2(1); +let g_temporary_storage_start_dt: Date | null; +let gb_temporary_storage_start_dt: string; // VARCHAR2(1); +let g_total_parent_trees: number | null; +let gb_total_parent_trees: string; // VARCHAR2(1) +let g_latitude_seconds: number | null; +let gb_latitude_seconds: string; // VARCHAR2(1) +let g_longitude_seconds: number | null; +let gb_longitude_seconds: string; // VARCHAR2(1) +let g_collection_lat_sec: number | null; +let gb_collection_lat_sec: string; // VARCHAR2(1) +let g_collection_long_sec: number | null; +let gb_collection_long_sec: string; // VARCHAR2(1) +let g_seedlot_number: string | null; +let gb_seedlot_number: string; // VARCHAR2(1) +let g_seedlot_status_code: string | null; +let gb_seedlot_status_code: string; // VARCHAR2(1) +let g_vegetation_code: string | null; +let gb_vegetation_code: string; // VARCHAR2(1) +let g_genetic_class_code: string | null; +let gb_genetic_class_code: string; // VARCHAR2(1) +let g_collection_source_code: string | null; +let gb_collection_source_code: string; // VARCHAR2(1) +let g_superior_prvnc_ind: string | null; +let gb_superior_prvnc_ind: string; // VARCHAR2(1) +let g_org_unit_no: number | null; // seedlot.org_unit_no%TYPE; +let gb_org_unit_no: string; // VARCHAR2(1) +let g_registered_seed_ind: string | null; +let gb_registered_seed_ind: string; // VARCHAR2(1) +let g_to_be_registrd_ind: string | null; +let gb_to_be_registrd_ind: string; // VARCHAR2(1) +let g_registered_date: Date | null; +let gb_registered_date: string; // VARCHAR2(1) +let g_fs721a_signed_ind: string | null; +let gb_fs721a_signed_ind: string; // VARCHAR2(1) +let g_nad_datum_code: string | null; +let gb_nad_datum_code: string; // VARCHAR2(1) +let g_utm_zone: number | null; +let gb_utm_zone: string; // VARCHAR2(1) +let g_utm_easting: number | null; +let gb_utm_easting: string; // VARCHAR2(1) +let g_utm_northing: number | null; +let gb_utm_northing: string; // VARCHAR2(1) +let g_longitude_degrees: number | null; +let gb_longitude_degrees: string; // VARCHAR2(1) +let g_longitude_minutes: number | null; +let gb_longitude_minutes: string; // VARCHAR2(1) +let g_longitude_deg_min: number | null; +let gb_longitude_deg_min: string; // VARCHAR2(1) +let g_longitude_min_min: number | null; +let gb_longitude_min_min: string; // VARCHAR2(1) +let g_longitude_deg_max: number | null; +let gb_longitude_deg_max: string; // VARCHAR2(1) +let g_longitude_min_max: number | null; +let gb_longitude_min_max: string; // VARCHAR2(1) +let g_latitude_degrees: number | null; +let gb_latitude_degrees: string; // VARCHAR2(1) +let g_latitude_minutes: number | null; +let gb_latitude_minutes: string; // VARCHAR2(1) +let g_latitude_deg_min: number | null; +let gb_latitude_deg_min: string; // VARCHAR2(1) +let g_latitude_min_min: number | null; +let gb_latitude_min_min: string; // VARCHAR2(1) +let g_latitude_deg_max: number | null; +let gb_latitude_deg_max: string; // VARCHAR2(1) +let g_latitude_min_max: number | null; +let gb_latitude_min_max: string; // VARCHAR2(1) +let g_seed_coast_area_code: string | null; +let gb_seed_coast_area_code: string; // VARCHAR2(1) +let g_elevation: number | null; +let gb_elevation: string; // VARCHAR2(1) +let g_elevation_min: number | null; +let gb_elevation_min: string; // VARCHAR2(1) +let g_elevation_max: number | null; +let gb_elevation_max: string; // VARCHAR2(1) +let g_orchard_id: string | null; +let gb_orchard_id: string; // VARCHAR2(1) +let g_collection_locn_desc: string | null; +let gb_collection_locn_desc: string; // VARCHAR2(1) +let g_collection_cli_number: string | null; +let gb_collection_cli_number: string; // VARCHAR2(1) +let g_collection_cli_locn_cd: string | null; +let gb_collection_cli_locn_cd: string; // VARCHAR2(1) +let g_collection_start_date: Date | null; +let gb_collection_start_date: string; // VARCHAR2(1) +let g_collection_end_date: Date | null; +let gb_collection_end_date: string; // VARCHAR2(1) +let g_cone_collection_method_cd: string | null; +let gb_cone_collection_method_cd: string; // VARCHAR2(1); +let g_no_of_containers: number | null; +let gb_no_of_containers: string; // VARCHAR2(1) +let g_clctn_volume: number | null; +let gb_clctn_volume: string; // VARCHAR2(1) +let g_vol_per_container: number | null; +let gb_vol_per_container: string; // VARCHAR2(1) +let g_nmbr_trees_from_code: string | null; +let gb_nmbr_trees_from_code: string; // VARCHAR2(1) +let g_coancestry: number | null; +let gb_coancestry: string; // VARCHAR2(1) +let g_effective_pop_size: number | null; +let gb_effective_pop_size: string; // VARCHAR2(1) +let g_original_seed_qty: number | null; +let gb_original_seed_qty: string; // VARCHAR2(1) +let g_interm_strg_client_number: string | null; +let gb_interm_strg_client_number: string; // VARCHAR2(1); +let g_interm_strg_client_locn: string | null; +let gb_interm_strg_client_locn: string; // VARCHAR2(1); +let g_interm_strg_st_date: Date | null; +let gb_interm_strg_st_date: string; // VARCHAR2(1) +let g_interm_strg_end_date: Date | null; +let gb_interm_strg_end_date: string; // VARCHAR2(1) +let g_interm_facility_code: string | null; +let gb_interm_facility_code: string; // VARCHAR2(1) +let g_extraction_st_date: Date | null; +let gb_extraction_st_date: string; // VARCHAR2(1) +let g_extraction_end_date: Date | null; +let gb_extraction_end_date: string; // VARCHAR2(1) +let g_extraction_volume: number | null; +let gb_extraction_volume: string; // VARCHAR2(1) +let g_extrct_cli_number: string | null; +let gb_extrct_cli_number: string; // VARCHAR2(1) +let g_extrct_cli_locn_cd: string | null; +let gb_extrct_cli_locn_cd: string; // VARCHAR2(1) +let g_stored_cli_number: string | null; +let gb_stored_cli_number: string; // VARCHAR2(1) +let g_stored_cli_locn_cd: string | null; +let gb_stored_cli_locn_cd: string; // VARCHAR2(1) +let g_lngterm_strg_st_date: Date | null; +let gb_lngterm_strg_st_date: string; // VARCHAR2(1) +let g_historical_tsr_date: Date | null; +let gb_historical_tsr_date: string; // VARCHAR2(1) +let g_collection_lat_deg: number | null; +let gb_collection_lat_deg: string; // VARCHAR2(1) +let g_collection_lat_min: number | null; +let gb_collection_lat_min: string; // VARCHAR2(1) +let g_collection_latitude_code: string | null; +let gb_collection_latitude_code: string; // VARCHAR2(1); +let g_collection_long_deg: number | null; // seedlot.collection_long_deg%TYPE; +let gb_collection_long_deg: string; // VARCHAR2(1) +let g_collection_long_min: number | null; // seedlot.collection_long_min%TYPE; +let gb_collection_long_min: string; // VARCHAR2(1) +let g_collection_longitude_code: string | null; // seedlot.collection_longitude_code%TYPE; +let gb_collection_longitude_code: string; // VARCHAR2(1); +let g_collection_elevation: number | null; // seedlot.collection_elevation%TYPE; +let gb_collection_elevation: string; // VARCHAR2(1) +let g_collection_elevation_min: number | null; // seedlot.collection_elevation_min%TYPE; +let gb_collection_elevation_min: string; // VARCHAR2(1); +let g_collection_elevation_max: number | null; // seedlot.collection_elevation_max%TYPE; +let gb_collection_elevation_max: string; // VARCHAR2(1); +let g_entry_timestamp: Date | null; // seedlot.entry_timestamp%TYPE; +let gb_entry_timestamp: string; // VARCHAR2(1) +let g_entry_userid: string | null; // seedlot.entry_userid%TYPE; +let gb_entry_userid: string; // VARCHAR2(1) +let g_update_timestamp: Date | null; // seedlot.update_timestamp%TYPE; +let gb_update_timestamp: string; // VARCHAR2(1) +let g_update_userid: string | null; // seedlot.update_userid%TYPE; +let gb_update_userid: string; // VARCHAR2(1) +let g_approved_timestamp: Date | null; // seedlot.approved_timestamp%TYPE; +let gb_approved_timestamp: string; // VARCHAR2(1) +let g_approved_userid: string | null; // seedlot.approved_userid%TYPE; +let gb_approved_userid: string; // VARCHAR2(1) +let g_revision_count: number | null; // seedlot.revision_count%TYPE; +let gb_revision_count: string; // VARCHAR2(1) +let g_interm_strg_locn: string | null; // seedlot.interm_strg_locn%TYPE; +let gb_interm_strg_locn: string; // VARCHAR2(1) +let g_interm_strg_cmt: string | null; // seedlot.interm_strg_cmt%TYPE; +let gb_interm_strg_cmt: string; // VARCHAR2(1) +let g_ownership_comment: string | null; // seedlot.ownership_comment%TYPE; +let gb_ownership_comment: string; // VARCHAR2(1) +let g_cone_seed_desc: string | null; // seedlot.cone_seed_desc%TYPE; +let gb_cone_seed_desc: string; // VARCHAR2(1) +let g_extraction_comment: string | null; // seedlot.extraction_comment%TYPE; +let gb_extraction_comment: string; // VARCHAR2(1) +let g_seedlot_comment: string | null; // seedlot.seedlot_comment%TYPE; +let gb_seedlot_comment: string; // VARCHAR2(1) +let g_bgc_zone_code: string | null; // seedlot.bgc_zone_code%TYPE; +let gb_bgc_zone_code: string; // VARCHAR2(1) +let g_bgc_subzone_code: string | null; // seedlot.bgc_subzone_code%TYPE; +let gb_bgc_subzone_code: string; // VARCHAR2(1) +let g_variant: string | null; // seedlot.variant%TYPE; +let gb_variant: string; // VARCHAR2(1) +let g_bec_version_id: string | null; // seedlot.bec_version_id%TYPE; +let gb_bec_version_id: string; // VARCHAR2(1) +let g_gw_AD: number | null; // seedlot_genetic_worth.genetic_worth_rtng%TYPE; +let g_gw_DFS: number | null; // seedlot_genetic_worth.genetic_worth_rtng%TYPE; +let g_gw_DFU: number | null; // seedlot_genetic_worth.genetic_worth_rtng%TYPE; +let g_gw_DFW: number | null; // seedlot_genetic_worth.genetic_worth_rtng%TYPE; +let g_gw_DSB: number | null; // seedlot_genetic_worth.genetic_worth_rtng%TYPE; +let g_gw_DSC: number | null; // seedlot_genetic_worth.genetic_worth_rtng%TYPE; +let g_gw_DSG: number | null; // seedlot_genetic_worth.genetic_worth_rtng%TYPE; +let g_gw_GVO: number | null; // seedlot_genetic_worth.genetic_worth_rtng%TYPE; +let g_gw_IWS: number | null; // seedlot_genetic_worth.genetic_worth_rtng%TYPE; +let g_gw_WDU: number | null; // seedlot_genetic_worth.genetic_worth_rtng%TYPE; +let g_gw_WVE: number | null; // seedlot_genetic_worth.genetic_worth_rtng%TYPE; +let g_gw_WWD: number | null; // seedlot_genetic_worth.genetic_worth_rtng%TYPE; +let g_spz_list: string | null; // VARCHAR2(100); +let g_spz_id_list: string | null; // VARCHAR2(100); +let g_tested_parent_trees_pct_AD: number | null; // NUMBER(3); +let g_tested_parent_trees_pct_DFS: number | null; // NUMBER(3); +let g_tested_parent_trees_pct_DFU: number | null; // NUMBER(3); +let g_tested_parent_trees_pct_DFW: number | null; // NUMBER(3); +let g_tested_parent_trees_pct_DSB: number | null; // NUMBER(3); +let g_tested_parent_trees_pct_DSC: number | null; // NUMBER(3); +let g_tested_parent_trees_pct_DSG: number | null; // NUMBER(3); +let g_tested_parent_trees_pct_GVO: number | null; // NUMBER(3); +let g_tested_parent_trees_pct_IWS: number | null; // NUMBER(3); +let g_tested_parent_trees_pct_WDU: number | null; // NUMBER(3); +let g_tested_parent_trees_pct_WVE: number | null; // NUMBER(3); +let g_tested_parent_trees_pct_WWD: number | null; // NUMBER(3); +let g_tested_parent_trees_pct: number | null; // NUMBER(3); +let g_untested_parent_trees_pct: number | null; // NUMBER(3); +let g_is_lot_split: boolean | null; // BOOLEAN; +//-- Record to hold previous values +let r_previous: object; // seedlot%ROWTYPE; +//-- Record to hold parent tree contribution calculations + +function nvlNumber(variable: number | null, value: number): number { + if (variable == null) { + return value; + } + return variable; +} + +function nvlString(variable: string | null, value: string): string { + if (variable == null) { + return value; + } + return variable; +} + +function rTrim(value: string | null, search: string): string { + const strVal = nvlString(value, ''); + if (strVal.lastIndexOf(search) == strVal.length -1) { + return strVal.substring(0, strVal.length-1); + } + return strVal; +} + +function getFakeResultFromSql(sql: string, arg: Type): Type { + return arg; +} + +interface t_pt_calc { + collection_elevation: number; // seedlot.collection_elevation%TYPE + collection_elevation_min: number; // seedlot.collection_elevation_min%TYPE + collection_elevation_max: number; //seedlot.collection_elevation_max%TYPE + collection_lat_deg: number; // seedlot.collection_lat_deg%TYPE + collection_lat_min: number; // seedlot.collection_lat_min%TYPE + collection_lat_sec: number; // seedlot.collection_lat_sec%TYPE + collection_long_deg: number; // seedlot.collection_long_deg%TYPE + collection_long_min: number; // seedlot.collection_long_min%TYPE + collection_long_sec: number; // seedlot.collection_long_sec%TYPE + smp_mean_bv_AD: number; // seedlot_parent_tree_smp_mix.smp_mix_value%TYPE + smp_mean_bv_DFS: number; // seedlot_parent_tree_smp_mix.smp_mix_value%TYPE + smp_mean_bv_DFU: number; // seedlot_parent_tree_smp_mix.smp_mix_value%TYPE + smp_mean_bv_DFW: number; // seedlot_parent_tree_smp_mix.smp_mix_value%TYPE + smp_mean_bv_DSB: number; // seedlot_parent_tree_smp_mix.smp_mix_value%TYPE + smp_mean_bv_DSC: number; // seedlot_parent_tree_smp_mix.smp_mix_value%TYPE + smp_mean_bv_DSG: number; // seedlot_parent_tree_smp_mix.smp_mix_value%TYPE + smp_mean_bv_GVO: number; // seedlot_parent_tree_smp_mix.smp_mix_value%TYPE + smp_mean_bv_IWS: number; // seedlot_parent_tree_smp_mix.smp_mix_value%TYPE + smp_mean_bv_WDU: number; // seedlot_parent_tree_smp_mix.smp_mix_value%TYPE + smp_mean_bv_WVE: number; // seedlot_parent_tree_smp_mix.smp_mix_value%TYPE + smp_mean_bv_WWD: number; // seedlot_parent_tree_smp_mix.smp_mix_value%TYPE + smp_success_pct: number; // seedlot.smp_success_pct%TYPE + orchard_contamination_pct: number; // seedlot.orchard_contamination_pct%TYPE + effective_pop_size: number; // seedlot.effective_pop_size%TYPE + gw_AD : number; // seedlot_genetic_worth.genetic_worth_rtng%TYPE + gw_DFS: number; // seedlot_genetic_worth.genetic_worth_rtng%TYPE + gw_DFU: number; // seedlot_genetic_worth.genetic_worth_rtng%TYPE + gw_DFW: number; // seedlot_genetic_worth.genetic_worth_rtng%TYPE + gw_DSB: number; // seedlot_genetic_worth.genetic_worth_rtng%TYPE + gw_DSC: number; // seedlot_genetic_worth.genetic_worth_rtng%TYPE + gw_DSG: number; // seedlot_genetic_worth.genetic_worth_rtng%TYPE + gw_GVO: number; // seedlot_genetic_worth.genetic_worth_rtng%TYPE + gw_IWS: number; // seedlot_genetic_worth.genetic_worth_rtng%TYPE + gw_WDU: number; // seedlot_genetic_worth.genetic_worth_rtng%TYPE + gw_WVE: number; // seedlot_genetic_worth.genetic_worth_rtng%TYPE + gw_WWD: number; // seedlot_genetic_worth.genetic_worth_rtng%TYPE + total_parent_trees: number; // seedlot.total_parent_trees%TYPE + pct_tested_parent_trees_AD : number; // NUMBER(3) + pct_tested_parent_trees_DFS: number; // NUMBER(3) + pct_tested_parent_trees_DFU: number; // NUMBER(3) + pct_tested_parent_trees_DFW: number; // NUMBER(3) + pct_tested_parent_trees_DSB: number; // NUMBER(3) + pct_tested_parent_trees_DSC: number; // NUMBER(3) + pct_tested_parent_trees_DSG: number; // NUMBER(3) + pct_tested_parent_trees_GVO: number; // NUMBER(3) + pct_tested_parent_trees_IWS: number; // NUMBER(3) + pct_tested_parent_trees_WDU: number; // NUMBER(3) + pct_tested_parent_trees_WVE: number; // NUMBER(3) + pct_tested_parent_trees_WWD: number; // NUMBER(3) + pct_tested_parent_trees: number; // NUMBER(3) + pct_untested_parent_trees: number; // NUMBER(3)); +} + +let r_pt_contrib: t_pt_calc | null; +const CONST_CLASS_B_LOTNUM_MIN: string; // CONSTANT VARCHAR2(5) = '53000'; +const CONST_CLASS_B_LOTNUM_MAX: string; // CONSTANT VARCHAR2(5) = '59999'; +const CONST_CLASS_A_LOTNUM_MIN: string; // CONSTANT VARCHAR2(5) = '63000'; +const CONST_CLASS_A_LOTNUM_MAX: string; // CONSTANT VARCHAR2(5) = '69999'; +const CONST_CLASS_B_COPY_MIN : string; // CONSTANT VARCHAR2(5) = '52000'; +const CONST_CLASS_B_COPY_MAX : string; // CONSTANT VARCHAR2(5) = '52999'; +const CONST_CLASS_A_COPY_MIN : string; // CONSTANT VARCHAR2(5) = '62000'; +const CONST_CLASS_A_COPY_MAX : string; // CONSTANT VARCHAR2(5) = '62999'; + +/* + * Procedure: get_previous_seedlot_values + * Purpose: Set the previous seedlot values used for validations. + * Pass p_force=true to force get of latest values when + * they have already been retrieved. + */ +function get_previous_seedlot_values(p_force: boolean = false) { + // Previous: SELECT * FROM seedlot WHERE seedlot_number = g_seedlot_number; + // --if record is empty or caller specified FORCE option + if (r_previous.seedlot_number == null || p_force) { + // OPEN c_previous; + // FETCH c_previous INTO r_previous; + // CLOSE c_previous; + // --Don't populate old values into vars if seedlot was not found + if (g_seedlot_number == r_previous.seedlot_number) { + // --Populate vars with previous values (don't get revision_count) + g_seed_plan_zone_code = r_previous.seed_plan_zone_code; + g_applicant_client_locn = r_previous.applicant_client_locn; + g_applicant_client_number = r_previous.applicant_client_number; + g_applicant_email_address = r_previous.applicant_email_address; + g_bc_source_ind = r_previous.bc_source_ind; + g_biotech_processes_ind = r_previous.biotech_processes_ind; + g_collection_area_radius = r_previous.collection_area_radius; + g_collection_bgc_ind = r_previous.collection_bgc_ind; + g_collection_spz_ind = r_previous.collection_seed_plan_zone_ind; + g_coll_standard_met_ind = r_previous.collection_standard_met_ind; + g_cone_collection_method2_cd = r_previous.cone_collection_method2_code; + g_contaminant_pollen_bv = r_previous.contaminant_pollen_bv; + g_controlled_cross_ind = r_previous.controlled_cross_ind; + g_declared_userid = r_previous.declared_userid; + g_declared_timestamp = r_previous.declared_timestamp; + g_female_gametic_mthd_code = r_previous.female_gametic_mthd_code; + g_latitude_sec_max = r_previous.latitude_sec_max; + g_latitude_sec_min = r_previous.latitude_sec_min; + g_longitude_sec_max = r_previous.longitude_sec_max; + g_longitude_sec_min = r_previous.longitude_sec_min; + g_male_gametic_mthd_code = r_previous.male_gametic_mthd_code; + g_orchard_comment = r_previous.orchard_comment; + g_orchard_contamination_pct = r_previous.orchard_contamination_pct; + g_pollen_contamination_ind = r_previous.pollen_contamination_ind; + g_pollen_contam_mthd_code = r_previous.pollen_contamination_mthd_code; + g_pollen_contamination_pct = r_previous.pollen_contamination_pct; + g_provenance_id = r_previous.provenance_id; + g_secondary_orchard_id = r_previous.secondary_orchard_id; + g_seed_plan_unit_id = r_previous.seed_plan_unit_id; + g_seed_store_client_locn = r_previous.seed_store_client_locn; + g_seed_store_client_number = r_previous.seed_store_client_number; + g_seedlot_source_code = r_previous.seedlot_source_code; + g_smp_mean_bv_GVO = r_previous.smp_mean_bv_growth; + g_smp_parents_outside = r_previous.smp_parents_outside; + g_smp_success_pct = r_previous.smp_success_pct; + g_temporary_storage_end_date = r_previous.temporary_storage_end_date; + g_temporary_storage_start_dt = r_previous.temporary_storage_start_date; + g_total_parent_trees = r_previous.total_parent_trees; + g_latitude_seconds = r_previous.latitude_seconds; + g_longitude_seconds = r_previous.longitude_seconds; + g_collection_lat_sec = r_previous.collection_lat_sec; + g_collection_long_sec = r_previous.collection_long_sec; + g_seedlot_status_code = r_previous.seedlot_status_code; + g_vegetation_code = r_previous.vegetation_code; + g_genetic_class_code = r_previous.genetic_class_code; + g_collection_source_code = r_previous.collection_source_code; + g_superior_prvnc_ind = r_previous.superior_prvnc_ind; + g_org_unit_no = r_previous.org_unit_no; + g_registered_seed_ind = r_previous.registered_seed_ind; + g_to_be_registrd_ind = r_previous.to_be_registrd_ind; + g_registered_date = r_previous.registered_date; + g_fs721a_signed_ind = r_previous.fs721a_signed_ind; + g_nad_datum_code = r_previous.nad_datum_code; + g_utm_zone = r_previous.utm_zone; + g_utm_easting = r_previous.utm_easting; + g_utm_northing = r_previous.utm_northing; + g_longitude_degrees = r_previous.longitude_degrees; + g_longitude_minutes = r_previous.longitude_minutes; + g_longitude_deg_min = r_previous.longitude_deg_min; + g_longitude_min_min = r_previous.longitude_min_min; + g_longitude_deg_max = r_previous.longitude_deg_max; + g_longitude_min_max = r_previous.longitude_min_max; + g_latitude_degrees = r_previous.latitude_degrees; + g_latitude_minutes = r_previous.latitude_minutes; + g_latitude_deg_min = r_previous.latitude_deg_min; + g_latitude_min_min = r_previous.latitude_min_min; + g_latitude_deg_max = r_previous.latitude_deg_max; + g_latitude_min_max = r_previous.latitude_min_max; + g_seed_coast_area_code = r_previous.seed_coast_area_code; + g_elevation = r_previous.elevation; + g_elevation_min = r_previous.elevation_min; + g_elevation_max = r_previous.elevation_max; + g_orchard_id = r_previous.orchard_id; + g_collection_locn_desc = r_previous.collection_locn_desc; + g_collection_cli_number = r_previous.collection_cli_number; + g_collection_cli_locn_cd = r_previous.collection_cli_locn_cd; + g_collection_start_date = r_previous.collection_start_date; + g_collection_end_date = r_previous.collection_end_date; + g_cone_collection_method_cd = r_previous.cone_collection_method_code; + g_no_of_containers = r_previous.no_of_containers; + g_clctn_volume = r_previous.clctn_volume; + g_vol_per_container = r_previous.vol_per_container; + g_nmbr_trees_from_code = r_previous.nmbr_trees_from_code; + g_coancestry = r_previous.coancestry; + g_effective_pop_size = r_previous.effective_pop_size; + g_original_seed_qty = r_previous.original_seed_qty; + g_interm_strg_client_number = r_previous.interm_strg_client_number; + g_interm_strg_client_locn = r_previous.interm_strg_client_locn; + g_interm_strg_st_date = r_previous.interm_strg_st_date; + g_interm_strg_end_date = r_previous.interm_strg_end_date; + g_interm_facility_code = r_previous.interm_facility_code; + g_extraction_st_date = r_previous.extraction_st_date; + g_extraction_end_date = r_previous.extraction_end_date; + g_extraction_volume = r_previous.extraction_volume; + g_extrct_cli_number = r_previous.extrct_cli_number; + g_extrct_cli_locn_cd = r_previous.extrct_cli_locn_cd; + g_stored_cli_number = r_previous.stored_cli_number; + g_stored_cli_locn_cd = r_previous.stored_cli_locn_cd; + g_lngterm_strg_st_date = r_previous.lngterm_strg_st_date; + g_historical_tsr_date = r_previous.historical_tsr_date; + g_collection_lat_deg = r_previous.collection_lat_deg; + g_collection_lat_min = r_previous.collection_lat_min; + g_collection_latitude_code = r_previous.collection_latitude_code; + g_collection_long_deg = r_previous.collection_long_deg; + g_collection_long_min = r_previous.collection_long_min; + g_collection_longitude_code = r_previous.collection_longitude_code; + g_collection_elevation = r_previous.collection_elevation; + g_collection_elevation_min = r_previous.collection_elevation_min; + g_collection_elevation_max = r_previous.collection_elevation_max; + g_entry_timestamp = r_previous.entry_timestamp; + g_entry_userid = r_previous.entry_userid; + g_update_timestamp = r_previous.update_timestamp; + g_update_userid = r_previous.update_userid; + g_approved_timestamp = r_previous.approved_timestamp; + g_approved_userid = r_previous.approved_userid; + g_interm_strg_locn = r_previous.interm_strg_locn; + g_interm_strg_cmt = r_previous.interm_strg_cmt; + g_ownership_comment = r_previous.ownership_comment; + g_cone_seed_desc = r_previous.cone_seed_desc; + g_extraction_comment = r_previous.extraction_comment; + g_seedlot_comment = r_previous.seedlot_comment; + g_bgc_zone_code = r_previous.bgc_zone_code; + g_bgc_subzone_code = r_previous.bgc_subzone_code; + g_variant = r_previous.variant; + g_bec_version_id = r_previous.bec_version_id; + // --get spz's + g_spz_list = get_seedlot_spz_list(g_seedlot_number); + } + } +} + +/* + * Procedure: init + * Purpose: Initialize member variables + */ +function init(p_seedlot_number: string | null = null) { + g_error_message = null; + g_seed_plan_zone_code = null; + gb_seed_plan_zone_code = 'N'; + g_seed_plan_zone_id = null; + gb_seed_plan_zone_id = 'N'; + g_applicant_client_locn = null; + gb_applicant_client_locn = 'N'; + g_applicant_client_number = null; + gb_applicant_client_number = 'N'; + g_applicant_email_address = null; + gb_applicant_email_address = 'N'; + g_bc_source_ind = null; + gb_bc_source_ind = 'N'; + g_biotech_processes_ind = null; + gb_biotech_processes_ind = 'N'; + g_collection_area_radius = null; + gb_collection_area_radius = 'N'; + g_collection_bgc_ind = null; + gb_collection_bgc_ind = 'N'; + g_collection_spz_ind = null; + gb_collection_spz_ind = 'N'; + g_coll_standard_met_ind = null; + gb_coll_standard_met_ind = 'N'; + g_cone_collection_method2_cd = null; + gb_cone_collection_method2_cd = 'N'; + g_contaminant_pollen_bv = null; + gb_contaminant_pollen_bv = 'N'; + g_controlled_cross_ind = null; + gb_controlled_cross_ind = 'N'; + g_declared_userid = null; + gb_declared_userid = 'N'; + g_declared_timestamp = null; + gb_declared_timestamp = 'N'; + g_female_gametic_mthd_code = null; + gb_female_gametic_mthd_code = 'N'; + g_latitude_sec_max = null; + gb_latitude_sec_max = 'N'; + g_latitude_sec_min = null; + gb_latitude_sec_min = 'N'; + g_longitude_sec_max = null; + gb_longitude_sec_max = 'N'; + g_longitude_sec_min = null; + gb_longitude_sec_min = 'N'; + g_male_gametic_mthd_code = null; + gb_male_gametic_mthd_code = 'N'; + g_orchard_comment = null; + gb_orchard_comment = 'N'; + g_orchard_contamination_pct = null; + gb_orchard_contamination_pct = 'N'; + g_pollen_contamination_ind = null; + gb_pollen_contamination_ind = 'N'; + g_pollen_contam_mthd_code = null; + gb_pollen_contam_mthd_code = 'N'; + g_pollen_contamination_pct = null; + gb_pollen_contamination_pct = 'N'; + g_provenance_id = null; + gb_provenance_id = 'N'; + g_secondary_orchard_id = null; + gb_secondary_orchard_id = 'N'; + g_seed_plan_unit_id = null; + gb_seed_plan_unit_id = 'N'; + g_seed_store_client_locn = null; + gb_seed_store_client_locn = 'N'; + g_seed_store_client_number = null; + gb_seed_store_client_number = 'N'; + g_seedlot_source_code = null; + gb_seedlot_source_code = 'N'; + g_smp_mean_bv_AD = null; + gb_smp_mean_bv_AD = 'N'; + g_smp_mean_bv_DFS = null; + gb_smp_mean_bv_DFS = 'N'; + g_smp_mean_bv_DFU = null; + gb_smp_mean_bv_DFU = 'N'; + g_smp_mean_bv_DFW = null; + gb_smp_mean_bv_DFW = 'N'; + g_smp_mean_bv_DSB = null; + gb_smp_mean_bv_DSB = 'N'; + g_smp_mean_bv_DSC = null; + gb_smp_mean_bv_DSC = 'N'; + g_smp_mean_bv_DSG = null; + gb_smp_mean_bv_DSG = 'N'; + g_smp_mean_bv_GVO = null; + gb_smp_mean_bv_GVO = 'N'; + g_smp_mean_bv_IWS = null; + gb_smp_mean_bv_IWS = 'N'; + g_smp_mean_bv_WDU = null; + gb_smp_mean_bv_WDU = 'N'; + g_smp_mean_bv_WVE = null; + gb_smp_mean_bv_WVE = 'N'; + g_smp_mean_bv_WWD = null; + gb_smp_mean_bv_WWD = 'N'; + g_smp_parents_outside = null; + gb_smp_parents_outside = 'N'; + g_smp_success_pct = null; + gb_smp_success_pct = 'N'; + g_temporary_storage_end_date = null; + gb_temporary_storage_end_date = 'N'; + g_temporary_storage_start_dt = null; + gb_temporary_storage_start_dt = 'N'; + g_total_parent_trees = null; + gb_total_parent_trees = 'N'; + g_latitude_seconds = null; + gb_latitude_seconds = 'N'; + g_longitude_seconds = null; + gb_longitude_seconds = 'N'; + g_collection_lat_sec = null; + gb_collection_lat_sec = 'N'; + g_collection_long_sec = null; + gb_collection_long_sec = 'N'; + g_seedlot_number = null; + gb_seedlot_number = 'N'; + g_seedlot_status_code = null; + gb_seedlot_status_code = 'N'; + g_vegetation_code = null; + gb_vegetation_code = 'N'; + g_genetic_class_code = null; + gb_genetic_class_code = 'N'; + g_collection_source_code = null; + gb_collection_source_code = 'N'; + g_superior_prvnc_ind = null; + gb_superior_prvnc_ind = 'N'; + g_org_unit_no = null; + gb_org_unit_no = 'N'; + g_registered_seed_ind = null; + gb_registered_seed_ind = 'N'; + g_to_be_registrd_ind = null; + gb_to_be_registrd_ind = 'N'; + g_registered_date = null; + gb_registered_date = 'N'; + g_fs721a_signed_ind = null; + gb_fs721a_signed_ind = 'N'; + g_nad_datum_code = null; + gb_nad_datum_code = 'N'; + g_utm_zone = null; + gb_utm_zone = 'N'; + g_utm_easting = null; + gb_utm_easting = 'N'; + g_utm_northing = null; + gb_utm_northing = 'N'; + g_longitude_degrees = null; + gb_longitude_degrees = 'N'; + g_longitude_minutes = null; + gb_longitude_minutes = 'N'; + g_longitude_deg_min = null; + gb_longitude_deg_min = 'N'; + g_longitude_min_min = null; + gb_longitude_min_min = 'N'; + g_longitude_deg_max = null; + gb_longitude_deg_max = 'N'; + g_longitude_min_max = null; + gb_longitude_min_max = 'N'; + g_latitude_degrees = null; + gb_latitude_degrees = 'N'; + g_latitude_minutes = null; + gb_latitude_minutes = 'N'; + g_latitude_deg_min = null; + gb_latitude_deg_min = 'N'; + g_latitude_min_min = null; + gb_latitude_min_min = 'N'; + g_latitude_deg_max = null; + gb_latitude_deg_max = 'N'; + g_latitude_min_max = null; + gb_latitude_min_max = 'N'; + g_seed_coast_area_code = null; + gb_seed_coast_area_code = 'N'; + g_elevation = null; + gb_elevation = 'N'; + g_elevation_min = null; + gb_elevation_min = 'N'; + g_elevation_max = null; + gb_elevation_max = 'N'; + g_orchard_id = null; + gb_orchard_id = 'N'; + g_collection_locn_desc = null; + gb_collection_locn_desc = 'N'; + g_collection_cli_number = null; + gb_collection_cli_number = 'N'; + g_collection_cli_locn_cd = null; + gb_collection_cli_locn_cd = 'N'; + g_collection_start_date = null; + gb_collection_start_date = 'N'; + g_collection_end_date = null; + gb_collection_end_date = 'N'; + g_cone_collection_method_cd = null; + gb_cone_collection_method_cd = 'N'; + g_no_of_containers = null; + gb_no_of_containers = 'N'; + g_clctn_volume = null; + gb_clctn_volume = 'N'; + g_vol_per_container = null; + gb_vol_per_container = 'N'; + g_nmbr_trees_from_code = null; + gb_nmbr_trees_from_code = 'N'; + g_coancestry = null; + gb_coancestry = 'N'; + g_effective_pop_size = null; + gb_effective_pop_size = 'N'; + g_original_seed_qty = null; + gb_original_seed_qty = 'N'; + g_interm_strg_client_number = null; + gb_interm_strg_client_number = 'N'; + g_interm_strg_client_locn = null; + gb_interm_strg_client_locn = 'N'; + g_interm_strg_st_date = null; + gb_interm_strg_st_date = 'N'; + g_interm_strg_end_date = null; + gb_interm_strg_end_date = 'N'; + g_interm_facility_code = null; + gb_interm_facility_code = 'N'; + g_extraction_st_date = null; + gb_extraction_st_date = 'N'; + g_extraction_end_date = null; + gb_extraction_end_date = 'N'; + g_extraction_volume = null; + gb_extraction_volume = 'N'; + g_extrct_cli_number = null; + gb_extrct_cli_number = 'N'; + g_extrct_cli_locn_cd = null; + gb_extrct_cli_locn_cd = 'N'; + g_stored_cli_number = null; + gb_stored_cli_number = 'N'; + g_stored_cli_locn_cd = null; + gb_stored_cli_locn_cd = 'N'; + g_lngterm_strg_st_date = null; + gb_lngterm_strg_st_date = 'N'; + g_historical_tsr_date = null; + gb_historical_tsr_date = 'N'; + g_collection_lat_deg = null; + gb_collection_lat_deg = 'N'; + g_collection_lat_min = null; + gb_collection_lat_min = 'N'; + g_collection_latitude_code = null; + gb_collection_latitude_code = 'N'; + g_collection_long_deg = null; + gb_collection_long_deg = 'N'; + g_collection_long_min = null; + gb_collection_long_min = 'N'; + g_collection_longitude_code = null; + gb_collection_longitude_code = 'N'; + g_collection_elevation = null; + gb_collection_elevation = 'N'; + g_collection_elevation_min = null; + gb_collection_elevation_min = 'N'; + g_collection_elevation_max = null; + gb_collection_elevation_max = 'N'; + g_entry_timestamp = null; + gb_entry_timestamp = 'N'; + g_entry_userid = null; + gb_entry_userid = 'N'; + g_update_timestamp = null; + gb_update_timestamp = 'N'; + g_update_userid = null; + gb_update_userid = 'N'; + g_approved_timestamp = null; + gb_approved_timestamp = 'N'; + g_approved_userid = null; + gb_approved_userid = 'N'; + g_revision_count = null; + gb_revision_count = 'N'; + g_interm_strg_locn = null; + gb_interm_strg_locn = 'N'; + g_interm_strg_cmt = null; + gb_interm_strg_cmt = 'N'; + g_ownership_comment = null; + gb_ownership_comment = 'N'; + g_cone_seed_desc = null; + gb_cone_seed_desc = 'N'; + g_extraction_comment = null; + gb_extraction_comment = 'N'; + g_seedlot_comment = null; + gb_seedlot_comment = 'N'; + g_bgc_zone_code = null; + gb_bgc_zone_code = 'N'; + g_bgc_subzone_code = null; + gb_bgc_subzone_code = 'N'; + g_variant = null; + gb_variant = 'N'; + g_bec_version_id = null; + gb_bec_version_id = 'N'; + g_gw_AD = null; + g_gw_DFS = null; + g_gw_DFU = null; + g_gw_DFW = null; + g_gw_DSB = null; + g_gw_DSC = null; + g_gw_DSG = null; + g_gw_GVO = null; + g_gw_IWS = null; + g_gw_WDU = null; + g_gw_WVE = null; + g_gw_WWD = null; + g_spz_list = null; + g_tested_parent_trees_pct = null; + g_tested_parent_trees_pct_AD = null; + g_tested_parent_trees_pct_DFS = null; + g_tested_parent_trees_pct_DFU = null; + g_tested_parent_trees_pct_DFW = null; + g_tested_parent_trees_pct_DSB = null; + g_tested_parent_trees_pct_DSC = null; + g_tested_parent_trees_pct_DSG = null; + g_tested_parent_trees_pct_GVO = null; + g_tested_parent_trees_pct_IWS = null; + g_tested_parent_trees_pct_WDU = null; + g_tested_parent_trees_pct_WVE = null; + g_tested_parent_trees_pct_WWD = null; + g_untested_parent_trees_pct = null; + g_is_lot_split = null; + // --Record types + r_previous = null; + r_pt_contrib = null; + if (p_seedlot_number != null) { + g_seedlot_number = p_seedlot_number; + get_previous_seedlot_values(true); + } +} + +// --***START GETTERS +function error_raised(): boolean { + return g_error_message != null; +} +function get_error_message(): string | null { + return g_error_message; +} +function get_seed_plan_zone_code(): string | null { // VARCHAR2; + return g_seed_plan_zone_code; +} +function get_seed_plan_zone_id(): string | null { // VARCHAR2; + return g_seed_plan_zone_id; +} +function get_applicant_client_locn(): string | null { // VARCHAR2; + return g_applicant_client_locn; +} +function get_applicant_client_number(): string | null { // VARCHAR2; + return g_applicant_client_number; +} +function get_applicant_email_address(): string | null { // VARCHAR2; + return g_applicant_email_address; +} +function get_bc_source_ind(): string | null { // VARCHAR2; + return g_bc_source_ind; +} +function get_biotech_processes_ind(): string | null { // VARCHAR2; + return g_biotech_processes_ind; +} +function get_collection_area_radius(): number | null { // NUMBER + return g_collection_area_radius; +} +function get_collection_bgc_ind(): string | null { // VARCHAR2; + return g_collection_bgc_ind; +} +function get_collection_spz_ind(): string | null { // VARCHAR2; + return g_collection_spz_ind; +} +function get_coll_standard_met_ind(): string | null { // VARCHAR2; + return g_coll_standard_met_ind; +} +function get_cone_collection_method2_cd(): string | null { // VARCHAR2; + return g_cone_collection_method2_cd; +} +function get_contaminant_pollen_bv(): number | null { // NUMBER + return g_contaminant_pollen_bv; +} +function get_controlled_cross_ind(): string | null { // VARCHAR2; + return g_controlled_cross_ind; +} +function get_declared_userid(): string | null { // VARCHAR2; + return g_declared_userid; +} +function get_declared_timestamp(): Date | null { // DATE + return g_declared_timestamp; +} +function get_female_gametic_mthd_code(): string | null { // VARCHAR2; + return g_female_gametic_mthd_code; +} +function get_latitude_sec_max(): number | null { // NUMBER + return g_latitude_sec_max; +} +function get_latitude_sec_min(): number | null { // NUMBER + return g_latitude_sec_min; +} +function get_longitude_sec_max(): number | null { // NUMBER + return g_longitude_sec_max; +} +function get_longitude_sec_min(): number | null { // NUMBER + return g_longitude_sec_min; +} +function get_male_gametic_mthd_code(): string | null { // VARCHAR2; + return g_male_gametic_mthd_code; +} +function get_orchard_comment(): string | null { // VARCHAR2; + return g_orchard_comment; +} +function get_orchard_contamination_pct(): number | null { // NUMBER + return g_orchard_contamination_pct; +} +function get_pollen_contamination_ind(): string | null { // VARCHAR2; + return g_pollen_contamination_ind; +} +function get_pollen_contam_mthd_code(): string | null { // VARCHAR2; + return g_pollen_contam_mthd_code; +} +function get_pollen_contamination_pct(): number | null { // NUMBER + return g_pollen_contamination_pct; +} +function get_provenance_id(): number | null { // NUMBER + return g_provenance_id; +} +function get_secondary_orchard_id(): string | null { // VARCHAR2; + return g_secondary_orchard_id; +} +function get_seed_plan_unit_id(): number | null { // NUMBER + return g_seed_plan_unit_id; +} +function get_seed_store_client_locn(): string | null { // VARCHAR2; + return g_seed_store_client_locn; +} +function get_seed_store_client_number(): string | null { // VARCHAR2; + return g_seed_store_client_number; +} +function get_seedlot_source_code(): string | null { // VARCHAR2; + return g_seedlot_source_code; +} +function get_smp_mean_bv_AD(): number | null { + return g_smp_mean_bv_AD; +} +function get_smp_mean_bv_DFS(): number | null { // VARCHAR2; + return g_smp_mean_bv_DFS; +} +function get_smp_mean_bv_DFU(): number | null { // VARCHAR2; + return g_smp_mean_bv_DFU; +} +function get_smp_mean_bv_DFW(): number | null { // VARCHAR2; + return g_smp_mean_bv_DFW; +} +function get_smp_mean_bv_DSB(): number | null { // VARCHAR2; + return g_smp_mean_bv_DSB; +} +function get_smp_mean_bv_DSC(): number | null { // VARCHAR2; + return g_smp_mean_bv_DSC; +} +function get_smp_mean_bv_DSG(): number | null { // VARCHAR2; + return g_smp_mean_bv_DSG; +} +function get_smp_mean_bv_GVO(): number | null { // VARCHAR2; + return g_smp_mean_bv_GVO; +} +function get_smp_mean_bv_IWS(): number | null { // VARCHAR2; + return g_smp_mean_bv_IWS; +} +function get_smp_mean_bv_WDU(): number | null { // VARCHAR2; + return g_smp_mean_bv_WDU; +} +function get_smp_mean_bv_WVE(): number | null { // VARCHAR2; + return g_smp_mean_bv_WVE; +} +function get_smp_mean_bv_WWD(): number | null { // VARCHAR2; + return g_smp_mean_bv_WWD; +} +function get_smp_parents_outside(): number | null { // NUMBER + return g_smp_parents_outside; +} +function get_smp_success_pct(): number | null { // NUMBER + return g_smp_success_pct; +} +function get_temporary_storage_end_date(): Date | null { // DATE + return g_temporary_storage_end_date; +} +function get_temporary_storage_start_dt(): Date | null { // DATE + return g_temporary_storage_start_dt; +} +function get_total_parent_trees(): number | null { // NUMBER + return g_total_parent_trees; +} +function get_latitude_seconds(): number | null { // NUMBER + return g_latitude_seconds; +} +function get_longitude_seconds(): number | null { // NUMBER + return g_longitude_seconds; +} +function get_collection_lat_sec(): number | null { // NUMBER + return g_collection_lat_sec; +} +function get_collection_long_sec(): number | null { // NUMBER + return g_collection_long_sec; +} +function get_seedlot_number(): string | null { // VARCHAR2; + return g_seedlot_number; +} +function get_seedlot_status_code(): string | null { // VARCHAR2; + return g_seedlot_status_code; +} +function get_vegetation_code(): string | null { // VARCHAR2; + return g_vegetation_code; +} +function get_genetic_class_code(): string | null { // VARCHAR2; + return g_genetic_class_code; +} +function get_collection_source_code(): string | null { // VARCHAR2; + return g_collection_source_code; +} +function get_superior_prvnc_ind(): string | null { // VARCHAR2; + return g_superior_prvnc_ind; +} +function get_org_unit_no(): number | null { // NUMBER + return g_org_unit_no; +} +function get_registered_seed_ind(): string | null { // VARCHAR2; + return g_registered_seed_ind; +} +function get_to_be_registrd_ind(): string | null { // VARCHAR2; + return g_to_be_registrd_ind; +} +function get_registered_date(): Date | null { // DATE + return g_registered_date; +} +function get_fs721a_signed_ind(): string | null { // VARCHAR2; + return g_fs721a_signed_ind; +} +function get_nad_datum_code(): string | null { // VARCHAR2; + return g_nad_datum_code; +} +function get_utm_zone(): number | null { // NUMBER + return g_utm_zone; +} +function get_utm_easting(): number | null { // NUMBER + return g_utm_easting; +} +function get_utm_northing(): number | null { // NUMBER + return g_utm_northing; +} +function get_longitude_degrees(): number | null { // NUMBER + return g_longitude_degrees; +} +function get_longitude_minutes(): number | null { // NUMBER + return g_longitude_minutes; +} +function get_longitude_deg_min(): number | null { // NUMBER + return g_longitude_deg_min; +} +function get_longitude_min_min(): number | null { // NUMBER + return g_longitude_min_min; +} +function get_longitude_deg_max(): number | null { // NUMBER + return g_longitude_deg_max; +} +function get_longitude_min_max(): number | null { // NUMBER + return g_longitude_min_max; +} +function get_latitude_degrees(): number | null { // NUMBER + return g_latitude_degrees; +} +function get_latitude_minutes(): number | null { // NUMBER + return g_latitude_minutes; +} +function get_latitude_deg_min(): number | null { // NUMBER + return g_latitude_deg_min; +} +function get_latitude_min_min(): number | null { // NUMBER + return g_latitude_min_min; +} +function get_latitude_deg_max(): number | null { // NUMBER + return g_latitude_deg_max; +} +function get_latitude_min_max(): number | null { // NUMBER + return g_latitude_min_max; +} +function get_seed_coast_area_code(): string | null { // VARCHAR2; + return g_seed_coast_area_code; +} +function get_elevation(): number | null { // NUMBER + return g_elevation; +} +function get_elevation_min(): number | null { // NUMBER + return g_elevation_min; +} +function get_elevation_max(): number | null { // NUMBER + return g_elevation_max; +} +function get_orchard_id(): string | null { // VARCHAR2; + return g_orchard_id; +} +function get_collection_locn_desc(): string | null { // VARCHAR2; + return g_collection_locn_desc; +} +function get_collection_cli_number(): string | null { // VARCHAR2; + return g_collection_cli_number; +} +function get_collection_cli_locn_cd(): string | null { // VARCHAR2; + return g_collection_cli_locn_cd; +} +function get_collection_start_date(): Date | null { // DATE + return g_collection_start_date; +} +function get_collection_end_date(): Date | null { // DATE + return g_collection_end_date; +} +function get_cone_collection_method_cd(): string | null { // VARCHAR2; + return g_cone_collection_method_cd; +} +function get_no_of_containers(): number | null { // NUMBER + return g_no_of_containers; +} +function get_clctn_volume(): number | null { // NUMBER + return g_clctn_volume; +} +function get_vol_per_container(): number | null { // NUMBER + return g_vol_per_container; +} +function get_nmbr_trees_from_code(): string | null { // VARCHAR2; + return g_nmbr_trees_from_code; +} +function get_coancestry(): number | null { // NUMBER + return g_coancestry; +} +function get_effective_pop_size(): number | null { // NUMBER + return g_effective_pop_size; +} +function get_original_seed_qty(): number | null { // NUMBER + return g_original_seed_qty; +} +function get_interm_strg_client_number(): string | null { // VARCHAR2; + return g_interm_strg_client_number; +} +function get_interm_strg_client_locn(): string | null { // VARCHAR2; + return g_interm_strg_client_locn; +} +function get_interm_strg_st_date(): Date | null { // DATE + return g_interm_strg_st_date; +} +function get_interm_strg_end_date(): Date | null { // DATE + return g_interm_strg_end_date; +} +function get_interm_facility_code(): string | null { // VARCHAR2; + return g_interm_facility_code; +} +function get_extraction_st_date(): Date | null { // DATE + return g_extraction_st_date; +} +function get_extraction_end_date(): Date | null { // DATE + return g_extraction_end_date; +} +function get_extraction_volume(): number | null { // NUMBER + return g_extraction_volume; +} +function get_extrct_cli_number(): string | null { // VARCHAR2; + return g_extrct_cli_number; +} +function get_extrct_cli_locn_cd(): string | null { // VARCHAR2; + return g_extrct_cli_locn_cd; +} +function get_stored_cli_number(): string | null { // VARCHAR2; + return g_stored_cli_number; +} +function get_stored_cli_locn_cd(): string | null { // VARCHAR2; + return g_stored_cli_locn_cd; +} +function get_lngterm_strg_st_date(): Date | null { // DATE + return g_lngterm_strg_st_date; +} +function get_historical_tsr_date(): Date | null { // DATE + return g_historical_tsr_date; +} +function get_collection_lat_deg(): number | null { // NUMBER + return g_collection_lat_deg; +} +function get_collection_lat_min(): number | null { // NUMBER + return g_collection_lat_min; +} +function get_collection_latitude_code(): string | null { // VARCHAR2; + return g_collection_latitude_code; +} +function get_collection_long_deg(): number | null { // NUMBER + return g_collection_long_deg; +} +function get_collection_long_min(): number | null { // NUMBER + return g_collection_long_min; +} +function get_collection_longitude_code(): string | null { // VARCHAR2; + return g_collection_longitude_code; +} +function get_collection_elevation(): number | null { // NUMBER + return g_collection_elevation; +} +function get_collection_elevation_min(): number | null { // NUMBER + return g_collection_elevation_min; +} +function get_collection_elevation_max(): number | null { // NUMBER + return g_collection_elevation_max; +} +function get_entry_timestamp(): Date | null { // DATE + return g_entry_timestamp; +} +function get_entry_userid(): string | null { // VARCHAR2; + return g_entry_userid; +} +function get_update_timestamp(): Date | null { // DATE + return g_update_timestamp; +} +function get_update_userid(): string | null { // VARCHAR2; + return g_update_userid; +} +function get_approved_timestamp(): Date | null { // DATE + return g_approved_timestamp; +} +function get_approved_userid(): string | null { // VARCHAR2; + return g_approved_userid; +} +function get_revision_count(): number | null { // NUMBER + return g_revision_count; +} +function get_interm_strg_locn(): string | null { // VARCHAR2; + return g_interm_strg_locn; +} +function get_interm_strg_cmt(): string | null { // VARCHAR2; + return g_interm_strg_cmt; +} +function get_ownership_comment(): string | null { // VARCHAR2; + return g_ownership_comment; +} +function get_cone_seed_desc(): string | null { // VARCHAR2; + return g_cone_seed_desc; +} +function get_extraction_comment(): string | null { // VARCHAR2; + return g_extraction_comment; +} +function get_seedlot_comment(): string | null { // VARCHAR2; + return g_seedlot_comment; +} +function get_bgc_zone_code(): string | null { // VARCHAR2; + return g_bgc_zone_code; +} +function get_bgc_subzone_code(): string | null { // VARCHAR2; + return g_bgc_subzone_code; +} +function get_variant(): string | null { // VARCHAR2; + return g_variant; +} +function get_bec_version_id(): string | null { // VARCHAR2; + return g_bec_version_id; +} +function get_prev_bgc_zone_code(): string | null { // VARCHAR2; + return r_previous.bgc_zone_code; +} +function get_prev_bgc_subzone_code(): string | null { // VARCHAR2; + return r_previous.bgc_subzone_code; +} +function get_prev_variant(): string | null { // VARCHAR2; + return r_previous.variant; +} +function get_prev_collection_lat_deg(): number { // NUMBER + return r_previous.collection_lat_deg; +} +function get_prev_collection_lat_min(): number { // NUMBER + return r_previous.collection_lat_min; +} +function get_prev_collection_lat_sec(): string { // VARCHAR2; + return r_previous.collection_lat_sec; +} +function get_prev_collection_long_deg(): number { // NUMBER + return r_previous.collection_long_deg; +} +function get_prev_collection_long_min(): number { // NUMBER + return r_previous.collection_long_min; +} +function get_prev_collection_long_sec(): string { // VARCHAR2; + return r_previous.collection_long_sec; +} +function get_gw_AD(): number | null { // NUMBER + return g_gw_AD; +} +function get_gw_DFS(): number | null { // NUMBER + return g_gw_DFS; +} +function get_gw_DFU(): number | null { // NUMBER + return g_gw_DFU; +} +function get_gw_DFW(): number | null { // NUMBER + return g_gw_DFW; +} +function get_gw_DSB(): number | null { // NUMBER + return g_gw_DSB; +} +function get_gw_DSC(): number | null { // NUMBER + return g_gw_DSC; +} +function get_gw_DSG(): number | null { // NUMBER + return g_gw_DSG; +} +function get_gw_GVO(): number | null { // NUMBER + return g_gw_GVO; +} +function get_gw_IWS(): number | null { // NUMBER + return g_gw_IWS; +} +function get_gw_WDU(): number | null { // NUMBER + return g_gw_WDU; +} +function get_gw_WVE(): number | null { // NUMBER + return g_gw_WVE; +} +function get_gw_WWD(): number | null { // NUMBER + return g_gw_WWD; +} +function get_spz_list(): string | null { // VARCHAR2; + return g_spz_list; +} +function get_spz_id_list(): string | null { // VARCHAR2; + return g_spz_id_list; +} +function get_tested_parent_trees_pct(): number | null { // NUMBER + return g_tested_parent_trees_pct; +} +function get_tested_parent_trees_pct_AD(): number | null { // NUMBER + return g_tested_parent_trees_pct_AD == null ? 0 : g_tested_parent_trees_pct_AD; +} +function get_tested_pt_pct_DFS(): number { // NUMBER + return g_tested_parent_trees_pct_DFS == null ? 0 : g_tested_parent_trees_pct_DFS; +} +function get_tested_pt_pct_DFU(): number { // NUMBER + return g_tested_parent_trees_pct_DFU == null? 0 : g_tested_parent_trees_pct_DFU; +} +function get_tested_pt_pct_DFW(): number { // NUMBER + return g_tested_parent_trees_pct_DFW == null? 0 : g_tested_parent_trees_pct_DFW; +} +function get_tested_pt_pct_DSB(): number { // NUMBER + return g_tested_parent_trees_pct_DSB == null? 0 : g_tested_parent_trees_pct_DSB; +} +function get_tested_pt_pct_DSC(): number | null { // NUMBER + return g_tested_parent_trees_pct_DSC = null? 0 : g_tested_parent_trees_pct_DSC; +} +function get_tested_pt_pct_DSG(): number { // NUMBER + return g_tested_parent_trees_pct_DSG == null? 0 : g_tested_parent_trees_pct_DSG; +} +function get_tested_pt_pct_GVO(): number { // NUMBER + return g_tested_parent_trees_pct_GVO == null? 0 : g_tested_parent_trees_pct_GVO; +} +function get_tested_pt_pct_IWS(): number { // NUMBER + return g_tested_parent_trees_pct_IWS == null? 0 : g_tested_parent_trees_pct_IWS; +} +function get_tested_pt_pct_WDU(): number { // NUMBER + return g_tested_parent_trees_pct_WDU == null? 0 : g_tested_parent_trees_pct_WDU; +} +function get_tested_pt_pct_WVE(): number { // NUMBER + return g_tested_parent_trees_pct_WVE == null? 0 : g_tested_parent_trees_pct_WVE; +} +function get_tested_pt_pct_WWD(): number { // NUMBER + return g_tested_parent_trees_pct_WWD == null? 0 : g_tested_parent_trees_pct_WWD; +} +function get_untested_parent_trees_pct(): number | null { // NUMBER + return g_untested_parent_trees_pct; +} +function get_is_lot_split(): boolean | null { // BOOLEAN + return g_is_lot_split; +} + +//-- Get the current bgc for the seedlot. +function get_new_bgc(p_seedlot_number: string): string { + let v_planting_site_point: object; // MDSYS.SDO_GEOMETRY; + let v_bgc_zone_code: string = ''; + let v_bgc_subzone_code: string = ''; + let v_variant: string = ''; + let v_bgc_string: string = ''; + + // -- We are in trouble if (there's no Geometry entry for the seedlot... + // -- retrieve and set the seedlot geometry capture method. + spr_seedlot_geometry.init(p_seedlot_number); + spr_seedlot_geometry.set_seedlot_number(p_seedlot_number); + spr_seedlot_geometry.get(); + v_planting_site_point = spr_seedlot_geometry.get_geometry(); + if (v_planting_site_point != null) { + spr_spatial_utils.get_bec(v_planting_site_point, v_bgc_zone_code, v_bgc_subzone_code, v_variant); + v_bgc_string = v_bgc_zone_code || ' ' || v_bgc_subzone_code || ' ' || v_variant; + } + return v_bgc_string; +} +// -- Get the current spz for the seedlot. +function get_new_spz(p_seedlot_number: string): string { + let v_planting_site_point: object; // MDSYS.SDO_GEOMETRY; + let v_vegetation_code: string = ''; + let v_spzb: string = ''; + + //-- We are in trouble if (there's no Geometry entry for the seedlot... + //-- retrieve and set the seedlot geometry capture method. + spr_seedlot_geometry.init(p_seedlot_number); + spr_seedlot_geometry.set_seedlot_number(p_seedlot_number); + spr_seedlot_geometry.get(); + v_planting_site_point = spr_seedlot_geometry.get_geometry; + //-- Get the vegetation code from the seedlot. + // SELECT vegetation_code INTO v_vegetation_code FROM seedlot WHERE seedlot_number = p_seedlot_number; + if (v_planting_site_point != null) { + v_spzb = spr_spatial_utils.get_spzb(v_planting_site_point, v_vegetation_code); + } + return v_spzb; +} + +function is_collection_lat_empty(): boolean { + return g_collection_lat_deg == null && g_collection_lat_min == null && g_collection_lat_sec == null; +} +function is_collection_long_empty(): boolean { + return g_collection_long_deg == null && g_collection_long_min == null && g_collection_long_sec == null; +} +// -->Area of use can is replaced for these statuses even if it is specified +function is_area_of_use_status(): boolean { + const list: string[] = ['INC', 'PND']; + let g_seedlot_status_code_not_null = g_seedlot_status_code == null? '' : g_seedlot_status_code; + return (list.includes(g_seedlot_status_code_not_null)) || (list.includes(r_previous.seedlot_status_code)); +} +function replace_area_of_use(p_check_value: number | string | null): boolean { + return is_area_of_use_status() || p_check_value == null; +} +function is_area_of_use_elev_empty(): boolean { + return g_elevation_min == null && g_elevation_max == null; +} +function is_area_of_use_lat_min_empty(): boolean { + return g_latitude_deg_min == null && g_latitude_min_min == null && g_latitude_sec_min == null; +} +function is_area_of_use_lat_max_empty(): boolean { + return g_latitude_deg_max == null && g_latitude_min_max == null && g_latitude_sec_max == null; +} +function is_area_of_use_lat_empty(): boolean { + return is_area_of_use_lat_min_empty() && is_area_of_use_lat_max_empty(); +} +function is_area_of_use_long_min_empty(): boolean { + return g_longitude_deg_min == null && g_longitude_min_min == null && g_longitude_sec_min == null; +} +function is_area_of_use_long_max_empty(): boolean { + return g_longitude_deg_max == null && g_longitude_min_max == null && g_longitude_sec_max == null; +} +function is_area_of_use_long_empty(): boolean { + return is_area_of_use_long_min_empty() && is_area_of_use_long_max_empty(); +} +// -->Lots approved on or after 2005-04-01 must adhere to Chief Forester's Standards +function is_lot_under_CFS(): boolean { + return g_approved_timestamp == null || g_approved_timestamp >= new Date('2005-04-01'); +} +// --***END GETTERS +// --***START SETTERS +function set_seed_plan_zone_code(p_value: string) { + g_seed_plan_zone_code = p_value; + gb_seed_plan_zone_code = 'Y'; +} +function set_seed_plan_zone_id(p_value: string) { + g_seed_plan_zone_id = p_value; + gb_seed_plan_zone_id = 'Y'; +} +function set_applicant_client_locn(p_value: string) { + g_applicant_client_locn = p_value; + gb_applicant_client_locn = 'Y'; +} +function set_applicant_client_number(p_value: string) { + g_applicant_client_number = p_value; + gb_applicant_client_number = 'Y'; +} +function set_applicant_email_address(p_value: string) { + g_applicant_email_address = p_value; + gb_applicant_email_address = 'Y'; +} +function set_bc_source_ind(p_value: string) { + g_bc_source_ind = p_value; + gb_bc_source_ind = 'Y'; +} +function set_biotech_processes_ind(p_value: string) { + g_biotech_processes_ind = p_value; + gb_biotech_processes_ind = 'Y'; +} +function set_collection_area_radius(p_value: number) { + g_collection_area_radius = p_value; + gb_collection_area_radius = 'Y'; +} +function set_collection_bgc_ind(p_value: string) { + g_collection_bgc_ind = p_value; + gb_collection_bgc_ind = 'Y'; +} +function set_collection_spz_ind(p_value: string) { + g_collection_spz_ind = p_value; + gb_collection_spz_ind = 'Y'; +} +function set_coll_standard_met_ind(p_value: string) { + g_coll_standard_met_ind = p_value; + gb_coll_standard_met_ind = 'Y'; +} +function set_cone_collection_method2_cd(p_value: string) { + g_cone_collection_method2_cd = p_value; + gb_cone_collection_method2_cd = 'Y'; +} +function set_contaminant_pollen_bv(p_value: number) { + g_contaminant_pollen_bv = p_value; + gb_contaminant_pollen_bv = 'Y'; +} +function set_controlled_cross_ind(p_value: string) { + g_controlled_cross_ind = p_value; + gb_controlled_cross_ind = 'Y'; +} +function set_declared_userid(p_value: string) { + g_declared_userid = p_value; + gb_declared_userid = 'Y'; +} +function set_declared_timestamp(p_value: Date) { + g_declared_timestamp = p_value; + gb_declared_timestamp = 'Y'; +} +function set_female_gametic_mthd_code(p_value: string) { + g_female_gametic_mthd_code = p_value; + gb_female_gametic_mthd_code = 'Y'; +} +function set_latitude_sec_max(p_value: number | null) { + g_latitude_sec_max = p_value; + gb_latitude_sec_max = 'Y'; +} +function set_latitude_sec_min(p_value: number | null) { + g_latitude_sec_min = p_value; + gb_latitude_sec_min = 'Y'; +} +function set_longitude_sec_max(p_value: number | null) { + g_longitude_sec_max = p_value; + gb_longitude_sec_max = 'Y'; +} +function set_longitude_sec_min(p_value: number | null) { + g_longitude_sec_min = p_value; + gb_longitude_sec_min = 'Y'; +} +function set_male_gametic_mthd_code(p_value: string) { + g_male_gametic_mthd_code = p_value; + gb_male_gametic_mthd_code = 'Y'; +} +function set_orchard_comment(p_value: string) { + g_orchard_comment = p_value; + gb_orchard_comment = 'Y'; +} +function set_orchard_contamination_pct(p_value: number) { + g_orchard_contamination_pct = p_value; + gb_orchard_contamination_pct = 'Y'; +} +function set_pollen_contamination_ind(p_value: string) { + g_pollen_contamination_ind = p_value; + gb_pollen_contamination_ind = 'Y'; +} +function set_pollen_contam_mthd_code(p_value: string) { + g_pollen_contam_mthd_code = p_value; + gb_pollen_contam_mthd_code = 'Y'; +} +function set_pollen_contamination_pct(p_value: number) { + g_pollen_contamination_pct = p_value; + gb_pollen_contamination_pct = 'Y'; +} +function set_provenance_id(p_value: number) { + g_provenance_id = p_value; + gb_provenance_id = 'Y'; +} +function set_secondary_orchard_id(p_value: string) { + g_secondary_orchard_id = p_value; + gb_secondary_orchard_id = 'Y'; +} +function set_seed_plan_unit_id(p_value: number) { + g_seed_plan_unit_id = p_value; + gb_seed_plan_unit_id = 'Y'; +} +function set_seed_store_client_locn(p_value: string) { + g_seed_store_client_locn = p_value; + gb_seed_store_client_locn = 'Y'; +} +function set_seed_store_client_number(p_value: string) { + g_seed_store_client_number = p_value; + gb_seed_store_client_number = 'Y'; +} +function set_seedlot_source_code(p_value: string) { + g_seedlot_source_code = p_value; + gb_seedlot_source_code = 'Y'; +} +function set_smp_mean_bv_AD(p_value: number) { + g_smp_mean_bv_AD = p_value; + gb_smp_mean_bv_AD = 'Y'; +} +function set_smp_mean_bv_DFS(p_value: number) { + g_smp_mean_bv_DFS = p_value; + gb_smp_mean_bv_DFS = 'Y'; +} +function set_smp_mean_bv_DFU(p_value: number) { + g_smp_mean_bv_DFU = p_value; + gb_smp_mean_bv_DFU = 'Y'; +} +function set_smp_mean_bv_DFW(p_value: number) { + g_smp_mean_bv_DFW = p_value; + gb_smp_mean_bv_DFW = 'Y'; +} +function set_smp_mean_bv_DSB(p_value: number) { + g_smp_mean_bv_DSB = p_value; + gb_smp_mean_bv_DSB = 'Y'; +} +function set_smp_mean_bv_DSC(p_value: number) { + g_smp_mean_bv_DSC = p_value; + gb_smp_mean_bv_DSC = 'Y'; +} +function set_smp_mean_bv_DSG(p_value: number) { + g_smp_mean_bv_DSG = p_value; + gb_smp_mean_bv_DSG = 'Y'; +} +function set_smp_mean_bv_GVO(p_value: number) { + g_smp_mean_bv_GVO = p_value; + gb_smp_mean_bv_GVO = 'Y'; +} +function set_smp_mean_bv_IWS(p_value: number) { + g_smp_mean_bv_IWS = p_value; + gb_smp_mean_bv_IWS = 'Y'; +} +function set_smp_mean_bv_WDU(p_value: number) { + g_smp_mean_bv_WDU = p_value; + gb_smp_mean_bv_WDU = 'Y'; +} +function set_smp_mean_bv_WVE(p_value: number) { + g_smp_mean_bv_WVE = p_value; + gb_smp_mean_bv_WVE = 'Y'; +} +function set_smp_mean_bv_WWD(p_value: number) { + g_smp_mean_bv_WWD = p_value; + gb_smp_mean_bv_WWD = 'Y'; +} +function set_smp_parents_outside(p_value: number) { + g_smp_parents_outside = p_value; + gb_smp_parents_outside = 'Y'; +} +function set_smp_success_pct(p_value: number) { + g_smp_success_pct = p_value; + gb_smp_success_pct = 'Y'; +} +function set_temporary_storage_end_date(p_value: Date) { + g_temporary_storage_end_date = p_value; + gb_temporary_storage_end_date = 'Y'; +} +function set_temporary_storage_start_dt(p_value: Date) { + g_temporary_storage_start_dt = p_value; + gb_temporary_storage_start_dt = 'Y'; +} +function set_total_parent_trees(p_value: number) { + g_total_parent_trees = p_value; + gb_total_parent_trees = 'Y'; +} +function set_latitude_seconds(p_value: number) { + g_latitude_seconds = p_value; + gb_latitude_seconds = 'Y'; +} +function set_longitude_seconds(p_value: number) { + g_longitude_seconds = p_value; + gb_longitude_seconds = 'Y'; +} +function set_collection_lat_sec(p_value: number) { + g_collection_lat_sec = p_value; + gb_collection_lat_sec = 'Y'; +} +function set_collection_long_sec(p_value: number) { + g_collection_long_sec = p_value; + gb_collection_long_sec = 'Y'; +} +function set_seedlot_number(p_value: string) { + g_seedlot_number = p_value; + gb_seedlot_number = 'Y'; +} +function set_seedlot_status_code(p_value: string) { + g_seedlot_status_code = p_value; + gb_seedlot_status_code = 'Y'; +} +function set_vegetation_code(p_value: string) { + g_vegetation_code = p_value; + gb_vegetation_code = 'Y'; +} +function set_genetic_class_code(p_value: string) { + g_genetic_class_code = p_value; + gb_genetic_class_code = 'Y'; +} +function set_collection_source_code(p_value: string) { + g_collection_source_code = p_value; + gb_collection_source_code = 'Y'; +} +function set_superior_prvnc_ind(p_value: string) { + g_superior_prvnc_ind = p_value; + gb_superior_prvnc_ind = 'Y'; +} +function set_org_unit_no(p_value: number) { + g_org_unit_no = p_value; + gb_org_unit_no = 'Y'; +} +function set_registered_seed_ind(p_value: string) { + g_registered_seed_ind = p_value; + gb_registered_seed_ind = 'Y'; +} +function set_to_be_registrd_ind(p_value: string) { + g_to_be_registrd_ind = p_value; + gb_to_be_registrd_ind = 'Y'; +} +function set_registered_date(p_value: Date) { + g_registered_date = p_value; + gb_registered_date = 'Y'; +} +function set_fs721a_signed_ind(p_value: string) { + g_fs721a_signed_ind = p_value; + gb_fs721a_signed_ind = 'Y'; +} +function set_nad_datum_code(p_value: string) { + g_nad_datum_code = p_value; + gb_nad_datum_code = 'Y'; +} +function set_utm_zone(p_value: number) { + g_utm_zone = p_value; + gb_utm_zone = 'Y'; +} +function set_utm_easting(p_value: number) { + g_utm_easting = p_value; + gb_utm_easting = 'Y'; +} +function set_utm_northing(p_value: number) { + g_utm_northing = p_value; + gb_utm_northing = 'Y'; +} +function set_longitude_degrees(p_value: number) { + g_longitude_degrees = p_value; + gb_longitude_degrees = 'Y'; +} +function set_longitude_minutes(p_value: number) { + g_longitude_minutes = p_value; + gb_longitude_minutes = 'Y'; +} +function set_longitude_deg_min(p_value: number | null) { + g_longitude_deg_min = p_value; + gb_longitude_deg_min = 'Y'; +} +function set_longitude_min_min(p_value: number | null) { + g_longitude_min_min = p_value; + gb_longitude_min_min = 'Y'; +} +function set_longitude_deg_max(p_value: number | null) { + g_longitude_deg_max = p_value; + gb_longitude_deg_max = 'Y'; +} +function set_longitude_min_max(p_value: number | null) { + g_longitude_min_max = p_value; + gb_longitude_min_max = 'Y'; +} +function set_latitude_degrees(p_value: number) { + g_latitude_degrees = p_value; + gb_latitude_degrees = 'Y'; +} +function set_latitude_minutes(p_value: number) { + g_latitude_minutes = p_value; + gb_latitude_minutes = 'Y'; +} +function set_latitude_deg_min(p_value: number | null) { + g_latitude_deg_min = p_value; + gb_latitude_deg_min = 'Y'; +} +function set_latitude_min_min(p_value: number | null) { + g_latitude_min_min = p_value; + gb_latitude_min_min = 'Y'; +} +function set_latitude_deg_max(p_value: number | null) { + g_latitude_deg_max = p_value; + gb_latitude_deg_max = 'Y'; +} +function set_latitude_min_max(p_value: number | null) { + g_latitude_min_max = p_value; + gb_latitude_min_max = 'Y'; +} +function set_seed_coast_area_code(p_value: string) { + g_seed_coast_area_code = p_value; + gb_seed_coast_area_code = 'Y'; +} +function set_elevation(p_value: number) { + g_elevation = p_value; + gb_elevation = 'Y'; +} +function set_elevation_min(p_value: number | null) { + g_elevation_min = p_value; + gb_elevation_min = 'Y'; +} +function set_elevation_max(p_value: number) { + g_elevation_max = p_value; + gb_elevation_max = 'Y'; +} +function set_orchard_id(p_value: string) { + g_orchard_id = p_value; + gb_orchard_id = 'Y'; +} +function set_collection_locn_desc(p_value: string) { + g_collection_locn_desc = p_value; + gb_collection_locn_desc = 'Y'; +} +function set_collection_cli_number(p_value: string) { + g_collection_cli_number = p_value; + gb_collection_cli_number = 'Y'; +} +function set_collection_cli_locn_cd(p_value: string) { + g_collection_cli_locn_cd = p_value; + gb_collection_cli_locn_cd = 'Y'; +} +function set_collection_start_date(p_value: Date) { + g_collection_start_date = p_value; + gb_collection_start_date = 'Y'; +} +function set_collection_end_date(p_value: Date) { + g_collection_end_date = p_value; + gb_collection_end_date = 'Y'; +} +function set_cone_collection_method_cd(p_value: string) { + g_cone_collection_method_cd = p_value; + gb_cone_collection_method_cd = 'Y'; +} +function set_no_of_containers(p_value: number) { + g_no_of_containers = p_value; + gb_no_of_containers = 'Y'; +} +function set_clctn_volume(p_value: number) { + g_clctn_volume = p_value; + gb_clctn_volume = 'Y'; +} +function set_vol_per_container(p_value: number) { + g_vol_per_container = p_value; + gb_vol_per_container = 'Y'; +} +function set_nmbr_trees_from_code(p_value: string) { + g_nmbr_trees_from_code = p_value; + gb_nmbr_trees_from_code = 'Y'; +} +function set_coancestry(p_value: number) { + g_coancestry = p_value; + gb_coancestry = 'Y'; +} +function set_effective_pop_size(p_value: number) { + g_effective_pop_size = p_value; + gb_effective_pop_size = 'Y'; +} +function set_original_seed_qty(p_value: number) { + g_original_seed_qty = p_value; + gb_original_seed_qty = 'Y'; +} +function set_interm_strg_client_number(p_value: string) { + g_interm_strg_client_number = p_value; + gb_interm_strg_client_number = 'Y'; +} +function set_interm_strg_client_locn(p_value: string) { + g_interm_strg_client_locn = p_value; + gb_interm_strg_client_locn = 'Y'; +} +function set_interm_strg_st_date(p_value: Date) { + g_interm_strg_st_date = p_value; + gb_interm_strg_st_date = 'Y'; +} +function set_interm_strg_end_date(p_value: Date) { + g_interm_strg_end_date = p_value; + gb_interm_strg_end_date = 'Y'; +} +function set_interm_facility_code(p_value: string) { + g_interm_facility_code = p_value; + gb_interm_facility_code = 'Y'; +} +function set_extraction_st_date(p_value: Date) { + g_extraction_st_date = p_value; + gb_extraction_st_date = 'Y'; +} +function set_extraction_end_date(p_value: Date) { + g_extraction_end_date = p_value; + gb_extraction_end_date = 'Y'; +} +function set_extraction_volume(p_value: number) { + g_extraction_volume = p_value; + gb_extraction_volume = 'Y'; +} +function set_extrct_cli_number(p_value: string) { + g_extrct_cli_number = p_value; + gb_extrct_cli_number = 'Y'; +} +function set_extrct_cli_locn_cd(p_value: string) { + g_extrct_cli_locn_cd = p_value; + gb_extrct_cli_locn_cd = 'Y'; +} +function set_stored_cli_number(p_value: string) { + g_stored_cli_number = p_value; + gb_stored_cli_number = 'Y'; +} +function set_stored_cli_locn_cd(p_value: string) { + g_stored_cli_locn_cd = p_value; + gb_stored_cli_locn_cd = 'Y'; +} +function set_lngterm_strg_st_date(p_value: Date) { + g_lngterm_strg_st_date = p_value; + gb_lngterm_strg_st_date = 'Y'; +} +function set_historical_tsr_date(p_value: Date) { + g_historical_tsr_date = p_value; + gb_historical_tsr_date = 'Y'; +} +function set_collection_lat_deg(p_value: number) { + g_collection_lat_deg = p_value; + gb_collection_lat_deg = 'Y'; +} +function set_collection_lat_min(p_value: number) { + g_collection_lat_min = p_value; + gb_collection_lat_min = 'Y'; +} +function set_collection_latitude_code(p_value: string) { + g_collection_latitude_code = p_value; + gb_collection_latitude_code = 'Y'; +} +function set_collection_long_deg(p_value: number) { + g_collection_long_deg = p_value; + gb_collection_long_deg = 'Y'; +} +function set_collection_long_min(p_value: number) { + g_collection_long_min = p_value; + gb_collection_long_min = 'Y'; +} +function set_collection_longitude_code(p_value: string) { + g_collection_longitude_code = p_value; + gb_collection_longitude_code = 'Y'; +} +function set_collection_elevation(p_value: number) { + g_collection_elevation = p_value; + gb_collection_elevation = 'Y'; +} +function set_collection_elevation_min(p_value: number) { + g_collection_elevation_min = p_value; + gb_collection_elevation_min = 'Y'; +} +function set_collection_elevation_max(p_value: number) { + g_collection_elevation_max = p_value; + gb_collection_elevation_max = 'Y'; +} +function set_entry_timestamp(p_value: Date) { + g_entry_timestamp = p_value; + gb_entry_timestamp = 'Y'; +} +function set_entry_userid(p_value: string) { + g_entry_userid = p_value; + gb_entry_userid = 'Y'; +} +function set_update_timestamp(p_value: Date) { + g_update_timestamp = p_value; + gb_update_timestamp = 'Y'; +} +function set_update_userid(p_value: string) { + g_update_userid = p_value; + gb_update_userid = 'Y'; +} +function set_approved_timestamp(p_value: Date) { + g_approved_timestamp = p_value; + gb_approved_timestamp = 'Y'; +} +function set_approved_userid(p_value: string) { + g_approved_userid = p_value; + gb_approved_userid = 'Y'; +} +function set_revision_count(p_value: number) { + g_revision_count = p_value; + gb_revision_count = 'Y'; +} +function set_interm_strg_locn(p_value: string) { + g_interm_strg_locn = p_value; + gb_interm_strg_locn = 'Y'; +} +function set_interm_strg_cmt(p_value: string) { + g_interm_strg_cmt = p_value; + gb_interm_strg_cmt = 'Y'; +} +function set_ownership_comment(p_value: string) { + g_ownership_comment = p_value; + gb_ownership_comment = 'Y'; +} +function set_cone_seed_desc(p_value: string) { + g_cone_seed_desc = p_value; + gb_cone_seed_desc = 'Y'; +} +function set_extraction_comment(p_value: string) { + g_extraction_comment = p_value; + gb_extraction_comment = 'Y'; +} +function set_seedlot_comment(p_value: string) { + g_seedlot_comment = p_value; + gb_seedlot_comment = 'Y'; +} +function set_bgc_zone_code(p_value: string) { + g_bgc_zone_code = p_value; + gb_bgc_zone_code = 'Y'; +} +function set_bgc_subzone_code(p_value: string) { + g_bgc_subzone_code = p_value; + gb_bgc_subzone_code = 'Y'; +} +function set_variant(p_value: string) { + g_variant = p_value; + gb_variant = 'Y'; +} +function set_bec_version_id(p_value: string) { + g_bec_version_id = p_value; + gb_bec_version_id = 'Y'; +} +function set_gw_AD(p_value: number) { + g_gw_AD = p_value; +} +function set_gw_DFS(p_value: number) { + g_gw_DFS = p_value; +} +function set_gw_DFU(p_value: number) { + g_gw_DFU = p_value; +} +function set_gw_DFW(p_value: number) { + g_gw_DFW = p_value; +} +function set_gw_DSB(p_value: number) { + g_gw_DSB = p_value; +} +function set_gw_DSC(p_value: number) { + g_gw_DSC = p_value; +} +function set_gw_DSG(p_value: number) { + g_gw_DSG = p_value; +} +function set_gw_GVO(p_value: number) { + g_gw_GVO = p_value; +} +function set_gw_IWS(p_value: number) { + g_gw_IWS = p_value; +} +function set_gw_WDU(p_value: number) { + g_gw_WDU = p_value; +} +function set_gw_WVE(p_value: number) { + g_gw_WVE = p_value; +} +function set_gw_WWD(p_value: number) { + g_gw_WWD = p_value; +} +function set_spz_list(p_value: string | null) { + g_spz_list = p_value; +} +function set_spz_id_list(p_value: string) { + g_spz_id_list = p_value; +} +//--***END SETTERS + +/* + * Procedure: apply_superior_prov_limits + * Purpose: Apply superior provenance limits + */ +function apply_superior_prov_limits() { + let b_first_row_processed: boolean; + let v_spz_list: string = ''; + + interface c_sup { + genetic_worth_code: string; + genetic_worth_rating: number; + limit_up_elevation: number; + limit_down_elevation: number; + limit_elevation_min: number; + limit_elevation_max: number; + limit_latitude_min_degrees: number; + limit_latitude_min_minutes: number; + limit_latitude_max_degrees: number; + limit_latitude_max_minutes: number; + seed_plan_zone_code: string; + } + + //--Limits and SPZ's for B Class sup provenance + /* CURSOR c_sup IS + SELECT sp.genetic_worth_code + , sp.genetic_worth_rating + , sp.limit_up_elevation + , sp.limit_down_elevation + , sp.limit_elevation_min + , sp.limit_elevation_max + , sp.limit_latitude_min_degrees + , sp.limit_latitude_min_minutes + , sp.limit_latitude_max_degrees + , sp.limit_latitude_max_minutes + , spz.seed_plan_zone_code + FROM superior_provenance sp + , superior_provenance_plan_zone spz + WHERE sp.provenance_id = g_provenance_id + AND sp.vegetation_code = g_vegetation_code + AND spz.provenance_id = sp.provenance_id + ORDER BY spz.seed_plan_zone_code;*/ + + b_first_row_processed = false; + let c_sup: c_sup[] = []; + + //--Derive Superior Provenance limits + for (let r_sup in c_sup) { + // --Area of Use SPZ's + v_spz_list = v_spz_list || c_sup[r_sup].seed_plan_zone_code || ','; + if (!b_first_row_processed) { + //--Set Genetic Worth + if (c_sup[r_sup].genetic_worth_code == 'AD' && replace_area_of_use(g_gw_AD)) { + set_gw_AD(c_sup[r_sup].genetic_worth_rating); + } else if (c_sup[r_sup].genetic_worth_code == 'DFS' && replace_area_of_use(g_gw_DFS)) { + set_gw_DFS(c_sup[r_sup].genetic_worth_rating); + } else if (c_sup[r_sup].genetic_worth_code == 'DFU' && replace_area_of_use(g_gw_DFU)) { + set_gw_DFU(c_sup[r_sup].genetic_worth_rating); + } else if (c_sup[r_sup].genetic_worth_code == 'DFW' && replace_area_of_use(g_gw_DFW)) { + set_gw_DFW(c_sup[r_sup].genetic_worth_rating); + } else if (c_sup[r_sup].genetic_worth_code == 'DSB' && replace_area_of_use(g_gw_DSB)) { + set_gw_DSB(c_sup[r_sup].genetic_worth_rating); + } else if (c_sup[r_sup].genetic_worth_code == 'DSC' && replace_area_of_use(g_gw_DSC)) { + set_gw_DSC(c_sup[r_sup].genetic_worth_rating); + } else if (c_sup[r_sup].genetic_worth_code == 'DSG' && replace_area_of_use(g_gw_DSG)) { + set_gw_DSG(c_sup[r_sup].genetic_worth_rating); + } else if (c_sup[r_sup].genetic_worth_code == 'GVO' && replace_area_of_use(g_gw_GVO)) { + set_gw_GVO(c_sup[r_sup].genetic_worth_rating); + } else if (c_sup[r_sup].genetic_worth_code == 'IWS' && replace_area_of_use(g_gw_IWS)) { + set_gw_IWS(c_sup[r_sup].genetic_worth_rating); + } else if (c_sup[r_sup].genetic_worth_code == 'WDU' && replace_area_of_use(g_gw_WDU)) { + set_gw_WDU(c_sup[r_sup].genetic_worth_rating); + } else if (c_sup[r_sup].genetic_worth_code == 'WVE' && replace_area_of_use(g_gw_WVE)) { + set_gw_WVE(c_sup[r_sup].genetic_worth_rating); + } else if (c_sup[r_sup].genetic_worth_code == 'WWD' && replace_area_of_use(g_gw_WWD)) { + set_gw_WWD(c_sup[r_sup].genetic_worth_rating); + } + + g_collection_elevation = g_collection_elevation == null? 0 : g_collection_elevation; + + if (is_area_of_use_status() || g_elevation_min == null) { + // --Set Area of Use Min Elevation + // -->limits can be up/down or a min/max + if (c_sup[r_sup].limit_down_elevation != null) { + // --Calculate min and max based on Collection Mean Elev + set_elevation_min(g_collection_elevation - c_sup[r_sup].limit_down_elevation); + set_elevation_max(g_collection_elevation + c_sup[r_sup].limit_up_elevation); + } else { + // --Take Min and Max Elevation directly from table + set_elevation_min (c_sup[r_sup].limit_elevation_min); + } + } + + if (is_area_of_use_status() || g_elevation_max == null) { + // --Set Area of Use Min/Max Elevation + // -->limits can be up/down or a min/max + if (c_sup[r_sup].limit_up_elevation != null) { + // --Calculate min and max based on Collection Mean Elev + set_elevation_max(g_collection_elevation + c_sup[r_sup].limit_up_elevation); + } else { + // --Take Min and Max Elevation directly from table + set_elevation_max(c_sup[r_sup].limit_elevation_max); + } + } + } + + if (is_area_of_use_status() || is_area_of_use_lat_min_empty()) { + // --Set Area of Use Min/Max Lat + if (c_sup[r_sup].limit_latitude_min_degrees != null) { + set_latitude_deg_min (c_sup[r_sup].limit_latitude_min_degrees); + set_latitude_min_min(c_sup[r_sup].limit_latitude_min_minutes); + set_latitude_sec_min(0); + } else { + // --Set to mean collection lat + set_latitude_deg_min(g_collection_lat_deg); + set_latitude_min_min(g_collection_lat_min); + set_latitude_sec_min(g_collection_lat_sec); + } + } + + if (is_area_of_use_status() || is_area_of_use_lat_max_empty()) { + if (c_sup[r_sup].limit_latitude_min_degrees != null) { + set_latitude_deg_max(c_sup[r_sup].limit_latitude_max_degrees); + set_latitude_min_max(c_sup[r_sup].limit_latitude_max_minutes); + set_latitude_sec_max(0); + } else { + // --Set to mean collection lat + set_latitude_deg_max(g_collection_lat_deg); + set_latitude_min_max(g_collection_lat_min); + set_latitude_sec_max(g_collection_lat_sec); + } + } + + //--Currently no longitude limits for superior provenance lots + //--(defined by SPZ's) so set to collection mean + if (is_area_of_use_status() || is_area_of_use_long_min_empty()) { + set_longitude_deg_min(g_collection_long_deg); + set_longitude_min_min(g_collection_long_min); + set_longitude_sec_min(g_collection_long_sec); + } + if (is_area_of_use_status() || is_area_of_use_long_max_empty()) { + set_longitude_deg_max(g_collection_long_deg); + set_longitude_min_max(g_collection_long_min); + set_longitude_sec_max(g_collection_long_sec); + } + b_first_row_processed = true; + } + + if (replace_area_of_use(g_spz_list)) { + // Original: https://docs.oracle.com/en/database/oracle/oracle-database/19/sqlrf/RTRIM.html + //set_spz_list(RTRIM(v_spz_list, ',')); + if (v_spz_list.lastIndexOf(',') == v_spz_list.length-1) { + v_spz_list = v_spz_list.substring(0, v_spz_list.length-1); + } + set_spz_list(v_spz_list); + } +} + +/* + * Procedure: apply_no_superior_prov_limits + * Purpose: Apply NO superior provenance limits for Class B lots from within British Columbia (BC Source=Y) + */ +function apply_no_superior_prov_limits() { + let e_no_limits_found: Error | null = null; // EXCEPTION; + // --Limits for B Class non-superior provenance + // CURSOR c_limit IS: + interface ic_limit { + transfer_limit_skey: string; + site_min_latdeg: number; + site_max_latdeg: number; + limit_north_latdeg: number; + limit_north_latmnt: number; + limit_south_latdeg: number; + limit_south_latmnt: number; + limit_east_longdeg: number; + limit_east_longmnt: number; + limit_west_longdeg: number; + limit_west_longmnt: number; + limit_up_elevatn: number; + limit_down_elevatn: number; + } + /* + SELECT transfer_limit_skey + , site_min_latdeg + , site_max_latdeg + , limit_north_latdeg + , limit_north_latmnt + , limit_south_latdeg + , limit_south_latmnt + , limit_east_longdeg + , limit_east_longmnt + , limit_west_longdeg + , limit_west_longmnt + , limit_up_elevatn + , limit_down_elevatn + FROM transfer_limit + WHERE (vegetation_code = g_vegetation_code || vegetation_code is null) + AND genetic_class_code = g_genetic_class_code + AND superior_prvnc_ind = NVL(g_superior_prvnc_ind, 'N') + AND coast_interior_code = spr_get_spz_type(g_seed_plan_zone_code) + AND (seed_plan_zone_code = g_seed_plan_zone_code || seed_plan_zone_code is null) + AND g_collection_lat_deg BETWEEN site_min_latdeg AND site_max_latdeg + ORDER BY vegetation_code, seed_plan_zone_code; + */ + let r_limit: number = 0; // c_limit%ROWTYPE; + let c_limit: ic_limit[] = []; // c_limit%ROWTYPE; + + // --B Class with No Superior Provenance has no Genetic Worth + g_gw_AD = null; + g_gw_DFS = null; + g_gw_DFU = null; + g_gw_DFW = null; + g_gw_DSB = null; + g_gw_DSC = null; + g_gw_DSG = null; + g_gw_GVO = null; + g_gw_IWS = null; + g_gw_WDU = null; + g_gw_WVE = null; + g_gw_WWD = null; + // --Area of Use SPZ = Collection SPZ + if (replace_area_of_use(g_spz_list)) { + set_spz_list(g_seed_plan_zone_code); + } + // --Derive Non Superior Provenance limits + // OPEN c_limit; FETCH c_limit INTO r_limit; CLOSE c_limit; + if (c_limit[r_limit].transfer_limit_skey == null) { + e_no_limits_found = new Error('e_no_limits_found'); + } + // --Set Area of Use Min/Max Elevation + g_collection_elevation = g_collection_elevation == null? 0 : g_collection_elevation; + c_limit[r_limit].limit_down_elevatn = c_limit[r_limit].limit_down_elevatn == null? 0 : c_limit[r_limit].limit_down_elevatn; + if (is_area_of_use_status() || g_elevation_min == null) { + set_elevation_min(g_collection_elevation - c_limit[r_limit].limit_down_elevatn); + } + // -->Ensure elevation is at least 1m + set_elevation_min(Math.max(nvlNumber(g_elevation_min,0),1)); + if (is_area_of_use_status() || g_elevation_max == null) { + set_elevation_max(g_collection_elevation + c_limit[r_limit].limit_up_elevatn); + } + // --Set Area of Use Min Lat + g_collection_lat_deg = g_collection_lat_deg == null? 0 : g_collection_lat_deg; + g_collection_lat_min = g_collection_lat_min == null? 0 : g_collection_lat_min; + g_latitude_min_min = g_latitude_min_min == null? 0 : g_latitude_min_min; + g_latitude_deg_min = g_latitude_deg_min == null? 0 : g_latitude_deg_min; + if (is_area_of_use_status() || is_area_of_use_lat_min_empty()) { + // -->Min + set_latitude_deg_min(g_collection_lat_deg - nvlNumber(c_limit[r_limit].limit_south_latdeg,0)); + set_latitude_min_min(g_collection_lat_min - nvlNumber(c_limit[r_limit].limit_south_latmnt,0)); + set_latitude_sec_min(g_collection_lat_sec); + // -->Adjust for provincial boundary at 48 00 00 + if (g_latitude_min_min < 0) { + set_latitude_deg_min(g_latitude_deg_min - 1); + set_latitude_min_min(g_latitude_min_min + 60); + } + if (g_latitude_deg_min < 48) { + set_latitude_deg_min(48); + set_latitude_min_min(0); + set_latitude_sec_min(0); + } + } + // --Set Area of Use Max Lat + if (is_area_of_use_status() || is_area_of_use_lat_max_empty()) { + // -->Max + set_latitude_deg_max(g_collection_lat_deg + nvlNumber(c_limit[r_limit].limit_north_latdeg,0)); + set_latitude_min_max(g_collection_lat_min + nvlNumber(c_limit[r_limit].limit_north_latmnt,0)); + set_latitude_sec_max(g_collection_lat_sec); + // -->Adjust for provincial boundary at 60 00 00 + if (nvlNumber(g_latitude_min_max, 0) > 60) { + set_latitude_deg_max(nvlNumber(g_latitude_deg_max, 0) + 1); + set_latitude_min_max(nvlNumber(g_latitude_min_max, 0) - 60); + } + if (nvlNumber(g_latitude_deg_max, 0) > 60) { + set_latitude_deg_max(60); + set_latitude_min_max(0); + set_latitude_sec_max(0); + } + } + // --Set Area of Use Min Long + if (is_area_of_use_status() || is_area_of_use_long_min_empty()) { + // -->Min + set_longitude_deg_min(nvlNumber(g_collection_long_deg, 0) - nvlNumber(c_limit[r_limit].limit_east_longdeg,0)); + set_longitude_min_min(nvlNumber(g_collection_long_min, 0) - nvlNumber(c_limit[r_limit].limit_east_longmnt,0)); + set_longitude_sec_min(g_collection_long_sec); + // -->Adjust for provincial boundary at 114 00 00 + if (nvlNumber(g_longitude_min_min, 0) < 0) { + set_longitude_deg_min(nvlNumber(g_longitude_deg_min, 0) - 1); + set_longitude_min_min(nvlNumber(g_longitude_min_min, 0) + 60); + } + if (nvlNumber(g_longitude_deg_min, 0) < 114) { + set_longitude_deg_min(114); + set_longitude_min_min(0); + set_longitude_sec_min(0); + } + } + // --Set Area of Use Min Long + if (is_area_of_use_status() || is_area_of_use_long_max_empty()) { + // -->Max + set_longitude_deg_max(nvlNumber(g_collection_long_deg, 0) + nvlNumber(c_limit[r_limit].limit_west_longdeg,0)); + set_longitude_min_max(nvlNumber(g_collection_long_min, 0) + nvlNumber(c_limit[r_limit].limit_west_longmnt,0)); + set_longitude_sec_max(g_collection_long_sec); + // -->Adjust for provincial boundary at 140 00 00 + if (nvlNumber(g_longitude_min_max, 0) > 60) { + set_longitude_deg_max(nvlNumber(g_longitude_deg_max, 0) + 1); + set_longitude_min_max(nvlNumber(g_longitude_min_max, 0) - 60); + } + if (nvlNumber(g_longitude_deg_max, 0) > 140) { + set_longitude_deg_max(140); + set_longitude_min_max(0); + set_longitude_sec_max(0); + } + } + if (e_no_limits_found) { + throw e_no_limits_found; + } +} + +/* + * Function: get_untested_common_spz + * Purpose: Get the SPZB that all Untested Parent Trees have in common. + * if (>1 SPZB found) { returns null + */ +function get_untested_common_spz(): string | null { + let v_spz: string; // parent_tree.seed_plan_zone_code%TYPE; + + let sql = ` + SELECT DISTINCT pt.seed_plan_zone_code + FROM parent_tree pt + , seedlot_parent_tree_gen_qlty sptgq + , seedlot_parent_tree spt --select from temp table for recalc + WHERE spt.seedlot_number = g_seedlot_number + AND spt.seedlot_number = sptgq.seedlot_number + AND spt.parent_tree_id = sptgq.parent_tree_id + AND pt.parent_tree_id = spt.parent_tree_id + AND sptgq.untested_ind = 'Y'; + `; + + v_spz = getFakeResultFromSql(sql, ''); + if (v_spz == 'TOO_MANY_ROWS' || 'NO_DATA_FOUND') { + throw new Error(v_spz); + return null; + } + + return v_spz; +} + +/* + * Procedure: get_a_class_area_of_use_spz + * Purpose: Get primary and non-primary SPZ's for A class lot. + */ +function get_a_class_area_of_use_spz() { + // CURSOR c_tested IS: + interface ic_tested { + seed_plan_zone_code: string; + primary_ind: string; + } + + const sql = ` + SELECT spz.seed_plan_zone_code , spz.primary_ind + FROM tested_pt_area_of_use_spz spz + , tested_pt_area_of_use aou + WHERE aou.seed_plan_unit_id = g_seed_plan_unit_id + AND spz.tested_pt_area_of_use_id = aou.tested_pt_area_of_use_id + ORDER BY 2 DESC; + `; + + let c_tested: ic_tested[] = getFakeResultFromSql(sql, []); + + // --Always reset for INC,PND + if (replace_area_of_use(g_spz_list)) { + // --Tested Parent Trees + if (['TPT','CUS'].includes(nvlString(g_seedlot_source_code, '')) && g_seed_plan_unit_id != null) { + // --Get primary/secondary SPZ's (primary sorted first) + set_spz_list(null); + for (let r_tested in c_tested) { + set_spz_list(g_spz_list || c_tested[r_tested].seed_plan_zone_code || ','); + } + } + // --Untested Parent Trees + else if (g_seedlot_source_code == 'UPT') { + // --Primary SPZ is the SPZ that all PT's share + set_spz_list(get_untested_common_spz()); + } + // --Remove trailing comma + set_spz_list(rTrim(g_spz_list, ',')); + } +} + +/* + * Procedure: get_tested_area_of_use_geog + * Purpose: Get Area of Use Geography for A Class lots from Tested Parent Trees + */ +function get_tested_area_of_use_geog() { + // --First row contains all necessary information + // CURSOR c_tested IS: + /* + SELECT MIN(spu.elevation_min) elevation_min + , MAX(spu.elevation_max) elevation_max + , MIN(spu.latitude_degrees_min +(spu.latitude_minutes_min / 60)) latitude_min + , MAX(spu.latitude_degrees_max +(spu.latitude_minutes_max / 60)) latitude_max + FROM tested_pt_area_of_use primary_spu + , tested_pt_area_of_use_spu included_spus + , seed_plan_unit spu + WHERE primary_spu.seed_plan_unit_id = g_seed_plan_unit_id + AND included_spus.tested_pt_area_of_use_id = primary_spu.tested_pt_area_of_use_id + AND included_spus.seed_plan_unit_id = spu.seed_plan_unit_id; + */ + let r_tested: any; // c_tested%ROWTYPE; + if (g_seed_plan_unit_id != null) { + // OPEN c_tested; FETCH c_tested INTO r_tested; CLOSE c_tested; + if (replace_area_of_use(g_elevation_min)) { + set_elevation_min(r_tested.elevation_min); + } + + if (replace_area_of_use(g_elevation_max)) { + set_elevation_max(r_tested.elevation_max); + } + + if (is_area_of_use_status() || is_area_of_use_lat_min_empty()) { + if (r_tested.latitude_min == null) { + // --No lat limits - set to mean of parent trees + set_latitude_deg_min(g_collection_lat_deg); + set_latitude_min_min(g_collection_lat_min); + set_latitude_sec_min(g_collection_lat_sec); + } else { + set_latitude_deg_min(Math.trunc(r_tested.latitude_min)); + set_latitude_min_min((r_tested.latitude_min - Math.trunc(r_tested.latitude_min)) * 60); + set_latitude_sec_min(0); + } + } + if (is_area_of_use_status() || is_area_of_use_lat_max_empty()) { + if (r_tested.latitude_max == null) { + // --No lat limits - set to mean of parent trees + set_latitude_deg_max(g_collection_lat_deg); + set_latitude_min_max(g_collection_lat_min); + set_latitude_sec_max(g_collection_lat_sec); + } else { + set_latitude_deg_max(Math.trunc(r_tested.latitude_max)); + set_latitude_min_max((r_tested.latitude_max - Math.trunc(r_tested.latitude_max)) * 60); + set_latitude_sec_max(0); + } + } + // --Currently no longitude limits for A class lots (defined by SPZ's) so set to collection mean + if (is_area_of_use_status() || is_area_of_use_long_min_empty()) { + set_longitude_deg_min(g_collection_long_deg); + set_longitude_min_min(g_collection_long_min); + set_longitude_sec_min(g_collection_long_sec); + } + if (is_area_of_use_status() || is_area_of_use_long_max_empty()) { + set_longitude_deg_max(g_collection_long_deg); + set_longitude_min_max(g_collection_long_min); + set_longitude_sec_max(g_collection_long_sec); + } + } +} + +/* + * Procedure: get_untested_area_of_use_geog + * Purpose: Get Area of Use Geography for A Class lots from Untested Parent Trees or Custom Lots + */ +function get_untested_area_of_use_geog() { + let e_no_limits_found: Error; // EXCEPTION; + let v_spz: string; // parent_tree.seed_plan_zone_code%TYPE; + let r_limit: number; // c_limit%ROWTYPE; + // CURSOR c_limit IS: + /* + SELECT transfer_limit_skey + , site_min_latdeg + , site_max_latdeg + , limit_north_latdeg + , limit_north_latmnt + , limit_south_latdeg + , limit_south_latmnt + , limit_east_longdeg + , limit_east_longmnt + , limit_west_longdeg + , limit_west_longmnt + , limit_up_elevatn + , limit_down_elevatn + FROM transfer_limit + WHERE ( vegetation_code = g_vegetation_code || vegetation_code is null) + AND genetic_class_code = g_genetic_class_code + AND coast_interior_code = spr_get_spz_type(v_spz) + AND (seed_plan_zone_code = v_spz || seed_plan_zone_code is null) + AND g_collection_lat_deg BETWEEN site_min_latdeg AND site_max_latdeg + ORDER BY vegetation_code, seed_plan_zone_code; + */ + + //--Limits are retrieved based on SPZB common to untested parent trees + v_spz = get_untested_common_spz(); + // --Derive Non Superior Provenance limits + // OPEN c_limit; FETCH c_limit INTO r_limit; CLOSE c_limit; + if (r_limit.transfer_limit_skey == null) { + throw new Error(e_no_limits_found); + } + // --Set Area of Use Min/Max Elevation + if (replace_area_of_use(g_elevation_min)) { + set_elevation_min(g_collection_elevation - nvl(r_limit.limit_down_elevatn,0)); + } + if (replace_area_of_use(g_elevation_max)) { + set_elevation_max(g_collection_elevation + nvl(r_limit.limit_up_elevatn,0)); + } + // -->Ensure elevation is at least 1m + set_elevation_min(Math.max(nvlNumber(g_elevation_min,0),1)); + // --Set Area of Use Min/Max Lat + // -->Min + if (is_area_of_use_lat_min_empty() || is_area_of_use_status()) { + set_latitude_deg_min(nvlNumber(g_collection_lat_deg, 0) - nvlValue(r_limit.limit_south_latdeg,0)); + set_latitude_min_min(nvlNumber(g_collection_lat_min, 0) - nvlNumber(r_limit.limit_south_latmnt,0)); + set_latitude_sec_min(g_collection_lat_sec); + // -->Adjust for provincial boundary at 48 00 00 + if (nvlNumber(g_latitude_min_min, 0) < 0) { + set_latitude_deg_min(nvlNumber(g_latitude_deg_min,0) - 1); + set_latitude_min_min(nvlNumber(g_latitude_min_min, 0) + 60); + } + if (nvlNumber(g_latitude_deg_min, 0) < 48) { + set_latitude_deg_min(48); + set_latitude_min_min(0); + set_latitude_sec_min(0); + } + } + // -->Max + if (is_area_of_use_lat_max_empty() || is_area_of_use_status()) { + set_latitude_deg_max(g_collection_lat_deg + nvl(r_limit.limit_north_latdeg,0)); + set_latitude_min_max(g_collection_lat_min + nvl(r_limit.limit_north_latmnt,0)); + set_latitude_sec_max(g_collection_lat_sec); + // -->Adjust for provincial boundary at 60 00 00 + if (nvlNumber(g_latitude_min_max, 0) > 60) { + set_latitude_deg_max(nvl(g_latitude_deg_max,0) + 1); + set_latitude_min_max(nvl(g_latitude_min_max,0) - 60); + } + if (nvlNumber(g_latitude_deg_max, 0) > 60) { + set_latitude_deg_max(60); + set_latitude_min_max(0); + set_latitude_sec_max(0); + } + } + // --Set Area of Use Min/Max Long + // -->Min + if (is_area_of_use_long_min_empty() || is_area_of_use_status()) { + set_longitude_deg_min(nvlNumber(g_collection_long_deg, 0) - nvlNumber(r_limit.limit_east_longdeg,0)); + set_longitude_min_min(nvlNumber(g_collection_long_min, 0) - nvlNumber(r_limit.limit_east_longmnt,0)); + set_longitude_sec_min(g_collection_long_sec); + // -->Adjust for provincial boundary at 114 00 00 + if (nvlNumber(g_longitude_min_min, 0) < 0) { + set_longitude_deg_min(nvlNumber(g_longitude_deg_min,0) - 1); + set_longitude_min_min(nvlNumber(g_longitude_min_min,0) + 60); + } + if (nvlNumber(g_longitude_deg_min, 0) < 114) { + set_longitude_deg_min(114); + set_longitude_min_min(0); + set_longitude_sec_min(0); + } + } + // -->Max + if (is_area_of_use_long_max_empty() || is_area_of_use_status()) { + set_longitude_deg_max(nvlNumber(g_collection_long_deg, 0) + nvlNumber(r_limit.limit_west_longdeg,0)); + set_longitude_min_max(nvlNumber(g_collection_long_min, 0) + nvlNumber(r_limit.limit_west_longmnt,0)); + set_longitude_sec_max(g_collection_long_sec); + // -->Adjust for provincial boundary at 140 00 00 + if (nvlNumber(g_longitude_min_max, 0) > 60) { + set_longitude_deg_max(nvlNumber(g_longitude_deg_max,0) + 1); + set_longitude_min_max(nvlNumber(g_longitude_min_max,0) - 60); + } + if (nvlNumber(g_longitude_deg_max, 0) > 140) { + set_longitude_deg_max(140); + set_longitude_min_max(0); + set_longitude_sec_max(0); + } + } + if (e_no_limits_found) { + throw new Error(e_no_limits_found); + } +} + +/* + * Procedure: get_a_class_area_of_use_geog + * Purpose: Get Area of Use Geography for A Class lots + */ +function get_a_class_area_of_use_geog() { + // --Tested Parent Trees + if (g_seedlot_source_code == 'TPT') { + get_tested_area_of_use_geog(); + } + // --Untested Parent Trees and Custom Lots + else if (['UPT','CUS'].includes(g_seedlot_source_code)) { + get_untested_area_of_use_geog(); + } +} + +/* + * Procedure: get_area_of_use + * Purpose: Derive area of use for A and B class lots. + * For certain statuses, defined by is_area_of_use_status(), + * Area of Use is always recalculated and replaced. + * For other statuses, each Area of Use item is only recalculated + * if it has been blanked-out (i.e. is null) by the user. + */ +function get_area_of_use() { + // --Blank area of use information for status where it will be replaced + if (is_area_of_use_status()) { + set_spz_list(null); + set_elevation_min(null); + set_elevation_max(null); + set_latitude_deg_min(null); + set_latitude_min_min(null); + set_latitude_sec_min(null); + set_latitude_deg_max(null); + set_latitude_min_max(null); + set_latitude_sec_max(null); + set_longitude_deg_min(null); + set_longitude_min_min(null); + set_longitude_sec_min(null); + set_longitude_deg_max(null); + set_longitude_min_max(null); + set_longitude_sec_max(null); + } + // --B Class limits + if (g_genetic_class_code == 'B') { + if (g_bc_source_ind == 'Y') { + if (g_superior_prvnc_ind == 'Y') { + apply_superior_prov_limits(); + } else if (g_superior_prvnc_ind == 'N') { + apply_no_superior_prov_limits(); + } + // --BC Source not entered - cannot derive limits + } + } + // --A Class limits + else { + get_a_class_area_of_use_geog(); + get_a_class_area_of_use_spz(); + } +} + +/* + * Procedure: provenance_is_valid_for_spp + * Purpose: Return true if Provenance is valid for species, otherwise return false. + */ +function provenance_is_valid_for_spp(): boolean { + let v_count: number; // NUMBER(10); + /* + SELECT COUNT(1) + INTO v_count + FROM superior_provenance + WHERE provenance_id = g_provenance_id + AND vegetation_code = g_vegetation_code; + */ + v_count = resultsql; + return v_count > 0; +} + +/* + * Procedure: spu_is_valid_for_species + * Purpose: Return true if Seed Plan Unit is valid for species, otherwise return false. + */ +function spu_is_valid_for_species(): boolean { + let v_count: number; // NUMBER(10); + /* + SELECT COUNT(1) + INTO v_count + FROM seed_plan_unit spu + , seed_plan_zone spz + WHERE spu.seed_plan_unit_id = g_seed_plan_unit_id + AND spz.seed_plan_zone_id = spu.seed_plan_zone_id + AND spz.vegetation_code = g_vegetation_code; + */ + v_count = resultsql; + return v_count > 0; +} + +/* + * Procedure: orchard_is_valid_for_species + * Purpose: Return true if Orchard passed-in is valid for species, otherwise return false. + */ +function orchard_is_valid_for_species(p_orchard_id: string): boolean { + let v_count: number; // NUMBER(10); + /* + SELECT COUNT(1) + INTO v_count + FROM orchard + WHERE orchard_id = p_orchard_id AND vegetation_code = g_vegetation_code; + */ + v_count = resultsql; + return v_count > 0; +} + +/* + * Procedure: set_mean_area_of_use_geography + * Purpose: Copy Mean Area of Use geography to Mean Collection geography + */ +function set_mean_area_of_use_geography() { + // --IMPORTANT: Mean Area of Use geography must be set as Transfer Guideline + // -- routines may use Mean Area of Use geography to derive + // -- Transfer Limits. + // -->Mean Elevation + if (g_elevation_min == g_elevation_max) { + // --->mean matches min and max + set_elevation(nvlNumber(g_elevation_max, 0)); + } else { + // --->default to collection elevation + set_elevation(nvlNumber(g_collection_elevation, 0)); + } + // -->Mean Latitude + if (g_latitude_deg_min == g_latitude_deg_max && g_latitude_min_min == g_latitude_min_max && g_latitude_sec_min == g_latitude_sec_max) { + // --->mean matches min and max + set_latitude_degrees(nvlNumber(g_latitude_deg_max, 0)); + set_latitude_minutes(nvlNumber(g_latitude_min_max, 0)); + set_latitude_seconds(nvlNumber(g_latitude_sec_max, 0)); + } else { + // --->default to mean collection lat + set_latitude_degrees(nvlNumber(g_collection_lat_deg, 0)); + set_latitude_minutes(nvlNumber(g_collection_lat_min, 0)); + set_latitude_seconds(nvlNumber(g_collection_lat_sec, 0)); + } + // -->Mean Longitude + if (g_longitude_deg_min == g_longitude_deg_max && g_longitude_min_min == g_longitude_min_max && g_longitude_sec_min == g_longitude_sec_max) { + // --->mean matches min and max + set_longitude_degrees(nvlNumber(g_longitude_deg_max, 0)); + set_longitude_minutes(nvlNumber(g_longitude_min_max, 0)); + set_longitude_seconds(nvlNumber(g_longitude_sec_max, 0)); + } else { + // --->default to mean collection long + set_longitude_degrees(nvlNumber(g_collection_long_deg, 0)); + set_longitude_minutes(nvlNumber(g_collection_long_min, 0)); + set_longitude_seconds(nvlNumber(g_collection_long_sec, 0)); + } +} + +/* + * Procedure: load_array + * Purpose: Populate array from saved data if (array != present. + * if p_get_current_tests is True: + * - Populate the BV/CV-G value in the array (whether passed-in + * or just generated as described above) with gq value flagged + * for use in calc . + */ +function load_array(p_pt_arrayZ: any[], p_get_current_tests: boolean) { + let v_current_test_ind: string; // VARCHAR2(1); + let v_genetic_type_code: string; // parent_tree_genetic_quality.genetic_type_code%TYPE; + let r_gq: string; // c_gq%ROWTYPE; + + // CURSOR c_gq (p_parent_tree_id: number, p_seed_plan_unit_id: number, p_genetic_type_code: string, p_genetic_worth_code :string) IS: + /* + SELECT ptgq.genetic_quality_value + FROM parent_tree_genetic_quality ptgq + , parent_tree pt + WHERE pt.parent_tree_id = p_parent_tree_id + AND pt.parent_tree_reg_status_code = 'APP' + AND pt.active_ind = 'Y' + AND ptgq.parent_tree_id = pt.parent_tree_id + AND ptgq.seed_plan_unit_id = p_seed_plan_unit_id + AND ptgq.genetic_type_code = p_genetic_type_code + AND ptgq.genetic_worth_code = p_genetic_worth_code + AND ptgq.genetic_worth_calc_ind = 'Y'; + */ + + // CURSOR c_refresh_list IS: + /* + SELECT parent_tree_id, parent_tree_number + , nvl(bv_AD_est, cv_AD_est) AD_estimated_ind + , nvl(bv_DFS_est,cv_DFS_est) DFS_estimated_ind + , nvl(bv_DFU_est,cv_DFU_est) DFU_estimated_ind + , nvl(bv_DFW_est,cv_DFW_est) DFW_estimated_ind + , nvl(bv_DSB_est,cv_DSB_est) DSB_estimated_ind + , nvl(bv_DSC_est,cv_DSC_est) DSC_estimated_ind + , nvl(bv_DSG_est,cv_DSG_est) DSG_estimated_ind + , nvl(bv_GVO_est,cv_GVO_est) GVO_estimated_ind + , nvl(bv_IWS_est,cv_IWS_est) IWS_estimated_ind + , nvl(bv_WDU_est,cv_WDU_est) WDU_estimated_ind + , nvl(bv_WVE_est,cv_WVE_est) WVE_estimated_ind + , nvl(bv_WWD_est,cv_WWD_est) WWD_estimated_ind + , untested_ind , bv_AD, bv_DFS, bv_DFU, bv_DFW + , bv_DSB, bv_DSC, bv_DSG, bv_GVO , bv_IWS, bv_WDU, bv_WVE, bv_WWD + , cv_AD, cv_DFS, cv_DFU, cv_DFW , cv_DSB, cv_DSC, cv_DSG, cv_GVO + , cv_IWS, cv_WDU, cv_WVE, cv_WWD + , cone_count + , pollen_count + , smp_success_pct + , smp_mix_latitude_degrees + , smp_mix_latitude_minutes + , smp_mix_longitude_degrees + , smp_mix_longitude_minutes + , smp_mix_elevation + , bv_AD_smp_mix smp_mix_bv_AD + , bv_DFS_smp_mix smp_mix_bv_DFS + , bv_DFU_smp_mix smp_mix_bv_DFU + , bv_DFW_smp_mix smp_mix_bv_DFW + , bv_DSB_smp_mix smp_mix_bv_DSB + , bv_DSC_smp_mix smp_mix_bv_DSC + , bv_DSG_smp_mix smp_mix_bv_DSG + , bv_GVO_smp_mix smp_mix_bv_GVO + , bv_IWS_smp_mix smp_mix_bv_IWS + , bv_WDU_smp_mix smp_mix_bv_WDU + , bv_WVE_smp_mix smp_mix_bv_WVE + , bv_WWD_smp_mix smp_mix_bv_WWD + , non_orchard_pollen_contam_pct + , total_genetic_worth_contrib + , revision_count + FROM ( + SELECT pt.parent_tree_id + , pt.parent_tree_number + , decode(v_current_test_ind,'Y',decode(gq.genetic_quality_value,null,sptgq.genetic_quality_value,gq.genetic_quality_value),sptgq.genetic_quality_value) genetic_quality_value + , decode(v_current_test_ind,'Y',decode(gq.genetic_quality_value,null,sptgq.estimated_ind,'N'),sptgq.estimated_ind) estimated_ind + , decode(v_current_test_ind,'Y',decode(gq.genetic_quality_value,null,sptgq.untested_ind,'N'),sptgq.untested_ind) untested_ind + , gwc.genetic_worth_code + , gtc.genetic_type_code + , spt.cone_count + , spt.pollen_count + , spt.smp_success_pct + , spt.smp_mix_latitude_degrees + , spt.smp_mix_latitude_minutes + , spt.smp_mix_longitude_degrees + , spt.smp_mix_longitude_minutes + , spt.smp_mix_elevation + , spt.non_orchard_pollen_contam_pct + , spt.total_genetic_worth_contrib + , spt.revision_count + , sptsm.smp_mix_value + FROM + PARENT_TREE pt + LEFT OUTER JOIN PARENT_TREE fem_pt on pt.parent_tree_id = fem_pt.parent_tree_id + LEFT OUTER JOIN SEEDLOT_PARENT_TREE spt on pt.parent_tree_id = spt.parent_tree_id + LEFT OUTER JOIN SEEDLOT_PARENT_TREE_SMP_MIX sptsm on pt.parent_tree_id = sptsm.parent_tree_id AND spt.seedlot_number = sptsm.seedlot_number + LEFT OUTER JOIN SEEDLOT_PARENT_TREE_GEN_QLTY sptgq on spt.parent_tree_id = sptgq.parent_tree_id AND spt.seedlot_number = sptgq.seedlot_number + LEFT OUTER JOIN PARENT_TREE_GENETIC_QUALITY gq on sptgq.parent_tree_id = gq.parent_tree_id AND gq.genetic_worth_calc_ind = 'Y' + JOIN GENETIC_WORTH_CODE gwc on sptgq.genetic_worth_code = gwc.genetic_worth_code --AND sptsm.genetic_worth_code = gwc.genetic_worth_code + JOIN GENETIC_TYPE_CODE gtc on sptgq.genetic_type_code = gtc.genetic_type_code --AND sptsm.genetic_type_code = gtc.genetic_type_code + WHERE pt.parent_tree_reg_status_code = 'APP' + AND spt.seedlot_number = g_seedlot_number + AND pt.active_ind = 'Y' + ) + PIVOT + (MAX(estimated_ind) est + ,MAX(genetic_quality_value) + ,MAX(smp_mix_value) smp_mix + FOR (genetic_type_code,genetic_worth_code) + IN (('BV','AD') as bv_AD, + ('BV','DFS') as bv_DFS, + ('BV','DFU') as bv_DFU, + ('BV','DFW') as bv_DFW, + ('BV','DSB') as bv_DSB, + ('BV','DSC') as bv_DSC, + ('BV','DSG') as bv_DSG, + ('BV','GVO') as bv_GVO, + ('BV','IWS') as bv_IWS, + ('BV','WDU') as bv_WDU, + ('BV','WVE') as bv_WVE, + ('BV','WWD') as bv_WWD, + ('CV','AD') as cv_AD, + ('CV','DFS') as cv_DFS, + ('CV','DFU') as cv_DFU, + ('CV','DFW') as cv_DFW, + ('CV','DSB') as cv_DSB, + ('CV','DSC') as cv_DSC, + ('CV','DSG') as cv_DSG, + ('CV','GVO') as cv_GVO, + ('CV','IWS') as cv_IWS, + ('CV','WDU') as cv_WDU, + ('CV','WVE') as cv_WVE, + ('CV','WWD') as cv_WWD)) + ORDER BY 1; + */ + let r_refresh_list: string; // c_refresh_list%ROWTYPE; + let r_contrib: string; // spar_parent_tree_contrib_temp%ROWTYPE; + let v_prev_parent_tree_id: number; //spar_parent_tree_contrib_temp.parent_tree_id%TYPE; + + if (p_pt_array == null) { + // --Set indicator to be used in query + v_current_test_ind = 'N'; + if (p_get_current_tests) { + v_current_test_ind = 'Y'; + } + // --Empty out global temp table + // DELETE FROM spar_parent_tree_contrib_temp; + // OPEN c_refresh_list; FETCH c_refresh_list INTO r_refresh_list; + while (c_refresh_list) { + r_contrib.parent_tree_id = r_refresh_list.parent_tree_id; + r_contrib.parent_tree_number = r_refresh_list.parent_tree_number; + if (r_refresh_list.bv_AD != null) { + r_contrib.bv_AD = r_refresh_list.bv_AD; + } + if (r_refresh_list.cv_AD != null) { + r_contrib.cv_AD = r_refresh_list.cv_AD; + } + if (r_refresh_list.bv_DFS != null) { + r_contrib.bv_DFS = r_refresh_list.bv_DFS; + } + if (r_refresh_list.cv_DFS != null) { + r_contrib.cv_DFS = r_refresh_list.cv_DFS; + } + if (r_refresh_list.bv_DFU != null) { + r_contrib.bv_DFU = r_refresh_list.bv_DFU; + } + if (r_refresh_list.cv_DFU != null) { + r_contrib.cv_DFU = r_refresh_list.cv_DFU; + } + if (r_refresh_list.bv_DFW != null) { + r_contrib.bv_DFW = r_refresh_list.bv_DFW; + } + if (r_refresh_list.cv_DFW != null) { + r_contrib.cv_DFW = r_refresh_list.cv_DFW; + } + if (r_refresh_list.bv_DSB != null) { + r_contrib.bv_DSB = r_refresh_list.bv_DSB; + } + if (r_refresh_list.cv_DSB != null) { + r_contrib.cv_DSB = r_refresh_list.cv_DSB; + } + if (r_refresh_list.bv_DSC != null) { + r_contrib.bv_DSC = r_refresh_list.bv_DSC; + } + if (r_refresh_list.cv_DSC != null) { + r_contrib.cv_DSC = r_refresh_list.cv_DSC; + } + if (r_refresh_list.bv_DSG != null) { + r_contrib.bv_DSG = r_refresh_list.bv_DSG; + } + if (r_refresh_list.cv_DSG != null) { + r_contrib.cv_DSG = r_refresh_list.cv_DSG; + } + if (r_refresh_list.bv_GVO != null) { + r_contrib.bv_GVO = r_refresh_list.bv_GVO; + } + if (r_refresh_list.cv_GVO != null) { + r_contrib.cv_GVO = r_refresh_list.cv_GVO; + } + if (r_refresh_list.bv_IWS != null) { + r_contrib.bv_IWS = r_refresh_list.bv_IWS; + } + if (r_refresh_list.cv_IWS != null) { + r_contrib.cv_IWS = r_refresh_list.cv_IWS; + } + if (r_refresh_list.bv_WDU != null) { + r_contrib.bv_WDU = r_refresh_list.bv_WDU; + } + if (r_refresh_list.cv_WDU != null) { + r_contrib.cv_WDU = r_refresh_list.cv_WDU; + } + if (r_refresh_list.bv_WVE != null) { + r_contrib.bv_WVE = r_refresh_list.bv_WVE; + } + if (r_refresh_list.cv_WVE != null) { + r_contrib.cv_WVE = r_refresh_list.cv_WVE; + } + if (r_refresh_list.bv_WWD != null) { + r_contrib.bv_WWD = r_refresh_list.bv_WWD; + } + if (r_refresh_list.cv_WWD != null) { + r_contrib.cv_WWD = r_refresh_list.cv_WWD; + } + // --Estimated ind - applies to Deer browse + if (r_refresh_list.AD_estimated_ind != null) { + r_contrib.AD_estimated_ind = r_refresh_list.AD_estimated_ind; + } + // --Estimated ind - applies to Dothistroma needle blight + if (r_refresh_list.DFS_estimated_ind != null) { + r_contrib.DFS_estimated_ind = r_refresh_list.DFS_estimated_ind; + } + // --Estimated ind - applies to Cedar leaf blight + if (r_refresh_list.DFU_estimated_ind != null) { + r_contrib.DFU_estimated_ind = r_refresh_list.DFU_estimated_ind; + } + // --Estimated ind - applies to Swiss neeld cast + if (r_refresh_list.DFW_estimated_ind != null) { + r_contrib.DFW_estimated_ind = r_refresh_list.DFW_estimated_ind; + } + // --Estimated ind - applies to White pine blister rust + if (r_refresh_list.DSB_estimated_ind != null) { + r_contrib.DSB_estimated_ind = r_refresh_list.DSB_estimated_ind; + } + // --Estimated ind - applies to ComADra blister rust + if (r_refresh_list.DSC_estimated_ind != null) { + r_contrib.DSC_estimated_ind = r_refresh_list.DSC_estimated_ind; + } + // --Estimated ind - applies to Western gall rust + if (r_refresh_list.DSG_estimated_ind != null) { + r_contrib.DSG_estimated_ind = r_refresh_list.DSG_estimated_ind; + } + // --Estimated ind - applies to Volume Growth + if (r_refresh_list.GVO_estimated_ind != null) { + r_contrib.GVO_estimated_ind = r_refresh_list.GVO_estimated_ind; + } + // --Estimated ind - applies to White pine terminal weevil + if (r_refresh_list.IWS_estimated_ind != null) { + r_contrib.IWS_estimated_ind = r_refresh_list.IWS_estimated_ind; + } + // --Estimated ind - applies to Durability + if (r_refresh_list.WDU_estimated_ind != null) { + r_contrib.WDU_estimated_ind = r_refresh_list.WDU_estimated_ind; + } + // --Estimated ind - applies to Wood velocity measures + if (r_refresh_list.WVE_estimated_ind != null) { + r_contrib.WVE_estimated_ind = r_refresh_list.WVE_estimated_ind; + } + // --Estimated ind - applies to Wood density + if (r_refresh_list.WWD_estimated_ind != null) { + r_contrib.WWD_estimated_ind = r_refresh_list.WWD_estimated_ind; + } + if (r_refresh_list.untested_ind != null) { + r_contrib.untested_ind = r_refresh_list.untested_ind; + } + // --Saved values + r_contrib.revision_count = nvlValue(r_contrib.revision_count, r_refresh_list.revision_count); + r_contrib.cone_count = r_contrib.cone_count == null? r_refresh_list.cone_count : r_contrib.cone_count; + r_contrib.pollen_count = nvl(r_contrib.pollen_count,r_refresh_list.pollen_count); + r_contrib.smp_success_pct = nvl(r_contrib.smp_success_pct,r_refresh_list.smp_success_pct); + r_contrib.smp_mix_bv_AD = nvlValue(r_contrib.smp_mix_bv_AD,r_refresh_list.smp_mix_bv_AD); + r_contrib.smp_mix_bv_DFS = nvlValue(r_contrib.smp_mix_bv_DFS,r_refresh_list.smp_mix_bv_DFS); + r_contrib.smp_mix_bv_DFU = nvlValue(r_contrib.smp_mix_bv_DFU,r_refresh_list.smp_mix_bv_DFU); + r_contrib.smp_mix_bv_DFW = nvlValue(r_contrib.smp_mix_bv_DFW,r_refresh_list.smp_mix_bv_DFW); + r_contrib.smp_mix_bv_DSB = nvlValue(r_contrib.smp_mix_bv_DSB,r_refresh_list.smp_mix_bv_DSB); + r_contrib.smp_mix_bv_DSC = nvlValue(r_contrib.smp_mix_bv_DSC,r_refresh_list.smp_mix_bv_DSC); + r_contrib.smp_mix_bv_DSG = nvlValue(r_contrib.smp_mix_bv_DSG,r_refresh_list.smp_mix_bv_DSG); + r_contrib.smp_mix_bv_GVO = nvlValue(r_contrib.smp_mix_bv_GVO,r_refresh_list.smp_mix_bv_GVO); + r_contrib.smp_mix_bv_IWS = nvlValue(r_contrib.smp_mix_bv_IWS,r_refresh_list.smp_mix_bv_IWS); + r_contrib.smp_mix_bv_WDU = nvlValue(r_contrib.smp_mix_bv_WDU,r_refresh_list.smp_mix_bv_WDU); + r_contrib.smp_mix_bv_WVE = nvlValue(r_contrib.smp_mix_bv_WVE,r_refresh_list.smp_mix_bv_WVE); + r_contrib.smp_mix_bv_WWD = nvlValue(r_contrib.smp_mix_bv_WWD,r_refresh_list.smp_mix_bv_WWD); + r_contrib.smp_mix_latitude_degrees = nvlValue(r_contrib.smp_mix_latitude_degrees,r_refresh_list.smp_mix_latitude_degrees); + r_contrib.smp_mix_latitude_minutes = nvlValue(r_contrib.smp_mix_latitude_minutes,r_refresh_list.smp_mix_latitude_minutes); + r_contrib.smp_mix_longitude_degrees = nvlValue(r_contrib.smp_mix_longitude_degrees,r_refresh_list.smp_mix_longitude_degrees); + r_contrib.smp_mix_longitude_minutes = nvlValue(r_contrib.smp_mix_longitude_minutes,r_refresh_list.smp_mix_longitude_minutes); + r_contrib.smp_mix_elevation = nvl(r_contrib.smp_mix_elevation,r_refresh_list.smp_mix_elevation); + r_contrib.non_orchard_pollen_contam_pct = nvl(r_contrib.non_orchard_pollen_contam_pct,r_refresh_list.non_orchard_pollen_contam_pct); + // --Save previous parent tree AND get next row + v_prev_parent_tree_id = r_refresh_list.parent_tree_id; + // FETCH c_refresh_list INTO r_refresh_list; + // --if (Parent Tree changed or last row + if (v_prev_parent_tree_id != r_refresh_list.parent_tree_id || c_refresh_list == null) { + // --Insert the row + /* + INSERT INTO spar_parent_tree_contrib_temp ( + parent_tree_id + , parent_tree_number + , AD_estimated_ind + , DFS_estimated_ind + , DFU_estimated_ind + , DFW_estimated_ind + , DSB_estimated_ind + , DSC_estimated_ind + , DSG_estimated_ind + , GVO_estimated_ind + , IWS_estimated_ind + , WDU_estimated_ind + , WVE_estimated_ind + , WWD_estimated_ind + , untested_ind + , bv_AD, bv_DFS, bv_DFU, bv_DFW + , bv_DSB, bv_DSC, bv_DSG, bv_GVO + , bv_IWS, bv_WDU, bv_WVE, bv_WWD + , cv_AD, cv_DFS, cv_DFU, cv_DFW + , cv_DSB, cv_DSC, cv_DSG, cv_GVO + , cv_IWS, cv_WDU, cv_WVE, cv_WWD + , cone_count + , pollen_count + , smp_success_pct + , smp_mix_bv_AD + , smp_mix_bv_DFS + , smp_mix_bv_DFU + , smp_mix_bv_DFW + , smp_mix_bv_DSB + , smp_mix_bv_DSC + , smp_mix_bv_DSG + , smp_mix_bv_GVO + , smp_mix_bv_IWS + , smp_mix_bv_WDU + , smp_mix_bv_WVE + , smp_mix_bv_WWD + , smp_mix_latitude_degrees + , smp_mix_latitude_minutes + , smp_mix_longitude_degrees + , smp_mix_longitude_minutes + , smp_mix_elevation + , non_orchard_pollen_contam_pct + , total_genetic_worth_contrib + , revision_count) + VALUES ( + r_contrib.parent_tree_id + , r_contrib.parent_tree_number + , r_contrib.AD_estimated_ind + , r_contrib.DFS_estimated_ind + , r_contrib.DFU_estimated_ind + , r_contrib.DFW_estimated_ind + , r_contrib.DSB_estimated_ind + , r_contrib.DSC_estimated_ind + , r_contrib.DSG_estimated_ind + , r_contrib.GVO_estimated_ind + , r_contrib.IWS_estimated_ind + , r_contrib.WDU_estimated_ind + , r_contrib.WVE_estimated_ind + , r_contrib.WWD_estimated_ind + , r_contrib.untested_ind + , r_contrib.bv_AD, r_contrib.bv_DFS, r_contrib.bv_DFU, r_contrib.bv_DFW + , r_contrib.bv_DSB, r_contrib.bv_DSC, r_contrib.bv_DSG, r_contrib.bv_GVO + , r_contrib.bv_IWS, r_contrib.bv_WDU, r_contrib.bv_WVE, r_contrib.bv_WWD + , r_contrib.cv_AD, r_contrib.cv_DFS, r_contrib.cv_DFU, r_contrib.cv_DFW + , r_contrib.cv_DSB, r_contrib.cv_DSC, r_contrib.cv_DSG, r_contrib.cv_GVO + , r_contrib.cv_IWS, r_contrib.cv_WDU, r_contrib.cv_WVE, r_contrib.cv_WWD + , r_contrib.cone_count + , r_contrib.pollen_count + , r_contrib.smp_success_pct + , r_contrib.smp_mix_bv_AD + , r_contrib.smp_mix_bv_DFS + , r_contrib.smp_mix_bv_DFU + , r_contrib.smp_mix_bv_DFW + , r_contrib.smp_mix_bv_DSB + , r_contrib.smp_mix_bv_DSC + , r_contrib.smp_mix_bv_DSG + , r_contrib.smp_mix_bv_GVO + , r_contrib.smp_mix_bv_IWS + , r_contrib.smp_mix_bv_WDU + , r_contrib.smp_mix_bv_WVE + , r_contrib.smp_mix_bv_WWD + , r_contrib.smp_mix_latitude_degrees + , r_contrib.smp_mix_latitude_minutes + , r_contrib.smp_mix_longitude_degrees + , r_contrib.smp_mix_longitude_minutes + , r_contrib.smp_mix_elevation + , r_contrib.non_orchard_pollen_contam_pct + , r_contrib.total_genetic_worth_contrib + , r_contrib.revision_count); + */ + r_contrib = null; + } + } + + /* + SELECT spr_001a_any( + parent_tree_id + , parent_tree_number + , AD_estimated_ind + , DFS_estimated_ind + , DFU_estimated_ind + , DFW_estimated_ind + , DSB_estimated_ind + , DSC_estimated_ind + , DSG_estimated_ind + , GVO_estimated_ind + , IWS_estimated_ind + , WDU_estimated_ind + , WVE_estimated_ind + , WWD_estimated_ind + , untested_ind + , bv_AD, bv_DFS, bv_DFU, bv_DFW + , bv_DSB, bv_DSC, bv_DSG, bv_GVO + , bv_IWS, bv_WDU, bv_WVE, bv_WWD + , cv_AD, cv_DFS, cv_DFU, cv_DFW + , cv_DSB, cv_DSC, cv_DSG, cv_GVO + , cv_IWS, cv_WDU, cv_WVE, cv_WWD + , cone_count + , pollen_count + , smp_success_pct + , smp_mix_latitude_degrees + , smp_mix_latitude_minutes + , smp_mix_longitude_degrees + , smp_mix_longitude_minutes + , smp_mix_elevation + , smp_mix_bv_AD + , smp_mix_bv_DFS + , smp_mix_bv_DFU + , smp_mix_bv_DFW + , smp_mix_bv_DSB + , smp_mix_bv_DSC + , smp_mix_bv_DSG + , smp_mix_bv_GVO + , smp_mix_bv_IWS + , smp_mix_bv_WDU + , smp_mix_bv_WVE + , smp_mix_bv_WWD + , non_orchard_pollen_contam_pct + , total_genetic_worth_contrib + , longitude_degrees + , longitude_minutes + , longitude_seconds + , latitude_degrees + , latitude_minutes + , latitude_seconds + , elevation + , geography_error_ind + , revision_count) + BULK COLLECT INTO p_pt_array + FROM spar_parent_tree_contrib_temp + ORDER BY DECODE(cone_count||pollen_count,null,2,1),TO_NUMBER(parent_tree_number); + ELSE + */ + // NOTE: this if condition below belongs to the ELSE condition of the above query. + if (p_get_current_tests && p_pt_array.COUNT > 0) { + // --Loop through array setting latest gq values + for (let i in p_pt_array) { + // --Assume only saved rows need to get latest gq value + // --(others should already have latest) + if (p_pt_array[i].revision_count != null) { + if (p_pt_array[i].bv_GVO != null) { + v_genetic_type_code = 'BV'; + } else { + v_genetic_type_code = 'CV'; + } + r_gq = null; + // --Get current Growth test flagged for use in GW calc + // OPEN c_gq(p_pt_array[i].parent_tree_id , g_seed_plan_unit_id, v_genetic_type_code, 'AD'); + // FETCH c_gq INTO r_gq; CLOSE c_gq; + // -->if (no result found, leave existing result + if (r_gq.genetic_quality_value != null) { + if (p_pt_array[i].bv_AD != null) { + p_pt_array[i].bv_AD = r_gq.genetic_quality_value; + p_pt_array[i].untested_ind = 'N'; + p_pt_array[i].AD_estimated_ind = 'N'; + } else { + p_pt_array[i].cv_AD = r_gq.genetic_quality_value; + p_pt_array[i].untested_ind = 'N'; + p_pt_array[i].AD_estimated_ind = 'N'; + } + } + r_gq = null; + // --Get current Growth test flagged for use in GW calc + // OPEN c_gq(p_pt_array[i].parent_tree_id , g_seed_plan_unit_id , v_genetic_type_code, 'DFS'); + // FETCH c_gq INTO r_gq; CLOSE c_gq; + //-->if (no result found, leave existing result + if (r_gq.genetic_quality_value != null) { + if (p_pt_array[i].bv_DFS != null) { + p_pt_array[i].bv_DFS = r_gq.genetic_quality_value; + p_pt_array[i].untested_ind = 'N'; + p_pt_array[i].DFS_estimated_ind = 'N'; + } else { + p_pt_array[i].cv_DFS = r_gq.genetic_quality_value; + p_pt_array[i].untested_ind = 'N'; + p_pt_array[i].DFS_estimated_ind = 'N'; + } + } + r_gq = null; + // --Get current Growth test flagged for use in GW calc + // OPEN c_gq(p_pt_array[i].parent_tree_id , g_seed_plan_unit_id, v_genetic_type_code , 'DFU'); + // FETCH c_gq INTO r_gq; CLOSE c_gq; + //-->if (no result found, leave existing result + if (r_gq.genetic_quality_value != null) { + if (p_pt_array[i].bv_DFU != null) { + p_pt_array[i].bv_DFU = r_gq.genetic_quality_value; + p_pt_array[i].untested_ind = 'N'; + p_pt_array[i].DFU_estimated_ind = 'N'; + } else { + p_pt_array[i].cv_DFU = r_gq.genetic_quality_value; + p_pt_array[i].untested_ind = 'N'; + p_pt_array[i].DFU_estimated_ind = 'N'; + } + } + r_gq = null; + // --Get current Growth test flagged for use in GW calc + // OPEN c_gq(p_pt_array[i].parent_tree_id, g_seed_plan_unit_id, v_genetic_type_code, 'DFW'); + // FETCH c_gq INTO r_gq; CLOSE c_gq; + // -->if (no result found, leave existing result + if (r_gq.genetic_quality_value != null) { + if (p_pt_array[i].bv_DFW != null) { + p_pt_array[i].bv_DFW = r_gq.genetic_quality_value; + p_pt_array[i].untested_ind = 'N'; + p_pt_array[i].DFW_estimated_ind = 'N'; + } else { + p_pt_array[i].cv_DFW = r_gq.genetic_quality_value; + p_pt_array[i].untested_ind = 'N'; + p_pt_array[i].DFW_estimated_ind = 'N'; + } + } + r_gq = null; + // --Get current Growth test flagged for use in GW calc + // OPEN c_gq(p_pt_array[i].parent_tree_id, g_seed_plan_unit_id, v_genetic_type_code, 'DSB'); + // FETCH c_gq INTO r_gq; CLOSE c_gq; + // -->if (no result found, leave existing result + if (r_gq.genetic_quality_value != null) { + if (p_pt_array[i].bv_DSB != null) { + p_pt_array[i].bv_DSB = r_gq.genetic_quality_value; + p_pt_array[i].untested_ind = 'N'; + p_pt_array[i].DSB_estimated_ind = 'N'; + } else { + p_pt_array[i].cv_DSB = r_gq.genetic_quality_value; + p_pt_array[i].untested_ind = 'N'; + p_pt_array[i].DSB_estimated_ind = 'N'; + } + } + r_gq = null; + // --Get current Growth test flagged for use in GW calc + // OPEN c_gq(p_pt_array[i].parent_tree_id, g_seed_plan_unit_id, v_genetic_type_code, 'DSC'); + // FETCH c_gq INTO r_gq; CLOSE c_gq; + // -->if (no result found, leave existing result + if (r_gq.genetic_quality_value != null) { + if (p_pt_array[i].bv_DSC != null) { + p_pt_array[i].bv_DSC = r_gq.genetic_quality_value; + p_pt_array[i].untested_ind = 'N'; + p_pt_array[i].DSC_estimated_ind = 'N'; + } else { + p_pt_array[i].cv_DSC = r_gq.genetic_quality_value; + p_pt_array[i].untested_ind = 'N'; + p_pt_array[i].DSC_estimated_ind = 'N'; + } + } + r_gq = null; + // --Get current Growth test flagged for use in GW calc + // OPEN c_gq(p_pt_array[i].parent_tree_id, g_seed_plan_unit_id, v_genetic_type_code, 'DSG'); + // FETCH c_gq INTO r_gq; CLOSE c_gq; + // -->if (no result found, leave existing result + if (r_gq.genetic_quality_value != null) { + if (p_pt_array[i].bv_DSG != null) { + p_pt_array[i].bv_DSG = r_gq.genetic_quality_value; + p_pt_array[i].untested_ind = 'N'; + p_pt_array[i].DSG_estimated_ind = 'N'; + } else { + p_pt_array[i].cv_DSG = r_gq.genetic_quality_value; + p_pt_array[i].untested_ind = 'N'; + p_pt_array[i].DSG_estimated_ind = 'N'; + } + } + r_gq = null; + // --Get current Growth test flagged for use in GW calc + // OPEN c_gq(p_pt_array[i].parent_tree_id, g_seed_plan_unit_id, v_genetic_type_code, 'GVO'); + // FETCH c_gq INTO r_gq; CLOSE c_gq; + // -->if (no result found, leave existing result + if (r_gq.genetic_quality_value != null) { + if (p_pt_array[i].bv_GVO != null) { + p_pt_array[i].bv_GVO = r_gq.genetic_quality_value; + p_pt_array[i].untested_ind = 'N'; + p_pt_array[i].GVO_estimated_ind = 'N'; + } else { + p_pt_array[i].cv_GVO = r_gq.genetic_quality_value; + p_pt_array[i].untested_ind = 'N'; + p_pt_array[i].GVO_estimated_ind = 'N'; + } + } + r_gq = null; + // --Get current Growth test flagged for use in GW calc + // OPEN c_gq(p_pt_array[i].parent_tree_id, g_seed_plan_unit_id, v_genetic_type_code, 'IWS'); + // FETCH c_gq INTO r_gq; CLOSE c_gq; + // -->if (no result found, leave existing result + if (r_gq.genetic_quality_value != null) { + if (p_pt_array[i].bv_IWS != null) { + p_pt_array[i].bv_IWS = r_gq.genetic_quality_value; + p_pt_array[i].untested_ind = 'N'; + p_pt_array[i].IWS_estimated_ind = 'N'; + } else { + p_pt_array[i].cv_IWS = r_gq.genetic_quality_value; + p_pt_array[i].untested_ind = 'N'; + p_pt_array[i].IWS_estimated_ind = 'N'; + } + } + r_gq = null; + // --Get current Growth test flagged for use in GW calc + // OPEN c_gq(p_pt_array[i].parent_tree_id, g_seed_plan_unit_id, v_genetic_type_code, 'WDU'); + // FETCH c_gq INTO r_gq; CLOSE c_gq; + // -->if (no result found, leave existing result + if (r_gq.genetic_quality_value != null) { + if (p_pt_array[i].bv_WDU != null) { + p_pt_array[i].bv_WDU = r_gq.genetic_quality_value; + p_pt_array[i].untested_ind = 'N'; + p_pt_array[i].WDU_estimated_ind = 'N'; + } else { + p_pt_array[i].cv_WDU = r_gq.genetic_quality_value; + p_pt_array[i].untested_ind = 'N'; + p_pt_array[i].WDU_estimated_ind = 'N'; + } + } + r_gq = null; + // --Get current Growth test flagged for use in GW calc + // OPEN c_gq(p_pt_array[i].parent_tree_id, g_seed_plan_unit_id, v_genetic_type_code, 'WVE'); + // FETCH c_gq INTO r_gq; CLOSE c_gq; + // -->if (no result found, leave existing result + if (r_gq.genetic_quality_value != null) { + if (p_pt_array[i].bv_WVE != null) { + p_pt_array[i].bv_WVE = r_gq.genetic_quality_value; + p_pt_array[i].untested_ind = 'N'; + p_pt_array[i].WVE_estimated_ind = 'N'; + } else { + p_pt_array[i].cv_WVE = r_gq.genetic_quality_value; + p_pt_array[i].untested_ind = 'N'; + p_pt_array[i].WVE_estimated_ind = 'N'; + } + } + r_gq = null; + // --Get current Growth test flagged for use in GW calc + // OPEN c_gq(p_pt_array[i].parent_tree_id, g_seed_plan_unit_id, v_genetic_type_code, 'WWD'); + // FETCH c_gq INTO r_gq; CLOSE c_gq; + // -->if (no result found, leave existing result + if (r_gq.genetic_quality_value != null) { + if (p_pt_array[i].bv_WWD != null) { + p_pt_array[i].bv_WWD = r_gq.genetic_quality_value; + p_pt_array[i].untested_ind = 'N'; + p_pt_array[i].WWD_estimated_ind = 'N'; + } else { + p_pt_array[i].cv_WWD = r_gq.genetic_quality_value; + p_pt_array[i].untested_ind = 'N'; + p_pt_array[i].WWD_estimated_ind = 'N'; + } + } + } + } + } + // Ends the SELECT ELSE statement + // -- Get the geographic information for each of the anys in the array + if (p_pt_array.COUNT > 0) { + for (let i in p_pt_array) { + spr_get_pt_geog(p_pt_array[i].parent_tree_id, p_pt_array[i].elevation, + p_pt_array[i].latitude_degrees, p_pt_array[i].latitude_minutes, + p_pt_array[i].latitude_seconds, p_pt_array[i].longitude_degrees, + p_pt_array[i].longitude_minutes, p_pt_array[i].longitude_seconds); + } + } + } +} + +/* + * Procedure: calc_pt_contrib + * Purpose: Recalculate Area of Use, GW, Ne, Collection geography. + * -All calculations taken from the 2004 version of the + * Seedlot Certification Template (Excel). + * -Excel treats empty cells as 0 for calculations so + * nvl(__,0) is used liberally. + * -CURRENTLY ONLY CALCULATES GW-G. + */ +function calc_pt_contrib( p_ptZ: any[], p_get_current_tests: boolean) { + let v_contaminant_pollen_bv: number; // seedlot.contaminant_pollen_bv%TYPE; + let v_smp_parents_outside: number; // seedlot.smp_parents_outside%TYPE; + //--Abbreviations used: + //-- f_ female m_ male p_ parent + //-- contrib_ contribution poll_ pollen prop_ proportion + //-- wtd_ weighted + //--col:xx = Seedlot Certification Template (xls) reference + let v_coll_lat: number; // NUMBER;--col:E + let v_coll_long: number; // NUMBER;--col:H + let v_lat: number; // NUMBER;--col:T + let v_long: number; // NUMBER;--col:U + let v_female_crop_pop: number; // NUMBER;--col:V + let v_parent_prop_orch_poll: number; // NUMBER;--col:W + let v_m_gw_AD_contrib_orch_poll: number; // NUMBER;--col:X + let v_sum_m_gw_AD_contb_orch_poll: number; // NUMBER = 0;--SUM(X) + let v_m_gw_DFS_contrib_orch_poll: number; // NUMBER;--col:X + let v_sum_m_gw_DFS_contb_orch_poll: number; // NUMBER = 0;--SUM(X) + let v_m_gw_DFU_contrib_orch_poll: number; // NUMBER;--col:X + let v_sum_m_gw_DFU_contb_orch_poll: number; // NUMBER = 0;--SUM(X) + let v_m_gw_DFW_contrib_orch_poll: number; // NUMBER;--col:X + let v_sum_m_gw_DFW_contb_orch_poll: number; // NUMBER = 0;--SUM(X) + let v_m_gw_DSB_contrib_orch_poll: number; // NUMBER;--col:X + let v_sum_m_gw_DSB_contb_orch_poll: number; // NUMBER = 0;--SUM(X) + let v_m_gw_DSC_contrib_orch_poll: number; // NUMBER;--col:X + let v_sum_m_gw_DSC_contb_orch_poll: number; // NUMBER = 0;--SUM(X) + let v_m_gw_DSG_contrib_orch_poll: number; // NUMBER;--col:X + let v_sum_m_gw_DSG_contb_orch_poll: number; // NUMBER = 0;--SUM(X) + let v_m_gw_GVO_contrib_orch_poll: number; // NUMBER;--col:X + let v_sum_m_gw_GVO_contb_orch_poll: number; // NUMBER = 0;--SUM(X) + let v_m_gw_IWS_contrib_orch_poll: number; // NUMBER;--col:X + let v_sum_m_gw_IWS_contb_orch_poll: number; // NUMBER = 0;--SUM(X) + let v_m_gw_WDU_contrib_orch_poll: number; // NUMBER;--col:X + let v_sum_m_gw_WDU_contb_orch_poll: number; // NUMBER = 0;--SUM(X) + let v_m_gw_WVE_contrib_orch_poll: number; // NUMBER;--col:X + let v_sum_m_gw_WVE_contb_orch_poll: number; // NUMBER = 0;--SUM(X) + let v_m_gw_WWD_contrib_orch_poll: number; // NUMBER;--col:X + let v_sum_m_gw_WWD_contb_orch_poll: number; // NUMBER = 0;--SUM(X) + let v_m_contam_contrib: number; // NUMBER;--col:AA + let v_f_gw_AD_contrib: number; // NUMBER;--col:Y + let v_m_smp_AD_contrib: number; // NUMBER;--col:Z + let v_f_gw_DFS_contrib: number; // NUMBER;--col:Y + let v_m_smp_DFS_contrib: number; // NUMBER;--col:Z + let v_f_gw_DFU_contrib: number; // NUMBER;--col:Y + let v_m_smp_DFU_contrib: number; // NUMBER;--col:Z + let v_f_gw_DFW_contrib: number; // NUMBER;--col:Y + let v_m_smp_DFW_contrib: number; // NUMBER;--col:Z + let v_f_gw_DSB_contrib: number; // NUMBER;--col:Y + let v_m_smp_DSB_contrib: number; // NUMBER;--col:Z + let v_f_gw_DSC_contrib: number; // NUMBER;--col:Y + let v_m_smp_DSC_contrib: number; // NUMBER;--col:Z + let v_f_gw_DSG_contrib: number; // NUMBER;--col:Y + let v_m_smp_DSG_contrib: number; // NUMBER;--col:Z + let v_f_gw_GVO_contrib: number; // NUMBER;--col:Y + let v_m_smp_GVO_contrib: number; // NUMBER;--col:Z + let v_f_gw_IWS_contrib: number; // NUMBER;--col:Y + let v_m_smp_IWS_contrib: number; // NUMBER;--col:Z + let v_f_gw_WDU_contrib: number; // NUMBER;--col:Y + let v_m_smp_WDU_contrib: number; // NUMBER;--col:Z + let v_f_gw_WVE_contrib: number; // NUMBER;--col:Y + let v_m_smp_WVE_contrib: number; // NUMBER;--col:Z + let v_f_gw_WWD_contrib: number; // NUMBER;--col:Y + let v_m_smp_WWD_contrib: number; // NUMBER;--col:Z + let v_m_orch_poll_contrib_AD: number; // NUMBER;--col:AB(dependent on SUM(X) + let v_m_orch_poll_contrib_DFS: number; //NUMBER;--col:AB(dependent on SUM(X) + let v_m_orch_poll_contrib_DFU: number; //NUMBER;--col:AB(dependent on SUM(X) + let v_m_orch_poll_contrib_DFW: number; //NUMBER;--col:AB(dependent on SUM(X) + let v_m_orch_poll_contrib_DSB: number; //NUMBER;--col:AB(dependent on SUM(X) + let v_m_orch_poll_contrib_DSC: number; //NUMBER;--col:AB(dependent on SUM(X) + let v_m_orch_poll_contrib_DSG: number; //NUMBER;--col:AB(dependent on SUM(X) + let v_m_orch_poll_contrib_GVO: number; //NUMBER;--col:AB(dependent on SUM(X) + let v_m_orch_poll_contrib_IWS: number; //NUMBER;--col:AB(dependent on SUM(X) + let v_m_orch_poll_contrib_WDU: number; //NUMBER;--col:AB(dependent on SUM(X) + let v_m_orch_poll_contrib_WVE: number; //NUMBER;--col:AB(dependent on SUM(X) + let v_m_orch_poll_contrib_WWD: number; //NUMBER;--col:AB(dependent on SUM(X) + let v_m_total_gw_AD_contrib: number; //NUMBER;--col:AC + let v_m_total_gw_DFS_contrib: number; //NUMBER;--col:AC + let v_m_total_gw_DFU_contrib: number; //NUMBER;--col:AC + let v_m_total_gw_DFW_contrib: number; //NUMBER;--col:AC + let v_m_total_gw_DSB_contrib: number; //NUMBER;--col:AC + let v_m_total_gw_DSC_contrib: number; //NUMBER;--col:AC + let v_m_total_gw_DSG_contrib: number; //NUMBER;--col:AC + let v_m_total_gw_GVO_contrib: number; //NUMBER;--col:AC + let v_m_total_gw_IWS_contrib: number; //NUMBER;--col:AC + let v_m_total_gw_WDU_contrib: number; //NUMBER;--col:AC + let v_m_total_gw_WVE_contrib: number; //NUMBER;--col:AC + let v_m_total_gw_WWD_contrib: number; //NUMBER;--col:AC + let v_p_total_gw_AD_contrib: number; //NUMBER;--col:AD + let v_sum_p_total_gw_AD_contrib: number; //NUMBER = 0;--SUM(AD) + let v_p_total_gw_DFS_contrib: number; //NUMBER;--col:AD + let v_sum_p_total_gw_DFS_contrib: number; //NUMBER = 0;--SUM(AD) + let v_p_total_gw_DFU_contrib: number; //NUMBER;--col:AD + let v_sum_p_total_gw_DFU_contrib: number; //NUMBER = 0;--SUM(AD) + let v_p_total_gw_DFW_contrib: number; //NUMBER;--col:AD + let v_sum_p_total_gw_DFW_contrib: number; //NUMBER = 0;--SUM(AD) + let v_p_total_gw_DSB_contrib: number; //NUMBER;--col:AD + let v_sum_p_total_gw_DSB_contrib: number; //NUMBER = 0;--SUM(AD) + let v_p_total_gw_DSC_contrib: number; //NUMBER;--col:AD + let v_sum_p_total_gw_DSC_contrib: number; //NUMBER = 0;--SUM(AD) + let v_p_total_gw_DSG_contrib: number; //NUMBER;--col:AD + let v_sum_p_total_gw_DSG_contrib: number; //NUMBER = 0;--SUM(AD) + let v_p_total_gw_GVO_contrib: number; //NUMBER;--col:AD + let v_sum_p_total_gw_GVO_contrib: number; //NUMBER = 0;--SUM(AD) + let v_p_total_gw_IWS_contrib: number; //NUMBER;--col:AD + let v_sum_p_total_gw_IWS_contrib: number; //NUMBER = 0;--SUM(AD) + let v_p_total_gw_WDU_contrib: number; //NUMBER;--col:AD + let v_sum_p_total_gw_WDU_contrib: number; //NUMBER = 0;--SUM(AD) + let v_p_total_gw_WVE_contrib: number; //NUMBER;--col:AD + let v_sum_p_total_gw_WVE_contrib: number; //NUMBER = 0;--SUM(AD) + let v_p_total_gw_WWD_contrib: number; //NUMBER;--col:AD + let v_sum_p_total_gw_WWD_contrib: number; //NUMBER = 0;--SUM(AD) + let v_p_prop_contrib: number; //NUMBER;--col:AE + let v_sum_p_prop_contrib: number; //NUMBER = 0;--SUM(AE) + let v_sum_p_prop_contrib_tested: number; //NUMBER = 0;--SUM(AE for Tested Parent Trees) + let v_sum_p_prop_contrib_test_AD: number; //NUMBER = 0;--SUM(AE for Tested Parent Trees) + let v_sum_p_prop_contrib_test_DFS: number; //NUMBER = 0;--SUM(AE for Tested Parent Trees) + let v_sum_p_prop_contrib_test_DFU: number; //NUMBER = 0;--SUM(AE for Tested Parent Trees) + let v_sum_p_prop_contrib_test_DFW: number; //NUMBER = 0;--SUM(AE for Tested Parent Trees) + let v_sum_p_prop_contrib_test_DSB: number; //NUMBER = 0;--SUM(AE for Tested Parent Trees) + let v_sum_p_prop_contrib_test_DSC: number; //NUMBER = 0;--SUM(AE for Tested Parent Trees) + let v_sum_p_prop_contrib_test_DSG: number; //NUMBER = 0;--SUM(AE for Tested Parent Trees) + let v_sum_p_prop_contrib_test_GVO: number; //NUMBER = 0;--SUM(AE for Tested Parent Trees) + let v_sum_p_prop_contrib_test_IWS: number; //NUMBER = 0;--SUM(AE for Tested Parent Trees) + let v_sum_p_prop_contrib_test_WDU: number; //NUMBER = 0;--SUM(AE for Tested Parent Trees) + let v_sum_p_prop_contrib_test_WVE: number; //NUMBER = 0;--SUM(AE for Tested Parent Trees) + let v_sum_p_prop_contrib_test_WWD: number; //NUMBER = 0;--SUM(AE for Tested Parent Trees) + let v_p_contrib_elev_no_smp_poll: number; //NUMBER;--col:AF + let v_p_contrib_lat_no_smp_poll: number; //NUMBER;--col:AG + let v_p_contrib_long_no_smp_poll: number; //NUMBER;--col:AH + let v_smp_poll_wtd_contrib_elev: number; //NUMBER;--col:AI + let v_smp_poll_wtd_contrib_lat: number; //NUMBER;--col:AJ + let v_smp_poll_wtd_contrib_long: number; //NUMBER;--col:AK + let v_wtd_elev_p_and_smp_poll: number; //NUMBER;--col:AL + let v_sum_wtd_elev_p_and_smp_poll: number; //NUMBER = 0;--SUM(AL) + let v_wtd_lat_p_and_smp_poll: number; //NUMBER;--col:AM + let v_sum_wtd_lat_p_and_smp_poll: number; //NUMBER = 0;--SUM(am) + let v_wtd_long_p_and_smp_poll: number; //NUMBER;--col:AN + let v_sum_wtd_long_p_and_smp_poll: number; //NUMBER = 0;--SUM(AN) + let v_ne_no_smp_contrib: number; //NUMBER;--col:AO + let v_sum_ne_no_smp_contrib: number; //NUMBER = 0;--SUM(AO) + let v_smp_success_wtd_by_f_p: number; //NUMBER;--col:AP + let v_sum_smp_success_wtd_by_f_p: number; //NUMBER = 0;--SUM(AP) + let v_orch_gamete_contr: number; //NUMBER;--col:AQ + let v_sum_orch_gamete_contr: number; //NUMBER = 0;--SUM(AQ) + let v_total_parent_trees: number; //NUMBER(5) = 0; + let v_gw_AD: number; //NUMBER; + let v_gw_DFS: number; //NUMBER; + let v_gw_DFU: number; //NUMBER; + let v_gw_DFW: number; //NUMBER; + let v_gw_DSB: number; //NUMBER; + let v_gw_DSC: number; //NUMBER; + let v_gw_DSG: number; //NUMBER; + let v_gw_GVO: number; //NUMBER; + let v_gw_IWS: number; //NUMBER; + let v_gw_WDU: number; //NUMBER; + let v_gw_WVE: number; //NUMBER; + let v_gw_WWD: number; //NUMBER; + let v_effective_pop_size: number; //NUMBER; + let v_lat_deg: number; //seedlot.collection_lat_deg%TYPE; + let v_lat_min: number; //seedlot.collection_lat_min%TYPE; + let v_lat_sec: number; //seedlot.collection_lat_sec%TYPE; + let v_long_deg: number; //seedlot.collection_long_deg%TYPE; + let v_long_min: number; //seedlot.collection_long_min%TYPE; + let v_long_sec: number; //seedlot.collection_long_sec%TYPE; + let v_elev: number; //seedlot.collection_elevation%TYPE; + let v_elev_min: number; //seedlot.collection_elevation%TYPE; + let v_elev_max: number; //seedlot.collection_elevation%TYPE; + let v_smp_mean_bv_AD: number; //seedlot_parent_tree_smp_mix.smp_mix_value%TYPE; + let v_smp_mean_bv_DFS: number; //seedlot_parent_tree_smp_mix.smp_mix_value%TYPE; + let v_smp_mean_bv_DFU: number; //seedlot_parent_tree_smp_mix.smp_mix_value%TYPE; + let v_smp_mean_bv_DFW: number; //seedlot_parent_tree_smp_mix.smp_mix_value%TYPE; + let v_smp_mean_bv_DSB: number; //seedlot_parent_tree_smp_mix.smp_mix_value%TYPE; + let v_smp_mean_bv_DSC: number; //seedlot_parent_tree_smp_mix.smp_mix_value%TYPE; + let v_smp_mean_bv_DSG: number; //seedlot_parent_tree_smp_mix.smp_mix_value%TYPE; + let v_smp_mean_bv_GVO: number; //seedlot_parent_tree_smp_mix.smp_mix_value%TYPE; + let v_smp_mean_bv_IWS: number; //seedlot_parent_tree_smp_mix.smp_mix_value%TYPE; + let v_smp_mean_bv_WDU: number; //seedlot_parent_tree_smp_mix.smp_mix_value%TYPE; + let v_smp_mean_bv_WVE: number; //seedlot_parent_tree_smp_mix.smp_mix_value%TYPE; + let v_smp_mean_bv_WWD: number; //seedlot_parent_tree_smp_mix.smp_mix_value%TYPE; + let v_smp_success_pct: number; //seedlot.smp_success_pct%TYPE; + let v_orchard_contamination_pct: number; //seedlot.orchard_contamination_pct%TYPE; + let v_pt_elevation: number; //seedlot.collection_elevation%TYPE; + let v_pt_latitude_degrees: number; //seedlot.collection_lat_deg%TYPE; + let v_pt_latitude_minutes: number; //seedlot.collection_lat_min%TYPE; + let v_pt_latitude_seconds: number; //seedlot.collection_lat_sec%TYPE; + let v_pt_longitude_degrees: number; //seedlot.collection_long_deg%TYPE; + let v_pt_longitude_minutes: number; //seedlot.collection_long_min%TYPE; + let v_pt_longitude_seconds: number; //seedlot.collection_long_sec%TYPE; + let v_total_cone_count: number; //NUMBER = 0; + let v_total_pollen_count: number; //NUMBER = 0; + // --Values used in calculating averages + let v_total_smp_mix_bv_AD: number; //NUMBER = 0; + let v_num_smp_mix_bv_AD: number; //NUMBER(5) = 0; + let v_avg_smp_mix_bv_AD: number; //NUMBER; + let v_total_smp_mix_bv_DFS: number; //NUMBER = 0; + let v_num_smp_mix_bv_DFS: number; //NUMBER(5) = 0; + let v_avg_smp_mix_bv_DFS: number; //NUMBER; + let v_total_smp_mix_bv_DFU: number; //NUMBER = 0; + let v_num_smp_mix_bv_DFU: number; //NUMBER(5) = 0; + let v_avg_smp_mix_bv_DFU: number; //NUMBER; + let v_total_smp_mix_bv_DFW: number; //NUMBER = 0; + let v_num_smp_mix_bv_DFW: number; //NUMBER(5) = 0; + let v_avg_smp_mix_bv_DFW: number; //NUMBER; + let v_total_smp_mix_bv_DSB: number; //NUMBER = 0; + let v_num_smp_mix_bv_DSB: number; //NUMBER(5) = 0; + let v_avg_smp_mix_bv_DSB: number; //NUMBER; + let v_total_smp_mix_bv_DSC: number; //NUMBER = 0; + let v_num_smp_mix_bv_DSC: number; //NUMBER(5) = 0; + let v_avg_smp_mix_bv_DSC: number; //NUMBER; + let v_total_smp_mix_bv_DSG: number; //NUMBER = 0; + let v_num_smp_mix_bv_DSG: number; //NUMBER(5) = 0; + let v_avg_smp_mix_bv_DSG: number; //NUMBER; + let v_total_smp_mix_bv_GVO: number; //NUMBER = 0; + let v_num_smp_mix_bv_GVO: number; //NUMBER(5) = 0; + let v_avg_smp_mix_bv_GVO: number; //NUMBER; + let v_total_smp_mix_bv_IWS: number; //NUMBER = 0; + let v_num_smp_mix_bv_IWS: number; //NUMBER(5) = 0; + let v_avg_smp_mix_bv_IWS: number; //NUMBER; + let v_total_smp_mix_bv_WDU: number; //NUMBER = 0; + let v_num_smp_mix_bv_WDU: number; //NUMBER(5) = 0; + let v_avg_smp_mix_bv_WDU: number; //NUMBER; + let v_total_smp_mix_bv_WVE: number; //NUMBER = 0; + let v_num_smp_mix_bv_WVE: number; //NUMBER(5) = 0; + let v_avg_smp_mix_bv_WVE: number; //NUMBER; + let v_total_smp_mix_bv_WWD: number; //NUMBER = 0; + let v_num_smp_mix_bv_WWD: number; // NUMBER(5) = 0; + let v_avg_smp_mix_bv_WWD: number; // NUMBER; + let v_total_non_orchard_pollen: number; //NUMBER = 0; + let v_num_non_orchard_pollen: number; //NUMBER(5) = 0; + let v_avg_non_orchard_pollen: number; //NUMBER; + // --Values pulled from the array + let v_a_cone_count: number; //NUMBER; + let v_a_pollen_count: number; //NUMBER; + let v_a_smp_success_pct: number; //NUMBER; + let v_a_smp_mix_bv_AD: number; //NUMBER; + let v_a_smp_mix_bv_DFS: number; //NUMBER; + let v_a_smp_mix_bv_DFU: number; //NUMBER; + let v_a_smp_mix_bv_DFW: number; //NUMBER; + let v_a_smp_mix_bv_DSB: number; //NUMBER; + let v_a_smp_mix_bv_DSC: number; //NUMBER; + let v_a_smp_mix_bv_DSG: number; //NUMBER; + let v_a_smp_mix_bv_GVO: number; //NUMBER; + let v_a_smp_mix_bv_IWS: number; //NUMBER; + let v_a_smp_mix_bv_WDU: number; //NUMBER; + let v_a_smp_mix_bv_WVE: number; //NUMBER; + let v_a_smp_mix_bv_WWD: number; //NUMBER; + let v_a_smp_mix_latitude_degrees: number; //NUMBER; + let v_a_smp_mix_latitude_minutes: number; //NUMBER; + let v_a_smp_mix_longitude_degrees: number; //NUMBER; + let v_a_smp_mix_longitude_minutes: number; //NUMBER; + let v_a_smp_mix_elevation: number; //NUMBER; + let v_a_non_orchard_pollen_contam: number; //NUMBER; + // -- replace smp mix if latest bv calculated. + let v_smp_records_exist: boolean; //BOOLEAN; + let v_old_smp_mix_bv_AD: number; //NUMBER; + let v_old_smp_mix_bv_DFS: number; //NUMBER; + let v_old_smp_mix_bv_DFU: number; //NUMBER; + let v_old_smp_mix_bv_DFW: number; //NUMBER; + let v_old_smp_mix_bv_DSB: number; //NUMBER; + let v_old_smp_mix_bv_DSC: number; //NUMBER; + let v_old_smp_mix_bv_DSG: number; //NUMBER; + let v_old_smp_mix_bv_GVO: number; //NUMBER; + let v_old_smp_mix_bv_IWS: number; //NUMBER; + let v_old_smp_mix_bv_WDU: number; //NUMBER; + let v_old_smp_mix_bv_WVE: number; //NUMBER; + let v_old_smp_mix_bv_WWD: number; //NUMBER; + let v_old_smp_mix_lat_deg: number; //NUMBER; + let v_old_smp_mix_lat_min: number; //NUMBER; + let v_old_smp_mix_long_deg: number; //NUMBER; + let v_old_smp_mix_long_min: number; //NUMBER; + let v_old_smp_mix_elevation: number; //NUMBER; + let v_new_smp_mix_bv_AD: number; //NUMBER; + let v_new_smp_mix_bv_DFS: number; //NUMBER; + let v_new_smp_mix_bv_DFU: number; //NUMBER; + let v_new_smp_mix_bv_DFW: number; //NUMBER; + let v_new_smp_mix_bv_DSB: number; //NUMBER; + let v_new_smp_mix_bv_DSC: number; //NUMBER; + let v_new_smp_mix_bv_DSG: number; //NUMBER; + let v_new_smp_mix_bv_GVO: number; //NUMBER; + let v_new_smp_mix_bv_IWS: number; //NUMBER; + let v_new_smp_mix_bv_WDU: number; //NUMBER; + let v_new_smp_mix_bv_WVE: number; //NUMBER; + let v_new_smp_mix_bv_WWD: number; //NUMBER; + let v_new_smp_mix_lat_deg: number; //NUMBER; + let v_new_smp_mix_lat_min: number; //NUMBER; + let v_new_smp_mix_long_deg: number; //NUMBER; + let v_new_smp_mix_long_min: number; //NUMBER; + let v_new_smp_mix_elevation: number; //NUMBER; + let b_bv_AD_not_estimated: boolean = false; + let b_bv_DFS_not_estimated: boolean = false; + let b_bv_DFU_not_estimated: boolean = false; + let b_bv_DFW_not_estimated: boolean = false; + let b_bv_DSB_not_estimated: boolean = false; + let b_bv_DSC_not_estimated: boolean = false; + let b_bv_DSG_not_estimated: boolean = false; + let b_bv_GVO_not_estimated: boolean = false; + let b_bv_IWS_not_estimated: boolean = false; + let b_bv_WDU_not_estimated: boolean = false; + let b_bv_WVE_not_estimated: boolean = false; + let b_bv_WWD_not_estimated: boolean = false; + + // --Get current GQ values if required and load array from saved data if not passed + load_array(p_pt,p_get_current_tests); + + // --Contam pollen bv used in calc - use set value if it has been set, otherwise + // --use the previous value...in either case, convert null to 0 as Excel would + if (gb_contaminant_pollen_bv == 'Y') { + v_contaminant_pollen_bv = g_contaminant_pollen_bv == null? 0 : g_contaminant_pollen_bv; + } else { + v_contaminant_pollen_bv = r_previous.contaminant_pollen_bv == null? 0 : r_previous.contaminant_pollen_bv; + } + // --SMP Parents Outside used in calc - use set value if it has been set, otherwise + // --use the previous value...in either case, convert null to 0 as Excel would + if (gb_smp_parents_outside == 'Y') { + v_smp_parents_outside = nvlNumber(g_smp_parents_outside, 0); + } else { + v_smp_parents_outside = nvlNumber(r_previous.smp_parents_outside, 0); + } + + // --First pass to calculate simple sums used in row-based calcs and to check if all bv d/r/m are estimated. + for (let i in p_pt.COUNT) { + v_total_cone_count = v_total_cone_count + (p_pt[i].cone_count == null? 0 : p_pt[i].cone_count); + v_total_pollen_count = v_total_pollen_count + (p_pt[i].pollen_count == null ? 0 : p_pt[i].pollen_count); + if (p_pt[i].AD_ESTIMATED_IND != 'Y' && (p_pt[i].CONE_COUNT != null || p_pt[i].POLLEN_COUNT != null)) { + b_bv_AD_not_estimated = true; + } + if (p_pt[i].DFS_ESTIMATED_IND != 'Y' && (p_pt[i].CONE_COUNT != null || p_pt[i].POLLEN_COUNT != null)) { + b_bv_DFS_not_estimated = true; + } + if (p_pt[i].DFU_ESTIMATED_IND != 'Y' && (p_pt[i].CONE_COUNT != null || p_pt[i].POLLEN_COUNT != null)) { + b_bv_DFU_not_estimated = true; + } + if (p_pt[i].DFW_ESTIMATED_IND != 'Y' && (p_pt[i].CONE_COUNT != null || p_pt[i].POLLEN_COUNT != null)) { + b_bv_DFW_not_estimated = true; + } + if (p_pt[i].DSB_ESTIMATED_IND != 'Y' && (p_pt[i].CONE_COUNT != null || p_pt[i].POLLEN_COUNT != null)) { + b_bv_DSB_not_estimated = true; + } + if (p_pt[i].DSC_ESTIMATED_IND != 'Y' && (p_pt[i].CONE_COUNT != null || p_pt[i].POLLEN_COUNT != null)) { + b_bv_DSC_not_estimated = true; + } + if (p_pt[i].DSG_ESTIMATED_IND != 'Y' && (p_pt[i].CONE_COUNT != null || p_pt[i].POLLEN_COUNT != null)) { + b_bv_DSG_not_estimated = true; + } + if (p_pt[i].GVO_ESTIMATED_IND != 'Y' && (p_pt[i].CONE_COUNT != null || p_pt[i].POLLEN_COUNT != null)) { + b_bv_GVO_not_estimated = true; + } + if (p_pt[i].IWS_ESTIMATED_IND != 'Y' && (p_pt[i].CONE_COUNT != null || p_pt[i].POLLEN_COUNT != null)) { + b_bv_IWS_not_estimated = true; + } + if (p_pt[i].WDU_ESTIMATED_IND != 'Y' && (p_pt[i].CONE_COUNT != null || p_pt[i].POLLEN_COUNT != null)) { + b_bv_WDU_not_estimated = true; + } + if (p_pt[i].WVE_ESTIMATED_IND != 'Y' && (p_pt[i].CONE_COUNT != null || p_pt[i].POLLEN_COUNT != null)) { + b_bv_WVE_not_estimated = true; + } + if (p_pt[i].WWD_ESTIMATED_IND != 'Y' && (p_pt[i].CONE_COUNT != null || p_pt[i].POLLEN_COUNT != null)) { + b_bv_WWD_not_estimated = true; + } + } + + // --Second pass to calculate total male gw contribution orchard pollen (uses v_total_pollen_count from first pass) + for (let i in p_pt.COUNT) { + // --col:W + if (v_total_pollen_count == 0) { + v_parent_prop_orch_poll = 0; + } else { + v_parent_prop_orch_poll = (p_pt[i].pollen_count == null? 0 : p_pt[i].pollen_count) / v_total_pollen_count; + } + // --col:X + v_m_gw_AD_contrib_orch_poll = v_parent_prop_orch_poll * (p_pt[i].bv_AD, p_pt[i].cv_AD == null? 0 : p_pt[i].bv_AD, p_pt[i].cv_AD ); + v_m_gw_DFS_contrib_orch_poll = v_parent_prop_orch_poll * (p_pt[i].bv_DFS,p_pt[i].cv_DFS == null? 0 : p_pt[i].bv_DFS,p_pt[i].cv_DFS); + v_m_gw_DFU_contrib_orch_poll = v_parent_prop_orch_poll * (p_pt[i].bv_DFU,p_pt[i].cv_DFU == null? 0 : p_pt[i].bv_DFU,p_pt[i].cv_DFU); + v_m_gw_DFW_contrib_orch_poll = v_parent_prop_orch_poll * (p_pt[i].bv_DFW,p_pt[i].cv_DFW == null? 0 : p_pt[i].bv_DFW,p_pt[i].cv_DFW); + v_m_gw_DSB_contrib_orch_poll = v_parent_prop_orch_poll * (p_pt[i].bv_DSB,p_pt[i].cv_DSB == null? 0 : p_pt[i].bv_DSB,p_pt[i].cv_DSB); + v_m_gw_DSC_contrib_orch_poll = v_parent_prop_orch_poll * (p_pt[i].bv_DSC,p_pt[i].cv_DSC == null? 0 : p_pt[i].bv_DSC,p_pt[i].cv_DSC); + v_m_gw_DSG_contrib_orch_poll = v_parent_prop_orch_poll * (p_pt[i].bv_DSG,p_pt[i].cv_DSG == null? 0 : p_pt[i].bv_DSG,p_pt[i].cv_DSG); + v_m_gw_GVO_contrib_orch_poll = v_parent_prop_orch_poll * (p_pt[i].bv_GVO,p_pt[i].cv_GVO == null? 0 : p_pt[i].bv_GVO,p_pt[i].cv_GVO); + v_m_gw_IWS_contrib_orch_poll = v_parent_prop_orch_poll * (p_pt[i].bv_IWS,p_pt[i].cv_IWS == null? 0 : p_pt[i].bv_IWS,p_pt[i].cv_IWS); + v_m_gw_WDU_contrib_orch_poll = v_parent_prop_orch_poll * (p_pt[i].bv_WDU,p_pt[i].cv_WDU == null? 0 : p_pt[i].bv_WDU,p_pt[i].cv_WDU); + v_m_gw_WVE_contrib_orch_poll = v_parent_prop_orch_poll * (p_pt[i].bv_WVE,p_pt[i].cv_WVE == null? 0 : p_pt[i].bv_WVE,p_pt[i].cv_WVE); + v_m_gw_WWD_contrib_orch_poll = v_parent_prop_orch_poll * (p_pt[i].bv_WWD,p_pt[i].cv_WWD == null? 0 : p_pt[i].bv_WWD,p_pt[i].cv_WWD); + // --accumulate total SUM(x) + v_sum_m_gw_AD_contb_orch_poll = v_sum_m_gw_AD_contb_orch_poll + v_m_gw_AD_contrib_orch_poll; + v_sum_m_gw_DFS_contb_orch_poll = v_sum_m_gw_DFS_contb_orch_poll + v_m_gw_DFS_contrib_orch_poll; + v_sum_m_gw_DFU_contb_orch_poll = v_sum_m_gw_DFU_contb_orch_poll + v_m_gw_DFU_contrib_orch_poll; + v_sum_m_gw_DFW_contb_orch_poll = v_sum_m_gw_DFW_contb_orch_poll + v_m_gw_DFW_contrib_orch_poll; + v_sum_m_gw_DSB_contb_orch_poll = v_sum_m_gw_DSB_contb_orch_poll + v_m_gw_DSB_contrib_orch_poll; + v_sum_m_gw_DSC_contb_orch_poll = v_sum_m_gw_DSC_contb_orch_poll + v_m_gw_DSC_contrib_orch_poll; + v_sum_m_gw_DSG_contb_orch_poll = v_sum_m_gw_DSG_contb_orch_poll + v_m_gw_DSG_contrib_orch_poll; + v_sum_m_gw_GVO_contb_orch_poll = v_sum_m_gw_GVO_contb_orch_poll + v_m_gw_GVO_contrib_orch_poll; + v_sum_m_gw_IWS_contb_orch_poll = v_sum_m_gw_IWS_contb_orch_poll + v_m_gw_IWS_contrib_orch_poll; + v_sum_m_gw_WDU_contb_orch_poll = v_sum_m_gw_WDU_contb_orch_poll + v_m_gw_WDU_contrib_orch_poll; + v_sum_m_gw_WVE_contb_orch_poll = v_sum_m_gw_WVE_contb_orch_poll + v_m_gw_WVE_contrib_orch_poll; + v_sum_m_gw_WWD_contb_orch_poll = v_sum_m_gw_WWD_contb_orch_poll + v_m_gw_WWD_contrib_orch_poll; + } + + // -- recalculate the smp mix values (only if (smp records exist for seedlot). + v_smp_records_exist = spr_001a_smp_calculation.smp_records_exist(get_seedlot_number()); + if (v_smp_records_exist) { + spr_001a_smp_calculation.recalculate(get_seedlot_number(), get_seedlot_status_code(), + get_vegetation_code(), get_orchard_id(), get_secondary_orchard_id(), get_seed_plan_unit_id(), 'N','N', + v_smp_parents_outside, v_old_smp_mix_bv_AD, v_old_smp_mix_bv_DFS, v_old_smp_mix_bv_DFU, + v_old_smp_mix_bv_DFW, v_old_smp_mix_bv_DSB, v_old_smp_mix_bv_DSC, v_old_smp_mix_bv_DSG, + v_old_smp_mix_bv_GVO, v_old_smp_mix_bv_IWS, v_old_smp_mix_bv_WDU, v_old_smp_mix_bv_WVE, + v_old_smp_mix_bv_WWD, v_old_smp_mix_elevation, v_old_smp_mix_lat_deg, v_old_smp_mix_lat_min, + v_old_smp_mix_long_deg, v_old_smp_mix_long_min, g_error_message); + + if (p_get_current_tests) { + spr_001a_smp_calculation.recalculate(get_seedlot_number(), get_seedlot_status_code(), + get_vegetation_code(), get_orchard_id(), get_secondary_orchard_id(), get_seed_plan_unit_id(), 'Y', + 'N', v_smp_parents_outside, v_new_smp_mix_bv_AD, v_new_smp_mix_bv_DFS, + v_new_smp_mix_bv_DFU, v_new_smp_mix_bv_DFW, v_new_smp_mix_bv_DSB, v_new_smp_mix_bv_DSC, + v_new_smp_mix_bv_DSG, v_new_smp_mix_bv_GVO, v_new_smp_mix_bv_IWS, v_new_smp_mix_bv_WDU, + v_new_smp_mix_bv_WVE, v_new_smp_mix_bv_WWD, v_new_smp_mix_elevation, v_new_smp_mix_lat_deg, + v_new_smp_mix_lat_min, v_new_smp_mix_long_deg, v_new_smp_mix_long_min, g_error_message); + } + g_smp_parents_outside = nvlNumber(v_smp_parents_outside, 0); + } + + // --Third pass to calc values that depend on totals derived above and the remainder + for (let i in p_pt.COUNT) { + // --ignore rows without cone or pollen count + if (p_pt[i].cone_count != null || p_pt[i].pollen_count != null) { + v_total_parent_trees = v_total_parent_trees + 1; + // -- replace smp mix values, if changed. + if (p_get_current_tests) { + if ((p_pt[i].smp_mix_bv_AD == null? 0 : p_pt[i].smp_mix_bv_AD ) == v_old_smp_mix_bv_AD && + (p_pt[i].smp_mix_bv_DFS == null? 0 : p_pt[i].smp_mix_bv_DFS) == v_old_smp_mix_bv_DFS && + (p_pt[i].smp_mix_bv_DFU == null? 0 : p_pt[i].smp_mix_bv_DFU) == v_old_smp_mix_bv_DFU && + (p_pt[i].smp_mix_bv_DFW == null? 0 : p_pt[i].smp_mix_bv_DFW) == v_old_smp_mix_bv_DFW && + (p_pt[i].smp_mix_bv_DSB == null? 0 : p_pt[i].smp_mix_bv_DSB) == v_old_smp_mix_bv_DSB && + (p_pt[i].smp_mix_bv_DSC == null? 0 : p_pt[i].smp_mix_bv_DSC) == v_old_smp_mix_bv_DSC && + (p_pt[i].smp_mix_bv_DSG == null? 0 : p_pt[i].smp_mix_bv_DSG) == v_old_smp_mix_bv_DSG && + (p_pt[i].smp_mix_bv_GVO == null? 0 : p_pt[i].smp_mix_bv_GVO) == v_old_smp_mix_bv_GVO && + (p_pt[i].smp_mix_bv_IWS == null? 0 : p_pt[i].smp_mix_bv_IWS) == v_old_smp_mix_bv_IWS && + (p_pt[i].smp_mix_bv_WDU == null? 0 : p_pt[i].smp_mix_bv_WDU) == v_old_smp_mix_bv_WDU && + (p_pt[i].smp_mix_bv_WVE == null? 0 : p_pt[i].smp_mix_bv_WVE) == v_old_smp_mix_bv_WVE && + (p_pt[i].smp_mix_bv_WWD == null? 0 : p_pt[i].smp_mix_bv_WWD) == v_old_smp_mix_bv_WWD && + (p_pt[i].smp_mix_elevation == null? 0 : p_pt[i].smp_mix_elevation) == v_old_smp_mix_elevation && + (p_pt[i].smp_mix_latitude_degrees == null? 0 : p_pt[i].smp_mix_latitude_degrees) == v_old_smp_mix_lat_deg && + (p_pt[i].smp_mix_latitude_minutes == null? 0 : p_pt[i].smp_mix_latitude_minutes) == v_old_smp_mix_lat_min && + (p_pt[i].smp_mix_longitude_degrees == null? 0 : p_pt[i].smp_mix_longitude_degrees) == v_old_smp_mix_long_deg && + (p_pt[i].smp_mix_longitude_minutes == null? 0 : p_pt[i].smp_mix_longitude_minutes) == v_old_smp_mix_long_min) { + + p_pt[i].smp_mix_bv_AD = v_new_smp_mix_bv_AD; + p_pt[i].smp_mix_bv_DFS = v_new_smp_mix_bv_DFS; + p_pt[i].smp_mix_bv_DFU = v_new_smp_mix_bv_DFU; + p_pt[i].smp_mix_bv_DFW = v_new_smp_mix_bv_DFW; + p_pt[i].smp_mix_bv_DSB = v_new_smp_mix_bv_DSB; + p_pt[i].smp_mix_bv_DSC = v_new_smp_mix_bv_DSC; + p_pt[i].smp_mix_bv_DSG = v_new_smp_mix_bv_DSG; + p_pt[i].smp_mix_bv_GVO = v_new_smp_mix_bv_GVO; + p_pt[i].smp_mix_bv_IWS = v_new_smp_mix_bv_IWS; + p_pt[i].smp_mix_bv_WDU = v_new_smp_mix_bv_WDU; + p_pt[i].smp_mix_bv_WVE = v_new_smp_mix_bv_WVE; + p_pt[i].smp_mix_bv_WWD = v_new_smp_mix_bv_WWD; + p_pt[i].smp_mix_elevation = v_new_smp_mix_elevation; + p_pt[i].smp_mix_latitude_degrees = v_new_smp_mix_lat_deg; + p_pt[i].smp_mix_latitude_minutes = v_new_smp_mix_lat_min; + p_pt[i].smp_mix_longitude_degrees = v_new_smp_mix_long_deg; + p_pt[i].smp_mix_longitude_minutes = v_new_smp_mix_long_min; + } + } + // --Convert null array values to 0 as Excel does + v_a_cone_count = p_pt[i].cone_count == null? 0 : p_pt[i].cone_count; + v_a_pollen_count = p_pt[i].pollen_count == null? 0 : p_pt[i].pollen_count; + v_a_smp_success_pct = p_pt[i].smp_success_pct == null? 0 : p_pt[i].smp_success_pct; + v_a_smp_mix_bv_AD = p_pt[i].smp_mix_bv_AD == null? 0 : p_pt[i].smp_mix_bv_AD; + v_a_smp_mix_bv_DFS = p_pt[i].smp_mix_bv_DFS == null? 0 : p_pt[i].smp_mix_bv_DFS; + v_a_smp_mix_bv_DFU = p_pt[i].smp_mix_bv_DFU == null? 0 : p_pt[i].smp_mix_bv_DFU; + v_a_smp_mix_bv_DFW = p_pt[i].smp_mix_bv_DFW == null? 0 : p_pt[i].smp_mix_bv_DFW; + v_a_smp_mix_bv_DSB = p_pt[i].smp_mix_bv_DSB == null? 0 : p_pt[i].smp_mix_bv_DSB; + v_a_smp_mix_bv_DSC = p_pt[i].smp_mix_bv_DSC == null? 0 : p_pt[i].smp_mix_bv_DSC; + v_a_smp_mix_bv_DSG = p_pt[i].smp_mix_bv_DSG == null? 0 : p_pt[i].smp_mix_bv_DSG; + v_a_smp_mix_bv_GVO = p_pt[i].smp_mix_bv_GVO == null? 0 : p_pt[i].smp_mix_bv_GVO; + v_a_smp_mix_bv_IWS = p_pt[i].smp_mix_bv_IWS == null? 0 : p_pt[i].smp_mix_bv_IWS; + v_a_smp_mix_bv_WDU = p_pt[i].smp_mix_bv_WDU == null? 0 : p_pt[i].smp_mix_bv_WDU; + v_a_smp_mix_bv_WVE = p_pt[i].smp_mix_bv_WVE == null? 0 : p_pt[i].smp_mix_bv_WVE; + v_a_smp_mix_bv_WWD = p_pt[i].smp_mix_bv_WWD == null? 0 : p_pt[i].smp_mix_bv_WWD; + v_a_smp_mix_latitude_degrees = p_pt[i].smp_mix_latitude_degrees == null? 0 : p_pt[i].smp_mix_latitude_degrees; + v_a_smp_mix_latitude_minutes = p_pt[i].smp_mix_latitude_minutes == null? 0 : p_pt[i].smp_mix_latitude_minutes; + v_a_smp_mix_longitude_degrees = p_pt[i].smp_mix_longitude_degrees == null? 0 : p_pt[i].smp_mix_longitude_degrees; + v_a_smp_mix_longitude_minutes = p_pt[i].smp_mix_longitude_minutes == null? 0 : p_pt[i].smp_mix_longitude_minutes; + v_a_smp_mix_elevation = p_pt[i].smp_mix_elevation == null? 0 : p_pt[i].smp_mix_elevation; + v_a_non_orchard_pollen_contam = p_pt[i].non_orchard_pollen_contam == null? 0 : p_pt[i].non_orchard_pollen_contam; + // --values to calc avg smp mix bv AD (only contribute to avg if specified) + if (p_pt[i].smp_mix_bv_AD != null) { + v_total_smp_mix_bv_AD = v_total_smp_mix_bv_AD + v_a_smp_mix_bv_AD; + v_num_smp_mix_bv_AD = v_num_smp_mix_bv_AD + 1; + } + // --values to calc avg smp mix bv DFS (only contribute to avg if specified) + if (p_pt[i].smp_mix_bv_DFS != null) { + v_total_smp_mix_bv_DFS = v_total_smp_mix_bv_DFS + v_a_smp_mix_bv_DFS; + v_num_smp_mix_bv_DFS = v_num_smp_mix_bv_DFS + 1; + } + // --values to calc avg smp mix bv DFU (only contribute to avg if specified) + if (p_pt[i].smp_mix_bv_DFU != null) { + v_total_smp_mix_bv_DFU = v_total_smp_mix_bv_DFU + v_a_smp_mix_bv_DFU; + v_num_smp_mix_bv_DFU = v_num_smp_mix_bv_DFU + 1; + } + // --values to calc avg smp mix bv DFW (only contribute to avg if specified) + if (p_pt[i].smp_mix_bv_DFW != null) { + v_total_smp_mix_bv_DFW = v_total_smp_mix_bv_DFW + v_a_smp_mix_bv_DFW; + v_num_smp_mix_bv_DFW = v_num_smp_mix_bv_DFW + 1; + } + // --values to calc avg smp mix bv DSB (only contribute to avg if specified) + if (p_pt[i].smp_mix_bv_DSB != null) { + v_total_smp_mix_bv_DSB = v_total_smp_mix_bv_DSB + v_a_smp_mix_bv_DSB; + v_num_smp_mix_bv_DSB = v_num_smp_mix_bv_DSB + 1; + } + // --values to calc avg smp mix bv DSC (only contribute to avg if specified) + if (p_pt[i].smp_mix_bv_DSC != null) { + v_total_smp_mix_bv_DSC = v_total_smp_mix_bv_DSC + v_a_smp_mix_bv_DSC; + v_num_smp_mix_bv_DSC = v_num_smp_mix_bv_DSC + 1; + } + // --values to calc avg smp mix bv DSG (only contribute to avg if specified) + if (p_pt[i].smp_mix_bv_DSG != null) { + v_total_smp_mix_bv_DSG = v_total_smp_mix_bv_DSG + v_a_smp_mix_bv_DSG; + v_num_smp_mix_bv_DSG = v_num_smp_mix_bv_DSG + 1; + } + // --values to calc avg smp mix bv GVO (only contribute to avg if specified) + if (p_pt[i].smp_mix_bv_GVO != null) { + v_total_smp_mix_bv_GVO = v_total_smp_mix_bv_GVO + v_a_smp_mix_bv_GVO; + v_num_smp_mix_bv_GVO = v_num_smp_mix_bv_GVO + 1; + } + // --values to calc avg smp mix bv IWS (only contribute to avg if specified) + if (p_pt[i].smp_mix_bv_IWS != null) { + v_total_smp_mix_bv_IWS = v_total_smp_mix_bv_IWS + v_a_smp_mix_bv_IWS; + v_num_smp_mix_bv_IWS = v_num_smp_mix_bv_IWS + 1; + } + // --values to calc avg smp mix bv WDU (only contribute to avg if specified) + if (p_pt[i].smp_mix_bv_WDU != null) { + v_total_smp_mix_bv_WDU = v_total_smp_mix_bv_WDU + v_a_smp_mix_bv_WDU; + v_num_smp_mix_bv_WDU = v_num_smp_mix_bv_WDU + 1; + } + // --values to calc avg smp mix bv WVE (only contribute to avg if specified) + if (p_pt[i].smp_mix_bv_WVE != null) { + v_total_smp_mix_bv_WVE = v_total_smp_mix_bv_WVE + v_a_smp_mix_bv_WVE; + v_num_smp_mix_bv_WVE = v_num_smp_mix_bv_WVE + 1; + } + // --values to calc avg smp mix bv WWD (only contribute to avg if specified) + if (p_pt[i].smp_mix_bv_WWD != null) { + v_total_smp_mix_bv_WWD = v_total_smp_mix_bv_WWD + v_a_smp_mix_bv_WWD; + v_num_smp_mix_bv_WWD = v_num_smp_mix_bv_WWD + 1; + } + // --values to calc avg non-orchard pollen contamination pct (only contribute to avg if specified) + if (p_pt[i].non_orchard_pollen_contam_pct != null) { + v_total_non_orchard_pollen = v_total_non_orchard_pollen + v_a_non_orchard_pollen_contam; + v_num_non_orchard_pollen = v_num_non_orchard_pollen + 1; + } + // --Get geography from parents if present + spr_get_pt_geog(p_pt[i].parent_tree_id,v_pt_elevation,v_pt_latitude_degrees,v_pt_latitude_minutes, + v_pt_latitude_seconds, v_pt_longitude_degrees, v_pt_longitude_minutes, v_pt_longitude_seconds); + // -->Set to 0 if null as Excel would + v_pt_elevation = nvl(v_pt_elevation,0); + v_pt_latitude_degrees = v_pt_latitude_degrees == null? 0 : v_pt_latitude_degrees; + v_pt_latitude_minutes = v_pt_latitude_minutes == null? 0 : v_pt_latitude_minutes; + v_pt_latitude_seconds = v_pt_latitude_seconds == null? 0 : v_pt_latitude_seconds; + v_pt_longitude_degrees = v_pt_longitude_degrees == null? 0 : v_pt_longitude_degrees; + v_pt_longitude_minutes = v_pt_longitude_minutes == null? 0 : v_pt_longitude_minutes; + v_pt_longitude_seconds = v_pt_longitude_seconds == null? 0 : v_pt_longitude_seconds; + //--NOTE for lat/long: converting all lat/longs to seconds instead of a decimal as spreadsheet did + // --col:E + v_coll_lat = (v_pt_latitude_degrees*3600) + (v_pt_latitude_minutes*60) + v_pt_latitude_seconds; + // --col:H + v_coll_long = (v_pt_longitude_degrees*3600) + (v_pt_longitude_minutes*60) + v_pt_longitude_seconds; + // --col:T + v_lat = (v_a_smp_mix_latitude_degrees*3600) + (v_a_smp_mix_latitude_minutes*60); + // --col:U + v_long = (v_a_smp_mix_longitude_degrees*3600) + (v_a_smp_mix_longitude_minutes*60); + // --col:V + if (v_total_cone_count == 0) { + v_female_crop_pop = 0; + } else { + v_female_crop_pop = v_a_cone_count / v_total_cone_count; + } + // --col:W + if (v_total_pollen_count == 0) { + v_parent_prop_orch_poll = 0; + } else { + v_parent_prop_orch_poll = v_a_pollen_count / v_total_pollen_count; + } + // --col:Y + v_f_gw_AD_contrib = v_female_crop_pop * (p_pt[i].bv_AD == null ? p_pt[i].cv_AD : p_pt[i].bv_AD); + v_f_gw_DFS_contrib = v_female_crop_pop * (p_pt[i].bv_DFS == null ? p_pt[i].cv_DFS : p_pt[i].bv_DFS); + v_f_gw_DFU_contrib = v_female_crop_pop * (p_pt[i].bv_DFU == null ? p_pt[i].cv_DFU : p_pt[i].bv_DFU); + v_f_gw_DFW_contrib = v_female_crop_pop * (p_pt[i].bv_DFW == null ? p_pt[i].cv_DFW : p_pt[i].bv_DFW); + v_f_gw_DSB_contrib = v_female_crop_pop * (p_pt[i].bv_DSB == null ? p_pt[i].cv_DSB : p_pt[i].bv_DSB); + v_f_gw_DSC_contrib = v_female_crop_pop * (p_pt[i].bv_DSC == null ? p_pt[i].cv_DSC : p_pt[i].bv_DSC); + v_f_gw_DSG_contrib = v_female_crop_pop * (p_pt[i].bv_DSG == null ? p_pt[i].cv_DSG : p_pt[i].bv_DSG); + v_f_gw_GVO_contrib = v_female_crop_pop * (p_pt[i].bv_GVO == null ? p_pt[i].cv_GVO : p_pt[i].bv_GVO); + v_f_gw_IWS_contrib = v_female_crop_pop * (p_pt[i].bv_IWS == null ? p_pt[i].cv_IWS : p_pt[i].bv_IWS); + v_f_gw_WDU_contrib = v_female_crop_pop * (p_pt[i].bv_WDU == null ? p_pt[i].cv_WDU : p_pt[i].bv_WDU); + v_f_gw_WVE_contrib = v_female_crop_pop * (p_pt[i].bv_WVE == null ? p_pt[i].cv_WVE : p_pt[i].bv_WVE); + v_f_gw_WWD_contrib = v_female_crop_pop * (p_pt[i].bv_WWD == null ? p_pt[i].cv_WWD : p_pt[i].bv_WWD); + // --col:Z + v_m_smp_AD_contrib = ( (v_a_smp_success_pct * v_a_smp_mix_bv_AD) / 100 ) * v_female_crop_pop; + v_m_smp_DFS_contrib = ( (v_a_smp_success_pct * v_a_smp_mix_bv_DFS) / 100 ) * v_female_crop_pop; + v_m_smp_DFU_contrib = ( (v_a_smp_success_pct * v_a_smp_mix_bv_DFU) / 100 ) * v_female_crop_pop; + v_m_smp_DFW_contrib = ( (v_a_smp_success_pct * v_a_smp_mix_bv_DFW) / 100 ) * v_female_crop_pop; + v_m_smp_DSB_contrib = ( (v_a_smp_success_pct * v_a_smp_mix_bv_DSB) / 100 ) * v_female_crop_pop; + v_m_smp_DSC_contrib = ( (v_a_smp_success_pct * v_a_smp_mix_bv_DSC) / 100 ) * v_female_crop_pop; + v_m_smp_DSG_contrib = ( (v_a_smp_success_pct * v_a_smp_mix_bv_DSG) / 100 ) * v_female_crop_pop; + v_m_smp_GVO_contrib = ( (v_a_smp_success_pct * v_a_smp_mix_bv_GVO) / 100 ) * v_female_crop_pop; + v_m_smp_IWS_contrib = ( (v_a_smp_success_pct * v_a_smp_mix_bv_IWS) / 100 ) * v_female_crop_pop; + v_m_smp_WDU_contrib = ( (v_a_smp_success_pct * v_a_smp_mix_bv_WDU) / 100 ) * v_female_crop_pop; + v_m_smp_WVE_contrib = ( (v_a_smp_success_pct * v_a_smp_mix_bv_WVE) / 100 ) * v_female_crop_pop; + v_m_smp_WWD_contrib = ( (v_a_smp_success_pct * v_a_smp_mix_bv_WWD) / 100 ) * v_female_crop_pop; + // --col:AA + v_m_contam_contrib = ( (1 - (v_a_smp_success_pct/100)) * (v_a_non_orchard_pollen_contam/100) * v_contaminant_pollen_bv ) * v_female_crop_pop; + // --col:AB (depends on SUM(X)=v_sum_m_gw_contrib_orch_poll) + v_m_orch_poll_contrib_AD = ( ( 1 - (v_a_smp_success_pct/100) - (v_a_non_orchard_pollen_contam/100) ) * v_sum_m_gw_AD_contb_orch_poll ) * v_female_crop_pop; + v_m_orch_poll_contrib_DFS = ( ( 1 - (v_a_smp_success_pct/100) - (v_a_non_orchard_pollen_contam/100) ) * v_sum_m_gw_DFS_contb_orch_poll ) * v_female_crop_pop; + v_m_orch_poll_contrib_DFU = ( ( 1 - (v_a_smp_success_pct/100) - (v_a_non_orchard_pollen_contam/100) ) * v_sum_m_gw_DFU_contb_orch_poll ) * v_female_crop_pop; + v_m_orch_poll_contrib_DFW = ( ( 1 - (v_a_smp_success_pct/100) - (v_a_non_orchard_pollen_contam/100) ) * v_sum_m_gw_DFW_contb_orch_poll ) * v_female_crop_pop; + v_m_orch_poll_contrib_DSB = ( ( 1 - (v_a_smp_success_pct/100) - (v_a_non_orchard_pollen_contam/100) ) * v_sum_m_gw_DSB_contb_orch_poll ) * v_female_crop_pop; + v_m_orch_poll_contrib_DSC = ( ( 1 - (v_a_smp_success_pct/100) - (v_a_non_orchard_pollen_contam/100) ) * v_sum_m_gw_DSC_contb_orch_poll ) * v_female_crop_pop; + v_m_orch_poll_contrib_DSG = ( ( 1 - (v_a_smp_success_pct/100) - (v_a_non_orchard_pollen_contam/100) ) * v_sum_m_gw_DSG_contb_orch_poll ) * v_female_crop_pop; + v_m_orch_poll_contrib_GVO = ( ( 1 - (v_a_smp_success_pct/100) - (v_a_non_orchard_pollen_contam/100) ) * v_sum_m_gw_GVO_contb_orch_poll ) * v_female_crop_pop; + v_m_orch_poll_contrib_IWS = ( ( 1 - (v_a_smp_success_pct/100) - (v_a_non_orchard_pollen_contam/100) ) * v_sum_m_gw_IWS_contb_orch_poll ) * v_female_crop_pop; + v_m_orch_poll_contrib_WDU = ( ( 1 - (v_a_smp_success_pct/100) - (v_a_non_orchard_pollen_contam/100) ) * v_sum_m_gw_WDU_contb_orch_poll ) * v_female_crop_pop; + v_m_orch_poll_contrib_WVE = ( ( 1 - (v_a_smp_success_pct/100) - (v_a_non_orchard_pollen_contam/100) ) * v_sum_m_gw_WVE_contb_orch_poll ) * v_female_crop_pop; + v_m_orch_poll_contrib_WWD = ( ( 1 - (v_a_smp_success_pct/100) - (v_a_non_orchard_pollen_contam/100) ) * v_sum_m_gw_WWD_contb_orch_poll ) * v_female_crop_pop; + // --col:AC depends on prev value + v_m_total_gw_AD_contrib = v_m_smp_AD_contrib + v_m_contam_contrib + v_m_orch_poll_contrib_AD; + v_m_total_gw_DFS_contrib = v_m_smp_DFS_contrib + v_m_contam_contrib + v_m_orch_poll_contrib_DFS; + v_m_total_gw_DFU_contrib = v_m_smp_DFU_contrib + v_m_contam_contrib + v_m_orch_poll_contrib_DFU; + v_m_total_gw_DFW_contrib = v_m_smp_DFW_contrib + v_m_contam_contrib + v_m_orch_poll_contrib_DFW; + v_m_total_gw_DSB_contrib = v_m_smp_DSB_contrib + v_m_contam_contrib + v_m_orch_poll_contrib_DSB; + v_m_total_gw_DSC_contrib = v_m_smp_DSC_contrib + v_m_contam_contrib + v_m_orch_poll_contrib_DSC; + v_m_total_gw_DSG_contrib = v_m_smp_DSG_contrib + v_m_contam_contrib + v_m_orch_poll_contrib_DSG; + v_m_total_gw_GVO_contrib = v_m_smp_GVO_contrib + v_m_contam_contrib + v_m_orch_poll_contrib_GVO; + v_m_total_gw_IWS_contrib = v_m_smp_IWS_contrib + v_m_contam_contrib + v_m_orch_poll_contrib_IWS; + v_m_total_gw_WDU_contrib = v_m_smp_WDU_contrib + v_m_contam_contrib + v_m_orch_poll_contrib_WDU; + v_m_total_gw_WVE_contrib = v_m_smp_WVE_contrib + v_m_contam_contrib + v_m_orch_poll_contrib_WVE; + v_m_total_gw_WWD_contrib = v_m_smp_WWD_contrib + v_m_contam_contrib + v_m_orch_poll_contrib_WWD; + // --col:AD + if (v_total_pollen_count = 0) { + v_p_total_gw_AD_contrib = v_f_gw_AD_contrib; + v_p_total_gw_DFS_contrib = v_f_gw_DFS_contrib; + v_p_total_gw_DFU_contrib = v_f_gw_DFU_contrib; + v_p_total_gw_DFW_contrib = v_f_gw_DFW_contrib; + v_p_total_gw_DSB_contrib = v_f_gw_DSB_contrib; + v_p_total_gw_DSC_contrib = v_f_gw_DSC_contrib; + v_p_total_gw_DSG_contrib = v_f_gw_DSG_contrib; + v_p_total_gw_GVO_contrib = v_f_gw_GVO_contrib; + v_p_total_gw_IWS_contrib = v_f_gw_IWS_contrib; + v_p_total_gw_WDU_contrib = v_f_gw_WDU_contrib; + v_p_total_gw_WVE_contrib = v_f_gw_WVE_contrib; + v_p_total_gw_WWD_contrib = v_f_gw_WWD_contrib; + } else { + v_p_total_gw_AD_contrib = (v_f_gw_AD_contrib + v_m_total_gw_AD_contrib) / 2; + v_p_total_gw_DFS_contrib = (v_f_gw_DFS_contrib + v_m_total_gw_DFS_contrib) / 2; + v_p_total_gw_DFU_contrib = (v_f_gw_DFU_contrib + v_m_total_gw_DFU_contrib) / 2; + v_p_total_gw_DFW_contrib = (v_f_gw_DFW_contrib + v_m_total_gw_DFW_contrib) / 2; + v_p_total_gw_DSB_contrib = (v_f_gw_DSB_contrib + v_m_total_gw_DSB_contrib) / 2; + v_p_total_gw_DSC_contrib = (v_f_gw_DSC_contrib + v_m_total_gw_DSC_contrib) / 2; + v_p_total_gw_DSG_contrib = (v_f_gw_DSG_contrib + v_m_total_gw_DSG_contrib) / 2; + v_p_total_gw_GVO_contrib = (v_f_gw_GVO_contrib + v_m_total_gw_GVO_contrib) / 2; + v_p_total_gw_IWS_contrib = (v_f_gw_IWS_contrib + v_m_total_gw_IWS_contrib) / 2; + v_p_total_gw_WDU_contrib = (v_f_gw_WDU_contrib + v_m_total_gw_WDU_contrib) / 2; + v_p_total_gw_WVE_contrib = (v_f_gw_WVE_contrib + v_m_total_gw_WVE_contrib) / 2; + v_p_total_gw_WWD_contrib = (v_f_gw_WWD_contrib + v_m_total_gw_WWD_contrib) / 2; + } + // --Set total gw contrib back into array so it can be displayed/saved + p_pt[i].total_genetic_worth_contrib = v_p_total_gw_GVO_contrib; + v_sum_p_total_gw_AD_contrib = v_sum_p_total_gw_AD_contrib + (v_p_total_gw_AD_contrib == null? 0 : v_p_total_gw_AD_contrib ); + v_sum_p_total_gw_DFS_contrib = v_sum_p_total_gw_DFS_contrib + (v_p_total_gw_DFS_contrib == null? 0 : v_p_total_gw_DFS_contrib); + v_sum_p_total_gw_DFU_contrib = v_sum_p_total_gw_DFU_contrib + (v_p_total_gw_DFU_contrib == null? 0 : v_p_total_gw_DFU_contrib); + v_sum_p_total_gw_DFW_contrib = v_sum_p_total_gw_DFW_contrib + (v_p_total_gw_DFW_contrib == null? 0 : v_p_total_gw_DFW_contrib); + v_sum_p_total_gw_DSB_contrib = v_sum_p_total_gw_DSB_contrib + (v_p_total_gw_DSB_contrib == null? 0 : v_p_total_gw_DSB_contrib); + v_sum_p_total_gw_DSC_contrib = v_sum_p_total_gw_DSC_contrib + (v_p_total_gw_DSC_contrib == null? 0 : v_p_total_gw_DSC_contrib); + v_sum_p_total_gw_DSG_contrib = v_sum_p_total_gw_DSG_contrib + (v_p_total_gw_DSG_contrib == null? 0 : v_p_total_gw_DSG_contrib); + v_sum_p_total_gw_GVO_contrib = v_sum_p_total_gw_GVO_contrib + v_p_total_gw_GVO_contrib; + v_sum_p_total_gw_IWS_contrib = v_sum_p_total_gw_IWS_contrib + (v_p_total_gw_IWS_contrib == null? 0 : v_p_total_gw_IWS_contrib); + v_sum_p_total_gw_WDU_contrib = v_sum_p_total_gw_WDU_contrib + (v_p_total_gw_WDU_contrib == null? 0 : v_p_total_gw_WDU_contrib); + v_sum_p_total_gw_WVE_contrib = v_sum_p_total_gw_WVE_contrib + (v_p_total_gw_WVE_contrib == null? 0 : v_p_total_gw_WVE_contrib); + v_sum_p_total_gw_WWD_contrib = v_sum_p_total_gw_WWD_contrib + (v_p_total_gw_WWD_contrib == null? 0 : v_p_total_gw_WWD_contrib); + // --col:AE + if (v_total_pollen_count == 0) { + v_p_prop_contrib = v_female_crop_pop; + } else { + v_p_prop_contrib = (v_female_crop_pop + v_parent_prop_orch_poll) / 2; + } + v_sum_p_prop_contrib = v_sum_p_prop_contrib + v_p_prop_contrib; + // --SUM(AE for Tested Parent Trees) + if (p_pt[i].untested_ind != 'Y' && ((p_pt[i].AD_estimated_ind != 'Y') + || (p_pt[i].DFS_estimated_ind != 'Y') + || (p_pt[i].DFU_estimated_ind != 'Y') + || (p_pt[i].DFW_estimated_ind != 'Y') + || (p_pt[i].DSB_estimated_ind != 'Y') + || (p_pt[i].DSC_estimated_ind != 'Y') + || (p_pt[i].DSG_estimated_ind != 'Y') + || (p_pt[i].GVO_estimated_ind != 'Y') + || (p_pt[i].IWS_estimated_ind != 'Y') + || (p_pt[i].WDU_estimated_ind != 'Y') + || (p_pt[i].WVE_estimated_ind != 'Y') + || (p_pt[i].WWD_estimated_ind != 'Y'))) { + v_sum_p_prop_contrib_tested = v_sum_p_prop_contrib_tested + v_p_prop_contrib; + } + if (p_pt[i].untested_ind != 'Y' && p_pt[i].AD_estimated_ind != 'Y') { + v_sum_p_prop_contrib_test_AD = v_sum_p_prop_contrib_test_AD + v_p_prop_contrib; + } + if (p_pt[i].untested_ind != 'Y' && p_pt[i].DFS_estimated_ind != 'Y') { + v_sum_p_prop_contrib_test_DFS = v_sum_p_prop_contrib_test_DFS + v_p_prop_contrib; + } + if (p_pt[i].untested_ind != 'Y' && p_pt[i].DFU_estimated_ind != 'Y') { + v_sum_p_prop_contrib_test_DFU = v_sum_p_prop_contrib_test_DFU + v_p_prop_contrib; + } + if (p_pt[i].untested_ind != 'Y' && p_pt[i].DFW_estimated_ind != 'Y' ) { + v_sum_p_prop_contrib_test_DFW = v_sum_p_prop_contrib_test_DFW + v_p_prop_contrib; + } + if (p_pt[i].untested_ind != 'Y' && p_pt[i].DSB_estimated_ind != 'Y' ) { + v_sum_p_prop_contrib_test_DSB = v_sum_p_prop_contrib_test_DSB + v_p_prop_contrib; + } + if (p_pt[i].untested_ind != 'Y' && p_pt[i].DSC_estimated_ind != 'Y' ) { + v_sum_p_prop_contrib_test_DSC = v_sum_p_prop_contrib_test_DSC + v_p_prop_contrib; + } + if (p_pt[i].untested_ind != 'Y' && p_pt[i].DSG_estimated_ind != 'Y' ) { + v_sum_p_prop_contrib_test_DSG = v_sum_p_prop_contrib_test_DSG + v_p_prop_contrib; + } + if (p_pt[i].untested_ind != 'Y' && p_pt[i].GVO_estimated_ind != 'Y' ) { + v_sum_p_prop_contrib_test_GVO = v_sum_p_prop_contrib_test_GVO + v_p_prop_contrib; + } + if (p_pt[i].untested_ind != 'Y' && p_pt[i].IWS_estimated_ind != 'Y' ) { + v_sum_p_prop_contrib_test_IWS = v_sum_p_prop_contrib_test_IWS + v_p_prop_contrib; + } + if (p_pt[i].untested_ind != 'Y' && p_pt[i].WDU_estimated_ind != 'Y' ) { + v_sum_p_prop_contrib_test_WDU = v_sum_p_prop_contrib_test_WDU + v_p_prop_contrib; + } + if (p_pt[i].untested_ind != 'Y' && p_pt[i].WVE_estimated_ind != 'Y' ) { + v_sum_p_prop_contrib_test_WVE = v_sum_p_prop_contrib_test_WVE + v_p_prop_contrib; + } + if (p_pt[i].untested_ind != 'Y' && p_pt[i].WWD_estimated_ind != 'Y' ) { + v_sum_p_prop_contrib_test_WWD = v_sum_p_prop_contrib_test_WWD + v_p_prop_contrib; + } + // --col:AF + v_p_contrib_elev_no_smp_poll = v_pt_elevation * (v_p_prop_contrib-((v_female_crop_pop*v_a_smp_success_pct)/200)); + // --col:AG + v_p_contrib_lat_no_smp_poll = v_coll_lat * (v_p_prop_contrib-((v_female_crop_pop*v_a_smp_success_pct)/200)); + // --col:AH + v_p_contrib_long_no_smp_poll = v_coll_long * (v_p_prop_contrib-((v_female_crop_pop*v_a_smp_success_pct)/200)); + // --col:AI + v_smp_poll_wtd_contrib_elev = ((v_a_smp_mix_elevation * v_a_smp_success_pct)/200)*v_female_crop_pop; + // --col:AJ + v_smp_poll_wtd_contrib_lat = ((v_lat * v_a_smp_success_pct)/200)*v_female_crop_pop; + // --col:AK + v_smp_poll_wtd_contrib_long = ((v_long * v_a_smp_success_pct)/200)*v_female_crop_pop; + // --col:AL + v_wtd_elev_p_and_smp_poll = v_p_contrib_elev_no_smp_poll + v_smp_poll_wtd_contrib_elev; + v_sum_wtd_elev_p_and_smp_poll = v_sum_wtd_elev_p_and_smp_poll + v_wtd_elev_p_and_smp_poll; + // --col:AM + v_wtd_lat_p_and_smp_poll = v_p_contrib_lat_no_smp_poll + v_smp_poll_wtd_contrib_lat; + v_sum_wtd_lat_p_and_smp_poll = v_sum_wtd_lat_p_and_smp_poll + v_wtd_lat_p_and_smp_poll; + // --col:AN + v_wtd_long_p_and_smp_poll = v_p_contrib_long_no_smp_poll + v_smp_poll_wtd_contrib_long; + v_sum_wtd_long_p_and_smp_poll = v_sum_wtd_long_p_and_smp_poll + v_wtd_long_p_and_smp_poll; + // --col:AO + v_ne_no_smp_contrib = Math.pow(v_p_prop_contrib, 2); + v_sum_ne_no_smp_contrib = v_sum_ne_no_smp_contrib + v_ne_no_smp_contrib; + // --col:AP (xls did /100 so left in for comparison and * 100 at end to get smp success %) + v_smp_success_wtd_by_f_p = (v_female_crop_pop * v_a_smp_success_pct) / 100; + v_sum_smp_success_wtd_by_f_p = v_sum_smp_success_wtd_by_f_p + v_smp_success_wtd_by_f_p; + // --col:AQ + v_orch_gamete_contr = Math.pow( ( v_female_crop_pop + (0.75*v_parent_prop_orch_poll) )/2,2); + v_sum_orch_gamete_contr = v_sum_orch_gamete_contr + v_orch_gamete_contr; + } else { + // --Set total gw contrib back into array so it can be displayed/saved + p_pt[i].total_genetic_worth_contrib = 0; + } + } + + r_pt_contrib.pct_tested_parent_trees_AD = (v_sum_p_prop_contrib_test_AD) * 100; + r_pt_contrib.pct_tested_parent_trees_DFS = (v_sum_p_prop_contrib_test_DFS) * 100; + r_pt_contrib.pct_tested_parent_trees_DFU = (v_sum_p_prop_contrib_test_DFU) * 100; + r_pt_contrib.pct_tested_parent_trees_DFW = (v_sum_p_prop_contrib_test_DFW) * 100; + r_pt_contrib.pct_tested_parent_trees_DSB = (v_sum_p_prop_contrib_test_DSB) * 100; + r_pt_contrib.pct_tested_parent_trees_DSC = (v_sum_p_prop_contrib_test_DSC) * 100; + r_pt_contrib.pct_tested_parent_trees_DSG = (v_sum_p_prop_contrib_test_DSG) * 100; + r_pt_contrib.pct_tested_parent_trees_GVO = (v_sum_p_prop_contrib_test_GVO) * 100; + r_pt_contrib.pct_tested_parent_trees_IWS = (v_sum_p_prop_contrib_test_IWS) * 100; + r_pt_contrib.pct_tested_parent_trees_WDU = (v_sum_p_prop_contrib_test_WDU) * 100; + r_pt_contrib.pct_tested_parent_trees_WVE = (v_sum_p_prop_contrib_test_WVE) * 100; + r_pt_contrib.pct_tested_parent_trees_WWD = (v_sum_p_prop_contrib_test_WWD) * 100; + + // --calc avg smp mix bv AD + if (v_num_smp_mix_bv_AD > 0) { + v_avg_smp_mix_bv_AD = v_total_smp_mix_bv_AD / v_num_smp_mix_bv_AD; + } + // --calc avg smp mix bv DFS + if (v_num_smp_mix_bv_DFS > 0) { + v_avg_smp_mix_bv_DFS = v_total_smp_mix_bv_DFS / v_num_smp_mix_bv_DFS; + } + // --calc avg smp mix bv DFU + if (v_num_smp_mix_bv_DFU > 0) { + v_avg_smp_mix_bv_DFU = v_total_smp_mix_bv_DFU / v_num_smp_mix_bv_DFU; + } + // --calc avg smp mix bv DFW + if (v_num_smp_mix_bv_DFW > 0) { + v_avg_smp_mix_bv_DFW = v_total_smp_mix_bv_DFW / v_num_smp_mix_bv_DFW; + } + // --calc avg smp mix bv DSB + if (v_num_smp_mix_bv_DSB > 0) { + v_avg_smp_mix_bv_DSB = v_total_smp_mix_bv_DSB / v_num_smp_mix_bv_DSB; + } + // --calc avg smp mix bv DSC + if (v_num_smp_mix_bv_DSC > 0) { + v_avg_smp_mix_bv_DSC = v_total_smp_mix_bv_DSC / v_num_smp_mix_bv_DSC; + } + // --calc avg smp mix bv DSG + if (v_num_smp_mix_bv_DSG > 0) { + v_avg_smp_mix_bv_DSG = v_total_smp_mix_bv_DSG / v_num_smp_mix_bv_DSG; + } + // --calc avg smp mix bv GVO + if (v_num_smp_mix_bv_GVO > 0) { + v_avg_smp_mix_bv_GVO = v_total_smp_mix_bv_GVO / v_num_smp_mix_bv_GVO; + } + // --calc avg smp mix bv IWS + if (v_num_smp_mix_bv_IWS > 0) { + v_avg_smp_mix_bv_IWS = v_total_smp_mix_bv_IWS / v_num_smp_mix_bv_IWS; + } + // --calc avg smp mix bv WDU + if (v_num_smp_mix_bv_WDU > 0) { + v_avg_smp_mix_bv_WDU = v_total_smp_mix_bv_WDU / v_num_smp_mix_bv_WDU; + } + // --calc avg smp mix bv WVE + if (v_num_smp_mix_bv_WVE > 0) { + v_avg_smp_mix_bv_WVE = v_total_smp_mix_bv_WVE / v_num_smp_mix_bv_WVE; + } + // --calc avg smp mix bv WWD + if (v_num_smp_mix_bv_WWD > 0) { + v_avg_smp_mix_bv_WWD = v_total_smp_mix_bv_WWD / v_num_smp_mix_bv_WWD; + } + // --calc avg non-orchard pollen contamination pct + if (v_num_non_orchard_pollen > 0) { + v_avg_non_orchard_pollen = v_total_non_orchard_pollen / v_num_non_orchard_pollen; + } + v_gw_AD = v_sum_p_total_gw_AD_contrib; + v_gw_DFS = Math.round(v_sum_p_total_gw_DFS_contrib); + v_gw_DFU = Math.round(v_sum_p_total_gw_DFU_contrib); + v_gw_DFW = Math.round(v_sum_p_total_gw_DFW_contrib); + v_gw_DSB = Math.round(v_sum_p_total_gw_DSB_contrib); + v_gw_DSC = Math.round(v_sum_p_total_gw_DSC_contrib); + v_gw_DSG = Math.round(v_sum_p_total_gw_DSG_contrib); + v_gw_GVO = Math.round(v_sum_p_total_gw_GVO_contrib); + v_gw_IWS = Math.round(v_sum_p_total_gw_IWS_contrib); + v_gw_WDU = Math.round(v_sum_p_total_gw_WDU_contrib); + v_gw_WVE = Math.round(v_sum_p_total_gw_WVE_contrib); + v_gw_WWD = Math.round(v_sum_p_total_gw_WWD_contrib); + + if (g_coancestry != null) { + // --Effective Population Size with Coancestry considered + if (g_coancestry == 0) { + v_effective_pop_size = 0; + } else { + v_effective_pop_size = 0.5/g_coancestry; + } + } else if (v_smp_parents_outside > 0) { + // --Effective Population Size with SMP (for Growth) + v_effective_pop_size = Math.round(1/(v_sum_orch_gamete_contr + ( Math.power(0.25/(2*v_smp_parents_outside),2) * v_smp_parents_outside )) ,1); + } else { + // --Effective Population Size + if (v_sum_orch_gamete_contr == 0) { + v_effective_pop_size = 0; + } else { + v_effective_pop_size = Math.round(1/v_sum_ne_no_smp_contrib,1); + } + } + + if (v_total_parent_trees > 0) { + v_lat_deg = Math.trunc(v_sum_wtd_lat_p_and_smp_poll/3600); + v_lat_min = Math.trunc(MOD(v_sum_wtd_lat_p_and_smp_poll,3600)/60); + v_lat_sec = Math.trunc(MOD(v_sum_wtd_lat_p_and_smp_poll,60)); + v_long_deg = Math.trunc(v_sum_wtd_long_p_and_smp_poll/3600); + v_long_min = Math.trunc(MOD(v_sum_wtd_long_p_and_smp_poll,3600)/60); + v_long_sec = Math.trunc(MOD(v_sum_wtd_long_p_and_smp_poll,60)); + v_elev = Math.round(v_sum_wtd_elev_p_and_smp_poll); + v_smp_mean_bv_AD = Math.round(v_avg_smp_mix_bv_AD,1); + v_smp_mean_bv_DFS = Math.round(v_avg_smp_mix_bv_DFS,1); + v_smp_mean_bv_DFU = Math.round(v_avg_smp_mix_bv_DFU,1); + v_smp_mean_bv_DFW = Math.round(v_avg_smp_mix_bv_DFW,1); + v_smp_mean_bv_DSB = Math.round(v_avg_smp_mix_bv_DSB,1); + v_smp_mean_bv_DSC = Math.round(v_avg_smp_mix_bv_DSC,1); + v_smp_mean_bv_DSG = Math.round(v_avg_smp_mix_bv_DSG,1); + v_smp_mean_bv_GVO = Math.round(v_avg_smp_mix_bv_GVO,1); + v_smp_mean_bv_IWS = Math.round(v_avg_smp_mix_bv_IWS,1); + v_smp_mean_bv_WDU = Math.round(v_avg_smp_mix_bv_WDU,1); + v_smp_mean_bv_WVE = Math.round(v_avg_smp_mix_bv_WVE,1); + v_smp_mean_bv_WWD = Math.round(v_avg_smp_mix_bv_WWD,1); + v_smp_success_pct = Math.round(v_sum_smp_success_wtd_by_f_p * 100); + if (v_total_non_orchard_pollen = 0) { + v_orchard_contamination_pct = 0; + } else { + v_orchard_contamination_pct = Math.round(v_avg_non_orchard_pollen); + } + // --No errors raised, set values in record + // -- however, trait values should only be set if > 70% for that trait + + r_pt_contrib.collection_elevation = v_elev; + r_pt_contrib.collection_elevation_min = v_elev_min; + r_pt_contrib.collection_elevation_max = v_elev_max; + r_pt_contrib.collection_lat_deg = v_lat_deg; + r_pt_contrib.collection_lat_min = v_lat_min; + r_pt_contrib.collection_lat_sec = v_lat_sec; + r_pt_contrib.collection_long_deg = v_long_deg; + r_pt_contrib.collection_long_min = v_long_min; + r_pt_contrib.collection_long_sec = v_long_sec; + r_pt_contrib.smp_mean_bv_AD = v_smp_mean_bv_AD; + r_pt_contrib.smp_mean_bv_DFS = v_smp_mean_bv_DFS; + r_pt_contrib.smp_mean_bv_DFU = v_smp_mean_bv_DFU; + r_pt_contrib.smp_mean_bv_DFW = v_smp_mean_bv_DFW; + r_pt_contrib.smp_mean_bv_DSB = v_smp_mean_bv_DSB; + r_pt_contrib.smp_mean_bv_DSC = v_smp_mean_bv_DSC; + r_pt_contrib.smp_mean_bv_DSG = v_smp_mean_bv_DSG; + r_pt_contrib.smp_mean_bv_GVO = v_smp_mean_bv_GVO; + r_pt_contrib.smp_mean_bv_IWS = v_smp_mean_bv_IWS; + r_pt_contrib.smp_mean_bv_WDU = v_smp_mean_bv_WDU; + r_pt_contrib.smp_mean_bv_WVE = v_smp_mean_bv_WVE; + r_pt_contrib.smp_mean_bv_WWD = v_smp_mean_bv_WWD; + r_pt_contrib.smp_success_pct = v_smp_success_pct; + r_pt_contrib.orchard_contamination_pct = v_orchard_contamination_pct; + r_pt_contrib.effective_pop_size = v_effective_pop_size; + if (b_bv_AD_not_estimated && r_pt_contrib.pct_tested_parent_trees_AD >= 70) { + r_pt_contrib.gw_AD = v_gw_AD; + } + if (b_bv_DFS_not_estimated && r_pt_contrib.pct_tested_parent_trees_DFS >= 70) { + r_pt_contrib.gw_DFS = v_gw_DFS; + } + if (b_bv_DFU_not_estimated && r_pt_contrib.pct_tested_parent_trees_DFU >= 70) { + r_pt_contrib.gw_DFU = v_gw_DFU; + } + if (b_bv_DFW_not_estimated && r_pt_contrib.pct_tested_parent_trees_DFW >= 70) { + r_pt_contrib.gw_DFW = v_gw_DFW; + } + if (b_bv_DSB_not_estimated && r_pt_contrib.pct_tested_parent_trees_DSB >= 70) { + r_pt_contrib.gw_DSB = v_gw_DSB; + } + if (b_bv_DSC_not_estimated && r_pt_contrib.pct_tested_parent_trees_DSC >= 70) { + r_pt_contrib.gw_DSC = v_gw_DSC; + } + if (b_bv_DSG_not_estimated && r_pt_contrib.pct_tested_parent_trees_DSG >= 70) { + r_pt_contrib.gw_DSG = v_gw_DSG; + } + if (b_bv_GVO_not_estimated && r_pt_contrib.pct_tested_parent_trees_GVO >= 70) { + r_pt_contrib.gw_GVO = v_gw_GVO; + } + if (b_bv_IWS_not_estimated && r_pt_contrib.pct_tested_parent_trees_IWS >= 70) { + r_pt_contrib.gw_IWS = v_gw_IWS; + } + if (b_bv_WDU_not_estimated && r_pt_contrib.pct_tested_parent_trees_WDU >= 70) { + r_pt_contrib.gw_WDU = v_gw_WDU; + } + if (b_bv_WVE_not_estimated && r_pt_contrib.pct_tested_parent_trees_WVE >= 70) { + r_pt_contrib.gw_WVE = v_gw_WVE; + } + if (b_bv_WWD_not_estimated && r_pt_contrib.pct_tested_parent_trees_WWD >= 70) { + r_pt_contrib.gw_WWD = v_gw_WWD; + } + r_pt_contrib.total_parent_trees = v_total_parent_trees; + // -- need to find out percent for each parent tree trait + + r_pt_contrib.pct_tested_parent_trees = Math.round((v_sum_p_prop_contrib_tested / v_sum_p_prop_contrib) * 100); + r_pt_contrib.pct_untested_parent_trees = 100 - r_pt_contrib.pct_tested_parent_trees; + } +} + +/* + * Procedure: set_pt_contrib + * Purpose: Set Mean Collection Geography, Genetic Worth (GW), + * Effective Population Size (Ne) and other values calculated + * based on Parent Tree Contribution. + * p_replace_values is true forces replacing of calculated values + * (otherwise they are only replaced based on status or the user + * blanking-out the current value) + */ +function set_pt_contrib(p_replace_values: boolean) { + const CONST_DEFAULT_UNTESTED_GW_AD : number = SPR_CONSTANTS.CONST_DEFAULT_GEN_QLTY_AD; + const CONST_DEFAULT_CUSTOM_GW_AD : number = SPR_CONSTANTS.CONST_DEFAULT_GEN_QLTY_AD; + const CONST_DEFAULT_UNTESTED_GW_DFS: number = SPR_CONSTANTS.CONST_DEFAULT_GEN_QLTY_DFS; + const CONST_DEFAULT_CUSTOM_GW_DFS : number = SPR_CONSTANTS.CONST_DEFAULT_GEN_QLTY_DFS; + const CONST_DEFAULT_UNTESTED_GW_DFU: number = SPR_CONSTANTS.CONST_DEFAULT_GEN_QLTY_DFU; + const CONST_DEFAULT_CUSTOM_GW_DFU : number = SPR_CONSTANTS.CONST_DEFAULT_GEN_QLTY_DFU; + const CONST_DEFAULT_UNTESTED_GW_DFW: number = SPR_CONSTANTS.CONST_DEFAULT_GEN_QLTY_DFW; + const CONST_DEFAULT_CUSTOM_GW_DFW : number = SPR_CONSTANTS.CONST_DEFAULT_GEN_QLTY_DFW; + const CONST_DEFAULT_UNTESTED_GW_DSB: number = SPR_CONSTANTS.CONST_DEFAULT_GEN_QLTY_DSB; + const CONST_DEFAULT_CUSTOM_GW_DSB : number = SPR_CONSTANTS.CONST_DEFAULT_GEN_QLTY_DSB; + const CONST_DEFAULT_UNTESTED_GW_DSC: number = SPR_CONSTANTS.CONST_DEFAULT_GEN_QLTY_DSC; + const CONST_DEFAULT_CUSTOM_GW_DSC : number = SPR_CONSTANTS.CONST_DEFAULT_GEN_QLTY_DSC; + const CONST_DEFAULT_UNTESTED_GW_DSG: number = SPR_CONSTANTS.CONST_DEFAULT_GEN_QLTY_DSG; + const CONST_DEFAULT_CUSTOM_GW_DSG : number = SPR_CONSTANTS.CONST_DEFAULT_GEN_QLTY_DSG; + const CONST_DEFAULT_UNTESTED_GW_GVO: number = SPR_CONSTANTS.CONST_DEFAULT_GEN_QLTY_GVO; + const CONST_DEFAULT_CUSTOM_GW_GVO : number = SPR_CONSTANTS.CONST_DEFAULT_GEN_QLTY_GVO; + const CONST_DEFAULT_UNTESTED_GW_IWS: number = SPR_CONSTANTS.CONST_DEFAULT_GEN_QLTY_IWS; + const CONST_DEFAULT_CUSTOM_GW_IWS : number = SPR_CONSTANTS.CONST_DEFAULT_GEN_QLTY_IWS; + const CONST_DEFAULT_UNTESTED_GW_WDU: number = SPR_CONSTANTS.CONST_DEFAULT_GEN_QLTY_WDU; + const CONST_DEFAULT_CUSTOM_GW_WDU : number = SPR_CONSTANTS.CONST_DEFAULT_GEN_QLTY_WDU; + const CONST_DEFAULT_UNTESTED_GW_WVE: number = SPR_CONSTANTS.CONST_DEFAULT_GEN_QLTY_WVE; + const CONST_DEFAULT_CUSTOM_GW_WVE : number = SPR_CONSTANTS.CONST_DEFAULT_GEN_QLTY_WVE; + const CONST_DEFAULT_UNTESTED_GW_WWD: number = SPR_CONSTANTS.CONST_DEFAULT_GEN_QLTY_WWD; + const CONST_DEFAULT_CUSTOM_GW_WWD : number = SPR_CONSTANTS.CONST_DEFAULT_GEN_QLTY_WWD; + let b_all_gw_blank: boolean = false; + + // --Collection Elev (do mean/min/max as a unit since only mean is on the page and can be blanked for replacing) + if (replace_area_of_use(g_collection_elevation) || p_replace_values) { + set_collection_elevation(r_pt_contrib.collection_elevation); + set_collection_elevation_min(r_pt_contrib.collection_elevation_min); + set_collection_elevation_max(r_pt_contrib.collection_elevation_max); + } + // --Collection Latitude + if (is_area_of_use_status() || is_collection_lat_empty() || p_replace_values) { + set_collection_lat_deg(r_pt_contrib.collection_lat_deg); + set_collection_lat_min(r_pt_contrib.collection_lat_min); + set_collection_lat_sec(r_pt_contrib.collection_lat_sec); + } + // --Collection Longitude + if (is_area_of_use_status() || is_collection_long_empty() || p_replace_values) { + set_collection_long_deg(r_pt_contrib.collection_long_deg); + set_collection_long_min(r_pt_contrib.collection_long_min); + set_collection_long_sec(r_pt_contrib.collection_long_sec); + } + // --SMP Mean BV-AD + if (replace_area_of_use(g_smp_mean_bv_AD) || p_replace_values) { + set_smp_mean_bv_AD(r_pt_contrib.smp_mean_bv_AD); + } + // --SMP Mean BV-DFS + if (replace_area_of_use(g_smp_mean_bv_DFS) || p_replace_values) { + set_smp_mean_bv_DFS(r_pt_contrib.smp_mean_bv_DFS); + } + // --SMP Mean BV-DFU + if (replace_area_of_use(g_smp_mean_bv_DFU) || p_replace_values) { + set_smp_mean_bv_DFU(r_pt_contrib.smp_mean_bv_DFU); + } + // --SMP Mean BV-DFW + if (replace_area_of_use(g_smp_mean_bv_DFW) || p_replace_values) { + set_smp_mean_bv_DFW(r_pt_contrib.smp_mean_bv_DFW); + } + // --SMP Mean BV-DSB + if (replace_area_of_use(g_smp_mean_bv_DSB) || p_replace_values) { + set_smp_mean_bv_DSB(r_pt_contrib.smp_mean_bv_DSB); + } + // --SMP Mean BV-DSC + if (replace_area_of_use(g_smp_mean_bv_DSC) || p_replace_values) { + set_smp_mean_bv_DSC(r_pt_contrib.smp_mean_bv_DSC); + } + // --SMP Mean BV-DSG + if (replace_area_of_use(g_smp_mean_bv_DSG) || p_replace_values) { + set_smp_mean_bv_DSG(r_pt_contrib.smp_mean_bv_DSG); + } + // --SMP Mean BV-GVO + if (replace_area_of_use(g_smp_mean_bv_GVO) || p_replace_values) { + set_smp_mean_bv_GVO(r_pt_contrib.smp_mean_bv_GVO); + } + // --SMP Mean BV-IWS + if (replace_area_of_use(g_smp_mean_bv_IWS) || p_replace_values) { + set_smp_mean_bv_IWS(r_pt_contrib.smp_mean_bv_IWS); + } + // --SMP Mean BV-WDU + if (replace_area_of_use(g_smp_mean_bv_WDU) || p_replace_values) { + set_smp_mean_bv_WDU(r_pt_contrib.smp_mean_bv_WDU); + } + // --SMP Mean BV-WVE + if (replace_area_of_use(g_smp_mean_bv_WVE) || p_replace_values) { + set_smp_mean_bv_WVE(r_pt_contrib.smp_mean_bv_WVE); + } + // --SMP Mean BV-WWD + if (replace_area_of_use(g_smp_mean_bv_WWD) || p_replace_values) { + set_smp_mean_bv_WWD(r_pt_contrib.smp_mean_bv_WWD); + } + // --SMP Success + if (replace_area_of_use(g_smp_success_pct) || p_replace_values) { + set_smp_success_pct(r_pt_contrib.smp_success_pct); + } + // --Orchard Contam + if (replace_area_of_use(g_orchard_contamination_pct) || p_replace_values) { + set_orchard_contamination_pct(r_pt_contrib.orchard_contamination_pct); + } + // --Effective Population Size + if (replace_area_of_use(_effective_pop_size) || p_replace_values) { + set_effective_pop_size(r_pt_contrib.effective_pop_size); + } + // --Total Parent Trees and ratio untested and tested + set_total_parent_trees(r_pt_contrib.total_parent_trees); + g_tested_parent_trees_pct = r_pt_contrib.pct_tested_parent_trees; + + g_tested_parent_trees_pct_AD = r_pt_contrib.pct_tested_parent_trees_AD; + g_tested_parent_trees_pct_DFS = r_pt_contrib.pct_tested_parent_trees_DFS; + g_tested_parent_trees_pct_DFU = r_pt_contrib.pct_tested_parent_trees_DFU; + g_tested_parent_trees_pct_DFW = r_pt_contrib.pct_tested_parent_trees_DFW; + g_tested_parent_trees_pct_DSB = r_pt_contrib.pct_tested_parent_trees_DSB; + g_tested_parent_trees_pct_DSC = r_pt_contrib.pct_tested_parent_trees_DSC; + g_tested_parent_trees_pct_DSG = r_pt_contrib.pct_tested_parent_trees_DSG; + g_tested_parent_trees_pct_GVO = r_pt_contrib.pct_tested_parent_trees_GVO; + g_tested_parent_trees_pct_IWS = r_pt_contrib.pct_tested_parent_trees_IWS; + g_tested_parent_trees_pct_WDU = r_pt_contrib.pct_tested_parent_trees_WDU; + g_tested_parent_trees_pct_WVE = r_pt_contrib.pct_tested_parent_trees_WVE; + g_tested_parent_trees_pct_WWD = r_pt_contrib.pct_tested_parent_trees_WWD; + + g_untested_parent_trees_pct = r_pt_contrib.pct_untested_parent_trees; + // -- if all the gw values have been blanked-out, reset to the calculated gw values. + if (g_gw_AD == null && g_gw_DFS == null && g_gw_DFU == null && g_gw_DFW == null && + g_gw_DSB == null && g_gw_DSC == null && g_gw_DSG == null && g_gw_GVO == null && + g_gw_IWS == null && g_gw_WDU == null && g_gw_WVE == null && g_gw_WWD == null) { + b_all_gw_blank = true; + } + + // --Genetic Worth - Deer browse + if (is_area_of_use_status() || p_replace_values || b_all_gw_blank) { + set_gw_AD(null); + if (g_seedlot_source_code == 'UPT' && r_pt_contrib.gw_AD != null) { + if (r_pt_contrib.total_parent_trees > 0) { + // --Untested lots get default GW-AD value + set_gw_AD(CONST_DEFAULT_UNTESTED_GW_AD); + } + } else if (g_seedlot_source_code == 'CUS' && r_pt_contrib.gw_AD != null) { + if (r_pt_contrib.total_parent_trees > 0) { + // --Custom lots get default GW-AD value + set_gw_AD(CONST_DEFAULT_CUSTOM_GW_AD); + } + } else { + set_gw_AD(r_pt_contrib.gw_AD); + } + } + // --Genetic Worth - Dothistroma needle blight + if (is_area_of_use_status() || p_replace_values || b_all_gw_blank) { + set_gw_DFS(null); + if (g_seedlot_source_code == 'UPT' && r_pt_contrib.gw_DFS != null) { + if (r_pt_contrib.total_parent_trees > 0) { + // --Untested lots get default GW-DFS value + set_gw_DFS(CONST_DEFAULT_UNTESTED_GW_DFS); + } + } else if (g_seedlot_source_code == 'CUS' && r_pt_contrib.gw_DFS != null) { + if (r_pt_contrib.total_parent_trees > 0) { + // --Custom lots get default GW-DFS value + set_gw_DFS(CONST_DEFAULT_CUSTOM_GW_DFS); + } + } else { + set_gw_DFS(r_pt_contrib.gw_DFS); + } + } + // --Genetic Worth - Cedar leaf blight + if (is_area_of_use_status() || p_replace_values || b_all_gw_blank) { + set_gw_DFU(null); + if (g_seedlot_source_code == 'UPT' && r_pt_contrib.gw_DFU != null) { + if (r_pt_contrib.total_parent_trees > 0) { + // --Untested lots get default GW-DFU value + set_gw_DFU(CONST_DEFAULT_UNTESTED_GW_DFU); + } + } else if (g_seedlot_source_code == 'CUS' && r_pt_contrib.gw_DFU != null) { + if (r_pt_contrib.total_parent_trees > 0) { + // --Custom lots get default GW-DFU value + set_gw_DFU(CONST_DEFAULT_CUSTOM_GW_DFU); + } + } else { + set_gw_DFU(r_pt_contrib.gw_DFU); + } + } + // --Genetic Worth - Swiss needle cast + if (is_area_of_use_status() || p_replace_values || b_all_gw_blank) { + set_gw_DFW(null); + if (g_seedlot_source_code == 'UPT' && r_pt_contrib.gw_DFW != null) { + if (r_pt_contrib.total_parent_trees > 0) { + // --Untested lots get default GW-DFW value + set_gw_DFW(CONST_DEFAULT_UNTESTED_GW_DFW); + } + } else if (g_seedlot_source_code == 'CUS' && r_pt_contrib.gw_DFW != null) { + if (r_pt_contrib.total_parent_trees > 0) { + // --Custom lots get default GW-DFW value + set_gw_DFW(CONST_DEFAULT_CUSTOM_GW_DFW); + } + } else { + set_gw_DFW(r_pt_contrib.gw_DFW); + } + } + // --Genetic Worth - White pine blister rust + if (is_area_of_use_status() || p_replace_values || b_all_gw_blank) { + set_gw_DSB(null); + if (g_seedlot_source_code == 'UPT' && r_pt_contrib.gw_DSB != null) { + if (r_pt_contrib.total_parent_trees > 0) { + // --Untested lots get default GW-DSB value + set_gw_DSB(CONST_DEFAULT_UNTESTED_GW_DSB); + } + } else if (g_seedlot_source_code == 'CUS' && r_pt_contrib.gw_DSB != null) { + if (r_pt_contrib.total_parent_trees > 0) { + // --Custom lots get default GW-DSB value + set_gw_DSB(CONST_DEFAULT_CUSTOM_GW_DSB); + } + } else { + set_gw_DSB(r_pt_contrib.gw_DSB); + } + } + // --Genetic Worth - Comandra blister rust + if (is_area_of_use_status() || p_replace_values || b_all_gw_blank) { + set_gw_DSC(null); + if (g_seedlot_source_code == 'UPT' && r_pt_contrib.gw_DSC != null) { + if (r_pt_contrib.total_parent_trees > 0) { + // --Untested lots get default GW-DSC value + set_gw_DSC(CONST_DEFAULT_UNTESTED_GW_DSC); + } + } else if (g_seedlot_source_code == 'CUS' && r_pt_contrib.gw_DSC != null) { + if (r_pt_contrib.total_parent_trees > 0) { + // --Custom lots get default GW-DSC value + set_gw_DSC(CONST_DEFAULT_CUSTOM_GW_DSC); + } + } else { + set_gw_DSC(r_pt_contrib.gw_DSC); + } + } + // --Genetic Worth - Western gall rust + if (is_area_of_use_status() || p_replace_values || b_all_gw_blank) { + set_gw_DSG(null); + if (g_seedlot_source_code == 'UPT' && r_pt_contrib.gw_DSG != null) { + if (r_pt_contrib.total_parent_trees > 0) { + // --Untested lots get default GW-DSG value + set_gw_DSG(CONST_DEFAULT_UNTESTED_GW_DSG); + } + } else if (g_seedlot_source_code == 'CUS' && r_pt_contrib.gw_DSG != null) { + if (r_pt_contrib.total_parent_trees > 0) { + // --Custom lots get default GW-DSG value + set_gw_DSG(CONST_DEFAULT_CUSTOM_GW_DSG); + } + } else { + set_gw_DSG(r_pt_contrib.gw_DSG); + } + } + // --Genetic Worth - wolume growth + if (is_area_of_use_status() || p_replace_values || b_all_gw_blank) { + set_gw_GVO(null); + if (g_seedlot_source_code == 'UPT') { + if (r_pt_contrib.total_parent_trees > 0) { + // --Untested lots get default GW-GVO value + set_gw_GVO(CONST_DEFAULT_UNTESTED_GW_GVO); + } + } else if (g_seedlot_source_code == 'CUS') { + if (r_pt_contrib.total_parent_trees > 0) { + // --Custom lots get default GW-GVO value + set_gw_GVO(CONST_DEFAULT_CUSTOM_GW_GVO); + } + } else { + set_gw_GVO(r_pt_contrib.gw_GVO); + } + } + // --Genetic Worth - White pine terminal weevil + if (is_area_of_use_status() || p_replace_values || b_all_gw_blank) { + set_gw_IWS(null); + if (g_seedlot_source_code == 'UPT' && r_pt_contrib.gw_IWS != null) { + if (r_pt_contrib.total_parent_trees > 0) { + // --Untested lots get default GW-IWS value + set_gw_IWS(CONST_DEFAULT_UNTESTED_GW_IWS); + } + } else if (g_seedlot_source_code == 'CUS' && r_pt_contrib.gw_IWS != null) { + if (r_pt_contrib.total_parent_trees > 0) { + // --Custom lots get default GW-IWS value + set_gw_IWS(CONST_DEFAULT_CUSTOM_GW_IWS); + } + } else { + set_gw_IWS(r_pt_contrib.gw_IWS); + } + } + // --Genetic Worth - Durability + if (is_area_of_use_status() || p_replace_values || b_all_gw_blank) { + set_gw_WDU(null); + if (g_seedlot_source_code == 'UPT' && r_pt_contrib.gw_WDU != null) { + if (r_pt_contrib.total_parent_trees > 0) { + // --Untested lots get default GW-WDU value + set_gw_WDU(CONST_DEFAULT_UNTESTED_GW_WDU); + } + } else if (g_seedlot_source_code =='CUS' && r_pt_contrib.gw_WDU != null) { + if (r_pt_contrib.total_parent_trees > 0) { + // --Custom lots get default GW-WDU value + set_gw_WDU(CONST_DEFAULT_CUSTOM_GW_WDU); + } + } else { + set_gw_WDU(r_pt_contrib.gw_WDU); + } + } + // --Genetic Worth - Wood velocity measures + if (is_area_of_use_status() || p_replace_values || b_all_gw_blank) { + set_gw_WVE(null); + if (g_seedlot_source_code == 'UPT' && r_pt_contrib.gw_WVE != null) { + if (r_pt_contrib.total_parent_trees > 0) { + // --Untested lots get default GW-WVE value + set_gw_WVE(CONST_DEFAULT_UNTESTED_GW_WVE); + } + } else if (g_seedlot_source_code == 'CUS' && r_pt_contrib.gw_WVE != null) { + if (r_pt_contrib.total_parent_trees > 0) { + // --Custom lots get default GW-WVE value + set_gw_WVE(CONST_DEFAULT_CUSTOM_GW_WVE); + } + } else { + set_gw_WVE(r_pt_contrib.gw_WVE); + } + } + // --Genetic Worth - Wood density + if (is_area_of_use_status() || p_replace_values || b_all_gw_blank) { + set_gw_WWD(null); + if (g_seedlot_source_code == 'UPT' && r_pt_contrib.gw_WWD != null) { + if (r_pt_contrib.total_parent_trees > 0) { + // --Untested lots get default GW-WWD value + set_gw_WWD(CONST_DEFAULT_UNTESTED_GW_WWD); + } + } else if (g_seedlot_source_code == 'CUS' && r_pt_contrib.gw_WWD != null) { + if (r_pt_contrib.total_parent_trees > 0) { + // --Custom lots get default GW-WWD value + set_gw_WWD(CONST_DEFAULT_CUSTOM_GW_WWD); + } + } else { + set_gw_WWD(r_pt_contrib.gw_WWD); + } + } +} + +/* + * Procedure: recalc_all + * Purpose: Determines context (A or B class, BC Source, etc) and + * recalculate all defaults and derives Area of Use information in context. + * p_get_current_tests ignored if is_area_of_use_status() + */ +function recalc_all(p_pt_arrayZ: any[], p_get_current_tests: boolean, p_replace_values: boolean, p_role_list: string) { + get_previous_seedlot_values(); + if (g_genetic_class_code == 'B') { + // --Reset 'A' only values + set_orchard_id(null); + set_effective_pop_size(null); + set_seed_plan_unit_id(null); + set_seedlot_source_code(null); + set_biotech_processes_ind(null); + set_contaminant_pollen_bv(null); + set_controlled_cross_ind(null); + set_female_gametic_mthd_code(null); + set_male_gametic_mthd_code(null); + set_orchard_comment(null); + set_orchard_contamination_pct(null); + set_pollen_contamination_ind(null); + set_pollen_contam_mthd_code(null); + set_pollen_contamination_pct(null); + set_secondary_orchard_id(null); + set_smp_mean_bv_AD(null); + set_smp_mean_bv_DFS(null); + set_smp_mean_bv_DFU(null); + set_smp_mean_bv_DFW(null); + set_smp_mean_bv_DSB(null); + set_smp_mean_bv_DSC(null); + set_smp_mean_bv_DSG(null); + set_smp_mean_bv_GVO(null); + set_smp_mean_bv_IWS(null); + set_smp_mean_bv_WDU(null); + set_smp_mean_bv_WVE(null); + set_smp_mean_bv_WWD(null); + set_smp_parents_outside(null); + set_smp_success_pct(null); + set_total_parent_trees(null); + set_collection_source_code(null); + set_fs721a_signed_ind(null); + } else { + // --Reset 'B' only values + set_superior_prvnc_ind(null); + set_provenance_id(null); + set_collection_locn_desc(null); + set_coll_standard_met_ind(null); + set_nmbr_trees_from_code(null); + set_collection_bgc_ind(null); + set_collection_spz_ind(null); + set_seed_plan_zone_code(null); + set_seed_coast_area_code(null); + set_collection_area_radius(null); + set_bgc_zone_code(null); + set_bgc_subzone_code(null); + set_variant(null); + set_bec_version_id(null); + } + // --BC Source + if (g_bc_source_ind == 'Y') { + // --Default latitude code + if (g_collection_lat_deg != null || g_collection_lat_min != null || g_collection_lat_sec != null) { + set_collection_latitude_code('N'); + } + // --Default longitude code + if (g_collection_long_deg != null || g_collection_long_min != null || g_collection_long_sec != null) { + set_collection_longitude_code('W'); + } + // --Non-BC Source + } else if (g_bc_source_ind == 'N') { + if (g_genetic_class_code == 'A') { + set_superior_prvnc_ind(null); + } else if (g_genetic_class_code == 'B') { + set_superior_prvnc_ind('N'); + } + set_org_unit_no(null); + set_provenance_id(null); + set_bgc_zone_code(null); + set_bgc_subzone_code(null); + set_variant(null); + set_bec_version_id(null); + set_collection_bgc_ind(null); + set_seed_plan_zone_code(null); + set_collection_spz_ind(null); + set_seed_coast_area_code(null); + set_coll_standard_met_ind(null); + } + + if (g_superior_prvnc_ind == 'Y') { + set_collection_locn_desc(null); + } else if (g_superior_prvnc_ind == 'N') { + set_provenance_id(null); + set_coll_standard_met_ind(null); + // --No GW for Non-Sup Prov B Lots + set_gw_AD(null); + set_gw_DFS(null); + set_gw_DFU(null); + set_gw_DFW(null); + set_gw_DSB(null); + set_gw_DSC(null); + set_gw_DSG(null); + set_gw_GVO(null); + set_gw_IWS(null); + set_gw_WDU(null); + set_gw_WVE(null); + set_gw_WWD(null); + } + // --Status + if (r_previous.seedlot_status_code == 'SUB' && ['PND','INC'].includes(g_seedlot_status_code) ){ // --java should not allow SUB->INC but just in case + set_declared_userid(null); + set_declared_timestamp(null); + } + // --Species + if (g_vegetation_code == null) { + set_provenance_id(null); + set_orchard_id(null); + set_secondary_orchard_id(null); + set_seed_plan_unit_id(null); + } else if (g_vegetation_code != nvl(r_previous.vegetation_code,'~')) { + // --Check species-specific information + // -->provenanance + if (g_provenance_id != null && !provenance_is_valid_for_spp()) { + set_provenance_id(null); + } + // -->orchard + if (g_orchard_id != null && !orchard_is_valid_for_species(g_orchard_id)) { + set_orchard_id(null); + } + // -->secondary orchard + if (g_secondary_orchard_id != null && !orchard_is_valid_for_species(g_secondary_orchard_id)) { + set_secondary_orchard_id(null); + } + // -->spu + if (g_seed_plan_unit_id == null && !spu_is_valid_for_species()) { + set_seed_plan_unit_id(null); + } + } + // --Derive Parent Tree Contribution information + // --> GW, Effective Population Size, Collection Geography, etc. + if (g_genetic_class_code == 'A' && r_previous.seedlot_status_code != 'INC') { + // --populates r_pt_contrib + calc_pt_contrib(p_pt_array,p_get_current_tests); + // --set pt contribution fields + set_pt_contrib(p_replace_values); + } + // --Apply transfer limits to get Area of Use + // --> may use Collection Geography derived from pt contrib + get_area_of_use(); + // --Set Mean Area of Use geography to Mean Collection geography. + set_mean_area_of_use_geography(); +} + +function valid_lot_number_range(p_seedlot_number: string): boolean { + let b_lot_number_valid: boolean = true; + + // --Check ranges (for pre-numbered FS721 and FS721A forms already issued) + if (g_genetic_class_code == 'A') { + if (parseInt(g_seedlot_number) <= parseInt(CONST_CLASS_A_LOTNUM_MIN)-1 || parseInt(g_seedlot_number) >= parseInt(CONST_CLASS_B_LOTNUM_MAX)+1) { + g_error_message = g_error_message || 'spar.web.error.usr.new_lot_number:' + || g_genetic_class_code ||',' + || TO_CHAR(parseInt(CONST_CLASS_B_LOTNUM_MAX)+1)||',' + || TO_CHAR(parseInt(CONST_CLASS_A_LOTNUM_MIN)-1)||';'; + b_lot_number_valid = false; + } + } else if (g_genetic_class_code == 'B') { + if (parseInt(g_seedlot_number) >= parseInt(CONST_CLASS_B_LOTNUM_MIN)) { + g_error_message = g_error_message || 'spar.web.error.usr.new_lot_number:' + || g_genetic_class_code ||',00001,' + || TO_CHAR(parseInt(CONST_CLASS_B_LOTNUM_MIN)-1)||';'; + b_lot_number_valid = false; + } + } + return b_lot_number_valid; +} + +/* + * Procedure: validate_lot_number + * Purpose: if lot number specified on add, ensure it is not a dup and that it is in acceptable range for entry + */ +function validate_lot_number() { + let v_temp_num: number; // NUMBER(1); + let b_lot_number_valid: boolean = true; + + // --if adding and lot number specified + if (g_revision_count == null && g_seedlot_number != null) { + b_lot_number_valid = valid_lot_number_range(g_seedlot_number); + if (b_lot_number_valid) { + // --Check for dups + /* + SELECT 1 + INTO v_temp_num + FROM seedlot + WHERE seedlot_number = g_seedlot_number; + */ + g_error_message = g_error_message || 'ca.bc.gov.mof.sil.webade.db.error.usr.field.alreadyExists:Seedlot;'; + } + } + + if ('NO_DATA_FOUND') { + throw new Error('NO_DATA_FOUND'); + } +} + +/* + * Procedure: validate_seedlot_cancel + * Purpose: Check whether it is OK to cancel seedlot + */ +function validate_seedlot_cancel(p_role_listZ: string, p_action_nameZ: string) { + let temp_code: string; // SEEDLOT.SEEDLOT_NUMBER%TYPE; + let status_code: string; // SEEDLOT.SEEDLOT_STATUS_CODE%TYPE; + let cancel_authority: string; // VARCHAR2(1); + let txn_count: number; // NUMBER(5); + let rqst_count: number; // NUMBER(5); + + // -- set authority/user flag + cancel_authority = Spr_Check_Field_Authority(p_role_list, 'CANCEL', p_action_name); + // -- don't bother WITH updates if lot NOT FOUND + /* + SELECT SEEDLOT_NUMBER, SEEDLOT_STATUS_CODE + INTO temp_code, status_code + FROM SEEDLOT + WHERE SEEDLOT_NUMBER = g_seedlot_number AND REVISION_COUNT = g_revision_count; + */ + // -- check if user has authority to cancel + if (g_seedlot_status_code == 'CAN') { + if (cancel_authority == 'N') { + g_error_message = g_error_message || 'spar.web.error.usr.seedlotstatus.noauthoritytocancel;'; + } else { + // -- Make sure there are no transactions or requests against the lot + /* + SELECT COUNT(*) + INTO txn_count + FROM SEEDLOT_TRANSACTION + WHERE SEEDLOT_NUMBER = g_seedlot_number; + SELECT COUNT(*) + INTO rqst_count + FROM REQUEST_SEEDLOT + WHERE SEEDLOT_NUMBER = g_seedlot_number; + */ + if (txn_count + rqst_count > 0) { + g_error_message = g_error_message || 'spar.web.error.usr.seedlotcancel.records_exist;'; + } + } + } +} + +/* + * Procedure: validate_seedlot_cancel (OVERLOADED) + * Purpose: Check whether it is OK to cancel seedlot - NEW RULES. + * Assumes interface has validated user's auth to CANcel. + */ +function validate_seedlot_cancel() { + let v_txn_count: number; // NUMBER(10); + let v_rqst_count: number; // NUMBER(10); + + if (g_seedlot_status_code == 'CAN' && r_previous.seedlot_status_code != 'CAN') { + // -- Make sure there are no transactions against the lot + /* + SELECT COUNT(1) + INTO v_txn_count + FROM seedlot_transaction + WHERE seedlot_number = g_seedlot_number; + */ + v_txn_count = resultsql; + + // -- Make sure there are no requests against the lot + /* + SELECT COUNT(1) + INTO v_rqst_count + FROM request_seedlot + WHERE seedlot_number = g_seedlot_number; + */ + v_rqst_count = resultsql; + if (v_txn_count + v_rqst_count > 0) { + g_error_message = g_error_message || 'spar.web.error.usr.seedlotcancel.records_exist;'; + } + } +} + +/* + * Procedure: validate_genetic_worth_change + * Purpose: Check whether it is OK to change genetic worth + */ +function validate_genetic_worth_change(p_role_listZ: string, p_action_nameZ: string) { + let temp_code: string; // SEEDLOT.SEEDLOT_NUMBER%TYPE; + let status_code: string; // SEEDLOT.SEEDLOT_STATUS_CODE%TYPE; + let prev_genetic_class_code: string; // SEEDLOT.GENETIC_CLASS_CODE%TYPE; + let gen_class_authority: string; // VARCHAR2(1); + + // -- set authority/user flag + gen_class_authority = Spr_Check_Field_Authority(p_role_list, 'CHANGE GENETIC CLASS', P_action_name); + + if (g_revision_count != null) { + // -- don't bother WITH updates if lot NOT FOUND + /* + SELECT SEEDLOT_NUMBER, SEEDLOT_STATUS_CODE, GENETIC_CLASS_CODE + INTO TEMP_CODE, STATUS_CODE, PREV_GENETIC_CLASS_CODE + FROM SEEDLOT + WHERE SEEDLOT_NUMBER = g_seedlot_number AND REVISION_COUNT = g_revision_count; + */ + temp_code = resultsql; + status_code = resultsql; + prev_genetic_class_code = resultsql; + + //-- check if genetic class has changed + if (prev_genetic_class_code != null && g_genetic_class_code != prev_genetic_class_code && gen_class_authority == 'N') { + g_error_message = g_error_message || 'spar.web.error.usr.class.insufficientauthority;'; + } + } +} + +/* + * Procedure: validate_genetic_class + * Purpose: Genetic Class and Collection Source cannot be null + */ +function validate_genetic_class() { + if (g_genetic_class_code == null || g_collection_source_code == null) { + g_error_message = g_error_message || 'spar.web.error.usr.required.field:Genetic Class;'; + } +} + +/* + * Procedure: validate_org + * Purpose: Validate org unit for Ministry user + */ +function validate_org(p_user_org_unit_no: number) { + if (g_org_unit_no != null && p_user_org_unit_no != null) { + if (sil_check_user_org_authority(p_user_org_unit_no,g_org_unit_no) == 'N') { + g_error_message = g_error_message || 'spar.web.error.usr.org_unit_code.you_must_belong_to_or_have_authority;'; + } + } +} + +/* + * Procedure: validate_orchard_id + * Purpose: Ensure Orchard Id != null in certain conditions + */ +function validate_orchard_id() { + if (g_orchard_id == null && g_collection_source_code.substring(1, 1) == 'A' && r_previous.genetic_class_code == 'A' && r_previous.orchard_id != null) { + g_error_message = g_error_message || 'spar.web.error.usr.mandatory:Orchard ID, Genetic Class, A;'; + } +} + +/* + * Procedure: validate_collection_locn + * Purpose: Ensure Collection Location != null in certain conditions + */ +function validate_collection_locn() { + if (g_collection_locn_desc == null && g_collection_source_code.subtring(1,1) == 'B' && r_previous.genetic_class_code == 'B' && r_previous.collection_locn_desc != null) { + g_error_message = g_error_message || 'spar.web.error.usr.mandatory:Collection LOCATION, Genetic Class, B;'; + } +} + +/* + * Procedure: validate_coast_geo + * Purpose: Ensure Coast geographic location code is entered if previously entered + */ +function validate_coast_geo (p_spz1Z: string) { + if (g_seed_coast_area_code == null && r_previous.seed_coast_area_code != null + && r_previous.genetic_class_code == 'B' && g_genetic_class_code == 'B' && p_spz1 == 'M' + && r_previous.seed_plan_zone_code != null) { + g_error_message = g_error_message || 'spar.web.error.usr.spz_coastal_geo_area_mandatory;'; + } +} + +/* + * Procedure: validate_mean_elevation + * Purpose: Ensure mean elevation is entered + */ +function validate_mean_elevation() { + if ((g_elevation == null? 0 : g_elevation) == 0 && r_previous.elevation > 0) { + g_error_message = g_error_message || 'spar.web.error.usr.required.field:Mean Elevation;'; + } +} + +/* + * Procedure: validate_mean_latitude + * Purpose: Ensure mean latitude is entered + */ +function validate_mean_latitude() { + if ((g_latitude_degrees == null? 0 : g_latitude_degrees) == 0 && r_previous.latitude_degrees > 0) { + g_error_message = g_error_message || 'spar.web.error.usr.required.field:Mean Latitude;'; + } +} + +/* + * Procedure: validate_min_latitude + * Purpose: Ensure min latitude is entered + */ +function validate_min_latitude() { + if ((g_latitude_deg_min == null? 0 : g_latitude_deg_min) == 0 && r_previous.latitude_deg_min > 0) { + g_error_message = g_error_message || 'spar.web.error.usr.required.field:Min Latitude;'; + } +} + +/* + * Procedure: validate_max_latitude + * Purpose: Ensure max latitude is entered + */ +function validate_max_latitude() { + if ((g_latitude_deg_max == null? 0 : g_latitude_deg_max) == 0 && r_previous.latitude_deg_max > 0) { + g_error_message = g_error_message || 'spar.web.error.usr.required.field:Max Latitude;'; + } +} + +/* + * Procedure: validate_mean_longitude + * Purpose: Ensure mean longitude is entered + */ +function validate_mean_longitude() { + if ((g_longitude_degrees == null? 0 : g_longitude_degrees) == 0 && r_previous.longitude_degrees > 0) { + g_error_message = g_error_message || 'spar.web.error.usr.required.field:Mean Longitude;'; + } +} + +/* + * Procedure: validate_min_longitude + * Purpose: Ensure min longitude is entered + */ +function validate_min_longitude() { + if ((g_longitude_deg_min == null? 0 : g_longitude_deg_min) == 0 && r_previous.longitude_deg_min > 0) { + g_error_message = g_error_message || 'spar.web.error.usr.required.field:Min Longitude;'; + } +} + +/* + * Procedure: validate_max_longitude + * Purpose: Ensure max longitude is entered + */ +function validate_max_longitude() { + if ((g_longitude_deg_max == null? 0 : g_longitude_deg_max) = 0 && r_previous.longitude_deg_max > 0) { + g_error_message = g_error_message || 'spar.web.error.usr.required.field:Max Longitude;'; + } +} + +/* + * Procedure: validate_bgc_zone + * Purpose: Ensure bgc zone is entered + */ +function validate_bgc_zone() { + if (g_bgc_zone_code == null && r_previous.bgc_zone_code != null && r_previous.genetic_class_code == 'B' && g_genetic_class_code == 'B') { + g_error_message = g_error_message || 'spar.web.error.usr.mandatory:BGC Zone, Genetic Class, B;'; + } +} + +/* + * Procedure: validate_collection_start + * Purpose: Ensure collection start date is entered + */ +function validate_collection_start() { + if (g_collection_start_date == null && r_previous.collection_start_date != null) { + g_error_message = g_error_message || 'spar.web.error.usr.required.field:Collection START DATE;'; + } +} + +/* + * Procedure: validate_collection_end + * Purpose: Ensure collection end date is entered + */ +function validate_collection_end() { + if (g_collection_end_date == null && r_previous.collection_end_date != null) { + g_error_message = g_error_message || 'spar.web.error.usr.required.field:Collection END DATE;'; + } +} + +/* + * Procedure: validate_vol_per_container + * Purpose: Ensure volume per container is entered + */ +function validate_vol_per_container() { + if ((g_vol_per_container == null? 0 : g_vol_per_container) == 0 && (r_previous.vol_per_container == null? 0 : r_previous.vol_per_container) != 0) { + g_error_message = g_error_message || 'spar.web.error.usr.required.field:Volume per Container;'; + } +} + +/* + * Procedure: validate_nmbr_trees + * Purpose: Ensure number of trees collected is entered + */ +function validate_nmbr_trees() { + if (g_nmbr_trees_from_code == null && r_previous.nmbr_trees_from_code != null && r_previous.genetic_class_code == 'B' && g_genetic_class_code == 'B') { + g_error_message = g_error_message || 'spar.web.error.usr.mandatory:NUMBER OF Trees Collected FROM, Genetic Class, B;'; + } +} + +/* + * Procedure: validate_cone_collection + * Purpose: Ensure cone collection method is entered + */ +function validate_cone_collection() { + if (g_cone_collection_method_cd == null && r_previous.cone_collection_method_code != null) { + g_error_message = g_error_message || 'spar.web.error.usr.required.field:Cone Collection Method;'; + } +} + +/* + * Procedure: validate_vegetation_code + * Purpose: Ensure vegetation code is not blank + */ +function validate_vegetation_code() { + if (g_vegetation_code == null) { + g_error_message = g_ERROR_MESSAGE || 'spar.web.error.usr.required.field:Species;'; + } +} + +/* + * Procedure: validate_coast_geo_mandatory + * Purpose: Ensure Coast geographic location code is not blank + */ +function validate_coast_geo_mandatory(p_spz1Z: string) { + if (g_seedlot_status_code != 'COM' && g_seed_coast_area_code == null && g_genetic_class_code == 'B' && p_spz1 == 'M' ) { + g_error_message = g_error_message || 'spar.web.error.usr.spz_coastal_geo_area_mandatory;'; + } +} + +/* + * Procedure: validate_superior_provenance + * Purpose: Ensure species and provenance agree + */ +function validate_superior_provenance() { + // CURSOR c_prov IS: + /* + SELECT vegetation_code + FROM superior_provenance + WHERE vegetation_code = g_vegetation_code; + */ + let r_prov: string; // c_prov%ROWTYPE; + + // --Can species have Superior Provenance? + if (g_superior_prvnc_ind == 'Y' && g_vegetation_code != null) { + // OPEN c_prov; FETCH c_prov INTO r_prov; CLOSE c_prov; + if (r_prov.vegetation_code == null) { + g_error_message = g_error_message || 'spar.web.error.usr.no.superior.prov;'; + } + } + // --Is Collection Provenance valid for Species? + if (g_provenance_id != null && g_vegetation_code != null && !provenance_is_valid_for_spp()) { + if (r_prov.vegetation_code != g_vegetation_code) { + g_error_message = g_error_message || 'spar.web.error.usr.lot_spp:Collection Provenance selected;'; + } + } +} + +/* + * Procedure: validate_b_sup_prov_geog + * Purpose: Validate collection geography based on superior provenance. + * Currently only ELEVATION is validated. We may be able to + * validate 8km radius rule spatially in future. + */ +function validate_b_sup_prov_geog() { + // CURSOR c_prov IS + /* + SELECT provenance_id + , collection_elevation_min + , collection_elevation_max + FROM superior_provenance + WHERE provenance_id = g_provenance_id and vegetation_code = g_vegetation_code; + */ + let r_prov: any; // c_prov%ROWTYPE; + + // CURSOR c_prov_spz IS: + /* + SELECT seed_plan_zone_code + FROM superior_provenance_plan_zone + WHERE provenance_id = r_prov.provenance_id and seed_plan_zone_code = g_seed_plan_zone_code; + */ + let r_prov_spz: any; // c_prov_spz%ROWTYPE; + + if (g_provenance_id != null && g_vegetation_code != null) { + // OPEN c_prov; FETCH c_prov INTO r_prov; CLOSE c_prov; + if (r_prov.provenance_id != null) { + // --Collection Elevation + r_prov.collection_elevation_min = r_prov.collection_elevation_min == null? -999999 : r_prov.collection_elevation_min; + r_prov.collection_elevation_max = r_prov.collection_elevation_max == null? 999999 : r_prov.collection_elevation_max; + + if (g_collection_elevation <= r_prov.collection_elevation_min || g_collection_elevation >= r_prov.collection_elevation_max) { + g_error_message = g_error_message || 'spar.web.error.usr.sup_prov_geog:Collection Elevation Mean,' + || TO_CHAR(r_prov.collection_elevation_min)||'-' + || TO_CHAR(r_prov.collection_elevation_max)||'m;'; + } + if (g_collection_elevation_min <= r_prov.collection_elevation_min || g_collection_elevation_min >= r_prov.collection_elevation_max) { + g_error_message = g_error_message || 'spar.web.error.usr.sup_prov_geog:Collection Elevation Min,' + || TO_CHAR(r_prov.collection_elevation_min)||'-' + || TO_CHAR(r_prov.collection_elevation_max)||'m;'; + } + if (g_collection_elevation_max <= r_prov.collection_elevation_min || g_collection_elevation_max >= r_prov.collection_elevation_max) { + g_error_message = g_error_message || 'spar.web.error.usr.sup_prov_geog:Collection Elevation Max,' + || TO_CHAR(r_prov.collection_elevation_min)||'-' + || TO_CHAR(r_prov.collection_elevation_max)||'m;'; + } + if (g_seed_plan_zone_code != null) { + // --Collection SPZ + // OPEN c_prov_spz; FETCH c_prov_spz INTO r_prov_spz; CLOSE c_prov_spz; + if (r_prov_spz.seed_plan_zone_code == null) { + g_error_message = g_error_message || 'spar.web.error.usr.lot_coll_spz;'; + } + } + } + } +} + +/* + * Procedure: validate_orchard_for_spp + * Purpose: Ensure species and orchard agree + */ +function validate_orchard_for_spp(p_orchard_id: string, p_msg: string) { + // CURSOR c_orchard IS: + /* + SELECT vegetation_code + FROM orchard + WHERE orchard_id = p_orchard_id; + */ + let r_orchard: any; // c_orchard%ROWTYPE; + + if (p_orchard_id != null) { + // OPEN c_orchard; FETCH c_orchard INTO r_orchard; CLOSE c_orchard; + if (r_orchard.vegetation_code != g_vegetation_code) { + g_error_message = g_error_message || 'spar.web.error.usr.lot_spp:'||p_msg||';'; + } + } +} + +/* + * Procedure: validate_collection_spz_for_spp + * Purpose: Ensure species and spz agree + */ +function validate_spz_spp(p_spz: string, p_msg: string) { + // CURSOR c_spz IS: + /* + SELECT seed_plan_zone_id + FROM seed_plan_zone + WHERE seed_plan_zone_code = p_spz + AND (vegetation_code = g_vegetation_code || vegetation_code is null) + AND genetic_class_code = g_genetic_class_code; + */ + let r_spz: any; // c_spz%ROWTYPE; + + if (p_spz != null && g_vegetation_code != null && g_genetic_class_code != null) { + // OPEN c_spz; FETCH c_spz INTO r_spz; CLOSE c_spz; + if (r_spz.seed_plan_zone_id == null) { + g_error_message = g_error_message || 'spar.web.error.usr.spz.invalid.combo.parm:'||p_msg||','||p_spz||';'; + } + } +} + +/* + * Procedure: validate_mandatory_APP + * Purpose: Performs mandatory validation for status of APP and above + */ +function validate_mandatory_APP(p_value: string, p_msg: string) { + if (['INC','PND','CAN','SUB'].includes(g_seedlot_status_code) && p_value == null) { + g_error_message = g_error_message || 'spar.web.error.usr.required.field:'||p_msg||';'; + } +} + +/* + * Procedure: validate_tested_untested_ratio + * Purpose: Validates the ratio of tested to untested parent trees + */ +function validate_tested_untested_ratio() { + const CONST_TESTED_PT_RATIO: number = 70; + const CONST_UNTESTED_PT_RATIO: number = 70; + const CONST_CUSTOM_TESTED_PT_RATIO: number = 70; + + if (g_genetic_class_code == 'A' && ['SUB','APP','COM','EXP'].includes(g_seedlot_status_code)) { + // --Lots from Tested Parent Trees must be comprised of 70% tested pt's + const greatest = GREATEST(r_pt_contrib.pct_tested_parent_trees_AD, + r_pt_contrib.pct_tested_parent_trees_DFS, + r_pt_contrib.pct_tested_parent_trees_DFU, + r_pt_contrib.pct_tested_parent_trees_DFW, + r_pt_contrib.pct_tested_parent_trees_DSB, + r_pt_contrib.pct_tested_parent_trees_DSC, + r_pt_contrib.pct_tested_parent_trees_DSG, + r_pt_contrib.pct_tested_parent_trees_GVO, + r_pt_contrib.pct_tested_parent_trees_IWS, + r_pt_contrib.pct_tested_parent_trees_WDU, + r_pt_contrib.pct_tested_parent_trees_WVE, + r_pt_contrib.pct_tested_parent_trees_WWD); + + if (is_lot_under_CFS() && g_seedlot_source_code == 'TPT' && r_pt_contrib.total_parent_trees > 0 && greatest < CONST_TESTED_PT_RATIO) { + g_error_message = g_error_message || 'spar.web.error.usr.a_class_ratio:Tested,at least ' + ||TO_CHAR(CONST_TESTED_PT_RATIO)||'%,Tested,' + ||TO_CHAR(r_pt_contrib.pct_tested_parent_trees)||';'; + // --Custom lots must be comprised of at least 70% tested pt's + } else if (is_lot_under_CFS() && g_seedlot_source_code == 'CUS' && r_pt_contrib.total_parent_trees > 0 && greatest < CONST_CUSTOM_TESTED_PT_RATIO) { + g_error_message = g_error_message || 'spar.web.error.usr.a_class_ratio:Custom,at least ' + ||TO_CHAR(CONST_CUSTOM_TESTED_PT_RATIO)||'%,Tested,' + ||TO_CHAR(r_pt_contrib.pct_tested_parent_trees)||';'; + // --Untested Parent Trees must be comprised of <70% tested pt's + } else if (is_lot_under_CFS() && g_seedlot_source_code == 'UPT' && r_pt_contrib.total_parent_trees > 0 && greatest >= CONST_UNTESTED_PT_RATIO) { + g_error_message = g_error_message || 'spar.web.error.usr.a_class_ratio:Untested,less than ' + ||TO_CHAR(CONST_UNTESTED_PT_RATIO)||'%,Tested,' + ||TO_CHAR(r_pt_contrib.pct_tested_parent_trees)||';'; + } + // --Lots under Chief Forester's Standards must have Parent Trees + } + +} + +/* + * Procedure: validate_seedlot_source_code + * Purpose: Ensures that TPT cannot be selected if the associated seedlot_plan_unit != primary. + */ +function validate_seedlot_source_code() { + let v_primary_spu_selected_ind: string; // CHAR(1); + + if (g_seedlot_source_code == 'TPT') { + // -- A primary spu must be selected in order to select tested parent trees. + /* + SELECT DECODE(COUNT('x'), 0, 'Y', 'N') + INTO v_primary_spu_selected_ind + FROM seed_plan_unit spu + WHERE spu.seed_plan_unit_id = g_seed_plan_unit_id AND spu.primary_ind = 'N'; + */ + v_primary_spu_selected_ind = resultsql; + if (v_primary_spu_selected_ind == 'N') { + g_error_message = g_error_message || 'spar.web.error.usr.database.primarySpuSelected;'; + } + } +} + +/* + * Procedure: validate_collection_elevation_min_max + * Purpose: Ensure the range between collection elevation min/max is within + * the CFS specified range for B class lots + */ +function validate_colln_elev_min_max() { + let e_no_limits_found: Error; + let v_min_max_range: number; + let r_limit: any; // c_limit%ROWTYPE; + + // --Limits for collection elevation min/max + // CURSOR c_limit IS: + /* + SELECT transfer_limit_skey + , max_collection_elevation_range + FROM transfer_limit tl + WHERE + ( vegetation_code = g_vegetation_code || vegetation_code is null) + AND genetic_class_code = g_genetic_class_code + AND superior_prvnc_ind = nvl(g_superior_prvnc_ind,'N') + AND coast_interior_code = spr_get_spz_type(g_seed_plan_zone_code) + AND ( seed_plan_zone_code = g_seed_plan_zone_code || seed_plan_zone_code is null) + AND g_collection_lat_deg BETWEEN site_min_latdeg AND site_max_latdeg + ORDER BY vegetation_code NULLS LAST, seed_plan_zone_code NULLS LAST; + */ + + if (g_genetic_class_code == 'B' && g_collection_elevation_min != null && g_collection_elevation_max != null) { + // --Derive collection elevation range limits (sorted so first record is all we need look at) + // OPEN c_limit; FETCH c_limit INTO r_limit; CLOSE c_limit; + if (r_limit.transfer_limit_skey == null) { + throw new Error(e_no_limits_found); + } + // --SPAR-135: don't want to validate on elevation min/max with new CBST policy. + } + + if (e_no_limits_found) { + throw new Error(e_no_limits_found); + } +} + +/* + * Procedure: validate (New Spr01 Registration validations) + * Purpose: Assumes mandatory validations are done in interface except + * for Collection Geography and Area of Use information. + * Performs cross-validations interface is not capable of. +*/ +function validate(p_user_org_unit_no: number) { + let v_spz_list: string; // g_spz_list%TYPE; + let v_pos: number; // NUMBER(3); + let v_spz: string; // g_seed_plan_zone_code%TYPE; + let v_spz_count: nullber; // NUMBER; + + get_previous_seedlot_values(); + if (g_seedlot_status_code == 'CAN' && r_previous.seedlot_status_code != 'CAN') { + validate_seedlot_cancel(); + } else { + validate_lot_number(); + validate_org(p_user_org_unit_no); + // --Collection geography information must be filled if from BC or B lot + // --(B- lot requires mean Area of Use geog (which may be copied from collection geog) + // -- for transfer guidelines) + if (g_bc_source_ind == 'Y' || g_genetic_class_code == 'B') { + validate_mandatory_APP(g_collection_lat_deg, 'Collection Mean Latitude'); + validate_mandatory_APP(g_collection_long_deg, 'Collection Mean Longitude'); + validate_mandatory_APP(g_collection_elevation, 'Collection Elevation Mean'); + } + if (g_genetic_class_code == 'B') { + validate_mandatory_APP(g_collection_elevation_min, 'Collection Elevation Min'); + validate_mandatory_APP(g_collection_elevation_max, 'Collection Elevation Max'); + // --validate maximum range between min and max elev for B class + validate_colln_elev_min_max(); + } else if (g_genetic_Class_code == 'A') { + validate_seedlot_source_code(); + } + // --Collections Provenance (Species dependent edit) + validate_superior_provenance(); + // --Collection Geography edits based on Superior Provenance + validate_b_sup_prov_geog(); + // --Orchard (Species dependent edits) + validate_orchard_for_spp(g_orchard_id, 'Orchard'); + // --Secondary Orchard (Species dependent edits) + validate_orchard_for_spp(g_secondary_orchard_id, 'Secondary Orchard'); + // --Collection SPZ (Species dependent edits) + validate_spz_spp(g_seed_plan_zone_code,'Collection SPZ'); + // --Area of Use information must be filled + validate_mandatory_APP(TO_CHAR(g_latitude_deg_min),'Area of Use Min Latitude'); + validate_mandatory_APP(TO_CHAR(g_latitude_deg_max),'Area of Use Max Latitude'); + validate_mandatory_APP(TO_CHAR(g_longitude_deg_min),'Area of Use Min Longitude'); + validate_mandatory_APP(TO_CHAR(g_longitude_deg_max),'Area of Use Max Longitude'); + validate_mandatory_APP(TO_CHAR(g_elevation_min),'Area of Use Elevation Min'); + validate_mandatory_APP(TO_CHAR(g_elevation_max),'Area of Use Elevation Max'); + validate_mandatory_APP(g_spz_list,'Area of Use SPZ'); + // --Area of Use SPZ's (Species dependent edit) + v_spz_list = g_spz_list; + while (TRIM(v_spz_list) != null) { + v_pos = INSTR(v_spz_list,','); + if (v_pos > 0) { + v_spz = SUBSTR(v_spz_list,1,v_pos-1); + v_spz_list = SUBSTR(v_spz_list,v_pos+1); + } else { + v_spz = RTRIM(v_spz_list,','); + v_spz_list = null; + } + validate_spz_spp(v_spz,'Area of Use SPZ'); + v_spz_count = v_spz_count+1; + } + // -- if superior provenance is N, only one SPZ is allowed + if (g_superior_prvnc_ind == 'N' && v_spz_count > 1) { + g_error_message = g_error_message || 'spar.web.error.usr.database.provenanceNo_SPZ_Limit;'; + } + validate_tested_untested_ratio(); + } +} + +/* + * Procedure: validate (OVERLOADED) + * Purpose: These are the validations from the original SPR01 Seedlot Maintenance page. + */ +function validate(p_spz1Z: string) { + get_previous_seedlot_values(); + if (g_seedlot_status_code = 'COM') { + validate_genetic_class(); + validate_orchard_id(); + validate_collection_locn(); + validate_coast_geo(p_spz1); + validate_mean_elevation(); + validate_mean_latitude(); + validate_min_latitude(); + validate_max_latitude(); + validate_mean_longitude(); + validate_min_longitude(); + validate_max_longitude(); + validate_bgc_zone(); + validate_collection_start(); + validate_collection_end(); + validate_vol_per_container(); + validate_nmbr_trees(); + validate_cone_collection(); + } +} + +/* + * Procedure: get_seedlot_spz_list + * Purpose: Returns a comma delimited list of Area of Use Seed Planning + * Zones for a Seedlot. + * Written as a function so that it may be used outside of this package. + */ +function get_seedlot_spz_list(p_seedlot_number:string | null): string { + // CURSOR c_spz IS: + let v_spz_list: string; // VARCHAR2(100); + /* + SELECT seed_plan_zone_code + FROM seedlot_plan_zone + WHERE seedlot_number = p_seedlot_number + ORDER BY DECODE(primary_ind,'Y',1,2) --sort primary_ind to top + , seed_plan_zone_code; + */ + + // --Get comma delim list of SPZ's + for (let r_spz in c_spz) { + v_spz_list = v_spz_list || r_spz.seed_plan_zone_code || ','; + } + v_spz_list = RTRIM(v_spz_list, ','); + return v_spz_list; +} + +/* + * Procedure: get_seedlot_spz_id_list + * Purpose: Returns a comma delimited list of Area of Use Seed Planning + * Zone Ids for a Seedlot. + * Written as a function so that it may be used outside of this + * package. + */ +function get_seedlot_spz_id_list(p_seedlot_number :string): string { + // CURSOR c_spz IS: + /* + SELECT spr_get_seed_plan_zone_Id(spz.SEED_PLAN_ZONE_CODE, seedlot.genetic_class_code, seedlot.vegetation_code) AS spz_id + FROM seedlot_plan_zone spz, SEEDLOT seedlot + WHERE spz.seedlot_number = seedlot.seedlot_number and spz.seedlot_number = p_seedlot_number; + */ + let v_spz_list: string; // VARCHAR2(100); + + // --Get comma delim list of SPZ's + for (let r_spz in c_spz) { + v_spz_list = v_spz_list || r_spz.spz_id || ','; + } + v_spz_list = RTRIM(v_spz_list,','); + return v_spz_list; +} + +/* + * Procedure: is_lot_split + * Purpose: Returns true of the lot has been split, otherwise returns false. + * Currently, the only was to tell if a lot has been split is if + * the lot has an ASP request and has heritage. + */ +function is_lot_split (p_seedlot_number :string): boolean { + // CURSOR c_asp IS + /* + SELECT COUNT(1) + FROM request_seedlot rs + , spar_request sr + WHERE rs.seedlot_number = p_seedlot_number + AND sr.request_skey = rs.request_skey + AND sr.request_type_code = 'ASP'; + */ + + // CURSOR c_heritage IS: + /* + SELECT COUNT(1) + FROM seedlot_heritage + WHERE parent_seedlot_no = p_seedlot_number || child_seedlot_no = p_seedlot_number; + */ + let b_is_lot_split: boolean; + let v_count: number; // NUMBER(10); + + b_is_lot_split = false; + // --ASP request exists + // OPEN c_asp; FETCH c_asp INTO v_count; CLOSE c_asp; + if (v_count > 0) { + // OPEN c_heritage; FETCH c_heritage INTO v_count; CLOSE c_heritage; + b_is_lot_split = v_count > 0; + } + return b_is_lot_split; +} + +/* + * Procedure: get + * Purpose: Retrieve ONE Seedlot row and associated Genetic Worth and Seed Plan Zone information. + * Security: HQ users can access all; otherwise: + * if user is a client or BCTS, access is allowed by applicant_client_number = user client number + * if user is an org, access is allowed by applicant_client_number = org unit resolving to a client number + */ +function get(p_user_client_number: string, p_user_org_unit_no: number) { + let v_user_client_number: string; // g_applicant_client_number%TYPE; + let v_tested: number; // NUMBER(5); + let v_total: number; // NUMBER(5); + + // --if user is not a client, resolve org to its client number + v_user_client_number = p_user_client_number == null? sil_get_org_cli_number(p_user_org_unit_no) : p_user_client_number; + /* + SELECT * INTO + g_seedlot_number + ,g_to_be_registrd_ind + ,g_registered_seed_ind + ,g_seedlot_status_code + ,g_vegetation_code + ,g_genetic_class_code + ,g_superior_prvnc_ind + ,g_seedlot_source_code + ,g_bc_source_ind + ,g_applicant_client_number + ,g_applicant_client_locn + ,g_applicant_email_address + ,g_org_unit_no + ,g_collection_locn_desc + ,g_provenance_id + ,g_coll_standard_met_ind + ,g_collection_elevation + ,g_collection_elevation_min + ,g_collection_elevation_max + ,g_collection_area_radius + ,g_collection_lat_deg + ,g_collection_lat_min + ,g_collection_lat_sec + ,g_collection_latitude_code + ,g_collection_long_deg + ,g_collection_long_min + ,g_collection_long_sec + ,g_collection_longitude_code + ,g_seed_plan_zone_code + ,g_seed_plan_zone_id + ,g_collection_spz_ind + ,g_seed_coast_area_code + ,g_bgc_zone_code + ,g_bgc_subzone_code + ,g_variant + ,g_bec_version_id + ,g_collection_bgc_ind + ,g_collection_start_date + ,g_collection_end_date + ,g_cone_collection_method_cd + ,g_cone_collection_method2_cd + ,g_nmbr_trees_from_code + ,g_no_of_containers + ,g_vol_per_container + ,g_clctn_volume + ,g_seedlot_comment + ,g_collection_cli_number + ,g_collection_cli_locn_cd + ,g_orchard_id + ,g_coancestry + ,g_effective_pop_size + ,g_seed_plan_unit_id + ,g_elevation_min + ,g_elevation_max + ,g_latitude_deg_min + ,g_latitude_min_min + ,g_latitude_sec_min + ,g_latitude_deg_max + ,g_latitude_min_max + ,g_latitude_sec_max + ,g_longitude_deg_min + ,g_longitude_min_min + ,g_longitude_sec_min + ,g_longitude_deg_max + ,g_longitude_min_max + ,g_longitude_sec_max + ,g_interm_strg_client_number + ,g_interm_strg_client_locn + ,g_interm_strg_st_date + ,g_interm_strg_end_date + ,g_interm_facility_code + ,g_interm_strg_locn + ,g_extrct_cli_number + ,g_extrct_cli_locn_cd + ,g_extraction_st_date + ,g_extraction_end_date + ,g_original_seed_qty + ,g_temporary_storage_end_date + ,g_temporary_storage_start_dt + ,g_seed_store_client_number + ,g_seed_store_client_locn + ,g_secondary_orchard_id + ,g_orchard_comment + ,g_smp_mean_bv_GVO + ,g_smp_success_pct + ,g_female_gametic_mthd_code + ,g_male_gametic_mthd_code + ,g_pollen_contamination_ind + ,g_pollen_contamination_pct + ,g_controlled_cross_ind + ,g_biotech_processes_ind + ,g_contaminant_pollen_bv + ,g_pollen_contam_mthd_code + ,g_orchard_contamination_pct + ,g_total_parent_trees + ,g_smp_parents_outside + ,g_declared_userid + ,g_declared_timestamp + ,g_update_userid + ,g_update_timestamp + ,g_entry_userid + ,g_entry_timestamp + ,g_approved_userid + ,g_approved_timestamp + ,g_revision_count + ,g_gw_AD + ,g_gw_DFS + ,g_gw_DFU + ,g_gw_DFW + ,g_gw_DSB + ,g_gw_DSC + ,g_gw_DSG + ,g_gw_GVO + ,g_gw_IWS + ,g_gw_WDU + ,g_gw_WVE + ,g_gw_WWD + FROM ( + SELECT DISTINCT + s.seedlot_number + ,s.to_be_registrd_ind + ,s.registered_seed_ind + ,s.seedlot_status_code + ,s.vegetation_code + ,s.genetic_class_code + ,s.superior_prvnc_ind + ,s.seedlot_source_code + ,s.bc_source_ind + ,s.applicant_client_number + ,s.applicant_client_locn + ,s.applicant_email_address + ,s.org_unit_no + ,s.collection_locn_desc + ,s.provenance_id + ,s.collection_standard_met_ind + ,s.collection_elevation + ,s.collection_elevation_min + ,s.collection_elevation_max + ,s.collection_area_radius + ,s.collection_lat_deg + ,s.collection_lat_min + ,s.collection_lat_sec + ,s.collection_latitude_code + ,s.collection_long_deg + ,s.collection_long_min + ,s.collection_long_sec + ,s.collection_longitude_code + ,s.seed_plan_zone_code + ,spr_get_seed_plan_zone_Id(s.seed_plan_zone_code,s.genetic_class_code,s.vegetation_code) seed_plan_zone_id + ,s.collection_seed_plan_zone_ind collection_spz_ind + ,s.seed_coast_area_code + ,s.bgc_zone_code + ,s.bgc_subzone_code + ,s.variant + ,s.bec_version_id + ,s.collection_bgc_ind + ,s.collection_start_date + ,s.collection_end_date + ,s.cone_collection_method_code + ,s.cone_collection_method2_code + ,s.nmbr_trees_from_code + ,s.no_of_containers + ,s.vol_per_container + ,s.clctn_volume + ,s.seedlot_comment + ,s.collection_cli_number + ,s.collection_cli_locn_cd + ,s.orchard_id + ,s.coancestry + ,s.effective_pop_size + ,s.seed_plan_unit_id + ,s.elevation_min + ,s.elevation_max + ,s.latitude_deg_min + ,s.latitude_min_min + ,s.latitude_sec_min + ,s.latitude_deg_max + ,s.latitude_min_max + ,s.latitude_sec_max + ,s.longitude_deg_min + ,s.longitude_min_min + ,s.longitude_sec_min + ,s.longitude_deg_max + ,s.longitude_min_max + ,s.longitude_sec_max + ,s.interm_strg_client_number + ,s.interm_strg_client_locn + ,s.interm_strg_st_date + ,s.interm_strg_end_date + ,s.interm_facility_code + ,s.interm_strg_locn + ,s.extrct_cli_number + ,s.extrct_cli_locn_cd + ,s.extraction_st_date + ,s.extraction_end_date + ,s.original_seed_qty + ,s.temporary_storage_end_date + ,s.temporary_storage_start_date + ,s.seed_store_client_number + ,s.seed_store_client_locn + ,s.secondary_orchard_id + ,s.orchard_comment + ,s.smp_mean_bv_growth + ,s.smp_success_pct + ,s.female_gametic_mthd_code + ,s.male_gametic_mthd_code + ,s.pollen_contamination_ind + ,s.pollen_contamination_pct + ,s.controlled_cross_ind + ,s.biotech_processes_ind + ,s.contaminant_pollen_bv + ,s.pollen_contamination_mthd_code + ,s.orchard_contamination_pct + ,s.total_parent_trees + ,s.smp_parents_outside + ,s.declared_userid + ,s.declared_timestamp + ,s.update_userid + ,s.update_timestamp + ,s.entry_userid + ,s.entry_timestamp + ,s.approved_userid + ,s.approved_timestamp + ,s.revision_count + ,gw.genetic_worth_code + ,gw.genetic_worth_rtng + FROM seedlot s + , seedlot_genetic_worth gw + WHERE s.seedlot_number = g_seedlot_number + AND (s.applicant_client_number = v_user_client_number + || s.applicant_client_number IN (select client_number + from org_client_xref + where client_number = v_user_client_number + or org_unit_no = p_user_org_unit_no) + || s.seedlot_number IN (select seedlot_number + from seedlot_owner_quantity + where seedlot_number = g_seedlot_number + AND client_number = v_user_client_number) + || sil_get_org_level(p_user_org_unit_no) = 'H') + AND gw.seedlot_number(+) = s.seedlot_number + ) + PIVOT + (MAX(genetic_worth_rtng) + FOR (genetic_worth_code) + IN (('AD') as bv_AD, + ('DFS') as bv_DFS, + ('DFU') as bv_DFU, + ('DFW') as bv_DFW, + ('DSB') as bv_DSB, + ('DSC') as bv_DSC, + ('DSG') as bv_DSG, + ('GVO') as bv_GVO, + ('IWS') as bv_IWS, + ('WDU') as bv_WDU, + ('WVE') as bv_WVE, + ('WWD') as bv_WWD)); + */ + set_spz_list(get_seedlot_spz_list(g_seedlot_number)); + set_spz_id_list(get_seedlot_spz_id_list(g_seedlot_number)); + g_is_lot_split = is_lot_split(g_seedlot_number); + + if ('NO_DATA_FOUND') { + throw new Error('NO_DATA_FOUND'); + } +} + +/* + * Procedure: set_status_defaults + * Purpose: Set defaults that depend on Status + */ +function set_status_defaults() { + // --Did we already get previous data? + if (g_revision_count != null && r_previous.seedlot_number == null) { + get_previous_seedlot_values(); + } + // --if approving, set approved userid/timestamp + if (g_seedlot_status_code == 'APP' && nvl(r_previous.seedlot_status_code,'~') != 'APP' && r_previous.approved_userid == null) { + set_approved_userid(g_update_userid); + set_approved_timestamp(new Date()); + // --In case skipped SUB status, set declaration data to approver + if (g_update_userid == null) { + set_declared_userid(g_update_userid); + set_declared_timestamp(new Date()); + } + } + // --if(submitting, set declared userid/timestamp + if (g_seedlot_status_code == 'SUB' && nvl(r_previous.seedlot_status_code,'~') != 'SUB' && r_previous.declared_userid == null) { + set_declared_userid(g_update_userid); + set_declared_timestamp(new Date()); + } +} + +/* + * Procedure: add + * Purpose: INSERT one row into SEEDLOT + */ +function add() { + let v_seedlot_number: number; // NUMBER(6); + let e_error_generating_lot_number: Error; + + // --Generate new number if none provided + if (g_seedlot_number == null) { + if (g_genetic_class_code == 'B') { + /* + SELECT MAX(TO_NUMBER(seedlot_number)) + 1 + INTO v_seedlot_number + FROM seedlot + WHERE seedlot_number BETWEEN CONST_CLASS_B_LOTNUM_MIN AND TO_CHAR(TO_NUMBER(CONST_CLASS_B_LOTNUM_MAX)-1 ); + */ + if (v_seedlot_number >= CONST_CLASS_B_LOTNUM_MAX) { + throw new Error(e_error_generating_lot_number); + } else if (v_seedlot_number == null) { + g_seedlot_number = CONST_CLASS_B_LOTNUM_MIN + 1; + } else { + g_seedlot_number = v_seedlot_number; + } + } else if (g_genetic_class_code == 'A') { + /* + SELECT MAX(TO_NUMBER(seedlot_number)) + 1 + INTO v_seedlot_number + FROM seedlot + WHERE seedlot_number BETWEEN CONST_CLASS_A_LOTNUM_MIN AND TO_CHAR(TO_NUMBER(CONST_CLASS_A_LOTNUM_MAX)-1 ); + */ + if (v_seedlot_number >= CONST_CLASS_A_LOTNUM_MAX) { + throw new Error(e_error_generating_lot_number); + } else if (v_seedlot_number == null) { + g_seedlot_number = CONST_CLASS_A_LOTNUM_MIN + 1; + } else { + g_seedlot_number = v_seedlot_number; + } + } + } + + // --Defaults for a new seedlot, taken from old SPR01 package + set_stored_cli_number('00012797'); + set_stored_cli_locn_cd('00'); + set_original_seed_qty(0); + set_extraction_volume(null); + set_utm_easting(0); + set_utm_northing(0); + set_utm_zone(0); + set_registered_seed_ind('N'); + + // --Set Mean Area of Use geography to Mean Collection geography. + // --Should already have been done in recalc() but IMPORTANT, thus insurance. + set_mean_area_of_use_geography(); + //--Set items that depend on status + set_status_defaults(); + + /* + INSERT INTO seedlot (seedlot_number, seedlot_status_code, vegetation_code, genetic_class_code, + collection_source_code, superior_prvnc_ind, org_unit_no, registered_seed_ind, to_be_registrd_ind, + registered_date, fs721a_signed_ind, nad_datum_code, utm_zone, utm_easting, utm_northing, + longitude_degrees, longitude_minutes, longitude_deg_min, longitude_min_min, longitude_deg_max, + longitude_min_max, latitude_degrees, latitude_minutes, latitude_deg_min, latitude_min_min, + latitude_deg_max ,latitude_min_max, seed_coast_area_code, elevation, elevation_min, elevation_max, + orchard_id, collection_locn_desc, collection_cli_number, collection_cli_locn_cd, collection_start_date, + collection_end_date, cone_collection_method_code, no_of_containers, clctn_volume, vol_per_container, + nmbr_trees_from_code, coancestry, effective_pop_size, original_seed_qty, interm_strg_st_date, + interm_strg_end_date, interm_facility_code, extraction_st_date, extraction_end_date, extraction_volume, + extrct_cli_number, extrct_cli_locn_cd, stored_cli_number, stored_cli_locn_cd, lngterm_strg_st_date, + historical_tsr_date, collection_lat_deg, collection_lat_min, collection_latitude_code, collection_long_deg, + collection_long_min, collection_longitude_code, collection_elevation, collection_elevation_min, + collection_elevation_max, entry_timestamp, entry_userid, update_timestamp, update_userid, + approved_timestamp, approved_userid, revision_count, interm_strg_locn, interm_strg_cmt, ownership_comment, + cone_seed_desc, extraction_comment, seedlot_comment, bgc_zone_code, bgc_subzone_code, variant, + bec_version_id, collection_lat_sec, collection_long_sec, latitude_seconds, longitude_seconds, + seed_plan_zone_code, applicant_client_locn, applicant_client_number, applicant_email_address, + bc_source_ind, biotech_processes_ind, collection_area_radius, collection_bgc_ind, + collection_seed_plan_zone_ind, collection_standard_met_ind, cone_collection_method2_code, + contaminant_pollen_bv, controlled_cross_ind, declared_userid, declared_timestamp, female_gametic_mthd_code, + latitude_sec_max, latitude_sec_min, longitude_sec_max, longitude_sec_min, male_gametic_mthd_code, + orchard_comment, orchard_contamination_pct, pollen_contamination_ind, pollen_contamination_mthd_code, + pollen_contamination_pct, provenance_id, secondary_orchard_id, seed_plan_unit_id, seed_store_client_locn, + seed_store_client_number, seedlot_source_code, smp_mean_bv_growth, smp_parents_outside, smp_success_pct, + temporary_storage_end_date, temporary_storage_start_date, total_parent_trees, interm_strg_client_number, interm_strg_client_locn) + VALUES (g_seedlot_number, g_seedlot_status_code, g_vegetation_code, g_genetic_class_code, g_collection_source_code, + g_superior_prvnc_ind, g_org_unit_no, g_registered_seed_ind, g_to_be_registrd_ind, g_registered_date, + g_fs721a_signed_ind, g_nad_datum_code, g_utm_zone, g_utm_easting, g_utm_northing, g_longitude_degrees, + g_longitude_minutes, g_longitude_deg_min, g_longitude_min_min, g_longitude_deg_max, g_longitude_min_max, + g_latitude_degrees, g_latitude_minutes, g_latitude_deg_min, g_latitude_min_min, g_latitude_deg_max, + g_latitude_min_max, g_seed_coast_area_code, g_elevation, g_elevation_min, g_elevation_max, g_orchard_id, + g_collection_locn_desc, g_collection_cli_number, g_collection_cli_locn_cd, g_collection_start_date, + g_collection_end_date, g_cone_collection_method_cd, g_no_of_containers, g_clctn_volume, g_vol_per_container, + g_nmbr_trees_from_code, g_coancestry, g_effective_pop_size, g_original_seed_qty, g_interm_strg_st_date, + g_interm_strg_end_date, g_interm_facility_code, g_extraction_st_date, g_extraction_end_date, g_extraction_volume, + g_extrct_cli_number, g_extrct_cli_locn_cd, g_stored_cli_number, g_stored_cli_locn_cd, g_lngterm_strg_st_date, + g_historical_tsr_date, g_collection_lat_deg, g_collection_lat_min, g_collection_latitude_code, + g_collection_long_deg, g_collection_long_min, g_collection_longitude_code, g_collection_elevation, + g_collection_elevation_min, g_collection_elevation_max, g_entry_timestamp, g_entry_userid, + g_update_timestamp, g_update_userid, g_approved_timestamp, g_approved_userid, g_revision_count, + g_interm_strg_locn, g_interm_strg_cmt, g_ownership_comment, g_cone_seed_desc, g_extraction_comment, + g_seedlot_comment, g_bgc_zone_code, g_bgc_subzone_code, g_variant, g_bec_version_id, + g_collection_lat_sec, g_collection_long_sec, g_latitude_seconds, g_longitude_seconds, g_seed_plan_zone_code, + g_applicant_client_locn, g_applicant_client_number, g_applicant_email_address, g_bc_source_ind, + g_biotech_processes_ind, g_collection_area_radius, g_collection_bgc_ind, g_collection_spz_ind, + g_coll_standard_met_ind, g_cone_collection_method2_cd, g_contaminant_pollen_bv, g_controlled_cross_ind, + g_declared_userid, g_declared_timestamp, g_female_gametic_mthd_code, g_latitude_sec_max, g_latitude_sec_min, + g_longitude_sec_max, g_longitude_sec_min, g_male_gametic_mthd_code, g_orchard_comment, g_orchard_contamination_pct, + g_pollen_contamination_ind, g_pollen_contam_mthd_code, g_pollen_contamination_pct, g_provenance_id, + g_secondary_orchard_id, g_seed_plan_unit_id, g_seed_store_client_locn, g_seed_store_client_number, + g_seedlot_source_code, g_smp_mean_bv_GVO, g_smp_parents_outside, g_smp_success_pct, + g_temporary_storage_end_date, g_temporary_storage_start_dt, g_total_parent_trees, g_interm_strg_client_number, + g_interm_strg_client_locn); + */ + if (e_error_generating_lot_number) { + throw new Error(g_error_message = g_error_message || 'spar.web.error.usr.gen_lot_number;';); + } +} + +/* + * Procedure: change + * Purpose: UPDATE one SEEDLOT row + */ +function change() { + if (g_seedlot_status_code != 'CAN') { + // --Set Mean Area of Use geography to Mean Collection geography. + // --Should already have been done in recalc() but IMPORTANT, thus insurance. + set_mean_area_of_use_geography(); + // --Set items that depend on status + set_status_defaults(); + } + /* + UPDATE seedlot + SET seedlot_status_code = DECODE(gb_seedlot_status_code,'Y',g_seedlot_status_code,seedlot_status_code) + ,vegetation_code = DECODE(gb_vegetation_code,'Y',g_vegetation_code,vegetation_code) + ,genetic_class_code = DECODE(gb_genetic_class_code,'Y',g_genetic_class_code,genetic_class_code) + ,collection_source_code = DECODE(gb_collection_source_code,'Y',g_collection_source_code,collection_source_code) + ,superior_prvnc_ind = DECODE(gb_superior_prvnc_ind,'Y',g_superior_prvnc_ind,superior_prvnc_ind) + ,org_unit_no = DECODE(gb_org_unit_no,'Y',g_org_unit_no,org_unit_no) + ,registered_seed_ind = DECODE(gb_registered_seed_ind,'Y',g_registered_seed_ind,registered_seed_ind) + ,to_be_registrd_ind = DECODE(gb_to_be_registrd_ind,'Y',g_to_be_registrd_ind,to_be_registrd_ind) + ,registered_date = DECODE(gb_registered_date,'Y',g_registered_date,registered_date) + ,fs721a_signed_ind = DECODE(gb_fs721a_signed_ind,'Y',g_fs721a_signed_ind,fs721a_signed_ind) + ,nad_datum_code = DECODE(gb_nad_datum_code,'Y',g_nad_datum_code,nad_datum_code) + ,utm_zone = DECODE(gb_utm_zone,'Y',g_utm_zone,utm_zone) + ,utm_easting = DECODE(gb_utm_easting,'Y',g_utm_easting,utm_easting) + ,utm_northing = DECODE(gb_utm_northing,'Y',g_utm_northing,utm_northing) + ,longitude_degrees = DECODE(gb_longitude_degrees,'Y',g_longitude_degrees,longitude_degrees) + ,longitude_minutes = DECODE(gb_longitude_minutes,'Y',g_longitude_minutes,longitude_minutes) + ,longitude_deg_min = DECODE(gb_longitude_deg_min,'Y',g_longitude_deg_min,longitude_deg_min) + ,longitude_min_min = DECODE(gb_longitude_min_min,'Y',g_longitude_min_min,longitude_min_min) + ,longitude_deg_max = DECODE(gb_longitude_deg_max,'Y',g_longitude_deg_max,longitude_deg_max) + ,longitude_min_max = DECODE(gb_longitude_min_max,'Y',g_longitude_min_max,longitude_min_max) + ,latitude_degrees = DECODE(gb_latitude_degrees,'Y',g_latitude_degrees,latitude_degrees) + ,latitude_minutes = DECODE(gb_latitude_minutes,'Y',g_latitude_minutes,latitude_minutes) + ,latitude_deg_min = DECODE(gb_latitude_deg_min,'Y',g_latitude_deg_min,latitude_deg_min) + ,latitude_min_min = DECODE(gb_latitude_min_min,'Y',g_latitude_min_min,latitude_min_min) + ,latitude_deg_max = DECODE(gb_latitude_deg_max,'Y',g_latitude_deg_max,latitude_deg_max) + ,latitude_min_max = DECODE(gb_latitude_min_max,'Y',g_latitude_min_max,latitude_min_max) + ,seed_coast_area_code = DECODE(gb_seed_coast_area_code,'Y',g_seed_coast_area_code,seed_coast_area_code) + ,elevation = DECODE(gb_elevation,'Y',g_elevation,elevation) + ,elevation_min = DECODE(gb_elevation_min,'Y',g_elevation_min,elevation_min) + ,elevation_max = DECODE(gb_elevation_max,'Y',g_elevation_max,elevation_max) + ,orchard_id = DECODE(gb_orchard_id,'Y',g_orchard_id,orchard_id) + ,collection_locn_desc = DECODE(gb_collection_locn_desc,'Y',g_collection_locn_desc,collection_locn_desc) + ,collection_cli_number = DECODE(gb_collection_cli_number,'Y',g_collection_cli_number,collection_cli_number) + ,collection_cli_locn_cd = DECODE(gb_collection_cli_locn_cd,'Y',g_collection_cli_locn_cd,collection_cli_locn_cd) + ,collection_start_date = DECODE(gb_collection_start_date,'Y',g_collection_start_date,collection_start_date) + ,collection_end_date = DECODE(gb_collection_end_date,'Y',g_collection_end_date,collection_end_date) + ,cone_collection_method_code = DECODE(gb_cone_collection_method_cd,'Y',g_cone_collection_method_cd,cone_collection_method_code) + ,no_of_containers = DECODE(gb_no_of_containers,'Y',g_no_of_containers,no_of_containers) + ,clctn_volume = DECODE(gb_clctn_volume,'Y',g_clctn_volume,clctn_volume) + ,vol_per_container = DECODE(gb_vol_per_container,'Y',g_vol_per_container,vol_per_container) + ,nmbr_trees_from_code = DECODE(gb_nmbr_trees_from_code,'Y',g_nmbr_trees_from_code,nmbr_trees_from_code) + ,coancestry = DECODE(gb_coancestry,'Y',g_coancestry,coancestry) + ,effective_pop_size = DECODE(gb_effective_pop_size,'Y',g_effective_pop_size,effective_pop_size) + ,original_seed_qty = DECODE(gb_original_seed_qty,'Y',g_original_seed_qty,original_seed_qty) + ,interm_strg_client_number = DECODE(gb_interm_strg_client_number,'Y',g_interm_strg_client_number,interm_strg_client_number) + ,interm_strg_client_locn = DECODE(gb_interm_strg_client_locn,'Y',g_interm_strg_client_locn,interm_strg_client_locn) + ,interm_strg_st_date = DECODE(gb_interm_strg_st_date,'Y',g_interm_strg_st_date,interm_strg_st_date) + ,interm_strg_end_date = DECODE(gb_interm_strg_end_date,'Y',g_interm_strg_end_date,interm_strg_end_date) + ,interm_facility_code = DECODE(gb_interm_facility_code,'Y',g_interm_facility_code,interm_facility_code) + ,extraction_st_date = DECODE(gb_extraction_st_date,'Y',g_extraction_st_date,extraction_st_date) + ,extraction_end_date = DECODE(gb_extraction_end_date,'Y',g_extraction_end_date,extraction_end_date) + ,extraction_volume = DECODE(gb_extraction_volume,'Y',g_extraction_volume,extraction_volume) + ,extrct_cli_number = DECODE(gb_extrct_cli_number,'Y',g_extrct_cli_number,extrct_cli_number) + ,extrct_cli_locn_cd = DECODE(gb_extrct_cli_locn_cd,'Y',g_extrct_cli_locn_cd,extrct_cli_locn_cd) + ,stored_cli_number = DECODE(gb_stored_cli_number,'Y',g_stored_cli_number,stored_cli_number) + ,stored_cli_locn_cd = DECODE(gb_stored_cli_locn_cd,'Y',g_stored_cli_locn_cd,stored_cli_locn_cd) + ,lngterm_strg_st_date = DECODE(gb_lngterm_strg_st_date,'Y',g_lngterm_strg_st_date,lngterm_strg_st_date) + ,historical_tsr_date = DECODE(gb_historical_tsr_date,'Y',g_historical_tsr_date,historical_tsr_date) + ,collection_lat_deg = DECODE(gb_collection_lat_deg,'Y',g_collection_lat_deg,collection_lat_deg) + ,collection_lat_min = DECODE(gb_collection_lat_min,'Y',g_collection_lat_min,collection_lat_min) + ,collection_latitude_code = DECODE(gb_collection_latitude_code,'Y',g_collection_latitude_code,collection_latitude_code) + ,collection_long_deg = DECODE(gb_collection_long_deg,'Y',g_collection_long_deg,collection_long_deg) + ,collection_long_min = DECODE(gb_collection_long_min,'Y',g_collection_long_min,collection_long_min) + ,collection_longitude_code = DECODE(gb_collection_longitude_code,'Y',g_collection_longitude_code,collection_longitude_code) + ,collection_elevation = DECODE(gb_collection_elevation,'Y',g_collection_elevation,collection_elevation) + ,collection_elevation_min = DECODE(gb_collection_elevation_min,'Y',g_collection_elevation_min,collection_elevation_min) + ,collection_elevation_max = DECODE(gb_collection_elevation_max,'Y',g_collection_elevation_max,collection_elevation_max) + ,approved_timestamp = DECODE(gb_approved_timestamp,'Y',g_approved_timestamp,approved_timestamp) + ,approved_userid = DECODE(gb_approved_userid,'Y',g_approved_userid,approved_userid) + ,interm_strg_locn = DECODE(gb_interm_strg_locn,'Y',g_interm_strg_locn,interm_strg_locn) + ,interm_strg_cmt = DECODE(gb_interm_strg_cmt,'Y',g_interm_strg_cmt,interm_strg_cmt) + ,ownership_comment = DECODE(gb_ownership_comment,'Y',g_ownership_comment,ownership_comment) + ,cone_seed_desc = DECODE(gb_cone_seed_desc,'Y',g_cone_seed_desc,cone_seed_desc) + ,extraction_comment = DECODE(gb_extraction_comment,'Y',g_extraction_comment,extraction_comment) + ,seedlot_comment = DECODE(gb_seedlot_comment,'Y',g_seedlot_comment,seedlot_comment) + ,bgc_zone_code = DECODE(gb_bgc_zone_code,'Y',g_bgc_zone_code,bgc_zone_code) + ,bgc_subzone_code = DECODE(gb_bgc_subzone_code,'Y',g_bgc_subzone_code,bgc_subzone_code) + ,variant = DECODE(gb_variant,'Y',g_variant,variant) + ,bec_version_id = DECODE(gb_bec_version_id,'Y',g_bec_version_id,bec_version_id) + ,collection_lat_sec = DECODE(gb_collection_lat_sec,'Y',g_collection_lat_sec,collection_lat_sec) + ,collection_long_sec = DECODE(gb_collection_long_sec,'Y',g_collection_long_sec,collection_long_sec) + ,latitude_seconds = DECODE(gb_latitude_seconds,'Y',g_latitude_seconds,latitude_seconds) + ,longitude_seconds = DECODE(gb_longitude_seconds,'Y',g_longitude_seconds,longitude_seconds) + ,seed_plan_zone_code = DECODE(gb_seed_plan_zone_code,'Y',g_seed_plan_zone_code,seed_plan_zone_code) + ,applicant_client_locn = DECODE(gb_applicant_client_locn,'Y',g_applicant_client_locn,applicant_client_locn) + ,applicant_client_number = DECODE(gb_applicant_client_number,'Y',g_applicant_client_number,applicant_client_number) + ,applicant_email_address = DECODE(gb_applicant_email_address,'Y',g_applicant_email_address,applicant_email_address) + ,bc_source_ind = DECODE(gb_bc_source_ind,'Y',g_bc_source_ind,bc_source_ind) + ,biotech_processes_ind = DECODE(gb_biotech_processes_ind,'Y',g_biotech_processes_ind,biotech_processes_ind) + ,collection_area_radius = DECODE(gb_collection_area_radius,'Y',g_collection_area_radius,collection_area_radius) + ,collection_bgc_ind = DECODE(gb_collection_bgc_ind,'Y',g_collection_bgc_ind,collection_bgc_ind) + ,collection_seed_plan_zone_ind = DECODE(gb_collection_spz_ind,'Y',g_collection_spz_ind,collection_seed_plan_zone_ind) + ,collection_standard_met_ind = DECODE(gb_coll_standard_met_ind,'Y',g_coll_standard_met_ind,collection_standard_met_ind) + ,cone_collection_method2_code = DECODE(gb_cone_collection_method2_cd,'Y',g_cone_collection_method2_cd,cone_collection_method2_code) + ,contaminant_pollen_bv = DECODE(gb_contaminant_pollen_bv,'Y',g_contaminant_pollen_bv,contaminant_pollen_bv) + ,controlled_cross_ind = DECODE(gb_controlled_cross_ind,'Y',g_controlled_cross_ind,controlled_cross_ind) + ,declared_userid = DECODE(gb_declared_userid,'Y',g_declared_userid,declared_userid) + ,declared_timestamp = DECODE(gb_declared_timestamp,'Y',g_declared_timestamp,declared_timestamp) + ,female_gametic_mthd_code = DECODE(gb_female_gametic_mthd_code,'Y',g_female_gametic_mthd_code,female_gametic_mthd_code) + ,latitude_sec_max = DECODE(gb_latitude_sec_max,'Y',g_latitude_sec_max,latitude_sec_max) + ,latitude_sec_min = DECODE(gb_latitude_sec_min,'Y',g_latitude_sec_min,latitude_sec_min) + ,longitude_sec_max = DECODE(gb_longitude_sec_max,'Y',g_longitude_sec_max,longitude_sec_max) + ,longitude_sec_min = DECODE(gb_longitude_sec_min,'Y',g_longitude_sec_min,longitude_sec_min) + ,male_gametic_mthd_code = DECODE(gb_male_gametic_mthd_code,'Y',g_male_gametic_mthd_code,male_gametic_mthd_code) + ,orchard_comment = DECODE(gb_orchard_comment,'Y',g_orchard_comment,orchard_comment) + ,orchard_contamination_pct = DECODE(gb_orchard_contamination_pct,'Y',g_orchard_contamination_pct,orchard_contamination_pct) + ,pollen_contamination_ind = DECODE(gb_pollen_contamination_ind,'Y',g_pollen_contamination_ind,pollen_contamination_ind) + ,pollen_contamination_mthd_code = DECODE(gb_pollen_contam_mthd_code,'Y',g_pollen_contam_mthd_code,pollen_contamination_mthd_code) + ,pollen_contamination_pct = DECODE(gb_pollen_contamination_pct,'Y',g_pollen_contamination_pct,pollen_contamination_pct) + ,provenance_id = DECODE(gb_provenance_id,'Y',g_provenance_id,provenance_id) + ,secondary_orchard_id = DECODE(gb_secondary_orchard_id,'Y',g_secondary_orchard_id,secondary_orchard_id) + ,seed_plan_unit_id = DECODE(gb_seed_plan_unit_id,'Y',g_seed_plan_unit_id,seed_plan_unit_id) + ,seed_store_client_locn = DECODE(gb_seed_store_client_locn,'Y',g_seed_store_client_locn,seed_store_client_locn) + ,seed_store_client_number = DECODE(gb_seed_store_client_number,'Y',g_seed_store_client_number,seed_store_client_number) + ,seedlot_source_code = DECODE(gb_seedlot_source_code,'Y',g_seedlot_source_code,seedlot_source_code) + ,smp_mean_bv_growth = DECODE(gb_smp_mean_bv_GVO,'Y',g_smp_mean_bv_GVO,smp_mean_bv_growth) + ,smp_parents_outside = DECODE(gb_smp_parents_outside,'Y',g_smp_parents_outside,smp_parents_outside) + ,smp_success_pct = DECODE(gb_smp_success_pct,'Y',g_smp_success_pct,smp_success_pct) + ,temporary_storage_end_date = DECODE(gb_temporary_storage_end_date,'Y',g_temporary_storage_end_date,temporary_storage_end_date) + ,temporary_storage_start_date = DECODE(gb_temporary_storage_start_dt,'Y',g_temporary_storage_start_dt,temporary_storage_start_date) + ,total_parent_trees = DECODE(gb_total_parent_trees,'Y',g_total_parent_trees,total_parent_trees) + ,update_userid = g_update_userid + ,update_timestamp = SYSDATE + ,revision_count = revision_count + 1 + WHERE seedlot_number = g_seedlot_number + AND revision_count = g_revision_count + RETURNING revision_count, update_timestamp + INTO g_revision_count, g_update_timestamp; + */ + let rowCount: number; + if (rowCount != 1) { + g_error_message = g_error_message || 'spar.web.error.usr.database.record.modified:Seedlot;'; + throw new Error(g_error_message); + } +} + +/* + * Procedure: remove + * Purpose: DELETE one row from SEEDLOT + */ +function remove() { + /* + DELETE FROM seedlot + WHERE seedlot_number = g_seedlot_number AND revision_count = g_revision_count; + */ +} + +/* + * Procedure: validate_new_copy_lot_number + * Purpose: Validate the entered seedlot number to be used for the copied seedlot, to make sure no errors. + */ +function validate_new_copy_lot_number(p_new_copy_seedlot_number: string) { + let v_count: number = 0; + let b_valid: boolean = true; + + // -- Check if the lot number already exists AND not INC status. + /* + SELECT COUNT('x') + INTO v_count + FROM seedlot + WHERE seedlot_number = p_new_copy_seedlot_number AND seedlot_status_code != 'INC' + || p_new_copy_seedlot_number = g_seedlot_number; + */ + if (v_count > 0) { + g_error_message = g_error_message || 'spar.web.error.spr01.usr.seedlot.copy.overwrite:' || 'INC;'; + } + + // -- Make sure entered lot number is a valid one. + // --Check ranges (for pre-numbered FS721 and FS721A forms already issued) + if (g_genetic_class_code == 'A') { + // WARN: Possible BUG found in the line below. + if (p_new_copy_seedlot_number >= CONST_CLASS_B_LOTNUM_MAX+1 || g_genetic_class_code <= TO_NUMBER(CONST_CLASS_A_LOTNUM_MAX)) { + g_error_message = g_error_message || 'spar.web.error.usr.new_lot_number:' || g_genetic_class_code ||',' + || TO_CHAR(TO_NUMBER(CONST_CLASS_B_LOTNUM_MAX)+1)||',' + || TO_CHAR(TO_NUMBER(CONST_CLASS_A_LOTNUM_MAX))||';'; + } + } else if (g_genetic_class_code == 'B') { + if (p_new_copy_seedlot_number > CONST_CLASS_B_LOTNUM_MAX) { + g_error_message = g_error_message || 'spar.web.error.usr.new_lot_number:' || g_genetic_class_code ||',00001,' + || TO_CHAR(TO_NUMBER(CONST_CLASS_B_LOTNUM_MAX))||';'; + } + } +} + +/* + * Procedure: copy_seedlot + * Purpose: DELETE one row from SEEDLOT + */ +function copy_seedlot(p_new_copy_seedlot_number: string, p_userid: string) { + let v_seedlot_number: number; //NUMBER; + let v_new_seedlot_number: string; // seedlot.seedlot_number%TYPE; + let v_seedlot_comment: string; // seedlot.seedlot_comment%TYPE; + let v_count: number; // NUMBER; + let e_error_generating_lot_number: Error; + + if (p_new_copy_seedlot_number == null) { + // --Generate new number for copied seedlot. 52xxx for B lots, 62xxx for A lots. + if (g_genetic_class_code == 'B') { + /* + SELECT MAX(TO_NUMBER(seedlot_number)) + 1 + INTO v_seedlot_number + FROM seedlot + WHERE seedlot_number BETWEEN CONST_CLASS_B_COPY_MIN AND CONST_CLASS_B_COPY_MAX; + */ + if (v_seedlot_number == null || v_seedlot_number >= CONST_CLASS_B_COPY_MAX) { + throw new Error(e_error_generating_lot_number); + } + } else if (g_genetic_class_code == 'A') { + /* + SELECT MAX(TO_NUMBER(seedlot_number)) + 1 + INTO v_seedlot_number + FROM seedlot + WHERE seedlot_number BETWEEN CONST_CLASS_A_COPY_MIN AND CONST_CLASS_A_COPY_MAX; + */ + if (v_seedlot_number == null || v_seedlot_number >= CONST_CLASS_A_COPY_MAX) { + throw new Error(e_error_generating_lot_number); + } + } + v_new_seedlot_number = v_seedlot_number; + } else { + validate_new_copy_lot_number(p_new_copy_seedlot_number); + v_new_seedlot_number = p_new_copy_seedlot_number; + } + if (g_error_message == null) { + /* + SELECT 'COPIED FROM LOT ' || g_seedlot_number || '. ' || SUBSTR(seedlot_comment, 1, 1950) + INTO v_seedlot_comment + FROM seedlot + WHERE seedlot_number = g_seedlot_number; + */ + + // -- if lot already exists, delete its child records, and overwrite seedlot record. + /* + SELECT COUNT('x') + INTO v_count + FROM seedlot + WHERE seedlot_number = v_new_seedlot_number; + */ + + if (v_count >= 1) { + /* + DELETE FROM smp_mix_gen_qlty + WHERE seedlot_number = v_new_seedlot_number; + DELETE FROM smp_mix + WHERE seedlot_number = v_new_seedlot_number; + DELETE FROM seedlot_characteristic + WHERE seedlot_number = v_new_seedlot_number; + DELETE FROM seedlot_genetic_worth + WHERE seedlot_number = v_new_seedlot_number; + DELETE FROM seedlot_geometry + WHERE seedlot_number = v_new_seedlot_number; + DELETE FROM seedlot_collection_geometry + WHERE seedlot_number = v_new_seedlot_number; + DELETE FROM seedlot_heritage + WHERE parent_seedlot_no = v_new_seedlot_number; + DELETE FROM seedlot_heritage + WHERE child_seedlot_no = v_new_seedlot_number; + DELETE FROM seedlot_override + WHERE seedlot_number = v_new_seedlot_number; + DELETE FROM seedlot_owner_quantity + WHERE seedlot_number = v_new_seedlot_number; + DELETE FROM seedlot_physical_balance + WHERE seedlot_number = v_new_seedlot_number; + DELETE FROM seedlot_plan_zone + WHERE seedlot_number = v_new_seedlot_number; + DELETE FROM seedlot_parent_tree_gen_qlty + WHERE seedlot_number = v_new_seedlot_number; + DELETE FROM seedlot_parent_tree + WHERE seedlot_number = v_new_seedlot_number; + DELETE FROM seedlot_transaction + WHERE seedlot_number = v_new_seedlot_number; + UPDATE SEEDLOT + SET seedlot_status_code = 'INC' + , vegetation_code = g_vegetation_code + , genetic_class_code = g_genetic_class_code + , collection_source_code = g_collection_source_code + , superior_prvnc_ind = g_superior_prvnc_ind + , org_unit_no = g_org_unit_no + , registered_seed_ind = 'N' -- registered_seed_ind + , to_be_registrd_ind = g_to_be_registrd_ind + , registered_date = null -- registered_date + , fs721a_signed_ind = g_fs721a_signed_ind + , bc_source_ind = g_bc_source_ind + , nad_datum_code = g_nad_datum_code + , utm_zone = g_utm_zone + , utm_easting = g_utm_easting + , utm_northing = g_utm_northing + , longitude_degrees = g_longitude_degrees + , longitude_minutes = g_longitude_minutes + , longitude_seconds = g_longitude_seconds + , longitude_deg_min = g_longitude_deg_min + , longitude_min_min = g_longitude_min_min + , longitude_sec_min = g_longitude_sec_min + , longitude_deg_max = g_longitude_deg_max + , longitude_min_max = g_longitude_min_max + , longitude_sec_max = g_longitude_sec_max + , latitude_degrees = g_latitude_degrees + , latitude_minutes = g_latitude_minutes + , latitude_seconds = g_latitude_seconds + , latitude_deg_min = g_latitude_deg_min + , latitude_min_min = g_latitude_min_min + , latitude_sec_min = g_latitude_sec_min + , latitude_deg_max = g_latitude_deg_max + , latitude_min_max = g_latitude_min_max + , latitude_sec_max = g_latitude_sec_max + , seed_coast_area_code = g_seed_coast_area_code + , elevation = g_elevation + , elevation_min = g_elevation_min + , elevation_max = g_elevation_max + , seed_plan_unit_id = g_seed_plan_unit_id + , orchard_id = g_orchard_id + , secondary_orchard_id = g_secondary_orchard_id + , collection_locn_desc = g_collection_locn_desc + , collection_cli_number = g_collection_cli_number + , collection_cli_locn_cd = g_collection_cli_locn_cd + , collection_start_date = g_collection_start_date + , collection_end_date = g_collection_end_date + , cone_collection_method_code = g_cone_collection_method_cd + , cone_collection_method2_code = g_cone_collection_method2_cd + , collection_lat_deg = g_collection_lat_deg + , collection_lat_min = g_collection_lat_min + , collection_lat_sec = g_collection_lat_sec + , collection_latitude_code = g_collection_latitude_code + , collection_long_deg = g_collection_long_deg + , collection_long_min = g_collection_long_min + , collection_long_sec = g_collection_long_sec + , collection_longitude_code = g_collection_longitude_code + , collection_elevation = g_collection_elevation + , collection_elevation_min = g_collection_elevation_min + , collection_elevation_max = g_collection_elevation_max + , collection_area_radius = g_collection_area_radius + , collection_seed_plan_zone_ind = g_collection_spz_ind + , collection_bgc_ind = g_collection_bgc_ind + , no_of_containers = 1 -- no_of_containers + , clctn_volume = 0.01 -- clctn_volume + , vol_per_container = 0.01-- vol_per_container + , nmbr_trees_from_code = g_nmbr_trees_from_code + , effective_pop_size = g_effective_pop_size + , original_seed_qty = 0 -- original_seed_qty + , interm_strg_client_number = g_interm_strg_client_number + , interm_strg_client_locn = g_interm_strg_client_locn + , interm_strg_st_date = g_interm_strg_st_date + , interm_strg_end_date = g_interm_strg_end_date + , interm_facility_code = g_interm_facility_code + , interm_strg_locn = g_interm_strg_locn + , interm_strg_cmt = g_interm_strg_cmt + , extraction_st_date = g_extraction_st_date + , extraction_end_date = g_extraction_end_date + , extraction_volume = 0 -- extraction_volume + , extrct_cli_number = g_extrct_cli_number + , extrct_cli_locn_cd = g_extrct_cli_locn_cd + , extraction_comment = g_extraction_comment + , stored_cli_number = g_stored_cli_number + , stored_cli_locn_cd = g_stored_cli_locn_cd + , lngterm_strg_st_date = g_lngterm_strg_st_date + , historical_tsr_date = g_historical_tsr_date + , ownership_comment = g_ownership_comment + , cone_seed_desc = g_cone_seed_desc + , seedlot_comment = v_seedlot_comment + , temporary_storage_start_date = g_temporary_storage_start_dt + , temporary_storage_end_date = g_temporary_storage_end_date + , collection_sandard_met_ind = g_coll_standard_met_ind + , applicant_email_address = g_applicant_email_address + , biotech_processes_ind = g_biotech_processes_ind + , pollen_contamination_ind = g_pollen_contamination_ind + , pollen_contamination_pct = g_pollen_contamination_pct + , controlled_cross_ind = g_controlled_cross_ind + , orchard_comment = g_orchard_comment + , total_parent_trees = g_total_parent_trees + , smp_parents_outside = g_smp_parents_outside + , smp_mean_bv_growth = g_smp_mean_bv_GVO + , smp_success_pct = g_smp_success_pct + , contaminant_pollen_bv = g_contaminant_pollen_bv + , orchard_contamination_pct = g_orchard_contamination_pct + , coancestry = g_coancestry + , provenance_id = g_provenance_id + , seed_plan_zone_code = g_seed_plan_zone_code + , pollen_contamination_mthd_code = g_pollen_contam_mthd_code + , applicant_client_number = g_applicant_client_number + , applicant_client_locn = g_applicant_client_locn + , seed_store_client_number = g_seed_store_client_number + , seed_store_client_locn = g_seed_store_client_locn + , seedlot_source_code = g_seedlot_source_code + , female_gametic_mthd_code = g_female_gametic_mthd_code + , male_gametic_mthd_code = g_male_gametic_mthd_code + , bgc_zone_code = g_bgc_zone_code + , bgc_subzone_code = g_bgc_subzone_code + , variant = g_variant + , bec_version_id = g_bec_version_id + , entry_userid = p_userid -- entry_userid + , entry_timestamp = SYSDATE -- entry_timestamp + , update_userid = p_userid -- update_userid + , update_timestamp = SYSDATE -- update_timestamp + , approved_userid = 'COPIED_LOT' -- approved_userid + , approved_timestamp = g_approved_timestamp -- retain the approved information, for older seedlots. + , declared_userid = null -- declared_userid + , declared_timestamp = null -- declared_timestamp + , revision_count = 1 -- revision_count + WHERE seedlot_number = v_new_seedlot_number; + */ + } else { + // -- Copy the SEEDLOT record, with status of INC and userid/timestamp to the + // -- current user's and current date. + /* + INSERT INTO seedlot + (seedlot_number + , seedlot_status_code + , vegetation_code + , genetic_class_code + , collection_source_code + , superior_prvnc_ind + , org_unit_no + , registered_seed_ind + , to_be_registrd_ind + , registered_date + , fs721a_signed_ind + , bc_source_ind + , nad_datum_code + , utm_zone + , utm_easting + , utm_northing + , longitude_degrees + , longitude_minutes + , longitude_seconds + , longitude_deg_min + , longitude_min_min + , longitude_sec_min + , longitude_deg_max + , longitude_min_max + , longitude_sec_max + , latitude_degrees + , latitude_minutes + , latitude_seconds + , latitude_deg_min + , latitude_min_min + , latitude_sec_min + , latitude_deg_max + , latitude_min_max + , latitude_sec_max + , seed_coast_area_code + , elevation + , elevation_min + , elevation_max + , seed_plan_unit_id + , orchard_id + , secondary_orchard_id + , collection_locn_desc + , collection_cli_number + , collection_cli_locn_cd + , collection_start_date + , collection_end_date + , cone_collection_method_code + , cone_collection_method2_code + , collection_lat_deg + , collection_lat_min + , collection_lat_sec + , collection_latitude_code + , collection_long_deg + , collection_long_min + , collection_long_sec + , collection_longitude_code + , collection_elevation + , collection_elevation_min + , collection_elevation_max + , collection_area_radius + , collection_seed_plan_zone_ind + , collection_bgc_ind + , no_of_containers + , clctn_volume + , vol_per_container + , nmbr_trees_from_code + , effective_pop_size + , original_seed_qty + , interm_strg_client_number + , interm_strg_client_locn + , interm_strg_st_date + , interm_strg_end_date + , interm_facility_code + , interm_strg_locn + , interm_strg_cmt + , extraction_st_date + , extraction_end_date + , extraction_volume + , extrct_cli_number + , extrct_cli_locn_cd + , extraction_comment + , stored_cli_number + , stored_cli_locn_cd + , lngterm_strg_st_date + , historical_tsr_date + , ownership_comment + , cone_seed_desc + , seedlot_comment + , temporary_storage_start_date + , temporary_storage_end_date + , collection_standard_met_ind + , applicant_email_address + , biotech_processes_ind + , pollen_contamination_ind + , pollen_contamination_pct + , controlled_cross_ind + , orchard_comment + , total_parent_trees + , smp_parents_outside + , smp_mean_bv_growth + , smp_success_pct + , contaminant_pollen_bv + , orchard_contamination_pct + , coancestry + , provenance_id + , seed_plan_zone_code + , pollen_contamination_mthd_code + , applicant_client_number + , applicant_client_locn + , seed_store_client_number + , seed_store_client_locn + , seedlot_source_code + , female_gametic_mthd_code + , male_gametic_mthd_code + , bgc_zone_code + , bgc_subzone_code + , variant + , bec_version_id + , entry_userid + , entry_timestamp + , update_userid + , update_timestamp + , approved_userid + , approved_timestamp + , declared_userid + , declared_timestamp + , revision_count) + SELECT v_new_seedlot_number + , 'INC' + , vegetation_code + , genetic_class_code + , collection_source_code + , superior_prvnc_ind + , org_unit_no + , 'N' -- registered_seed_ind + , to_be_registrd_ind + , null -- registered_date + , fs721a_signed_ind + , bc_source_ind + , nad_datum_code + , utm_zone + , utm_easting + , utm_northing + , longitude_degrees + , longitude_minutes + , longitude_seconds + , longitude_deg_min + , longitude_min_min + , longitude_sec_min + , longitude_deg_max + , longitude_min_max + , longitude_sec_max + , latitude_degrees + , latitude_minutes + , latitude_seconds + , latitude_deg_min + , latitude_min_min + , latitude_sec_min + , latitude_deg_max + , latitude_min_max + , latitude_sec_max + , seed_coast_area_code + , elevation + , elevation_min + , elevation_max + , seed_plan_unit_id + , orchard_id + , secondary_orchard_id + , collection_locn_desc + , collection_cli_number + , collection_cli_locn_cd + , collection_start_date + , collection_end_date + , cone_collection_method_code + , cone_collection_method2_code + , collection_lat_deg + , collection_lat_min + , collection_lat_sec + , collection_latitude_code + , collection_long_deg + , collection_long_min + , collection_long_sec + , collection_longitude_code + , collection_elevation + , collection_elevation_min + , collection_elevation_max + , collection_area_radius + , collection_seed_plan_zone_ind + , collection_bgc_ind + , 1 -- no_of_containers + , 0.01 -- clctn_volume + , 0.01-- vol_per_container + , nmbr_trees_from_code + , effective_pop_size + , 0 -- original_seed_qty + , interm_strg_client_number + , interm_strg_client_locn + , interm_strg_st_date + , interm_strg_end_date + , interm_facility_code + , interm_strg_locn + , interm_strg_cmt + , extraction_st_date + , extraction_end_date + , 0 -- extraction_volume + , extrct_cli_number + , extrct_cli_locn_cd + , extraction_comment + , stored_cli_number + , stored_cli_locn_cd + , lngterm_strg_st_date + , historical_tsr_date + , ownership_comment + , cone_seed_desc + , v_seedlot_comment + , temporary_storage_start_date + , temporary_storage_end_date + , collection_standard_met_ind + , applicant_email_address + , biotech_processes_ind + , pollen_contamination_ind + , pollen_contamination_pct + , controlled_cross_ind + , orchard_comment + , total_parent_trees + , smp_parents_outside + , smp_mean_bv_growth + , smp_success_pct + , contaminant_pollen_bv + , orchard_contamination_pct + , coancestry + , provenance_id + , seed_plan_zone_code + , pollen_contamination_mthd_code + , applicant_client_number + , applicant_client_locn + , seed_store_client_number + , seed_store_client_locn + , seedlot_source_code + , female_gametic_mthd_code + , male_gametic_mthd_code + , bgc_zone_code + , bgc_subzone_code + , variant + , bec_version_id + , p_userid -- entry_userid + , SYSDATE -- entry_timestamp + , p_userid -- update_userid + , SYSDATE -- update_timestamp + , 'COPIED_LOT' -- approved_userid + , approved_timestamp -- retain the approved information, for older seedlots. + , null -- declared_userid + , null -- declared_timestamp + , 1 -- revision_count + FROM seedlot + WHERE seedlot_number = g_seedlot_number; + */ + + // -- Copy the SEEDLOT_GENETIC_WORTH data to the new seedlot. + spr_seedlot_genetic_worth.copy_genetic_worth(g_seedlot_number, v_new_seedlot_number, p_userid); + + // -- Copy the SEEDLOT_PLAN_ZONE data to the new seedlot. + /* + INSERT INTO seedlot_plan_zone + (seedlot_number + ,seed_plan_zone_code + ,revision_count + ,entry_userid + ,entry_timestamp + ,primary_ind) + SELECT v_new_seedlot_number + , seed_plan_zone_code + , 1 + , p_userid + , SYSDATE + , primary_ind + FROM seedlot_plan_zone + WHERE seedlot_number = g_seedlot_number; + */ + + // -- Copy the SEEDLOT_PARENT_TREE data to the new seedlot, with a new revision count. + /* + INSERT INTO seedlot_parent_tree + (seedlot_number + , parent_tree_id + , cone_count + , pollen_count + , smp_success_pct + , smp_mix_latitude_degrees + , smp_mix_latitude_minutes + , smp_mix_longitude_degrees + , smp_mix_longitude_minutes + , smp_mix_elevation + , non_orchard_pollen_contam_pct + , total_genetic_worth_contrib + , revision_count) + SELECT v_new_seedlot_number + , parent_tree_id + , cone_count + , pollen_count + , smp_success_pct + , smp_mix_latitude_degrees + , smp_mix_latitude_minutes + , smp_mix_longitude_degrees + , smp_mix_longitude_minutes + , smp_mix_elevation + , non_orchard_pollen_contam_pct + , total_genetic_worth_contrib + , 1 + FROM seedlot_parent_tree + WHERE seedlot_number = g_seedlot_number; + */ + + // -- Copy the SEEDLOT_PARENT_TREE_SMP_MIX data to the new seedlot. + /* + INSERT INTO seedlot_parent_tree_smp_mix + (seedlot_number + , parent_tree_id + , genetic_type_code + , genetic_worth_code + , smp_mix_value + , revision_count) + SELECT v_new_seedlot_number + , parent_tree_id + , genetic_type_code + , genetic_worth_code + , smp_mix_value + , 1 + FROM seedlot_parent_tree_smp_mix + WHERE seedlot_number = g_seedlot_number; + */ + + //-- Copy the SEEDLOT_PARENT_TREE_GEN_QLTY data to the new seedlot. + /* + INSERT INTO seedlot_parent_tree_gen_qlty + (seedlot_number + , parent_tree_id + , genetic_type_code + , genetic_worth_code + , genetic_quality_value + , revision_count + , seed_plan_unit_id + , estimated_ind + , untested_ind) + SELECT v_new_seedlot_number + , parent_tree_id + , genetic_type_code + , genetic_worth_code + , genetic_quality_value + , 1 + , seed_plan_unit_id + , estimated_ind + , untested_ind + FROM seedlot_parent_tree_gen_qlty + WHERE seedlot_number = g_seedlot_number; + */ + + // -- Copy the SMP_MIX data to the new seedlot. + /* + INSERT INTO smp_mix + (seedlot_number + , parent_tree_id + , amount_of_material + , revision_count) + SELECT v_new_seedlot_number + , parent_tree_id + , amount_of_material + , 1 + FROM smp_mix + WHERE seedlot_number = g_seedlot_number; + */ + + // -- Copy the SMP_MIX_GEN_QLTY data to the new seedlot. + /* + INSERT INTO smp_mix_gen_qlty + (seedlot_number + , parent_tree_id + , genetic_type_code + , genetic_worth_code + , genetic_quality_value + , estimated_ind + , revision_count) + SELECT v_new_seedlot_number + , parent_tree_id + , genetic_type_code + , genetic_worth_code + , genetic_quality_value + , estimated_ind + , 1 + FROM smp_mix_gen_qlty + WHERE seedlot_number = g_seedlot_number; + */ + g_seedlot_number = v_new_seedlot_number; + } + } +} + +/* + * Procedure: get_rank_a_germ_test_type + * Purpose: Static method to get Current Rank A Germ Test Type + */ +function get_rank_a_germ_test_type(p_seedlot_number: string): string { + // CURSOR c_test IS: + /* + SELECT seedlot_test_code + FROM seedlot_test + WHERE seedlot_number = p_seedlot_number AND current_test_ind = 'Y' AND preferred_prep_rnk = 'A'; + */ + let r_test: any; // c_test%ROWTYPE; + + // OPEN c_test; FETCH c_test INTO r_test; CLOSE c_test; + return r_test.seedlot_test_code; +} + +/* + * Procedure: get_original_germ_pct + * Purpose: Static method to get Original Rank A Germ Test Type + */ +function get_original_germ_pct(p_seedlot_number: string): number { + // CURSOR c_test IS + /* + SELECT germination_pct + FROM seedlot_test + WHERE seedlot_number = p_seedlot_number AND preferred_prep_rnk = 'A' + ORDER BY test_date; + */ + let r_test: any; // c_test%ROWTYPE; + + // OPEN c_test; FETCH c_test INTO r_test; CLOSE c_test; + return r_test.germination_pct; +} + +/* + * Procedure: get_original_tpg + * Purpose: Static method to get original trees per gram. + Code based on CONSEP function cns_fn_get_orig_tpg + */ +function get_original_tpg(p_seedlot_number: string): number { + let v_trees_per_gram: number; // NUMBER(11, 4); + + // CURSOR c_lot IS: + /* + SELECT s.vegetation_code , s.genetic_class_code + FROM seedlot s + WHERE s.seedlot_number = p_seedlot_number; + */ + let r_lot: any; // c_lot%ROWTYPE; + + // --First row returned is original germ + // CURSOR c_germ IS: + /* + SELECT s.germination_pct + FROM seedlot_test s + WHERE s.seedlot_number = p_seedlot_number AND s.preferred_prep_rnk = 'A' + ORDER BY s.test_date; + */ + let r_germ: any; // c_germ%ROWTYPE; + + // --First row returned is original spg + // CURSOR c_spg IS + /* + SELECT s.seeds_per_gram + FROM seedlot_test s + WHERE s.seedlot_number = p_seedlot_number AND s.seedlot_test_code = 'SPG' + ORDER BY s.test_date; + */ + let r_spg: any; // c_spg%ROWTYPE; + + // CURSOR c_cpb IS: + /* + SELECT cavities_per_block + FROM cavities_block cb + WHERE cb.seedling_container_code = '415B'; + */ + let r_cpb: any; // c_cpb%ROWTYPE; + + /* + CURSOR c_sow_rule( + p_germ_pct IN NUMBER + , p_genetic_class_code IN VARCHAR2 + , p_vegetation_code IN VARCHAR2) + IS: + SELECT seeds_per_cavity + , swng_crctn_factor + FROM sowing_rule_factor sr + WHERE sr.genetic_class_code = p_genetic_class_code + AND ( sr.vegetation_code = p_vegetation_code || sr.vegetation_code is null) + AND sr.min_grmntn_pct <= p_germ_pct + AND sr.max_grmntn_pct >= p_germ_pct + ORDER BY sr.vegetation_code NULLS LAST; + */ + let r_sow_rule: any; // c_sow_rule%ROWTYPE; + + // CURSOR c_spb(p_seeds_cavity IN NUMBER) IS: + /* + SELECT seeds_per_block + FROM seeds_block sb + WHERE sb.seedling_container_code = '415B' AND sb.seeds_per_cavity = p_seeds_cavity; + */ + let r_spb: string; // c_spb%ROWTYPE; + + // OPEN c_lot; FETCH c_lot INTO r_lot; CLOSE c_lot; + // OPEN c_germ; FETCH c_germ INTO r_germ; CLOSE c_germ; + // OPEN c_spg; FETCH c_spg INTO r_spg; CLOSE c_spg; + if (r_spg.seeds_per_gram == null) { + // --unexpected + v_trees_per_gram = 0; + } else { + // OPEN c_cpb; FETCH c_cpb INTO r_cpb; CLOSE c_cpb; + if (r_germ.germination_pct == null) { + // --unexpected + v_trees_per_gram = 0; + } else { + // OPEN c_sow_rule(r_germ.germination_pct, r_lot.genetic_class_code, r_lot.vegetation_code); FETCH c_sow_rule INTO r_sow_rule; CLOSE c_sow_rule; + // OPEN c_spb (r_sow_rule.seeds_per_cavity); FETCH c_spb INTO r_spb; CLOSE c_spb; + if (r_spb.seeds_per_block == 0 || r_sow_rule.swng_crctn_factor == 0) { + // --unexpected + v_trees_per_gram = 0; + } else { + v_trees_per_gram = (r_spg.seeds_per_gram / r_spb.seeds_per_block) * (r_cpb.cavities_per_block / r_sow_rule.swng_crctn_factor); + } + } + } + return(v_trees_per_gram); +} + +/* + * Procedure: get_original_potential_trees + * Purpose: Static method to get original potential trees + */ +function get_original_potential_trees(p_seedlot_number: string): number { + let v_potential_trees: number; // NUMBER(11, 1); + // CURSOR c_lot IS: + /* + SELECT s.original_seed_qty + FROM seedlot s + WHERE s.seedlot_number = p_seedlot_number; + */ + let r_lot: any; // c_lot%ROWTYPE; + + // OPEN c_lot; FETCH c_lot INTO r_lot; CLOSE c_lot; + v_potential_trees = r_lot.original_seed_qty * get_original_tpg(p_seedlot_number); + return v_potential_trees; +} + +/* + * Procedure: is_seedlot_b_plus + * Purpose: Returns true if the seedlot is B+(i.e. B class with superior provenance) + */ +function is_seedlot_b_plus(p_seedlot_number :string): boolean { + // CURSOR c_lot IS: + /* + SELECT COUNT(1) + FROM seedlot + WHERE seedlot_number = p_seedlot_number + AND genetic_class_code = 'B' + AND superior_prvnc_ind = 'Y'; + */ + let v_count: number; // NUMBER(10); + + // --B+ row exists? + // OPEN c_lot; FETCH c_lot INTO v_count; CLOSE c_lot; + return v_count > 0; +} + +/* + * Procedure: is_seedlot_b_not_supprov + * Purpose: Returns true if the seedlot is B and is not of superior provenance. (i.e. B class with superior provenance of N) + */ +function is_seedlot_b_not_supprov(p_seedlot_number: string): boolean { + // CURSOR c_lot IS: + /* + SELECT COUNT(1) + FROM seedlot + WHERE seedlot_number = p_seedlot_number + AND genetic_class_code = 'B' + AND superior_prvnc_ind = 'N'; + */ + let v_count: number; // NUMBER(10); + + // --B row exists? + // OPEN c_lot; FETCH c_lot INTO v_count; CLOSE c_lot; + return v_count > 0; +} + +/* + * Procedure: get_gw + * Purpose: Static: returns Genetic Worth value identified by the id and code passed in. + */ +function get_gw(p_seedlot_number: string, p_genetic_worth_code: string): number { + // CURSOR c_lot IS: + /* + SELECT genetic_worth_rtng + FROM seedlot_genetic_worth + WHERE seedlot_number = p_seedlot_number + AND genetic_worth_code = p_genetic_worth_code; + */ + let v_gw: any; // seedlot_genetic_worth.genetic_worth_rtng%TYPE; + + // OPEN c_lot; FETCH c_lot INTO v_gw; CLOSE c_lot; + return v_gw; +} diff --git a/legacy_translated/SPR_SEEDLOT_GEOMETRY.ts b/legacy_translated/SPR_SEEDLOT_GEOMETRY.ts new file mode 100644 index 000000000..a6acafd10 --- /dev/null +++ b/legacy_translated/SPR_SEEDLOT_GEOMETRY.ts @@ -0,0 +1,16 @@ +// Not working functions, just for error and reference for now + +export const init = (p_seedlot_number: string): string => { + console.log('p_seedlot_number', p_seedlot_number); + return ''; +} + +export const set_seedlot_number = (p_seedlot_number: string) => { + console.log('p_seedlot_number', p_seedlot_number); +} + +export const get = () => {} + +export const get_geometry = (): object => { + return {} +} diff --git a/legacy_translated/SPR_SPATIAL_UTILS.ts b/legacy_translated/SPR_SPATIAL_UTILS.ts new file mode 100644 index 000000000..c5267ae29 --- /dev/null +++ b/legacy_translated/SPR_SPATIAL_UTILS.ts @@ -0,0 +1,14 @@ +// Not working functions, just for error and reference for now + +export const get_bec = ( + v_planting_site_point: object, + v_bgc_zone_code: string, + v_bgc_subzone_code: string, + v_variant: string) => {} + +export const get_spzb = ( + v_planting_site_point: object, + v_vegetation_code: string +): string => { + return 'hey'; +} diff --git a/oracle-api/.eslintrc.json b/oracle-api/.eslintrc.json deleted file mode 100644 index 7530e96ed..000000000 --- a/oracle-api/.eslintrc.json +++ /dev/null @@ -1,67 +0,0 @@ -{ - "env": { - "browser": true, - "es2021": true - }, - "extends": [ - "plugin:react/recommended", - "airbnb", - "plugin:jsdoc/recommended" - ], - "globals": { - "JSX": true, - "RequestInit": true, - "BodyInit": true - }, - "parser": "@typescript-eslint/parser", - "parserOptions": { - "ecmaFeatures": { - "jsx": true - }, - "ecmaVersion": "latest", - "sourceType": "module" - }, - "settings": { - "import/resolver": { - "node": { - "extensions": [".js", ".jsx", ".ts", ".tsx"] - } - } - }, - "plugins": [ - "react", - "@typescript-eslint", - "jsdoc" - ], - "rules": { - "react/require-default-props": "off", - "linebreak-style": 0, - "react/jsx-filename-extension": [2, { "extensions": [".js", ".jsx", ".ts", ".tsx"] }], - "comma-dangle": ["error", "never"], - "import/no-extraneous-dependencies": ["error", {"devDependencies": true}], - "import/extensions": [ - "error", - "ignorePackages", - { - "js": "never", - "jsx": "never", - "ts": "never", - "tsx": "never" - } - ], - "react/function-component-definition": [ - "error", - { - "namedComponents": ["function-declaration", "arrow-function"], - "unnamedComponents": "arrow-function" - } - ], - "jsx-a11y/label-has-associated-control": [ 2, { - "depth": 3 - }], - "no-shadow": "off", - "no-unused-vars": "off", - "@typescript-eslint/no-shadow": ["error"], - "@typescript-eslint/no-unused-vars": "error" - } -} diff --git a/oracle-api/.mvn/wrapper/maven-wrapper.properties b/oracle-api/.mvn/wrapper/maven-wrapper.properties index ecbbc633c..443d8849e 100644 --- a/oracle-api/.mvn/wrapper/maven-wrapper.properties +++ b/oracle-api/.mvn/wrapper/maven-wrapper.properties @@ -15,4 +15,4 @@ # specific language governing permissions and limitations # under the License. wrapperVersion=3.3.1 -distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.9.8/apache-maven-3.9.8-bin.zip +distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.9.9/apache-maven-3.9.9-bin.zip diff --git a/oracle-api/CHANGELOG.md b/oracle-api/CHANGELOG.md deleted file mode 100644 index 1bb9ba4fc..000000000 --- a/oracle-api/CHANGELOG.md +++ /dev/null @@ -1,25 +0,0 @@ -## [0.12.2](https://github.com/bcgov/nr-spar-oracle-api/compare/v0.12.1...v0.12.2) (2023-05-02) - - - -## [0.12.1](https://github.com/bcgov/nr-spar-oracle-api/compare/v0.12.0...v0.12.1) (2023-05-02) - - - -# [0.12.0](https://github.com/bcgov/nr-spar-oracle-api/compare/v0.11.9...v0.12.0) (2023-04-28) - - -### Features - -* orchard parent tree api ([#120](https://github.com/bcgov/nr-spar-oracle-api/issues/120)) ([d014d2c](https://github.com/bcgov/nr-spar-oracle-api/commit/d014d2c8bf6d6de2de05e87c79be1d7b017b8056)) - - - -## [0.11.9](https://github.com/bcgov/nr-spar-oracle-api/compare/v0.11.8...v0.11.9) (2023-04-24) - - - -## [0.11.8](https://github.com/bcgov/nr-spar-oracle-api/compare/v0.11.7...v0.11.8) (2023-04-10) - - - diff --git a/oracle-api/CODE_OF_CONDUCT.md b/oracle-api/CODE_OF_CONDUCT.md deleted file mode 100644 index 6f5be045d..000000000 --- a/oracle-api/CODE_OF_CONDUCT.md +++ /dev/null @@ -1,132 +0,0 @@ -# Contributor Covenant Code of Conduct - -## Overview - -Act in the best interests of the community, the government of British Columbia and your fellow collaborators. We welcome and appreciate your contributions, in any capacity. - -## Our Pledge - -We as members, contributors, and leaders pledge to make participation in our -community a harassment-free experience for everyone, regardless of age, body -size, visible or invisible disability, ethnicity, sex characteristics, gender -identity and expression, level of experience, education, socio-economic status, -nationality, personal appearance, race, religion, or sexual identity -and orientation. - -We pledge to act and interact in ways that contribute to an open, welcoming, -diverse, inclusive, and healthy community. - -## Our Standards - -Examples of behavior that contributes to a positive environment for our -community include: - -* Demonstrating empathy and kindness toward other people -* Being respectful of differing opinions, viewpoints, and experiences -* Giving and gracefully accepting constructive feedback -* Accepting responsibility and apologizing to those affected by our mistakes, - and learning from the experience -* Focusing on what is best not just for us as individuals, but for the - overall community - -Examples of unacceptable behavior include: - -* The use of sexualized language or imagery, and sexual attention or - advances of any kind -* Trolling, insulting or derogatory comments, and personal or political attacks -* Public or private harassment -* Publishing others' private information, such as a physical or email - address, without their explicit permission -* Other conduct which could reasonably be considered inappropriate in a - professional setting - -## Enforcement Responsibilities - -Community leaders are responsible for clarifying and enforcing our standards of -acceptable behavior and will take appropriate and fair corrective action in -response to any behavior that they deem inappropriate, threatening, offensive, -or harmful. - -Community leaders have the right and responsibility to remove, edit, or reject -comments, commits, code, wiki edits, issues, and other contributions that are -not aligned to this Code of Conduct, and will communicate reasons for moderation -decisions when appropriate. - -## Scope - -This Code of Conduct applies within all community spaces, and also applies when -an individual is officially representing the community in public spaces. -Examples of representing our community include using an official e-mail address, -posting via an official social media account, or acting as an appointed -representative at an online or offline event. - -## Enforcement - -Instances of abusive, harassing, or otherwise unacceptable behavior may be -reported to the community leaders responsible for enforcement at -derek.roberts@gmail.com. -All complaints will be reviewed and investigated promptly and fairly. - -All community leaders are obligated to respect the privacy and security of the -reporter of any incident. - -## Enforcement Guidelines - -Community leaders will follow these Community Impact Guidelines in determining -the consequences for any action they deem in violation of this Code of Conduct: - -### 1. Correction - -**Community Impact**: Use of inappropriate language or other behavior deemed -unprofessional or unwelcome in the community. - -**Consequence**: A private, written warning from community leaders, providing -clarity around the nature of the violation and an explanation of why the -behavior was inappropriate. A public apology may be requested. - -### 2. Warning - -**Community Impact**: A violation through a single incident or series -of actions. - -**Consequence**: A warning with consequences for continued behavior. No -interaction with the people involved, including unsolicited interaction with -those enforcing the Code of Conduct, for a specified period of time. This -includes avoiding interactions in community spaces as well as external channels -like social media. Violating these terms may lead to a temporary or -permanent ban. - -### 3. Temporary Ban - -**Community Impact**: A serious violation of community standards, including -sustained inappropriate behavior. - -**Consequence**: A temporary ban from any sort of interaction or public -communication with the community for a specified period of time. No public or -private interaction with the people involved, including unsolicited interaction -with those enforcing the Code of Conduct, is allowed during this period. -Violating these terms may lead to a permanent ban. - -### 4. Permanent Ban - -**Community Impact**: Demonstrating a pattern of violation of community -standards, including sustained inappropriate behavior, harassment of an -individual, or aggression toward or disparagement of classes of individuals. - -**Consequence**: A permanent ban from any sort of public interaction within -the community. - -## Attribution - -This Code of Conduct is adapted from the [Contributor Covenant][homepage], -version 2.0, available at -https://www.contributor-covenant.org/version/2/0/code_of_conduct.html. - -Community Impact Guidelines were inspired by [Mozilla's code of conduct -enforcement ladder](https://github.com/mozilla/diversity). - -[homepage]: https://www.contributor-covenant.org - -For answers to common questions about this code of conduct, see the FAQ at -https://www.contributor-covenant.org/faq. Translations are available at -https://www.contributor-covenant.org/translations. diff --git a/oracle-api/COMPLIANCE.yaml b/oracle-api/COMPLIANCE.yaml deleted file mode 100644 index f18d0a7ad..000000000 --- a/oracle-api/COMPLIANCE.yaml +++ /dev/null @@ -1,11 +0,0 @@ -name: compliance -description: | - This document is used to track a projects PIA and STRA - compliance. -spec: - - name: PIA - status: not-required - last-updated: '2022-01-26T23:07:19.992Z' - - name: STRA - status: not-required - last-updated: '2022-01-26T23:07:19.992Z' diff --git a/oracle-api/Dockerfile b/oracle-api/Dockerfile index d85e56e39..69dbf16aa 100644 --- a/oracle-api/Dockerfile +++ b/oracle-api/Dockerfile @@ -2,17 +2,19 @@ FROM ghcr.io/graalvm/native-image:22.3.3 AS build # Copy WORKDIR /app -COPY pom.xml mvnw ./ -COPY src ./src -COPY .mvn/ ./.mvn -COPY InstallCert.java . +COPY . ./ # Build RUN ./mvnw package -Pnative -DskipTests -Dskip.unit.tests=true && \ - javac InstallCert.java + javac InstallCert.java - ### Deployer -FROM eclipse-temurin:17.0.11_9-jdk-jammy AS deploy + +### Deployer +FROM eclipse-temurin:17.0.12_7-jdk-jammy AS deploy + +# Receive build number as argument, retain as environment variable +ARG BUILD_NUMBER +ENV BUILD_NUMBER=${BUILD_NUMBER} # Java vars ENV LANG=en_CA.UTF-8 @@ -23,6 +25,7 @@ ENV LC_ALL=en_CA.UTF-8 WORKDIR /app COPY --from=build /app/target/nr-spar-oracle-api ./nr-spar-oracle-api COPY --from=build /app/*.class ./artifacts/ +COPY --from=build /app/install_cert.sh ./ # User, port and healthcheck USER 1001 diff --git a/oracle-api/LICENSE.md b/oracle-api/LICENSE.md deleted file mode 100644 index 8dada3eda..000000000 --- a/oracle-api/LICENSE.md +++ /dev/null @@ -1,201 +0,0 @@ - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - - TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - - 1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - - 2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - - 3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - - 4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - - 5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - - 6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - - 7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - - 8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - - 9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - - END OF TERMS AND CONDITIONS - - APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "{}" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - - Copyright {yyyy} {name of copyright owner} - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. diff --git a/oracle-api/SECURITY.md b/oracle-api/SECURITY.md deleted file mode 100644 index 67ff97678..000000000 --- a/oracle-api/SECURITY.md +++ /dev/null @@ -1,10 +0,0 @@ -# Security Policy - -## Supported Versions - -This product currently has no support and is experimental. That could change in future. - - -## Reporting a Vulnerability - -Please report any issues or vulerabilities with an [issue](https://github.com/bcgov/greenfield-template/issues). diff --git a/common/startup.sh b/oracle-api/install_cert.sh similarity index 100% rename from common/startup.sh rename to oracle-api/install_cert.sh diff --git a/oracle-api/mvnw.cmd b/oracle-api/mvnw.cmd deleted file mode 100644 index 1ff8c9ddc..000000000 --- a/oracle-api/mvnw.cmd +++ /dev/null @@ -1,146 +0,0 @@ -<# : batch portion -@REM ---------------------------------------------------------------------------- -@REM Licensed to the Apache Software Foundation (ASF) under one -@REM or more contributor license agreements. See the NOTICE file -@REM distributed with this work for additional information -@REM regarding copyright ownership. The ASF licenses this file -@REM to you under the Apache License, Version 2.0 (the -@REM "License"); you may not use this file except in compliance -@REM with the License. You may obtain a copy of the License at -@REM -@REM http://www.apache.org/licenses/LICENSE-2.0 -@REM -@REM Unless required by applicable law or agreed to in writing, -@REM software distributed under the License is distributed on an -@REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -@REM KIND, either express or implied. See the License for the -@REM specific language governing permissions and limitations -@REM under the License. -@REM ---------------------------------------------------------------------------- - -@REM ---------------------------------------------------------------------------- -@REM Apache Maven Wrapper startup batch script, version 3.3.1 -@REM -@REM Optional ENV vars -@REM MVNW_REPOURL - repo url base for downloading maven distribution -@REM MVNW_USERNAME/MVNW_PASSWORD - user and password for downloading maven -@REM MVNW_VERBOSE - true: enable verbose log; others: silence the output -@REM ---------------------------------------------------------------------------- - -@IF "%__MVNW_ARG0_NAME__%"=="" (SET __MVNW_ARG0_NAME__=%~nx0) -@SET __MVNW_CMD__= -@SET __MVNW_ERROR__= -@SET __MVNW_PSMODULEP_SAVE=%PSModulePath% -@SET PSModulePath= -@FOR /F "usebackq tokens=1* delims==" %%A IN (`powershell -noprofile "& {$scriptDir='%~dp0'; $script='%__MVNW_ARG0_NAME__%'; icm -ScriptBlock ([Scriptblock]::Create((Get-Content -Raw '%~f0'))) -NoNewScope}"`) DO @( - IF "%%A"=="MVN_CMD" (set __MVNW_CMD__=%%B) ELSE IF "%%B"=="" (echo %%A) ELSE (echo %%A=%%B) -) -@SET PSModulePath=%__MVNW_PSMODULEP_SAVE% -@SET __MVNW_PSMODULEP_SAVE= -@SET __MVNW_ARG0_NAME__= -@SET MVNW_USERNAME= -@SET MVNW_PASSWORD= -@IF NOT "%__MVNW_CMD__%"=="" (%__MVNW_CMD__% %*) -@echo Cannot start maven from wrapper >&2 && exit /b 1 -@GOTO :EOF -: end batch / begin powershell #> - -$ErrorActionPreference = "Stop" -if ($env:MVNW_VERBOSE -eq "true") { - $VerbosePreference = "Continue" -} - -# calculate distributionUrl, requires .mvn/wrapper/maven-wrapper.properties -$distributionUrl = (Get-Content -Raw "$scriptDir/.mvn/wrapper/maven-wrapper.properties" | ConvertFrom-StringData).distributionUrl -if (!$distributionUrl) { - Write-Error "cannot read distributionUrl property in $scriptDir/.mvn/wrapper/maven-wrapper.properties" -} - -switch -wildcard -casesensitive ( $($distributionUrl -replace '^.*/','') ) { - "maven-mvnd-*" { - $USE_MVND = $true - $distributionUrl = $distributionUrl -replace '-bin\.[^.]*$',"-windows-amd64.zip" - $MVN_CMD = "mvnd.cmd" - break - } - default { - $USE_MVND = $false - $MVN_CMD = $script -replace '^mvnw','mvn' - break - } -} - -# apply MVNW_REPOURL and calculate MAVEN_HOME -# maven home pattern: ~/.m2/wrapper/dists/{apache-maven-,maven-mvnd--}/ -if ($env:MVNW_REPOURL) { - $MVNW_REPO_PATTERN = if ($USE_MVND) { "/org/apache/maven/" } else { "/maven/mvnd/" } - $distributionUrl = "$env:MVNW_REPOURL$MVNW_REPO_PATTERN$($distributionUrl -replace '^.*'+$MVNW_REPO_PATTERN,'')" -} -$distributionUrlName = $distributionUrl -replace '^.*/','' -$distributionUrlNameMain = $distributionUrlName -replace '\.[^.]*$','' -replace '-bin$','' -$MAVEN_HOME_PARENT = "$HOME/.m2/wrapper/dists/$distributionUrlNameMain" -$MAVEN_HOME_NAME = ([System.Security.Cryptography.MD5]::Create().ComputeHash([byte[]][char[]]$distributionUrl) | ForEach-Object {$_.ToString("x2")}) -join '' -$MAVEN_HOME = "$MAVEN_HOME_PARENT/$MAVEN_HOME_NAME" - -if (Test-Path -Path "$MAVEN_HOME" -PathType Container) { - Write-Verbose "found existing MAVEN_HOME at $MAVEN_HOME" - Write-Output "MVN_CMD=$MAVEN_HOME/bin/$MVN_CMD" - exit $? -} - -if (! $distributionUrlNameMain -or ($distributionUrlName -eq $distributionUrlNameMain)) { - Write-Error "distributionUrl is not valid, must end with *-bin.zip, but found $distributionUrl" -} - -# prepare tmp dir -$TMP_DOWNLOAD_DIR_HOLDER = New-TemporaryFile -$TMP_DOWNLOAD_DIR = New-Item -Itemtype Directory -Path "$TMP_DOWNLOAD_DIR_HOLDER.dir" -$TMP_DOWNLOAD_DIR_HOLDER.Delete() | Out-Null -trap { - if ($TMP_DOWNLOAD_DIR.Exists) { - try { Remove-Item $TMP_DOWNLOAD_DIR -Recurse -Force | Out-Null } - catch { Write-Warning "Cannot remove $TMP_DOWNLOAD_DIR" } - } -} - -New-Item -Itemtype Directory -Path "$MAVEN_HOME_PARENT" -Force | Out-Null - -# Download and Install Apache Maven -Write-Verbose "Couldn't find MAVEN_HOME, downloading and installing it ..." -Write-Verbose "Downloading from: $distributionUrl" -Write-Verbose "Downloading to: $TMP_DOWNLOAD_DIR/$distributionUrlName" - -$webclient = New-Object System.Net.WebClient -if ($env:MVNW_USERNAME -and $env:MVNW_PASSWORD) { - $webclient.Credentials = New-Object System.Net.NetworkCredential($env:MVNW_USERNAME, $env:MVNW_PASSWORD) -} -[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 -$webclient.DownloadFile($distributionUrl, "$TMP_DOWNLOAD_DIR/$distributionUrlName") | Out-Null - -# If specified, validate the SHA-256 sum of the Maven distribution zip file -$distributionSha256Sum = (Get-Content -Raw "$scriptDir/.mvn/wrapper/maven-wrapper.properties" | ConvertFrom-StringData).distributionSha256Sum -if ($distributionSha256Sum) { - if ($USE_MVND) { - Write-Error "Checksum validation is not supported for maven-mvnd. `nPlease disable validation by removing 'distributionSha256Sum' from your maven-wrapper.properties." - } - Import-Module $PSHOME\Modules\Microsoft.PowerShell.Utility -Function Get-FileHash - if ((Get-FileHash "$TMP_DOWNLOAD_DIR/$distributionUrlName" -Algorithm SHA256).Hash.ToLower() -ne $distributionSha256Sum) { - Write-Error "Error: Failed to validate Maven distribution SHA-256, your Maven distribution might be compromised. If you updated your Maven version, you need to update the specified distributionSha256Sum property." - } -} - -# unzip and move -Expand-Archive "$TMP_DOWNLOAD_DIR/$distributionUrlName" -DestinationPath "$TMP_DOWNLOAD_DIR" | Out-Null -Rename-Item -Path "$TMP_DOWNLOAD_DIR/$distributionUrlNameMain" -NewName $MAVEN_HOME_NAME | Out-Null -try { - Move-Item -Path "$TMP_DOWNLOAD_DIR/$MAVEN_HOME_NAME" -Destination $MAVEN_HOME_PARENT | Out-Null -} catch { - if (! (Test-Path -Path "$MAVEN_HOME" -PathType Container)) { - Write-Error "fail to move MAVEN_HOME" - } -} finally { - try { Remove-Item $TMP_DOWNLOAD_DIR -Recurse -Force | Out-Null } - catch { Write-Warning "Cannot remove $TMP_DOWNLOAD_DIR" } -} - -Write-Output "MVN_CMD=$MAVEN_HOME/bin/$MVN_CMD" diff --git a/oracle-api/openshift.deploy.yml b/oracle-api/openshift.deploy.yml index 29be71b47..99af8b58c 100644 --- a/oracle-api/openshift.deploy.yml +++ b/oracle-api/openshift.deploy.yml @@ -24,13 +24,13 @@ parameters: - name: DOMAIN value: apps.silver.devops.gov.bc.ca - name: CPU_REQUEST - value: 100m + value: 25m - name: CPU_LIMIT - value: 500m + value: 100m - name: MEMORY_REQUEST - value: 300Mi + value: 150Mi - name: MEMORY_LIMIT - value: 500Mi + value: 225Mi - name: ALLOWED_ORIGINS description: Sets all the allowed request origins value: "http://localhost:300*,https://*.apps.silver.devops.gov.bc.ca,https://*.nrs.gov.bc.ca" @@ -44,6 +44,9 @@ parameters: description: Oracle API environment for OpenSearch. # One of: development, test, production required: false value: development + - name: DATABASE_PORT + description: Oracle database port + value: "1543" - name: ORACLEDB_KEYSTORE description: Keystore location path - name: AWS_COGNITO_ISSUER_URI @@ -52,6 +55,10 @@ parameters: - name: CERT_PVC_SIZE description: The amount of storage the cert PVC should have value: 25Mi + - name: RANDOM_EXPRESSION + description: Random expression to make sure deployments update + from: "[a-zA-Z0-9]{32}" + generate: expression objects: - apiVersion: v1 kind: PersistentVolumeClaim @@ -73,7 +80,7 @@ objects: app: ${NAME}-${ZONE} name: ${NAME}-${ZONE}-${COMPONENT} spec: - replicas: 1 + replicas: ${{MIN_REPLICAS}} selector: matchLabels: deployment: ${NAME}-${ZONE}-${COMPONENT} @@ -91,7 +98,13 @@ objects: claimName: ${NAME}-${ZONE}-${COMPONENT} initContainers: - name: ${NAME}-${ZONE}-${COMPONENT}-init - image: ${REGISTRY}/${ORG}/${NAME}/common:${TAG} + command: + - /bin/sh + - -c + - | + cd /app + ./install_cert.sh + image: ${REGISTRY}/${ORG}/${NAME}/${COMPONENT}:${TAG} imagePullPolicy: Always env: - name: DATABASE_HOST @@ -105,10 +118,7 @@ objects: name: ${NAME}-${ZONE}-${COMPONENT} key: oracle-secret - name: DATABASE_PORT - valueFrom: - secretKeyRef: - name: ${NAME}-${ZONE}-${COMPONENT} - key: oracle-port + value: ${DATABASE_PORT} volumeMounts: - name: ${NAME}-${ZONE}-${COMPONENT}-certs mountPath: /cert @@ -136,10 +146,7 @@ objects: name: ${NAME}-${ZONE}-${COMPONENT} key: oracle-host - name: DATABASE_PORT - valueFrom: - secretKeyRef: - name: ${NAME}-${ZONE}-${COMPONENT} - key: oracle-port + value: ${DATABASE_PORT} - name: SERVICE_NAME valueFrom: secretKeyRef: @@ -164,6 +171,8 @@ objects: value: /cert/jssecacerts - name: AWS_COGNITO_ISSUER_URI value: ${AWS_COGNITO_ISSUER_URI} + - name: RANDOM_EXPRESSION + value: ${RANDOM_EXPRESSION} volumeMounts: - name: ${NAME}-${ZONE}-${COMPONENT}-certs mountPath: /cert @@ -239,3 +248,9 @@ objects: target: type: Utilization averageUtilization: 80 + - type: Resource + resource: + name: memory + target: + type: Utilization + averageUtilization: 80 diff --git a/oracle-api/pom.xml b/oracle-api/pom.xml index b8de57d2d..67abaad5e 100644 --- a/oracle-api/pom.xml +++ b/oracle-api/pom.xml @@ -6,7 +6,7 @@ org.springframework.boot spring-boot-starter-parent - 3.3.1 + 3.3.2 ca.bc.gov @@ -129,7 +129,7 @@ org.springdoc springdoc-openapi-starter-webmvc-ui - 2.5.0 + 2.6.0 @@ -228,7 +228,7 @@ org.apache.maven.plugins maven-failsafe-plugin - 3.3.0 + 3.3.1 integration-tests @@ -251,7 +251,7 @@ org.apache.maven.plugins maven-surefire-plugin - 3.3.0 + 3.3.1 @{argLine} -Xmx1024m ${skip.unit.tests} @@ -372,7 +372,7 @@ org.apache.maven.plugins maven-javadoc-plugin - 3.7.0 + 3.8.0 17 Javadoc Documentation for ${project.name} ${project.version} diff --git a/oracle-api/src/main/java/ca/bc/gov/oracleapi/config/NativeImageConfig.java b/oracle-api/src/main/java/ca/bc/gov/oracleapi/config/NativeImageConfig.java index 498975b7c..e178282ab 100644 --- a/oracle-api/src/main/java/ca/bc/gov/oracleapi/config/NativeImageConfig.java +++ b/oracle-api/src/main/java/ca/bc/gov/oracleapi/config/NativeImageConfig.java @@ -7,6 +7,7 @@ import ca.bc.gov.oracleapi.dto.ListItemDto; import ca.bc.gov.oracleapi.dto.OrchardDto; import ca.bc.gov.oracleapi.dto.OrchardParentTreeDto; +import ca.bc.gov.oracleapi.dto.ParentTreeByVegCodeDto; import ca.bc.gov.oracleapi.dto.ParentTreeDto; import ca.bc.gov.oracleapi.dto.ParentTreeGeneticInfoDto; import ca.bc.gov.oracleapi.dto.ParentTreeGeneticQualityDto; @@ -41,6 +42,7 @@ PaginatedViaQuery.class, PaginationParameters.class, ModelMapper.class, + ParentTreeByVegCodeDto.class }) @ImportRuntimeHints(value = {HttpServletRequestRuntimeHint.class}) public class NativeImageConfig {} diff --git a/oracle-api/src/main/java/ca/bc/gov/oracleapi/dto/ParentTreeByVegCodeDto.java b/oracle-api/src/main/java/ca/bc/gov/oracleapi/dto/ParentTreeByVegCodeDto.java new file mode 100644 index 000000000..c1522f67a --- /dev/null +++ b/oracle-api/src/main/java/ca/bc/gov/oracleapi/dto/ParentTreeByVegCodeDto.java @@ -0,0 +1,28 @@ +package ca.bc.gov.oracleapi.dto; + +import io.swagger.v3.oas.annotations.media.Schema; +import java.util.List; +import java.util.Map; +import lombok.Getter; +import lombok.Setter; +import lombok.ToString; + +/** An object that contains a parent tree data and its genetic worth values by SPU ID. */ +@Getter +@Setter +@ToString +public class ParentTreeByVegCodeDto { + @Schema(description = "A unique identifier for each Parent Tree.", example = "4032") + private Long parentTreeId; + + @Schema(description = "Indicates whether the tree is tested.", example = "True") + private Boolean testedInd; + + @Schema(description = "A list of orchard that this tree belongs to.", example = "[112, 222]") + private List orchardIds; + + @Schema( + description = + "A map with the Spu as the key and a list of ParentTreeGeneticQualityDto as value") + private Map> geneticQualitiesBySpu; +} diff --git a/oracle-api/src/main/java/ca/bc/gov/oracleapi/dto/ParentTreeGeoNodeDto.java b/oracle-api/src/main/java/ca/bc/gov/oracleapi/dto/ParentTreeGeoNodeDto.java new file mode 100644 index 000000000..e2e18827d --- /dev/null +++ b/oracle-api/src/main/java/ca/bc/gov/oracleapi/dto/ParentTreeGeoNodeDto.java @@ -0,0 +1,62 @@ +package ca.bc.gov.oracleapi.dto; + +import ca.bc.gov.oracleapi.entity.ParentTreeEntity; +import java.util.Optional; +import lombok.NoArgsConstructor; +import lombok.Setter; + +/** This class represents a GeoNode with all elevation, lat and long mean values. */ +@Setter +@NoArgsConstructor +public class ParentTreeGeoNodeDto { + + private Integer elevation; + private Integer latitudeDegrees; + private Integer latitudeMinutes; + private Integer latitudeSeconds; + private Integer longitudeDegrees; + private Integer longitudeMinutes; + private Integer longitudeSeconds; + + ParentTreeGeoNodeDto(ParentTreeEntity entity) { + this.elevation = entity.getElevation(); + this.latitudeDegrees = entity.getLatitudeDegrees(); + this.latitudeMinutes = entity.getLatitudeMinutes(); + this.latitudeSeconds = entity.getLatitudeSeconds(); + this.longitudeDegrees = entity.getLongitudeDegrees(); + this.longitudeMinutes = entity.getLongitudeMinutes(); + this.longitudeSeconds = entity.getLongitudeSeconds(); + } + + public Integer getElevation() { + return this.elevation; + } + + public int getElevationIntVal() { + return Optional.ofNullable(elevation).orElse(0); + } + + public int getLatitudeDegreesIntVal() { + return Optional.ofNullable(latitudeDegrees).orElse(0); + } + + public int getLatitudeMinutesIntVal() { + return Optional.ofNullable(latitudeMinutes).orElse(0); + } + + public int getLatitudeSecondsIntVal() { + return Optional.ofNullable(latitudeSeconds).orElse(0); + } + + public int getLongitudeDegreesIntVal() { + return Optional.ofNullable(longitudeDegrees).orElse(0); + } + + public int getLongitudeMinutesIntVal() { + return Optional.ofNullable(longitudeMinutes).orElse(0); + } + + public int getLongitudeSecondsIntVal() { + return Optional.ofNullable(longitudeSeconds).orElse(0); + } +} diff --git a/oracle-api/src/main/java/ca/bc/gov/oracleapi/dto/ParentTreeNodeDto.java b/oracle-api/src/main/java/ca/bc/gov/oracleapi/dto/ParentTreeNodeDto.java new file mode 100644 index 000000000..d912ac19f --- /dev/null +++ b/oracle-api/src/main/java/ca/bc/gov/oracleapi/dto/ParentTreeNodeDto.java @@ -0,0 +1,195 @@ +package ca.bc.gov.oracleapi.dto; + +import ca.bc.gov.oracleapi.config.SparLog; +import ca.bc.gov.oracleapi.entity.ParentTreeEntity; +import java.util.Optional; +import lombok.Getter; +import lombok.Setter; + +/** This class represents a parent tree node. */ +@Getter +@Setter +public class ParentTreeNodeDto { + + private final ParentTreeEntity value; + private ParentTreeNodeDto femaleParent; + private ParentTreeNodeDto maleParent; + private ParentTreeGeoNodeDto geoNode; + + /** + * Creates a Parent Tree Node. + * + * @param value The parent tree entity instance. + */ + public ParentTreeNodeDto(ParentTreeEntity value) { + this.value = value; + this.geoNode = new ParentTreeGeoNodeDto(value); + } + + /** + * Add a parent tree node female or male. + * + * @param parentTreeId the reference parent id tree to look for. + * @param entity the parent tree entity instance. + */ + public void add(Long parentTreeId, ParentTreeEntity entity) { + if (parentTreeId.equals(value.getFemaleParentTreeId())) { + femaleParent = new ParentTreeNodeDto(entity); + } else if (parentTreeId.equals(value.getMaleParentTreeId())) { + maleParent = new ParentTreeNodeDto(entity); + } else { + if (femaleParent != null) { + femaleParent.add(parentTreeId, entity); + } + if (maleParent != null) { + maleParent.add(parentTreeId, entity); + } + } + } + + /** + * Get a Parent tree's parent elevation (and all lat and long mean values), recursively, looking + * at parent's parent and finding the 'mean' value when required. The calculation piece of code + * was brought exactly as is in SPR_GET_PT_GEOG.prc file on SPAR Legacy. + * + * @param current Current node in the tree + * @param femaleNode Female parent node in the tree + * @param maleNode Male parent node in the tree + * @return A female node if female has data, or a new node with mean values from female and male. + */ + private ParentTreeGeoNodeDto getParentsMeanElevation( + ParentTreeNodeDto current, ParentTreeNodeDto femaleNode, ParentTreeNodeDto maleNode) { + if (current.geoNode.getElevation() != null) { + return current.geoNode; + } + ParentTreeGeoNodeDto femaleElevation = new ParentTreeGeoNodeDto(); + if (current.femaleParent != null) { + femaleElevation = + Optional.ofNullable( + getParentsMeanElevation( + current.femaleParent, + current.femaleParent.femaleParent, + current.femaleParent.maleParent)) + .orElse(new ParentTreeGeoNodeDto()); + } + ParentTreeGeoNodeDto maleElevation = new ParentTreeGeoNodeDto(); + if (current.maleParent != null) { + maleElevation = + Optional.ofNullable( + getParentsMeanElevation( + current.maleParent, + current.maleParent.femaleParent, + current.maleParent.maleParent)) + .orElse(new ParentTreeGeoNodeDto()); + } + if (maleElevation.getElevationIntVal() == 0 && femaleElevation.getElevationIntVal() > 0) { + return femaleElevation; + } else if (maleElevation.getElevationIntVal() > 0 && femaleElevation.getElevationIntVal() > 0) { + int noOfParents = 2; + ParentTreeGeoNodeDto meanNode = new ParentTreeGeoNodeDto(); + + // Elevation + int meanElevation = + (maleElevation.getElevationIntVal() + femaleElevation.getElevationIntVal()) / noOfParents; + meanNode.setElevation(meanElevation); + + // All other calculations (Lat and Long) + int calc = + (femaleElevation.getLatitudeDegreesIntVal() * 3600) + + (femaleElevation.getLatitudeMinutesIntVal() * 60) + + femaleElevation.getLatitudeSecondsIntVal(); + calc = + calc + + (maleElevation.getLatitudeDegreesIntVal() * 3600) + + (maleElevation.getLatitudeMinutesIntVal() * 60) + + maleElevation.getLatitudeSecondsIntVal(); + // --derive mean + calc = calc / noOfParents; + int latitudeDegrees = calc / 3600; + meanNode.setLatitudeDegrees(latitudeDegrees); + + int buff = calc % 3600; + int latitudeMinutes = buff / 60; + meanNode.setLatitudeMinutes(latitudeMinutes); + + buff = calc % 60; + int latitudeSeconds = buff; + meanNode.setLatitudeSeconds(latitudeSeconds); + + calc = + (femaleElevation.getLongitudeDegreesIntVal() * 3600) + + (femaleElevation.getLongitudeMinutesIntVal() * 60) + + femaleElevation.getLongitudeSecondsIntVal(); + calc = + calc + + (maleElevation.getLongitudeDegreesIntVal() * 3600) + + (maleElevation.getLongitudeMinutesIntVal() * 60) + + maleElevation.getLongitudeSecondsIntVal(); + // --derive mean + calc = calc / noOfParents; + int longitudeDegrees = calc / 3600; + meanNode.setLongitudeDegrees(longitudeDegrees); + + buff = calc % 3600; + int longitudeMinutes = buff / 60; + meanNode.setLongitudeMinutes(longitudeMinutes); + + buff = calc % 60; + int longitudeSeconds = buff; + meanNode.setLongitudeSeconds(longitudeSeconds); + + return meanNode; + } + return null; + } + + /** + * Get the parent tree mean elevation looking into parent trees family. + * + * @return an integer representing the mean elevation. + */ + public ParentTreeGeoNodeDto getParentTreeElevation() { + ParentTreeGeoNodeDto elevation = + getParentsMeanElevation(this, this.femaleParent, this.maleParent); + return elevation; + } + + /** + * Prints the current node. + * + * @param level Current level in the tree + */ + public String printLevel(int level) { + String message = String.format("Level %d - %s", level, toString()); + SparLog.info(message); + if (femaleParent != null) { + return femaleParent.printLevel(level + 1); + } + if (maleParent != null) { + return maleParent.printLevel(level + 1); + } + return message; + } + + /** Gets the string version of the node. */ + @Override + public String toString() { + StringBuilder sb = new StringBuilder("ParentTreeId="); + sb.append(value.getId()); + sb.append(" (elev: ").append(value.getElevation()).append(") "); + sb.append("["); + boolean added = false; + if (value.getFemaleParentTreeId() != null) { + sb.append("femaleParentId=").append(value.getFemaleParentTreeId()); + added = true; + } + if (value.getMaleParentTreeId() != null) { + if (added) { + sb.append(", "); + } + sb.append("male=").append(value.getMaleParentTreeId()); + } + sb.append("]"); + return sb.toString(); + } +} diff --git a/oracle-api/src/main/java/ca/bc/gov/oracleapi/endpoint/OrchardEndpoint.java b/oracle-api/src/main/java/ca/bc/gov/oracleapi/endpoint/OrchardEndpoint.java index 8dfce058e..e92fe7577 100644 --- a/oracle-api/src/main/java/ca/bc/gov/oracleapi/endpoint/OrchardEndpoint.java +++ b/oracle-api/src/main/java/ca/bc/gov/oracleapi/endpoint/OrchardEndpoint.java @@ -3,8 +3,6 @@ import ca.bc.gov.oracleapi.config.SparLog; import ca.bc.gov.oracleapi.dto.OrchardDto; import ca.bc.gov.oracleapi.dto.OrchardParentTreeDto; -import ca.bc.gov.oracleapi.dto.ParentTreeDto; -import ca.bc.gov.oracleapi.dto.SameSpeciesTreeDto; import ca.bc.gov.oracleapi.security.RoleAccessConfig; import ca.bc.gov.oracleapi.service.OrchardService; import io.swagger.v3.oas.annotations.Operation; @@ -14,17 +12,12 @@ import io.swagger.v3.oas.annotations.media.Schema; import io.swagger.v3.oas.annotations.responses.ApiResponse; import io.swagger.v3.oas.annotations.tags.Tag; -import jakarta.validation.constraints.Pattern; import java.util.List; -import java.util.Map; import java.util.Optional; import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; -import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; -import org.springframework.web.bind.annotation.PostMapping; -import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.server.ResponseStatusException; @@ -130,7 +123,7 @@ public OrchardParentTreeDto getParentTreeGeneticQualityData( * @return an {@link List} of {@link OrchardDto} * @throws ResponseStatusException if error occurs */ - @GetMapping(path = "/vegetation-code/{vegCode}", produces = MediaType.APPLICATION_JSON_VALUE) + @GetMapping(path = "/vegetation-codes/{vegCode}", produces = MediaType.APPLICATION_JSON_VALUE) @Operation( summary = "Fetch the orchards with the provided vegCode.", description = @@ -157,34 +150,4 @@ public List getOrchardsByVegCode( HttpStatus.NOT_FOUND, String.format("Orchards not found with vegCode: %s.", vegCode))); } - - /** - * Consumed by backend (postgres) service to retrieve a list of parent trees with a vegCode. - * - * @param vegCode an {@link Orchard}'s vegCode - * @return an {@link List} of {@link ParentTreeDto} - * @throws ResponseStatusException if error occurs - */ - @PostMapping( - path = "/parent-trees/vegetation-codes/{vegCode}", - consumes = "application/json", - produces = "application/json") - @RoleAccessConfig({"SPAR_TSC_ADMIN", "SPAR_MINISTRY_ORCHARD", "SPAR_NONMINISTRY_ORCHARD"}) - public ResponseEntity> findParentTreesWithVegCode( - @PathVariable("vegCode") - @Parameter(description = "The vegetation code of an orchard.") - @Pattern(regexp = "^[a-zA-Z]{1,8}$") - String vegCode, - @io.swagger.v3.oas.annotations.parameters.RequestBody( - description = "A map of ", - required = true) - @RequestBody - Map orchardSpuMap) { - try { - return ResponseEntity.ok(orchardService.findParentTreesWithVegCode(vegCode, orchardSpuMap)); - } catch (Exception e) { - SparLog.error("Orchard endpoint error from findParentTreesWithVegCode: {}", e.getMessage()); - throw new ResponseStatusException(HttpStatus.INTERNAL_SERVER_ERROR, e.getMessage(), e); - } - } } diff --git a/oracle-api/src/main/java/ca/bc/gov/oracleapi/endpoint/ParentTreeEndpoint.java b/oracle-api/src/main/java/ca/bc/gov/oracleapi/endpoint/ParentTreeEndpoint.java index 7124422bc..618862414 100644 --- a/oracle-api/src/main/java/ca/bc/gov/oracleapi/endpoint/ParentTreeEndpoint.java +++ b/oracle-api/src/main/java/ca/bc/gov/oracleapi/endpoint/ParentTreeEndpoint.java @@ -2,20 +2,27 @@ import ca.bc.gov.oracleapi.dto.GeospatialRequestDto; import ca.bc.gov.oracleapi.dto.GeospatialRespondDto; +import ca.bc.gov.oracleapi.dto.ParentTreeByVegCodeDto; import ca.bc.gov.oracleapi.entity.ParentTreeEntity; import ca.bc.gov.oracleapi.security.RoleAccessConfig; import ca.bc.gov.oracleapi.service.ParentTreeService; import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; import io.swagger.v3.oas.annotations.media.Content; import io.swagger.v3.oas.annotations.media.Schema; import io.swagger.v3.oas.annotations.responses.ApiResponse; import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.validation.constraints.Pattern; import java.util.List; +import java.util.Map; import lombok.RequiredArgsConstructor; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.server.ResponseStatusException; /** This class exposes resources for handling {@link ParentTreeEntity} records. */ @Tag( @@ -57,4 +64,23 @@ public List getPtGeoSpatialData( List ptIds) { return parentTreeService.getPtGeoSpatialData(ptIds); } + + /** + * Consumed by backend (postgres) service to retrieve a list of parent trees with a vegCode. + * + * @param vegCode a Seedlot's vegCode + * @return an {@link List} of Map<{@link String}, {@link ParentTreeByVegCodeDto}> with parent tree + * number as the key. + * @throws ResponseStatusException if error occurs + */ + @GetMapping("/vegetation-codes/{vegCode}") + @RoleAccessConfig({"SPAR_TSC_ADMIN", "SPAR_MINISTRY_ORCHARD", "SPAR_NONMINISTRY_ORCHARD"}) + public Map findParentTreesWithVegCode( + @PathVariable("vegCode") + @Parameter(description = "The vegetation code of an orchard.") + @Pattern(regexp = "^[a-zA-Z]{1,8}$") + String vegCode) { + + return parentTreeService.findParentTreesWithVegCode(vegCode.toUpperCase()); + } } diff --git a/oracle-api/src/main/java/ca/bc/gov/oracleapi/endpoint/VegetationCodeEndpoint.java b/oracle-api/src/main/java/ca/bc/gov/oracleapi/endpoint/VegetationCodeEndpoint.java index 200656f9e..2035e3d69 100644 --- a/oracle-api/src/main/java/ca/bc/gov/oracleapi/endpoint/VegetationCodeEndpoint.java +++ b/oracle-api/src/main/java/ca/bc/gov/oracleapi/endpoint/VegetationCodeEndpoint.java @@ -106,7 +106,7 @@ Search for valid vegetation codes (ones which `effectiveDate` ≤ today < `expir @RoleAccessConfig({"SPAR_TSC_ADMIN", "SPAR_MINISTRY_ORCHARD", "SPAR_NONMINISTRY_ORCHARD"}) @PaginatedViaQuery public List findEffectiveByCodeOrDescription( - @RequestParam(name = "search", defaultValue = "") + @RequestParam(name = "search", defaultValue = "", required = true) @Parameter( description = """ diff --git a/oracle-api/src/main/java/ca/bc/gov/oracleapi/entity/OrchardEntity.java b/oracle-api/src/main/java/ca/bc/gov/oracleapi/entity/OrchardEntity.java index d3517532d..9eaedabf0 100644 --- a/oracle-api/src/main/java/ca/bc/gov/oracleapi/entity/OrchardEntity.java +++ b/oracle-api/src/main/java/ca/bc/gov/oracleapi/entity/OrchardEntity.java @@ -3,6 +3,7 @@ import jakarta.persistence.CascadeType; import jakarta.persistence.Column; import jakarta.persistence.Entity; +import jakarta.persistence.FetchType; import jakarta.persistence.Id; import jakarta.persistence.JoinColumn; import jakarta.persistence.ManyToOne; @@ -29,8 +30,11 @@ public class OrchardEntity { @Column(name = "VEGETATION_CODE", length = 8) private String vegetationCode; - @ManyToOne(cascade = CascadeType.ALL) - @JoinColumn(name = "ORCHARD_LOT_TYPE_CODE", referencedColumnName = "ORCHARD_LOT_TYPE_CODE") + @ManyToOne(cascade = CascadeType.ALL, fetch = FetchType.LAZY) + @JoinColumn( + name = "ORCHARD_LOT_TYPE_CODE", + referencedColumnName = "ORCHARD_LOT_TYPE_CODE", + updatable = false) private OrchardLotTypeCode orchardLotTypeCode; @Column(name = "ORCHARD_STAGE_CODE", length = 3) diff --git a/oracle-api/src/main/java/ca/bc/gov/oracleapi/entity/ParentTreeEntity.java b/oracle-api/src/main/java/ca/bc/gov/oracleapi/entity/ParentTreeEntity.java index 415a04e41..6d7b693b5 100644 --- a/oracle-api/src/main/java/ca/bc/gov/oracleapi/entity/ParentTreeEntity.java +++ b/oracle-api/src/main/java/ca/bc/gov/oracleapi/entity/ParentTreeEntity.java @@ -1,6 +1,7 @@ package ca.bc.gov.oracleapi.entity; import jakarta.persistence.Column; +import jakarta.persistence.Convert; import jakarta.persistence.Entity; import jakarta.persistence.Id; import jakarta.persistence.Table; @@ -8,6 +9,7 @@ import lombok.Getter; import lombok.NoArgsConstructor; import lombok.Setter; +import org.hibernate.type.YesNoConverter; /** This class represents a Parent Tree of an {@link Orchard} in the database. */ @Getter @@ -34,9 +36,11 @@ public class ParentTreeEntity { @Column(name = "LOCAL_NUMBER", length = 20) private String localNumber; + @Convert(converter = YesNoConverter.class) @Column(name = "ACTIVE_IND") private Boolean active; + @Convert(converter = YesNoConverter.class) @Column(name = "TESTED_IND") private Boolean tested; @@ -69,4 +73,9 @@ public class ParentTreeEntity { @Column(name = "ELEVATION") private Integer elevation; + + @Override + public String toString() { + return getId().toString(); + } } diff --git a/oracle-api/src/main/java/ca/bc/gov/oracleapi/entity/SeedPlanZone.java b/oracle-api/src/main/java/ca/bc/gov/oracleapi/entity/SeedPlanZone.java index 2c4128f44..22038fbe4 100644 --- a/oracle-api/src/main/java/ca/bc/gov/oracleapi/entity/SeedPlanZone.java +++ b/oracle-api/src/main/java/ca/bc/gov/oracleapi/entity/SeedPlanZone.java @@ -2,6 +2,7 @@ import jakarta.persistence.Column; import jakarta.persistence.Entity; +import jakarta.persistence.FetchType; import jakarta.persistence.Id; import jakarta.persistence.JoinColumn; import jakarta.persistence.ManyToOne; @@ -26,11 +27,11 @@ public class SeedPlanZone { @Column(name = "GENETIC_CLASS_CODE", nullable = false) private Character geneticClassCode; - @ManyToOne - @JoinColumn(name = "SEED_PLAN_ZONE_CODE") + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "SEED_PLAN_ZONE_CODE", updatable = false) private SeedPlanZoneCode seedPlanZoneCode; - @ManyToOne - @JoinColumn(name = "VEGETATION_CODE") + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "VEGETATION_CODE", updatable = false) private VegetationCode vegetationCode; } diff --git a/oracle-api/src/main/java/ca/bc/gov/oracleapi/entity/TestedPtAreaOfUseSpz.java b/oracle-api/src/main/java/ca/bc/gov/oracleapi/entity/TestedPtAreaOfUseSpz.java index 39141cbea..e53727a7e 100644 --- a/oracle-api/src/main/java/ca/bc/gov/oracleapi/entity/TestedPtAreaOfUseSpz.java +++ b/oracle-api/src/main/java/ca/bc/gov/oracleapi/entity/TestedPtAreaOfUseSpz.java @@ -4,6 +4,7 @@ import jakarta.persistence.Column; import jakarta.persistence.Convert; import jakarta.persistence.Entity; +import jakarta.persistence.FetchType; import jakarta.persistence.Id; import jakarta.persistence.IdClass; import jakarta.persistence.JoinColumn; @@ -26,14 +27,14 @@ @Table(name = "TESTED_PT_AREA_OF_USE_SPZ") public class TestedPtAreaOfUseSpz { @Id - @JoinColumn(name = "TESTED_PT_AREA_OF_USE_ID") - @ManyToOne + @JoinColumn(name = "TESTED_PT_AREA_OF_USE_ID", updatable = false) + @ManyToOne(fetch = FetchType.LAZY) @NonNull private TestedPtAreaOfUse testedPtAreaOfUse; @Id - @JoinColumn(name = "SEED_PLAN_ZONE_CODE") - @ManyToOne + @JoinColumn(name = "SEED_PLAN_ZONE_CODE", updatable = false) + @ManyToOne(fetch = FetchType.LAZY) @NonNull private SeedPlanZoneCode seedPlanZoneCode; diff --git a/oracle-api/src/main/java/ca/bc/gov/oracleapi/entity/projection/ParentTreeProj.java b/oracle-api/src/main/java/ca/bc/gov/oracleapi/entity/projection/ParentTreeProj.java index d2802e02f..24e85e6e5 100644 --- a/oracle-api/src/main/java/ca/bc/gov/oracleapi/entity/projection/ParentTreeProj.java +++ b/oracle-api/src/main/java/ca/bc/gov/oracleapi/entity/projection/ParentTreeProj.java @@ -1,5 +1,7 @@ package ca.bc.gov.oracleapi.entity.projection; +import java.math.BigDecimal; + /** This projection consists of a mix of joined column from various tables. */ public interface ParentTreeProj { Long getParentTreeId(); @@ -10,6 +12,14 @@ public interface ParentTreeProj { Long getSpu(); + Character getTested(); + + String getGeneticTypeCode(); + + String getGeneticWorthCode(); + + BigDecimal getGeneticQualityValue(); + void setParentTreeId(Long parentTreeId); void setParentTreeNumber(String parentTreeNumber); @@ -17,4 +27,12 @@ public interface ParentTreeProj { void setOrchardId(String orchardId); void setSpu(Long spu); + + void setTested(Character tested); + + void setGeneticTypeCode(String geneticTypeCode); + + void setGeneticWorthCode(String geneticWorthCode); + + void setGeneticQualityValue(BigDecimal geneticQualityValue); } diff --git a/oracle-api/src/main/java/ca/bc/gov/oracleapi/repository/ParentTreeRepository.java b/oracle-api/src/main/java/ca/bc/gov/oracleapi/repository/ParentTreeRepository.java index ac09a11d3..82ce715af 100644 --- a/oracle-api/src/main/java/ca/bc/gov/oracleapi/repository/ParentTreeRepository.java +++ b/oracle-api/src/main/java/ca/bc/gov/oracleapi/repository/ParentTreeRepository.java @@ -9,24 +9,30 @@ /** This class holds methods for retrieving {@link ParentTreeEntity} data from the database. */ public interface ParentTreeRepository extends JpaRepository { - @Query("from ParentTreeEntity where id in ?1") - List findAllIn(List ids); + List findAllByIdIn(List ids); @Query( value = - """ - SELECT DISTINCT PT.PARENT_TREE_ID AS \"parentTreeId\", - PT.PARENT_TREE_NUMBER AS \"parentTreeNumber\", - O.ORCHARD_ID AS \"orchardId\", Q.SEED_PLAN_UNIT_ID AS \"spu\" - FROM PARENT_TREE PT - JOIN PARENT_TREE_ORCHARD PTO - ON PTO.PARENT_TREE_ID = PT.PARENT_TREE_ID - JOIN ORCHARD O - ON O.ORCHARD_ID = PTO.ORCHARD_ID - JOIN PARENT_TREE_GENETIC_QUALITY Q - ON PT.PARENT_TREE_ID = Q.PARENT_TREE_ID - WHERE PT.VEGETATION_CODE = ?1 - """, + """ + SELECT PT.PARENT_TREE_ID AS \"parentTreeId\", + PT.PARENT_TREE_NUMBER AS \"parentTreeNumber\", + PTO.ORCHARD_ID AS \"orchardId\", + PTSPU.SEED_PLAN_UNIT_ID AS \"spu\", + PT.TESTED_IND AS \"tested\", + Q.GENETIC_TYPE_CODE AS \"geneticTypeCode\", + Q.GENETIC_WORTH_CODE AS \"geneticWorthCode\", + Q.GENETIC_QUALITY_VALUE AS \"geneticQualityValue\" + FROM parent_tree PT + JOIN parent_tree_orchard PTO ON PTO.parent_tree_id = PT.parent_tree_id + LEFT JOIN parent_tree_seed_plan_unit PTSPU ON PTSPU.parent_tree_id = PT.parent_tree_id + LEFT JOIN parent_tree_genetic_quality Q ON PT.parent_tree_id = Q.parent_tree_id + AND Q.seed_plan_unit_id = PTSPU.seed_plan_unit_id + AND Q.genetic_worth_calc_ind = 'Y' + WHERE PT.VEGETATION_CODE = ?1 + AND PT.ACTIVE_IND = 'Y' + AND PT.parent_tree_reg_status_code = 'APP' + ORDER BY PT.parent_tree_id + """, nativeQuery = true) List findAllParentTreeWithVegCode(String vegCode); } diff --git a/oracle-api/src/main/java/ca/bc/gov/oracleapi/service/OrchardService.java b/oracle-api/src/main/java/ca/bc/gov/oracleapi/service/OrchardService.java index 1a2f68a1f..e2d045209 100644 --- a/oracle-api/src/main/java/ca/bc/gov/oracleapi/service/OrchardService.java +++ b/oracle-api/src/main/java/ca/bc/gov/oracleapi/service/OrchardService.java @@ -62,10 +62,16 @@ public Optional findNotRetiredOrchardValidLotType(String id) { } List bacZones = new ArrayList<>(1); - bacZones.add(orchardEntity.getBecZoneCode()); + if (orchardEntity.getBecZoneCode() != null) { + bacZones.add(orchardEntity.getBecZoneCode()); + } + Map becZoneDescMap = sparBecCatalogueService.getBecDescriptionsByCode(bacZones); + String becZoneDescription = + becZoneDescMap.isEmpty() ? null : becZoneDescMap.get(orchardEntity.getBecZoneCode()); + OrchardDto orchardDto = new OrchardDto( orchardEntity.getId(), @@ -75,7 +81,7 @@ public Optional findNotRetiredOrchardValidLotType(String id) { orchardLotTypeCode.getDescription(), orchardEntity.getStageCode(), orchardEntity.getBecZoneCode(), - becZoneDescMap.get(orchardEntity.getBecZoneCode()), + becZoneDescription, orchardEntity.getBecSubzoneCode(), orchardEntity.getVariant(), orchardEntity.getBecVersionId()); @@ -277,7 +283,7 @@ private List findAllParentTree( long endingFour = Instant.now().toEpochMilli(); SparLog.debug("Time elapsed mapping all parent tree orchard ids: {}", endingFour - endingThree); - List parentTreeList = parentTreeRepository.findAllIn(parentTreeIdList); + List parentTreeList = parentTreeRepository.findAllByIdIn(parentTreeIdList); long endingFive = Instant.now().toEpochMilli(); SparLog.debug("Time elapsed finding all parent tree (select in): {}", endingFive - endingFour); diff --git a/oracle-api/src/main/java/ca/bc/gov/oracleapi/service/ParentTreeService.java b/oracle-api/src/main/java/ca/bc/gov/oracleapi/service/ParentTreeService.java index 3eef740e1..e93eac252 100644 --- a/oracle-api/src/main/java/ca/bc/gov/oracleapi/service/ParentTreeService.java +++ b/oracle-api/src/main/java/ca/bc/gov/oracleapi/service/ParentTreeService.java @@ -3,12 +3,21 @@ import ca.bc.gov.oracleapi.config.SparLog; import ca.bc.gov.oracleapi.dto.GeospatialRequestDto; import ca.bc.gov.oracleapi.dto.GeospatialRespondDto; +import ca.bc.gov.oracleapi.dto.ParentTreeByVegCodeDto; +import ca.bc.gov.oracleapi.dto.ParentTreeGeneticQualityDto; +import ca.bc.gov.oracleapi.dto.ParentTreeGeoNodeDto; +import ca.bc.gov.oracleapi.dto.ParentTreeNodeDto; import ca.bc.gov.oracleapi.entity.ParentTreeEntity; +import ca.bc.gov.oracleapi.entity.projection.ParentTreeProj; import ca.bc.gov.oracleapi.repository.ParentTreeRepository; import java.util.ArrayList; +import java.util.HashMap; import java.util.List; +import java.util.Map; import java.util.Optional; +import java.util.stream.Stream; import lombok.RequiredArgsConstructor; +import org.hibernate.type.YesNoConverter; import org.springframework.stereotype.Service; /** This class holds methods for handling {@link ParentTreeEntity} records. */ @@ -18,6 +27,8 @@ public class ParentTreeService { private final ParentTreeRepository parentTreeRepository; + private static final Integer MAX_LEVELS = 5; + /** * Gets latitude, longite and elevation data for each parent tree given a list of Parent Tree ids. * @@ -28,27 +39,282 @@ public List getPtGeoSpatialData(List SparLog.info("Getting lat long elevation data for {} parent tree id(s)", ptIds); List idList = ptIds.stream().map(GeospatialRequestDto::parentTreeId).toList(); - List ptEntityList = parentTreeRepository.findAllIn(idList); + List ptEntityList = parentTreeRepository.findAllByIdIn(idList); + Map parentTreeRootMap; + + // If there's one or more null elevation, go up on the hierarchy + boolean isAnyMissing = parentTreeListHasAnyElevationMissing(ptEntityList); + if (isAnyMissing) { + parentTreeRootMap = checkParentTreeHierarchy(ptEntityList); + } else { + parentTreeRootMap = new HashMap<>(); + ptEntityList.forEach((pt) -> parentTreeRootMap.put(pt.getId(), new ParentTreeNodeDto(pt))); + } List resultList = new ArrayList<>(); + for (Map.Entry rootEntry : parentTreeRootMap.entrySet()) { + Long parentTreeId = rootEntry.getKey(); + + // navigate the tree here! + ParentTreeNodeDto root = rootEntry.getValue(); + SparLog.debug(root.printLevel(0)); + + ParentTreeGeoNodeDto elevation = root.getParentTreeElevation(); + if (elevation == null) { + SparLog.error("No elevation for Parent tree ID {}", parentTreeId); + continue; + } + + GeospatialRespondDto dto = + new GeospatialRespondDto( + parentTreeId, + elevation.getLatitudeDegreesIntVal(), + elevation.getLatitudeMinutesIntVal(), + elevation.getLatitudeSecondsIntVal(), + elevation.getLongitudeDegreesIntVal(), + elevation.getLongitudeMinutesIntVal(), + elevation.getLongitudeSecondsIntVal(), + elevation.getElevation()); - ptEntityList.forEach( - (pt) -> { - GeospatialRespondDto dto = - new GeospatialRespondDto( - pt.getId(), - Optional.ofNullable(pt.getLatitudeDegrees()).orElse(0), - Optional.ofNullable(pt.getLatitudeMinutes()).orElse(0), - Optional.ofNullable(pt.getLatitudeSeconds()).orElse(0), - Optional.ofNullable(pt.getLongitudeDegrees()).orElse(0), - Optional.ofNullable(pt.getLongitudeMinutes()).orElse(0), - Optional.ofNullable(pt.getLongitudeSeconds()).orElse(0), - Optional.ofNullable(pt.getElevation()).orElse(0)); - - resultList.add(dto); - }); + resultList.add(dto); + } SparLog.info("{} records found for lat long data", resultList.size()); return resultList; } + + private Map checkParentTreeHierarchy( + List ptEntityList) { + // Map to store a combination of a ParentTree ID and it's parents in the tree architecture. + Map resultMap = new HashMap<>(); + + // Map to store a combination of a ParentTree ID and it's direct parents, for easily access + Map> parentTreeRelationMap = new HashMap<>(); + + // Create root level + for (ParentTreeEntity ptEntity : ptEntityList) { + resultMap.putIfAbsent(ptEntity.getId(), new ParentTreeNodeDto(ptEntity)); + } + + for (int i = 0; i < MAX_LEVELS; i++) { + SparLog.debug("Hierarchy level {}", i); + + List testList = new ArrayList<>(); + + // Loop through all ParentTree records, getting their female and male parents for the ones + // that has no elevation data + for (ParentTreeEntity ptEntity : ptEntityList) { + if (ptEntity.getElevation() == null) { + parentTreeRelationMap.put(ptEntity.getId(), new ArrayList<>(2)); + + if (ptEntity.getFemaleParentTreeId() != null) { + testList.add(ptEntity.getFemaleParentTreeId()); + parentTreeRelationMap.get(ptEntity.getId()).add(ptEntity.getFemaleParentTreeId()); + } + if (ptEntity.getMaleParentTreeId() != null) { + testList.add(ptEntity.getMaleParentTreeId()); + parentTreeRelationMap.get(ptEntity.getId()).add(ptEntity.getMaleParentTreeId()); + } + } + } + + // Query from the DB all female and male parents (gathered from the loop above) + List nextLevelList = parentTreeRepository.findAllByIdIn(testList); + + // Loop through the relation of ParentTree ids and its parents + for (Map.Entry> entry : parentTreeRelationMap.entrySet()) { + // The 'sonParentTreeId' represents the parent tree without elevation data + Long sonParentTreeId = entry.getKey(); + + // The 'femaleAndMaleParentsIds' represents their parents ids + List femaleAndMaleParentsIds = entry.getValue(); + + // Loop through the list of parent tree from DB, aiming to connect them with their sons + for (ParentTreeEntity ptEntity : nextLevelList) { + + // If 'femaleAndMaleParentsIds' contains the current parent tree id, it means that the + // current parent tree id is one of the parents, it could be either the female or male. + if (femaleAndMaleParentsIds.contains(ptEntity.getId())) { + + // If resultMap doesn't have 'sonParentTreeId' key, it means this is not the first level + // and it should look at the 'parentTreeRelationMap' who has ALL the parent tree + // 'son-parents' relation + if (resultMap.get(sonParentTreeId) == null) { + SparLog.debug("Key (sonParentTreeId) not found for PT id {}", sonParentTreeId); + for (Map.Entry> entryTwo : parentTreeRelationMap.entrySet()) { + if (entryTwo.getValue().contains(sonParentTreeId)) { + sonParentTreeId = entryTwo.getKey(); + break; + } + } + } + + // Get female parent tree data and connect with son. Female is always the first + Long femaleParentTreeId = femaleAndMaleParentsIds.get(0); + if (femaleParentTreeId.equals(ptEntity.getId())) { + SparLog.debug("{} is female parent of {}", ptEntity.getId(), sonParentTreeId); + resultMap.get(sonParentTreeId).add(femaleParentTreeId, ptEntity); + } + + // Get male parent tree data and connect with son. Male is optional + if (femaleAndMaleParentsIds.size() > 1) { + Long maleParentTreeId = entry.getValue().get(1); + if (maleParentTreeId.equals(ptEntity.getId())) { + SparLog.debug("{} is male parent of {}", ptEntity.getId(), sonParentTreeId); + resultMap.get(sonParentTreeId).add(maleParentTreeId, ptEntity); + } + } + } + } + } + + // After loop through all records, check if all parents now has elevation data + // If yes, leave. If not, keep going up looking for the parent's parent + boolean isAnyMissing = parentTreeListHasAnyElevationMissing(nextLevelList); + if (!isAnyMissing) { + SparLog.debug("All elevations has been found. Leaving!"); + break; + } else { + SparLog.debug("Not all elevations has been found. Going up on the hierarchy!"); + ptEntityList = new ArrayList<>(nextLevelList); + } + } + + return resultMap; + } + + private boolean parentTreeListHasAnyElevationMissing(List list) { + return list.stream().filter(tree -> tree.getElevation() == null).count() > 0; + } + + /** + * Find all parent trees under a vegCode. + * + * @param vegCode the vegetation code to search on. + * @return A {@link Map} where Key = Parent Tree Number, Value = {@link ParentTreeByVegCodeDto}. + */ + public Map findParentTreesWithVegCode(String vegCode) { + SparLog.info("Finding all parent trees under VegCode: {}", vegCode); + + Map ptMap = new HashMap<>(); + + // Step 1: Get all the parent trees under a species + List parentTreesProjList = + parentTreeRepository.findAllParentTreeWithVegCode(vegCode); + + YesNoConverter yesNoConverter = new YesNoConverter(); + + // Step 2: Aggregate by parent tree number + for (ParentTreeProj ptProj : parentTreesProjList) { + String ptNumber = ptProj.getParentTreeNumber(); + // No key (pt number) exist + if (!ptMap.containsKey(ptNumber)) { + + ParentTreeByVegCodeDto ptByVegCode = new ParentTreeByVegCodeDto(); + + ptByVegCode.setParentTreeId(ptProj.getParentTreeId()); + + ptByVegCode.setTestedInd(yesNoConverter.toDomainValue(ptProj.getTested())); + + ptByVegCode.setOrchardIds(new ArrayList<>(List.of(ptProj.getOrchardId()))); + + Optional optPtGenQualDto = getPtGenQualDtoFromProj(ptProj); + + Long spukey = ptProj.getSpu(); + + // Ignore gen worth with no spu + if (ptProj.getSpu() != null && optPtGenQualDto.isPresent()) { + ParentTreeGeneticQualityDto ptGenQualDto = optPtGenQualDto.get(); + ptByVegCode.setGeneticQualitiesBySpu( + new HashMap<>(Map.of(spukey, new ArrayList<>(List.of(ptGenQualDto))))); + } else { + ptByVegCode.setGeneticQualitiesBySpu(new HashMap<>()); + } + + ptMap.put(ptNumber, ptByVegCode); + } else { + // Key already exists + ParentTreeByVegCodeDto ptByVegCode = ptMap.get(ptNumber); + + insertOrchardId(ptByVegCode, ptProj.getOrchardId()); + + insertGenQualObj(ptByVegCode, ptProj); + } + } + + return ptMap; + } + + private Optional getPtGenQualDtoFromProj(ParentTreeProj ptProj) { + + if (ptProj.getGeneticTypeCode() == null || ptProj.getGeneticWorthCode() == null) { + return Optional.empty(); + } + + ParentTreeGeneticQualityDto ptGenQualDto = new ParentTreeGeneticQualityDto(); + + ptGenQualDto.setGeneticTypeCode(ptProj.getGeneticTypeCode()); + ptGenQualDto.setGeneticWorthCode(ptProj.getGeneticWorthCode()); + ptGenQualDto.setGeneticQualityValue(ptProj.getGeneticQualityValue()); + + return Optional.of(ptGenQualDto); + } + + /** + * Insert orchard id if it does not exist. + * + * @param ptByVegCode the object {@link ParentTreeByVegCodeDto} to modify with + * @param orchardId the orchard id to be inserted + */ + private void insertOrchardId(ParentTreeByVegCodeDto ptByVegCode, String orchardId) { + List orchardIdList = ptByVegCode.getOrchardIds(); + if (!orchardIdList.contains(orchardId)) { + orchardIdList.add(orchardId); + ptByVegCode.setOrchardIds(orchardIdList); + } + } + + private void insertGenQualObj(ParentTreeByVegCodeDto ptByVegCode, ParentTreeProj ptProj) { + Long spukey = ptProj.getSpu(); + + Map> genQualMap = + ptByVegCode.getGeneticQualitiesBySpu(); + + Optional optPtGenQualToInsert = getPtGenQualDtoFromProj(ptProj); + + if (spukey != null && optPtGenQualToInsert.isPresent()) { + + ParentTreeGeneticQualityDto ptGenQualToInsert = optPtGenQualToInsert.get(); + + List listToInsert = new ArrayList<>(List.of(ptGenQualToInsert)); + + // Add to existing list + if (genQualMap.containsKey(spukey)) { + List existingList = genQualMap.get(spukey); + + // Make sure not to insert the same gen worth more than once + List dupList = + existingList.stream() + .filter( + ptGenQualDto -> + ptGenQualDto + .getGeneticTypeCode() + .equals(ptGenQualToInsert.getGeneticTypeCode()) + && ptGenQualDto + .getGeneticWorthCode() + .equals(ptGenQualToInsert.getGeneticWorthCode())) + .toList(); + + if (dupList.size() > 0) { + listToInsert = existingList; + } else { + listToInsert = Stream.concat(existingList.stream(), listToInsert.stream()).toList(); + } + } + + genQualMap.put(spukey, listToInsert); + + ptByVegCode.setGeneticQualitiesBySpu(genQualMap); + } + } } diff --git a/oracle-api/src/main/java/ca/bc/gov/oracleapi/service/SparBecCatalogueService.java b/oracle-api/src/main/java/ca/bc/gov/oracleapi/service/SparBecCatalogueService.java index 4e200b3a0..228074aeb 100644 --- a/oracle-api/src/main/java/ca/bc/gov/oracleapi/service/SparBecCatalogueService.java +++ b/oracle-api/src/main/java/ca/bc/gov/oracleapi/service/SparBecCatalogueService.java @@ -21,7 +21,8 @@ public class SparBecCatalogueService { * null. */ public Map getBecDescriptionsByCode(List becZoneCodes) { - SparLog.info("Begin service request to find the description of a given BEC zone code"); + SparLog.info( + "Begin service request to find the description of a given BEC zone code {}", becZoneCodes); if (becZoneCodes.isEmpty()) { SparLog.info("No BEC Zone code param, returning empty values for BEC zone descriptions"); diff --git a/oracle-api/src/test/java/ca/bc/gov/oracleapi/endpoint/OrchardEndpointTest.java b/oracle-api/src/test/java/ca/bc/gov/oracleapi/endpoint/OrchardEndpointTest.java index c37101b32..5deaeef11 100644 --- a/oracle-api/src/test/java/ca/bc/gov/oracleapi/endpoint/OrchardEndpointTest.java +++ b/oracle-api/src/test/java/ca/bc/gov/oracleapi/endpoint/OrchardEndpointTest.java @@ -4,7 +4,6 @@ import static org.mockito.Mockito.when; import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.csrf; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; @@ -12,20 +11,16 @@ import ca.bc.gov.oracleapi.dto.OrchardParentTreeDto; import ca.bc.gov.oracleapi.dto.ParentTreeGeneticInfoDto; import ca.bc.gov.oracleapi.dto.ParentTreeGeneticQualityDto; -import ca.bc.gov.oracleapi.dto.SameSpeciesTreeDto; import ca.bc.gov.oracleapi.service.OrchardService; import java.math.BigDecimal; import java.util.ArrayList; -import java.util.HashMap; import java.util.List; -import java.util.Map; import java.util.Optional; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; import org.springframework.boot.test.mock.mockito.MockBean; -import org.springframework.dao.DataRetrievalFailureException; import org.springframework.http.MediaType; import org.springframework.security.test.context.support.WithMockUser; import org.springframework.test.web.servlet.MockMvc; @@ -222,7 +217,7 @@ void findOrchardsWithVegCodeSuccessEndpointTest() throws Exception { mockMvc .perform( - get("/api/orchards/vegetation-code/{vegCode}", vegCode) + get("/api/orchards/vegetation-codes/{vegCode}", vegCode) .with(csrf().asHeader()) .header("Content-Type", "application/json") .accept(MediaType.APPLICATION_JSON)) @@ -263,56 +258,58 @@ void findOrchardsWithVegCodeNotFoundEndpointTest() throws Exception { .andReturn(); } - @Test - @DisplayName("getAllParentTreeByVegCodeTest") - void getAllParentTreeByVegCodeTest() throws Exception { - - SameSpeciesTreeDto firstDto = - new SameSpeciesTreeDto(Long.valueOf(123), "1000", "1", Long.valueOf(7), List.of()); - SameSpeciesTreeDto secondDto = - new SameSpeciesTreeDto(Long.valueOf(456), "2000", "1", Long.valueOf(7), List.of()); - - List testList = List.of(firstDto, secondDto); - - String vegCode = "PLI"; - Map testMap = new HashMap<>(); - - when(orchardService.findParentTreesWithVegCode(vegCode, testMap)).thenReturn(testList); - - mockMvc - .perform( - post("/api/orchards/parent-trees/vegetation-codes/{vegCode}", vegCode) - .with(csrf().asHeader()) - .content(testMap.toString()) - .contentType(MediaType.APPLICATION_JSON) - .header("Content-Type", "application/json") - .accept(MediaType.APPLICATION_JSON)) - .andExpect(status().isOk()) - .andExpect(jsonPath("$[0].parentTreeId").value(firstDto.getParentTreeId())) - .andExpect(jsonPath("$[0].parentTreeNumber").value(firstDto.getParentTreeNumber())) - .andExpect(jsonPath("$[1].parentTreeId").value(secondDto.getParentTreeId())) - .andExpect(jsonPath("$[1].parentTreeNumber").value(secondDto.getParentTreeNumber())) - .andReturn(); - } - - @Test - @DisplayName("getAllParentTreeByVegCodeErrorTest") - void getAllParentTreeByVegCodeErrorTest() throws Exception { - String vegCode = "FDI"; - - Map testMap = new HashMap<>(); - when(orchardService.findParentTreesWithVegCode(vegCode, testMap)) - .thenThrow(new DataRetrievalFailureException("")); - - mockMvc - .perform( - post("/api/orchards/parent-trees/vegetation-codes/{vegCode}", vegCode) - .with(csrf().asHeader()) - .content(testMap.toString()) - .contentType(MediaType.APPLICATION_JSON) - .header("Content-Type", "application/json") - .accept(MediaType.APPLICATION_JSON)) - .andExpect(status().isInternalServerError()) - .andReturn(); - } + // TODO + // @Test + // @DisplayName("getAllParentTreeByVegCodeTest") + // void getAllParentTreeByVegCodeTest() throws Exception { + + // SameSpeciesTreeDto firstDto = + // new SameSpeciesTreeDto(Long.valueOf(123), "1000", "1", Long.valueOf(7), List.of()); + // SameSpeciesTreeDto secondDto = + // new SameSpeciesTreeDto(Long.valueOf(456), "2000", "1", Long.valueOf(7), List.of()); + + // List testList = List.of(firstDto, secondDto); + + // String vegCode = "PLI"; + // Map testMap = new HashMap<>(); + + // when(orchardService.findParentTreesWithVegCode(vegCode, testMap)).thenReturn(testList); + + // mockMvc + // .perform( + // post("/api/orchards/parent-trees/vegetation-codes/{vegCode}", vegCode) + // .with(csrf().asHeader()) + // .content(testMap.toString()) + // .contentType(MediaType.APPLICATION_JSON) + // .header("Content-Type", "application/json") + // .accept(MediaType.APPLICATION_JSON)) + // .andExpect(status().isOk()) + // .andExpect(jsonPath("$[0].parentTreeId").value(firstDto.getParentTreeId())) + // .andExpect(jsonPath("$[0].parentTreeNumber").value(firstDto.getParentTreeNumber())) + // .andExpect(jsonPath("$[1].parentTreeId").value(secondDto.getParentTreeId())) + // .andExpect(jsonPath("$[1].parentTreeNumber").value(secondDto.getParentTreeNumber())) + // .andReturn(); + // } + + // TODO + // @Test + // @DisplayName("getAllParentTreeByVegCodeErrorTest") + // void getAllParentTreeByVegCodeErrorTest() throws Exception { + // String vegCode = "FDI"; + + // Map testMap = new HashMap<>(); + // when(orchardService.findParentTreesWithVegCode(vegCode, testMap)) + // .thenThrow(new DataRetrievalFailureException("")); + + // mockMvc + // .perform( + // post("/api/orchards/parent-trees/vegetation-codes/{vegCode}", vegCode) + // .with(csrf().asHeader()) + // .content(testMap.toString()) + // .contentType(MediaType.APPLICATION_JSON) + // .header("Content-Type", "application/json") + // .accept(MediaType.APPLICATION_JSON)) + // .andExpect(status().isInternalServerError()) + // .andReturn(); + // } } diff --git a/oracle-api/src/test/java/ca/bc/gov/oracleapi/endpoint/VegetationCodeEndpointTest.java b/oracle-api/src/test/java/ca/bc/gov/oracleapi/endpoint/VegetationCodeEndpointTest.java index 78ba5b1d6..4abd6427d 100644 --- a/oracle-api/src/test/java/ca/bc/gov/oracleapi/endpoint/VegetationCodeEndpointTest.java +++ b/oracle-api/src/test/java/ca/bc/gov/oracleapi/endpoint/VegetationCodeEndpointTest.java @@ -13,6 +13,7 @@ import java.util.List; import java.util.Optional; import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; import org.springframework.boot.test.mock.mockito.MockBean; @@ -60,7 +61,8 @@ void fetchExistentCode() throws Exception { mockMvc .perform(get("/api/vegetation-codes/C1").accept(MediaType.APPLICATION_JSON)) .andExpect(status().isOk()) - .andExpect(content().contentType(MediaType.APPLICATION_JSON)); + .andExpect(content().contentType(MediaType.APPLICATION_JSON)) + .andReturn(); } @Test @@ -82,7 +84,8 @@ void fetchNonExistentCode() throws Exception { .with(csrf().asHeader()) .accept(MediaType.APPLICATION_JSON)) .andExpect(status().isNotFound()) - .andExpect(content().string("")); + .andExpect(content().string("")) + .andReturn(); } @Test @@ -105,7 +108,8 @@ void searchWithMatches() throws Exception { .with(csrf().asHeader()) .accept(MediaType.APPLICATION_JSON)) .andExpect(status().isOk()) - .andExpect(content().contentType(MediaType.APPLICATION_JSON)); + .andExpect(content().contentType(MediaType.APPLICATION_JSON)) + .andReturn(); } @Test @@ -121,7 +125,8 @@ void searchWithNoMatch() throws Exception { .with(csrf().asHeader()) .accept(MediaType.APPLICATION_JSON)) .andExpect(status().isOk()) - .andExpect(content().contentType(MediaType.APPLICATION_JSON)); + .andExpect(content().contentType(MediaType.APPLICATION_JSON)) + .andReturn(); } @Test @@ -136,7 +141,8 @@ void searchWithNegativePage() throws Exception { get("/api/vegetation-codes?search=1&page=-1") .with(csrf().asHeader()) .accept(MediaType.APPLICATION_JSON)) - .andExpect(status().isBadRequest()); + .andExpect(status().isBadRequest()) + .andReturn(); } @Test @@ -159,6 +165,33 @@ void searchWithNonPositivePageSize() throws Exception { get("/api/vegetation-codes?search=1&perPage=0") .with(csrf().asHeader()) .accept(MediaType.APPLICATION_JSON)) - .andExpect(status().isBadRequest()); + .andExpect(status().isBadRequest()) + .andReturn(); + } + + @Test + @DisplayName("Find effective by code or description happy path should succeed") + void findEffectiveByCodeOrDescription_happyPath_shouldSucceed() throws Exception { + VegetationCode vc = + new VegetationCode( + "C1", + "Code 1", + LocalDate.of(2020, 1, 1), + LocalDate.of(2025, 1, 1), + LocalDateTime.now()); + + Pageable pageable = PageRequest.of(0, 10); + Page vegPage = new PageImpl(List.of(vc)); + + when(vegetationCodeRepository.findByCodeOrDescription("1", pageable)).thenReturn(vegPage); + + mockMvc + .perform( + get("/api/vegetation-codes?search=1&page=0&perPage=1") + .with(csrf().asHeader()) + .accept(MediaType.APPLICATION_JSON)) + .andExpect(status().isOk()) + .andExpect(content().contentType(MediaType.APPLICATION_JSON)) + .andReturn(); } } diff --git a/oracle-api/src/test/java/ca/bc/gov/oracleapi/repository/ParentTreeRepositoryTest.java b/oracle-api/src/test/java/ca/bc/gov/oracleapi/repository/ParentTreeRepositoryTest.java index df201f803..cdf4394da 100644 --- a/oracle-api/src/test/java/ca/bc/gov/oracleapi/repository/ParentTreeRepositoryTest.java +++ b/oracle-api/src/test/java/ca/bc/gov/oracleapi/repository/ParentTreeRepositoryTest.java @@ -23,9 +23,10 @@ class ParentTreeRepositoryTest { @Autowired private ParentTreeRepository parentTreeRepository; @Test - @DisplayName("findAllInTest") - void findAllInTest() { - List parentTreeList = parentTreeRepository.findAllIn(List.of(4032L, 4033L)); + @DisplayName("findAllByIdInTest") + void findAllByIdInTest() { + List parentTreeList = + parentTreeRepository.findAllByIdIn(List.of(4032L, 4033L)); assertFalse(parentTreeList.isEmpty()); assertEquals(2, parentTreeList.size()); @@ -58,7 +59,7 @@ void findAllInTest() { @Test @DisplayName("getPtGeoSpatialData_successTest") void getPtGeoSpatialData_successTest() { - List ptreeEntity = parentTreeRepository.findAllIn(List.of(4032L)); + List ptreeEntity = parentTreeRepository.findAllByIdIn(List.of(4032L)); assertFalse(ptreeEntity.isEmpty()); assertEquals(1, ptreeEntity.size()); diff --git a/oracle-api/src/test/java/ca/bc/gov/oracleapi/service/OrchardServiceTest.java b/oracle-api/src/test/java/ca/bc/gov/oracleapi/service/OrchardServiceTest.java index 7965c6864..5a9b196c7 100644 --- a/oracle-api/src/test/java/ca/bc/gov/oracleapi/service/OrchardServiceTest.java +++ b/oracle-api/src/test/java/ca/bc/gov/oracleapi/service/OrchardServiceTest.java @@ -216,7 +216,7 @@ void findParentTreeGeneticQualityDataTest_Success() { parentTree.setTested(true); parentTree.setBreedingProgram(true); - when(parentTreeRepository.findAllIn(any())).thenReturn(List.of(parentTree)); + when(parentTreeRepository.findAllByIdIn(any())).thenReturn(List.of(parentTree)); Long spuId = 7L; diff --git a/oracle-api/src/test/java/ca/bc/gov/oracleapi/service/ParentTreeServiceTest.java b/oracle-api/src/test/java/ca/bc/gov/oracleapi/service/ParentTreeServiceTest.java index 806c47b56..946f04c46 100644 --- a/oracle-api/src/test/java/ca/bc/gov/oracleapi/service/ParentTreeServiceTest.java +++ b/oracle-api/src/test/java/ca/bc/gov/oracleapi/service/ParentTreeServiceTest.java @@ -4,10 +4,14 @@ import ca.bc.gov.oracleapi.dto.GeospatialRequestDto; import ca.bc.gov.oracleapi.dto.GeospatialRespondDto; +import ca.bc.gov.oracleapi.dto.ParentTreeByVegCodeDto; import ca.bc.gov.oracleapi.entity.ParentTreeEntity; +import ca.bc.gov.oracleapi.entity.projection.ParentTreeProj; import ca.bc.gov.oracleapi.repository.ParentTreeRepository; +import java.math.BigDecimal; import java.util.ArrayList; import java.util.List; +import java.util.Map; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; @@ -23,6 +27,75 @@ class ParentTreeServiceTest { private ParentTreeService parentTreeService; + private ParentTreeProj mockParentTreeProj() { + return new ParentTreeProj() { + + @Override + public Long getParentTreeId() { + return 20012L; + } + + @Override + public String getParentTreeNumber() { + return "29"; + } + + @Override + public String getOrchardId() { + return "211"; + } + + @Override + public Long getSpu() { + return 68L; + } + + @Override + public Character getTested() { + return 'Y'; + } + + @Override + public String getGeneticTypeCode() { + return "BV"; + } + + @Override + public String getGeneticWorthCode() { + return "GVO"; + } + + @Override + public BigDecimal getGeneticQualityValue() { + return new BigDecimal("15"); + } + + @Override + public void setParentTreeId(Long parentTreeId) {} + + @Override + public void setParentTreeNumber(String parentTreeNumber) {} + + @Override + public void setOrchardId(String orchardId) {} + + @Override + public void setSpu(Long spu) {} + + @Override + public void setTested(Character tested) {} + + @Override + public void setGeneticTypeCode(String geneticTypeCode) {} + + @Override + public void setGeneticWorthCode(String geneticWorthCode) {} + + @Override + public void setGeneticQualityValue(BigDecimal geneticQualityValue) {} + }; + } + @BeforeEach void setup() { parentTreeService = new ParentTreeService(parentTreeRepository); @@ -41,7 +114,7 @@ void getPtGeoSpatialData_successTest() { ptreeEntity.setLongitudeSeconds(0); ptreeEntity.setElevation(451); - when(parentTreeRepository.findAllIn(List.of(4110L))).thenReturn(List.of(ptreeEntity)); + when(parentTreeRepository.findAllByIdIn(List.of(4110L))).thenReturn(List.of(ptreeEntity)); List ptIds = new ArrayList<>(); ptIds.add(new GeospatialRequestDto(4110L)); @@ -62,7 +135,7 @@ void getPtGeoSpatialData_successTest() { @Test @DisplayName("getPtGeoSpatialData_emptyTest") void getPtGeoSpatialData_emptyTest() { - when(parentTreeRepository.findAllIn(List.of(4110L))).thenReturn(List.of()); + when(parentTreeRepository.findAllByIdIn(List.of(4110L))).thenReturn(List.of()); List ptIds = new ArrayList<>(); ptIds.add(new GeospatialRequestDto(4110L)); @@ -71,4 +144,217 @@ void getPtGeoSpatialData_emptyTest() { Assertions.assertTrue(dtoList.isEmpty()); } + + @Test + @DisplayName("Find parent trees with veg code happy path should succeed") + void findParentTreesWithVegCode_happyPath_shouldSucceed() { + String vegCode = "SX"; + + ParentTreeProj parentProj = mockParentTreeProj(); + + when(parentTreeRepository.findAllParentTreeWithVegCode(vegCode)) + .thenReturn(List.of(parentProj)); + + Map response = + parentTreeService.findParentTreesWithVegCode(vegCode); + + Assertions.assertNotNull(response); + Assertions.assertEquals(1, response.size()); + Assertions.assertTrue(response.containsKey("29")); + } + + private ParentTreeEntity mock( + Long id, Integer elevation, Integer[] lat, Integer[] lng, Long femaleId, Long maleId) { + ParentTreeEntity ptreeEntityReq1 = new ParentTreeEntity(); + ptreeEntityReq1.setId(id); + ptreeEntityReq1.setLatitudeDegrees(lat == null ? null : lat[0]); + ptreeEntityReq1.setLatitudeMinutes(lat == null ? null : lat[1]); + ptreeEntityReq1.setLatitudeSeconds(lat == null ? null : lat[2]); + ptreeEntityReq1.setLongitudeDegrees(lng == null ? null : lng[0]); + ptreeEntityReq1.setLongitudeMinutes(lng == null ? null : lng[1]); + ptreeEntityReq1.setLongitudeSeconds(lng == null ? null : lng[2]); + ptreeEntityReq1.setElevation(elevation); + ptreeEntityReq1.setFemaleParentTreeId(femaleId); + ptreeEntityReq1.setMaleParentTreeId(maleId); + return ptreeEntityReq1; + } + + private ParentTreeEntity mockNull(Long id, Long femaleId, Long maleId) { + return mock(id, null, null, null, femaleId, maleId); + } + + @Test + @DisplayName("Get parent tree geo spatial data hierarchy tree should succeed") + void getPtGeoSpatialData_hierarchyTree_shouldSucceed() { + /* + * TEST CASE: PT id 1002614 = mean elevation is 675 - female parent id 4168 (elevation 610) - + * male parent id 4638 (elevation 740) PT id 1001097 = mean elevation is 465 - female parent id + * 4668 (elevation 823) - male parent id 4628 (elevation 107) PT id 1001096 = mean elevation is + * 465 - female parent id 4668 (elevation 823) - male parent id 4628 (elevation 107) PT id + * 1004423 = mean elevation is 526 - female parent id 1000031 = mean elevation is 573 - female + * parent id 4700 (elevation 628) - male parent id 4035 (elevation 518) - male parent id 1004424 + * = mean elevation is 480 - female parent id 4035 (elevation 518) - male parent id 4078 + * (elevation 442) PT id 1001093 = mean elevation is 84 - female parent id 4197 (elevation 61) - + * male parent id 4182 (elevation 107) + */ + + // First run start + ParentTreeEntity ptreeRoot1 = mockNull(1002614L, 4168L, 4638L); + ParentTreeEntity ptreeRoot2 = mockNull(1001097L, 4668L, 4628L); + ParentTreeEntity ptreeRoot3 = mockNull(1001096L, 4668L, 4628L); + ParentTreeEntity ptreeRoot4 = mockNull(1004423L, 1000031L, 1004424L); + ParentTreeEntity ptreeRoot5 = mockNull(1001093L, 4197L, 4182L); + + List firstRequestList = + List.of( + ptreeRoot1.getId(), + ptreeRoot2.getId(), + ptreeRoot3.getId(), + ptreeRoot4.getId(), + ptreeRoot5.getId()); + + List firstResponseList = + List.of(ptreeRoot1, ptreeRoot2, ptreeRoot3, ptreeRoot4, ptreeRoot5); + + when(parentTreeRepository.findAllByIdIn(firstRequestList)).thenReturn(firstResponseList); + // First run end + + // Second run start + ParentTreeEntity ptreeRoot1Mother = + mock(4168L, 610, new Integer[] {49, 7, 0}, new Integer[] {121, 36, 0}, null, null); + ParentTreeEntity ptreeRoot1Father = + mock(4638L, 740, new Integer[] {48, 5, 0}, new Integer[] {124, 0, 0}, null, null); + ParentTreeEntity ptreeRoot2Mother = + mock(4668L, 823, new Integer[] {49, 7, 0}, new Integer[] {121, 49, 0}, null, null); + ParentTreeEntity ptreeRoot2Father = + mock(4628L, 107, new Integer[] {49, 40, 0}, new Integer[] {125, 50, 3}, null, null); + ParentTreeEntity ptreeRoot3Mother = + mock(4668L, 823, new Integer[] {49, 7, 0}, new Integer[] {121, 49, 0}, null, null); + ParentTreeEntity ptreeRoot3Father = + mock(4628L, 107, new Integer[] {49, 40, 0}, new Integer[] {125, 50, 3}, null, null); + ParentTreeEntity ptreeRoot4Mother = mockNull(1000031L, 4700L, 4035L); + ParentTreeEntity ptreeRoot4Father = mockNull(1004424L, 4035L, 4078L); + ParentTreeEntity ptreeRoot5Mother = + mock(4197L, 61, new Integer[] {49, 54, 0}, new Integer[] {126, 49, 0}, null, null); + ParentTreeEntity ptreeRoot5Father = + mock(4182L, 107, new Integer[] {50, 1, 0}, new Integer[] {127, 16, 0}, null, null); + + List secondRequestList = + List.of( + ptreeRoot1Mother.getId(), + ptreeRoot1Father.getId(), + ptreeRoot2Mother.getId(), + ptreeRoot2Father.getId(), + ptreeRoot3Mother.getId(), + ptreeRoot3Father.getId(), + ptreeRoot4Mother.getId(), + ptreeRoot4Father.getId(), + ptreeRoot5Mother.getId(), + ptreeRoot5Father.getId()); + + List secondResponseList = + List.of( + ptreeRoot1Mother, + ptreeRoot1Father, + ptreeRoot2Mother, + ptreeRoot2Father, + ptreeRoot3Mother, + ptreeRoot3Father, + ptreeRoot4Mother, + ptreeRoot4Father, + ptreeRoot5Mother, + ptreeRoot5Father); + + when(parentTreeRepository.findAllByIdIn(secondRequestList)).thenReturn(secondResponseList); + // Second run end + + // Third run start + ParentTreeEntity ptreeRoot4MotherGranMother = + mock(4700L, 628, new Integer[] {49, 22, 0}, new Integer[] {123, 13, 0}, null, null); + ParentTreeEntity ptreeRoot4MotherGranFather = + mock(4035L, 518, new Integer[] {49, 2, 0}, new Integer[] {124, 3, 0}, null, null); + ParentTreeEntity ptreeRoot4FatherGranMother = + mock(4035L, 518, new Integer[] {49, 2, 0}, new Integer[] {124, 3, 0}, null, null); + ParentTreeEntity ptreeRoot4FatherGranFather = + mock(4078L, 442, new Integer[] {48, 28, 0}, new Integer[] {123, 40, 0}, null, null); + + List thirdRequestList = + List.of( + ptreeRoot4MotherGranMother.getId(), + ptreeRoot4MotherGranFather.getId(), + ptreeRoot4FatherGranMother.getId(), + ptreeRoot4FatherGranFather.getId()); + + List thirdResponseList = + List.of( + ptreeRoot4MotherGranMother, + ptreeRoot4MotherGranFather, + ptreeRoot4FatherGranMother, + ptreeRoot4FatherGranFather); + + when(parentTreeRepository.findAllByIdIn(thirdRequestList)).thenReturn(thirdResponseList); + // Third run end + + List ptIds = new ArrayList<>(); + ptIds.add(new GeospatialRequestDto(ptreeRoot1.getId())); + ptIds.add(new GeospatialRequestDto(ptreeRoot2.getId())); + ptIds.add(new GeospatialRequestDto(ptreeRoot3.getId())); + ptIds.add(new GeospatialRequestDto(ptreeRoot4.getId())); + ptIds.add(new GeospatialRequestDto(ptreeRoot5.getId())); + + List dtoList = parentTreeService.getPtGeoSpatialData(ptIds); + + Assertions.assertFalse(dtoList.isEmpty()); + Assertions.assertEquals(5, dtoList.size()); + + // First: 1001097 + Assertions.assertEquals(ptreeRoot2.getId(), dtoList.get(0).parentTreeId()); + Assertions.assertEquals(465, dtoList.get(0).elevation()); + Assertions.assertEquals(49, dtoList.get(0).latitudeDegree()); + Assertions.assertEquals(23, dtoList.get(0).latitudeMinute()); + Assertions.assertEquals(30, dtoList.get(0).latitudeSecond()); + Assertions.assertEquals(123, dtoList.get(0).longitudeDegree()); + Assertions.assertEquals(49, dtoList.get(0).longitudeMinute()); + Assertions.assertEquals(31, dtoList.get(0).longitudeSecond()); + + // Second: 1001096 + Assertions.assertEquals(ptreeRoot3.getId(), dtoList.get(1).parentTreeId()); + Assertions.assertEquals(465, dtoList.get(1).elevation()); + Assertions.assertEquals(49, dtoList.get(1).latitudeDegree()); + Assertions.assertEquals(23, dtoList.get(1).latitudeMinute()); + Assertions.assertEquals(30, dtoList.get(1).latitudeSecond()); + Assertions.assertEquals(123, dtoList.get(1).longitudeDegree()); + Assertions.assertEquals(49, dtoList.get(1).longitudeMinute()); + Assertions.assertEquals(31, dtoList.get(1).longitudeSecond()); + + // Third: 1004423 + Assertions.assertEquals(ptreeRoot4.getId(), dtoList.get(2).parentTreeId()); + Assertions.assertEquals(526, dtoList.get(2).elevation()); + Assertions.assertEquals(48, dtoList.get(2).latitudeDegree()); + Assertions.assertEquals(58, dtoList.get(2).latitudeMinute()); + Assertions.assertEquals(30, dtoList.get(2).latitudeSecond()); + Assertions.assertEquals(123, dtoList.get(2).longitudeDegree()); + Assertions.assertEquals(44, dtoList.get(2).longitudeMinute()); + Assertions.assertEquals(45, dtoList.get(2).longitudeSecond()); + + // Fourth: 1002614L + Assertions.assertEquals(ptreeRoot1.getId(), dtoList.get(3).parentTreeId()); + Assertions.assertEquals(675, dtoList.get(3).elevation()); + Assertions.assertEquals(48, dtoList.get(3).latitudeDegree()); + Assertions.assertEquals(36, dtoList.get(3).latitudeMinute()); + Assertions.assertEquals(0, dtoList.get(3).latitudeSecond()); + Assertions.assertEquals(122, dtoList.get(3).longitudeDegree()); + Assertions.assertEquals(48, dtoList.get(3).longitudeMinute()); + Assertions.assertEquals(0, dtoList.get(3).longitudeSecond()); + + // Fifth: 1001093L + Assertions.assertEquals(ptreeRoot5.getId(), dtoList.get(4).parentTreeId()); + Assertions.assertEquals(84, dtoList.get(4).elevation()); + Assertions.assertEquals(49, dtoList.get(4).latitudeDegree()); + Assertions.assertEquals(57, dtoList.get(4).latitudeMinute()); + Assertions.assertEquals(30, dtoList.get(4).latitudeSecond()); + Assertions.assertEquals(127, dtoList.get(4).longitudeDegree()); + Assertions.assertEquals(2, dtoList.get(4).longitudeMinute()); + Assertions.assertEquals(30, dtoList.get(4).longitudeSecond()); + } } diff --git a/sync/Dockerfile b/sync/Dockerfile index a6e8af047..8bef73c89 100644 --- a/sync/Dockerfile +++ b/sync/Dockerfile @@ -1,5 +1,9 @@ FROM python:3.12-slim +# Receive build number as argument, retain as environment variable +ARG BUILD_NUMBER +ENV BUILD_NUMBER=${BUILD_NUMBER} + # Packages and nonroot user RUN apt update && \ apt install -y --no-install-recommends gcc libpq-dev python3-dev && \ diff --git a/sync/config/SQL/SPAR/POSTGRES_SEEDLOT_EXTRACT.sql b/sync/config/SQL/SPAR/POSTGRES_SEEDLOT_EXTRACT.sql index afa8dfa4c..d8af40c10 100644 --- a/sync/config/SQL/SPAR/POSTGRES_SEEDLOT_EXTRACT.sql +++ b/sync/config/SQL/SPAR/POSTGRES_SEEDLOT_EXTRACT.sql @@ -4,104 +4,202 @@ WITH seedlot_coll_methods (SELECT seedlot_number , TO_CHAR(cone_collection_method_code,'FM00') cone_collection_method_code , ROW_NUMBER() OVER (PARTITION BY seedlot_number ORDER BY entry_timestamp) rown - FROM spar.seedlot_collection_method) + FROM spar.seedlot_collection_method + WHERE seedlot_number = %(p_seedlot_number)s) + , draft_seedlots + AS + (select seedlot_number + , case when all_step_data->'extractionStorageStep'->'extraction'->'agency'->>'isInvalid' = 'false' + and all_step_data->'extractionStorageStep'->'extraction'->'locationCode'->>'isInvalid' = 'false' + then NULLIF(all_step_data->'extractionStorageStep'->'extraction'->'agency'->>'value','') + else null + end as extrct_cli_number + , case when all_step_data->'extractionStorageStep'->'extraction'->'agency'->>'isInvalid' = 'false' + and all_step_data->'extractionStorageStep'->'extraction'->'locationCode'->>'isInvalid' = 'false' + then NULLIF(all_step_data->'extractionStorageStep'->'extraction'->'locationCode'->>'value','') + else null + end as extrct_cli_locn_cd + , case when all_step_data->'interimStep'->'facilityType'->>'isInvalid' = 'false' + then NULLIF(all_step_data->'interimStep'->'facilityType'->>'value' ,'') + else null + end as interm_facility_code + , case when all_step_data->'orchardStep'->'orchards'->'primaryOrchard'->>'isInvalid' = 'false' + then NULLIF(all_step_data->'orchardStep'->'orchards'->'primaryOrchard'->'value'->>'code','') + else null + end as orchard_id + , case when all_step_data->'orchardStep'->'orchards'->'secondaryOrchard'->>'isInvalid' = 'false' + then NULLIF(all_step_data->'orchardStep'->'orchards'->'secondaryOrchard'->'value'->>'code','') + else null + end as secondary_orchard_id + , CAST(case when all_step_data->'collectionStep'->'startDate'->>'isInvalid' = 'false' + then NULLIF(all_step_data->'collectionStep'->'startDate'->>'value','') + else null + end AS DATE) as collection_start_date + , CAST(case when all_step_data->'collectionStep'->'endDate'->>'isInvalid' = 'false' + then NULLIF(all_step_data->'collectionStep'->'endDate'->>'value','') + else null + end AS DATE) as collection_end_date + , case when all_step_data->'collectionStep'->'collectorAgency'->>'isInvalid' = 'false' + and all_step_data->'collectionStep'->'locationCode'->>'isInvalid' = 'false' + then NULLIF(all_step_data->'collectionStep'->'collectorAgency'->>'value' ,'') + else null + end as collection_cli_number + , case when all_step_data->'collectionStep'->'collectorAgency'->>'isInvalid' = 'false' + and all_step_data->'collectionStep'->'locationCode'->>'isInvalid' = 'false' + then NULLIF(all_step_data->'collectionStep'->'locationCode'->>'value','') + else null + end as collection_cli_locn_cd + , CAST(case when all_step_data->'collectionStep'->'volumeOfCones'->>'isInvalid' = 'false' + then NULLIF(all_step_data->'collectionStep'->'volumeOfCones'->>'value','') + else null + end as NUMERIC) as clctn_volume + , CAST(case when all_step_data->'collectionStep'->'numberOfContainers'->>'isInvalid' = 'false' + then NULLIF(all_step_data->'collectionStep'->'numberOfContainers'->>'value','') + else null + end as NUMERIC) as no_of_containers + , CAST(case when all_step_data->'collectionStep'->'volumePerContainers'->>'isInvalid' = 'false' + then NULLIF(all_step_data->'collectionStep'->'volumePerContainers'->>'value','') + else null + end as NUMERIC) as vol_per_container + , TO_CHAR(CAST(case when all_step_data->'collectionStep'->'selectedCollectionCodes'->>'isInvalid' = 'false' + then NULLIF(all_step_data->'collectionStep'->'selectedCollectionCodes'->'value'->>0,'') + else null + end AS NUMERIC),'FM00') as cone_collection_method_code + , TO_CHAR(CAST(case when all_step_data->'collectionStep'->'selectedCollectionCodes'->>'isInvalid' = 'false' + then NULLIF(all_step_data->'collectionStep'->'selectedCollectionCodes'->'value'->>1,'') + else null + end AS NUMERIC),'FM00') as cone_collection_method2_code + , case when all_step_data->'collectionStep'->'comments'->>'isInvalid' = 'false' + then NULLIF(all_step_data->'collectionStep'->'comments'->>'value','') + else null + end as seedlot_comment + from spar.seedlot_registration_a_class_save + where seedlot_number = %(p_seedlot_number)s) SELECT s.applicant_client_number, s.applicant_email_address, s.applicant_locn_code AS applicant_client_locn, - s.approved_timestamp, + s.approved_timestamp AT TIME ZONE 'UTC' AT TIME ZONE 'America/Los_Angeles' AS approved_timestamp, replace(s.approved_userid, '\', '@') approved_userid -- 'Replacing @ to \ for Provider@User - ,CASE WHEN s.BC_SOURCE_IND THEN 'Y' ELSE 'N' END as BC_SOURCE_IND - ,s.BEC_VERSION_ID - ,s.BGC_SUBZONE_CODE - ,s.BGC_ZONE_CODE - ,case when s.BIOTECH_PROCESSES_IND = True then 'Y' when BIOTECH_PROCESSES_IND = False then 'N' else '' end as BIOTECH_PROCESSES_IND - ,s.CLCTN_VOLUME - ,s.collection_locn_code as COLLECTION_CLI_LOCN_CD - ,s.collection_client_number as COLLECTION_CLI_NUMBER - ,s.COLLECTION_ELEVATION - ,s.COLLECTION_ELEVATION_MAX - ,s.COLLECTION_ELEVATION_MIN - ,s.COLLECTION_END_DATE - ,s.COLLECTION_LATITUDE_CODE - ,s.collection_latitude_deg as COLLECTION_LAT_DEG - ,s.collection_latitude_min as COLLECTION_LAT_MIN - ,s.collection_latitude_sec as COLLECTION_LAT_SEC - ,s.COLLECTION_LONGITUDE_CODE - ,s.collection_longitude_deg as COLLECTION_LONG_DEG - ,s.collection_longitude_min as COLLECTION_LONG_MIN - ,s.collection_longitude_sec as COLLECTION_LONG_SEC - ,s.COLLECTION_START_DATE - ,scm1.cone_collection_method_code - ,scm2.cone_collection_method_code as CONE_COLLECTION_METHOD2_CODE - ,s.CONTAMINANT_POLLEN_BV - ,case when s.CONTROLLED_CROSS_IND = True then 'Y' when CONTROLLED_CROSS_IND = False then 'N' else '' end as CONTROLLED_CROSS_IND - ,s.DECLARED_TIMESTAMP - ,REPLACE(s.DECLARED_USERID,'\', '@') DECLARED_USERID -- 'Replacing @ to \ for Provider@User - ,s.EFFECTIVE_POP_SIZE - ,s.ELEVATION - ,s.ELEVATION_MAX - ,s.ELEVATION_MIN - ,s.ENTRY_TIMESTAMP - ,REPLACE(s.ENTRY_USERID,'\', '@') ENTRY_USERID -- 'Replacing @ to \ for Provider@User - ,s.EXTRACTION_END_DATE - ,s.EXTRACTION_ST_DATE - ,s.extractory_client_number as EXTRCT_CLI_NUMBER - ,s.extractory_locn_code as EXTRCT_CLI_LOCN_CD - ,s.FEMALE_GAMETIC_MTHD_CODE - ,s.GENETIC_CLASS_CODE - ,s.INTERM_FACILITY_CODE - ,s.interm_strg_locn_code as INTERM_STRG_CLIENT_LOCN - ,s.INTERM_STRG_CLIENT_NUMBER - ,s.INTERM_STRG_END_DATE - ,s.INTERM_STRG_LOCN - ,s.INTERM_STRG_ST_DATE - ,s.LATITUDE_DEG_MAX - ,s.LATITUDE_DEG_MIN - ,s.LATITUDE_DEGREES - ,s.LATITUDE_MIN_MAX - ,s.LATITUDE_MIN_MIN - ,s.LATITUDE_MINUTES - ,s.LATITUDE_SEC_MAX - ,s.LATITUDE_SEC_MIN - ,s.LATITUDE_SECONDS - ,s.LONGITUDE_DEG_MAX - ,s.LONGITUDE_DEG_MIN - ,s.LONGITUDE_DEGREES - ,s.LONGITUDE_MIN_MAX - ,s.LONGITUDE_MIN_MIN - ,s.LONGITUDE_MINUTES - ,s.LONGITUDE_SEC_MAX - ,s.LONGITUDE_SEC_MIN - ,s.LONGITUDE_SECONDS - ,s.MALE_GAMETIC_MTHD_CODE - ,s.NO_OF_CONTAINERS - ,s.area_of_use_comment as ORCHARD_COMMENT - ,prim.orchard_id - ,sec.secondary_orchard_id - ,case when s.POLLEN_CONTAMINATION_IND = True then 'Y' when POLLEN_CONTAMINATION_IND = False then 'N' else '' end as POLLEN_CONTAMINATION_IND - ,s.POLLEN_CONTAMINATION_MTHD_CODE - ,s.POLLEN_CONTAMINATION_PCT - ,s.REVISION_COUNT - ,s.SEED_PLAN_UNIT_ID - ,s.SEEDLOT_COMMENT - ,s.SEEDLOT_NUMBER - ,s.SEEDLOT_SOURCE_CODE - ,s.SEEDLOT_STATUS_CODE - ,s.temporary_strg_locn_code as SEED_STORE_CLIENT_LOCN - ,s.temporary_strg_client_number as SEED_STORE_CLIENT_NUMBER - ,s.SMP_MEAN_BV_GROWTH - ,s.SMP_PARENTS_OUTSIDE - ,s.SMP_SUCCESS_PCT - ,s.temporary_strg_end_date::date as TEMPORARY_STORAGE_END_DATE - ,s.temporary_strg_start_date::date as TEMPORARY_STORAGE_START_DATE - ,CASE WHEN s.TO_BE_REGISTRD_IND THEN 'Y' ELSE 'N' END AS TO_BE_REGISTRD_IND - ,s.TOTAL_PARENT_TREES - ,s.UPDATE_TIMESTAMP - ,REPLACE(s.UPDATE_USERID,'\', '@') UPDATE_USERID -- 'Replacing @ to \ for Provider@User - ,s.VARIANT - ,s.VEGETATION_CODE - ,s.VOL_PER_CONTAINER + ,CASE WHEN s.bc_source_ind THEN 'Y' ELSE 'N' END as bc_source_ind + ,S.bec_version_id + ,S.bgc_subzone_code + ,S.bgc_zone_code + ,case when s.biotech_processes_ind = True then 'Y' when biotech_processes_ind = False then 'N' else '' end as biotech_processes_ind + ,CASE WHEN s.seedlot_status_code = 'PND' THEN drft.clctn_volume + ELSE s.clctn_volume + END as clctn_volume + ,CASE WHEN s.seedlot_status_code = 'PND' THEN drft.collection_cli_locn_cd + ELSE s.collection_locn_code + END as collection_cli_locn_cd + ,CASE WHEN s.seedlot_status_code = 'PND' THEN drft.collection_cli_number + ELSE s.collection_client_number + END as collection_cli_number + ,S.collection_elevation + ,S.collection_elevation_max + ,S.collection_elevation_min + ,CASE WHEN s.seedlot_status_code = 'PND' THEN drft.collection_end_date + ELSE s.collection_end_date::date + END as collection_end_date + ,s.collection_latitude_code + ,s.collection_latitude_deg as collection_lat_deg + ,s.collection_latitude_min as collection_lat_min + ,s.collection_latitude_sec as collection_lat_sec + ,s.collection_longitude_code + ,s.collection_longitude_deg as collection_long_deg + ,s.collection_longitude_min as collection_long_min + ,s.collection_longitude_sec as collection_long_sec + ,CASE WHEN s.seedlot_status_code = 'PND' THEN drft.collection_start_date + ELSE s.collection_start_date::date + END as collection_start_date + ,CASE WHEN s.seedlot_status_code = 'PND' THEN drft.cone_collection_method_code + ELSE scm1.cone_collection_method_code + END as cone_collection_method_code + ,CASE WHEN s.seedlot_status_code = 'PND' THEN drft.cone_collection_method2_code + ELSE scm2.cone_collection_method_code + END as cone_collection_method2_code + ,s.contaminant_pollen_bv + ,CASE WHEN s.controlled_cross_ind = True THEN 'Y' WHEN controlled_cross_ind = False THEN 'N' ELSE '' END as CONTROLLED_CROSS_IND + ,s.declared_timestamp AT TIME ZONE 'UTC' AT TIME ZONE 'America/Los_Angeles' AS declared_timestamp + ,REPLACE(s.declared_userid,'\', '@') declared_userid -- 'Replacing @ to \ for Provider@User + ,s.effective_pop_size + ,s.elevation + ,s.elevation_max + ,s.elevation_min + ,s.entry_timestamp AT TIME ZONE 'UTC' AT TIME ZONE 'America/Los_Angeles' AS entry_timestamp + ,REPLACE(s.entry_userid,'\', '@') entry_userid -- 'Replacing @ to \ for Provider@User + ,S.extraction_end_date::date + ,S.extraction_st_date::date + ,CASE WHEN s.seedlot_status_code = 'PND' THEN drft.extrct_cli_number + ELSE s.extractory_client_number + END as extrct_cli_number + ,CASE WHEN s.seedlot_status_code = 'PND' THEN drft.extrct_cli_locn_cd + ELSE s.extractory_locn_code + END as extrct_cli_locn_cd + ,s.female_gametic_mthd_code + ,S.genetic_class_code + ,CASE WHEN s.seedlot_status_code = 'PND' THEN drft.interm_facility_code + ELSE s.interm_facility_code + END as interm_facility_code + ,s.interm_strg_locn_code as interm_strg_client_locn + ,S.interm_strg_client_number + ,S.interm_strg_end_date::date + ,S.interm_strg_locn + ,S.interm_strg_st_date::date + ,S.latitude_deg_max + ,S.latitude_deg_min + ,S.latitude_degrees + ,S.latitude_min_max + ,S.latitude_min_min + ,S.latitude_minutes + ,S.latitude_sec_max + ,S.latitude_sec_min + ,S.latitude_seconds + ,S.longitude_deg_max + ,S.longitude_deg_min + ,S.longitude_degrees + ,S.longitude_min_max + ,S.longitude_min_min + ,S.longitude_minutes + ,S.longitude_sec_max + ,S.longitude_sec_min + ,S.longitude_seconds + ,S.male_gametic_mthd_code + ,CASE WHEN s.seedlot_status_code = 'PND' THEN drft.no_of_containers + ELSE s.no_of_containers + END no_of_containers + ,s.area_of_use_comment as orchard_comment + ,CASE WHEN s.seedlot_status_code = 'PND' THEN drft.orchard_id + ELSE prim.orchard_id + END orchard_id + ,CASE WHEN s.seedlot_status_code = 'PND' THEN drft.secondary_orchard_id + ELSE sec.secondary_orchard_id + END secondary_orchard_id + ,CASE WHEN s.pollen_contamination_ind = True THEN 'Y' WHEN pollen_contamination_ind = False THEN 'N' ELSE '' END as pollen_contamination_ind + ,s.pollen_contamination_mthd_code + ,s.pollen_contamination_pct + ,s.revision_count + ,s.seed_plan_unit_id + ,s.seedlot_comment + ,s.seedlot_number + ,s.seedlot_source_code + ,s.seedlot_status_code + ,s.temporary_strg_locn_code as seed_store_client_locn + ,s.temporary_strg_client_number as seed_store_client_number + ,s.smp_mean_bv_growth + ,s.smp_parents_outside + ,s.smp_success_pct + ,s.temporary_strg_end_date::date as temporary_storage_end_date + ,s.temporary_strg_start_date::date as temporary_storage_start_date + ,CASE WHEN s.to_be_registrd_ind THEN 'Y' Else 'N' END as to_be_registrd_ind + ,s.total_parent_trees + ,s.update_timestamp AT TIME ZONE 'UTC' AT TIME ZONE 'America/Los_Angeles' AS update_timestamp + ,REPLACE(s.update_userid,'\', '@') update_userid -- 'Replacing @ to \ for Provider@User + ,s.variant + ,s.vegetation_code + ,CASE WHEN s.seedlot_status_code = 'PND' THEN drft.vol_per_container + ELSE s.vol_per_container + END vol_per_container FROM spar.seedlot s LEFT OUTER JOIN seedlot_coll_methods scm1 ON (scm1.seedlot_number = s.seedlot_number AND scm1.rown = 1) @@ -110,14 +208,19 @@ LEFT OUTER JOIN seedlot_coll_methods scm2 LEFT OUTER JOIN (SELECT po.seedlot_number , po.orchard_id FROM spar.seedlot_orchard po - WHERE po.primary_ind = 'Y') prim + WHERE po.primary_ind = True) prim ON prim.seedlot_number = s.seedlot_number LEFT OUTER JOIN (SELECT DISTINCT so.seedlot_number , FIRST_VALUE(so.orchard_id) OVER (PARTITION BY so.seedlot_number ORDER BY so.entry_timestamp) secondary_orchard_id FROM spar.seedlot_orchard so - WHERE so.primary_ind = 'N') sec + WHERE so.primary_ind = False) sec ON sec.seedlot_number = s.seedlot_number +LEFT OUTER JOIN spar.active_orchard_spu ospu + ON ospu.orchard_id = prim.orchard_id + AND ospu.active_ind = True +LEFT OUTER JOIN draft_seedlots drft + ON drft.seedlot_number = s.seedlot_number WHERE s.seedlot_number = %(p_seedlot_number)s -order by seedlot_number desc +ORDER BY s.seedlot_number DESC \ No newline at end of file diff --git a/sync/config/SQL/SPAR/POSTGRES_SEEDLOT_GENETIC_WORTH_EXTRACT.sql b/sync/config/SQL/SPAR/POSTGRES_SEEDLOT_GENETIC_WORTH_EXTRACT.sql index 02ed8941f..ddbdff1b5 100644 --- a/sync/config/SQL/SPAR/POSTGRES_SEEDLOT_GENETIC_WORTH_EXTRACT.sql +++ b/sync/config/SQL/SPAR/POSTGRES_SEEDLOT_GENETIC_WORTH_EXTRACT.sql @@ -1,13 +1,17 @@ SELECT - seedlot_number -, genetic_worth_code -, genetic_quality_value genetic_worth_rtng -, REPLACE(entry_userid,'\', '@') as entry_userid -, entry_timestamp -, REPLACE(update_userid,'\', '@') as update_userid -, update_timestamp -, revision_count + sgw.seedlot_number +, sgw.genetic_worth_code +, sgw.genetic_quality_value genetic_worth_rtng +, REPLACE(sgw.entry_userid,'\', '@') as entry_userid +, sgw.entry_timestamp AT TIME ZONE 'UTC' AT TIME ZONE 'America/Los_Angeles' AS entry_timestamp +, REPLACE(sgw.update_userid,'\', '@') as update_userid +, sgw.update_timestamp AT TIME ZONE 'UTC' AT TIME ZONE 'America/Los_Angeles' AS update_timestamp +, sgw.revision_count FROM spar.seedlot_genetic_worth sgw + JOIN spar.seedlot s + ON s.seedlot_number = sgw.seedlot_number WHERE sgw.seedlot_number = %(p_seedlot_number)s -ORDER BY seedlot_number - , genetic_worth_code \ No newline at end of file + AND s.seedlot_status_code != 'PND' +ORDER BY sgw.seedlot_number + , sgw.genetic_worth_code + \ No newline at end of file diff --git a/sync/config/SQL/SPAR/POSTGRES_SEEDLOT_OWNER_QUANTITY_EXTRACT.sql b/sync/config/SQL/SPAR/POSTGRES_SEEDLOT_OWNER_QUANTITY_EXTRACT.sql index bcb80d534..8f249a749 100644 --- a/sync/config/SQL/SPAR/POSTGRES_SEEDLOT_OWNER_QUANTITY_EXTRACT.sql +++ b/sync/config/SQL/SPAR/POSTGRES_SEEDLOT_OWNER_QUANTITY_EXTRACT.sql @@ -1,21 +1,71 @@ SELECT - seedlot_number -, owner_client_number client_number -, owner_locn_code client_locn_code -, original_pct_owned -, original_pct_rsrvd -, original_pct_srpls + soq.seedlot_number +, soq.owner_client_number client_number +, soq.owner_locn_code client_locn_code +, soq.original_pct_owned +, soq.original_pct_rsrvd +, soq.original_pct_srpls , 0 qty_reserved , 0 qty_rsrvd_cmtd_pln , 0 qty_rsrvd_cmtd_apr , 0 qty_surplus , 0 qty_srpls_cmtd_pln , 0 qty_srpls_cmtd_apr -, method_of_payment_code -, spar_fund_srce_code -, revision_count +, soq.method_of_payment_code +, soq.spar_fund_srce_code +, soq.revision_count FROM spar.seedlot_owner_quantity soq + JOIN spar.seedlot s + ON s.seedlot_number = soq.seedlot_number WHERE soq.seedlot_number = %(p_seedlot_number)s -ORDER BY seedlot_number - , owner_client_number - , owner_locn_code \ No newline at end of file + AND s.seedlot_status_code != 'PND' +UNION ALL +SELECT drft.seedlot_number + , CASE WHEN ownerdata->'ownerAgency'->>'isInvalid' = 'false' + AND ownerdata->'ownerCode'->>'isInvalid' = 'false' + THEN NULLIF(ownerdata->'ownerAgency'->>'value','') + ELSE NULL + END AS client_number + , CASE WHEN ownerdata->'ownerAgency'->>'isInvalid' = 'false' + AND ownerdata->'ownerCode'->>'isInvalid' = 'false' + THEN NULLIF(ownerdata->'ownerCode'->>'value','') + ELSE NULL + END AS client_locn_code + , CAST(CASE WHEN ownerdata->'ownerPortion'->>'isInvalid' = 'false' + THEN NULLIF(ownerdata->'ownerPortion'->>'value','') + ELSE NULL + End AS NUMERIC) as original_pct_owned + , CAST(CASE WHEN ownerdata->'reservedPerc'->>'isInvalid' = 'false' + THEN NULLIF(ownerdata->'reservedPerc'->>'value','') + ELSE NULL + END AS NUMERIC) as original_pct_rsrvd + , CAST(CASE WHEN ownerdata->'surplusPerc'->>'isInvalid' = 'false' + Then NULLIF(ownerdata->'surplusPerc'->>'value','') + ELSE NULL + END AS NUMERIC) as original_pct_srpls + , 0 qty_reserved + , 0 qty_rsrvd_cmtd_pln + , 0 qty_rsrvd_cmtd_apr + , 0 qty_surplus + , 0 qty_srpls_cmtd_pln + , 0 qty_srpls_cmtd_apr + , CAST(NULL AS VARCHAR) AS method_of_payment_code + , CASE WHEN ownerdata->'fundingSource'->>'isInvalid' = 'false' + THEN ownerdata->'fundingSource'->'value'->>'code' + ELSE NULL + END AS spar_fund_srce_code + , drft.revision_count + FROM spar.seedlot_registration_a_class_save drft + LEFT JOIN spar.seedlot s ON drft.seedlot_number = s.seedlot_number + , json_array_elements(cast(all_step_data->'ownershipStep' as json)) ownerdata + WHERE drft.seedlot_number = %(p_seedlot_number)s + AND s.seedlot_status_code = 'PND' + AND NOT(drft.all_step_data @? '$.ownershipStep[*].*.isInvalid ? (@ == true)') + AND TRIM(ownerdata->'ownerAgency'->>'value') != '' + AND TRIM(ownerdata->'ownerCode'->>'value') != '' + AND TRIM(ownerdata->'ownerPortion'->>'value') != '' + AND TRIM(ownerdata->'reservedPerc'->>'value') != '' + AND TRIM(ownerdata->'surplusPerc'->>'value') != '' +ORDER BY 1 + , 2 + , 3 \ No newline at end of file diff --git a/sync/config/SQL/SPAR/POSTGRES_SEEDLOT_PARENT_TREE_EXTRACT.sql b/sync/config/SQL/SPAR/POSTGRES_SEEDLOT_PARENT_TREE_EXTRACT.sql index f446d8dde..1906ebb4e 100644 --- a/sync/config/SQL/SPAR/POSTGRES_SEEDLOT_PARENT_TREE_EXTRACT.sql +++ b/sync/config/SQL/SPAR/POSTGRES_SEEDLOT_PARENT_TREE_EXTRACT.sql @@ -8,6 +8,9 @@ SELECT , COALESCE(spt.total_genetic_worth_contrib,0) total_genetic_worth_contrib , spt.revision_count FROM spar.seedlot_parent_tree spt + JOIN spar.seedlot s + ON s.seedlot_number = spt.seedlot_number WHERE spt.seedlot_number = %(p_seedlot_number)s + AND s.seedlot_status_code != 'PND' ORDER BY spt.seedlot_number - , spt.parent_tree_id \ No newline at end of file + , spt.parent_tree_id \ No newline at end of file diff --git a/sync/config/SQL/SPAR/POSTGRES_SEEDLOT_PARENT_TREE_GEN_QLTY.sql b/sync/config/SQL/SPAR/POSTGRES_SEEDLOT_PARENT_TREE_GEN_QLTY.sql index 509d95689..1f20563fd 100644 --- a/sync/config/SQL/SPAR/POSTGRES_SEEDLOT_PARENT_TREE_GEN_QLTY.sql +++ b/sync/config/SQL/SPAR/POSTGRES_SEEDLOT_PARENT_TREE_GEN_QLTY.sql @@ -9,6 +9,8 @@ SELECT , aos.seed_plan_unit_id , soqgq.revision_count FROM spar.seedlot_parent_tree_gen_qlty soqgq + JOIN spar.seedlot s + ON s.seedlot_number = soqgq.seedlot_number LEFT OUTER JOIN spar.seedlot_orchard so @@ -19,5 +21,6 @@ SELECT JOIN spar.active_orchard_spu aos ON aos.orchard_id = so.orchard_id WHERE soqgq.seedlot_number = %(p_seedlot_number)s + AND s.seedlot_status_code != 'PND' ORDER BY soqgq.seedlot_number - , soqgq.parent_tree_id \ No newline at end of file + , soqgq.parent_tree_id \ No newline at end of file diff --git a/sync/config/SQL/SPAR/POSTGRES_SEEDLOT_PARENT_TREE_SMP_MIX.sql b/sync/config/SQL/SPAR/POSTGRES_SEEDLOT_PARENT_TREE_SMP_MIX.sql index 82b1ff595..07afb5ff6 100644 --- a/sync/config/SQL/SPAR/POSTGRES_SEEDLOT_PARENT_TREE_SMP_MIX.sql +++ b/sync/config/SQL/SPAR/POSTGRES_SEEDLOT_PARENT_TREE_SMP_MIX.sql @@ -1,11 +1,14 @@ SELECT - seedlot_number -, parent_tree_id -, genetic_type_code -, genetic_worth_code -, genetic_quality_value smp_mix_value -, revision_count - FROM spar.seedlot_parent_tree_smp_mix -WHERE seedlot_number = %(p_seedlot_number)s -ORDER BY seedlot_number - , parent_tree_id + smp.seedlot_number +, smp.parent_tree_id +, smp.genetic_type_code +, smp.genetic_worth_code +, smp.genetic_quality_value smp_mix_value +, smp.revision_count + FROM spar.seedlot_parent_tree_smp_mix smp + JOIN spar.seedlot s + ON s.seedlot_number = smp.seedlot_number +WHERE smp.seedlot_number = %(p_seedlot_number)s + AND s.seedlot_status_code != 'PND' +ORDER BY smp.seedlot_number + , smp.parent_tree_id diff --git a/sync/config/SQL/SPAR/POSTGRES_SEEDLOT_SEED_PLAN_ZONE_EXTRACT.sql b/sync/config/SQL/SPAR/POSTGRES_SEEDLOT_SEED_PLAN_ZONE_EXTRACT.sql index 8ff673667..99cfcacc3 100644 --- a/sync/config/SQL/SPAR/POSTGRES_SEEDLOT_SEED_PLAN_ZONE_EXTRACT.sql +++ b/sync/config/SQL/SPAR/POSTGRES_SEEDLOT_SEED_PLAN_ZONE_EXTRACT.sql @@ -3,9 +3,12 @@ SELECT , slpz.seed_plan_zone_code , CASE slpz.primary_ind WHEN TRUE THEN 'Y' ELSE 'N' END primary_ind , REPLACE(slpz.entry_userid,'\', '@') as entry_userid -, slpz.entry_timestamp +, slpz.entry_timestamp AT TIME ZONE 'UTC' AT TIME ZONE 'America/Los_Angeles' AS entry_timestamp , slpz.revision_count FROM spar.seedlot_seed_plan_zone slpz + JOIN spar.seedlot s + ON s.seedlot_number = slpz.seedlot_number WHERE slpz.seedlot_number = %(p_seedlot_number)s + AND s.seedlot_status_code != 'PND' ORDER BY slpz.seedlot_number , slpz.primary_ind \ No newline at end of file diff --git a/sync/config/SQL/SPAR/POSTGRES_SMP_MIX_EXTRACT.sql b/sync/config/SQL/SPAR/POSTGRES_SMP_MIX_EXTRACT.sql index d43fbced6..b62d859e2 100644 --- a/sync/config/SQL/SPAR/POSTGRES_SMP_MIX_EXTRACT.sql +++ b/sync/config/SQL/SPAR/POSTGRES_SMP_MIX_EXTRACT.sql @@ -4,6 +4,9 @@ SELECT , smp.amount_of_material , smp.revision_count FROM spar.smp_mix smp + JOIN spar.seedlot s + ON s.seedlot_number = smp.seedlot_number WHERE smp.seedlot_number = %(p_seedlot_number)s + AND s.seedlot_status_code != 'PND' ORDER BY smp.seedlot_number , smp.parent_tree_id \ No newline at end of file diff --git a/sync/config/SQL/SPAR/POSTGRES_SMP_MIX_GEN_QLTY_EXTRACT.sql b/sync/config/SQL/SPAR/POSTGRES_SMP_MIX_GEN_QLTY_EXTRACT.sql index 9477ff1d0..9dc1e703a 100644 --- a/sync/config/SQL/SPAR/POSTGRES_SMP_MIX_GEN_QLTY_EXTRACT.sql +++ b/sync/config/SQL/SPAR/POSTGRES_SMP_MIX_GEN_QLTY_EXTRACT.sql @@ -7,6 +7,9 @@ SELECT , CASE smpgq.estimated_ind WHEN TRUE THEN 'Y' ELSE 'N' END estimated_ind , smpgq.revision_count FROM spar.smp_mix_gen_qlty smpgq + JOIN spar.seedlot s + ON s.seedlot_number = smpgq.seedlot_number WHERE smpgq.seedlot_number = %(p_seedlot_number)s + AND s.seedlot_status_code != 'PND' ORDER BY smpgq.seedlot_number , smpgq.parent_tree_id \ No newline at end of file diff --git a/sync/oc_run.sh b/sync/oc_run.sh new file mode 100755 index 000000000..8393e3886 --- /dev/null +++ b/sync/oc_run.sh @@ -0,0 +1,33 @@ +#!/bin/sh +# +# Exit on errors or unset variables +set -eu + +# Run and verify ETL jobs in OpenShift +# +# Usage: ./oc_run.sh [pr#|test|prod] [optional:token] + +# Check inputs +if [ -z "${1:-}" ]; then + echo -e "\nAn environment input is required. Exiting.\n" + exit 1 +fi + +# Login (optional) +if [ ! -z "${2:-}" ]; then + oc login --token=${2} --server=https://api.silver.devops.gov.bc.ca:6443 + oc project #Safeguard! +fi + +# Create job +CRONJOB=nr-spar-${1}-sync +RUN_JOB=${CRONJOB}--$(date +"%Y-%m-%d--%H-%M-%S") +oc create job ${RUN_JOB} --from=cronjob/${CRONJOB} + +# Follow +oc wait --for=condition=ready pod --selector=job-name=${RUN_JOB} --timeout=5m +oc logs -l job-name=${RUN_JOB} --tail=50 --follow + +# Verify successful completion +oc wait --for jsonpath='{.status.phase}'=Succeeded pod --selector=job-name=${RUN_JOB} --timeout=1m +echo "Job successful!" diff --git a/sync/openshift.deploy.yml b/sync/openshift.deploy.yml index 4aa5d7eac..c607fcbd5 100644 --- a/sync/openshift.deploy.yml +++ b/sync/openshift.deploy.yml @@ -13,6 +13,9 @@ parameters: - name: APP description: Application/component name value: sync + - name: ORACLE_PORT + description: Oracle database port + value: "1543" - name: EXECUTION_ID description: Process execution ID for running ETL Tool value: "100" @@ -27,16 +30,12 @@ parameters: value: "false" ### Usually a bad idea - not recommended - - name: CRON_MINUTES - description: Random number, 0-60, for scheduling cronjobs - from: "[0-5]{1}[0-9]{1}" - generate: expression - name: JOB_BACKOFF_LIMIT description: "The number of attempts to try for a successful job outcome" - value: "3" + value: "0" - name: JOB_HISTORY_FAIL description: "The number of failed jobs that will be retained" - value: "2" + value: "1" - name: JOB_HISTORY_SUCCESS description: "The number of successful jobs that will be retained" value: "5" @@ -83,27 +82,24 @@ objects: - name: ORACLE_HOST valueFrom: secretKeyRef: - name: ${REPO}-${ZONE}-oracle-api + name: ${REPO}-${ZONE}-sync key: oracle-host - name: ORACLE_SYNC_PASSWORD valueFrom: secretKeyRef: - name: ${REPO}-${ZONE}-oracle-api + name: ${REPO}-${ZONE}-sync key: oracle-sync-password - name: ORACLE_PORT - valueFrom: - secretKeyRef: - name: ${REPO}-${ZONE}-oracle-api - key: oracle-port + value: ${ORACLE_PORT} - name: ORACLE_SERVICE valueFrom: secretKeyRef: - name: ${REPO}-${ZONE}-oracle-api + name: ${REPO}-${ZONE}-sync key: oracle-service - name: ORACLE_SYNC_USER valueFrom: secretKeyRef: - name: ${REPO}-${ZONE}-oracle-api + name: ${REPO}-${ZONE}-sync key: oracle-sync-user - name: POSTGRES_HOST value: ${REPO}-${ZONE}-database diff --git a/sync/requirements.txt b/sync/requirements.txt index acb761fa2..5dff317a4 100644 --- a/sync/requirements.txt +++ b/sync/requirements.txt @@ -1,7 +1,7 @@ #cx-Oracle==8.3.0 oracledb -numpy==2.0.0 +numpy==2.1.0 pandas==2.2.2 psycopg2==2.9.9 -SQLAlchemy==2.0.31 -pyyaml==6.0.1 +SQLAlchemy==2.0.32 +pyyaml==6.0.2 diff --git a/sync/src/main.py b/sync/src/main.py index 201ebca8c..8f45a94c2 100644 --- a/sync/src/main.py +++ b/sync/src/main.py @@ -46,6 +46,9 @@ def generate_db_config(type_,schema_,settings): } return dbconfig +def get_build_number(): + return os.environ.get("BUILD_NUMBER") + def required_variables_exists(): ret = True @@ -108,45 +111,58 @@ def read_settings(): print(f"A fatal error has occurred when trying to load settings.yml ({type(err)}): {err}") -def main() -> None: - definition_of_yes = ["Y","YES","1","T","TRUE","t","true"] - # print(os.environ.get("TEST_MODE")) - if os.environ.get("TEST_MODE") is None: - print("Error: test mode variable is None") - elif os.environ.get("EXECUTION_ID") is None: - print("Error: EXECUTION_ID is None, no execution defined to be executed in this run.") - else: - this_is_a_test = os.environ.get("TEST_MODE") - settings = read_settings() - print("<------------------ settings ----------------->") - print(settings) - print("<------------------ settings ----------------->") - if this_is_a_test in definition_of_yes: - print("Executing in Test mode") - required_variables_exists() - testPostgresConnection(settings["postgres"]) - testOracleConnection(settings["oracle"]) - # Vault disabled - # testVault() - else: - print("-------------------------------------") - print("Starting ETL main process ") - print("-------------------------------------") - - dbOracle = generate_db_config("ORACLE","THE",settings["oracle"]) - dbPostgres = generate_db_config("POSTGRES","spar",settings["postgres"]) - - execute_etl(dbPostgres, dbOracle) +def main() -> int: + try: + definition_of_yes = ["Y","YES","1","T","TRUE","t","true"] + job_return_code = 0 + + build_number = get_build_number() + print("<------------------ b.u.i.l.d n.u.m.b.e.r ----------------->") + print(f"Running Sync BUILD NUMBER: {build_number}") + print("<------------------ b.u.i.l.d n.u.m.b.e.r ----------------->") - print("-------------------------------------") - print("ETL Main process finished ") - print("-------------------------------------") + # print(os.environ.get("TEST_MODE")) + if os.environ.get("TEST_MODE") is None: + print("Error: test mode variable is None") + elif os.environ.get("EXECUTION_ID") is None: + print("Error: EXECUTION_ID is None, no execution defined to be executed in this run.") + else: + this_is_a_test = os.environ.get("TEST_MODE") + settings = read_settings() + print("<------------------ settings ----------------->") + print(settings) + print("<------------------ settings ----------------->") + if this_is_a_test in definition_of_yes: + print("Executing in Test mode") + required_variables_exists() + testPostgresConnection(settings["postgres"]) + testOracleConnection(settings["oracle"]) + # Vault disabled + # testVault() + else: + print("-------------------------------------") + print("Starting ETL main process ") + print("-------------------------------------") + + dbOracle = generate_db_config("ORACLE","THE",settings["oracle"]) + dbPostgres = generate_db_config("POSTGRES","spar",settings["postgres"]) + + job_return_code = execute_etl(dbPostgres, dbOracle) + + print("-------------------------------------") + print("ETL Main process finished ") + print("-------------------------------------") + return job_return_code + + except Exception as err: + print(f"A fatal error has occurred ({type(err)}): {err}") + return 1 #failure # MAIN Execution -def execute_etl(dbPostgres, dbOracle) -> None: +def execute_etl(dbPostgres, dbOracle): loggingBasicConfig(level=loggingDEBUG, stream=sys.stdout) - data_sync.execute_instance( oracle_config = dbOracle, postgres_config = dbPostgres ,track_config = dbPostgres) + return data_sync.execute_instance( oracle_config = dbOracle, postgres_config = dbPostgres ,track_config = dbPostgres) if __name__ == '__main__': - main() + sys.exit(main()) diff --git a/sync/src/module/data_sync_control.py b/sync/src/module/data_sync_control.py index fae02abcb..c2b81c338 100644 --- a/sync/src/module/data_sync_control.py +++ b/sync/src/module/data_sync_control.py @@ -41,7 +41,7 @@ def get_scheduler(track_db_conn:object, database_schema:str) -> list: select_sync_id_stm = f""" select COALESCE(MAX(to_timestamp) + INTERVAL '1 microsecond' - , CURRENT_TIMESTAMP - INTERVAL '2 days') as current_start_time, + , CURRENT_TIMESTAMP - INTERVAL '3 days') as current_start_time, CURRENT_TIMESTAMP as current_end_time, (select run_status from spar.etl_execution_log diff --git a/sync/src/module/data_synchronization.py b/sync/src/module/data_synchronization.py index acb0b85fc..2f77a75b8 100644 --- a/sync/src/module/data_synchronization.py +++ b/sync/src/module/data_synchronization.py @@ -37,6 +37,7 @@ def execute_instance(oracle_config, postgres_config, track_config): current_cwd = path.join(path.abspath(path.dirname(__file__).split('src')[0]) , "config") logger.info('Initializing Tracking Database Connection') is_error = False + job_return_code = 1 #fail with db_conn.database_connection(track_config) as track_db_conn: temp_time = time.time() @@ -50,6 +51,7 @@ def execute_instance(oracle_config, postgres_config, track_config): #if job is already running, stop if schedule_times['last_run_status'] == "RUNNING": + logger.info("Critical error: previous job still RUNNING or did not report SUCCESS or FAILURE") raise Exception("Critical error: previous job still RUNNING or did not report SUCCESS or FAILURE") logger.info('Insert execution log - signal RUNNING') @@ -66,7 +68,9 @@ def execute_instance(oracle_config, postgres_config, track_config): #if not data_sync_ctl.validate_execution_map(execution_map): # raise ETLConfigurationException ("ETL configuration validation failed") - process_seedlots(oracle_config, postgres_config, track_config, track_db_conn, schedule_times) + seedlot_metrics = process_seedlots(oracle_config, postgres_config, track_config, track_db_conn, schedule_times) + if "ERROR" in seedlot_metrics: + raise Exception(seedlot_metrics["ERROR"]) # Exception when validate_execution_map is false except ETLConfigurationException: @@ -83,10 +87,12 @@ def execute_instance(oracle_config, postgres_config, track_config): logger.info('***** ETL Process finished with error *****') logger.info(f'ETL Tool whole process took {timedelta(seconds=sync_elapsed_time)}') run_status = "FAILURE" + job_return_code = 1 else: stored_metrics["time_process"]=timedelta(seconds=sync_elapsed_time) print_process_metrics(stored_metrics) run_status = "SUCCESS" + job_return_code = 0 data_sync_ctl.update_execution_log(database_conn=track_db_conn, database_schema=track_config['schema'], @@ -95,6 +101,7 @@ def execute_instance(oracle_config, postgres_config, track_config): run_status=run_status) logger.info('***** Finish ETL Run *****') + return job_return_code def identifyQueryParams(query, db_type, params) -> object: if db_type == 'ORACLE': @@ -280,8 +287,12 @@ def process_seedlots(oracle_config, postgres_config, track_config, track_db_conn logger.debug('Source Database connection established') - query_sql = """SELECT seedlot_number FROM spar.seedlot - WHERE update_timestamp between %(start_time)s AND %(end_time)s + query_sql = """SELECT s.seedlot_number + FROM spar.seedlot s + FULL OUTER JOIN spar.seedlot_registration_a_class_save drft + ON drft.seedlot_number = s.seedlot_number + WHERE s.update_timestamp between %(start_time)s AND %(end_time)s + OR drft.update_timestamp between %(start_time)s AND %(end_time)s ORDER BY seedlot_number """ logger.debug(f"Main driver query to be executed in Source database is: {query_sql}") @@ -348,6 +359,8 @@ def process_seedlots(oracle_config, postgres_config, track_config, track_db_conn logger.critical("A fatal error has occurred", exc_info = True) log_message =f"Error type: {type(err)}: {err}" metrics['end_time'] = datetime.now().strftime('%Y-%m-%d %H:%M:%S.%f') + metrics.setdefault('ERROR', '') + metrics['ERROR'] += log_message + "\r\n" data_sync_ctl.save_execution_log(track_db_conn,track_config['schema'],metrics) seedlot_metrics['processes'] = processlst seedlotlst.append(seedlot_metrics) @@ -357,6 +370,8 @@ def process_seedlots(oracle_config, postgres_config, track_config, track_db_conn logger.critical("A fatal error has occurred", exc_info = True) log_message =f"Error type: {type(err)}: {err}" metrics['end_time'] = datetime.now().strftime('%Y-%m-%d %H:%M:%S.%f') + metrics.setdefault('ERROR', '') + metrics['ERROR'] += log_message + "\r\n" data_sync_ctl.save_execution_log(track_db_conn,track_config['schema'],metrics) #data_sync_ctl.save_execution_log(track_db_conn,track_config['schema'],process["interface_id"],process["execution_id"],process_log) diff --git a/sync/src/module/database_connection.py b/sync/src/module/database_connection.py index 63b2210c3..4d9d0c150 100644 --- a/sync/src/module/database_connection.py +++ b/sync/src/module/database_connection.py @@ -254,6 +254,7 @@ def bulk_upsert_oracle(self, dataframe:object, table_name:str, table_pk:str, run for column in dataframe.columns.values: params["s_"+column] = getattr(row,column) params["q_"+column] = getattr(row,column) + additionalprocessing = "" onconflictstatement = f""" EXCEPTION WHEN DUP_VAL_ON_INDEX THEN @@ -264,22 +265,40 @@ def bulk_upsert_oracle(self, dataframe:object, table_name:str, table_pk:str, run onconflictstatement += """, seedlot_status_code = CASE WHEN (:s_seedlot_status_code='INC' AND seedlot_status_code = 'INC') - OR (:s_seedlot_status_code='PND' AND seedlot_status_code IN ('INC','PND')) + --SUB is a special case for when new spar reverts from SUB to PND + OR (:s_seedlot_status_code='PND' AND seedlot_status_code IN ('INC','PND','SUB')) OR (:s_seedlot_status_code='SUB' AND seedlot_status_code IN ('INC','PND','SUB')) OR (:s_seedlot_status_code='APP' AND seedlot_status_code IN ('INC','PND','SUB','APP')) OR (:s_seedlot_status_code='COM' AND seedlot_status_code IN ('INC','PND','SUB','APP','COM')) THEN :s_seedlot_status_code ELSE seedlot_status_code END """ + additionalprocessing += f"""UPDATE the.seedlot s + SET ( s.bec_version_id, s.bgc_zone_code, s.bgc_subzone_code, s.variant) + = (SELECT o.bec_version_id, o.bec_zone, o.bec_subzone, o.variant + FROM the.orchard o + WHERE o.orchard_id = s.orchard_id) + , seed_plan_zone_code = (SELECT spz.seed_plan_zone_code + FROM seed_plan_unit spu + JOIN seed_plan_zone spz + ON spz.seed_plan_zone_id = spu.seed_plan_zone_id + WHERE spu.seed_plan_unit_id = s.seed_plan_unit_id) + {whStatement} + AND s.seedlot_status_code = 'PND';""" print(params) onconflictstatement += f"{whStatement} {whStatement2};" sql_text = f""" - BEGIN - INSERT INTO {table_name}({', '.join(dataframe.columns.values)}) - VALUES(:{', :'.join(dataframe.columns.values)}) ; - {onconflictstatement} - END; """ + BEGIN + BEGIN + INSERT INTO {table_name}({', '.join(dataframe.columns.values)}) + VALUES(:{', :'.join(dataframe.columns.values)}) ; + {onconflictstatement} + END; + {additionalprocessing} + END;""" + logger.debug(f'---Executing statement for row {i}') logger.debug(sql_text) result = self.conn.execute(text(sql_text), params) + #TODO rowcount will not be accurate due to additionalprocessing rows_affected = result.rowcount self.commit() # If everything is ok, a commit will be executed. diff --git a/tools/pg-test-portforward.sh b/tools/pg-test-portforward.sh index d1e3c5278..c84f41482 100755 --- a/tools/pg-test-portforward.sh +++ b/tools/pg-test-portforward.sh @@ -49,10 +49,9 @@ fi # 3. Get DB name, user and password echo "Getting credentials..." -SECRETS=$(oc extract secret/nr-spar-$TARGET_ENV-database -n b9d53b-$TARGET_ENV --to=- 2> /dev/null) -DB_NAME=$(echo $SECRETS | cut -d ' ' -f 1) -DB_USER=$(echo $SECRETS | cut -d ' ' -f 3) -DB_PASS=$(echo $SECRETS | cut -d ' ' -f 2) +DB_NAME=$(oc extract secret/nr-spar-$TARGET_ENV-database -n b9d53b-$TARGET_ENV --keys=database-name --to=- 2> /dev/null) +DB_USER=$(oc extract secret/nr-spar-$TARGET_ENV-database -n b9d53b-$TARGET_ENV --keys=database-user --to=- 2> /dev/null) +DB_PASS=$(oc extract secret/nr-spar-$TARGET_ENV-database -n b9d53b-$TARGET_ENV --keys=database-password --to=- 2> /dev/null) echo "Use this information to set up the database connection with '$TARGET_ENV' on your end:" echo "- DB_HOST = localhost" echo "- DB_PORT = $PORT"