| | |
| | | > **æ¯å¤©ç»ææ¶**ï¼æå½å¤©è¿å±ã踩è¿çåãæ°éæ±ãæªå®æäºé¡¹æ´æ°å°æ¬æä»¶ï¼ç¶åå¯ä»¥æ¾å¿å¼æ°å¯¹è¯ã |
| | | > **åä½çº¦å®ï¼2026-08-31ï¼**ï¼è¯´ãæäº¤ã= æ¬å° commit + æ¨éå°è¿ç«¯ gitï¼origin mainï¼Gitblitï¼ï¼ä¸ååªåæ¬å° commitã |
| | | |
| | | ## ææ°ï¼2026-10-06ï¼ |
| | | |
| | | ### 10-06ï¼æçº¿å¾çºµåé´è·å é«ï¼éªæ¶éè¿ï¼ |
| | | |
| | | - **ç¨æ·èæ¯**ï¼å®¢è¿éå¾çº¿é´è·è¾å¥½ãæ ç¾ææ¾åçï¼å
¶ä»å¤åºåæçº¿å¾å 线è·åè¿ï¼æ ç¾ä»å¯è½å线ãç¨æ·åæéå½å¢å å¾é«ï¼Word åªæ¯å徿¥æºï¼ä¸è¦æ±å½åå页为æç»çï¼ä½å¾ä¸è½è¿é«ã |
| | | - **å®ç°**ï¼ |
| | | - `HolidayTrendChartService`ï¼æçº¿çºµè½´çç½æ¶ç´§ä¸º `lowerMargin=0.04 / upperMargin=0.07`ï¼`LINE_CHART_HEIGHT_SCALE=1.20`ï¼æææçº¿ PNG é«åº¦æ¾å¤§çº¦ 20%ã |
| | | - `HolidayTrendService`ï¼12 å¼ æçº¿å¾ç Word å
åµå¾çé«åº¦åæ¥æ¾å¤§çº¦ 20%ï¼ä¿æ PNG ä¸ Word å¾çæ¡æ¯ä¾ä¸è´ï¼é¥¼å¾ãå å 徿ªæ¹ã |
| | | - `HolidayTrendChartServiceTest`ï¼æ°å¢æçº¿å¾é«åº¦ç¼©æ¾æè¨ã |
| | | - **éªæ¶**ï¼ç¨æ·äº 2026-10-06 æ¥ç `C:\Users\jcxiong\Downloads\2026å¹´åä¸åæå
¨ç综åè¿è¾å®¢æµéèµ°å¿å¾ï¼æçº¿å é«20å®éªï¼.docx` åéªæ¶éè¿ã |
| | | - **éªè¯ç»æ**ï¼ |
| | | 1. 15% å é«çå¨é路客è¿éå¾ `image5` æ£åº OCR æ ç¾æ¡éå ï¼20% ç 12 å¼ æçº¿å¾ï¼`image1/2/4/5/6/7/8/9/10/11/14/15`ï¼æªæ£åºæ ç¾æ¡éå ã |
| | | 2. PNG 宽髿¯ä¸ Word `wp:extent` 宽髿¯æå¤§è¯¯å·®çº¦ `0.162%`ï¼æªåçææ¾æä¼¸ã |
| | | 3. Word ä»ä¸º 8 é¡µãæ¯é¡µ 2 å¼ å¾ï¼é¦é¡µ 1 å¼ ï¼ï¼ä¸ 15% å é«å®éªç¸åã |
| | | 4. å端å
¨é `mvn -q test -f traffic-audit-server\pom.xml` éè¿ï¼`HolidayTrendChartServiceTest` èç¦æµè¯éè¿ã |
| | | 5. æ ç¾äº²ååº¦æ£æ¥è®°å½å° 3 夿 ç¾æ¡æ´é è¿é»è¿çº¿ï¼`image1` ç `1,518.02`ã`image4` ç `1,346.17`ã`image9` ç `248.65`ï¼ï¼ç½æè¾¹ä¿è¯å¯è¯»ï¼ç¨æ·æ´ä½éªæ¶éè¿å¹¶ä¿ç该æççç¥ã |
| | | 6. `image13` å å 徿 4 å¤ OCR æ ç¾éå ï¼ä½è¯¥å¾æªå䏿¬å®éªä¸ä»£ç æªæ¹ã |
| | | - **æ°æ®è¾¹ç**ï¼æªå¯¼å
¥ãè¦çæé导任ä½ä¸å¡æ°æ®ï¼ä»
æ¬å°åªè¯»çæèµ°å¿å¾åå®éªã |
| | | |
| | | ### 10-06ï¼å¹³åæ¥æ¤åº¦æ ç¾æ¢å¤ä¸º 2 ä½å°æ° |
| | | |
| | | - **è°æ´**ï¼æç¨æ·è¦æ±ï¼âæ®éå½çéè§æµç«å¹³åæ¥æ¤åº¦âä¸ååç¬ä½¿ç¨ 3 ä½å°æ°ï¼æ¢å¤ä¸ºæçº¿å¾é»è®¤ 2 ä½ã |
| | | - **ä¸è´æ§**ï¼ç±äºæçº¿å¾ç°å·²ç»ä¸ææ¾ç¤ºæ ç¾ç²¾åº¦ç»å¶ï¼10-04ã10-05 çæ¥æ¤åº¦é½ä¼æ¾ç¤ºå¹¶ç»å¶ä¸º `0.49`ï¼ä¸ä¼ååºç°åæ ç¾ä¸åé«åº¦ã |
| | | - **éªè¯**ï¼å端å
¨é `mvn -q test -f traffic-audit-server\pom.xml` éè¿ã |
| | | |
| | | ### 10-06ï¼æçº¿å¾ç»ä¸ææ¾ç¤ºæ ç¾ç²¾åº¦ç»å¶ |
| | | |
| | | - **å£å¾ï¼ç¨æ· 2026-10-06 确认ï¼**ï¼åä¸å¼ æçº¿å¾ä¸ï¼åä¸ä¸ªæ¾ç¤ºæ ç¾å¿
须对åºåä¸ä¸ªç¹ä½é«åº¦ï¼åææåä¸å¾å¼ç¨æ¯è¯¥å¾æ¾ç¤ºç²¾åº¦æ´ç»çå·®å¼ã |
| | | - **å®ç°**ï¼`HolidayTrendChartService.lineChart` 卿æåå§ç»è®¡è®¡ç®å®æåï¼äºæç»å
¥å¾è¾¹çè°ç¨ `displayValue`ï¼æ®éå¾æè¯¥å¾ `valueDecimals` å `HALF_UP` èå
¥ï¼ç¾åæ¯ç¹æ ç¾æ 1 ä½ç¾åæ¯ç²¾åº¦ï¼å³åå§å°æ° 3 ä½ï¼èå
¥ãæçº¿åæ åç¹æ ç¾å
±ç¨åä¸èå
¥å¼ï¼çºµè½´æ ¼å¼æ²¿ç¨è¯¥å¾ç²¾åº¦ã |
| | | - **è¾¹ç**ï¼åªå¨æç»å¾è¡¨æ¾ç¤ºå±èå
¥ï¼ä¸æ¹æ°æ®åºã䏿¹ç´¯è®¡ãå¿«æ¥ã忝ãç¯æ¯æè°æ´å£å¾ï¼è¯¥è§åç¨äºéæåææ¥åå¾ï¼ä¸è½è®©æåå¼ç¨å¾ä¸æ æ³å辨çå¾®å°å·®å¼ã |
| | | - **éªè¯**ï¼`HolidayTrendChartServiceTest` èç¦æµè¯éè¿ï¼å端å
¨é `mvn -q test -f traffic-audit-server\pom.xml` éè¿ãæ°å¢æµè¯è¦çâ两个ä¸åå弿 2 使 ç¾èå
¥åçæç¸åå ä½âåç¾åæ¯ç²¾åº¦æ¢ç®ãæµè¯æ¥åï¼`docs/åè½æµè¯æ¥å/åè½æµè¯æ¥å_æçº¿å¾ææ ç¾ç²¾åº¦ç»å¶_2026-10-06.md`ï¼æ¬å°çåï¼ä¸å
¥åºï¼ã |
| | | |
| | | ### 10-06ï¼è§æµç«å¹³åæ¥æ¤åº¦èµ°å¿å¾æ¹ä¸º 3 ä½å°æ° |
| | | |
| | | - **é®é¢**ï¼ç¨æ·å¯¼å
¥ 10-05 è§æµç«æ°æ®ååé¦ï¼æåä¸å¼ æ¥æ¤åº¦å¾ä¸ 10-04ã10-05 æ ç¾é½æ¾ç¤º `0.49`ï¼ä½ç¹ä½é«åº¦ä¸åã |
| | | - **æ ¹å **ï¼å¹³åæ¥æ¤åº¦åå¼åå«ä¸º `0.490890014472` å `0.492414492754`ï¼æçº¿ææªåæ´åå¼ç»å¶ï¼æ ç¾å为æå¤ 2 ä½å°æ°ï¼å¯¼è´æ ç¾ç²¾åº¦ä¸å¾å½¢ç²¾åº¦ä¸ä¸è´ï¼ä¸æ¯æ¥æ±æ»æç¹ä½è®¡ç®é误ã |
| | | - **ä¿®æ£**ï¼`HolidayTrendChartService.lineChart` æ°å¢å¯é `valueDecimals`ï¼é»è®¤ä»ä¸º 2ï¼ä»
âæ®éå½çéè§æµç«å¹³åæ¥æ¤åº¦âå¾ä¼ 3 ä½ã10-04ã10-05 ç°å¨å嫿¾ç¤º `0.491`ã`0.492`ï¼å
¶ä½å¾å£å¾ä¸åã |
| | | - **éªè¯**ï¼å端å
¨é `mvn -q test -f traffic-audit-server\pom.xml` éè¿ï¼éå¯ååªè¯»çææ° DOCXï¼OCR æ ç¾ä¸º `0.504/0.476/0.467/0.491/0.492`ï¼ä¸ç¨æ·æä¾çææ¡£é徿¯å¯¹ï¼15 å¼ å¾ä¸ä»
æ¥æ¤åº¦å¾ `image15` ååï¼å
¶ä» 14 å¼ SHA-256 å®å
¨ä¸è´ãæµè¯æ¥åï¼`docs/åè½æµè¯æ¥å/åè½æµè¯æ¥å_æ¥æ¤åº¦èµ°å¿å¾ç²¾åº¦_2026-10-06.md`ï¼æ¬å°çåï¼ä¸å
¥åºï¼ã |
| | | - **æ°æ®è¾¹ç**ï¼æªå¯¼å
¥ãè¦çæé导任ä½ä¸å¡æ°æ®ã |
| | | |
| | | ### 10-06ï¼å¾å¹´å½çéè°æ´å表补å
è°æ´åå¼ |
| | | |
| | | - **æ¹å¨**ï¼è°æ´å表æ¥å£å¨åä¸å¹´ä»½ãå䏿¥æãæ®éå½çéå£å¾ä¸ï¼ä» `holiday_vehicle_flow` è¡¥è¿å `originalValue`ï¼æ²¡æåå§è®°å½æ¶ä¿æç©ºï¼é¡µé¢ä¸æ¾ç¤ºå ä½ç¬¦ãè¡¨æ ¼æ°å¢âè°æ´åå½çé车æµéï¼ä¸è¾æ¬¡ï¼âï¼åâè°æ´åå½çé车æµâæ¹ä¸ºâè°æ´åå½çé车æµéâã |
| | | - **å¼¹çª**ï¼æ°å¢/ç¼è¾å¼¹çªåæ¥å¢å åªè¯»âè°æ´åå½çé车æµéï¼ä¸è¾æ¬¡ï¼âï¼ç¼è¾å·²æè®°å½æ¶å±ç¤º `originalValue`ï¼æ å弿æ°éæ©æ¥ææ¶ä¸æ¾ç¤ºå
容ãåæ¢å¹´ä»½ææ¥æä¼æ¸
空æ§åå¼ï¼é¿å
鿥å±ç¤ºã |
| | | - **鿥æå·æ°**ï¼æ°å¢æ¥å£ `GET /api/holiday/adjustments/source-value`ï¼ææé年份忥æå®æ¶è¯»åæ®éå½çéåå¼ï¼æ°å¢/ç¼è¾å¼¹çªéæ¥æåèªå¨å·æ°ï¼æªæ¥å°æºæ°æ®æ¶æ¾ç¤ºä¸ºç©ºãå端ç¨è¯·æ±åºå·ä¸¢å¼è¿å°çæ§è¯·æ±ï¼é¿å
å¿«éæ¢æ¥ææ¶ä¸²å¼ã |
| | | - **è¾¹ç**ï¼`originalValue` åªæ¯å表å±ç¤ºåæ®µï¼æ 记为 `@TableField(exist = false)`ï¼ä¸ååºãä¸åä¸è°æ´ãç¯æ¯åæ¯ã累计æèµ°å¿å¾è®¡ç®ã |
| | | - **éªè¯**ï¼`mvn -o test` 11 个æµè¯å
¨é¨éè¿ï¼`npm run build` éè¿ï¼æ¬å°éå¯ååªè¯»æ ¸å¯¹ 2025-10-03/04/05 åå¼åå«ä¸º `362.0035/337.9501/334.2040`ï¼æ æºæ°æ®ç 2025-10-09 è¿åç©ºãæµè¯æ¥åï¼`docs/åè½æµè¯æ¥å/åè½æµè¯æ¥å_å¾å¹´å½çéè°æ´åå¼å±ç¤º_2026-10-06.md`ï¼æ¬å°çåï¼ä¸å
¥åºï¼ã |
| | | |
| | | ### 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` éè¿ï¼é¡µé¢å®æµâå¾å¹´å½çéæ°æ®è°æ´âå¡ç䏿°å¢å¼¹çªæ£å¸¸ã |
| | | - **åæ´å£å¾ï¼ç¨æ· 2026-10-06 确认ï¼**ï¼ä¸åâä»åå§å¼ä¸æ¬¡åæ´âçå¤éãå¯ä¸è¦æ±æ¯ä¸ Excel æ¾ç¤ºå¼ä¸è´ï¼å
ææº Excel åå
æ ¼æ ¼å¼çææ¾ç¤ºå¼ï¼åæå¿«æ¥ç»ä¸ 1 ä½å°æ°ã10-04 æ»è®¡ç¯æ¯åå§å¼çº¦ `4.5481%`ï¼æºæ ¼æ¾ç¤º `4.55%`ï¼å¿«æ¥å 1 ä½å¾å° `4.6%`ï¼è¿å±äºæ¢å®å£å¾ï¼ä¸æ¯éè¦ä¿®æ£çè¾¹çé®é¢ã |
| | | |
| | | > 说æï¼æ¬æä»¶æ©åâç¯æ¯åºæåªä½å¤ç¨ãä¸åä¸è®¡ç®âçæ®µè½å·²ä½åºãè¯¥çæ¬éå¯¹çæ¯æ§å®ç°è¯¯æ 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%ï¼ã |
| | | - **æ°è¡ä¸ºï¼æ¬æ¬¡å·²æ¹ï¼**ï¼å½çéç¯æ¯åºå®ã彿¥ / å䏿¥ - 1ãï¼2024/2025/2026 ä¸è´ï¼`comparison_base` åªå¨å¯¼å
¥/导åº/页é¢å±ç¤ºæ¶åæ ·ä¿åä¸ååï¼ä¸åä¸ç¯æ¯ãæç®ã累计ãå¯¼åº E åæ¹ä¸º `=(D{è¡}-D{ä¸ä¸æ¥è¡})/D{ä¸ä¸æ¥è¡}`ï¼C åç
§å¸¸ååå¤ç¨å¼ã |
| | | - **æ¹å¨**ï¼`HolidayStatService.java`ï¼`nationalProvincialRingRatio` 廿 year/comparisonBase å½¢åï¼ã`HolidayWorkbookService.java`ï¼æ°å¢ `ringRatioFormula`ï¼å¯¼åºæä¸ä¸è¡åå¹´ D åï¼ã`docs/database.md`ï¼`comparison_base` ä¸è®¡ç®å£å¾ä¸¤å¤ï¼ã |
| | | - **éªè¯**ï¼æ°å¢ `HolidayComparisonBaseTest`ï¼â åºå
å `comparison_base=320.2418` æ¶ï¼10-04 å½çéç¯æ¯ä»æå䏿¥ 323.7837 计ç®ï¼ä¸ä¸çäºæåºæç®åºçå¼ï¼â¡ 导åºå
¬å¼ = `=(D6-D5)/D5`ã`mvn -o test` å
¨ç»¿ï¼4 个æµè¯ç±»ï¼ã |
| | | |
| | | ### 10-06ï¼å¾
确认ââ10-04 å½çéåç«ï¼éå·æ¨å®¶åºï¼æä¹è¿ç³»ç» |
| | | |
| | | - **æå·¥å£å¾å·²éé¡¹æ ¸å¯¹ï¼ä¸åºå
`observation_station_flow` å®å
¨å¯¹å¾ä¸**ï¼ |
| | | - 10-04 å½çéå¼ï¼2026/2025/2024ï¼= åå¼ â æ¨å®¶åºå½æ¥å¼ -> 322.7695 / 337.5009 / 322.5277 ä¸è¾æ¬¡ï¼æ¨å®¶åº 8856 / 4492 / 2328ï¼ã |
| | | - 10-04 ç¯æ¯åæ¯ = 2026-10-03 åå¼ â æ¨å®¶åº 10-03 å¼ = 323.7837 â 3.5419 = **320.2418** ä¸è¾æ¬¡ -> ç¯æ¯ = 322.7695/320.2418 â 1 = **+0.79%**ã |
| | | - 10-03 æ¾ç¤ºå¼ä¸ç´¯è®¡ä»ç¨åå¼ 323.7837ï¼è¢«åç 320.2418 åªè½å° C åå¤ç¨ã |
| | | - **è¶å¿ç»è®ºï¼éå
è·é¢å¯¼å¯¹é½ï¼**ï¼åªåæ¨å®¶åºï¼10-04 å½çéç¯æ¯åªæ **+0.79%**ï¼åæ¥é«éå°å®¢è½¦ **+9.78%**ãéè·¯ +9.94%ãæ°èª +11.69%ãéè·¯ +4.99%ãæ°´è·¯ +4.33%ã**åä¸ç«å¡«ä¸å¹³çº¦ 9 个ç¾åç¹çè¶å¿å·®**ï¼ä¸ã让å½çéåé«éè¶å¿ä¸è´ãç颿并ä¸ç¸ç¬¦ï¼å»ºè®®åæ¶è¯·éå·æ ¸å®åå§è®°å½ã |
| | | - **å¾
å³ççä»£ç æ¹æ¡**ï¼å `comparison_base` å·²å®ä¸ºçº¯å¤ç¨ï¼ç³»ç»æ æ³ä»ã彿¥å¼ã忍åç«åºæï¼éè¦å¦è®¾æºå¶ãæ¨èæ°å¢ãå¼å¸¸ç«åé¤ãé
ç½®ï¼ç«ç¹ç¼å· + çææ¥æ + 年份ï¼ï¼ç®ç¯æ¯åæ¯æ¶ç¨ `observation_station_flow` çã该æ¥ç«ç¹å计 â åç«å¼ãä½åºæï¼æ¾ç¤ºå¼ä¸ç´¯è®¡ä»ç¨åå¼ã |
| | | - **å³çç¹**ï¼â åæå¯é
ç½®çéç¨ãå¼å¸¸ç«åé¤ãï¼æ¨èï¼å¯å¤ç¨äºå
¶ä»è忥/ç«ç¹ï¼ï¼è¿æ¯åªææ¬æ¬¡ 10-04 䏿¬¡æ§å¤çï¼â¡ åç«åªå½±åå½çéç¯æ¯åæ¯ï¼è¿æ¯ä¹è¦ä¸æ²å°éè¥ä¸æ§å°å®¢è½¦/å
¬è·¯/å
¨ç¤¾ä¼çç¯æ¯ï¼æå·¥ç¨¿å®é
æ 4.17% / 4.55% ä¹ä¸èµ·æ¹äºï¼ã |
| | | |
| | | ## ææ°ï¼2026-10-05ï¼ |
| | | |
| | | ### 10-05 ä¸åï¼å
³æºå¿«ç
§ï¼å½åç¶æï¼ |
| | |
| | | - **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'; |
| | |
| | | | road_category | varchar(32) | é«éå
¬è·¯ EXPRESSWAY / æ®éå½çé NATIONAL_PROVINCIAL | |
| | | | vehicle_type | varchar(32) | å°å®¢è½¦ SMALL_PASSENGER / ä¸å°å®¢è½¦ MEDIUM_SMALL_PASSENGER | |
| | | | vehicle_count | decimal(30,15) | 车æµéï¼ä¸è¾æ¬¡ï¼ | |
| | | | comparison_base | decimal(30,15) | æ®éå½çéç¯æ¯åºæï¼ä¸è¾æ¬¡ï¼ä»
å½å¹´ä½¿ç¨ï¼ | |
| | | | comparison_base | decimal(30,15) | æ®éå½çéç¯æ¯åºæï¼ä¸è¾æ¬¡ï¼äººå·¥å¤ç¨å¼ï¼ä»
2026 å¹´å¯ç¨ãä¸åä¸è®¡ç®ï¼ | |
| | | | created_at / updated_at | datetime | å建/æ´æ°æ¶é´ | |
| | | |
| | | å¯ä¸é®ï¼`(year, flow_date, road_category, vehicle_type)`ã |
| | |
| | | |
| | | ### 计ç®ä¸å¯¼å
¥å¯¼åºå£å¾ |
| | | - éè¥ä¸æ§äººåæµå¨é =ï¼é«éè½¦æµ + æ®éå½çé车æµï¼Ã å½å¹´è忥客è¿ç³»æ°ã |
| | | - 2026 å¹´æ®éå½çéå¦å¡«åç¯æ¯åºæï¼ååªå½±åæ®éå½çé车æµéèªèº«ç¯æ¯ï¼å¡«åæ¶æâ彿¥ / ç¯æ¯åºæ - 1âï¼æªå¡«åæ¶æâ彿¥ / å䏿¥ - 1âï¼æç®äººåæµå¨éãæ»äººåæµå¨éçç»§ç»æ Excel åå
¬å¼è®¡ç®ã2024ã2025 å岿°æ®ä¸ä½¿ç¨è¯¥å段ã |
| | | - æ®éå½çéç¯æ¯åºå®æâ彿¥ / å䏿¥ - 1â计ç®ï¼2024/2025/2026 åå¹´ä¸è´ã`comparison_base`ï¼Excel æ®éå½çé表 C åâç¯æ¯åºæâï¼èª 2026-10-05 èµ·ä»
ä½äººå·¥å¤ç¨ï¼å¯¼å
¥å¯¼åºåæ ·ä¿å/ååï¼ä¸åä¸ç¯æ¯ãæç®æç´¯è®¡ï¼å¯¼åºæ¶ C åç
§å¸¸ååºï¼E åâç¯æ¯âå
¬å¼å§ç»å¼ç¨ä¸ä¸è¡åå¹´ D åã |
| | | - å
¬è·¯äººåæµå¨é = éè·¯è¥ä¸æ§å®¢è¿é + éè¥ä¸æ§äººåæµå¨éã |
| | | - æ»äººåæµå¨é = å
¬è·¯ + éè·¯ + æ°èª + æ°´è·¯ã |
| | | - 导å
¥æâ年份 + è忥åç±»â读å `holiday_calendar` ä¸ `day_index>=1` çæ¥æï¼å°å·¥ä½ç°¿â第Næ¥âè¡è¦çåå
¥å½å¹´å®¢æµ/车æµï¼åæ¶è¯»åå½å年份éè·¯ãæ°èªçâå䏿¥âè¡ï¼åå
¥ `day_index=0` 对åºçæ¥æã |
| | |
| | | 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.math.BigDecimal; |
| | | import java.time.LocalDate; |
| | | 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)); |
| | | } |
| | | |
| | | @GetMapping("/source-value") |
| | | public Result<BigDecimal> sourceValue(@RequestParam("year") Integer year, |
| | | @RequestParam("flowDate") String flowDate) { |
| | | try { |
| | | return Result.ok(adjustmentService.findOriginalValue(year, LocalDate.parse(flowDate))); |
| | | } catch (Exception e) { |
| | | return Result.error(400, e.getMessage()); |
| | | } |
| | | } |
| | | |
| | | @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.TableField; |
| | | 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; |
| | | @TableField(exist = false) |
| | | private BigDecimal originalValue; |
| | | 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( |
| | | year, nationalProvincial, previousNationalProvincial, comparisonBase); |
| | | 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; |
| | | } |
| | | |
| | | private BigDecimal nationalProvincialRingRatio(int year, BigDecimal current, |
| | | BigDecimal previous, BigDecimal comparisonBase) { |
| | | if (current == null) return null; |
| | | if (HolidayConstants.supportsComparisonBase(year) && comparisonBase != null) { |
| | | return ratio(current, comparisonBase); |
| | | } |
| | | return previous == null ? null : ratio(current, previous); |
| | | } |
| | | |
| | | private BigDecimal ratio(BigDecimal current, BigDecimal base) { |
| | | BigDecimal quotient = HolidayNumberUtils.divide(current, base); |
| | |
| | | import java.lang.reflect.Field; |
| | | import java.lang.reflect.Modifier; |
| | | import java.math.BigDecimal; |
| | | import java.math.RoundingMode; |
| | | import java.text.DecimalFormat; |
| | | import java.text.NumberFormat; |
| | | import java.util.ArrayList; |
| | |
| | | private static final double RIGHT_LABEL_BAND = 74.0; |
| | | private static final double ITEM_LABEL_GAP = 10.0; |
| | | private static final float HALO_OUTLINE_WIDTH = 4.0f; |
| | | private static final int PERCENT_ITEM_LABEL_DECIMALS = 1; |
| | | private static final double LINE_CHART_HEIGHT_SCALE = 1.20; |
| | | private static final Color ITEM_LABEL_COLOR = new Color(55, 55, 55); |
| | | private static final Color[] SERIES_COLORS = { |
| | | new Color(192, 80, 77), |
| | |
| | | |
| | | public byte[] lineChart(String yAxisLabel, List<ChartSeries> series, boolean percentAxis, |
| | | int width, int height) { |
| | | return lineChart(yAxisLabel, series, percentAxis, 2, width, height); |
| | | } |
| | | |
| | | public byte[] lineChart(String yAxisLabel, List<ChartSeries> series, boolean percentAxis, |
| | | int valueDecimals, int width, int height) { |
| | | XYSeriesCollection dataset = new XYSeriesCollection(); |
| | | for (int seriesIndex = 0; seriesIndex < series.size(); seriesIndex++) { |
| | | ChartSeries item = series.get(seriesIndex); |
| | |
| | | for (int i = 0; i < item.getValues().size(); i++) { |
| | | BigDecimal value = item.getValues().get(i); |
| | | if (value != null) { |
| | | xySeries.add(i, value.doubleValue()); |
| | | xySeries.add(i, displayValue(value, percentAxis, valueDecimals).doubleValue()); |
| | | } |
| | | } |
| | | dataset.addSeries(xySeries); |
| | |
| | | |
| | | NumberAxis rangeAxis = new NumberAxis(); |
| | | rangeAxis.setAutoRangeIncludesZero(false); |
| | | rangeAxis.setLowerMargin(0.12); |
| | | rangeAxis.setUpperMargin(0.16); |
| | | rangeAxis.setLowerMargin(0.04); |
| | | rangeAxis.setUpperMargin(0.07); |
| | | rangeAxis.setTickLabelFont(TICK_FONT); |
| | | rangeAxis.setLabelFont(LABEL_FONT); |
| | | rangeAxis.setTickLabelPaint(MUTED_TEXT_COLOR); |
| | |
| | | } |
| | | NumberFormat valueFormat = percentAxis |
| | | ? NumberFormat.getPercentInstance() : new DecimalFormat("#,##0.##"); |
| | | valueFormat.setMaximumFractionDigits(percentAxis ? 0 : 2); |
| | | valueFormat.setMaximumFractionDigits(percentAxis ? 0 : valueDecimals); |
| | | rangeAxis.setNumberFormatOverride(valueFormat); |
| | | plot.setRangeAxis(rangeAxis); |
| | | plot.setAxisOffset(new RectangleInsets(2.0, 2.0, 2.0, 2.0)); |
| | |
| | | BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND)); |
| | | } |
| | | plot.setRenderer(renderer); |
| | | return render(chart, width, height, yAxisLabel, true, percentAxis); |
| | | return render(chart, width, scaleLineChartHeight(height), |
| | | yAxisLabel, true, percentAxis, valueDecimals); |
| | | } |
| | | |
| | | public byte[] pieChart(String valueSuffix, List<ChartCategory> categories, |
| | |
| | | for (ChartCategory category : categories) { |
| | | plot.setSectionPaint(category.getName(), seriesColor(index++)); |
| | | } |
| | | return render(chart, width, height, valueSuffix, false, false); |
| | | return render(chart, width, height, valueSuffix, false, false, 2); |
| | | } |
| | | |
| | | public byte[] stackedBarChart(String yAxisLabel, List<String> categories, |
| | |
| | | renderer.setSeriesPaint(i, seriesColor(i)); |
| | | } |
| | | plot.setRenderer(renderer); |
| | | return render(chart, width, height, yAxisLabel, true, percentAxis); |
| | | return render(chart, width, height, yAxisLabel, true, percentAxis, 2); |
| | | } |
| | | |
| | | private static class WaterRightPiePlot<K extends Comparable<K>> extends PiePlot<K> { |
| | |
| | | } |
| | | |
| | | private byte[] render(JFreeChart chart, int width, int height, String unitLabel, |
| | | boolean axisChart, boolean percentItemLabels) { |
| | | boolean axisChart, boolean percentItemLabels, int valueDecimals) { |
| | | try { |
| | | reserveUnitLabelBand(chart, axisChart, unitLabel); |
| | | ByteArrayOutputStream out = new ByteArrayOutputStream(); |
| | |
| | | drawDateLabel(graphics, info.getPlotInfo().getDataArea()); |
| | | } |
| | | drawXYItemLabels(graphics, image, chart, |
| | | info.getPlotInfo().getDataArea(), percentItemLabels, unitLabel); |
| | | info.getPlotInfo().getDataArea(), percentItemLabels, unitLabel, valueDecimals); |
| | | drawStackedBarLabels(graphics, image, chart, |
| | | info.getPlotInfo().getDataArea(), percentItemLabels); |
| | | } finally { |
| | |
| | | |
| | | private void drawXYItemLabels(Graphics2D graphics, BufferedImage image, JFreeChart chart, |
| | | Rectangle2D dataArea, boolean percentItemLabels, |
| | | String unitLabel) { |
| | | String unitLabel, int valueDecimals) { |
| | | if (!(chart.getPlot() instanceof XYPlot) || dataArea == null |
| | | || dataArea.getWidth() <= 0 || dataArea.getHeight() <= 0) { |
| | | return; |
| | |
| | | Point2D point = itemPoint(plot, dataset, seriesIndex, itemIndex, dataArea); |
| | | if (yValue == null || point == null) continue; |
| | | labels.add(new DayLabel(seriesIndex, point, |
| | | formatLineLabel(yValue, percentItemLabels))); |
| | | formatLineLabel(yValue, percentItemLabels, valueDecimals))); |
| | | } |
| | | // A day's labels are stacked in the same top-to-bottom order as its data |
| | | // points, so a label never looks attached to a neighbouring line. |
| | |
| | | return total; |
| | | } |
| | | |
| | | private String formatLineLabel(Number value, boolean percentItemLabels) { |
| | | static int scaleLineChartHeight(int height) { |
| | | return (int) Math.round(height * LINE_CHART_HEIGHT_SCALE); |
| | | } |
| | | |
| | | static long scaleLineChartHeight(long height) { |
| | | return Math.round(height * LINE_CHART_HEIGHT_SCALE); |
| | | } |
| | | |
| | | static BigDecimal displayValue(BigDecimal value, boolean percentItemLabels, int valueDecimals) { |
| | | if (value == null) return null; |
| | | int scale = percentItemLabels ? PERCENT_ITEM_LABEL_DECIMALS + 2 : valueDecimals; |
| | | return value.setScale(scale, RoundingMode.HALF_UP); |
| | | } |
| | | |
| | | private String formatLineLabel(Number value, boolean percentItemLabels, int valueDecimals) { |
| | | if (percentItemLabels) { |
| | | NumberFormat formatter = NumberFormat.getPercentInstance(); |
| | | formatter.setMaximumFractionDigits(1); |
| | | formatter.setMaximumFractionDigits(PERCENT_ITEM_LABEL_DECIMALS); |
| | | return formatter.format(value.doubleValue()); |
| | | } |
| | | return new DecimalFormat("#,##0.##").format(value.doubleValue()); |
| | | DecimalFormat formatter = new DecimalFormat("#,##0"); |
| | | formatter.setMaximumFractionDigits(valueDecimals); |
| | | return formatter.format(value.doubleValue()); |
| | | } |
| | | |
| | | private void drawLabelWithHalo(Graphics2D graphics, String text, Rectangle2D rectangle, |
| | |
| | | prefix + "å
¨çè·¨åºåäººåæµå¨éæ
åµ", |
| | | chartService.lineChart("ä¸äººæ¬¡", |
| | | series(years, yearData, item -> item.totalDaily), false, 1100, 715), |
| | | new HolidayTrendWordService.ImageSize(5268595L, 3424555L))); |
| | | lineImageSize(5268595L, 3424555L))); |
| | | blocks.add(new HolidayTrendWordService.ChartBlock( |
| | | "â" + holidayLabel + "âåæç´¯è®¡è·¨åºåäººåæµå¨éèµ°å¿", |
| | | chartService.lineChart("ä¸äººæ¬¡", |
| | | series(years, yearData, item -> item.totalCumulative), false, 1100, 590), |
| | | new HolidayTrendWordService.ImageSize(5268595L, 2821305L))); |
| | | lineImageSize(5268595L, 2821305L))); |
| | | blocks.add(new HolidayTrendWordService.ChartBlock( |
| | | "â" + holidayLabel + "âåæå交éè¿è¾æ¹å¼å®¢è¿éå æ¯æ
åµ", |
| | | chartService.pieChart("人次", selected.modalShare(), 1000, 607), true, |
| | |
| | | "â" + holidayLabel + "âåæèªé©¾åºè¡éæ
åµ", |
| | | chartService.lineChart("ä¸äººæ¬¡", |
| | | series(years, yearData, item -> item.selfDrivingPersonFlow), false, 1100, 522), |
| | | new HolidayTrendWordService.ImageSize(5232400L, 2484120L))); |
| | | lineImageSize(5232400L, 2484120L))); |
| | | blocks.add(new HolidayTrendWordService.ChartBlock( |
| | | "â" + holidayLabel + "âåæé路客è¿éæ
åµ", |
| | | chartService.lineChart("ä¸äººæ¬¡", |
| | | series(years, yearData, item -> item.rail), false, 1100, 445), |
| | | new HolidayTrendWordService.ImageSize(5264785L, 2129155L))); |
| | | lineImageSize(5264785L, 2129155L))); |
| | | blocks.add(new HolidayTrendWordService.ChartBlock( |
| | | "â" + holidayLabel + "âåææ°´è·¯å®¢è¿éæ
åµ", |
| | | chartService.lineChart("ä¸äººæ¬¡", |
| | | series(years, yearData, item -> item.water), false, 1100, 433), |
| | | new HolidayTrendWordService.ImageSize(5274310L, 2075815L))); |
| | | lineImageSize(5274310L, 2075815L))); |
| | | blocks.add(new HolidayTrendWordService.ChartBlock( |
| | | "â" + holidayLabel + "âåææ°èªå®¢è¿éæ
åµ", |
| | | chartService.lineChart("ä¸äººæ¬¡", |
| | | series(years, yearData, item -> item.civilAviation), false, 1100, 521), |
| | | new HolidayTrendWordService.ImageSize(5271135L, 2496185L))); |
| | | lineImageSize(5271135L, 2496185L))); |
| | | blocks.add(new HolidayTrendWordService.ChartBlock( |
| | | "â" + holidayLabel + "âåæé路客è¿éæ
åµ", |
| | | chartService.lineChart("ä¸äººæ¬¡", |
| | | series(years, yearData, item -> item.road), false, 1100, 590), |
| | | new HolidayTrendWordService.ImageSize(5268595L, 2821305L))); |
| | | lineImageSize(5268595L, 2821305L))); |
| | | |
| | | blocks.add(new HolidayTrendWordService.SectionBlock("ä¸ã车æµéèµ°å¿")); |
| | | blocks.add(new HolidayTrendWordService.ChartBlock( |
| | | "â" + holidayLabel + "âåæé«éå
¬è·¯è½¦æµéæ
åµ", |
| | | chartService.lineChart("ä¸è¾æ¬¡", |
| | | series(years, yearData, item -> item.expressway), false, 1100, 590), |
| | | new HolidayTrendWordService.ImageSize(5268595L, 2821305L))); |
| | | lineImageSize(5268595L, 2821305L))); |
| | | blocks.add(new HolidayTrendWordService.ChartBlock( |
| | | "â" + holidayLabel + "âåææ®éå½çé车æµéæ
åµ", |
| | | chartService.lineChart("ä¸è¾æ¬¡", |
| | | series(years, yearData, item -> item.nationalProvincial), false, 1100, 590), |
| | | new HolidayTrendWordService.ImageSize(5268595L, 2821305L))); |
| | | lineImageSize(5268595L, 2821305L))); |
| | | blocks.add(new HolidayTrendWordService.ChartBlock( |
| | | "â" + holidayLabel + "âåæå
¨çè½¦æµæ»éæ
åµ", |
| | | chartService.lineChart("ä¸è¾æ¬¡", |
| | | series(years, yearData, item -> item.totalVehicleFlow), false, 1100, 590), |
| | | new HolidayTrendWordService.ImageSize(5268595L, 2821305L))); |
| | | lineImageSize(5268595L, 2821305L))); |
| | | blocks.add(new HolidayTrendWordService.ChartBlock( |
| | | "â" + holidayLabel + "âåæå
¬è·¯åºè¡ç»æ", |
| | | chartService.stackedBarChart("ä¸äººæ¬¡", |
| | |
| | | prefix + "æ®éå½çéè§æµç«å¹³åæºå¨è½¦é度", |
| | | chartService.lineChart("å
¬é/å°æ¶", |
| | | Collections.singletonList(series("å¹³åé度", observation.speed)), false, 1100, 590), |
| | | new HolidayTrendWordService.ImageSize(5268595L, 2821305L))); |
| | | lineImageSize(5268595L, 2821305L))); |
| | | blocks.add(new HolidayTrendWordService.ChartBlock( |
| | | prefix + "æ®éå½çéè§æµç«å¹³åæ¥æ¤åº¦", |
| | | chartService.lineChart("æ¥æ¤åº¦", |
| | | Collections.singletonList(series("平忥æ¤åº¦", observation.congestion)), false, 1100, 590), |
| | | new HolidayTrendWordService.ImageSize(5268595L, 2821305L))); |
| | | lineImageSize(5268595L, 2821305L))); |
| | | |
| | | File template = resolveTemplateFile(); |
| | | if (!template.exists() || !template.isFile()) { |
| | |
| | | return result; |
| | | } |
| | | |
| | | private HolidayTrendWordService.ImageSize lineImageSize(long width, long height) { |
| | | return new HolidayTrendWordService.ImageSize( |
| | | width, HolidayTrendChartService.scaleLineChartHeight(height)); |
| | | } |
| | | |
| | | private File resolveTemplateFile() { |
| | | List<File> candidates = new ArrayList<>(); |
| | | File configured = new File(trendTemplatePath); |
| New file |
| | |
| | | package com.trafficaudit.holiday.service; |
| | | |
| | | import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; |
| | | import com.trafficaudit.holiday.HolidayConstants; |
| | | import com.trafficaudit.holiday.HolidayNumberUtils; |
| | | import com.trafficaudit.holiday.entity.HolidayVehicleAdjustment; |
| | | import com.trafficaudit.holiday.entity.HolidayVehicleFlow; |
| | | import com.trafficaudit.holiday.mapper.HolidayVehicleFlowMapper; |
| | | 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.HashMap; |
| | | import java.util.HashSet; |
| | | import java.util.List; |
| | | import java.util.Map; |
| | | import java.util.Set; |
| | | |
| | | @Service |
| | | public class HolidayVehicleAdjustmentService { |
| | | |
| | | private static final int REASON_MAX_LENGTH = 500; |
| | | |
| | | @Resource |
| | | private HolidayVehicleAdjustmentMapper adjustmentMapper; |
| | | @Resource |
| | | private HolidayVehicleFlowMapper vehicleFlowMapper; |
| | | |
| | | public List<HolidayVehicleAdjustment> list(Integer year) { |
| | | List<HolidayVehicleAdjustment> rows = adjustmentMapper.selectList( |
| | | new LambdaQueryWrapper<HolidayVehicleAdjustment>() |
| | | .eq(year != null, HolidayVehicleAdjustment::getYear, year) |
| | | .orderByDesc(HolidayVehicleAdjustment::getYear) |
| | | .orderByAsc(HolidayVehicleAdjustment::getFlowDate)); |
| | | if (rows.isEmpty()) return rows; |
| | | |
| | | Set<Integer> years = new HashSet<>(); |
| | | for (HolidayVehicleAdjustment row : rows) { |
| | | if (row.getYear() != null) years.add(row.getYear()); |
| | | } |
| | | if (years.isEmpty()) return rows; |
| | | |
| | | List<HolidayVehicleFlow> sourceRows = vehicleFlowMapper.selectList( |
| | | new LambdaQueryWrapper<HolidayVehicleFlow>() |
| | | .eq(HolidayVehicleFlow::getRoadCategory, HolidayConstants.ROAD_NATIONAL_PROVINCIAL) |
| | | .eq(HolidayVehicleFlow::getVehicleType, HolidayConstants.VEHICLE_MEDIUM_SMALL_PASSENGER) |
| | | .in(HolidayVehicleFlow::getYear, years)); |
| | | Map<String, BigDecimal> originalValues = new HashMap<>(); |
| | | for (HolidayVehicleFlow sourceRow : sourceRows) { |
| | | if (sourceRow.getYear() == null || sourceRow.getFlowDate() == null || sourceRow.getVehicleCount() == null) { |
| | | continue; |
| | | } |
| | | originalValues.put(sourceKey(sourceRow.getYear(), sourceRow.getFlowDate()), sourceRow.getVehicleCount()); |
| | | } |
| | | for (HolidayVehicleAdjustment row : rows) { |
| | | if (row.getYear() != null && row.getFlowDate() != null) { |
| | | row.setOriginalValue(originalValues.get(sourceKey(row.getYear(), row.getFlowDate()))); |
| | | } |
| | | } |
| | | return rows; |
| | | } |
| | | |
| | | public BigDecimal findOriginalValue(Integer year, LocalDate flowDate) { |
| | | if (year == null || flowDate == null || flowDate.getYear() != year) { |
| | | throw new IllegalArgumentException("年份忥æä¸æ£ç¡®"); |
| | | } |
| | | HolidayVehicleFlow sourceRow = vehicleFlowMapper.selectOne( |
| | | new LambdaQueryWrapper<HolidayVehicleFlow>() |
| | | .eq(HolidayVehicleFlow::getYear, year) |
| | | .eq(HolidayVehicleFlow::getFlowDate, flowDate) |
| | | .eq(HolidayVehicleFlow::getRoadCategory, HolidayConstants.ROAD_NATIONAL_PROVINCIAL) |
| | | .eq(HolidayVehicleFlow::getVehicleType, HolidayConstants.VEHICLE_MEDIUM_SMALL_PASSENGER) |
| | | .last("limit 1")); |
| | | return sourceRow == null ? null : sourceRow.getVehicleCount(); |
| | | } |
| | | |
| | | @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 String sourceKey(Integer year, LocalDate flowDate) { |
| | | return year + "|" + flowDate; |
| | | } |
| | | |
| | | 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(); |
| | | } |
| | | } |
| | |
| | | setNumeric(sheet, row.getRowNum(), yearColumn, values.get(day.getValue())); |
| | | } |
| | | if (comparisonBaseColumn != null && HolidayConstants.supportsComparisonBase(year)) { |
| | | // Column C (comparison base) is written as an operator backup value only. The ring |
| | | // ratio in column E always compares the current day with the previous day row of |
| | | // the same year, so the base never changes the calculated percentage. |
| | | Map<LocalDate, BigDecimal> bases = loadComparisonBaseValues(year); |
| | | for (Map.Entry<Integer, LocalDate> day : dayDates.entrySet()) { |
| | | if (!bases.containsKey(day.getValue())) continue; |
| | |
| | | if (row == null) continue; |
| | | setNumeric(sheet, row.getRowNum(), comparisonBaseColumn, bases.get(day.getValue())); |
| | | } |
| | | Integer previousRowIndex = null; |
| | | for (Map.Entry<Integer, LocalDate> day : dayDates.entrySet()) { |
| | | if (!bases.containsKey(day.getValue())) continue; |
| | | Row row = findDayRow(sheet, evaluator, day.getKey()); |
| | | if (row == null) continue; |
| | | int rowIndex = row.getRowNum(); |
| | | int excelRow = rowIndex + 1; |
| | | setFormula(sheet, rowIndex, 4, "=(D" + excelRow + "-C" + excelRow + ")/C" + excelRow); |
| | | if (row == null) { |
| | | previousRowIndex = null; |
| | | continue; |
| | | } |
| | | if (previousRowIndex != null) { |
| | | setFormula(sheet, row.getRowNum(), 4, |
| | | ringRatioFormula(row.getRowNum() + 1, previousRowIndex + 1)); |
| | | } |
| | | previousRowIndex = row.getRowNum(); |
| | | } |
| | | } |
| | | } |
| | | } |
| | | |
| | | // Ring ratio = (current day value - previous day value) / previous day value, always taken |
| | | // from the active year column (D). This intentionally ignores the comparison base column. |
| | | static String ringRatioFormula(int excelRow, int previousExcelRow) { |
| | | return "=(D" + excelRow + "-D" + previousExcelRow + ")/D" + previousExcelRow; |
| | | } |
| | | |
| | | private void fillCoefficients(XSSFWorkbook workbook, FormulaEvaluator evaluator, int selectedYear, String holidayType) { |
| | |
| | | if (row == null) row = sheet.createRow(rowIndex); |
| | | Cell cell = row.getCell(columnIndex); |
| | | if (cell == null) cell = row.createCell(columnIndex); |
| | | cell.setCellFormula(formula); |
| | | // POI's setCellFormula rejects a leading '='; the helper accepts either form. |
| | | String normalized = formula.startsWith("=") ? formula.substring(1) : formula; |
| | | cell.setCellFormula(normalized); |
| | | } |
| | | |
| | | private Map<String, Object> toMap(HolidayCalendar calendar) { |
| New file |
| | |
| | | package com.trafficaudit.holiday.service; |
| | | |
| | | import com.trafficaudit.holiday.HolidayConstants; |
| | | 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.lang.reflect.Field; |
| | | 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; |
| | | |
| | | /** |
| | | * 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 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(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); |
| | | 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 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(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 roadCategory, String value, String base) { |
| | | HolidayVehicleFlow row = new HolidayVehicleFlow(); |
| | | row.setYear(LocalDate.parse(date).getYear()); |
| | | row.setFlowDate(LocalDate.parse(date)); |
| | | 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<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(year); |
| | | day.setHolidayType(HolidayConstants.TYPE_NATIONAL_DAY); |
| | | day.setDayIndex(i); |
| | | day.setHolidayDate(LocalDate.of(year, 10, i)); |
| | | days.add(day); |
| | | } |
| | | return days; |
| | | } |
| | | |
| | | private static void inject(Object target, String field, Object value) throws Exception { |
| | | Field declared = target.getClass().getDeclaredField(field); |
| | | declared.setAccessible(true); |
| | | declared.set(target, value); |
| | | } |
| | | } |
| | |
| | | import javax.imageio.ImageIO; |
| | | import java.awt.image.BufferedImage; |
| | | import java.io.ByteArrayInputStream; |
| | | import java.math.BigDecimal; |
| | | |
| | | import static org.junit.jupiter.api.Assertions.assertArrayEquals; |
| | | import static org.junit.jupiter.api.Assertions.assertEquals; |
| | | import static org.junit.jupiter.api.Assertions.assertFalse; |
| | | import static org.junit.jupiter.api.Assertions.assertNotNull; |
| | | import static org.junit.jupiter.api.Assertions.assertTrue; |
| | | |
| | |
| | | assertNoLargeLightGrayBackground(stacked); |
| | | } |
| | | |
| | | @Test |
| | | void roundsLineDataToTheSamePrecisionAsLabels() { |
| | | assertEquals(new BigDecimal("0.49"), |
| | | HolidayTrendChartService.displayValue(new BigDecimal("0.4904"), false, 2)); |
| | | assertEquals(new BigDecimal("0.49"), |
| | | HolidayTrendChartService.displayValue(new BigDecimal("0.4911"), false, 2)); |
| | | assertEquals(new BigDecimal("0.491"), |
| | | HolidayTrendChartService.displayValue(new BigDecimal("0.4911"), false, 3)); |
| | | assertEquals(new BigDecimal("0.123"), |
| | | HolidayTrendChartService.displayValue(new BigDecimal("0.1234"), true, 2)); |
| | | } |
| | | |
| | | @Test |
| | | void rendersSameGeometryWhenValuesRoundToSameLabel() { |
| | | HolidayTrendChartService service = new HolidayTrendChartService(); |
| | | HolidayTrendChartService.ChartSeries first = new HolidayTrendChartService.ChartSeries(); |
| | | first.setName("平忥æ¤åº¦"); |
| | | first.setValues(java.util.Arrays.asList( |
| | | new BigDecimal("0.4904"), new BigDecimal("0.4923"), new BigDecimal("0.5100"))); |
| | | HolidayTrendChartService.ChartSeries second = new HolidayTrendChartService.ChartSeries(); |
| | | second.setName("平忥æ¤åº¦"); |
| | | second.setValues(java.util.Arrays.asList( |
| | | new BigDecimal("0.4911"), new BigDecimal("0.4934"), new BigDecimal("0.5100"))); |
| | | |
| | | byte[] firstChart = service.lineChart("æ¥æ¤åº¦", |
| | | java.util.Collections.singletonList(first), false, 2, 1100, 590); |
| | | byte[] secondChart = service.lineChart("æ¥æ¤åº¦", |
| | | java.util.Collections.singletonList(second), false, 2, 1100, 590); |
| | | |
| | | assertPng(firstChart); |
| | | assertPng(secondChart); |
| | | assertArrayEquals(firstChart, secondChart); |
| | | } |
| | | |
| | | @Test |
| | | void scalesLineChartHeightForBetterVerticalSeparation() throws Exception { |
| | | HolidayTrendChartService service = new HolidayTrendChartService(); |
| | | HolidayTrendChartService.ChartSeries series = new HolidayTrendChartService.ChartSeries(); |
| | | series.setName("æµè¯"); |
| | | series.setValues(java.util.Arrays.asList( |
| | | new BigDecimal("1.00"), new BigDecimal("2.00"), new BigDecimal("3.00"))); |
| | | |
| | | byte[] png = service.lineChart("ä¸äººæ¬¡", |
| | | java.util.Collections.singletonList(series), false, 1100, 590); |
| | | BufferedImage image = ImageIO.read(new ByteArrayInputStream(png)); |
| | | |
| | | assertNotNull(image); |
| | | assertEquals(HolidayTrendChartService.scaleLineChartHeight(590), image.getHeight()); |
| | | } |
| | | |
| | | @Test |
| | | void rendersHigherPrecisionLineLabelsWhenRequested() { |
| | | HolidayTrendChartService service = new HolidayTrendChartService(); |
| | | HolidayTrendChartService.ChartSeries congestion = new HolidayTrendChartService.ChartSeries(); |
| | | congestion.setName("平忥æ¤åº¦"); |
| | | congestion.setValues(java.util.Arrays.asList( |
| | | new java.math.BigDecimal("0.467029"), |
| | | new java.math.BigDecimal("0.490890"), |
| | | new java.math.BigDecimal("0.492414"))); |
| | | |
| | | byte[] twoDecimals = service.lineChart("æ¥æ¤åº¦", |
| | | java.util.Collections.singletonList(congestion), false, 2, 1100, 590); |
| | | byte[] threeDecimals = service.lineChart("æ¥æ¤åº¦", |
| | | java.util.Collections.singletonList(congestion), false, 3, 1100, 590); |
| | | |
| | | assertPng(twoDecimals); |
| | | assertPng(threeDecimals); |
| | | assertFalse(java.util.Arrays.equals(twoDecimals, threeDecimals)); |
| | | } |
| | | |
| | | private void assertNoLargeLightGrayBackground(byte[] data) throws Exception { |
| | | BufferedImage image = ImageIO.read(new ByteArrayInputStream(data)); |
| | | int matches = 0; |
| New file |
| | |
| | | package com.trafficaudit.holiday.service; |
| | | |
| | | import com.trafficaudit.holiday.HolidayConstants; |
| | | import com.trafficaudit.holiday.entity.HolidayVehicleAdjustment; |
| | | import com.trafficaudit.holiday.entity.HolidayVehicleFlow; |
| | | import com.trafficaudit.holiday.mapper.HolidayVehicleAdjustmentMapper; |
| | | import com.trafficaudit.holiday.mapper.HolidayVehicleFlowMapper; |
| | | import org.junit.jupiter.api.Test; |
| | | |
| | | import java.lang.reflect.Field; |
| | | import java.math.BigDecimal; |
| | | import java.time.LocalDate; |
| | | import java.util.Arrays; |
| | | import java.util.Collections; |
| | | import java.util.List; |
| | | |
| | | import static org.junit.jupiter.api.Assertions.assertEquals; |
| | | import static org.junit.jupiter.api.Assertions.assertNull; |
| | | import static org.junit.jupiter.api.Assertions.assertThrows; |
| | | import static org.mockito.ArgumentMatchers.any; |
| | | import static org.mockito.Mockito.mock; |
| | | import static org.mockito.Mockito.when; |
| | | |
| | | class HolidayVehicleAdjustmentServiceTest { |
| | | |
| | | @Test |
| | | void listAddsOriginalNationalProvincialValueAndLeavesMissingSourceBlank() throws Exception { |
| | | HolidayVehicleAdjustment adjusted = adjustment("2025-10-04", "337.5009"); |
| | | HolidayVehicleAdjustment missingSource = adjustment("2025-10-05", "330.0000"); |
| | | |
| | | HolidayVehicleAdjustmentMapper adjustmentMapper = mock(HolidayVehicleAdjustmentMapper.class); |
| | | HolidayVehicleFlowMapper vehicleFlowMapper = mock(HolidayVehicleFlowMapper.class); |
| | | when(adjustmentMapper.selectList(any())).thenReturn(Arrays.asList(adjusted, missingSource)); |
| | | when(vehicleFlowMapper.selectList(any())).thenReturn(Collections.singletonList( |
| | | vehicleFlow("2025-10-04", HolidayConstants.ROAD_NATIONAL_PROVINCIAL, "350.0000"))); |
| | | |
| | | HolidayVehicleAdjustmentService service = new HolidayVehicleAdjustmentService(); |
| | | inject(service, "adjustmentMapper", adjustmentMapper); |
| | | inject(service, "vehicleFlowMapper", vehicleFlowMapper); |
| | | |
| | | List<HolidayVehicleAdjustment> rows = service.list(2025); |
| | | |
| | | assertEquals(0, new BigDecimal("350.0000").compareTo(rows.get(0).getOriginalValue())); |
| | | assertNull(rows.get(1).getOriginalValue()); |
| | | } |
| | | |
| | | @Test |
| | | void findOriginalValueReturnsMatchedValueAndNullWhenMissing() throws Exception { |
| | | HolidayVehicleFlowMapper vehicleFlowMapper = mock(HolidayVehicleFlowMapper.class); |
| | | when(vehicleFlowMapper.selectOne(any())) |
| | | .thenReturn(vehicleFlow("2025-10-04", HolidayConstants.ROAD_NATIONAL_PROVINCIAL, "337.9501")) |
| | | .thenReturn(null); |
| | | |
| | | HolidayVehicleAdjustmentService service = new HolidayVehicleAdjustmentService(); |
| | | inject(service, "vehicleFlowMapper", vehicleFlowMapper); |
| | | |
| | | assertEquals(0, new BigDecimal("337.9501") |
| | | .compareTo(service.findOriginalValue(2025, LocalDate.of(2025, 10, 4)))); |
| | | assertNull(service.findOriginalValue(2025, LocalDate.of(2025, 10, 5))); |
| | | assertThrows(IllegalArgumentException.class, |
| | | () -> service.findOriginalValue(2025, LocalDate.of(2024, 10, 4))); |
| | | } |
| | | private static HolidayVehicleAdjustment adjustment(String date, String adjustedValue) { |
| | | HolidayVehicleAdjustment row = new HolidayVehicleAdjustment(); |
| | | row.setYear(LocalDate.parse(date).getYear()); |
| | | row.setFlowDate(LocalDate.parse(date)); |
| | | row.setAdjustedValue(new BigDecimal(adjustedValue)); |
| | | return row; |
| | | } |
| | | |
| | | private static HolidayVehicleFlow vehicleFlow(String date, String roadCategory, String value) { |
| | | HolidayVehicleFlow row = new HolidayVehicleFlow(); |
| | | row.setYear(LocalDate.parse(date).getYear()); |
| | | row.setFlowDate(LocalDate.parse(date)); |
| | | row.setRoadCategory(roadCategory); |
| | | row.setVehicleCount(new BigDecimal(value)); |
| | | return row; |
| | | } |
| | | |
| | | private static void inject(Object target, String fieldName, Object value) throws Exception { |
| | | Field field = target.getClass().getDeclaredField(fieldName); |
| | | field.setAccessible(true); |
| | | field.set(target, value); |
| | | } |
| | | } |
| | |
| | | |
| | | <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="240" align="right"> |
| | | <template slot-scope="s">{{ formatOriginalAdjustmentValue(s.row.originalValue) }}</template> |
| | | </el-table-column> |
| | | <el-table-column label="è°æ´åå½çé车æµéï¼ä¸è¾æ¬¡ï¼" width="240" 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="220px"> |
| | | <el-form-item label="年份"> |
| | | <el-select v-model="adjustmentForm.year" style="width:100%" @change="onAdjustmentYearChange"> |
| | | <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="éæ©æ¥æ" |
| | | @change="onAdjustmentDateChange"></el-date-picker> |
| | | </el-form-item> |
| | | <el-form-item label="è°æ´åå½çé车æµéï¼ä¸è¾æ¬¡ï¼"> |
| | | <el-input :value="formatOriginalAdjustmentValue(adjustmentForm.originalValue)" readonly |
| | | :placeholder="adjustmentOriginalLoading ? 'æ¥è¯¢ä¸...' : ''"></el-input> |
| | | </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, |
| | | adjustmentOriginalLoading: false, |
| | | adjustmentOriginalRequestId: 0, |
| | | adjustmentForm: { |
| | | id: null, |
| | | year: new Date().getFullYear() - 1, |
| | | flowDate: '', |
| | | originalValue: null, |
| | | 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 || '', |
| | | originalValue: source.originalValue === undefined || source.originalValue === null |
| | | ? null : Number(source.originalValue), |
| | | adjustedValue: source.adjustedValue === undefined || source.adjustedValue === null |
| | | ? null : Number(source.adjustedValue), |
| | | reason: source.reason || '' |
| | | } |
| | | this.adjustmentDialogVisible = true |
| | | this.loadAdjustmentOriginalValue() |
| | | }, |
| | | onAdjustmentYearChange() { |
| | | this.adjustmentOriginalRequestId += 1 |
| | | this.adjustmentOriginalLoading = false |
| | | this.adjustmentForm.flowDate = '' |
| | | this.adjustmentForm.originalValue = null |
| | | }, |
| | | onAdjustmentDateChange() { |
| | | this.loadAdjustmentOriginalValue() |
| | | }, |
| | | loadAdjustmentOriginalValue() { |
| | | const year = this.adjustmentForm.year |
| | | const flowDate = this.adjustmentForm.flowDate |
| | | const requestId = ++this.adjustmentOriginalRequestId |
| | | this.adjustmentForm.originalValue = null |
| | | if (!year || !flowDate) { |
| | | this.adjustmentOriginalLoading = false |
| | | return |
| | | } |
| | | this.adjustmentOriginalLoading = true |
| | | api.get('/holiday/adjustments/source-value', { params: { year: year, flowDate: flowDate } }) |
| | | .then(res => { |
| | | if (requestId !== this.adjustmentOriginalRequestId) return |
| | | if (res.code !== 200) throw new Error(res.message || 'è°æ´åå¼å 载失败') |
| | | this.adjustmentForm.originalValue = res.data === null || res.data === undefined || res.data === '' |
| | | ? null : Number(res.data) |
| | | }) |
| | | .catch(err => { |
| | | if (requestId !== this.adjustmentOriginalRequestId) return |
| | | this.adjustmentForm.originalValue = null |
| | | this.showError(err, 'è°æ´åå¼å 载失败') |
| | | }) |
| | | .finally(() => { |
| | | if (requestId === this.adjustmentOriginalRequestId) this.adjustmentOriginalLoading = false |
| | | }) |
| | | }, |
| | | 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) |
| | | }, |
| | | formatOriginalAdjustmentValue(value) { |
| | | if (value === null || value === undefined || value === '') return '' |
| | | return this.formatAdjustmentValue(value) |
| | | }, |
| | | 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) |