Compare commits

..

371 Commits

Author SHA1 Message Date
Hargata Softworks
bd7cbffe10 Merge pull request #741 from hargata/Hargata/60
SimplyAuto Import
2024-11-25 09:27:24 -07:00
DESKTOP-T0O5CDB\DESK-555BD
b293882f75 add more column aliases. 2024-11-25 09:26:39 -07:00
DESKTOP-T0O5CDB\DESK-555BD
0984b2049b Added support for SimplyAuto Fuel Import. 2024-11-25 09:18:10 -07:00
Hargata Softworks
d4ec4989e7 Merge pull request #740 from hargata/Hargata/739
Refactored code in ConfigHelper.
2024-11-25 08:42:28 -07:00
DESKTOP-T0O5CDB\DESK-555BD
230433f784 Refactored a few more methods that rely on the IConfiguration object to use IConfigHelper instead. 2024-11-25 08:40:14 -07:00
DESKTOP-T0O5CDB\DESK-555BD
d4dda481be pattern. 2024-11-25 08:18:14 -07:00
DESKTOP-T0O5CDB\DESK-555BD
1d3b94f544 Refactored code in ConfigHelper. 2024-11-25 08:14:56 -07:00
Hargata Softworks
78434f0016 Merge pull request #737 from hargata/Hargata/overhaul.reminder.tab
Reminder Record Tab overhaul.
2024-11-24 20:42:37 -07:00
DESKTOP-T0O5CDB\DESK-555BD
c75145de49 Reminder Record Tab overhaul. 2024-11-24 20:19:48 -07:00
Hargata Softworks
5e3480f9f3 Merge pull request #736 from hargata/Hargata/context.menu.remaster
Remaster the context menu
2024-11-24 14:41:35 -07:00
DESKTOP-T0O5CDB\DESK-555BD
f74fdc8ed4 context menu remaster for both notes and reminders. 2024-11-24 14:40:34 -07:00
DESKTOP-T0O5CDB\DESK-555BD
9ba62f8bf8 prevent multiple gas record edits if selected ids is less than 2 2024-11-24 13:48:30 -07:00
DESKTOP-T0O5CDB\DESK-555BD
f2b9dea2b6 Added context menu to plan records. 2024-11-24 13:47:01 -07:00
DESKTOP-T0O5CDB\DESK-555BD
f92aa22375 Remaster the context menu 2024-11-23 22:22:16 -07:00
Hargata Softworks
d65001f412 Merge pull request #735 from hargata/Hargata/costtable.row.highlight
added row highlighting when viewing cost tables.
2024-11-23 15:34:37 -07:00
DESKTOP-T0O5CDB\DESK-555BD
9ea223cd0a added row highlighting when viewing cost tables. 2024-11-23 15:34:07 -07:00
Hargata Softworks
addf73bc27 Merge pull request #734 from hargata/Hargata/fix.aggregate.label
Fuel Consumption label now updates based on fuel consumption unit sel…
2024-11-23 11:32:32 -07:00
DESKTOP-T0O5CDB\DESK-555BD
3f853d0d5e Fuel Consumption label now updates based on fuel consumption unit selected. 2024-11-23 11:31:48 -07:00
Hargata Softworks
4c46278be1 Merge pull request #733 from hargata/Hargata/select.mode
Added select mode.
2024-11-22 14:06:53 -07:00
DESKTOP-T0O5CDB\DESK-555BD
0edc51083c Added select mode. 2024-11-22 14:04:36 -07:00
Hargata Softworks
d906bec333 Merge pull request #732 from hargata/Hargata/fix.demo
more minor changes.
2024-11-22 10:57:17 -07:00
DESKTOP-T0O5CDB\DESK-555BD
a47c4942b7 more minor changes. 2024-11-22 10:56:53 -07:00
Hargata Softworks
cd8df9e938 Merge pull request #731 from hargata/Hargata/fix.demo
Update the demo db.
2024-11-22 10:53:32 -07:00
DESKTOP-T0O5CDB\DESK-555BD
5c37f98274 Update the demo db. 2024-11-22 10:51:08 -07:00
Hargata Softworks
f452436dbd Merge pull request #730 from hargata/Hargata/523
Sort notes in ascending order by description
2024-11-22 10:27:14 -07:00
DESKTOP-T0O5CDB\DESK-555BD
9375bb103b spacing. 2024-11-22 10:25:40 -07:00
DESKTOP-T0O5CDB\DESK-555BD
3222579cb4 allow notes to be sorted in ascending order as well as minor code clean up. 2024-11-22 10:20:53 -07:00
Hargata Softworks
4348b50e54 Merge pull request #728 from hargata/Hargata/716
additional enhancements
2024-11-21 23:15:52 -07:00
DESKTOP-T0O5CDB\DESK-555BD
3b7c58b6e8 additional enhancements 2024-11-21 23:15:05 -07:00
Hargata Softworks
664b89a5df Merge pull request #727 from hargata/Hargata/716
Added functionality to display multi year trends.
2024-11-21 22:14:45 -07:00
DESKTOP-T0O5CDB\DESK-555BD
628c22ad61 Added functionality to display multi year trends. 2024-11-21 22:13:08 -07:00
Hargata Softworks
db62a041c4 Merge pull request #726 from hargata/Hargata/454.2
Fixed wording.
2024-11-21 15:07:46 -07:00
DESKTOP-T0O5CDB\DESK-555BD
ea3ffea797 Fixed wording. 2024-11-21 15:07:24 -07:00
Hargata Softworks
c49c8d67a2 Merge pull request #725 from hargata/Hargata/454
Allow users to add supplies onto existing records.
2024-11-21 13:56:24 -07:00
DESKTOP-T0O5CDB\DESK-555BD
3b01df4c56 Updated verbiage 2024-11-21 13:51:38 -07:00
DESKTOP-T0O5CDB\DESK-555BD
9221516bad fix bug when there are multiple supplies with the same id. 2024-11-20 21:18:42 -07:00
DESKTOP-T0O5CDB\DESK-555BD
1988cf54f5 Allow users to add supplies onto existing records. 2024-11-20 20:48:38 -07:00
Hargata Softworks
8a8d8979a7 Merge pull request #724 from hargata/Hargata/429
Replenish Supplies By Deleting Records
2024-11-20 12:14:32 -07:00
DESKTOP-T0O5CDB\DESK-555BD
e983b39f8e Fixed minor bug with plan type not displaying correctly in kiosk mode. 2024-11-20 12:13:59 -07:00
DESKTOP-T0O5CDB\DESK-555BD
b6c5cd4b46 important bug fix. 2024-11-20 11:57:01 -07:00
DESKTOP-T0O5CDB\DESK-555BD
210b27ab25 Allow users to to restore supplies by either deleting the record or deleting the requistion history. 2024-11-20 11:54:29 -07:00
Hargata Softworks
63e9e5ecec Merge pull request #720 from hargata/Hargata/planner.drag.drop.fix
1.4.1 Changes
2024-11-19 14:45:26 -07:00
DESKTOP-T0O5CDB\DESK-555BD
771ca15ea2 bump version. 2024-11-19 13:42:15 -07:00
DESKTOP-T0O5CDB\DESK-555BD
b6b403b5e2 styling fixes. 2024-11-19 13:41:52 -07:00
DESKTOP-T0O5CDB\DESK-555BD
9976c94481 Merge branch 'main' into Hargata/planner.drag.drop.fix 2024-11-19 13:31:29 -07:00
Hargata Softworks
b9dca8ceae Merge pull request #722 from hargata/Hargata/operationresponse.cleanup.2
Added Conditional OperationalResponse constructor.
2024-11-19 13:14:14 -07:00
DESKTOP-T0O5CDB\DESK-555BD
03b9331a9a Added Conditional OperationalResponse constructor. 2024-11-19 13:04:40 -07:00
Hargata Softworks
f629834d6a Merge pull request #721 from hargata/Hargata/operationresponse.cleanup.1
Clean up OperationResponse method Part 1
2024-11-19 12:37:37 -07:00
DESKTOP-T0O5CDB\DESK-555BD
25952cce50 Clean up OperationResponse method Part 1 2024-11-19 12:26:05 -07:00
DESKTOP-T0O5CDB\DESK-555BD
6eba83ccec added enhancement to bulk gas edit modal 2024-11-19 10:46:33 -07:00
DESKTOP-T0O5CDB\DESK-555BD
3c1ce80528 Added user setting for fixing gas consumption to two decimal places. 2024-11-19 10:43:34 -07:00
DESKTOP-T0O5CDB\DESK-555BD
b7851e87b2 Save Vehicle Specific Parameters in SessionStorage. 2024-11-19 09:11:12 -07:00
DESKTOP-T0O5CDB\DESK-555BD
d8e06fd968 Fix an issue with the drag and drop functionality on the planner page. 2024-11-17 11:02:11 -07:00
Hargata Softworks
c5a5de50a7 Merge pull request #712 from hargata/Hargata/702
simplify code.
2024-11-12 16:02:41 -07:00
DESKTOP-T0O5CDB\DESK-555BD
7715e7b4ab simplify code. 2024-11-12 16:02:18 -07:00
Hargata Softworks
5a3ed3d3d4 Merge pull request #711 from hargata/Hargata/702
set width
2024-11-12 14:14:26 -07:00
DESKTOP-T0O5CDB\DESK-555BD
c1e2394194 set width 2024-11-12 13:01:10 -07:00
Hargata Softworks
6abb569df1 Merge pull request #710 from hargata/Hargata/702
User customizable fields in Vehicle Maintenance Report
2024-11-12 12:59:26 -07:00
DESKTOP-T0O5CDB\DESK-555BD
e59e33bc3b fix bug. 2024-11-12 12:58:59 -07:00
DESKTOP-T0O5CDB\DESK-555BD
a671dc1937 allow users to define what field sthey want to see in vehicle maintenance report. 2024-11-12 12:54:39 -07:00
Hargata Softworks
426b52aee4 Merge pull request #709 from hargata/Hargata/702
show custom logos in reports and add a banner for credit instead.
2024-11-11 20:36:36 -07:00
DESKTOP-T0O5CDB\DESK-555BD
87fe011565 show custom logos in reports and add a banner for credit instead. 2024-11-11 20:35:09 -07:00
Hargata Softworks
30c03af5b0 Merge pull request #707 from hargata/Hargata/706
Further translation editor enhancements
2024-11-11 11:54:01 -07:00
DESKTOP-T0O5CDB\DESK-555BD
c9c8fc8705 ensures that all future translations check if the default translation has the key. 2024-11-11 09:06:19 -07:00
DESKTOP-T0O5CDB\DESK-555BD
9cd8030808 see requirements for 706 2024-11-11 09:03:22 -07:00
Hargata Softworks
5411d4152c Merge pull request #704 from Scorpoon/Code-CleanUp-1
#1 Code clean up
2024-11-11 08:08:50 -07:00
Hargata Softworks
c5a05afe1c Merge pull request #703 from Scorpoon/Missing-Translations
Added missing translations
2024-11-10 07:34:50 -07:00
Scorpoon
5b44f2ae33 Renamend UserAccessDataAcces.cs to UserAccessDataAccess.cs 2024-11-10 15:09:19 +01:00
Scorpoon
039b30da4f Optimized imports & removed redundant qualifiers 2024-11-10 14:57:50 +01:00
Scorpoon
df45378bce Added missing translation logic for "Upload documents(optional)" in _UpgradeRecordModal.cshtml 2024-11-10 14:10:49 +01:00
Scorpoon
3729d2c23f - Added translation for "No Recurring Reminders Found" in _RecurringReminderSelector.cshtml
- Added translation for "No Vehicles Available" in _VehicleSelector.cshtml
- Added translations for the DB-Migration tool
- Added the new translation records to the default language file en_US.json
2024-11-10 13:30:07 +01:00
Hargata Softworks
89ece413c8 Merge pull request #696 from hargata/Hargata/custom.widgets
require environment variable to be injected for security reasons.
2024-11-04 11:58:00 -07:00
DESKTOP-T0O5CDB\DESK-555BD
c1b361397f require environment variable to be injected for security reasons. 2024-11-04 11:57:27 -07:00
Hargata Softworks
ada0715343 Merge pull request #695 from hargata/Hargata/custom.widgets
Custom Widgets
2024-11-04 09:52:31 -07:00
DESKTOP-T0O5CDB\DESK-555BD
5cdc687f6d backup widgets and further enhance the cleanup api method. 2024-11-04 09:51:50 -07:00
DESKTOP-T0O5CDB\DESK-555BD
48f0e16fde additional enhancements to widget editor. 2024-11-04 08:37:38 -07:00
DESKTOP-T0O5CDB\DESK-555BD
d6b6600ce9 Custom Widgets 2024-11-03 13:22:48 -07:00
Hargata Softworks
82634e9e1b Merge pull request #694 from hargata/Hargata/kiosk.token
more kiosk fixes.
2024-11-03 09:17:29 -07:00
DESKTOP-T0O5CDB\DESK-555BD
225e5e58bc more kiosk fixes. 2024-11-03 09:16:46 -07:00
Hargata Softworks
21d135a8ab Merge pull request #693 from hargata/Hargata/order.missing.supplies
Add functionality to order insufficient supplies for plan templates.
2024-11-02 20:41:06 -06:00
DESKTOP-T0O5CDB\DESK-555BD
7a0636a48a add cancel button to modal. 2024-11-02 20:40:50 -06:00
DESKTOP-T0O5CDB\DESK-555BD
0b9ca77281 Add functionality to order insufficient supplies for plan templates. 2024-11-02 19:13:02 -06:00
Hargata Softworks
feaf631b47 Merge pull request #692 from hargata/Hargata/611
Styling fixes for supply selector
2024-11-02 16:45:16 -06:00
DESKTOP-T0O5CDB\DESK-555BD
ec24c19821 Styling fixes for supply selector 2024-11-02 16:44:27 -06:00
Hargata Softworks
36a120fa0f Merge pull request #691 from hargata/Hargata/611
automatic decimal formatting.
2024-11-02 15:47:28 -06:00
DESKTOP-T0O5CDB\DESK-555BD
fb169d5054 automatic decimal formatting. 2024-11-02 15:46:21 -06:00
Hargata Softworks
59fa7b6a1d Merge pull request #688 from hargata/Hargata/526
Add functionality to duplicate records to other vehicles.
2024-11-01 21:08:59 -06:00
DESKTOP-T0O5CDB\DESK-555BD
201d832b92 Add functionality to duplicate records to other vehicles. 2024-11-01 21:05:46 -06:00
Hargata Softworks
31b4ef5458 Merge pull request #685 from hargata/Hargata/bump.version
bump version.
2024-11-01 16:59:55 -06:00
DESKTOP-T0O5CDB\DESK-555BD
b06ad0cdaa bump version and kiosk enhancement. 2024-11-01 16:59:10 -06:00
DESKTOP-T0O5CDB\DESK-555BD
f3914bc3b7 bump version. 2024-10-31 23:54:09 -06:00
Hargata Softworks
aea870974b Merge pull request #683 from hargata/Hargata/kiosk
Added kiosk view.
2024-10-31 23:51:21 -06:00
Hargata Softworks
7f6265ba05 Merge pull request #681 from hargata/Hargata/fix.translation.editor
Fixed minor bug with translation editor.
2024-10-31 23:51:00 -06:00
DESKTOP-T0O5CDB\DESK-555BD
85542cb278 minor feature enhancement for #684 2024-10-31 23:49:32 -06:00
DESKTOP-T0O5CDB\DESK-555BD
f6e0873d83 fix small screen spacing. 2024-10-31 23:41:23 -06:00
DESKTOP-T0O5CDB\DESK-555BD
745b262dfc Added kiosk view. 2024-10-31 22:39:20 -06:00
DESKTOP-T0O5CDB\DESK-555BD
73df755f70 remove unncessary slash 2024-10-31 18:19:08 -06:00
DESKTOP-T0O5CDB\DESK-555BD
762a1b98d3 Fixed minor bug. 2024-10-31 14:49:51 -06:00
Hargata Softworks
571e87dd14 Merge pull request #673 from hargata/Hargata/update.docs
Update screenshots and docs site.
2024-10-31 13:33:34 -06:00
DESKTOP-T0O5CDB\DESK-555BD
22eb38709a added show password button, updated configurator, fixed login page width. 2024-10-31 09:41:05 -06:00
DESKTOP-T0O5CDB\DESK-555BD
986a8420f8 Merge branch 'main' into Hargata/update.docs 2024-10-30 10:43:33 -06:00
Hargata Softworks
cff5f8e8e9 Merge pull request #679 from hargata/Hargata/518.2
Add ability to re-order tabs.
2024-10-30 09:36:30 -06:00
DESKTOP-T0O5CDB\DESK-555BD
0df6cea88c backend for saving tab order 2024-10-30 09:33:17 -06:00
DESKTOP-T0O5CDB\DESK-555BD
88ff2432f6 Frontend for reordering tabs. 2024-10-29 23:18:31 -06:00
Hargata Softworks
efb7fb999b Merge pull request #677 from hargata/hargata-patch-1
Update screenshots.md
2024-10-28 20:48:35 -06:00
Hargata Softworks
1a2b503791 Update screenshots.md 2024-10-28 20:47:50 -06:00
DESKTOP-T0O5CDB\DESK-555BD
e68d4ffa3a Merge branch 'main' into Hargata/update.docs 2024-10-28 19:21:50 -06:00
Hargata Softworks
d701a8964f Merge pull request #676 from hargata/Hargata/ios.resizebug
fix bug with iOS closing mobile nav when scrolling
2024-10-28 19:19:12 -06:00
DESKTOP-T0O5CDB\DESK-555BD
b6929113bc fix bug with iOS closing mobile nav when scrolling 2024-10-28 19:18:44 -06:00
DESKTOP-T0O5CDB\DESK-555BD
fc1eac1049 Merge branch 'main' into Hargata/update.docs 2024-10-28 15:58:56 -06:00
Hargata Softworks
189659ff1e Merge pull request #674 from hargata/Hargata/fix.wonky.columns
Fix bug where extra fields causes columns to be misaligned.
2024-10-28 15:58:37 -06:00
DESKTOP-T0O5CDB\DESK-555BD
cb19130119 Fix bug where extra fields causes columns to be misaligned. 2024-10-28 15:52:40 -06:00
DESKTOP-T0O5CDB\DESK-555BD
1596bf1deb Update screenshots and docs site. 2024-10-28 10:11:34 -06:00
Hargata Softworks
636cbb3d21 Merge pull request #672 from hargata/Hargata/translation.getter
further hardening.
2024-10-28 09:55:18 -06:00
DESKTOP-T0O5CDB\DESK-555BD
cff7845dd4 further hardening. 2024-10-28 09:19:09 -06:00
Hargata Softworks
2d9cc3507f Merge pull request #671 from hargata/Hargata/translation.getter
Add functionality to download all translations.
2024-10-27 19:04:26 -06:00
DESKTOP-T0O5CDB\DESK-555BD
4c6ac96dd1 Add functionality to download all translations. 2024-10-27 19:01:53 -06:00
Hargata Softworks
e5a594367e Merge pull request #670 from hargata/Hargata/translation.getter
Add translation getter
2024-10-27 09:54:44 -06:00
DESKTOP-T0O5CDB\DESK-555BD
12aaf8c209 spacing issue 2024-10-27 09:53:39 -06:00
DESKTOP-T0O5CDB\DESK-555BD
cafbb156af Add translation getter 2024-10-27 09:52:21 -06:00
Hargata Softworks
36ac61d848 Merge pull request #669 from hargata/Hargata/translation.editor
fixed height for built in translator.
2024-10-25 16:56:53 -06:00
DESKTOP-T0O5CDB\DESK-555BD
df066a593e fixed height for built in translator. 2024-10-25 16:56:24 -06:00
Hargata Softworks
7ac9185e4c Merge pull request #668 from hargata/Hargata/translation.editor
Added translation editor.
2024-10-25 11:08:36 -06:00
DESKTOP-T0O5CDB\DESK-555BD
090cbba1a0 Added translation editor. 2024-10-25 11:06:40 -06:00
Hargata Softworks
5e3529f9cc Merge pull request #667 from hargata/Hargata/gps.timeout.fix
Fix distance accuracy
2024-10-24 22:35:51 -06:00
DESKTOP-T0O5CDB\DESK-555BD
f90ddeabb4 Fix distance accuracy 2024-10-24 22:34:53 -06:00
Hargata Softworks
f54419b993 Merge pull request #666 from hargata/Hargata/gps.timeout.fix
set time out to 4 seconds and max age to 1 second to improve accuracy
2024-10-24 14:14:35 -06:00
DESKTOP-T0O5CDB\DESK-555BD
cbbd7ff25f set time out to 4 seconds and max age to 1 second to improve accuracy 2024-10-24 14:13:47 -06:00
Hargata Softworks
0c2c089828 Merge pull request #664 from hargata/Hargata/485
hide identifier selector if no extra fields configured.
2024-10-23 11:08:17 -06:00
DESKTOP-T0O5CDB\DESK-555BD
6a1813399f hide identifier selector if no extra fields configured. 2024-10-23 11:07:23 -06:00
Hargata Softworks
4c8d2b3704 Merge pull request #663 from hargata/Hargata/485
Allow users to select any extra field as vehicle identifier.
2024-10-23 10:54:12 -06:00
DESKTOP-T0O5CDB\DESK-555BD
23f97ebd46 Allow users to select any extra field as vehicle identifier. 2024-10-23 10:52:41 -06:00
Hargata Softworks
e3dccf0182 Merge pull request #662 from hargata/Hargata/gps.integration
further hardening
2024-10-22 11:32:21 -06:00
DESKTOP-T0O5CDB\DESK-555BD
272596878c further hardening 2024-10-22 11:25:48 -06:00
Hargata Softworks
46953e9063 Merge pull request #659 from hargata/Hargata/gps.integration
further bug fixes and hardening.
2024-10-21 10:53:40 -06:00
DESKTOP-T0O5CDB\DESK-555BD
30f35855f2 further bug fixes and hardening. 2024-10-21 10:53:09 -06:00
Hargata Softworks
df94132917 Merge pull request #658 from hargata/Hargata/gps.integration
Hargata/gps.integration
2024-10-20 20:59:03 -06:00
DESKTOP-T0O5CDB\DESK-555BD
de37d689be moved variable to partial view. 2024-10-20 20:51:47 -06:00
DESKTOP-T0O5CDB\DESK-555BD
4f941aad52 add functionality to show sub odometer readings. 2024-10-20 20:41:43 -06:00
Hargata Softworks
2febdf1259 Merge pull request #657 from hargata/Hargata/gps.integration
fixes for GPS integration and experimental feature banner.
2024-10-20 19:12:47 -06:00
DESKTOP-T0O5CDB\DESK-555BD
349808a609 fixes for GPS integration and experimental feature banner. 2024-10-20 19:10:23 -06:00
Hargata Softworks
7d58d1bc40 Merge pull request #656 from hargata/Hargata/gps.integration
GPS methods to track vehicle odometer. experimental feature.
2024-10-20 15:31:06 -06:00
DESKTOP-T0O5CDB\DESK-555BD
5e91a26230 GPS methods to track vehicle odometer. experimental feature. 2024-10-20 15:30:22 -06:00
Hargata Softworks
0942ae1742 Merge pull request #655 from hargata/Hargata/chore.update.deps
CHORE: Update Dependencies
2024-10-18 09:42:42 -06:00
DESKTOP-T0O5CDB\DESK-555BD
cce9bd29e5 z 2024-10-18 09:42:21 -06:00
DESKTOP-T0O5CDB\DESK-555BD
05fdeef862 CHORE: Update Dependencies 2024-10-18 09:41:44 -06:00
Hargata Softworks
dfa08b287c Merge pull request #654 from hargata/Hargata/fix.tabs
improve tabs appearance
2024-10-17 15:06:21 -06:00
DESKTOP-T0O5CDB\DESK-555BD
77920f94cd improve tabs appearance 2024-10-17 15:05:20 -06:00
Hargata Softworks
3631a036c6 Merge pull request #653 from hargata/Hargata/616
thumbnail resize using Hermite interpolation
2024-10-17 10:37:54 -06:00
DESKTOP-T0O5CDB\DESK-555BD
1445959d05 thumbnail resize using Hermite interpolation 2024-10-17 10:34:00 -06:00
Hargata Softworks
0b45de5ba8 Merge pull request #652 from hargata/Hargata/fix.titles
Fixed the Titles.
2024-10-16 15:25:44 -06:00
DESKTOP-T0O5CDB\DESK-555BD
d86cdfcefd Fixed the Titles. 2024-10-16 14:25:19 -06:00
Hargata Softworks
0c7aa304f7 Create CONTRIBUTING.md 2024-10-16 12:36:38 -06:00
Hargata Softworks
e5114f491d Merge pull request #651 from hargata/Hargata/fix.adjustedodomete
1.3.9 Changes
2024-10-15 12:43:11 -06:00
DESKTOP-T0O5CDB\DESK-555BD
e238bd8a12 translation keys 2024-10-15 11:50:30 -06:00
DESKTOP-T0O5CDB\DESK-555BD
f53ad74c72 fix bugs with alternate fuel units when the average min and max labels don't exist. 2024-10-14 21:37:26 -06:00
DESKTOP-T0O5CDB\DESK-555BD
3d456d1cd5 quick styling fix 2024-10-14 10:16:37 -06:00
DESKTOP-T0O5CDB\DESK-555BD
de32fe4d85 load sponsors in ajax to account for users with slower internet connection. 2024-10-14 09:42:26 -06:00
DESKTOP-T0O5CDB\DESK-555BD
29a90fe814 add note records to attachment export. 2024-10-13 12:25:02 -06:00
DESKTOP-T0O5CDB\DESK-555BD
3f31f091dc fixed bug with UK units. 2024-10-10 15:41:21 -06:00
DESKTOP-T0O5CDB\DESK-555BD
571b558181 allow extra fields edit for multiple odometer records. 2024-10-10 09:38:04 -07:00
DESKTOP-T0O5CDB\DESK-555BD
ed0775ab71 Display fuel mileage unit and fix bug with metric fuel mileages 2024-10-10 10:08:34 -06:00
DESKTOP-T0O5CDB\DESK-555BD
19e19d7c15 extra field edit for multiple gas records. 2024-10-09 22:23:18 -06:00
DESKTOP-T0O5CDB\DESK-555BD
b7e574889c Allow Extra Fields to be edited for multiple records for Service, Repair, and Upgrade Records. 2024-10-09 21:29:38 -06:00
DESKTOP-T0O5CDB\DESK-555BD
2a66c97254 separated out the import and export methods. 2024-10-09 16:02:37 -06:00
DESKTOP-T0O5CDB\DESK-555BD
e14ef961b9 improved maintainability by splitting massive controller down to sections. 2024-10-09 15:58:04 -06:00
DESKTOP-T0O5CDB\DESK-555BD
ec2c17be23 updated configurator and streamlined getAndValidateExtraFields method. 2024-10-09 09:13:07 -06:00
DESKTOP-T0O5CDB\DESK-555BD
b5162d2044 configurator webpage 2024-10-05 20:04:27 -06:00
DESKTOP-T0O5CDB\DESK-555BD
ae428b94d1 Update ENV file to direct users to configurator 2024-10-05 20:04:07 -06:00
DESKTOP-T0O5CDB\DESK-555BD
3350f3c39a confirm deletion. 2024-10-05 18:40:37 -06:00
DESKTOP-T0O5CDB\DESK-555BD
68caf82167 moved token into modal. 2024-10-05 18:02:42 -06:00
DESKTOP-T0O5CDB\DESK-555BD
8c5f1c3a01 fixed minor bug issue in admin panel page and allow for multiple email addresses to generate token for 2024-10-05 17:28:39 -06:00
DESKTOP-T0O5CDB\DESK-555BD
e1bbc232e1 removed icon. 2024-10-05 16:12:48 -06:00
DESKTOP-T0O5CDB\DESK-555BD
fbc7a39996 Merge branch 'Hargata/admin.panel.enhancement' into Hargata/fix.adjustedodomete 2024-10-05 16:03:58 -06:00
DESKTOP-T0O5CDB\DESK-555BD
1ce77ad1ee create app schema if not exists without relying on init.sql 2024-10-05 12:03:46 -06:00
DESKTOP-T0O5CDB\DESK-555BD
08cee8029f more ctrl enter forms. 2024-09-29 19:44:28 -06:00
DESKTOP-T0O5CDB\DESK-555BD
e33bcf5e57 re-align some settings switches 2024-09-29 15:25:29 -06:00
DESKTOP-T0O5CDB\DESK-555BD
cf5ceaa959 Ctrl and Enter to submit record forms 2024-09-29 15:13:38 -06:00
DESKTOP-T0O5CDB\DESK-555BD
8293bed89c added warning for when user is running LubeLogger without a preconfigured culture. 2024-09-29 10:58:23 -06:00
DESKTOP-T0O5CDB\DESK-555BD
a830580c27 moved delete vehicle button to edit vehicle modal. 2024-09-29 10:31:53 -06:00
DESKTOP-T0O5CDB\DESK-555BD
94c6cca25c add enter event on sweetalert modals see #639 2024-09-28 12:44:09 -06:00
DESKTOP-T0O5CDB\DESK-555BD
78e0a0146c chore: fix deprecated click function on DOM elements 2024-09-28 12:05:48 -06:00
DESKTOP-T0O5CDB\DESK-555BD
ea667ed950 visual enhancements for admin panel 2024-09-28 10:44:04 -06:00
DESKTOP-T0O5CDB\DESK-555BD
f8b23b5f20 Fix the adjustedodometer endpoint for decimal odometer multiplier 2024-09-25 21:02:49 -06:00
Hargata Softworks
b69749b073 Merge pull request #635 from hargata/Hargata/update.demo
Update demo defaults.
2024-09-24 22:35:47 -06:00
DESKTOP-T0O5CDB\DESK-555BD
951eea844f Updated demo default 2024-09-24 22:35:23 -06:00
DESKTOP-T0O5CDB\DESK-555BD
f3e5aff0c9 Updated demo file 2024-09-24 22:33:00 -06:00
Hargata Softworks
ef3e450e4f Merge pull request #634 from hargata/Hargata/api.enhancement.dos
Added documentation and new API endpoint to calculate adjusted odometer.
2024-09-24 22:05:02 -06:00
DESKTOP-T0O5CDB\DESK-555BD
8272fa20da Added documentation and new API endpoint to calculate adjusted odometer. 2024-09-24 22:04:31 -06:00
Hargata Softworks
9ef0a748a3 Merge pull request #633 from hargata/Hargata/563.619
added flag for optional odometer.
2024-09-24 19:20:30 -06:00
DESKTOP-T0O5CDB\DESK-555BD
6de928aef4 added flag for optional odometer. 2024-09-24 19:18:13 -06:00
Hargata Softworks
34ff874949 Merge pull request #632 from hargata/Hargata/563.619
make odometer optional
2024-09-24 18:43:11 -06:00
DESKTOP-T0O5CDB\DESK-555BD
a653f3ac7f make odometer optional 2024-09-24 18:36:59 -06:00
Hargata Softworks
dc96084406 Merge pull request #631 from hargata/Hargata/630
Fix light mode bug.
2024-09-24 18:30:40 -06:00
DESKTOP-T0O5CDB\DESK-555BD
e056aaa7dc Fix light mode bug. 2024-09-24 18:11:50 -06:00
Hargata Softworks
73bb3c11b4 Merge pull request #628 from hargata/Hargata/adaptive.color.mode
fix label.
2024-09-23 08:37:37 -06:00
DESKTOP-T0O5CDB\DESK-555BD
c88a040728 fix label. 2024-09-23 08:36:58 -06:00
Hargata Softworks
dfa56c2b12 Merge pull request #627 from hargata/Hargata/adaptive.color.mode
Added switch for adaptive color mode.
2024-09-23 08:34:01 -06:00
DESKTOP-T0O5CDB\DESK-555BD
104d4bab52 Added switch for adaptive color mode. 2024-09-23 08:31:54 -06:00
Hargata Softworks
6916161e87 Merge pull request #626 from hargata/Hargata/info.endpoint.update
#482 - Allow Root Users to Login via OIDC
2024-09-22 14:36:23 -06:00
DESKTOP-T0O5CDB\DESK-555BD
512852d217 added flag to enable root user to login via OIDC 2024-09-22 14:32:51 -06:00
DESKTOP-T0O5CDB\DESK-555BD
a61c699417 Added PlanRecords to info endpoint 2024-09-22 13:48:01 -06:00
Hargata Softworks
1e832f014f Merge pull request #625 from hargata/Hargata/update.links
Updated documentation links and removed static documentation.
2024-09-22 10:42:10 -06:00
DESKTOP-T0O5CDB\DESK-555BD
e9a463e2e5 Updated documentation links and removed static documentation. 2024-09-22 10:41:36 -06:00
Hargata Softworks
d0c19d0436 Merge pull request #624 from hargata/Hargata/618
#618
2024-09-22 10:24:01 -06:00
DESKTOP-T0O5CDB\DESK-555BD
3428abf358 code clean up for default reminder email and added flag to disable registration on front end. 2024-09-22 10:20:25 -06:00
Hargata Softworks
e6857331e2 Merge pull request #617 from hargata/Hargata/603
adaptive color mode if dark mode is not enabled.
2024-09-13 14:24:59 -06:00
DESKTOP-T0O5CDB\DESK-555BD
1fd93d2efe update variable name 2024-09-13 14:24:11 -06:00
DESKTOP-T0O5CDB\DESK-555BD
84e44acbfe adaptive color mode if dark mode is not enabled. 2024-09-13 07:51:32 -06:00
Hargata Softworks
77014c71f2 Merge pull request #614 from hargata/Hargata/517
Add Default Reminder Email Recipient
2024-09-12 11:15:55 -06:00
DESKTOP-T0O5CDB\DESK-555BD
2db01aa4ad reworded response message 2024-09-12 10:00:39 -06:00
DESKTOP-T0O5CDB\DESK-555BD
6c3fa21cc5 Added environment variable to configure default reminder email recipient. Fixed bug with due date in email body and improved logging and error catching when sending emails. 2024-09-12 09:43:52 -06:00
Hargata Softworks
26b7d101ab Merge pull request #610 from hargata/Hargata/609
Added custom thresholds for reminder.
2024-09-07 13:37:52 -06:00
DESKTOP-T0O5CDB\DESK-555BD
f3c3cdf0cb Added custom thresholds for reminder. 2024-09-07 08:39:26 -06:00
Hargata Softworks
e62fa31485 Merge pull request #608 from hargata/Hargata/606
See #606
2024-09-05 13:19:27 -06:00
DESKTOP-T0O5CDB\DESK-555BD
5f283c1558 uh 2024-09-05 13:11:24 -06:00
DESKTOP-T0O5CDB\DESK-555BD
52163098d2 See #606 2024-09-05 13:04:16 -06:00
Hargata Softworks
dfe27f0577 Merge pull request #605 from hargata/Hargata/598
Added colored icons for Planner items.
2024-09-04 09:23:29 -06:00
DESKTOP-T0O5CDB\DESK-555BD
e415a3468f Added colored icons for Planner items. 2024-09-03 20:13:07 -06:00
Hargata Softworks
71302a52e4 Merge pull request #602 from hargata/Hargata/light.mode.menu.fix
Fix menu color when in light mode.
2024-09-02 11:41:14 -06:00
DESKTOP-T0O5CDB\DESK-555BD
762730eb0f Fix menu color when in light mode. 2024-09-02 11:40:11 -06:00
Hargata Softworks
3999a8a54d Merge pull request #601 from hargata/Hargata/div.zero.hotfix
Fix divide by zero error in Cost Per Mile metric
2024-09-01 11:02:42 -06:00
DESKTOP-T0O5CDB\DESK-555BD
065291e146 Fix divide by zero error in Cost Per Mile metric 2024-09-01 11:02:04 -06:00
Hargata Softworks
99376aba1c Merge pull request #600 from hargata/Hargata/537
Add user-configurable dashboard metric
2024-09-01 10:48:30 -06:00
DESKTOP-T0O5CDB\DESK-555BD
698e8a0a95 fix default placeholder label for data table. 2024-08-31 23:50:17 -06:00
DESKTOP-T0O5CDB\DESK-555BD
b1b3a127dc fix bug with data table. 2024-08-31 20:28:51 -06:00
DESKTOP-T0O5CDB\DESK-555BD
ec55c95c71 Allow users to set which dashboard metric they want to see in the garage. 2024-08-31 20:28:32 -06:00
Hargata Softworks
e6eb2b473c Merge pull request #599 from hargata/Hargata/551
Added data table
2024-08-31 17:36:06 -06:00
DESKTOP-T0O5CDB\DESK-555BD
a4b39820e4 updated label. 2024-08-31 10:39:36 -06:00
DESKTOP-T0O5CDB\DESK-555BD
4a20c81047 convert to number of days and replace zero values with dashes 2024-08-31 10:33:45 -06:00
DESKTOP-T0O5CDB\DESK-555BD
b72ab461e5 revert and enhance. 2024-08-31 09:46:08 -06:00
DESKTOP-T0O5CDB\DESK-555BD
0be498d9bd Added data table 2024-08-30 15:29:09 -06:00
Hargata Softworks
00bad986e5 Merge pull request #596 from hargata/Hargata/reminder.tooltip.fix
Stats API Endpoint
2024-08-29 08:19:52 -06:00
DESKTOP-T0O5CDB\DESK-555BD
4710e84dcf more stats. 2024-08-27 12:20:50 -06:00
DESKTOP-T0O5CDB\DESK-555BD
ee55f8c884 added vehicle info API endpoint for dashboards and whatnot. 2024-08-27 12:04:57 -06:00
DESKTOP-T0O5CDB\DESK-555BD
3a74116e70 Do not load tooltips on touch screen only device. 2024-08-27 11:07:59 -06:00
Hargata Softworks
cfd83b5b4e Merge pull request #594 from hargata/Hargata/588
removed order by
2024-08-26 15:25:06 -06:00
DESKTOP-T0O5CDB\DESK-555BD
e468261095 removed order by 2024-08-26 15:23:20 -06:00
Hargata Softworks
1362dc87df Merge pull request #593 from hargata/Hargata/588
Allow extra fields to be added via API
2024-08-26 15:13:30 -06:00
DESKTOP-T0O5CDB\DESK-555BD
b89902bbdb Allow extra fields to be added via API 2024-08-26 15:12:05 -06:00
Hargata Softworks
f31b70a6dc Merge pull request #592 from hargata/Hargata/531
Display the due date and odometer in a tooltip in the reminder record…
2024-08-26 12:43:58 -06:00
DESKTOP-T0O5CDB\DESK-555BD
47a1f0c4d5 Display the due date and odometer in a tooltip in the reminder records view. 2024-08-26 12:38:42 -06:00
Hargata Softworks
3234b530d5 Merge pull request #591 from hargata/Hargata/reminder.api.enhancement
Add due date and due odometer to reminder output for API method.
2024-08-26 10:33:23 -06:00
DESKTOP-T0O5CDB\DESK-555BD
75d16544db Add due date and due odometer to reminder output for API method. 2024-08-26 10:32:14 -06:00
Hargata Softworks
7179b60116 Merge pull request #590 from hargata/Hargata/csv.extrafield.export
remove unused variable.
2024-08-23 13:45:22 -06:00
DESKTOP-T0O5CDB\DESK-555BD
7cb5d8254f remove unused variable. 2024-08-23 13:44:58 -06:00
Hargata Softworks
6ab7ee6e4f Merge pull request #589 from hargata/Hargata/csv.extrafield.export
Add functionality to export extra fields in CSV exports.
2024-08-23 13:41:45 -06:00
DESKTOP-T0O5CDB\DESK-555BD
a57ce55f8b Fix type 2024-08-23 13:41:12 -06:00
DESKTOP-T0O5CDB\DESK-555BD
190484762d Add functionality to export extra fields in CSV exports. 2024-08-23 13:39:08 -06:00
Hargata Softworks
78554ade5d Merge pull request #587 from hargata/Hargata/api.enhancement
Further Enhance Cleanup API Endpoint.
2024-08-22 11:13:04 -06:00
DESKTOP-T0O5CDB\DESK-555BD
364a13226a Added return object. 2024-08-22 11:11:17 -06:00
Hargata Softworks
691813838c Merge pull request #586 from hargata/Hargata/cleanup.api
Add API Controller method to clean up temp files and unlinked thumbnails
2024-08-21 21:24:32 -06:00
DESKTOP-T0O5CDB\DESK-555BD
0defb0fadf json json. 2024-08-21 21:23:52 -06:00
DESKTOP-T0O5CDB\DESK-555BD
08bf00ee37 Add API Controller method to clean up temp files and unlinked thumbnails. 2024-08-21 21:21:24 -06:00
Hargata Softworks
522322ee2a Merge pull request #585 from hargata/Hargata/570
Upload files on paste.
2024-08-21 18:13:14 -06:00
DESKTOP-T0O5CDB\DESK-555BD
062e3600e7 update uploaded files in real time for records that already have uploaded files and fix note attachment bug 2024-08-21 18:11:20 -06:00
DESKTOP-T0O5CDB\DESK-555BD
a92160f6d7 Upload files on paste. 2024-08-21 14:38:15 -06:00
Hargata Softworks
4adf967f7f Merge pull request #584 from hargata/Hargata/534
Add fuel type dropdown and diesel fuel type.
2024-08-19 12:13:12 -06:00
DESKTOP-T0O5CDB\DESK-555BD
fd6bd98a25 Missing translation key. 2024-08-19 12:12:36 -06:00
DESKTOP-T0O5CDB\DESK-555BD
fe76f32778 Add fuel type dropdown and diesel fuel type. 2024-08-19 12:09:50 -06:00
Hargata Softworks
897b4f2efe Merge pull request #581 from hargata/Hargata/576
Resolve Empty Tables Postgres Bug.
2024-08-14 09:27:24 -06:00
DESKTOP-T0O5CDB\DESK-555BD
267f903ffe Resolve bug where attempting to delete a vehicle with zero records returns an error. 2024-08-13 08:19:05 -06:00
Hargata Softworks
db884d0a6a Merge pull request #569 from hargata/Hargata/568
Add PKCE to OIDC Auth Flow
2024-07-31 12:28:20 -06:00
DESKTOP-T0O5CDB\DESK-555BD
d1190e7ddb Update readme to add helm chart by anza labs. 2024-07-31 12:27:36 -06:00
DESKTOP-T0O5CDB\DESK-555BD
dae8ab679e add better logging for when IdP returns errors. 2024-07-30 11:57:35 -06:00
DESKTOP-T0O5CDB\DESK-555BD
86ec9409c3 added pkce code for when user logins automatically. 2024-07-29 15:28:00 -06:00
DESKTOP-T0O5CDB\DESK-555BD
2c4ddb0c38 Updated version. 2024-07-29 15:23:31 -06:00
DESKTOP-T0O5CDB\DESK-555BD
44d10f11ca Add PKCE functionality(RFC 7636) to OIDC functionality. 2024-07-29 15:23:10 -06:00
Hargata Softworks
6cf733b9c6 Merge pull request #558 from hargata/Hargata/545
enable extra fields to be imported via CSV
2024-07-09 14:52:24 -06:00
DESKTOP-T0O5CDB\DESK-555BD
1eb6e2cedf enable extra fields to be imported via CSV 2024-07-09 14:48:09 -06:00
Hargata Softworks
ef4deaba8f Merge pull request #557 from hargata/Hargata/512
recurring interval are now based on reminder metric.
2024-07-09 11:16:09 -06:00
DESKTOP-T0O5CDB\DESK-555BD
e8c196c2fa recurring interval are now based on reminder metric. 2024-07-09 11:15:02 -06:00
Hargata Softworks
f7c9db6353 Merge pull request #556 from hargata/Hargata/mailconfig.cleanup
Clean up MailConfig and update npsql
2024-07-09 10:35:16 -06:00
DESKTOP-T0O5CDB\DESK-555BD
4ce720ff97 Update npgSQL 2024-07-09 10:34:11 -06:00
DESKTOP-T0O5CDB\DESK-555BD
a96011629b clean up mailconfig 2024-07-09 10:32:40 -06:00
Hargata Softworks
9dcdcf97e8 Merge pull request #508 from snpaul22/main
Create app schema automatically
2024-07-06 17:40:13 -06:00
snpaul22
5148338f52 Merge branch 'hargata:main' into main 2024-07-06 18:54:14 -04:00
Hargata Softworks
0d3c04d8f8 Merge pull request #554 from hargata/Hargata/root.user.update
Make it easier to update root user credentials and patch bug.
2024-07-05 11:26:20 -06:00
DESKTOP-T0O5CDB\DESK-555BD
15328a14b4 Make it easier to update root user credentials and patch bug. 2024-07-05 11:18:46 -06:00
Hargata Softworks
2d092f722a Merge pull request #550 from hargata/Hargata/disabled.init.odo
updated button color.
2024-07-02 15:05:39 -06:00
DESKTOP-T0O5CDB\DESK-555BD
8825cb9b9b updated button color. 2024-07-02 15:05:16 -06:00
Hargata Softworks
2f17e303ab Merge pull request #549 from hargata/Hargata/disabled.init.odo
Disable the initial odometer reading field
2024-07-02 11:51:53 -06:00
DESKTOP-T0O5CDB\DESK-555BD
4b56c8a343 a better implementation of disabled attribute 2024-07-02 11:09:41 -06:00
DESKTOP-T0O5CDB\DESK-555BD
7b6b62c623 Disable the initial odometer reading field if it's non-zero to prevent accidental editing. 2024-07-02 10:54:11 -06:00
Hargata Softworks
07f5e66491 Merge pull request #519 from NateWright/main
Add maskable icons for Android PWA
2024-06-18 08:25:49 -06:00
Hargata Softworks
fe633f3220 Merge pull request #542 from kapcake/patch-1
Update money regex to allow more than 6 figures on integer part
2024-06-18 08:25:00 -06:00
kapcake
af1090553f Update money regex to not allow unlimited figures
The regex now allows up to 8 groups of 3 digits on the integer part, which is within the bounds of C# decimal, preventing the user to go out of bounds on the form input.
2024-06-18 15:14:26 +02:00
kapcake
92c2e66660 Update money regex to allow more than 6 figures on integer part 2024-06-18 15:02:20 +02:00
Nathan Wright
08372f9dcb Merge branch 'hargata:main' into main 2024-06-08 15:20:31 -04:00
Hargata Softworks
64ea0e2eee Merge pull request #533 from hargata/Hargata/532
add support for smtp clients that requires no authentication.
2024-05-31 09:11:52 -06:00
DESKTOP-T0O5CDB\DESK-555BD
7ab476a88f add support for smtp clients that requires no authentication. 2024-05-31 09:11:09 -06:00
Hargata Softworks
de41ca911d Merge pull request #530 from hargata/Hargata/527
Minor Bug Fixes
2024-05-29 09:36:56 -06:00
DESKTOP-T0O5CDB\DESK-555BD
dde9688f96 Updated supplies usage validation to allow for free supplies. 2024-05-28 14:54:12 -06:00
DESKTOP-T0O5CDB\DESK-555BD
c1ca63edc0 removed unnecessary tostring 2024-05-28 14:26:05 -06:00
DESKTOP-T0O5CDB\DESK-555BD
cbc430499f added new currency formatting function 2024-05-28 14:14:25 -06:00
DESKTOP-T0O5CDB\DESK-555BD
4da9fa4802 See 514 2024-05-28 13:55:14 -06:00
DESKTOP-T0O5CDB\DESK-555BD
42586d9556 Updated version number 2024-05-28 12:56:29 -06:00
DESKTOP-T0O5CDB\DESK-555BD
ea4387d4ab Add missing translation keys 2024-05-28 12:53:20 -06:00
Hargata Softworks
0707b515ab Merge pull request #525 from hargata/Hargata/sponsor.tiers
Updated Sponsors File Path
2024-05-21 15:42:58 -06:00
DESKTOP-T0O5CDB\DESK-555BD
78ae71fc46 Updated Sponsors File Path 2024-05-21 15:41:05 -06:00
Hargata Softworks
3f62cd40e7 Merge pull request #524 from hargata/Hargata/sponsor.tiers
Add Sponsor Section
2024-05-21 15:32:20 -06:00
DESKTOP-T0O5CDB\DESK-555BD
47657c0093 Added sponsor section. 2024-05-21 15:29:46 -06:00
Hargata Softworks
a5b0fde4b6 Merge pull request #522 from hargata/Hargata/swal.uncensored
Hargata/swal.uncensored
2024-05-20 12:26:10 -06:00
DESKTOP-T0O5CDB\DESK-555BD
78cc0b34b1 Updated version number 2024-05-20 12:25:07 -06:00
Hargata Softworks
e3017e986b Merge pull request #520 from hargata/Hargata/license.change
Removed commercial license restrictions from license.
2024-05-20 12:22:21 -06:00
DESKTOP-T0O5CDB\DESK-555BD
e12cd876db removed unused files 2024-05-20 12:21:54 -06:00
DESKTOP-T0O5CDB\DESK-555BD
5292e4b814 utilize uncensored version of sweet alert 2024-05-20 12:16:10 -06:00
DESKTOP-T0O5CDB\DESK-555BD
dbdd16ab89 Removed commercial license restrictions from license. 2024-05-14 08:46:47 -06:00
nwright
61c2600286 added maskable icons 2024-05-13 19:34:03 -04:00
snpaul22
163a33ae3a Create init.sql
Script checks for "app" schema in Postgres during startup. It will create the schema automatically if it doesn't exist or skip if it does exist.
2024-05-04 11:27:50 -04:00
snpaul22
37d064aa62 Update docker-compose.postgresql.yml
Added Postgres volume bind for initialization script
2024-05-04 11:23:55 -04:00
Hargata Softworks
ddc3c2e1b5 Merge pull request #503 from hargata/Hargata/null.mail.config
null check for mail config
2024-04-25 12:45:36 -06:00
DESKTOP-T0O5CDB\DESK-555BD
49184b287b null check for mail config 2024-04-25 12:45:21 -06:00
Hargata Softworks
f6139bda0d Merge pull request #502 from hargata/Hargata/mailkit.upgrade
fix inefficiencies
2024-04-25 11:33:14 -06:00
DESKTOP-T0O5CDB\DESK-555BD
a6471b823b fix inefficiencies 2024-04-25 11:11:54 -06:00
Hargata Softworks
7c34003647 Merge pull request #501 from hargata/Hargata/mailkit.upgrade
MailKit Upgrade
2024-04-25 10:46:53 -06:00
DESKTOP-T0O5CDB\DESK-555BD
1aa21f9980 Uprgade from .NET SMTPClient to MailKit as the default smtpclient does not support modern protocols. 2024-04-25 10:45:55 -06:00
Hargata Softworks
ce4ca50939 Merge pull request #499 from hargata/Hargata/persist.metric.bug
fix getyear method
2024-04-23 23:50:31 -06:00
DESKTOP-T0O5CDB\DESK-555BD
fb28260c4a fix getyear method 2024-04-23 23:50:06 -06:00
Hargata Softworks
626a904747 Merge pull request #498 from hargata/Hargata/persist.metric.bug
Minor bug fix
2024-04-23 23:43:18 -06:00
DESKTOP-T0O5CDB\DESK-555BD
893cdafdc5 Fixes a very minor bug where the persisted year metric blanks out when viewing a different car that doesn't have that specific year. 2024-04-23 23:42:09 -06:00
Hargata Softworks
dbfb7d7d9c Merge pull request #493 from hargata/Hargata/persist.dashboard
check against null instead of undefined,
2024-04-15 08:42:43 -06:00
DESKTOP-GENO133\IvanPlex
a66538a7db check against null instead of undefined, 2024-04-15 08:41:53 -06:00
Hargata Softworks
2f77d87d4f Merge pull request #492 from hargata/Hargata/persist.dashboard
temporarily persist dashboard metrics in sessionStorage
2024-04-15 08:25:54 -06:00
DESKTOP-GENO133\IvanPlex
de85ba984c temporarily persist dashboard metrics in sessionStorage 2024-04-15 08:20:37 -06:00
Hargata Softworks
caac1a05ae Merge pull request #480 from hargata/Hargata/fix.link.color
fix donation link colors
2024-04-12 08:04:44 -06:00
Hargata Softworks
eb5793b819 Merge pull request #490 from hargata/Hargata/fix.alt.fuel
Fix Alternate Fuel Mileage Bug
2024-04-12 08:04:28 -06:00
DESKTOP-GENO133\IvanPlex
5ef3e1e2ce updated version number 2024-04-12 08:04:04 -06:00
DESKTOP-GENO133\IvanPlex
d8b459e5ee persist regional formatting when viewing record statistics. 2024-04-12 08:01:25 -06:00
DESKTOP-GENO133\IvanPlex
7b40d58aa1 fix alternate fuel mileage auto converting to NA decimal format bug. 2024-04-12 07:54:10 -06:00
DESKTOP-T0O5CDB\DESK-555BD
809e9b838e fix donation link colors 2024-04-09 21:29:19 -06:00
Hargata Softworks
23ae36ebd9 Merge pull request #475 from hargata/Hargata/update.readme
check if function exists.
2024-04-07 21:03:31 -06:00
DESKTOP-GENO133\IvanPlex
9c3f7d20f5 fix plans not updating when deleted. 2024-04-07 21:01:06 -06:00
DESKTOP-GENO133\IvanPlex
083298303c check if function exists. 2024-04-07 20:51:55 -06:00
Hargata Softworks
224970a07e Merge pull request #474 from hargata/Hargata/update.readme
Fix the problem with time.
2024-04-07 19:15:41 -06:00
DESKTOP-GENO133\IvanPlex
86d039e5b0 Fix the problem with time. 2024-04-07 17:59:08 -06:00
Hargata Softworks
6a8038aac9 Merge pull request #473 from hargata/Hargata/update.readme
update readme
2024-04-07 13:59:02 -06:00
DESKTOP-GENO133\IvanPlex
e748f08a8e update readme 2024-04-07 13:58:32 -06:00
Hargata Softworks
3580963e9f Merge pull request #472 from hargata/Hargata/planner.improvement.part2
Improved UI UX For Planner Edit
2024-04-07 12:51:28 -06:00
DESKTOP-GENO133\IvanPlex
b008ce2ab8 Improved UI UX 2024-04-07 12:50:28 -06:00
Hargata Softworks
ea0c2c7061 Merge pull request #471 from hargata/Hargata/transl
Updated translation
2024-04-07 11:26:35 -06:00
DESKTOP-GENO133\IvanPlex
0926220933 Updated translation 2024-04-07 11:26:12 -06:00
Hargata Softworks
ca975bbdd3 Merge pull request #470 from hargata/Hargata/planner.template.edit
updated translation
2024-04-07 11:22:20 -06:00
DESKTOP-GENO133\IvanPlex
b16c5c5302 updated translation 2024-04-07 11:21:43 -06:00
Hargata Softworks
cad05fe5d9 Merge pull request #466 from hargata/Hargata/planner.improvement
Moved template modal outside of add modal.
2024-04-07 11:20:42 -06:00
Hargata Softworks
a7cd466d9c Merge pull request #469 from hargata/Hargata/planner.template.edit
Add functionality to let users edit plan record templates
2024-04-07 11:20:28 -06:00
DESKTOP-GENO133\IvanPlex
4472a67ec0 fixed label function 2024-04-07 11:19:52 -06:00
DESKTOP-GENO133\IvanPlex
c84a4029ec basic functionality to edit templates 2024-04-07 11:09:05 -06:00
DESKTOP-GENO133\IvanPlex
d7d9ab505e Add functionality to let users edit plan record templates 2024-04-07 08:31:17 -06:00
DESKTOP-GENO133\IvanPlex
c582f5f5c7 Moved template modal outside of add modal. 2024-04-05 19:36:26 -06:00
Hargata Softworks
4c30939339 Merge pull request #465 from hargata/Hargata/unused.translation.key
Updated translation file and unsaved changes label color.
2024-04-05 18:50:02 -06:00
DESKTOP-GENO133\IvanPlex
cecd6a1d2b Updated translation file and unsaved changes label color. 2024-04-05 18:48:54 -06:00
Hargata Softworks
853dcbb364 Merge pull request #464 from hargata/Hargata/unused.translation.key
removed cached translation key
2024-04-05 07:18:08 -06:00
DESKTOP-GENO133\IvanPlex
ae327ed26d removed cached translation key 2024-04-05 07:17:49 -06:00
DESKTOP-GENO133\IvanPlex
3e416aa255 Revert "removed unused translation key"
This reverts commit ab98d02106faceaca1c6c76b8f0de7bf3e28cebe.
2024-04-05 07:16:11 -06:00
DESKTOP-GENO133\IvanPlex
61c2c3fc83 removed unused translation key 2024-04-05 07:16:10 -06:00
Hargata Softworks
ca749aaf1e Merge pull request #463 from hargata/Hargata/unsaved.changes
updated cached view to only show when there are unsaved changes present.
2024-04-05 07:11:50 -06:00
DESKTOP-GENO133\IvanPlex
2a2cb3bd0c updated cached view to only show when there are unsaved changes present. 2024-04-05 07:10:09 -06:00
Hargata Softworks
44e3d19844 Merge pull request #462 from hargata/Hargata/global.search
added incremental search
2024-04-04 19:16:22 -06:00
DESKTOP-GENO133\IvanPlex
1e25fffc70 added incremental search 2024-04-04 19:14:49 -06:00
Hargata Softworks
44645ed23d Merge pull request #461 from hargata/Hargata/global.search
added global search
2024-04-04 10:48:57 -06:00
DESKTOP-GENO133\IvanPlex
fa557e5f76 added global search 2024-04-04 10:46:43 -06:00
Hargata Softworks
7202fda38e Merge pull request #458 from hargata/Hargata/cached.records
Cached Record.
2024-04-02 21:53:46 -06:00
DESKTOP-GENO133\IvanPlex
d05afe41d6 Cache the record the user was viewing if they didn't save, delete, or move it. 2024-04-02 21:51:02 -06:00
Hargata Softworks
bfc0b58728 Merge pull request #453 from hargata/Hargata/bring.back.modal
Allow users to bring back modal window they accidentally closed.
2024-04-02 07:39:25 -06:00
DESKTOP-GENO133\IvanPlex
22e8aaca81 Allow users to bring back modal window they accidentally closed. 2024-04-02 07:29:16 -06:00
Hargata Softworks
5cb1247fb2 Merge pull request #452 from hargata/Hargata/odometer.increment
Odometer Increments
2024-04-01 18:31:27 -06:00
DESKTOP-GENO133\IvanPlex
17a6d99703 Allow users to increment for last reported odometer reading when creating new records. 2024-04-01 18:25:20 -06:00
Hargata Softworks
3e7917f767 Create FUNDING.yml 2024-04-01 09:14:46 -06:00
Hargata Softworks
e9277d4dd9 Merge pull request #443 from hargata/Hargata/reminder.icons
reminders will now display urgency in icons instead of a full badge w…
2024-03-29 09:35:17 -06:00
DESKTOP-GENO133\IvanPlex
058edd8af6 reminders will now display urgency in icons instead of a full badge when viewed in small screens. 2024-03-29 08:15:55 -06:00
229 changed files with 10703 additions and 14822 deletions

7
.env
View File

@@ -2,12 +2,11 @@ LC_ALL=en_US.UTF-8
LANG=en_US.UTF-8
MailConfig__EmailServer=""
MailConfig__EmailFrom=""
MailConfig__UseSSL="false"
MailConfig__Port=587
MailConfig__Username=""
MailConfig__Password=""
LOGGING__LOGLEVEL__DEFAULT=Error
# * Uncoment this line if you use postgresSQL as database backend.
# * Check the docker-compose.postgresql.yml file
#POSTGRES_CONNECTION="Host=postgres;Username=lubelogger;Password=lubepass;Database=lubelogger;"
# This file is provided as a GUIDELINE ONLY
# Use the LubeLogger Configurator to configure your environment variables
# https://lubelogger.com/configure

48
.github/CONTRIBUTING.md vendored Normal file
View File

@@ -0,0 +1,48 @@
# Contributing Guidelines
## Asking Questions
Before submitting a question, please check the [Troubleshooting Guide](https://docs.lubelogger.com/Installation/Troubleshooting) and search through existing issues.
Ideally, the Issues tab should only consist of bug reports and feature requests instead of debugging your LubeLogger installation.
## Feature Requests
Feature Requests are cool, but we do want to avoid bloat and scope/feature creep.
LubeLogger is a Vehicle Maintenance and Fuel Mileage Tracker.
It is not and should not be used for the following:
- Project Management Software(e.g.: Jira)
- Budget Management(e.g: YNAB/Actual)
- Inventory Management
Before submitting a feature request, please consider the following:
- Will this feature benefit more than just a niche subset of users
- Will this feature result in scope creep
- Has this feature already been requested/exist
## Bug Reports
Please fill out the issue template for bug reports.
## Security Vulnerabilities
Contact us via [Email](mailto:hargatasoftworks@gmail.com)
## Submitting Pull Requests
Pull Requests are welcome, but please consider the following:
CI/CD, Bugs, Tech-Debt Related PRs:
- Make sure changes are not breaking.
- If any dependencies are added, they must be cross-platform compatible.
- Please test changes thoroughly.
Feature Related PRs:
- Have you requested this feature in the Issues tab
- Will this feature benefit more than just a niche subset of users
- Will this feature result in scope creep
- Do these changes fix a root cause or is it simply a workaround
Ideally, you should first submit a feature request in the Issues tab before submitting a PR just in case it's something we are already working on.
Note the following:
We(the maintainers) are not responsible for cleaning up, testing, or fixing your changes.
If your changes doesn't have a wide-ranging use-case, has not been thoroughly tested, or cannot be easily maintained, we won't merge your PR.

14
.github/FUNDING.yml vendored Normal file
View File

@@ -0,0 +1,14 @@
# These are supported funding model platforms
github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2]
patreon: lubelogger
open_collective: # Replace with a single Open Collective username
ko_fi: # Replace with a single Ko-fi username
tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
liberapay: # Replace with a single Liberapay username
issuehunt: # Replace with a single IssueHunt username
lfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry
polar: # Replace with a single Polar username
buy_me_a_coffee: # Replace with a single Buy Me a Coffee username
custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2']

View File

@@ -10,7 +10,7 @@ assignees: ''
**Checklist**
Please make sure you have performed the following steps before opening a new bug ticket, change `[ ]` to `[x]` to mark it as done
- [ ] I have read and tried the steps outlined in the [Troubleshooting Guide](https://docs.lubelogger.com/Troubleshooting)
- [ ] I have read and tried the steps outlined in the [Troubleshooting Guide](https://docs.lubelogger.com/Installation/Troubleshooting)
- [ ] I have searched through existing issues.
**Description**

1
.gitignore vendored
View File

@@ -12,3 +12,4 @@ config/userConfig.json
CarCareTracker.csproj.user
Properties/launchSettings.json
data/cartracker-log.db
data/widgets.html

View File

@@ -13,7 +13,8 @@
<ItemGroup>
<PackageReference Include="CsvHelper" Version="30.0.1" />
<PackageReference Include="LiteDB" Version="5.0.17" />
<PackageReference Include="Npgsql" Version="8.0.2" />
<PackageReference Include="MailKit" Version="4.8.0" />
<PackageReference Include="Npgsql" Version="8.0.5" />
<PackageReference Include="System.IdentityModel.Tokens.Jwt" Version="7.3.1" />
</ItemGroup>

View File

@@ -3,7 +3,7 @@ Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 17
VisualStudioVersion = 17.8.34330.188
MinimumVisualStudioVersion = 10.0.40219.1
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CarCareTracker", "CarCareTracker.csproj", "{0DB85611-6555-4127-A66E-DADD25A16526}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CarCareTracker", "CarCareTracker.csproj", "{0DB85611-6555-4127-A66E-DADD25A16526}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution

View File

@@ -21,6 +21,9 @@ namespace CarCareTracker.Controllers
private readonly IReminderRecordDataAccess _reminderRecordDataAccess;
private readonly IUpgradeRecordDataAccess _upgradeRecordDataAccess;
private readonly IOdometerRecordDataAccess _odometerRecordDataAccess;
private readonly ISupplyRecordDataAccess _supplyRecordDataAccess;
private readonly IPlanRecordDataAccess _planRecordDataAccess;
private readonly IPlanRecordTemplateDataAccess _planRecordTemplateDataAccess;
private readonly IUserAccessDataAccess _userAccessDataAccess;
private readonly IUserRecordDataAccess _userRecordDataAccess;
private readonly IReminderHelper _reminderHelper;
@@ -42,6 +45,9 @@ namespace CarCareTracker.Controllers
IReminderRecordDataAccess reminderRecordDataAccess,
IUpgradeRecordDataAccess upgradeRecordDataAccess,
IOdometerRecordDataAccess odometerRecordDataAccess,
ISupplyRecordDataAccess supplyRecordDataAccess,
IPlanRecordDataAccess planRecordDataAccess,
IPlanRecordTemplateDataAccess planRecordTemplateDataAccess,
IUserAccessDataAccess userAccessDataAccess,
IUserRecordDataAccess userRecordDataAccess,
IMailHelper mailHelper,
@@ -60,6 +66,9 @@ namespace CarCareTracker.Controllers
_reminderRecordDataAccess = reminderRecordDataAccess;
_upgradeRecordDataAccess = upgradeRecordDataAccess;
_odometerRecordDataAccess = odometerRecordDataAccess;
_supplyRecordDataAccess = supplyRecordDataAccess;
_planRecordDataAccess = planRecordDataAccess;
_planRecordTemplateDataAccess = planRecordTemplateDataAccess;
_userAccessDataAccess = userAccessDataAccess;
_userRecordDataAccess = userRecordDataAccess;
_mailHelper = mailHelper;
@@ -90,37 +99,82 @@ namespace CarCareTracker.Controllers
}
return Json(result);
}
[HttpGet]
[Route("/api/vehicle/info")]
public IActionResult VehicleInfo(int vehicleId)
{
//stats for a specific or all vehicles
List<Vehicle> vehicles = new List<Vehicle>();
if (vehicleId != default)
{
if (_userLogic.UserCanEditVehicle(GetUserID(), vehicleId))
{
vehicles.Add(_dataAccess.GetVehicleById(vehicleId));
} else
{
return new RedirectResult("/Error/Unauthorized");
}
} else
{
var result = _dataAccess.GetVehicles();
if (!User.IsInRole(nameof(UserData.IsRootUser)))
{
result = _userLogic.FilterUserVehicles(result, GetUserID());
}
vehicles.AddRange(result);
}
var apiResult = _vehicleLogic.GetVehicleInfo(vehicles);
return Json(apiResult);
}
[TypeFilter(typeof(CollaboratorFilter))]
[HttpGet]
[Route("/api/vehicle/adjustedodometer")]
public IActionResult AdjustedOdometer(int vehicleId, int odometer)
{
var vehicle = _dataAccess.GetVehicleById(vehicleId);
if (vehicle == null || !vehicle.HasOdometerAdjustment)
{
return Json(odometer);
} else
{
var convertedOdometer = (odometer + int.Parse(vehicle.OdometerDifference)) * decimal.Parse(vehicle.OdometerMultiplier);
return Json(convertedOdometer);
}
}
[TypeFilter(typeof(CollaboratorFilter))]
[HttpGet]
[Route("/api/vehicle/servicerecords")]
public IActionResult ServiceRecords(int vehicleId)
{
if (vehicleId == default)
{
var response = OperationResponse.Failed("Must provide a valid vehicle id");
Response.StatusCode = 400;
return Json(response);
}
var vehicleRecords = _serviceRecordDataAccess.GetServiceRecordsByVehicleId(vehicleId);
var result = vehicleRecords.Select(x => new ServiceRecordExportModel { Date = x.Date.ToShortDateString(), Description = x.Description, Cost = x.Cost.ToString(), Notes = x.Notes, Odometer = x.Mileage.ToString() });
var result = vehicleRecords.Select(x => new GenericRecordExportModel { Date = x.Date.ToShortDateString(), Description = x.Description, Cost = x.Cost.ToString(), Notes = x.Notes, Odometer = x.Mileage.ToString(), ExtraFields = x.ExtraFields });
return Json(result);
}
[TypeFilter(typeof(CollaboratorFilter))]
[HttpPost]
[Route("/api/vehicle/servicerecords/add")]
public IActionResult AddServiceRecord(int vehicleId, ServiceRecordExportModel input)
public IActionResult AddServiceRecord(int vehicleId, GenericRecordExportModel input)
{
var response = new OperationResponse();
if (vehicleId == default)
{
response.Success = false;
response.Message = "Must provide a valid vehicle id";
Response.StatusCode = 400;
return Json(response);
return Json(OperationResponse.Failed("Must provide a valid vehicle id"));
}
if (string.IsNullOrWhiteSpace(input.Date) ||
string.IsNullOrWhiteSpace(input.Description) ||
string.IsNullOrWhiteSpace(input.Odometer) ||
string.IsNullOrWhiteSpace(input.Cost))
{
response.Success = false;
response.Message = "Input object invalid, Date, Description, Odometer, and Cost cannot be empty.";
Response.StatusCode = 400;
return Json(response);
return Json(OperationResponse.Failed("Input object invalid, Date, Description, Odometer, and Cost cannot be empty."));
}
try
{
@@ -131,7 +185,9 @@ namespace CarCareTracker.Controllers
Mileage = int.Parse(input.Odometer),
Description = input.Description,
Notes = string.IsNullOrWhiteSpace(input.Notes) ? "" : input.Notes,
Cost = decimal.Parse(input.Cost)
Cost = decimal.Parse(input.Cost),
ExtraFields = input.ExtraFields,
Tags = string.IsNullOrWhiteSpace(input.Tags) ? new List<string>() : input.Tags.Split(' ').Distinct().ToList()
};
_serviceRecordDataAccess.SaveServiceRecordToVehicle(serviceRecord);
if (_config.GetUserConfig(User).EnableAutoOdometerInsert)
@@ -146,16 +202,12 @@ namespace CarCareTracker.Controllers
_odometerLogic.AutoInsertOdometerRecord(odometerRecord);
}
StaticHelper.NotifyAsync(_config.GetWebHookUrl(), vehicleId, User.Identity.Name, $"Added Service Record via API - Description: {serviceRecord.Description}");
response.Success = true;
response.Message = "Service Record Added";
return Json(response);
return Json(OperationResponse.Succeed("Service Record Added"));
}
catch (Exception ex)
{
response.Success = false;
response.Message = ex.Message;
Response.StatusCode = 500;
return Json(response);
return Json(OperationResponse.Failed(ex.Message));
}
}
[TypeFilter(typeof(CollaboratorFilter))]
@@ -163,32 +215,33 @@ namespace CarCareTracker.Controllers
[Route("/api/vehicle/repairrecords")]
public IActionResult RepairRecords(int vehicleId)
{
if (vehicleId == default)
{
var response = OperationResponse.Failed("Must provide a valid vehicle id");
Response.StatusCode = 400;
return Json(response);
}
var vehicleRecords = _collisionRecordDataAccess.GetCollisionRecordsByVehicleId(vehicleId);
var result = vehicleRecords.Select(x => new ServiceRecordExportModel { Date = x.Date.ToShortDateString(), Description = x.Description, Cost = x.Cost.ToString(), Notes = x.Notes, Odometer = x.Mileage.ToString() });
var result = vehicleRecords.Select(x => new GenericRecordExportModel { Date = x.Date.ToShortDateString(), Description = x.Description, Cost = x.Cost.ToString(), Notes = x.Notes, Odometer = x.Mileage.ToString(), ExtraFields = x.ExtraFields });
return Json(result);
}
[TypeFilter(typeof(CollaboratorFilter))]
[HttpPost]
[Route("/api/vehicle/repairrecords/add")]
public IActionResult AddRepairRecord(int vehicleId, ServiceRecordExportModel input)
public IActionResult AddRepairRecord(int vehicleId, GenericRecordExportModel input)
{
var response = new OperationResponse();
if (vehicleId == default)
{
response.Success = false;
response.Message = "Must provide a valid vehicle id";
Response.StatusCode = 400;
return Json(response);
return Json(OperationResponse.Failed("Must provide a valid vehicle id"));
}
if (string.IsNullOrWhiteSpace(input.Date) ||
string.IsNullOrWhiteSpace(input.Description) ||
string.IsNullOrWhiteSpace(input.Odometer) ||
string.IsNullOrWhiteSpace(input.Cost))
{
response.Success = false;
response.Message = "Input object invalid, Date, Description, Odometer, and Cost cannot be empty.";
Response.StatusCode = 400;
return Json(response);
return Json(OperationResponse.Failed("Input object invalid, Date, Description, Odometer, and Cost cannot be empty."));
}
try
{
@@ -199,7 +252,9 @@ namespace CarCareTracker.Controllers
Mileage = int.Parse(input.Odometer),
Description = input.Description,
Notes = string.IsNullOrWhiteSpace(input.Notes) ? "" : input.Notes,
Cost = decimal.Parse(input.Cost)
Cost = decimal.Parse(input.Cost),
ExtraFields = input.ExtraFields,
Tags = string.IsNullOrWhiteSpace(input.Tags) ? new List<string>() : input.Tags.Split(' ').Distinct().ToList()
};
_collisionRecordDataAccess.SaveCollisionRecordToVehicle(repairRecord);
if (_config.GetUserConfig(User).EnableAutoOdometerInsert)
@@ -214,16 +269,12 @@ namespace CarCareTracker.Controllers
_odometerLogic.AutoInsertOdometerRecord(odometerRecord);
}
StaticHelper.NotifyAsync(_config.GetWebHookUrl(), vehicleId, User.Identity.Name, $"Added Repair Record via API - Description: {repairRecord.Description}");
response.Success = true;
response.Message = "Repair Record Added";
return Json(response);
return Json(OperationResponse.Succeed("Repair Record Added"));
}
catch (Exception ex)
{
response.Success = false;
response.Message = ex.Message;
Response.StatusCode = 500;
return Json(response);
return Json(OperationResponse.Failed(ex.Message));
}
}
[TypeFilter(typeof(CollaboratorFilter))]
@@ -231,32 +282,33 @@ namespace CarCareTracker.Controllers
[Route("/api/vehicle/upgraderecords")]
public IActionResult UpgradeRecords(int vehicleId)
{
if (vehicleId == default)
{
var response = OperationResponse.Failed("Must provide a valid vehicle id");
Response.StatusCode = 400;
return Json(response);
}
var vehicleRecords = _upgradeRecordDataAccess.GetUpgradeRecordsByVehicleId(vehicleId);
var result = vehicleRecords.Select(x => new ServiceRecordExportModel { Date = x.Date.ToShortDateString(), Description = x.Description, Cost = x.Cost.ToString(), Notes = x.Notes, Odometer = x.Mileage.ToString() });
var result = vehicleRecords.Select(x => new GenericRecordExportModel { Date = x.Date.ToShortDateString(), Description = x.Description, Cost = x.Cost.ToString(), Notes = x.Notes, Odometer = x.Mileage.ToString(), ExtraFields = x.ExtraFields });
return Json(result);
}
[TypeFilter(typeof(CollaboratorFilter))]
[HttpPost]
[Route("/api/vehicle/upgraderecords/add")]
public IActionResult AddUpgradeRecord(int vehicleId, ServiceRecordExportModel input)
public IActionResult AddUpgradeRecord(int vehicleId, GenericRecordExportModel input)
{
var response = new OperationResponse();
if (vehicleId == default)
{
response.Success = false;
response.Message = "Must provide a valid vehicle id";
Response.StatusCode = 400;
return Json(response);
return Json(OperationResponse.Failed("Must provide a valid vehicle id"));
}
if (string.IsNullOrWhiteSpace(input.Date) ||
string.IsNullOrWhiteSpace(input.Description) ||
string.IsNullOrWhiteSpace(input.Odometer) ||
string.IsNullOrWhiteSpace(input.Cost))
{
response.Success = false;
response.Message = "Input object invalid, Date, Description, Odometer, and Cost cannot be empty.";
Response.StatusCode = 400;
return Json(response);
return Json(OperationResponse.Failed("Input object invalid, Date, Description, Odometer, and Cost cannot be empty."));
}
try
{
@@ -267,7 +319,9 @@ namespace CarCareTracker.Controllers
Mileage = int.Parse(input.Odometer),
Description = input.Description,
Notes = string.IsNullOrWhiteSpace(input.Notes) ? "" : input.Notes,
Cost = decimal.Parse(input.Cost)
Cost = decimal.Parse(input.Cost),
ExtraFields = input.ExtraFields,
Tags = string.IsNullOrWhiteSpace(input.Tags) ? new List<string>() : input.Tags.Split(' ').Distinct().ToList()
};
_upgradeRecordDataAccess.SaveUpgradeRecordToVehicle(upgradeRecord);
if (_config.GetUserConfig(User).EnableAutoOdometerInsert)
@@ -282,16 +336,12 @@ namespace CarCareTracker.Controllers
_odometerLogic.AutoInsertOdometerRecord(odometerRecord);
}
StaticHelper.NotifyAsync(_config.GetWebHookUrl(), vehicleId, User.Identity.Name, $"Added Upgrade Record via API - Description: {upgradeRecord.Description}");
response.Success = true;
response.Message = "Upgrade Record Added";
return Json(response);
return Json(OperationResponse.Succeed("Upgrade Record Added"));
}
catch (Exception ex)
{
response.Success = false;
response.Message = ex.Message;
Response.StatusCode = 500;
return Json(response);
return Json(OperationResponse.Failed(ex.Message));
}
}
[TypeFilter(typeof(CollaboratorFilter))]
@@ -299,7 +349,13 @@ namespace CarCareTracker.Controllers
[Route("/api/vehicle/taxrecords")]
public IActionResult TaxRecords(int vehicleId)
{
var result = _taxRecordDataAccess.GetTaxRecordsByVehicleId(vehicleId);
if (vehicleId == default)
{
var response = OperationResponse.Failed("Must provide a valid vehicle id");
Response.StatusCode = 400;
return Json(response);
}
var result = _taxRecordDataAccess.GetTaxRecordsByVehicleId(vehicleId).Select(x => new TaxRecordExportModel { Date = x.Date.ToShortDateString(), Description = x.Description, Cost = x.Cost.ToString(), Notes = x.Notes, ExtraFields = x.ExtraFields });
return Json(result);
}
[TypeFilter(typeof(CollaboratorFilter))]
@@ -307,22 +363,17 @@ namespace CarCareTracker.Controllers
[Route("/api/vehicle/taxrecords/add")]
public IActionResult AddTaxRecord(int vehicleId, TaxRecordExportModel input)
{
var response = new OperationResponse();
if (vehicleId == default)
{
response.Success = false;
response.Message = "Must provide a valid vehicle id";
Response.StatusCode = 400;
return Json(response);
return Json(OperationResponse.Failed("Must provide a valid vehicle id"));
}
if (string.IsNullOrWhiteSpace(input.Date) ||
string.IsNullOrWhiteSpace(input.Description) ||
string.IsNullOrWhiteSpace(input.Cost))
{
response.Success = false;
response.Message = "Input object invalid, Date, Description, and Cost cannot be empty.";
Response.StatusCode = 400;
return Json(response);
return Json(OperationResponse.Failed("Input object invalid, Date, Description, and Cost cannot be empty."));
}
try
{
@@ -332,20 +383,18 @@ namespace CarCareTracker.Controllers
Date = DateTime.Parse(input.Date),
Description = input.Description,
Notes = string.IsNullOrWhiteSpace(input.Notes) ? "" : input.Notes,
Cost = decimal.Parse(input.Cost)
Cost = decimal.Parse(input.Cost),
ExtraFields = input.ExtraFields,
Tags = string.IsNullOrWhiteSpace(input.Tags) ? new List<string>() : input.Tags.Split(' ').Distinct().ToList()
};
_taxRecordDataAccess.SaveTaxRecordToVehicle(taxRecord);
StaticHelper.NotifyAsync(_config.GetWebHookUrl(), vehicleId, User.Identity.Name, $"Added Tax Record via API - Description: {taxRecord.Description}");
response.Success = true;
response.Message = "Tax Record Added";
return Json(response);
return Json(OperationResponse.Succeed("Tax Record Added"));
}
catch (Exception ex)
{
response.Success = false;
response.Message = ex.Message;
Response.StatusCode = 500;
return Json(response);
return Json(OperationResponse.Failed(ex.Message));
}
}
[TypeFilter(typeof(CollaboratorFilter))]
@@ -353,6 +402,12 @@ namespace CarCareTracker.Controllers
[Route("/api/vehicle/odometerrecords/latest")]
public IActionResult LastOdometer(int vehicleId)
{
if (vehicleId == default)
{
var response = OperationResponse.Failed("Must provide a valid vehicle id");
Response.StatusCode = 400;
return Json(response);
}
var result = _vehicleLogic.GetMaxMileage(vehicleId);
return Json(result);
}
@@ -361,13 +416,19 @@ namespace CarCareTracker.Controllers
[Route("/api/vehicle/odometerrecords")]
public IActionResult OdometerRecords(int vehicleId)
{
if (vehicleId == default)
{
var response = OperationResponse.Failed("Must provide a valid vehicle id");
Response.StatusCode = 400;
return Json(response);
}
var vehicleRecords = _odometerRecordDataAccess.GetOdometerRecordsByVehicleId(vehicleId);
//determine if conversion is needed.
if (vehicleRecords.All(x => x.InitialMileage == default))
{
vehicleRecords = _odometerLogic.AutoConvertOdometerRecord(vehicleRecords);
}
var result = vehicleRecords.Select(x => new OdometerRecordExportModel { Date = x.Date.ToShortDateString(), InitialOdometer = x.InitialMileage.ToString(), Odometer = x.Mileage.ToString(), Notes = x.Notes });
var result = vehicleRecords.Select(x => new OdometerRecordExportModel { Date = x.Date.ToShortDateString(), InitialOdometer = x.InitialMileage.ToString(), Odometer = x.Mileage.ToString(), Notes = x.Notes, ExtraFields = x.ExtraFields });
return Json(result);
}
[TypeFilter(typeof(CollaboratorFilter))]
@@ -375,21 +436,16 @@ namespace CarCareTracker.Controllers
[Route("/api/vehicle/odometerrecords/add")]
public IActionResult AddOdometerRecord(int vehicleId, OdometerRecordExportModel input)
{
var response = new OperationResponse();
if (vehicleId == default)
{
response.Success = false;
response.Message = "Must provide a valid vehicle id";
Response.StatusCode = 400;
return Json(response);
return Json(OperationResponse.Failed("Must provide a valid vehicle id"));
}
if (string.IsNullOrWhiteSpace(input.Date) ||
string.IsNullOrWhiteSpace(input.Odometer))
{
response.Success = false;
response.Message = "Input object invalid, Date and Odometer cannot be empty.";
Response.StatusCode = 400;
return Json(response);
return Json(OperationResponse.Failed("Input object invalid, Date and Odometer cannot be empty."));
}
try
{
@@ -399,19 +455,17 @@ namespace CarCareTracker.Controllers
Date = DateTime.Parse(input.Date),
Notes = string.IsNullOrWhiteSpace(input.Notes) ? "" : input.Notes,
InitialMileage = (string.IsNullOrWhiteSpace(input.InitialOdometer) || int.Parse(input.InitialOdometer) == default) ? _odometerLogic.GetLastOdometerRecordMileage(vehicleId, new List<OdometerRecord>()) : int.Parse(input.InitialOdometer),
Mileage = int.Parse(input.Odometer)
Mileage = int.Parse(input.Odometer),
ExtraFields = input.ExtraFields,
Tags = string.IsNullOrWhiteSpace(input.Tags) ? new List<string>() : input.Tags.Split(' ').Distinct().ToList()
};
_odometerRecordDataAccess.SaveOdometerRecordToVehicle(odometerRecord);
StaticHelper.NotifyAsync(_config.GetWebHookUrl(), vehicleId, User.Identity.Name, $"Added Odometer Record via API - Mileage: {odometerRecord.Mileage.ToString()}");
response.Success = true;
response.Message = "Odometer Record Added";
return Json(response);
return Json(OperationResponse.Succeed("Odometer Record Added"));
} catch (Exception ex)
{
response.Success = false;
response.Message = ex.Message;
Response.StatusCode = 500;
return Json(response);
return Json(OperationResponse.Failed(ex.Message));
}
}
[TypeFilter(typeof(CollaboratorFilter))]
@@ -419,6 +473,12 @@ namespace CarCareTracker.Controllers
[Route("/api/vehicle/gasrecords")]
public IActionResult GasRecords(int vehicleId, bool useMPG, bool useUKMPG)
{
if (vehicleId == default)
{
var response = OperationResponse.Failed("Must provide a valid vehicle id");
Response.StatusCode = 400;
return Json(response);
}
var vehicleRecords = _gasRecordDataAccess.GetGasRecordsByVehicleId(vehicleId);
var result = _gasHelper.GetGasRecordViewModels(vehicleRecords, useMPG, useUKMPG)
.Select(x => new GasRecordExportModel {
@@ -429,7 +489,8 @@ namespace CarCareTracker.Controllers
FuelEconomy = x.MilesPerGallon.ToString(),
IsFillToFull = x.IsFillToFull.ToString(),
MissedFuelUp = x.MissedFuelUp.ToString(),
Notes = x.Notes
Notes = x.Notes,
ExtraFields = x.ExtraFields
});
return Json(result);
}
@@ -438,13 +499,10 @@ namespace CarCareTracker.Controllers
[Route("/api/vehicle/gasrecords/add")]
public IActionResult AddGasRecord(int vehicleId, GasRecordExportModel input)
{
var response = new OperationResponse();
if (vehicleId == default)
{
response.Success = false;
response.Message = "Must provide a valid vehicle id";
Response.StatusCode = 400;
return Json(response);
return Json(OperationResponse.Failed("Must provide a valid vehicle id"));
}
if (string.IsNullOrWhiteSpace(input.Date) ||
string.IsNullOrWhiteSpace(input.Odometer) ||
@@ -454,10 +512,8 @@ namespace CarCareTracker.Controllers
string.IsNullOrWhiteSpace(input.MissedFuelUp)
)
{
response.Success = false;
response.Message = "Input object invalid, Date, Odometer, FuelConsumed, IsFillToFull, MissedFuelUp, and Cost cannot be empty.";
Response.StatusCode = 400;
return Json(response);
return Json(OperationResponse.Failed("Input object invalid, Date, Odometer, FuelConsumed, IsFillToFull, MissedFuelUp, and Cost cannot be empty."));
}
try
{
@@ -470,7 +526,9 @@ namespace CarCareTracker.Controllers
IsFillToFull = bool.Parse(input.IsFillToFull),
MissedFuelUp = bool.Parse(input.MissedFuelUp),
Notes = string.IsNullOrWhiteSpace(input.Notes) ? "" : input.Notes,
Cost = decimal.Parse(input.Cost)
Cost = decimal.Parse(input.Cost),
ExtraFields = input.ExtraFields,
Tags = string.IsNullOrWhiteSpace(input.Tags) ? new List<string>() : input.Tags.Split(' ').Distinct().ToList()
};
_gasRecordDataAccess.SaveGasRecordToVehicle(gasRecord);
if (_config.GetUserConfig(User).EnableAutoOdometerInsert)
@@ -485,16 +543,12 @@ namespace CarCareTracker.Controllers
_odometerLogic.AutoInsertOdometerRecord(odometerRecord);
}
StaticHelper.NotifyAsync(_config.GetWebHookUrl(), vehicleId, User.Identity.Name, $"Added Gas record via API - Mileage: {gasRecord.Mileage.ToString()}");
response.Success = true;
response.Message = "Gas Record Added";
return Json(response);
return Json(OperationResponse.Succeed("Gas Record Added"));
}
catch (Exception ex)
{
response.Success = false;
response.Message = ex.Message;
Response.StatusCode = 500;
return Json(response);
return Json(OperationResponse.Failed(ex.Message));
}
}
[TypeFilter(typeof(CollaboratorFilter))]
@@ -502,9 +556,14 @@ namespace CarCareTracker.Controllers
[Route("/api/vehicle/reminders")]
public IActionResult Reminders(int vehicleId)
{
if (vehicleId == default)
{
Response.StatusCode = 400;
return Json(OperationResponse.Failed("Must provide a valid vehicle id"));
}
var currentMileage = _vehicleLogic.GetMaxMileage(vehicleId);
var reminders = _reminderRecordDataAccess.GetReminderRecordsByVehicleId(vehicleId);
var results = _reminderHelper.GetReminderRecordViewModels(reminders, currentMileage, DateTime.Now).Select(x=> new ReminderExportModel { Description = x.Description, Urgency = x.Urgency.ToString(), Metric = x.Metric.ToString(), Notes = x.Notes});
var results = _reminderHelper.GetReminderRecordViewModels(reminders, currentMileage, DateTime.Now).Select(x=> new ReminderExportModel { Description = x.Description, Urgency = x.Urgency.ToString(), Metric = x.Metric.ToString(), Notes = x.Notes, DueDate = x.Date.ToShortDateString(), DueOdometer = x.Mileage.ToString()});
return Json(results);
}
[Authorize(Roles = nameof(UserData.IsRootUser))]
@@ -514,6 +573,7 @@ namespace CarCareTracker.Controllers
{
var vehicles = _dataAccess.GetVehicles();
List<OperationResponse> operationResponses = new List<OperationResponse>();
var defaultEmailAddress = _config.GetUserConfig(User).DefaultReminderEmail;
foreach(Vehicle vehicle in vehicles)
{
var vehicleId = vehicle.Id;
@@ -529,6 +589,10 @@ namespace CarCareTracker.Controllers
//get list of recipients.
var userIds = _userAccessDataAccess.GetUserAccessByVehicleId(vehicleId).Select(x => x.Id.UserId);
List<string> emailRecipients = new List<string>();
if (!string.IsNullOrWhiteSpace(defaultEmailAddress))
{
emailRecipients.Add(defaultEmailAddress);
}
foreach (int userId in userIds)
{
var userData = _userRecordDataAccess.GetUserRecordById(userId);
@@ -541,15 +605,19 @@ namespace CarCareTracker.Controllers
var result = _mailHelper.NotifyUserForReminders(vehicle, emailRecipients, results);
operationResponses.Add(result);
}
if (operationResponses.All(x => x.Success))
if (!operationResponses.Any())
{
return Json(new OperationResponse { Success = true, Message = "Emails sent" });
return Json(OperationResponse.Failed("No Emails Sent, No Vehicles Available or No Recipients Configured"));
}
else if (operationResponses.All(x => x.Success))
{
return Json(OperationResponse.Succeed($"Emails Sent({operationResponses.Count()})"));
} else if (operationResponses.All(x => !x.Success))
{
return Json(new OperationResponse { Success = false, Message = "All emails failed, check SMTP settings" });
return Json(OperationResponse.Failed($"All Emails Failed({operationResponses.Count()}), Check SMTP Settings"));
} else
{
return Json(new OperationResponse { Success = true, Message = "Some emails sent, some failed, check recipient settings" });
return Json(OperationResponse.Succeed($"Emails Sent({operationResponses.Count(x => x.Success)}), Emails Failed({operationResponses.Count(x => !x.Success)}), Check Recipient Settings"));
}
}
[Authorize(Roles = nameof(UserData.IsRootUser))]
@@ -562,6 +630,49 @@ namespace CarCareTracker.Controllers
}
[Authorize(Roles = nameof(UserData.IsRootUser))]
[HttpGet]
[Route("/api/cleanup")]
public IActionResult CleanUp(bool deepClean = false)
{
var jsonResponse = new Dictionary<string, string>();
//Clear out temp folder
var tempFilesDeleted = _fileHelper.ClearTempFolder();
jsonResponse.Add("temp_files_deleted", tempFilesDeleted.ToString());
if (deepClean)
{
//clear out unused vehicle thumbnails
var vehicles = _dataAccess.GetVehicles();
var vehicleImages = vehicles.Select(x => x.ImageLocation).Where(x => x.StartsWith("/images/")).Select(x=>Path.GetFileName(x)).ToList();
if (vehicleImages.Any())
{
var thumbnailsDeleted = _fileHelper.ClearUnlinkedThumbnails(vehicleImages);
jsonResponse.Add("unlinked_thumbnails_deleted", thumbnailsDeleted.ToString());
}
var vehicleDocuments = new List<string>();
foreach(Vehicle vehicle in vehicles)
{
vehicleDocuments.AddRange(_serviceRecordDataAccess.GetServiceRecordsByVehicleId(vehicle.Id).SelectMany(x => x.Files).Select(y=>Path.GetFileName(y.Location)));
vehicleDocuments.AddRange(_collisionRecordDataAccess.GetCollisionRecordsByVehicleId(vehicle.Id).SelectMany(x => x.Files).Select(y => Path.GetFileName(y.Location)));
vehicleDocuments.AddRange(_upgradeRecordDataAccess.GetUpgradeRecordsByVehicleId(vehicle.Id).SelectMany(x => x.Files).Select(y => Path.GetFileName(y.Location)));
vehicleDocuments.AddRange(_taxRecordDataAccess.GetTaxRecordsByVehicleId(vehicle.Id).SelectMany(x => x.Files).Select(y => Path.GetFileName(y.Location)));
vehicleDocuments.AddRange(_gasRecordDataAccess.GetGasRecordsByVehicleId(vehicle.Id).SelectMany(x => x.Files).Select(y => Path.GetFileName(y.Location)));
vehicleDocuments.AddRange(_noteDataAccess.GetNotesByVehicleId(vehicle.Id).SelectMany(x => x.Files).Select(y => Path.GetFileName(y.Location)));
vehicleDocuments.AddRange(_odometerRecordDataAccess.GetOdometerRecordsByVehicleId(vehicle.Id).SelectMany(x => x.Files).Select(y => Path.GetFileName(y.Location)));
vehicleDocuments.AddRange(_supplyRecordDataAccess.GetSupplyRecordsByVehicleId(vehicle.Id).SelectMany(x => x.Files).Select(y => Path.GetFileName(y.Location)));
vehicleDocuments.AddRange(_planRecordDataAccess.GetPlanRecordsByVehicleId(vehicle.Id).SelectMany(x => x.Files).Select(y => Path.GetFileName(y.Location)));
vehicleDocuments.AddRange(_planRecordTemplateDataAccess.GetPlanRecordTemplatesByVehicleId(vehicle.Id).SelectMany(x => x.Files).Select(y => Path.GetFileName(y.Location)));
}
//shop supplies
vehicleDocuments.AddRange(_supplyRecordDataAccess.GetSupplyRecordsByVehicleId(0).SelectMany(x => x.Files).Select(y => Path.GetFileName(y.Location)));
if (vehicleDocuments.Any())
{
var documentsDeleted = _fileHelper.ClearUnlinkedDocuments(vehicleDocuments);
jsonResponse.Add("unlinked_documents_deleted", documentsDeleted.ToString());
}
}
return Json(jsonResponse);
}
[Authorize(Roles = nameof(UserData.IsRootUser))]
[HttpGet]
[Route("/api/demo/restore")]
public IActionResult RestoreDemo()
{

View File

@@ -22,15 +22,46 @@ namespace CarCareTracker.Controllers
{
var viewModel = new AdminViewModel
{
Users = _loginLogic.GetAllUsers(),
Users = _loginLogic.GetAllUsers().OrderBy(x=>x.Id).ToList(),
Tokens = _loginLogic.GetAllTokens()
};
return View(viewModel);
}
public IActionResult GetTokenPartialView()
{
var viewModel = _loginLogic.GetAllTokens();
return PartialView("_Tokens", viewModel);
}
public IActionResult GetUserPartialView()
{
var viewModel = _loginLogic.GetAllUsers().OrderBy(x => x.Id).ToList();
return PartialView("_Users", viewModel);
}
public IActionResult GenerateNewToken(string emailAddress, bool autoNotify)
{
var result = _loginLogic.GenerateUserToken(emailAddress, autoNotify);
return Json(result);
if (emailAddress.Contains(","))
{
string[] emailAddresses = emailAddress.Split(',');
foreach(string emailAdd in emailAddresses)
{
var trimmedEmail = emailAdd.Trim();
if (!string.IsNullOrWhiteSpace(trimmedEmail))
{
var result = _loginLogic.GenerateUserToken(emailAdd.Trim(), autoNotify);
if (!result.Success)
{
//if fail, return prematurely
return Json(result);
}
}
}
var successResponse = OperationResponse.Succeed("Token Generated!");
return Json(successResponse);
} else
{
var result = _loginLogic.GenerateUserToken(emailAddress, autoNotify);
return Json(result);
}
}
[HttpPost]
public IActionResult DeleteToken(int tokenId)

View File

@@ -32,7 +32,7 @@ namespace CarCareTracker.Controllers
var originalFileName = Path.GetFileNameWithoutExtension(file.FileName);
if (originalFileName == "en_US")
{
return Json(new OperationResponse { Success = false, Message = "The translation file name en_US is reserved." });
return Json(OperationResponse.Failed("The translation file name en_US is reserved."));
}
var fileName = UploadFile(file);
//move file from temp to translation folder.
@@ -41,9 +41,9 @@ namespace CarCareTracker.Controllers
if (!string.IsNullOrWhiteSpace(uploadedFilePath))
{
var result = _fileHelper.RenameFile(uploadedFilePath, originalFileName);
return Json(new OperationResponse { Success = result, Message = string.Empty });
return Json(OperationResponse.Conditional(result));
}
return Json(new OperationResponse { Success = false, Message = StaticHelper.GenericErrorMessage });
return Json(OperationResponse.Failed());
}
[HttpPost]
@@ -92,5 +92,18 @@ namespace CarCareTracker.Controllers
}
return Path.Combine("/", uploadDirectory, fileName);
}
public IActionResult UploadCoordinates(List<string> coordinates)
{
string uploadDirectory = "temp/";
string uploadPath = Path.Combine(_webEnv.WebRootPath, uploadDirectory);
if (!Directory.Exists(uploadPath))
Directory.CreateDirectory(uploadPath);
string fileName = Guid.NewGuid() + ".csv";
string filePath = Path.Combine(uploadPath, fileName);
string fileData = string.Join("\r\n", coordinates);
System.IO.File.WriteAllText(filePath, fileData);
var uploadedFile = new UploadedFiles { Name = "coordinates.csv", Location = Path.Combine("/", uploadDirectory, fileName) };
return Json(uploadedFile);
}
}
}

View File

@@ -22,6 +22,7 @@ namespace CarCareTracker.Controllers
private readonly IExtraFieldDataAccess _extraFieldDataAccess;
private readonly IReminderRecordDataAccess _reminderRecordDataAccess;
private readonly IReminderHelper _reminderHelper;
private readonly ITranslationHelper _translationHelper;
public HomeController(ILogger<HomeController> logger,
IVehicleDataAccess dataAccess,
IUserLogic userLogic,
@@ -31,7 +32,8 @@ namespace CarCareTracker.Controllers
IFileHelper fileHelper,
IExtraFieldDataAccess extraFieldDataAccess,
IReminderRecordDataAccess reminderRecordDataAccess,
IReminderHelper reminderHelper)
IReminderHelper reminderHelper,
ITranslationHelper translationHelper)
{
_logger = logger;
_dataAccess = dataAccess;
@@ -43,6 +45,7 @@ namespace CarCareTracker.Controllers
_reminderHelper = reminderHelper;
_loginLogic = loginLogic;
_vehicleLogic = vehicleLogic;
_translationHelper = translationHelper;
}
private int GetUserID()
{
@@ -52,6 +55,59 @@ namespace CarCareTracker.Controllers
{
return View(model: tab);
}
[Route("/kiosk")]
public IActionResult Kiosk(string exclusions, KioskMode kioskMode = KioskMode.Vehicle)
{
try {
var viewModel = new KioskViewModel
{
Exclusions = string.IsNullOrWhiteSpace(exclusions) ? new List<int>() : exclusions.Split(',').Select(x => int.Parse(x)).ToList(),
KioskMode = kioskMode
};
return View(viewModel);
}
catch (Exception ex)
{
_logger.LogError(ex.Message);
return View(new KioskViewModel());
}
}
[HttpPost]
public IActionResult KioskContent(KioskViewModel kioskParameters)
{
var vehiclesStored = _dataAccess.GetVehicles();
if (!User.IsInRole(nameof(UserData.IsRootUser)))
{
vehiclesStored = _userLogic.FilterUserVehicles(vehiclesStored, GetUserID());
}
vehiclesStored.RemoveAll(x => kioskParameters.Exclusions.Contains(x.Id));
var userConfig = _config.GetUserConfig(User);
if (userConfig.HideSoldVehicles)
{
vehiclesStored.RemoveAll(x => !string.IsNullOrWhiteSpace(x.SoldDate));
}
switch (kioskParameters.KioskMode)
{
case KioskMode.Vehicle:
{
var kioskResult = _vehicleLogic.GetVehicleInfo(vehiclesStored);
return PartialView("_Kiosk", kioskResult);
}
case KioskMode.Plan:
{
var kioskResult = _vehicleLogic.GetPlans(vehiclesStored, true);
return PartialView("_KioskPlan", kioskResult);
}
break;
case KioskMode.Reminder:
{
var kioskResult = _vehicleLogic.GetReminders(vehiclesStored, false);
return PartialView("_KioskReminder", kioskResult);
}
}
var result = _vehicleLogic.GetVehicleInfo(vehiclesStored);
return PartialView("_Kiosk", result);
}
public IActionResult Garage()
{
var vehiclesStored = _dataAccess.GetVehicles();
@@ -59,21 +115,52 @@ namespace CarCareTracker.Controllers
{
vehiclesStored = _userLogic.FilterUserVehicles(vehiclesStored, GetUserID());
}
var vehicleViewModels = vehiclesStored.Select(x => new VehicleViewModel
var vehicleViewModels = vehiclesStored.Select(x =>
{
Id = x.Id,
ImageLocation = x.ImageLocation,
Year = x.Year,
Make = x.Make,
Model = x.Model,
LicensePlate = x.LicensePlate,
SoldDate = x.SoldDate,
IsElectric = x.IsElectric,
UseHours = x.UseHours,
ExtraFields = x.ExtraFields,
Tags = x.Tags,
LastReportedMileage = _vehicleLogic.GetMaxMileage(x.Id),
HasReminders = _vehicleLogic.GetVehicleHasUrgentOrPastDueReminders(x.Id)
var vehicleVM = new VehicleViewModel
{
Id = x.Id,
ImageLocation = x.ImageLocation,
Year = x.Year,
Make = x.Make,
Model = x.Model,
LicensePlate = x.LicensePlate,
SoldDate = x.SoldDate,
IsElectric = x.IsElectric,
IsDiesel = x.IsDiesel,
UseHours = x.UseHours,
OdometerOptional = x.OdometerOptional,
ExtraFields = x.ExtraFields,
Tags = x.Tags,
DashboardMetrics = x.DashboardMetrics,
VehicleIdentifier = x.VehicleIdentifier
};
//dashboard metrics
if (x.DashboardMetrics.Any())
{
var vehicleRecords = _vehicleLogic.GetVehicleRecords(x.Id);
var userConfig = _config.GetUserConfig(User);
var distanceUnit = x.UseHours ? "h" : userConfig.UseMPG ? "mi." : "km";
if (vehicleVM.DashboardMetrics.Contains(DashboardMetric.Default))
{
vehicleVM.LastReportedMileage = _vehicleLogic.GetMaxMileage(vehicleRecords);
vehicleVM.HasReminders = _vehicleLogic.GetVehicleHasUrgentOrPastDueReminders(x.Id, vehicleVM.LastReportedMileage);
}
if (vehicleVM.DashboardMetrics.Contains(DashboardMetric.CostPerMile))
{
var vehicleTotalCost = _vehicleLogic.GetVehicleTotalCost(vehicleRecords);
var maxMileage = _vehicleLogic.GetMaxMileage(vehicleRecords);
var minMileage = _vehicleLogic.GetMinMileage(vehicleRecords);
var totalDistance = maxMileage - minMileage;
vehicleVM.CostPerMile = totalDistance != default ? vehicleTotalCost / totalDistance : 0.00M;
vehicleVM.DistanceUnit = distanceUnit;
}
if (vehicleVM.DashboardMetrics.Contains(DashboardMetric.TotalCost))
{
vehicleVM.TotalCost = _vehicleLogic.GetVehicleTotalCost(vehicleRecords);
}
}
return vehicleVM;
}).ToList();
return PartialView("_GarageDisplay", vehicleViewModels);
}
@@ -84,19 +171,7 @@ namespace CarCareTracker.Controllers
{
vehiclesStored = _userLogic.FilterUserVehicles(vehiclesStored, GetUserID());
}
List<ReminderRecordViewModel> reminders = new List<ReminderRecordViewModel>();
foreach (Vehicle vehicle in vehiclesStored)
{
var vehicleReminders = _reminderRecordDataAccess.GetReminderRecordsByVehicleId(vehicle.Id);
vehicleReminders.RemoveAll(x => x.Metric == ReminderMetric.Odometer);
//we don't care about mileages so we can basically fake the current vehicle mileage.
if (vehicleReminders.Any())
{
var reminderUrgency = _reminderHelper.GetReminderRecordViewModels(vehicleReminders, 0, DateTime.Now);
reminderUrgency = reminderUrgency.Select(x => new ReminderRecordViewModel { Id = x.Id, Date = x.Date, Urgency = x.Urgency, Description = $"{vehicle.Year} {vehicle.Make} {vehicle.Model} #{vehicle.LicensePlate} - {x.Description}" }).ToList();
reminders.AddRange(reminderUrgency);
}
}
var reminders = _vehicleLogic.GetReminders(vehiclesStored, true);
return PartialView("_Calendar", reminders);
}
public IActionResult ViewCalendarReminder(int reminderId)
@@ -105,7 +180,7 @@ namespace CarCareTracker.Controllers
var reminderUrgency = _reminderHelper.GetReminderRecordViewModels(new List<ReminderRecord> { reminder }, 0, DateTime.Now).FirstOrDefault();
return PartialView("_ReminderRecordCalendarModal", reminderUrgency);
}
public IActionResult Settings()
public async Task<IActionResult> Settings()
{
var userConfig = _config.GetUserConfig(User);
var languages = _fileHelper.GetLanguages();
@@ -116,6 +191,20 @@ namespace CarCareTracker.Controllers
};
return PartialView("_Settings", viewModel);
}
public async Task<IActionResult> Sponsors()
{
try
{
var httpClient = new HttpClient();
var sponsorsData = await httpClient.GetFromJsonAsync<Sponsors>(StaticHelper.SponsorsPath) ?? new Sponsors();
return PartialView("_Sponsors", sponsorsData);
}
catch (Exception ex)
{
_logger.LogError($"Unable to retrieve sponsors: {ex.Message}");
return PartialView("_Sponsors", new Sponsors());
}
}
[HttpPost]
public IActionResult WriteToSettings(UserConfig userConfig)
{
@@ -136,10 +225,6 @@ namespace CarCareTracker.Controllers
var result = _config.SaveUserConfig(User, existingConfig);
return Json(result);
}
public IActionResult Privacy()
{
return View();
}
[Authorize(Roles = nameof(UserData.IsRootUser))]
public IActionResult GetExtraFieldsModal(int importMode = 0)
{
@@ -177,7 +262,8 @@ namespace CarCareTracker.Controllers
return Json(result);
}
return Json(false);
} catch (Exception ex)
}
catch (Exception ex)
{
_logger.LogError(ex.Message);
return Json(false);
@@ -194,12 +280,12 @@ namespace CarCareTracker.Controllers
var result = _loginLogic.UpdateUserDetails(userId, userAccount);
return Json(result);
}
return Json(new OperationResponse { Success = false, Message = StaticHelper.GenericErrorMessage});
return Json(OperationResponse.Failed());
}
catch (Exception ex)
{
_logger.LogError(ex.Message);
return Json(new OperationResponse { Success = false, Message = StaticHelper.GenericErrorMessage });
return Json(OperationResponse.Failed());
}
}
[HttpGet]
@@ -209,6 +295,267 @@ namespace CarCareTracker.Controllers
var userName = User.Identity.Name;
return PartialView("_AccountModal", new UserData() { EmailAddress = emailAddress, UserName = userName });
}
[Authorize(Roles = nameof(UserData.IsRootUser))]
[HttpGet]
public IActionResult GetRootAccountInformationModal()
{
var userName = User.Identity.Name;
return PartialView("_RootAccountModal", new UserData() { UserName = userName });
}
[Authorize(Roles = nameof(UserData.IsRootUser))]
[HttpGet]
public IActionResult GetTranslatorEditor(string userLanguage)
{
var translationData = _translationHelper.GetTranslations(userLanguage);
return PartialView("_TranslationEditor", translationData);
}
[Authorize(Roles = nameof(UserData.IsRootUser))]
[HttpPost]
public IActionResult SaveTranslation(string userLanguage, Dictionary<string, string> translationData)
{
var result = _translationHelper.SaveTranslation(userLanguage, translationData);
return Json(result);
}
[Authorize(Roles = nameof(UserData.IsRootUser))]
[HttpPost]
public IActionResult ExportTranslation(Dictionary<string, string> translationData)
{
var result = _translationHelper.ExportTranslation(translationData);
return Json(result);
}
[Authorize(Roles = nameof(UserData.IsRootUser))]
[HttpGet]
public async Task<IActionResult> GetAvailableTranslations()
{
try
{
var httpClient = new HttpClient();
var translations = await httpClient.GetFromJsonAsync<Translations>(StaticHelper.TranslationDirectoryPath) ?? new Translations();
return PartialView("_Translations", translations);
}
catch (Exception ex)
{
_logger.LogError($"Unable to retrieve translations: {ex.Message}");
return PartialView("_Translations", new Translations());
}
}
[Authorize(Roles = nameof(UserData.IsRootUser))]
[HttpGet]
public async Task<IActionResult> DownloadTranslation(string continent, string name)
{
try
{
var httpClient = new HttpClient();
var translationData = await httpClient.GetFromJsonAsync<Dictionary<string, string>>(StaticHelper.GetTranslationDownloadPath(continent, name)) ?? new Dictionary<string, string>();
if (translationData.Any())
{
var result = _translationHelper.SaveTranslation(name, translationData);
if (!result.Success)
{
return Json(false);
}
}
else
{
_logger.LogError($"Unable to download translation: {name}");
return Json(false);
}
return Json(true);
}
catch (Exception ex)
{
_logger.LogError($"Unable to download translation: {ex.Message}");
return Json(false);
}
}
[Authorize(Roles = nameof(UserData.IsRootUser))]
[HttpGet]
public async Task<IActionResult> DownloadAllTranslations()
{
try
{
var httpClient = new HttpClient();
var translations = await httpClient.GetFromJsonAsync<Translations>(StaticHelper.TranslationDirectoryPath) ?? new Translations();
int translationsDownloaded = 0;
foreach (string translation in translations.Asia)
{
try
{
var translationData = await httpClient.GetFromJsonAsync<Dictionary<string, string>>(StaticHelper.GetTranslationDownloadPath("Asia", translation)) ?? new Dictionary<string, string>();
if (translationData.Any())
{
var result = _translationHelper.SaveTranslation(translation, translationData);
if (result.Success)
{
translationsDownloaded++;
};
}
}
catch (Exception ex)
{
_logger.LogError($"Error Downloading Translation {translation}: {ex.Message} ");
}
}
foreach (string translation in translations.Africa)
{
try
{
var translationData = await httpClient.GetFromJsonAsync<Dictionary<string, string>>(StaticHelper.GetTranslationDownloadPath("Africa", translation)) ?? new Dictionary<string, string>();
if (translationData.Any())
{
var result = _translationHelper.SaveTranslation(translation, translationData);
if (result.Success)
{
translationsDownloaded++;
};
}
}
catch (Exception ex)
{
_logger.LogError($"Error Downloading Translation {translation}: {ex.Message} ");
}
}
foreach (string translation in translations.Europe)
{
try
{
var translationData = await httpClient.GetFromJsonAsync<Dictionary<string, string>>(StaticHelper.GetTranslationDownloadPath("Europe", translation)) ?? new Dictionary<string, string>();
if (translationData.Any())
{
var result = _translationHelper.SaveTranslation(translation, translationData);
if (result.Success)
{
translationsDownloaded++;
};
}
}
catch (Exception ex)
{
_logger.LogError($"Error Downloading Translation {translation}: {ex.Message} ");
}
}
foreach (string translation in translations.NorthAmerica)
{
try
{
var translationData = await httpClient.GetFromJsonAsync<Dictionary<string, string>>(StaticHelper.GetTranslationDownloadPath("NorthAmerica", translation)) ?? new Dictionary<string, string>();
if (translationData.Any())
{
var result = _translationHelper.SaveTranslation(translation, translationData);
if (result.Success)
{
translationsDownloaded++;
};
}
}
catch (Exception ex)
{
_logger.LogError($"Error Downloading Translation {translation}: {ex.Message} ");
}
}
foreach (string translation in translations.SouthAmerica)
{
try
{
var translationData = await httpClient.GetFromJsonAsync<Dictionary<string, string>>(StaticHelper.GetTranslationDownloadPath("SouthAmerica", translation)) ?? new Dictionary<string, string>();
if (translationData.Any())
{
var result = _translationHelper.SaveTranslation(translation, translationData);
if (result.Success)
{
translationsDownloaded++;
};
}
}
catch (Exception ex)
{
_logger.LogError($"Error Downloading Translation {translation}: {ex.Message} ");
}
}
foreach (string translation in translations.Oceania)
{
try
{
var translationData = await httpClient.GetFromJsonAsync<Dictionary<string, string>>(StaticHelper.GetTranslationDownloadPath("Oceania", translation)) ?? new Dictionary<string, string>();
if (translationData.Any())
{
var result = _translationHelper.SaveTranslation(translation, translationData);
if (result.Success)
{
translationsDownloaded++;
};
}
}
catch (Exception ex)
{
_logger.LogError($"Error Downloading Translation {translation}: {ex.Message} ");
}
}
if (translationsDownloaded > 0)
{
return Json(OperationResponse.Succeed($"{translationsDownloaded} Translations Downloaded"));
} else
{
return Json(OperationResponse.Failed("No Translations Downloaded"));
}
}
catch (Exception ex)
{
_logger.LogError($"Unable to retrieve translations: {ex.Message}");
return Json(OperationResponse.Failed());
}
}
public ActionResult GetVehicleSelector(int vehicleId)
{
var vehiclesStored = _dataAccess.GetVehicles();
if (!User.IsInRole(nameof(UserData.IsRootUser)))
{
vehiclesStored = _userLogic.FilterUserVehicles(vehiclesStored, GetUserID());
}
if (vehicleId != default)
{
vehiclesStored.RemoveAll(x => x.Id == vehicleId);
}
var userConfig = _config.GetUserConfig(User);
if (userConfig.HideSoldVehicles)
{
vehiclesStored.RemoveAll(x => !string.IsNullOrWhiteSpace(x.SoldDate));
}
return PartialView("_VehicleSelector", vehiclesStored);
}
[Authorize(Roles = nameof(UserData.IsRootUser))]
[HttpGet]
public IActionResult GetCustomWidgetEditor()
{
if (_config.GetCustomWidgetsEnabled())
{
var customWidgetData = _fileHelper.GetWidgets();
return PartialView("_WidgetEditor", customWidgetData);
}
return Json(string.Empty);
}
[Authorize(Roles = nameof(UserData.IsRootUser))]
[HttpPost]
public IActionResult SaveCustomWidgets(string widgetsData)
{
if (_config.GetCustomWidgetsEnabled())
{
var saveResult = _fileHelper.SaveWidgets(widgetsData);
return Json(saveResult);
}
return Json(false);
}
[Authorize(Roles = nameof(UserData.IsRootUser))]
[HttpPost]
public IActionResult DeleteCustomWidgets()
{
if (_config.GetCustomWidgetsEnabled())
{
var deleteResult = _fileHelper.DeleteWidgets();
return Json(deleteResult);
}
return Json(false);
}
[ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)]
public IActionResult Error()
{

View File

@@ -34,10 +34,16 @@ namespace CarCareTracker.Controllers
{
var generatedState = Guid.NewGuid().ToString().Substring(0, 8);
remoteAuthConfig.State = generatedState;
var pkceKeyPair = _loginLogic.GetPKCEChallengeCode();
remoteAuthConfig.CodeChallenge = pkceKeyPair.Value;
if (remoteAuthConfig.ValidateState)
{
Response.Cookies.Append("OIDC_STATE", remoteAuthConfig.State, new CookieOptions { Expires = new DateTimeOffset(DateTime.Now.AddMinutes(5)) });
}
if (remoteAuthConfig.UsePKCE)
{
Response.Cookies.Append("OIDC_VERIFIER", pkceKeyPair.Key, new CookieOptions { Expires = new DateTimeOffset(DateTime.Now.AddMinutes(5)) });
}
var remoteAuthURL = remoteAuthConfig.RemoteAuthURL;
return Redirect(remoteAuthURL);
}
@@ -45,6 +51,10 @@ namespace CarCareTracker.Controllers
}
public IActionResult Registration()
{
if (_config.GetServerDisabledRegistration())
{
return RedirectToAction("Index");
}
return View();
}
public IActionResult ForgotPassword()
@@ -60,10 +70,16 @@ namespace CarCareTracker.Controllers
var remoteAuthConfig = _config.GetOpenIDConfig();
var generatedState = Guid.NewGuid().ToString().Substring(0, 8);
remoteAuthConfig.State = generatedState;
var pkceKeyPair = _loginLogic.GetPKCEChallengeCode();
remoteAuthConfig.CodeChallenge = pkceKeyPair.Value;
if (remoteAuthConfig.ValidateState)
{
Response.Cookies.Append("OIDC_STATE", remoteAuthConfig.State, new CookieOptions { Expires = new DateTimeOffset(DateTime.Now.AddMinutes(5)) });
}
if (remoteAuthConfig.UsePKCE)
{
Response.Cookies.Append("OIDC_VERIFIER", pkceKeyPair.Key, new CookieOptions { Expires = new DateTimeOffset(DateTime.Now.AddMinutes(5)) });
}
var remoteAuthURL = remoteAuthConfig.RemoteAuthURL;
return Json(remoteAuthURL);
}
@@ -99,6 +115,16 @@ namespace CarCareTracker.Controllers
new KeyValuePair<string, string>("client_secret", openIdConfig.ClientSecret),
new KeyValuePair<string, string>("redirect_uri", openIdConfig.RedirectURL)
};
if (openIdConfig.UsePKCE)
{
//retrieve stored challenge verifier
var storedVerifier = Request.Cookies["OIDC_VERIFIER"];
if (!string.IsNullOrWhiteSpace(storedVerifier))
{
httpParams.Add(new KeyValuePair<string, string>("code_verifier", storedVerifier));
Response.Cookies.Delete("OIDC_VERIFIER");
}
}
var httpRequest = new HttpRequestMessage(HttpMethod.Post, openIdConfig.TokenURL)
{
Content = new FormUrlEncodedContent(httpParams)
@@ -137,6 +163,11 @@ namespace CarCareTracker.Controllers
} else
{
_logger.LogInformation("OpenID Provider did not provide a valid id_token");
if (!string.IsNullOrWhiteSpace(tokenResult))
{
//if something was returned from the IdP but it's invalid, we want to log it as an error.
_logger.LogError($"Expected id_token, received {tokenResult}");
}
}
} else
{
@@ -220,7 +251,7 @@ namespace CarCareTracker.Controllers
var result = _loginLogic.ResetPasswordByUser(credentials);
return Json(result);
}
[Authorize] //User must already be logged in to do this.
[Authorize(Roles = nameof(UserData.IsRootUser))] //User must already be logged in as root user to do this.
[HttpPost]
public IActionResult CreateLoginCreds(LoginModel credentials)
{
@@ -235,7 +266,7 @@ namespace CarCareTracker.Controllers
}
return Json(false);
}
[Authorize]
[Authorize(Roles = nameof(UserData.IsRootUser))]
[HttpPost]
public IActionResult DestroyLoginCreds()
{

View File

@@ -5,6 +5,7 @@ using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Npgsql;
using System.IO.Compression;
using JsonSerializer=System.Text.Json.JsonSerializer;
namespace CarCareTracker.Controllers
{
@@ -14,7 +15,7 @@ namespace CarCareTracker.Controllers
private IConfigHelper _configHelper;
private IFileHelper _fileHelper;
private readonly ILogger<MigrationController> _logger;
public MigrationController(IConfigHelper configHelper, IFileHelper fileHelper, IConfiguration serverConfig, ILogger<MigrationController> logger)
public MigrationController(IConfigHelper configHelper, IFileHelper fileHelper, ILogger<MigrationController> logger)
{
_configHelper = configHelper;
_fileHelper = fileHelper;
@@ -34,6 +35,7 @@ namespace CarCareTracker.Controllers
{
var cmds = new List<string>
{
"CREATE SCHEMA IF NOT EXISTS app",
"CREATE TABLE IF NOT EXISTS app.vehicles (id INT GENERATED BY DEFAULT AS IDENTITY primary key, data jsonb not null)",
"CREATE TABLE IF NOT EXISTS app.collisionrecords (id INT GENERATED BY DEFAULT AS IDENTITY primary key, vehicleId INT not null, data jsonb not null)",
"CREATE TABLE IF NOT EXISTS app.upgraderecords (id INT GENERATED BY DEFAULT AS IDENTITY primary key, vehicleId INT not null, data jsonb not null)",
@@ -64,7 +66,7 @@ namespace CarCareTracker.Controllers
{
if (string.IsNullOrWhiteSpace(_configHelper.GetServerPostgresConnection()))
{
return Json(new OperationResponse { Success = false, Message = "Postgres connection not set up" });
return Json(OperationResponse.Failed("Postgres connection not set up"));
}
var tempFolder = $"temp/{Guid.NewGuid()}";
var tempPath = $"{tempFolder}/cartracker.db";
@@ -104,7 +106,7 @@ namespace CarCareTracker.Controllers
using (NpgsqlDataReader reader = ctext.ExecuteReader())
while (reader.Read())
{
Vehicle vehicle = System.Text.Json.JsonSerializer.Deserialize<Vehicle>(reader["data"] as string);
Vehicle vehicle = JsonSerializer.Deserialize<Vehicle>(reader["data"] as string);
vehicles.Add(vehicle);
}
}
@@ -122,7 +124,7 @@ namespace CarCareTracker.Controllers
using (NpgsqlDataReader reader = ctext.ExecuteReader())
while (reader.Read())
{
repairrecords.Add(System.Text.Json.JsonSerializer.Deserialize<CollisionRecord>(reader["data"] as string));
repairrecords.Add(JsonSerializer.Deserialize<CollisionRecord>(reader["data"] as string));
}
}
foreach (var record in repairrecords)
@@ -139,7 +141,7 @@ namespace CarCareTracker.Controllers
using (NpgsqlDataReader reader = ctext.ExecuteReader())
while (reader.Read())
{
upgraderecords.Add(System.Text.Json.JsonSerializer.Deserialize<UpgradeRecord>(reader["data"] as string));
upgraderecords.Add(JsonSerializer.Deserialize<UpgradeRecord>(reader["data"] as string));
}
}
foreach (var record in upgraderecords)
@@ -156,7 +158,7 @@ namespace CarCareTracker.Controllers
using (NpgsqlDataReader reader = ctext.ExecuteReader())
while (reader.Read())
{
servicerecords.Add(System.Text.Json.JsonSerializer.Deserialize<ServiceRecord>(reader["data"] as string));
servicerecords.Add(JsonSerializer.Deserialize<ServiceRecord>(reader["data"] as string));
}
}
foreach (var record in servicerecords)
@@ -175,7 +177,7 @@ namespace CarCareTracker.Controllers
using (NpgsqlDataReader reader = ctext.ExecuteReader())
while (reader.Read())
{
gasrecords.Add(System.Text.Json.JsonSerializer.Deserialize<GasRecord>(reader["data"] as string));
gasrecords.Add(JsonSerializer.Deserialize<GasRecord>(reader["data"] as string));
}
}
foreach (var record in gasrecords)
@@ -192,7 +194,7 @@ namespace CarCareTracker.Controllers
using (NpgsqlDataReader reader = ctext.ExecuteReader())
while (reader.Read())
{
noterecords.Add(System.Text.Json.JsonSerializer.Deserialize<Note>(reader["data"] as string));
noterecords.Add(JsonSerializer.Deserialize<Note>(reader["data"] as string));
}
}
foreach (var record in noterecords)
@@ -209,7 +211,7 @@ namespace CarCareTracker.Controllers
using (NpgsqlDataReader reader = ctext.ExecuteReader())
while (reader.Read())
{
odometerrecords.Add(System.Text.Json.JsonSerializer.Deserialize<OdometerRecord>(reader["data"] as string));
odometerrecords.Add(JsonSerializer.Deserialize<OdometerRecord>(reader["data"] as string));
}
}
foreach (var record in odometerrecords)
@@ -226,7 +228,7 @@ namespace CarCareTracker.Controllers
using (NpgsqlDataReader reader = ctext.ExecuteReader())
while (reader.Read())
{
reminderrecords.Add(System.Text.Json.JsonSerializer.Deserialize<ReminderRecord>(reader["data"] as string));
reminderrecords.Add(JsonSerializer.Deserialize<ReminderRecord>(reader["data"] as string));
}
}
foreach (var record in reminderrecords)
@@ -245,7 +247,7 @@ namespace CarCareTracker.Controllers
using (NpgsqlDataReader reader = ctext.ExecuteReader())
while (reader.Read())
{
planrecords.Add(System.Text.Json.JsonSerializer.Deserialize<PlanRecord>(reader["data"] as string));
planrecords.Add(JsonSerializer.Deserialize<PlanRecord>(reader["data"] as string));
}
}
foreach (var record in planrecords)
@@ -262,7 +264,7 @@ namespace CarCareTracker.Controllers
using (NpgsqlDataReader reader = ctext.ExecuteReader())
while (reader.Read())
{
planrecordtemplates.Add(System.Text.Json.JsonSerializer.Deserialize<PlanRecordInput>(reader["data"] as string));
planrecordtemplates.Add(JsonSerializer.Deserialize<PlanRecordInput>(reader["data"] as string));
}
}
foreach (var record in planrecordtemplates)
@@ -279,7 +281,7 @@ namespace CarCareTracker.Controllers
using (NpgsqlDataReader reader = ctext.ExecuteReader())
while (reader.Read())
{
supplyrecords.Add(System.Text.Json.JsonSerializer.Deserialize<SupplyRecord>(reader["data"] as string));
supplyrecords.Add(JsonSerializer.Deserialize<SupplyRecord>(reader["data"] as string));
}
}
foreach (var record in supplyrecords)
@@ -296,7 +298,7 @@ namespace CarCareTracker.Controllers
using (NpgsqlDataReader reader = ctext.ExecuteReader())
while (reader.Read())
{
taxrecords.Add(System.Text.Json.JsonSerializer.Deserialize<TaxRecord>(reader["data"] as string));
taxrecords.Add(JsonSerializer.Deserialize<TaxRecord>(reader["data"] as string));
}
}
foreach (var record in taxrecords)
@@ -359,7 +361,7 @@ namespace CarCareTracker.Controllers
using (NpgsqlDataReader reader = ctext.ExecuteReader())
while (reader.Read())
{
userconfigrecords.Add(System.Text.Json.JsonSerializer.Deserialize<UserConfigData>(reader["data"] as string));
userconfigrecords.Add(JsonSerializer.Deserialize<UserConfigData>(reader["data"] as string));
}
}
foreach (var record in userconfigrecords)
@@ -403,7 +405,7 @@ namespace CarCareTracker.Controllers
using (NpgsqlDataReader reader = ctext.ExecuteReader())
while (reader.Read())
{
extrafields.Add(System.Text.Json.JsonSerializer.Deserialize<RecordExtraField>(reader["data"] as string));
extrafields.Add(JsonSerializer.Deserialize<RecordExtraField>(reader["data"] as string));
}
}
foreach (var record in extrafields)
@@ -417,24 +419,24 @@ namespace CarCareTracker.Controllers
#endregion
var destFilePath = $"{fullFolderPath}.zip";
ZipFile.CreateFromDirectory(fullFolderPath, destFilePath);
return Json(new OperationResponse { Success = true, Message = $"/{tempFolder}.zip" });
return Json(OperationResponse.Succeed($"/{tempFolder}.zip"));
}
catch (Exception ex)
{
_logger.LogError(ex.Message);
return Json(new OperationResponse { Success = false, Message = StaticHelper.GenericErrorMessage });
return Json(OperationResponse.Failed());
}
}
public IActionResult Import(string fileName)
{
if (string.IsNullOrWhiteSpace(_configHelper.GetServerPostgresConnection()))
{
return Json(new OperationResponse { Success = false, Message = "Postgres connection not set up" });
return Json(OperationResponse.Failed("Postgres connection not set up"));
}
var fullFileName = _fileHelper.GetFullFilePath(fileName);
if (string.IsNullOrWhiteSpace(fullFileName))
{
return Json(new OperationResponse { Success = false, Message = StaticHelper.GenericErrorMessage });
return Json(OperationResponse.Failed());
}
try
{
@@ -474,7 +476,7 @@ namespace CarCareTracker.Controllers
using (var ctext = pgDataSource.CreateCommand(cmd))
{
ctext.Parameters.AddWithValue("id", vehicle.Id);
ctext.Parameters.AddWithValue("data", System.Text.Json.JsonSerializer.Serialize(vehicle));
ctext.Parameters.AddWithValue("data", JsonSerializer.Serialize(vehicle));
ctext.ExecuteNonQuery();
}
}
@@ -490,7 +492,7 @@ namespace CarCareTracker.Controllers
{
ctext.Parameters.AddWithValue("id", record.Id);
ctext.Parameters.AddWithValue("vehicleId", record.VehicleId);
ctext.Parameters.AddWithValue("data", System.Text.Json.JsonSerializer.Serialize(record));
ctext.Parameters.AddWithValue("data", JsonSerializer.Serialize(record));
ctext.ExecuteNonQuery();
}
}
@@ -506,7 +508,7 @@ namespace CarCareTracker.Controllers
{
ctext.Parameters.AddWithValue("id", record.Id);
ctext.Parameters.AddWithValue("vehicleId", record.VehicleId);
ctext.Parameters.AddWithValue("data", System.Text.Json.JsonSerializer.Serialize(record));
ctext.Parameters.AddWithValue("data", JsonSerializer.Serialize(record));
ctext.ExecuteNonQuery();
}
}
@@ -522,7 +524,7 @@ namespace CarCareTracker.Controllers
{
ctext.Parameters.AddWithValue("id", record.Id);
ctext.Parameters.AddWithValue("vehicleId", record.VehicleId);
ctext.Parameters.AddWithValue("data", System.Text.Json.JsonSerializer.Serialize(record));
ctext.Parameters.AddWithValue("data", JsonSerializer.Serialize(record));
ctext.ExecuteNonQuery();
}
}
@@ -540,7 +542,7 @@ namespace CarCareTracker.Controllers
{
ctext.Parameters.AddWithValue("id", record.Id);
ctext.Parameters.AddWithValue("vehicleId", record.VehicleId);
ctext.Parameters.AddWithValue("data", System.Text.Json.JsonSerializer.Serialize(record));
ctext.Parameters.AddWithValue("data", JsonSerializer.Serialize(record));
ctext.ExecuteNonQuery();
}
}
@@ -556,7 +558,7 @@ namespace CarCareTracker.Controllers
{
ctext.Parameters.AddWithValue("id", record.Id);
ctext.Parameters.AddWithValue("vehicleId", record.VehicleId);
ctext.Parameters.AddWithValue("data", System.Text.Json.JsonSerializer.Serialize(record));
ctext.Parameters.AddWithValue("data", JsonSerializer.Serialize(record));
ctext.ExecuteNonQuery();
}
}
@@ -572,7 +574,7 @@ namespace CarCareTracker.Controllers
{
ctext.Parameters.AddWithValue("id", record.Id);
ctext.Parameters.AddWithValue("vehicleId", record.VehicleId);
ctext.Parameters.AddWithValue("data", System.Text.Json.JsonSerializer.Serialize(record));
ctext.Parameters.AddWithValue("data", JsonSerializer.Serialize(record));
ctext.ExecuteNonQuery();
}
}
@@ -588,7 +590,7 @@ namespace CarCareTracker.Controllers
{
ctext.Parameters.AddWithValue("id", record.Id);
ctext.Parameters.AddWithValue("vehicleId", record.VehicleId);
ctext.Parameters.AddWithValue("data", System.Text.Json.JsonSerializer.Serialize(record));
ctext.Parameters.AddWithValue("data", JsonSerializer.Serialize(record));
ctext.ExecuteNonQuery();
}
}
@@ -606,7 +608,7 @@ namespace CarCareTracker.Controllers
{
ctext.Parameters.AddWithValue("id", record.Id);
ctext.Parameters.AddWithValue("vehicleId", record.VehicleId);
ctext.Parameters.AddWithValue("data", System.Text.Json.JsonSerializer.Serialize(record));
ctext.Parameters.AddWithValue("data", JsonSerializer.Serialize(record));
ctext.ExecuteNonQuery();
}
}
@@ -622,7 +624,7 @@ namespace CarCareTracker.Controllers
{
ctext.Parameters.AddWithValue("id", record.Id);
ctext.Parameters.AddWithValue("vehicleId", record.VehicleId);
ctext.Parameters.AddWithValue("data", System.Text.Json.JsonSerializer.Serialize(record));
ctext.Parameters.AddWithValue("data", JsonSerializer.Serialize(record));
ctext.ExecuteNonQuery();
}
}
@@ -638,7 +640,7 @@ namespace CarCareTracker.Controllers
{
ctext.Parameters.AddWithValue("id", record.Id);
ctext.Parameters.AddWithValue("vehicleId", record.VehicleId);
ctext.Parameters.AddWithValue("data", System.Text.Json.JsonSerializer.Serialize(record));
ctext.Parameters.AddWithValue("data", JsonSerializer.Serialize(record));
ctext.ExecuteNonQuery();
}
}
@@ -654,7 +656,7 @@ namespace CarCareTracker.Controllers
{
ctext.Parameters.AddWithValue("id", record.Id);
ctext.Parameters.AddWithValue("vehicleId", record.VehicleId);
ctext.Parameters.AddWithValue("data", System.Text.Json.JsonSerializer.Serialize(record));
ctext.Parameters.AddWithValue("data", JsonSerializer.Serialize(record));
ctext.ExecuteNonQuery();
}
}
@@ -705,7 +707,7 @@ namespace CarCareTracker.Controllers
using (var ctext = pgDataSource.CreateCommand(cmd))
{
ctext.Parameters.AddWithValue("id", record.Id);
ctext.Parameters.AddWithValue("data", System.Text.Json.JsonSerializer.Serialize(record));
ctext.Parameters.AddWithValue("data", JsonSerializer.Serialize(record));
ctext.ExecuteNonQuery();
}
}
@@ -737,17 +739,17 @@ namespace CarCareTracker.Controllers
using (var ctext = pgDataSource.CreateCommand(cmd))
{
ctext.Parameters.AddWithValue("id", record.Id);
ctext.Parameters.AddWithValue("data", System.Text.Json.JsonSerializer.Serialize(record));
ctext.Parameters.AddWithValue("data", JsonSerializer.Serialize(record));
ctext.ExecuteNonQuery();
}
}
#endregion
return Json(new OperationResponse { Success = true, Message = "Data Imported Successfully" });
return Json(OperationResponse.Succeed("Data Imported Successfully"));
}
catch (Exception ex)
{
_logger.LogError(ex.Message);
return Json(new OperationResponse { Success = false, Message = StaticHelper.GenericErrorMessage });
return Json(OperationResponse.Failed());
}
}
}

View File

@@ -0,0 +1,198 @@
using CarCareTracker.Filter;
using CarCareTracker.Helper;
using CarCareTracker.Models;
using Microsoft.AspNetCore.Mvc;
namespace CarCareTracker.Controllers
{
public partial class VehicleController
{
[TypeFilter(typeof(CollaboratorFilter))]
[HttpGet]
public IActionResult GetGasRecordsByVehicleId(int vehicleId)
{
var result = _gasRecordDataAccess.GetGasRecordsByVehicleId(vehicleId);
//check if the user uses MPG or Liters per 100km.
var userConfig = _config.GetUserConfig(User);
bool useMPG = userConfig.UseMPG;
bool useUKMPG = userConfig.UseUKMPG;
var computedResults = _gasHelper.GetGasRecordViewModels(result, useMPG, useUKMPG);
if (userConfig.UseDescending)
{
computedResults = computedResults.OrderByDescending(x => DateTime.Parse(x.Date)).ThenByDescending(x => x.Mileage).ToList();
}
var vehicleData = _dataAccess.GetVehicleById(vehicleId);
var vehicleIsElectric = vehicleData.IsElectric;
var vehicleUseHours = vehicleData.UseHours;
var viewModel = new GasRecordViewModelContainer()
{
UseKwh = vehicleIsElectric,
UseHours = vehicleUseHours,
GasRecords = computedResults
};
return PartialView("_Gas", viewModel);
}
[HttpPost]
public IActionResult SaveGasRecordToVehicleId(GasRecordInput gasRecord)
{
if (gasRecord.Id == default && _config.GetUserConfig(User).EnableAutoOdometerInsert)
{
_odometerLogic.AutoInsertOdometerRecord(new OdometerRecord
{
Date = DateTime.Parse(gasRecord.Date),
VehicleId = gasRecord.VehicleId,
Mileage = gasRecord.Mileage,
Notes = $"Auto Insert From Gas Record. {gasRecord.Notes}"
});
}
gasRecord.Files = gasRecord.Files.Select(x => { return new UploadedFiles { Name = x.Name, Location = _fileHelper.MoveFileFromTemp(x.Location, "documents/") }; }).ToList();
var result = _gasRecordDataAccess.SaveGasRecordToVehicle(gasRecord.ToGasRecord());
if (result)
{
StaticHelper.NotifyAsync(_config.GetWebHookUrl(), gasRecord.VehicleId, User.Identity.Name, $"{(gasRecord.Id == default ? "Created" : "Edited")} Gas Record - Mileage: {gasRecord.Mileage.ToString()}");
}
return Json(result);
}
[HttpGet]
public IActionResult GetAddGasRecordPartialView(int vehicleId)
{
var vehicleData = _dataAccess.GetVehicleById(vehicleId);
var vehicleIsElectric = vehicleData.IsElectric;
var vehicleUseHours = vehicleData.UseHours;
return PartialView("_GasModal", new GasRecordInputContainer() { UseKwh = vehicleIsElectric, UseHours = vehicleUseHours, GasRecord = new GasRecordInput() { ExtraFields = _extraFieldDataAccess.GetExtraFieldsById((int)ImportMode.GasRecord).ExtraFields } });
}
[HttpGet]
public IActionResult GetGasRecordForEditById(int gasRecordId)
{
var result = _gasRecordDataAccess.GetGasRecordById(gasRecordId);
var convertedResult = new GasRecordInput
{
Id = result.Id,
Mileage = result.Mileage,
VehicleId = result.VehicleId,
Cost = result.Cost,
Date = result.Date.ToShortDateString(),
Files = result.Files,
Gallons = result.Gallons,
IsFillToFull = result.IsFillToFull,
MissedFuelUp = result.MissedFuelUp,
Notes = result.Notes,
Tags = result.Tags,
ExtraFields = StaticHelper.AddExtraFields(result.ExtraFields, _extraFieldDataAccess.GetExtraFieldsById((int)ImportMode.GasRecord).ExtraFields)
};
var vehicleData = _dataAccess.GetVehicleById(convertedResult.VehicleId);
var vehicleIsElectric = vehicleData.IsElectric;
var vehicleUseHours = vehicleData.UseHours;
var viewModel = new GasRecordInputContainer()
{
UseKwh = vehicleIsElectric,
UseHours = vehicleUseHours,
GasRecord = convertedResult
};
return PartialView("_GasModal", viewModel);
}
private bool DeleteGasRecordWithChecks(int gasRecordId)
{
var existingRecord = _gasRecordDataAccess.GetGasRecordById(gasRecordId);
//security check.
if (!_userLogic.UserCanEditVehicle(GetUserID(), existingRecord.VehicleId))
{
return false;
}
var result = _gasRecordDataAccess.DeleteGasRecordById(existingRecord.Id);
return result;
}
[HttpPost]
public IActionResult DeleteGasRecordById(int gasRecordId)
{
var result = DeleteGasRecordWithChecks(gasRecordId);
if (result)
{
StaticHelper.NotifyAsync(_config.GetWebHookUrl(), 0, User.Identity.Name, $"Deleted Gas Record - Id: {gasRecordId}");
}
return Json(result);
}
[HttpPost]
public IActionResult SaveUserGasTabPreferences(string gasUnit, string fuelMileageUnit)
{
var currentConfig = _config.GetUserConfig(User);
currentConfig.PreferredGasUnit = gasUnit;
currentConfig.PreferredGasMileageUnit = fuelMileageUnit;
var result = _config.SaveUserConfig(User, currentConfig);
return Json(result);
}
[HttpPost]
public IActionResult GetGasRecordsEditModal(List<int> recordIds)
{
var extraFields = _extraFieldDataAccess.GetExtraFieldsById((int)ImportMode.GasRecord).ExtraFields;
return PartialView("_GasRecordsModal", new GasRecordEditModel { RecordIds = recordIds, EditRecord = new GasRecord { ExtraFields = extraFields } });
}
[HttpPost]
public IActionResult SaveMultipleGasRecords(GasRecordEditModel editModel)
{
var dateIsEdited = editModel.EditRecord.Date != default;
var mileageIsEdited = editModel.EditRecord.Mileage != default;
var consumptionIsEdited = editModel.EditRecord.Gallons != default;
var costIsEdited = editModel.EditRecord.Cost != default;
var noteIsEdited = !string.IsNullOrWhiteSpace(editModel.EditRecord.Notes);
var tagsIsEdited = editModel.EditRecord.Tags.Any();
var extraFieldIsEdited = editModel.EditRecord.ExtraFields.Any();
//handle clear overrides
if (tagsIsEdited && editModel.EditRecord.Tags.Contains("---"))
{
editModel.EditRecord.Tags = new List<string>();
}
if (noteIsEdited && editModel.EditRecord.Notes == "---")
{
editModel.EditRecord.Notes = "";
}
bool result = false;
foreach (int recordId in editModel.RecordIds)
{
var existingRecord = _gasRecordDataAccess.GetGasRecordById(recordId);
if (dateIsEdited)
{
existingRecord.Date = editModel.EditRecord.Date;
}
if (consumptionIsEdited)
{
existingRecord.Gallons = editModel.EditRecord.Gallons;
}
if (costIsEdited)
{
existingRecord.Cost = editModel.EditRecord.Cost;
}
if (mileageIsEdited)
{
existingRecord.Mileage = editModel.EditRecord.Mileage;
}
if (noteIsEdited)
{
existingRecord.Notes = editModel.EditRecord.Notes;
}
if (tagsIsEdited)
{
existingRecord.Tags = editModel.EditRecord.Tags;
}
if (extraFieldIsEdited)
{
foreach (ExtraField extraField in editModel.EditRecord.ExtraFields)
{
if (existingRecord.ExtraFields.Any(x => x.Name == extraField.Name))
{
var insertIndex = existingRecord.ExtraFields.FindIndex(x => x.Name == extraField.Name);
existingRecord.ExtraFields.RemoveAll(x => x.Name == extraField.Name);
existingRecord.ExtraFields.Insert(insertIndex, extraField);
}
else
{
existingRecord.ExtraFields.Add(extraField);
}
}
}
result = _gasRecordDataAccess.SaveGasRecordToVehicle(existingRecord);
}
return Json(result);
}
}
}

View File

@@ -0,0 +1,495 @@
using CarCareTracker.Filter;
using CarCareTracker.Helper;
using CarCareTracker.MapProfile;
using CarCareTracker.Models;
using CsvHelper;
using CsvHelper.Configuration;
using Microsoft.AspNetCore.Mvc;
using System.Globalization;
namespace CarCareTracker.Controllers
{
public partial class VehicleController
{
[HttpGet]
public IActionResult GetBulkImportModalPartialView(ImportMode mode)
{
return PartialView("_BulkDataImporter", mode);
}
[TypeFilter(typeof(CollaboratorFilter))]
[HttpGet]
public IActionResult ExportFromVehicleToCsv(int vehicleId, ImportMode mode)
{
if (vehicleId == default && mode != ImportMode.SupplyRecord)
{
return Json(false);
}
string uploadDirectory = "temp/";
string uploadPath = Path.Combine(_webEnv.WebRootPath, uploadDirectory);
if (!Directory.Exists(uploadPath))
Directory.CreateDirectory(uploadPath);
var fileNameToExport = $"temp/{Guid.NewGuid()}.csv";
var fullExportFilePath = _fileHelper.GetFullFilePath(fileNameToExport, false);
if (mode == ImportMode.ServiceRecord)
{
var vehicleRecords = _serviceRecordDataAccess.GetServiceRecordsByVehicleId(vehicleId);
if (vehicleRecords.Any())
{
var exportData = vehicleRecords.Select(x => new GenericRecordExportModel
{
Date = x.Date.ToShortDateString(),
Description = x.Description,
Cost = x.Cost.ToString("C"),
Notes = x.Notes,
Odometer = x.Mileage.ToString(),
Tags = string.Join(" ", x.Tags),
ExtraFields = x.ExtraFields
});
using (var writer = new StreamWriter(fullExportFilePath))
{
using (var csv = new CsvWriter(writer, CultureInfo.InvariantCulture))
{
//custom writer
StaticHelper.WriteGenericRecordExportModel(csv, exportData);
}
writer.Dispose();
}
return Json($"/{fileNameToExport}");
}
}
else if (mode == ImportMode.RepairRecord)
{
var vehicleRecords = _collisionRecordDataAccess.GetCollisionRecordsByVehicleId(vehicleId);
if (vehicleRecords.Any())
{
var exportData = vehicleRecords.Select(x => new GenericRecordExportModel
{
Date = x.Date.ToShortDateString(),
Description = x.Description,
Cost = x.Cost.ToString("C"),
Notes = x.Notes,
Odometer = x.Mileage.ToString(),
Tags = string.Join(" ", x.Tags),
ExtraFields = x.ExtraFields
});
using (var writer = new StreamWriter(fullExportFilePath))
{
using (var csv = new CsvWriter(writer, CultureInfo.InvariantCulture))
{
StaticHelper.WriteGenericRecordExportModel(csv, exportData);
}
}
return Json($"/{fileNameToExport}");
}
}
else if (mode == ImportMode.UpgradeRecord)
{
var vehicleRecords = _upgradeRecordDataAccess.GetUpgradeRecordsByVehicleId(vehicleId);
if (vehicleRecords.Any())
{
var exportData = vehicleRecords.Select(x => new GenericRecordExportModel
{
Date = x.Date.ToShortDateString(),
Description = x.Description,
Cost = x.Cost.ToString("C"),
Notes = x.Notes,
Odometer = x.Mileage.ToString(),
Tags = string.Join(" ", x.Tags),
ExtraFields = x.ExtraFields
});
using (var writer = new StreamWriter(fullExportFilePath))
{
using (var csv = new CsvWriter(writer, CultureInfo.InvariantCulture))
{
StaticHelper.WriteGenericRecordExportModel(csv, exportData);
}
}
return Json($"/{fileNameToExport}");
}
}
else if (mode == ImportMode.OdometerRecord)
{
var vehicleRecords = _odometerRecordDataAccess.GetOdometerRecordsByVehicleId(vehicleId);
if (vehicleRecords.Any())
{
var exportData = vehicleRecords.Select(x => new OdometerRecordExportModel
{
Date = x.Date.ToShortDateString(),
Notes = x.Notes,
InitialOdometer = x.InitialMileage.ToString(),
Odometer = x.Mileage.ToString(),
Tags = string.Join(" ", x.Tags),
ExtraFields = x.ExtraFields
});
using (var writer = new StreamWriter(fullExportFilePath))
{
using (var csv = new CsvWriter(writer, CultureInfo.InvariantCulture))
{
StaticHelper.WriteOdometerRecordExportModel(csv, exportData);
}
}
return Json($"/{fileNameToExport}");
}
}
else if (mode == ImportMode.SupplyRecord)
{
var vehicleRecords = _supplyRecordDataAccess.GetSupplyRecordsByVehicleId(vehicleId);
if (vehicleRecords.Any())
{
var exportData = vehicleRecords.Select(x => new SupplyRecordExportModel
{
Date = x.Date.ToShortDateString(),
Description = x.Description,
Cost = x.Cost.ToString("C"),
PartNumber = x.PartNumber,
PartQuantity = x.Quantity.ToString(),
PartSupplier = x.PartSupplier,
Notes = x.Notes,
Tags = string.Join(" ", x.Tags),
ExtraFields = x.ExtraFields
});
using (var writer = new StreamWriter(fullExportFilePath))
{
using (var csv = new CsvWriter(writer, CultureInfo.InvariantCulture))
{
StaticHelper.WriteSupplyRecordExportModel(csv, exportData);
}
}
return Json($"/{fileNameToExport}");
}
}
else if (mode == ImportMode.TaxRecord)
{
var vehicleRecords = _taxRecordDataAccess.GetTaxRecordsByVehicleId(vehicleId);
if (vehicleRecords.Any())
{
var exportData = vehicleRecords.Select(x => new TaxRecordExportModel
{
Date = x.Date.ToShortDateString(),
Description = x.Description,
Cost = x.Cost.ToString("C"),
Notes = x.Notes,
Tags = string.Join(" ", x.Tags),
ExtraFields = x.ExtraFields
});
using (var writer = new StreamWriter(fullExportFilePath))
{
using (var csv = new CsvWriter(writer, CultureInfo.InvariantCulture))
{
StaticHelper.WriteTaxRecordExportModel(csv, exportData);
}
}
return Json($"/{fileNameToExport}");
}
}
else if (mode == ImportMode.PlanRecord)
{
var vehicleRecords = _planRecordDataAccess.GetPlanRecordsByVehicleId(vehicleId);
if (vehicleRecords.Any())
{
var exportData = vehicleRecords.Select(x => new PlanRecordExportModel
{
DateCreated = x.DateCreated.ToString("G"),
DateModified = x.DateModified.ToString("G"),
Description = x.Description,
Cost = x.Cost.ToString("C"),
Type = x.ImportMode.ToString(),
Priority = x.Priority.ToString(),
Progress = x.Progress.ToString(),
Notes = x.Notes,
ExtraFields = x.ExtraFields
});
using (var writer = new StreamWriter(fullExportFilePath))
{
using (var csv = new CsvWriter(writer, CultureInfo.InvariantCulture))
{
StaticHelper.WritePlanRecordExportModel(csv, exportData);
}
}
return Json($"/{fileNameToExport}");
}
}
else if (mode == ImportMode.GasRecord)
{
var vehicleRecords = _gasRecordDataAccess.GetGasRecordsByVehicleId(vehicleId);
bool useMPG = _config.GetUserConfig(User).UseMPG;
bool useUKMPG = _config.GetUserConfig(User).UseUKMPG;
var convertedRecords = _gasHelper.GetGasRecordViewModels(vehicleRecords, useMPG, useUKMPG);
var exportData = convertedRecords.Select(x => new GasRecordExportModel
{
Date = x.Date.ToString(),
Cost = x.Cost.ToString(),
FuelConsumed = x.Gallons.ToString(),
FuelEconomy = x.MilesPerGallon.ToString(),
Odometer = x.Mileage.ToString(),
IsFillToFull = x.IsFillToFull.ToString(),
MissedFuelUp = x.MissedFuelUp.ToString(),
Notes = x.Notes,
Tags = string.Join(" ", x.Tags),
ExtraFields = x.ExtraFields
});
using (var writer = new StreamWriter(fullExportFilePath))
{
using (var csv = new CsvWriter(writer, CultureInfo.InvariantCulture))
{
StaticHelper.WriteGasRecordExportModel(csv, exportData);
}
}
return Json($"/{fileNameToExport}");
}
return Json(false);
}
[TypeFilter(typeof(CollaboratorFilter))]
[HttpPost]
public IActionResult ImportToVehicleIdFromCsv(int vehicleId, ImportMode mode, string fileName)
{
if (vehicleId == default && mode != ImportMode.SupplyRecord)
{
return Json(false);
}
if (string.IsNullOrWhiteSpace(fileName))
{
return Json(false);
}
var fullFileName = _fileHelper.GetFullFilePath(fileName);
if (string.IsNullOrWhiteSpace(fullFileName))
{
return Json(false);
}
try
{
using (var reader = new StreamReader(fullFileName))
{
var config = new CsvConfiguration(CultureInfo.InvariantCulture);
config.MissingFieldFound = null;
config.HeaderValidated = null;
config.PrepareHeaderForMatch = args => { return args.Header.Trim().ToLower(); };
using (var csv = new CsvReader(reader, config))
{
csv.Context.RegisterClassMap<ImportMapper>();
var records = csv.GetRecords<ImportModel>().ToList();
if (records.Any())
{
var requiredExtraFields = _extraFieldDataAccess.GetExtraFieldsById((int)mode).ExtraFields.Where(x => x.IsRequired).Select(y => y.Name);
foreach (ImportModel importModel in records)
{
var parsedDate = DateTime.Now.Date;
if (!string.IsNullOrWhiteSpace(importModel.Date))
{
parsedDate = DateTime.Parse(importModel.Date);
}
else if (!string.IsNullOrWhiteSpace(importModel.Day) && !string.IsNullOrWhiteSpace(importModel.Month) && !string.IsNullOrWhiteSpace(importModel.Year))
{
parsedDate = new DateTime(int.Parse(importModel.Year), int.Parse(importModel.Month), int.Parse(importModel.Day));
}
if (mode == ImportMode.GasRecord)
{
//convert to gas model.
var convertedRecord = new GasRecord()
{
VehicleId = vehicleId,
Date = parsedDate,
Mileage = decimal.ToInt32(decimal.Parse(importModel.Odometer, NumberStyles.Any)),
Gallons = decimal.Parse(importModel.FuelConsumed, NumberStyles.Any),
Notes = string.IsNullOrWhiteSpace(importModel.Notes) ? "" : importModel.Notes,
Tags = string.IsNullOrWhiteSpace(importModel.Tags) ? [] : importModel.Tags.Split(" ").ToList(),
ExtraFields = importModel.ExtraFields.Any() ? importModel.ExtraFields.Select(x => new ExtraField { Name = x.Key, Value = x.Value, IsRequired = requiredExtraFields.Contains(x.Key) }).ToList() : new List<ExtraField>()
};
if (string.IsNullOrWhiteSpace(importModel.Cost) && !string.IsNullOrWhiteSpace(importModel.Price))
{
//cost was not given but price is.
//fuelly sometimes exports CSVs without total cost.
var parsedPrice = decimal.Parse(importModel.Price, NumberStyles.Any);
convertedRecord.Cost = convertedRecord.Gallons * parsedPrice;
}
else
{
convertedRecord.Cost = decimal.Parse(importModel.Cost, NumberStyles.Any);
}
if (string.IsNullOrWhiteSpace(importModel.IsFillToFull) && !string.IsNullOrWhiteSpace(importModel.PartialFuelUp))
{
var parsedBool = importModel.PartialFuelUp.Trim() == "1";
convertedRecord.IsFillToFull = !parsedBool;
}
else if (!string.IsNullOrWhiteSpace(importModel.IsFillToFull))
{
var possibleFillToFullValues = new List<string> { "1", "true", "full" };
var parsedBool = possibleFillToFullValues.Contains(importModel.IsFillToFull.Trim().ToLower());
convertedRecord.IsFillToFull = parsedBool;
}
if (!string.IsNullOrWhiteSpace(importModel.MissedFuelUp))
{
var possibleMissedFuelUpValues = new List<string> { "1", "true" };
var parsedBool = possibleMissedFuelUpValues.Contains(importModel.MissedFuelUp.Trim().ToLower());
convertedRecord.MissedFuelUp = parsedBool;
}
//insert record into db, check to make sure fuelconsumed is not zero so we don't get a divide by zero error.
if (convertedRecord.Gallons > 0)
{
_gasRecordDataAccess.SaveGasRecordToVehicle(convertedRecord);
if (_config.GetUserConfig(User).EnableAutoOdometerInsert)
{
_odometerLogic.AutoInsertOdometerRecord(new OdometerRecord
{
Date = convertedRecord.Date,
VehicleId = convertedRecord.VehicleId,
Mileage = convertedRecord.Mileage,
Notes = $"Auto Insert From Gas Record via CSV Import. {convertedRecord.Notes}"
});
}
}
}
else if (mode == ImportMode.ServiceRecord)
{
var convertedRecord = new ServiceRecord()
{
VehicleId = vehicleId,
Date = parsedDate,
Mileage = decimal.ToInt32(decimal.Parse(importModel.Odometer, NumberStyles.Any)),
Description = string.IsNullOrWhiteSpace(importModel.Description) ? $"Service Record on {parsedDate.ToShortDateString()}" : importModel.Description,
Notes = string.IsNullOrWhiteSpace(importModel.Notes) ? "" : importModel.Notes,
Cost = decimal.Parse(importModel.Cost, NumberStyles.Any),
Tags = string.IsNullOrWhiteSpace(importModel.Tags) ? [] : importModel.Tags.Split(" ").ToList(),
ExtraFields = importModel.ExtraFields.Any() ? importModel.ExtraFields.Select(x => new ExtraField { Name = x.Key, Value = x.Value, IsRequired = requiredExtraFields.Contains(x.Key) }).ToList() : new List<ExtraField>()
};
_serviceRecordDataAccess.SaveServiceRecordToVehicle(convertedRecord);
if (_config.GetUserConfig(User).EnableAutoOdometerInsert)
{
_odometerLogic.AutoInsertOdometerRecord(new OdometerRecord
{
Date = convertedRecord.Date,
VehicleId = convertedRecord.VehicleId,
Mileage = convertedRecord.Mileage,
Notes = $"Auto Insert From Service Record via CSV Import. {convertedRecord.Notes}"
});
}
}
else if (mode == ImportMode.OdometerRecord)
{
var convertedRecord = new OdometerRecord()
{
VehicleId = vehicleId,
Date = parsedDate,
InitialMileage = string.IsNullOrWhiteSpace(importModel.InitialOdometer) ? 0 : decimal.ToInt32(decimal.Parse(importModel.InitialOdometer, NumberStyles.Any)),
Mileage = decimal.ToInt32(decimal.Parse(importModel.Odometer, NumberStyles.Any)),
Notes = string.IsNullOrWhiteSpace(importModel.Notes) ? "" : importModel.Notes,
Tags = string.IsNullOrWhiteSpace(importModel.Tags) ? [] : importModel.Tags.Split(" ").ToList(),
ExtraFields = importModel.ExtraFields.Any() ? importModel.ExtraFields.Select(x => new ExtraField { Name = x.Key, Value = x.Value, IsRequired = requiredExtraFields.Contains(x.Key) }).ToList() : new List<ExtraField>()
};
_odometerRecordDataAccess.SaveOdometerRecordToVehicle(convertedRecord);
}
else if (mode == ImportMode.PlanRecord)
{
var progressIsEnum = Enum.TryParse(importModel.Progress, out PlanProgress parsedProgress);
var typeIsEnum = Enum.TryParse(importModel.Type, out ImportMode parsedType);
var priorityIsEnum = Enum.TryParse(importModel.Priority, out PlanPriority parsedPriority);
var convertedRecord = new PlanRecord()
{
VehicleId = vehicleId,
DateCreated = DateTime.Parse(importModel.DateCreated),
DateModified = DateTime.Parse(importModel.DateModified),
Progress = parsedProgress,
ImportMode = parsedType,
Priority = parsedPriority,
Description = string.IsNullOrWhiteSpace(importModel.Description) ? $"Plan Record on {importModel.DateCreated}" : importModel.Description,
Notes = string.IsNullOrWhiteSpace(importModel.Notes) ? "" : importModel.Notes,
Cost = decimal.Parse(importModel.Cost, NumberStyles.Any),
ExtraFields = importModel.ExtraFields.Any() ? importModel.ExtraFields.Select(x => new ExtraField { Name = x.Key, Value = x.Value, IsRequired = requiredExtraFields.Contains(x.Key) }).ToList() : new List<ExtraField>()
};
_planRecordDataAccess.SavePlanRecordToVehicle(convertedRecord);
}
else if (mode == ImportMode.RepairRecord)
{
var convertedRecord = new CollisionRecord()
{
VehicleId = vehicleId,
Date = parsedDate,
Mileage = decimal.ToInt32(decimal.Parse(importModel.Odometer, NumberStyles.Any)),
Description = string.IsNullOrWhiteSpace(importModel.Description) ? $"Repair Record on {parsedDate.ToShortDateString()}" : importModel.Description,
Notes = string.IsNullOrWhiteSpace(importModel.Notes) ? "" : importModel.Notes,
Cost = decimal.Parse(importModel.Cost, NumberStyles.Any),
Tags = string.IsNullOrWhiteSpace(importModel.Tags) ? [] : importModel.Tags.Split(" ").ToList(),
ExtraFields = importModel.ExtraFields.Any() ? importModel.ExtraFields.Select(x => new ExtraField { Name = x.Key, Value = x.Value, IsRequired = requiredExtraFields.Contains(x.Key) }).ToList() : new List<ExtraField>()
};
_collisionRecordDataAccess.SaveCollisionRecordToVehicle(convertedRecord);
if (_config.GetUserConfig(User).EnableAutoOdometerInsert)
{
_odometerLogic.AutoInsertOdometerRecord(new OdometerRecord
{
Date = convertedRecord.Date,
VehicleId = convertedRecord.VehicleId,
Mileage = convertedRecord.Mileage,
Notes = $"Auto Insert From Repair Record via CSV Import. {convertedRecord.Notes}"
});
}
}
else if (mode == ImportMode.UpgradeRecord)
{
var convertedRecord = new UpgradeRecord()
{
VehicleId = vehicleId,
Date = parsedDate,
Mileage = decimal.ToInt32(decimal.Parse(importModel.Odometer, NumberStyles.Any)),
Description = string.IsNullOrWhiteSpace(importModel.Description) ? $"Upgrade Record on {parsedDate.ToShortDateString()}" : importModel.Description,
Notes = string.IsNullOrWhiteSpace(importModel.Notes) ? "" : importModel.Notes,
Cost = decimal.Parse(importModel.Cost, NumberStyles.Any),
Tags = string.IsNullOrWhiteSpace(importModel.Tags) ? [] : importModel.Tags.Split(" ").ToList(),
ExtraFields = importModel.ExtraFields.Any() ? importModel.ExtraFields.Select(x => new ExtraField { Name = x.Key, Value = x.Value, IsRequired = requiredExtraFields.Contains(x.Key) }).ToList() : new List<ExtraField>()
};
_upgradeRecordDataAccess.SaveUpgradeRecordToVehicle(convertedRecord);
if (_config.GetUserConfig(User).EnableAutoOdometerInsert)
{
_odometerLogic.AutoInsertOdometerRecord(new OdometerRecord
{
Date = convertedRecord.Date,
VehicleId = convertedRecord.VehicleId,
Mileage = convertedRecord.Mileage,
Notes = $"Auto Insert From Upgrade Record via CSV Import. {convertedRecord.Notes}"
});
}
}
else if (mode == ImportMode.SupplyRecord)
{
var convertedRecord = new SupplyRecord()
{
VehicleId = vehicleId,
Date = parsedDate,
PartNumber = importModel.PartNumber,
PartSupplier = importModel.PartSupplier,
Quantity = decimal.Parse(importModel.PartQuantity, NumberStyles.Any),
Description = importModel.Description,
Cost = decimal.Parse(importModel.Cost, NumberStyles.Any),
Notes = importModel.Notes,
Tags = string.IsNullOrWhiteSpace(importModel.Tags) ? [] : importModel.Tags.Split(" ").ToList(),
ExtraFields = importModel.ExtraFields.Any() ? importModel.ExtraFields.Select(x => new ExtraField { Name = x.Key, Value = x.Value, IsRequired = requiredExtraFields.Contains(x.Key) }).ToList() : new List<ExtraField>()
};
_supplyRecordDataAccess.SaveSupplyRecordToVehicle(convertedRecord);
}
else if (mode == ImportMode.TaxRecord)
{
var convertedRecord = new TaxRecord()
{
VehicleId = vehicleId,
Date = parsedDate,
Description = string.IsNullOrWhiteSpace(importModel.Description) ? $"Tax Record on {parsedDate.ToShortDateString()}" : importModel.Description,
Notes = string.IsNullOrWhiteSpace(importModel.Notes) ? "" : importModel.Notes,
Cost = decimal.Parse(importModel.Cost, NumberStyles.Any),
Tags = string.IsNullOrWhiteSpace(importModel.Tags) ? [] : importModel.Tags.Split(" ").ToList(),
ExtraFields = importModel.ExtraFields.Any() ? importModel.ExtraFields.Select(x => new ExtraField { Name = x.Key, Value = x.Value, IsRequired = requiredExtraFields.Contains(x.Key) }).ToList() : new List<ExtraField>()
};
_taxRecordDataAccess.SaveTaxRecordToVehicle(convertedRecord);
}
}
}
}
}
return Json(true);
}
catch (Exception ex)
{
_logger.LogError(ex, "Error Occurred While Bulk Inserting");
return Json(false);
}
}
}
}

View File

@@ -0,0 +1,89 @@
using CarCareTracker.Filter;
using CarCareTracker.Helper;
using CarCareTracker.Models;
using Microsoft.AspNetCore.Mvc;
namespace CarCareTracker.Controllers
{
public partial class VehicleController
{
[TypeFilter(typeof(CollaboratorFilter))]
[HttpGet]
public IActionResult GetNotesByVehicleId(int vehicleId)
{
var result = _noteDataAccess.GetNotesByVehicleId(vehicleId);
result = result.OrderByDescending(x => x.Pinned).ThenBy(x => x.Description).ToList();
return PartialView("_Notes", result);
}
[TypeFilter(typeof(CollaboratorFilter))]
[HttpGet]
public IActionResult GetPinnedNotesByVehicleId(int vehicleId)
{
var result = _noteDataAccess.GetNotesByVehicleId(vehicleId);
result = result.Where(x => x.Pinned).ToList();
return Json(result);
}
[HttpPost]
public IActionResult SaveNoteToVehicleId(Note note)
{
note.Files = note.Files.Select(x => { return new UploadedFiles { Name = x.Name, Location = _fileHelper.MoveFileFromTemp(x.Location, "documents/") }; }).ToList();
var result = _noteDataAccess.SaveNoteToVehicle(note);
if (result)
{
StaticHelper.NotifyAsync(_config.GetWebHookUrl(), note.VehicleId, User.Identity.Name, $"{(note.Id == default ? "Created" : "Edited")} Note - Description: {note.Description}");
}
return Json(result);
}
[HttpGet]
public IActionResult GetAddNotePartialView()
{
return PartialView("_NoteModal", new Note());
}
[HttpGet]
public IActionResult GetNoteForEditById(int noteId)
{
var result = _noteDataAccess.GetNoteById(noteId);
return PartialView("_NoteModal", result);
}
private bool DeleteNoteWithChecks(int noteId)
{
var existingRecord = _noteDataAccess.GetNoteById(noteId);
//security check.
if (!_userLogic.UserCanEditVehicle(GetUserID(), existingRecord.VehicleId))
{
return false;
}
var result = _noteDataAccess.DeleteNoteById(existingRecord.Id);
return result;
}
[HttpPost]
public IActionResult DeleteNoteById(int noteId)
{
var result = DeleteNoteWithChecks(noteId);
if (result)
{
StaticHelper.NotifyAsync(_config.GetWebHookUrl(), 0, User.Identity.Name, $"Deleted Note - Id: {noteId}");
}
return Json(result);
}
[HttpPost]
public IActionResult PinNotes(List<int> noteIds, bool isToggle = false, bool pinStatus = false)
{
var result = false;
foreach (int noteId in noteIds)
{
var existingNote = _noteDataAccess.GetNoteById(noteId);
if (isToggle)
{
existingNote.Pinned = !existingNote.Pinned;
}
else
{
existingNote.Pinned = pinStatus;
}
result = _noteDataAccess.SaveNoteToVehicle(existingNote);
}
return Json(result);
}
}
}

View File

@@ -0,0 +1,165 @@
using CarCareTracker.Filter;
using CarCareTracker.Helper;
using CarCareTracker.Models;
using Microsoft.AspNetCore.Mvc;
namespace CarCareTracker.Controllers
{
public partial class VehicleController
{
[TypeFilter(typeof(CollaboratorFilter))]
[HttpPost]
public IActionResult ForceRecalculateDistanceByVehicleId(int vehicleId)
{
var result = _odometerRecordDataAccess.GetOdometerRecordsByVehicleId(vehicleId);
result = _odometerLogic.AutoConvertOdometerRecord(result);
return Json(result.Any());
}
[TypeFilter(typeof(CollaboratorFilter))]
[HttpGet]
public IActionResult GetOdometerRecordsByVehicleId(int vehicleId)
{
var result = _odometerRecordDataAccess.GetOdometerRecordsByVehicleId(vehicleId);
//determine if conversion is needed.
if (result.All(x => x.InitialMileage == default))
{
result = _odometerLogic.AutoConvertOdometerRecord(result);
}
bool _useDescending = _config.GetUserConfig(User).UseDescending;
if (_useDescending)
{
result = result.OrderByDescending(x => x.Date).ThenByDescending(x => x.Mileage).ToList();
}
else
{
result = result.OrderBy(x => x.Date).ThenBy(x => x.Mileage).ToList();
}
return PartialView("_OdometerRecords", result);
}
[HttpPost]
public IActionResult SaveOdometerRecordToVehicleId(OdometerRecordInput odometerRecord)
{
//move files from temp.
odometerRecord.Files = odometerRecord.Files.Select(x => { return new UploadedFiles { Name = x.Name, Location = _fileHelper.MoveFileFromTemp(x.Location, "documents/") }; }).ToList();
var result = _odometerRecordDataAccess.SaveOdometerRecordToVehicle(odometerRecord.ToOdometerRecord());
if (result)
{
StaticHelper.NotifyAsync(_config.GetWebHookUrl(), odometerRecord.VehicleId, User.Identity.Name, $"{(odometerRecord.Id == default ? "Created" : "Edited")} Odometer Record - Mileage: {odometerRecord.Mileage.ToString()}");
}
return Json(result);
}
[HttpGet]
public IActionResult GetAddOdometerRecordPartialView(int vehicleId)
{
return PartialView("_OdometerRecordModal", new OdometerRecordInput() { InitialMileage = _odometerLogic.GetLastOdometerRecordMileage(vehicleId, new List<OdometerRecord>()), ExtraFields = _extraFieldDataAccess.GetExtraFieldsById((int)ImportMode.OdometerRecord).ExtraFields });
}
[HttpPost]
public IActionResult GetOdometerRecordsEditModal(List<int> recordIds)
{
var extraFields = _extraFieldDataAccess.GetExtraFieldsById((int)ImportMode.OdometerRecord).ExtraFields;
return PartialView("_OdometerRecordsModal", new OdometerRecordEditModel { RecordIds = recordIds, EditRecord = new OdometerRecord { ExtraFields = extraFields } });
}
[HttpPost]
public IActionResult SaveMultipleOdometerRecords(OdometerRecordEditModel editModel)
{
var dateIsEdited = editModel.EditRecord.Date != default;
var initialMileageIsEdited = editModel.EditRecord.InitialMileage != default;
var mileageIsEdited = editModel.EditRecord.Mileage != default;
var noteIsEdited = !string.IsNullOrWhiteSpace(editModel.EditRecord.Notes);
var tagsIsEdited = editModel.EditRecord.Tags.Any();
var extraFieldIsEdited = editModel.EditRecord.ExtraFields.Any();
//handle clear overrides
if (tagsIsEdited && editModel.EditRecord.Tags.Contains("---"))
{
editModel.EditRecord.Tags = new List<string>();
}
if (noteIsEdited && editModel.EditRecord.Notes == "---")
{
editModel.EditRecord.Notes = "";
}
bool result = false;
foreach (int recordId in editModel.RecordIds)
{
var existingRecord = _odometerRecordDataAccess.GetOdometerRecordById(recordId);
if (dateIsEdited)
{
existingRecord.Date = editModel.EditRecord.Date;
}
if (initialMileageIsEdited)
{
existingRecord.InitialMileage = editModel.EditRecord.InitialMileage;
}
if (mileageIsEdited)
{
existingRecord.Mileage = editModel.EditRecord.Mileage;
}
if (noteIsEdited)
{
existingRecord.Notes = editModel.EditRecord.Notes;
}
if (tagsIsEdited)
{
existingRecord.Tags = editModel.EditRecord.Tags;
}
if (extraFieldIsEdited)
{
foreach (ExtraField extraField in editModel.EditRecord.ExtraFields)
{
if (existingRecord.ExtraFields.Any(x => x.Name == extraField.Name))
{
var insertIndex = existingRecord.ExtraFields.FindIndex(x => x.Name == extraField.Name);
existingRecord.ExtraFields.RemoveAll(x => x.Name == extraField.Name);
existingRecord.ExtraFields.Insert(insertIndex, extraField);
}
else
{
existingRecord.ExtraFields.Add(extraField);
}
}
}
result = _odometerRecordDataAccess.SaveOdometerRecordToVehicle(existingRecord);
}
return Json(result);
}
[HttpGet]
public IActionResult GetOdometerRecordForEditById(int odometerRecordId)
{
var result = _odometerRecordDataAccess.GetOdometerRecordById(odometerRecordId);
//convert to Input object.
var convertedResult = new OdometerRecordInput
{
Id = result.Id,
Date = result.Date.ToShortDateString(),
InitialMileage = result.InitialMileage,
Mileage = result.Mileage,
Notes = result.Notes,
VehicleId = result.VehicleId,
Files = result.Files,
Tags = result.Tags,
ExtraFields = StaticHelper.AddExtraFields(result.ExtraFields, _extraFieldDataAccess.GetExtraFieldsById((int)ImportMode.OdometerRecord).ExtraFields)
};
return PartialView("_OdometerRecordModal", convertedResult);
}
private bool DeleteOdometerRecordWithChecks(int odometerRecordId)
{
var existingRecord = _odometerRecordDataAccess.GetOdometerRecordById(odometerRecordId);
//security check.
if (!_userLogic.UserCanEditVehicle(GetUserID(), existingRecord.VehicleId))
{
return false;
}
var result = _odometerRecordDataAccess.DeleteOdometerRecordById(existingRecord.Id);
return result;
}
[HttpPost]
public IActionResult DeleteOdometerRecordById(int odometerRecordId)
{
var result = DeleteOdometerRecordWithChecks(odometerRecordId);
if (result)
{
StaticHelper.NotifyAsync(_config.GetWebHookUrl(), 0, User.Identity.Name, $"Deleted Odometer Record - Id: {odometerRecordId}");
}
return Json(result);
}
}
}

View File

@@ -0,0 +1,284 @@
using CarCareTracker.Filter;
using CarCareTracker.Helper;
using CarCareTracker.Models;
using Microsoft.AspNetCore.Mvc;
namespace CarCareTracker.Controllers
{
public partial class VehicleController
{
[TypeFilter(typeof(CollaboratorFilter))]
[HttpGet]
public IActionResult GetPlanRecordsByVehicleId(int vehicleId)
{
var result = _planRecordDataAccess.GetPlanRecordsByVehicleId(vehicleId);
return PartialView("_PlanRecords", result);
}
[HttpPost]
public IActionResult SavePlanRecordToVehicleId(PlanRecordInput planRecord)
{
//populate createdDate
if (planRecord.Id == default)
{
planRecord.DateCreated = DateTime.Now.ToString("G");
}
planRecord.DateModified = DateTime.Now.ToString("G");
//move files from temp.
planRecord.Files = planRecord.Files.Select(x => { return new UploadedFiles { Name = x.Name, Location = _fileHelper.MoveFileFromTemp(x.Location, "documents/") }; }).ToList();
if (planRecord.Supplies.Any())
{
planRecord.RequisitionHistory.AddRange(RequisitionSupplyRecordsByUsage(planRecord.Supplies, DateTime.Parse(planRecord.DateCreated), planRecord.Description));
if (planRecord.CopySuppliesAttachment)
{
planRecord.Files.AddRange(GetSuppliesAttachments(planRecord.Supplies));
}
}
if (planRecord.DeletedRequisitionHistory.Any())
{
RestoreSupplyRecordsByUsage(planRecord.DeletedRequisitionHistory, planRecord.Description);
}
var result = _planRecordDataAccess.SavePlanRecordToVehicle(planRecord.ToPlanRecord());
if (result)
{
StaticHelper.NotifyAsync(_config.GetWebHookUrl(), planRecord.VehicleId, User.Identity.Name, $"{(planRecord.Id == default ? "Created" : "Edited")} Plan Record - Description: {planRecord.Description}");
}
return Json(result);
}
[HttpPost]
public IActionResult SavePlanRecordTemplateToVehicleId(PlanRecordInput planRecord)
{
//check if template name already taken.
var existingRecord = _planRecordTemplateDataAccess.GetPlanRecordTemplatesByVehicleId(planRecord.VehicleId).Where(x => x.Description == planRecord.Description).Any();
if (planRecord.Id == default && existingRecord)
{
return Json(OperationResponse.Failed("A template with that description already exists for this vehicle"));
}
planRecord.Files = planRecord.Files.Select(x => { return new UploadedFiles { Name = x.Name, Location = _fileHelper.MoveFileFromTemp(x.Location, "documents/") }; }).ToList();
var result = _planRecordTemplateDataAccess.SavePlanRecordTemplateToVehicle(planRecord);
return Json(OperationResponse.Conditional(result, "Template Added", string.Empty));
}
[TypeFilter(typeof(CollaboratorFilter))]
[HttpGet]
public IActionResult GetPlanRecordTemplatesForVehicleId(int vehicleId)
{
var result = _planRecordTemplateDataAccess.GetPlanRecordTemplatesByVehicleId(vehicleId);
return PartialView("_PlanRecordTemplateModal", result);
}
[HttpPost]
public IActionResult DeletePlanRecordTemplateById(int planRecordTemplateId)
{
var result = _planRecordTemplateDataAccess.DeletePlanRecordTemplateById(planRecordTemplateId);
return Json(result);
}
[HttpGet]
public IActionResult OrderPlanSupplies(int planRecordTemplateId)
{
var existingRecord = _planRecordTemplateDataAccess.GetPlanRecordTemplateById(planRecordTemplateId);
if (existingRecord.Id == default)
{
return Json(OperationResponse.Failed("Unable to find template"));
}
if (existingRecord.Supplies.Any())
{
var suppliesToOrder = CheckSupplyRecordsAvailability(existingRecord.Supplies);
return PartialView("_PlanOrderSupplies", suppliesToOrder);
}
else
{
return Json(OperationResponse.Failed("Template has No Supplies"));
}
}
[HttpPost]
public IActionResult ConvertPlanRecordTemplateToPlanRecord(int planRecordTemplateId)
{
var existingRecord = _planRecordTemplateDataAccess.GetPlanRecordTemplateById(planRecordTemplateId);
if (existingRecord.Id == default)
{
return Json(OperationResponse.Failed("Unable to find template"));
}
if (existingRecord.Supplies.Any())
{
//check if all supplies are available
var supplyAvailability = CheckSupplyRecordsAvailability(existingRecord.Supplies);
if (supplyAvailability.Any(x => x.Missing))
{
return Json(OperationResponse.Failed("Missing Supplies, Please Delete This Template and Recreate It."));
}
else if (supplyAvailability.Any(x => x.Insufficient))
{
return Json(OperationResponse.Failed("Insufficient Supplies"));
}
}
if (existingRecord.ReminderRecordId != default)
{
//check if reminder still exists and is still recurring.
var existingReminder = _reminderRecordDataAccess.GetReminderRecordById(existingRecord.ReminderRecordId);
if (existingReminder is null || existingReminder.Id == default || !existingReminder.IsRecurring)
{
return Json(OperationResponse.Failed("Missing or Non-recurring Reminder, Please Delete This Template and Recreate It."));
}
}
//populate createdDate
existingRecord.DateCreated = DateTime.Now.ToString("G");
existingRecord.DateModified = DateTime.Now.ToString("G");
existingRecord.Id = default;
if (existingRecord.Supplies.Any())
{
existingRecord.RequisitionHistory = RequisitionSupplyRecordsByUsage(existingRecord.Supplies, DateTime.Parse(existingRecord.DateCreated), existingRecord.Description);
if (existingRecord.CopySuppliesAttachment)
{
existingRecord.Files.AddRange(GetSuppliesAttachments(existingRecord.Supplies));
}
}
var result = _planRecordDataAccess.SavePlanRecordToVehicle(existingRecord.ToPlanRecord());
return Json(OperationResponse.Conditional(result, "Plan Record Added", string.Empty));
}
[HttpGet]
public IActionResult GetAddPlanRecordPartialView()
{
return PartialView("_PlanRecordModal", new PlanRecordInput() { ExtraFields = _extraFieldDataAccess.GetExtraFieldsById((int)ImportMode.PlanRecord).ExtraFields });
}
[HttpPost]
public IActionResult GetAddPlanRecordPartialView(PlanRecordInput? planModel)
{
if (planModel is not null)
{
planModel.ExtraFields = _extraFieldDataAccess.GetExtraFieldsById((int)ImportMode.PlanRecord).ExtraFields;
return PartialView("_PlanRecordModal", planModel);
}
return PartialView("_PlanRecordModal", new PlanRecordInput() { ExtraFields = _extraFieldDataAccess.GetExtraFieldsById((int)ImportMode.PlanRecord).ExtraFields });
}
[HttpPost]
public IActionResult UpdatePlanRecordProgress(int planRecordId, PlanProgress planProgress, int odometer = 0)
{
if (planRecordId == default)
{
return Json(false);
}
var existingRecord = _planRecordDataAccess.GetPlanRecordById(planRecordId);
existingRecord.Progress = planProgress;
existingRecord.DateModified = DateTime.Now;
var result = _planRecordDataAccess.SavePlanRecordToVehicle(existingRecord);
if (planProgress == PlanProgress.Done)
{
if (_config.GetUserConfig(User).EnableAutoOdometerInsert)
{
_odometerLogic.AutoInsertOdometerRecord(new OdometerRecord
{
Date = DateTime.Now.Date,
VehicleId = existingRecord.VehicleId,
Mileage = odometer,
Notes = $"Auto Insert From Plan Record: {existingRecord.Description}",
ExtraFields = existingRecord.ExtraFields
});
}
//convert plan record to service/upgrade/repair record.
if (existingRecord.ImportMode == ImportMode.ServiceRecord)
{
var newRecord = new ServiceRecord()
{
VehicleId = existingRecord.VehicleId,
Date = DateTime.Now.Date,
Mileage = odometer,
Description = existingRecord.Description,
Cost = existingRecord.Cost,
Notes = existingRecord.Notes,
Files = existingRecord.Files,
RequisitionHistory = existingRecord.RequisitionHistory,
ExtraFields = existingRecord.ExtraFields
};
_serviceRecordDataAccess.SaveServiceRecordToVehicle(newRecord);
}
else if (existingRecord.ImportMode == ImportMode.RepairRecord)
{
var newRecord = new CollisionRecord()
{
VehicleId = existingRecord.VehicleId,
Date = DateTime.Now.Date,
Mileage = odometer,
Description = existingRecord.Description,
Cost = existingRecord.Cost,
Notes = existingRecord.Notes,
Files = existingRecord.Files,
RequisitionHistory = existingRecord.RequisitionHistory,
ExtraFields = existingRecord.ExtraFields
};
_collisionRecordDataAccess.SaveCollisionRecordToVehicle(newRecord);
}
else if (existingRecord.ImportMode == ImportMode.UpgradeRecord)
{
var newRecord = new UpgradeRecord()
{
VehicleId = existingRecord.VehicleId,
Date = DateTime.Now.Date,
Mileage = odometer,
Description = existingRecord.Description,
Cost = existingRecord.Cost,
Notes = existingRecord.Notes,
Files = existingRecord.Files,
RequisitionHistory = existingRecord.RequisitionHistory,
ExtraFields = existingRecord.ExtraFields
};
_upgradeRecordDataAccess.SaveUpgradeRecordToVehicle(newRecord);
}
//push back any reminders
if (existingRecord.ReminderRecordId != default)
{
PushbackRecurringReminderRecordWithChecks(existingRecord.ReminderRecordId, DateTime.Now, odometer);
}
}
return Json(result);
}
[HttpGet]
public IActionResult GetPlanRecordTemplateForEditById(int planRecordTemplateId)
{
var result = _planRecordTemplateDataAccess.GetPlanRecordTemplateById(planRecordTemplateId);
return PartialView("_PlanRecordTemplateEditModal", result);
}
[HttpGet]
public IActionResult GetPlanRecordForEditById(int planRecordId)
{
var result = _planRecordDataAccess.GetPlanRecordById(planRecordId);
//convert to Input object.
var convertedResult = new PlanRecordInput
{
Id = result.Id,
Description = result.Description,
DateCreated = result.DateCreated.ToString("G"),
DateModified = result.DateModified.ToString("G"),
ImportMode = result.ImportMode,
Priority = result.Priority,
Progress = result.Progress,
Cost = result.Cost,
Notes = result.Notes,
VehicleId = result.VehicleId,
Files = result.Files,
RequisitionHistory = result.RequisitionHistory,
ReminderRecordId = result.ReminderRecordId,
ExtraFields = StaticHelper.AddExtraFields(result.ExtraFields, _extraFieldDataAccess.GetExtraFieldsById((int)ImportMode.PlanRecord).ExtraFields)
};
return PartialView("_PlanRecordModal", convertedResult);
}
[HttpPost]
public IActionResult DeletePlanRecordById(int planRecordId)
{
var existingRecord = _planRecordDataAccess.GetPlanRecordById(planRecordId);
//security check.
if (!_userLogic.UserCanEditVehicle(GetUserID(), existingRecord.VehicleId))
{
return Json(false);
}
//restore any requisitioned supplies if it has not been converted to other record types.
if (existingRecord.RequisitionHistory.Any() && existingRecord.Progress != PlanProgress.Done)
{
RestoreSupplyRecordsByUsage(existingRecord.RequisitionHistory, existingRecord.Description);
}
var result = _planRecordDataAccess.DeletePlanRecordById(existingRecord.Id);
if (result)
{
StaticHelper.NotifyAsync(_config.GetWebHookUrl(), 0, User.Identity.Name, $"Deleted Plan Record - Id: {planRecordId}");
}
return Json(result);
}
}
}

View File

@@ -0,0 +1,174 @@
using CarCareTracker.Filter;
using CarCareTracker.Helper;
using CarCareTracker.Models;
using Microsoft.AspNetCore.Mvc;
namespace CarCareTracker.Controllers
{
public partial class VehicleController
{
private List<ReminderRecordViewModel> GetRemindersAndUrgency(int vehicleId, DateTime dateCompare)
{
var currentMileage = _vehicleLogic.GetMaxMileage(vehicleId);
var reminders = _reminderRecordDataAccess.GetReminderRecordsByVehicleId(vehicleId);
List<ReminderRecordViewModel> results = _reminderHelper.GetReminderRecordViewModels(reminders, currentMileage, dateCompare);
return results;
}
private bool GetAndUpdateVehicleUrgentOrPastDueReminders(int vehicleId)
{
var result = GetRemindersAndUrgency(vehicleId, DateTime.Now);
//check if user wants auto-refresh past-due reminders
if (_config.GetUserConfig(User).EnableAutoReminderRefresh)
{
//check for past due reminders that are eligible for recurring.
var pastDueAndRecurring = result.Where(x => x.Urgency == ReminderUrgency.PastDue && x.IsRecurring);
if (pastDueAndRecurring.Any())
{
foreach (ReminderRecordViewModel reminderRecord in pastDueAndRecurring)
{
//update based on recurring intervals.
//pull reminderRecord based on ID
var existingReminder = _reminderRecordDataAccess.GetReminderRecordById(reminderRecord.Id);
existingReminder = _reminderHelper.GetUpdatedRecurringReminderRecord(existingReminder, null, null);
//save to db.
_reminderRecordDataAccess.SaveReminderRecordToVehicle(existingReminder);
//set urgency to not urgent so it gets excluded in count.
reminderRecord.Urgency = ReminderUrgency.NotUrgent;
}
}
}
//check for very urgent or past due reminders that were not eligible for recurring.
var pastDueAndUrgentReminders = result.Where(x => x.Urgency == ReminderUrgency.VeryUrgent || x.Urgency == ReminderUrgency.PastDue);
if (pastDueAndUrgentReminders.Any())
{
return true;
}
return false;
}
[TypeFilter(typeof(CollaboratorFilter))]
[HttpGet]
public IActionResult GetVehicleHaveUrgentOrPastDueReminders(int vehicleId)
{
var result = GetAndUpdateVehicleUrgentOrPastDueReminders(vehicleId);
return Json(result);
}
[TypeFilter(typeof(CollaboratorFilter))]
[HttpGet]
public IActionResult GetReminderRecordsByVehicleId(int vehicleId)
{
var result = GetRemindersAndUrgency(vehicleId, DateTime.Now);
result = result.OrderByDescending(x => x.Urgency).ToList();
return PartialView("_ReminderRecords", result);
}
[HttpGet]
public IActionResult GetRecurringReminderRecordsByVehicleId(int vehicleId)
{
var result = _reminderRecordDataAccess.GetReminderRecordsByVehicleId(vehicleId);
result.RemoveAll(x => !x.IsRecurring);
return PartialView("_RecurringReminderSelector", result);
}
[HttpPost]
public IActionResult PushbackRecurringReminderRecord(int reminderRecordId)
{
var result = PushbackRecurringReminderRecordWithChecks(reminderRecordId, null, null);
return Json(result);
}
private bool PushbackRecurringReminderRecordWithChecks(int reminderRecordId, DateTime? currentDate, int? currentMileage)
{
try
{
var existingReminder = _reminderRecordDataAccess.GetReminderRecordById(reminderRecordId);
if (existingReminder is not null && existingReminder.Id != default && existingReminder.IsRecurring)
{
existingReminder = _reminderHelper.GetUpdatedRecurringReminderRecord(existingReminder, currentDate, currentMileage);
//save to db.
var reminderUpdateResult = _reminderRecordDataAccess.SaveReminderRecordToVehicle(existingReminder);
if (!reminderUpdateResult)
{
_logger.LogError("Unable to update reminder either because the reminder no longer exists or is no longer recurring");
return false;
}
return true;
}
else
{
_logger.LogError("Unable to update reminder because it no longer exists.");
return false;
}
}
catch (Exception ex)
{
_logger.LogError(ex.Message);
return false;
}
}
[HttpPost]
public IActionResult SaveReminderRecordToVehicleId(ReminderRecordInput reminderRecord)
{
var result = _reminderRecordDataAccess.SaveReminderRecordToVehicle(reminderRecord.ToReminderRecord());
if (result)
{
StaticHelper.NotifyAsync(_config.GetWebHookUrl(), reminderRecord.VehicleId, User.Identity.Name, $"{(reminderRecord.Id == default ? "Created" : "Edited")} Reminder - Description: {reminderRecord.Description}");
}
return Json(result);
}
[HttpPost]
public IActionResult GetAddReminderRecordPartialView(ReminderRecordInput? reminderModel)
{
if (reminderModel is not null)
{
return PartialView("_ReminderRecordModal", reminderModel);
}
else
{
return PartialView("_ReminderRecordModal", new ReminderRecordInput());
}
}
[HttpGet]
public IActionResult GetReminderRecordForEditById(int reminderRecordId)
{
var result = _reminderRecordDataAccess.GetReminderRecordById(reminderRecordId);
//convert to Input object.
var convertedResult = new ReminderRecordInput
{
Id = result.Id,
Date = result.Date.ToShortDateString(),
Description = result.Description,
Notes = result.Notes,
VehicleId = result.VehicleId,
Mileage = result.Mileage,
Metric = result.Metric,
IsRecurring = result.IsRecurring,
UseCustomThresholds = result.UseCustomThresholds,
CustomThresholds = result.CustomThresholds,
ReminderMileageInterval = result.ReminderMileageInterval,
ReminderMonthInterval = result.ReminderMonthInterval,
CustomMileageInterval = result.CustomMileageInterval,
CustomMonthInterval = result.CustomMonthInterval,
Tags = result.Tags
};
return PartialView("_ReminderRecordModal", convertedResult);
}
private bool DeleteReminderRecordWithChecks(int reminderRecordId)
{
var existingRecord = _reminderRecordDataAccess.GetReminderRecordById(reminderRecordId);
//security check.
if (!_userLogic.UserCanEditVehicle(GetUserID(), existingRecord.VehicleId))
{
return false;
}
var result = _reminderRecordDataAccess.DeleteReminderRecordById(existingRecord.Id);
return result;
}
[HttpPost]
public IActionResult DeleteReminderRecordById(int reminderRecordId)
{
var result = DeleteReminderRecordWithChecks(reminderRecordId);
if (result)
{
StaticHelper.NotifyAsync(_config.GetWebHookUrl(), 0, User.Identity.Name, $"Deleted Reminder - Id: {reminderRecordId}");
}
return Json(result);
}
}
}

View File

@@ -0,0 +1,121 @@
using CarCareTracker.Filter;
using CarCareTracker.Helper;
using CarCareTracker.Models;
using Microsoft.AspNetCore.Mvc;
namespace CarCareTracker.Controllers
{
public partial class VehicleController
{
[TypeFilter(typeof(CollaboratorFilter))]
[HttpGet]
public IActionResult GetCollisionRecordsByVehicleId(int vehicleId)
{
var result = _collisionRecordDataAccess.GetCollisionRecordsByVehicleId(vehicleId);
bool _useDescending = _config.GetUserConfig(User).UseDescending;
if (_useDescending)
{
result = result.OrderByDescending(x => x.Date).ThenByDescending(x => x.Mileage).ToList();
}
else
{
result = result.OrderBy(x => x.Date).ThenBy(x => x.Mileage).ToList();
}
return PartialView("_CollisionRecords", result);
}
[HttpPost]
public IActionResult SaveCollisionRecordToVehicleId(CollisionRecordInput collisionRecord)
{
if (collisionRecord.Id == default && _config.GetUserConfig(User).EnableAutoOdometerInsert)
{
_odometerLogic.AutoInsertOdometerRecord(new OdometerRecord
{
Date = DateTime.Parse(collisionRecord.Date),
VehicleId = collisionRecord.VehicleId,
Mileage = collisionRecord.Mileage,
Notes = $"Auto Insert From Repair Record: {collisionRecord.Description}"
});
}
//move files from temp.
collisionRecord.Files = collisionRecord.Files.Select(x => { return new UploadedFiles { Name = x.Name, Location = _fileHelper.MoveFileFromTemp(x.Location, "documents/") }; }).ToList();
if (collisionRecord.Supplies.Any())
{
collisionRecord.RequisitionHistory.AddRange(RequisitionSupplyRecordsByUsage(collisionRecord.Supplies, DateTime.Parse(collisionRecord.Date), collisionRecord.Description));
if (collisionRecord.CopySuppliesAttachment)
{
collisionRecord.Files.AddRange(GetSuppliesAttachments(collisionRecord.Supplies));
}
}
if (collisionRecord.DeletedRequisitionHistory.Any())
{
RestoreSupplyRecordsByUsage(collisionRecord.DeletedRequisitionHistory, collisionRecord.Description);
}
//push back any reminders
if (collisionRecord.ReminderRecordId.Any())
{
foreach (int reminderRecordId in collisionRecord.ReminderRecordId)
{
PushbackRecurringReminderRecordWithChecks(reminderRecordId, DateTime.Parse(collisionRecord.Date), collisionRecord.Mileage);
}
}
var result = _collisionRecordDataAccess.SaveCollisionRecordToVehicle(collisionRecord.ToCollisionRecord());
if (result)
{
StaticHelper.NotifyAsync(_config.GetWebHookUrl(), collisionRecord.VehicleId, User.Identity.Name, $"{(collisionRecord.Id == default ? "Created" : "Edited")} Repair Record - Description: {collisionRecord.Description}");
}
return Json(result);
}
[HttpGet]
public IActionResult GetAddCollisionRecordPartialView()
{
return PartialView("_CollisionRecordModal", new CollisionRecordInput() { ExtraFields = _extraFieldDataAccess.GetExtraFieldsById((int)ImportMode.RepairRecord).ExtraFields });
}
[HttpGet]
public IActionResult GetCollisionRecordForEditById(int collisionRecordId)
{
var result = _collisionRecordDataAccess.GetCollisionRecordById(collisionRecordId);
//convert to Input object.
var convertedResult = new CollisionRecordInput
{
Id = result.Id,
Cost = result.Cost,
Date = result.Date.ToShortDateString(),
Description = result.Description,
Mileage = result.Mileage,
Notes = result.Notes,
VehicleId = result.VehicleId,
Files = result.Files,
Tags = result.Tags,
RequisitionHistory = result.RequisitionHistory,
ExtraFields = StaticHelper.AddExtraFields(result.ExtraFields, _extraFieldDataAccess.GetExtraFieldsById((int)ImportMode.RepairRecord).ExtraFields)
};
return PartialView("_CollisionRecordModal", convertedResult);
}
private bool DeleteCollisionRecordWithChecks(int collisionRecordId)
{
var existingRecord = _collisionRecordDataAccess.GetCollisionRecordById(collisionRecordId);
//security check.
if (!_userLogic.UserCanEditVehicle(GetUserID(), existingRecord.VehicleId))
{
return false;
}
//restore any requisitioned supplies.
if (existingRecord.RequisitionHistory.Any())
{
RestoreSupplyRecordsByUsage(existingRecord.RequisitionHistory, existingRecord.Description);
}
var result = _collisionRecordDataAccess.DeleteCollisionRecordById(existingRecord.Id);
return result;
}
[HttpPost]
public IActionResult DeleteCollisionRecordById(int collisionRecordId)
{
var result = DeleteCollisionRecordWithChecks(collisionRecordId);
if (result)
{
StaticHelper.NotifyAsync(_config.GetWebHookUrl(), 0, User.Identity.Name, $"Deleted Repair Record - Id: {collisionRecordId}");
}
return Json(result);
}
}
}

View File

@@ -0,0 +1,617 @@
using CarCareTracker.Filter;
using CarCareTracker.Helper;
using CarCareTracker.Models;
using Microsoft.AspNetCore.Mvc;
using System.Globalization;
namespace CarCareTracker.Controllers
{
public partial class VehicleController
{
[TypeFilter(typeof(CollaboratorFilter))]
[HttpGet]
public IActionResult GetReportPartialView(int vehicleId)
{
//get records
var vehicleData = _dataAccess.GetVehicleById(vehicleId);
var serviceRecords = _serviceRecordDataAccess.GetServiceRecordsByVehicleId(vehicleId);
var gasRecords = _gasRecordDataAccess.GetGasRecordsByVehicleId(vehicleId);
var collisionRecords = _collisionRecordDataAccess.GetCollisionRecordsByVehicleId(vehicleId);
var taxRecords = _taxRecordDataAccess.GetTaxRecordsByVehicleId(vehicleId);
var upgradeRecords = _upgradeRecordDataAccess.GetUpgradeRecordsByVehicleId(vehicleId);
var odometerRecords = _odometerRecordDataAccess.GetOdometerRecordsByVehicleId(vehicleId);
var userConfig = _config.GetUserConfig(User);
var viewModel = new ReportViewModel();
//check if custom widgets are configured
viewModel.CustomWidgetsConfigured = _fileHelper.WidgetsExist();
//get totalCostMakeUp
viewModel.CostMakeUpForVehicle = new CostMakeUpForVehicle
{
ServiceRecordSum = serviceRecords.Sum(x => x.Cost),
GasRecordSum = gasRecords.Sum(x => x.Cost),
CollisionRecordSum = collisionRecords.Sum(x => x.Cost),
TaxRecordSum = taxRecords.Sum(x => x.Cost),
UpgradeRecordSum = upgradeRecords.Sum(x => x.Cost)
};
//get costbymonth
List<CostForVehicleByMonth> allCosts = StaticHelper.GetBaseLineCosts();
allCosts.AddRange(_reportHelper.GetServiceRecordSum(serviceRecords, 0));
allCosts.AddRange(_reportHelper.GetRepairRecordSum(collisionRecords, 0));
allCosts.AddRange(_reportHelper.GetUpgradeRecordSum(upgradeRecords, 0));
allCosts.AddRange(_reportHelper.GetGasRecordSum(gasRecords, 0));
allCosts.AddRange(_reportHelper.GetTaxRecordSum(taxRecords, 0));
allCosts.AddRange(_reportHelper.GetOdometerRecordSum(odometerRecords, 0));
viewModel.CostForVehicleByMonth = allCosts.GroupBy(x => new { x.MonthName, x.MonthId }).OrderBy(x => x.Key.MonthId).Select(x => new CostForVehicleByMonth
{
MonthName = x.Key.MonthName,
Cost = x.Sum(y => y.Cost),
DistanceTraveled = x.Max(y => y.DistanceTraveled)
}).ToList();
//get reminders
var reminders = GetRemindersAndUrgency(vehicleId, DateTime.Now);
viewModel.ReminderMakeUpForVehicle = new ReminderMakeUpForVehicle
{
NotUrgentCount = reminders.Where(x => x.Urgency == ReminderUrgency.NotUrgent).Count(),
UrgentCount = reminders.Where(x => x.Urgency == ReminderUrgency.Urgent).Count(),
VeryUrgentCount = reminders.Where(x => x.Urgency == ReminderUrgency.VeryUrgent).Count(),
PastDueCount = reminders.Where(x => x.Urgency == ReminderUrgency.PastDue).Count()
};
//populate year dropdown.
var numbersArray = new List<int>();
if (serviceRecords.Any())
{
numbersArray.Add(serviceRecords.Min(x => x.Date.Year));
}
if (collisionRecords.Any())
{
numbersArray.Add(collisionRecords.Min(x => x.Date.Year));
}
if (gasRecords.Any())
{
numbersArray.Add(gasRecords.Min(x => x.Date.Year));
}
if (upgradeRecords.Any())
{
numbersArray.Add(upgradeRecords.Min(x => x.Date.Year));
}
if (odometerRecords.Any())
{
numbersArray.Add(odometerRecords.Min(x => x.Date.Year));
}
var minYear = numbersArray.Any() ? numbersArray.Min() : DateTime.Now.AddYears(-5).Year;
var yearDifference = DateTime.Now.Year - minYear + 1;
for (int i = 0; i < yearDifference; i++)
{
viewModel.Years.Add(DateTime.Now.AddYears(i * -1).Year);
}
//get collaborators
var collaborators = _userLogic.GetCollaboratorsForVehicle(vehicleId);
viewModel.Collaborators = collaborators;
//get MPG per month.
var mileageData = _gasHelper.GetGasRecordViewModels(gasRecords, userConfig.UseMPG, userConfig.UseUKMPG);
string preferredFuelMileageUnit = _config.GetUserConfig(User).PreferredGasMileageUnit;
var fuelEconomyMileageUnit = StaticHelper.GetFuelEconomyUnit(vehicleData.IsElectric, vehicleData.UseHours, userConfig.UseMPG, userConfig.UseUKMPG);
mileageData.RemoveAll(x => x.MilesPerGallon == default);
bool invertedFuelMileageUnit = fuelEconomyMileageUnit == "l/100km" && preferredFuelMileageUnit == "km/l";
var monthlyMileageData = StaticHelper.GetBaseLineCostsNoMonthName();
monthlyMileageData.AddRange(mileageData.GroupBy(x => x.MonthId).Select(x => new CostForVehicleByMonth
{
MonthId = x.Key,
Cost = x.Average(y => y.MilesPerGallon)
}));
monthlyMileageData = monthlyMileageData.GroupBy(x => x.MonthId).OrderBy(x => x.Key).Select(x => new CostForVehicleByMonth
{
MonthId = x.Key,
MonthName = CultureInfo.CurrentCulture.DateTimeFormat.GetMonthName(x.Key),
Cost = x.Sum(y => y.Cost)
}).ToList();
if (invertedFuelMileageUnit)
{
foreach (CostForVehicleByMonth monthMileage in monthlyMileageData)
{
if (monthMileage.Cost != default)
{
monthMileage.Cost = 100 / monthMileage.Cost;
}
}
}
var mpgViewModel = new MPGForVehicleByMonth {
CostData = monthlyMileageData,
Unit = invertedFuelMileageUnit ? preferredFuelMileageUnit : fuelEconomyMileageUnit,
SortedCostData = (userConfig.UseMPG || invertedFuelMileageUnit) ? monthlyMileageData.OrderByDescending(x => x.Cost).ToList() : monthlyMileageData.OrderBy(x => x.Cost).ToList()
};
viewModel.FuelMileageForVehicleByMonth = mpgViewModel;
return PartialView("_Report", viewModel);
}
[TypeFilter(typeof(CollaboratorFilter))]
[HttpGet]
public IActionResult GetCollaboratorsForVehicle(int vehicleId)
{
var result = _userLogic.GetCollaboratorsForVehicle(vehicleId);
return PartialView("_Collaborators", result);
}
[TypeFilter(typeof(CollaboratorFilter))]
[HttpPost]
public IActionResult AddCollaboratorsToVehicle(int vehicleId, string username)
{
var result = _userLogic.AddCollaboratorToVehicle(vehicleId, username);
return Json(result);
}
[TypeFilter(typeof(CollaboratorFilter))]
[HttpPost]
public IActionResult DeleteCollaboratorFromVehicle(int userId, int vehicleId)
{
var result = _userLogic.DeleteCollaboratorFromVehicle(userId, vehicleId);
return Json(result);
}
[TypeFilter(typeof(CollaboratorFilter))]
[HttpGet]
public IActionResult GetCostMakeUpForVehicle(int vehicleId, int year = 0)
{
var serviceRecords = _serviceRecordDataAccess.GetServiceRecordsByVehicleId(vehicleId);
var gasRecords = _gasRecordDataAccess.GetGasRecordsByVehicleId(vehicleId);
var collisionRecords = _collisionRecordDataAccess.GetCollisionRecordsByVehicleId(vehicleId);
var taxRecords = _taxRecordDataAccess.GetTaxRecordsByVehicleId(vehicleId);
var upgradeRecords = _upgradeRecordDataAccess.GetUpgradeRecordsByVehicleId(vehicleId);
if (year != default)
{
serviceRecords.RemoveAll(x => x.Date.Year != year);
gasRecords.RemoveAll(x => x.Date.Year != year);
collisionRecords.RemoveAll(x => x.Date.Year != year);
taxRecords.RemoveAll(x => x.Date.Year != year);
upgradeRecords.RemoveAll(x => x.Date.Year != year);
}
var viewModel = new CostMakeUpForVehicle
{
ServiceRecordSum = serviceRecords.Sum(x => x.Cost),
GasRecordSum = gasRecords.Sum(x => x.Cost),
CollisionRecordSum = collisionRecords.Sum(x => x.Cost),
TaxRecordSum = taxRecords.Sum(x => x.Cost),
UpgradeRecordSum = upgradeRecords.Sum(x => x.Cost)
};
return PartialView("_CostMakeUpReport", viewModel);
}
[TypeFilter(typeof(CollaboratorFilter))]
[HttpGet]
public IActionResult GetCostTableForVehicle(int vehicleId, int year = 0)
{
var vehicleRecords = _vehicleLogic.GetVehicleRecords(vehicleId);
var serviceRecords = vehicleRecords.ServiceRecords;
var gasRecords = vehicleRecords.GasRecords;
var collisionRecords = vehicleRecords.CollisionRecords;
var taxRecords = vehicleRecords.TaxRecords;
var upgradeRecords = vehicleRecords.UpgradeRecords;
var odometerRecords = vehicleRecords.OdometerRecords;
if (year != default)
{
serviceRecords.RemoveAll(x => x.Date.Year != year);
gasRecords.RemoveAll(x => x.Date.Year != year);
collisionRecords.RemoveAll(x => x.Date.Year != year);
taxRecords.RemoveAll(x => x.Date.Year != year);
upgradeRecords.RemoveAll(x => x.Date.Year != year);
odometerRecords.RemoveAll(x => x.Date.Year != year);
}
var maxMileage = _vehicleLogic.GetMaxMileage(vehicleRecords);
var minMileage = _vehicleLogic.GetMinMileage(vehicleRecords);
var vehicleData = _dataAccess.GetVehicleById(vehicleId);
var userConfig = _config.GetUserConfig(User);
var totalDistanceTraveled = maxMileage - minMileage;
var totalDays = _vehicleLogic.GetOwnershipDays(vehicleData.PurchaseDate, vehicleData.SoldDate, serviceRecords, collisionRecords, gasRecords, upgradeRecords, odometerRecords, taxRecords);
var viewModel = new CostTableForVehicle
{
ServiceRecordSum = serviceRecords.Sum(x => x.Cost),
GasRecordSum = gasRecords.Sum(x => x.Cost),
CollisionRecordSum = collisionRecords.Sum(x => x.Cost),
TaxRecordSum = taxRecords.Sum(x => x.Cost),
UpgradeRecordSum = upgradeRecords.Sum(x => x.Cost),
TotalDistance = totalDistanceTraveled,
DistanceUnit = vehicleData.UseHours ? "Cost Per Hour" : userConfig.UseMPG ? "Cost Per Mile" : "Cost Per Kilometer",
NumberOfDays = totalDays
};
return PartialView("_CostTableReport", viewModel);
}
[TypeFilter(typeof(CollaboratorFilter))]
public IActionResult GetReminderMakeUpByVehicle(int vehicleId, int daysToAdd)
{
var reminders = GetRemindersAndUrgency(vehicleId, DateTime.Now.AddDays(daysToAdd));
var viewModel = new ReminderMakeUpForVehicle
{
NotUrgentCount = reminders.Where(x => x.Urgency == ReminderUrgency.NotUrgent).Count(),
UrgentCount = reminders.Where(x => x.Urgency == ReminderUrgency.Urgent).Count(),
VeryUrgentCount = reminders.Where(x => x.Urgency == ReminderUrgency.VeryUrgent).Count(),
PastDueCount = reminders.Where(x => x.Urgency == ReminderUrgency.PastDue).Count()
};
return PartialView("_ReminderMakeUpReport", viewModel);
}
[TypeFilter(typeof(CollaboratorFilter))]
[HttpPost]
public IActionResult GetVehicleAttachments(int vehicleId, List<ImportMode> exportTabs)
{
List<GenericReportModel> attachmentData = new List<GenericReportModel>();
if (exportTabs.Contains(ImportMode.ServiceRecord))
{
var records = _serviceRecordDataAccess.GetServiceRecordsByVehicleId(vehicleId).Where(x => x.Files.Any());
attachmentData.AddRange(records.Select(x => new GenericReportModel
{
DataType = ImportMode.ServiceRecord,
Date = x.Date,
Odometer = x.Mileage,
Files = x.Files
}));
}
if (exportTabs.Contains(ImportMode.RepairRecord))
{
var records = _collisionRecordDataAccess.GetCollisionRecordsByVehicleId(vehicleId).Where(x => x.Files.Any());
attachmentData.AddRange(records.Select(x => new GenericReportModel
{
DataType = ImportMode.RepairRecord,
Date = x.Date,
Odometer = x.Mileage,
Files = x.Files
}));
}
if (exportTabs.Contains(ImportMode.UpgradeRecord))
{
var records = _upgradeRecordDataAccess.GetUpgradeRecordsByVehicleId(vehicleId).Where(x => x.Files.Any());
attachmentData.AddRange(records.Select(x => new GenericReportModel
{
DataType = ImportMode.UpgradeRecord,
Date = x.Date,
Odometer = x.Mileage,
Files = x.Files
}));
}
if (exportTabs.Contains(ImportMode.GasRecord))
{
var records = _gasRecordDataAccess.GetGasRecordsByVehicleId(vehicleId).Where(x => x.Files.Any());
attachmentData.AddRange(records.Select(x => new GenericReportModel
{
DataType = ImportMode.GasRecord,
Date = x.Date,
Odometer = x.Mileage,
Files = x.Files
}));
}
if (exportTabs.Contains(ImportMode.TaxRecord))
{
var records = _taxRecordDataAccess.GetTaxRecordsByVehicleId(vehicleId).Where(x => x.Files.Any());
attachmentData.AddRange(records.Select(x => new GenericReportModel
{
DataType = ImportMode.TaxRecord,
Date = x.Date,
Odometer = 0,
Files = x.Files
}));
}
if (exportTabs.Contains(ImportMode.OdometerRecord))
{
var records = _odometerRecordDataAccess.GetOdometerRecordsByVehicleId(vehicleId).Where(x => x.Files.Any());
attachmentData.AddRange(records.Select(x => new GenericReportModel
{
DataType = ImportMode.OdometerRecord,
Date = x.Date,
Odometer = x.Mileage,
Files = x.Files
}));
}
if (exportTabs.Contains(ImportMode.NoteRecord))
{
var records = _noteDataAccess.GetNotesByVehicleId(vehicleId).Where(x => x.Files.Any());
attachmentData.AddRange(records.Select(x => new GenericReportModel
{
DataType = ImportMode.NoteRecord,
Date = DateTime.Now,
Odometer = 0,
Files = x.Files
}));
}
if (attachmentData.Any())
{
attachmentData = attachmentData.OrderBy(x => x.Date).ThenBy(x => x.Odometer).ToList();
var result = _fileHelper.MakeAttachmentsExport(attachmentData);
if (string.IsNullOrWhiteSpace(result))
{
return Json(OperationResponse.Failed());
}
return Json(OperationResponse.Succeed(result));
}
else
{
return Json(OperationResponse.Failed("No Attachments Found"));
}
}
public IActionResult GetReportParameters()
{
var viewModel = new ReportParameter() {
VisibleColumns = new List<string> {
nameof(GenericReportModel.DataType),
nameof(GenericReportModel.Date),
nameof(GenericReportModel.Odometer),
nameof(GenericReportModel.Description),
nameof(GenericReportModel.Cost),
nameof(GenericReportModel.Notes)
}
};
//get all extra fields from service records, repairs, upgrades, and tax records.
var recordTypes = new List<int>() { 0, 1, 3, 4 };
var extraFields = new List<string>();
foreach(int recordType in recordTypes)
{
extraFields.AddRange(_extraFieldDataAccess.GetExtraFieldsById(recordType).ExtraFields.Select(x => x.Name));
}
viewModel.ExtraFields = extraFields.Distinct().ToList();
return PartialView("_ReportParameters", viewModel);
}
[TypeFilter(typeof(CollaboratorFilter))]
public IActionResult GetVehicleHistory(int vehicleId, ReportParameter reportParameter)
{
var vehicleHistory = new VehicleHistoryViewModel();
vehicleHistory.ReportParameters = reportParameter;
vehicleHistory.VehicleData = _dataAccess.GetVehicleById(vehicleId);
var maxMileage = _vehicleLogic.GetMaxMileage(vehicleId);
vehicleHistory.Odometer = maxMileage.ToString("N0");
var minMileage = _vehicleLogic.GetMinMileage(vehicleId);
var distanceTraveled = maxMileage - minMileage;
if (!string.IsNullOrWhiteSpace(vehicleHistory.VehicleData.PurchaseDate))
{
var endDate = vehicleHistory.VehicleData.SoldDate;
int daysOwned = 0;
if (string.IsNullOrWhiteSpace(endDate))
{
endDate = DateTime.Now.ToShortDateString();
}
try
{
daysOwned = (DateTime.Parse(endDate) - DateTime.Parse(vehicleHistory.VehicleData.PurchaseDate)).Days;
vehicleHistory.DaysOwned = daysOwned.ToString("N0");
}
catch (Exception ex)
{
_logger.LogError(ex.Message);
vehicleHistory.DaysOwned = string.Empty;
}
//calculate depreciation
var totalDepreciation = vehicleHistory.VehicleData.PurchasePrice - vehicleHistory.VehicleData.SoldPrice;
//we only calculate depreciation if a sold price is provided.
if (totalDepreciation != default && vehicleHistory.VehicleData.SoldPrice != default)
{
vehicleHistory.TotalDepreciation = totalDepreciation;
if (daysOwned != default)
{
vehicleHistory.DepreciationPerDay = Math.Abs(totalDepreciation / daysOwned);
}
if (distanceTraveled != default)
{
vehicleHistory.DepreciationPerMile = Math.Abs(totalDepreciation / distanceTraveled);
}
}
}
List<GenericReportModel> reportData = new List<GenericReportModel>();
var serviceRecords = _serviceRecordDataAccess.GetServiceRecordsByVehicleId(vehicleId);
var repairRecords = _collisionRecordDataAccess.GetCollisionRecordsByVehicleId(vehicleId);
var upgradeRecords = _upgradeRecordDataAccess.GetUpgradeRecordsByVehicleId(vehicleId);
var taxRecords = _taxRecordDataAccess.GetTaxRecordsByVehicleId(vehicleId);
var gasRecords = _gasRecordDataAccess.GetGasRecordsByVehicleId(vehicleId);
bool useMPG = _config.GetUserConfig(User).UseMPG;
bool useUKMPG = _config.GetUserConfig(User).UseUKMPG;
string preferredFuelMileageUnit = _config.GetUserConfig(User).PreferredGasMileageUnit;
vehicleHistory.DistanceUnit = vehicleHistory.VehicleData.UseHours ? "h" : useMPG ? "mi." : "km";
vehicleHistory.TotalGasCost = gasRecords.Sum(x => x.Cost);
vehicleHistory.TotalCost = serviceRecords.Sum(x => x.Cost) + repairRecords.Sum(x => x.Cost) + upgradeRecords.Sum(x => x.Cost) + taxRecords.Sum(x => x.Cost);
if (distanceTraveled != default)
{
vehicleHistory.DistanceTraveled = distanceTraveled.ToString("N0");
vehicleHistory.TotalCostPerMile = vehicleHistory.TotalCost / distanceTraveled;
vehicleHistory.TotalGasCostPerMile = vehicleHistory.TotalGasCost / distanceTraveled;
}
var averageMPG = "0";
var gasViewModels = _gasHelper.GetGasRecordViewModels(gasRecords, useMPG, useUKMPG);
if (gasViewModels.Any())
{
averageMPG = _gasHelper.GetAverageGasMileage(gasViewModels, useMPG);
}
var fuelEconomyMileageUnit = StaticHelper.GetFuelEconomyUnit(vehicleHistory.VehicleData.IsElectric, vehicleHistory.VehicleData.UseHours, useMPG, useUKMPG);
if (fuelEconomyMileageUnit == "l/100km" && preferredFuelMileageUnit == "km/l")
{
//conversion needed.
var newAverageMPG = decimal.Parse(averageMPG, NumberStyles.Any);
if (newAverageMPG != 0)
{
newAverageMPG = 100 / newAverageMPG;
}
averageMPG = newAverageMPG.ToString("F");
fuelEconomyMileageUnit = preferredFuelMileageUnit;
}
vehicleHistory.MPG = $"{averageMPG} {fuelEconomyMileageUnit}";
//insert servicerecords
reportData.AddRange(serviceRecords.Select(x => new GenericReportModel
{
Date = x.Date,
Odometer = x.Mileage,
Description = x.Description,
Notes = x.Notes,
Cost = x.Cost,
DataType = ImportMode.ServiceRecord,
ExtraFields = x.ExtraFields
}));
//repair records
reportData.AddRange(repairRecords.Select(x => new GenericReportModel
{
Date = x.Date,
Odometer = x.Mileage,
Description = x.Description,
Notes = x.Notes,
Cost = x.Cost,
DataType = ImportMode.RepairRecord,
ExtraFields = x.ExtraFields
}));
reportData.AddRange(upgradeRecords.Select(x => new GenericReportModel
{
Date = x.Date,
Odometer = x.Mileage,
Description = x.Description,
Notes = x.Notes,
Cost = x.Cost,
DataType = ImportMode.UpgradeRecord,
ExtraFields = x.ExtraFields
}));
reportData.AddRange(taxRecords.Select(x => new GenericReportModel
{
Date = x.Date,
Odometer = 0,
Description = x.Description,
Notes = x.Notes,
Cost = x.Cost,
DataType = ImportMode.TaxRecord,
ExtraFields = x.ExtraFields
}));
vehicleHistory.VehicleHistory = reportData.OrderBy(x => x.Date).ThenBy(x => x.Odometer).ToList();
return PartialView("_VehicleHistory", vehicleHistory);
}
[TypeFilter(typeof(CollaboratorFilter))]
[HttpPost]
public IActionResult GetMonthMPGByVehicle(int vehicleId, int year = 0)
{
var vehicleData = _dataAccess.GetVehicleById(vehicleId);
var gasRecords = _gasRecordDataAccess.GetGasRecordsByVehicleId(vehicleId);
var userConfig = _config.GetUserConfig(User);
string preferredFuelMileageUnit = _config.GetUserConfig(User).PreferredGasMileageUnit;
var fuelEconomyMileageUnit = StaticHelper.GetFuelEconomyUnit(vehicleData.IsElectric, vehicleData.UseHours, userConfig.UseMPG, userConfig.UseUKMPG);
bool invertedFuelMileageUnit = fuelEconomyMileageUnit == "l/100km" && preferredFuelMileageUnit == "km/l";
var mileageData = _gasHelper.GetGasRecordViewModels(gasRecords, userConfig.UseMPG, userConfig.UseUKMPG);
if (year != 0)
{
mileageData.RemoveAll(x => DateTime.Parse(x.Date).Year != year);
}
mileageData.RemoveAll(x => x.MilesPerGallon == default);
var monthlyMileageData = StaticHelper.GetBaseLineCostsNoMonthName();
monthlyMileageData.AddRange(mileageData.GroupBy(x => x.MonthId).Select(x => new CostForVehicleByMonth
{
MonthId = x.Key,
Cost = x.Average(y => y.MilesPerGallon)
}));
monthlyMileageData = monthlyMileageData.GroupBy(x => x.MonthId).OrderBy(x => x.Key).Select(x => new CostForVehicleByMonth
{
MonthId = x.Key,
MonthName = CultureInfo.CurrentCulture.DateTimeFormat.GetMonthName(x.Key),
Cost = x.Sum(y => y.Cost)
}).ToList();
if (invertedFuelMileageUnit)
{
foreach (CostForVehicleByMonth monthMileage in monthlyMileageData)
{
if (monthMileage.Cost != default)
{
monthMileage.Cost = 100 / monthMileage.Cost;
}
}
}
var mpgViewModel = new MPGForVehicleByMonth
{
CostData = monthlyMileageData,
Unit = invertedFuelMileageUnit ? preferredFuelMileageUnit : fuelEconomyMileageUnit,
SortedCostData = (userConfig.UseMPG || invertedFuelMileageUnit) ? monthlyMileageData.OrderByDescending(x => x.Cost).ToList() : monthlyMileageData.OrderBy(x => x.Cost).ToList()
};
return PartialView("_MPGByMonthReport", mpgViewModel);
}
[TypeFilter(typeof(CollaboratorFilter))]
[HttpPost]
public IActionResult GetCostByMonthByVehicle(int vehicleId, List<ImportMode> selectedMetrics, int year = 0)
{
List<CostForVehicleByMonth> allCosts = StaticHelper.GetBaseLineCosts();
if (selectedMetrics.Contains(ImportMode.ServiceRecord))
{
var serviceRecords = _serviceRecordDataAccess.GetServiceRecordsByVehicleId(vehicleId);
allCosts.AddRange(_reportHelper.GetServiceRecordSum(serviceRecords, year));
}
if (selectedMetrics.Contains(ImportMode.RepairRecord))
{
var repairRecords = _collisionRecordDataAccess.GetCollisionRecordsByVehicleId(vehicleId);
allCosts.AddRange(_reportHelper.GetRepairRecordSum(repairRecords, year));
}
if (selectedMetrics.Contains(ImportMode.UpgradeRecord))
{
var upgradeRecords = _upgradeRecordDataAccess.GetUpgradeRecordsByVehicleId(vehicleId);
allCosts.AddRange(_reportHelper.GetUpgradeRecordSum(upgradeRecords, year));
}
if (selectedMetrics.Contains(ImportMode.GasRecord))
{
var gasRecords = _gasRecordDataAccess.GetGasRecordsByVehicleId(vehicleId);
allCosts.AddRange(_reportHelper.GetGasRecordSum(gasRecords, year));
}
if (selectedMetrics.Contains(ImportMode.TaxRecord))
{
var taxRecords = _taxRecordDataAccess.GetTaxRecordsByVehicleId(vehicleId);
allCosts.AddRange(_reportHelper.GetTaxRecordSum(taxRecords, year));
}
if (selectedMetrics.Contains(ImportMode.OdometerRecord))
{
var odometerRecords = _odometerRecordDataAccess.GetOdometerRecordsByVehicleId(vehicleId);
allCosts.AddRange(_reportHelper.GetOdometerRecordSum(odometerRecords, year));
}
var groupedRecord = allCosts.GroupBy(x => new { x.MonthName, x.MonthId }).OrderBy(x => x.Key.MonthId).Select(x => new CostForVehicleByMonth
{
MonthName = x.Key.MonthName,
Cost = x.Sum(y => y.Cost),
DistanceTraveled = x.Max(y => y.DistanceTraveled)
}).ToList();
return PartialView("_GasCostByMonthReport", groupedRecord);
}
[TypeFilter(typeof(CollaboratorFilter))]
[HttpPost]
public IActionResult GetCostByMonthAndYearByVehicle(int vehicleId, List<ImportMode> selectedMetrics, int year = 0)
{
List<CostForVehicleByMonth> allCosts = StaticHelper.GetBaseLineCosts();
if (selectedMetrics.Contains(ImportMode.ServiceRecord))
{
var serviceRecords = _serviceRecordDataAccess.GetServiceRecordsByVehicleId(vehicleId);
allCosts.AddRange(_reportHelper.GetServiceRecordSum(serviceRecords, year, true));
}
if (selectedMetrics.Contains(ImportMode.RepairRecord))
{
var repairRecords = _collisionRecordDataAccess.GetCollisionRecordsByVehicleId(vehicleId);
allCosts.AddRange(_reportHelper.GetRepairRecordSum(repairRecords, year, true));
}
if (selectedMetrics.Contains(ImportMode.UpgradeRecord))
{
var upgradeRecords = _upgradeRecordDataAccess.GetUpgradeRecordsByVehicleId(vehicleId);
allCosts.AddRange(_reportHelper.GetUpgradeRecordSum(upgradeRecords, year, true));
}
if (selectedMetrics.Contains(ImportMode.GasRecord))
{
var gasRecords = _gasRecordDataAccess.GetGasRecordsByVehicleId(vehicleId);
allCosts.AddRange(_reportHelper.GetGasRecordSum(gasRecords, year, true));
}
if (selectedMetrics.Contains(ImportMode.TaxRecord))
{
var taxRecords = _taxRecordDataAccess.GetTaxRecordsByVehicleId(vehicleId);
allCosts.AddRange(_reportHelper.GetTaxRecordSum(taxRecords, year, true));
}
if (selectedMetrics.Contains(ImportMode.OdometerRecord))
{
var odometerRecords = _odometerRecordDataAccess.GetOdometerRecordsByVehicleId(vehicleId);
allCosts.AddRange(_reportHelper.GetOdometerRecordSum(odometerRecords, year, true));
}
var groupedRecord = allCosts.GroupBy(x => new { x.MonthName, x.MonthId, x.Year }).OrderByDescending(x=>x.Key.Year).Select(x => new CostForVehicleByMonth
{
Year = x.Key.Year,
MonthName = x.Key.MonthName,
Cost = x.Sum(y => y.Cost),
DistanceTraveled = x.Max(y => y.DistanceTraveled),
MonthId = x.Key.MonthId
}).ToList();
var vehicleData = _dataAccess.GetVehicleById(vehicleId);
var userConfig = _config.GetUserConfig(User);
var viewModel = new CostDistanceTableForVehicle { CostData = groupedRecord };
viewModel.DistanceUnit = vehicleData.UseHours ? "h" : userConfig.UseMPG ? "mi." : "km";
return PartialView("_CostDistanceTableReport", viewModel);
}
[HttpGet]
public IActionResult GetAdditionalWidgets()
{
var widgets = _fileHelper.GetWidgets();
return PartialView("_ReportWidgets", widgets);
}
}
}

View File

@@ -0,0 +1,121 @@
using CarCareTracker.Filter;
using CarCareTracker.Helper;
using CarCareTracker.Models;
using Microsoft.AspNetCore.Mvc;
namespace CarCareTracker.Controllers
{
public partial class VehicleController
{
[TypeFilter(typeof(CollaboratorFilter))]
[HttpGet]
public IActionResult GetServiceRecordsByVehicleId(int vehicleId)
{
var result = _serviceRecordDataAccess.GetServiceRecordsByVehicleId(vehicleId);
bool _useDescending = _config.GetUserConfig(User).UseDescending;
if (_useDescending)
{
result = result.OrderByDescending(x => x.Date).ThenByDescending(x => x.Mileage).ToList();
}
else
{
result = result.OrderBy(x => x.Date).ThenBy(x => x.Mileage).ToList();
}
return PartialView("_ServiceRecords", result);
}
[HttpPost]
public IActionResult SaveServiceRecordToVehicleId(ServiceRecordInput serviceRecord)
{
if (serviceRecord.Id == default && _config.GetUserConfig(User).EnableAutoOdometerInsert)
{
_odometerLogic.AutoInsertOdometerRecord(new OdometerRecord
{
Date = DateTime.Parse(serviceRecord.Date),
VehicleId = serviceRecord.VehicleId,
Mileage = serviceRecord.Mileage,
Notes = $"Auto Insert From Service Record: {serviceRecord.Description}"
});
}
//move files from temp.
serviceRecord.Files = serviceRecord.Files.Select(x => { return new UploadedFiles { Name = x.Name, Location = _fileHelper.MoveFileFromTemp(x.Location, "documents/") }; }).ToList();
if (serviceRecord.Supplies.Any())
{
serviceRecord.RequisitionHistory.AddRange(RequisitionSupplyRecordsByUsage(serviceRecord.Supplies, DateTime.Parse(serviceRecord.Date), serviceRecord.Description));
if (serviceRecord.CopySuppliesAttachment)
{
serviceRecord.Files.AddRange(GetSuppliesAttachments(serviceRecord.Supplies));
}
}
if (serviceRecord.DeletedRequisitionHistory.Any())
{
RestoreSupplyRecordsByUsage(serviceRecord.DeletedRequisitionHistory, serviceRecord.Description);
}
//push back any reminders
if (serviceRecord.ReminderRecordId.Any())
{
foreach (int reminderRecordId in serviceRecord.ReminderRecordId)
{
PushbackRecurringReminderRecordWithChecks(reminderRecordId, DateTime.Parse(serviceRecord.Date), serviceRecord.Mileage);
}
}
var result = _serviceRecordDataAccess.SaveServiceRecordToVehicle(serviceRecord.ToServiceRecord());
if (result)
{
StaticHelper.NotifyAsync(_config.GetWebHookUrl(), serviceRecord.VehicleId, User.Identity.Name, $"{(serviceRecord.Id == default ? "Created" : "Edited")} Service Record - Description: {serviceRecord.Description}");
}
return Json(result);
}
[HttpGet]
public IActionResult GetAddServiceRecordPartialView()
{
return PartialView("_ServiceRecordModal", new ServiceRecordInput() { ExtraFields = _extraFieldDataAccess.GetExtraFieldsById((int)ImportMode.ServiceRecord).ExtraFields });
}
[HttpGet]
public IActionResult GetServiceRecordForEditById(int serviceRecordId)
{
var result = _serviceRecordDataAccess.GetServiceRecordById(serviceRecordId);
//convert to Input object.
var convertedResult = new ServiceRecordInput
{
Id = result.Id,
Cost = result.Cost,
Date = result.Date.ToShortDateString(),
Description = result.Description,
Mileage = result.Mileage,
Notes = result.Notes,
VehicleId = result.VehicleId,
Files = result.Files,
Tags = result.Tags,
RequisitionHistory = result.RequisitionHistory,
ExtraFields = StaticHelper.AddExtraFields(result.ExtraFields, _extraFieldDataAccess.GetExtraFieldsById((int)ImportMode.ServiceRecord).ExtraFields)
};
return PartialView("_ServiceRecordModal", convertedResult);
}
private bool DeleteServiceRecordWithChecks(int serviceRecordId)
{
var existingRecord = _serviceRecordDataAccess.GetServiceRecordById(serviceRecordId);
//security check.
if (!_userLogic.UserCanEditVehicle(GetUserID(), existingRecord.VehicleId))
{
return false;
}
//restore any requisitioned supplies.
if (existingRecord.RequisitionHistory.Any())
{
RestoreSupplyRecordsByUsage(existingRecord.RequisitionHistory, existingRecord.Description);
}
var result = _serviceRecordDataAccess.DeleteServiceRecordById(existingRecord.Id);
return result;
}
[HttpPost]
public IActionResult DeleteServiceRecordById(int serviceRecordId)
{
var result = DeleteServiceRecordWithChecks(serviceRecordId);
if (result)
{
StaticHelper.NotifyAsync(_config.GetWebHookUrl(), 0, User.Identity.Name, $"Deleted Service Record - Id: {serviceRecordId}");
}
return Json(result);
}
}
}

View File

@@ -0,0 +1,252 @@
using CarCareTracker.Filter;
using CarCareTracker.Helper;
using CarCareTracker.Models;
using Microsoft.AspNetCore.Mvc;
namespace CarCareTracker.Controllers
{
public partial class VehicleController
{
private List<SupplyAvailability> CheckSupplyRecordsAvailability(List<SupplyUsage> supplyUsage)
{
//returns empty string if all supplies are available
var result = new List<SupplyAvailability>();
foreach (SupplyUsage supply in supplyUsage)
{
//get supply record.
var supplyData = _supplyRecordDataAccess.GetSupplyRecordById(supply.SupplyId);
if (supplyData == null)
{
result.Add(new SupplyAvailability { Missing = true });
}
else
{
result.Add(new SupplyAvailability { Missing = false, Description = supplyData.Description, Required = supply.Quantity, InStock = supplyData.Quantity });
}
}
return result;
}
private List<UploadedFiles> GetSuppliesAttachments(List<SupplyUsage> supplyUsage)
{
List<UploadedFiles> results = new List<UploadedFiles>();
foreach (SupplyUsage supply in supplyUsage)
{
var result = _supplyRecordDataAccess.GetSupplyRecordById(supply.SupplyId);
results.AddRange(result.Files);
}
return results;
}
private List<SupplyUsageHistory> RequisitionSupplyRecordsByUsage(List<SupplyUsage> supplyUsage, DateTime dateRequisitioned, string usageDescription)
{
List<SupplyUsageHistory> results = new List<SupplyUsageHistory>();
foreach (SupplyUsage supply in supplyUsage)
{
//get supply record.
var result = _supplyRecordDataAccess.GetSupplyRecordById(supply.SupplyId);
var unitCost = (result.Quantity != 0) ? result.Cost / result.Quantity : 0;
//deduct quantity used.
result.Quantity -= supply.Quantity;
//deduct cost.
result.Cost -= (supply.Quantity * unitCost);
//check decimal places to ensure that it always has a max of 3 decimal places.
var roundedDecimal = decimal.Round(result.Cost, 3);
if (roundedDecimal != result.Cost)
{
//Too many decimals
result.Cost = roundedDecimal;
}
//create new requisitionrrecord
var requisitionRecord = new SupplyUsageHistory
{
Id = supply.SupplyId,
Date = dateRequisitioned,
Description = usageDescription,
Quantity = supply.Quantity,
Cost = (supply.Quantity * unitCost)
};
result.RequisitionHistory.Add(requisitionRecord);
//save
_supplyRecordDataAccess.SaveSupplyRecordToVehicle(result);
requisitionRecord.Description = result.Description; //change the name of the description for plan/service/repair/upgrade records
requisitionRecord.PartNumber = result.PartNumber; //populate part number if not displayed in supplies modal.
results.Add(requisitionRecord);
}
return results;
}
private void RestoreSupplyRecordsByUsage(List<SupplyUsageHistory> supplyUsage, string usageDescription)
{
foreach (SupplyUsageHistory supply in supplyUsage)
{
try
{
if (supply.Id == default)
{
continue; //no id, skip current supply.
}
var result = _supplyRecordDataAccess.GetSupplyRecordById(supply.Id);
if (result != null && result.Id != default)
{
//supply exists, re-add the quantity and cost
result.Quantity += supply.Quantity;
result.Cost += supply.Cost;
var requisitionRecord = new SupplyUsageHistory
{
Id = supply.Id,
Date = DateTime.Now.Date,
Description = $"Restored from {usageDescription}",
Quantity = supply.Quantity,
Cost = supply.Cost
};
result.RequisitionHistory.Add(requisitionRecord);
//save
_supplyRecordDataAccess.SaveSupplyRecordToVehicle(result);
}
else
{
_logger.LogError($"Unable to find supply with id {supply.Id}");
}
} catch (Exception ex)
{
_logger.LogError($"Error restoring supply with id {supply.Id} : {ex.Message}");
}
}
}
[TypeFilter(typeof(CollaboratorFilter))]
[HttpGet]
public IActionResult GetSupplyRecordsByVehicleId(int vehicleId)
{
var result = _supplyRecordDataAccess.GetSupplyRecordsByVehicleId(vehicleId);
bool _useDescending = _config.GetUserConfig(User).UseDescending;
if (_useDescending)
{
result = result.OrderByDescending(x => x.Date).ToList();
}
else
{
result = result.OrderBy(x => x.Date).ToList();
}
return PartialView("_SupplyRecords", result);
}
[HttpGet]
public IActionResult GetSupplyRecordsForPlanRecordTemplate(int planRecordTemplateId)
{
var viewModel = new SupplyUsageViewModel();
var planRecordTemplate = _planRecordTemplateDataAccess.GetPlanRecordTemplateById(planRecordTemplateId);
if (planRecordTemplate != default && planRecordTemplate.VehicleId != default)
{
var supplies = _supplyRecordDataAccess.GetSupplyRecordsByVehicleId(planRecordTemplate.VehicleId);
if (_config.GetServerEnableShopSupplies())
{
supplies.AddRange(_supplyRecordDataAccess.GetSupplyRecordsByVehicleId(0)); // add shop supplies
}
supplies.RemoveAll(x => x.Quantity <= 0);
bool _useDescending = _config.GetUserConfig(User).UseDescending;
if (_useDescending)
{
supplies = supplies.OrderByDescending(x => x.Date).ToList();
}
else
{
supplies = supplies.OrderBy(x => x.Date).ToList();
}
viewModel.Supplies = supplies;
viewModel.Usage = planRecordTemplate.Supplies;
}
return PartialView("_SupplyUsage", viewModel);
}
[TypeFilter(typeof(CollaboratorFilter))]
[HttpGet]
public IActionResult GetSupplyRecordsForRecordsByVehicleId(int vehicleId)
{
var result = _supplyRecordDataAccess.GetSupplyRecordsByVehicleId(vehicleId);
if (_config.GetServerEnableShopSupplies())
{
result.AddRange(_supplyRecordDataAccess.GetSupplyRecordsByVehicleId(0)); // add shop supplies
}
result.RemoveAll(x => x.Quantity <= 0);
bool _useDescending = _config.GetUserConfig(User).UseDescending;
if (_useDescending)
{
result = result.OrderByDescending(x => x.Date).ToList();
}
else
{
result = result.OrderBy(x => x.Date).ToList();
}
var viewModel = new SupplyUsageViewModel
{
Supplies = result
};
return PartialView("_SupplyUsage", viewModel);
}
[HttpPost]
public IActionResult SaveSupplyRecordToVehicleId(SupplyRecordInput supplyRecord)
{
//move files from temp.
supplyRecord.Files = supplyRecord.Files.Select(x => { return new UploadedFiles { Name = x.Name, Location = _fileHelper.MoveFileFromTemp(x.Location, "documents/") }; }).ToList();
var result = _supplyRecordDataAccess.SaveSupplyRecordToVehicle(supplyRecord.ToSupplyRecord());
if (result)
{
StaticHelper.NotifyAsync(_config.GetWebHookUrl(), supplyRecord.VehicleId, User.Identity.Name, $"{(supplyRecord.Id == default ? "Created" : "Edited")} Supply Record - Description: {supplyRecord.Description}");
}
return Json(result);
}
[HttpGet]
public IActionResult GetAddSupplyRecordPartialView()
{
return PartialView("_SupplyRecordModal", new SupplyRecordInput() { ExtraFields = _extraFieldDataAccess.GetExtraFieldsById((int)ImportMode.SupplyRecord).ExtraFields });
}
[HttpGet]
public IActionResult GetSupplyRecordForEditById(int supplyRecordId)
{
var result = _supplyRecordDataAccess.GetSupplyRecordById(supplyRecordId);
if (result.RequisitionHistory.Any())
{
//requisition history when viewed through the supply is always immutable.
result.RequisitionHistory = result.RequisitionHistory.Select(x => new SupplyUsageHistory { Id = default, Cost = x.Cost, Description = x.Description, Date = x.Date, PartNumber = x.PartNumber, Quantity = x.Quantity }).ToList();
}
//convert to Input object.
var convertedResult = new SupplyRecordInput
{
Id = result.Id,
Cost = result.Cost,
Date = result.Date.ToShortDateString(),
Description = result.Description,
PartNumber = result.PartNumber,
Quantity = result.Quantity,
PartSupplier = result.PartSupplier,
Notes = result.Notes,
VehicleId = result.VehicleId,
Files = result.Files,
Tags = result.Tags,
RequisitionHistory = result.RequisitionHistory,
ExtraFields = StaticHelper.AddExtraFields(result.ExtraFields, _extraFieldDataAccess.GetExtraFieldsById((int)ImportMode.SupplyRecord).ExtraFields)
};
return PartialView("_SupplyRecordModal", convertedResult);
}
private bool DeleteSupplyRecordWithChecks(int supplyRecordId)
{
var existingRecord = _supplyRecordDataAccess.GetSupplyRecordById(supplyRecordId);
if (existingRecord.VehicleId != default)
{
//security check only if not editing shop supply.
if (!_userLogic.UserCanEditVehicle(GetUserID(), existingRecord.VehicleId))
{
return false;
}
}
var result = _supplyRecordDataAccess.DeleteSupplyRecordById(existingRecord.Id);
return result;
}
[HttpPost]
public IActionResult DeleteSupplyRecordById(int supplyRecordId)
{
var result = DeleteSupplyRecordWithChecks(supplyRecordId);
if (result)
{
StaticHelper.NotifyAsync(_config.GetWebHookUrl(), 0, User.Identity.Name, $"Deleted Supply Record - Id: {supplyRecordId}");
}
return Json(result);
}
}
}

View File

@@ -0,0 +1,135 @@
using CarCareTracker.Filter;
using CarCareTracker.Helper;
using CarCareTracker.Models;
using Microsoft.AspNetCore.Mvc;
namespace CarCareTracker.Controllers
{
public partial class VehicleController
{
[TypeFilter(typeof(CollaboratorFilter))]
[HttpGet]
public IActionResult GetTaxRecordsByVehicleId(int vehicleId)
{
var result = _taxRecordDataAccess.GetTaxRecordsByVehicleId(vehicleId);
bool _useDescending = _config.GetUserConfig(User).UseDescending;
if (_useDescending)
{
result = result.OrderByDescending(x => x.Date).ToList();
}
else
{
result = result.OrderBy(x => x.Date).ToList();
}
return PartialView("_TaxRecords", result);
}
private void UpdateRecurringTaxes(int vehicleId)
{
var result = _taxRecordDataAccess.GetTaxRecordsByVehicleId(vehicleId);
var recurringFees = result.Where(x => x.IsRecurring);
if (recurringFees.Any())
{
foreach (TaxRecord recurringFee in recurringFees)
{
var newDate = new DateTime();
if (recurringFee.RecurringInterval != ReminderMonthInterval.Other)
{
newDate = recurringFee.Date.AddMonths((int)recurringFee.RecurringInterval);
}
else
{
newDate = recurringFee.Date.AddMonths(recurringFee.CustomMonthInterval);
}
if (DateTime.Now > newDate)
{
recurringFee.IsRecurring = false;
var newRecurringFee = new TaxRecord()
{
VehicleId = recurringFee.VehicleId,
Date = newDate,
Description = recurringFee.Description,
Cost = recurringFee.Cost,
IsRecurring = true,
Notes = recurringFee.Notes,
RecurringInterval = recurringFee.RecurringInterval,
CustomMonthInterval = recurringFee.CustomMonthInterval,
Files = recurringFee.Files,
Tags = recurringFee.Tags,
ExtraFields = recurringFee.ExtraFields
};
_taxRecordDataAccess.SaveTaxRecordToVehicle(recurringFee);
_taxRecordDataAccess.SaveTaxRecordToVehicle(newRecurringFee);
}
}
}
}
[HttpPost]
public IActionResult SaveTaxRecordToVehicleId(TaxRecordInput taxRecord)
{
//move files from temp.
taxRecord.Files = taxRecord.Files.Select(x => { return new UploadedFiles { Name = x.Name, Location = _fileHelper.MoveFileFromTemp(x.Location, "documents/") }; }).ToList();
//push back any reminders
if (taxRecord.ReminderRecordId.Any())
{
foreach (int reminderRecordId in taxRecord.ReminderRecordId)
{
PushbackRecurringReminderRecordWithChecks(reminderRecordId, DateTime.Parse(taxRecord.Date), null);
}
}
var result = _taxRecordDataAccess.SaveTaxRecordToVehicle(taxRecord.ToTaxRecord());
if (result)
{
StaticHelper.NotifyAsync(_config.GetWebHookUrl(), taxRecord.VehicleId, User.Identity.Name, $"{(taxRecord.Id == default ? "Created" : "Edited")} Tax Record - Description: {taxRecord.Description}");
}
return Json(result);
}
[HttpGet]
public IActionResult GetAddTaxRecordPartialView()
{
return PartialView("_TaxRecordModal", new TaxRecordInput() { ExtraFields = _extraFieldDataAccess.GetExtraFieldsById((int)ImportMode.TaxRecord).ExtraFields });
}
[HttpGet]
public IActionResult GetTaxRecordForEditById(int taxRecordId)
{
var result = _taxRecordDataAccess.GetTaxRecordById(taxRecordId);
//convert to Input object.
var convertedResult = new TaxRecordInput
{
Id = result.Id,
Cost = result.Cost,
Date = result.Date.ToShortDateString(),
Description = result.Description,
Notes = result.Notes,
VehicleId = result.VehicleId,
IsRecurring = result.IsRecurring,
RecurringInterval = result.RecurringInterval,
CustomMonthInterval = result.CustomMonthInterval,
Files = result.Files,
Tags = result.Tags,
ExtraFields = StaticHelper.AddExtraFields(result.ExtraFields, _extraFieldDataAccess.GetExtraFieldsById((int)ImportMode.TaxRecord).ExtraFields)
};
return PartialView("_TaxRecordModal", convertedResult);
}
private bool DeleteTaxRecordWithChecks(int taxRecordId)
{
var existingRecord = _taxRecordDataAccess.GetTaxRecordById(taxRecordId);
//security check.
if (!_userLogic.UserCanEditVehicle(GetUserID(), existingRecord.VehicleId))
{
return false;
}
var result = _taxRecordDataAccess.DeleteTaxRecordById(existingRecord.Id);
return result;
}
[HttpPost]
public IActionResult DeleteTaxRecordById(int taxRecordId)
{
var result = DeleteTaxRecordWithChecks(taxRecordId);
if (result)
{
StaticHelper.NotifyAsync(_config.GetWebHookUrl(), 0, User.Identity.Name, $"Deleted Tax Record - Id: {taxRecordId}");
}
return Json(result);
}
}
}

View File

@@ -0,0 +1,121 @@
using CarCareTracker.Filter;
using CarCareTracker.Helper;
using CarCareTracker.Models;
using Microsoft.AspNetCore.Mvc;
namespace CarCareTracker.Controllers
{
public partial class VehicleController
{
[TypeFilter(typeof(CollaboratorFilter))]
[HttpGet]
public IActionResult GetUpgradeRecordsByVehicleId(int vehicleId)
{
var result = _upgradeRecordDataAccess.GetUpgradeRecordsByVehicleId(vehicleId);
bool _useDescending = _config.GetUserConfig(User).UseDescending;
if (_useDescending)
{
result = result.OrderByDescending(x => x.Date).ThenByDescending(x => x.Mileage).ToList();
}
else
{
result = result.OrderBy(x => x.Date).ThenBy(x => x.Mileage).ToList();
}
return PartialView("_UpgradeRecords", result);
}
[HttpPost]
public IActionResult SaveUpgradeRecordToVehicleId(UpgradeRecordInput upgradeRecord)
{
if (upgradeRecord.Id == default && _config.GetUserConfig(User).EnableAutoOdometerInsert)
{
_odometerLogic.AutoInsertOdometerRecord(new OdometerRecord
{
Date = DateTime.Parse(upgradeRecord.Date),
VehicleId = upgradeRecord.VehicleId,
Mileage = upgradeRecord.Mileage,
Notes = $"Auto Insert From Upgrade Record: {upgradeRecord.Description}"
});
}
//move files from temp.
upgradeRecord.Files = upgradeRecord.Files.Select(x => { return new UploadedFiles { Name = x.Name, Location = _fileHelper.MoveFileFromTemp(x.Location, "documents/") }; }).ToList();
if (upgradeRecord.Supplies.Any())
{
upgradeRecord.RequisitionHistory.AddRange(RequisitionSupplyRecordsByUsage(upgradeRecord.Supplies, DateTime.Parse(upgradeRecord.Date), upgradeRecord.Description));
if (upgradeRecord.CopySuppliesAttachment)
{
upgradeRecord.Files.AddRange(GetSuppliesAttachments(upgradeRecord.Supplies));
}
}
if (upgradeRecord.DeletedRequisitionHistory.Any())
{
RestoreSupplyRecordsByUsage(upgradeRecord.DeletedRequisitionHistory, upgradeRecord.Description);
}
//push back any reminders
if (upgradeRecord.ReminderRecordId.Any())
{
foreach (int reminderRecordId in upgradeRecord.ReminderRecordId)
{
PushbackRecurringReminderRecordWithChecks(reminderRecordId, DateTime.Parse(upgradeRecord.Date), upgradeRecord.Mileage);
}
}
var result = _upgradeRecordDataAccess.SaveUpgradeRecordToVehicle(upgradeRecord.ToUpgradeRecord());
if (result)
{
StaticHelper.NotifyAsync(_config.GetWebHookUrl(), upgradeRecord.VehicleId, User.Identity.Name, $"{(upgradeRecord.Id == default ? "Created" : "Edited")} Upgrade Record - Description: {upgradeRecord.Description}");
}
return Json(result);
}
[HttpGet]
public IActionResult GetAddUpgradeRecordPartialView()
{
return PartialView("_UpgradeRecordModal", new UpgradeRecordInput() { ExtraFields = _extraFieldDataAccess.GetExtraFieldsById((int)ImportMode.UpgradeRecord).ExtraFields });
}
[HttpGet]
public IActionResult GetUpgradeRecordForEditById(int upgradeRecordId)
{
var result = _upgradeRecordDataAccess.GetUpgradeRecordById(upgradeRecordId);
//convert to Input object.
var convertedResult = new UpgradeRecordInput
{
Id = result.Id,
Cost = result.Cost,
Date = result.Date.ToShortDateString(),
Description = result.Description,
Mileage = result.Mileage,
Notes = result.Notes,
VehicleId = result.VehicleId,
Files = result.Files,
Tags = result.Tags,
RequisitionHistory = result.RequisitionHistory,
ExtraFields = StaticHelper.AddExtraFields(result.ExtraFields, _extraFieldDataAccess.GetExtraFieldsById((int)ImportMode.UpgradeRecord).ExtraFields)
};
return PartialView("_UpgradeRecordModal", convertedResult);
}
private bool DeleteUpgradeRecordWithChecks(int upgradeRecordId)
{
var existingRecord = _upgradeRecordDataAccess.GetUpgradeRecordById(upgradeRecordId);
//security check.
if (!_userLogic.UserCanEditVehicle(GetUserID(), existingRecord.VehicleId))
{
return false;
}
//restore any requisitioned supplies.
if (existingRecord.RequisitionHistory.Any())
{
RestoreSupplyRecordsByUsage(existingRecord.RequisitionHistory, existingRecord.Description);
}
var result = _upgradeRecordDataAccess.DeleteUpgradeRecordById(existingRecord.Id);
return result;
}
[HttpPost]
public IActionResult DeleteUpgradeRecordById(int upgradeRecordId)
{
var result = DeleteUpgradeRecordWithChecks(upgradeRecordId);
if (result)
{
StaticHelper.NotifyAsync(_config.GetWebHookUrl(), 0, User.Identity.Name, $"Deleted Upgrade Record - Id: {upgradeRecordId}");
}
return Json(result);
}
}
}

File diff suppressed because it is too large Load Diff

9
Enum/DashboardMetric.cs Normal file
View File

@@ -0,0 +1,9 @@
namespace CarCareTracker.Models
{
public enum DashboardMetric
{
Default = 0,
TotalCost = 1,
CostPerMile = 2
}
}

10
Enum/KioskMode.cs Normal file
View File

@@ -0,0 +1,10 @@
namespace CarCareTracker.Models
{
public enum KioskMode
{
Vehicle = 0,
Plan = 1,
Reminder = 2,
Cycle = 3
}
}

View File

@@ -1,7 +1,6 @@
using CarCareTracker.External.Interfaces;
using CarCareTracker.Helper;
using CarCareTracker.Models;
using LiteDB;
namespace CarCareTracker.External.Implementations
{

View File

@@ -1,7 +1,6 @@
using CarCareTracker.External.Interfaces;
using CarCareTracker.Helper;
using CarCareTracker.Models;
using LiteDB;
namespace CarCareTracker.External.Implementations
{

View File

@@ -18,7 +18,7 @@ namespace CarCareTracker.External.Implementations
try
{
//create table if not exist.
string initCMD = $"CREATE TABLE IF NOT EXISTS app.{tableName} (id INT GENERATED BY DEFAULT AS IDENTITY primary key, vehicleId INT not null, data jsonb not null)";
string initCMD = $"CREATE SCHEMA IF NOT EXISTS app; CREATE TABLE IF NOT EXISTS app.{tableName} (id INT GENERATED BY DEFAULT AS IDENTITY primary key, vehicleId INT not null, data jsonb not null)";
using (var ctext = pgDataSource.CreateCommand(initCMD))
{
ctext.ExecuteNonQuery();
@@ -147,7 +147,8 @@ namespace CarCareTracker.External.Implementations
using (var ctext = pgDataSource.CreateCommand(cmd))
{
ctext.Parameters.AddWithValue("id", vehicleId);
return ctext.ExecuteNonQuery() > 0;
ctext.ExecuteNonQuery();
return true;
}
}
catch (Exception ex)

View File

@@ -18,7 +18,7 @@ namespace CarCareTracker.External.Implementations
try
{
//create table if not exist.
string initCMD = $"CREATE TABLE IF NOT EXISTS app.{tableName} (id INT primary key, data jsonb not null)";
string initCMD = $"CREATE SCHEMA IF NOT EXISTS app; CREATE TABLE IF NOT EXISTS app.{tableName} (id INT primary key, data jsonb not null)";
using (var ctext = pgDataSource.CreateCommand(initCMD))
{
ctext.ExecuteNonQuery();

View File

@@ -17,7 +17,7 @@ namespace CarCareTracker.External.Implementations
try
{
//create table if not exist.
string initCMD = $"CREATE TABLE IF NOT EXISTS app.{tableName} (id INT GENERATED BY DEFAULT AS IDENTITY primary key, vehicleId INT not null, data jsonb not null)";
string initCMD = $"CREATE SCHEMA IF NOT EXISTS app; CREATE TABLE IF NOT EXISTS app.{tableName} (id INT GENERATED BY DEFAULT AS IDENTITY primary key, vehicleId INT not null, data jsonb not null)";
using (var ctext = pgDataSource.CreateCommand(initCMD))
{
ctext.ExecuteNonQuery();
@@ -146,7 +146,8 @@ namespace CarCareTracker.External.Implementations
using (var ctext = pgDataSource.CreateCommand(cmd))
{
ctext.Parameters.AddWithValue("id", vehicleId);
return ctext.ExecuteNonQuery() > 0;
ctext.ExecuteNonQuery();
return true;
}
}
catch (Exception ex)

View File

@@ -17,7 +17,7 @@ namespace CarCareTracker.External.Implementations
try
{
//create table if not exist.
string initCMD = $"CREATE TABLE IF NOT EXISTS app.{tableName} (id INT GENERATED BY DEFAULT AS IDENTITY primary key, vehicleId INT not null, data jsonb not null)";
string initCMD = $"CREATE SCHEMA IF NOT EXISTS app; CREATE TABLE IF NOT EXISTS app.{tableName} (id INT GENERATED BY DEFAULT AS IDENTITY primary key, vehicleId INT not null, data jsonb not null)";
using (var ctext = pgDataSource.CreateCommand(initCMD))
{
ctext.ExecuteNonQuery();
@@ -141,7 +141,8 @@ namespace CarCareTracker.External.Implementations
using (var ctext = pgDataSource.CreateCommand(cmd))
{
ctext.Parameters.AddWithValue("id", vehicleId);
return ctext.ExecuteNonQuery() > 0;
ctext.ExecuteNonQuery();
return true;
}
} catch (Exception ex)
{

View File

@@ -17,7 +17,7 @@ namespace CarCareTracker.External.Implementations
try
{
//create table if not exist.
string initCMD = $"CREATE TABLE IF NOT EXISTS app.{tableName} (id INT GENERATED BY DEFAULT AS IDENTITY primary key, vehicleId INT not null, data jsonb not null)";
string initCMD = $"CREATE SCHEMA IF NOT EXISTS app; CREATE TABLE IF NOT EXISTS app.{tableName} (id INT GENERATED BY DEFAULT AS IDENTITY primary key, vehicleId INT not null, data jsonb not null)";
using (var ctext = pgDataSource.CreateCommand(initCMD))
{
ctext.ExecuteNonQuery();
@@ -146,7 +146,8 @@ namespace CarCareTracker.External.Implementations
using (var ctext = pgDataSource.CreateCommand(cmd))
{
ctext.Parameters.AddWithValue("id", vehicleId);
return ctext.ExecuteNonQuery() > 0;
ctext.ExecuteNonQuery();
return true;
}
}
catch (Exception ex)

View File

@@ -17,7 +17,7 @@ namespace CarCareTracker.External.Implementations
try
{
//create table if not exist.
string initCMD = $"CREATE TABLE IF NOT EXISTS app.{tableName} (id INT GENERATED BY DEFAULT AS IDENTITY primary key, vehicleId INT not null, data jsonb not null)";
string initCMD = $"CREATE SCHEMA IF NOT EXISTS app; CREATE TABLE IF NOT EXISTS app.{tableName} (id INT GENERATED BY DEFAULT AS IDENTITY primary key, vehicleId INT not null, data jsonb not null)";
using (var ctext = pgDataSource.CreateCommand(initCMD))
{
ctext.ExecuteNonQuery();
@@ -146,7 +146,8 @@ namespace CarCareTracker.External.Implementations
using (var ctext = pgDataSource.CreateCommand(cmd))
{
ctext.Parameters.AddWithValue("id", vehicleId);
return ctext.ExecuteNonQuery() > 0;
ctext.ExecuteNonQuery();
return true;
}
}
catch (Exception ex)

View File

@@ -17,7 +17,7 @@ namespace CarCareTracker.External.Implementations
try
{
//create table if not exist.
string initCMD = $"CREATE TABLE IF NOT EXISTS app.{tableName} (id INT GENERATED BY DEFAULT AS IDENTITY primary key, vehicleId INT not null, data jsonb not null)";
string initCMD = $"CREATE SCHEMA IF NOT EXISTS app; CREATE TABLE IF NOT EXISTS app.{tableName} (id INT GENERATED BY DEFAULT AS IDENTITY primary key, vehicleId INT not null, data jsonb not null)";
using (var ctext = pgDataSource.CreateCommand(initCMD))
{
ctext.ExecuteNonQuery();
@@ -146,7 +146,8 @@ namespace CarCareTracker.External.Implementations
using (var ctext = pgDataSource.CreateCommand(cmd))
{
ctext.Parameters.AddWithValue("id", vehicleId);
return ctext.ExecuteNonQuery() > 0;
ctext.ExecuteNonQuery();
return true;
}
}
catch (Exception ex)

View File

@@ -17,7 +17,7 @@ namespace CarCareTracker.External.Implementations
try
{
//create table if not exist.
string initCMD = $"CREATE TABLE IF NOT EXISTS app.{tableName} (id INT GENERATED BY DEFAULT AS IDENTITY primary key, vehicleId INT not null, data jsonb not null)";
string initCMD = $"CREATE SCHEMA IF NOT EXISTS app; CREATE TABLE IF NOT EXISTS app.{tableName} (id INT GENERATED BY DEFAULT AS IDENTITY primary key, vehicleId INT not null, data jsonb not null)";
using (var ctext = pgDataSource.CreateCommand(initCMD))
{
ctext.ExecuteNonQuery();
@@ -146,7 +146,8 @@ namespace CarCareTracker.External.Implementations
using (var ctext = pgDataSource.CreateCommand(cmd))
{
ctext.Parameters.AddWithValue("id", vehicleId);
return ctext.ExecuteNonQuery() > 0;
ctext.ExecuteNonQuery();
return true;
}
}
catch (Exception ex)

View File

@@ -17,7 +17,7 @@ namespace CarCareTracker.External.Implementations
try
{
//create table if not exist.
string initCMD = $"CREATE TABLE IF NOT EXISTS app.{tableName} (id INT GENERATED BY DEFAULT AS IDENTITY primary key, vehicleId INT not null, data jsonb not null)";
string initCMD = $"CREATE SCHEMA IF NOT EXISTS app; CREATE TABLE IF NOT EXISTS app.{tableName} (id INT GENERATED BY DEFAULT AS IDENTITY primary key, vehicleId INT not null, data jsonb not null)";
using (var ctext = pgDataSource.CreateCommand(initCMD))
{
ctext.ExecuteNonQuery();
@@ -146,7 +146,8 @@ namespace CarCareTracker.External.Implementations
using (var ctext = pgDataSource.CreateCommand(cmd))
{
ctext.Parameters.AddWithValue("id", vehicleId);
return ctext.ExecuteNonQuery() > 0;
ctext.ExecuteNonQuery();
return true;
}
}
catch (Exception ex)

View File

@@ -17,7 +17,7 @@ namespace CarCareTracker.External.Implementations
try
{
//create table if not exist.
string initCMD = $"CREATE TABLE IF NOT EXISTS app.{tableName} (id INT GENERATED BY DEFAULT AS IDENTITY primary key, vehicleId INT not null, data jsonb not null)";
string initCMD = $"CREATE SCHEMA IF NOT EXISTS app; CREATE TABLE IF NOT EXISTS app.{tableName} (id INT GENERATED BY DEFAULT AS IDENTITY primary key, vehicleId INT not null, data jsonb not null)";
using (var ctext = pgDataSource.CreateCommand(initCMD))
{
ctext.ExecuteNonQuery();
@@ -146,7 +146,8 @@ namespace CarCareTracker.External.Implementations
using (var ctext = pgDataSource.CreateCommand(cmd))
{
ctext.Parameters.AddWithValue("id", vehicleId);
return ctext.ExecuteNonQuery() > 0;
ctext.ExecuteNonQuery();
return true;
}
}
catch (Exception ex)

View File

@@ -17,7 +17,7 @@ namespace CarCareTracker.External.Implementations
try
{
//create table if not exist.
string initCMD = $"CREATE TABLE IF NOT EXISTS app.{tableName} (id INT GENERATED BY DEFAULT AS IDENTITY primary key, vehicleId INT not null, data jsonb not null)";
string initCMD = $"CREATE SCHEMA IF NOT EXISTS app; CREATE TABLE IF NOT EXISTS app.{tableName} (id INT GENERATED BY DEFAULT AS IDENTITY primary key, vehicleId INT not null, data jsonb not null)";
using (var ctext = pgDataSource.CreateCommand(initCMD))
{
ctext.ExecuteNonQuery();
@@ -146,7 +146,8 @@ namespace CarCareTracker.External.Implementations
using (var ctext = pgDataSource.CreateCommand(cmd))
{
ctext.Parameters.AddWithValue("id", vehicleId);
return ctext.ExecuteNonQuery() > 0;
ctext.ExecuteNonQuery();
return true;
}
}
catch (Exception ex)

View File

@@ -16,7 +16,7 @@ namespace CarCareTracker.External.Implementations
try
{
//create table if not exist.
string initCMD = $"CREATE TABLE IF NOT EXISTS app.{tableName} (id INT GENERATED BY DEFAULT AS IDENTITY primary key, body TEXT not null, emailaddress TEXT not null)";
string initCMD = $"CREATE SCHEMA IF NOT EXISTS app; CREATE TABLE IF NOT EXISTS app.{tableName} (id INT GENERATED BY DEFAULT AS IDENTITY primary key, body TEXT not null, emailaddress TEXT not null)";
using (var ctext = pgDataSource.CreateCommand(initCMD))
{
ctext.ExecuteNonQuery();

View File

@@ -17,7 +17,7 @@ namespace CarCareTracker.External.Implementations
try
{
//create table if not exist.
string initCMD = $"CREATE TABLE IF NOT EXISTS app.{tableName} (id INT GENERATED BY DEFAULT AS IDENTITY primary key, vehicleId INT not null, data jsonb not null)";
string initCMD = $"CREATE SCHEMA IF NOT EXISTS app; CREATE TABLE IF NOT EXISTS app.{tableName} (id INT GENERATED BY DEFAULT AS IDENTITY primary key, vehicleId INT not null, data jsonb not null)";
using (var ctext = pgDataSource.CreateCommand(initCMD))
{
ctext.ExecuteNonQuery();
@@ -146,7 +146,8 @@ namespace CarCareTracker.External.Implementations
using (var ctext = pgDataSource.CreateCommand(cmd))
{
ctext.Parameters.AddWithValue("id", vehicleId);
return ctext.ExecuteNonQuery() > 0;
ctext.ExecuteNonQuery();
return true;
}
}
catch (Exception ex)

View File

@@ -1,7 +1,6 @@
using CarCareTracker.External.Interfaces;
using CarCareTracker.Models;
using Npgsql;
using System.Net.Mail;
namespace CarCareTracker.External.Implementations
{
@@ -17,7 +16,7 @@ namespace CarCareTracker.External.Implementations
try
{
//create table if not exist.
string initCMD = $"CREATE TABLE IF NOT EXISTS app.{tableName} (userId INT, vehicleId INT, PRIMARY KEY(userId, vehicleId))";
string initCMD = $"CREATE SCHEMA IF NOT EXISTS app; CREATE TABLE IF NOT EXISTS app.{tableName} (userId INT, vehicleId INT, PRIMARY KEY(userId, vehicleId))";
using (var ctext = pgDataSource.CreateCommand(initCMD))
{
ctext.ExecuteNonQuery();
@@ -176,7 +175,8 @@ namespace CarCareTracker.External.Implementations
using (var ctext = pgDataSource.CreateCommand(cmd))
{
ctext.Parameters.AddWithValue("vehicleId", vehicleId);
return ctext.ExecuteNonQuery() > 0;
ctext.ExecuteNonQuery();
return true;
}
}
catch (Exception ex)
@@ -198,7 +198,8 @@ namespace CarCareTracker.External.Implementations
using (var ctext = pgDataSource.CreateCommand(cmd))
{
ctext.Parameters.AddWithValue("userId", userId);
return ctext.ExecuteNonQuery() > 0;
ctext.ExecuteNonQuery();
return true;
}
}
catch (Exception ex)

View File

@@ -17,7 +17,7 @@ namespace CarCareTracker.External.Implementations
try
{
//create table if not exist.
string initCMD = $"CREATE TABLE IF NOT EXISTS app.{tableName} (id INT primary key, data jsonb not null)";
string initCMD = $"CREATE SCHEMA IF NOT EXISTS app; CREATE TABLE IF NOT EXISTS app.{tableName} (id INT primary key, data jsonb not null)";
using (var ctext = pgDataSource.CreateCommand(initCMD))
{
ctext.ExecuteNonQuery();
@@ -94,7 +94,8 @@ namespace CarCareTracker.External.Implementations
using (var ctext = pgDataSource.CreateCommand(cmd))
{
ctext.Parameters.AddWithValue("id", userId);
return ctext.ExecuteNonQuery() > 0;
ctext.ExecuteNonQuery();
return true;
}
}
catch (Exception ex)

View File

@@ -16,7 +16,7 @@ namespace CarCareTracker.External.Implementations
try
{
//create table if not exist.
string initCMD = $"CREATE TABLE IF NOT EXISTS app.{tableName} (id INT GENERATED BY DEFAULT AS IDENTITY primary key, username TEXT not null, emailaddress TEXT not null, password TEXT not null, isadmin BOOLEAN)";
string initCMD = $"CREATE SCHEMA IF NOT EXISTS app; CREATE TABLE IF NOT EXISTS app.{tableName} (id INT GENERATED BY DEFAULT AS IDENTITY primary key, username TEXT not null, emailaddress TEXT not null, password TEXT not null, isadmin BOOLEAN)";
using (var ctext = pgDataSource.CreateCommand(initCMD))
{
ctext.ExecuteNonQuery();

View File

@@ -17,7 +17,7 @@ namespace CarCareTracker.External.Implementations
try
{
//create table if not exist.
string initCMD = $"CREATE TABLE IF NOT EXISTS app.{tableName} (id INT GENERATED BY DEFAULT AS IDENTITY primary key, data jsonb not null)";
string initCMD = $"CREATE SCHEMA IF NOT EXISTS app; CREATE TABLE IF NOT EXISTS app.{tableName} (id INT GENERATED BY DEFAULT AS IDENTITY primary key, data jsonb not null)";
using (var ctext = pgDataSource.CreateCommand(initCMD))
{
ctext.ExecuteNonQuery();

View File

@@ -2,6 +2,7 @@
using CarCareTracker.Models;
using Microsoft.Extensions.Caching.Memory;
using System.Security.Claims;
using System.Text.Json;
namespace CarCareTracker.Helper
{
@@ -9,13 +10,17 @@ namespace CarCareTracker.Helper
{
OpenIDConfig GetOpenIDConfig();
ReminderUrgencyConfig GetReminderUrgencyConfig();
MailConfig GetMailConfig();
UserConfig GetUserConfig(ClaimsPrincipal user);
bool SaveUserConfig(ClaimsPrincipal user, UserConfig configData);
bool AuthenticateRootUser(string username, string password);
bool AuthenticateRootUserOIDC(string email);
string GetWebHookUrl();
bool GetCustomWidgetsEnabled();
string GetMOTD();
string GetLogoUrl();
string GetServerLanguage();
bool GetServerDisabledRegistration();
bool GetServerEnableShopSupplies();
string GetServerPostgresConnection();
string GetAllowedFileUploadExtensions();
@@ -25,31 +30,30 @@ namespace CarCareTracker.Helper
{
private readonly IConfiguration _config;
private readonly IUserConfigDataAccess _userConfig;
private readonly ILogger<IConfigHelper> _logger;
private IMemoryCache _cache;
public ConfigHelper(IConfiguration serverConfig,
IUserConfigDataAccess userConfig,
IMemoryCache memoryCache)
IMemoryCache memoryCache,
ILogger<IConfigHelper> logger)
{
_config = serverConfig;
_userConfig = userConfig;
_cache = memoryCache;
_logger = logger;
}
public string GetWebHookUrl()
{
var webhook = _config["LUBELOGGER_WEBHOOK"];
if (string.IsNullOrWhiteSpace(webhook))
{
webhook = "";
}
var webhook = CheckString("LUBELOGGER_WEBHOOK");
return webhook;
}
public bool GetCustomWidgetsEnabled()
{
return CheckBool(CheckString("LUBELOGGER_CUSTOM_WIDGETS"));
}
public string GetMOTD()
{
var motd = _config["LUBELOGGER_MOTD"];
if (string.IsNullOrWhiteSpace(motd))
{
motd = "";
}
var motd = CheckString("LUBELOGGER_MOTD");
return motd;
}
public OpenIDConfig GetOpenIDConfig()
@@ -62,51 +66,59 @@ namespace CarCareTracker.Helper
ReminderUrgencyConfig reminderUrgencyConfig = _config.GetSection("ReminderUrgencyConfig").Get<ReminderUrgencyConfig>() ?? new ReminderUrgencyConfig();
return reminderUrgencyConfig;
}
public MailConfig GetMailConfig()
{
MailConfig mailConfig = _config.GetSection("MailConfig").Get<MailConfig>() ?? new MailConfig();
return mailConfig;
}
public string GetLogoUrl()
{
var logoUrl = _config["LUBELOGGER_LOGO_URL"];
if (string.IsNullOrWhiteSpace(logoUrl))
{
logoUrl = "/defaults/lubelogger_logo.png";
}
var logoUrl = CheckString("LUBELOGGER_LOGO_URL", "/defaults/lubelogger_logo.png");
return logoUrl;
}
public string GetAllowedFileUploadExtensions()
{
var allowedFileExtensions = _config["LUBELOGGER_ALLOWED_FILE_EXTENSIONS"];
if (string.IsNullOrWhiteSpace(allowedFileExtensions)){
return StaticHelper.DefaultAllowedFileExtensions;
}
var allowedFileExtensions = CheckString("LUBELOGGER_ALLOWED_FILE_EXTENSIONS", StaticHelper.DefaultAllowedFileExtensions);
return allowedFileExtensions;
}
public bool AuthenticateRootUser(string username, string password)
{
var rootUsername = _config[nameof(UserConfig.UserNameHash)] ?? string.Empty;
var rootPassword = _config[nameof(UserConfig.UserPasswordHash)] ?? string.Empty;
var rootUsername = CheckString(nameof(UserConfig.UserNameHash));
var rootPassword = CheckString(nameof(UserConfig.UserPasswordHash));
if (string.IsNullOrWhiteSpace(rootUsername) || string.IsNullOrWhiteSpace(rootPassword))
{
return false;
}
return username == rootUsername && password == rootPassword;
}
public bool AuthenticateRootUserOIDC(string email)
{
var rootEmail = CheckString(nameof(UserConfig.DefaultReminderEmail));
var rootUserOIDC = CheckBool(CheckString(nameof(UserConfig.EnableRootUserOIDC)));
if (!rootUserOIDC || string.IsNullOrWhiteSpace(rootEmail))
{
return false;
}
return email == rootEmail;
}
public string GetServerLanguage()
{
var serverLanguage = _config[nameof(UserConfig.UserLanguage)] ?? "en_US";
var serverLanguage = CheckString(nameof(UserConfig.UserLanguage), "en_US");
return serverLanguage;
}
public bool GetServerDisabledRegistration()
{
var registrationDisabled = CheckBool(CheckString(nameof(UserConfig.DisableRegistration)));
return registrationDisabled;
}
public string GetServerPostgresConnection()
{
if (!string.IsNullOrWhiteSpace(_config["POSTGRES_CONNECTION"]))
{
return _config["POSTGRES_CONNECTION"];
} else
{
return string.Empty;
}
var postgresConnection = CheckString("POSTGRES_CONNECTION");
return postgresConnection;
}
public bool GetServerEnableShopSupplies()
{
return bool.Parse(_config[nameof(UserConfig.EnableShopSupplies)] ?? "false");
return CheckBool(CheckString(nameof(UserConfig.EnableShopSupplies)));
}
public bool SaveUserConfig(ClaimsPrincipal user, UserConfig configData)
{
@@ -124,18 +136,19 @@ namespace CarCareTracker.Helper
if (!File.Exists(StaticHelper.UserConfigPath))
{
//if file doesn't exist it might be because it's running on a mounted volume in docker.
File.WriteAllText(StaticHelper.UserConfigPath, System.Text.Json.JsonSerializer.Serialize(new UserConfig()));
File.WriteAllText(StaticHelper.UserConfigPath, JsonSerializer.Serialize(new UserConfig()));
}
var configFileContents = File.ReadAllText(StaticHelper.UserConfigPath);
configData.EnableAuth = bool.Parse(_config[nameof(UserConfig.EnableAuth)] ?? "false");
configData.UserNameHash = _config[nameof(UserConfig.UserNameHash)] ?? string.Empty;
configData.UserPasswordHash = _config[nameof(UserConfig.UserPasswordHash)] ?? string.Empty;
File.WriteAllText(StaticHelper.UserConfigPath, System.Text.Json.JsonSerializer.Serialize(configData));
File.WriteAllText(StaticHelper.UserConfigPath, JsonSerializer.Serialize(configData));
_cache.Set<UserConfig>($"userConfig_{userId}", configData);
return true;
}
catch (Exception ex)
{
_logger.LogWarning(ex.Message);
return false;
}
} else
@@ -156,31 +169,72 @@ namespace CarCareTracker.Helper
var result = _userConfig.DeleteUserConfig(userId);
return result;
}
private bool CheckBool(string value, bool defaultValue = false)
{
try
{
if (string.IsNullOrWhiteSpace(value))
{
return defaultValue;
}
else if (bool.TryParse(value, out bool result))
{
return result;
}
else
{
return defaultValue;
}
} catch (Exception ex)
{
_logger.LogWarning($"ConfigHelper Warning: You might be missing keys in appsettings.json, Message: ${ex.Message}");
return defaultValue;
}
}
private string CheckString(string configName, string defaultValue = "")
{
try
{
var configValue = _config[configName] ?? defaultValue;
return configValue;
} catch(Exception ex)
{
_logger.LogWarning($"ConfigHelper Warning: You might be missing keys in appsettings.json, Message: ${ex.Message}");
return defaultValue;
}
}
public UserConfig GetUserConfig(ClaimsPrincipal user)
{
var serverConfig = new UserConfig
{
EnableCsvImports = bool.Parse(_config[nameof(UserConfig.EnableCsvImports)]),
UseDarkMode = bool.Parse(_config[nameof(UserConfig.UseDarkMode)]),
UseMPG = bool.Parse(_config[nameof(UserConfig.UseMPG)]),
UseDescending = bool.Parse(_config[nameof(UserConfig.UseDescending)]),
EnableAuth = bool.Parse(_config[nameof(UserConfig.EnableAuth)]),
HideZero = bool.Parse(_config[nameof(UserConfig.HideZero)]),
UseUKMPG = bool.Parse(_config[nameof(UserConfig.UseUKMPG)]),
UseMarkDownOnSavedNotes = bool.Parse(_config[nameof(UserConfig.UseMarkDownOnSavedNotes)]),
UseThreeDecimalGasCost = bool.Parse(_config[nameof(UserConfig.UseThreeDecimalGasCost)]),
EnableAutoReminderRefresh = bool.Parse(_config[nameof(UserConfig.EnableAutoReminderRefresh)]),
EnableAutoOdometerInsert = bool.Parse(_config[nameof(UserConfig.EnableAutoOdometerInsert)]),
PreferredGasMileageUnit = _config[nameof(UserConfig.PreferredGasMileageUnit)],
PreferredGasUnit = _config[nameof(UserConfig.PreferredGasUnit)],
UserLanguage = _config[nameof(UserConfig.UserLanguage)],
HideSoldVehicles = bool.Parse(_config[nameof(UserConfig.HideSoldVehicles)]),
EnableShopSupplies = bool.Parse(_config[nameof(UserConfig.EnableShopSupplies)]),
EnableExtraFieldColumns = bool.Parse(_config[nameof(UserConfig.EnableExtraFieldColumns)]),
VisibleTabs = _config.GetSection(nameof(UserConfig.VisibleTabs)).Get<List<ImportMode>>(),
EnableCsvImports = CheckBool(CheckString(nameof(UserConfig.EnableCsvImports)), true),
UseDarkMode = CheckBool(CheckString(nameof(UserConfig.UseDarkMode))),
UseSystemColorMode = CheckBool(CheckString(nameof(UserConfig.UseSystemColorMode))),
UseMPG = CheckBool(CheckString(nameof(UserConfig.UseMPG)), true),
UseDescending = CheckBool(CheckString(nameof(UserConfig.UseDescending))),
EnableAuth = CheckBool(CheckString(nameof(UserConfig.EnableAuth))),
EnableRootUserOIDC = CheckBool(CheckString(nameof(UserConfig.EnableRootUserOIDC))),
HideZero = CheckBool(CheckString(nameof(UserConfig.HideZero))),
AutomaticDecimalFormat = CheckBool(CheckString(nameof(UserConfig.AutomaticDecimalFormat))),
UseUKMPG = CheckBool(CheckString(nameof(UserConfig.UseUKMPG))),
UseMarkDownOnSavedNotes = CheckBool(CheckString(nameof(UserConfig.UseMarkDownOnSavedNotes))),
UseThreeDecimalGasCost = CheckBool(CheckString(nameof(UserConfig.UseThreeDecimalGasCost)), true),
UseThreeDecimalGasConsumption = CheckBool(CheckString(nameof(UserConfig.UseThreeDecimalGasConsumption)), true),
EnableAutoReminderRefresh = CheckBool(CheckString(nameof(UserConfig.EnableAutoReminderRefresh))),
EnableAutoOdometerInsert = CheckBool(CheckString(nameof(UserConfig.EnableAutoOdometerInsert))),
PreferredGasMileageUnit = CheckString(nameof(UserConfig.PreferredGasMileageUnit)),
PreferredGasUnit = CheckString(nameof(UserConfig.PreferredGasUnit)),
UserLanguage = CheckString(nameof(UserConfig.UserLanguage), "en_US"),
HideSoldVehicles = CheckBool(CheckString(nameof(UserConfig.HideSoldVehicles))),
EnableShopSupplies = CheckBool(CheckString(nameof(UserConfig.EnableShopSupplies))),
EnableExtraFieldColumns = CheckBool(CheckString(nameof(UserConfig.EnableExtraFieldColumns))),
VisibleTabs = _config.GetSection(nameof(UserConfig.VisibleTabs)).Get<List<ImportMode>>() ?? new UserConfig().VisibleTabs,
TabOrder = _config.GetSection(nameof(UserConfig.TabOrder)).Get<List<ImportMode>>() ?? new UserConfig().TabOrder,
UserColumnPreferences = _config.GetSection(nameof(UserConfig.UserColumnPreferences)).Get<List<UserColumnPreference>>() ?? new List<UserColumnPreference>(),
ReminderUrgencyConfig = _config.GetSection(nameof(UserConfig.ReminderUrgencyConfig)).Get<ReminderUrgencyConfig>() ?? new ReminderUrgencyConfig(),
DefaultTab = (ImportMode)int.Parse(_config[nameof(UserConfig.DefaultTab)])
DefaultTab = (ImportMode)int.Parse(CheckString(nameof(UserConfig.DefaultTab), "8")),
DefaultReminderEmail = CheckString(nameof(UserConfig.DefaultReminderEmail)),
DisableRegistration = CheckBool(CheckString(nameof(UserConfig.DisableRegistration)))
};
int userId = 0;
if (user != null)

View File

@@ -13,6 +13,13 @@ namespace CarCareTracker.Helper
bool RestoreBackup(string fileName, bool clearExisting = false);
string MakeAttachmentsExport(List<GenericReportModel> exportData);
List<string> GetLanguages();
int ClearTempFolder();
int ClearUnlinkedThumbnails(List<string> linkedImages);
int ClearUnlinkedDocuments(List<string> linkedDocuments);
string GetWidgets();
bool WidgetsExist();
bool SaveWidgets(string widgetsData);
bool DeleteWidgets();
}
public class FileHelper : IFileHelper
{
@@ -97,6 +104,7 @@ namespace CarCareTracker.Helper
var documentPath = Path.Combine(tempPath, "documents");
var translationPath = Path.Combine(tempPath, "translations");
var dataPath = Path.Combine(tempPath, StaticHelper.DbName);
var widgetPath = Path.Combine(tempPath, StaticHelper.AdditionalWidgetsPath);
var configPath = Path.Combine(tempPath, StaticHelper.UserConfigPath);
if (Directory.Exists(imagePath))
{
@@ -171,6 +179,10 @@ namespace CarCareTracker.Helper
//data path will always exist as it is created on startup if not.
File.Move(dataPath, StaticHelper.DbName, true);
}
if (File.Exists(widgetPath))
{
File.Move(widgetPath, StaticHelper.AdditionalWidgetsPath, true);
}
if (File.Exists(configPath))
{
//check if config folder exists.
@@ -220,6 +232,7 @@ namespace CarCareTracker.Helper
var documentPath = Path.Combine(_webEnv.WebRootPath, "documents");
var translationPath = Path.Combine(_webEnv.WebRootPath, "translations");
var dataPath = StaticHelper.DbName;
var widgetPath = StaticHelper.AdditionalWidgetsPath;
var configPath = StaticHelper.UserConfigPath;
if (!Directory.Exists(tempPath))
Directory.CreateDirectory(tempPath);
@@ -259,6 +272,12 @@ namespace CarCareTracker.Helper
Directory.CreateDirectory(newPath);
File.Copy(dataPath, $"{newPath}/{Path.GetFileName(dataPath)}");
}
if (File.Exists(widgetPath))
{
var newPath = Path.Combine(tempPath, "data");
Directory.CreateDirectory(newPath);
File.Copy(widgetPath, $"{newPath}/{Path.GetFileName(widgetPath)}");
}
if (File.Exists(configPath))
{
var newPath = Path.Combine(tempPath, "config");
@@ -314,5 +333,116 @@ namespace CarCareTracker.Helper
return false;
}
}
public int ClearTempFolder()
{
int filesDeleted = 0;
var tempPath = GetFullFilePath("temp", false);
if (Directory.Exists(tempPath))
{
//delete files
var files = Directory.GetFiles(tempPath);
foreach (var file in files)
{
File.Delete(file);
filesDeleted++;
}
//delete folders
var folders = Directory.GetDirectories(tempPath);
foreach(var folder in folders)
{
Directory.Delete(folder, true);
filesDeleted++;
}
}
return filesDeleted;
}
public int ClearUnlinkedThumbnails(List<string> linkedImages)
{
int filesDeleted = 0;
var imagePath = GetFullFilePath("images", false);
if (Directory.Exists(imagePath))
{
var files = Directory.GetFiles(imagePath);
foreach(var file in files)
{
if (!linkedImages.Contains(Path.GetFileName(file)))
{
File.Delete(file);
filesDeleted++;
}
}
}
return filesDeleted;
}
public int ClearUnlinkedDocuments(List<string> linkedDocuments)
{
int filesDeleted = 0;
var documentPath = GetFullFilePath("documents", false);
if (Directory.Exists(documentPath))
{
var files = Directory.GetFiles(documentPath);
foreach (var file in files)
{
if (!linkedDocuments.Contains(Path.GetFileName(file)))
{
File.Delete(file);
filesDeleted++;
}
}
}
return filesDeleted;
}
public string GetWidgets()
{
if (File.Exists(StaticHelper.AdditionalWidgetsPath))
{
try
{
//read file
var widgets = File.ReadAllText(StaticHelper.AdditionalWidgetsPath);
return widgets;
}
catch (Exception ex)
{
_logger.LogError(ex.Message);
return string.Empty;
}
}
return string.Empty;
}
public bool WidgetsExist()
{
return File.Exists(StaticHelper.AdditionalWidgetsPath);
}
public bool SaveWidgets(string widgetsData)
{
try
{
//Delete Widgets if exists
DeleteWidgets();
File.WriteAllText(StaticHelper.AdditionalWidgetsPath, widgetsData);
return true;
} catch (Exception ex)
{
_logger.LogError(ex.Message);
return false;
}
}
public bool DeleteWidgets()
{
try
{
if (File.Exists(StaticHelper.AdditionalWidgetsPath))
{
File.Delete(StaticHelper.AdditionalWidgetsPath);
}
return true;
}
catch (Exception ex)
{
_logger.LogError(ex.Message);
return false;
}
}
}
}

View File

@@ -57,6 +57,10 @@ namespace CarCareTracker.Helper
if (i > 0)
{
var deltaMileage = currentObject.Mileage - previousMileage;
if (deltaMileage < 0)
{
deltaMileage = 0;
}
var gasRecordViewModel = new GasRecordViewModel()
{
Id = currentObject.Id,

View File

@@ -1,6 +1,7 @@
using CarCareTracker.Models;
using System.Net.Mail;
using System.Net;
using MimeKit;
using MailKit.Net.Smtp;
using MailKit.Security;
namespace CarCareTracker.Helper
{
@@ -15,144 +16,158 @@ namespace CarCareTracker.Helper
{
private readonly MailConfig mailConfig;
private readonly IFileHelper _fileHelper;
private readonly ILogger<MailHelper> _logger;
public MailHelper(
IConfiguration config,
IFileHelper fileHelper
IConfigHelper config,
IFileHelper fileHelper,
ILogger<MailHelper> logger
) {
//load mailConfig from Configuration
mailConfig = config.GetSection("MailConfig").Get<MailConfig>();
mailConfig = config.GetMailConfig();
_fileHelper = fileHelper;
_logger = logger;
}
public OperationResponse NotifyUserForRegistration(string emailAddress, string token)
{
if (string.IsNullOrWhiteSpace(mailConfig.EmailServer))
{
return new OperationResponse { Success = false, Message = "SMTP Server Not Setup" };
return OperationResponse.Failed("SMTP Server Not Setup");
}
if (string.IsNullOrWhiteSpace(emailAddress) || string.IsNullOrWhiteSpace(token)) {
return new OperationResponse { Success = false, Message = "Email Address or Token is invalid" };
return OperationResponse.Failed("Email Address or Token is invalid");
}
string emailSubject = "Your Registration Token for LubeLogger";
string emailBody = $"A token has been generated on your behalf, please complete your registration for LubeLogger using the token: {token}";
var result = SendEmail(emailAddress, emailSubject, emailBody);
var result = SendEmail(new List<string> { emailAddress }, emailSubject, emailBody);
if (result)
{
return new OperationResponse { Success = true, Message = "Email Sent!" };
return OperationResponse.Succeed("Email Sent!");
} else
{
return new OperationResponse { Success = false, Message = StaticHelper.GenericErrorMessage };
return OperationResponse.Failed();
}
}
public OperationResponse NotifyUserForPasswordReset(string emailAddress, string token)
{
if (string.IsNullOrWhiteSpace(mailConfig.EmailServer))
{
return new OperationResponse { Success = false, Message = "SMTP Server Not Setup" };
return OperationResponse.Failed("SMTP Server Not Setup");
}
if (string.IsNullOrWhiteSpace(emailAddress) || string.IsNullOrWhiteSpace(token))
{
return new OperationResponse { Success = false, Message = "Email Address or Token is invalid" };
return OperationResponse.Failed("Email Address or Token is invalid");
}
string emailSubject = "Your Password Reset Token for LubeLogger";
string emailBody = $"A token has been generated on your behalf, please reset your password for LubeLogger using the token: {token}";
var result = SendEmail(emailAddress, emailSubject, emailBody);
var result = SendEmail(new List<string> { emailAddress }, emailSubject, emailBody);
if (result)
{
return new OperationResponse { Success = true, Message = "Email Sent!" };
return OperationResponse.Succeed("Email Sent!");
}
else
{
return new OperationResponse { Success = false, Message = StaticHelper.GenericErrorMessage };
return OperationResponse.Failed();
}
}
public OperationResponse NotifyUserForAccountUpdate(string emailAddress, string token)
{
if (string.IsNullOrWhiteSpace(mailConfig.EmailServer))
{
return new OperationResponse { Success = false, Message = "SMTP Server Not Setup" };
return OperationResponse.Failed("SMTP Server Not Setup");
}
if (string.IsNullOrWhiteSpace(emailAddress) || string.IsNullOrWhiteSpace(token))
{
return new OperationResponse { Success = false, Message = "Email Address or Token is invalid" };
return OperationResponse.Failed("Email Address or Token is invalid");
}
string emailSubject = "Your User Account Update Token for LubeLogger";
string emailBody = $"A token has been generated on your behalf, please update your account for LubeLogger using the token: {token}";
var result = SendEmail(emailAddress, emailSubject, emailBody);
var result = SendEmail(new List<string> { emailAddress}, emailSubject, emailBody);
if (result)
{
return new OperationResponse { Success = true, Message = "Email Sent!" };
return OperationResponse.Succeed("Email Sent!");
}
else
{
return new OperationResponse { Success = false, Message = StaticHelper.GenericErrorMessage };
return OperationResponse.Failed();
}
}
public OperationResponse NotifyUserForReminders(Vehicle vehicle, List<string> emailAddresses, List<ReminderRecordViewModel> reminders)
{
if (string.IsNullOrWhiteSpace(mailConfig.EmailServer))
{
return new OperationResponse { Success = false, Message = "SMTP Server Not Setup" };
return OperationResponse.Failed("SMTP Server Not Setup");
}
if (!emailAddresses.Any())
{
return new OperationResponse { Success = false, Message = "No recipients could be found" };
return OperationResponse.Failed("No recipients could be found");
}
if (!reminders.Any())
{
return new OperationResponse { Success = false, Message = "No reminders could be found" };
return OperationResponse.Failed("No reminders could be found");
}
//get email template, this file has to exist since it's a static file.
var emailTemplatePath = _fileHelper.GetFullFilePath(StaticHelper.ReminderEmailTemplate);
string emailSubject = $"Vehicle Reminders From LubeLogger - {DateTime.Now.ToShortDateString()}";
//construct html table.
string emailBody = File.ReadAllText(emailTemplatePath);
emailBody = emailBody.Replace("{VehicleInformation}", $"{vehicle.Year} {vehicle.Make} {vehicle.Model} #{vehicle.LicensePlate}");
emailBody = emailBody.Replace("{VehicleInformation}", $"{vehicle.Year} {vehicle.Make} {vehicle.Model} #{StaticHelper.GetVehicleIdentifier(vehicle)}");
string tableBody = "";
foreach(ReminderRecordViewModel reminder in reminders)
{
var dueOn = reminder.Metric == ReminderMetric.Both ? $"{reminder.Date} or {reminder.Mileage}" : reminder.Metric == ReminderMetric.Date ? $"{reminder.Date.ToShortDateString()}" : $"{reminder.Mileage}";
var dueOn = reminder.Metric == ReminderMetric.Both ? $"{reminder.Date.ToShortDateString()} or {reminder.Mileage}" : reminder.Metric == ReminderMetric.Date ? $"{reminder.Date.ToShortDateString()}" : $"{reminder.Mileage}";
tableBody += $"<tr class='{reminder.Urgency}'><td>{StaticHelper.GetTitleCaseReminderUrgency(reminder.Urgency)}</td><td>{reminder.Description}</td><td>{dueOn}</td></tr>";
}
emailBody = emailBody.Replace("{TableBody}", tableBody);
try
{
foreach (string emailAddress in emailAddresses)
var result = SendEmail(emailAddresses, emailSubject, emailBody);
if (result)
{
SendEmail(emailAddress, emailSubject, emailBody, true, true);
return OperationResponse.Succeed("Email Sent!");
} else
{
return OperationResponse.Failed();
}
return new OperationResponse { Success = true, Message = "Email Sent!" };
} catch (Exception ex)
{
return new OperationResponse { Success = false, Message = ex.Message };
return OperationResponse.Failed(ex.Message);
}
}
private bool SendEmail(string emailTo, string emailSubject, string emailBody, bool isBodyHtml = false, bool useAsync = false) {
string to = emailTo;
private bool SendEmail(List<string> emailTo, string emailSubject, string emailBody) {
string from = mailConfig.EmailFrom;
var server = mailConfig.EmailServer;
MailMessage message = new MailMessage(from, to);
message.Subject = emailSubject;
message.Body = emailBody;
message.IsBodyHtml = isBodyHtml;
SmtpClient client = new SmtpClient(server);
client.EnableSsl = mailConfig.UseSSL;
client.Port = mailConfig.Port;
client.Credentials = new NetworkCredential(mailConfig.Username, mailConfig.Password);
try
var message = new MimeMessage();
message.From.Add(new MailboxAddress(from, from));
foreach(string emailRecipient in emailTo)
{
if (useAsync)
{
client.SendMailAsync(message, new CancellationToken());
message.To.Add(new MailboxAddress(emailRecipient, emailRecipient));
}
message.Subject = emailSubject;
var builder = new BodyBuilder();
builder.HtmlBody = emailBody;
message.Body = builder.ToMessageBody();
using (var client = new SmtpClient())
{
client.Connect(server, mailConfig.Port, SecureSocketOptions.Auto);
//perform authentication if either username or password is provided.
//do not perform authentication if neither are provided.
if (!string.IsNullOrWhiteSpace(mailConfig.Username) || !string.IsNullOrWhiteSpace(mailConfig.Password)) {
client.Authenticate(mailConfig.Username, mailConfig.Password);
}
else
try
{
client.Send(message);
client.Disconnect(true);
return true;
} catch (Exception ex)
{
_logger.LogError(ex.Message);
return false;
}
return true;
}
catch (Exception ex)
{
return false;
}
}
}

View File

@@ -66,6 +66,10 @@ namespace CarCareTracker.Helper
var reminderUrgencyConfig = _config.GetReminderUrgencyConfig();
foreach (var reminder in reminders)
{
if (reminder.UseCustomThresholds)
{
reminderUrgencyConfig = reminder.CustomThresholds;
}
var reminderViewModel = new ReminderRecordViewModel()
{
Id = reminder.Id,
@@ -75,6 +79,7 @@ namespace CarCareTracker.Helper
Description = reminder.Description,
Notes = reminder.Notes,
Metric = reminder.Metric,
UserMetric = reminder.Metric,
IsRecurring = reminder.IsRecurring,
Tags = reminder.Tags
};

View File

@@ -5,93 +5,166 @@ namespace CarCareTracker.Helper
{
public interface IReportHelper
{
IEnumerable<CostForVehicleByMonth> GetOdometerRecordSum(List<OdometerRecord> odometerRecords, int year = 0);
IEnumerable<CostForVehicleByMonth> GetServiceRecordSum(List<ServiceRecord> serviceRecords, int year = 0);
IEnumerable<CostForVehicleByMonth> GetRepairRecordSum(List<CollisionRecord> repairRecords, int year = 0);
IEnumerable<CostForVehicleByMonth> GetUpgradeRecordSum(List<UpgradeRecord> upgradeRecords, int year = 0);
IEnumerable<CostForVehicleByMonth> GetGasRecordSum(List<GasRecord> gasRecords, int year = 0);
IEnumerable<CostForVehicleByMonth> GetTaxRecordSum(List<TaxRecord> taxRecords, int year = 0);
IEnumerable<CostForVehicleByMonth> GetOdometerRecordSum(List<OdometerRecord> odometerRecords, int year = 0, bool sortIntoYear = false);
IEnumerable<CostForVehicleByMonth> GetServiceRecordSum(List<ServiceRecord> serviceRecords, int year = 0, bool sortIntoYear = false);
IEnumerable<CostForVehicleByMonth> GetRepairRecordSum(List<CollisionRecord> repairRecords, int year = 0, bool sortIntoYear = false);
IEnumerable<CostForVehicleByMonth> GetUpgradeRecordSum(List<UpgradeRecord> upgradeRecords, int year = 0, bool sortIntoYear = false);
IEnumerable<CostForVehicleByMonth> GetGasRecordSum(List<GasRecord> gasRecords, int year = 0, bool sortIntoYear = false);
IEnumerable<CostForVehicleByMonth> GetTaxRecordSum(List<TaxRecord> taxRecords, int year = 0, bool sortIntoYear = false);
}
public class ReportHelper: IReportHelper
{
public IEnumerable<CostForVehicleByMonth> GetOdometerRecordSum(List<OdometerRecord> odometerRecords, int year = 0)
public IEnumerable<CostForVehicleByMonth> GetOdometerRecordSum(List<OdometerRecord> odometerRecords, int year = 0, bool sortIntoYear = false)
{
if (year != default)
{
odometerRecords.RemoveAll(x => x.Date.Year != year);
}
return odometerRecords.GroupBy(x => x.Date.Month).OrderBy(x => x.Key).Select(x => new CostForVehicleByMonth
if (sortIntoYear)
{
MonthId = x.Key,
MonthName = CultureInfo.CurrentCulture.DateTimeFormat.GetMonthName(x.Key),
Cost = 0,
DistanceTraveled = x.Sum(y=>y.DistanceTraveled)
});
return odometerRecords.GroupBy(x => new { x.Date.Month, x.Date.Year }).OrderBy(x => x.Key.Month).Select(x => new CostForVehicleByMonth
{
MonthId = x.Key.Month,
MonthName = CultureInfo.CurrentCulture.DateTimeFormat.GetMonthName(x.Key.Month),
Year = x.Key.Year,
Cost = 0,
DistanceTraveled = x.Sum(y => y.DistanceTraveled)
});
} else
{
return odometerRecords.GroupBy(x => x.Date.Month).OrderBy(x => x.Key).Select(x => new CostForVehicleByMonth
{
MonthId = x.Key,
MonthName = CultureInfo.CurrentCulture.DateTimeFormat.GetMonthName(x.Key),
Cost = 0,
DistanceTraveled = x.Sum(y => y.DistanceTraveled)
});
}
}
public IEnumerable<CostForVehicleByMonth> GetServiceRecordSum(List<ServiceRecord> serviceRecords, int year = 0)
public IEnumerable<CostForVehicleByMonth> GetServiceRecordSum(List<ServiceRecord> serviceRecords, int year = 0, bool sortIntoYear = false)
{
if (year != default)
{
serviceRecords.RemoveAll(x => x.Date.Year != year);
}
return serviceRecords.GroupBy(x => x.Date.Month).OrderBy(x => x.Key).Select(x => new CostForVehicleByMonth
if (sortIntoYear)
{
MonthId = x.Key,
MonthName = CultureInfo.CurrentCulture.DateTimeFormat.GetMonthName(x.Key),
Cost = x.Sum(y => y.Cost)
});
return serviceRecords.GroupBy(x => new { x.Date.Month, x.Date.Year }).OrderBy(x => x.Key.Month).Select(x => new CostForVehicleByMonth
{
MonthId = x.Key.Month,
MonthName = CultureInfo.CurrentCulture.DateTimeFormat.GetMonthName(x.Key.Month),
Year = x.Key.Year,
Cost = x.Sum(y => y.Cost)
});
} else
{
return serviceRecords.GroupBy(x => x.Date.Month).OrderBy(x => x.Key).Select(x => new CostForVehicleByMonth
{
MonthId = x.Key,
MonthName = CultureInfo.CurrentCulture.DateTimeFormat.GetMonthName(x.Key),
Cost = x.Sum(y => y.Cost)
});
}
}
public IEnumerable<CostForVehicleByMonth> GetRepairRecordSum(List<CollisionRecord> repairRecords, int year = 0)
public IEnumerable<CostForVehicleByMonth> GetRepairRecordSum(List<CollisionRecord> repairRecords, int year = 0, bool sortIntoYear = false)
{
if (year != default)
{
repairRecords.RemoveAll(x => x.Date.Year != year);
}
return repairRecords.GroupBy(x => x.Date.Month).OrderBy(x => x.Key).Select(x => new CostForVehicleByMonth
if (sortIntoYear)
{
MonthId = x.Key,
MonthName = CultureInfo.CurrentCulture.DateTimeFormat.GetMonthName(x.Key),
Cost = x.Sum(y => y.Cost)
});
return repairRecords.GroupBy(x => new { x.Date.Month, x.Date.Year }).OrderBy(x => x.Key.Month).Select(x => new CostForVehicleByMonth
{
MonthId = x.Key.Month,
MonthName = CultureInfo.CurrentCulture.DateTimeFormat.GetMonthName(x.Key.Month),
Year = x.Key.Year,
Cost = x.Sum(y => y.Cost)
});
} else
{
return repairRecords.GroupBy(x => x.Date.Month).OrderBy(x => x.Key).Select(x => new CostForVehicleByMonth
{
MonthId = x.Key,
MonthName = CultureInfo.CurrentCulture.DateTimeFormat.GetMonthName(x.Key),
Cost = x.Sum(y => y.Cost)
});
}
}
public IEnumerable<CostForVehicleByMonth> GetUpgradeRecordSum(List<UpgradeRecord> upgradeRecords, int year = 0)
public IEnumerable<CostForVehicleByMonth> GetUpgradeRecordSum(List<UpgradeRecord> upgradeRecords, int year = 0, bool sortIntoYear = false)
{
if (year != default)
{
upgradeRecords.RemoveAll(x => x.Date.Year != year);
}
return upgradeRecords.GroupBy(x => x.Date.Month).OrderBy(x => x.Key).Select(x => new CostForVehicleByMonth
if (sortIntoYear)
{
MonthId = x.Key,
MonthName = CultureInfo.CurrentCulture.DateTimeFormat.GetMonthName(x.Key),
Cost = x.Sum(y => y.Cost)
});
return upgradeRecords.GroupBy(x => new { x.Date.Month, x.Date.Year }).OrderBy(x => x.Key.Month).Select(x => new CostForVehicleByMonth
{
MonthId = x.Key.Month,
MonthName = CultureInfo.CurrentCulture.DateTimeFormat.GetMonthName(x.Key.Month),
Year = x.Key.Year,
Cost = x.Sum(y => y.Cost)
});
} else
{
return upgradeRecords.GroupBy(x => x.Date.Month).OrderBy(x => x.Key).Select(x => new CostForVehicleByMonth
{
MonthId = x.Key,
MonthName = CultureInfo.CurrentCulture.DateTimeFormat.GetMonthName(x.Key),
Cost = x.Sum(y => y.Cost)
});
}
}
public IEnumerable<CostForVehicleByMonth> GetGasRecordSum(List<GasRecord> gasRecords, int year = 0)
public IEnumerable<CostForVehicleByMonth> GetGasRecordSum(List<GasRecord> gasRecords, int year = 0, bool sortIntoYear = false)
{
if (year != default)
{
gasRecords.RemoveAll(x => x.Date.Year != year);
}
return gasRecords.GroupBy(x => x.Date.Month).OrderBy(x => x.Key).Select(x => new CostForVehicleByMonth
if (sortIntoYear)
{
MonthId = x.Key,
MonthName = CultureInfo.CurrentCulture.DateTimeFormat.GetMonthName(x.Key),
Cost = x.Sum(y => y.Cost)
});
return gasRecords.GroupBy(x => new { x.Date.Month, x.Date.Year }).OrderBy(x => x.Key.Month).Select(x => new CostForVehicleByMonth
{
MonthId = x.Key.Month,
MonthName = CultureInfo.CurrentCulture.DateTimeFormat.GetMonthName(x.Key.Month),
Year = x.Key.Year,
Cost = x.Sum(y => y.Cost)
});
} else
{
return gasRecords.GroupBy(x => x.Date.Month).OrderBy(x => x.Key).Select(x => new CostForVehicleByMonth
{
MonthId = x.Key,
MonthName = CultureInfo.CurrentCulture.DateTimeFormat.GetMonthName(x.Key),
Cost = x.Sum(y => y.Cost)
});
}
}
public IEnumerable<CostForVehicleByMonth> GetTaxRecordSum(List<TaxRecord> taxRecords, int year = 0)
public IEnumerable<CostForVehicleByMonth> GetTaxRecordSum(List<TaxRecord> taxRecords, int year = 0, bool sortIntoYear = false)
{
if (year != default)
{
taxRecords.RemoveAll(x => x.Date.Year != year);
}
return taxRecords.GroupBy(x => x.Date.Month).OrderBy(x => x.Key).Select(x => new CostForVehicleByMonth
if (sortIntoYear)
{
MonthId = x.Key,
MonthName = CultureInfo.CurrentCulture.DateTimeFormat.GetMonthName(x.Key),
Cost = x.Sum(y => y.Cost)
});
return taxRecords.GroupBy(x => new { x.Date.Month, x.Date.Year }).OrderBy(x => x.Key.Month).Select(x => new CostForVehicleByMonth
{
MonthId = x.Key.Month,
MonthName = CultureInfo.CurrentCulture.DateTimeFormat.GetMonthName(x.Key.Month),
Year = x.Key.Year,
Cost = x.Sum(y => y.Cost)
});
} else
{
return taxRecords.GroupBy(x => x.Date.Month).OrderBy(x => x.Key).Select(x => new CostForVehicleByMonth
{
MonthId = x.Key,
MonthName = CultureInfo.CurrentCulture.DateTimeFormat.GetMonthName(x.Key),
Cost = x.Sum(y => y.Cost)
});
}
}
}
}

View File

@@ -1,4 +1,5 @@
using CarCareTracker.Models;
using CsvHelper;
using System.Globalization;
namespace CarCareTracker.Helper
@@ -8,13 +9,17 @@ namespace CarCareTracker.Helper
/// </summary>
public static class StaticHelper
{
public static string VersionNumber = "1.2.9";
public static string DbName = "data/cartracker.db";
public static string UserConfigPath = "config/userConfig.json";
public static string GenericErrorMessage = "An error occurred, please try again later";
public static string ReminderEmailTemplate = "defaults/reminderemailtemplate.txt";
public static string DefaultAllowedFileExtensions = ".png,.jpg,.jpeg,.pdf,.xls,.xlsx,.docx";
public const string VersionNumber = "1.4.1";
public const string DbName = "data/cartracker.db";
public const string UserConfigPath = "config/userConfig.json";
public const string AdditionalWidgetsPath = "data/widgets.html";
public const string GenericErrorMessage = "An error occurred, please try again later";
public const string ReminderEmailTemplate = "defaults/reminderemailtemplate.txt";
public const string DefaultAllowedFileExtensions = ".png,.jpg,.jpeg,.pdf,.xls,.xlsx,.docx";
public const string SponsorsPath = "https://hargata.github.io/hargata/sponsors.json";
public const string TranslationPath = "https://hargata.github.io/lubelog_translations";
public const string TranslationDirectoryPath = $"{TranslationPath}/directory.json";
public const string ReportNote = "Report generated by LubeLogger, a Free and Open Source Vehicle Maintenance Tracker - LubeLogger.com";
public static string GetTitleCaseReminderUrgency(ReminderUrgency input)
{
switch (input)
@@ -29,6 +34,66 @@ namespace CarCareTracker.Helper
return input.ToString();
}
}
public static string GetTitleCaseReminderUrgency(string input)
{
switch (input)
{
case "NotUrgent":
return "Not Urgent";
case "VeryUrgent":
return "Very Urgent";
case "PastDue":
return "Past Due";
default:
return input;
}
}
public static string GetReminderUrgencyColor(ReminderUrgency input)
{
switch (input)
{
case ReminderUrgency.NotUrgent:
return "text-bg-success";
case ReminderUrgency.VeryUrgent:
return "text-bg-danger";
case ReminderUrgency.PastDue:
return "text-bg-secondary";
default:
return "text-bg-warning";
}
}
public static string GetPlanRecordColor(PlanPriority input)
{
switch (input)
{
case PlanPriority.Critical:
return "text-bg-danger";
case PlanPriority.Normal:
return "text-bg-primary";
case PlanPriority.Low:
return "text-bg-info";
default:
return "text-bg-primary";
}
}
public static string GetPlanRecordProgress(PlanProgress input)
{
switch (input)
{
case PlanProgress.Backlog:
return "Planned";
case PlanProgress.InProgress:
return "Doing";
case PlanProgress.Testing:
return "Testing";
case PlanProgress.Done:
return "Done";
default:
return input.ToString();
}
}
public static string TruncateStrings(string input, int maxLength = 25)
{
@@ -118,6 +183,10 @@ namespace CarCareTracker.Helper
new CostForVehicleByMonth { MonthId = 12, Cost = 0M}
};
}
public static List<string> GetBarChartColors()
{
return new List<string> { "#00876c", "#43956e", "#67a371", "#89b177", "#a9be80", "#c8cb8b", "#e6d79b", "#e4c281", "#e3ab6b", "#e2925b", "#e07952", "#db5d4f" };
}
public static ServiceRecord GenericToServiceRecord(GenericRecord input)
{
@@ -198,6 +267,8 @@ namespace CarCareTracker.Helper
recordExtraFields.Add(extraField);
}
}
//re-order extra fields
recordExtraFields = recordExtraFields.OrderBy(x => templateExtraFields.FindIndex(y => y.Name == x.Name)).ToList();
return recordExtraFields;
}
@@ -243,6 +314,10 @@ namespace CarCareTracker.Helper
}
var motd = config["LUBELOGGER_MOTD"] ?? "Not Configured";
Console.WriteLine($"Message Of The Day: {motd}");
if (string.IsNullOrWhiteSpace(CultureInfo.CurrentCulture.Name))
{
Console.WriteLine("No Locale or Culture Configured for LubeLogger, Check Environment Variables");
}
}
public static async void NotifyAsync(string webhookURL, int vehicleId, string username, string action)
{
@@ -259,5 +334,330 @@ namespace CarCareTracker.Helper
};
httpClient.PostAsJsonAsync(webhookURL, httpParams);
}
public static string GetImportModeIcon(ImportMode importMode)
{
switch (importMode)
{
case ImportMode.ServiceRecord:
return "bi-card-checklist";
case ImportMode.RepairRecord:
return "bi-exclamation-octagon";
case ImportMode.UpgradeRecord:
return "bi-wrench-adjustable";
case ImportMode.TaxRecord:
return "bi-currency-dollar";
case ImportMode.SupplyRecord:
return "bi-shop";
case ImportMode.PlanRecord:
return "bi-bar-chart-steps";
case ImportMode.OdometerRecord:
return "bi-speedometer";
case ImportMode.GasRecord:
return "bi-fuel-pump";
case ImportMode.NoteRecord:
return "bi-journal-bookmark";
case ImportMode.ReminderRecord:
return "bi-bell";
default:
return "bi-file-bar-graph";
}
}
public static string GetVehicleIdentifier(Vehicle vehicle)
{
if (vehicle.VehicleIdentifier == "LicensePlate")
{
return vehicle.LicensePlate;
} else
{
if (vehicle.ExtraFields.Any(x=>x.Name == vehicle.VehicleIdentifier))
{
return vehicle.ExtraFields?.FirstOrDefault(x=>x.Name == vehicle.VehicleIdentifier)?.Value;
} else
{
return "N/A";
}
}
}
public static string GetVehicleIdentifier(VehicleViewModel vehicle)
{
if (vehicle.VehicleIdentifier == "LicensePlate")
{
return vehicle.LicensePlate;
}
else
{
if (vehicle.ExtraFields.Any(x => x.Name == vehicle.VehicleIdentifier))
{
return vehicle.ExtraFields?.FirstOrDefault(x => x.Name == vehicle.VehicleIdentifier)?.Value;
}
else
{
return "N/A";
}
}
}
//Translations
public static string GetTranslationDownloadPath(string continent, string name)
{
if (string.IsNullOrWhiteSpace(continent) || string.IsNullOrWhiteSpace(name)){
return string.Empty;
}
else
{
switch (continent)
{
case "NorthAmerica":
continent = "North America";
break;
case "SouthAmerica":
continent = "South America";
break;
}
return $"{TranslationPath}/{continent}/{name}.json";
}
}
public static string GetTranslationName(string name)
{
if (string.IsNullOrWhiteSpace(name))
{
return string.Empty;
} else
{
try
{
string cleanedName = name.Contains("_") ? name.Replace("_", "-") : name;
string displayName = CultureInfo.GetCultureInfo(cleanedName).DisplayName;
if (string.IsNullOrWhiteSpace(displayName))
{
return name;
}
else
{
return displayName;
}
} catch (Exception ex)
{
return name;
}
}
}
//CSV Write Methods
public static void WriteGenericRecordExportModel(CsvWriter _csv, IEnumerable<GenericRecordExportModel> genericRecords)
{
var extraHeaders = genericRecords.SelectMany(x => x.ExtraFields).Select(y => y.Name).Distinct();
//write headers
_csv.WriteField(nameof(GenericRecordExportModel.Date));
_csv.WriteField(nameof(GenericRecordExportModel.Description));
_csv.WriteField(nameof(GenericRecordExportModel.Cost));
_csv.WriteField(nameof(GenericRecordExportModel.Notes));
_csv.WriteField(nameof(GenericRecordExportModel.Odometer));
_csv.WriteField(nameof(GenericRecordExportModel.Tags));
foreach (string extraHeader in extraHeaders)
{
_csv.WriteField($"extrafield_{extraHeader}");
}
_csv.NextRecord();
foreach (GenericRecordExportModel genericRecord in genericRecords)
{
_csv.WriteField(genericRecord.Date);
_csv.WriteField(genericRecord.Description);
_csv.WriteField(genericRecord.Cost);
_csv.WriteField(genericRecord.Notes);
_csv.WriteField(genericRecord.Odometer);
_csv.WriteField(genericRecord.Tags);
foreach (string extraHeader in extraHeaders)
{
var extraField = genericRecord.ExtraFields.Where(x => x.Name == extraHeader).FirstOrDefault();
_csv.WriteField(extraField != null ? extraField.Value : string.Empty);
}
_csv.NextRecord();
}
}
public static void WriteOdometerRecordExportModel(CsvWriter _csv, IEnumerable<OdometerRecordExportModel> genericRecords)
{
var extraHeaders = genericRecords.SelectMany(x => x.ExtraFields).Select(y => y.Name).Distinct();
//write headers
_csv.WriteField(nameof(OdometerRecordExportModel.Date));
_csv.WriteField(nameof(OdometerRecordExportModel.InitialOdometer));
_csv.WriteField(nameof(OdometerRecordExportModel.Odometer));
_csv.WriteField(nameof(OdometerRecordExportModel.Notes));
_csv.WriteField(nameof(OdometerRecordExportModel.Tags));
foreach (string extraHeader in extraHeaders)
{
_csv.WriteField($"extrafield_{extraHeader}");
}
_csv.NextRecord();
foreach (OdometerRecordExportModel genericRecord in genericRecords)
{
_csv.WriteField(genericRecord.Date);
_csv.WriteField(genericRecord.InitialOdometer);
_csv.WriteField(genericRecord.Odometer);
_csv.WriteField(genericRecord.Notes);
_csv.WriteField(genericRecord.Tags);
foreach (string extraHeader in extraHeaders)
{
var extraField = genericRecord.ExtraFields.Where(x => x.Name == extraHeader).FirstOrDefault();
_csv.WriteField(extraField != null ? extraField.Value : string.Empty);
}
_csv.NextRecord();
}
}
public static void WriteTaxRecordExportModel(CsvWriter _csv, IEnumerable<TaxRecordExportModel> genericRecords)
{
var extraHeaders = genericRecords.SelectMany(x => x.ExtraFields).Select(y => y.Name).Distinct();
//write headers
_csv.WriteField(nameof(TaxRecordExportModel.Date));
_csv.WriteField(nameof(TaxRecordExportModel.Description));
_csv.WriteField(nameof(TaxRecordExportModel.Cost));
_csv.WriteField(nameof(TaxRecordExportModel.Notes));
_csv.WriteField(nameof(TaxRecordExportModel.Tags));
foreach (string extraHeader in extraHeaders)
{
_csv.WriteField($"extrafield_{extraHeader}");
}
_csv.NextRecord();
foreach (TaxRecordExportModel genericRecord in genericRecords)
{
_csv.WriteField(genericRecord.Date);
_csv.WriteField(genericRecord.Description);
_csv.WriteField(genericRecord.Cost);
_csv.WriteField(genericRecord.Notes);
_csv.WriteField(genericRecord.Tags);
foreach (string extraHeader in extraHeaders)
{
var extraField = genericRecord.ExtraFields.Where(x => x.Name == extraHeader).FirstOrDefault();
_csv.WriteField(extraField != null ? extraField.Value : string.Empty);
}
_csv.NextRecord();
}
}
public static void WriteSupplyRecordExportModel(CsvWriter _csv, IEnumerable<SupplyRecordExportModel> genericRecords)
{
var extraHeaders = genericRecords.SelectMany(x => x.ExtraFields).Select(y => y.Name).Distinct();
//write headers
_csv.WriteField(nameof(SupplyRecordExportModel.Date));
_csv.WriteField(nameof(SupplyRecordExportModel.PartNumber));
_csv.WriteField(nameof(SupplyRecordExportModel.PartSupplier));
_csv.WriteField(nameof(SupplyRecordExportModel.PartQuantity));
_csv.WriteField(nameof(SupplyRecordExportModel.Description));
_csv.WriteField(nameof(SupplyRecordExportModel.Notes));
_csv.WriteField(nameof(SupplyRecordExportModel.Cost));
_csv.WriteField(nameof(SupplyRecordExportModel.Tags));
foreach (string extraHeader in extraHeaders)
{
_csv.WriteField($"extrafield_{extraHeader}");
}
_csv.NextRecord();
foreach (SupplyRecordExportModel genericRecord in genericRecords)
{
_csv.WriteField(genericRecord.Date);
_csv.WriteField(genericRecord.PartNumber);
_csv.WriteField(genericRecord.PartSupplier);
_csv.WriteField(genericRecord.PartQuantity);
_csv.WriteField(genericRecord.Description);
_csv.WriteField(genericRecord.Notes);
_csv.WriteField(genericRecord.Cost);
_csv.WriteField(genericRecord.Tags);
foreach (string extraHeader in extraHeaders)
{
var extraField = genericRecord.ExtraFields.Where(x => x.Name == extraHeader).FirstOrDefault();
_csv.WriteField(extraField != null ? extraField.Value : string.Empty);
}
_csv.NextRecord();
}
}
public static void WritePlanRecordExportModel(CsvWriter _csv, IEnumerable<PlanRecordExportModel> genericRecords)
{
var extraHeaders = genericRecords.SelectMany(x => x.ExtraFields).Select(y => y.Name).Distinct();
//write headers
_csv.WriteField(nameof(PlanRecordExportModel.DateCreated));
_csv.WriteField(nameof(PlanRecordExportModel.DateModified));
_csv.WriteField(nameof(PlanRecordExportModel.Description));
_csv.WriteField(nameof(PlanRecordExportModel.Notes));
_csv.WriteField(nameof(PlanRecordExportModel.Type));
_csv.WriteField(nameof(PlanRecordExportModel.Priority));
_csv.WriteField(nameof(PlanRecordExportModel.Progress));
_csv.WriteField(nameof(PlanRecordExportModel.Cost));
foreach (string extraHeader in extraHeaders)
{
_csv.WriteField($"extrafield_{extraHeader}");
}
_csv.NextRecord();
foreach (PlanRecordExportModel genericRecord in genericRecords)
{
_csv.WriteField(genericRecord.DateCreated);
_csv.WriteField(genericRecord.DateModified);
_csv.WriteField(genericRecord.Description);
_csv.WriteField(genericRecord.Notes);
_csv.WriteField(genericRecord.Type);
_csv.WriteField(genericRecord.Priority);
_csv.WriteField(genericRecord.Progress);
_csv.WriteField(genericRecord.Cost);
foreach (string extraHeader in extraHeaders)
{
var extraField = genericRecord.ExtraFields.Where(x => x.Name == extraHeader).FirstOrDefault();
_csv.WriteField(extraField != null ? extraField.Value : string.Empty);
}
_csv.NextRecord();
}
}
public static string HideZeroCost(string input, bool hideZero, string decorations = "")
{
if (input == 0M.ToString("C2") && hideZero)
{
return "---";
} else
{
return string.IsNullOrWhiteSpace(decorations) ? input : $"{input}{decorations}";
}
}
public static string HideZeroCost(decimal input, bool hideZero, string decorations = "")
{
if (input == default && hideZero)
{
return "---";
}
else
{
return string.IsNullOrWhiteSpace(decorations) ? input.ToString("C2") : $"{input.ToString("C2")}{decorations}";
}
}
public static void WriteGasRecordExportModel(CsvWriter _csv, IEnumerable<GasRecordExportModel> genericRecords)
{
var extraHeaders = genericRecords.SelectMany(x => x.ExtraFields).Select(y => y.Name).Distinct();
//write headers
_csv.WriteField(nameof(GasRecordExportModel.Date));
_csv.WriteField(nameof(GasRecordExportModel.Odometer));
_csv.WriteField(nameof(GasRecordExportModel.FuelConsumed));
_csv.WriteField(nameof(GasRecordExportModel.Cost));
_csv.WriteField(nameof(GasRecordExportModel.FuelEconomy));
_csv.WriteField(nameof(GasRecordExportModel.IsFillToFull));
_csv.WriteField(nameof(GasRecordExportModel.MissedFuelUp));
_csv.WriteField(nameof(GasRecordExportModel.Notes));
_csv.WriteField(nameof(GasRecordExportModel.Tags));
foreach (string extraHeader in extraHeaders)
{
_csv.WriteField($"extrafield_{extraHeader}");
}
_csv.NextRecord();
foreach (GasRecordExportModel genericRecord in genericRecords)
{
_csv.WriteField(genericRecord.Date);
_csv.WriteField(genericRecord.Odometer);
_csv.WriteField(genericRecord.FuelConsumed);
_csv.WriteField(genericRecord.Cost);
_csv.WriteField(genericRecord.FuelEconomy);
_csv.WriteField(genericRecord.IsFillToFull);
_csv.WriteField(genericRecord.MissedFuelUp);
_csv.WriteField(genericRecord.Notes);
_csv.WriteField(genericRecord.Tags);
foreach (string extraHeader in extraHeaders)
{
var extraField = genericRecord.ExtraFields.Where(x => x.Name == extraHeader).FirstOrDefault();
_csv.WriteField(extraField != null ? extraField.Value : string.Empty);
}
_csv.NextRecord();
}
}
}
}

View File

@@ -1,4 +1,5 @@
using Microsoft.Extensions.Caching.Memory;
using CarCareTracker.Models;
using Microsoft.Extensions.Caching.Memory;
using System.Text.Json;
namespace CarCareTracker.Helper
@@ -6,17 +7,22 @@ namespace CarCareTracker.Helper
public interface ITranslationHelper
{
string Translate(string userLanguage, string text);
Dictionary<string, string> GetTranslations(string userLanguage);
OperationResponse SaveTranslation(string userLanguage, Dictionary<string, string> translations);
string ExportTranslation(Dictionary<string, string> translations);
}
public class TranslationHelper : ITranslationHelper
{
private readonly IFileHelper _fileHelper;
private readonly IConfiguration _config;
private readonly ILogger<ITranslationHelper> _logger;
private IMemoryCache _cache;
public TranslationHelper(IFileHelper fileHelper, IConfiguration config, IMemoryCache memoryCache)
public TranslationHelper(IFileHelper fileHelper, IConfiguration config, IMemoryCache memoryCache, ILogger<ITranslationHelper> logger)
{
_fileHelper = fileHelper;
_config = config;
_cache = memoryCache;
_logger = logger;
}
public string Translate(string userLanguage, string text)
{
@@ -36,11 +42,13 @@ namespace CarCareTracker.Helper
return translationDictionary ?? new Dictionary<string, string>();
} catch (Exception ex)
{
_logger.LogError(ex.Message);
return new Dictionary<string, string>();
}
}
else
{
_logger.LogError($"Could not find translation file for {userLanguage}");
return new Dictionary<string, string>();
}
});
@@ -52,10 +60,138 @@ namespace CarCareTracker.Helper
{
//create entry
dictionary.Add(translationKey, text);
_logger.LogInformation($"Translation key added to {userLanguage} for {translationKey} with value {text}");
File.WriteAllText(translationFilePath, JsonSerializer.Serialize(dictionary));
return text;
}
return text;
}
private Dictionary<string, string> GetDefaultTranslation()
{
//this method always returns en_US translation.
var translationFilePath = _fileHelper.GetFullFilePath($"/defaults/en_US.json");
if (!string.IsNullOrWhiteSpace(translationFilePath))
{
//file exists.
try
{
var translationFile = File.ReadAllText(translationFilePath);
var translationDictionary = JsonSerializer.Deserialize<Dictionary<string, string>>(translationFile);
return translationDictionary ?? new Dictionary<string, string>();
}
catch (Exception ex)
{
_logger.LogError(ex.Message);
return new Dictionary<string, string>();
}
}
_logger.LogError($"Could not find translation file for en_US");
return new Dictionary<string, string>();
}
public Dictionary<string, string> GetTranslations(string userLanguage)
{
var defaultTranslation = GetDefaultTranslation();
if (userLanguage == "en_US")
{
return defaultTranslation;
}
var translationFilePath = _fileHelper.GetFullFilePath($"/translations/{userLanguage}.json");
if (!string.IsNullOrWhiteSpace(translationFilePath))
{
//file exists.
try
{
var translationFile = File.ReadAllText(translationFilePath);
var translationDictionary = JsonSerializer.Deserialize<Dictionary<string, string>>(translationFile);
if (translationDictionary != null)
{
foreach(var translation in translationDictionary)
{
if (defaultTranslation.ContainsKey(translation.Key))
{
defaultTranslation[translation.Key] = translation.Value;
}
}
}
return defaultTranslation ?? new Dictionary<string, string>();
}
catch (Exception ex)
{
_logger.LogError(ex.Message);
return new Dictionary<string, string>();
}
}
_logger.LogError($"Could not find translation file for {userLanguage}");
return new Dictionary<string, string>();
}
public OperationResponse SaveTranslation(string userLanguage, Dictionary<string, string> translations)
{
bool create = bool.Parse(_config["LUBELOGGER_TRANSLATOR"] ?? "false");
bool isDefaultLanguage = userLanguage == "en_US";
if (isDefaultLanguage && !create)
{
return OperationResponse.Failed("The translation file name en_US is reserved.");
}
if (string.IsNullOrWhiteSpace(userLanguage))
{
return OperationResponse.Failed("File name is not provided.");
}
if (!translations.Any())
{
return OperationResponse.Failed("Translation has no data.");
}
var translationFilePath = isDefaultLanguage ? _fileHelper.GetFullFilePath($"/defaults/en_US.json") : _fileHelper.GetFullFilePath($"/translations/{userLanguage}.json", false);
try
{
if (File.Exists(translationFilePath))
{
//write to file
File.WriteAllText(translationFilePath, JsonSerializer.Serialize(translations));
_cache.Remove($"lang_{userLanguage}"); //clear out cache, force a reload from file.
} else
{
//check if directory exists first.
var translationDirectory = _fileHelper.GetFullFilePath("translations/", false);
if (!Directory.Exists(translationDirectory))
{
Directory.CreateDirectory(translationDirectory);
}
//write to file
File.WriteAllText(translationFilePath, JsonSerializer.Serialize(translations));
}
return OperationResponse.Succeed("Translation Updated");
}
catch (Exception ex)
{
_logger.LogError(ex.Message);
return OperationResponse.Failed();
}
}
public string ExportTranslation(Dictionary<string, string> translations)
{
try
{
var tempFileName = $"/temp/{Guid.NewGuid()}.json";
string uploadDirectory = _fileHelper.GetFullFilePath("temp/", false);
if (!Directory.Exists(uploadDirectory))
{
Directory.CreateDirectory(uploadDirectory);
}
var saveFilePath = _fileHelper.GetFullFilePath(tempFileName, false);
//standardize translation format for export only.
Dictionary<string, string> sortedTranslations = new Dictionary<string, string>();
foreach (var translation in translations.OrderBy(x => x.Key))
{
sortedTranslations.Add(translation.Key, translation.Value);
};
File.WriteAllText(saveFilePath, JsonSerializer.Serialize(sortedTranslations, new JsonSerializerOptions { WriteIndented = true }));
return tempFileName;
}
catch(Exception ex)
{
_logger.LogError(ex.Message);
return string.Empty;
}
}
}
}

View File

@@ -1,11 +1,6 @@
LubeLogger by Hargata Softworks is licensed under the MIT License for individual
and personal use. Commercial users and/or corporate entities are required
to maintain an active subscription in order to continue using LubeLogger.
For pricing information please contact us at hargatasoftworks@gmail.com
MIT License
Copyright (c) 2023 Hargata Softworks
Copyright (c) 2024 Hargata Softworks
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal

View File

@@ -2,6 +2,7 @@
using CarCareTracker.Helper;
using CarCareTracker.Models;
using Microsoft.Extensions.Caching.Memory;
using Microsoft.IdentityModel.Tokens;
using System.Security.Cryptography;
using System.Text;
using System.Text.Json;
@@ -28,7 +29,7 @@ namespace CarCareTracker.Logic
bool GenerateTokenForEmailAddress(string emailAddress, bool isPasswordReset);
List<UserData> GetAllUsers();
List<Token> GetAllTokens();
KeyValuePair<string, string> GetPKCEChallengeCode();
}
public class LoginLogic : ILoginLogic
{
@@ -70,13 +71,13 @@ namespace CarCareTracker.Logic
var existingUser = _userData.GetUserRecordById(userId);
if (existingUser.Id == default)
{
return new OperationResponse { Success = false, Message = "Invalid user" };
return OperationResponse.Failed("Invalid user");
}
//validate user token
var existingToken = _tokenData.GetTokenRecordByBody(credentials.Token);
if (existingToken.Id == default || existingToken.EmailAddress != existingUser.EmailAddress)
{
return new OperationResponse { Success = false, Message = "Invalid Token" };
return OperationResponse.Failed("Invalid Token");
}
if (!string.IsNullOrWhiteSpace(credentials.UserName) && existingUser.UserName != credentials.UserName)
{
@@ -84,7 +85,7 @@ namespace CarCareTracker.Logic
var existingUserWithUserName = _userData.GetUserRecordByUserName(credentials.UserName);
if (existingUserWithUserName.Id != default)
{
return new OperationResponse { Success = false, Message = "Username already taken" };
return OperationResponse.Failed("Username already taken");
}
existingUser.UserName = credentials.UserName;
}
@@ -94,7 +95,7 @@ namespace CarCareTracker.Logic
var existingUserWithEmailAddress = _userData.GetUserRecordByEmailAddress(credentials.EmailAddress);
if (existingUserWithEmailAddress.Id != default)
{
return new OperationResponse { Success = false, Message = "A user with that email already exists" };
return OperationResponse.Failed("A user with that email already exists");
}
existingUser.EmailAddress = credentials.EmailAddress;
}
@@ -106,7 +107,7 @@ namespace CarCareTracker.Logic
//delete token
_tokenData.DeleteToken(existingToken.Id);
var result = _userData.SaveUserRecord(existingUser);
return new OperationResponse { Success = result, Message = result ? "User Updated" : StaticHelper.GenericErrorMessage };
return OperationResponse.Conditional(result, "User Updated", string.Empty);
}
public OperationResponse RegisterOpenIdUser(LoginModel credentials)
{
@@ -114,21 +115,21 @@ namespace CarCareTracker.Logic
var existingToken = _tokenData.GetTokenRecordByBody(credentials.Token);
if (existingToken.Id == default || existingToken.EmailAddress != credentials.EmailAddress)
{
return new OperationResponse { Success = false, Message = "Invalid Token" };
return OperationResponse.Failed("Invalid Token");
}
if (string.IsNullOrWhiteSpace(credentials.EmailAddress) || string.IsNullOrWhiteSpace(credentials.UserName))
{
return new OperationResponse { Success = false, Message = "Username cannot be blank" };
return OperationResponse.Failed("Username cannot be blank");
}
var existingUser = _userData.GetUserRecordByUserName(credentials.UserName);
if (existingUser.Id != default)
{
return new OperationResponse { Success = false, Message = "Username already taken" };
return OperationResponse.Failed("Username already taken");
}
var existingUserWithEmail = _userData.GetUserRecordByEmailAddress(credentials.EmailAddress);
if (existingUserWithEmail.Id != default)
{
return new OperationResponse { Success = false, Message = "A user with that email already exists" };
return OperationResponse.Failed("A user with that email already exists");
}
_tokenData.DeleteToken(existingToken.Id);
var newUser = new UserData()
@@ -140,11 +141,11 @@ namespace CarCareTracker.Logic
var result = _userData.SaveUserRecord(newUser);
if (result)
{
return new OperationResponse { Success = true, Message = "You will be logged in briefly." };
return OperationResponse.Succeed("You will be logged in briefly.");
}
else
{
return new OperationResponse { Success = false, Message = "Something went wrong, please try again later." };
return OperationResponse.Failed("Something went wrong, please try again later.");
}
}
//handles user registration
@@ -154,22 +155,22 @@ namespace CarCareTracker.Logic
var existingToken = _tokenData.GetTokenRecordByBody(credentials.Token);
if (existingToken.Id == default || existingToken.EmailAddress != credentials.EmailAddress)
{
return new OperationResponse { Success = false, Message = "Invalid Token" };
return OperationResponse.Failed("Invalid Token");
}
//token is valid, check if username and password is acceptable and that username is unique.
if (string.IsNullOrWhiteSpace(credentials.EmailAddress) || string.IsNullOrWhiteSpace(credentials.UserName) || string.IsNullOrWhiteSpace(credentials.Password))
{
return new OperationResponse { Success = false, Message = "Neither username nor password can be blank" };
return OperationResponse.Failed("Neither username nor password can be blank");
}
var existingUser = _userData.GetUserRecordByUserName(credentials.UserName);
if (existingUser.Id != default)
{
return new OperationResponse { Success = false, Message = "Username already taken" };
return OperationResponse.Failed("Username already taken");
}
var existingUserWithEmail = _userData.GetUserRecordByEmailAddress(credentials.EmailAddress);
if (existingUserWithEmail.Id != default)
{
return new OperationResponse { Success = false, Message = "A user with that email already exists" };
return OperationResponse.Failed("A user with that email already exists");
}
//username is unique then we delete the token and create the user.
_tokenData.DeleteToken(existingToken.Id);
@@ -182,11 +183,11 @@ namespace CarCareTracker.Logic
var result = _userData.SaveUserRecord(newUser);
if (result)
{
return new OperationResponse { Success = true, Message = "You will be redirected to the login page briefly." };
return OperationResponse.Succeed("You will be redirected to the login page briefly.");
}
else
{
return new OperationResponse { Success = false, Message = "Something went wrong, please try again later." };
return OperationResponse.Failed();
}
}
/// <summary>
@@ -204,24 +205,24 @@ namespace CarCareTracker.Logic
}
//for security purposes we want to always return true for this method.
//otherwise someone can spam the reset password method to sniff out users.
return new OperationResponse { Success = true, Message = "If your user exists in the system you should receive an email shortly with instructions on how to proceed." };
return OperationResponse.Succeed("If your user exists in the system you should receive an email shortly with instructions on how to proceed.");
}
public OperationResponse ResetPasswordByUser(LoginModel credentials)
{
var existingToken = _tokenData.GetTokenRecordByBody(credentials.Token);
if (existingToken.Id == default || existingToken.EmailAddress != credentials.EmailAddress)
{
return new OperationResponse { Success = false, Message = "Invalid Token" };
return OperationResponse.Failed("Invalid Token");
}
if (string.IsNullOrWhiteSpace(credentials.Password))
{
return new OperationResponse { Success = false, Message = "New Password cannot be blank" };
return OperationResponse.Failed("New Password cannot be blank");
}
//if token is valid.
var existingUser = _userData.GetUserRecordByEmailAddress(credentials.EmailAddress);
if (existingUser.Id == default)
{
return new OperationResponse { Success = false, Message = "Unable to locate user" };
return OperationResponse.Failed("Unable to locate user");
}
existingUser.Password = GetHash(credentials.Password);
var result = _userData.SaveUserRecord(existingUser);
@@ -229,10 +230,10 @@ namespace CarCareTracker.Logic
_tokenData.DeleteToken(existingToken.Id);
if (result)
{
return new OperationResponse { Success = true, Message = "Password resetted, you will be redirected to login page shortly." };
return OperationResponse.Succeed("Password resetted, you will be redirected to login page shortly.");
} else
{
return new OperationResponse { Success = false, Message = StaticHelper.GenericErrorMessage };
return OperationResponse.Failed();
}
}
/// <summary>
@@ -244,14 +245,7 @@ namespace CarCareTracker.Logic
{
if (UserIsRoot(credentials))
{
return new UserData()
{
Id = -1,
UserName = credentials.UserName,
IsAdmin = true,
IsRootUser = true,
EmailAddress = string.Empty
};
return GetRootUserData(credentials.UserName);
}
else
{
@@ -270,6 +264,13 @@ namespace CarCareTracker.Logic
}
public UserData ValidateOpenIDUser(LoginModel credentials)
{
//validate for root user
var isRootUser = _configHelper.AuthenticateRootUserOIDC(credentials.EmailAddress);
if (isRootUser)
{
return GetRootUserData(credentials.EmailAddress);
}
var result = _userData.GetUserRecordByEmailAddress(credentials.EmailAddress);
if (result.Id != default)
{
@@ -309,7 +310,7 @@ namespace CarCareTracker.Logic
var existingToken = _tokenData.GetTokenRecordByEmailAddress(emailAddress);
if (existingToken.Id != default)
{
return new OperationResponse { Success = false, Message = "There is an existing token tied to this email address" };
return OperationResponse.Failed("There is an existing token tied to this email address");
}
var token = new Token()
{
@@ -322,16 +323,16 @@ namespace CarCareTracker.Logic
result = _mailHelper.NotifyUserForRegistration(emailAddress, token.Body).Success;
if (!result)
{
return new OperationResponse { Success = false, Message = "Token Generated, but Email failed to send, please check your SMTP settings." };
return OperationResponse.Failed("Token Generated, but Email failed to send, please check your SMTP settings.");
}
}
if (result)
{
return new OperationResponse { Success = true, Message = "Token Generated!" };
return OperationResponse.Succeed("Token Generated!");
}
else
{
return new OperationResponse { Success = false, Message = StaticHelper.GenericErrorMessage };
return OperationResponse.Failed();
}
}
public bool DeleteUserToken(int tokenId)
@@ -350,18 +351,18 @@ namespace CarCareTracker.Logic
var existingUser = _userData.GetUserRecordByUserName(credentials.UserName);
if (existingUser.Id == default)
{
return new OperationResponse { Success = false, Message = "Unable to find user" };
return OperationResponse.Failed("Unable to find user");
}
var newPassword = Guid.NewGuid().ToString().Substring(0, 8);
existingUser.Password = GetHash(newPassword);
var result = _userData.SaveUserRecord(existingUser);
if (result)
{
return new OperationResponse { Success = true, Message = newPassword };
return OperationResponse.Succeed(newPassword);
}
else
{
return new OperationResponse { Success = false, Message = "Something went wrong, please try again later." };
return OperationResponse.Failed();
}
}
#endregion
@@ -419,6 +420,17 @@ namespace CarCareTracker.Logic
var hashedPassword = GetHash(credentials.Password);
return _configHelper.AuthenticateRootUser(hashedUserName, hashedPassword);
}
private UserData GetRootUserData(string username)
{
return new UserData()
{
Id = -1,
UserName = username,
IsAdmin = true,
IsRootUser = true,
EmailAddress = string.Empty
};
}
#endregion
private static string GetHash(string value)
{
@@ -439,6 +451,14 @@ namespace CarCareTracker.Logic
{
return Guid.NewGuid().ToString().Substring(0, 8);
}
public KeyValuePair<string, string> GetPKCEChallengeCode()
{
var verifierCode = Base64UrlEncoder.Encode(Guid.NewGuid().ToString().Replace("-", ""));
var verifierBytes = Encoding.UTF8.GetBytes(verifierCode);
var hashedCode = SHA256.Create().ComputeHash(verifierBytes);
var encodedChallengeCode = Base64UrlEncoder.Encode(hashedCode);
return new KeyValuePair<string, string>(verifierCode, encodedChallengeCode);
}
public bool GenerateTokenForEmailAddress(string emailAddress, bool isPasswordReset)
{
bool result = false;

View File

@@ -33,6 +33,10 @@ namespace CarCareTracker.Logic
}
public bool AutoInsertOdometerRecord(OdometerRecord odometer)
{
if (odometer.Mileage == default)
{
return false;
}
var lastReportedMileage = GetLastOdometerRecordMileage(odometer.VehicleId, new List<OdometerRecord>());
odometer.InitialMileage = lastReportedMileage != default ? lastReportedMileage : odometer.Mileage;

View File

@@ -51,16 +51,16 @@ namespace CarCareTracker.Logic
var userAccess = _userAccess.GetUserAccessByVehicleAndUserId(existingUser.Id, vehicleId);
if (userAccess != null)
{
return new OperationResponse { Success = false, Message = "User is already a collaborator" };
return OperationResponse.Failed("User is already a collaborator");
}
var result = AddUserAccessToVehicle(existingUser.Id, vehicleId);
if (result)
{
return new OperationResponse { Success = true, Message = "Collaborator Added" };
return OperationResponse.Succeed("Collaborator Added");
}
return new OperationResponse { Success = false, Message = StaticHelper.GenericErrorMessage };
return OperationResponse.Failed();
}
return new OperationResponse { Success = false, Message = $"Unable to find user {username} in the system" };
return OperationResponse.Failed($"Unable to find user {username} in the system");
}
public bool DeleteCollaboratorFromVehicle(int userId, int vehicleId)
{

View File

@@ -6,9 +6,17 @@ namespace CarCareTracker.Logic
{
public interface IVehicleLogic
{
VehicleRecords GetVehicleRecords(int vehicleId);
decimal GetVehicleTotalCost(VehicleRecords vehicleRecords);
int GetMaxMileage(int vehicleId);
int GetMaxMileage(VehicleRecords vehicleRecords);
int GetMinMileage(int vehicleId);
bool GetVehicleHasUrgentOrPastDueReminders(int vehicleId);
int GetMinMileage(VehicleRecords vehicleRecords);
int GetOwnershipDays(string purchaseDate, string soldDate, List<ServiceRecord> serviceRecords, List<CollisionRecord> repairRecords, List<GasRecord> gasRecords, List<UpgradeRecord> upgradeRecords, List<OdometerRecord> odometerRecords, List<TaxRecord> taxRecords);
bool GetVehicleHasUrgentOrPastDueReminders(int vehicleId, int currentMileage);
List<VehicleInfo> GetVehicleInfo(List<Vehicle> vehicles);
List<ReminderRecordViewModel> GetReminders(List<Vehicle> vehicles, bool isCalendar);
List<PlanRecord> GetPlans(List<Vehicle> vehicles, bool excludeDone);
}
public class VehicleLogic: IVehicleLogic
{
@@ -16,26 +24,53 @@ namespace CarCareTracker.Logic
private readonly IGasRecordDataAccess _gasRecordDataAccess;
private readonly ICollisionRecordDataAccess _collisionRecordDataAccess;
private readonly IUpgradeRecordDataAccess _upgradeRecordDataAccess;
private readonly ITaxRecordDataAccess _taxRecordDataAccess;
private readonly IOdometerRecordDataAccess _odometerRecordDataAccess;
private readonly IReminderRecordDataAccess _reminderRecordDataAccess;
private readonly IPlanRecordDataAccess _planRecordDataAccess;
private readonly IReminderHelper _reminderHelper;
public VehicleLogic(
IServiceRecordDataAccess serviceRecordDataAccess,
IGasRecordDataAccess gasRecordDataAccess,
ICollisionRecordDataAccess collisionRecordDataAccess,
IUpgradeRecordDataAccess upgradeRecordDataAccess,
ITaxRecordDataAccess taxRecordDataAccess,
IOdometerRecordDataAccess odometerRecordDataAccess,
IReminderRecordDataAccess reminderRecordDataAccess,
IPlanRecordDataAccess planRecordDataAccess,
IReminderHelper reminderHelper
) {
_serviceRecordDataAccess = serviceRecordDataAccess;
_gasRecordDataAccess = gasRecordDataAccess;
_collisionRecordDataAccess = collisionRecordDataAccess;
_upgradeRecordDataAccess = upgradeRecordDataAccess;
_taxRecordDataAccess = taxRecordDataAccess;
_odometerRecordDataAccess = odometerRecordDataAccess;
_planRecordDataAccess = planRecordDataAccess;
_reminderRecordDataAccess = reminderRecordDataAccess;
_reminderHelper = reminderHelper;
}
public VehicleRecords GetVehicleRecords(int vehicleId)
{
return new VehicleRecords
{
ServiceRecords = _serviceRecordDataAccess.GetServiceRecordsByVehicleId(vehicleId),
GasRecords = _gasRecordDataAccess.GetGasRecordsByVehicleId(vehicleId),
CollisionRecords = _collisionRecordDataAccess.GetCollisionRecordsByVehicleId(vehicleId),
TaxRecords = _taxRecordDataAccess.GetTaxRecordsByVehicleId(vehicleId),
UpgradeRecords = _upgradeRecordDataAccess.GetUpgradeRecordsByVehicleId(vehicleId),
OdometerRecords = _odometerRecordDataAccess.GetOdometerRecordsByVehicleId(vehicleId),
};
}
public decimal GetVehicleTotalCost(VehicleRecords vehicleRecords)
{
var serviceRecordSum = vehicleRecords.ServiceRecords.Sum(x => x.Cost);
var repairRecordSum = vehicleRecords.CollisionRecords.Sum(x => x.Cost);
var upgradeRecordSum = vehicleRecords.UpgradeRecords.Sum(x => x.Cost);
var taxRecordSum = vehicleRecords.TaxRecords.Sum(x => x.Cost);
var gasRecordSum = vehicleRecords.GasRecords.Sum(x => x.Cost);
return serviceRecordSum + repairRecordSum + upgradeRecordSum + taxRecordSum + gasRecordSum;
}
public int GetMaxMileage(int vehicleId)
{
var numbersArray = new List<int>();
@@ -66,6 +101,31 @@ namespace CarCareTracker.Logic
}
return numbersArray.Any() ? numbersArray.Max() : 0;
}
public int GetMaxMileage(VehicleRecords vehicleRecords)
{
var numbersArray = new List<int>();
if (vehicleRecords.ServiceRecords.Any())
{
numbersArray.Add(vehicleRecords.ServiceRecords.Max(x => x.Mileage));
}
if (vehicleRecords.CollisionRecords.Any())
{
numbersArray.Add(vehicleRecords.CollisionRecords.Max(x => x.Mileage));
}
if (vehicleRecords.GasRecords.Any())
{
numbersArray.Add(vehicleRecords.GasRecords.Max(x => x.Mileage));
}
if (vehicleRecords.UpgradeRecords.Any())
{
numbersArray.Add(vehicleRecords.UpgradeRecords.Max(x => x.Mileage));
}
if (vehicleRecords.OdometerRecords.Any())
{
numbersArray.Add(vehicleRecords.OdometerRecords.Max(x => x.Mileage));
}
return numbersArray.Any() ? numbersArray.Max() : 0;
}
public int GetMinMileage(int vehicleId)
{
var numbersArray = new List<int>();
@@ -96,12 +156,166 @@ namespace CarCareTracker.Logic
}
return numbersArray.Any() ? numbersArray.Min() : 0;
}
public bool GetVehicleHasUrgentOrPastDueReminders(int vehicleId)
public int GetMinMileage(VehicleRecords vehicleRecords)
{
var numbersArray = new List<int>();
var _serviceRecords = vehicleRecords.ServiceRecords.Where(x => x.Mileage != default).ToList();
if (_serviceRecords.Any())
{
numbersArray.Add(_serviceRecords.Min(x => x.Mileage));
}
var _repairRecords = vehicleRecords.CollisionRecords.Where(x => x.Mileage != default).ToList();
if (_repairRecords.Any())
{
numbersArray.Add(_repairRecords.Min(x => x.Mileage));
}
var _gasRecords = vehicleRecords.GasRecords.Where(x => x.Mileage != default).ToList();
if (_gasRecords.Any())
{
numbersArray.Add(_gasRecords.Min(x => x.Mileage));
}
var _upgradeRecords = vehicleRecords.UpgradeRecords.Where(x => x.Mileage != default).ToList();
if (_upgradeRecords.Any())
{
numbersArray.Add(_upgradeRecords.Min(x => x.Mileage));
}
var _odometerRecords = vehicleRecords.OdometerRecords.Where(x => x.Mileage != default).ToList();
if (_odometerRecords.Any())
{
numbersArray.Add(_odometerRecords.Min(x => x.Mileage));
}
return numbersArray.Any() ? numbersArray.Min() : 0;
}
public int GetOwnershipDays(string purchaseDate, string soldDate, List<ServiceRecord> serviceRecords, List<CollisionRecord> repairRecords, List<GasRecord> gasRecords, List<UpgradeRecord> upgradeRecords, List<OdometerRecord> odometerRecords, List<TaxRecord> taxRecords)
{
var startDate = DateTime.Now;
var endDate = DateTime.Now;
if (!string.IsNullOrWhiteSpace(soldDate))
{
endDate = DateTime.Parse(soldDate);
}
if (!string.IsNullOrWhiteSpace(purchaseDate))
{
//if purchase date is provided, then we just have to subtract the begin date to end date and return number of months
startDate = DateTime.Parse(purchaseDate);
var timeElapsed = (int)Math.Floor((endDate - startDate).TotalDays);
return timeElapsed;
}
var dateArray = new List<DateTime>();
dateArray.AddRange(serviceRecords.Select(x => x.Date));
dateArray.AddRange(repairRecords.Select(x => x.Date));
dateArray.AddRange(gasRecords.Select(x => x.Date));
dateArray.AddRange(upgradeRecords.Select(x => x.Date));
dateArray.AddRange(odometerRecords.Select(x => x.Date));
dateArray.AddRange(taxRecords.Select(x => x.Date));
if (dateArray.Any())
{
startDate = dateArray.Min();
var timeElapsed = (int)Math.Floor((endDate - startDate).TotalDays);
return timeElapsed;
} else
{
return 1;
}
}
public bool GetVehicleHasUrgentOrPastDueReminders(int vehicleId, int currentMileage)
{
var currentMileage = GetMaxMileage(vehicleId);
var reminders = _reminderRecordDataAccess.GetReminderRecordsByVehicleId(vehicleId);
var results = _reminderHelper.GetReminderRecordViewModels(reminders, currentMileage, DateTime.Now);
return results.Any(x => x.Urgency == ReminderUrgency.VeryUrgent || x.Urgency == ReminderUrgency.PastDue);
}
public List<VehicleInfo> GetVehicleInfo(List<Vehicle> vehicles)
{
List<VehicleInfo> apiResult = new List<VehicleInfo>();
foreach (Vehicle vehicle in vehicles)
{
var currentMileage = GetMaxMileage(vehicle.Id);
var reminders = _reminderRecordDataAccess.GetReminderRecordsByVehicleId(vehicle.Id);
var results = _reminderHelper.GetReminderRecordViewModels(reminders, currentMileage, DateTime.Now);
var serviceRecords = _serviceRecordDataAccess.GetServiceRecordsByVehicleId(vehicle.Id);
var repairRecords = _collisionRecordDataAccess.GetCollisionRecordsByVehicleId(vehicle.Id);
var upgradeRecords = _upgradeRecordDataAccess.GetUpgradeRecordsByVehicleId(vehicle.Id);
var gasRecords = _gasRecordDataAccess.GetGasRecordsByVehicleId(vehicle.Id);
var taxRecords = _taxRecordDataAccess.GetTaxRecordsByVehicleId(vehicle.Id);
var planRecords = _planRecordDataAccess.GetPlanRecordsByVehicleId(vehicle.Id);
var resultToAdd = new VehicleInfo()
{
VehicleData = vehicle,
LastReportedOdometer = currentMileage,
ServiceRecordCount = serviceRecords.Count(),
ServiceRecordCost = serviceRecords.Sum(x => x.Cost),
RepairRecordCount = repairRecords.Count(),
RepairRecordCost = repairRecords.Sum(x => x.Cost),
UpgradeRecordCount = upgradeRecords.Count(),
UpgradeRecordCost = upgradeRecords.Sum(x => x.Cost),
GasRecordCount = gasRecords.Count(),
GasRecordCost = gasRecords.Sum(x => x.Cost),
TaxRecordCount = taxRecords.Count(),
TaxRecordCost = taxRecords.Sum(x => x.Cost),
VeryUrgentReminderCount = results.Count(x => x.Urgency == ReminderUrgency.VeryUrgent),
PastDueReminderCount = results.Count(x => x.Urgency == ReminderUrgency.PastDue),
UrgentReminderCount = results.Count(x => x.Urgency == ReminderUrgency.Urgent),
NotUrgentReminderCount = results.Count(x => x.Urgency == ReminderUrgency.NotUrgent),
PlanRecordBackLogCount = planRecords.Count(x => x.Progress == PlanProgress.Backlog),
PlanRecordInProgressCount = planRecords.Count(x => x.Progress == PlanProgress.InProgress),
PlanRecordTestingCount = planRecords.Count(x => x.Progress == PlanProgress.Testing),
PlanRecordDoneCount = planRecords.Count(x => x.Progress == PlanProgress.Done)
};
//set next reminder
if (results.Any(x => (x.Metric == ReminderMetric.Date || x.Metric == ReminderMetric.Both) && x.Date >= DateTime.Now.Date))
{
resultToAdd.NextReminder = results.Where(x => x.Date >= DateTime.Now.Date).OrderBy(x => x.Date).Select(x => new ReminderExportModel { Description = x.Description, Urgency = x.Urgency.ToString(), Metric = x.Metric.ToString(), Notes = x.Notes, DueDate = x.Date.ToShortDateString(), DueOdometer = x.Mileage.ToString() }).First();
}
else if (results.Any(x => (x.Metric == ReminderMetric.Odometer || x.Metric == ReminderMetric.Both) && x.Mileage >= currentMileage))
{
resultToAdd.NextReminder = results.Where(x => x.Mileage >= currentMileage).OrderBy(x => x.Mileage).Select(x => new ReminderExportModel { Description = x.Description, Urgency = x.Urgency.ToString(), Metric = x.Metric.ToString(), Notes = x.Notes, DueDate = x.Date.ToShortDateString(), DueOdometer = x.Mileage.ToString() }).First();
}
apiResult.Add(resultToAdd);
}
return apiResult;
}
public List<ReminderRecordViewModel> GetReminders(List<Vehicle> vehicles, bool isCalendar)
{
List<ReminderRecordViewModel> reminders = new List<ReminderRecordViewModel>();
foreach (Vehicle vehicle in vehicles)
{
var vehicleReminders = _reminderRecordDataAccess.GetReminderRecordsByVehicleId(vehicle.Id);
if (isCalendar)
{
vehicleReminders.RemoveAll(x => x.Metric == ReminderMetric.Odometer);
//we don't care about mileages so we can basically fake the current vehicle mileage.
}
if (vehicleReminders.Any())
{
var vehicleMileage = isCalendar ? 0 : GetMaxMileage(vehicle.Id);
var reminderUrgency = _reminderHelper.GetReminderRecordViewModels(vehicleReminders, vehicleMileage, DateTime.Now);
reminderUrgency = reminderUrgency.Select(x => new ReminderRecordViewModel { Id = x.Id, Metric = x.Metric, Date = x.Date, Notes = x.Notes, Mileage = x.Mileage, Urgency = x.Urgency, Description = $"{vehicle.Year} {vehicle.Make} {vehicle.Model} #{StaticHelper.GetVehicleIdentifier(vehicle)} - {x.Description}" }).ToList();
reminders.AddRange(reminderUrgency);
}
}
return reminders.OrderByDescending(x=>x.Urgency).ToList();
}
public List<PlanRecord> GetPlans(List<Vehicle> vehicles, bool excludeDone)
{
List<PlanRecord> plans = new List<PlanRecord>();
foreach (Vehicle vehicle in vehicles)
{
var vehiclePlans = _planRecordDataAccess.GetPlanRecordsByVehicleId(vehicle.Id);
if (excludeDone)
{
vehiclePlans.RemoveAll(x => x.Progress == PlanProgress.Done);
}
if (vehiclePlans.Any())
{
var convertedPlans = vehiclePlans.Select(x => new PlanRecord { ImportMode = x.ImportMode, Priority = x.Priority, Progress = x.Progress, Notes = x.Notes, RequisitionHistory = x.RequisitionHistory, Description = $"{vehicle.Year} {vehicle.Make} {vehicle.Model} #{StaticHelper.GetVehicleIdentifier(vehicle)} - {x.Description}" });
plans.AddRange(convertedPlans);
}
}
return plans.OrderBy(x => x.Priority).ThenBy(x=>x.Progress).ToList();
}
}
}

View File

@@ -8,18 +8,21 @@ namespace CarCareTracker.MapProfile
public ImportMapper()
{
Map(m => m.Date).Name(["date", "fuelup_date"]);
Map(m => m.Day).Name(["day"]);
Map(m => m.Month).Name(["month"]);
Map(m => m.Year).Name(["year"]);
Map(m => m.DateCreated).Name(["datecreated"]);
Map(m => m.DateModified).Name(["datemodified"]);
Map(m => m.InitialOdometer).Name(["initialodometer"]);
Map(m => m.Odometer).Name(["odometer"]);
Map(m => m.FuelConsumed).Name(["gallons", "liters", "litres", "consumption", "quantity", "fuelconsumed"]);
Map(m => m.Odometer).Name(["odometer", "odo"]);
Map(m => m.FuelConsumed).Name(["gallons", "liters", "litres", "consumption", "quantity", "fuelconsumed", "qty"]);
Map(m => m.Cost).Name(["cost", "total cost", "totalcost", "total price"]);
Map(m => m.Notes).Name("notes", "note");
Map(m => m.Price).Name(["price"]);
Map(m => m.PartialFuelUp).Name(["partial_fuelup"]);
Map(m => m.PartialFuelUp).Name(["partial_fuelup", "partial tank", "partial_fill"]);
Map(m => m.IsFillToFull).Name(["isfilltofull", "filled up"]);
Map(m => m.Description).Name(["description"]);
Map(m => m.MissedFuelUp).Name(["missed_fuelup", "missedfuelup"]);
Map(m => m.MissedFuelUp).Name(["missed_fuelup", "missedfuelup", "missed fill up", "missed_fill"]);
Map(m => m.PartSupplier).Name(["partsupplier"]);
Map(m => m.PartQuantity).Name(["partquantity"]);
Map(m => m.PartNumber).Name(["partnumber"]);
@@ -27,6 +30,18 @@ namespace CarCareTracker.MapProfile
Map(m => m.Type).Name(["type"]);
Map(m => m.Priority).Name(["priority"]);
Map(m => m.Tags).Name(["tags"]);
Map(m => m.ExtraFields).Convert(row =>
{
var attributes = new Dictionary<string, string>();
foreach (var header in row.Row.HeaderRecord)
{
if (header.ToLower().StartsWith("extrafield_"))
{
attributes.Add(header.Substring(11), row.Row.GetField(header));
}
}
return attributes;
});
}
}
}

View File

@@ -4,6 +4,7 @@ using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.DataProtection;
using Microsoft.Extensions.Options;
using System.Security.Claims;
using System.Text;
using System.Text.Encodings.Web;
using System.Text.Json;
@@ -27,7 +28,7 @@ namespace CarCareTracker.Middleware
_httpContext = httpContext;
_dataProtector = securityProvider.CreateProtector("login");
_loginLogic = loginLogic;
enableAuth = bool.Parse(configuration["EnableAuth"]);
enableAuth = bool.Parse(configuration["EnableAuth"] ?? "false");
}
protected override async Task<AuthenticateResult> HandleAuthenticateAsync()
{
@@ -42,7 +43,7 @@ namespace CarCareTracker.Middleware
new(ClaimTypes.Role, nameof(UserData.IsRootUser))
};
appIdentity.AddClaims(userIdentity);
AuthenticationTicket ticket = new AuthenticationTicket(new ClaimsPrincipal(appIdentity), this.Scheme.Name);
AuthenticationTicket ticket = new AuthenticationTicket(new ClaimsPrincipal(appIdentity), Scheme.Name);
return AuthenticateResult.Success(ticket);
}
else
@@ -59,7 +60,7 @@ namespace CarCareTracker.Middleware
{
var cleanedHeader = request_header.ToString().Replace("Basic ", "").Trim();
byte[] data = Convert.FromBase64String(cleanedHeader);
string decodedString = System.Text.Encoding.UTF8.GetString(data);
string decodedString = Encoding.UTF8.GetString(data);
var splitString = decodedString.Split(":");
if (splitString.Count() != 2)
{
@@ -85,7 +86,7 @@ namespace CarCareTracker.Middleware
userIdentity.Add(new(ClaimTypes.Role, nameof(UserData.IsRootUser)));
}
appIdentity.AddClaims(userIdentity);
AuthenticationTicket ticket = new AuthenticationTicket(new ClaimsPrincipal(appIdentity), this.Scheme.Name);
AuthenticationTicket ticket = new AuthenticationTicket(new ClaimsPrincipal(appIdentity), Scheme.Name);
return AuthenticateResult.Success(ticket);
}
}
@@ -133,7 +134,7 @@ namespace CarCareTracker.Middleware
userIdentity.Add(new(ClaimTypes.Role, nameof(UserData.IsRootUser)));
}
appIdentity.AddClaims(userIdentity);
AuthenticationTicket ticket = new AuthenticationTicket(new ClaimsPrincipal(appIdentity), this.Scheme.Name);
AuthenticationTicket ticket = new AuthenticationTicket(new ClaimsPrincipal(appIdentity), Scheme.Name);
return AuthenticateResult.Success(ticket);
}
}

27
Models/API/VehicleInfo.cs Normal file
View File

@@ -0,0 +1,27 @@
namespace CarCareTracker.Models
{
public class VehicleInfo
{
public Vehicle VehicleData { get; set; } = new Vehicle();
public int VeryUrgentReminderCount { get; set; }
public int UrgentReminderCount { get; set;}
public int NotUrgentReminderCount { get; set; }
public int PastDueReminderCount { get; set; }
public ReminderExportModel NextReminder { get; set; }
public int ServiceRecordCount { get; set; }
public decimal ServiceRecordCost { get; set; }
public int RepairRecordCount { get; set; }
public decimal RepairRecordCost { get; set; }
public int UpgradeRecordCount { get; set; }
public decimal UpgradeRecordCost { get; set; }
public int TaxRecordCount { get; set; }
public decimal TaxRecordCost { get; set; }
public int GasRecordCount { get; set; }
public decimal GasRecordCost { get; set; }
public int LastReportedOdometer { get; set; }
public int PlanRecordBackLogCount { get; set; }
public int PlanRecordInProgressCount { get; set; }
public int PlanRecordTestingCount { get; set; }
public int PlanRecordDoneCount { get; set; }
}
}

View File

@@ -15,6 +15,7 @@
public List<string> Tags { get; set; } = new List<string>();
public List<ExtraField> ExtraFields { get; set; } = new List<ExtraField>();
public List<SupplyUsageHistory> RequisitionHistory { get; set; } = new List<SupplyUsageHistory>();
public List<SupplyUsageHistory> DeletedRequisitionHistory { get; set; } = new List<SupplyUsageHistory>();
public bool CopySuppliesAttachment { get; set; } = false;
public CollisionRecord ToCollisionRecord() { return new CollisionRecord {
Id = Id,

View File

@@ -4,7 +4,6 @@
{
public string EmailServer { get; set; }
public string EmailFrom { get; set; }
public bool UseSSL { get; set; }
public int Port { get; set; }
public string Username { get; set; }
public string Password { get; set; }

View File

@@ -0,0 +1,14 @@
namespace CarCareTracker.Models
{
public class KioskViewModel
{
/// <summary>
/// List of vehicle ids to exclude from Kiosk Dashboard
/// </summary>
public List<int> Exclusions { get; set; } = new List<int>();
/// <summary>
/// Whether to retrieve data for vehicle, plans, or reminder view.
/// </summary>
public KioskMode KioskMode { get; set; } = KioskMode.Vehicle;
}
}

View File

@@ -10,9 +10,18 @@
public string RedirectURL { get; set; }
public string Scope { get; set; }
public string State { get; set; }
public string CodeChallenge { get; set; }
public bool ValidateState { get; set; } = false;
public bool DisableRegularLogin { get; set; } = false;
public bool UsePKCE { get; set; } = false;
public string LogOutURL { get; set; } = "";
public string RemoteAuthURL { get { return $"{AuthURL}?client_id={ClientId}&response_type=code&redirect_uri={RedirectURL}&scope={Scope}&state={State}"; } }
public string RemoteAuthURL { get {
var redirectUrl = $"{AuthURL}?client_id={ClientId}&response_type=code&redirect_uri={RedirectURL}&scope={Scope}&state={State}";
if (UsePKCE)
{
redirectUrl += $"&code_challenge={CodeChallenge}&code_challenge_method=S256";
}
return redirectUrl;
} }
}
}

View File

@@ -1,8 +1,33 @@
namespace CarCareTracker.Models
using CarCareTracker.Helper;
namespace CarCareTracker.Models
{
public class OperationResponse
public class OperationResponseBase
{
public bool Success { get; set; }
public string Message { get; set; }
}
public class OperationResponse: OperationResponseBase
{
public static OperationResponse Succeed(string message = "")
{
return new OperationResponse { Success = true, Message = message };
}
public static OperationResponse Failed(string message = "")
{
if (string.IsNullOrWhiteSpace(message))
{
message = StaticHelper.GenericErrorMessage;
}
return new OperationResponse { Success = false, Message = message};
}
public static OperationResponse Conditional(bool result, string successMessage = "", string errorMessage = "")
{
if (string.IsNullOrWhiteSpace(errorMessage))
{
errorMessage = StaticHelper.GenericErrorMessage;
}
return new OperationResponse { Success = result, Message = result ? successMessage : errorMessage };
}
}
}

View File

@@ -17,6 +17,7 @@
public decimal Cost { get; set; }
public List<ExtraField> ExtraFields { get; set; } = new List<ExtraField>();
public List<SupplyUsageHistory> RequisitionHistory { get; set; } = new List<SupplyUsageHistory>();
public List<SupplyUsageHistory> DeletedRequisitionHistory { get; set; } = new List<SupplyUsageHistory>();
public bool CopySuppliesAttachment { get; set; } = false;
public PlanRecord ToPlanRecord() { return new PlanRecord {
Id = Id,

View File

@@ -9,6 +9,8 @@
public string Description { get; set; }
public string Notes { get; set; }
public bool IsRecurring { get; set; } = false;
public bool UseCustomThresholds { get; set; } = false;
public ReminderUrgencyConfig CustomThresholds { get; set; } = new ReminderUrgencyConfig();
public int CustomMileageInterval { get; set; } = 0;
public int CustomMonthInterval { get; set; } = 0;
public ReminderMileageInterval ReminderMileageInterval { get; set; } = ReminderMileageInterval.FiveThousandMiles;

View File

@@ -9,6 +9,8 @@
public string Description { get; set; }
public string Notes { get; set; }
public bool IsRecurring { get; set; } = false;
public bool UseCustomThresholds { get; set; } = false;
public ReminderUrgencyConfig CustomThresholds { get; set; } = new ReminderUrgencyConfig();
public int CustomMileageInterval { get; set; } = 0;
public int CustomMonthInterval { get; set; } = 0;
public ReminderMileageInterval ReminderMileageInterval { get; set; } = ReminderMileageInterval.FiveThousandMiles;
@@ -26,6 +28,8 @@
Description = Description,
Metric = Metric,
IsRecurring = IsRecurring,
UseCustomThresholds = UseCustomThresholds,
CustomThresholds = CustomThresholds,
ReminderMileageInterval = ReminderMileageInterval,
ReminderMonthInterval = ReminderMonthInterval,
CustomMileageInterval = CustomMileageInterval,

View File

@@ -9,6 +9,10 @@
public string Description { get; set; }
public string Notes { get; set; }
/// <summary>
/// The metric the user selected to calculate the urgency of this reminder.
/// </summary>
public ReminderMetric UserMetric { get; set; } = ReminderMetric.Date;
/// <summary>
/// Reason why this reminder is urgent
/// </summary>
public ReminderMetric Metric { get; set; } = ReminderMetric.Date;

View File

@@ -0,0 +1,8 @@
namespace CarCareTracker.Models
{
public class CostDistanceTableForVehicle
{
public string DistanceUnit { get; set; } = "mi.";
public List<CostForVehicleByMonth> CostData { get; set; } = new List<CostForVehicleByMonth>();
}
}

View File

@@ -2,9 +2,11 @@
{
public class CostForVehicleByMonth
{
public int Year { get; set; }
public int MonthId { get; set; }
public string MonthName { get; set; }
public decimal Cost { get; set; }
public int DistanceTraveled { get; set; }
public decimal CostPerDistanceTraveled { get { if (DistanceTraveled > 0) { return Cost / DistanceTraveled; } else { return 0M; } } }
}
}

View File

@@ -0,0 +1,27 @@
namespace CarCareTracker.Models
{
public class CostTableForVehicle
{
public string DistanceUnit { get; set; } = "Cost Per Mile";
public int TotalDistance { get; set; }
public int NumberOfDays { get; set; }
public decimal ServiceRecordSum { get; set; }
public decimal GasRecordSum { get; set; }
public decimal TaxRecordSum { get; set; }
public decimal CollisionRecordSum { get; set; }
public decimal UpgradeRecordSum { get; set; }
public decimal ServiceRecordPerMile { get { return TotalDistance != default ? ServiceRecordSum / TotalDistance : 0; } }
public decimal GasRecordPerMile { get { return TotalDistance != default ? GasRecordSum / TotalDistance : 0; } }
public decimal CollisionRecordPerMile { get { return TotalDistance != default ? CollisionRecordSum / TotalDistance : 0; } }
public decimal UpgradeRecordPerMile { get { return TotalDistance != default ? UpgradeRecordSum / TotalDistance : 0; } }
public decimal TaxRecordPerMile { get { return TotalDistance != default ? TaxRecordSum / TotalDistance : 0; } }
public decimal ServiceRecordPerDay { get { return NumberOfDays != default ? ServiceRecordSum / NumberOfDays : 0; } }
public decimal GasRecordPerDay { get { return NumberOfDays != default ? GasRecordSum / NumberOfDays : 0; } }
public decimal CollisionRecordPerDay { get { return NumberOfDays != default ? CollisionRecordSum / NumberOfDays : 0; } }
public decimal UpgradeRecordPerDay { get { return NumberOfDays != default ? UpgradeRecordSum / NumberOfDays : 0; } }
public decimal TaxRecordPerDay { get { return NumberOfDays != default ? TaxRecordSum / NumberOfDays : 0; } }
public decimal TotalPerDay { get { return ServiceRecordPerDay + CollisionRecordPerDay + UpgradeRecordPerDay + GasRecordPerDay + TaxRecordPerDay; } }
public decimal TotalPerMile { get { return ServiceRecordPerMile + CollisionRecordPerMile + UpgradeRecordPerMile + GasRecordPerMile + TaxRecordPerMile; } }
public decimal TotalCost { get { return ServiceRecordSum + CollisionRecordSum + UpgradeRecordSum + GasRecordSum + TaxRecordSum; } }
}
}

View File

@@ -12,5 +12,6 @@
public string Notes { get; set; }
public decimal Cost { get; set; }
public List<UploadedFiles> Files { get; set; } = new List<UploadedFiles>();
public List<ExtraField> ExtraFields { get; set; } = new List<ExtraField>();
}
}

View File

@@ -0,0 +1,9 @@
namespace CarCareTracker.Models
{
public class MPGForVehicleByMonth
{
public List<CostForVehicleByMonth> CostData { get; set; } = new List<CostForVehicleByMonth>();
public List<CostForVehicleByMonth> SortedCostData { get; set; } = new List<CostForVehicleByMonth>();
public string Unit { get; set; }
}
}

View File

@@ -0,0 +1,8 @@
namespace CarCareTracker.Models
{
public class ReportParameter
{
public List<string> VisibleColumns { get; set; } = new List<string>();
public List<string> ExtraFields { get; set; } = new List<string>();
}
}

View File

@@ -3,10 +3,11 @@
public class ReportViewModel
{
public List<CostForVehicleByMonth> CostForVehicleByMonth { get; set; } = new List<CostForVehicleByMonth>();
public List<CostForVehicleByMonth> FuelMileageForVehicleByMonth { get; set; } = new List<CostForVehicleByMonth>();
public MPGForVehicleByMonth FuelMileageForVehicleByMonth { get; set; } = new MPGForVehicleByMonth();
public CostMakeUpForVehicle CostMakeUpForVehicle { get; set; } = new CostMakeUpForVehicle();
public ReminderMakeUpForVehicle ReminderMakeUpForVehicle { get; set; } = new ReminderMakeUpForVehicle();
public List<int> Years { get; set; } = new List<int>();
public List<UserCollaborator> Collaborators { get; set; } = new List<UserCollaborator>();
public bool CustomWidgetsConfigured { get; set; } = false;
}
}

View File

@@ -4,6 +4,7 @@
{
public Vehicle VehicleData { get; set; }
public List<GenericReportModel> VehicleHistory { get; set; }
public ReportParameter ReportParameters { get; set; }
public string Odometer { get; set; }
public string MPG { get; set; }
public decimal TotalCost { get; set; }

9
Models/SearchResult.cs Normal file
View File

@@ -0,0 +1,9 @@
namespace CarCareTracker.Models
{
public class SearchResult
{
public int Id { get; set; }
public ImportMode RecordType { get; set; }
public string Description { get; set; }
}
}

View File

@@ -15,6 +15,7 @@
public List<string> Tags { get; set; } = new List<string>();
public List<ExtraField> ExtraFields { get; set; } = new List<ExtraField>();
public List<SupplyUsageHistory> RequisitionHistory { get; set; } = new List<SupplyUsageHistory>();
public List<SupplyUsageHistory> DeletedRequisitionHistory { get; set; } = new List<SupplyUsageHistory>();
public bool CopySuppliesAttachment { get; set; } = false;
public ServiceRecord ToServiceRecord() { return new ServiceRecord {
Id = Id,

View File

@@ -6,6 +6,9 @@
public class ImportModel
{
public string Date { get; set; }
public string Day { get; set; }
public string Month { get; set; }
public string Year { get; set; }
public string DateCreated { get; set; }
public string DateModified { get; set; }
public string Type { get; set; }
@@ -25,6 +28,7 @@
public string PartSupplier { get; set; }
public string PartQuantity { get; set; }
public string Tags { get; set; }
public Dictionary<string,string> ExtraFields {get;set;}
}
public class SupplyRecordExportModel
@@ -37,9 +41,9 @@
public string Cost { get; set; }
public string Notes { get; set; }
public string Tags { get; set; }
public List<ExtraField> ExtraFields { get; set; }
}
public class ServiceRecordExportModel
public class GenericRecordExportModel
{
public string Date { get; set; }
public string Odometer { get; set; }
@@ -47,6 +51,7 @@
public string Notes { get; set; }
public string Cost { get; set; }
public string Tags { get; set; }
public List<ExtraField> ExtraFields { get; set; }
}
public class OdometerRecordExportModel
{
@@ -55,6 +60,7 @@
public string Odometer { get; set; }
public string Notes { get; set; }
public string Tags { get; set; }
public List<ExtraField> ExtraFields { get; set; }
}
public class TaxRecordExportModel
{
@@ -63,6 +69,7 @@
public string Notes { get; set; }
public string Cost { get; set; }
public string Tags { get; set; }
public List<ExtraField> ExtraFields { get; set; }
}
public class GasRecordExportModel
{
@@ -75,6 +82,7 @@
public string MissedFuelUp { get; set; }
public string Notes { get; set; }
public string Tags { get; set; }
public List<ExtraField> ExtraFields { get; set; }
}
public class ReminderExportModel
{
@@ -82,6 +90,8 @@
public string Urgency { get; set; }
public string Metric { get; set; }
public string Notes { get; set; }
public string DueDate { get; set; }
public string DueOdometer { get; set; }
}
public class PlanRecordExportModel
{
@@ -93,6 +103,6 @@
public string Priority { get; set; }
public string Progress { get; set; }
public string Cost { get; set; }
public List<ExtraField> ExtraFields { get; set; }
}
}

View File

@@ -0,0 +1,12 @@
namespace CarCareTracker.Models
{
public class VehicleRecords
{
public List<ServiceRecord> ServiceRecords { get; set; } = new List<ServiceRecord>();
public List<CollisionRecord> CollisionRecords { get; set; } = new List<CollisionRecord>();
public List<UpgradeRecord> UpgradeRecords { get; set; } = new List<UpgradeRecord>();
public List<GasRecord> GasRecords { get; set; } = new List<GasRecord>();
public List<TaxRecord> TaxRecords { get; set; } = new List<TaxRecord>();
public List<OdometerRecord> OdometerRecords { get; set; } = new List<OdometerRecord>();
}
}

10
Models/Sponsors.cs Normal file
View File

@@ -0,0 +1,10 @@
namespace CarCareTracker.Models
{
public class Sponsors
{
public List<string> LifeTime { get; set; } = new List<string>();
public List<string> Bronze { get; set; } = new List<string>();
public List<string> Silver { get; set; } = new List<string>();
public List<string> Gold { get; set; } = new List<string>();
}
}

View File

@@ -0,0 +1,11 @@
namespace CarCareTracker.Models
{
public class SupplyAvailability
{
public bool Missing { get; set; }
public string Description { get; set; } = string.Empty;
public decimal Required { get; set; }
public decimal InStock { get; set; }
public bool Insufficient { get { return Required > InStock; } }
}
}

View File

@@ -0,0 +1,8 @@
namespace CarCareTracker.Models
{
public class SupplyRequisitionHistory
{
public string CostInputId { get; set; }
public List<SupplyUsageHistory> RequisitionHistory { get; set; } = new List<SupplyUsageHistory>();
}
}

View File

@@ -0,0 +1,8 @@
namespace CarCareTracker.Models
{
public class SupplyStore
{
public string Tab { get; set; }
public bool AdditionalSupplies { get; set; }
}
}

View File

@@ -1,6 +1,7 @@
namespace CarCareTracker.Models
{
public class SupplyUsageHistory {
public int Id { get; set; }
public DateTime Date { get; set; }
public string PartNumber { get; set; }
public string Description { get; set; }

View File

@@ -0,0 +1,8 @@
namespace CarCareTracker.Models
{
public class SupplyUsageViewModel
{
public List<SupplyRecord> Supplies { get; set; } = new List<SupplyRecord>();
public List<SupplyUsage> Usage { get; set; } = new List<SupplyUsage>();
}
}

12
Models/Translations.cs Normal file
View File

@@ -0,0 +1,12 @@
namespace CarCareTracker.Models
{
public class Translations
{
public List<string> Africa { get; set; } = new List<string>();
public List<string> Asia { get; set; } = new List<string>();
public List<string> Europe { get; set; } = new List<string>();
public List<string> NorthAmerica { get; set; } = new List<string>();
public List<string> SouthAmerica { get; set; } = new List<string>();
public List<string> Oceania { get; set; } = new List<string>();
}
}

View File

@@ -15,6 +15,7 @@
public List<string> Tags { get; set; } = new List<string>();
public List<ExtraField> ExtraFields { get; set; } = new List<ExtraField>();
public List<SupplyUsageHistory> RequisitionHistory { get; set; } = new List<SupplyUsageHistory>();
public List<SupplyUsageHistory> DeletedRequisitionHistory { get; set; } = new List<SupplyUsageHistory>();
public bool CopySuppliesAttachment { get; set; } = false;
public UpgradeRecord ToUpgradeRecord() { return new UpgradeRecord {
Id = Id,

View File

@@ -3,25 +3,31 @@
public class UserConfig
{
public bool UseDarkMode { get; set; }
public bool UseSystemColorMode { get; set; }
public bool EnableCsvImports { get; set; }
public bool UseMPG { get; set; }
public bool UseDescending { get; set; }
public bool EnableAuth { get; set; }
public bool DisableRegistration { get; set; }
public bool EnableRootUserOIDC { get; set; }
public bool HideZero { get; set; }
public bool UseUKMPG {get;set;}
public bool UseThreeDecimalGasCost { get; set; }
public bool UseThreeDecimalGasConsumption { get; set; }
public bool UseMarkDownOnSavedNotes { get; set; }
public bool EnableAutoReminderRefresh { get; set; }
public bool EnableAutoOdometerInsert { get; set; }
public bool EnableShopSupplies { get; set; }
public bool EnableExtraFieldColumns { get; set; }
public bool HideSoldVehicles { get; set; }
public bool AutomaticDecimalFormat { get; set; }
public string PreferredGasUnit { get; set; } = string.Empty;
public string PreferredGasMileageUnit { get; set; } = string.Empty;
public List<UserColumnPreference> UserColumnPreferences { get; set; } = new List<UserColumnPreference>();
public ReminderUrgencyConfig ReminderUrgencyConfig { get; set; } = new ReminderUrgencyConfig();
public string UserNameHash { get; set; }
public string UserPasswordHash { get; set;}
public string DefaultReminderEmail { get; set; } = string.Empty;
public string UserLanguage { get; set; } = "en_US";
public List<ImportMode> VisibleTabs { get; set; } = new List<ImportMode>() {
ImportMode.Dashboard,
@@ -31,7 +37,21 @@
ImportMode.UpgradeRecord,
ImportMode.TaxRecord,
ImportMode.ReminderRecord,
ImportMode.NoteRecord};
ImportMode.NoteRecord
};
public ImportMode DefaultTab { get; set; } = ImportMode.Dashboard;
public List<ImportMode> TabOrder { get; set; } = new List<ImportMode>() {
ImportMode.Dashboard,
ImportMode.PlanRecord,
ImportMode.OdometerRecord,
ImportMode.ServiceRecord,
ImportMode.RepairRecord,
ImportMode.UpgradeRecord,
ImportMode.GasRecord,
ImportMode.SupplyRecord,
ImportMode.TaxRecord,
ImportMode.NoteRecord,
ImportMode.ReminderRecord
};
}
}

View File

@@ -13,7 +13,9 @@
public decimal PurchasePrice { get; set; }
public decimal SoldPrice { get; set; }
public bool IsElectric { get; set; } = false;
public bool IsDiesel { get; set; } = false;
public bool UseHours { get; set; } = false;
public bool OdometerOptional { get; set; } = false;
public List<ExtraField> ExtraFields { get; set; } = new List<ExtraField>();
public List<string> Tags { get; set; } = new List<string>();
public bool HasOdometerAdjustment { get; set; } = false;
@@ -25,5 +27,10 @@
/// Primarily used for vehicles where the odometer does not reflect actual mileage.
/// </summary>
public string OdometerDifference { get; set; } = "0";
public List<DashboardMetric> DashboardMetrics { get; set; } = new List<DashboardMetric>();
/// <summary>
/// Determines what is displayed in place of the license plate.
/// </summary>
public string VehicleIdentifier { get; set; } = "LicensePlate";
}
}

View File

@@ -10,10 +10,18 @@
public string LicensePlate { get; set; }
public string SoldDate { get; set; }
public bool IsElectric { get; set; } = false;
public bool IsDiesel { get; set; } = false;
public bool UseHours { get; set; } = false;
public bool OdometerOptional { get; set; } = false;
public List<ExtraField> ExtraFields { get; set; } = new List<ExtraField>();
public List<string> Tags { get; set; } = new List<string>();
public int LastReportedMileage;
public bool HasReminders = false;
public string VehicleIdentifier { get; set; } = "LicensePlate";
//Dashboard Metric Attributes
public List<DashboardMetric> DashboardMetrics { get; set; } = new List<DashboardMetric>();
public int LastReportedMileage { get; set; }
public bool HasReminders { get; set; } = false;
public decimal CostPerMile { get; set; }
public decimal TotalCost { get; set; }
public string DistanceUnit { get; set; }
}
}

View File

@@ -65,8 +65,8 @@ builder.Services.AddSingleton<IFileHelper, FileHelper>();
builder.Services.AddSingleton<IGasHelper, GasHelper>();
builder.Services.AddSingleton<IReminderHelper, ReminderHelper>();
builder.Services.AddSingleton<IReportHelper, ReportHelper>();
builder.Services.AddSingleton<IMailHelper, MailHelper>();
builder.Services.AddSingleton<IConfigHelper, ConfigHelper>();
builder.Services.AddSingleton<IMailHelper, MailHelper>();
builder.Services.AddSingleton<ITranslationHelper, TranslationHelper>();
//configure logic

View File

@@ -20,38 +20,32 @@ Try it out before you download it! The live demo resets every 20 minutes.
## Download
LubeLogger is available as both a Docker Image and a Windows Standalone Executable.
Read this [Getting Started Guide](https://docs.lubelogger.com/Getting%20Started) on how to download either of them
Read this [Getting Started Guide](https://docs.lubelogger.com/Installation/Getting%20Started) on how to download either of them
### Docker Setup (Manual Build for Advanced Users)
1. Install Docker
2. Clone this repo
3. CHECK culture in .env file, default is en_US, also setup SMTP for user management if you want that.
4. Run `docker build -t lubelogger -f Dockerfile .`
5. CHECK docker-compose.yml and make sure the mounting directories look correct.
6. If using traefik, use docker-compose.traefik.yml
7. Run `docker-compose up`
### Kubernetes Deployment
[Helm Chart](https://artifacthub.io/packages/helm/anza-labs/lubelogger) provided by [Anza-Labs](https://github.com/anza-labs)
### Need Help?
[Documentation](https://docs.lubelogger.com/)
[Troubleshooting Guide](https://docs.lubelogger.com/Troubleshooting)
[Troubleshooting Guide](https://docs.lubelogger.com/Installation/Troubleshooting)
[Search Existing Issues](https://github.com/hargata/lubelog/issues)
## Dependencies
- Bootstrap
- LiteDB
- Npgsql
- Bootstrap-DatePicker
- SweetAlert2
- CsvHelper
- Chart.js
- Drawdown
- [Bootstrap](https://github.com/twbs/bootstrap)
- [LiteDB](https://github.com/mbdavid/litedb)
- [Npgsql](https://github.com/npgsql/npgsql)
- [Bootstrap-DatePicker](https://github.com/uxsolutions/bootstrap-datepicker)
- [SweetAlert2](https://github.com/sweetalert2/sweetalert2)
- [CsvHelper](https://github.com/JoshClose/CsvHelper)
- [Chart.js](https://github.com/chartjs/Chart.js)
- [Drawdown](https://github.com/adamvleggett/drawdown)
- [MailKit](https://github.com/jstedfast/MailKit)
- [Masonry](https://github.com/desandro/masonry)
## License
LubeLogger utilizes a dual-licensing model, see [License](/LICENSE) for more information
MIT
## Support
Support this project by [Subscribing on Patreon](https://patreon.com/LubeLogger) or [Making a Donation](https://buy.stripe.com/aEU9Egc8DdMc9bO144)
Note: Commercial users are required to maintain an active Patreon subscripton to be compliant with our licensing model.
Support this project by [Subscribing on Patreon](https://patreon.com/LubeLogger) or [Making a Donation](https://buy.stripe.com/aEU9Egc8DdMc9bO144)

View File

@@ -1,5 +1,5 @@
@{
ViewData["Title"] = "LubeLogger API";
ViewData["Title"] = "API";
}
<div class="row">
<div class="d-flex justify-content-center">
@@ -26,7 +26,7 @@
<h6>Parameters</h6>
</div>
</div>
<div class="row">
<div class="row api-method">
<div class="col-1">
GET
</div>
@@ -40,7 +40,88 @@
No Params
</div>
</div>
<div class="row">
<div class="row api-method">
<div class="col-1">
GET
</div>
<div class="col-5 copyable">
<code>/api/vehicle/info</code>
</div>
<div class="col-3">
Returns details for list of vehicles or specific vehicle
</div>
<div class="col-3">
VehicleId - Id of Vehicle(optional)
</div>
</div>
<div class="row api-method">
<div class="col-1">
GET
</div>
<div class="col-5 copyable">
<code>/api/vehicle/adjustedodometer</code>
</div>
<div class="col-3">
Returns odometer reading with adjustments applied
</div>
<div class="col-3">
vehicleId - Id of Vehicle
<br />
odometer - Unadjusted odometer
</div>
</div>
<div class="row api-method">
<div class="col-1">
GET
</div>
<div class="col-5 copyable">
<code>/api/vehicle/odometerrecords</code>
</div>
<div class="col-3">
Returns a list of odometer records for the vehicle
</div>
<div class="col-3">
vehicleId - Id of Vehicle
</div>
</div>
<div class="row api-method">
<div class="col-1">
GET
</div>
<div class="col-5 copyable">
<code>/api/vehicle/odometerrecords/latest</code>
</div>
<div class="col-3">
Returns last reported odometer for the vehicle
</div>
<div class="col-3">
vehicleId - Id of Vehicle
</div>
</div>
<div class="row api-method">
<div class="col-1">
POST
</div>
<div class="col-5 copyable">
<code>/api/vehicle/odometerrecords/add</code>
</div>
<div class="col-3">
Adds Odometer Record to the vehicle
</div>
<div class="col-3">
vehicleId - Id of Vehicle
<br />
Body(form-data): {<br />
date - Date to be entered<br />
initialOdometer - Initial Odometer reading(optional)<br />
odometer - Odometer reading<br />
notes - notes(optional)<br />
tags - tags separated by space(optional)<br />
extrafields - <a class="link-body-emphasis link-offset-2 link-underline-opacity-25 link-underline-opacity-100-hover reminder-calendar-item" onclick="showExtraFieldsInfo()">extrafields(optional)</a><br />
}
</div>
</div>
<div class="row api-method">
<div class="col-1">
GET
</div>
@@ -54,7 +135,7 @@
vehicleId - Id of Vehicle
</div>
</div>
<div class="row">
<div class="row api-method">
<div class="col-1">
POST
</div>
@@ -73,10 +154,12 @@
description - Description<br/>
cost - Cost<br />
notes - notes(optional)<br />
tags - tags separated by space(optional)<br />
extrafields - <a class="link-body-emphasis link-offset-2 link-underline-opacity-25 link-underline-opacity-100-hover reminder-calendar-item" onclick="showExtraFieldsInfo()">extrafields(optional)</a><br />
}
</div>
</div>
<div class="row">
<div class="row api-method">
<div class="col-1">
GET
</div>
@@ -90,7 +173,7 @@
vehicleId - Id of Vehicle
</div>
</div>
<div class="row">
<div class="row api-method">
<div class="col-1">
POST
</div>
@@ -109,10 +192,12 @@
description - Description<br />
cost - Cost<br />
notes - notes(optional)<br />
tags - tags separated by space(optional)<br />
extrafields - <a class="link-body-emphasis link-offset-2 link-underline-opacity-25 link-underline-opacity-100-hover reminder-calendar-item" onclick="showExtraFieldsInfo()">extrafields(optional)</a><br />
}
</div>
</div>
<div class="row">
<div class="row api-method">
<div class="col-1">
GET
</div>
@@ -126,7 +211,7 @@
vehicleId - Id of Vehicle
</div>
</div>
<div class="row">
<div class="row api-method">
<div class="col-1">
POST
</div>
@@ -145,10 +230,12 @@
description - Description<br />
cost - Cost<br />
notes - notes(optional)<br />
tags - tags separated by space(optional)<br />
extrafields - <a class="link-body-emphasis link-offset-2 link-underline-opacity-25 link-underline-opacity-100-hover reminder-calendar-item" onclick="showExtraFieldsInfo()">extrafields(optional)</a><br />
}
</div>
</div>
<div class="row">
<div class="row api-method">
<div class="col-1">
GET
</div>
@@ -162,7 +249,7 @@
vehicleId - Id of Vehicle
</div>
</div>
<div class="row">
<div class="row api-method">
<div class="col-1">
POST
</div>
@@ -180,10 +267,12 @@
description - Description<br />
cost - Cost<br />
notes - notes(optional)<br />
tags - tags separated by space(optional)<br />
extrafields - <a class="link-body-emphasis link-offset-2 link-underline-opacity-25 link-underline-opacity-100-hover reminder-calendar-item" onclick="showExtraFieldsInfo()">extrafields(optional)</a><br />
}
</div>
</div>
<div class="row">
<div class="row api-method">
<div class="col-1">
GET
</div>
@@ -201,7 +290,7 @@
useUKMPG(bool) - Use UK Imperial Calculation
</div>
</div>
<div class="row">
<div class="row api-method">
<div class="col-1">
POST
</div>
@@ -222,10 +311,12 @@
isFillToFull(bool) - Filled To Full<br />
missedFuelUp(bool) - Missed Fuel Up<br />
notes - notes(optional)<br />
tags - tags separated by space(optional)<br />
extrafields - <a class="link-body-emphasis link-offset-2 link-underline-opacity-25 link-underline-opacity-100-hover reminder-calendar-item" onclick="showExtraFieldsInfo()">extrafields(optional)</a><br />
}
</div>
</div>
<div class="row">
<div class="row api-method">
<div class="col-1">
GET
</div>
@@ -241,7 +332,7 @@
</div>
@if (User.IsInRole(nameof(UserData.IsRootUser)))
{
<div class="row">
<div class="row api-method">
<div class="col-1">
GET
</div>
@@ -256,7 +347,7 @@
urgencies[]=[NotUrgent,Urgent,VeryUrgent,PastDue]
</div>
</div>
<div class="row">
<div class="row api-method">
<div class="col-1">
GET
</div>
@@ -270,58 +361,31 @@
No Params(must be root user)
</div>
</div>
<div class="row api-method">
<div class="col-1">
GET
</div>
<div class="col-5 copyable">
<code>/api/cleanup</code>
</div>
<div class="col-3">
Clears out temp files. Deep clean will also delete unlinked thumbnails and documents. Returns number of deleted files.
</div>
<div class="col-3">
(must be root user)<br />
deepClean(bool) - Perform deep clean
</div>
</div>
}
<div class="row">
<div class="col-1">
GET
</div>
<div class="col-5 copyable">
<code>/api/vehicle/odometerrecords</code>
</div>
<div class="col-3">
Returns a list of odometer records for the vehicle
</div>
<div class="col-3">
vehicleId - Id of Vehicle
</div>
</div>
<div class="row">
<div class="col-1">
GET
</div>
<div class="col-5 copyable">
<code>/api/vehicle/odometerrecords/latest</code>
</div>
<div class="col-3">
Returns last reported odometer for the vehicle
</div>
<div class="col-3">
vehicleId - Id of Vehicle
</div>
</div>
<div class="row">
<div class="col-1">
POST
</div>
<div class="col-5 copyable">
<code>/api/vehicle/odometerrecords/add</code>
</div>
<div class="col-3">
Adds Odometer Record to the vehicle
</div>
<div class="col-3">
vehicleId - Id of Vehicle
<br />
Body(form-data): {<br />
date - Date to be entered<br />
initialOdometer - Initial Odometer reading(optional)<br />
odometer - Odometer reading<br />
notes - notes(optional)<br />
}
</div>
</div>
<script>
$('.copyable').on('click', function (e) {
copyToClipboard(e.currentTarget);
})
function showExtraFieldsInfo(){
Swal.fire({
title: "Format for Extra Fields",
html: "extrafields[i][name] - Name of Extra Field<br/>extrafields[i][value] - Value of Extra Field<br/>i is an integer that starts at 0 and increments with each extrafield",
icon: "info"
});
}
</script>

View File

@@ -1,13 +1,13 @@
@using CarCareTracker.Helper
@{
ViewData["Title"] = "Admin";
ViewData["Title"] = "Admin Panel";
}
@inject IConfiguration config;
@inject IConfigHelper config
@inject ITranslationHelper translator
@{
bool emailServerIsSetup = true;
var mailConfig = config.GetSection("MailConfig").Get<MailConfig>();
var userLanguage = config[nameof(UserConfig.UserLanguage)] ?? "en_US";
var mailConfig = config.GetMailConfig();
var userLanguage = config.GetServerLanguage();
if (mailConfig is null || string.IsNullOrWhiteSpace(mailConfig.EmailServer))
{
emailServerIsSetup = false;
@@ -25,44 +25,17 @@
</div>
<hr />
<div class="row">
<div class="col-md-5 col-12">
<span class="lead">@translator.Translate(userLanguage, "Tokens")</span>
<hr />
<div class="col-12">
<div class="row">
<div class="col-6">
<button onclick="generateNewToken()" class="btn btn-primary btn-md mt-1 mb-1"><i class="bi bi-pencil-square me-2"></i>@translator.Translate(userLanguage, "Generate User Token")</button>
<div class="col-2 d-flex align-items-center">
<span class="lead">@translator.Translate(userLanguage, "Users")</span>
</div>
<div class="col-6 d-flex align-items-center">
<div class="form-check form-switch">
<input class="form-check-input" type="checkbox" role="switch" id="enableAutoNotify" @(emailServerIsSetup ? "checked" : "disabled")>
<label class="form-check-label" for="enableAutoNotify">@translator.Translate(userLanguage, "Auto Notify(via Email)")</label>
</div>
<div class="col-10 d-flex align-items-center justify-content-end">
<button onclick="showTokenModal()" class="btn btn-primary btn-md mt-1 mb-1">
<i class="bi bi-pencil-square me-2"></i>@translator.Translate(userLanguage, "Manage Tokens")
</button>
</div>
</div>
<table class="table table-hover">
<thead class="sticky-top">
<tr class="d-flex">
<th scope="col" class="col-4">@translator.Translate(userLanguage, "Token")</th>
<th scope="col" class="col-6">@translator.Translate(userLanguage, "Issued To")</th>
<th scope="col" class="col-2">@translator.Translate(userLanguage, "Delete")</th>
</tr>
</thead>
<tbody>
@foreach (Token token in Model.Tokens)
{
<tr class="d-flex">
<td class="col-4" style="cursor:pointer;" onclick="copyToClipboard(this)">@token.Body</td>
<td class="col-6 text-truncate">@token.EmailAddress</td>
<td class="col-2">
<button type="button" class="btn btn-danger" onclick="deleteToken(@token.Id, this)"><i class="bi bi-trash"></i></button>
</td>
</tr>
}
</tbody>
</table>
</div>
<div class="col-12 col-md-7">
<span class="lead">@translator.Translate(userLanguage, "Users")</span>
<hr />
<table class="table table-hover">
<thead class="sticky-top">
@@ -73,59 +46,120 @@
<th scope="col" class="col-2">@translator.Translate(userLanguage, "Delete")</th>
</tr>
</thead>
<tbody>
@foreach (UserData userData in Model.Users)
{
<tr class="d-flex" style="cursor:pointer;">
<td class="col-4">@userData.UserName</td>
<td class="col-4">@userData.EmailAddress</td>
<td class="col-2"><input class="form-check-input" type="checkbox" value="" onchange="updateUserAdmin(@userData.Id, this)" @(userData.IsAdmin ? "checked" : "")/></td>
<td class="col-2"><button type="button" class="btn btn-danger" onclick="deleteUser(@userData.Id, this)"><i class="bi bi-trash"></i></button></td>
</tr>
}
<tbody id="userTable">
@await Html.PartialAsync("_Users", Model.Users)
</tbody>
</table>
</div>
</div>
<div class="modal fade" data-bs-focus="false" id="tokenModal" tabindex="-1" role="dialog" aria-hidden="true">
<div class="modal-dialog modal-lg" role="document">
<div class="modal-content">
<div class="modal-header">
<div class="d-flex align-items-center">
<span class="lead">@translator.Translate(userLanguage, "Tokens")</span>
</div>
<div class="d-flex align-items-center ms-auto">
<div class="btn-group">
<button onclick="generateNewToken()" class="btn btn-primary btn-md mt-1 mb-1">
<i class="bi bi-pencil-square me-2"></i>@translator.Translate(userLanguage, "Generate")
</button>
<button class="btn btn-outline-primary btn-md mt-1 mb-1" @(emailServerIsSetup ? "" : "disabled") onclick="toggleAutoNotify(event)">
<div class="form-check">
<input class="form-check-input" type="checkbox" role="switch" id="enableAutoNotify" @(emailServerIsSetup ? "checked" : "disabled")>
<label class="form-check-label" for="enableAutoNotify">@translator.Translate(userLanguage, "Notify")</label>
</div>
</button>
</div>
<button class="btn btn-close btn-md mt-1 mb-1 ms-2" onclick="hideTokenModal()" aria-label="Close"></button>
</div>
</div>
<div class="modal-body">
<table class="table table-hover">
<thead class="sticky-top">
<tr class="d-flex">
<th scope="col" class="col-4">@translator.Translate(userLanguage, "Token")</th>
<th scope="col" class="col-6">@translator.Translate(userLanguage, "Issued To")</th>
<th scope="col" class="col-2">@translator.Translate(userLanguage, "Delete")</th>
</tr>
</thead>
<tbody id="tokenTable">
@await Html.PartialAsync("_Tokens", Model.Tokens)
</tbody>
</table>
</div>
</div>
</div>
</div>
</div>
<script>
function updateUserAdmin(userId, sender){
function showTokenModal() {
$("#tokenModal").modal('show');
}
function hideTokenModal() {
$("#tokenModal").modal('hide');
}
function reloadTokenTable() {
$.get('/Admin/GetTokenPartialView', function (data) {
$("#tokenTable").html(data);
});
}
function reloadUserTable() {
$.get('/Admin/GetUserPartialView', function (data) {
$("#userTable").html(data);
});
}
function updateUserAdmin(userId, sender) {
var isChecked = $(sender).is(":checked");
$.post('/Admin/UpdateUserAdminStatus', { userId: userId, isAdmin: isChecked }, function (data) {
if (data){
reloadPage();
if (data) {
reloadUserTable();
} else {
errorToast(genericErrorMessage());
}
});
}
function reloadPage() {
window.location.reload();
function toggleAutoNotify(e) {
if (!$("#enableAutoNotify").attr("disabled")) {
if ($(e.target).hasClass('btn')) {
$("#enableAutoNotify").trigger('click');
}
}
}
function deleteToken(tokenId) {
$.post(`/Admin/DeleteToken?tokenId=${tokenId}`, function (data) {
if (data) {
reloadPage();
reloadTokenTable();
} else {
errorToast(genericErrorMessage());
}
});
}
function deleteUser(userId) {
$.post(`/Admin/DeleteUser?userId=${userId}`, function (data) {
if (data) {
reloadPage();
} else {
errorToast(genericErrorMessage());
Swal.fire({
title: "Confirm Deletion?",
text: "Deleted Users cannot be restored.",
showCancelButton: true,
confirmButtonText: "Delete",
confirmButtonColor: "#dc3545"
}).then((result) => {
if (result.isConfirmed) {
$.post(`/Admin/DeleteUser?userId=${userId}`, function (data) {
if (data) {
reloadUserTable();
} else {
errorToast(genericErrorMessage());
}
});
}
})
});
}
function generateNewToken() {
Swal.fire({
title: 'Generate Token',
html: `
<input type="text" id="inputEmail" class="swal2-input" placeholder="Email Address">
`,
<input type="text" id="inputEmail" class="swal2-input" placeholder="Email Address" onkeydown="handleSwalEnter(event)">
`,
confirmButtonText: 'Generate',
focusConfirm: false,
preConfirm: () => {
@@ -140,7 +174,7 @@
var autoNotify = $("#enableAutoNotify").is(":checked");
$.get('/Admin/GenerateNewToken', { emailAddress: result.value.emailAddress, autoNotify: autoNotify }, function (data) {
if (data.success) {
reloadPage();
reloadTokenTable();
} else {
errorToast(data.message)
}

View File

@@ -0,0 +1,12 @@
@using CarCareTracker.Helper
@model List<Token>
@foreach (Token token in Model)
{
<tr class="d-flex">
<td class="col-4" style="cursor:pointer;" onclick="copyToClipboard(this)">@token.Body</td>
<td class="col-6 text-truncate">@StaticHelper.TruncateStrings(token.EmailAddress)</td>
<td class="col-2">
<button type="button" class="btn btn-danger" onclick="deleteToken(@token.Id, this)"><i class="bi bi-trash"></i></button>
</td>
</tr>
}

Some files were not shown because too many files have changed in this diff Show More