Skip to content

Commit

Permalink
CM-508: add payroll files tab (#34)
Browse files Browse the repository at this point in the history
* CM-508: add payroll files tab

* CM-508: fix linter

---------

Co-authored-by: Jan <[email protected]>
  • Loading branch information
jdolkowski and Jan authored Feb 23, 2024
1 parent 988496a commit f35ebde
Show file tree
Hide file tree
Showing 8 changed files with 252 additions and 7 deletions.
10 changes: 10 additions & 0 deletions src/actions.js
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,11 @@ const PAYROLL_PROJECTION = (modulesManager) => [
'isDeleted',
];

const CSV_RECONCILIATION_PROJECTION = () => [
'fileName',
'status',
];

const PAYMENT_METHOD_PROJECTION = () => [
'paymentMethods {name}',
];
Expand Down Expand Up @@ -129,6 +134,11 @@ export function fetchPaymentPoint(modulesManager, params) {
return graphql(payload, ACTION_TYPE.GET_PAYMENT_POINT);
}

export function fetchPayrollPaymentFiles(modulesManager, params) {
const payload = formatPageQueryWithCount('csvReconciliationUpload', params, CSV_RECONCILIATION_PROJECTION());
return graphql(payload, ACTION_TYPE.GET_PAYROLL_PAYMENT_FILES);
}

export const clearPaymentPoint = () => (dispatch) => {
dispatch({
type: CLEAR(ACTION_TYPE.GET_PAYMENT_POINT),
Expand Down
111 changes: 111 additions & 0 deletions src/components/payroll/PayrollPaymentFilesSearcher.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
import React from 'react';
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';

import { IconButton, Tooltip } from '@material-ui/core';
import DownloadIcon from '@material-ui/icons/CloudDownload';

import {
Searcher,
useModulesManager,
useTranslations,
} from '@openimis/fe-core';
import {
DEFAULT_PAGE_SIZE, MODULE_NAME,
ROWS_PER_PAGE_OPTIONS, PAYROLL_PAYMENT_FILE_STATUS,
} from '../../constants';
import { fetchPayrollPaymentFiles } from '../../actions';
import downloadPayroll from '../../utils/export';

function PayrollPaymentFilesSearcher({
fetchingPayrollFiles,
fetchedPayrollFiles,
errorPayrollFiles,
files,
pageInfo,
totalCount,
fetchPayrollPaymentFiles,
payrollUuid,
}) {
const modulesManager = useModulesManager();
const { formatMessage, formatMessageWithValues } = useTranslations(MODULE_NAME, modulesManager);

const headers = () => [
'payrollPaymentFile.fileName',
'payrollPaymentFile.status',
'payrollPaymentFile.download',
];

const defaultFilters = () => {
const filters = {
isDeleted: {
value: false,
filter: 'isDeleted: false',
},
};
if (payrollUuid) {
filters.payrollId = {
value: payrollUuid,
filter: `payroll_Id: "${payrollUuid}"`,
};
}
return filters;
};

const download = (payrollId, fileName) => {
downloadPayroll(payrollId, fileName, false);
};

const fetchFiles = (params) => fetchPayrollPaymentFiles(modulesManager, params);

const rowIdentifier = (file) => file.fileName;

const itemFormatters = () => [
(file) => file.fileName,
(file) => file.status,
(file) => (
<Tooltip title={formatMessage('tooltip.delete')}>
<IconButton
onClick={() => download(payrollUuid, file.fileName)}
disabled={file.status !== PAYROLL_PAYMENT_FILE_STATUS.SUCCESS}
>
<DownloadIcon />
</IconButton>
</Tooltip>
),
];

return (
<Searcher
module="payroll"
FilterPane={null}
fetch={fetchFiles}
items={files}
itemsPageInfo={pageInfo}
fetchedItems={fetchedPayrollFiles}
fetchingItems={fetchingPayrollFiles}
errorItems={errorPayrollFiles}
tableTitle={formatMessageWithValues('payrollPaymentFilesSearcher.results', { totalCount })}
headers={headers}
itemFormatters={itemFormatters}
rowsPerPageOptions={ROWS_PER_PAGE_OPTIONS}
defaultPageSize={DEFAULT_PAGE_SIZE}
rowIdentifier={rowIdentifier}
defaultFilters={defaultFilters()}
/>
);
}
const mapStateToProps = (state) => ({
fetchingPayrollFiles: state.payroll.fetchingPayrollFiles,
fetchedPayrollFiles: state.payroll.fetchedPayrollFiles,
errorPayrollFiles: state.payroll.errorPayrollFiles,
files: state.payroll.payrollFiles,
pageInfo: state.payroll.payrollFilesPageInfo,
totalCount: state.payroll.payrollFilesTotalCount,
});

const mapDispatchToProps = (dispatch) => bindActionCreators({
fetchPayrollPaymentFiles,
}, dispatch);

export default connect(mapStateToProps, mapDispatchToProps)(PayrollPaymentFilesSearcher);
53 changes: 53 additions & 0 deletions src/components/payroll/PayrollPaymentFilesTab.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import React from 'react';
import { useSelector } from 'react-redux';
import { Tab } from '@material-ui/core';
import { PublishedComponent, useTranslations } from '@openimis/fe-core';
import {
MODULE_NAME, PAYROLL_PAYMENT_FILES_TAB_VALUE,
} from '../../constants';
import PayrollPaymentFilesSearcher from './PayrollPaymentFilesSearcher';

function PayrollPaymentFilesTabLabel({
onChange, tabStyle, isSelected, modulesManager, payrollUuid, isInTask, isPayrollFromFailedInvoices,
}) {
const { formatMessage } = useTranslations(MODULE_NAME, modulesManager);
if (!payrollUuid || isInTask || isPayrollFromFailedInvoices) {
return null;
}
return (
<Tab
onChange={onChange}
className={tabStyle(PAYROLL_PAYMENT_FILES_TAB_VALUE)}
selected={isSelected(PAYROLL_PAYMENT_FILES_TAB_VALUE)}
value={PAYROLL_PAYMENT_FILES_TAB_VALUE}
label={formatMessage('PayrollPaymentFilesTab.label')}
/>
);
}

function PayrollPaymentFilesTabPanel({ value, payrollUuid, isInTask }) {
const rights = useSelector((store) => store?.core?.user?.i_user?.rights ?? []);
if (isInTask) return null;

return (
<PublishedComponent
pubRef="policyHolder.TabPanel"
module="payroll"
index={PAYROLL_PAYMENT_FILES_TAB_VALUE}
value={value}
>
{
payrollUuid && (
<PayrollPaymentFilesSearcher
module="payroll"
payrollUuid={payrollUuid}
rights={rights}
showFilters={false}
/>
)
}
</PublishedComponent>
);
}

export { PayrollPaymentFilesTabLabel, PayrollPaymentFilesTabPanel };
15 changes: 14 additions & 1 deletion src/constants.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ export const MODULE_NAME = 'payroll';

export const BENEFIT_CONSUMPTION_LIST_TAB_VALUE = 'benefitConsumptionsTab';
export const PAYROLL_TASK_TAB_VALUE = 'payrollTaskTab';

export const PAYROLL_PAYMENT_FILES_TAB_VALUE = 'payrollPaymentFilesTab';
export const PAYROLL_TABS_LABEL_CONTRIBUTION_KEY = 'payroll.TabPanel.label';
export const PAYROLL_TABS_PANEL_CONTRIBUTION_KEY = 'payroll.TabPanel.panel';

Expand Down Expand Up @@ -84,12 +84,25 @@ export const BENEFIT_CONSUMPTION_STATUS = {
RECONCILED: 'RECONCILED',
};

export const PAYROLL_PAYMENT_FILE_STATUS = {
TRIGGERED: 'TRIGGERED',
IN_PROGRESS: 'IN_PROGRESS',
SUCCESS: 'SUCCESS',
WAITING_FOR_VERIFICATION: 'WAITING_FOR_VERIFICATION',
FAIL: 'FAIL',
};

export const BENEFIT_CONSUMPTION_STATUS_LIST = [
BENEFIT_CONSUMPTION_STATUS.ACCEPTED, BENEFIT_CONSUMPTION_STATUS.CREATED,
BENEFIT_CONSUMPTION_STATUS.APPROVE_FOR_PAYMENT, BENEFIT_CONSUMPTION_STATUS.REJECTED,
BENEFIT_CONSUMPTION_STATUS.DUPLICATE, BENEFIT_CONSUMPTION_STATUS.RECONCILED,
];

export const PAYROLL_PAYMENT_FILE_STATUS_LIST = [
PAYROLL_PAYMENT_FILE_STATUS.TRIGGERED, PAYROLL_PAYMENT_FILE_STATUS.IN_PROGRESS, PAYROLL_PAYMENT_FILE_STATUS.SUCCESS,
PAYROLL_PAYMENT_FILE_STATUS.WAITING_FOR_VERIFICATION, PAYROLL_PAYMENT_FILE_STATUS.FAIL,
];

export const BENEFIT_PLAN_CONTENT_TYPE_ID = 175;

export const PAYROLL_FROM_FAILED_INVOICES_URL_PARAM = 'createPayrollFromFailedInvoices=true';
5 changes: 3 additions & 2 deletions src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ import {
PayrollTaskTabPanel,
} from './components/payroll/PayrollTaskTabPanel';
import { PayrollDeleteTaskItemFormatters, PayrollDeleteTaskTableHeaders } from './components/tasks/PayrollDeleteTasks';
import { PayrollPaymentFilesTabLabel, PayrollPaymentFilesTabPanel } from './components/payroll/PayrollPaymentFilesTab';

const ROUTE_PAYMENT_POINTS = 'paymentPoints';
const ROUTE_PAYMENT_POINT = 'paymentPoints/paymentPoint';
Expand Down Expand Up @@ -93,8 +94,8 @@ const DEFAULT_CONFIG = {
filter: (rights) => rights.includes(RIGHT_PAYROLL_SEARCH),
},
],
'payroll.TabPanel.label': [BenefitConsumptionsTabLabel, PayrollTaskTabLabel],
'payroll.TabPanel.panel': [BenefitConsumptionsTabPanel, PayrollTaskTabPanel],
'payroll.TabPanel.label': [BenefitConsumptionsTabLabel, PayrollTaskTabLabel, PayrollPaymentFilesTabLabel],
'payroll.TabPanel.panel': [BenefitConsumptionsTabPanel, PayrollTaskTabPanel, PayrollPaymentFilesTabPanel],
'tasksManagement.tasks': [{
text: <FormattedMessage module="payroll" id="payroll.tasks.update.title" />,
tableHeaders: PayrollTaskTableHeaders,
Expand Down
46 changes: 46 additions & 0 deletions src/reducer.js
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ export const ACTION_TYPE = {
GET_PAYMENT_METHODS: 'PAYROLL_PAYMENT_METHODS',
CLOSE_PAYROLL: 'PAYROLL_MUTATION_CLOSE_PAYROLL',
REJECT_PAYROLL: 'PAYROLL_MUTATION_REJECT_PAYROLL',
GET_PAYROLL_PAYMENT_FILES: 'GET_PAYROLL_PAYMENT_FILES',
};

export const MUTATION_SERVICE = {
Expand Down Expand Up @@ -90,6 +91,13 @@ const STORE_STATE = {
paymentMethods: [],
fetchedPaymentMethods: false,
errorPaymentMethods: null,

fetchingPayrollFiles: false,
fetchedPayrollFiles: false,
errorPayrollFiles: null,
payrollFiles: [],
payrollFilesPageInfo: {},
payrollFilesTotalCount: 0,
};

function reducer(
Expand Down Expand Up @@ -320,6 +328,44 @@ function reducer(
fetchingPaymentMethods: false,
errorPaymentMethods: formatServerError(action.payload),
};
case REQUEST(ACTION_TYPE.GET_PAYROLL_PAYMENT_FILES):
return {
...state,
fetchingPayrollFiles: true,
fetchedPayrollFiles: false,
errorPayrollFiles: null,
payrollFiles: [],
payrollFilesPageInfo: {},
payrollFilesTotalCount: 0,
};
case SUCCESS(ACTION_TYPE.GET_PAYROLL_PAYMENT_FILES):
return {
...state,
fetchingPayrollFiles: false,
fetchedPayrollFiles: true,
errorPayrollFiles: formatGraphQLError(action.payload),
payrollFiles: parseData(action.payload.data.csvReconciliationUpload)?.map((file) => ({
...file,
})),
payrollFilesPageInfo: pageInfo(action.payload.data.csvReconciliationUpload),
payrollFilesTotalCount: action.payload.data.csvReconciliationUpload?.totalCount ?? 0,
};
case ERROR(ACTION_TYPE.GET_PAYROLL_PAYMENT_FILES):
return {
...state,
fetchingPayrollFiles: false,
errorPayrollFiles: formatGraphQLError(action.payload),
};
case CLEAR(ACTION_TYPE.GET_PAYROLL_PAYMENT_FILES):
return {
...state,
fetchingPayrollFiles: false,
fetchedPayrollFiles: false,
errorPayrollFiles: null,
payrollFiles: [],
payrollFilesPageInfo: {},
payrollFilesTotalCount: 0,
};
case REQUEST(ACTION_TYPE.MUTATION):
return dispatchMutationReq(state, action);
case ERROR(ACTION_TYPE.MUTATION):
Expand Down
8 changes: 7 additions & 1 deletion src/translations/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -113,5 +113,11 @@
"payroll.route.payrollsReconciled": "Reconciled Payrolls",
"payroll.tasks.rejected.title": "Rejected Payrolls",
"payroll.PayrollTaskTab.label": "Tasks",
"payroll.tasks.delete.title": "Delete Payroll Tasks"
"payroll.tasks.delete.title": "Delete Payroll Tasks",

"payroll.payrollPaymentFile.fileName": "Filename",
"payroll.payrollPaymentFile.status": "Status",
"payrollPaymentFile.download": "Download",
"payroll.payrollPaymentFilesSearcher.results": "{totalCount} Files Found",
"payroll.PayrollPaymentFilesTab.label": "Payment Files"
}
11 changes: 8 additions & 3 deletions src/utils/export.js
Original file line number Diff line number Diff line change
@@ -1,20 +1,25 @@
import { baseApiUrl } from '@openimis/fe-core';

export default function downloadPayroll(payrollId, payrollName) {
export default function downloadPayroll(payrollId, payrollFileName, blank = true) {
const url = new URL(
`${window.location.origin}${baseApiUrl}/payroll/csv_reconciliation/?payroll_id=${payrollId}`,
`${window.location.origin}${baseApiUrl}/payroll/csv_reconciliation/`,
);
url.searchParams.append('payroll_id', payrollId);
url.searchParams.append('blank', blank);
url.searchParams.append('payroll_file_name', payrollFileName);

fetch(url)
.then((response) => response.blob())
.then((blob) => {
const link = document.createElement('a');
link.href = URL.createObjectURL(blob);
link.download = `reconciliation_${payrollName}.csv`;
link.download = blank ? `reconciliation_${payrollFileName}.csv` : payrollFileName;
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
})
.catch((error) => {
// eslint-disable-next-line no-console
console.error('Export failed, reason: ', error);
});
}

0 comments on commit f35ebde

Please sign in to comment.