버튼 과정이 조금 복잡하지만 위도경도 연속추적기능도 넣음

This commit is contained in:
leeheejin
2025-12-01 16:49:02 +09:00
parent 7263c9c3ff
commit fbeb3ec2c9
5 changed files with 823 additions and 578 deletions

View File

@@ -482,3 +482,125 @@ export const updateFieldValue = async (
});
}
};
/**
* 위치 이력 저장 (연속 위치 추적용)
* POST /api/dynamic-form/location-history
*/
export const saveLocationHistory = async (
req: AuthenticatedRequest,
res: Response
): Promise<Response | void> => {
try {
const { companyCode, userId } = req.user as any;
const {
latitude,
longitude,
accuracy,
altitude,
speed,
heading,
tripId,
tripStatus,
departure,
arrival,
departureName,
destinationName,
recordedAt,
vehicleId,
} = req.body;
console.log("📍 [saveLocationHistory] 요청:", {
userId,
companyCode,
latitude,
longitude,
tripId,
});
// 필수 필드 검증
if (latitude === undefined || longitude === undefined) {
return res.status(400).json({
success: false,
message: "필수 필드가 누락되었습니다. (latitude, longitude)",
});
}
const result = await dynamicFormService.saveLocationHistory({
userId,
companyCode,
latitude,
longitude,
accuracy,
altitude,
speed,
heading,
tripId,
tripStatus: tripStatus || "active",
departure,
arrival,
departureName,
destinationName,
recordedAt: recordedAt || new Date().toISOString(),
vehicleId,
});
console.log("✅ [saveLocationHistory] 성공:", result);
res.json({
success: true,
data: result,
message: "위치 이력이 저장되었습니다.",
});
} catch (error: any) {
console.error("❌ [saveLocationHistory] 실패:", error);
res.status(500).json({
success: false,
message: error.message || "위치 이력 저장에 실패했습니다.",
});
}
};
/**
* 위치 이력 조회 (경로 조회용)
* GET /api/dynamic-form/location-history/:tripId
*/
export const getLocationHistory = async (
req: AuthenticatedRequest,
res: Response
): Promise<Response | void> => {
try {
const { companyCode } = req.user as any;
const { tripId } = req.params;
const { userId, startDate, endDate, limit } = req.query;
console.log("📍 [getLocationHistory] 요청:", {
tripId,
userId,
startDate,
endDate,
limit,
});
const result = await dynamicFormService.getLocationHistory({
companyCode,
tripId,
userId: userId as string,
startDate: startDate as string,
endDate: endDate as string,
limit: limit ? parseInt(limit as string) : 1000,
});
res.json({
success: true,
data: result,
count: result.length,
});
} catch (error: any) {
console.error("❌ [getLocationHistory] 실패:", error);
res.status(500).json({
success: false,
message: error.message || "위치 이력 조회에 실패했습니다.",
});
}
};

View File

@@ -12,6 +12,8 @@ import {
validateFormData,
getTableColumns,
getTablePrimaryKeys,
saveLocationHistory,
getLocationHistory,
} from "../controllers/dynamicFormController";
const router = express.Router();
@@ -40,4 +42,8 @@ router.get("/table/:tableName/columns", getTableColumns);
// 테이블 기본키 조회
router.get("/table/:tableName/primary-keys", getTablePrimaryKeys);
// 위치 이력 (연속 위치 추적)
router.post("/location-history", saveLocationHistory);
router.get("/location-history/:tripId", getLocationHistory);
export default router;

View File

@@ -1731,6 +1731,191 @@ export class DynamicFormService {
client.release();
}
}
/**
* 위치 이력 저장 (연속 위치 추적용)
*/
async saveLocationHistory(data: {
userId: string;
companyCode: string;
latitude: number;
longitude: number;
accuracy?: number;
altitude?: number;
speed?: number;
heading?: number;
tripId?: string;
tripStatus?: string;
departure?: string;
arrival?: string;
departureName?: string;
destinationName?: string;
recordedAt?: string;
vehicleId?: number;
}): Promise<{ id: number }> {
const pool = getPool();
const client = await pool.connect();
try {
console.log("📍 [saveLocationHistory] 저장 시작:", data);
const sqlQuery = `
INSERT INTO vehicle_location_history (
user_id,
company_code,
latitude,
longitude,
accuracy,
altitude,
speed,
heading,
trip_id,
trip_status,
departure,
arrival,
departure_name,
destination_name,
recorded_at,
vehicle_id
) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16)
RETURNING id
`;
const params = [
data.userId,
data.companyCode,
data.latitude,
data.longitude,
data.accuracy || null,
data.altitude || null,
data.speed || null,
data.heading || null,
data.tripId || null,
data.tripStatus || "active",
data.departure || null,
data.arrival || null,
data.departureName || null,
data.destinationName || null,
data.recordedAt ? new Date(data.recordedAt) : new Date(),
data.vehicleId || null,
];
const result = await client.query(sqlQuery, params);
console.log("✅ [saveLocationHistory] 저장 완료:", {
id: result.rows[0]?.id,
});
return { id: result.rows[0]?.id };
} catch (error) {
console.error("❌ [saveLocationHistory] 오류:", error);
throw error;
} finally {
client.release();
}
}
/**
* 위치 이력 조회 (경로 조회용)
*/
async getLocationHistory(params: {
companyCode: string;
tripId?: string;
userId?: string;
startDate?: string;
endDate?: string;
limit?: number;
}): Promise<any[]> {
const pool = getPool();
const client = await pool.connect();
try {
console.log("📍 [getLocationHistory] 조회 시작:", params);
const conditions: string[] = [];
const queryParams: any[] = [];
let paramIndex = 1;
// 멀티테넌시: company_code 필터
if (params.companyCode && params.companyCode !== "*") {
conditions.push(`company_code = $${paramIndex}`);
queryParams.push(params.companyCode);
paramIndex++;
}
// trip_id 필터
if (params.tripId) {
conditions.push(`trip_id = $${paramIndex}`);
queryParams.push(params.tripId);
paramIndex++;
}
// user_id 필터
if (params.userId) {
conditions.push(`user_id = $${paramIndex}`);
queryParams.push(params.userId);
paramIndex++;
}
// 날짜 범위 필터
if (params.startDate) {
conditions.push(`recorded_at >= $${paramIndex}`);
queryParams.push(new Date(params.startDate));
paramIndex++;
}
if (params.endDate) {
conditions.push(`recorded_at <= $${paramIndex}`);
queryParams.push(new Date(params.endDate));
paramIndex++;
}
const whereClause = conditions.length > 0 ? `WHERE ${conditions.join(" AND ")}` : "";
const limitClause = params.limit ? `LIMIT ${params.limit}` : "LIMIT 1000";
const sqlQuery = `
SELECT
id,
user_id,
vehicle_id,
latitude,
longitude,
accuracy,
altitude,
speed,
heading,
trip_id,
trip_status,
departure,
arrival,
departure_name,
destination_name,
recorded_at,
created_at,
company_code
FROM vehicle_location_history
${whereClause}
ORDER BY recorded_at ASC
${limitClause}
`;
console.log("🔍 [getLocationHistory] 쿼리:", sqlQuery);
console.log("🔍 [getLocationHistory] 파라미터:", queryParams);
const result = await client.query(sqlQuery, queryParams);
console.log("✅ [getLocationHistory] 조회 완료:", {
count: result.rowCount,
});
return result.rows;
} catch (error) {
console.error("❌ [getLocationHistory] 오류:", error);
throw error;
} finally {
client.release();
}
}
}
// 싱글톤 인스턴스 생성 및 export