Enhance backend controllers, frontend pages, and V2 components
- Fix department, receiving, shippingOrder, shippingPlan controllers - Update admin pages (company management, disk usage) - Improve sales/logistics pages (order, shipping, outbound, receiving) - Enhance V2 components (file-upload, split-panel-layout, table-list) - Add SmartSelect common component - Update DataGrid, FullscreenDialog common components - Add gitignore rules for personal pipeline tools Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -67,16 +67,17 @@ export async function getDepartments(req: AuthenticatedRequest, res: Response):
|
||||
export async function getDepartment(req: AuthenticatedRequest, res: Response): Promise<void> {
|
||||
try {
|
||||
const { deptCode } = req.params;
|
||||
const companyCode = req.user!.companyCode;
|
||||
|
||||
const department = await queryOne<any>(`
|
||||
SELECT
|
||||
SELECT
|
||||
dept_code,
|
||||
dept_name,
|
||||
company_code,
|
||||
parent_dept_code
|
||||
FROM dept_info
|
||||
WHERE dept_code = $1
|
||||
`, [deptCode]);
|
||||
WHERE dept_code = $1 AND company_code = $2
|
||||
`, [deptCode, companyCode]);
|
||||
|
||||
if (!department) {
|
||||
res.status(404).json({
|
||||
@@ -105,7 +106,7 @@ export async function getDepartment(req: AuthenticatedRequest, res: Response): P
|
||||
export async function createDepartment(req: AuthenticatedRequest, res: Response): Promise<void> {
|
||||
try {
|
||||
const { companyCode } = req.params;
|
||||
const { dept_name, parent_dept_code } = req.body;
|
||||
const { dept_name, parent_dept_code, dept_code: requestedDeptCode } = req.body;
|
||||
|
||||
if (!dept_name || !dept_name.trim()) {
|
||||
res.status(400).json({
|
||||
@@ -131,6 +132,30 @@ export async function createDepartment(req: AuthenticatedRequest, res: Response)
|
||||
return;
|
||||
}
|
||||
|
||||
// 프론트에서 채번 시스템으로 할당된 dept_code 필수
|
||||
if (!requestedDeptCode || !requestedDeptCode.trim()) {
|
||||
res.status(400).json({
|
||||
success: false,
|
||||
message: "부서코드가 필요합니다. 채번 규칙을 먼저 등록해주세요.",
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// 같은 회사 내 부서코드 중복 체크
|
||||
const codeDuplicate = await queryOne<any>(`
|
||||
SELECT dept_code FROM dept_info WHERE dept_code = $1 AND company_code = $2
|
||||
`, [requestedDeptCode.trim(), companyCode]);
|
||||
|
||||
if (codeDuplicate) {
|
||||
res.status(409).json({
|
||||
success: false,
|
||||
message: `부서코드 "${requestedDeptCode}" 가 이미 존재합니다.`,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
const deptCode = requestedDeptCode.trim();
|
||||
|
||||
// 회사 이름 조회
|
||||
const company = await queryOne<any>(`
|
||||
SELECT company_name FROM company_mng WHERE company_code = $1
|
||||
@@ -138,16 +163,6 @@ export async function createDepartment(req: AuthenticatedRequest, res: Response)
|
||||
|
||||
const companyName = company?.company_name || companyCode;
|
||||
|
||||
// 부서 코드 생성 (전역 카운트: DEPT_1, DEPT_2, ...)
|
||||
const codeResult = await queryOne<any>(`
|
||||
SELECT COALESCE(MAX(CAST(SUBSTRING(dept_code FROM 6) AS INTEGER)), 0) + 1 as next_number
|
||||
FROM dept_info
|
||||
WHERE dept_code ~ '^DEPT_[0-9]+$'
|
||||
`);
|
||||
|
||||
const nextNumber = codeResult?.next_number || 1;
|
||||
const deptCode = `DEPT_${nextNumber}`;
|
||||
|
||||
// 부서 생성
|
||||
const result = await query<any>(`
|
||||
INSERT INTO dept_info (
|
||||
@@ -207,6 +222,7 @@ export async function updateDepartment(req: AuthenticatedRequest, res: Response)
|
||||
try {
|
||||
const { deptCode } = req.params;
|
||||
const { dept_name, parent_dept_code } = req.body;
|
||||
const companyCode = req.user!.companyCode;
|
||||
|
||||
if (!dept_name || !dept_name.trim()) {
|
||||
res.status(400).json({
|
||||
@@ -218,12 +234,12 @@ export async function updateDepartment(req: AuthenticatedRequest, res: Response)
|
||||
|
||||
const result = await query<any>(`
|
||||
UPDATE dept_info
|
||||
SET
|
||||
SET
|
||||
dept_name = $1,
|
||||
parent_dept_code = $2
|
||||
WHERE dept_code = $3
|
||||
WHERE dept_code = $3 AND company_code = $4
|
||||
RETURNING *
|
||||
`, [dept_name.trim(), parent_dept_code || null, deptCode]);
|
||||
`, [dept_name.trim(), parent_dept_code || null, deptCode, companyCode]);
|
||||
|
||||
if (result.length === 0) {
|
||||
res.status(404).json({
|
||||
@@ -270,13 +286,14 @@ export async function updateDepartment(req: AuthenticatedRequest, res: Response)
|
||||
export async function deleteDepartment(req: AuthenticatedRequest, res: Response): Promise<void> {
|
||||
try {
|
||||
const { deptCode } = req.params;
|
||||
const companyCode = req.user!.companyCode;
|
||||
|
||||
// 하위 부서 확인
|
||||
const hasChildren = await queryOne<any>(`
|
||||
SELECT COUNT(*) as count
|
||||
FROM dept_info
|
||||
WHERE parent_dept_code = $1
|
||||
`, [deptCode]);
|
||||
WHERE parent_dept_code = $1 AND company_code = $2
|
||||
`, [deptCode, companyCode]);
|
||||
|
||||
if (parseInt(hasChildren?.count || "0") > 0) {
|
||||
res.status(400).json({
|
||||
@@ -286,21 +303,22 @@ export async function deleteDepartment(req: AuthenticatedRequest, res: Response)
|
||||
return;
|
||||
}
|
||||
|
||||
// 부서원 삭제 (부서 삭제 전에 먼저 삭제)
|
||||
// 부서원 삭제 (부서 삭제 전에 먼저 삭제 — 해당 회사 부서만)
|
||||
const deletedMembers = await query<any>(`
|
||||
DELETE FROM user_dept
|
||||
WHERE dept_code = $1
|
||||
AND dept_code IN (SELECT dept_code FROM dept_info WHERE dept_code = $1 AND company_code = $2)
|
||||
RETURNING user_id
|
||||
`, [deptCode]);
|
||||
`, [deptCode, companyCode]);
|
||||
|
||||
const memberCount = deletedMembers.length;
|
||||
|
||||
// 부서 삭제
|
||||
const result = await query<any>(`
|
||||
DELETE FROM dept_info
|
||||
WHERE dept_code = $1
|
||||
WHERE dept_code = $1 AND company_code = $2
|
||||
RETURNING dept_code, dept_name
|
||||
`, [deptCode]);
|
||||
`, [deptCode, companyCode]);
|
||||
|
||||
if (result.length === 0) {
|
||||
res.status(404).json({
|
||||
@@ -352,9 +370,10 @@ export async function deleteDepartment(req: AuthenticatedRequest, res: Response)
|
||||
export async function getDepartmentMembers(req: AuthenticatedRequest, res: Response): Promise<void> {
|
||||
try {
|
||||
const { deptCode } = req.params;
|
||||
const companyCode = req.user!.companyCode;
|
||||
|
||||
const members = await query<any>(`
|
||||
SELECT
|
||||
SELECT
|
||||
u.user_id,
|
||||
u.user_name,
|
||||
u.email,
|
||||
@@ -367,9 +386,9 @@ export async function getDepartmentMembers(req: AuthenticatedRequest, res: Respo
|
||||
FROM user_dept ud
|
||||
JOIN user_info u ON ud.user_id = u.user_id
|
||||
JOIN dept_info d ON ud.dept_code = d.dept_code
|
||||
WHERE ud.dept_code = $1
|
||||
WHERE ud.dept_code = $1 AND d.company_code = $2
|
||||
ORDER BY ud.is_primary DESC, u.user_name
|
||||
`, [deptCode]);
|
||||
`, [deptCode, companyCode]);
|
||||
|
||||
res.status(200).json({
|
||||
success: true,
|
||||
@@ -438,6 +457,7 @@ export async function addDepartmentMember(req: AuthenticatedRequest, res: Respon
|
||||
try {
|
||||
const { deptCode } = req.params;
|
||||
const { user_id } = req.body;
|
||||
const companyCode = req.user!.companyCode;
|
||||
|
||||
if (!user_id) {
|
||||
res.status(400).json({
|
||||
@@ -447,12 +467,25 @@ export async function addDepartmentMember(req: AuthenticatedRequest, res: Respon
|
||||
return;
|
||||
}
|
||||
|
||||
// 부서 소유권 확인 (해당 회사의 부서인지)
|
||||
const dept = await queryOne<any>(`
|
||||
SELECT dept_code FROM dept_info WHERE dept_code = $1 AND company_code = $2
|
||||
`, [deptCode, companyCode]);
|
||||
|
||||
if (!dept) {
|
||||
res.status(403).json({
|
||||
success: false,
|
||||
message: "해당 부서에 접근할 권한이 없습니다.",
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// 사용자 존재 확인
|
||||
const user = await queryOne<any>(`
|
||||
SELECT user_id, user_name
|
||||
FROM user_info
|
||||
WHERE user_id = $1
|
||||
`, [user_id]);
|
||||
WHERE user_id = $1 AND company_code = $2
|
||||
`, [user_id, companyCode]);
|
||||
|
||||
if (!user) {
|
||||
res.status(404).json({
|
||||
@@ -512,6 +545,20 @@ export async function addDepartmentMember(req: AuthenticatedRequest, res: Respon
|
||||
export async function removeDepartmentMember(req: AuthenticatedRequest, res: Response): Promise<void> {
|
||||
try {
|
||||
const { deptCode, userId } = req.params;
|
||||
const companyCode = req.user!.companyCode;
|
||||
|
||||
// 부서 소유권 확인
|
||||
const dept = await queryOne<any>(`
|
||||
SELECT dept_code FROM dept_info WHERE dept_code = $1 AND company_code = $2
|
||||
`, [deptCode, companyCode]);
|
||||
|
||||
if (!dept) {
|
||||
res.status(403).json({
|
||||
success: false,
|
||||
message: "해당 부서에 접근할 권한이 없습니다.",
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
const result = await query<any>(`
|
||||
DELETE FROM user_dept
|
||||
@@ -548,6 +595,20 @@ export async function removeDepartmentMember(req: AuthenticatedRequest, res: Res
|
||||
export async function setPrimaryDepartment(req: AuthenticatedRequest, res: Response): Promise<void> {
|
||||
try {
|
||||
const { deptCode, userId } = req.params;
|
||||
const companyCode = req.user!.companyCode;
|
||||
|
||||
// 부서 소유권 확인
|
||||
const dept = await queryOne<any>(`
|
||||
SELECT dept_code FROM dept_info WHERE dept_code = $1 AND company_code = $2
|
||||
`, [deptCode, companyCode]);
|
||||
|
||||
if (!dept) {
|
||||
res.status(403).json({
|
||||
success: false,
|
||||
message: "해당 부서에 접근할 권한이 없습니다.",
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// 다른 부서의 주 부서 해제
|
||||
await query<any>(`
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
* 입고관리 컨트롤러
|
||||
*
|
||||
* 입고유형별 소스 테이블:
|
||||
* - 구매입고 → purchase_order_mng (발주)
|
||||
* - 구매입고 → purchase_order_mng (발주 헤더) + purchase_detail (발주 디테일)
|
||||
* - 반품입고 → shipment_instruction + shipment_instruction_detail (출하)
|
||||
* - 기타입고 → item_info (품목)
|
||||
*/
|
||||
@@ -228,6 +228,39 @@ export async function create(req: AuthenticatedRequest, res: Response) {
|
||||
[item.inbound_qty || 0, item.source_id, companyCode]
|
||||
);
|
||||
}
|
||||
|
||||
// 구매입고인 경우 purchase_detail 기반 발주의 헤더 상태 업데이트
|
||||
if (item.inbound_type === "구매입고" && item.source_id && item.source_table === "purchase_detail") {
|
||||
// 해당 디테일의 발주번호 조회
|
||||
const detailInfo = await client.query(
|
||||
`SELECT purchase_no FROM purchase_detail WHERE id = $1 AND company_code = $2`,
|
||||
[item.source_id, companyCode]
|
||||
);
|
||||
if (detailInfo.rows.length > 0) {
|
||||
const purchaseNo = detailInfo.rows[0].purchase_no;
|
||||
// 해당 발주의 모든 디테일 잔량 확인
|
||||
const unreceived = await client.query(
|
||||
`SELECT pd.id
|
||||
FROM purchase_detail pd
|
||||
LEFT JOIN (
|
||||
SELECT source_id, SUM(COALESCE(inbound_qty, 0)) AS total_received
|
||||
FROM inbound_mng
|
||||
WHERE source_table = 'purchase_detail' AND company_code = $1
|
||||
GROUP BY source_id
|
||||
) r ON r.source_id = pd.id
|
||||
WHERE pd.purchase_no = $2 AND pd.company_code = $1
|
||||
AND COALESCE(CAST(NULLIF(pd.order_qty, '') AS numeric), 0) - COALESCE(r.total_received, 0) > 0
|
||||
LIMIT 1`,
|
||||
[companyCode, purchaseNo]
|
||||
);
|
||||
const newStatus = unreceived.rows.length === 0 ? '입고완료' : '부분입고';
|
||||
await client.query(
|
||||
`UPDATE purchase_order_mng SET status = $1, updated_date = NOW()
|
||||
WHERE purchase_no = $2 AND company_code = $3`,
|
||||
[newStatus, purchaseNo, companyCode]
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
await client.query("COMMIT");
|
||||
@@ -332,50 +365,115 @@ export async function deleteReceiving(req: AuthenticatedRequest, res: Response)
|
||||
}
|
||||
}
|
||||
|
||||
// 구매입고용: 발주 데이터 조회 (미입고분)
|
||||
// 구매입고용: 발주 데이터 조회 (미입고분) - 신규 헤더-디테일 구조 + 레거시 단일 테이블 UNION ALL
|
||||
export async function getPurchaseOrders(req: AuthenticatedRequest, res: Response) {
|
||||
try {
|
||||
const companyCode = req.user!.companyCode;
|
||||
const { keyword } = req.query;
|
||||
const { keyword, page, pageSize } = req.query;
|
||||
const currentPage = Math.max(1, Number(page) || 1);
|
||||
const limit = Math.min(500, Math.max(1, Number(pageSize) || 20));
|
||||
const offset = (currentPage - 1) * limit;
|
||||
|
||||
const conditions: string[] = ["company_code = $1"];
|
||||
const params: any[] = [companyCode];
|
||||
let paramIdx = 2;
|
||||
|
||||
// 잔량이 있는 것만 조회
|
||||
conditions.push(
|
||||
`COALESCE(CAST(NULLIF(remain_qty, '') AS numeric), COALESCE(CAST(NULLIF(order_qty, '') AS numeric), 0) - COALESCE(CAST(NULLIF(received_qty, '') AS numeric), 0)) > 0`
|
||||
);
|
||||
conditions.push(`status NOT IN ('입고완료', '취소')`);
|
||||
|
||||
let keywordConditionDetail = "";
|
||||
let keywordConditionLegacy = "";
|
||||
if (keyword) {
|
||||
conditions.push(
|
||||
`(purchase_no ILIKE $${paramIdx} OR item_name ILIKE $${paramIdx} OR item_code ILIKE $${paramIdx} OR supplier_name ILIKE $${paramIdx})`
|
||||
);
|
||||
keywordConditionDetail = `AND (pd.purchase_no ILIKE $${paramIdx} OR COALESCE(NULLIF(pd.item_name, ''), ii.item_name) ILIKE $${paramIdx} OR COALESCE(NULLIF(pd.item_code, ''), ii.item_number) ILIKE $${paramIdx} OR COALESCE(pd.supplier_name, po.supplier_name) ILIKE $${paramIdx})`;
|
||||
keywordConditionLegacy = `AND (po.purchase_no ILIKE $${paramIdx} OR po.item_name ILIKE $${paramIdx} OR po.item_code ILIKE $${paramIdx} OR po.supplier_name ILIKE $${paramIdx})`;
|
||||
params.push(`%${keyword}%`);
|
||||
paramIdx++;
|
||||
}
|
||||
|
||||
const baseQuery = `
|
||||
WITH detail_received AS (
|
||||
SELECT source_id, SUM(COALESCE(inbound_qty, 0)) AS total_received
|
||||
FROM inbound_mng
|
||||
WHERE source_table = 'purchase_detail' AND company_code = $1
|
||||
GROUP BY source_id
|
||||
),
|
||||
combined AS (
|
||||
-- 디테일 기반 발주 데이터 (신규 헤더-디테일 구조, 헤더 없는 디테일도 포함)
|
||||
SELECT
|
||||
pd.id,
|
||||
COALESCE(po.purchase_no, pd.purchase_no) AS purchase_no,
|
||||
po.order_date,
|
||||
COALESCE(pd.supplier_code, po.supplier_code) AS supplier_code,
|
||||
COALESCE(pd.supplier_name, po.supplier_name) AS supplier_name,
|
||||
COALESCE(NULLIF(pd.item_code, ''), ii.item_number) AS item_code,
|
||||
COALESCE(NULLIF(pd.item_name, ''), ii.item_name) AS item_name,
|
||||
COALESCE(NULLIF(pd.spec, ''), ii.size) AS spec,
|
||||
COALESCE(NULLIF(pd.material, ''), ii.material) AS material,
|
||||
COALESCE(CAST(NULLIF(pd.order_qty, '') AS numeric), 0) AS order_qty,
|
||||
COALESCE(dr.total_received, 0) AS received_qty,
|
||||
COALESCE(CAST(NULLIF(pd.order_qty, '') AS numeric), 0) - COALESCE(dr.total_received, 0) AS remain_qty,
|
||||
COALESCE(CAST(NULLIF(pd.unit_price, '') AS numeric), 0) AS unit_price,
|
||||
COALESCE(po.status, '') AS status,
|
||||
COALESCE(pd.due_date, po.due_date) AS due_date,
|
||||
'purchase_detail' AS source_table
|
||||
FROM purchase_detail pd
|
||||
LEFT JOIN purchase_order_mng po
|
||||
ON pd.purchase_no = po.purchase_no AND pd.company_code = po.company_code
|
||||
LEFT JOIN item_info ii ON pd.item_id = ii.id
|
||||
LEFT JOIN detail_received dr ON dr.source_id = pd.id
|
||||
WHERE pd.company_code = $1
|
||||
AND COALESCE(CAST(NULLIF(pd.order_qty, '') AS numeric), 0) - COALESCE(dr.total_received, 0) > 0
|
||||
AND COALESCE(pd.approval_status, '') NOT IN ('반려')
|
||||
AND COALESCE(po.status, '') NOT IN ('입고완료', '취소')
|
||||
${keywordConditionDetail}
|
||||
|
||||
UNION ALL
|
||||
|
||||
-- 레거시 단일 테이블 데이터 (purchase_detail에 없는 발주)
|
||||
SELECT
|
||||
po.id,
|
||||
po.purchase_no,
|
||||
po.order_date,
|
||||
po.supplier_code,
|
||||
po.supplier_name,
|
||||
po.item_code,
|
||||
po.item_name,
|
||||
po.spec,
|
||||
po.material,
|
||||
COALESCE(CAST(NULLIF(po.order_qty, '') AS numeric), 0) AS order_qty,
|
||||
COALESCE(CAST(NULLIF(po.received_qty, '') AS numeric), 0) AS received_qty,
|
||||
COALESCE(CAST(NULLIF(po.remain_qty, '') AS numeric),
|
||||
COALESCE(CAST(NULLIF(po.order_qty, '') AS numeric), 0)
|
||||
- COALESCE(CAST(NULLIF(po.received_qty, '') AS numeric), 0)
|
||||
) AS remain_qty,
|
||||
COALESCE(CAST(NULLIF(po.unit_price, '') AS numeric), 0) AS unit_price,
|
||||
po.status,
|
||||
po.due_date,
|
||||
'purchase_order_mng' AS source_table
|
||||
FROM purchase_order_mng po
|
||||
WHERE po.company_code = $1
|
||||
AND NOT EXISTS (
|
||||
SELECT 1 FROM purchase_detail pd
|
||||
WHERE pd.purchase_no = po.purchase_no AND pd.company_code = po.company_code
|
||||
)
|
||||
AND COALESCE(CAST(NULLIF(po.remain_qty, '') AS numeric),
|
||||
COALESCE(CAST(NULLIF(po.order_qty, '') AS numeric), 0)
|
||||
- COALESCE(CAST(NULLIF(po.received_qty, '') AS numeric), 0)
|
||||
) > 0
|
||||
AND po.status NOT IN ('입고완료', '취소')
|
||||
${keywordConditionLegacy}
|
||||
)`;
|
||||
|
||||
const pool = getPool();
|
||||
const result = await pool.query(
|
||||
`SELECT
|
||||
id, purchase_no, order_date, supplier_code, supplier_name,
|
||||
item_code, item_name, spec, material,
|
||||
COALESCE(CAST(NULLIF(order_qty, '') AS numeric), 0) AS order_qty,
|
||||
COALESCE(CAST(NULLIF(received_qty, '') AS numeric), 0) AS received_qty,
|
||||
COALESCE(CAST(NULLIF(remain_qty, '') AS numeric),
|
||||
COALESCE(CAST(NULLIF(order_qty, '') AS numeric), 0)
|
||||
- COALESCE(CAST(NULLIF(received_qty, '') AS numeric), 0)
|
||||
) AS remain_qty,
|
||||
COALESCE(CAST(NULLIF(unit_price, '') AS numeric), 0) AS unit_price,
|
||||
status, due_date
|
||||
FROM purchase_order_mng
|
||||
WHERE ${conditions.join(" AND ")}
|
||||
ORDER BY order_date DESC, purchase_no`,
|
||||
|
||||
const countResult = await pool.query(
|
||||
`${baseQuery} SELECT COUNT(*) AS total FROM combined`,
|
||||
params
|
||||
);
|
||||
const totalCount = parseInt(countResult.rows[0].total, 10);
|
||||
|
||||
const dataResult = await pool.query(
|
||||
`${baseQuery} SELECT * FROM combined ORDER BY order_date DESC, purchase_no LIMIT ${limit} OFFSET ${offset}`,
|
||||
params
|
||||
);
|
||||
|
||||
return res.json({ success: true, data: result.rows });
|
||||
return res.json({ success: true, data: dataResult.rows, totalCount });
|
||||
} catch (error: any) {
|
||||
logger.error("발주 데이터 조회 실패", { error: error.message });
|
||||
return res.status(500).json({ success: false, message: error.message });
|
||||
@@ -386,7 +484,10 @@ export async function getPurchaseOrders(req: AuthenticatedRequest, res: Response
|
||||
export async function getShipments(req: AuthenticatedRequest, res: Response) {
|
||||
try {
|
||||
const companyCode = req.user!.companyCode;
|
||||
const { keyword } = req.query;
|
||||
const { keyword, page, pageSize } = req.query;
|
||||
const currentPage = Math.max(1, Number(page) || 1);
|
||||
const limit = Math.min(500, Math.max(1, Number(pageSize) || 20));
|
||||
const offset = (currentPage - 1) * limit;
|
||||
|
||||
const conditions: string[] = ["si.company_code = $1"];
|
||||
const params: any[] = [companyCode];
|
||||
@@ -400,8 +501,20 @@ export async function getShipments(req: AuthenticatedRequest, res: Response) {
|
||||
paramIdx++;
|
||||
}
|
||||
|
||||
const whereClause = conditions.join(" AND ");
|
||||
const pool = getPool();
|
||||
const result = await pool.query(
|
||||
|
||||
const countResult = await pool.query(
|
||||
`SELECT COUNT(*) AS total
|
||||
FROM shipment_instruction si
|
||||
JOIN shipment_instruction_detail sid
|
||||
ON si.id = sid.instruction_id AND si.company_code = sid.company_code
|
||||
WHERE ${whereClause}`,
|
||||
params
|
||||
);
|
||||
const totalCount = parseInt(countResult.rows[0].total, 10);
|
||||
|
||||
const dataResult = await pool.query(
|
||||
`SELECT
|
||||
sid.id AS detail_id,
|
||||
si.id AS instruction_id,
|
||||
@@ -420,12 +533,13 @@ export async function getShipments(req: AuthenticatedRequest, res: Response) {
|
||||
JOIN shipment_instruction_detail sid
|
||||
ON si.id = sid.instruction_id
|
||||
AND si.company_code = sid.company_code
|
||||
WHERE ${conditions.join(" AND ")}
|
||||
ORDER BY si.instruction_date DESC, si.instruction_no`,
|
||||
WHERE ${whereClause}
|
||||
ORDER BY si.instruction_date DESC, si.instruction_no
|
||||
LIMIT ${limit} OFFSET ${offset}`,
|
||||
params
|
||||
);
|
||||
|
||||
return res.json({ success: true, data: result.rows });
|
||||
return res.json({ success: true, data: dataResult.rows, totalCount });
|
||||
} catch (error: any) {
|
||||
logger.error("출하 데이터 조회 실패", { error: error.message });
|
||||
return res.status(500).json({ success: false, message: error.message });
|
||||
@@ -436,7 +550,10 @@ export async function getShipments(req: AuthenticatedRequest, res: Response) {
|
||||
export async function getItems(req: AuthenticatedRequest, res: Response) {
|
||||
try {
|
||||
const companyCode = req.user!.companyCode;
|
||||
const { keyword } = req.query;
|
||||
const { keyword, page, pageSize } = req.query;
|
||||
const currentPage = Math.max(1, Number(page) || 1);
|
||||
const limit = Math.min(500, Math.max(1, Number(pageSize) || 20));
|
||||
const offset = (currentPage - 1) * limit;
|
||||
|
||||
const conditions: string[] = ["company_code = $1"];
|
||||
const params: any[] = [companyCode];
|
||||
@@ -450,18 +567,27 @@ export async function getItems(req: AuthenticatedRequest, res: Response) {
|
||||
paramIdx++;
|
||||
}
|
||||
|
||||
const whereClause = conditions.join(" AND ");
|
||||
const pool = getPool();
|
||||
const result = await pool.query(
|
||||
|
||||
const countResult = await pool.query(
|
||||
`SELECT COUNT(*) AS total FROM item_info WHERE ${whereClause}`,
|
||||
params
|
||||
);
|
||||
const totalCount = parseInt(countResult.rows[0].total, 10);
|
||||
|
||||
const dataResult = await pool.query(
|
||||
`SELECT
|
||||
id, item_number, item_name, size AS spec, material, unit,
|
||||
COALESCE(CAST(NULLIF(standard_price, '') AS numeric), 0) AS standard_price
|
||||
FROM item_info
|
||||
WHERE ${conditions.join(" AND ")}
|
||||
ORDER BY item_name`,
|
||||
WHERE ${whereClause}
|
||||
ORDER BY item_name
|
||||
LIMIT ${limit} OFFSET ${offset}`,
|
||||
params
|
||||
);
|
||||
|
||||
return res.json({ success: true, data: result.rows });
|
||||
return res.json({ success: true, data: dataResult.rows, totalCount });
|
||||
} catch (error: any) {
|
||||
logger.error("품목 데이터 조회 실패", { error: error.message });
|
||||
return res.status(500).json({ success: false, message: error.message });
|
||||
|
||||
@@ -338,7 +338,7 @@ export async function getShipmentPlanSource(req: AuthenticatedRequest, res: Resp
|
||||
LIMIT 1
|
||||
) i ON true
|
||||
LEFT JOIN customer_mng c
|
||||
ON COALESCE(m.partner_id, d.delivery_partner_code) = c.customer_code AND sp.company_code = c.company_code
|
||||
ON COALESCE(NULLIF(m.partner_id, ''), NULLIF(d.delivery_partner_code, '')) = c.customer_code AND sp.company_code = c.company_code
|
||||
WHERE ${whereClause}
|
||||
`;
|
||||
|
||||
@@ -354,7 +354,7 @@ export async function getShipmentPlanSource(req: AuthenticatedRequest, res: Resp
|
||||
COALESCE(i.item_name, d.part_name, m.part_name, COALESCE(d.part_code, m.part_code, '')) AS item_name,
|
||||
COALESCE(d.spec, m.spec, '') AS spec,
|
||||
COALESCE(m.material, '') AS material,
|
||||
COALESCE(c.customer_name, m.partner_id, d.delivery_partner_code, '') AS customer_name,
|
||||
COALESCE(c.customer_name, '') AS customer_name,
|
||||
COALESCE(m.partner_id, d.delivery_partner_code, '') AS partner_code,
|
||||
sp.detail_id, sp.sales_order_id
|
||||
${fromClause}
|
||||
|
||||
@@ -215,7 +215,7 @@ export async function getList(req: AuthenticatedRequest, res: Response) {
|
||||
COALESCE(i.item_name, d.part_name, m.part_name, COALESCE(d.part_code, m.part_code, '')) AS part_name,
|
||||
COALESCE(d.spec, m.spec, '') AS spec,
|
||||
COALESCE(m.material, '') AS material,
|
||||
COALESCE(c.customer_name, m.partner_id, d.delivery_partner_code, '') AS customer_name,
|
||||
COALESCE(c.customer_name, '') AS customer_name,
|
||||
COALESCE(m.partner_id, d.delivery_partner_code, '') AS partner_code,
|
||||
COALESCE(d.due_date, m.due_date::text, '') AS due_date,
|
||||
COALESCE(NULLIF(d.qty,'')::numeric, m.order_qty, 0) AS order_qty,
|
||||
@@ -232,7 +232,7 @@ export async function getList(req: AuthenticatedRequest, res: Response) {
|
||||
LIMIT 1
|
||||
) i ON true
|
||||
LEFT JOIN customer_mng c
|
||||
ON COALESCE(m.partner_id, d.delivery_partner_code) = c.customer_code
|
||||
ON COALESCE(NULLIF(m.partner_id, ''), NULLIF(d.delivery_partner_code, '')) = c.customer_code
|
||||
AND sp.company_code = c.company_code
|
||||
${whereClause}
|
||||
ORDER BY sp.created_date DESC
|
||||
|
||||
Reference in New Issue
Block a user