feat: P5-W3 본론 완료 — 회계결산/이상치예측/연말명세서 (V74~V78)
P5-W2 원천세·부가세 분기신고 미커밋분 + P5-W3 메뉴/권한 보강(V74) + P5-W3 본론(V75~V78)을 일괄 정리. - V75 accounting_close / account_balance_snapshot (회계 결산) - V76 retention_anomaly_alert / forecast_kpi_monthly (이상치·예측 KPI) - V77 year_end_statement (연말 지급명세서) - V78 P5-W3 본론 메뉴 4 + 권한 + 공통코드 7그룹 + 테스트데이터 - api: AccountingClose/YearEndStatement/ForecastKpi/RetentionAnomaly Service+Controller - batch: BatchConfig Step 11/12 (원천세·부가세 분기) 연결 - frontend: 4화면(AccountingClose/YearEndStatement/RetentionAnomaly/ForecastKpi) + API 모듈 4 + 라우트 - frontend: usePermission/PermissionButton EXECUTE 권한 누락 보강 - fix: YearEndStatementMapper.xml 미존재 컬럼 a.agent_code 참조 제거 통합검증: Flyway V74~V78 운영DB 적용(v78), 4모듈 compileJava + 프론트 build PASS, 스모크 GET 8/8 + 쓰기 4/4 PASS, verify_v74.py 20/20 PASS. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,64 @@
|
||||
import api, { unwrap } from './request';
|
||||
|
||||
export interface AccountingCloseResp extends Record<string, unknown> {
|
||||
closeId: number;
|
||||
closePeriod: string;
|
||||
closeType: string;
|
||||
closeTypeName?: string;
|
||||
closeStatus: string;
|
||||
closeStatusName?: string;
|
||||
journalPrefix?: string;
|
||||
journalCount?: number;
|
||||
totalDebit?: number;
|
||||
totalCredit?: number;
|
||||
balanceDiff?: number;
|
||||
closedAt?: string;
|
||||
closedByName?: string;
|
||||
reopenedAt?: string;
|
||||
reopenedByName?: string;
|
||||
reopenReason?: string;
|
||||
createdAt?: string;
|
||||
updatedAt?: string;
|
||||
}
|
||||
|
||||
export interface AccountBalanceSnapshot extends Record<string, unknown> {
|
||||
snapshotId: number;
|
||||
closeId: number;
|
||||
accountCode: string;
|
||||
accountName?: string;
|
||||
accountType?: string;
|
||||
debitTotal: number;
|
||||
creditTotal: number;
|
||||
balance: number;
|
||||
createdAt?: string;
|
||||
}
|
||||
|
||||
export interface AccountingCloseSearchParam {
|
||||
closePeriod?: string;
|
||||
closeType?: string;
|
||||
closeStatus?: string;
|
||||
pageNum?: number;
|
||||
pageSize?: number;
|
||||
}
|
||||
|
||||
export interface AccountingCloseDraftReq {
|
||||
closePeriod: string;
|
||||
closeType: string;
|
||||
}
|
||||
|
||||
export const accountingCloseApi = {
|
||||
list: (params?: AccountingCloseSearchParam) =>
|
||||
unwrap<{ list: AccountingCloseResp[]; total: number }>(
|
||||
api.get('/api/accounting-closes', { params })
|
||||
),
|
||||
detail: (id: number) =>
|
||||
unwrap<AccountingCloseResp>(api.get(`/api/accounting-closes/${id}`)),
|
||||
snapshots: (id: number) =>
|
||||
unwrap<AccountBalanceSnapshot[]>(api.get(`/api/accounting-closes/${id}/snapshots`)),
|
||||
createDraft: (req: AccountingCloseDraftReq) =>
|
||||
unwrap<number>(api.post('/api/accounting-closes/draft', req)),
|
||||
close: (id: number) =>
|
||||
unwrap<AccountingCloseResp>(api.post(`/api/accounting-closes/${id}/close`, {})),
|
||||
reopen: (id: number, reopenReason: string) =>
|
||||
unwrap<void>(api.post(`/api/accounting-closes/${id}/reopen`, { reopenReason })),
|
||||
};
|
||||
@@ -0,0 +1,40 @@
|
||||
import api, { unwrap } from './request';
|
||||
|
||||
export interface ForecastKpiResp extends Record<string, unknown> {
|
||||
forecastId: number;
|
||||
forecastMonth: string;
|
||||
targetOrgId?: number;
|
||||
targetOrgName?: string;
|
||||
metricCode: string;
|
||||
metricName?: string;
|
||||
forecastValue?: number;
|
||||
lowerBound?: number;
|
||||
upperBound?: number;
|
||||
method?: string;
|
||||
methodName?: string;
|
||||
basePeriod?: string;
|
||||
sampleCount?: number;
|
||||
generatedAt?: string;
|
||||
}
|
||||
|
||||
export interface ForecastKpiSearchParam {
|
||||
forecastMonth?: string;
|
||||
targetOrgId?: number;
|
||||
metricCode?: string;
|
||||
pageNum?: number;
|
||||
pageSize?: number;
|
||||
}
|
||||
|
||||
export interface ForecastKpiRegenerateReq {
|
||||
forecastMonth: string;
|
||||
method?: string;
|
||||
}
|
||||
|
||||
export const forecastKpiApi = {
|
||||
list: (params?: ForecastKpiSearchParam) =>
|
||||
unwrap<{ list: ForecastKpiResp[]; total: number }>(
|
||||
api.get('/api/forecast-kpi', { params })
|
||||
),
|
||||
regenerate: (req: ForecastKpiRegenerateReq) =>
|
||||
unwrap<number>(api.post('/api/forecast-kpi/regenerate', req)),
|
||||
};
|
||||
@@ -0,0 +1,51 @@
|
||||
import api, { unwrap } from './request';
|
||||
|
||||
export interface RetentionAnomalyResp extends Record<string, unknown> {
|
||||
alertId: number;
|
||||
targetType: string;
|
||||
targetTypeName?: string;
|
||||
targetId?: number;
|
||||
targetName?: string;
|
||||
alertMonth: string;
|
||||
retentionBand: string;
|
||||
expectedRate?: number;
|
||||
actualRate?: number;
|
||||
deviation?: number;
|
||||
severity: string;
|
||||
severityName?: string;
|
||||
status: string;
|
||||
statusName?: string;
|
||||
note?: string;
|
||||
detectedAt?: string;
|
||||
reviewedAt?: string;
|
||||
reviewedByName?: string;
|
||||
}
|
||||
|
||||
export interface RetentionAnomalySearchParam {
|
||||
alertMonth?: string;
|
||||
targetType?: string;
|
||||
retentionBand?: string;
|
||||
severity?: string;
|
||||
status?: string;
|
||||
pageNum?: number;
|
||||
pageSize?: number;
|
||||
}
|
||||
|
||||
export interface RetentionAnomalyReviewReq {
|
||||
ids: number[];
|
||||
status: string;
|
||||
note?: string;
|
||||
}
|
||||
|
||||
export const retentionAnomalyApi = {
|
||||
list: (params?: RetentionAnomalySearchParam) =>
|
||||
unwrap<{ list: RetentionAnomalyResp[]; total: number }>(
|
||||
api.get('/api/retention-anomalies', { params })
|
||||
),
|
||||
detail: (id: number) =>
|
||||
unwrap<RetentionAnomalyResp>(api.get(`/api/retention-anomalies/${id}`)),
|
||||
detect: (alertMonth: string) =>
|
||||
unwrap<number>(api.post('/api/retention-anomalies/detect', { alertMonth })),
|
||||
review: (req: RetentionAnomalyReviewReq) =>
|
||||
unwrap<void>(api.put('/api/retention-anomalies/review', req)),
|
||||
};
|
||||
@@ -0,0 +1,61 @@
|
||||
import api, { unwrap } from './request';
|
||||
|
||||
export interface YearEndStatementResp extends Record<string, unknown> {
|
||||
statementId: number;
|
||||
agentId: number;
|
||||
agentName?: string;
|
||||
agentCode?: string;
|
||||
businessNo?: string;
|
||||
orgName?: string;
|
||||
statementYear: number;
|
||||
statementType?: string;
|
||||
statementTypeName?: string;
|
||||
totalIncome: number;
|
||||
totalWithheld: number;
|
||||
totalLocalTax: number;
|
||||
filePath?: string;
|
||||
fileFormat?: string;
|
||||
fileStatus?: string;
|
||||
fileStatusName?: string;
|
||||
generatedAt?: string;
|
||||
sentAt?: string;
|
||||
sentTo?: string;
|
||||
createdAt?: string;
|
||||
updatedAt?: string;
|
||||
}
|
||||
|
||||
export interface YearEndStatementSearchParam {
|
||||
statementYear?: number;
|
||||
agentId?: number;
|
||||
orgId?: number;
|
||||
fileStatus?: string;
|
||||
fileFormat?: string;
|
||||
pageNum?: number;
|
||||
pageSize?: number;
|
||||
}
|
||||
|
||||
export interface YearEndStatementRegenerateReq {
|
||||
statementYear: number;
|
||||
agentIds?: number[];
|
||||
statementType?: string;
|
||||
}
|
||||
|
||||
export interface YearEndStatementSendReq {
|
||||
ids: number[];
|
||||
sentTo: string;
|
||||
}
|
||||
|
||||
export const yearEndStatementApi = {
|
||||
list: (params?: YearEndStatementSearchParam) =>
|
||||
unwrap<{ list: YearEndStatementResp[]; total: number }>(
|
||||
api.get('/api/year-end-statements', { params })
|
||||
),
|
||||
detail: (id: number) =>
|
||||
unwrap<YearEndStatementResp>(api.get(`/api/year-end-statements/${id}`)),
|
||||
regenerate: (req: YearEndStatementRegenerateReq) =>
|
||||
unwrap<number>(api.post('/api/year-end-statements/regenerate', req)),
|
||||
generate: (id: number, fileFormat?: string) =>
|
||||
unwrap<void>(api.post(`/api/year-end-statements/${id}/generate`, { fileFormat })),
|
||||
send: (req: YearEndStatementSendReq) =>
|
||||
unwrap<void>(api.put('/api/year-end-statements/send', req)),
|
||||
};
|
||||
Reference in New Issue
Block a user