feat: 羚牛 BI 报表服务初始版本
All checks were successful
ci/woodpecker/manual/woodpecker Pipeline was successful
All checks were successful
ci/woodpecker/manual/woodpecker Pipeline was successful
- Hono + TypeScript 后端,连接 MySQL 数据库 - React + Vite + Tailwind 前端 - 车辆资产实时汇总(按车型/品牌型号分组) - 本周交车/还车/替换统计(关联业务单据) - 车牌号详情弹窗 - Dockerfile + Woodpecker CI 流水线 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
40
scripts/check-full-tags.ts
Normal file
40
scripts/check-full-tags.ts
Normal file
@@ -0,0 +1,40 @@
|
||||
import dotenv from 'dotenv';
|
||||
dotenv.config();
|
||||
import mysql from 'mysql2/promise';
|
||||
|
||||
async function main() {
|
||||
const pool = mysql.createPool({
|
||||
host: process.env.DB_HOST,
|
||||
port: Number(process.env.DB_PORT),
|
||||
user: process.env.DB_USER,
|
||||
password: process.env.DB_PASSWORD,
|
||||
database: process.env.DB_NAME,
|
||||
});
|
||||
|
||||
const [rows] = await pool.query(`
|
||||
SELECT
|
||||
CONCAT(
|
||||
IFNULL(dic_brand.dic_name,''),
|
||||
'-',
|
||||
IFNULL(dic_type.dic_name,''),
|
||||
'-',
|
||||
IFNULL(truck.color,''),
|
||||
IF(dic_asc.dic_name='外租', IFNULL(truck.rent_from_company,''), '')
|
||||
) AS tag,
|
||||
COUNT(*) AS cnt
|
||||
FROM tab_truck truck
|
||||
LEFT JOIN tab_dic dic_type ON dic_type.parent_code='dic_truck_type' AND dic_type.dic_code=truck.model AND dic_type.is_deleted=0
|
||||
LEFT JOIN tab_dic dic_brand ON dic_brand.parent_code='dic_vehicle_brand' AND dic_brand.dic_code=truck.brand AND dic_brand.is_deleted=0
|
||||
LEFT JOIN tab_dic dic_asc ON dic_asc.parent_code='dic_truck_ascription_status' AND dic_asc.dic_code=truck.ascription_status AND dic_asc.is_deleted=0
|
||||
WHERE truck.is_deleted=0 AND truck.is_operation=1
|
||||
GROUP BY tag
|
||||
ORDER BY cnt DESC
|
||||
`);
|
||||
|
||||
for (const r of rows as any[]) {
|
||||
console.log(`[${String(r.cnt).padStart(3)}] ${r.tag}`);
|
||||
}
|
||||
|
||||
await pool.end();
|
||||
}
|
||||
main();
|
||||
54
scripts/check-schema.ts
Normal file
54
scripts/check-schema.ts
Normal file
@@ -0,0 +1,54 @@
|
||||
import dotenv from 'dotenv';
|
||||
dotenv.config();
|
||||
import mysql from 'mysql2/promise';
|
||||
|
||||
async function main() {
|
||||
const pool = mysql.createPool({
|
||||
host: process.env.DB_HOST,
|
||||
port: Number(process.env.DB_PORT),
|
||||
user: process.env.DB_USER,
|
||||
password: process.env.DB_PASSWORD,
|
||||
database: process.env.DB_NAME,
|
||||
});
|
||||
|
||||
// Check tab_truck columns for time-related fields
|
||||
const [truckCols] = await pool.query(`
|
||||
SELECT COLUMN_NAME, DATA_TYPE, COLUMN_COMMENT
|
||||
FROM information_schema.COLUMNS
|
||||
WHERE TABLE_SCHEMA='lingniu_prod3' AND TABLE_NAME='tab_truck'
|
||||
AND (COLUMN_NAME LIKE '%time%' OR COLUMN_NAME LIKE '%date%' OR COLUMN_NAME LIKE '%status%' OR COLUMN_NAME LIKE '%create%' OR COLUMN_NAME LIKE '%update%' OR COLUMN_NAME LIKE '%delete%' OR COLUMN_NAME LIKE '%operation%')
|
||||
ORDER BY ORDINAL_POSITION
|
||||
`);
|
||||
console.log('=== tab_truck time/status columns ===');
|
||||
for (const c of truckCols as any[]) {
|
||||
console.log(` ${c.COLUMN_NAME} (${c.DATA_TYPE}) — ${c.COLUMN_COMMENT || ''}`);
|
||||
}
|
||||
|
||||
// Check for status change/history tables
|
||||
const [tables] = await pool.query(`
|
||||
SELECT TABLE_NAME, TABLE_COMMENT
|
||||
FROM information_schema.TABLES
|
||||
WHERE TABLE_SCHEMA='lingniu_prod3'
|
||||
AND (TABLE_NAME LIKE '%log%' OR TABLE_NAME LIKE '%history%' OR TABLE_NAME LIKE '%change%' OR TABLE_NAME LIKE '%record%' OR TABLE_NAME LIKE '%status%')
|
||||
ORDER BY TABLE_NAME
|
||||
`);
|
||||
console.log('\n=== Related history/log tables ===');
|
||||
for (const t of tables as any[]) {
|
||||
console.log(` ${t.TABLE_NAME} — ${t.TABLE_COMMENT || ''}`);
|
||||
}
|
||||
|
||||
// Check tab_truck_status_info structure
|
||||
const [statusCols] = await pool.query(`
|
||||
SELECT COLUMN_NAME, DATA_TYPE, COLUMN_COMMENT
|
||||
FROM information_schema.COLUMNS
|
||||
WHERE TABLE_SCHEMA='lingniu_prod3' AND TABLE_NAME='tab_truck_status_info'
|
||||
ORDER BY ORDINAL_POSITION
|
||||
`);
|
||||
console.log('\n=== tab_truck_status_info columns ===');
|
||||
for (const c of statusCols as any[]) {
|
||||
console.log(` ${c.COLUMN_NAME} (${c.DATA_TYPE}) — ${c.COLUMN_COMMENT || ''}`);
|
||||
}
|
||||
|
||||
await pool.end();
|
||||
}
|
||||
main();
|
||||
66
scripts/check-status.ts
Normal file
66
scripts/check-status.ts
Normal file
@@ -0,0 +1,66 @@
|
||||
import dotenv from 'dotenv';
|
||||
dotenv.config();
|
||||
import mysql from 'mysql2/promise';
|
||||
|
||||
async function main() {
|
||||
const pool = mysql.createPool({
|
||||
host: process.env.DB_HOST,
|
||||
port: Number(process.env.DB_PORT),
|
||||
user: process.env.DB_USER,
|
||||
password: process.env.DB_PASSWORD,
|
||||
database: process.env.DB_NAME,
|
||||
});
|
||||
|
||||
const [statusRows] = await pool.query(`
|
||||
SELECT dic_status.dic_name AS status_label, truck.truck_rent_status AS status_code, COUNT(*) AS cnt
|
||||
FROM tab_truck truck
|
||||
LEFT JOIN tab_dic dic_status
|
||||
ON dic_status.parent_code = 'dic_truck_rent_status'
|
||||
AND dic_status.dic_code = truck.truck_rent_status
|
||||
AND dic_status.is_deleted = 0
|
||||
WHERE truck.is_deleted = 0 AND truck.is_operation = 1
|
||||
GROUP BY dic_status.dic_name, truck.truck_rent_status
|
||||
`);
|
||||
console.log('=== Rental Status ===');
|
||||
console.log(JSON.stringify(statusRows, null, 2));
|
||||
|
||||
const [ownerRows] = await pool.query(`
|
||||
SELECT dic.dic_name AS label, truck.ascription_status AS code, COUNT(*) AS cnt
|
||||
FROM tab_truck truck
|
||||
LEFT JOIN tab_dic dic
|
||||
ON dic.parent_code = 'dic_truck_ascription_status'
|
||||
AND dic.dic_code = truck.ascription_status
|
||||
AND dic.is_deleted = 0
|
||||
WHERE truck.is_deleted = 0 AND truck.is_operation = 1
|
||||
GROUP BY dic.dic_name, truck.ascription_status
|
||||
`);
|
||||
console.log('=== Ownership Status ===');
|
||||
console.log(JSON.stringify(ownerRows, null, 2));
|
||||
|
||||
const [regionRows] = await pool.query(`
|
||||
SELECT info.province, info.city, COUNT(*) AS cnt
|
||||
FROM tab_truck truck
|
||||
LEFT JOIN tab_truck_remote_sync_realtime_info info ON info.id = truck.id
|
||||
WHERE truck.is_deleted = 0 AND truck.is_operation = 1
|
||||
GROUP BY info.province, info.city
|
||||
ORDER BY cnt DESC
|
||||
LIMIT 20
|
||||
`);
|
||||
console.log('=== Top Regions ===');
|
||||
console.log(JSON.stringify(regionRows, null, 2));
|
||||
|
||||
const [modelRows] = await pool.query(`
|
||||
SELECT dic_type.dic_name AS model_label, dic_brand.dic_name AS brand_label, COUNT(*) AS cnt
|
||||
FROM tab_truck truck
|
||||
LEFT JOIN tab_dic dic_type ON dic_type.parent_code = 'dic_truck_type' AND dic_type.dic_code = truck.model AND dic_type.is_deleted = 0
|
||||
LEFT JOIN tab_dic dic_brand ON dic_brand.parent_code = 'dic_vehicle_brand' AND dic_brand.dic_code = truck.brand AND dic_brand.is_deleted = 0
|
||||
WHERE truck.is_deleted = 0 AND truck.is_operation = 1
|
||||
GROUP BY dic_type.dic_name, dic_brand.dic_name
|
||||
ORDER BY cnt DESC
|
||||
`);
|
||||
console.log('=== Models ===');
|
||||
console.log(JSON.stringify(modelRows, null, 2));
|
||||
|
||||
await pool.end();
|
||||
}
|
||||
main();
|
||||
42
scripts/check-tags.ts
Normal file
42
scripts/check-tags.ts
Normal file
@@ -0,0 +1,42 @@
|
||||
import dotenv from 'dotenv';
|
||||
dotenv.config();
|
||||
import mysql from 'mysql2/promise';
|
||||
|
||||
async function main() {
|
||||
const pool = mysql.createPool({
|
||||
host: process.env.DB_HOST,
|
||||
port: Number(process.env.DB_PORT),
|
||||
user: process.env.DB_USER,
|
||||
password: process.env.DB_PASSWORD,
|
||||
database: process.env.DB_NAME,
|
||||
});
|
||||
|
||||
const [rows] = await pool.query(`
|
||||
SELECT
|
||||
CONCAT(
|
||||
IFNULL(dic_brand.dic_name,''),
|
||||
'-',
|
||||
IFNULL(dic_type.dic_name,''),
|
||||
'-',
|
||||
IFNULL(truck.color,''),
|
||||
IF(dic_asc.dic_name='外租', IFNULL(truck.rent_from_company,''), '')
|
||||
) AS tag,
|
||||
dic_brand.dic_name AS brand,
|
||||
dic_type.dic_name AS model_label,
|
||||
truck.color AS color,
|
||||
dic_asc.dic_name AS ownership,
|
||||
truck.rent_from_company AS rent_company,
|
||||
COUNT(*) AS cnt
|
||||
FROM tab_truck truck
|
||||
LEFT JOIN tab_dic dic_type ON dic_type.parent_code='dic_truck_type' AND dic_type.dic_code=truck.model AND dic_type.is_deleted=0
|
||||
LEFT JOIN tab_dic dic_brand ON dic_brand.parent_code='dic_vehicle_brand' AND dic_brand.dic_code=truck.brand AND dic_brand.is_deleted=0
|
||||
LEFT JOIN tab_dic dic_asc ON dic_asc.parent_code='dic_truck_ascription_status' AND dic_asc.dic_code=truck.ascription_status AND dic_asc.is_deleted=0
|
||||
WHERE truck.is_deleted=0 AND truck.is_operation=1
|
||||
GROUP BY tag, dic_brand.dic_name, dic_type.dic_name, truck.color, dic_asc.dic_name, truck.rent_from_company
|
||||
ORDER BY dic_type.dic_name, cnt DESC
|
||||
`);
|
||||
console.log(JSON.stringify(rows, null, 2));
|
||||
|
||||
await pool.end();
|
||||
}
|
||||
main();
|
||||
87
scripts/check-weekly.ts
Normal file
87
scripts/check-weekly.ts
Normal file
@@ -0,0 +1,87 @@
|
||||
import dotenv from 'dotenv';
|
||||
dotenv.config();
|
||||
import mysql from 'mysql2/promise';
|
||||
|
||||
async function main() {
|
||||
const pool = mysql.createPool({
|
||||
host: process.env.DB_HOST,
|
||||
port: Number(process.env.DB_PORT),
|
||||
user: process.env.DB_USER,
|
||||
password: process.env.DB_PASSWORD,
|
||||
database: process.env.DB_NAME,
|
||||
});
|
||||
|
||||
// Check rent_status_check table structure
|
||||
const [checkCols] = await pool.query(`
|
||||
SELECT COLUMN_NAME, DATA_TYPE, COLUMN_COMMENT
|
||||
FROM information_schema.COLUMNS
|
||||
WHERE TABLE_SCHEMA='lingniu_prod3' AND TABLE_NAME='tab_truck_rent_status_check'
|
||||
ORDER BY ORDINAL_POSITION
|
||||
`);
|
||||
console.log('=== tab_truck_rent_status_check columns ===');
|
||||
for (const c of checkCols as any[]) {
|
||||
console.log(` ${c.COLUMN_NAME} (${c.DATA_TYPE}) — ${c.COLUMN_COMMENT || ''}`);
|
||||
}
|
||||
|
||||
// Weekly stats: is_operation set to 1 this week (newly added to operation)
|
||||
const [newOp] = await pool.query(`
|
||||
SELECT COUNT(*) AS cnt FROM tab_truck
|
||||
WHERE is_deleted=0 AND is_operation=1
|
||||
AND create_time >= DATE_SUB(CURDATE(), INTERVAL WEEKDAY(CURDATE())+2 DAY)
|
||||
`);
|
||||
console.log('\n=== This week new (by create_time, since last Saturday) ===');
|
||||
console.log(JSON.stringify(newOp));
|
||||
|
||||
// Pending delivery count (status=7)
|
||||
const [pending] = await pool.query(`
|
||||
SELECT COUNT(*) AS cnt FROM tab_truck
|
||||
WHERE is_deleted=0 AND is_operation=1 AND truck_rent_status=7
|
||||
`);
|
||||
console.log('\n=== Pending delivery (status=7) ===');
|
||||
console.log(JSON.stringify(pending));
|
||||
|
||||
// Weekly deliveries (take_date this week)
|
||||
const [delivered] = await pool.query(`
|
||||
SELECT COUNT(*) AS cnt FROM tab_truck_status_info
|
||||
WHERE is_deleted=0
|
||||
AND take_date >= DATE_SUB(CURDATE(), INTERVAL WEEKDAY(CURDATE())+2 DAY)
|
||||
AND take_date < DATE_ADD(DATE_SUB(CURDATE(), INTERVAL WEEKDAY(CURDATE())+2 DAY), INTERVAL 7 DAY)
|
||||
`);
|
||||
console.log('\n=== This week delivered (by take_date) ===');
|
||||
console.log(JSON.stringify(delivered));
|
||||
|
||||
// Weekly returns (return_date this week)
|
||||
const [returned] = await pool.query(`
|
||||
SELECT COUNT(*) AS cnt FROM tab_truck_status_info
|
||||
WHERE is_deleted=0
|
||||
AND return_date >= DATE_SUB(CURDATE(), INTERVAL WEEKDAY(CURDATE())+2 DAY)
|
||||
AND return_date < DATE_ADD(DATE_SUB(CURDATE(), INTERVAL WEEKDAY(CURDATE())+2 DAY), INTERVAL 7 DAY)
|
||||
`);
|
||||
console.log('\n=== This week returned (by return_date) ===');
|
||||
console.log(JSON.stringify(returned));
|
||||
|
||||
// return_change_record values
|
||||
const [rcValues] = await pool.query(`
|
||||
SELECT return_change_record, COUNT(*) AS cnt
|
||||
FROM tab_truck_status_info
|
||||
WHERE is_deleted=0
|
||||
GROUP BY return_change_record
|
||||
`);
|
||||
console.log('\n=== return_change_record values ===');
|
||||
console.log(JSON.stringify(rcValues));
|
||||
|
||||
// Check aa_temp table
|
||||
const [tempCols] = await pool.query(`
|
||||
SELECT COLUMN_NAME, DATA_TYPE, COLUMN_COMMENT
|
||||
FROM information_schema.COLUMNS
|
||||
WHERE TABLE_SCHEMA='lingniu_prod3' AND TABLE_NAME='tab_aa_temp_truck_rent_status'
|
||||
ORDER BY ORDINAL_POSITION
|
||||
`);
|
||||
console.log('\n=== tab_aa_temp_truck_rent_status columns ===');
|
||||
for (const c of tempCols as any[]) {
|
||||
console.log(` ${c.COLUMN_NAME} (${c.DATA_TYPE}) — ${c.COLUMN_COMMENT || ''}`);
|
||||
}
|
||||
|
||||
await pool.end();
|
||||
}
|
||||
main();
|
||||
66
scripts/check-weekly2.ts
Normal file
66
scripts/check-weekly2.ts
Normal file
@@ -0,0 +1,66 @@
|
||||
import dotenv from 'dotenv';
|
||||
dotenv.config();
|
||||
import mysql from 'mysql2/promise';
|
||||
|
||||
async function main() {
|
||||
const pool = mysql.createPool({
|
||||
host: process.env.DB_HOST,
|
||||
port: Number(process.env.DB_PORT),
|
||||
user: process.env.DB_USER,
|
||||
password: process.env.DB_PASSWORD,
|
||||
database: process.env.DB_NAME,
|
||||
});
|
||||
|
||||
// return_change_record dic
|
||||
const [rcDic] = await pool.query(`
|
||||
SELECT dic_code, dic_name FROM tab_dic
|
||||
WHERE parent_code LIKE '%change%' OR parent_code LIKE '%return%'
|
||||
AND is_deleted=0
|
||||
ORDER BY parent_code, dic_code
|
||||
`);
|
||||
console.log('=== return/change dic ===');
|
||||
console.log(JSON.stringify(rcDic, null, 2));
|
||||
|
||||
// Weekly removed: is_operation changed to 0 this week, or is_deleted set to 1
|
||||
const [removed] = await pool.query(`
|
||||
SELECT COUNT(*) AS cnt FROM tab_truck
|
||||
WHERE (is_deleted=1 OR is_operation=0)
|
||||
AND update_time >= DATE_SUB(CURDATE(), INTERVAL WEEKDAY(CURDATE())+2 DAY)
|
||||
`);
|
||||
console.log('\n=== This week removed (is_operation=0 or deleted, by update_time) ===');
|
||||
console.log(JSON.stringify(removed));
|
||||
|
||||
// Weekly new: is_operation set to 1 this week
|
||||
const [newByUpdate] = await pool.query(`
|
||||
SELECT COUNT(*) AS cnt FROM tab_truck
|
||||
WHERE is_deleted=0 AND is_operation=1
|
||||
AND buy_time >= DATE_SUB(CURDATE(), INTERVAL WEEKDAY(CURDATE())+2 DAY)
|
||||
`);
|
||||
console.log('\n=== This week new (by buy_time) ===');
|
||||
console.log(JSON.stringify(newByUpdate));
|
||||
|
||||
// Replacements this week: return_change_record=3 means replacement?
|
||||
const [replaced] = await pool.query(`
|
||||
SELECT return_change_record, COUNT(*) AS cnt FROM tab_truck_status_info
|
||||
WHERE is_deleted=0
|
||||
AND return_change_record IN (2, 3)
|
||||
AND update_time >= DATE_SUB(CURDATE(), INTERVAL WEEKDAY(CURDATE())+2 DAY)
|
||||
GROUP BY return_change_record
|
||||
`);
|
||||
console.log('\n=== This week replace records (rc=2,3 by update_time) ===');
|
||||
console.log(JSON.stringify(replaced));
|
||||
|
||||
// Sample recent take_date
|
||||
const [recentTake] = await pool.query(`
|
||||
SELECT si.truck_id, t.plate_number, si.take_date, si.return_date, si.return_change_record
|
||||
FROM tab_truck_status_info si
|
||||
JOIN tab_truck t ON t.id = si.truck_id
|
||||
WHERE si.is_deleted=0 AND si.take_date IS NOT NULL
|
||||
ORDER BY si.take_date DESC LIMIT 5
|
||||
`);
|
||||
console.log('\n=== Recent deliveries ===');
|
||||
console.log(JSON.stringify(recentTake, null, 2));
|
||||
|
||||
await pool.end();
|
||||
}
|
||||
main();
|
||||
Reference in New Issue
Block a user