diff --git a/backend/openshift.deploy.yml b/backend/openshift.deploy.yml
index da13d72ce..7b3ba7213 100644
--- a/backend/openshift.deploy.yml
+++ b/backend/openshift.deploy.yml
@@ -103,12 +103,12 @@ objects:
- name: ALLOWED_ORIGINS
valueFrom:
secretKeyRef:
- name: ${NAME}
+ name: ${NAME}-backend
key: allowed_origins
- name: KEYCLOAK_REALM_URL
valueFrom:
secretKeyRef:
- name: ${NAME}
+ name: ${NAME}-backend
key: keycloak-realm-url
- name: POSTGRESQL_HOST
value: ${NAME}-${ZONE}-database
@@ -130,12 +130,12 @@ objects:
- name: FORESTCLIENTAPI_ADDRESS
valueFrom:
secretKeyRef:
- name: ${NAME}
+ name: ${NAME}-backend
key: forest-client-api.address
- name: FORESTCLIENTAPI_KEY
valueFrom:
secretKeyRef:
- name: ${NAME}
+ name: ${NAME}-backend
key: forest-client-api.key
ports:
- containerPort: 8090
diff --git a/backend/pom.xml b/backend/pom.xml
index b3d64a63c..5600e8a0c 100644
--- a/backend/pom.xml
+++ b/backend/pom.xml
@@ -10,7 +10,7 @@
ca.bc.gov
nr-spar-backend
- 0.18.5
+ 0.19.1
nr-spar-backend
Starting backend API project
diff --git a/backend/src/main/java/ca/bc/gov/backendstartapi/endpoint/ActiveOrchardSeedPlanningUnitEndpoint.java b/backend/src/main/java/ca/bc/gov/backendstartapi/endpoint/ActiveOrchardSeedPlanningUnitEndpoint.java
new file mode 100644
index 000000000..c81c523d1
--- /dev/null
+++ b/backend/src/main/java/ca/bc/gov/backendstartapi/endpoint/ActiveOrchardSeedPlanningUnitEndpoint.java
@@ -0,0 +1,48 @@
+package ca.bc.gov.backendstartapi.endpoint;
+
+import ca.bc.gov.backendstartapi.entity.ActiveOrchardSeedPlanningUnit;
+import ca.bc.gov.backendstartapi.repository.ActiveOrchardSeedPlanningUnitRepository;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.Parameter;
+import io.swagger.v3.oas.annotations.responses.ApiResponse;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import java.util.List;
+import lombok.RequiredArgsConstructor;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.util.MimeTypeUtils;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PathVariable;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.bind.annotation.RestController;
+
+/** Rest controller to fetch relations between orchards and Seed Plan Units (SPU). */
+@RestController
+@RequestMapping(path = "/api/orchards", produces = MimeTypeUtils.APPLICATION_JSON_VALUE)
+@Tag(name = "Orchard")
+@RequiredArgsConstructor
+public class ActiveOrchardSeedPlanningUnitEndpoint {
+
+ private final ActiveOrchardSeedPlanningUnitRepository repository;
+
+ @Operation(
+ operationId = "findSpuByOrchard",
+ summary = "Find associations between seed plan units and an orchard",
+ description =
+ "Find the associations of seed plan units and the orchard identified by `orchardId`.",
+ responses = {
+ @ApiResponse(
+ responseCode = "200",
+ description = "A list of the associations between the orchard and seed plan units.")
+ })
+ @GetMapping(path = "/{orchardId}/seed-plan-units")
+ @PreAuthorize("hasRole('user_read')")
+ public List findByOrchard(
+ @Parameter(description = "The identifier of an orchard") @PathVariable(name = "orchardId")
+ String orchardId,
+ @Parameter(description = "If the association must be active or not")
+ @RequestParam(name = "active", defaultValue = "true")
+ boolean active) {
+ return repository.findByOrchardIdAndActive(orchardId, active);
+ }
+}
diff --git a/backend/src/main/java/ca/bc/gov/backendstartapi/entity/ActiveOrchardSeedPlanningUnit.java b/backend/src/main/java/ca/bc/gov/backendstartapi/entity/ActiveOrchardSeedPlanningUnit.java
new file mode 100644
index 000000000..0a1effea0
--- /dev/null
+++ b/backend/src/main/java/ca/bc/gov/backendstartapi/entity/ActiveOrchardSeedPlanningUnit.java
@@ -0,0 +1,62 @@
+package ca.bc.gov.backendstartapi.entity;
+
+import ca.bc.gov.backendstartapi.entity.idclass.ActiveOrchardSeedPlanningUnitId;
+import io.swagger.v3.oas.annotations.media.Schema;
+import jakarta.persistence.Column;
+import jakarta.persistence.Entity;
+import jakarta.persistence.Id;
+import jakarta.persistence.IdClass;
+import jakarta.persistence.Table;
+import lombok.AccessLevel;
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+import lombok.NonNull;
+import lombok.RequiredArgsConstructor;
+import lombok.Setter;
+
+/** Auxiliary entity connecting an Orchard and a Seed Plan Unit (SPU). */
+@Entity
+@Table(name = "active_orchard_spu")
+@IdClass(ActiveOrchardSeedPlanningUnitId.class)
+@NoArgsConstructor(access = AccessLevel.PRIVATE)
+@RequiredArgsConstructor
+@Getter
+@Setter
+@Schema(description = "An association between an orchard and a Seed Plan Unit (SPU).")
+public class ActiveOrchardSeedPlanningUnit {
+
+ @Id
+ @Column(name = "orchard_id", length = 3, nullable = false)
+ @NonNull
+ private String orchardId;
+
+ @Id
+ @Column(name = "seed_plan_unit_id", nullable = false)
+ @NonNull
+ private int seedPlanningUnitId;
+
+ @Column(name = "active_ind", nullable = false)
+ @NonNull
+ @Schema(
+ description =
+ "If this association is active; if `false`, it should not be used for new registries.")
+ private boolean active;
+
+ @Column(name = "retired_ind", nullable = false)
+ @NonNull
+ @Schema(
+ description =
+ """
+ If the orchard has been retired (e.g. is out of business); could be the reason for the
+ inactivity of this association.""")
+ private boolean retired;
+
+ /**
+ * If the orchard hasn't had a SPU assigned to it. If {@code true}, {@link #seedPlanningUnitId}'s
+ * value will most likely be {@code -1}.
+ */
+ @Column(name = "no_spu_ind", nullable = false)
+ @NonNull
+ @Schema(description = "If this orchard has never had a SPU assigned to it.")
+ private boolean spuNotAssigned;
+}
diff --git a/backend/src/main/java/ca/bc/gov/backendstartapi/entity/idclass/ActiveOrchardSeedPlanningUnitId.java b/backend/src/main/java/ca/bc/gov/backendstartapi/entity/idclass/ActiveOrchardSeedPlanningUnitId.java
new file mode 100644
index 000000000..fc2d7cb26
--- /dev/null
+++ b/backend/src/main/java/ca/bc/gov/backendstartapi/entity/idclass/ActiveOrchardSeedPlanningUnitId.java
@@ -0,0 +1,19 @@
+package ca.bc.gov.backendstartapi.entity.idclass;
+
+import ca.bc.gov.backendstartapi.entity.ActiveOrchardSeedPlanningUnit;
+import lombok.AccessLevel;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+import lombok.NonNull;
+import lombok.RequiredArgsConstructor;
+
+/** Composite key for {@link ActiveOrchardSeedPlanningUnit}. */
+@Data
+@NoArgsConstructor(access = AccessLevel.PRIVATE)
+@RequiredArgsConstructor
+public class ActiveOrchardSeedPlanningUnitId {
+
+ @NonNull private String orchardId;
+
+ @NonNull private int seedPlanningUnitId;
+}
diff --git a/backend/src/main/java/ca/bc/gov/backendstartapi/repository/ActiveOrchardSeedPlanningUnitRepository.java b/backend/src/main/java/ca/bc/gov/backendstartapi/repository/ActiveOrchardSeedPlanningUnitRepository.java
new file mode 100644
index 000000000..bc56bb4c5
--- /dev/null
+++ b/backend/src/main/java/ca/bc/gov/backendstartapi/repository/ActiveOrchardSeedPlanningUnitRepository.java
@@ -0,0 +1,13 @@
+package ca.bc.gov.backendstartapi.repository;
+
+import ca.bc.gov.backendstartapi.entity.ActiveOrchardSeedPlanningUnit;
+import ca.bc.gov.backendstartapi.entity.idclass.ActiveOrchardSeedPlanningUnitId;
+import java.util.List;
+import org.springframework.data.jpa.repository.JpaRepository;
+
+/** The repository for {@link ActiveOrchardSeedPlanningUnit ActiveOrchardSeedPlanningUnits}. */
+public interface ActiveOrchardSeedPlanningUnitRepository
+ extends JpaRepository {
+
+ List findByOrchardIdAndActive(String orchardId, boolean active);
+}
diff --git a/backend/src/main/resources/db/migration/V14__create_active_orchard_spu.sql b/backend/src/main/resources/db/migration/V14__create_active_orchard_spu.sql
new file mode 100644
index 000000000..871c153af
--- /dev/null
+++ b/backend/src/main/resources/db/migration/V14__create_active_orchard_spu.sql
@@ -0,0 +1,8 @@
+create table spar.active_orchard_spu (
+ orchard_id varchar(3) not null,
+ seed_plan_unit_id int not null,
+ active_ind boolean not null,
+ retired_ind boolean not null,
+ no_spu_ind boolean not null,
+ constraint active_orchard_spu_pk
+ primary key(orchard_id, seed_plan_unit_id));
diff --git a/backend/src/main/resources/db/migration/V15__populate_active_orchard_spu.sql b/backend/src/main/resources/db/migration/V15__populate_active_orchard_spu.sql
new file mode 100644
index 000000000..dc03f95bb
--- /dev/null
+++ b/backend/src/main/resources/db/migration/V15__populate_active_orchard_spu.sql
@@ -0,0 +1,262 @@
+insert into spar.active_orchard_spu
+ (orchard_id, seed_plan_unit_id, active_ind, retired_ind, no_spu_ind)
+ values ('101', 7, 'False', 'True', 'False'),
+ ('102', 5, 'True', 'False', 'False'),
+ ('103', 78, 'True', 'False', 'False'),
+ ('105', 140, 'True', 'False', 'False'),
+ ('109', 7, 'False', 'True', 'False'),
+ ('111', 7, 'False', 'True', 'False'),
+ ('114', 6, 'False', 'True', 'False'),
+ ('115', 7, 'False', 'True', 'False'),
+ ('116', 6, 'False', 'True', 'False'),
+ ('118', 55, 'False', 'True', 'False'),
+ ('120', 8, 'False', 'True', 'False'),
+ ('121', 6, 'False', 'True', 'False'),
+ ('122', 7, 'False', 'True', 'False'),
+ ('123', 7, 'False', 'True', 'False'),
+ ('124', 7, 'False', 'True', 'False'),
+ ('126', 22, 'False', 'True', 'False'),
+ ('127', 21, 'False', 'True', 'False'),
+ ('128', 4, 'False', 'True', 'False'),
+ ('129', 1, 'False', 'True', 'False'),
+ ('130', 21, 'False', 'True', 'False'),
+ ('131', 77, 'False', 'True', 'False'),
+ ('132', 22, 'False', 'True', 'False'),
+ ('133', 22, 'False', 'True', 'False'),
+ ('134', 7, 'True', 'False', 'False'),
+ ('135', 1, 'False', 'True', 'False'),
+ ('136', 22, 'False', 'True', 'False'),
+ ('137', 78, 'False', 'True', 'False'),
+ ('138', 78, 'False', 'True', 'False'),
+ ('139', 4, 'False', 'True', 'False'),
+ ('140', 4, 'True', 'False', 'False'),
+ ('141', 1, 'False', 'True', 'False'),
+ ('142', 55, 'False', 'True', 'False'),
+ ('143', 21, 'False', 'True', 'False'),
+ ('145', 54, 'True', 'False', 'False'),
+ ('146', 8, 'False', 'True', 'False'),
+ ('147', 8, 'False', 'True', 'False'),
+ ('148', 4, 'True', 'False', 'False'),
+ ('149', 7, 'False', 'True', 'False'),
+ ('150', 22, 'False', 'True', 'False'),
+ ('151', 68, 'False', 'True', 'False'),
+ ('152', 4, 'True', 'False', 'False'),
+ ('153', 4, 'False', 'True', 'False'),
+ ('154', 7, 'True', 'False', 'False'),
+ ('155', 4, 'False', 'True', 'False'),
+ ('156', 22, 'False', 'True', 'False'),
+ ('157', 55, 'False', 'True', 'False'),
+ ('158', 4, 'False', 'True', 'False'),
+ ('159', -1, 'False', 'True', 'True'),
+ ('160', 1, 'False', 'True', 'False'),
+ ('161', -1, 'False', 'True', 'True'),
+ ('162', 7, 'False', 'True', 'False'),
+ ('164', 78, 'False', 'True', 'False'),
+ ('165', 22, 'False', 'True', 'False'),
+ ('166', 7, 'True', 'False', 'False'),
+ ('168', 7, 'False', 'True', 'False'),
+ ('169', 7, 'False', 'True', 'False'),
+ ('170', 22, 'True', 'False', 'False'),
+ ('171', 4, 'False', 'True', 'False'),
+ ('172', 55, 'True', 'False', 'False'),
+ ('173', 54, 'False', 'True', 'False'),
+ ('174', 54, 'False', 'True', 'False'),
+ ('175', 54, 'True', 'False', 'False'),
+ ('176', 22, 'False', 'True', 'False'),
+ ('177', 7, 'False', 'True', 'False'),
+ ('178', 77, 'False', 'True', 'False'),
+ ('179', 22, 'False', 'True', 'False'),
+ ('180', 1, 'False', 'True', 'False'),
+ ('181', 8, 'True', 'False', 'False'),
+ ('182', 22, 'False', 'True', 'False'),
+ ('183', 7, 'True', 'False', 'False'),
+ ('184', 4, 'True', 'False', 'False'),
+ ('185', 7, 'False', 'True', 'False'),
+ ('186', 4, 'False', 'True', 'False'),
+ ('187', 21, 'True', 'False', 'False'),
+ ('188', 22, 'False', 'True', 'False'),
+ ('189', 4, 'False', 'True', 'False'),
+ ('190', 4, 'True', 'False', 'False'),
+ ('191', 78, 'False', 'True', 'False'),
+ ('192', 55, 'True', 'False', 'False'),
+ ('193', 4, 'False', 'True', 'False'),
+ ('194', 78, 'False', 'True', 'False'),
+ ('195', 55, 'True', 'False', 'False'),
+ ('196', 21, 'True', 'False', 'False'),
+ ('197', 7, 'True', 'False', 'False'),
+ ('198', 4, 'True', 'False', 'False'),
+ ('199', 7, 'True', 'False', 'False'),
+ ('201', 35, 'False', 'True', 'False'),
+ ('202', 35, 'False', 'True', 'False'),
+ ('203', 45, 'False', 'True', 'False'),
+ ('204', 29, 'False', 'True', 'False'),
+ ('205', 68, 'False', 'True', 'False'),
+ ('207', 57, 'True', 'False', 'False'),
+ ('208', 57, 'True', 'False', 'False'),
+ ('209', 68, 'False', 'True', 'False'),
+ ('210', 68, 'False', 'True', 'False'),
+ ('211', 68, 'True', 'False', 'False'),
+ ('212', 90, 'True', 'False', 'False'),
+ ('213', 72, 'True', 'False', 'False'),
+ ('215', 68, 'False', 'True', 'False'),
+ ('216', 68, 'False', 'True', 'False'),
+ ('218', 35, 'True', 'False', 'False'),
+ ('219', 29, 'True', 'False', 'False'),
+ ('220', 45, 'True', 'False', 'False'),
+ ('221', 45, 'True', 'False', 'False'),
+ ('222', 45, 'True', 'False', 'False'),
+ ('223', 35, 'True', 'False', 'False'),
+ ('224', 35, 'False', 'True', 'False'),
+ ('225', 14, 'True', 'False', 'False'),
+ ('226', 16, 'True', 'False', 'False'),
+ ('228', 29, 'True', 'False', 'False'),
+ ('229', 57, 'True', 'False', 'False'),
+ ('231', 10, 'True', 'False', 'False'),
+ ('232', 16, 'True', 'False', 'False'),
+ ('233', 14, 'True', 'False', 'False'),
+ ('234', 29, 'True', 'False', 'False'),
+ ('236', 45, 'True', 'False', 'False'),
+ ('237', 45, 'True', 'False', 'False'),
+ ('238', 35, 'True', 'False', 'False'),
+ ('239', 67, 'True', 'False', 'False'),
+ ('240', 29, 'True', 'False', 'False'),
+ ('241', 35, 'True', 'False', 'False'),
+ ('242', 57, 'True', 'False', 'False'),
+ ('243', 57, 'True', 'False', 'False'),
+ ('244', 45, 'True', 'False', 'False'),
+ ('245', 29, 'True', 'False', 'False'),
+ ('246', 35, 'True', 'False', 'False'),
+ ('247', 68, 'True', 'False', 'False'),
+ ('249', 57, 'True', 'False', 'False'),
+ ('250', 57, 'True', 'False', 'False'),
+ ('301', 89, 'False', 'True', 'False'),
+ ('302', 63, 'False', 'True', 'False'),
+ ('304', 60, 'False', 'True', 'False'),
+ ('305', 89, 'True', 'False', 'False'),
+ ('306', 63, 'True', 'False', 'False'),
+ ('307', 41, 'False', 'True', 'False'),
+ ('308', 51, 'False', 'True', 'False'),
+ ('310', 50, 'True', 'False', 'False'),
+ ('311', 51, 'True', 'False', 'False'),
+ ('313', 41, 'True', 'False', 'False'),
+ ('321', 13, 'True', 'False', 'False'),
+ ('324', 12, 'True', 'False', 'False'),
+ ('332', 24, 'True', 'False', 'False'),
+ ('333', 23, 'True', 'False', 'False'),
+ ('334', 23, 'False', 'True', 'False'),
+ ('335', 85, 'True', 'False', 'False'),
+ ('336', 11, 'True', 'False', 'False'),
+ ('337', 41, 'True', 'False', 'False'),
+ ('338', 51, 'True', 'False', 'False'),
+ ('339', 50, 'True', 'False', 'False'),
+ ('341', 62, 'True', 'False', 'False'),
+ ('342', 74, 'True', 'False', 'False'),
+ ('343', 73, 'True', 'False', 'False'),
+ ('345', -1, 'True', 'False', 'True'),
+ ('346', 135, 'True', 'False', 'False'),
+ ('347', 41, 'True', 'False', 'False'),
+ ('349', 40, 'True', 'False', 'False'),
+ ('350', 51, 'True', 'False', 'False'),
+ ('351', 85, 'True', 'False', 'False'),
+ ('352', 35, 'True', 'False', 'False'),
+ ('353', 80, 'True', 'False', 'False'),
+ ('354', 142, 'True', 'False', 'False'),
+ ('355', 141, 'True', 'False', 'False'),
+ ('356', 60, 'True', 'False', 'False'),
+ ('357', 140, 'True', 'False', 'False'),
+ ('358', 50, 'True', 'False', 'False'),
+ ('401', 7, 'False', 'True', 'False'),
+ ('403', 54, 'True', 'False', 'False'),
+ ('405', 7, 'True', 'False', 'False'),
+ ('406', 6, 'True', 'False', 'False'),
+ ('407', -1, 'True', 'False', 'True'),
+ ('408', 4, 'True', 'False', 'False'),
+ ('409', 130, 'True', 'False', 'False'),
+ ('410', 130, 'True', 'False', 'False'),
+ ('411', 29, 'True', 'False', 'False'),
+ ('412', 29, 'True', 'False', 'False'),
+ ('413', 29, 'True', 'False', 'False'),
+ ('414', 85, 'True', 'False', 'False'),
+ ('415', 85, 'True', 'False', 'False'),
+ ('416', 54, 'True', 'False', 'False'),
+ ('417', 29, 'True', 'False', 'False'),
+ ('418', 29, 'True', 'False', 'False'),
+ ('607', 85, 'False', 'True', 'False'),
+ ('609', 85, 'True', 'False', 'False'),
+ ('610', 56, 'False', 'True', 'False'),
+ ('611', 67, 'False', 'True', 'False'),
+ ('612', 60, 'False', 'True', 'False'),
+ ('620', 57, 'False', 'True', 'False'),
+ ('800', 78, 'True', 'False', 'False'),
+ ('801', 4, 'True', 'False', 'False'),
+ ('802', 78, 'False', 'True', 'False'),
+ ('803', 78, 'True', 'False', 'False'),
+ ('804', 78, 'False', 'True', 'False'),
+ ('805', 78, 'True', 'False', 'False'),
+ ('806', 78, 'False', 'True', 'False'),
+ ('807', 78, 'False', 'True', 'False'),
+ ('808', 78, 'True', 'False', 'False'),
+ ('810', -1, 'False', 'True', 'True'),
+ ('811', 4, 'True', 'False', 'False'),
+ ('813', -1, 'True', 'False', 'True'),
+ ('814', 103, 'False', 'True', 'False'),
+ ('815', -1, 'True', 'False', 'True'),
+ ('816', 103, 'False', 'True', 'False'),
+ ('818', 103, 'False', 'True', 'False'),
+ ('819', 103, 'False', 'True', 'False'),
+ ('820', -1, 'True', 'False', 'True'),
+ ('821', 103, 'False', 'True', 'False'),
+ ('822', 68, 'False', 'True', 'False'),
+ ('823', 68, 'False', 'True', 'False'),
+ ('824', 103, 'True', 'False', 'False'),
+ ('825', 103, 'False', 'True', 'False'),
+ ('826', 103, 'False', 'True', 'False'),
+ ('829', 78, 'False', 'True', 'False'),
+ ('830', 104, 'True', 'False', 'False'),
+ ('831', 108, 'False', 'True', 'False'),
+ ('833', 78, 'False', 'True', 'False'),
+ ('834', -1, 'False', 'True', 'True'),
+ ('835', 7, 'False', 'True', 'False'),
+ ('836', 78, 'True', 'False', 'False'),
+ ('837', -1, 'False', 'True', 'True'),
+ ('838', 7, 'False', 'True', 'False'),
+ ('839', 68, 'False', 'True', 'False'),
+ ('840', 55, 'False', 'True', 'False'),
+ ('841', 103, 'False', 'True', 'False'),
+ ('842', 78, 'True', 'False', 'False'),
+ ('989', -1, 'False', 'True', 'True'),
+ ('990', -1, 'True', 'False', 'True'),
+ ('991', 7, 'False', 'True', 'False'),
+ ('992', 22, 'True', 'False', 'False'),
+ ('993', 4, 'True', 'False', 'False'),
+ ('994', -1, 'True', 'False', 'True'),
+ ('995', -1, 'True', 'False', 'True'),
+ ('996', 7, 'True', 'False', 'False'),
+ ('997', -1, 'True', 'False', 'True'),
+ ('998', 54, 'True', 'False', 'False'),
+ ('999', 85, 'True', 'False', 'False'),
+ ('110', 6, 'False', 'True', 'False'),
+ ('110', 7, 'False', 'True', 'False'),
+ ('144', 55, 'False', 'False', 'False'),
+ ('144', 108, 'True', 'False', 'False'),
+ ('163', 21, 'False', 'True', 'False'),
+ ('163', 22, 'False', 'True', 'False'),
+ ('206', 67, 'True', 'False', 'False'),
+ ('206', 68, 'False', 'False', 'False'),
+ ('214', 67, 'False', 'False', 'False'),
+ ('214', 68, 'True', 'False', 'False'),
+ ('217', 56, 'False', 'True', 'False'),
+ ('217', 57, 'False', 'True', 'False'),
+ ('230', 29, 'True', 'False', 'False'),
+ ('230', 43, 'False', 'False', 'False'),
+ ('235', 29, 'False', 'False', 'False'),
+ ('235', 35, 'True', 'False', 'False'),
+ ('235', 45, 'False', 'False', 'False'),
+ ('303', 73, 'False', 'True', 'False'),
+ ('303', 74, 'False', 'True', 'False'),
+ ('340', 38, 'True', 'False', 'False'),
+ ('340', 39, 'False', 'False', 'False'),
+ ('614', 67, 'False', 'True', 'True'),
+ ('614', 68, 'False', 'True', 'True'),
+ ('809', 103, 'False', 'True', 'False'),
+ ('809', 104, 'False', 'True', 'False');
diff --git a/backend/src/test/java/ca/bc/gov/backendstartapi/endpoint/ActiveOrchardSeedPlanningUnitEndpointTest.java b/backend/src/test/java/ca/bc/gov/backendstartapi/endpoint/ActiveOrchardSeedPlanningUnitEndpointTest.java
new file mode 100644
index 000000000..79b566819
--- /dev/null
+++ b/backend/src/test/java/ca/bc/gov/backendstartapi/endpoint/ActiveOrchardSeedPlanningUnitEndpointTest.java
@@ -0,0 +1,80 @@
+package ca.bc.gov.backendstartapi.endpoint;
+
+import static org.mockito.BDDMockito.given;
+import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
+import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
+import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
+import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
+
+import ca.bc.gov.backendstartapi.entity.ActiveOrchardSeedPlanningUnit;
+import ca.bc.gov.backendstartapi.repository.ActiveOrchardSeedPlanningUnitRepository;
+import java.util.List;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.boot.test.context.SpringBootTest.WebEnvironment;
+import org.springframework.boot.test.mock.mockito.MockBean;
+import org.springframework.http.MediaType;
+import org.springframework.security.test.context.support.WithMockUser;
+import org.springframework.test.context.junit.jupiter.SpringExtension;
+import org.springframework.test.web.servlet.MockMvc;
+import org.springframework.test.web.servlet.setup.MockMvcBuilders;
+import org.springframework.web.context.WebApplicationContext;
+
+@ExtendWith(SpringExtension.class)
+@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT)
+@WithMockUser(roles = "user_read")
+class ActiveOrchardSeedPlanningUnitEndpointTest {
+
+ @MockBean private ActiveOrchardSeedPlanningUnitRepository repository;
+
+ private MockMvc mockMvc;
+
+ private final WebApplicationContext webApplicationContext;
+
+ ActiveOrchardSeedPlanningUnitEndpointTest(WebApplicationContext webApplicationContext) {
+ this.webApplicationContext = webApplicationContext;
+ }
+
+ @BeforeEach
+ void setup() {
+ this.mockMvc = MockMvcBuilders.webAppContextSetup(webApplicationContext).build();
+ }
+
+ @Test
+ void testSearchActiveDefault() throws Exception {
+ List actives =
+ List.of(new ActiveOrchardSeedPlanningUnit("000", 1, true, false, false));
+ List inactives =
+ List.of(new ActiveOrchardSeedPlanningUnit("000", 2, false, false, false));
+ given(repository.findByOrchardIdAndActive("000", true)).willReturn(actives);
+ given(repository.findByOrchardIdAndActive("000", false)).willReturn(inactives);
+
+ mockMvc
+ .perform(get("/api/orchards/000/seed-plan-units").accept(MediaType.APPLICATION_JSON))
+ .andExpect(status().isOk())
+ .andExpect(content().contentType(MediaType.APPLICATION_JSON))
+ .andExpectAll(
+ jsonPath("$[0].active").value("true"), jsonPath("$[0].seedPlanningUnitId").value("1"));
+ }
+
+ @Test
+ void testSearchInactiveDefault() throws Exception {
+ List actives =
+ List.of(new ActiveOrchardSeedPlanningUnit("000", 1, true, false, false));
+ List inactives =
+ List.of(new ActiveOrchardSeedPlanningUnit("000", 2, false, false, false));
+ given(repository.findByOrchardIdAndActive("000", true)).willReturn(actives);
+ given(repository.findByOrchardIdAndActive("000", false)).willReturn(inactives);
+
+ mockMvc
+ .perform(
+ get("/api/orchards/000/seed-plan-units?active=false")
+ .accept(MediaType.APPLICATION_JSON))
+ .andExpect(status().isOk())
+ .andExpect(content().contentType(MediaType.APPLICATION_JSON))
+ .andExpectAll(
+ jsonPath("$[0].active").value("false"), jsonPath("$[0].seedPlanningUnitId").value("2"));
+ }
+}
diff --git a/backend/src/test/java/ca/bc/gov/backendstartapi/repository/ActiveOrchardSeedPlanningUnitRepositoryTest.java b/backend/src/test/java/ca/bc/gov/backendstartapi/repository/ActiveOrchardSeedPlanningUnitRepositoryTest.java
new file mode 100644
index 000000000..f56e1eb67
--- /dev/null
+++ b/backend/src/test/java/ca/bc/gov/backendstartapi/repository/ActiveOrchardSeedPlanningUnitRepositoryTest.java
@@ -0,0 +1,44 @@
+package ca.bc.gov.backendstartapi.repository;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import org.junit.jupiter.api.Test;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.context.SpringBootTest;
+
+@SpringBootTest
+class ActiveOrchardSeedPlanningUnitRepositoryTest {
+
+ private final ActiveOrchardSeedPlanningUnitRepository repository;
+
+ @Autowired
+ ActiveOrchardSeedPlanningUnitRepositoryTest(
+ ActiveOrchardSeedPlanningUnitRepository activeOrchardSeedPlanningUnitRepository) {
+ repository = activeOrchardSeedPlanningUnitRepository;
+ }
+
+ @Test
+ void testFindByOrchardIdAndActive() {
+ var actives = repository.findByOrchardIdAndActive("144", true);
+ assertEquals(1, actives.size());
+
+ var active = actives.get(0);
+ assertEquals("144", active.getOrchardId());
+ assertEquals(108, active.getSeedPlanningUnitId());
+ assertTrue(active.isActive());
+ assertFalse(active.isRetired());
+ assertFalse(active.isSpuNotAssigned());
+
+ var inactives = repository.findByOrchardIdAndActive("144", false);
+ assertEquals(1, inactives.size());
+
+ var inactive = inactives.get(0);
+ assertEquals("144", inactive.getOrchardId());
+ assertEquals(55, inactive.getSeedPlanningUnitId());
+ assertFalse(inactive.isActive());
+ assertFalse(inactive.isRetired());
+ assertFalse(inactive.isSpuNotAssigned());
+ }
+}