Files
wace_rps/frontend/components/purchase/DeadlineInfoDialog.tsx
T
hjjeong e51f5f7b69 구매관리 입고관리 입고등록 + 입고일별 마감정보입력 + 매입마감
- backend purchaseInboundService 신설 — getInboundFormInit / saveInboundForm
  (arrival_plan UPSERT 트랜잭션) / saveDeadlineInfo (8필드 일괄 UPDATE) /
  closeArrival (이미 마감된 건 차단) + listWarehouseOptions / listAcctCodeOptions
- backend routes — GET /inbound-form/:pomObjid / POST /inbound-form/save /
  POST /arrival/deadline / POST /arrival/close + 옵션 2개
- InboundFormDialog 신설 — wace deliveryAcceptanceFormPopUp_new.jsp 1:1
  (좌 발주품목 read-only + 우 차수별 입고입력 + 미입고 일괄적용)
- DeadlineInfoDialog 신설 — wace swal 모달 1:1 (8필드 일괄, 단건 시 prefill)
- inbound 페이지 입고등록 / inbound-by-date 마감정보입력+매입마감 연결
- 입고등록 master SELECT 함정 수정 — RPS 에 POM.delivery_status 없어 reception_status fallback
- DataGrid 다중 frozen 누적 left 계산 인프라 추가 (frozenLeftPx props 보강)
  — shadcn Table 기반이라 진짜 column pinning 불가 (자연 위치 도달 후 sticky),
    입고 3페이지의 frozen 부여는 일단 제거. 진짜 pinning 은 별도 작업
2026-05-20 10:04:39 +09:00

166 lines
6.2 KiB
TypeScript

"use client";
// 구매관리 > 입고일별 입고관리 > 마감정보입력 다이얼로그
// wace 원본: purchaseCloseList.jsp:75-246 swal 모달 1:1
// - 다중 행 선택 → 8필드 일괄 UPDATE
// - 단건 선택 시 그리드 행에서 기존 값 자동 채움 (호출자가 prefill 로 전달)
import React, { useEffect, useState } from "react";
import { Dialog, DialogContent, DialogTitle, DialogDescription, DialogFooter, DialogHeader } from "@/components/ui/dialog";
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
import { Loader2 } from "lucide-react";
import { toast } from "sonner";
import { SmartSelect, SmartSelectOption } from "@/components/common/SmartSelect";
import { DateInput } from "@/components/common/DateInput";
import { NumberInput } from "@/components/common/NumberInput";
import { purchaseApi, DeadlineInfoPayload } from "@/lib/api/purchase";
// wace purchaseCloseList.jsp:490-499 하드코딩 옵션
const FOREIGN_TYPE_OPTS: SmartSelectOption[] = [
{ code: "0001220", label: "국내" },
{ code: "0001221", label: "해외" },
];
const TAX_TYPE_OPTS: SmartSelectOption[] = [
{ code: "0900218", label: "과세매입" },
{ code: "0900219", label: "영세매입" },
{ code: "0900220", label: "수입" },
];
export interface DeadlinePrefill {
taxType?: string;
taxInvoiceDate?: string;
exportDeclNo?: string;
loadingDate?: string;
foreignType?: string;
duty?: string;
exchangeRate?: string;
importVat?: string;
}
interface Props {
open: boolean;
onClose: () => void;
onSaved?: () => void;
/** 선택된 arrival_plan.OBJID 목록 */
objIds: string[];
/** 단건 선택 시 기존 값 자동 채움 */
prefill?: DeadlinePrefill;
}
export function DeadlineInfoDialog({ open, onClose, onSaved, objIds, prefill }: Props) {
const [saving, setSaving] = useState(false);
const [form, setForm] = useState<DeadlinePrefill>({});
useEffect(() => {
if (!open) return;
setForm({
taxType: prefill?.taxType ?? "",
taxInvoiceDate: prefill?.taxInvoiceDate ?? "",
exportDeclNo: prefill?.exportDeclNo ?? "",
loadingDate: prefill?.loadingDate ?? "",
foreignType: prefill?.foreignType ?? "",
duty: prefill?.duty ?? "",
exchangeRate: prefill?.exchangeRate ?? "",
importVat: prefill?.importVat ?? "",
});
}, [open, prefill]);
const handleSave = async () => {
if (objIds.length === 0) {
toast.warning("선택된 입고건이 없습니다");
return;
}
setSaving(true);
try {
const payload: DeadlineInfoPayload = {
objIds,
taxType: form.taxType ?? "",
taxInvoiceDate: form.taxInvoiceDate,
exportDeclNo: form.exportDeclNo,
loadingDate: form.loadingDate,
foreignType: form.foreignType,
duty: form.duty,
exchangeRate: form.exchangeRate,
importVat: form.importVat,
};
const r = await purchaseApi.saveArrivalDeadline(payload);
toast.success(`마감정보 저장 완료 (${r.updated}건)`);
onSaved?.();
onClose();
} catch (e: any) {
toast.error(e?.response?.data?.message ?? e?.message ?? "저장 실패");
} finally {
setSaving(false);
}
};
return (
<Dialog open={open} onOpenChange={(v) => { if (!v && !saving) onClose(); }}>
<DialogContent className="max-w-[600px] bg-white">
<DialogHeader>
<DialogTitle></DialogTitle>
<DialogDescription> {objIds.length} </DialogDescription>
</DialogHeader>
<div className="space-y-2">
<FieldRow label="국내/해외">
<SmartSelect options={FOREIGN_TYPE_OPTS} value={form.foreignType ?? ""}
onValueChange={(v) => setForm({ ...form, foreignType: v })} />
</FieldRow>
<FieldRow label="환율">
<NumberInput value={form.exchangeRate || ""} decimals={2}
onChange={(v) => setForm({ ...form, exchangeRate: String(v) })}
className="h-8 text-[12px]" />
</FieldRow>
<FieldRow label="과세구분">
<SmartSelect options={TAX_TYPE_OPTS} value={form.taxType ?? ""}
onValueChange={(v) => setForm({ ...form, taxType: v })} />
</FieldRow>
<FieldRow label="세금계산서발행일">
<DateInput value={form.taxInvoiceDate ?? ""}
onChange={(v) => setForm({ ...form, taxInvoiceDate: v })} />
</FieldRow>
<FieldRow label="수출신고필증신고번호">
<Input value={form.exportDeclNo ?? ""}
onChange={(e) => setForm({ ...form, exportDeclNo: e.target.value })}
className="h-8 text-[12px]" />
</FieldRow>
<FieldRow label="선적일자">
<DateInput value={form.loadingDate ?? ""}
onChange={(v) => setForm({ ...form, loadingDate: v })} />
</FieldRow>
<FieldRow label="관세">
<NumberInput value={form.duty || ""} decimals={0}
onChange={(v) => setForm({ ...form, duty: String(v) })}
className="h-8 text-[12px]" />
</FieldRow>
<FieldRow label="수입부가세">
<NumberInput value={form.importVat || ""} decimals={0}
onChange={(v) => setForm({ ...form, importVat: String(v) })}
className="h-8 text-[12px]" />
</FieldRow>
</div>
<DialogFooter>
<Button variant="outline" onClick={onClose} disabled={saving}></Button>
<Button onClick={handleSave} disabled={saving}>
{saving ? <Loader2 className="w-4 h-4 mr-1 animate-spin" /> : null}
</Button>
</DialogFooter>
</DialogContent>
</Dialog>
);
}
function FieldRow({ label, children }: { label: string; children: React.ReactNode }) {
return (
<div className="grid grid-cols-[180px_1fr] items-center gap-2 border-b border-gray-100 py-1.5">
<Label className="text-[12px] font-semibold text-right pr-3">{label}</Label>
<div>{children}</div>
</div>
);
}