feat(holiday): 新增往年国省道调整并按前一日环比基期重算汇总
| | |
| | | |
| | | ## ææ°ï¼2026-10-06ï¼ |
| | | |
| | | ### 10-06ï¼æ®éå½çéãç¯æ¯åºæãæ¹ä¸ºäººå·¥å¤ç¨å¼ï¼ä¸ååä¸è®¡ç®ï¼ |
| | | ### 10-06ï¼æ¹æ¡ D å·²å®ç°ï¼å¾å¹´å½çéè°æ´è¡¨ + ç¯æ¯åºæåªä½ç¨äºå䏿¥åæ¯ï¼ |
| | | |
| | | - **æç»å£å¾**ï¼`HolidayStatService.loadVehicleByDate` å
ç¨ `holiday_vehicle_adjustment` è¦ç对åºåå²å¹´ä»½çâ彿¥å¼âï¼ä¾èµ°å¿å¾ã忝ã累计åå¿«æ¥ä½¿ç¨ï¼éåæé âå䏿¥ç¯æ¯åæ¯âï¼è¥å䏿¥æ®éå½çé C åæå¼ï¼åç¨ C æ¿æ¢è¯¥æ¥å䏿¥å½çéå¼ï¼å¹¶æ®æ¤éç®éè¥ä¸æ§äººåæµå¨éãå
¬è·¯äººåæµå¨éåæ»è®¡ãææç¯æ¯è¡åªç»§æ¿è¿ä»½åæ¯ã |
| | | - **å
³é®è¾¹ç**ï¼C åªå½±åâä¸ä¸æ¥çç¯æ¯åæ¯âï¼ä¸å½±å C æå¨æ¥ç彿¥å¼ã累计æèµ°å¿å¾ï¼è°æ´è¡¨åªç»´æ¤åå²å¹´ä»½ï¼`year < å½å年份`ï¼ï¼2026 å¹´è°æ´ä»ç±å¯¼å
¥ Excel ç D åè¿å
¥å½æ¥å¼ã |
| | | - **æ¬è½®å®ç°**ï¼æ°å¢ `holiday_vehicle_adjustment` 表å mapper/service/controllerï¼å端å¨ä¸é¡¹ç»è®¡-è忥页çâèåæ¥æ°æ®âåâè§æµç«åæ¥è°æ¥æ°æ®âä¹é´æ°å¢âå¾å¹´å½çéæ°æ®è°æ´âå¡çï¼åç»æç¨æ·æè®®ä¿æè¯¥ä½ç½®ï¼ï¼æ¯æå¹´ä»½çéãæ°å¢ãç¼è¾ãå é¤ååå ççï¼é¨ç½²èæ¬ä¸ migration æ¸
å已忥ã |
| | | - **10-04 导å
¥é¢æ£ï¼åªè¯»ï¼ä¸ååºï¼**ï¼ç¨ `C:\Users\jcxiong\Downloads\2026å¹´å䏿°æ® (2).xlsx` 以 `allowHistoricalOverwrite=false` è°å¯¼å
¥æ¥å£ï¼è¿åç¡®è®¤é¡¹ï¼ |
| | | - `第4æ¥ 2026-10-04 æ®éå½çéï¼åå¼ 323.6551ï¼å¯¼å
¥å¼ 322.7695` |
| | | - `第3æ¥ 2026-10-03 æ®éå½çéç¯æ¯åºæï¼åå¼ ç©ºï¼å¯¼å
¥å¼ 320.2418` |
| | | å æ¤ C5 ç±ç©ºåæå¼ä¸ä¼ç¡¬æç»å¯¼å
¥ï¼åªä¼è¦æ±åå²åæ´ç¡®è®¤ï¼ç¨æ·ç¡®è®¤åå³å¯éè°æ´åç Excel é导åå
¥ã |
| | | - **å½ååºç¶æ**ï¼æ¬å° `traffic_audit.holiday_vehicle_adjustment` å·²æ§è¡ DDLï¼0 è¡ï¼ï¼2026-10-04 ä¸å¡å¼ä»ä¸ºæ§å¼ `323.6551`ï¼çå¾
ç¨æ·èªè¡éå¯¼è°æ´åç Excelãå©ææªæ§è¡ä»»ä½ä¸å¡æ°æ®å¯¼å
¥ãè¦çæé导ã |
| | | - **éªè¯**ï¼å端 `mvn -o test` 9 个æµè¯å
¨ç»¿ï¼å« C5 åªæ¹ 10-04 ç¯æ¯åæ¯ãåå²è°æ´è¿å
¥ 2025 彿¥å¼ï¼ãå端 `npm run build` éè¿ï¼é¡µé¢å®æµâå¾å¹´å½çéæ°æ®è°æ´âå¡ç䏿°å¢å¼¹çªæ£å¸¸ã |
| | | - **å·²ç¥åæ´è¾¹çï¼é沿ç¨ç°æå£å¾æå¦ä½å³çï¼**ï¼å¿«æ¥ç¾åæ¯ç®åææ¢å®è§åå
å¤ç°æº Excel åå
æ ¼æ ¼å¼ï¼åå 1 ä½å°æ°ã10-04 æ»è®¡ç¯æ¯åå§å¼çº¦ `4.5481%`ï¼æºæ ¼æ¾ç¤º `4.55%`ï¼åå 1 ä½å¾å° `4.6%`ï¼è¥ç´æ¥ä»åå§å¼ä¸æ¬¡å 1 ä½åæ¯ `4.5%`ãå½å代ç ä¿çâå
Excel æ¾ç¤ºãåå¿«æ¥ 1 ä½âçæ¢æå£å¾ï¼è¥é¢å¯¼è¦æ±æ¹ä¸ºä¸æ¬¡åæ´ï¼éè¦åç¬æ¹å¿«æ¥ç¾åæ¯è·¯å¾ã |
| | | |
| | | > 说æï¼æ¬æä»¶æ©åâç¯æ¯åºæåªä½å¤ç¨ãä¸åä¸è®¡ç®âçæ®µè½å·²ä½åºãè¯¥çæ¬éå¯¹çæ¯æ§å®ç°è¯¯æ C æ¾å° C æå¨æ¥æ¬èº«è®¡ç®çé®é¢ï¼èªæ¹æ¡ D èµ·ï¼C çæ£ç¡®è¯ä¹æ¯**å䏿¥ç¯æ¯åæ¯çè°æ´å¼**ã |
| | | |
| | | ### 10-06ï¼å·²ä½åºï¼è§ä¸æ¹æ¹æ¡ Dï¼ï¼æ®éå½çéãç¯æ¯åºæãæ¹ä¸ºäººå·¥å¤ç¨å¼ï¼ä¸ååä¸è®¡ç®ï¼ |
| | | |
| | | - **èæ¯**ï¼10-04 éå·æ¨å®¶åºç«ä¸å°å®¢æµéæå´ä¸è·ï¼35419 -> 8856ï¼ï¼é¢å¯¼è¦æ±åé¤ãæå·¥ç¨¿å¿«æ¥çåæ³æ¯ï¼å
ç¨åç«åçæ°å¼ç®åº 10-04 ç¯æ¯ï¼æ 10-03 å½çé弿ªå°ãç¯æ¯åºæãC åå¤ç¨ï¼åæ 10-03 æ¢å¤åå¼ä»¥å
å½±å累计ãç¨æ·æç¡®å£å¾ï¼**ãç¯æ¯åºæãåªç¨äºæ°æ®å¤ç¨ï¼ä¸åä¸ä»»ä½è®¡ç®**ã |
| | | - **æ§è¡ä¸ºï¼éï¼**ï¼`HolidayStatService.nationalProvincialRingRatio` å¨ 2026 å¹´åªè¦ `comparison_base` æå¼å°±æã彿¥ / ç¯æ¯åºæ - 1ãç®å½çéç¯æ¯ï¼å¯¼åºæ¶ E åå `=(D{è¡}-C{è¡})/C{è¡}`ãææå·¥ç¨¿ç C åææ³ï¼åºææ¾å¨è¢«æ¯æ¥é£è¡ï¼ï¼è¿æ ·ç®è¿ä¼éä½ï¼10-03 ç¯æ¯è¢«ç®æ +1.11%ï¼ã |
| | |
| | | - **Excel æµ®ç¹ç²¾åº¦ï¼2026-10-02 追å ï¼**ï¼åè `docs/Excelæµ®ç¹æ°åå¨.txt`ï¼æ°å¢ `HolidayNumberUtils`ï¼`NumberToTextConverter.toText` + `BigDecimal`ï¼`MathContext(15, HALF_UP)`ï¼ï¼èåæ¥æ¨¡åç± `Double` å
¨é¢æ¹ `BigDecimal`ï¼ä¸å¼ 表æ°å¼åæ¹ `DECIMAL(30,15)`ï¼åºå
6 æ¡å®¢è¿ç³»æ°ç± 2.3999999999999995 æ ¡æ£ä¸º 2.4ãè¿ç§»èæ¬ `docs/sql_holiday_decimal_migration.sql`ã |
| | | - **èå顺åº**ï¼âä¸é¡¹ç»è®¡â已移å°âæ¥è¡¨çæâä¹åï¼å½å为 æ¥è¡¨çæ -> ä¸é¡¹ç»è®¡ -> è§å管ç -> ç¨æ·ç®¡ç -> ç³»ç»é
ç½®ã |
| | | - **è忥å é¤å¤æ´»ä¿®å¤ï¼2026-10-02 追å ï¼**ï¼ååå§åé»è¾æ¯æ¬¡å¯å¨è¡¥é½ç¼ºå¤±ç第 N æ¥ï¼å¯¼è´ admin å é¤ 2026-10-08 åè¢«éæ°æå
¥ï¼ç°æ¹ä¸ºä»
`holiday_calendar` æ´è¡¨ä¸ºç©ºæ¶é¦æ¬¡åå§åãéå¯å 2026 å¹´åä¸ä¿æ 10-01 è³ 10-07 å
± 7 天ã |
| | | - **æ®éå½çéç¯æ¯åºæï¼2026-10-02 追å ï¼**ï¼æ®éå½çé C å为å¯éç¯æ¯åºæï¼ä»
2026 å¹´å¯ç¨ï¼å¡«åååªå½±åæ®éå½çé车æµéèªèº«ç¯æ¯ `彿¥ / åºæ - 1`ï¼æªå¡«åæå䏿¥ã`holiday_vehicle_flow.comparison_base` å·²å
¥åºï¼å¯¼å
¥å¯¼åºä¸åå²åæ´ç¡®è®¤åå·²æ¥å
¥ï¼æç®äººåæµå¨éãæ»äººåæµå¨éå Word å¿«æ¥ç»§ç»æ Excel åå
¬å¼ï¼ä¸ååºæå½±åã2024/2025 ä¸ä½¿ç¨ã |
| | | - **æ®éå½çéç¯æ¯åºæï¼2026-10-02 追å ï¼2026-10-06 æ¹æ¡ D 已修æ£ï¼**ï¼C åè¯ä¹æç»ç¡®å®ä¸ºâä¸ä¸æ¥ç¯æ¯åæ¯çè°æ´å¼âï¼å¡«åååªå½±åä¸ä¸æ¥çç¯æ¯åæ¯ï¼ä¸å½±å C æå¨æ¥å累计ãèµ°å¿å¾ã`holiday_vehicle_flow.comparison_base` å·²å
¥åºï¼å¯¼å
¥å¯¼åºä¸åå²åæ´ç¡®è®¤åå·²æ¥å
¥ã |
| | | - **å¿«æ¥é¦æ¥å£å¾ï¼2026-10-02 追å ï¼**ï¼åæç¬¬1天没æå䏿¥ç¯æ¯åºæ°ï¼æ£æä¸è¾åºâç¯æ¯å¢é¿/ä¸é/æå¹³âçè¯ï¼è¡¨æ ¼å½æ¥ç¯æ¯å为âââï¼ç¬¬2å¤©èµ·ä»æå®é
å䏿¥è¾åºç¯æ¯ãå·²ç¨ 2025-10-01/02 å¿«æ¥åå½éªè¯ã |
| | | - **å¿«æ¥ç¾åæ¯ç²¾åº¦ï¼2026-10-02 追å ï¼**ï¼Word å¿«æ¥ä¸åæ¯/ç¯æ¯ç¾åæ¯ç»ä¸ä¿ç1ä½å°æ°ï¼å¦6.79%æ¾ç¤º6.8%ã11.46%æ¾ç¤º11.5%ï¼ï¼äººæ¬¡ã车æµçä¸å¡æ°å¼ä»ä¿ç2ä½å°æ°ã |
| | | - **éªè¯ç»æ**ï¼å端ç¼è¯ãå端æå»ºãæé/CRUD/è´å导å
¥ãåå²åæ´ä¸¤é¶æ®µå¯¼å
¥ãExcel 导åºãWord å¿«æ¥ãå端 admin/é admin çæµåéè¿ï¼èå顺åºã客è¿ç³»æ° 2.4 å±ç¤ºï¼ç³»ç»é
置页 + è忥ç»è®¡é¡µï¼ã`decimal(30,15)` ååãåå²åæ´ä¸¤é¶æ®µå¯¼å
¥åå½å已宿µï¼åè½æµè¯æ¥åä¸å·¥ä½æ¥ç»æ 2026-09-27 æäº¤è§èä»
æ¬å°çåï¼ä¸å
¥åºï¼ï¼`docs/åè½æµè¯æ¥å/åè½æµè¯æ¥å_éè¦è忥ä¸é¡¹ç»è®¡_2026-10-02.md`ã`docs/工使¥ç»/工使¥ç»_2026-10-02.md`ã |
| | |
| | | sql_holiday_decimal_migration.sql è忥æ°å¼å精度è¿ç§»ï¼æ§åºåçº§ï¼ |
| | | sql_holiday_comparison_base_migration.sql èåæ¥ç¯æ¯åºæå段è¿ç§»ï¼æ§åºåçº§ï¼ |
| | | sql_holiday_previous_day_migration.sql è忥å䏿¥é
ç½®è¿ç§»ï¼æ§åºåçº§ï¼ |
| | | sql_holiday_vehicle_adjustment_migration.sql å¾å¹´å½çéæ°æ®è°æ´è¡¨ï¼æ§åºåçº§ï¼ |
| | | sql_observation_station.sql è§æµç«åæ¥è°æ¥æ°æ®è¡¨ï¼æ°åè½ï¼æ§åºç¼ºè¡¨æ¶æ§è¡ï¼ |
| | | README-é¨ç½²è¯´æ.txt |
| | | |
| | |
| | | mysql --default-character-set=utf8mb4 -u root -p traffic_audit < db/sql_city_bus.sql |
| | | mysql --default-character-set=utf8mb4 -u root -p traffic_audit < db/sql_city_taxi.sql |
| | | mysql --default-character-set=utf8mb4 -u root -p traffic_audit < db/sql_wyc.sql |
| | | 注ï¼å
¨æ°åºç init.sql å·²å«è忥 4 å¼ è¡¨ï¼è¥æ¯å¨æ©æçæ¬åºä¸è¡¥èåæ¥æ¨¡åï¼åæ§è¡ db/sql_holiday.sqlï¼å¹¶æéæ§è¡ä¸ä¸ªè¿ç§»èæ¬ï¼æ°å¼ç²¾åº¦ãç¯æ¯åºæãå䏿¥é
ç½®ï¼ãå䏿¥è¿ç§»èæ¬å¯é夿§è¡ã |
| | | 注ï¼å
¨æ°åºç init.sql å·²å«è忥åç¸å
³è¡¨ï¼è¥æ¯å¨æ©æçæ¬åºä¸è¡¥èåæ¥æ¨¡åï¼åæ§è¡ db/sql_holiday.sqlï¼å¹¶æéæ§è¡è¿ç§»èæ¬ï¼æ°å¼ç²¾åº¦ãç¯æ¯åºæãå䏿¥é
ç½®ãå¾å¹´å½çéæ°æ®è°æ´ãè§æµç«åæ¥è°æ¥æ°æ®ï¼ãè¿ç§»èæ¬åå¯é夿§è¡ã |
| | | é¢ç½®è´¦å·ï¼admin / admin123ï¼é¦æ¬¡ç»å½å请æ¹å¯ç ï¼ |
| | | |
| | | 2. æ¹é
ç½® backend/application.yml |
| | |
| | | KEY idx_holiday_vehicle_date (flow_date) |
| | | ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='èåæ¥ç»¼åè¿è¾éè¥ä¸æ§è½¦æµæ
åµ'; |
| | | |
| | | -- å¾å¹´å½çé车æµéè°æ´ |
| | | DROP TABLE IF EXISTS holiday_vehicle_adjustment; |
| | | CREATE TABLE holiday_vehicle_adjustment ( |
| | | id BIGINT NOT NULL AUTO_INCREMENT, |
| | | year INT NOT NULL COMMENT '年份', |
| | | flow_date DATE NOT NULL COMMENT 'æ¥æ', |
| | | adjusted_value DECIMAL(30,15) NOT NULL COMMENT 'è°æ´åæ®éå½çé车æµéï¼ä¸è¾æ¬¡ï¼', |
| | | reason VARCHAR(500) NOT NULL COMMENT 'è°æ´åå ', |
| | | created_at DATETIME DEFAULT CURRENT_TIMESTAMP, |
| | | updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, |
| | | PRIMARY KEY (id), |
| | | UNIQUE KEY uk_holiday_vehicle_adjustment (year, flow_date), |
| | | KEY idx_holiday_vehicle_adjustment_date (flow_date) |
| | | ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='å¾å¹´å½çé车æµéè°æ´'; |
| | | |
| | | -- 客è¿ç³»æ°é
ç½® |
| | | DROP TABLE IF EXISTS holiday_passenger_coefficient; |
| | | CREATE TABLE holiday_passenger_coefficient ( |
| | |
| | | KEY idx_holiday_vehicle_date (flow_date) |
| | | ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; |
| | | |
| | | CREATE TABLE IF NOT EXISTS holiday_vehicle_adjustment ( |
| | | id BIGINT NOT NULL AUTO_INCREMENT, |
| | | year INT NOT NULL, |
| | | flow_date DATE NOT NULL, |
| | | adjusted_value DECIMAL(30,15) NOT NULL, |
| | | reason VARCHAR(500) NOT NULL, |
| | | created_at DATETIME DEFAULT CURRENT_TIMESTAMP, |
| | | updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, |
| | | PRIMARY KEY (id), |
| | | UNIQUE KEY uk_holiday_vehicle_adjustment (year, flow_date), |
| | | KEY idx_holiday_vehicle_adjustment_date (flow_date) |
| | | ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; |
| | | |
| | | CREATE TABLE IF NOT EXISTS holiday_passenger_coefficient ( |
| | | id BIGINT NOT NULL AUTO_INCREMENT, |
| | | year INT NOT NULL, |
| New file |
| | |
| | | -- Add the historical national/provincial road adjustment table. |
| | | -- Run against the traffic_audit database on upgrade. |
| | | CREATE TABLE IF NOT EXISTS holiday_vehicle_adjustment ( |
| | | id BIGINT NOT NULL AUTO_INCREMENT, |
| | | year INT NOT NULL COMMENT 'year', |
| | | flow_date DATE NOT NULL COMMENT 'holiday date', |
| | | adjusted_value DECIMAL(30,15) NOT NULL COMMENT 'adjusted national/provincial vehicle flow (10k vehicle trips)', |
| | | reason VARCHAR(500) NOT NULL COMMENT 'adjustment reason', |
| | | created_at DATETIME DEFAULT CURRENT_TIMESTAMP, |
| | | updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, |
| | | PRIMARY KEY (id), |
| | | UNIQUE KEY uk_holiday_vehicle_adjustment (year, flow_date), |
| | | KEY idx_holiday_vehicle_adjustment_date (flow_date) |
| | | ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='historical national/provincial vehicle flow adjustments'; |
| | |
| | | KEY idx_holiday_vehicle_date (flow_date) |
| | | ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='èåæ¥ç»¼åè¿è¾éè¥ä¸æ§è½¦æµæ
åµ'; |
| | | |
| | | -- å¾å¹´å½çé车æµéè°æ´ |
| | | DROP TABLE IF EXISTS holiday_vehicle_adjustment; |
| | | CREATE TABLE holiday_vehicle_adjustment ( |
| | | id BIGINT NOT NULL AUTO_INCREMENT, |
| | | year INT NOT NULL COMMENT '年份', |
| | | flow_date DATE NOT NULL COMMENT 'æ¥æ', |
| | | adjusted_value DECIMAL(30,15) NOT NULL COMMENT 'è°æ´åæ®éå½çé车æµéï¼ä¸è¾æ¬¡ï¼', |
| | | reason VARCHAR(500) NOT NULL COMMENT 'è°æ´åå ', |
| | | created_at DATETIME DEFAULT CURRENT_TIMESTAMP, |
| | | updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, |
| | | PRIMARY KEY (id), |
| | | UNIQUE KEY uk_holiday_vehicle_adjustment (year, flow_date), |
| | | KEY idx_holiday_vehicle_adjustment_date (flow_date) |
| | | ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='å¾å¹´å½çé车æµéè°æ´'; |
| | | |
| | | -- 客è¿ç³»æ°é
ç½® |
| | | DROP TABLE IF EXISTS holiday_passenger_coefficient; |
| | | CREATE TABLE holiday_passenger_coefficient ( |
| | |
| | | KEY idx_holiday_vehicle_date (flow_date) |
| | | ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; |
| | | |
| | | CREATE TABLE IF NOT EXISTS holiday_vehicle_adjustment ( |
| | | id BIGINT NOT NULL AUTO_INCREMENT, |
| | | year INT NOT NULL, |
| | | flow_date DATE NOT NULL, |
| | | adjusted_value DECIMAL(30,15) NOT NULL, |
| | | reason VARCHAR(500) NOT NULL, |
| | | created_at DATETIME DEFAULT CURRENT_TIMESTAMP, |
| | | updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, |
| | | PRIMARY KEY (id), |
| | | UNIQUE KEY uk_holiday_vehicle_adjustment (year, flow_date), |
| | | KEY idx_holiday_vehicle_adjustment_date (flow_date) |
| | | ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; |
| | | |
| | | CREATE TABLE IF NOT EXISTS holiday_passenger_coefficient ( |
| | | id BIGINT NOT NULL AUTO_INCREMENT, |
| | | year INT NOT NULL, |
| New file |
| | |
| | | -- Add the historical national/provincial road adjustment table. |
| | | -- Run against the traffic_audit database on upgrade. |
| | | CREATE TABLE IF NOT EXISTS holiday_vehicle_adjustment ( |
| | | id BIGINT NOT NULL AUTO_INCREMENT, |
| | | year INT NOT NULL COMMENT 'year', |
| | | flow_date DATE NOT NULL COMMENT 'holiday date', |
| | | adjusted_value DECIMAL(30,15) NOT NULL COMMENT 'adjusted national/provincial vehicle flow (10k vehicle trips)', |
| | | reason VARCHAR(500) NOT NULL COMMENT 'adjustment reason', |
| | | created_at DATETIME DEFAULT CURRENT_TIMESTAMP, |
| | | updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, |
| | | PRIMARY KEY (id), |
| | | UNIQUE KEY uk_holiday_vehicle_adjustment (year, flow_date), |
| | | KEY idx_holiday_vehicle_adjustment_date (flow_date) |
| | | ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='historical national/provincial vehicle flow adjustments'; |
| | |
| | | â ââ sql_holiday.sql ä¸é¡¹ç»è®¡-éè¦èåæ¥è¡¨ï¼æ§åºè¡¥å»ºæ¶æ§è¡ï¼ |
| | | â ââ sql_holiday_decimal_migration.sql è忥æ°å¼å精度è¿ç§»ï¼æ§åºåçº§ï¼ |
| | | â ââ sql_holiday_comparison_base_migration.sql èåæ¥ç¯æ¯åºæå段è¿ç§»ï¼æ§åºåçº§ï¼ |
| | | â ââ sql_holiday_previous_day_migration.sql è忥å䏿¥é
ç½®è¿ç§»ï¼æ§åºåçº§ï¼ |
| | | â ââ sql_holiday_previous_day_migration.sql è忥å䏿¥é
ç½®è¿ç§»ï¼æ§åºåçº§ï¼ |
| | | â ââ sql_holiday_vehicle_adjustment_migration.sql å¾å¹´å½çéæ°æ®è°æ´è¡¨ï¼æ§åºåçº§ï¼ |
| | | â ââ sql_observation_station.sql è§æµç«åæ¥è°æ¥æ°æ®è¡¨ï¼æ§åºè¡¥å»ºæ¶æ§è¡ï¼ |
| | | ââ README-é¨ç½²è¯´æ.txt |
| | | ``` |
| | | |
| | |
| | | ``` |
| | | |
| | | > â ï¸ **`init.sql` æ¯ç ´åæ§ç**ï¼éé¢çåºç¡è¡¨é½å¸¦ `DROP TABLE IF EXISTS`ã**å·²ææ°æ®çåºä¸åä¸ä¸è¦åè· init.sql**ï¼å¦åè¡¨æ°æ®ä¼è¢«æ¸
空é建ã |
| | | > æ©æçæ¬åºè¥æ²¡æè忥 4 å¼ è¡¨ï¼æ§è¡ `sql_holiday.sql`ï¼è¥å·²æèåæ¥è¡¨ä½ç¼ºç²¾åº¦ãç¯æ¯åºæå段æå䏿¥é
ç½®ï¼æéæ§è¡ `sql_holiday_decimal_migration.sql`ã`sql_holiday_comparison_base_migration.sql`ã`sql_holiday_previous_day_migration.sql`ã |
| | | > æ©æçæ¬åºè¥æ²¡æè忥ç¸å
³è¡¨ï¼æ§è¡ `sql_holiday.sql`ï¼è¥å·²æèåæ¥è¡¨ä½ç¼ºç²¾åº¦ãç¯æ¯åºæå段ãå䏿¥é
ç½®ãå¾å¹´å½çéè°æ´è¡¨æè§æµç«è¡¨ï¼æéæ§è¡å¯¹åºçè¿ç§»èæ¬ãè¿ç§»èæ¬åå¯é夿§è¡ã |
| | | > 4 ä¸ªèæ¬å
±è¦ç 33 å¼ è¡¨ï¼init.sql 28 å¼ ï¼å
¶ä¸å«è忥 4 å¼ + åå¸å®¢è¿ 3 å¼ + ç½çº¦è½¦ 2 å¼ ï¼ã |
| | | |
| | | #### 路线 Bï¼æç°ç½/å¼åæºçæ°æ®æ´å¥æ¬è¿å»ï¼æ¨èï¼ä¸ç¨è·èæ¬ï¼ |
| | |
| | | | å¯¼åºæ¥ãæªæ¾å°æ±æ»è¡¨æ¯çãYYYYå¹´Mæâ¦ãã | `docs/çææ±æ»å¤§è¡¨` ç¼ºè¯¥ææ¯ç | |
| | | | ç»å½æ¥å£ 500 | `jwt.secret` å¡«äºéæ³å符ï¼å¦ä¸æï¼ï¼æ¹æ 32 ä½åå
è¿å¶ä¸² | |
| | | | 页é¢è½å¼ä½æ¥å£ 404 | èµ° nginx æ¹æ¡æ¶æ²¡é
`/api` åä»£ï¼æ¹ç¨ jar å
åµè®¿é® 8090 | |
| | | | æ°æ®åºè¿æ¥å¤±è´¥ | æ£æ¥ `application.yml`ï¼ç¡®è®¤ 5 个 SQL èæ¬å·²æ§è¡ | |
| | | | æ°æ®åºè¿æ¥å¤±è´¥ | æ£æ¥ `application.yml`ï¼ç¡®è®¤åå§åèæ¬å对åºå级è¿ç§»èæ¬å·²æ§è¡ | |
| | | | å¯å¨æ¥ `Access denied ... (using password: NO)` | å端æ¿å°çåºå£ä»¤æ¯**空串**ï¼jar å
ç½® `${TRAFFIC_DB_PASSWORD:}` æªè¢«å¤ç½®é
ç½®è¦çãæ `backend/application.yml` æ¾å¨ jar å级并填 `spring.datasource.password`ï¼æè®¾ç¯å¢åé `TRAFFIC_DB_PASSWORD`ï¼æ¹å®éå¯ï¼æ ééæå
| |
| | | | 端å£å ç¨ | æ¹ `server.port`ï¼å端å¼åç«¯å£ 8080 | |
| | | | æå¡å·²æ³¨åä½ 8090 æä¸å¼ | ç `backend\logs\trafficAudit.err.log`ï¼æå¡æ¹å¼è¯»çæ¯ `backend\application.yml`ï¼`password` å¿
é¡»æ¯çå®å£ä»¤ | |
| | |
| | | cmd /c "robocopy `"$root\docs`" `"$docsDest`" /E /NFL /NDL /NJH /NJS /NP /XD _cache 工使¥ç» åè½æµè¯æ¥å é®é¢æ±æ» /XF çæ_*.xlsx _bak_* >nul" |
| | | if ($LASTEXITCODE -ge 8) { throw "docs å¤å¶å¤±è´¥ï¼robocopy è¿å $LASTEXITCODEï¼" } |
| | | # db èæ¬ä¹æ¾ä¸ä»½å° docsï¼ä¸æ§å
ä¸è´ï¼docs/init.sql çï¼ |
| | | Copy-Item (Join-Path $tplDir 'db\init.sql'), (Join-Path $tplDir 'db\sql_city_bus.sql'), (Join-Path $tplDir 'db\sql_city_taxi.sql'), (Join-Path $tplDir 'db\sql_wyc.sql'), (Join-Path $tplDir 'db\sql_holiday.sql'), (Join-Path $tplDir 'db\sql_holiday_decimal_migration.sql'), (Join-Path $tplDir 'db\sql_holiday_comparison_base_migration.sql'), (Join-Path $tplDir 'db\sql_holiday_previous_day_migration.sql'), (Join-Path $tplDir 'db\sql_observation_station.sql') $docsDest -Force -ErrorAction SilentlyContinue |
| | | Copy-Item (Join-Path $tplDir 'db\init.sql'), (Join-Path $tplDir 'db\sql_city_bus.sql'), (Join-Path $tplDir 'db\sql_city_taxi.sql'), (Join-Path $tplDir 'db\sql_wyc.sql'), (Join-Path $tplDir 'db\sql_holiday.sql'), (Join-Path $tplDir 'db\sql_holiday_decimal_migration.sql'), (Join-Path $tplDir 'db\sql_holiday_comparison_base_migration.sql'), (Join-Path $tplDir 'db\sql_holiday_previous_day_migration.sql'), (Join-Path $tplDir 'db\sql_holiday_vehicle_adjustment_migration.sql'), (Join-Path $tplDir 'db\sql_observation_station.sql') $docsDest -Force -ErrorAction SilentlyContinue |
| | | |
| | | # READMEï¼ç¨æ¨¡æ¿å¹¶æ¿æ¢æ¥æå ä½ |
| | | $readme = Get-Content (Join-Path $tplDir 'README-é¨ç½²è¯´æ.txt') -Raw -Encoding UTF8 |
| New file |
| | |
| | | package com.trafficaudit.holiday.controller; |
| | | |
| | | import com.trafficaudit.common.Result; |
| | | import com.trafficaudit.holiday.entity.HolidayVehicleAdjustment; |
| | | import com.trafficaudit.holiday.service.HolidayVehicleAdjustmentService; |
| | | import com.trafficaudit.security.utils.JwtUtils; |
| | | import org.springframework.web.bind.annotation.GetMapping; |
| | | import org.springframework.web.bind.annotation.PostMapping; |
| | | import org.springframework.web.bind.annotation.RequestBody; |
| | | import org.springframework.web.bind.annotation.RequestHeader; |
| | | import org.springframework.web.bind.annotation.RequestMapping; |
| | | import org.springframework.web.bind.annotation.RequestParam; |
| | | import org.springframework.web.bind.annotation.RestController; |
| | | |
| | | import javax.annotation.Resource; |
| | | import java.util.List; |
| | | import java.util.Map; |
| | | |
| | | @RestController |
| | | @RequestMapping("/api/holiday/adjustments") |
| | | public class HolidayVehicleAdjustmentController { |
| | | |
| | | @Resource |
| | | private HolidayVehicleAdjustmentService adjustmentService; |
| | | @Resource |
| | | private JwtUtils jwtUtils; |
| | | |
| | | @GetMapping |
| | | public Result<List<HolidayVehicleAdjustment>> list(@RequestParam(value = "year", required = false) Integer year) { |
| | | return Result.ok(adjustmentService.list(year)); |
| | | } |
| | | |
| | | @PostMapping("/save") |
| | | public Result<HolidayVehicleAdjustment> save(@RequestHeader(value = "Authorization", required = false) String auth, |
| | | @RequestBody Map<String, Object> params) { |
| | | if (!isAdmin(auth)) return Result.error(403, "ä»
admin å¯ä»¥ç»´æ¤å¾å¹´å½çéæ°æ®è°æ´"); |
| | | try { |
| | | return Result.ok(adjustmentService.save(params)); |
| | | } catch (Exception e) { |
| | | return Result.error(400, e.getMessage()); |
| | | } |
| | | } |
| | | |
| | | @PostMapping("/delete") |
| | | public Result<Void> delete(@RequestHeader(value = "Authorization", required = false) String auth, |
| | | @RequestParam("id") Long id) { |
| | | if (!isAdmin(auth)) return Result.error(403, "ä»
admin å¯ä»¥ç»´æ¤å¾å¹´å½çéæ°æ®è°æ´"); |
| | | try { |
| | | adjustmentService.delete(id); |
| | | return Result.ok(); |
| | | } catch (Exception e) { |
| | | return Result.error(400, e.getMessage()); |
| | | } |
| | | } |
| | | |
| | | private boolean isAdmin(String auth) { |
| | | return "admin".equals(jwtUtils.resolveUsername(auth)); |
| | | } |
| | | } |
| New file |
| | |
| | | package com.trafficaudit.holiday.entity; |
| | | |
| | | import com.baomidou.mybatisplus.annotation.IdType; |
| | | import com.baomidou.mybatisplus.annotation.TableId; |
| | | import com.baomidou.mybatisplus.annotation.TableName; |
| | | import lombok.Data; |
| | | |
| | | import java.math.BigDecimal; |
| | | import java.time.LocalDate; |
| | | import java.time.LocalDateTime; |
| | | |
| | | @Data |
| | | @TableName("holiday_vehicle_adjustment") |
| | | public class HolidayVehicleAdjustment { |
| | | @TableId(type = IdType.AUTO) |
| | | private Long id; |
| | | private Integer year; |
| | | private LocalDate flowDate; |
| | | private BigDecimal adjustedValue; |
| | | private String reason; |
| | | private LocalDateTime createdAt; |
| | | private LocalDateTime updatedAt; |
| | | } |
| New file |
| | |
| | | package com.trafficaudit.holiday.mapper; |
| | | |
| | | import com.baomidou.mybatisplus.core.mapper.BaseMapper; |
| | | import com.trafficaudit.holiday.entity.HolidayVehicleAdjustment; |
| | | import org.apache.ibatis.annotations.Mapper; |
| | | |
| | | @Mapper |
| | | public interface HolidayVehicleAdjustmentMapper extends BaseMapper<HolidayVehicleAdjustment> { |
| | | } |
| | |
| | | throw new IllegalArgumentException("æéæ¥æä¸å¨å·²é
ç½®çè忥èå´å
"); |
| | | } |
| | | DailyMetrics previousDay = current.getDayIndex() > 1 |
| | | ? statService.dailyMetrics(current.getYear(), current.getHolidayType(), current.getDayIndex() - 1) : null; |
| | | ? statService.ringRatioBaseMetrics(current.getYear(), current.getHolidayType(), current.getDayIndex()) : null; |
| | | HolidayCalendar previousDayConfig = configService.findPreviousDay(current.getYear(), current.getHolidayType()); |
| | | LocalDate previousDayDate = previousDayConfig == null || previousDayConfig.getHolidayDate() == null |
| | | ? current.getDate().minusDays(1) : previousDayConfig.getHolidayDate(); |
| | |
| | | import com.trafficaudit.holiday.entity.HolidayCalendar; |
| | | import com.trafficaudit.holiday.entity.HolidayPassengerFlow; |
| | | import com.trafficaudit.holiday.entity.HolidayVehicleFlow; |
| | | import com.trafficaudit.holiday.entity.HolidayVehicleAdjustment; |
| | | import com.trafficaudit.holiday.mapper.HolidayPassengerFlowMapper; |
| | | import com.trafficaudit.holiday.mapper.HolidayVehicleAdjustmentMapper; |
| | | import com.trafficaudit.holiday.mapper.HolidayVehicleFlowMapper; |
| | | import lombok.Data; |
| | | import org.springframework.stereotype.Service; |
| | |
| | | private HolidayPassengerFlowMapper passengerFlowMapper; |
| | | @Resource |
| | | private HolidayVehicleFlowMapper vehicleFlowMapper; |
| | | @Resource |
| | | private HolidayVehicleAdjustmentMapper vehicleAdjustmentMapper; |
| | | @Resource |
| | | private ImportBatchMapper importBatchMapper; |
| | | |
| | |
| | | Map<LocalDate, Map<String, BigDecimal>> vehicleByDate = loadVehicleByDate(year); |
| | | Map<LocalDate, BigDecimal> comparisonBaseByDate = loadComparisonBaseByDate(year); |
| | | BigDecimal coefficient = configService.coefficientOf(year, HolidayConstants.CATEGORY_HOLIDAY); |
| | | List<Map<String, Object>> rows = new ArrayList<>(); |
| | | BigDecimal previousNationalProvincial = null; |
| | | Map<Integer, HolidayCalendar> calendarByDay = new LinkedHashMap<>(); |
| | | for (HolidayCalendar day : calendar) { |
| | | Map<String, BigDecimal> passenger = passengerByDate.getOrDefault(day.getHolidayDate(), Collections.emptyMap()); |
| | | Map<String, BigDecimal> vehicle = vehicleByDate.getOrDefault(day.getHolidayDate(), Collections.emptyMap()); |
| | | BigDecimal expressway = vehicle.get(HolidayConstants.ROAD_EXPRESSWAY); |
| | | BigDecimal nationalProvincial = vehicle.get(HolidayConstants.ROAD_NATIONAL_PROVINCIAL); |
| | | BigDecimal comparisonBase = comparisonBaseByDate.get(day.getHolidayDate()); |
| | | BigDecimal nationalProvincialRingRatio = nationalProvincialRingRatio( |
| | | nationalProvincial, previousNationalProvincial); |
| | | BigDecimal nonBusinessPersonFlow = null; |
| | | if (coefficient != null && (expressway != null || nationalProvincial != null)) { |
| | | nonBusinessPersonFlow = HolidayNumberUtils.multiply( |
| | | HolidayNumberUtils.sumNullable(expressway, nationalProvincial), coefficient); |
| | | } |
| | | if (day.getDayIndex() != null) calendarByDay.put(day.getDayIndex(), day); |
| | | } |
| | | List<Map<String, Object>> rows = new ArrayList<>(); |
| | | for (HolidayCalendar day : calendar) { |
| | | DailyMetrics current = buildDailyMetrics(year, holidayType, day.getDayIndex(), day.getHolidayDate(), |
| | | passengerByDate, vehicleByDate, comparisonBaseByDate, coefficient); |
| | | DailyMetrics ringRatioBase = ringRatioBaseFromMaps(year, holidayType, day.getDayIndex(), calendarByDay, |
| | | passengerByDate, vehicleByDate, comparisonBaseByDate, coefficient); |
| | | BigDecimal nationalProvincialRingRatio = ratio(current.getNationalProvincial(), |
| | | ringRatioBase == null ? null : ringRatioBase.getNationalProvincial()); |
| | | Map<String, Object> row = new LinkedHashMap<>(); |
| | | row.put("dayIndex", day.getDayIndex()); |
| | | row.put("date", day.getHolidayDate()); |
| | | row.put("rail", passenger.get(HolidayConstants.MODE_RAIL)); |
| | | row.put("civilAviation", passenger.get(HolidayConstants.MODE_CIVIL_AVIATION)); |
| | | row.put("road", passenger.get(HolidayConstants.MODE_ROAD)); |
| | | row.put("water", passenger.get(HolidayConstants.MODE_WATER)); |
| | | row.put("expressway", expressway); |
| | | row.put("nationalProvincial", nationalProvincial); |
| | | row.put("comparisonBase", comparisonBase); |
| | | row.put("rail", current.getRail()); |
| | | row.put("civilAviation", current.getCivilAviation()); |
| | | row.put("road", current.getRoad()); |
| | | row.put("water", current.getWater()); |
| | | row.put("expressway", current.getExpressway()); |
| | | row.put("nationalProvincial", current.getNationalProvincial()); |
| | | row.put("comparisonBase", current.getComparisonBase()); |
| | | row.put("nationalProvincialRingRatio", nationalProvincialRingRatio); |
| | | row.put("coefficient", coefficient); |
| | | row.put("nonBusinessPersonFlow", nonBusinessPersonFlow); |
| | | row.put("totalPersonFlow", totalPersonFlow(passenger, nonBusinessPersonFlow)); |
| | | row.put("coefficient", current.getCoefficient()); |
| | | row.put("nonBusinessPersonFlow", current.getNonBusinessPersonFlow()); |
| | | row.put("totalPersonFlow", current.getTotalPersonFlow()); |
| | | rows.add(row); |
| | | previousNationalProvincial = nationalProvincial; |
| | | } |
| | | return rows; |
| | | } |
| | |
| | | Map<LocalDate, Map<String, BigDecimal>> passengerByDate = loadPassengerByDate(year); |
| | | Map<LocalDate, Map<String, BigDecimal>> vehicleByDate = loadVehicleByDate(year); |
| | | Map<LocalDate, BigDecimal> comparisonBaseByDate = loadComparisonBaseByDate(year); |
| | | BigDecimal coefficient = configService.coefficientOf(year, HolidayConstants.CATEGORY_HOLIDAY); |
| | | return buildDailyMetrics(year, holidayType, dayIndex, date, passengerByDate, vehicleByDate, |
| | | comparisonBaseByDate, coefficient); |
| | | } |
| | | |
| | | public DailyMetrics ringRatioBaseMetrics(int year, String holidayType, int dayIndex) { |
| | | if (dayIndex <= 1) return null; |
| | | List<HolidayCalendar> calendar = configService.findHolidayDays(year, holidayType); |
| | | Map<Integer, HolidayCalendar> calendarByDay = new LinkedHashMap<>(); |
| | | for (HolidayCalendar day : calendar) { |
| | | if (day.getDayIndex() != null) calendarByDay.put(day.getDayIndex(), day); |
| | | } |
| | | Map<LocalDate, Map<String, BigDecimal>> passengerByDate = loadPassengerByDate(year); |
| | | Map<LocalDate, Map<String, BigDecimal>> vehicleByDate = loadVehicleByDate(year); |
| | | Map<LocalDate, BigDecimal> comparisonBaseByDate = loadComparisonBaseByDate(year); |
| | | BigDecimal coefficient = configService.coefficientOf(year, HolidayConstants.CATEGORY_HOLIDAY); |
| | | return ringRatioBaseFromMaps(year, holidayType, dayIndex, calendarByDay, passengerByDate, |
| | | vehicleByDate, comparisonBaseByDate, coefficient); |
| | | } |
| | | |
| | | private DailyMetrics buildDailyMetrics(int year, String holidayType, int dayIndex, LocalDate date, |
| | | Map<LocalDate, Map<String, BigDecimal>> passengerByDate, |
| | | Map<LocalDate, Map<String, BigDecimal>> vehicleByDate, |
| | | Map<LocalDate, BigDecimal> comparisonBaseByDate, |
| | | BigDecimal coefficient) { |
| | | Map<String, BigDecimal> passenger = passengerByDate.getOrDefault(date, Collections.emptyMap()); |
| | | Map<String, BigDecimal> vehicle = vehicleByDate.getOrDefault(date, Collections.emptyMap()); |
| | | BigDecimal coefficient = configService.coefficientOf(year, HolidayConstants.CATEGORY_HOLIDAY); |
| | | BigDecimal expressway = vehicle.get(HolidayConstants.ROAD_EXPRESSWAY); |
| | | BigDecimal nationalProvincial = vehicle.get(HolidayConstants.ROAD_NATIONAL_PROVINCIAL); |
| | | BigDecimal comparisonBase = comparisonBaseByDate.get(date); |
| | | BigDecimal nonBusiness = null; |
| | | if (coefficient != null && (expressway != null || nationalProvincial != null)) { |
| | | nonBusiness = HolidayNumberUtils.multiply( |
| | | HolidayNumberUtils.sumNullable(expressway, nationalProvincial), coefficient); |
| | | } |
| | | BigDecimal nonBusiness = nonBusinessPersonFlow(expressway, nationalProvincial, coefficient); |
| | | |
| | | DailyMetrics metrics = new DailyMetrics(); |
| | | metrics.setYear(year); |
| | |
| | | metrics.setComparisonBase(comparisonBase); |
| | | metrics.setCoefficient(coefficient); |
| | | metrics.setNonBusinessPersonFlow(nonBusiness); |
| | | metrics.setRoadPersonFlow(sumNullable(metrics.getRoad(), nonBusiness)); |
| | | metrics.setTotalPersonFlow(totalPersonFlow(passenger, nonBusiness)); |
| | | recalculateDerived(metrics); |
| | | return metrics; |
| | | } |
| | | |
| | | private DailyMetrics ringRatioBaseFromMaps(int year, String holidayType, int dayIndex, |
| | | Map<Integer, HolidayCalendar> calendarByDay, |
| | | Map<LocalDate, Map<String, BigDecimal>> passengerByDate, |
| | | Map<LocalDate, Map<String, BigDecimal>> vehicleByDate, |
| | | Map<LocalDate, BigDecimal> comparisonBaseByDate, |
| | | BigDecimal coefficient) { |
| | | if (dayIndex <= 1) return null; |
| | | HolidayCalendar previous = calendarByDay.get(dayIndex - 1); |
| | | if (previous == null || previous.getHolidayDate() == null) return null; |
| | | DailyMetrics base = buildDailyMetrics(year, holidayType, previous.getDayIndex(), previous.getHolidayDate(), |
| | | passengerByDate, vehicleByDate, comparisonBaseByDate, coefficient); |
| | | BigDecimal comparisonBase = comparisonBaseByDate.get(previous.getHolidayDate()); |
| | | if (comparisonBase != null) { |
| | | base.setNationalProvincial(comparisonBase); |
| | | recalculateDerived(base); |
| | | } |
| | | return base; |
| | | } |
| | | |
| | | private void recalculateDerived(DailyMetrics metrics) { |
| | | BigDecimal nonBusiness = nonBusinessPersonFlow(metrics.getExpressway(), metrics.getNationalProvincial(), |
| | | metrics.getCoefficient()); |
| | | metrics.setNonBusinessPersonFlow(nonBusiness); |
| | | metrics.setRoadPersonFlow(sumNullable(metrics.getRoad(), nonBusiness)); |
| | | metrics.setTotalPersonFlow(totalPersonFlow(nonBusiness, metrics.getRoad(), metrics.getRail(), |
| | | metrics.getCivilAviation(), metrics.getWater())); |
| | | } |
| | | |
| | | private BigDecimal nonBusinessPersonFlow(BigDecimal expressway, BigDecimal nationalProvincial, |
| | | BigDecimal coefficient) { |
| | | if (coefficient == null || (expressway == null && nationalProvincial == null)) return null; |
| | | return HolidayNumberUtils.multiply( |
| | | HolidayNumberUtils.sumNullable(expressway, nationalProvincial), coefficient); |
| | | } |
| | | |
| | | public DailyMetrics cumulativeMetrics(int year, String holidayType, int dayIndex) { |
| | |
| | | result.computeIfAbsent(row.getFlowDate(), key -> new LinkedHashMap<>()) |
| | | .put(row.getRoadCategory(), row.getVehicleCount()); |
| | | } |
| | | Map<LocalDate, BigDecimal> adjustments = loadNationalProvincialAdjustmentsByDate(year); |
| | | for (Map.Entry<LocalDate, BigDecimal> entry : adjustments.entrySet()) { |
| | | result.computeIfAbsent(entry.getKey(), key -> new LinkedHashMap<>()) |
| | | .put(HolidayConstants.ROAD_NATIONAL_PROVINCIAL, entry.getValue()); |
| | | } |
| | | return result; |
| | | } |
| | | |
| | | private Map<LocalDate, BigDecimal> loadNationalProvincialAdjustmentsByDate(int year) { |
| | | Map<LocalDate, BigDecimal> result = new LinkedHashMap<>(); |
| | | List<HolidayVehicleAdjustment> rows = vehicleAdjustmentMapper.selectList( |
| | | new LambdaQueryWrapper<HolidayVehicleAdjustment>().eq(HolidayVehicleAdjustment::getYear, year)); |
| | | for (HolidayVehicleAdjustment row : rows) { |
| | | if (row.getFlowDate() != null && row.getAdjustedValue() != null) { |
| | | result.put(row.getFlowDate(), row.getAdjustedValue()); |
| | | } |
| | | } |
| | | return result; |
| | | } |
| | | |
| | |
| | | return result; |
| | | } |
| | | |
| | | private BigDecimal totalPersonFlow(Map<String, BigDecimal> passenger, BigDecimal nonBusinessPersonFlow) { |
| | | private BigDecimal totalPersonFlow(BigDecimal nonBusinessPersonFlow, BigDecimal road, BigDecimal rail, |
| | | BigDecimal civilAviation, BigDecimal water) { |
| | | BigDecimal total = nonBusinessPersonFlow; |
| | | total = sumNullable(total, passenger.get(HolidayConstants.MODE_ROAD)); |
| | | total = sumNullable(total, passenger.get(HolidayConstants.MODE_RAIL)); |
| | | total = sumNullable(total, passenger.get(HolidayConstants.MODE_CIVIL_AVIATION)); |
| | | total = sumNullable(total, passenger.get(HolidayConstants.MODE_WATER)); |
| | | total = sumNullable(total, road); |
| | | total = sumNullable(total, rail); |
| | | total = sumNullable(total, civilAviation); |
| | | total = sumNullable(total, water); |
| | | return total; |
| | | } |
| | | |
| | | // The national/provincial ring ratio always compares the current day with the previous day of |
| | | // the same year. The stored comparison base (Excel column C) is kept as an operator backup |
| | | // value only and never feeds this calculation. |
| | | private BigDecimal nationalProvincialRingRatio(BigDecimal current, BigDecimal previous) { |
| | | if (current == null) return null; |
| | | return previous == null ? null : ratio(current, previous); |
| | | } |
| | | |
| | | private BigDecimal ratio(BigDecimal current, BigDecimal base) { |
| | | BigDecimal quotient = HolidayNumberUtils.divide(current, base); |
| New file |
| | |
| | | package com.trafficaudit.holiday.service; |
| | | |
| | | import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; |
| | | import com.trafficaudit.holiday.HolidayNumberUtils; |
| | | import com.trafficaudit.holiday.entity.HolidayVehicleAdjustment; |
| | | import com.trafficaudit.holiday.mapper.HolidayVehicleAdjustmentMapper; |
| | | import org.springframework.stereotype.Service; |
| | | import org.springframework.transaction.annotation.Transactional; |
| | | |
| | | import javax.annotation.Resource; |
| | | import java.math.BigDecimal; |
| | | import java.time.LocalDate; |
| | | import java.time.LocalDateTime; |
| | | import java.util.List; |
| | | import java.util.Map; |
| | | |
| | | @Service |
| | | public class HolidayVehicleAdjustmentService { |
| | | |
| | | private static final int REASON_MAX_LENGTH = 500; |
| | | |
| | | @Resource |
| | | private HolidayVehicleAdjustmentMapper adjustmentMapper; |
| | | |
| | | public List<HolidayVehicleAdjustment> list(Integer year) { |
| | | return adjustmentMapper.selectList(new LambdaQueryWrapper<HolidayVehicleAdjustment>() |
| | | .eq(year != null, HolidayVehicleAdjustment::getYear, year) |
| | | .orderByDesc(HolidayVehicleAdjustment::getYear) |
| | | .orderByAsc(HolidayVehicleAdjustment::getFlowDate)); |
| | | } |
| | | |
| | | @Transactional |
| | | public HolidayVehicleAdjustment save(Map<String, Object> params) { |
| | | Long id = longValue(params.get("id")); |
| | | Integer year = intValue(params.get("year")); |
| | | LocalDate flowDate = dateValue(params.get("flowDate")); |
| | | BigDecimal adjustedValue = decimalValue(params.get("adjustedValue")); |
| | | String reason = textValue(params.get("reason")); |
| | | |
| | | if (year == null || year < 2000 || year > 2100) { |
| | | throw new IllegalArgumentException("年份䏿£ç¡®"); |
| | | } |
| | | if (year >= LocalDate.now().getYear()) { |
| | | throw new IllegalArgumentException("å¾å¹´å½çéæ°æ®è°æ´ä»
ç»´æ¤åå²å¹´ä»½"); |
| | | } |
| | | if (flowDate == null || flowDate.getYear() != year) { |
| | | throw new IllegalArgumentException("æ¥æå¿
é¡»å±äºæé年份"); |
| | | } |
| | | if (adjustedValue == null || adjustedValue.compareTo(BigDecimal.ZERO) < 0) { |
| | | throw new IllegalArgumentException("è°æ´åçå½çé车æµéä¸è½å°äº 0"); |
| | | } |
| | | if (reason.isEmpty()) { |
| | | throw new IllegalArgumentException("è°æ´åå ä¸è½ä¸ºç©º"); |
| | | } |
| | | if (reason.length() > REASON_MAX_LENGTH) { |
| | | throw new IllegalArgumentException("è°æ´åå ä¸è½è¶
è¿ " + REASON_MAX_LENGTH + " 个å符"); |
| | | } |
| | | |
| | | HolidayVehicleAdjustment sameKey = adjustmentMapper.selectOne( |
| | | new LambdaQueryWrapper<HolidayVehicleAdjustment>() |
| | | .eq(HolidayVehicleAdjustment::getYear, year) |
| | | .eq(HolidayVehicleAdjustment::getFlowDate, flowDate) |
| | | .last("limit 1")); |
| | | HolidayVehicleAdjustment entity; |
| | | if (id == null) { |
| | | entity = sameKey == null ? new HolidayVehicleAdjustment() : sameKey; |
| | | } else { |
| | | entity = adjustmentMapper.selectById(id); |
| | | if (entity == null) { |
| | | throw new IllegalArgumentException("è°æ´è®°å½ä¸åå¨"); |
| | | } |
| | | if (sameKey != null && !sameKey.getId().equals(id)) { |
| | | throw new IllegalArgumentException("è¯¥å¹´ä»½åæ¥æå·²åå¨è°æ´è®°å½"); |
| | | } |
| | | } |
| | | |
| | | if (entity.getId() == null) { |
| | | entity.setCreatedAt(LocalDateTime.now()); |
| | | } |
| | | entity.setYear(year); |
| | | entity.setFlowDate(flowDate); |
| | | entity.setAdjustedValue(HolidayNumberUtils.normalize(adjustedValue)); |
| | | entity.setReason(reason); |
| | | entity.setUpdatedAt(LocalDateTime.now()); |
| | | if (entity.getId() == null) { |
| | | adjustmentMapper.insert(entity); |
| | | } else { |
| | | adjustmentMapper.updateById(entity); |
| | | } |
| | | return entity; |
| | | } |
| | | |
| | | @Transactional |
| | | public void delete(Long id) { |
| | | if (id == null) { |
| | | throw new IllegalArgumentException("缺å°è®°å½ id"); |
| | | } |
| | | adjustmentMapper.deleteById(id); |
| | | } |
| | | |
| | | private Long longValue(Object value) { |
| | | if (value == null || value.toString().trim().isEmpty()) return null; |
| | | return Long.valueOf(value.toString().trim()); |
| | | } |
| | | |
| | | private Integer intValue(Object value) { |
| | | if (value == null || value.toString().trim().isEmpty()) return null; |
| | | return Integer.valueOf(value.toString().trim()); |
| | | } |
| | | |
| | | private BigDecimal decimalValue(Object value) { |
| | | if (value == null || value.toString().trim().isEmpty()) return null; |
| | | return HolidayNumberUtils.decimalValue(value); |
| | | } |
| | | |
| | | private LocalDate dateValue(Object value) { |
| | | if (value == null || value.toString().trim().isEmpty()) return null; |
| | | return LocalDate.parse(value.toString().trim()); |
| | | } |
| | | |
| | | private String textValue(Object value) { |
| | | return value == null ? "" : value.toString().trim(); |
| | | } |
| | | } |
| | |
| | | import com.trafficaudit.holiday.HolidayNumberUtils; |
| | | import com.trafficaudit.holiday.entity.HolidayCalendar; |
| | | import com.trafficaudit.holiday.entity.HolidayPassengerFlow; |
| | | import com.trafficaudit.holiday.entity.HolidayVehicleAdjustment; |
| | | import com.trafficaudit.holiday.entity.HolidayVehicleFlow; |
| | | import com.trafficaudit.holiday.mapper.HolidayPassengerFlowMapper; |
| | | import com.trafficaudit.holiday.mapper.HolidayVehicleAdjustmentMapper; |
| | | import com.trafficaudit.holiday.mapper.HolidayVehicleFlowMapper; |
| | | import org.junit.jupiter.api.Test; |
| | | |
| | |
| | | import java.math.BigDecimal; |
| | | import java.time.LocalDate; |
| | | import java.util.ArrayList; |
| | | import java.util.Arrays; |
| | | import java.util.Collections; |
| | | import java.util.List; |
| | | import java.util.Map; |
| | | |
| | | import static org.junit.jupiter.api.Assertions.assertEquals; |
| | | import static org.junit.jupiter.api.Assertions.assertNotEquals; |
| | | import static org.junit.jupiter.api.Assertions.assertNotNull; |
| | | import static org.mockito.ArgumentMatchers.any; |
| | | import static org.mockito.Mockito.mock; |
| | | import static org.mockito.Mockito.when; |
| | | |
| | | /** |
| | | * The Excel comparison base column (C) is an operator backup value only: it must never change the |
| | | * national/provincial ring ratio. This locks in that behaviour. |
| | | * Ring-ratio denominators are built one day at a time. The Excel comparison base (column C) is |
| | | * consumed on the source day and inherited by the next day's denominator, while historical |
| | | * adjustment records change only that historical day's same-day value. |
| | | */ |
| | | class HolidayComparisonBaseTest { |
| | | |
| | | @Test |
| | | void ringRatioUsesPreviousDayEvenWhenComparisonBaseIsStored() throws Exception { |
| | | void comparisonBaseChangesOnlyTheNextDayRingRatioDenominator() throws Exception { |
| | | HolidayStatService service = serviceWithData( |
| | | 2026, |
| | | passengerRows(2026, "50", "10", "6", "3"), |
| | | vehicleRows(2026), |
| | | Collections.<HolidayVehicleAdjustment>emptyList()); |
| | | |
| | | List<Map<String, Object>> rows = service.buildOverviewRows( |
| | | 2026, HolidayConstants.TYPE_NATIONAL_DAY, calendar(2026)); |
| | | |
| | | assertEquals(new BigDecimal("323.7837"), rows.get(2).get("nationalProvincial")); |
| | | assertEquals(new BigDecimal("320.2418"), rows.get(2).get("comparisonBase")); |
| | | assertEquals(new BigDecimal("322.7695"), rows.get(3).get("nationalProvincial")); |
| | | |
| | | BigDecimal nationalRatio = (BigDecimal) rows.get(3).get("nationalProvincialRingRatio"); |
| | | assertEquals(ratio(new BigDecimal("322.7695"), new BigDecimal("320.2418")), nationalRatio); |
| | | assertNotEquals(ratio(new BigDecimal("322.7695"), new BigDecimal("323.7837")), nationalRatio); |
| | | |
| | | HolidayStatService.DailyMetrics base = service.ringRatioBaseMetrics( |
| | | 2026, HolidayConstants.TYPE_NATIONAL_DAY, 4); |
| | | assertNotNull(base); |
| | | assertEquals(new BigDecimal("320.2418"), base.getNationalProvincial()); |
| | | assertDecimalEquals("1008.58032", base.getNonBusinessPersonFlow()); |
| | | assertDecimalEquals("1058.58032", base.getRoadPersonFlow()); |
| | | assertDecimalEquals("1077.58032", base.getTotalPersonFlow()); |
| | | } |
| | | |
| | | @Test |
| | | void historicalAdjustmentChangesTheHistoricalSameDayValue() throws Exception { |
| | | HolidayVehicleAdjustment adjustment = new HolidayVehicleAdjustment(); |
| | | adjustment.setYear(2025); |
| | | adjustment.setFlowDate(LocalDate.of(2025, 10, 4)); |
| | | adjustment.setAdjustedValue(new BigDecimal("337.5009")); |
| | | |
| | | HolidayStatService service = serviceWithData( |
| | | 2025, |
| | | passengerRows(2025, "50", "10", "6", "3"), |
| | | Arrays.asList( |
| | | vehicle("2025-10-03", HolidayConstants.ROAD_EXPRESSWAY, "100", null), |
| | | vehicle("2025-10-03", HolidayConstants.ROAD_NATIONAL_PROVINCIAL, "362.0035", null), |
| | | vehicle("2025-10-04", HolidayConstants.ROAD_EXPRESSWAY, "110", null), |
| | | vehicle("2025-10-04", HolidayConstants.ROAD_NATIONAL_PROVINCIAL, "350.0000", null)), |
| | | Collections.singletonList(adjustment)); |
| | | |
| | | List<Map<String, Object>> rows = service.buildOverviewRows( |
| | | 2025, HolidayConstants.TYPE_NATIONAL_DAY, calendar(2025)); |
| | | |
| | | assertEquals(new BigDecimal("337.5009"), rows.get(3).get("nationalProvincial")); |
| | | } |
| | | |
| | | private static HolidayStatService serviceWithData(int year, |
| | | List<HolidayPassengerFlow> passengerRows, |
| | | List<HolidayVehicleFlow> vehicleRows, |
| | | List<HolidayVehicleAdjustment> adjustments) throws Exception { |
| | | HolidayConfigService configService = mock(HolidayConfigService.class); |
| | | HolidayPassengerFlowMapper passengerMapper = mock(HolidayPassengerFlowMapper.class); |
| | | HolidayVehicleFlowMapper vehicleMapper = mock(HolidayVehicleFlowMapper.class); |
| | | HolidayVehicleAdjustmentMapper adjustmentMapper = mock(HolidayVehicleAdjustmentMapper.class); |
| | | |
| | | when(configService.coefficientOf(2026, HolidayConstants.CATEGORY_HOLIDAY)).thenReturn(new BigDecimal("2.4")); |
| | | when(passengerMapper.selectList(any())).thenReturn(new ArrayList<HolidayPassengerFlow>()); |
| | | when(vehicleMapper.selectList(any())).thenReturn(vehicleRows()); |
| | | when(configService.coefficientOf(year, HolidayConstants.CATEGORY_HOLIDAY)).thenReturn(new BigDecimal("2.4")); |
| | | when(configService.findHolidayDays(year, HolidayConstants.TYPE_NATIONAL_DAY)).thenReturn(calendar(year)); |
| | | when(passengerMapper.selectList(any())).thenReturn(passengerRows); |
| | | when(vehicleMapper.selectList(any())).thenReturn(vehicleRows); |
| | | when(adjustmentMapper.selectList(any())).thenReturn(adjustments); |
| | | |
| | | HolidayStatService service = new HolidayStatService(); |
| | | inject(service, "configService", configService); |
| | | inject(service, "passengerFlowMapper", passengerMapper); |
| | | inject(service, "vehicleFlowMapper", vehicleMapper); |
| | | |
| | | List<Map<String, Object>> rows = service.buildOverviewRows(2026, HolidayConstants.TYPE_NATIONAL_DAY, calendar()); |
| | | |
| | | BigDecimal ringRatio = (BigDecimal) rows.get(3).get("nationalProvincialRingRatio"); |
| | | assertEquals(ratio(new BigDecimal("322.7695"), new BigDecimal("323.7837")), ringRatio); |
| | | assertNotEquals(ratio(new BigDecimal("322.7695"), new BigDecimal("320.2418")), ringRatio); |
| | | assertEquals(new BigDecimal("320.2418"), rows.get(2).get("comparisonBase")); |
| | | } |
| | | |
| | | @Test |
| | | void exportFormulaComparesPreviousRowInsteadOfComparisonBaseColumn() { |
| | | assertEquals("=(D6-D5)/D5", HolidayWorkbookService.ringRatioFormula(6, 5)); |
| | | inject(service, "vehicleAdjustmentMapper", adjustmentMapper); |
| | | return service; |
| | | } |
| | | |
| | | private static BigDecimal ratio(BigDecimal current, BigDecimal base) { |
| | | return HolidayNumberUtils.normalize(HolidayNumberUtils.divide(current, base).subtract(BigDecimal.ONE)); |
| | | } |
| | | |
| | | private static List<HolidayVehicleFlow> vehicleRows() { |
| | | private static void assertDecimalEquals(String expected, BigDecimal actual) { |
| | | assertNotNull(actual); |
| | | assertEquals(0, new BigDecimal(expected).compareTo(actual)); |
| | | } |
| | | |
| | | private static List<HolidayVehicleFlow> vehicleRows(int year) { |
| | | List<HolidayVehicleFlow> rows = new ArrayList<>(); |
| | | rows.add(vehicle("2026-10-03", "323.7837", "320.2418")); |
| | | rows.add(vehicle("2026-10-04", "322.7695", null)); |
| | | rows.add(vehicle(year + "-10-03", HolidayConstants.ROAD_EXPRESSWAY, "100", null)); |
| | | rows.add(vehicle(year + "-10-03", HolidayConstants.ROAD_NATIONAL_PROVINCIAL, "323.7837", "320.2418")); |
| | | rows.add(vehicle(year + "-10-04", HolidayConstants.ROAD_EXPRESSWAY, "110", null)); |
| | | rows.add(vehicle(year + "-10-04", HolidayConstants.ROAD_NATIONAL_PROVINCIAL, "322.7695", null)); |
| | | return rows; |
| | | } |
| | | |
| | | private static HolidayVehicleFlow vehicle(String date, String value, String base) { |
| | | private static HolidayVehicleFlow vehicle(String date, String roadCategory, String value, String base) { |
| | | HolidayVehicleFlow row = new HolidayVehicleFlow(); |
| | | row.setYear(2026); |
| | | row.setYear(LocalDate.parse(date).getYear()); |
| | | row.setFlowDate(LocalDate.parse(date)); |
| | | row.setRoadCategory(HolidayConstants.ROAD_NATIONAL_PROVINCIAL); |
| | | row.setRoadCategory(roadCategory); |
| | | row.setVehicleType(HolidayConstants.VEHICLE_MEDIUM_SMALL_PASSENGER); |
| | | row.setVehicleCount(new BigDecimal(value)); |
| | | row.setComparisonBase(base == null ? null : new BigDecimal(base)); |
| | | return row; |
| | | } |
| | | |
| | | private static List<HolidayCalendar> calendar() { |
| | | private static List<HolidayPassengerFlow> passengerRows(int year, String road, String rail, |
| | | String civilAviation, String water) { |
| | | List<HolidayPassengerFlow> rows = new ArrayList<>(); |
| | | rows.add(passenger(year + "-10-03", HolidayConstants.MODE_ROAD, road)); |
| | | rows.add(passenger(year + "-10-03", HolidayConstants.MODE_RAIL, rail)); |
| | | rows.add(passenger(year + "-10-03", HolidayConstants.MODE_CIVIL_AVIATION, civilAviation)); |
| | | rows.add(passenger(year + "-10-03", HolidayConstants.MODE_WATER, water)); |
| | | rows.add(passenger(year + "-10-04", HolidayConstants.MODE_ROAD, road)); |
| | | rows.add(passenger(year + "-10-04", HolidayConstants.MODE_RAIL, rail)); |
| | | rows.add(passenger(year + "-10-04", HolidayConstants.MODE_CIVIL_AVIATION, civilAviation)); |
| | | rows.add(passenger(year + "-10-04", HolidayConstants.MODE_WATER, water)); |
| | | return rows; |
| | | } |
| | | |
| | | private static HolidayPassengerFlow passenger(String date, String mode, String value) { |
| | | HolidayPassengerFlow row = new HolidayPassengerFlow(); |
| | | row.setYear(LocalDate.parse(date).getYear()); |
| | | row.setFlowDate(LocalDate.parse(date)); |
| | | row.setTransportMode(mode); |
| | | row.setPassengerCount(new BigDecimal(value)); |
| | | return row; |
| | | } |
| | | |
| | | private static List<HolidayCalendar> calendar(int year) { |
| | | List<HolidayCalendar> days = new ArrayList<>(); |
| | | for (int i = 1; i <= 4; i++) { |
| | | HolidayCalendar day = new HolidayCalendar(); |
| | | day.setYear(2026); |
| | | day.setYear(year); |
| | | day.setHolidayType(HolidayConstants.TYPE_NATIONAL_DAY); |
| | | day.setDayIndex(i); |
| | | day.setHolidayDate(LocalDate.of(2026, 10, i)); |
| | | day.setHolidayDate(LocalDate.of(year, 10, i)); |
| | | days.add(day); |
| | | } |
| | | return days; |
| | |
| | | |
| | | <el-card shadow="never" style="margin-top:14px"> |
| | | <div slot="header"> |
| | | <span>å¾å¹´å½çéæ°æ®è°æ´</span> |
| | | </div> |
| | | <div class="operation-row"> |
| | | <div class="operation-item"> |
| | | <span class="label">年份</span> |
| | | <el-select v-model="adjustmentYear" style="width:120px" @change="loadAdjustments"> |
| | | <el-option v-for="item in availableAdjustmentYears" :key="item" :label="item + 'å¹´'" :value="item"></el-option> |
| | | </el-select> |
| | | </div> |
| | | <div v-if="operatorIsAdmin" class="operation-item"> |
| | | <el-button type="primary" icon="el-icon-plus" @click="openAdjustmentDialog()">æ°å¢è°æ´</el-button> |
| | | </div> |
| | | </div> |
| | | <el-table :data="adjustments" border stripe size="small" style="margin-top:12px" v-loading="adjustmentLoading"> |
| | | <el-table-column prop="year" label="年份" width="90" align="center"></el-table-column> |
| | | <el-table-column prop="flowDate" label="æ¥æ" width="130" align="center"></el-table-column> |
| | | <el-table-column label="è°æ´åå½çé车æµï¼ä¸è¾æ¬¡ï¼" width="230" align="right"> |
| | | <template slot-scope="s">{{ formatAdjustmentValue(s.row.adjustedValue) }}</template> |
| | | </el-table-column> |
| | | <el-table-column prop="reason" label="è°æ´åå " min-width="280" show-overflow-tooltip></el-table-column> |
| | | <el-table-column label="æ´æ°æ¶é´" min-width="170"><template slot-scope="s">{{ formatDateTime(s.row.updatedAt) }}</template></el-table-column> |
| | | <el-table-column v-if="operatorIsAdmin" label="æä½" width="130" align="center"> |
| | | <template slot-scope="s"> |
| | | <el-button type="text" @click="openAdjustmentDialog(s.row)">ç¼è¾</el-button> |
| | | <el-button type="text" class="danger-text" @click="deleteAdjustment(s.row)">å é¤</el-button> |
| | | </template> |
| | | </el-table-column> |
| | | </el-table> |
| | | <el-dialog :title="adjustmentForm.id ? 'ç¼è¾è°æ´' : 'æ°å¢è°æ´'" :visible.sync="adjustmentDialogVisible" width="560px" v-dialog-drag> |
| | | <el-form label-width="120px"> |
| | | <el-form-item label="年份"> |
| | | <el-select v-model="adjustmentForm.year" style="width:100%" @change="adjustmentForm.flowDate = ''"> |
| | | <el-option v-for="item in availableAdjustmentYears" :key="item" :label="item + 'å¹´'" :value="item"></el-option> |
| | | </el-select> |
| | | </el-form-item> |
| | | <el-form-item label="æ¥æ"> |
| | | <el-date-picker v-model="adjustmentForm.flowDate" type="date" value-format="yyyy-MM-dd" |
| | | style="width:100%" :picker-options="adjustmentDatePickerOptions" placeholder="éæ©æ¥æ"></el-date-picker> |
| | | </el-form-item> |
| | | <el-form-item label="è°æ´å车æµé"> |
| | | <el-input-number v-model="adjustmentForm.adjustedValue" :min="0" :precision="4" :step="0.0001" controls-position="right" style="width:100%"></el-input-number> |
| | | </el-form-item> |
| | | <el-form-item label="è°æ´åå "> |
| | | <el-input v-model="adjustmentForm.reason" type="textarea" :rows="3" maxlength="500" show-word-limit></el-input> |
| | | </el-form-item> |
| | | </el-form> |
| | | <div slot="footer"> |
| | | <el-button @click="adjustmentDialogVisible = false">åæ¶</el-button> |
| | | <el-button type="primary" :loading="adjustmentSaving" @click="saveAdjustment">ä¿å</el-button> |
| | | </div> |
| | | </el-dialog> |
| | | </el-card> |
| | | |
| | | <el-card shadow="never" style="margin-top:14px"> |
| | | <div slot="header"> |
| | | <span>è§æµç«åæ¥è°æ¥æ°æ®</span> |
| | | <span class="hint">ç¨äºåå§æ°æ®çåï¼ä¸ä¸ä¸æ¹è忥ç»è®¡æ°æ®å
³è</span> |
| | | </div> |
| | |
| | | observationDialogVisible: false, |
| | | observationDialogDate: '', |
| | | observationRows: [], |
| | | observationRowsLoading: false |
| | | observationRowsLoading: false, |
| | | adjustmentYear: new Date().getFullYear() - 1, |
| | | adjustments: [], |
| | | adjustmentLoading: false, |
| | | adjustmentDialogVisible: false, |
| | | adjustmentSaving: false, |
| | | adjustmentForm: { |
| | | id: null, |
| | | year: new Date().getFullYear() - 1, |
| | | flowDate: '', |
| | | adjustedValue: null, |
| | | reason: '' |
| | | } |
| | | } |
| | | }, |
| | | computed: { |
| | | holidayTypeLabel() { |
| | | const item = this.holidayTypeOptions.find(x => x.value === this.holidayType) |
| | | return item ? item.label : this.holidayType |
| | | }, |
| | | availableAdjustmentYears() { |
| | | const currentYear = new Date().getFullYear() |
| | | return this.years.filter(item => item < currentYear) |
| | | }, |
| | | adjustmentDatePickerOptions() { |
| | | return { |
| | | disabledDate: date => date.getFullYear() !== Number(this.adjustmentForm.year) |
| | | } |
| | | } |
| | | }, |
| | | mounted() { |
| | | this.loadOptions() |
| | | this.loadOverview() |
| | | this.loadObservationDates() |
| | | this.loadAdjustments() |
| | | }, |
| | | methods: { |
| | | previousDateString() { |
| | |
| | | }) |
| | | .catch(err => this.showError(err, 'èåæ¥æ°æ®å 载失败')) |
| | | .finally(() => { this.loading = false }) |
| | | }, |
| | | loadAdjustments() { |
| | | this.adjustmentLoading = true |
| | | api.get('/holiday/adjustments', { params: { year: this.adjustmentYear } }) |
| | | .then(res => { |
| | | if (res.code !== 200) throw new Error(res.message || 'å 载失败') |
| | | this.adjustments = res.data || [] |
| | | }) |
| | | .catch(err => this.showError(err, 'å¾å¹´å½çéæ°æ®è°æ´å 载失败')) |
| | | .finally(() => { this.adjustmentLoading = false }) |
| | | }, |
| | | openAdjustmentDialog(row) { |
| | | const source = row || {} |
| | | this.adjustmentForm = { |
| | | id: source.id || null, |
| | | year: source.year || this.adjustmentYear, |
| | | flowDate: source.flowDate || '', |
| | | adjustedValue: source.adjustedValue === undefined || source.adjustedValue === null |
| | | ? null : Number(source.adjustedValue), |
| | | reason: source.reason || '' |
| | | } |
| | | this.adjustmentDialogVisible = true |
| | | }, |
| | | saveAdjustment() { |
| | | const form = this.adjustmentForm |
| | | if (!form.year || !form.flowDate) { |
| | | this.$message.warning('è¯·éæ©å¹´ä»½åæ¥æ') |
| | | return |
| | | } |
| | | if (form.adjustedValue === null || form.adjustedValue === undefined || Number(form.adjustedValue) < 0) { |
| | | this.$message.warning('请è¾å
¥ä¸ä½äº 0 çè°æ´å车æµé') |
| | | return |
| | | } |
| | | if (!form.reason || !form.reason.trim()) { |
| | | this.$message.warning('请填åè°æ´åå ') |
| | | return |
| | | } |
| | | this.adjustmentSaving = true |
| | | api.post('/holiday/adjustments/save', { |
| | | id: form.id, |
| | | year: form.year, |
| | | flowDate: form.flowDate, |
| | | adjustedValue: form.adjustedValue, |
| | | reason: form.reason |
| | | }) |
| | | .then(res => { |
| | | if (res.code !== 200) throw new Error(res.message || 'ä¿å失败') |
| | | this.adjustmentDialogVisible = false |
| | | this.adjustmentYear = form.year |
| | | this.loadAdjustments() |
| | | this.loadOverview() |
| | | this.$message.success('è°æ´å·²ä¿å') |
| | | }) |
| | | .catch(err => this.showError(err, 'ä¿å失败')) |
| | | .finally(() => { this.adjustmentSaving = false }) |
| | | }, |
| | | deleteAdjustment(row) { |
| | | this.$confirm('ç¡®å®å é¤ ' + row.flowDate + ' çå½çéæ°æ®è°æ´åï¼', 'å é¤ç¡®è®¤', { type: 'warning' }) |
| | | .then(() => api.post('/holiday/adjustments/delete', null, { params: { id: row.id } })) |
| | | .then(res => { |
| | | if (res && res.code !== 200) throw new Error(res.message || 'å é¤å¤±è´¥') |
| | | this.loadAdjustments() |
| | | this.loadOverview() |
| | | this.$message.success('è°æ´å·²å é¤') |
| | | }) |
| | | .catch(err => { if (err !== 'cancel' && err !== 'close') this.showError(err, 'å é¤å¤±è´¥') }) |
| | | }, |
| | | onFileChange(file) { |
| | | this.selectedFile = file.raw |
| | |
| | | }) |
| | | .catch(err => { if (err !== 'cancel' && err !== 'close') this.showError(err, 'å é¤å¤±è´¥') }) |
| | | }, |
| | | formatAdjustmentValue(value) { |
| | | if (value === null || value === undefined || value === '') return 'â' |
| | | const number = Number(value) |
| | | if (!Number.isFinite(number)) return value |
| | | return number.toFixed(4) |
| | | }, |
| | | formatDateTime(value) { |
| | | if (!value) return 'â' |
| | | return String(value).replace('T', ' ').slice(0, 19) |
| | | }, |
| | | formatNumber(value) { |
| | | if (value === null || value === undefined || value === '') return 'â' |
| | | return Number(value).toFixed(2) |