Files
wace_rps/frontend/lib/api/salesEstimate.ts
T
hjjeong b17d7b063d PR-D G11 견적 결재상신 — Amaranth 직행 (wace estimateList_new.jsp btnApproval 1:1)
G11 수주 결재상신(905d5c09)과 동일 패턴을 견적관리에 확장. target_type='CONTRACT_ESTIMATE',
target_objid=estimate_template.objid(최신 차수), formId='1162' (수주 1161과 별도 양식).

- 백엔드: salesEstimateService.startEstimateApproval + POST /sales/estimate/:id/amaranth-approval
- 견적 list SQL: LEFT JOIN amaranth_approval(CONTRACT_ESTIMATE) + APPR_STATUS 4단계 한글 라벨 + approval_required='N' fallback (wace contractMgmt.xml:513~522 1:1)
- 프론트: 견적관리 placeholder 토스트 → handleAmaranthApproval 핸들러 + sky-600 Send 버튼 (수주 페이지와 통일)
- docker-compose 3개: AMARANTH_OUT_PROCESS_CODE_CONTRACT_ESTIMATE + AMARANTH_FORM_ID_CONTRACT_ESTIMATE=1162 추가
- 가드: 행 미선택 / est_objid 없음(견적서 미작성) / inProcess+complete / notRequired+approval_required='N'
- 사전판정(checkApprovalRequired)은 G4 영역으로 분리 — 이번 PR은 단순 SSO 흐름만

검증: BEGIN/ROLLBACK으로 26C-0712(est_objid=-452406811) 4단계 상태(create→inProcess→complete→reject)
+ amaranth row 삭제 시 approval_required='N' fallback 모두 한글 라벨 정상. 문서 08-estimate-approval-verify.md.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-11 18:16:43 +09:00

312 lines
9.7 KiB
TypeScript

import { apiClient } from "./client";
// ============================================================
// 영업관리 > 견적관리 API (wace_plm 도메인 이식)
// ============================================================
export interface EstimateListFilter {
category_cd?: string;
customer_objid?: string;
search_partNo?: string;
search_partObjId?: string;
search_partName?: string;
search_serialNo?: string;
appr_status?: string;
receipt_start_date?: string;
receipt_end_date?: string;
product?: string;
area_cd?: string;
paid_type?: string;
due_start_date?: string;
due_end_date?: string;
}
export interface EstimateRow {
objid: string;
contract_no: string | null;
category_cd: string | null;
category_name: string | null;
customer_objid: string | null;
customer_name: string | null;
customer_type: string | null;
product: string | null;
product_name: string | null;
area_cd: string | null;
area_name: string | null;
paid_type: string | null;
paid_type_name: string | null;
contract_result: string | null;
approval_required: string | null;
return_reason_summary: string | null;
contract_currency: string | null;
contract_currency_name: string | null;
exchange_rate: string | null;
receipt_date: string | null;
regdate: string | null;
writer: string | null;
writer_name: string | null;
pm_user_id: string | null;
pm_user_name: string | null;
est_objid: string | null;
estimate_no: string | null;
template_type: string | null;
est_total_amount: string | null;
est_total_amount_krw: string | null;
manager_name: string | null;
manager_contact: string | null;
estimate_quantity: number | null;
est_status: number | null;
item_summary: string | null;
part_no: string | null;
serial_no: string | null;
earliest_due_date: string | null;
other_due_date_count: number | null;
appr_status: string | null;
amaranth_status: string | null;
add_est_cnt: number | null;
cu01_cnt: number | null;
mail_send_status: string | null;
mail_send_date: string | null;
}
// wace estimateRegistFormPopup 폼 — 라인 8개 항목
export interface EstimateItem {
objid?: string;
seq: number;
product: string; // 제품구분 (필수)
part_objid: string; // 품목 마스터 id (필수)
part_no: string;
part_name: string;
serials?: string[]; // S/N 목록
quantity?: string; // 견적수량
due_date?: string; // 요청납기 (YYYY-MM-DD)
return_reason?: string; // 반납사유 (comm_code)
customer_request?: string; // 고객요청사항
}
// wace estimateRegistFormPopup 폼 — 헤더 8개 항목
export interface EstimateBody {
contract_no?: string; // 신규: 자동 채번 / 수정: 변경 안 함
category_cd: string; // 주문유형 *
area_cd: string; // 국내/해외 *
customer_objid: string; // 고객사 *
paid_type: string; // 유/무상 * ('paid' | 'free')
receipt_date: string; // 접수일 *
contract_currency?: string; // 견적환종
exchange_rate?: string; // 견적환율
approval_required: string; // 결재여부 * ('Y' | 'N')
items: EstimateItem[];
}
export const salesEstimateApi = {
async list(filter: EstimateListFilter = {}) {
const res = await apiClient.get("/sales/estimate/list", { params: filter });
return (res.data?.data ?? []) as EstimateRow[];
},
async detail(objid: string) {
const res = await apiClient.get(`/sales/estimate/${objid}`);
return res.data?.data;
},
async generateNumber(): Promise<string> {
const res = await apiClient.get("/sales/estimate/generate-number");
return res.data?.data?.estimateNo ?? "";
},
async create(body: EstimateBody) {
const res = await apiClient.post("/sales/estimate", body);
return res.data?.data as { objid: string; contract_no: string };
},
async update(objid: string, body: EstimateBody) {
const res = await apiClient.put(`/sales/estimate/${objid}`, body);
return res.data;
},
async remove(objid: string) {
const res = await apiClient.delete(`/sales/estimate/${objid}`);
return res.data;
},
async sendMail(body: {
contractObjid: string;
toEmails: string;
ccEmails?: string;
subject: string;
contents: string;
pdfBase64?: string;
useAddEstOnly?: "Y" | "N";
}) {
const res = await apiClient.post("/sales/estimate/mail", body, {
// PDF base64 포함 시 페이로드 큼 — 충분한 타임아웃
timeout: 120_000,
});
return res.data as { success: boolean; message: string; objid?: string };
},
/** 메일 다이얼로그 자동 채움 (제목/수신/참조용 고객·작성자 정보) */
async getMailInfo(contractObjid: string) {
const res = await apiClient.get(`/sales/estimate/mail-info/${contractObjid}`);
return res.data?.data as {
contract_objid: string;
contract_no: string | null;
customer_objid: string | null;
customer_name: string | null;
customer_email: string | null;
writer: string | null;
writer_email: string | null;
writer_name: string | null;
};
},
/** 메일 다이얼로그 고객사 담당자 체크박스 리스트용 */
async getMailManagers(customerObjid: string) {
const res = await apiClient.get(`/sales/estimate/customer/${customerObjid}/managers`);
return (res.data?.data ?? []) as {
name: string;
email: string;
phone: string;
department: string;
is_main: string;
}[];
},
// ─── G5 견적작성 (estimate_template) ──────────────────────────
async saveTemplate1(body: EstimateTemplate1Body) {
const res = await apiClient.post("/sales/estimate/template1", body);
return res.data?.data as { templateObjid: string; isUpdate: boolean };
},
async saveTemplate2(body: EstimateTemplate2Body) {
const res = await apiClient.post("/sales/estimate/template2", body);
return res.data?.data as { templateObjid: string; isUpdate: boolean };
},
async getTemplate(templateObjid: string) {
const res = await apiClient.get(`/sales/estimate/template/${templateObjid}`);
return res.data?.data as EstimateTemplateDetail | null;
},
async listTemplates(contractObjid: string) {
const res = await apiClient.get(`/sales/estimate/templates/${contractObjid}`);
return (res.data?.data ?? []) as EstimateTemplateRow[];
},
// G4/G11 동일 패턴 — 견적 결재상신 (Amaranth SSO URL 발급)
// wace estimateList_new.jsp:887 fn_openAmaranthApproval 1:1
async startApproval(objid: string, body: { approvalTitle?: string; subjectStr?: string } = {})
: Promise<{ fullUrl: string; approKey: string; status: string; estObjid: string }> {
const res = await apiClient.post(`/sales/estimate/${objid}/amaranth-approval`, body);
return res.data?.data;
},
};
// ─── G5 견적작성 타입 ───────────────────────────────────────────
export interface EstimateTemplateItemRow {
seq?: number;
category?: string | null;
part_objid?: string | null;
description?: string | null;
specification?: string | null;
quantity?: string | null;
unit?: string | null;
unit_price?: string | null;
amount?: string | null;
note?: string | null;
remark?: string | null;
}
// 일반(template1) 저장 페이로드 — wace estimateTemplate1.jsp fn_save
export interface EstimateTemplate1Body {
contract_objid: string;
template_objid?: string;
executor?: string;
recipient?: string;
estimate_no?: string;
contact_person?: string;
greeting_text?: string;
model_name?: string;
model_code?: string;
executor_date?: string;
note1?: string;
note2?: string;
note3?: string;
note4?: string;
note_remarks?: string;
total_amount?: string;
total_amount_krw?: string;
manager_name?: string;
manager_contact?: string;
show_total_row?: "Y" | "N";
items: EstimateTemplateItemRow[];
}
// 장비(template2) 저장 페이로드 — wace estimateTemplate2.jsp fn_save
export interface EstimateTemplate2Body {
contract_objid: string;
template_objid?: string;
executor_date?: string;
recipient?: string;
part_name?: string;
part_objid?: string;
notes_content?: string;
validity_period?: string;
categories_json: string;
group1_subtotal?: string;
total_amount?: string;
total_amount_krw?: string;
}
// 단건 조회 응답
export interface EstimateTemplateDetail {
objid: string;
contract_objid: string;
template_type: "1" | "2";
executor: string | null;
recipient: string | null;
estimate_no: string | null;
contact_person: string | null;
greeting_text: string | null;
model_name: string | null;
model_code: string | null;
executor_date: string | null;
note1: string | null;
note2: string | null;
note3: string | null;
note4: string | null;
note_remarks: string | null;
notes_content: string | null;
validity_period: string | null;
categories_json: string | null;
group1_subtotal: string | null;
total_amount: string | null;
total_amount_krw: string | null;
manager_name: string | null;
manager_contact: string | null;
show_total_row: string | null;
part_name: string | null;
part_objid: string | null;
writer: string | null;
regdate_str: string | null;
chgdate_str: string | null;
exchange_rate: string | null;
contract_currency: string | null;
contract_currency_name: string | null;
items: EstimateTemplateItemRow[];
}
// 차수 리스트 행
export interface EstimateTemplateRow {
objid: string;
template_type: "1" | "2";
estimate_no: string | null;
recipient: string | null;
total_amount: string | null;
total_amount_krw: string | null;
writer: string | null;
regdate: string | null;
chgdate: string | null;
}