export default async function parcelRoutes(fastify) { // 전체 택배 목록 fastify.get('/', async (request) => { const { status } = request.query; let sql = 'SELECT * FROM parcels ORDER BY created_at DESC'; const params = []; if (status === 'active') { sql = 'SELECT * FROM parcels WHERE status != ? ORDER BY created_at DESC'; params.push('DELIVERED'); } else if (status === 'delivered') { sql = 'SELECT * FROM parcels WHERE status = ? ORDER BY created_at DESC'; params.push('DELIVERED'); } const [rows] = await fastify.db.execute(sql, params); return rows; }); // 운송장 등록 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: '이미 등록된 운송장입니다' }); } // 등록 const [result] = await fastify.db.execute( 'INSERT INTO parcels (carrier_id, carrier_name, tracking_number, label) VALUES (?, ?, ?, ?)', [carrierId, carrier.name, trackingNumber, label || null] ); 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) WHERE id = ?`, [status, lastDetail, deliveredAt, 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)] ); } }