diff --git a/backend-node/src/services/salesOrderMgmtService.ts b/backend-node/src/services/salesOrderMgmtService.ts index fe217a36..8e5ba5af 100644 --- a/backend-node/src/services/salesOrderMgmtService.ts +++ b/backend-node/src/services/salesOrderMgmtService.ts @@ -92,8 +92,8 @@ export async function getList(filter: OrderListFilter) { if (filter.paid_type) { conditions.push(`T.PAID_TYPE = $${idx++}`); params.push(filter.paid_type); } if (filter.product) { conditions.push(`T.PRODUCT = $${idx++}`); params.push(filter.product); } if (filter.contract_currency) { conditions.push(`T.CONTRACT_CURRENCY = $${idx++}`); params.push(filter.contract_currency); } - if (filter.order_start_date) { conditions.push(`T.CONTRACT_DATE >= $${idx++}`); params.push(filter.order_start_date); } - if (filter.order_end_date) { conditions.push(`T.CONTRACT_DATE <= $${idx++}`); params.push(filter.order_end_date); } + if (filter.order_start_date) { conditions.push(`T.ORDER_DATE >= $${idx++}`); params.push(filter.order_start_date); } + if (filter.order_end_date) { conditions.push(`T.ORDER_DATE <= $${idx++}`); params.push(filter.order_end_date); } if (filter.search_partObjId) { conditions.push(`EXISTS (SELECT 1 FROM contract_item CI WHERE CI.contract_objid = T.OBJID AND CI.status='ACTIVE' AND CI.part_objid = $${idx++})`); params.push(filter.search_partObjId); @@ -136,7 +136,7 @@ export async function getList(filter: OrderListFilter) { ,CC_CUR.code_name AS CONTRACT_CURRENCY_NAME ,T.EXCHANGE_RATE ,T.PO_NO - ,T.CONTRACT_DATE AS ORDER_DATE + ,T.ORDER_DATE AS ORDER_DATE ,T.RECEIPT_DATE ,T.REQ_DEL_DATE ,T.CONTRACT_RESULT @@ -144,6 +144,16 @@ export async function getList(filter: OrderListFilter) { ,T.ORDER_SUPPLY_PRICE AS ORDER_SUPPLY_PRICE_SUM ,T.ORDER_VAT AS ORDER_VAT_SUM ,T.ORDER_TOTAL_AMOUNT AS ORDER_TOTAL_AMOUNT_SUM + ,CASE + WHEN T.EXCHANGE_RATE IS NOT NULL AND T.EXCHANGE_RATE != '' + AND REPLACE(T.EXCHANGE_RATE, ',', '') ~ '^[0-9]+\\.{0,1}[0-9]*$' + AND CAST(REPLACE(T.EXCHANGE_RATE, ',', '') AS NUMERIC) != 0 + THEN ROUND( + CAST(REPLACE(COALESCE(T.ORDER_TOTAL_AMOUNT, '0'), ',', '') AS NUMERIC) + * CAST(REPLACE(T.EXCHANGE_RATE, ',', '') AS NUMERIC), 2) + ELSE CAST(REPLACE(COALESCE(T.ORDER_TOTAL_AMOUNT, '0'), ',', '') AS NUMERIC) + END AS ORDER_TOTAL_AMOUNT_KRW + ,T.CUSTOMER_REQUEST AS CUSTOMER_REQUEST ,T.WRITER ,U_WR.user_name AS WRITER_NAME ,T.PM_USER_ID diff --git a/backend-node/src/services/salesSaleService.ts b/backend-node/src/services/salesSaleService.ts index 9bfe5c57..d7e2292a 100644 --- a/backend-node/src/services/salesSaleService.ts +++ b/backend-node/src/services/salesSaleService.ts @@ -59,88 +59,149 @@ export interface DeadlineInfoBody { remark?: string; } -// ─── 판매 그리드 (라인 단위) ────────────────────────────────── -// 수주된 contract_item 라인을 기준으로 sales_registration 매칭 표시. +// ─── 판매 그리드 (project_mgmt 라인 단위) ───────────────────── +// project_mgmt가 메인 (수주 확정된 라인 1건당 1행). sales_registration은 LEFT JOIN. +// wace mapper salesNcollectMgmt.xml getSalesMgmtGridList(line 815~) 패턴. export async function getSaleList(filter: SaleListFilter) { const pool = getPool(); - const conditions: string[] = ["CI.status = 'ACTIVE'"]; + const conditions: string[] = ["T.project_no IS NOT NULL", "T.project_no <> ''"]; const params: any[] = []; let idx = 1; if (filter.orderType) { conditions.push(`T.category_cd = $${idx++}`); params.push(filter.orderType); } if (filter.poNo) { conditions.push(`T.po_no ILIKE $${idx++}`); params.push(`%${filter.poNo}%`); } if (filter.customer_objid) { conditions.push(`T.customer_objid = $${idx++}`); params.push(filter.customer_objid); } - if (filter.search_partObjId) { conditions.push(`CI.part_objid = $${idx++}`); params.push(filter.search_partObjId); } + if (filter.search_partObjId) { conditions.push(`T.part_objid = $${idx++}`); params.push(filter.search_partObjId); } if (filter.serialNo) { conditions.push(`EXISTS (SELECT 1 FROM contract_item_serial CIS - WHERE CIS.item_objid = CI.objid AND CIS.status='ACTIVE' + WHERE CIS.item_objid = T.contract_item_objid AND CIS.status='ACTIVE' AND UPPER(CIS.serial_no) LIKE UPPER($${idx++}))`); params.push(`%${filter.serialNo}%`); } - if (filter.orderDateFrom) { conditions.push(`T.contract_date >= $${idx++}`); params.push(filter.orderDateFrom); } - if (filter.orderDateTo) { conditions.push(`T.contract_date <= $${idx++}`); params.push(filter.orderDateTo); } + if (filter.orderDateFrom) { conditions.push(`COALESCE(T.contract_date, CM.order_date) >= $${idx++}`); params.push(filter.orderDateFrom); } + if (filter.orderDateTo) { conditions.push(`COALESCE(T.contract_date, CM.order_date) <= $${idx++}`); params.push(filter.orderDateTo); } if (filter.shippingDateFrom) { conditions.push(`SR.shipping_date >= $${idx++}`); params.push(filter.shippingDateFrom); } - if (filter.shippingDateTo) { conditions.push(`SR.shipping_date <= $${idx++}`); params.push(filter.shippingDateTo); } - if (filter.shippingStatus) { conditions.push(`SR.shipping_order_status = $${idx++}`); params.push(filter.shippingStatus); } + if (filter.shippingDateTo) { conditions.push(`SR.shipping_date <= $${idx++}`); params.push(filter.shippingDateTo); } + if (filter.shippingStatus) { conditions.push(`SR.shipping_order_status = $${idx++}`); params.push(filter.shippingStatus); } const where = `WHERE ${conditions.join(" AND ")}`; const sql = ` SELECT - T.objid AS project_no - ,T.contract_no AS contract_no - ,T.category_cd AS order_type - ,CC_CAT.code_name AS order_type_name - ,T.contract_date AS order_date + T.objid AS project_objid + ,T.project_no AS project_no + ,T.contract_objid + ,T.contract_item_objid + ,T.contract_no AS contract_no + ,T.category_cd AS order_type + ,CC_CAT.code_name AS order_type_name + ,COALESCE(T.contract_date, CM.order_date) AS order_date ,T.po_no - ,T.req_del_date AS request_date + ,COALESCE(NULLIF(CI.due_date, ''), NULLIF(T.due_date, ''), NULLIF(CM.due_date, '')) AS request_date + ,T.due_date ,T.customer_objid - ,C.customer_name AS customer - ,T.product AS product_type - ,CI.objid AS contract_item_objid - ,CI.seq - ,CI.part_objid - ,CI.part_no AS product_no - ,CI.part_name AS product_name - ,CI.order_quantity - ,COALESCE(SR.sales_quantity, 0) AS sales_quantity + ,C.customer_name AS customer + ,T.product AS product_type + ,CC_PRD.code_name AS product_type_name + ,T.area_cd AS nation + ,CC_AREA.code_name AS nation_name + ,T.part_objid + ,T.part_no AS product_no + ,T.part_name AS product_name + ,T.quantity AS order_quantity + ,COALESCE(SR.sales_quantity, 0) AS sales_quantity ,GREATEST( - CAST(REPLACE(NULLIF(CI.order_quantity,''), ',', '') AS NUMERIC) - COALESCE(SR.sales_quantity, 0), + COALESCE(CAST(NULLIF(REPLACE(T.quantity, ',', ''), '') AS NUMERIC), 0) + - COALESCE(SR.sales_quantity, 0), 0 - ) AS remaining_quantity + ) AS remaining_quantity ,SR.sale_no ,SR.sales_unit_price ,SR.sales_supply_price ,SR.sales_vat ,SR.sales_total_amount - ,SR.sales_currency + ,CASE + WHEN SR.sales_exchange_rate IS NOT NULL AND SR.sales_exchange_rate <> 0 + THEN ROUND(COALESCE(SR.sales_total_amount, 0) * SR.sales_exchange_rate, 2) + ELSE COALESCE(SR.sales_total_amount, 0) + END AS sales_total_amount_krw + ,CASE + WHEN SR.sales_exchange_rate IS NOT NULL AND SR.sales_exchange_rate <> 0 + THEN ROUND( + GREATEST( + COALESCE(CAST(NULLIF(REPLACE(T.quantity, ',', ''), '') AS NUMERIC), 0) + - COALESCE(SR.sales_quantity, 0), + 0 + ) * COALESCE(SR.sales_unit_price, 0) * SR.sales_exchange_rate, 2) + ELSE 0 + END AS remaining_amount_krw + /* 환종: sales_registration 우선, 없으면 project_mgmt.contract_currency (wace 패턴) */ + ,COALESCE(NULLIF(SR.sales_currency, ''), T.contract_currency) AS sales_currency + ,COALESCE(CC_CUR_S.code_name, CC_CUR.code_name) AS sales_currency_name + ,T.contract_currency + ,CC_CUR.code_name AS contract_currency_name ,SR.sales_exchange_rate ,SR.shipping_date ,SR.shipping_method ,SR.shipping_order_status ,SR.manager_user_id + ,U_MGR.user_name AS manager_name ,SR.incoterms ,SR.has_split_shipment - ,T.contract_result AS order_status - ,CC_RES.code_name AS order_status_name - ,CASE WHEN SR.sale_no IS NULL THEN 'NOT_REGISTERED' ELSE 'REGISTERED' END AS sales_status - ,T.production_status - ,T.paid_type AS payment_type - ,CASE WHEN T.paid_type='paid' THEN '유상' WHEN T.paid_type='free' THEN '무상' ELSE T.paid_type END AS payment_type_name - ,T.area_cd AS nation - ,CC_AREA.code_name AS nation_name - ,SR.serial_no - FROM contract_item CI - JOIN contract_mgmt T ON T.objid = CI.contract_objid + /* S/N: contract_item_serial 집계 우선, 없으면 sales_registration 텍스트 fallback (wace 패턴) */ + ,COALESCE(NULLIF(CIS_AGG.serial_list, ''), SR.serial_no) AS serial_no + ,T.contract_result AS order_status + ,CC_RES.code_name AS order_status_name + /* 판매상태 — wace 로직: 합계가 수주량 이상이면 완판, 일부만 판매면 분할판매, 0이면 미판매 */ + ,CASE + WHEN COALESCE(SR_AGG.sales_qty_sum, 0) = 0 THEN '미판매' + WHEN COALESCE(SR_AGG.sales_qty_sum, 0) >= COALESCE(CAST(NULLIF(REPLACE(T.quantity, ',', ''), '') AS NUMERIC), 0) THEN '완판' + WHEN COALESCE(SR_AGG.sales_qty_sum, 0) > 0 THEN '분할판매' + ELSE '' + END AS sales_status + ,T.sales_status AS sales_status_raw + ,CM.paid_type AS payment_type + ,CASE WHEN CM.paid_type='paid' THEN '유상' WHEN CM.paid_type='free' THEN '무상' ELSE CM.paid_type END AS payment_type_name + ,CM.receipt_date + ,CM.customer_request + ,T.sales_deadline_date + ,T.regdate + /* 출하지시상태/생산상태/분할S/N/거래명세서/주문서첨부 — 1차 placeholder */ + ,NULL::text AS production_status + ,NULL::text AS split_serial_no + ,'N'::text AS has_transaction_statement + ,0 AS cu01_cnt + FROM project_mgmt T + LEFT JOIN contract_mgmt CM ON CM.objid = T.contract_objid + LEFT JOIN contract_item CI ON CI.objid = T.contract_item_objid AND CI.status = 'ACTIVE' LEFT JOIN customer_mng C ON C.customer_code = CASE WHEN T.customer_objid LIKE 'C_%' THEN substring(T.customer_objid, 3) ELSE T.customer_objid END - LEFT JOIN sales_registration SR ON SR.project_no = T.objid - LEFT JOIN comm_code CC_CAT ON CC_CAT.code_id = T.category_cd AND CC_CAT.status='active' - LEFT JOIN comm_code CC_AREA ON CC_AREA.code_id = T.area_cd AND CC_AREA.status='active' - LEFT JOIN comm_code CC_RES ON CC_RES.code_id = T.contract_result AND CC_RES.status='active' + LEFT JOIN sales_registration SR ON SR.project_no = T.project_no + LEFT JOIN user_info U_MGR ON U_MGR.user_id = SR.manager_user_id + LEFT JOIN comm_code CC_CAT ON CC_CAT.code_id = T.category_cd AND CC_CAT.status='active' + LEFT JOIN comm_code CC_AREA ON CC_AREA.code_id = T.area_cd AND CC_AREA.status='active' + LEFT JOIN comm_code CC_PRD ON CC_PRD.code_id = T.product AND CC_PRD.status='active' + LEFT JOIN comm_code CC_RES ON CC_RES.code_id = T.contract_result AND CC_RES.status='active' + LEFT JOIN comm_code CC_CUR ON CC_CUR.code_id = T.contract_currency AND CC_CUR.status='active' + LEFT JOIN comm_code CC_CUR_S ON CC_CUR_S.code_id = SR.sales_currency AND CC_CUR_S.status='active' + /* 판매수량 합계 (분할 판매 LIKE 패턴 — wace 동일) */ + LEFT JOIN ( + SELECT SR2.project_no, SUM(SR2.sales_quantity) AS sales_qty_sum + FROM sales_registration SR2 + GROUP BY SR2.project_no + ) SR_AGG ON SR_AGG.project_no LIKE T.project_no || '%' + /* contract_item_serial 집계 (S/N 표시) */ + LEFT JOIN ( + SELECT CIS.item_objid, + STRING_AGG(CIS.serial_no, ',' ORDER BY CIS.seq) AS serial_list + FROM contract_item_serial CIS + WHERE UPPER(CIS.status) = 'ACTIVE' + AND CIS.serial_no IS NOT NULL AND CIS.serial_no != '' + GROUP BY CIS.item_objid + ) CIS_AGG ON CIS_AGG.item_objid = T.contract_item_objid ${where} - ORDER BY T.regdate DESC, CI.seq ASC + ORDER BY T.regdate DESC NULLS LAST, T.project_no DESC `; const res = await pool.query(sql, params); @@ -159,52 +220,82 @@ export async function getRevenueList(filter: SaleListFilter) { if (filter.orderType) { conditions.push(`T.category_cd = $${idx++}`); params.push(filter.orderType); } if (filter.poNo) { conditions.push(`T.po_no ILIKE $${idx++}`); params.push(`%${filter.poNo}%`); } if (filter.customer_objid) { conditions.push(`T.customer_objid = $${idx++}`); params.push(filter.customer_objid); } - if (filter.salesDeadlineFrom) { conditions.push(`SL.sales_deadline_date >= $${idx++}`); params.push(filter.salesDeadlineFrom); } - if (filter.salesDeadlineTo) { conditions.push(`SL.sales_deadline_date <= $${idx++}`); params.push(filter.salesDeadlineTo); } - if (filter.shippingDateFrom) { conditions.push(`SL.shipping_date >= $${idx++}`); params.push(filter.shippingDateFrom); } - if (filter.shippingDateTo) { conditions.push(`SL.shipping_date <= $${idx++}`); params.push(filter.shippingDateTo); } + if (filter.salesDeadlineFrom) { conditions.push(`T.sales_deadline_date >= $${idx++}`); params.push(filter.salesDeadlineFrom); } + if (filter.salesDeadlineTo) { conditions.push(`T.sales_deadline_date <= $${idx++}`); params.push(filter.salesDeadlineTo); } + if (filter.shippingDateFrom) { conditions.push(`SR.shipping_date >= $${idx++}`); params.push(filter.shippingDateFrom); } + if (filter.shippingDateTo) { conditions.push(`SR.shipping_date <= $${idx++}`); params.push(filter.shippingDateTo); } - const where = conditions.length ? `WHERE ${conditions.join(" AND ")}` : ""; + conditions.push("T.project_no IS NOT NULL"); + conditions.push("T.project_no <> ''"); + /* wace mapper: shippingDateRequired='Y' — 매출관리는 출하 등록된 프로젝트만 */ + conditions.push(`EXISTS ( + SELECT 1 FROM sales_registration SR_X + WHERE SR_X.project_no = T.project_no + AND SR_X.shipping_date IS NOT NULL + )`); + const where = `WHERE ${conditions.join(" AND ")}`; const sql = ` SELECT - T.objid AS project_no - ,T.contract_no - ,T.category_cd AS order_type - ,T.contract_date AS order_date + T.objid AS project_objid + ,T.project_no AS project_no + ,T.contract_objid + ,T.contract_no AS contract_no + ,T.category_cd AS order_type + ,CC_CAT.code_name AS order_type_name + ,COALESCE(T.contract_date, CM.order_date) AS order_date ,T.po_no ,T.customer_objid - ,C.customer_name AS customer - ,T.product AS product_type - ,T.area_cd AS nation - ,SL.log_id - ,SL.target_objid - ,SL.parent_sale_no - ,SL.split_quantity AS sales_quantity - ,SL.sales_unit_price - ,SL.sales_supply_price - ,SL.sales_vat - ,SL.sales_total_amount - ,SL.sales_currency - ,SL.sales_exchange_rate - ,SL.shipping_date - ,SL.shipping_method - ,SL.serial_no - ,SL.split_serial_no - ,SL.sales_deadline_date - ,SL.tax_type - ,SL.tax_invoice_date - ,SL.export_decl_no - ,SL.loading_date - ,SL.sales_slip_date - ,SL.sales_slip_menu_sq - ,SL.remark - FROM shipment_log SL - JOIN contract_mgmt T ON T.objid = SL.target_objid + ,C.customer_name AS customer + ,T.product AS product_type + ,CC_PRD.code_name AS product_type_name + ,T.area_cd AS nation + ,CC_AREA.code_name AS nation_name + ,T.part_no AS product_no + ,T.part_name AS product_name + ,T.quantity AS sales_quantity_raw + ,COALESCE(SR.sales_quantity, + CAST(NULLIF(REPLACE(T.quantity, ',', ''), '') AS NUMERIC), 0) AS sales_quantity + ,SR.sales_unit_price + ,SR.sales_supply_price + ,SR.sales_vat + ,SR.sales_total_amount + ,CASE + WHEN SR.sales_exchange_rate IS NOT NULL AND SR.sales_exchange_rate <> 0 + THEN ROUND(COALESCE(SR.sales_total_amount, 0) * SR.sales_exchange_rate, 2) + ELSE COALESCE(SR.sales_total_amount, 0) + END AS sales_total_amount_krw + ,T.contract_currency + ,CC_CUR.code_name AS contract_currency_name + ,SR.sales_currency + ,CC_CUR_S.code_name AS sales_currency_name + ,SR.sales_exchange_rate + ,SR.shipping_date + ,SR.shipping_method + ,SR.serial_no + ,T.sales_deadline_date + ,T.tax_type + ,T.tax_invoice_date + ,T.export_decl_no + ,T.loading_date + ,T.sales_slip_date + ,T.sales_slip_menu_sq + ,T.sales_status + /* 분할S/N · 거래명세서 — 1차 placeholder */ + ,NULL::text AS split_serial_no + ,'N'::text AS has_transaction_statement + FROM project_mgmt T + LEFT JOIN contract_mgmt CM ON CM.objid = T.contract_objid LEFT JOIN customer_mng C ON C.customer_code = CASE WHEN T.customer_objid LIKE 'C_%' THEN substring(T.customer_objid, 3) ELSE T.customer_objid END + LEFT JOIN sales_registration SR ON SR.project_no = T.project_no + LEFT JOIN comm_code CC_CAT ON CC_CAT.code_id = T.category_cd AND CC_CAT.status='active' + LEFT JOIN comm_code CC_AREA ON CC_AREA.code_id = T.area_cd AND CC_AREA.status='active' + LEFT JOIN comm_code CC_PRD ON CC_PRD.code_id = T.product AND CC_PRD.status='active' + LEFT JOIN comm_code CC_CUR ON CC_CUR.code_id = T.contract_currency AND CC_CUR.status='active' + LEFT JOIN comm_code CC_CUR_S ON CC_CUR_S.code_id = SR.sales_currency AND CC_CUR_S.status='active' ${where} - ORDER BY SL.reg_date DESC + ORDER BY T.regdate DESC NULLS LAST, T.project_no DESC `; const res = await pool.query(sql, params); diff --git a/docs/migration/sales/ddl-extracted/104_create_project_mgmt.sql b/docs/migration/sales/ddl-extracted/104_create_project_mgmt.sql new file mode 100644 index 00000000..c00a56c5 --- /dev/null +++ b/docs/migration/sales/ddl-extracted/104_create_project_mgmt.sql @@ -0,0 +1,102 @@ +-- ============================================================ +-- project_mgmt — 판매·매출관리 메인 테이블 +-- ---------------------------------------------------------------- +-- 출처: wace_plm 운영 DB (211.115.91.141:11133/waceplm), 75 컬럼 / 89건 +-- 추출: information_schema.columns 직접 조회 (2026-05-07) +-- 역할: contract_item이 수주 확정되면 라인당 1행이 project_mgmt에 생성됨 +-- PROJECT_NO 형식: [R/S/T 등]-[CT/BS/AC/DS/...]-[YYMMDD]-[NNN] +-- ============================================================ + +CREATE TABLE IF NOT EXISTS project_mgmt ( + objid VARCHAR NOT NULL, + contract_objid VARCHAR NOT NULL, + category_cd VARCHAR, + customer_objid VARCHAR, + product VARCHAR, + customer_project_name VARCHAR, + status_cd VARCHAR, + due_date VARCHAR, + location VARCHAR, + setup VARCHAR, + facility VARCHAR, + facility_qty VARCHAR, + facility_type VARCHAR, + facility_depth VARCHAR, + production_no VARCHAR, + bus_cal_cd VARCHAR, + category1_cd VARCHAR, + chg_user_id VARCHAR, + plan_date VARCHAR, + complete_date VARCHAR, + result_cd VARCHAR, + project_no VARCHAR, + pm_user_id VARCHAR, + contract_price VARCHAR, + regdate TIMESTAMP WITHOUT TIME ZONE, + writer VARCHAR, + contract_no VARCHAR, + customer_equip_name VARCHAR, + req_del_date VARCHAR, + contract_del_date VARCHAR, + contract_company VARCHAR, + contract_date VARCHAR, + po_no VARCHAR, + manufacture_plant VARCHAR, + contract_result VARCHAR, + project_name VARCHAR, + spec_user_id VARCHAR, + spec_plan_date VARCHAR, + spec_comp_date VARCHAR, + spec_result_cd VARCHAR, + est_plan_date VARCHAR, + est_user_id VARCHAR, + est_comp_date VARCHAR, + est_result_cd VARCHAR, + area_cd VARCHAR, + contract_price_currency VARCHAR, + contract_currency VARCHAR, + mechanical_type VARCHAR, + is_temp VARCHAR, + overhaul_order VARCHAR, + part_objid VARCHAR, + part_no VARCHAR(100), + part_name VARCHAR(200), + quantity VARCHAR(50), + ebom_status VARCHAR(50), + mbom_status VARCHAR(50), + receiving_rate VARCHAR(10), + production_team_12 VARCHAR(50), + production_team_3 VARCHAR(50), + sales_status VARCHAR, + bom_report_objid VARCHAR(100), + mbom_version INTEGER DEFAULT 0, + mbom_regdate TIMESTAMP WITHOUT TIME ZONE, + mbom_writer VARCHAR(100), + sales_deadline_date VARCHAR(10), + source_bom_type VARCHAR(20), + source_ebom_objid VARCHAR(64), + source_mbom_objid VARCHAR(64), + tax_type VARCHAR(20), + tax_invoice_date VARCHAR(10), + export_decl_no VARCHAR(100), + loading_date VARCHAR(10), + sales_slip_date VARCHAR(8), + sales_slip_menu_sq INTEGER, + contract_item_objid VARCHAR(50), + CONSTRAINT project_mgmt_pkey PRIMARY KEY (objid) +); + +CREATE INDEX IF NOT EXISTS idx_project_mgmt_contract_objid ON project_mgmt(contract_objid); +CREATE INDEX IF NOT EXISTS idx_project_mgmt_contract_part ON project_mgmt(contract_objid, part_objid); +CREATE INDEX IF NOT EXISTS idx_project_mgmt_project_no ON project_mgmt(project_no); +CREATE INDEX IF NOT EXISTS idx_project_mgmt_customer ON project_mgmt(customer_objid); +CREATE INDEX IF NOT EXISTS idx_project_mgmt_regdate ON project_mgmt(regdate DESC); + +COMMENT ON TABLE project_mgmt IS '프로젝트(수주 라인 단위) 헤더 — 판매·매출관리 메인'; +COMMENT ON COLUMN project_mgmt.objid IS 'PK (wace 동일)'; +COMMENT ON COLUMN project_mgmt.project_no IS '프로젝트번호 (예: R-CT-260323-001) — 화면 표시 식별자'; +COMMENT ON COLUMN project_mgmt.contract_objid IS 'contract_mgmt.objid 참조'; +COMMENT ON COLUMN project_mgmt.contract_item_objid IS 'contract_item.objid 참조'; +COMMENT ON COLUMN project_mgmt.part_objid IS 'item_info.id 참조'; +COMMENT ON COLUMN project_mgmt.sales_status IS '판매상태 (NULL/등록/완료 등)'; +COMMENT ON COLUMN project_mgmt.sales_deadline_date IS '매출마감일 (YYYY-MM-DD)'; diff --git a/frontend/app/(main)/COMPANY_16/sales/estimate/page.tsx b/frontend/app/(main)/COMPANY_16/sales/estimate/page.tsx index 5d181928..eefa3bfd 100644 --- a/frontend/app/(main)/COMPANY_16/sales/estimate/page.tsx +++ b/frontend/app/(main)/COMPANY_16/sales/estimate/page.tsx @@ -36,7 +36,7 @@ const GRID_COLUMNS: DataGridColumn[] = [ { key: "add_est_cnt", label: "추가견적", width: "w-[80px]", align: "center" }, { key: "appr_status", label: "결재상태", width: "w-[90px]", align: "center" }, { key: "mail_send_status_label", label: "메일발송", width: "w-[110px]", align: "center" }, - { key: "contract_currency", label: "환종", width: "w-[70px]", align: "center" }, + { key: "contract_currency_name", label: "환종", width: "w-[70px]", align: "center" }, { key: "exchange_rate", label: "환율", width: "w-[80px]", formatMoney: true }, { key: "serial_no", label: "S/N", width: "w-[140px]" }, { key: "part_no", label: "품번", width: "w-[120px]" }, diff --git a/frontend/app/(main)/COMPANY_16/sales/order/page.tsx b/frontend/app/(main)/COMPANY_16/sales/order/page.tsx index 9c554cda..2bfc1431 100644 --- a/frontend/app/(main)/COMPANY_16/sales/order/page.tsx +++ b/frontend/app/(main)/COMPANY_16/sales/order/page.tsx @@ -18,10 +18,12 @@ import { useConfirmDialog } from "@/components/common/ConfirmDialog"; import { DataGrid, DataGridColumn } from "@/components/common/DataGrid"; import { salesOrderMgmtApi, OrderRow, OrderBody, OrderItem } from "@/lib/api/salesOrderMgmt"; +// wace_plm orderMgmtList.jsp 컬럼 순서/라벨에 맞춤 const GRID_COLUMNS: DataGridColumn[] = [ { key: "contract_no", label: "영업번호", width: "w-[120px]" }, - { key: "po_no", label: "발주번호", width: "w-[110px]" }, + { key: "category_name", label: "주문유형", width: "w-[90px]", align: "center" }, { key: "order_date", label: "발주일", width: "w-[100px]", align: "center" }, + { key: "po_no", label: "발주번호", width: "w-[110px]" }, { key: "earliest_due_date_label", label: "요청납기", width: "w-[110px]", align: "center" }, { key: "customer_name", label: "고객사", width: "w-[160px]" }, { key: "item_summary", label: "품명", width: "w-[200px]" }, @@ -32,8 +34,15 @@ const GRID_COLUMNS: DataGridColumn[] = [ { key: "order_supply_price_sum", label: "공급가액", width: "w-[120px]", formatMoney: true }, { key: "order_vat_sum", label: "부가세", width: "w-[100px]", formatMoney: true }, { key: "order_total_amount_sum", label: "총액", width: "w-[120px]", formatMoney: true }, + { key: "order_total_amount_krw", label: "원화총액", width: "w-[120px]", formatMoney: true }, + { key: "cu01_cnt", label: "주문서첨부", width: "w-[90px]", align: "center", formatNumber: true }, + { key: "has_order_data", label: "주문서", width: "w-[80px]", align: "center", formatNumber: true }, + { key: "customer_request", label: "고객사요청사항", width: "w-[180px]" }, + { key: "order_appr_status", label: "결재상태", width: "w-[90px]", align: "center" }, + { key: "contract_currency_name", label: "환종", width: "w-[70px]", align: "center" }, { key: "exchange_rate", label: "환율", width: "w-[80px]", formatMoney: true }, - { key: "contract_currency", label: "환종", width: "w-[70px]", align: "center" }, + { key: "serial_no", label: "S/N", width: "w-[140px]" }, + { key: "part_no", label: "품번", width: "w-[120px]" }, { key: "writer_name", label: "작성자", width: "w-[110px]" }, ]; diff --git a/frontend/app/(main)/COMPANY_16/sales/revenue/page.tsx b/frontend/app/(main)/COMPANY_16/sales/revenue/page.tsx index cb200bfc..ee359957 100644 --- a/frontend/app/(main)/COMPANY_16/sales/revenue/page.tsx +++ b/frontend/app/(main)/COMPANY_16/sales/revenue/page.tsx @@ -18,25 +18,34 @@ import { useConfirmDialog } from "@/components/common/ConfirmDialog"; import { DataGrid, DataGridColumn } from "@/components/common/DataGrid"; import { salesSaleApi, RevenueListRow, DeadlineInfoBody } from "@/lib/api/salesSale"; +// wace_plm revenueMgmtList.jsp 컬럼 순서/라벨에 맞춤 const GRID_COLUMNS: DataGridColumn[] = [ - { key: "contract_no", label: "영업번호", width: "w-[120px]" }, - { key: "po_no", label: "발주번호", width: "w-[110px]" }, + { key: "project_no", label: "프로젝트번호", width: "w-[140px]" }, + { key: "order_type_name", label: "주문유형", width: "w-[90px]", align: "center" }, + { key: "sales_deadline_date", label: "매출마감", width: "w-[110px]", align: "center" }, { key: "order_date", label: "발주일", width: "w-[100px]", align: "center" }, - { key: "shipping_date", label: "출하일", width: "w-[100px]", align: "center" }, - { key: "sales_deadline_date", label: "매출마감일", width: "w-[110px]", align: "center" }, + { key: "po_no", label: "발주번호", width: "w-[110px]" }, { key: "customer", label: "고객사", width: "w-[160px]" }, - { key: "product_type", label: "제품구분", width: "w-[90px]", align: "center" }, - { key: "sales_quantity", label: "수량", width: "w-[80px]", formatNumber: true }, + { key: "product_type_name", label: "제품구분", width: "w-[90px]", align: "center" }, + { key: "product_name", label: "품명", width: "w-[180px]" }, + { key: "sales_quantity", label: "수량", width: "w-[80px]", align: "right", formatNumber: true }, { key: "sales_unit_price", label: "단가", width: "w-[110px]", formatMoney: true }, { key: "sales_supply_price", label: "공급가액", width: "w-[120px]", formatMoney: true }, { key: "sales_vat", label: "부가세", width: "w-[100px]", formatMoney: true }, { key: "sales_total_amount", label: "총액", width: "w-[120px]", formatMoney: true }, - { key: "sales_currency", label: "환종", width: "w-[70px]", align: "center" }, - { key: "tax_type", label: "과세구분", width: "w-[100px]", align: "center" }, - { key: "tax_invoice_date", label: "세금계산서일", width: "w-[120px]", align: "center" }, - { key: "export_decl_no", label: "수출신고필증", width: "w-[140px]" }, - { key: "loading_date", label: "선적일자", width: "w-[100px]", align: "center" }, + { key: "sales_total_amount_krw", label: "원화총액", width: "w-[120px]", formatMoney: true }, + { key: "shipping_date", label: "출하일", width: "w-[100px]", align: "center" }, + { key: "nation_name", label: "국내/해외", width: "w-[90px]", align: "center" }, + { key: "sales_currency_name", label: "환종", width: "w-[70px]", align: "center" }, + { key: "sales_exchange_rate", label: "환율", width: "w-[80px]", formatMoney: true }, { key: "serial_no", label: "S/N", width: "w-[140px]" }, + { key: "split_serial_no", label: "분할S/N", width: "w-[140px]" }, + { key: "product_no", label: "품번", width: "w-[120px]" }, + { key: "tax_type", label: "과세구분", width: "w-[100px]", align: "center" }, + { key: "tax_invoice_date", label: "세금계산서발행일", width: "w-[140px]", align: "center" }, + { key: "export_decl_no", label: "수출신고필증신고번호", width: "w-[160px]" }, + { key: "loading_date", label: "선적일자", width: "w-[100px]", align: "center" }, + { key: "has_transaction_statement", label: "거래명세서", width: "w-[100px]", align: "center" }, ]; export default function SalesRevenuePage() { diff --git a/frontend/app/(main)/COMPANY_16/sales/sale/page.tsx b/frontend/app/(main)/COMPANY_16/sales/sale/page.tsx index 6f2c1c7d..06696c63 100644 --- a/frontend/app/(main)/COMPANY_16/sales/sale/page.tsx +++ b/frontend/app/(main)/COMPANY_16/sales/sale/page.tsx @@ -16,27 +16,36 @@ import { useAuth } from "@/hooks/useAuth"; import { DataGrid, DataGridColumn } from "@/components/common/DataGrid"; import { salesSaleApi, SaleListRow, SaleRegisterBody } from "@/lib/api/salesSale"; +// wace_plm salesMgmtList.jsp 컬럼 순서/라벨에 맞춤 const GRID_COLUMNS: DataGridColumn[] = [ - { key: "contract_no", label: "영업번호", width: "w-[120px]" }, - { key: "po_no", label: "발주번호", width: "w-[110px]" }, + { key: "project_no", label: "프로젝트번호", width: "w-[140px]" }, + { key: "order_type_name", label: "주문유형", width: "w-[90px]", align: "center" }, { key: "order_date", label: "발주일", width: "w-[100px]", align: "center" }, + { key: "po_no", label: "발주번호", width: "w-[110px]" }, { key: "request_date", label: "요청납기", width: "w-[100px]", align: "center" }, { key: "shipping_date", label: "출하일", width: "w-[100px]", align: "center" }, { key: "customer", label: "고객사", width: "w-[160px]" }, { key: "product_name", label: "품명", width: "w-[180px]" }, - { key: "product_no", label: "품번", width: "w-[120px]" }, { key: "order_quantity", label: "수주수량", width: "w-[90px]", align: "right", formatNumber: true }, { key: "sales_quantity", label: "판매수량", width: "w-[90px]", align: "right", formatNumber: true }, { key: "remaining_quantity", label: "잔량", width: "w-[80px]", align: "right", formatNumber: true }, { key: "sales_unit_price", label: "판매단가", width: "w-[110px]", formatMoney: true }, - { key: "sales_supply_price", label: "공급가액", width: "w-[120px]", formatMoney: true }, + { key: "sales_supply_price", label: "판매공급가액", width: "w-[130px]", formatMoney: true }, { key: "sales_vat", label: "부가세", width: "w-[100px]", formatMoney: true }, - { key: "sales_total_amount", label: "총액", width: "w-[120px]", formatMoney: true }, - { key: "sales_currency", label: "환종", width: "w-[70px]", align: "center" }, - { key: "order_status", label: "수주상태", width: "w-[90px]", align: "center" }, + { key: "sales_total_amount", label: "판매총액", width: "w-[120px]", formatMoney: true }, + { key: "sales_total_amount_krw", label: "판매원화총액", width: "w-[130px]", formatMoney: true }, + { key: "remaining_amount_krw", label: "잔량원화총액", width: "w-[130px]", formatMoney: true }, + { key: "order_status_name", label: "수주상태", width: "w-[90px]", align: "center" }, { key: "sales_status", label: "판매상태", width: "w-[100px]", align: "center" }, - { key: "shipping_method", label: "출하방법", width: "w-[100px]" }, + { key: "production_status", label: "생산상태", width: "w-[100px]", align: "center" }, + { key: "shipping_order_status", label: "출하지시상태", width: "w-[110px]", align: "center" }, + { key: "payment_type_name", label: "유/무상", width: "w-[80px]", align: "center" }, + { key: "sales_currency_name", label: "환종", width: "w-[70px]", align: "center" }, + { key: "sales_exchange_rate", label: "환율", width: "w-[80px]", formatMoney: true }, { key: "serial_no", label: "S/N", width: "w-[140px]" }, + { key: "split_serial_no", label: "분할S/N", width: "w-[140px]" }, + { key: "product_no", label: "품번", width: "w-[120px]" }, + { key: "has_transaction_statement", label: "거래명세서", width: "w-[100px]", align: "center" }, ]; export default function SalesSalePage() {