diff --git a/.gitignore b/.gitignore
index 549e00a2..a8c95021 100644
--- a/.gitignore
+++ b/.gitignore
@@ -3,6 +3,7 @@ target/
!.mvn/wrapper/maven-wrapper.jar
!**/src/main/**/target/
!**/src/test/**/target/
+.env
### STS ###
.apt_generated
diff --git a/NEWS.md b/NEWS.md
index 3e13bb31..f3145148 100644
--- a/NEWS.md
+++ b/NEWS.md
@@ -1,5 +1,14 @@
-# 1.0.0
-- Initial release
+# 1.0.x
+
+## 1.0.3
+- [MODFQMMGR-58](https://issues.folio.org/browse/MODFQMMGR-58) Refactor drv_item_callnumber_location view
+- [MODFQMMGR-76](https://issues.folio.org/browse/MODFQMMGR-76) Periodically refresh materialized views
+
+## 1.0.2
+- Remove the instance_title_searchable field from the Items entity type
+- Purge old query results based on query start date/time instead of the end date/time
+- Fix bug in user preferred contact type
+- Update the provided `_tenant` interface in the module descriptor to 2.0
## 1.0.1
- [MODFQMMGR-57](https://issues.folio.org/browse/MODFQMMGR-57) Use a different version of f_unaccent(), to allow us to make use of an index
@@ -8,3 +17,6 @@
- [MODFQMMGR-67](https://issues.folio.org/browse/MODFQMMGR-67) Enable batched inserts
- [MODFQMMGR-71](https://issues.folio.org/browse/MODFQMMGR-71) Update item and user entity types
- [MODFQMMGR-31](https://issues.folio.org/browse/MODFQMMGR-31) Fix Users dropdown
+
+## 1.0.0
+- Initial release
diff --git a/descriptors/ModuleDescriptor-template.json b/descriptors/ModuleDescriptor-template.json
index c6d18b4e..a0e4b8e8 100644
--- a/descriptors/ModuleDescriptor-template.json
+++ b/descriptors/ModuleDescriptor-template.json
@@ -1,18 +1,21 @@
{
"id": "@artifactId@-@version@",
- "name": "The module descriptor for mod-fqm-manager.",
+ "name": "FQM Manager Module",
"provides": [
{
"id": "_tenant",
- "version": "1.2",
+ "version": "2.0",
"interfaceType": "system",
"handlers": [
{
"methods": ["POST"],
- "pathPattern": "/_/tenant"
- }, {
- "methods": ["DELETE"],
- "pathPattern": "/_/tenant"
+ "pathPattern": "/_/tenant",
+ "permissionsRequired": []
+ },
+ {
+ "methods": ["GET", "DELETE"],
+ "pathPattern": "/_/tenant/{id}",
+ "permissionsRequired": []
}
]
},
@@ -34,6 +37,11 @@
"methods": ["GET"],
"pathPattern": "/entity-types/{entity-type-id}/columns/{column-name}/values",
"permissionsRequired": ["fqm.entityTypes.item.columnValues.get"]
+ },
+ {
+ "methods": ["POST"],
+ "pathPattern": "/entity-types/materialized-views/refresh",
+ "permissionsRequired": ["fqm.materializedViews.post"]
}
]
},
@@ -84,10 +92,22 @@
"interfaceType": "system",
"handlers": [
{
- "methods": [ "POST" ],
+ "methods": ["POST"],
"pathPattern": "/query/purge",
"unit": "hour",
- "delay": "1"
+ "delay": "1",
+ "modulePermissions": [
+ "fqm.query.purge"
+ ]
+ },
+ {
+ "methods": ["POST"],
+ "pathPattern": "/entity-types/materialized-views/refresh",
+ "unit": "hour",
+ "delay": "24",
+ "modulePermissions": [
+ "fqm.materializedViews.post"
+ ]
}
]
}
@@ -141,6 +161,12 @@
"description": "Run a query synchronously and get results",
"visible": true
},
+ {
+ "permissionName": "fqm.materializedViews.post",
+ "displayName": "FQM - Refresh materialized views",
+ "description": "Refresh FQM materialized views",
+ "visible": true
+ },
{
"permissionName": "fqm.query.all",
"displayName": "FQM - All permissions",
diff --git a/pom.xml b/pom.xml
index dbaa8d85..ea457a3d 100644
--- a/pom.xml
+++ b/pom.xml
@@ -10,7 +10,7 @@
org.folio
mod-fqm-manager
mod-fqm-manager
- 1.0.2-SNAPSHOT
+ 1.0.4-SNAPSHOT
FOLIO Query Machine manager
jar
@@ -373,10 +373,10 @@
- https://github.com/EBSCOIS/${project.artifactId}
- scm:git:git://github.com/EBSCOIS/${project.artifactId}.git
- scm:git:git@github.com:EBSCOIS/${project.artifactId}.git
- v1.0.0
+ https://github.com/folio-org/${artifactId}
+ scm:git:git://github.com/folio-org/${artifactId}.git
+ scm:git:git@github.com:folio-org/${artifactId}.git
+ HEAD
diff --git a/src/main/java/org/folio/fqm/repository/MaterializedViewRefreshRepository.java b/src/main/java/org/folio/fqm/repository/MaterializedViewRefreshRepository.java
new file mode 100644
index 00000000..e45d9f83
--- /dev/null
+++ b/src/main/java/org/folio/fqm/repository/MaterializedViewRefreshRepository.java
@@ -0,0 +1,30 @@
+package org.folio.fqm.repository;
+
+import lombok.RequiredArgsConstructor;
+import lombok.extern.log4j.Log4j2;
+import org.jooq.DSLContext;
+import org.springframework.stereotype.Repository;
+
+import java.util.List;
+
+@Repository
+@RequiredArgsConstructor
+@Log4j2
+public class MaterializedViewRefreshRepository {
+ private static final String REFRESH_MATERIALIZED_VIEW_SQL = "REFRESH MATERIALIZED VIEW CONCURRENTLY ";
+
+ private static final List materializedViewNames = List.of(
+ "drv_circulation_loan_status",
+ "drv_inventory_item_status"
+ );
+
+ private final DSLContext jooqContext;
+
+ public void refreshMaterializedViews(String tenantId) {
+ for (String matViewName : materializedViewNames) {
+ String fullName = tenantId + "_mod_fqm_manager." + matViewName;
+ log.info("Refreshing materialized view {}", fullName);
+ jooqContext.execute(REFRESH_MATERIALIZED_VIEW_SQL + fullName);
+ }
+ }
+}
diff --git a/src/main/java/org/folio/fqm/repository/QueryRepository.java b/src/main/java/org/folio/fqm/repository/QueryRepository.java
index 73c27312..d04a3e13 100644
--- a/src/main/java/org/folio/fqm/repository/QueryRepository.java
+++ b/src/main/java/org/folio/fqm/repository/QueryRepository.java
@@ -73,10 +73,10 @@ public Optional getQuery(UUID queryId, boolean useCache) {
.fetchOneInto(Query.class));
}
- public List getQueryIdsCompletedBefore(Duration duration) {
+ public List getQueryIdsStartedBefore(Duration duration) {
return jooqContext.select(field(QUERY_ID))
.from(table(QUERY_DETAILS_TABLE))
- .where(field("end_date").
+ .where(field("start_date").
lessOrEqual(OffsetDateTime.now().minus(duration)))
.fetchInto(UUID.class);
}
diff --git a/src/main/java/org/folio/fqm/resource/MaterializedViewRefreshController.java b/src/main/java/org/folio/fqm/resource/MaterializedViewRefreshController.java
new file mode 100644
index 00000000..7da2c3cb
--- /dev/null
+++ b/src/main/java/org/folio/fqm/resource/MaterializedViewRefreshController.java
@@ -0,0 +1,21 @@
+package org.folio.fqm.resource;
+
+import lombok.RequiredArgsConstructor;
+import org.folio.fqm.service.MaterializedViewRefreshService;
+import org.folio.spring.FolioExecutionContext;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.ResponseEntity;
+import org.springframework.web.bind.annotation.RestController;
+
+@RestController
+@RequiredArgsConstructor
+public class MaterializedViewRefreshController implements MaterializedViewsApi {
+ private final FolioExecutionContext executionContext;
+ private final MaterializedViewRefreshService materializedViewRefreshService;
+
+ @Override
+ public ResponseEntity refreshMaterializedViews() {
+ materializedViewRefreshService.refreshMaterializedViews(executionContext.getTenantId());
+ return new ResponseEntity<>(HttpStatus.NO_CONTENT);
+ }
+}
diff --git a/src/main/java/org/folio/fqm/service/MaterializedViewRefreshService.java b/src/main/java/org/folio/fqm/service/MaterializedViewRefreshService.java
new file mode 100644
index 00000000..a7d1e00f
--- /dev/null
+++ b/src/main/java/org/folio/fqm/service/MaterializedViewRefreshService.java
@@ -0,0 +1,15 @@
+package org.folio.fqm.service;
+
+import lombok.RequiredArgsConstructor;
+import org.folio.fqm.repository.MaterializedViewRefreshRepository;
+import org.springframework.stereotype.Service;
+
+@Service
+@RequiredArgsConstructor
+public class MaterializedViewRefreshService {
+ private final MaterializedViewRefreshRepository materializedViewRefreshRepository;
+
+ public void refreshMaterializedViews(String tenantId) {
+ materializedViewRefreshRepository.refreshMaterializedViews(tenantId);
+ }
+}
diff --git a/src/main/java/org/folio/fqm/service/QueryManagementService.java b/src/main/java/org/folio/fqm/service/QueryManagementService.java
index f6d3f4e1..925b65b0 100644
--- a/src/main/java/org/folio/fqm/service/QueryManagementService.java
+++ b/src/main/java/org/folio/fqm/service/QueryManagementService.java
@@ -133,7 +133,7 @@ public Optional getQuery(UUID queryId, boolean includeResults, int
*/
@Transactional
public PurgedQueries deleteOldQueries() {
- List queryIds = queryRepository.getQueryIdsCompletedBefore(queryRetentionDuration);
+ List queryIds = queryRepository.getQueryIdsStartedBefore(queryRetentionDuration);
log.info("Deleting the queries with queryIds {}", queryIds);
deleteQueryAndResults(queryIds);
return new PurgedQueries().deletedQueryIds(queryIds);
diff --git a/src/main/resources/db/changelog/changelog-master.xml b/src/main/resources/db/changelog/changelog-master.xml
index 731d2d0a..3389bd25 100644
--- a/src/main/resources/db/changelog/changelog-master.xml
+++ b/src/main/resources/db/changelog/changelog-master.xml
@@ -6,5 +6,7 @@
+
+
diff --git a/src/main/resources/db/changelog/changes/v1.0.2/changelog-v1.0.2.xml b/src/main/resources/db/changelog/changes/v1.0.2/changelog-v1.0.2.xml
new file mode 100644
index 00000000..838e3368
--- /dev/null
+++ b/src/main/resources/db/changelog/changes/v1.0.2/changelog-v1.0.2.xml
@@ -0,0 +1,16 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/main/resources/db/changelog/changes/v1.0.2/fix_user_details_preferred_contact_type.xml b/src/main/resources/db/changelog/changes/v1.0.2/fix_user_details_preferred_contact_type.xml
new file mode 100644
index 00000000..3c0edd39
--- /dev/null
+++ b/src/main/resources/db/changelog/changes/v1.0.2/fix_user_details_preferred_contact_type.xml
@@ -0,0 +1,98 @@
+
+
+
+
+
+
+
+
+ SELECT
+ userDetails.jsonb -> 'personal' ->> 'firstName' as user_first_name,
+ userDetails.jsonb -> 'personal' ->> 'lastName' as user_last_name,
+ userDetails.jsonb ->>'barcode' as user_barcode,
+ userDetails.jsonb ->>'username' as username,
+ userDetails.id as id,
+ userDetails.jsonb ->> 'externalSystemId' as user_external_system_id,
+ userDetails.jsonb ->> 'active' as user_active,
+ userDetails.jsonb -> 'personal' ->> 'email' as user_email,
+ userDetails.jsonb -> 'metadata' ->> 'createdDate' as user_created_date,
+ userDetails.jsonb -> 'metadata' ->> 'updatedDate' as user_updated_date,
+ userDetails.jsonb -> 'personal' ->> 'preferredFirstName' as user_preferred_first_name,
+ userDetails.jsonb -> 'personal' ->> 'middleName' as user_middle_name,
+ userDetails.jsonb -> 'personal' ->> 'phone' as user_phone,
+ userDetails.jsonb -> 'personal' ->> 'mobilePhone' as user_mobile_phone,
+ userDetails.jsonb -> 'personal' ->> 'dateOfBirth' as user_date_of_birth,
+ userDetails.jsonb ->> 'expirationDate'::text AS user_expiration_date,
+ userDetails.jsonb ->> 'enrollmentDate'::text AS user_enrollment_date,
+ patron_id_ref_data.jsonb ->> 'group'::text AS user_patron_group,
+ patron_id_ref_data.id::text AS user_patron_group_id,
+ UserDetails.jsonb -> 'personal' ->> 'preferredContactTypeId' as user_preferred_contact_type_id,
+ CASE UserDetails.jsonb -> 'personal' ->> 'preferredContactTypeId'
+ WHEN '001' THEN 'Mail (Primary Address)'
+ WHEN '002' THEN 'Email'
+ WHEN '003' THEN 'Text Message'
+ ELSE 'unknown'
+ END AS user_preferred_contact_type,
+ concat_ws(', '::text,
+ NULLIF(( SELECT subquery.addressline1
+ FROM ( SELECT add_id.value ->> 'addressLine1'::text AS addressline1,
+ row_number() OVER (ORDER BY (add_id.value ->> 'primaryAddress'::text)) AS row_num
+ FROM jsonb_array_elements((userdetails.jsonb -> 'personal'::text) -> 'addresses'::text) add_id(value)) subquery
+ WHERE subquery.row_num = 1), ''::text),
+ NULLIF(( SELECT subquery.addressline2
+ FROM ( SELECT add_id.value ->> 'addressLine2'::text AS addressline2,
+ row_number() OVER (ORDER BY (add_id.value ->> 'primaryAddress'::text)) AS row_num
+ FROM jsonb_array_elements((userdetails.jsonb -> 'personal'::text) -> 'addresses'::text) add_id(value)) subquery
+ WHERE subquery.row_num = 1), ''::text),
+ NULLIF(( SELECT subquery.city
+ FROM ( SELECT add_id.value ->> 'city'::text AS city,
+ row_number() OVER (ORDER BY (add_id.value ->> 'primaryAddress'::text)) AS row_num
+ FROM jsonb_array_elements((userdetails.jsonb -> 'personal'::text) -> 'addresses'::text) add_id(value)) subquery
+ WHERE subquery.row_num = 1), ''::text),
+ NULLIF(( SELECT subquery.region
+ FROM ( SELECT add_id.value ->> 'region'::text AS region,
+ row_number() OVER (ORDER BY (add_id.value ->> 'primaryAddress'::text)) AS row_num
+ FROM jsonb_array_elements((userdetails.jsonb -> 'personal'::text) -> 'addresses'::text) add_id(value)) subquery
+ WHERE subquery.row_num = 1), ''::text),
+ NULLIF(( SELECT subquery.postalcode
+ FROM ( SELECT add_id.value ->> 'postalCode'::text AS postalcode,
+ row_number() OVER (ORDER BY (add_id.value ->> 'primaryAddress'::text)) AS row_num
+ FROM jsonb_array_elements((userdetails.jsonb -> 'personal'::text) -> 'addresses'::text) add_id(value)) subquery
+ WHERE subquery.row_num = 1), ''::text),
+ NULLIF(( SELECT subquery.countryid
+ FROM ( SELECT add_id.value ->> 'countryId'::text AS countryid,
+ row_number() OVER (ORDER BY (add_id.value ->> 'primaryAddress'::text)) AS row_num
+ FROM jsonb_array_elements((userdetails.jsonb -> 'personal'::text) -> 'addresses'::text) add_id(value)) subquery
+ WHERE subquery.row_num = 1), ''::text)) AS user_primary_address,
+ ( SELECT array_agg(add_id.value ->> 'city'::text) FILTER (WHERE (add_id.value ->> 'city'::text) IS NOT NULL) AS array_agg
+ FROM jsonb_array_elements((userdetails.jsonb -> 'personal'::text) -> 'addresses'::text) add_id(value)) AS cities,
+ ( SELECT array_agg(add_id.value ->> 'region'::text) FILTER (WHERE (add_id.value ->> 'region'::text) IS NOT NULL) AS array_agg
+ FROM jsonb_array_elements((userdetails.jsonb -> 'personal'::text) -> 'addresses'::text) add_id(value)) AS regions,
+ ( SELECT array_agg(add_id.value ->> 'countryId'::text) FILTER (WHERE (add_id.value ->> 'countryId'::text) IS NOT NULL) AS array_agg
+ FROM jsonb_array_elements((userdetails.jsonb -> 'personal'::text) -> 'addresses'::text) add_id(value)) AS country_ids,
+ ( SELECT array_agg(add_id.value ->> 'postalCode'::text) FILTER (WHERE (add_id.value ->> 'postalCode'::text) IS NOT NULL) AS array_agg
+ FROM jsonb_array_elements((userdetails.jsonb -> 'personal'::text) -> 'addresses'::text) add_id(value)) AS postal_codes,
+ ( SELECT array_agg(add_id.value ->> 'addressLine1'::text) FILTER (WHERE (add_id.value ->> 'addressLine1'::text) IS NOT NULL) AS array_agg
+ FROM jsonb_array_elements((userdetails.jsonb -> 'personal'::text) -> 'addresses'::text) add_id(value)) AS address_line1,
+ ( SELECT array_agg(add_id.value ->> 'addressLine2'::text) FILTER (WHERE (add_id.value ->> 'addressLine2'::text) IS NOT NULL) AS array_agg
+ FROM jsonb_array_elements((userdetails.jsonb -> 'personal'::text) -> 'addresses'::text) add_id(value)) AS address_line2,
+ ( SELECT array_agg(add_id.value ->> 'addressTypeId'::text) FILTER (WHERE (add_id.value ->> 'addressTypeId'::text) IS NOT NULL) AS array_agg
+ FROM jsonb_array_elements((userdetails.jsonb -> 'personal'::text) -> 'addresses'::text) add_id(value)) AS address_ids,
+ ( SELECT array_agg(a.jsonb ->> 'addressType'::text) FILTER (WHERE (a.jsonb ->> 'addressType'::text) IS NOT NULL) AS array_agg
+ FROM jsonb_array_elements((userdetails.jsonb -> 'personal'::text) -> 'addresses'::text) add_id(value)
+ JOIN src_users_addresstype a ON (add_id.value ->> 'addressTypeId'::text) = a.id::text) AS address_type_names,
+ array_agg(temp_departments.id::text) FILTER (where temp_departments.id is not null) as department_ids,
+ array_agg(temp_departments.jsonb ->> 'name') FILTER (where temp_departments.jsonb ->> 'name' is not null) as department_names
+ FROM src_users_users userdetails
+ LEFT JOIN src_users_groups patron_id_ref_data ON patron_id_ref_data.id = ((userdetails.jsonb ->> 'patronGroup'::text)::uuid)
+ LEFT JOIN src_users_departments as temp_departments ON userdetails.jsonb -> 'departments' ?? temp_departments.id::text
+ GROUP BY userdetails.id, userdetails.jsonb, patron_id_ref_data.id, patron_id_ref_data.jsonb
+
+
+
+
diff --git a/src/main/resources/db/changelog/changes/v1.0.2/remove_items_holdingsrecord_instance_title_searchable.xml b/src/main/resources/db/changelog/changes/v1.0.2/remove_items_holdingsrecord_instance_title_searchable.xml
new file mode 100644
index 00000000..61dd64b8
--- /dev/null
+++ b/src/main/resources/db/changelog/changes/v1.0.2/remove_items_holdingsrecord_instance_title_searchable.xml
@@ -0,0 +1,48 @@
+
+
+
+
+
+
+
+
+ SELECT COUNT(*) FROM pg_matviews WHERE schemaname = '${tenant_id}_mod_fqm_manager'AND matviewname = 'drv_inventory_item_status';
+
+
+
+
+ SELECT src_inventory_item.id,
+ "left"(lower((instance_details.jsonb -> 'metadata'::text) ->> 'createdDate'::text), 600) AS instance_created_date,
+ hrim.instanceid AS instance_id,
+ jsonb_path_query_first(instance_details.jsonb, '$."contributors"[*]?(@."primary" == true)."name"'::jsonpath) #>> '{}'::text[] AS instance_primary_contributor,
+ instance_details.jsonb ->> 'title'::text AS instance_title,
+ "left"(lower((instance_details.jsonb -> 'metadata'::text) ->> 'updatedDate'::text), 600) AS instance_updated_date,
+ src_inventory_item.jsonb ->> 'barcode'::text AS item_barcode,
+ src_inventory_item.jsonb ->> 'copyNumber'::text AS item_copy_number,
+ (src_inventory_item.jsonb -> 'metadata'::text) ->> 'createdDate'::text AS item_created_date,
+ (src_inventory_item.jsonb -> 'effectiveCallNumberComponents'::text) ->> 'prefix'::text AS item_effective_call_number_prefix,
+ (src_inventory_item.jsonb -> 'effectiveCallNumberComponents'::text) ->> 'callNumber'::text AS item_effective_call_number_callnumber,
+ (src_inventory_item.jsonb -> 'effectiveCallNumberComponents'::text) ->> 'suffix'::text AS item_effective_call_number_suffix,
+ src_inventory_item.jsonb ->> 'copyNumber'::text AS item_effective_call_number_copynumber,
+ concat_ws(', '::text, NULLIF((src_inventory_item.jsonb -> 'effectiveCallNumberComponents'::text) ->> 'prefix'::text, ''::text), NULLIF((src_inventory_item.jsonb -> 'effectiveCallNumberComponents'::text) ->> 'callNumber'::text, ''::text), NULLIF((src_inventory_item.jsonb -> 'effectiveCallNumberComponents'::text) ->> 'suffix'::text, ''::text), NULLIF(src_inventory_item.jsonb ->> 'copyNumber'::text, ''::text)) AS item_effective_call_number,
+ (src_inventory_item.jsonb -> 'effectiveCallNumberComponents'::text) ->> 'typeId'::text AS item_effective_call_number_typeid,
+ src_inventory_item.effectivelocationid AS item_effective_location_id,
+ src_inventory_item.jsonb ->> 'hrid'::text AS item_hrid,
+ src_inventory_item.holdingsrecordid AS holdings_id,
+ src_inventory_item.jsonb ->> 'itemLevelCallNumber'::text AS item_level_call_number,
+ src_inventory_item.jsonb ->> 'itemLevelCallNumberTypeId'::text AS item_level_call_number_typeid,
+ src_inventory_item.materialtypeid AS item_material_type_id,
+ src_inventory_item.permanentlocationid AS item_permanent_location_id,
+ src_inventory_item.temporarylocationid AS item_temporary_location_id,
+ "left"(lower(${tenant_id}_mod_inventory_storage.f_unaccent((src_inventory_item.jsonb -> 'status'::text) ->> 'name'::text)), 600) AS item_status,
+ (src_inventory_item.jsonb -> 'metadata'::text) ->> 'updatedDate'::text AS item_updated_date
+ FROM src_inventory_item
+ JOIN src_inventory_holdings_record hrim ON src_inventory_item.holdingsrecordid = hrim.id
+ JOIN src_inventory_instance instance_details ON hrim.instanceid = instance_details.id;
+
+
+
+
diff --git a/src/main/resources/db/changelog/changes/v1.0.2/remove_items_instance_title_searchable.xml b/src/main/resources/db/changelog/changes/v1.0.2/remove_items_instance_title_searchable.xml
new file mode 100644
index 00000000..a45b9a51
--- /dev/null
+++ b/src/main/resources/db/changelog/changes/v1.0.2/remove_items_instance_title_searchable.xml
@@ -0,0 +1,68 @@
+
+
+
+
+
+
+
+
+
+
+
+
+ SELECT COUNT(*) FROM pg_matviews WHERE schemaname = '${tenant_id}_mod_fqm_manager'AND matviewname = 'drv_inventory_item_status';
+
+
+
+
+ SELECT src_inventory_item.id,
+ "left"(lower((instance_details.jsonb -> 'metadata'::text) ->> 'createdDate'::text), 600) AS instance_created_date,
+ hrim.instanceid AS instance_id,
+ jsonb_path_query_first(instance_details.jsonb, '$."contributors"[*]?(@."primary" == true)."name"'::jsonpath) #>> '{}'::text[] AS instance_primary_contributor,
+ instance_details.jsonb ->> 'title'::text AS instance_title,
+ "left"(lower((instance_details.jsonb -> 'metadata'::text) ->> 'updatedDate'::text), 600) AS instance_updated_date,
+ lower(src_inventory_item.jsonb ->> 'barcode'::text) AS item_barcode,
+ src_inventory_item.jsonb ->> 'copyNumber'::text AS item_copy_number,
+ (src_inventory_item.jsonb -> 'metadata'::text) ->> 'createdDate'::text AS item_created_date,
+ (src_inventory_item.jsonb -> 'effectiveCallNumberComponents'::text) ->> 'prefix'::text AS item_effective_call_number_prefix,
+ (src_inventory_item.jsonb -> 'effectiveCallNumberComponents'::text) ->> 'callNumber'::text AS item_effective_call_number_callnumber,
+ (src_inventory_item.jsonb -> 'effectiveCallNumberComponents'::text) ->> 'suffix'::text AS item_effective_call_number_suffix,
+ src_inventory_item.jsonb ->> 'copyNumber'::text AS item_effective_call_number_copynumber,
+ concat_ws(', '::text, NULLIF((src_inventory_item.jsonb -> 'effectiveCallNumberComponents'::text) ->> 'prefix'::text, ''::text), NULLIF((src_inventory_item.jsonb -> 'effectiveCallNumberComponents'::text) ->> 'callNumber'::text, ''::text), NULLIF((src_inventory_item.jsonb -> 'effectiveCallNumberComponents'::text) ->> 'suffix'::text, ''::text), NULLIF(src_inventory_item.jsonb ->> 'copyNumber'::text, ''::text)) AS item_effective_call_number,
+ call_number_type_ref_data.jsonb ->> 'name'::text AS item_effective_call_number_type_name,
+ (src_inventory_item.jsonb -> 'effectiveCallNumberComponents'::text) ->> 'typeId'::text AS item_effective_call_number_typeid,
+ loclib_ref_data.jsonb ->> 'code'::text AS item_effective_library_code,
+ loclib_ref_data.id AS item_effective_library_id,
+ loclib_ref_data.jsonb ->> 'name'::text AS item_effective_library_name,
+ src_inventory_item.effectivelocationid AS item_effective_location_id,
+ effective_location_ref_data.jsonb ->> 'name'::text AS item_effective_location_name,
+ src_inventory_item.jsonb ->> 'hrid'::text AS item_hrid,
+ src_inventory_item.holdingsrecordid AS holdings_id,
+ src_inventory_item.jsonb ->> 'itemLevelCallNumber'::text AS item_level_call_number,
+ call_item_number_type_ref_data.jsonb ->> 'name'::text AS item_level_call_number_type_name,
+ src_inventory_item.jsonb ->> 'itemLevelCallNumberTypeId'::text AS item_level_call_number_typeid,
+ material_type_ref_data.jsonb ->> 'name'::text AS item_material_type,
+ src_inventory_item.materialtypeid AS item_material_type_id,
+ src_inventory_item.permanentlocationid AS item_permanent_location_id,
+ permanent_location_ref_data.jsonb ->> 'name'::text AS item_permanent_location_name,
+ src_inventory_item.temporarylocationid AS item_temporary_location_id,
+ temporary_location_ref_data.jsonb ->> 'name'::text AS item_temporary_location_name,
+ "left"(lower(${tenant_id}_mod_inventory_storage.f_unaccent((src_inventory_item.jsonb -> 'status'::text) ->> 'name'::text)), 600) AS item_status,
+ (src_inventory_item.jsonb -> 'metadata'::text) ->> 'updatedDate'::text AS item_updated_date
+ FROM src_inventory_item
+ LEFT JOIN src_inventory_location effective_location_ref_data ON effective_location_ref_data.id = src_inventory_item.effectivelocationid
+ LEFT JOIN src_inventory_call_number_type call_number_type_ref_data ON call_number_type_ref_data.id::text = ((src_inventory_item.jsonb -> 'effectiveCallNumberComponents'::text) ->> 'typeId'::text)
+ LEFT JOIN src_inventory_call_number_type call_item_number_type_ref_data ON call_item_number_type_ref_data.id::text = (src_inventory_item.jsonb ->> 'itemLevelCallNumberTypeId'::text)
+ LEFT JOIN src_inventory_loclibrary loclib_ref_data ON loclib_ref_data.id = effective_location_ref_data.libraryid
+ LEFT JOIN src_inventory_location permanent_location_ref_data ON permanent_location_ref_data.id = src_inventory_item.permanentlocationid
+ LEFT JOIN src_inventory_material_type material_type_ref_data ON material_type_ref_data.id = src_inventory_item.materialtypeid
+ LEFT JOIN src_inventory_location temporary_location_ref_data ON temporary_location_ref_data.id = src_inventory_item.temporarylocationid
+ JOIN src_inventory_holdings_record hrim ON src_inventory_item.holdingsrecordid = hrim.id
+ JOIN src_inventory_instance instance_details ON hrim.instanceid = instance_details.id;
+
+
+
+
diff --git a/src/main/resources/db/changelog/changes/v1.0.2/update_item_details_entity_type_definition.xml b/src/main/resources/db/changelog/changes/v1.0.2/update_item_details_entity_type_definition.xml
new file mode 100644
index 00000000..40a939f7
--- /dev/null
+++ b/src/main/resources/db/changelog/changes/v1.0.2/update_item_details_entity_type_definition.xml
@@ -0,0 +1,322 @@
+
+
+
+
+ {
+ "id": "0cb79a4c-f7eb-4941-a104-745224ae0292",
+ "name": "drv_item_details",
+ "labelAlias": "Items",
+ "subEntityTypeIds": [
+ "097a6f96-edd0-11ed-a05b-0242ac120003",
+ "0cb79a4c-f7eb-4941-a104-745224ae0293"
+ ],
+ "private": false,
+ "columns": [
+ {
+ "name": "holdings_id",
+ "dataType": {
+ "dataType": "rangedUUIDType"
+ },
+ "labelAlias": "Holdings ID",
+ "visibleByDefault": false
+ },
+ {
+ "name": "instance_created_date",
+ "dataType": {
+ "dataType": "dateType"
+ },
+ "labelAlias": "Instance created date",
+ "visibleByDefault": false
+ },
+ {
+ "name": "instance_id",
+ "dataType": {
+ "dataType": "rangedUUIDType"
+ },
+ "labelAlias": "Instance ID",
+ "visibleByDefault": false
+ },
+ {
+ "name": "instance_primary_contributor",
+ "dataType": {
+ "dataType": "stringType"
+ },
+ "labelAlias": "Instance primary contributor",
+ "visibleByDefault": false
+ },
+ {
+ "name": "instance_title",
+ "dataType": {
+ "dataType": "stringType"
+ },
+ "labelAlias": "Instance title",
+ "visibleByDefault": true
+ },
+ {
+ "name": "instance_updated_date",
+ "dataType": {
+ "dataType": "dateType"
+ },
+ "labelAlias": "Instance updated date",
+ "visibleByDefault": false
+ },
+ {
+ "name": "item_barcode",
+ "dataType": {
+ "dataType": "stringType"
+ },
+ "labelAlias": "Item barcode",
+ "visibleByDefault": true
+ },
+ {
+ "name": "item_level_call_number",
+ "dataType": {
+ "dataType": "stringType"
+ },
+ "labelAlias": "Item call number",
+ "visibleByDefault": false
+ },
+ {
+ "name": "item_level_call_number_type_name",
+ "dataType": {
+ "dataType": "stringType"
+ },
+ "labelAlias": "Item call number type name",
+ "visibleByDefault": false,
+ "idColumnName": "item_level_call_number_typeid",
+ "source": {
+ "entityTypeId": "5c8315be-13f5-4df5-ae8b-086bae83484d",
+ "columnName": "call_number_type_name"
+ }
+ },
+ {
+ "name": "item_level_call_number_typeid",
+ "dataType": {
+ "dataType": "rangedUUIDType"
+ },
+ "labelAlias": "Item call number type ID",
+ "visibleByDefault": false
+ },
+ {
+ "name": "item_copy_number",
+ "dataType": {
+ "dataType": "stringType"
+ },
+ "labelAlias": "Item copy number",
+ "visibleByDefault": true
+ },
+ {
+ "name": "item_created_date",
+ "dataType": {
+ "dataType": "dateType"
+ },
+ "labelAlias": "Item created date",
+ "visibleByDefault": false
+ },
+ {
+ "name": "item_effective_call_number",
+ "dataType": {
+ "dataType": "stringType"
+ },
+ "labelAlias": "Item effective call number",
+ "visibleByDefault": true
+ },
+ {
+ "name": "item_effective_call_number_type_name",
+ "dataType": {
+ "dataType": "stringType"
+ },
+ "labelAlias": "Item effective call number type name",
+ "visibleByDefault": false,
+ "idColumnName": "item_effective_call_number_typeid",
+ "source": {
+ "entityTypeId": "5c8315be-13f5-4df5-ae8b-086bae83484d",
+ "columnName": "call_number_type_name"
+ }
+ },
+ {
+ "name": "item_effective_call_number_typeid",
+ "dataType": {
+ "dataType": "rangedUUIDType"
+ },
+ "labelAlias": "Item effective call number type ID",
+ "visibleByDefault": false
+ },
+ {
+ "name": "item_effective_library_code",
+ "dataType": {
+ "dataType": "stringType"
+ },
+ "labelAlias": "Item effective library code",
+ "visibleByDefault": false
+ },
+ {
+ "name": "item_effective_library_id",
+ "dataType": {
+ "dataType": "rangedUUIDType"
+ },
+ "labelAlias": "Item effective library ID",
+ "visibleByDefault": false
+ },
+ {
+ "name": "item_effective_library_name",
+ "dataType": {
+ "dataType": "stringType"
+ },
+ "labelAlias": "Item effective library name",
+ "visibleByDefault": false,
+ "idColumnName": "item_effective_library_id",
+ "source": {
+ "entityTypeId": "cf9f5c11-e943-483c-913b-81d1e338accc",
+ "columnName": "loclibrary_name"
+ }
+ },
+ {
+ "name": "item_effective_location_id",
+ "dataType": {
+ "dataType": "rangedUUIDType"
+ },
+ "labelAlias": "Item effective location ID",
+ "visibleByDefault": false
+ },
+ {
+ "name": "item_effective_location_name",
+ "dataType": {
+ "dataType": "stringType"
+ },
+ "labelAlias": "Item effective location name",
+ "visibleByDefault": true,
+ "idColumnName": "item_effective_location_id",
+ "source": {
+ "entityTypeId": "a9d6305e-fdb4-4fc4-8a73-4a5f76d8410b",
+ "columnName": "location_name"
+ }
+ },
+ {
+ "name": "item_hrid",
+ "dataType": {
+ "dataType": "stringType"
+ },
+ "labelAlias": "Item hrid",
+ "visibleByDefault": false
+ },
+ {
+ "name": "id",
+ "dataType": {
+ "dataType": "rangedUUIDType"
+ },
+ "labelAlias": "Item ID",
+ "visibleByDefault": false
+ },
+ {
+ "name": "item_material_type",
+ "dataType": {
+ "dataType": "stringType"
+ },
+ "labelAlias": "Item material type",
+ "visibleByDefault": false,
+ "idColumnName": "item_material_type_id",
+ "source": {
+ "entityTypeId": "917ea5c8-cafe-4fa6-a942-e2388a88c6f6",
+ "columnName": "material_type_name"
+ }
+ },
+ {
+ "name": "item_material_type_id",
+ "dataType": {
+ "dataType": "rangedUUIDType"
+ },
+ "labelAlias": "Item material ID",
+ "visibleByDefault": false
+ },
+ {
+ "name": "item_permanent_location_id",
+ "dataType": {
+ "dataType": "rangedUUIDType"
+ },
+ "labelAlias": "Item permanent ID",
+ "visibleByDefault": false
+ },
+ {
+ "name": "item_permanent_location_name",
+ "dataType": {
+ "dataType": "stringType"
+ },
+ "labelAlias": "Item permanent location name",
+ "visibleByDefault": false,
+ "idColumnName": "item_permanent_location_id",
+ "source": {
+ "entityTypeId": "a9d6305e-fdb4-4fc4-8a73-4a5f76d8410b",
+ "columnName": "location_name"
+ }
+ },
+ {
+ "name": "item_status",
+ "dataType": {
+ "dataType": "stringType"
+ },
+ "labelAlias": "Item status",
+ "visibleByDefault": false,
+ "source": {
+ "entityTypeId": "a1a37288-1afe-4fa5-ab59-a5bcf5d8ca2d",
+ "columnName": "item_status"
+ }
+ },
+ {
+ "name": "item_temporary_location_id",
+ "dataType": {
+ "dataType": "rangedUUIDType"
+ },
+ "labelAlias": "Item temporary ID",
+ "visibleByDefault": false
+ },
+ {
+ "name": "item_temporary_location_name",
+ "dataType": {
+ "dataType": "stringType"
+ },
+ "labelAlias": "Item temporary location name",
+ "visibleByDefault": false,
+ "idColumnName": "item_temporary_location_id",
+ "source": {
+ "entityTypeId": "a9d6305e-fdb4-4fc4-8a73-4a5f76d8410b",
+ "columnName": "location_name"
+ }
+ },
+ {
+ "name": "item_updated_date",
+ "dataType": {
+ "dataType": "dateType"
+ },
+ "labelAlias": "Item updated date",
+ "visibleByDefault": false
+ }
+ ],
+ "defaultSort": [
+ {
+ "columnName": "item_effective_location_name",
+ "direction": "ASC"
+ },
+ {
+ "columnName": "instance_title",
+ "direction": "ASC"
+ },
+ {
+ "columnName": "instance_primary_contributor",
+ "direction": "ASC"
+ },
+ {
+ "columnName": "id",
+ "direction": "ASC"
+ }
+ ]
+ }
+
+ id = '0cb79a4c-f7eb-4941-a104-745224ae0292'
+
+
+
+
diff --git a/src/main/resources/db/changelog/changes/v1.0.2/update_item_holdingsrecord_entity_type_definition.xml b/src/main/resources/db/changelog/changes/v1.0.2/update_item_holdingsrecord_entity_type_definition.xml
new file mode 100644
index 00000000..958e1928
--- /dev/null
+++ b/src/main/resources/db/changelog/changes/v1.0.2/update_item_holdingsrecord_entity_type_definition.xml
@@ -0,0 +1,193 @@
+
+
+
+
+ {
+ "id": "0cb79a4c-f7eb-4941-a104-745224ae0293",
+ "name": "drv_item_holdingsrecord_instance",
+ "labelAlias": "Items, Holdings records, Instance",
+ "private": true,
+ "columns": [
+ {
+ "name": "holdings_id",
+ "dataType": {
+ "dataType": "rangedUUIDType"
+ },
+ "labelAlias": "Holdings ID",
+ "visibleByDefault": false
+ },
+ {
+ "name": "instance_created_date",
+ "dataType": {
+ "dataType": "dateType"
+ },
+ "labelAlias": "Instance created date",
+ "visibleByDefault": false
+ },
+ {
+ "name": "instance_id",
+ "dataType": {
+ "dataType": "rangedUUIDType"
+ },
+ "labelAlias": "Instance ID",
+ "visibleByDefault": false
+ },
+ {
+ "name": "instance_primary_contributor",
+ "dataType": {
+ "dataType": "stringType"
+ },
+ "labelAlias": "Instance primary contributor",
+ "visibleByDefault": false
+ },
+ {
+ "name": "instance_title",
+ "dataType": {
+ "dataType": "stringType"
+ },
+ "labelAlias": "Instance title",
+ "visibleByDefault": true
+ },
+ {
+ "name": "instance_updated_date",
+ "dataType": {
+ "dataType": "dateType"
+ },
+ "labelAlias": "Instance updated date",
+ "visibleByDefault": false
+ },
+ {
+ "name": "item_barcode",
+ "dataType": {
+ "dataType": "stringType"
+ },
+ "labelAlias": "Item barcode",
+ "visibleByDefault": true
+ },
+ {
+ "name": "item_level_call_number",
+ "dataType": {
+ "dataType": "stringType"
+ },
+ "labelAlias": "Item call number",
+ "visibleByDefault": false
+ },
+ {
+ "name": "item_level_call_number_typeid",
+ "dataType": {
+ "dataType": "rangedUUIDType"
+ },
+ "labelAlias": "Item call number type ID",
+ "visibleByDefault": false
+ },
+ {
+ "name": "item_copy_number",
+ "dataType": {
+ "dataType": "stringType"
+ },
+ "labelAlias": "Item copy number",
+ "visibleByDefault": true
+ },
+ {
+ "name": "item_created_date",
+ "dataType": {
+ "dataType": "dateType"
+ },
+ "labelAlias": "Item created date",
+ "visibleByDefault": false
+ },
+ {
+ "name": "item_effective_call_number",
+ "dataType": {
+ "dataType": "stringType"
+ },
+ "labelAlias": "Item effective call number",
+ "visibleByDefault": true
+ },
+ {
+ "name": "item_effective_call_number_typeid",
+ "dataType": {
+ "dataType": "rangedUUIDType"
+ },
+ "labelAlias": "Item effective call number type ID",
+ "visibleByDefault": false
+ },
+ {
+ "name": "item_effective_location_id",
+ "dataType": {
+ "dataType": "rangedUUIDType"
+ },
+ "labelAlias": "Item location ID",
+ "visibleByDefault": false
+ },
+ {
+ "name": "item_hrid",
+ "dataType": {
+ "dataType": "stringType"
+ },
+ "labelAlias": "Item hrid",
+ "visibleByDefault": false
+ },
+ {
+ "name": "id",
+ "dataType": {
+ "dataType": "rangedUUIDType"
+ },
+ "labelAlias": "Item ID",
+ "visibleByDefault": false
+ },
+ {
+ "name": "item_material_type_id",
+ "dataType": {
+ "dataType": "rangedUUIDType"
+ },
+ "labelAlias": "Item material ID",
+ "visibleByDefault": false
+ },
+ {
+ "name": "item_permanent_location_id",
+ "dataType": {
+ "dataType": "rangedUUIDType"
+ },
+ "labelAlias": "Item permanent ID",
+ "visibleByDefault": false
+ },
+ {
+ "name": "item_status",
+ "dataType": {
+ "dataType": "stringType"
+ },
+ "labelAlias": "Item status",
+ "visibleByDefault": false,
+ "source": {
+ "entityTypeId": "a1a37288-1afe-4fa5-ab59-a5bcf5d8ca2d",
+ "columnName": "item_status"
+ }
+ },
+ {
+ "name": "item_temporary_location_id",
+ "dataType": {
+ "dataType": "rangedUUIDType"
+ },
+ "labelAlias": "Item temporary ID",
+ "visibleByDefault": false
+ },
+ {
+ "name": "item_updated_date",
+ "dataType": {
+ "dataType": "dateType"
+ },
+ "labelAlias": "Item updated date",
+ "visibleByDefault": false
+ }
+ ]
+ }
+
+ id = '0cb79a4c-f7eb-4941-a104-745224ae0293'
+
+
+
+
diff --git a/src/main/resources/db/changelog/changes/v1.0.2/update_user_details_entity_type_definition.xml b/src/main/resources/db/changelog/changes/v1.0.2/update_user_details_entity_type_definition.xml
new file mode 100644
index 00000000..1b26988d
--- /dev/null
+++ b/src/main/resources/db/changelog/changes/v1.0.2/update_user_details_entity_type_definition.xml
@@ -0,0 +1,224 @@
+
+
+
+
+ {
+ "id": "0069cf6f-2833-46db-8a51-8934769b8289",
+ "name":"drv_user_details",
+ "labelAlias" : "Users",
+ "private" : false,
+ "columns": [
+ {
+ "name": "user_active",
+ "dataType":{
+ "dataType":"booleanType"
+ },
+ "labelAlias": "User active",
+ "visibleByDefault": true,
+ "values": [
+ {
+ "value": "true",
+ "label": "True"
+ },
+ {
+ "value": "false",
+ "label": "False"
+ }
+ ]
+ },
+ {
+ "name": "user_barcode",
+ "dataType":{
+ "dataType":"stringType"
+ },
+ "labelAlias": "User barcode",
+ "visibleByDefault": true
+ },
+ {
+ "name": "user_created_date",
+ "dataType":{
+ "dataType":"dateType"
+ },
+ "labelAlias": "User created date",
+ "visibleByDefault": false
+ },
+ {
+ "name": "user_date_of_birth",
+ "dataType":{
+ "dataType":"dateType"
+ },
+ "labelAlias": "User date of birth",
+ "visibleByDefault": false
+ },
+ {
+ "name": "user_email",
+ "dataType":{
+ "dataType":"stringType"
+ },
+ "labelAlias": "User email",
+ "visibleByDefault": false
+ },
+ {
+ "name": "user_enrollment_date",
+ "dataType":{
+ "dataType":"dateType"
+ },
+ "labelAlias": "User enrollment date",
+ "visibleByDefault": false
+ },
+ {
+ "name": "user_expiration_date",
+ "dataType":{
+ "dataType":"dateType"
+ },
+ "labelAlias": "User expiration date",
+ "visibleByDefault": false
+ },
+ {
+ "name": "user_external_system_id",
+ "dataType":{
+ "dataType":"stringType"
+ },
+ "labelAlias": "User external system ID",
+ "visibleByDefault": false
+ },
+ {
+ "name": "user_first_name",
+ "dataType":{
+ "dataType":"stringType"
+ },
+ "labelAlias": "User first name",
+ "visibleByDefault": true
+ },
+ {
+ "name": "id",
+ "dataType":{
+ "dataType":"rangedUUIDType"
+ },
+ "labelAlias": "User ID",
+ "visibleByDefault": true
+ },
+ {
+ "name": "user_last_name",
+ "dataType":{
+ "dataType":"stringType"
+ },
+ "labelAlias": "User last name",
+ "visibleByDefault": true
+ },
+ {
+ "name": "user_middle_name",
+ "dataType":{
+ "dataType":"stringType"
+ },
+ "labelAlias": "User middle name",
+ "visibleByDefault": false
+ },
+ {
+ "name": "user_mobile_phone",
+ "dataType":{
+ "dataType":"stringType"
+ },
+ "labelAlias": "User mobile phone",
+ "visibleByDefault": false
+ },
+ {
+ "name": "user_patron_group",
+ "dataType":{
+ "dataType":"stringType"
+ },
+ "labelAlias": "User patron group",
+ "visibleByDefault": false,
+ "idColumnName": "user_patron_group_id",
+ "source": {
+ "entityTypeId": "e611264d-377e-4d87-a93f-f1ca327d3db0",
+ "columnName": "group"
+ }
+ },
+ {
+ "name": "user_patron_group_id",
+ "dataType":{
+ "dataType":"rangedUUIDType"
+ },
+ "labelAlias": "User patron group ID",
+ "visibleByDefault": false
+ },
+ {
+ "name": "user_phone",
+ "dataType":{
+ "dataType":"stringType"
+ },
+ "labelAlias": "User phone",
+ "visibleByDefault": true
+ },
+ {
+ "name": "user_preferred_contact_type",
+ "dataType":{
+ "dataType":"stringType"
+ },
+ "labelAlias": "User preferred contact type",
+ "visibleByDefault": false,
+ "values": [
+ {
+ "value": "Email",
+ "label": "Email"
+ },
+ {
+ "value": "Mail (Primary Address)",
+ "label": "Mail (Primary Address)"
+ },
+ {
+ "value": "Text Message",
+ "label": "Text Message"
+ }
+ ]
+ },
+ {
+ "name": "user_preferred_first_name",
+ "dataType":{
+ "dataType":"stringType"
+ },
+ "labelAlias": "User preferred first name",
+ "visibleByDefault": true
+ },
+ {
+ "name": "user_primary_address",
+ "dataType":{
+ "dataType":"stringType"
+ },
+ "labelAlias": "User primary address",
+ "visibleByDefault": false
+ },
+ {
+ "name": "user_updated_date",
+ "dataType":{
+ "dataType":"dateType"
+ },
+ "labelAlias": "User updated date",
+ "visibleByDefault": false
+ },
+ {
+ "name": "username",
+ "dataType":{
+ "dataType":"stringType"
+ },
+ "labelAlias": "Username",
+ "visibleByDefault": true
+ }
+ ],
+ "defaultSort": [
+ {
+ "columnName": "id",
+ "direction": "ASC"
+ }
+ ]
+ }
+
+ id = '0069cf6f-2833-46db-8a51-8934769b8289'
+
+
+
+
diff --git a/src/main/resources/db/changelog/changes/v1.0.3/add_materialized_view_indexes.xml b/src/main/resources/db/changelog/changes/v1.0.3/add_materialized_view_indexes.xml
new file mode 100644
index 00000000..d9b5eaec
--- /dev/null
+++ b/src/main/resources/db/changelog/changes/v1.0.3/add_materialized_view_indexes.xml
@@ -0,0 +1,22 @@
+
+
+
+
+
+ SELECT COUNT(*) FROM pg_matviews WHERE schemaname = '${tenant_id}_mod_fqm_manager'AND matviewname = 'drv_inventory_item_status';
+
+
+ SELECT COUNT(*) FROM pg_matviews WHERE schemaname = '${tenant_id}_mod_fqm_manager'AND matviewname = 'drv_circulation_loan_status';
+
+
+
+
+
+
+
+
+
+
diff --git a/src/main/resources/db/changelog/changes/v1.0.3/changelog-v1.0.3.xml b/src/main/resources/db/changelog/changes/v1.0.3/changelog-v1.0.3.xml
new file mode 100644
index 00000000..52ca9bd9
--- /dev/null
+++ b/src/main/resources/db/changelog/changes/v1.0.3/changelog-v1.0.3.xml
@@ -0,0 +1,9 @@
+
+
+
+
+
+
diff --git a/src/main/resources/db/changelog/changes/v1.0.3/refactor_drv_item_callnumber_location.xml b/src/main/resources/db/changelog/changes/v1.0.3/refactor_drv_item_callnumber_location.xml
new file mode 100644
index 00000000..ad326b43
--- /dev/null
+++ b/src/main/resources/db/changelog/changes/v1.0.3/refactor_drv_item_callnumber_location.xml
@@ -0,0 +1,53 @@
+
+
+
+
+
+
+
+
+
+ SELECT COUNT(*) FROM pg_matviews WHERE schemaname = '${tenant_id}_mod_fqm_manager'AND matviewname = 'drv_inventory_item_status';
+
+
+
+
+ SELECT
+ src_inventory_item.id,
+ src_inventory_item.holdingsrecordid AS item_holdings_record_id,
+ src_inventory_item.jsonb ->> 'hrid'::text AS item_hrid,
+ src_inventory_item.jsonb ->> 'itemLevelCallNumber'::text AS item_level_call_number,
+ src_inventory_item.jsonb ->> 'itemLevelCallNumberTypeId'::text AS item_level_call_number_typeid,
+ call_item_number_type_ref_data.jsonb ->> 'name'::text AS item_level_call_number_type_name,
+ concat_ws(', '::text, NULLIF((src_inventory_item.jsonb -> 'effectiveCallNumberComponents'::text) ->> 'prefix'::text, ''::text), NULLIF((src_inventory_item.jsonb -> 'effectiveCallNumberComponents'::text) ->> 'callNumber'::text, ''::text), NULLIF((src_inventory_item.jsonb -> 'effectiveCallNumberComponents'::text) ->> 'suffix'::text, ''::text), NULLIF(src_inventory_item.jsonb ->> 'copyNumber'::text, ''::text)) AS item_effective_call_number,
+ (src_inventory_item.jsonb -> 'effectiveCallNumberComponents'::text) ->> 'typeId'::text AS item_effective_call_number_typeid,
+ call_number_type_ref_data.jsonb ->> 'name'::text AS item_effective_call_number_type_name,
+ loclib_ref_data.id AS item_effective_library_id,
+ loclib_ref_data.jsonb ->> 'name'::text AS item_effective_library_name,
+ loclib_ref_data.jsonb ->> 'code'::text AS item_effective_library_code,
+ "left"(lower(${tenant_id}_mod_inventory_storage.f_unaccent((src_inventory_item.jsonb -> 'status'::text) ->> 'name'::text)), 600) AS item_status,
+ src_inventory_item.jsonb ->> 'copyNumber'::text AS item_copy_number,
+ src_inventory_item.jsonb ->> 'barcode'::text AS item_barcode,
+ (src_inventory_item.jsonb -> 'metadata'::text) ->> 'createdDate'::text AS item_created_date,
+ (src_inventory_item.jsonb -> 'metadata'::text) ->> 'updatedDate'::text AS item_updated_date,
+ effective_location_ref_data.id AS item_effective_location_id,
+ effective_location_ref_data.jsonb ->> 'name'::text AS item_effective_location_name,
+ permanent_location_ref_data.id AS item_permanent_location_id,
+ permanent_location_ref_data.jsonb ->> 'name'::text AS item_permanent_location_name,
+ material_type_ref_data.id AS item_material_type_id,
+ material_type_ref_data.jsonb ->> 'name'::text AS item_material_type
+ FROM src_inventory_item
+ LEFT JOIN src_inventory_location effective_location_ref_data ON effective_location_ref_data.id = src_inventory_item.effectivelocationid
+ LEFT JOIN src_inventory_call_number_type call_number_type_ref_data ON call_number_type_ref_data.id::text = ((src_inventory_item.jsonb -> 'effectiveCallNumberComponents'::text) ->> 'typeId'::text)
+ LEFT JOIN src_inventory_call_number_type call_item_number_type_ref_data ON call_item_number_type_ref_data.id::text = (src_inventory_item.jsonb ->> 'itemLevelCallNumberTypeId'::text)
+ LEFT JOIN src_inventory_loclibrary loclib_ref_data ON loclib_ref_data.id = effective_location_ref_data.libraryid
+ LEFT JOIN src_inventory_location permanent_location_ref_data ON permanent_location_ref_data.id = src_inventory_item.permanentlocationid
+ LEFT JOIN src_inventory_material_type material_type_ref_data ON material_type_ref_data.id = src_inventory_item.materialtypeid;
+
+
+
+
diff --git a/src/main/resources/swagger.api/mod-fqm-manager.yaml b/src/main/resources/swagger.api/mod-fqm-manager.yaml
index 789f6019..c38a0739 100644
--- a/src/main/resources/swagger.api/mod-fqm-manager.yaml
+++ b/src/main/resources/swagger.api/mod-fqm-manager.yaml
@@ -44,6 +44,19 @@ paths:
$ref: '#/components/responses/badRequestResponse'
'500':
$ref: '#/components/responses/internalServerErrorResponse'
+ /entity-types/materialized-views/refresh:
+ post:
+ operationId: refreshMaterializedViews
+ tags:
+ - materializedViews
+ description: Refresh all materialized views for a tenant.
+ responses:
+ '204':
+ description: 'Views refreshed'
+ '400':
+ $ref: '#/components/responses/badRequestResponse'
+ '500':
+ $ref: '#/components/responses/internalServerErrorResponse'
components:
diff --git a/src/test/java/org/folio/fqm/controller/MaterializedViewRefreshControllerTest.java b/src/test/java/org/folio/fqm/controller/MaterializedViewRefreshControllerTest.java
new file mode 100644
index 00000000..27acab39
--- /dev/null
+++ b/src/test/java/org/folio/fqm/controller/MaterializedViewRefreshControllerTest.java
@@ -0,0 +1,43 @@
+package org.folio.fqm.controller;
+
+import org.folio.fqm.resource.MaterializedViewRefreshController;
+import org.folio.fqm.service.MaterializedViewRefreshService;
+import org.folio.spring.FolioExecutionContext;
+import org.folio.spring.integration.XOkapiHeaders;
+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.test.web.servlet.MockMvc;
+import org.springframework.test.web.servlet.RequestBuilder;
+import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
+
+import static org.mockito.Mockito.doNothing;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+import static org.springframework.http.MediaType.APPLICATION_JSON;
+import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
+
+@WebMvcTest(MaterializedViewRefreshController.class)
+class MaterializedViewRefreshControllerTest {
+ @Autowired
+ private MockMvc mockMvc;
+ @MockBean
+ private FolioExecutionContext executionContext;
+ @MockBean
+ private MaterializedViewRefreshService materializedViewRefreshService;
+
+ @Test
+ void refreshMaterializedViewsTest() throws Exception {
+ String tenantId = "tenant_01";
+ RequestBuilder requestBuilder = MockMvcRequestBuilders.post("/entity-types/materialized-views/refresh")
+ .header(XOkapiHeaders.TENANT, tenantId)
+ .contentType(APPLICATION_JSON);
+ when(executionContext.getTenantId()).thenReturn(tenantId);
+ doNothing().when(materializedViewRefreshService).refreshMaterializedViews(tenantId);
+ mockMvc.perform(requestBuilder)
+ .andExpect(status().isNoContent());
+ verify(materializedViewRefreshService, times(1)).refreshMaterializedViews(tenantId);
+ }
+}
diff --git a/src/test/java/org/folio/fqm/repository/MaterializedViewRefreshRepositoryTest.java b/src/test/java/org/folio/fqm/repository/MaterializedViewRefreshRepositoryTest.java
new file mode 100644
index 00000000..565b963c
--- /dev/null
+++ b/src/test/java/org/folio/fqm/repository/MaterializedViewRefreshRepositoryTest.java
@@ -0,0 +1,29 @@
+package org.folio.fqm.repository;
+
+import org.jooq.DSLContext;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.mockito.InjectMocks;
+import org.mockito.Mock;
+import org.mockito.junit.jupiter.MockitoExtension;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.Mockito.*;
+
+@ExtendWith(MockitoExtension.class)
+class MaterializedViewRefreshRepositoryTest {
+ @InjectMocks
+ private MaterializedViewRefreshRepository materializedViewRefreshRepository;
+ @Mock
+ private DSLContext jooqContext;
+
+ @Test
+ void refreshMaterializedViewsTest() {
+ String tenantId = "tenant_01";
+ String expectedItemStatusSql = "REFRESH MATERIALIZED VIEW CONCURRENTLY tenant_01_mod_fqm_manager.drv_inventory_item_status";
+ String expectedLoanStatusSql = "REFRESH MATERIALIZED VIEW CONCURRENTLY tenant_01_mod_fqm_manager.drv_circulation_loan_status";
+ when(jooqContext.execute(anyString())).thenReturn(1);
+ materializedViewRefreshRepository.refreshMaterializedViews(tenantId);
+ verify(jooqContext, times(1)).execute(expectedItemStatusSql);
+ verify(jooqContext, times(1)).execute(expectedLoanStatusSql);
+ }
+}
diff --git a/src/test/java/org/folio/fqm/repository/QueryRepositoryTest.java b/src/test/java/org/folio/fqm/repository/QueryRepositoryTest.java
index bce45bdf..f1b60bfc 100644
--- a/src/test/java/org/folio/fqm/repository/QueryRepositoryTest.java
+++ b/src/test/java/org/folio/fqm/repository/QueryRepositoryTest.java
@@ -12,9 +12,9 @@
import java.time.Duration;
import java.time.OffsetDateTime;
+import java.time.temporal.ChronoUnit;
import java.util.List;
import java.util.UUID;
-import java.util.concurrent.TimeUnit;
import static org.junit.jupiter.api.Assertions.*;
import static org.springframework.util.StringUtils.hasText;
@@ -86,15 +86,26 @@ void shouldGetQueriesForDeletion() throws InterruptedException {
{"field1": {"$in": ["value1", "value2", "value3", "value4", "value5" ] }}
""";
List fields = List.of("id", "field1");
- Query query = new Query(queryId, UUID.randomUUID(), fqlQuery, fields,
+ Query queryToDelete = new Query(queryId, UUID.randomUUID(), fqlQuery, fields,
UUID.randomUUID(), OffsetDateTime.now(), null, QueryStatus.IN_PROGRESS, null);
- repo.saveQuery(query);
+ repo.saveQuery(queryToDelete);
Query updatedQuery = new Query(queryId, UUID.randomUUID(), fqlQuery, fields,
UUID.randomUUID(), null, OffsetDateTime.now(), QueryStatus.SUCCESS, null);
+
+ UUID queryId2 = UUID.randomUUID();
+ Query queryToNotDelete = new Query(queryId2, UUID.randomUUID(), fqlQuery, fields,
+ UUID.randomUUID(), OffsetDateTime.now().plusHours(1), null, QueryStatus.IN_PROGRESS, null);
+ repo.saveQuery(queryToNotDelete);
+
repo.updateQuery(updatedQuery.queryId(), updatedQuery.status(), updatedQuery.endDate(), updatedQuery.failureReason());
- List expectedIds = List.of(queryId);
- List actualIds = repo.getQueryIdsCompletedBefore(Duration.ofMillis(0));
- assertEquals(expectedIds, actualIds);
+ UUID expectedId = queryId;
+ List actualIds = repo.getQueryIdsStartedBefore(Duration.ofMillis(0));
+
+ assertTrue(actualIds.contains(expectedId));
+ assertFalse(actualIds.contains(queryId2));
+
+ // Clean up
+ repo.deleteQueries(List.of(queryId2));
}
@Test
diff --git a/src/test/java/org/folio/fqm/service/MaterializedViewRefreshServiceTest.java b/src/test/java/org/folio/fqm/service/MaterializedViewRefreshServiceTest.java
new file mode 100644
index 00000000..70afa579
--- /dev/null
+++ b/src/test/java/org/folio/fqm/service/MaterializedViewRefreshServiceTest.java
@@ -0,0 +1,28 @@
+package org.folio.fqm.service;
+
+import org.folio.fqm.repository.MaterializedViewRefreshRepository;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.mockito.InjectMocks;
+import org.mockito.Mock;
+import org.mockito.junit.jupiter.MockitoExtension;
+
+import static org.mockito.Mockito.doNothing;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+
+@ExtendWith(MockitoExtension.class)
+class MaterializedViewRefreshServiceTest {
+ @InjectMocks
+ private MaterializedViewRefreshService materializedViewRefreshService;
+ @Mock
+ private MaterializedViewRefreshRepository materializedViewRefreshRepository;
+
+ @Test
+ void refreshMaterializedViewsTest() {
+ String tenantId = "tenant_01";
+ doNothing().when(materializedViewRefreshRepository).refreshMaterializedViews(tenantId);
+ materializedViewRefreshService.refreshMaterializedViews(tenantId);
+ verify(materializedViewRefreshRepository, times(1)).refreshMaterializedViews(tenantId);
+ }
+}
diff --git a/src/test/java/org/folio/fqm/service/QueryManagementServiceTest.java b/src/test/java/org/folio/fqm/service/QueryManagementServiceTest.java
index 36b3d4bd..3885c91d 100644
--- a/src/test/java/org/folio/fqm/service/QueryManagementServiceTest.java
+++ b/src/test/java/org/folio/fqm/service/QueryManagementServiceTest.java
@@ -381,7 +381,7 @@ void validateQueryShouldThrowErrorForInvalidFql() {
void shouldPurgeQueries() {
List queryIds = List.of(UUID.randomUUID(), UUID.randomUUID());
PurgedQueries expectedPurgedQueries = new PurgedQueries().deletedQueryIds(queryIds);
- when(queryRepository.getQueryIdsCompletedBefore(Mockito.any())).thenReturn(queryIds);
+ when(queryRepository.getQueryIdsStartedBefore(Mockito.any())).thenReturn(queryIds);
PurgedQueries actualPurgedQueries = queryManagementService.deleteOldQueries();
verify(queryResultsRepository, times(1)).deleteQueryResults(queryIds);
verify(queryRepository, times(1)).deleteQueries(queryIds);