feat(purchase): 구매 취소/반품 + 입고일 기준 월별 분석
- 상태 추가: cancelled(구매취소), returned(반품) - API: PUT /:id/cancel, /:id/return, /:id/revert-cancel - 데스크탑: 구매완료→취소 버튼, 입고완료→반품 버튼, 취소→되돌리기 - 분석 페이지: 구매일/입고일 기준 전환 토글, 입고일 기준 월간 분류 집계 + 입고 목록 - Settlement API: GET /received-summary, /received-list (입고일 기준) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -358,6 +358,73 @@ const PurchaseRequestController = {
|
||||
logger.error('PurchaseRequest receive error:', err);
|
||||
res.status(500).json({ success: false, message: '서버 오류가 발생했습니다.' });
|
||||
}
|
||||
},
|
||||
|
||||
// 구매 취소 (purchased → cancelled)
|
||||
cancel: async (req, res) => {
|
||||
try {
|
||||
const existing = await PurchaseRequestModel.getById(req.params.id);
|
||||
if (!existing) return res.status(404).json({ success: false, message: '신청 건을 찾을 수 없습니다.' });
|
||||
if (existing.status !== 'purchased') {
|
||||
return res.status(400).json({ success: false, message: '구매완료 상태의 신청만 취소할 수 있습니다.' });
|
||||
}
|
||||
const { cancel_reason } = req.body;
|
||||
const updated = await PurchaseRequestModel.cancelPurchase(req.params.id, {
|
||||
cancelledBy: req.user.id,
|
||||
cancelReason: cancel_reason
|
||||
});
|
||||
res.json({ success: true, data: updated, message: '구매가 취소되었습니다.' });
|
||||
} catch (err) {
|
||||
logger.error('PurchaseRequest cancel error:', err);
|
||||
res.status(500).json({ success: false, message: '서버 오류가 발생했습니다.' });
|
||||
}
|
||||
},
|
||||
|
||||
// 반품 (received → returned)
|
||||
returnItem: async (req, res) => {
|
||||
try {
|
||||
const existing = await PurchaseRequestModel.getById(req.params.id);
|
||||
if (!existing) return res.status(404).json({ success: false, message: '신청 건을 찾을 수 없습니다.' });
|
||||
if (existing.status !== 'received') {
|
||||
return res.status(400).json({ success: false, message: '입고완료 상태의 신청만 반품할 수 있습니다.' });
|
||||
}
|
||||
const { cancel_reason } = req.body;
|
||||
const updated = await PurchaseRequestModel.returnItem(req.params.id, {
|
||||
cancelledBy: req.user.id,
|
||||
cancelReason: cancel_reason
|
||||
});
|
||||
|
||||
// 신청자에게 반품 알림
|
||||
notifyHelper.send({
|
||||
type: 'purchase',
|
||||
title: '소모품 반품 처리',
|
||||
message: `${existing.item_name || existing.custom_item_name} 반품 처리되었습니다.${cancel_reason ? ' 사유: ' + cancel_reason : ''}`,
|
||||
link_url: '/pages/purchase/request-mobile.html?view=' + req.params.id,
|
||||
target_user_ids: [existing.requester_id],
|
||||
created_by: req.user.id
|
||||
}).catch(() => {});
|
||||
|
||||
res.json({ success: true, data: updated, message: '반품 처리되었습니다.' });
|
||||
} catch (err) {
|
||||
logger.error('PurchaseRequest return error:', err);
|
||||
res.status(500).json({ success: false, message: '서버 오류가 발생했습니다.' });
|
||||
}
|
||||
},
|
||||
|
||||
// 취소 → 대기로 되돌리기
|
||||
revertCancel: async (req, res) => {
|
||||
try {
|
||||
const existing = await PurchaseRequestModel.getById(req.params.id);
|
||||
if (!existing) return res.status(404).json({ success: false, message: '신청 건을 찾을 수 없습니다.' });
|
||||
if (existing.status !== 'cancelled') {
|
||||
return res.status(400).json({ success: false, message: '취소 상태의 신청만 되돌릴 수 있습니다.' });
|
||||
}
|
||||
const updated = await PurchaseRequestModel.revertCancel(req.params.id);
|
||||
res.json({ success: true, data: updated, message: '대기 상태로 되돌렸습니다.' });
|
||||
} catch (err) {
|
||||
logger.error('PurchaseRequest revertCancel error:', err);
|
||||
res.status(500).json({ success: false, message: '서버 오류가 발생했습니다.' });
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user