diff --git a/src/component/YearPagination/index.tsx b/src/component/YearPagination/index.tsx index 56ffb04..6604c2e 100644 --- a/src/component/YearPagination/index.tsx +++ b/src/component/YearPagination/index.tsx @@ -8,9 +8,10 @@ import * as S from './style'; interface YearPaginationProps { duesYear: number; setDuesYear: React.Dispatch>; + routeParam: string; } -export default function YearPagination({ duesYear, setDuesYear }: YearPaginationProps) { +export default function YearPagination({ duesYear, setDuesYear, routeParam }: YearPaginationProps) { const navigate = useNavigate(); const currentYear = new Date().getFullYear(); const param = useQueryParam('page'); @@ -20,14 +21,14 @@ export default function YearPagination({ duesYear, setDuesYear }: YearPagination // 재학생 회비 내역이 2021년부터 시작하므로 2021년 이전으로 이동할 수 없음 const prevYear = page ? page + 1 : 2; if (prevYear <= currentYear - 2020) { - navigate(`/dues?page=${prevYear}`); + navigate(`/${routeParam}?page=${prevYear}`); setDuesYear((prev) => prev - 1); } }; const goToNextYear = () => { if (page && page > 1) { - navigate(`/dues?page=${page - 1}`); + navigate(`/${routeParam}?page=${page - 1}`); setDuesYear((prev) => prev + 1); } }; diff --git a/src/model/member.ts b/src/model/member.ts index b9ab472..2d2e5c0 100644 --- a/src/model/member.ts +++ b/src/model/member.ts @@ -42,6 +42,7 @@ export interface Member { updatedAt: string; isAuthed: boolean; isDeleted: boolean; + isFeeExempt: boolean; deleteReason: string; birthday: string; } diff --git a/src/page/DuesManagement/index.tsx b/src/page/DuesManagement/index.tsx index d33426a..8f009ad 100644 --- a/src/page/DuesManagement/index.tsx +++ b/src/page/DuesManagement/index.tsx @@ -94,7 +94,7 @@ function DefaultTable() { const updatedTrack = [...prevTrack]; updatedTrack[trackIndex] = !updatedTrack[trackIndex]; setFilteredValue(allDues.dues.filter((row) => updatedTrack[tracks.map((track) => track.name).indexOf(row.track.name)] - && members?.content.some((member) => member.memberType === 'REGULAR' && member.id === row.memberId))); + && members?.content.some((member) => member.memberType === 'REGULAR' && member.id === row.memberId))); return updatedTrack; }); }; @@ -154,7 +154,7 @@ function DefaultTable() { <>
- +
; + prevYearDues: DuesInfo; + currentYearDues: DuesInfo; +} + +export type MemberDuesInfo = { id: number, name: string, notPaidMonthInfo: { year: number, month: number }[] }; + +function findUnpaidMonths(duesInfo: DuesInfo | undefined, memberId: number, year: number) { + if (!duesInfo) return []; + const memberDues = duesInfo.dues.find((dues) => dues.memberId === memberId); + return memberDues && memberDues.unpaidCount > 0 + ? memberDues.detail.filter((detail) => detail.status === 'NOT_PAID').map((detail) => ({ year, month: detail.month })) + : []; +} + +export function findMemberDuesInfo({ + worksheet, members, prevYearDues, currentYearDues, +}: FindMemberDuesInfoProps) { + const result: MemberDuesInfo[] = []; + const currentYear = new Date().getFullYear(); + + worksheet.forEach((row) => { + const memberName = row[NAME_ROW_NUMBER] as string; + const memberInfo = members?.content.find((member) => member.name === memberName); + + if (memberInfo) { + const unpaidMonthsPrevYear = findUnpaidMonths(prevYearDues, memberInfo.id, currentYear - 1); + const unpaidMonthsCurrentYear = findUnpaidMonths(currentYearDues, memberInfo.id, currentYear); + + if (unpaidMonthsPrevYear.length > 0 || unpaidMonthsCurrentYear.length > 0) { + result.push({ + id: memberInfo.id, + name: memberName, + notPaidMonthInfo: [...unpaidMonthsPrevYear, ...unpaidMonthsCurrentYear], + }); + } + } + }); + + return result; +} diff --git a/src/page/DuesSetup/hooks/updateDues.ts b/src/page/DuesSetup/hooks/updateDues.ts new file mode 100644 index 0000000..e24f2ea --- /dev/null +++ b/src/page/DuesSetup/hooks/updateDues.ts @@ -0,0 +1,130 @@ +import * as Excel from 'exceljs'; +import { DuesInfo } from 'model/dues/allDues'; +import { Pagination } from 'model/page'; +import { Member } from 'model/member'; +import { UseMutationResult } from '@tanstack/react-query'; +import { NewDuesData } from 'api/dues'; +import { MemberDuesInfo } from './findMemberDuesInfo'; + +interface UpdateDuesProps { + worksheet: Excel.CellValue[][]; + members: Pagination; + unpaidMemberDuesInfo: MemberDuesInfo[]; + currentYearDues: DuesInfo; + putDuesMutation: UseMutationResult; + postDuesMutation: UseMutationResult; +} + +type UpdateNotPaidDuesToPaidProps = Pick & { + name: string; + depositDues: number; +}; + +type UpdateNullDuesToPaidProps = Pick & { + id: number; + name: string; + depositDues: number; +}; + +type UpdateWavierDuesProps = Pick; +type UpdateNullToNotPaidDuesProps = Pick; + +// depositDues만큼 unpaidMemberDuesInfo를 반영하여 mutate하기 +function updateNotPaidDuesToPaid({ + name, depositDues, unpaidMemberDuesInfo, putDuesMutation, +}: UpdateNotPaidDuesToPaidProps) { + let leftDepositDues = depositDues; + if (depositDues % 10000 === 0) { + Array.from({ length: depositDues }).forEach((_, index) => { + // TODO: 동명이인 처리 + const memberDuesInfo = unpaidMemberDuesInfo.find((info) => info.name === name); + if (memberDuesInfo) { + const { id, notPaidMonthInfo } = memberDuesInfo; + const { year, month } = notPaidMonthInfo[index]; + putDuesMutation.mutate({ + memberId: id, + year, + month, + status: 'PAID', + }); + leftDepositDues -= 10000; + } + }); + } + return leftDepositDues; +} + +function updateNullDuesToPaid({ id, depositDues, postDuesMutation }: UpdateNullDuesToPaidProps) { + // 회비를 매 달 1일에 정리하기 때문에 저번 달을 기준으로 처리한다. + const currentYear = new Date().getFullYear(); + const prevMonth = new Date().getMonth(); + Array.from({ length: depositDues }).forEach((_, index) => { + const year = prevMonth + index > 12 ? currentYear + 1 : currentYear; + const month = ((prevMonth + index) % 12) + 1; + postDuesMutation.mutate({ + memberId: id, + year, + month, + status: 'PAID', + }); + }); +} + +function updateWavierDues({ postDuesMutation, members }: UpdateWavierDuesProps) { + members.content.forEach((member) => { + if (member.isFeeExempted) { + postDuesMutation.mutate({ + memberId: member.id, + year: new Date().getFullYear(), + month: new Date().getMonth() + 1, + status: 'SKIP', + }); + } + }); +} + +function updateNullToNotPaidDues({ currentYearDues, postDuesMutation }: UpdateNullToNotPaidDuesProps) { + currentYearDues.dues.forEach((dues) => { + const prevMonth = new Date().getMonth(); + if (dues.detail[prevMonth].status === null) { + postDuesMutation.mutate({ + memberId: dues.memberId, + year: new Date().getFullYear(), + month: prevMonth + 1, + status: 'NOT_PAID', + }); + } + }); +} + +export function updateDues({ + worksheet, members, unpaidMemberDuesInfo, currentYearDues, putDuesMutation, postDuesMutation, +}: UpdateDuesProps) { + worksheet.forEach((row) => { + const [, , depositValue, , content, , note] = row; + const depositDues = Number(depositValue); + const name = String(content); + const id = members.content.find((member) => member.name === name)?.id; + let leftDepositDues; + + if (name && depositDues && id) { + // type note = 'X' | '-' | `id=${id}`; + if (note !== 'X') { + /** + * 1. 미납된 회비를 납부한 경우, 미납된 회비를 납부 처리한다. + * 2. 미납된 회비가 없는 경우, 납부한 달의 회비를 납부 처리한다. + * 3. 면제 인원의 경우 면제 처리한다. + * 4. 납부한 회비가 없는 경우 미납 처리한다. (1~3번 까지 처리가 끝난 후 아무값도 없는 경우 NOT_PAID 처리) + */ + leftDepositDues = updateNotPaidDuesToPaid({ + name, depositDues, unpaidMemberDuesInfo, putDuesMutation, + }); + updateNullDuesToPaid({ + id, name, depositDues: leftDepositDues, postDuesMutation, + }); + updateWavierDues({ members, postDuesMutation }); + updateNullToNotPaidDues({ postDuesMutation, currentYearDues }); + } + } + }); +} diff --git a/src/page/DuesSetup/hooks/updateWorksheetwithDuesInfo.ts b/src/page/DuesSetup/hooks/updateWorksheetwithDuesInfo.ts new file mode 100644 index 0000000..4cc9329 --- /dev/null +++ b/src/page/DuesSetup/hooks/updateWorksheetwithDuesInfo.ts @@ -0,0 +1,29 @@ +import * as Excel from 'exceljs'; +import { MemberDuesInfo } from './findMemberDuesInfo'; + +const NAME_ROW_NUMBER = 4; +const UNPAID_INFO_ROW_NUMBER = 7; + +export function updateWorksheetWithDuesInfo( + worksheet: Excel.CellValue[][], + memberDuesInfo: MemberDuesInfo[], +) { + const updatedWorksheet = worksheet.filter((_, index) => index > 0); + return updatedWorksheet.map((row) => { + const memberName = row[NAME_ROW_NUMBER] as string; + const memberInfo = memberDuesInfo.find((info) => info.name === memberName); + const updatedRow = [...row]; + + if (memberInfo) { + const unpaidMonths = memberInfo.notPaidMonthInfo + .map(({ year, month }) => `${year}년 ${month}월`) + .join(', '); + + updatedRow[UNPAID_INFO_ROW_NUMBER] = unpaidMonths; + } else { + updatedRow[UNPAID_INFO_ROW_NUMBER] = ''; + } + + return updatedRow; + }); +} diff --git a/src/page/DuesSetup/hooks/useReadExcelFile.ts b/src/page/DuesSetup/hooks/useReadExcelFile.ts new file mode 100644 index 0000000..859df4b --- /dev/null +++ b/src/page/DuesSetup/hooks/useReadExcelFile.ts @@ -0,0 +1,43 @@ +import * as Excel from 'exceljs'; + +function makeWorksheetToArray(worksheet: Excel.Worksheet): Excel.CellValue[][] { + const result: Excel.CellValue[][] = []; + worksheet.eachRow((row, rowNumber) => { + result.push([]); + row.eachCell((cell) => { + if (cell.value !== null && cell.value !== undefined) { + result[rowNumber - 1].push(cell.value); + } + }); + }); + return result; +} + +export function useReadExcelFile(excelFileRef: React.RefObject): () => Promise { + const readFile: () => Promise = async () => { + const workbook = new Excel.Workbook(); + const file = excelFileRef.current?.files?.[0]; + if (!file) return null; + + const reader = new FileReader(); + return new Promise((resolve, reject) => { + reader.onload = async (e) => { + try { + const data = new Uint8Array(e.target?.result as ArrayBuffer); + await workbook.xlsx.load(data); + const worksheet = workbook.getWorksheet(1); + if (!worksheet) { + reject(new Error('worksheet is not found')); + } + resolve(worksheet ? makeWorksheetToArray(worksheet) : null); + } catch (error) { + reject(error); + } + }; + reader.onerror = (error) => reject(error); + reader.readAsArrayBuffer(file); + }); + }; + + return readFile; +} diff --git a/src/page/DuesSetup/index.tsx b/src/page/DuesSetup/index.tsx index bebd5b5..6ea500f 100644 --- a/src/page/DuesSetup/index.tsx +++ b/src/page/DuesSetup/index.tsx @@ -1,426 +1,64 @@ /* eslint-disable react-hooks/exhaustive-deps */ import { - Button, ButtonGroup, Popover, Table, TableBody, TableCell, TableHead, TableRow, + Button, ButtonGroup, Table, TableBody, TableCell, TableHead, TableRow, } from '@mui/material'; import { - Suspense, useRef, useState, useEffect, + Suspense, useRef, useState, } from 'react'; +import LoadingSpinner from 'layout/LoadingSpinner'; import * as Excel from 'exceljs'; -import { Dues } from 'model/dues/allDues'; -import { useMutation } from '@tanstack/react-query'; -import { - NewDuesData, postDues, putDues, -} from 'api/dues'; import { useGetMe, useGetMembers } from 'query/members'; -import { useGetAllDues } from 'query/dues'; +import { useGetAllDues, usePostDues, usePutDues } from 'query/dues'; import { useSnackBar } from 'ts/useSnackBar'; -import LoadingSpinner from 'layout/LoadingSpinner'; -import { ArrowDownward, ArrowUpward, Sort } from '@mui/icons-material'; +import { useReadExcelFile } from './hooks/useReadExcelFile'; import * as S from './style'; - -interface TableBodyData { - value: string[]; -} - -interface DatesDuesApply { - prevYearMonth: number[]; - currentYearMonth: number[]; - nextYearMonth: number[]; -} - -type Column = 'date' | 'category' | 'amount' | 'balance' | 'name' | 'note' | 'month'; -// 회비 생성 -// 매월 1일에 회비 생성 (단 한번만 하는 기능임) +import { MemberDuesInfo, findMemberDuesInfo } from './hooks/findMemberDuesInfo'; +import { updateWorksheetWithDuesInfo } from './hooks/updateWorksheetwithDuesInfo'; +import { updateDues } from './hooks/updateDues'; function DefaultTable() { const currentYear = new Date().getFullYear(); - const prevMonth = new Date().getMonth(); const excelFileRef = useRef(null); - const workbook = new Excel.Workbook(); - const tableHead = ['거래 일자', '구분', '거래 금액', '거래 후 잔액', '이름', '비고', '회비가 적용되는 날짜']; - const [tableBody, setTableBody] = useState([ - { value: [] }, - { value: [] }, - { value: [] }, - { value: [] }, - { value: [] }, - { value: [] }, - { value: [] }, - ]); - const [datesDuesApply, setDatesDuesApply] = useState([{ prevYearMonth: [], currentYearMonth: [], nextYearMonth: [] }]); + const tableHead = ['No', '거래 일시', '입금액', '출금액', '이름', '잔액', '비고', '현재 미납한 날짜']; + const [tableBody, setTableBody] = useState(Array.from({ length: tableHead.length }).map(() => [])); const [buttonDisabled, setButtonDisabled] = useState(true); - const [anchorEl, setAnchorEl] = useState(null); - const isSortPopoverOpen = Boolean(anchorEl); - - const handlePopoverClick = (e: React.MouseEvent) => { - setAnchorEl(e.currentTarget); - }; + let unpaidMemberDuesInfo: MemberDuesInfo[] = []; + const { data: getMe } = useGetMe(); const { data: members } = useGetMembers({ pageIndex: 0, pageSize: 1000, trackId: null }); - const { data: me } = useGetMe(); - const { data: currentYearDues } = useGetAllDues({ year: currentYear }); const { data: prevYearDues } = useGetAllDues({ year: currentYear - 1 }); - const { data: nextYearDues } = useGetAllDues({ year: currentYear + 1 }); + const { data: currentYearDues } = useGetAllDues({ year: currentYear }); + const putDuesMutation = usePutDues(); + const postDuesMutation = usePostDues(); const openSnackBar = useSnackBar(); - const onMutationSuccess = () => { - openSnackBar({ type: 'success', message: '회비가 생성되었습니다.' }); - setButtonDisabled(true); - }; - - const postDuesMutation = useMutation({ - mutationKey: ['postDues'], - mutationFn: (data: NewDuesData) => { - if (me.authority === 'ADMIN') { - return postDues(data); - } - throw new Error('권한이 없습니다.'); - }, - onError: (error) => openSnackBar({ type: 'error', message: error.message }), - onSuccess: () => onMutationSuccess(), - }); - - const putDuesMutation = useMutation({ - mutationKey: ['putDues'], - mutationFn: (data: NewDuesData) => { - if (me.authority === 'ADMIN') { - return putDues(data); - } - throw new Error('권한이 없습니다.'); - }, - onError: (error) => openSnackBar({ type: 'error', message: error.message }), - onSuccess: () => onMutationSuccess(), - }); + const readExcelFile = useReadExcelFile(excelFileRef); - const columns: Column[] = ['date', 'category', 'amount', 'balance', 'name', 'note', 'month']; - const sortInAscendingOrderByName = () => { - const rowData = tableBody[4].value.map((_, index) => { - const newRowData: Record = { - date: '', - category: '', - amount: '', - balance: '', - name: '', - note: '', - month: '', - }; - columns.forEach((col, colIndex) => { - newRowData[col] = tableBody[colIndex].value[index]; - }); - return newRowData; - }); - rowData.sort((a, b) => a.name.localeCompare(b.name)); - rowData.forEach((value, index) => { - setTableBody((prev) => { - const newTableBody = [...prev]; - columns.forEach((col, colIndex) => { - newTableBody[colIndex].value[index] = value[col]; - }); - return newTableBody; - }); - }); - setAnchorEl(null); - }; - - const sortInDescendingOrderByName = () => { - const rowData = tableBody[4].value.map((_, index) => { - const newRowData: Record = { - date: '', - category: '', - amount: '', - balance: '', - name: '', - note: '', - month: '', - }; - columns.forEach((col, colIndex) => { - newRowData[col] = tableBody[colIndex].value[index]; - }); - return newRowData; - }); - rowData.sort((a, b) => b.name.localeCompare(a.name)); - rowData.forEach((value, index) => { - setTableBody((prev) => { - const newTableBody = [...prev]; - columns.forEach((col, colIndex) => { - newTableBody[colIndex].value[index] = value[col]; - }); - return newTableBody; + const handleFileUpload = async () => { + try { + const worksheet = await readExcelFile(); + if (worksheet === null) return; + setButtonDisabled(false); + unpaidMemberDuesInfo = findMemberDuesInfo({ + worksheet, members, prevYearDues, currentYearDues, }); - }); - setAnchorEl(null); - }; - - const findUnpaidMonth = (dues: Dues[], name: string) => { - const unpaidPeople = dues.filter((value) => name !== '' && value.name === name && value.unpaidCount > 0); - return (unpaidPeople.map((value) => value.detail.filter((detail) => detail.status === 'NOT_PAID'))).map((value) => value.map((detail) => detail.month)); - }; - - const findStatus = (memberId: number, month: number, dues: Dues[]) => { - const memberDuesInfo = dues.filter((value) => value.memberId === memberId)[0]; - return memberDuesInfo?.detail[month - 1].status; - }; - - const findNullStatusMonth = (memberId: number, dues: Dues[]) => { - const memberDuesInfo = dues.filter((value) => value.memberId === memberId)[0]; - const result = Array.from({ length: 12 }).map((_, index) => { - if (memberDuesInfo.detail[index].status === null) { - return index + 1; - } - return null; - }); - return result.filter((value): value is number => value !== null); - }; - - const findMemberId = (name: string, index: number) => { - // 동명이인인 경우 비고에 memberId가 적혀있음(수기로 반드시 작성해야 함) 예시) memberId: 12 - const memberIdInNotes = tableBody[tableHead.indexOf('비고')].value[index]?.includes('memberId:'); - if (memberIdInNotes) { - const memberId = tableBody[tableHead.indexOf('비고')].value[index].split('memberId:')[1]; - return Number(memberId); - } - - const member = members?.content.find((value) => value.name === name); - if (member) { - return member.id; - } - - return null; - }; - - // 회비가 적용될 달을 찾아서 테이블에 추가 - const findDuesMonths = () => { - tableBody[4].value.forEach((name, index) => { - const prevResult: number[] = []; - const currentResult: number[] = []; - const nextResult: number[] = []; - const memberId = findMemberId(name, index); - const transactionAmount = Number(tableBody[2].value[index].replace(/,/g, '')); - const count = transactionAmount / 10000; - const unpaidMonthsInPrevYear = findUnpaidMonth(prevYearDues.dues, name)?.[0]; - const unpaidMonthsInCurrentYear = findUnpaidMonth(currentYearDues.dues, name)?.[0]; - // 작년에 미납된 회비가 있을 경우 - if (unpaidMonthsInPrevYear && count > 0) { - const updatedMonths = unpaidMonthsInPrevYear.slice(0, count); - prevResult.push(...updatedMonths); + setTableBody(updateWorksheetWithDuesInfo(worksheet, unpaidMemberDuesInfo)); + } catch (error) { + if (error instanceof Error) { + openSnackBar({ type: 'error', message: error.message }); } - // 작년에 미납된 회비가 null일 경우 - if (prevResult.length < count && prevMonth === 12) { - const inAdvanceMonths = Array.from({ length: count - prevResult.length }).map((_, monthIndex) => { - if (memberId) { - const prevYearDuesStatus = findStatus(memberId, prevMonth, prevYearDues.dues); - if (prevYearDuesStatus === null) { - if (prevMonth + 1 + monthIndex > 12) { - return null; - } - return prevMonth + 1 + monthIndex; - } - } - return null; - }); - prevResult.push(...inAdvanceMonths.filter((value): value is number => value !== null)); - } - // 이번 해에 미납된 회비가 있을 경우 - if (unpaidMonthsInCurrentYear && prevResult.length < count) { - const updatedMonths = unpaidMonthsInCurrentYear.slice(0, count - prevResult.length); - currentResult.push(...updatedMonths); - } - // 이번 해에 미리 납부할 회비가 있을 경우 - if (currentResult.length < count) { - const inAdvanceMonths = Array.from({ length: count - prevResult.length - currentResult.length }).map((_, monthIndex) => { - if (memberId) { - // 반복해서 null인 값을 찾아야 함 - const nullStatusMonths = findNullStatusMonth(memberId, currentYearDues.dues); - return nullStatusMonths[monthIndex]; - } - return null; - }); - currentResult.push(...inAdvanceMonths.filter((value): value is number => value !== null && value !== undefined)); - } - - // 다음 해에 미리 납부할 회비가 있을 경우 - if (currentResult.length < count) { - const inAdvanceMonths = Array.from({ length: count - prevResult.length - currentResult.length }).map((_, monthIndex) => { - if (memberId) { - const nullStatusMonths = findNullStatusMonth(memberId, nextYearDues.dues); - return nullStatusMonths[monthIndex]; - } - return null; - }); - nextResult.push(...inAdvanceMonths.filter((value): value is number => value !== null)); - } - const yearInfo: string[] = []; - - if (prevResult.length > 0) { - yearInfo.push(`${currentYear - 1}년 ${prevResult.join('월, ')}월`); - } - if (currentResult.length > 0) { - yearInfo.push(`${currentYear}년 ${currentResult.join('월, ')}월`); - } - if (nextResult.length > 0) { - yearInfo.push(`${currentYear + 1}년 ${nextResult.join('월, ')}월`); - } - - if (yearInfo.length > 0) { - setTableBody((prev) => { - const newTableBody = [...prev]; - newTableBody[6].value[index] = yearInfo.join(' / '); - return newTableBody; - }); - setDatesDuesApply((prev) => { - const newDatesDuesApply = [...prev]; - newDatesDuesApply[index] = { prevYearMonth: prevResult, currentYearMonth: currentResult, nextYearMonth: nextResult }; - return newDatesDuesApply; - }); - } - }); - }; - - const handleExcelFileChange = async () => { - setTableBody([ - { value: [] }, - { value: [] }, - { value: [] }, - { value: [] }, - { value: [] }, - { value: [] }, - { value: [] }, - ]); - const file = excelFileRef.current?.files?.[0]; - const reader = new FileReader(); - if (file) { - setButtonDisabled(false); - reader.readAsArrayBuffer(file); - reader.onload = async (e) => { - const buffer = e.target?.result; - const data = new Uint8Array(buffer as ArrayBuffer); - await workbook.xlsx.load(data); - const worksheet = workbook.getWorksheet(1); - - worksheet?.eachRow((row, rowNumber) => { - const totalRowNumber = worksheet.actualRowCount; - row.eachCell((cell, cellNumber) => { - if (cell.value !== null && rowNumber > 4 && rowNumber < totalRowNumber) { - if (cellNumber !== 6) { - setTableBody((prev) => { - const newTableBody = [...prev]; - newTableBody[cellNumber - 1].value.push(cell.text); - return newTableBody; - }); - } else { - setTableBody((prev) => { - const newTableBody = [...prev]; - newTableBody[cellNumber - 1].value[rowNumber - 5] = cell.text; - return newTableBody; - }); - } - } - }); - }); - }; } }; - // status가 NOT_PAID인 월을 찾아서 PAID로 변경 (PUT /dues) - // status가 없는 경우 PAID로 변경 (POST /dues) - const updateUnpaidtoPaid = (memberId: number, year: number, months: number[], dues: Dues[]) => { - months.forEach((month) => { - const monthStatus = findStatus(memberId, month, dues); - if (monthStatus === 'NOT_PAID') { - const data: NewDuesData = { - memberId, - year, - month, - status: 'PAID', - }; - putDuesMutation.mutate(data); - } else if (monthStatus === null) { - const data: NewDuesData = { - memberId, - year, - month, - status: 'PAID', - }; - postDuesMutation.mutate(data); - } - }); - }; - - // 회비 면제인 경우 null -> SKIP (POST /dues) - // authority가 manger, admin인 경우 적용 - const applyForDuesWaiver = () => { - const waiverMember = members?.content.filter((value) => value.authority === 'MANAGER' || value.authority === 'ADMIN'); - if (waiverMember) { - const waiverMembersData: NewDuesData[] = waiverMember.map((value) => { - return { - memberId: value.id, - year: prevMonth === 12 ? currentYear - 1 : currentYear, - month: prevMonth, - status: 'SKIP', - }; - }); - waiverMembersData.forEach((data, index) => { - const memberId = waiverMember[index].id; - // status null인 경우에만 면제 적용됨 - if (currentYearDues.dues.find((value) => value.memberId === memberId)?.detail[prevMonth - 1].status === null) { - postDuesMutation.mutate(data); - } + const handleCreateDues = () => { + if (getMe.authority === 'MANAGER') { + updateDues({ + worksheet: tableBody, members, unpaidMemberDuesInfo, currentYearDues, putDuesMutation, postDuesMutation, }); } }; - // status가 null인 prevMonth를 찾아서 NOT_PAID로 변경 (POST /dues) - // 오직 regular user만 적용 - const updateNullToNotPaid = () => { - const memberIds = members?.content.map((value) => value.id); - memberIds?.forEach((memberId) => { - if (members?.content.find((value) => value.id === memberId)?.memberType === 'REGULAR') { - const prevMonthStatus = findStatus(memberId, prevMonth, prevMonth === 12 ? prevYearDues.dues : currentYearDues.dues); - if (prevMonthStatus === null) { - const data: NewDuesData = { - memberId, - year: prevMonth === 12 ? currentYear - 1 : currentYear, - month: prevMonth, - status: 'NOT_PAID', - }; - postDuesMutation.mutate(data); - } - } - }); - }; - - const handleCreateDuesClick = () => { - applyForDuesWaiver(); - tableBody[4].value.forEach((name, index) => { - const memberId = findMemberId(name, index); - const prevYearDuesApplyMonth = datesDuesApply[index]?.prevYearMonth; - const currentYearDuesApplyMonth = datesDuesApply[index]?.currentYearMonth; - const nextYearDuesApplyMonth = datesDuesApply[index]?.nextYearMonth; - if (memberId) { - if (prevYearDuesApplyMonth) { - updateUnpaidtoPaid(memberId, currentYear - 1, prevYearDuesApplyMonth, prevYearDues.dues); - } - if (currentYearDuesApplyMonth) { - updateUnpaidtoPaid(memberId, currentYear, currentYearDuesApplyMonth, currentYearDues.dues); - } - if (nextYearDuesApplyMonth) { - updateUnpaidtoPaid(memberId, currentYear + 1, nextYearDuesApplyMonth, nextYearDues.dues); - } - } - }); - updateNullToNotPaid(); - }; - const mix = async () => { - handleExcelFileChange(); - }; - - useEffect(() => { - if (tableBody[4].value.length > 0) { - findDuesMonths(); - } - }, [tableBody]); - return (
@@ -433,70 +71,30 @@ function DefaultTable() { accept=".xlsx" css={S.fileUpload} ref={excelFileRef} - onChange={mix} + onChange={handleFileUpload} /> - + - {tableHead.map((head) => { - if (head === '이름') { - return ( - - {head} - - - ); - } - return ( - {head} - ); - })} + {tableHead.map((head) => ( + {head} + ))} - {tableBody[4].value.map((date, index) => { - return ( - - {tableBody.map((dues) => { - return ( - {dues.value[index]} - ); - })} - - ); - })} + {tableBody.map((row, rowIndex) => ( + + {row.map((cell) => ( + {cell as string | number} + ))} + + ))}
- setAnchorEl(null)} - anchorOrigin={{ - vertical: 'bottom', - horizontal: 'left', - }} - > -
-

- 이름순 정렬 -

-
- - -
-
-
); } diff --git a/src/page/EditDues/index.tsx b/src/page/EditDues/index.tsx index a068de9..a19fcfc 100644 --- a/src/page/EditDues/index.tsx +++ b/src/page/EditDues/index.tsx @@ -246,7 +246,7 @@ function DefaultTable() { <>
- +
{(myInfo.authority === 'ADMIN' || myInfo.authority === 'MANAGER') && ( diff --git a/src/page/PersonalDues/index.tsx b/src/page/PersonalDues/index.tsx index 1c45b4a..08af709 100644 --- a/src/page/PersonalDues/index.tsx +++ b/src/page/PersonalDues/index.tsx @@ -27,7 +27,7 @@ export default function PersonalDues() { return (
- +