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 은 별도 작업
276 lines
9.0 KiB
TypeScript
276 lines
9.0 KiB
TypeScript
// ============================================================
|
|
// 구매관리 — 7개 메뉴 그리드 API.
|
|
// 백엔드: /api/purchase/{menu-path}, /api/purchase/options/{suppliers|users|projects}
|
|
// ============================================================
|
|
|
|
import { apiClient } from "./client";
|
|
|
|
export interface PurchaseListFilter {
|
|
year?: string;
|
|
customer_objid?: string;
|
|
customer_cd?: string;
|
|
project_no?: string;
|
|
part_no?: string;
|
|
part_name?: string;
|
|
part_spec?: string;
|
|
partner_objid?: string;
|
|
purchase_order_no?: string;
|
|
proposal_no?: string;
|
|
search_status?: string;
|
|
writer?: string;
|
|
request_user?: string;
|
|
purchase_type?: string;
|
|
part_type?: string;
|
|
product_cd?: string;
|
|
category_cd?: string;
|
|
paid_type?: string;
|
|
mail_send_yn?: string;
|
|
delivery_status?: string;
|
|
close_status?: string;
|
|
sales_mng_user_id?: string;
|
|
regdate_start?: string;
|
|
regdate_end?: string;
|
|
receipt_date_start?: string;
|
|
receipt_date_end?: string;
|
|
delivery_start_date?: string;
|
|
delivery_end_date?: string;
|
|
reg_start_date?: string;
|
|
reg_end_date?: string;
|
|
page?: number;
|
|
page_size?: number;
|
|
}
|
|
|
|
export interface PurchaseListResponse<T = any> {
|
|
rows: T[];
|
|
totalCount: number;
|
|
page: number;
|
|
pageSize: number;
|
|
}
|
|
|
|
export interface OptionItem {
|
|
code: string;
|
|
label: string;
|
|
}
|
|
|
|
async function getList<T = any>(path: string, filter: PurchaseListFilter): Promise<PurchaseListResponse<T>> {
|
|
const res = await apiClient.get(`/purchase/${path}`, { params: filter });
|
|
return res.data?.data as PurchaseListResponse<T>;
|
|
}
|
|
|
|
export interface OrderFormData {
|
|
master: Record<string, any>;
|
|
parts: Record<string, any>[];
|
|
}
|
|
|
|
export interface SaveOrderPayload {
|
|
master: Record<string, any>;
|
|
parts: Record<string, any>[];
|
|
deletedPartObjids?: string[];
|
|
}
|
|
|
|
export interface SaveOrderResult {
|
|
objid: string;
|
|
purchase_order_no: string;
|
|
}
|
|
|
|
export interface OrderMailInfo {
|
|
pom_objid: string;
|
|
purchase_order_no: string;
|
|
partner_objid: string;
|
|
partner_name: string;
|
|
partner_email: string;
|
|
writer_email: string;
|
|
writer_name: string;
|
|
form_type: string;
|
|
}
|
|
|
|
export interface PartnerManager {
|
|
name: string;
|
|
email: string;
|
|
phone: string;
|
|
department: string;
|
|
is_main: string;
|
|
}
|
|
|
|
export interface SendOrderMailPayload {
|
|
pomObjid: string;
|
|
toEmails: string;
|
|
ccEmails?: string;
|
|
subject: string;
|
|
contents: string;
|
|
pdfBase64: string;
|
|
}
|
|
|
|
export interface InboundFormData {
|
|
master: {
|
|
pom_objid: string;
|
|
purchase_order_no: string;
|
|
project_no: string;
|
|
contract_mgmt_objid: string;
|
|
partner_objid: string;
|
|
partner_name: string;
|
|
delivery_status: string;
|
|
};
|
|
parts: {
|
|
order_part_objid: string;
|
|
part_objid: string;
|
|
part_no: string;
|
|
part_name: string;
|
|
spec: string;
|
|
maker: string;
|
|
unit_title: string;
|
|
order_qty: number;
|
|
arrival_qty: number;
|
|
non_arrival_qty: number;
|
|
delivery_request_date: string;
|
|
}[];
|
|
arrivals: {
|
|
objid: string;
|
|
order_part_objid: string;
|
|
part_objid: string;
|
|
group_seq: string;
|
|
seq: string;
|
|
receipt_date: string;
|
|
location: string;
|
|
sub_location: string;
|
|
receipt_qty: number;
|
|
arrival_qty: number;
|
|
inventory_status: string;
|
|
}[];
|
|
}
|
|
|
|
export interface InboundSaveRow {
|
|
objid?: string;
|
|
parent_objid: string;
|
|
order_part_objid: string;
|
|
part_objid: string;
|
|
group_seq: string;
|
|
seq: string;
|
|
receipt_date: string;
|
|
location: string;
|
|
sub_location: string;
|
|
receipt_qty: number;
|
|
arrival_qty: number;
|
|
}
|
|
|
|
export interface DeadlineInfoPayload {
|
|
objIds: string[];
|
|
taxType: string;
|
|
taxInvoiceDate?: string;
|
|
exportDeclNo?: string;
|
|
loadingDate?: string;
|
|
foreignType?: string;
|
|
duty?: string;
|
|
importVat?: string;
|
|
exchangeRate?: string;
|
|
}
|
|
|
|
export const purchaseApi = {
|
|
// 그리드 7종
|
|
listPurchaseRequest: (f: PurchaseListFilter = {}) => getList("purchase-request", f),
|
|
listQuotationRequest: (f: PurchaseListFilter = {}) => getList("quotation-request", f),
|
|
listProposal: (f: PurchaseListFilter = {}) => getList("proposal", f),
|
|
listInbound: (f: PurchaseListFilter = {}) => getList("inbound", f),
|
|
listInboundByItem: (f: PurchaseListFilter = {}) => getList("inbound-by-item", f),
|
|
listInboundByDate: (f: PurchaseListFilter = {}) => getList("inbound-by-date", f),
|
|
listProjectStatus: (f: PurchaseListFilter = {}) => getList("project-status", f),
|
|
listOrder: (f: PurchaseListFilter = {}) => getList("order-list", f),
|
|
|
|
// ─── 발주서 폼 (general 양식) ──────────────────────────
|
|
/** 품의서 OBJID 로 발주서 폼 초기값 + 품목 자동채움. */
|
|
async initOrderForm(proposalObjid: string): Promise<OrderFormData> {
|
|
const r = await apiClient.get("/purchase/order-form/init", {
|
|
params: { proposal_objid: proposalObjid },
|
|
});
|
|
return r.data?.data as OrderFormData;
|
|
},
|
|
/** 기존 발주서 마스터+파트 조회 (수정 모드). */
|
|
async getOrderForm(objid: string): Promise<OrderFormData> {
|
|
const r = await apiClient.get(`/purchase/order-form/${encodeURIComponent(objid)}`);
|
|
return r.data?.data as OrderFormData;
|
|
},
|
|
/** 마스터+파트 UPSERT + 누락파트 삭제. */
|
|
async saveOrderForm(payload: SaveOrderPayload): Promise<SaveOrderResult> {
|
|
const r = await apiClient.post("/purchase/order-form/save", payload);
|
|
return r.data?.data as SaveOrderResult;
|
|
},
|
|
/** 발주서 삭제 (cascade). */
|
|
async deleteOrderForm(objid: string): Promise<void> {
|
|
await apiClient.delete(`/purchase/order-form/${encodeURIComponent(objid)}`);
|
|
},
|
|
|
|
// ─── 발주서 메일 발송 ─────────────────────────────────────
|
|
/** 메일 다이얼로그 자동채움 — 공급업체 이메일/이름 + 작성자 이메일 + 발주번호 */
|
|
async getOrderMailInfo(pomObjid: string): Promise<OrderMailInfo | null> {
|
|
const r = await apiClient.get(`/purchase/order-form/mail-info/${encodeURIComponent(pomObjid)}`);
|
|
return (r.data?.data ?? null) as OrderMailInfo | null;
|
|
},
|
|
/** 공급업체 담당자 리스트 — RPS client_mng 단일 email */
|
|
async listPartnerManagers(partnerObjid: string): Promise<PartnerManager[]> {
|
|
const r = await apiClient.get(
|
|
`/purchase/options/partner-managers/${encodeURIComponent(partnerObjid)}`,
|
|
);
|
|
return (r.data?.data ?? []) as PartnerManager[];
|
|
},
|
|
/** PDF 첨부 + SMTP 발송. 성공 시 mail_send_yn='Y'/mail_send_date=NOW() 갱신. */
|
|
async sendOrderMail(payload: SendOrderMailPayload): Promise<{ success: boolean; message: string; objid?: string }> {
|
|
const r = await apiClient.post("/purchase/order-form/mail", payload);
|
|
return r.data as { success: boolean; message: string; objid?: string };
|
|
},
|
|
|
|
// ─── 입고관리 (입고등록 / 마감정보 / 매입마감) ─────────────
|
|
async getInboundForm(pomObjid: string): Promise<InboundFormData> {
|
|
const r = await apiClient.get(`/purchase/inbound-form/${encodeURIComponent(pomObjid)}`);
|
|
return r.data?.data as InboundFormData;
|
|
},
|
|
async saveInboundForm(pomObjid: string, rows: InboundSaveRow[]): Promise<{ saved: number }> {
|
|
const r = await apiClient.post("/purchase/inbound-form/save", { pomObjid, rows });
|
|
return r.data?.data as { saved: number };
|
|
},
|
|
async saveArrivalDeadline(body: DeadlineInfoPayload): Promise<{ updated: number }> {
|
|
const r = await apiClient.post("/purchase/arrival/deadline", body);
|
|
return r.data?.data as { updated: number };
|
|
},
|
|
async closeArrival(objIds: string[]): Promise<{ updated: number }> {
|
|
const r = await apiClient.post("/purchase/arrival/close", { objIds });
|
|
return r.data?.data as { updated: number };
|
|
},
|
|
async listWarehouses(): Promise<OptionItem[]> {
|
|
const r = await apiClient.get("/purchase/options/warehouses");
|
|
return (r.data?.data ?? []) as OptionItem[];
|
|
},
|
|
async listAcctCodes(): Promise<OptionItem[]> {
|
|
const r = await apiClient.get("/purchase/options/acct-codes");
|
|
return (r.data?.data ?? []) as OptionItem[];
|
|
},
|
|
|
|
// 공통 옵션
|
|
async listSuppliers(): Promise<OptionItem[]> {
|
|
const r = await apiClient.get("/purchase/options/suppliers");
|
|
return (r.data?.data ?? []) as OptionItem[];
|
|
},
|
|
// 견적요청서 / 발주서 vendor (wace client_mng 매칭)
|
|
async listVendors(): Promise<OptionItem[]> {
|
|
const r = await apiClient.get("/purchase/options/vendors");
|
|
return (r.data?.data ?? []) as OptionItem[];
|
|
},
|
|
async listUsers(): Promise<OptionItem[]> {
|
|
const r = await apiClient.get("/purchase/options/users");
|
|
return (r.data?.data ?? []) as OptionItem[];
|
|
},
|
|
async listProjects(): Promise<OptionItem[]> {
|
|
const r = await apiClient.get("/purchase/options/projects");
|
|
return (r.data?.data ?? []) as OptionItem[];
|
|
},
|
|
};
|
|
|
|
/** 년도 옵션 — wace 운영판 동일 (현재년도 ±4) */
|
|
export function getYearOptions(): OptionItem[] {
|
|
const y = new Date().getFullYear();
|
|
const out: OptionItem[] = [];
|
|
for (let i = y + 4; i >= y - 4; i--) {
|
|
out.push({ code: String(i), label: String(i) });
|
|
}
|
|
return out;
|
|
}
|