// ============================================================
// 자재관리 — 자재리스트 + 불출의뢰서 서비스
// wace_plm inventoryMng.xml + InventoryMngService.java 1:1 베이스
//
// 테이블:
//   inventory_mgmt              (자재 마스터: contract_objid+unit+part_objid PK, objid는 별도)
//   inventory_mgmt_in           (입고/이동 라인: parent_objid → inventory_mgmt.objid)
//   inventory_mgmt_out          (불출 라인: parent_objid → inventory_mgmt.objid,
//                                inventory_request_master_objid → inventory_mgmt_out_master.objid)
//   inventory_mgmt_out_master   (불출의뢰 마스터: inventory_out_no = Rfw-YYYY-seq)
//   inventory_mgmt_history      (자재 투입 이력 — 본 메뉴에서는 조회용)
//
// 보유수량 공식:
//   USE_CNT = SUM(receipt_qty - move_qty) FROM inventory_mgmt_in WHERE parent=IM.objid
//           - SUM(request_qty) FROM inventory_mgmt_out WHERE parent=IM.objid
//
// 불출 흐름: 의뢰(request_qty) → 접수(reception_status=reception) → 불출(out_qty, outstatus=complete)
// ============================================================

import { getPool } from "../database/db";
import { logger } from "../utils/logger";
import { createObjId } from "../utils/objidUtil";

export interface InventoryListFilter {
  project_objid?: string;
  unit_code?: string;
  part_no?: string;
  part_name?: string;
  part_type?: string;
  location?: string;
  cls_cd?: string;
  cau_cd?: string;
  writer?: string;
  page?: number;
  page_size?: number;
}

export interface IssueRequestFilter {
  part_no?: string;
  part_name?: string;
  request_start_date?: string;
  request_end_date?: string;
  request_user?: string;
  reception_status?: string;
  reception_user?: string;
  reception_start_date?: string;
  reception_end_date?: string;
  out_status?: string;
  page?: number;
  page_size?: number;
}

interface ListResult<T> {
  rows: T[];
  totalCount: number;
  page: number;
  pageSize: number;
}

function paging(f: { page?: number; page_size?: number }) {
  const page = Math.max(1, Number(f.page ?? 1));
  const pageSize = Math.max(1, Math.min(500, Number(f.page_size ?? 50)));
  return { page, pageSize, limit: pageSize, offset: (page - 1) * pageSize };
}

// ─── 자재리스트 그리드 ─────────────────────────────────────────
//   wace `inventoryMngNewGridList` 매퍼 1:1 베이스
//   USE_CNT = inbound − move − out_request (각 부모 행 단위 합산)
//   USE_CNT_ALL = 같은 part_objid 전체 가상 합계 (윈도우 함수)
//   REQUEST_QTY = 누적 불출의뢰 수량
//   LOCATION_NAME = inventory_mgmt_in.location distinct 목록 (콤마)
export async function listInventory(filter: InventoryListFilter): Promise<ListResult<any>> {
  const pool = getPool();
  const { page, pageSize, limit, offset } = paging(filter);

  const where: string[] = [];
  const params: any[] = [];
  const addP = (v: any) => { params.push(v); return `$${params.length}`; };

  if (filter.project_objid) where.push(`IM.contract_objid = ${addP(filter.project_objid)}`);
  if (filter.unit_code)     where.push(`IM.unit = ${addP(filter.unit_code)}`);
  if (filter.part_no)       where.push(`P.part_no ILIKE ${addP(`%${filter.part_no}%`)}`);
  if (filter.part_name)     where.push(`P.part_name ILIKE ${addP(`%${filter.part_name}%`)}`);
  if (filter.part_type)     where.push(`P.part_type = ${addP(filter.part_type)}`);
  if (filter.location)      where.push(`IM.location = ${addP(filter.location)}`);
  if (filter.cls_cd)        where.push(`IM.cls_cd = ${addP(filter.cls_cd)}`);
  if (filter.cau_cd)        where.push(`IM.cau_cd = ${addP(filter.cau_cd)}`);
  if (filter.writer)        where.push(`IM.writer = ${addP(filter.writer)}`);

  const whereSql = where.length ? `WHERE ${where.join(" AND ")}` : "";

  const dataSql = `
    WITH IN_SUM AS (
      SELECT parent_objid,
             SUM(COALESCE(NULLIF(receipt_qty,'')::numeric,0) - COALESCE(NULLIF(move_qty,'')::numeric,0)) AS in_qty
      FROM inventory_mgmt_in
      GROUP BY parent_objid
    ),
    OUT_SUM AS (
      SELECT parent_objid,
             SUM(COALESCE(NULLIF(request_qty,'')::numeric,0)) AS req_qty,
             SUM(COALESCE(NULLIF(out_qty,'')::numeric,0))     AS out_qty
      FROM inventory_mgmt_out
      GROUP BY parent_objid
    ),
    LOC_LIST AS (
      SELECT parent_objid, STRING_AGG(DISTINCT NULLIF(location,''), ',') AS loc_names
      FROM inventory_mgmt_in
      GROUP BY parent_objid
    )
    SELECT
      IM.objid                                       AS objid,
      IM.contract_objid                              AS contract_objid,
      COALESCE(PJ.project_no, CT.contract_no, '')    AS project_no,
      IM.unit                                        AS unit,
      COALESCE(WT.task_name, '')                     AS unit_name,
      IM.part_objid                                  AS part_objid,
      P.part_no                                      AS part_no,
      P.part_name                                    AS part_name,
      P.material                                     AS material,
      P.spec                                         AS spec,
      P.part_type                                    AS part_type,
      P.part_type                                    AS part_type_name,
      IM.cls_cd                                      AS cls_cd,
      IM.cau_cd                                      AS cau_cd,
      IM.location                                    AS location,
      IM.sub_location                                AS sub_location,
      COALESCE(LL.loc_names, IM.location, '')        AS location_name,
      IM.reg_date                                    AS reg_date,
      IM.price                                       AS price,
      IM.writer                                      AS writer,
      COALESCE(user_name(IM.writer), IM.writer, '')  AS writer_name,
      COALESCE(IS_.in_qty, 0) - COALESCE(OS.req_qty, 0)  AS use_cnt,
      COALESCE(OS.req_qty, 0)                            AS request_qty,
      COALESCE(OS.out_qty, 0)                            AS out_qty_total,
      SUM(COALESCE(IS_.in_qty, 0) - COALESCE(OS.req_qty, 0))
        OVER (PARTITION BY IM.part_objid)                AS use_cnt_all,
      '' AS remark
    FROM inventory_mgmt IM
    LEFT JOIN part_mng     P  ON P.objid::varchar = IM.part_objid
    LEFT JOIN contract_mgmt CT ON CT.objid       = IM.contract_objid
    LEFT JOIN project_mgmt  PJ ON PJ.contract_objid = IM.contract_objid
    LEFT JOIN pms_wbs_task  WT ON WT.contract_objid = IM.contract_objid AND WT.task_seq = IM.unit
    LEFT JOIN IN_SUM   IS_ ON IS_.parent_objid = IM.objid
    LEFT JOIN OUT_SUM  OS  ON OS.parent_objid  = IM.objid
    LEFT JOIN LOC_LIST LL  ON LL.parent_objid  = IM.objid
    ${whereSql}
    ORDER BY IM.reg_date DESC NULLS LAST, IM.objid DESC
    LIMIT ${addP(limit)} OFFSET ${addP(offset)}
  `;

  const countSql = `
    SELECT COUNT(*)::int AS cnt
      FROM inventory_mgmt IM
      LEFT JOIN part_mng P ON P.objid::varchar = IM.part_objid
      ${whereSql}
  `;
  try {
    const [d, c] = await Promise.all([
      pool.query(dataSql, params),
      pool.query(countSql, params.slice(0, params.length - 2)),
    ]);
    return { rows: d.rows, totalCount: c.rows[0]?.cnt ?? 0, page, pageSize };
  } catch (e: any) {
    logger.error("listInventory 실패", { error: e.message });
    return { rows: [], totalCount: 0, page, pageSize };
  }
}

// ─── 재고 등록 (자재 신규/추가입고) ───────────────────────────
//   1) inventory_mgmt: (contract_objid+unit+part_objid) UNIQUE → 존재 시 재사용, 없으면 INSERT
//   2) inventory_mgmt_in: 입고 1건 INSERT
export interface SaveInventoryInput {
  project_objid: string;          // contract_objid
  unit?: string;
  part_objid: string;
  qty: number;
  price?: string;
  location: string;
  sub_location?: string;
  cls_cd?: string;
  cau_cd?: string;
  receipt_date?: string;
  writer?: string;
  order_objid?: string;
}
export async function saveInventory(input: SaveInventoryInput): Promise<{ objid: string; in_objid: string }> {
  const pool = getPool();
  const client = await pool.connect();
  try {
    await client.query("BEGIN");

    const probe = await client.query(
      `SELECT objid FROM inventory_mgmt
        WHERE contract_objid = $1 AND unit = $2 AND part_objid = $3
        LIMIT 1`,
      [input.project_objid, input.unit ?? "", input.part_objid],
    );
    let objid = probe.rows[0]?.objid as string | undefined;
    if (!objid) {
      objid = createObjId();
      await client.query(
        `INSERT INTO inventory_mgmt
          (objid, contract_objid, unit, part_objid, cls_cd, cau_cd, qty, location, sub_location,
           reg_date, price, writer)
         VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9, TO_CHAR(NOW(),'YYYY-MM-DD'), $10, $11)`,
        [objid, input.project_objid, input.unit ?? "", input.part_objid,
         input.cls_cd ?? "", input.cau_cd ?? "", String(input.qty),
         input.location, input.sub_location ?? "",
         input.price ?? "", input.writer ?? ""],
      );
    }
    const inObjid = createObjId();
    await client.query(
      `INSERT INTO inventory_mgmt_in
        (objid, parent_objid, receipt_qty, location, sub_location, writer, regdate,
         contract_mgmt_objid, purchase_order_master_objid, receipt_date)
       VALUES ($1, $2, $3, $4, $5, $6, NOW(), $7, $8, $9)`,
      [inObjid, objid, String(input.qty), input.location, input.sub_location ?? "",
       input.writer ?? "", input.project_objid,
       input.order_objid ?? "", input.receipt_date ?? new Date().toISOString().slice(0, 10)],
    );
    await client.query("COMMIT");
    return { objid, in_objid: inObjid };
  } catch (e) {
    await client.query("ROLLBACK");
    throw e;
  } finally {
    client.release();
  }
}

// ─── 재고 삭제 ─────────────────────────────────────────────
export async function deleteInventory(objidArr: string[]): Promise<{ deleted: number }> {
  if (!objidArr.length) return { deleted: 0 };
  const pool = getPool();
  const client = await pool.connect();
  try {
    await client.query("BEGIN");
    await client.query(`DELETE FROM inventory_mgmt_in  WHERE parent_objid = ANY($1::varchar[])`, [objidArr]);
    await client.query(`DELETE FROM inventory_mgmt_out WHERE parent_objid = ANY($1::varchar[])`, [objidArr]);
    const r = await client.query(`DELETE FROM inventory_mgmt WHERE objid = ANY($1::varchar[])`, [objidArr]);
    await client.query("COMMIT");
    return { deleted: r.rowCount ?? 0 };
  } catch (e) {
    await client.query("ROLLBACK");
    throw e;
  } finally {
    client.release();
  }
}

// ─── 자재이동: inventory_mgmt_in 행에 move_qty/move_date/move_user 누적 ──
export interface MoveInventoryInput {
  in_objid: string;
  move_qty: number;
  location?: string;
  sub_location?: string;
  move_date?: string;
  move_user?: string;
  writer?: string;
}
export async function moveInventoryBulk(items: MoveInventoryInput[]): Promise<{ updated: number }> {
  if (!items.length) return { updated: 0 };
  const pool = getPool();
  const client = await pool.connect();
  try {
    await client.query("BEGIN");
    for (const it of items) {
      const prev = await client.query(
        `SELECT COALESCE(NULLIF(move_qty,''),'0')::numeric AS mq, parent_objid
           FROM inventory_mgmt_in WHERE objid = $1`,
        [it.in_objid],
      );
      const parent = prev.rows[0]?.parent_objid as string | undefined;
      if (!parent) continue;
      const newMove = Number(prev.rows[0].mq) + Number(it.move_qty);
      await client.query(
        `UPDATE inventory_mgmt_in
            SET move_qty = $1,
                move_date = COALESCE($2, move_date),
                move_user = COALESCE($3, move_user)
          WHERE objid = $4`,
        [String(newMove), it.move_date ?? null, it.move_user ?? null, it.in_objid],
      );
      // 이동 이력 라인 추가
      await client.query(
        `INSERT INTO inventory_mgmt_in
          (objid, parent_objid, receipt_qty, location, sub_location, writer, regdate,
           move_qty, move_date, move_user)
         VALUES ($1, $2, $3, $4, $5, $6, NOW(), $7, $8, $9)`,
        [createObjId(), parent, String(it.move_qty),
         it.location ?? "", it.sub_location ?? "",
         it.writer ?? it.move_user ?? "",
         String(it.move_qty), it.move_date ?? null, it.move_user ?? null],
      );
    }
    await client.query("COMMIT");
    return { updated: items.length };
  } catch (e) {
    await client.query("ROLLBACK");
    throw e;
  } finally {
    client.release();
  }
}

// ─── 자재 입출고 이력 ─────────────────────────────────────────
export async function getInventoryHistory(parentObjid: string): Promise<any[]> {
  const pool = getPool();
  const sql = `
    -- 입고
    SELECT
      II.objid                                    AS objid,
      P.part_no                                   AS part_no,
      P.part_name                                 AS part_name,
      '입고'                                       AS gubun,
      COALESCE(II.receipt_qty, '')                AS qty,
      II.location                                 AS location_name,
      II.sub_location                             AS sub_location_name,
      II.regdate                                  AS regdate,
      II.writer                                   AS writer,
      COALESCE(user_name(II.writer), II.writer,'') AS writer_name
    FROM inventory_mgmt_in II
    LEFT JOIN inventory_mgmt IM ON IM.objid = II.parent_objid
    LEFT JOIN part_mng       P  ON P.objid::varchar = IM.part_objid
    WHERE II.parent_objid = $1 AND COALESCE(II.move_date, '') = ''

    UNION ALL

    -- 이동
    SELECT
      II.objid, P.part_no, P.part_name, '이동',
      COALESCE(II.move_qty, II.receipt_qty, ''),
      II.location, II.sub_location, II.regdate,
      II.move_user,
      COALESCE(user_name(II.move_user), II.move_user, '')
    FROM inventory_mgmt_in II
    LEFT JOIN inventory_mgmt IM ON IM.objid = II.parent_objid
    LEFT JOIN part_mng       P  ON P.objid::varchar = IM.part_objid
    WHERE II.parent_objid = $1 AND COALESCE(II.move_date, '') <> ''

    UNION ALL

    -- 출고
    SELECT
      IO.objid, P.part_no, P.part_name, '출고',
      COALESCE(IO.out_qty, IO.request_qty, ''),
      '', '', IO.regdate,
      IO.writer,
      COALESCE(user_name(IO.writer), IO.writer, '')
    FROM inventory_mgmt_out IO
    LEFT JOIN inventory_mgmt IM ON IM.objid = IO.parent_objid
    LEFT JOIN part_mng       P  ON P.objid::varchar = IM.part_objid
    WHERE IO.parent_objid = $1

    ORDER BY regdate DESC NULLS LAST
  `;
  const r = await pool.query(sql, [parentObjid]);
  return r.rows;
}

// ─── 불출의뢰서 그리드 ─────────────────────────────────────────
export async function listIssueRequest(filter: IssueRequestFilter): Promise<ListResult<any>> {
  const pool = getPool();
  const { page, pageSize, limit, offset } = paging(filter);

  const where: string[] = [];
  const params: any[] = [];
  const addP = (v: any) => { params.push(v); return `$${params.length}`; };

  if (filter.request_user)   where.push(`OM.request_id  = ${addP(filter.request_user)}`);
  if (filter.reception_user) where.push(`OM.reception_id = ${addP(filter.reception_user)}`);
  if (filter.reception_status) {
    if (filter.reception_status === "reception") where.push(`OM.reception_status = 'reception'`);
    else if (filter.reception_status === "AA")  where.push(`COALESCE(OM.reception_status,'') <> 'reception'`);
  }
  if (filter.out_status) {
    if (filter.out_status === "complete") where.push(`OM.outstatus = 'complete'`);
    else if (filter.out_status === "NG")  where.push(`COALESCE(OM.outstatus,'') <> 'complete'`);
  }
  if (filter.request_start_date) where.push(`OM.request_date >= ${addP(filter.request_start_date)}`);
  if (filter.request_end_date)   where.push(`OM.request_date <= ${addP(filter.request_end_date)}`);
  if (filter.reception_start_date) where.push(`OM.reception_date >= ${addP(filter.reception_start_date)}`);
  if (filter.reception_end_date)   where.push(`OM.reception_date <= ${addP(filter.reception_end_date)}`);

  if (filter.part_no) {
    where.push(`EXISTS (
      SELECT 1 FROM inventory_mgmt_out IO
        JOIN inventory_mgmt IM ON IM.objid = IO.parent_objid
        JOIN part_mng P ON P.objid::varchar = IM.part_objid
       WHERE IO.inventory_request_master_objid = OM.objid
         AND P.part_no ILIKE ${addP(`%${filter.part_no}%`)}
    )`);
  }
  if (filter.part_name) {
    where.push(`EXISTS (
      SELECT 1 FROM inventory_mgmt_out IO
        JOIN inventory_mgmt IM ON IM.objid = IO.parent_objid
        JOIN part_mng P ON P.objid::varchar = IM.part_objid
       WHERE IO.inventory_request_master_objid = OM.objid
         AND P.part_name ILIKE ${addP(`%${filter.part_name}%`)}
    )`);
  }

  const whereSql = where.length ? `WHERE ${where.join(" AND ")}` : "";

  const dataSql = `
    SELECT
      OM.objid                                              AS objid,
      OM.inventory_out_no                                   AS inventory_out_no,
      OM.request_date                                       AS request_date,
      OM.request_id                                         AS request_id,
      COALESCE(user_name(OM.request_id), OM.request_id,'')  AS request_user_name,
      OM.reception_status                                   AS reception_status,
      CASE WHEN OM.reception_status='reception' THEN '접수' ELSE '미접수' END AS reception_status_title,
      OM.reception_id                                       AS reception_id,
      COALESCE(user_name(OM.reception_id), OM.reception_id,'') AS reception_user_name,
      OM.reception_date                                     AS reception_date,
      OM.outstatus                                          AS outstatus,
      CASE WHEN OM.outstatus='complete' THEN '완료' ELSE '미완료' END AS outstatus_title,
      OM.remark                                             AS remark,
      OM.writer                                             AS writer,
      OM.regdate                                            AS regdate,
      COALESCE((
        SELECT STRING_AGG(P.part_no, ',' ORDER BY P.part_no)
          FROM inventory_mgmt_out IO
          JOIN inventory_mgmt IM ON IM.objid = IO.parent_objid
          JOIN part_mng P ON P.objid::varchar = IM.part_objid
         WHERE IO.inventory_request_master_objid = OM.objid
      ), '')                                                AS part_no_arr,
      COALESCE((
        SELECT STRING_AGG(P.part_name, ',' ORDER BY P.part_no)
          FROM inventory_mgmt_out IO
          JOIN inventory_mgmt IM ON IM.objid = IO.parent_objid
          JOIN part_mng P ON P.objid::varchar = IM.part_objid
         WHERE IO.inventory_request_master_objid = OM.objid
      ), '')                                                AS part_name_arr,
      COALESCE((
        SELECT SUM(COALESCE(NULLIF(IO.request_qty,'')::numeric,0))
          FROM inventory_mgmt_out IO
         WHERE IO.inventory_request_master_objid = OM.objid
      ), 0)                                                 AS request_qty_total,
      COALESCE((
        SELECT SUM(COALESCE(NULLIF(IO.out_qty,'')::numeric,0))
          FROM inventory_mgmt_out IO
         WHERE IO.inventory_request_master_objid = OM.objid
      ), 0)                                                 AS out_qty_total
    FROM inventory_mgmt_out_master OM
    ${whereSql}
    ORDER BY OM.regdate DESC NULLS LAST, OM.objid DESC
    LIMIT ${addP(limit)} OFFSET ${addP(offset)}
  `;
  const countSql = `SELECT COUNT(*)::int AS cnt FROM inventory_mgmt_out_master OM ${whereSql}`;

  try {
    const [d, c] = await Promise.all([
      pool.query(dataSql, params),
      pool.query(countSql, params.slice(0, params.length - 2)),
    ]);
    return { rows: d.rows, totalCount: c.rows[0]?.cnt ?? 0, page, pageSize };
  } catch (e: any) {
    logger.error("listIssueRequest 실패", { error: e.message });
    return { rows: [], totalCount: 0, page, pageSize };
  }
}

// ─── 불출의뢰 상세: 마스터 + 라인 ────────────────────────────
export async function getIssueRequest(masterObjid: string): Promise<{ master: any; lines: any[] }> {
  const pool = getPool();
  const master = await pool.query(
    `SELECT OM.*,
            COALESCE(user_name(OM.request_id),  OM.request_id, '')  AS request_user_name,
            COALESCE(user_name(OM.reception_id),OM.reception_id,'') AS reception_user_name
       FROM inventory_mgmt_out_master OM
      WHERE OM.objid = $1`,
    [masterObjid],
  );
  const lines = await pool.query(
    `SELECT
       IO.objid               AS objid,
       IO.parent_objid        AS parent_objid,
       IO.request_qty         AS request_qty,
       IO.out_qty             AS out_qty,
       IO.out_date            AS out_date,
       IO.writer              AS writer,
       COALESCE(user_name(IO.writer), IO.writer, '')     AS writer_name,
       IO.acq_user            AS acq_user,
       COALESCE(user_name(IO.acq_user), IO.acq_user, '') AS acq_user_name,
       IO.sign                AS sign,
       IO.unit                AS unit,
       IO.contract_mgmt_objid AS contract_mgmt_objid,
       IM.location            AS location,
       IM.sub_location        AS sub_location,
       IM.part_objid          AS part_objid,
       P.part_no              AS part_no,
       P.part_name            AS part_name,
       P.material             AS material,
       P.spec                 AS spec,
       P.part_type            AS part_type
     FROM inventory_mgmt_out IO
     LEFT JOIN inventory_mgmt IM ON IM.objid = IO.parent_objid
     LEFT JOIN part_mng       P  ON P.objid::varchar = IM.part_objid
     WHERE IO.inventory_request_master_objid = $1
     ORDER BY P.part_no NULLS LAST, IO.objid`,
    [masterObjid],
  );
  return { master: master.rows[0] ?? null, lines: lines.rows };
}

// ─── 불출 가능 자재 후보 (자재리스트에서 선택한 OBJID들 기반) ────
export async function getIssueRequestCandidates(parentObjids: string[]): Promise<any[]> {
  if (!parentObjids.length) return [];
  const pool = getPool();
  const r = await pool.query(
    `SELECT
       IM.objid                                       AS objid,
       IM.contract_objid                              AS contract_objid,
       COALESCE(PJ.project_no, CT.contract_no, '')    AS project_no,
       IM.unit                                        AS unit,
       COALESCE(WT.task_name, '')                     AS unit_name,
       P.part_no                                      AS part_no,
       P.part_name                                    AS part_name,
       P.material                                     AS material,
       P.spec                                         AS spec,
       P.part_type                                    AS part_type_name,
       IM.location                                    AS location,
       IM.sub_location                                AS sub_location,
       (
         COALESCE((SELECT SUM(
            COALESCE(NULLIF(receipt_qty,'')::numeric,0)
          - COALESCE(NULLIF(move_qty,'')::numeric,0))
            FROM inventory_mgmt_in WHERE parent_objid = IM.objid), 0)
         - COALESCE((SELECT SUM(COALESCE(NULLIF(request_qty,'')::numeric,0))
            FROM inventory_mgmt_out WHERE parent_objid = IM.objid), 0)
       )                                              AS use_cnt
     FROM inventory_mgmt IM
     LEFT JOIN part_mng     P  ON P.objid::varchar = IM.part_objid
     LEFT JOIN contract_mgmt CT ON CT.objid       = IM.contract_objid
     LEFT JOIN project_mgmt  PJ ON PJ.contract_objid = IM.contract_objid
     LEFT JOIN pms_wbs_task  WT ON WT.contract_objid = IM.contract_objid AND WT.task_seq = IM.unit
    WHERE IM.objid = ANY($1::varchar[])`,
    [parentObjids],
  );
  return r.rows;
}

// ─── 불출의뢰 생성/수정 ───────────────────────────────────────
export interface IssueRequestSaveInput {
  master_objid?: string; // 수정 시 기존 키
  contract_mgmt_objid?: string;
  request_date?: string;
  request_id?: string;
  remark?: string;
  writer: string;
  lines: Array<{
    parent_objid: string;
    request_qty: number;
    unit?: string;
  }>;
}
export async function saveIssueRequest(input: IssueRequestSaveInput): Promise<{ master_objid: string; inventory_out_no: string }> {
  const pool = getPool();
  const client = await pool.connect();
  try {
    await client.query("BEGIN");
    const today = new Date().toISOString().slice(0, 10);
    let masterObjid = input.master_objid;
    let inventoryOutNo = "";

    if (masterObjid) {
      await client.query(
        `UPDATE inventory_mgmt_out_master
            SET remark = COALESCE($1, remark),
                request_date = COALESCE($2, request_date),
                contract_mgmt_objid = COALESCE($3, contract_mgmt_objid)
          WHERE objid = $4`,
        [input.remark ?? null, input.request_date ?? today, input.contract_mgmt_objid ?? null, masterObjid],
      );
      const m = await client.query(
        `SELECT inventory_out_no FROM inventory_mgmt_out_master WHERE objid = $1`,
        [masterObjid],
      );
      inventoryOutNo = m.rows[0]?.inventory_out_no ?? "";
      await client.query(
        `DELETE FROM inventory_mgmt_out WHERE inventory_request_master_objid = $1`,
        [masterObjid],
      );
    } else {
      masterObjid = createObjId();
      const seq = await client.query(
        `SELECT 'Rfw-' || TO_CHAR(NOW(),'YYYY') || '-' ||
          (COALESCE(MAX(NULLIF(SPLIT_PART(inventory_out_no, '-', 3),'')::int), 0) + 1)::text
            AS no
           FROM inventory_mgmt_out_master
          WHERE inventory_out_no LIKE 'Rfw-' || TO_CHAR(NOW(),'YYYY') || '-%'`,
      );
      inventoryOutNo = seq.rows[0]?.no ?? `Rfw-${new Date().getFullYear()}-1`;
      await client.query(
        `INSERT INTO inventory_mgmt_out_master
          (objid, inventory_out_no, request_date, request_id, writer, regdate, remark, contract_mgmt_objid)
         VALUES ($1, $2, $3, $4, $5, NOW(), $6, $7)`,
        [masterObjid, inventoryOutNo, input.request_date ?? today,
         input.request_id ?? input.writer, input.writer, input.remark ?? "",
         input.contract_mgmt_objid ?? ""],
      );
    }

    for (const ln of input.lines) {
      if (!ln.parent_objid || !ln.request_qty || ln.request_qty <= 0) continue;
      const ioObjid = createObjId();
      await client.query(
        `INSERT INTO inventory_mgmt_out
          (objid, parent_objid, request_qty, writer, regdate,
           inventory_request_master_objid, contract_mgmt_objid, unit)
         VALUES ($1, $2, $3, $4, NOW(), $5, $6, $7)`,
        [ioObjid, ln.parent_objid, String(ln.request_qty), input.writer,
         masterObjid, input.contract_mgmt_objid ?? "", ln.unit ?? ""],
      );
    }
    await client.query("COMMIT");
    return { master_objid: masterObjid!, inventory_out_no: inventoryOutNo };
  } catch (e) {
    await client.query("ROLLBACK");
    throw e;
  } finally {
    client.release();
  }
}

// ─── 불출의뢰 삭제 ─────────────────────────────────────────
export async function deleteIssueRequest(masterObjid: string): Promise<void> {
  const pool = getPool();
  const client = await pool.connect();
  try {
    await client.query("BEGIN");
    await client.query(`DELETE FROM inventory_mgmt_out         WHERE inventory_request_master_objid = $1`, [masterObjid]);
    await client.query(`DELETE FROM inventory_mgmt_out_master  WHERE objid = $1`, [masterObjid]);
    await client.query("COMMIT");
  } catch (e) {
    await client.query("ROLLBACK");
    throw e;
  } finally {
    client.release();
  }
}

// ─── 접수 처리 ────────────────────────────────────────────
export async function receiveIssueRequest(objids: string[], receptionId: string): Promise<{ updated: number }> {
  if (!objids.length) return { updated: 0 };
  const pool = getPool();
  const today = new Date().toISOString().slice(0, 10);
  const r = await pool.query(
    `UPDATE inventory_mgmt_out_master
        SET reception_status = 'reception',
            reception_date   = $1,
            reception_id     = $2
      WHERE objid = ANY($3::varchar[])
        AND COALESCE(reception_status,'') <> 'reception'`,
    [today, receptionId, objids],
  );
  return { updated: r.rowCount ?? 0 };
}

// ─── 자재불출 처리 (실제 불출 + 재고 차감) ──────────────────
export interface DispatchInput {
  master_objid: string;
  lines: Array<{
    objid: string;              // inventory_mgmt_out.objid
    out_qty: number;
    out_date: string;
    acq_user: string;
    sign?: string;
    writer?: string;
  }>;
}
export async function dispatchIssueRequest(input: DispatchInput): Promise<{ updated: number }> {
  const pool = getPool();
  const client = await pool.connect();
  try {
    await client.query("BEGIN");
    for (const ln of input.lines) {
      await client.query(
        `UPDATE inventory_mgmt_out
            SET out_qty  = $1,
                out_date = $2,
                acq_user = $3,
                sign     = COALESCE($4, sign),
                writer   = COALESCE($5, writer)
          WHERE objid = $6`,
        [String(ln.out_qty), ln.out_date, ln.acq_user, ln.sign ?? null, ln.writer ?? null, ln.objid],
      );
    }
    await client.query(
      `UPDATE inventory_mgmt_out_master
          SET outstatus = 'complete'
        WHERE objid = $1`,
      [input.master_objid],
    );
    await client.query("COMMIT");
    return { updated: input.lines.length };
  } catch (e) {
    await client.query("ROLLBACK");
    throw e;
  } finally {
    client.release();
  }
}

// ─── 옵션: 프로젝트 / 유닛 / Location / 사용자 ──────────────
export async function listProjectOptions(): Promise<Array<{ code: string; label: string }>> {
  const pool = getPool();
  const r = await pool.query(
    `SELECT DISTINCT PM.contract_objid AS code,
            COALESCE(PM.project_no, CT.contract_no, PM.contract_objid) AS label
       FROM project_mgmt PM
       LEFT JOIN contract_mgmt CT ON CT.objid = PM.contract_objid
      WHERE PM.contract_objid IS NOT NULL
      ORDER BY label`,
  );
  return r.rows;
}

export async function listUnitOptions(contractObjid: string): Promise<Array<{ code: string; label: string }>> {
  if (!contractObjid) return [];
  const pool = getPool();
  const r = await pool.query(
    `SELECT task_seq AS code, task_name AS label
       FROM pms_wbs_task
      WHERE contract_objid = $1 AND COALESCE(task_seq,'') <> ''
      ORDER BY task_seq`,
    [contractObjid],
  );
  return r.rows;
}

export async function listUserOptions(): Promise<Array<{ code: string; label: string }>> {
  const pool = getPool();
  const r = await pool.query(
    `SELECT user_id AS code, COALESCE(user_name, user_id) AS label
       FROM user_info
      WHERE COALESCE(status,'') <> 'inactive'
      ORDER BY label`,
  );
  return r.rows;
}

export async function listPartOptions(keyword: string, limit: number = 30): Promise<Array<{ code: string; label: string; part_name: string }>> {
  const pool = getPool();
  const kw = keyword?.trim() ?? "";
  const params: any[] = [];
  const conds: string[] = [];
  if (kw) {
    params.push(`%${kw}%`);
    conds.push(`(part_no ILIKE $1 OR part_name ILIKE $1)`);
  }
  params.push(limit);
  const r = await pool.query(
    `SELECT objid::varchar AS code, part_no AS label, part_name
       FROM part_mng
      ${conds.length ? `WHERE ${conds.join(" AND ")}` : ""}
      ORDER BY part_no
      LIMIT $${params.length}`,
    params,
  );
  return r.rows;
}
