import { useEffect, useMemo, useState } from 'react' import { useMutation, useQuery } from '@tanstack/react-query' import dayjs, { type Dayjs } from 'dayjs' import { Button, Card, DatePicker, Form, Input, InputNumber, Select, Space, Typography, message } from 'antd' import CareTable from '../../components/CareTable' import { DownloadOutlined, RightOutlined, SearchOutlined } from '@ant-design/icons' import client, { apiPrefix, unwrap, type ApiResponse } from '../../api/client' import { resourceApi } from '../../api/resources' const { RangePicker } = DatePicker const sortFields = ['종류', '모델명', '요금제', '개통일', '출고처', '지원방법', '고객명', '통신사'] const defaultSorts = ['종류', '모델명', '요금제', '개통일', '출고처', '지원방법', '고객명'] const options = (values: string[]) => values.map((value) => ({ value, label: value })) const money = (v: unknown) => (v == null || v === '' ? '0' : Number(v).toLocaleString()) type AdjustRow = { id: number opendate?: string incompanyName?: string outcompanyName?: string name?: string openhow?: string pmodel?: string pserial?: string decltype?: string plan?: string sellprice?: number basicprice1?: number basicprice2?: number basicprice3?: number } export default function OpenAdjustPage() { const [form] = Form.useForm() const [searched, setSearched] = useState(false) const [filters, setFilters] = useState>({}) const [rows, setRows] = useState([]) const [batch, setBatch] = useState({ basicprice1: undefined as number | undefined, basicprice2: undefined as number | undefined, basicprice3: undefined as number | undefined }) const companies = useQuery({ queryKey: ['adjust-companies'], queryFn: () => resourceApi.list('companies', { size: 200 }) }) const inOptions = useMemo( () => (companies.data?.items ?? []).filter((c) => String(c.type) === 'IN').map((c) => ({ value: c.id, label: String(c.name) })), [companies.data], ) const outOptions = useMemo( () => (companies.data?.items ?? []).filter((c) => ['SHOP', 'DEALER', 'OUT'].includes(String(c.type))).map((c) => ({ value: c.id, label: String(c.name) })), [companies.data], ) const enabled = searched && !!filters.dateFrom && !!filters.dateTo const query = useQuery({ queryKey: ['open-adjust', filters], queryFn: async () => { const { data } = await client.get>(`${apiPrefix()}/opens/adjust`, { params: filters }) return unwrap(data) }, enabled }) useEffect(() => { if (query.data?.list) { setRows(query.data.list.map((row) => ({ ...row, basicprice1: Number(row.basicprice1 ?? 0), basicprice2: Number(row.basicprice2 ?? 0), basicprice3: Number(row.basicprice3 ?? 0) }))) } }, [query.data]) const setPreset = (from: Dayjs, to: Dayjs) => { if (to.diff(from, 'day') > 30) { message.warning('최대 31일까지 조회할 수 있습니다.') return } form.setFieldsValue({ dates: [from, to] }) } const onSearch = (values: Record) => { const dates = values.dates as [Dayjs, Dayjs] | undefined if (!dates?.[0] || !dates?.[1]) { message.warning('개통일을 선택하세요.') return } if (dates[1].diff(dates[0], 'day') > 30) { message.warning('최대 31일까지 조회할 수 있습니다.') return } const next: Record = { dateFrom: dates[0].format('YYYY-MM-DD'), dateTo: dates[1].format('YYYY-MM-DD'), openhow: values.openhow, telecom: values.telecom, incompanyId: values.incompanyId, outcompanyId: values.outcompanyId, salesperson: values.salesperson, model: values.model, plan: values.plan } for (let i = 1; i <= 7; i++) next[`sort${i}`] = values[`sort${i}`] setSearched(true) setFilters(next) } const updateRow = (id: number, field: 'basicprice1' | 'basicprice2' | 'basicprice3', value: number | null) => { setRows((prev) => prev.map((row) => (row.id === id ? { ...row, [field]: value ?? 0 } : row))) } const applyBatch = (field: 'basicprice1' | 'basicprice2' | 'basicprice3') => { const value = batch[field] if (value == null) { message.warning('금액을 입력하세요.') return } setRows((prev) => prev.map((row) => ({ ...row, [field]: value }))) message.success('일괄 입력했습니다.') } const saveOne = useMutation({ mutationFn: async (row: AdjustRow) => { const { data } = await client.put(`${apiPrefix()}/opens/${row.id}`, { basicprice1: row.basicprice1, basicprice2: row.basicprice2, basicprice3: row.basicprice3 }) return unwrap(data as ApiResponse) }, onSuccess: () => message.success('변경했습니다.'), onError: (e: Error) => message.error(e.message) }) const saveAll = useMutation({ mutationFn: async () => { const { data } = await client.post(`${apiPrefix()}/opens/adjust/batch`, { items: rows.map((row) => ({ id: row.id, basicprice1: row.basicprice1, basicprice2: row.basicprice2, basicprice3: row.basicprice3 })) }) return unwrap(data as ApiResponse<{ updated: number }>) }, onSuccess: (data) => message.success(`${data.updated}건을 변경했습니다.`), onError: (e: Error) => message.error(e.message) }) const download = async () => { if (!filters.dateFrom || !filters.dateTo) { message.warning('개통일을 선택하세요.') return } const { data } = await client.get(`${apiPrefix()}/opens/adjust/export`, { params: filters, responseType: 'blob' }) const url = URL.createObjectURL(data) const a = document.createElement('a') a.href = url a.download = '일괄개통정산.csv' a.click() URL.revokeObjectURL(url) } const columns = [ { title: '개통일', dataIndex: 'opendate', width: 100 }, { title: '입고처', dataIndex: 'incompanyName', width: 100, ellipsis: true }, { title: '출고처', dataIndex: 'outcompanyName', width: 100, ellipsis: true }, { title: '고객명', dataIndex: 'name', width: 80 }, { title: '종류', dataIndex: 'openhow', width: 70 }, { title: '모델명', dataIndex: 'pmodel', width: 140, ellipsis: true }, { title: '일련번호', dataIndex: 'pserial', width: 110, ellipsis: true }, { title: '지원방법', dataIndex: 'decltype', width: 90, render: (v: string) => v || '-' }, { title: '요금제', dataIndex: 'plan', width: 110, ellipsis: true }, { title: '정산금액', dataIndex: 'sellprice', width: 90, align: 'right' as const, render: money }, { title: '기본정책', dataIndex: 'basicprice1', width: 110, render: (_: unknown, row: AdjustRow) => ( updateRow(row.id, 'basicprice1', v)} /> ) }, { title: '구두정책', dataIndex: 'basicprice2', width: 110, render: (_: unknown, row: AdjustRow) => ( updateRow(row.id, 'basicprice2', v)} /> ) }, { title: '추가정책', dataIndex: 'basicprice3', width: 110, render: (_: unknown, row: AdjustRow) => ( updateRow(row.id, 'basicprice3', v)} /> ) }, { title: '개별', width: 70, fixed: 'right' as const, render: (_: unknown, row: AdjustRow) => ( ) }, ] return (

일괄개통정산

등록된 개통정보에 일괄적으로 정책금액(정산)을 변경하실 수 있습니다. (유심개통 제외)

개통일 * 최대 31일 조회
검색조건
정렬순서 {defaultSorts.map((_, index) => ( {index > 0 && }