From db992bf37844875e62fcbb42b59718b7f5b86ee9 Mon Sep 17 00:00:00 2001 From: anahar-cfa Date: Thu, 6 Feb 2025 17:59:39 -0800 Subject: [PATCH] [CCAP 636], Implement ccms data storage (#1115) Co-authored-by: Ana Medrano --- build.gradle | 4 +- .../org/ilgcc/app/data/CCMSDataService.java | 41 +++++++ src/main/java/org/ilgcc/app/data/County.java | 44 ++++++++ .../org/ilgcc/app/data/CountyRepository.java | 9 ++ .../java/org/ilgcc/app/data/Provider.java | 9 ++ .../ilgcc/app/data/ProviderRepository.java | 1 + .../ilgcc/app/data/ResourceOrganization.java | 51 +++++++++ .../data/ResourceOrganizationRepository.java | 15 +++ .../data/importer/CCMSDataServiceImpl.java | 47 ++++++++ ...__Add_Resource_ZipCode_Counties_tables.sql | 41 +++++++ ...__Update_providers_add_resource_org_id.sql | 8 ++ .../data/EmbeddedPostgresIntegrationTest.java | 100 ++++++++++++++++++ 12 files changed, 369 insertions(+), 1 deletion(-) create mode 100644 src/main/java/org/ilgcc/app/data/CCMSDataService.java create mode 100644 src/main/java/org/ilgcc/app/data/County.java create mode 100644 src/main/java/org/ilgcc/app/data/CountyRepository.java create mode 100644 src/main/java/org/ilgcc/app/data/ResourceOrganization.java create mode 100644 src/main/java/org/ilgcc/app/data/ResourceOrganizationRepository.java create mode 100644 src/main/java/org/ilgcc/app/data/importer/CCMSDataServiceImpl.java create mode 100644 src/main/resources/db/migration/V2025.01.29.13.54.57__Add_Resource_ZipCode_Counties_tables.sql create mode 100644 src/main/resources/db/migration/V2025.01.29.13.57.09__Update_providers_add_resource_org_id.sql create mode 100644 src/test/java/org/ilgcc/app/data/EmbeddedPostgresIntegrationTest.java diff --git a/build.gradle b/build.gradle index ce87a55f5..b12f9277c 100644 --- a/build.gradle +++ b/build.gradle @@ -86,8 +86,10 @@ dependencies { testImplementation 'com.deque.html.axe-core:selenium:4.10.1' testImplementation 'org.projectlombok:lombok:1.18.36' testImplementation 'net.javacrumbs.json-unit:json-unit-assertj:4.1.0' - + testImplementation 'io.zonky.test:embedded-database-spring-test:2.6.0' + testImplementation 'io.zonky.test:embedded-postgres:2.1.0' testAnnotationProcessor 'org.projectlombok:lombok' + testCompileOnly 'org.projectlombok:lombok' runtimeOnly 'org.postgresql:postgresql:42.7.5' diff --git a/src/main/java/org/ilgcc/app/data/CCMSDataService.java b/src/main/java/org/ilgcc/app/data/CCMSDataService.java new file mode 100644 index 000000000..8e2a5a50b --- /dev/null +++ b/src/main/java/org/ilgcc/app/data/CCMSDataService.java @@ -0,0 +1,41 @@ +package org.ilgcc.app.data; + +import java.math.BigInteger; +import java.util.List; +import java.util.Optional; + +public interface CCMSDataService { + + /** + * Retrieves a county based on the given zip code. + * + * @param zipCode the zip code to search for + * @return an Optional containing the matching county if found, or an empty Optional if not found + */ + Optional getCountyByZipCode(String zipCode); + + /** + * Retrieves a provider based on the given provider ID. + * + * @param providerId the unique identifier of the provider + * @return an Optional containing the provider if found, or an empty Optional if not found + */ + Optional getProviderById(BigInteger providerId); + + /** + * Retrieves a resource organization associated with a given provider ID. + * + * @param providerId the unique identifier of the provider + * @return an Optional containing the matching resource organization if found, or an empty Optional if not found + */ + Optional getResourceOrganizationByProviderId(BigInteger providerId); + + /** + * Retrieves a list of resource organizations based on the given caseload code. + * + * @param caseloadCode the caseload code to search for + * @return a list of matching resource organizations + */ + List getResourceOrganizationsByCaseloadCode(String caseloadCode); +} + diff --git a/src/main/java/org/ilgcc/app/data/County.java b/src/main/java/org/ilgcc/app/data/County.java new file mode 100644 index 000000000..9eb7f38fc --- /dev/null +++ b/src/main/java/org/ilgcc/app/data/County.java @@ -0,0 +1,44 @@ +package org.ilgcc.app.data; + +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.Id; +import jakarta.persistence.Table; +import java.io.Serializable; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; +import org.springframework.stereotype.Component; + +@Entity +@Table(name = "zip_codes") +@Getter +@Setter +@AllArgsConstructor +@NoArgsConstructor +@Component +@Builder +public class County implements Serializable { + + @Id + @Column(name = "zip_code") + private String zipCode; + + @Column(name = "city") + private String city; + + @Column(name = "county") + private String county; + + @Column(name = "fips_county_code") + private Integer fipsCountyCode; + + @Column(name = "dpa_county_code") + private Integer dpaCountyCode; + + @Column(name = "caseload_code") + private String caseloadCode; + +} diff --git a/src/main/java/org/ilgcc/app/data/CountyRepository.java b/src/main/java/org/ilgcc/app/data/CountyRepository.java new file mode 100644 index 000000000..ad40e40c0 --- /dev/null +++ b/src/main/java/org/ilgcc/app/data/CountyRepository.java @@ -0,0 +1,9 @@ +package org.ilgcc.app.data; + +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; + +@Repository +public interface CountyRepository extends JpaRepository { + +} \ No newline at end of file diff --git a/src/main/java/org/ilgcc/app/data/Provider.java b/src/main/java/org/ilgcc/app/data/Provider.java index 347663844..5b8e4eac8 100644 --- a/src/main/java/org/ilgcc/app/data/Provider.java +++ b/src/main/java/org/ilgcc/app/data/Provider.java @@ -3,6 +3,8 @@ import jakarta.persistence.Column; import jakarta.persistence.Entity; import jakarta.persistence.Id; +import jakarta.persistence.JoinColumn; +import jakarta.persistence.ManyToOne; import jakarta.persistence.Table; import java.io.Serializable; import java.math.BigInteger; @@ -12,6 +14,8 @@ import lombok.Getter; import lombok.NoArgsConstructor; import lombok.Setter; +import org.hibernate.annotations.OnDelete; +import org.hibernate.annotations.OnDeleteAction; import org.springframework.stereotype.Component; @@ -55,4 +59,9 @@ public class Provider implements Serializable { @Column(name = "date_of_last_approval") private OffsetDateTime dateOfLastApproval; + + @JoinColumn(name = "resource_org_id", nullable = true) + @OnDelete(action = OnDeleteAction.CASCADE) + @ManyToOne + private ResourceOrganization resourceOrganization; } diff --git a/src/main/java/org/ilgcc/app/data/ProviderRepository.java b/src/main/java/org/ilgcc/app/data/ProviderRepository.java index 2fc5fa7d8..27fafd6f5 100644 --- a/src/main/java/org/ilgcc/app/data/ProviderRepository.java +++ b/src/main/java/org/ilgcc/app/data/ProviderRepository.java @@ -16,4 +16,5 @@ public interface ProviderRepository extends JpaRepository + "AND date_of_last_approval >= :threeYearsAgo", nativeQuery = true) boolean existsByStatusInAndProviderIdAndDateOfLastApprovalAfter(@Param("statuses") List statuses, @Param("providerId") BigInteger providerId, @Param("threeYearsAgo") LocalDate threeYearsAgo); + } diff --git a/src/main/java/org/ilgcc/app/data/ResourceOrganization.java b/src/main/java/org/ilgcc/app/data/ResourceOrganization.java new file mode 100644 index 000000000..a1464bd78 --- /dev/null +++ b/src/main/java/org/ilgcc/app/data/ResourceOrganization.java @@ -0,0 +1,51 @@ +package org.ilgcc.app.data; + +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.FetchType; +import jakarta.persistence.Id; +import jakarta.persistence.OneToMany; +import jakarta.persistence.Table; +import java.math.BigInteger; +import java.util.Set; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; +import lombok.Builder; +import org.springframework.stereotype.Component; + +@Entity +@Table(name = "resource_organizations") +@Getter +@Setter +@AllArgsConstructor +@NoArgsConstructor +@Component +@Builder +public class ResourceOrganization { + @Id + @Column(name = "resource_org_id") + private BigInteger resourceOrgId; + + @Column(name = "caseload_code") + private String caseloadCode; + + @Column(name = "name") + private String name; + + @Column(name = "address") + private String address; + + @Column(name = "phone") + private String phone; + + @Column(name = "email") + private String email; + + @Column(name = "sda") + private Short sda; + + @OneToMany(mappedBy = "resourceOrganization", fetch =FetchType.LAZY) + private Set providers; +} diff --git a/src/main/java/org/ilgcc/app/data/ResourceOrganizationRepository.java b/src/main/java/org/ilgcc/app/data/ResourceOrganizationRepository.java new file mode 100644 index 000000000..830d46ccd --- /dev/null +++ b/src/main/java/org/ilgcc/app/data/ResourceOrganizationRepository.java @@ -0,0 +1,15 @@ +package org.ilgcc.app.data; + +import java.math.BigInteger; +import java.util.List; +import java.util.Optional; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; + +@Repository +public interface ResourceOrganizationRepository extends JpaRepository { + + List findByCaseloadCode(String caseloadCode); + + Optional findByProvidersProviderId(BigInteger providerId); +} diff --git a/src/main/java/org/ilgcc/app/data/importer/CCMSDataServiceImpl.java b/src/main/java/org/ilgcc/app/data/importer/CCMSDataServiceImpl.java new file mode 100644 index 000000000..1b4a69cdb --- /dev/null +++ b/src/main/java/org/ilgcc/app/data/importer/CCMSDataServiceImpl.java @@ -0,0 +1,47 @@ +package org.ilgcc.app.data.importer; + +import java.math.BigInteger; +import java.util.List; +import java.util.Optional; +import org.ilgcc.app.data.CCMSDataService; +import org.ilgcc.app.data.County; +import org.ilgcc.app.data.CountyRepository; +import org.ilgcc.app.data.Provider; +import org.ilgcc.app.data.ProviderRepository; +import org.ilgcc.app.data.ResourceOrganization; +import org.ilgcc.app.data.ResourceOrganizationRepository; +import org.springframework.stereotype.Service; + +@Service +public class CCMSDataServiceImpl implements CCMSDataService { + + private final ProviderRepository providerRepository; + private final CountyRepository countyRepository; + private final ResourceOrganizationRepository resourceOrganizationRepository; + + public CCMSDataServiceImpl(ProviderRepository providerRepository, CountyRepository countyRepository, ResourceOrganizationRepository resourceOrganizationRepository) { + this.providerRepository = providerRepository; + this.countyRepository = countyRepository; + this.resourceOrganizationRepository = resourceOrganizationRepository; + } + + @Override + public Optional getCountyByZipCode(String zipCode) { + return countyRepository.findById(zipCode); + } + + @Override + public Optional getProviderById(BigInteger providerId) { + return providerRepository.findById(providerId); + } + + @Override + public Optional getResourceOrganizationByProviderId(BigInteger providerId) { + return resourceOrganizationRepository.findByProvidersProviderId(providerId); + } + + @Override + public List getResourceOrganizationsByCaseloadCode(String caseloadCode) { + return resourceOrganizationRepository.findByCaseloadCode(caseloadCode); + } +} diff --git a/src/main/resources/db/migration/V2025.01.29.13.54.57__Add_Resource_ZipCode_Counties_tables.sql b/src/main/resources/db/migration/V2025.01.29.13.54.57__Add_Resource_ZipCode_Counties_tables.sql new file mode 100644 index 000000000..a23f297ac --- /dev/null +++ b/src/main/resources/db/migration/V2025.01.29.13.54.57__Add_Resource_ZipCode_Counties_tables.sql @@ -0,0 +1,41 @@ +DO +$$ +BEGIN + -- Create 'resource_organizations' table if it does not exist + IF +NOT EXISTS (SELECT * FROM pg_tables WHERE tablename = 'resource_organizations') THEN +CREATE TABLE resource_organizations +( + resource_org_id BIGINT PRIMARY KEY, + caseload_code VARCHAR, + name VARCHAR, + address VARCHAR, + phone VARCHAR, + email VARCHAR, + sda SMALLINT +); + +END IF; + + + -- Create 'zip_codes' table if it does not exist + IF +NOT EXISTS (SELECT * FROM pg_tables WHERE tablename = 'zip_codes') THEN +CREATE TABLE zip_codes +( + zip_code BIGINT PRIMARY KEY, + city VARCHAR, + county VARCHAR, + fips_county_code INT, + dpa_county_code INT, + caseload_code VARCHAR +); + +END IF; + +-- Create indexes for case load and county +CREATE INDEX caseload_code_idx ON resource_organizations (caseload_code); +CREATE INDEX county_idx ON zip_codes (county); + +END +$$; \ No newline at end of file diff --git a/src/main/resources/db/migration/V2025.01.29.13.57.09__Update_providers_add_resource_org_id.sql b/src/main/resources/db/migration/V2025.01.29.13.57.09__Update_providers_add_resource_org_id.sql new file mode 100644 index 000000000..4bba28c6f --- /dev/null +++ b/src/main/resources/db/migration/V2025.01.29.13.57.09__Update_providers_add_resource_org_id.sql @@ -0,0 +1,8 @@ +-- Add the 'resource_org_id' column to the 'providers' table +ALTER TABLE providers ADD COLUMN resource_org_id BIGINT; + +-- Add foreign key constraint on the providers table +ALTER TABLE providers + ADD CONSTRAINT fk_providers_resource_org + FOREIGN KEY (resource_org_id) REFERENCES resource_organizations(resource_org_id) + ON DELETE CASCADE; diff --git a/src/test/java/org/ilgcc/app/data/EmbeddedPostgresIntegrationTest.java b/src/test/java/org/ilgcc/app/data/EmbeddedPostgresIntegrationTest.java new file mode 100644 index 000000000..b9ff5bc37 --- /dev/null +++ b/src/test/java/org/ilgcc/app/data/EmbeddedPostgresIntegrationTest.java @@ -0,0 +1,100 @@ +package org.ilgcc.app.data; + +import formflow.library.data.Submission; +import io.zonky.test.db.AutoConfigureEmbeddedDatabase; +import io.zonky.test.db.AutoConfigureEmbeddedDatabase.DatabaseProvider; +import io.zonky.test.db.AutoConfigureEmbeddedDatabase.DatabaseType; +import io.zonky.test.db.postgres.embedded.EmbeddedPostgres; +import java.math.BigInteger; +import java.util.List; +import java.util.Optional; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.domain.EntityScan; +import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest; +import org.springframework.data.jpa.repository.config.EnableJpaRepositories; +import org.springframework.test.context.ActiveProfiles; +@DataJpaTest +@AutoConfigureEmbeddedDatabase(type = DatabaseType.POSTGRES, provider = DatabaseProvider.ZONKY) +@EnableJpaRepositories(basePackageClasses = {TransmissionRepository.class, ProviderRepository.class, County.class, ResourceOrganization.class}) +@EntityScan(basePackageClasses = {Transmission.class, Submission.class, Provider.class, County.class, ResourceOrganization.class}) +@ActiveProfiles("test") +public class EmbeddedPostgresIntegrationTest { + + private static EmbeddedPostgres embeddedPostgres; + + @Autowired + private ProviderRepository repository; + + @Autowired + private CountyRepository countyRepository; + + @Autowired + private ResourceOrganizationRepository resourceOrganizationRepository ; + + @Test + public void testFindProviderById() { + BigInteger id = BigInteger.valueOf(43545l); + Provider provider = new Provider(); + provider.setName("New Provider"); + provider.setProviderId(id); + Provider savedPerson = repository.save(provider); + Assertions.assertNotNull(savedPerson.getProviderId()); + Assertions.assertEquals(savedPerson.getProviderId(), provider.getProviderId()); + } + + @Test + public void testFindCountyById() { + County county = new County(); + county.setCounty("Test County"); + county.setCity("Test City"); + county.setZipCode("12345"); + County savedCounty = countyRepository.save(county); + + Optional byId = countyRepository.findById("12345"); + County countyById = byId.get(); + Assertions.assertNotNull(countyById); + Assertions.assertEquals(savedCounty, countyById); + } + + @Test + public void testResourceOrganizationById() { + BigInteger id = BigInteger.valueOf(43545l); + String caseloadCode = "TestCode"; + ResourceOrganization organization = new ResourceOrganization(); + organization.setSda(Short.valueOf("3434")); + organization.setResourceOrgId(id); + organization.setCaseloadCode(caseloadCode); + organization.setName("test name"); + ResourceOrganization savedOrganization = resourceOrganizationRepository.save(organization); + + List byIds = resourceOrganizationRepository.findByCaseloadCode(caseloadCode); + Assertions.assertNotNull(byIds); + Assertions.assertEquals(savedOrganization, byIds.get(0)); + } + + @Test + public void testResourceOrganizationByProviderId() { + BigInteger id = BigInteger.valueOf(34334l); + String caseloadCode = "TestCode"; + ResourceOrganization organization = new ResourceOrganization(); + organization.setSda(Short.valueOf("3434")); + organization.setResourceOrgId(id); + organization.setCaseloadCode(caseloadCode); + organization.setName("test name"); + ResourceOrganization savedOrganization = resourceOrganizationRepository.save(organization); + + BigInteger providerId = BigInteger.valueOf(1213l); + Provider provider = new Provider(); + provider.setName("New Provider"); + provider.setProviderId(providerId); + provider.setResourceOrganization(savedOrganization); + repository.save(provider); + + Optional byId = resourceOrganizationRepository.findByProvidersProviderId(providerId); + ResourceOrganization resourceOrganization = byId.get(); + Assertions.assertNotNull(resourceOrganization); + Assertions.assertEquals(savedOrganization, resourceOrganization); + } +} \ No newline at end of file