traeon/backend/src/routes/parcels.js

231 lines
6.6 KiB
JavaScript
Raw Normal View History

import { fetchGoodsName } from '../services/goods-name.js';
export default async function parcelRoutes(fastify) {
// 전체 택배 목록 (페이징)
fastify.get('/', async (request) => {
const { status, page = 1, limit = 20 } = request.query;
const offset = (Math.max(1, Number(page)) - 1) * Number(limit);
const lim = Math.min(100, Math.max(1, Number(limit)));
let where = '';
const params = [];
if (status === 'active') {
where = 'WHERE status != ?';
params.push('DELIVERED');
} else if (status === 'delivered') {
where = 'WHERE status = ?';
params.push('DELIVERED');
}
const [[{ total }]] = await fastify.db.execute(
`SELECT COUNT(*) as total FROM parcels ${where}`,
params
);
const [rows] = await fastify.db.execute(
`SELECT * FROM parcels ${where} ORDER BY created_at DESC LIMIT ? OFFSET ?`,
[...params, String(lim), String(offset)]
);
return {
data: rows,
pagination: {
page: Number(page),
limit: lim,
total,
totalPages: Math.ceil(total / lim),
},
};
});
// 운송장 등록
fastify.post('/', async (request, reply) => {
const { carrierId, trackingNumber, label } = request.body;
// 택배사 조회
const [carriers] = await fastify.db.execute(
'SELECT id, name FROM carriers WHERE id = ?',
[carrierId]
);
if (carriers.length === 0) {
return reply.code(400).send({ error: '지원하지 않는 택배사입니다' });
}
const carrier = carriers[0];
// 중복 확인
const [existing] = await fastify.db.execute(
'SELECT id FROM parcels WHERE carrier_id = ? AND tracking_number = ?',
[carrierId, trackingNumber]
);
if (existing.length > 0) {
return reply.code(409).send({ error: '이미 등록된 운송장입니다' });
}
// 물품명 자동 조회 (CJ대한통운, 한진택배)
let goodsName = null;
try {
goodsName = await fetchGoodsName(carrierId, trackingNumber);
} catch (err) {
fastify.log.warn(`물품명 조회 실패: ${err.message}`);
}
// 등록
const [result] = await fastify.db.execute(
'INSERT INTO parcels (carrier_id, carrier_name, tracking_number, label, goods_name) VALUES (?, ?, ?, ?, ?)',
[carrierId, carrier.name, trackingNumber, label || goodsName || null, goodsName]
);
const parcelId = result.insertId;
// 즉시 배송 조회
try {
await refreshParcel(fastify, parcelId);
} catch (err) {
fastify.log.warn(`등록 후 조회 실패: ${err.message}`);
}
const [parcels] = await fastify.db.execute(
'SELECT * FROM parcels WHERE id = ?',
[parcelId]
);
return reply.code(201).send(parcels[0]);
});
// 개별 택배 상세 + 추적 이벤트
fastify.get('/:id', async (request, reply) => {
const { id } = request.params;
const [parcels] = await fastify.db.execute(
'SELECT * FROM parcels WHERE id = ?',
[id]
);
if (parcels.length === 0) {
return reply.code(404).send({ error: '택배를 찾을 수 없습니다' });
}
const [events] = await fastify.db.execute(
'SELECT * FROM tracking_events WHERE parcel_id = ? ORDER BY event_time ASC',
[id]
);
return { ...parcels[0], events };
});
// 별칭 수정
fastify.put('/:id', async (request, reply) => {
const { id } = request.params;
const { label } = request.body;
const [result] = await fastify.db.execute(
'UPDATE parcels SET label = ? WHERE id = ?',
[label, id]
);
if (result.affectedRows === 0) {
return reply.code(404).send({ error: '택배를 찾을 수 없습니다' });
}
const [parcels] = await fastify.db.execute(
'SELECT * FROM parcels WHERE id = ?',
[id]
);
return parcels[0];
});
// 운송장 삭제
fastify.delete('/:id', async (request, reply) => {
const { id } = request.params;
const [result] = await fastify.db.execute(
'DELETE FROM parcels WHERE id = ?',
[id]
);
if (result.affectedRows === 0) {
return reply.code(404).send({ error: '택배를 찾을 수 없습니다' });
}
return { success: true };
});
// 수동 새로고침
fastify.post('/:id/refresh', async (request, reply) => {
const { id } = request.params;
const [parcels] = await fastify.db.execute(
'SELECT * FROM parcels WHERE id = ?',
[id]
);
if (parcels.length === 0) {
return reply.code(404).send({ error: '택배를 찾을 수 없습니다' });
}
await refreshParcel(fastify, id);
const [updated] = await fastify.db.execute(
'SELECT * FROM parcels WHERE id = ?',
[id]
);
const [events] = await fastify.db.execute(
'SELECT * FROM tracking_events WHERE parcel_id = ? ORDER BY event_time ASC',
[id]
);
return { ...updated[0], events };
});
}
function toMysqlDatetime(isoString) {
if (!isoString) return null;
const d = new Date(isoString);
if (isNaN(d.getTime())) return null;
return d.toISOString().slice(0, 19).replace('T', ' ');
}
async function refreshParcel(fastify, parcelId) {
const [parcels] = await fastify.db.execute(
'SELECT * FROM parcels WHERE id = ?',
[parcelId]
);
if (parcels.length === 0) return;
const parcel = parcels[0];
const result = await fastify.tracker.track(parcel.carrier_id, parcel.tracking_number);
if (!result) {
await fastify.db.execute(
'UPDATE parcels SET last_checked_at = NOW() WHERE id = ?',
[parcelId]
);
return;
}
// 상태 업데이트
const status = result.lastEvent?.status || 'UNKNOWN';
const lastDetail = result.lastEvent?.description || '';
const deliveredAt = status === 'DELIVERED' ? toMysqlDatetime(result.lastEvent?.time) : null;
await fastify.db.execute(
`UPDATE parcels SET status = ?, last_detail = ?, last_checked_at = NOW(),
delivered_at = COALESCE(?, delivered_at),
sender_name = COALESCE(?, sender_name),
recipient_name = COALESCE(?, recipient_name)
WHERE id = ?`,
[status, lastDetail, deliveredAt, result.senderName, result.recipientName, parcelId]
);
// 기존 이벤트 삭제 후 새로 삽입
await fastify.db.execute('DELETE FROM tracking_events WHERE parcel_id = ?', [parcelId]);
for (const event of result.events) {
await fastify.db.execute(
`INSERT INTO tracking_events (parcel_id, status, status_name, description, location, event_time)
VALUES (?, ?, ?, ?, ?, ?)`,
[parcelId, event.status, event.statusName, event.description, event.location, toMysqlDatetime(event.time)]
);
}
}