Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

UIEXPMGR-113 Support claims export #212

Open
wants to merge 5 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@

## (IN PROGRESS)

* Support EDIFACT claims export. Refs UIEXPMGR-113.
* Support CSV claims export. Refs UIEXPMGR-114.

## [3.2.0](https://github.com/folio-org/ui-export-manager/tree/v3.2.0) (2024-10-31)
[Full Changelog](https://github.com/folio-org/ui-export-manager/compare/v3.1.1...v3.2.0)

Expand Down
27 changes: 20 additions & 7 deletions src/ExportEdiJobs/ExportEdiJobDetails/ExportEdiJobDetails.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
import React, { useCallback } from 'react';
import { useCallback } from 'react';
import PropTypes from 'prop-types';
import { FormattedMessage, useIntl } from 'react-intl';
import {
FormattedMessage,
useIntl,
} from 'react-intl';

import {
Col,
Expand All @@ -26,14 +29,24 @@ import { useNavigation } from '../../hooks';
import { useExportJobQuery } from '../../ExportJob/apiQuery';
import { ExportEdiJobDetailsActionMenu } from '../ExportEdiJobDetailsActionMenu';

const FILE_DOWNLOAD = 'File download';

const getSentToValue = (exportConfig, intl) => {
if (exportConfig?.transmissionMethod === FILE_DOWNLOAD) {
return intl.formatMessage({ id: 'ui-export-manager.exportJob.download' });
}

return `${exportConfig?.ediFtp?.serverAddress}${exportConfig?.ediFtp?.orderDirectory || ''}`;
};

export const ExportEdiJobDetails = ({ refetchJobs, uuid }) => {
const { formatMessage } = useIntl();
const intl = useIntl();
const { navigateToEdiJobs } = useNavigation();
const perms = useExportManagerPerms()
const perms = useExportManagerPerms();

const {
hasAllExportManagerPerms
} = perms
} = perms;

const {
isLoading: isJobLoading,
Expand All @@ -54,7 +67,7 @@ export const ExportEdiJobDetails = ({ refetchJobs, uuid }) => {
isLoading: isOrganizationLoading,
} = useOrganization(exportConfig?.vendorId);

const title = formatMessage(
const title = intl.formatMessage(
{ id: 'ui-export-manager.exportJob' },
{ jobId: exportJob.jobId },
);
Expand Down Expand Up @@ -164,7 +177,7 @@ export const ExportEdiJobDetails = ({ refetchJobs, uuid }) => {
<Col xs={3}>
<KeyValue
label={<FormattedMessage id="ui-export-manager.exportJob.sentTo" />}
value={`${exportConfig?.ediFtp?.serverAddress}${exportConfig?.ediFtp?.orderDirectory || ''}`}
value={getSentToValue(exportConfig, intl)}
/>
</Col>

Expand Down
22 changes: 18 additions & 4 deletions src/ExportEdiJobs/ExportEdiJobsFilters/ExportEdiJobsFilters.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,17 +15,20 @@
PluggableOrganizationFilter,
} from '@folio/stripes-acq-components';

import { EXPORT_JOB_STATUS_OPTIONS } from '../../common/constants';
import {
EXPORT_JOB_STATUS_OPTIONS,
ORGANIZATION_INTEGRATION_TYPE_OPTIONS,
} from '../../common/constants';
import { ExportMethodFilter } from './ExportMethodFilter';

const applyFiltersAdapter = (applyFilters) => ({ name, values }) => applyFilters(name, values);

export const ExportEdiJobsFilters = ({
disabled = false,
activeFilters,
applyFilters,
disabled = false,
activeFilters,
applyFilters,
}) => {
const adaptedApplyFilters = useCallback(

Check warning on line 31 in src/ExportEdiJobs/ExportEdiJobsFilters/ExportEdiJobsFilters.js

View workflow job for this annotation

GitHub Actions / github-actions-ci

React Hook useCallback received a function whose dependencies are unknown. Pass an inline function instead

Check warning on line 31 in src/ExportEdiJobs/ExportEdiJobsFilters/ExportEdiJobsFilters.js

View workflow job for this annotation

GitHub Actions / github-actions-ci

React Hook useCallback received a function whose dependencies are unknown. Pass an inline function instead
applyFiltersAdapter(applyFilters),
[applyFilters],
);
Expand All @@ -43,6 +46,17 @@
closedByDefault={false}
/>

<AcqCheckboxFilter
id="integration-type-filter"
activeFilters={activeFilters?.integrationType}
disabled={disabled}
labelId="ui-export-manager.exportJob.integrationType"
name="integrationType"
onChange={adaptedApplyFilters}
options={ORGANIZATION_INTEGRATION_TYPE_OPTIONS}
closedByDefault={false}
/>

<ExportMethodFilter
id="exportConfigId"
activeFilters={activeFilters?.exportConfigId}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,27 +1,48 @@
import React from 'react';
import { useQuery } from 'react-query';

import {
useNamespace,
useOkapiKy,
} from '@folio/stripes/core';
import {
CQL_OR_OPERATOR,
DATA_EXPORT_CONFIGS_API,
LIMIT_MAX,
ORGANIZATION_INTEGRATION_EXPORT_TYPES,
} from '@folio/stripes-acq-components';

const JOIN_STRING = ` ${CQL_OR_OPERATOR} `;

import { LIMIT_MAX } from '@folio/stripes-acq-components';
const buildConfigNameCql = (organizationId) => {
const configName = ORGANIZATION_INTEGRATION_EXPORT_TYPES
.map(type => `"${type}_${organizationId}*"`)
.join(JOIN_STRING);

return `configName==(${configName})`;
};

const buildTypeCql = () => {
const type = ORGANIZATION_INTEGRATION_EXPORT_TYPES
.map((t) => `"${t}"`)
.join(JOIN_STRING);

return `type==(${type})`;
};

export const useConfigs = (organizationId) => {
const ky = useOkapiKy();
const [namespace] = useNamespace({ key: 'edi-job-configs' });

const searchParams = {
query: organizationId
? `configName==EDIFACT_ORDERS_EXPORT_${organizationId}*`
: 'type==EDIFACT_ORDERS_EXPORT',
? buildConfigNameCql(organizationId)
: buildTypeCql(),
limit: LIMIT_MAX,
};

const { isFetching, data = {} } = useQuery(
[namespace, organizationId],
() => ky.get('data-export-spring/configs', { searchParams }).json(),
() => ky.get(DATA_EXPORT_CONFIGS_API, { searchParams }).json(),
);

return ({
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ describe('useConfigs', () => {
'data-export-spring/configs',
{
searchParams: {
query: 'type==EDIFACT_ORDERS_EXPORT',
query: 'type==("CLAIMS" or "EDIFACT_ORDERS_EXPORT")',
limit: LIMIT_MAX,
},
},
Expand Down
29 changes: 25 additions & 4 deletions src/ExportEdiJobs/apiQuery.js
Original file line number Diff line number Diff line change
@@ -1,14 +1,29 @@
import { useInfiniteQuery } from 'react-query';
import queryString from 'query-string';
import { useInfiniteQuery } from 'react-query';

import { useOkapiKy, useNamespace } from '@folio/stripes/core';
import {
useOkapiKy,
useNamespace,
} from '@folio/stripes/core';
import {
buildDateRangeQuery,
CQL_OR_OPERATOR,
makeQueryBuilder,
ORGANIZATION_INTEGRATION_EXPORT_TYPES,
} from '@folio/stripes-acq-components';

const buildIntegrationTypesCqlValue = (type) => {
if (!Array.isArray(type)) return type;

const value = type
.map((t) => `"${t}"`)
.join(` ${CQL_OR_OPERATOR} `);

return `(${value})`;
};

const buildJobsQuery = makeQueryBuilder(
'type="EDIFACT_ORDERS_EXPORT"',
`type=${buildIntegrationTypesCqlValue(ORGANIZATION_INTEGRATION_EXPORT_TYPES)}`,
(query) => {
return `name="*${query}*" or description="*${query}*"`;
},
Expand All @@ -18,6 +33,9 @@ const buildJobsQuery = makeQueryBuilder(
startTime: buildDateRangeQuery.bind(null, ['startTime']),
vendorId: (id) => `jsonb.exportTypeSpecificParameters.vendorEdiOrdersExportConfig.vendorId=="${id}"`,
exportConfigId: (id) => `jsonb.exportTypeSpecificParameters.vendorEdiOrdersExportConfig.exportConfigId=="${id}"`,
integrationType: (type) => (
`jsonb.exportTypeSpecificParameters.vendorEdiOrdersExportConfig.integrationType==${buildIntegrationTypesCqlValue(type)}`
),
},
{
jobId: 'name',
Expand All @@ -41,7 +59,10 @@ export const useExportEdiJobsQuery = (search, pagination, filters) => {
searchParams: {
limit: pagination.limit,
offset: pagination.offset,
query: buildJobsQuery(queryString.parse(`${search}&type=EDIFACT_ORDERS_EXPORT`)),
query: buildJobsQuery({
type: ORGANIZATION_INTEGRATION_EXPORT_TYPES,
...queryString.parse(`${search}`),
}),
},
};

Expand Down
6 changes: 3 additions & 3 deletions src/ExportJobs/ExportJobsFilters/ExportJobsFilters.js
Original file line number Diff line number Diff line change
Expand Up @@ -49,11 +49,11 @@
}));

export const ExportJobsFilters = ({
disabled = false,
activeFilters,
applyFilters,
disabled = false,
activeFilters,
applyFilters,
}) => {
const adaptedApplyFilters = useCallback(

Check warning on line 56 in src/ExportJobs/ExportJobsFilters/ExportJobsFilters.js

View workflow job for this annotation

GitHub Actions / github-actions-ci

React Hook useCallback received a function whose dependencies are unknown. Pass an inline function instead

Check warning on line 56 in src/ExportJobs/ExportJobsFilters/ExportJobsFilters.js

View workflow job for this annotation

GitHub Actions / github-actions-ci

React Hook useCallback received a function whose dependencies are unknown. Pass an inline function instead
applyFiltersAdapter(applyFilters),
[applyFilters],
);
Expand Down
92 changes: 75 additions & 17 deletions src/ExportJobs/apiQuery.js
Original file line number Diff line number Diff line change
@@ -1,36 +1,94 @@
import { useInfiniteQuery } from 'react-query';
import queryString from 'query-string';
import { useInfiniteQuery } from 'react-query';

import { useOkapiKy } from '@folio/stripes/core';
import {
buildDateRangeQuery,
CQL_AND_OPERATOR,
CQL_OR_OPERATOR,
makeQueryBuilder,
ORGANIZATION_INTEGRATION_EXPORT_TYPES,
} from '@folio/stripes-acq-components';

const BULK_EDIT_QUERY = 'type==("BULK_EDIT_IDENTIFIERS" or "BULK_EDIT_QUERY" or "BULK_EDIT_UPDATE")';
import { EXPORT_FILE_TYPE } from '../common/constants';
import { EXPORT_JOB_TYPE_KEYS } from './constants';

const AND_SEPARATOR = ` ${CQL_AND_OPERATOR} `;
const OR_SEPARATOR = ` ${CQL_OR_OPERATOR} `;
const BULK_EDIT_TYPE = '"BULK_EDIT_IDENTIFIERS" or "BULK_EDIT_QUERY" or "BULK_EDIT_UPDATE"';
const BULK_EDIT = 'BULK_EDIT';
const EDI_ORDERS_FILE_FORMAT_KEY = 'jsonb.exportTypeSpecificParameters.vendorEdiOrdersExportConfig.fileFormat';

const ORDERS_JOB_TYPES_CQL_VALUE = ORGANIZATION_INTEGRATION_EXPORT_TYPES
.map((t) => `"${t}"`)
.join(` ${CQL_OR_OPERATOR} `);

const buildOrdersJobTypeQueryDict = (fileType) => ({
type: `${ORDERS_JOB_TYPES_CQL_VALUE}`,
[EDI_ORDERS_FILE_FORMAT_KEY]: `"${fileType}"`,
});

const typeQueryDict = {
[EXPORT_JOB_TYPE_KEYS.BULK_EDIT]: { type: `${BULK_EDIT_TYPE}` },
[EXPORT_JOB_TYPE_KEYS.ORDERS_CSV]: buildOrdersJobTypeQueryDict(EXPORT_FILE_TYPE.csv),
[EXPORT_JOB_TYPE_KEYS.ORDERS_EDI]: buildOrdersJobTypeQueryDict(EXPORT_FILE_TYPE.edi),
};

/*
* Function to build CQL query from an array of dictionaries (objects)
*/
const buildCqlQueryFromDicts = (arr) => {
/* Group objects by their keys to optimize the CQL query */
const grouped = arr.reduce((acc, obj) => {
const key = Object.keys(obj)
.sort((a, b) => a.localeCompare(b))
.join('_');

acc[key] = acc[key] || [];
acc[key].push(obj);

return acc;
}, {});

/* Transform grouped objects into CQL conditions */
const cqlParts = Object.values(grouped).map((group) => {
const uniqueConditions = group.reduce((acc, obj) => {
Object.entries(obj).forEach(([key, value]) => {
if (!acc[key]) {
acc[key] = new Set();
}
acc[key].add(value);
});

return acc;
}, {});

const conditions = Object.entries(uniqueConditions)
.map(([key, values]) => `${key}==(${[...values].join(OR_SEPARATOR)})`)
.join(AND_SEPARATOR);

return `(${conditions})`;
});

return cqlParts.join(OR_SEPARATOR);
};

/*
* Function to map job types to their CQL representation
*/
const mapJobTypesToCql = (types) => {
const queryDicts = types.map((type) => typeQueryDict[type] ?? { type });

return buildCqlQueryFromDicts(queryDicts);
};

const buildJobsQuery = makeQueryBuilder(
'cql.allRecords=1',
(query) => {
return `name="*${query}*" or description="*${query}*"`;
},
(query) => `name="*${query}*" or description="*${query}*"`,
'sortby name/sort.descending',
{
endTime: buildDateRangeQuery.bind(null, ['endTime']),
startTime: buildDateRangeQuery.bind(null, ['startTime']),
type: (query) => {
if (query === BULK_EDIT) {
return BULK_EDIT_QUERY;
} else if (Array.isArray(query)) {
return `type==(${query.map(v => {
if (v === BULK_EDIT) {
return BULK_EDIT_TYPE;
} else return `"${v}"`;
}).join(' or ')})`;
} else return `type==${query}`;
},
type: (query) => mapJobTypesToCql(Array.isArray(query) ? query : [query]),
},
{
jobId: 'name',
Expand Down
Loading
Loading