feat(mileage): 支持车型按考核年度查看
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
This commit is contained in:
@@ -32,6 +32,58 @@ app.get('/', async (c) => {
|
||||
const statsMap = new Map<number, any>();
|
||||
for (const s of vehicleStats) statsMap.set(s.target_id, s);
|
||||
|
||||
const [firstYearRows] = await pool.execute(`
|
||||
SELECT
|
||||
v.target_id,
|
||||
COUNT(*) as first_year_total,
|
||||
SUM(t.annual_mileage_per_vehicle) as first_year_target,
|
||||
SUM(LEAST(v.current_mileage, t.annual_mileage_per_vehicle)) as first_year_completed,
|
||||
SUM(GREATEST(t.annual_mileage_per_vehicle - v.current_mileage, 0)) as first_year_remaining,
|
||||
SUM(LEAST(v.current_mileage, t.annual_mileage_per_vehicle)) / NULLIF(SUM(t.annual_mileage_per_vehicle), 0) as first_year_completion_rate,
|
||||
SUM(CASE WHEN v.current_mileage >= t.annual_mileage_per_vehicle THEN 1 ELSE 0 END) as first_year_qualified_count,
|
||||
SUM(CASE WHEN v.current_mileage >= t.annual_mileage_per_vehicle * 0.5 THEN 1 ELSE 0 END) as first_year_half_qualified_count,
|
||||
DATE_FORMAT(MIN(v.assessment_start_date), '%Y-%m-%d') as first_year_start_date,
|
||||
DATE_FORMAT(MAX(DATE_SUB(DATE_ADD(v.assessment_start_date, INTERVAL 1 YEAR), INTERVAL 1 DAY)), '%Y-%m-%d') as first_year_end_date
|
||||
FROM tab_mileage_assessment_vehicle v
|
||||
JOIN tab_mileage_assessment_target t ON t.id = v.target_id AND t.is_deleted = 0
|
||||
WHERE v.is_deleted = 0
|
||||
GROUP BY v.target_id
|
||||
`) as [any[], unknown];
|
||||
|
||||
const firstYearMap = new Map<number, any>();
|
||||
for (const s of firstYearRows) firstYearMap.set(s.target_id, s);
|
||||
|
||||
const [yearlyRows] = await pool.execute(`
|
||||
SELECT
|
||||
v.target_id,
|
||||
y.year_number,
|
||||
COUNT(*) as vehicle_count,
|
||||
SUM(t.annual_mileage_per_vehicle * y.year_number) as target_mileage,
|
||||
SUM(LEAST(v.current_mileage, t.annual_mileage_per_vehicle * y.year_number)) as completed_mileage,
|
||||
SUM(GREATEST(t.annual_mileage_per_vehicle * y.year_number - v.current_mileage, 0)) as remaining_mileage,
|
||||
SUM(LEAST(v.current_mileage, t.annual_mileage_per_vehicle * y.year_number))
|
||||
/ NULLIF(SUM(t.annual_mileage_per_vehicle * y.year_number), 0) as completion_rate,
|
||||
SUM(CASE WHEN v.current_mileage >= t.annual_mileage_per_vehicle * y.year_number THEN 1 ELSE 0 END) as qualified_count,
|
||||
SUM(CASE WHEN v.current_mileage >= t.annual_mileage_per_vehicle * y.year_number * 0.5 THEN 1 ELSE 0 END) as half_qualified_count,
|
||||
DATE_FORMAT(MIN(DATE_ADD(v.assessment_start_date, INTERVAL y.year_number - 1 YEAR)), '%Y-%m-%d') as start_date,
|
||||
DATE_FORMAT(MAX(DATE_SUB(DATE_ADD(v.assessment_start_date, INTERVAL y.year_number YEAR), INTERVAL 1 DAY)), '%Y-%m-%d') as end_date
|
||||
FROM tab_mileage_assessment_vehicle v
|
||||
JOIN tab_mileage_assessment_target t ON t.id = v.target_id AND t.is_deleted = 0
|
||||
JOIN (
|
||||
SELECT 1 as year_number UNION ALL SELECT 2 UNION ALL SELECT 3 UNION ALL SELECT 4 UNION ALL SELECT 5
|
||||
) y ON y.year_number <= LEAST(t.assessment_years, v.current_year_number)
|
||||
WHERE v.is_deleted = 0
|
||||
GROUP BY v.target_id, y.year_number
|
||||
ORDER BY v.target_id, y.year_number
|
||||
`) as [any[], unknown];
|
||||
|
||||
const yearlyMap = new Map<number, any[]>();
|
||||
for (const row of yearlyRows) {
|
||||
const list = yearlyMap.get(row.target_id) || [];
|
||||
list.push(row);
|
||||
yearlyMap.set(row.target_id, list);
|
||||
}
|
||||
|
||||
const [periodRows] = await pool.execute(`
|
||||
SELECT target_id,
|
||||
DATE_FORMAT(assessment_start_date, '%Y-%m-%d') as start_date,
|
||||
@@ -71,12 +123,43 @@ app.get('/', async (c) => {
|
||||
const now = new Date();
|
||||
const result = targets.map((t: any) => {
|
||||
const s = statsMap.get(t.id) || {};
|
||||
const fy = firstYearMap.get(t.id) || {};
|
||||
const currentYearTarget = Number(s.current_year_target) || 0;
|
||||
const currentYearCompleted = Number(s.current_year_completed) || 0;
|
||||
const remaining = Math.max(0, currentYearTarget - currentYearCompleted);
|
||||
const yearEnd = s.year_end_date ? new Date(s.year_end_date) : now;
|
||||
const daysLeft = Math.max(1, Math.ceil((yearEnd.getTime() - now.getTime()) / 86400000));
|
||||
const dailyTarget = remaining / daysLeft;
|
||||
const firstYearEnd = fy.first_year_end_date ? new Date(fy.first_year_end_date) : now;
|
||||
const firstYearDaysLeft = Math.max(0, Math.ceil((firstYearEnd.getTime() - now.getTime()) / 86400000));
|
||||
const firstYearRemaining = Number(fy.first_year_remaining) || 0;
|
||||
const firstYearVehicleCount = Number(fy.first_year_total) || 0;
|
||||
const firstYearQualifiedCount = Number(fy.first_year_qualified_count) || 0;
|
||||
const yearlyAssessments = (yearlyMap.get(t.id) || []).map((row: any) => {
|
||||
const vehicleCount = Number(row.vehicle_count) || 0;
|
||||
const qualifiedCount = Number(row.qualified_count) || 0;
|
||||
const remainingMileage = Number(row.remaining_mileage) || 0;
|
||||
const endDate = row.end_date ? new Date(row.end_date) : now;
|
||||
const assessmentDaysLeft = Math.max(0, Math.ceil((endDate.getTime() - now.getTime()) / 86400000));
|
||||
const yearNumber = Number(row.year_number) || 0;
|
||||
|
||||
return {
|
||||
yearNumber,
|
||||
label: `第${yearNumber}年`,
|
||||
vehicleCount,
|
||||
target: Number(row.target_mileage) || 0,
|
||||
completed: Number(row.completed_mileage) || 0,
|
||||
remaining: remainingMileage,
|
||||
completionRate: (Number(row.completion_rate) || 0) * 100,
|
||||
qualifiedCount,
|
||||
qualifiedRate: vehicleCount > 0 ? (qualifiedCount / vehicleCount) * 100 : 0,
|
||||
halfQualifiedCount: Number(row.half_qualified_count) || 0,
|
||||
daysLeft: assessmentDaysLeft,
|
||||
dailyTarget: assessmentDaysLeft > 0 ? Math.round((remainingMileage / assessmentDaysLeft) * 10) / 10 : 0,
|
||||
startDate: row.start_date || null,
|
||||
endDate: row.end_date || null,
|
||||
};
|
||||
});
|
||||
|
||||
const periods = periodsMap.get(t.id) || [];
|
||||
if (periods.length === 0) {
|
||||
@@ -104,6 +187,19 @@ app.get('/', async (c) => {
|
||||
remaining,
|
||||
daysLeft,
|
||||
dailyTarget: Math.round(dailyTarget * 10) / 10,
|
||||
firstYearVehicleCount,
|
||||
firstYearTarget: Number(fy.first_year_target) || 0,
|
||||
firstYearCompleted: Number(fy.first_year_completed) || 0,
|
||||
firstYearRemaining,
|
||||
firstYearCompletionRate: (Number(fy.first_year_completion_rate) || 0) * 100,
|
||||
firstYearQualifiedCount,
|
||||
firstYearQualifiedRate: firstYearVehicleCount > 0 ? (firstYearQualifiedCount / firstYearVehicleCount) * 100 : 0,
|
||||
firstYearHalfQualifiedCount: Number(fy.first_year_half_qualified_count) || 0,
|
||||
firstYearDaysLeft,
|
||||
firstYearDailyTarget: firstYearDaysLeft > 0 ? Math.round((firstYearRemaining / firstYearDaysLeft) * 10) / 10 : 0,
|
||||
firstYearStartDate: fy.first_year_start_date || null,
|
||||
firstYearEndDate: fy.first_year_end_date || null,
|
||||
yearlyAssessments,
|
||||
};
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user