feat: Enhance user management and reporting features

- Added `end_date` field to user management for better tracking of user status.
- Updated SQL queries in `adminController` to include `end_date` during user save operations.
- Improved purchase report data handling by refining the logic for received quantities.
- Enhanced file preview functionality to streamline file path handling.
- Updated outbound and receiving controllers to ensure accurate updates to shipment and purchase order details.

These changes aim to improve the overall functionality and user experience in managing user data and reporting processes.
This commit is contained in:
kjs
2026-04-08 15:33:09 +09:00
parent 3421d95e4e
commit 2a23cadb41
124 changed files with 5541 additions and 5662 deletions

View File

@@ -4108,6 +4108,7 @@ interface UserWithDeptRequest {
dept_name?: string;
position_code?: string;
position_name?: string;
end_date?: string | null;
};
mainDept?: {
dept_code: string;
@@ -4199,6 +4200,7 @@ export const saveUserWithDept = async (
dept_name: deptName,
position_code: userInfo.position_code,
position_name: positionName,
end_date: userInfo.end_date !== undefined ? (userInfo.end_date ? `${userInfo.end_date.substring(0, 10)}T00:00:00+09:00` : null) : undefined,
company_code: companyCode !== "*" ? companyCode : undefined,
};
@@ -4230,8 +4232,8 @@ export const saveUserWithDept = async (
email, tel, cell_phone, sabun,
user_type, user_type_name, status, locale,
dept_code, dept_name, position_code, position_name,
company_code, regdate
) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, NOW())`,
company_code, end_date, regdate
) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18, NOW())`,
[
userInfo.user_id,
userInfo.user_name,
@@ -4250,6 +4252,7 @@ export const saveUserWithDept = async (
userInfo.position_code || null,
positionName,
companyCode !== "*" ? companyCode : null,
userInfo.end_date ? `${userInfo.end_date.substring(0, 10)}T00:00:00+09:00` : null,
]
);
}

View File

@@ -256,11 +256,11 @@ export async function getPurchaseReportData(req: any, res: Response): Promise<vo
COALESCE(po.manager, '미지정') as manager,
COALESCE(po.status, '') as status,
CAST(COALESCE(NULLIF(pd.order_qty, ''), '0') AS numeric) as "orderQty",
CAST(COALESCE(NULLIF(po.received_qty, ''), '0') AS numeric) as "receiveQty",
CAST(COALESCE(NULLIF(pd.received_qty, ''), NULLIF(po.received_qty, ''), '0') AS numeric) as "receiveQty",
CAST(COALESCE(NULLIF(pd.unit_price, ''), '0') AS numeric) as "unitPrice",
CAST(COALESCE(NULLIF(pd.order_qty, ''), '0') AS numeric)
* CAST(COALESCE(NULLIF(pd.unit_price, ''), '0') AS numeric) as "orderAmt",
CAST(COALESCE(NULLIF(po.received_qty, ''), '0') AS numeric)
CAST(COALESCE(NULLIF(pd.received_qty, ''), NULLIF(po.received_qty, ''), '0') AS numeric)
* CAST(COALESCE(NULLIF(pd.unit_price, ''), '0') AS numeric) as "receiveAmt",
1 as "orderCnt",
pd.company_code

View File

@@ -843,45 +843,45 @@ export const previewFile = async (
return;
}
// 파일 경로에서 회사코드와 날짜 폴더 추출
const filePathParts = fileRecord.file_path!.split("/");
let fileCompanyCode = filePathParts[2] || "DEFAULT";
// company_* 처리 (실제 회사 코드로 변환)
if (fileCompanyCode === "company_*") {
fileCompanyCode = "company_*"; // 실제 디렉토리명 유지
}
// file_path의 /uploads/ 이후를 baseUploadDir과 직접 결합
const fileName = fileRecord.saved_file_name!;
// 파일 경로에 날짜 구조가 있는지 확인 (YYYY/MM/DD)
let dateFolder = "";
if (filePathParts.length >= 6) {
dateFolder = `${filePathParts[3]}/${filePathParts[4]}/${filePathParts[5]}`;
const dbFilePath = fileRecord.file_path || "";
const uploadsIdx = dbFilePath.indexOf("/uploads/");
let finalPath: string;
if (uploadsIdx !== -1) {
const relativePath = dbFilePath.substring(uploadsIdx + "/uploads/".length);
finalPath = path.join(baseUploadDir, relativePath);
} else {
// fallback: 기존 방식
const filePathParts = dbFilePath.split("/");
let fileCompanyCode = filePathParts[2] || "DEFAULT";
if (fileCompanyCode === "company_*") {
fileCompanyCode = "company_*";
}
let dateFolder = "";
if (filePathParts.length >= 6) {
dateFolder = `${filePathParts[3]}/${filePathParts[4]}/${filePathParts[5]}`;
}
const companyUploadDir = getCompanyUploadDir(
fileCompanyCode,
dateFolder || undefined
);
finalPath = path.join(companyUploadDir, fileName);
}
const companyUploadDir = getCompanyUploadDir(
fileCompanyCode,
dateFolder || undefined
);
const filePath = path.join(companyUploadDir, fileName);
console.log("🔍 파일 미리보기 경로 확인:", {
objid: objid,
filePathFromDB: fileRecord.file_path,
companyCode: companyCode,
dateFolder: dateFolder,
fileName: fileName,
companyUploadDir: companyUploadDir,
finalFilePath: filePath,
fileExists: fs.existsSync(filePath),
finalFilePath: finalPath,
fileExists: fs.existsSync(finalPath),
});
if (!fs.existsSync(filePath)) {
console.error("❌ 파일 없음:", filePath);
if (!fs.existsSync(finalPath)) {
console.error("❌ 파일 없음:", finalPath);
res.status(404).json({
success: false,
message: `실제 파일을 찾을 수 없습니다: ${filePath}`,
message: `실제 파일을 찾을 수 없습니다: ${finalPath}`,
});
return;
}
@@ -929,7 +929,7 @@ export const previewFile = async (
res.setHeader("Content-Type", mimeType);
// 파일 스트림으로 전송
const fileStream = fs.createReadStream(filePath);
const fileStream = fs.createReadStream(finalPath);
fileStream.pipe(res);
} catch (error) {
console.error("파일 미리보기 오류:", error);

View File

@@ -229,15 +229,33 @@ export async function create(req: AuthenticatedRequest, res: Response) {
);
}
// 판매출고인 경우 출하지시의 ship_qty 업데이트
// 판매출고인 경우 출하지시의 ship_qty 업데이트 + 수주상세 ship_qty 반영
if (item.outbound_type === "판매출고" && item.source_id && item.source_type === "shipment_instruction_detail") {
const outQtyNum = Number(item.outbound_qty) || 0;
await client.query(
`UPDATE shipment_instruction_detail
SET ship_qty = COALESCE(ship_qty, 0) + $1,
updated_date = NOW()
WHERE id = $2 AND company_code = $3`,
[item.outbound_qty || 0, item.source_id, companyCode]
[outQtyNum, item.source_id, companyCode]
);
// 출하지시 상세의 detail_id로 수주상세(sales_order_detail) ship_qty도 업데이트
const sidRes = await client.query(
`SELECT detail_id FROM shipment_instruction_detail WHERE id = $1 AND company_code = $2`,
[item.source_id, companyCode]
);
const detailId = sidRes.rows[0]?.detail_id;
if (detailId) {
await client.query(
`UPDATE sales_order_detail
SET ship_qty = (COALESCE(NULLIF(ship_qty,'')::numeric, 0) + $1)::text,
balance_qty = (COALESCE(NULLIF(qty,'')::numeric, 0) - COALESCE(NULLIF(ship_qty,'')::numeric, 0) - $1)::text,
updated_date = NOW()
WHERE id = $2 AND company_code = $3`,
[outQtyNum, detailId, companyCode]
);
}
}
}

View File

@@ -332,8 +332,24 @@ export async function create(req: AuthenticatedRequest, res: Response) {
[purchaseNo, companyCode]
);
const newStatus = unreceived.rows.length === 0 ? '입고완료' : '부분입고';
// 발주 헤더의 received_qty도 디테일 합계로 동기화
await client.query(
`UPDATE purchase_order_mng SET status = $1, updated_date = NOW()
`UPDATE purchase_order_mng SET
status = $1,
received_qty = (
SELECT CAST(COALESCE(SUM(CAST(NULLIF(received_qty, '') AS numeric)), 0) AS text)
FROM purchase_detail
WHERE purchase_no = $2 AND company_code = $3
),
remain_qty = (
SELECT CAST(COALESCE(SUM(
COALESCE(CAST(NULLIF(order_qty, '') AS numeric), 0)
- COALESCE(CAST(NULLIF(received_qty, '') AS numeric), 0)
), 0) AS text)
FROM purchase_detail
WHERE purchase_no = $2 AND company_code = $3
),
updated_date = NOW()
WHERE purchase_no = $2 AND company_code = $3`,
[newStatus, purchaseNo, companyCode]
);