Compare commits

...

186 Commits

Author SHA1 Message Date
DESKTOP-T0O5CDB\DESK-555BD
2f1b585893 all kinds of updates to make decimal odometer input possible. 2024-11-14 09:07:00 -07:00
DESKTOP-T0O5CDB\DESK-555BD
92c0c103be Merge branch 'main' into Hargata/decimal.odo
# Conflicts:
#	Controllers/APIController.cs
#	Controllers/VehicleController.cs
#	Helper/ReminderHelper.cs
2024-11-14 08:25:48 -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
DESKTOP-GENO133\IvanPlex
4dc896084e odometer input in decimal form. 2024-03-25 07:03:16 -06:00
198 changed files with 7606 additions and 3820 deletions

6
.env
View File

@@ -7,6 +7,6 @@ MailConfig__Username=""
MailConfig__Password="" MailConfig__Password=""
LOGGING__LOGLEVEL__DEFAULT=Error LOGGING__LOGLEVEL__DEFAULT=Error
# * Uncoment this line if you use postgresSQL as database backend. # This file is provided as a GUIDELINE ONLY
# * Check the docker-compose.postgresql.yml file # Use the LubeLogger Configurator to configure your environment variables
#POSTGRES_CONNECTION="Host=postgres;Username=lubelogger;Password=lubepass;Database=lubelogger;" # 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.

View File

@@ -10,7 +10,7 @@ assignees: ''
**Checklist** **Checklist**
Please make sure you have performed the following steps before opening a new bug ticket, change `[ ]` to `[x]` to mark it as done 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. - [ ] I have searched through existing issues.
**Description** **Description**

1
.gitignore vendored
View File

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

View File

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

View File

@@ -3,7 +3,7 @@ Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 17 # Visual Studio Version 17
VisualStudioVersion = 17.8.34330.188 VisualStudioVersion = 17.8.34330.188
MinimumVisualStudioVersion = 10.0.40219.1 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 EndProject
Global Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution GlobalSection(SolutionConfigurationPlatforms) = preSolution

View File

@@ -99,6 +99,50 @@ namespace CarCareTracker.Controllers
} }
return Json(result); 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))] [TypeFilter(typeof(CollaboratorFilter))]
[HttpGet] [HttpGet]
[Route("/api/vehicle/servicerecords")] [Route("/api/vehicle/servicerecords")]
@@ -149,7 +193,8 @@ namespace CarCareTracker.Controllers
Description = input.Description, Description = input.Description,
Notes = string.IsNullOrWhiteSpace(input.Notes) ? "" : input.Notes, Notes = string.IsNullOrWhiteSpace(input.Notes) ? "" : input.Notes,
Cost = decimal.Parse(input.Cost), Cost = decimal.Parse(input.Cost),
ExtraFields = input.ExtraFields ExtraFields = input.ExtraFields,
Tags = string.IsNullOrWhiteSpace(input.Tags) ? new List<string>() : input.Tags.Split(' ').Distinct().ToList()
}; };
_serviceRecordDataAccess.SaveServiceRecordToVehicle(serviceRecord); _serviceRecordDataAccess.SaveServiceRecordToVehicle(serviceRecord);
if (_config.GetUserConfig(User).EnableAutoOdometerInsert) if (_config.GetUserConfig(User).EnableAutoOdometerInsert)
@@ -226,7 +271,8 @@ namespace CarCareTracker.Controllers
Description = input.Description, Description = input.Description,
Notes = string.IsNullOrWhiteSpace(input.Notes) ? "" : input.Notes, Notes = string.IsNullOrWhiteSpace(input.Notes) ? "" : input.Notes,
Cost = decimal.Parse(input.Cost), Cost = decimal.Parse(input.Cost),
ExtraFields = input.ExtraFields ExtraFields = input.ExtraFields,
Tags = string.IsNullOrWhiteSpace(input.Tags) ? new List<string>() : input.Tags.Split(' ').Distinct().ToList()
}; };
_collisionRecordDataAccess.SaveCollisionRecordToVehicle(repairRecord); _collisionRecordDataAccess.SaveCollisionRecordToVehicle(repairRecord);
if (_config.GetUserConfig(User).EnableAutoOdometerInsert) if (_config.GetUserConfig(User).EnableAutoOdometerInsert)
@@ -303,7 +349,8 @@ namespace CarCareTracker.Controllers
Description = input.Description, Description = input.Description,
Notes = string.IsNullOrWhiteSpace(input.Notes) ? "" : input.Notes, Notes = string.IsNullOrWhiteSpace(input.Notes) ? "" : input.Notes,
Cost = decimal.Parse(input.Cost), Cost = decimal.Parse(input.Cost),
ExtraFields = input.ExtraFields ExtraFields = input.ExtraFields,
Tags = string.IsNullOrWhiteSpace(input.Tags) ? new List<string>() : input.Tags.Split(' ').Distinct().ToList()
}; };
_upgradeRecordDataAccess.SaveUpgradeRecordToVehicle(upgradeRecord); _upgradeRecordDataAccess.SaveUpgradeRecordToVehicle(upgradeRecord);
if (_config.GetUserConfig(User).EnableAutoOdometerInsert) if (_config.GetUserConfig(User).EnableAutoOdometerInsert)
@@ -377,7 +424,8 @@ namespace CarCareTracker.Controllers
Description = input.Description, Description = input.Description,
Notes = string.IsNullOrWhiteSpace(input.Notes) ? "" : input.Notes, Notes = string.IsNullOrWhiteSpace(input.Notes) ? "" : input.Notes,
Cost = decimal.Parse(input.Cost), Cost = decimal.Parse(input.Cost),
ExtraFields = input.ExtraFields ExtraFields = input.ExtraFields,
Tags = string.IsNullOrWhiteSpace(input.Tags) ? new List<string>() : input.Tags.Split(' ').Distinct().ToList()
}; };
_taxRecordDataAccess.SaveTaxRecordToVehicle(taxRecord); _taxRecordDataAccess.SaveTaxRecordToVehicle(taxRecord);
StaticHelper.NotifyAsync(_config.GetWebHookUrl(), vehicleId, User.Identity.Name, $"Added Tax Record via API - Description: {taxRecord.Description}"); StaticHelper.NotifyAsync(_config.GetWebHookUrl(), vehicleId, User.Identity.Name, $"Added Tax Record via API - Description: {taxRecord.Description}");
@@ -461,7 +509,8 @@ namespace CarCareTracker.Controllers
Notes = string.IsNullOrWhiteSpace(input.Notes) ? "" : input.Notes, 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), 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 ExtraFields = input.ExtraFields,
Tags = string.IsNullOrWhiteSpace(input.Tags) ? new List<string>() : input.Tags.Split(' ').Distinct().ToList()
}; };
_odometerRecordDataAccess.SaveOdometerRecordToVehicle(odometerRecord); _odometerRecordDataAccess.SaveOdometerRecordToVehicle(odometerRecord);
StaticHelper.NotifyAsync(_config.GetWebHookUrl(), vehicleId, User.Identity.Name, $"Added Odometer Record via API - Mileage: {odometerRecord.Mileage.ToString()}"); StaticHelper.NotifyAsync(_config.GetWebHookUrl(), vehicleId, User.Identity.Name, $"Added Odometer Record via API - Mileage: {odometerRecord.Mileage.ToString()}");
@@ -542,7 +591,8 @@ namespace CarCareTracker.Controllers
MissedFuelUp = bool.Parse(input.MissedFuelUp), MissedFuelUp = bool.Parse(input.MissedFuelUp),
Notes = string.IsNullOrWhiteSpace(input.Notes) ? "" : input.Notes, Notes = string.IsNullOrWhiteSpace(input.Notes) ? "" : input.Notes,
Cost = decimal.Parse(input.Cost), Cost = decimal.Parse(input.Cost),
ExtraFields = input.ExtraFields ExtraFields = input.ExtraFields,
Tags = string.IsNullOrWhiteSpace(input.Tags) ? new List<string>() : input.Tags.Split(' ').Distinct().ToList()
}; };
_gasRecordDataAccess.SaveGasRecordToVehicle(gasRecord); _gasRecordDataAccess.SaveGasRecordToVehicle(gasRecord);
if (_config.GetUserConfig(User).EnableAutoOdometerInsert) if (_config.GetUserConfig(User).EnableAutoOdometerInsert)
@@ -594,6 +644,7 @@ namespace CarCareTracker.Controllers
{ {
var vehicles = _dataAccess.GetVehicles(); var vehicles = _dataAccess.GetVehicles();
List<OperationResponse> operationResponses = new List<OperationResponse>(); List<OperationResponse> operationResponses = new List<OperationResponse>();
var defaultEmailAddress = _config.GetUserConfig(User).DefaultReminderEmail;
foreach(Vehicle vehicle in vehicles) foreach(Vehicle vehicle in vehicles)
{ {
var vehicleId = vehicle.Id; var vehicleId = vehicle.Id;
@@ -609,6 +660,10 @@ namespace CarCareTracker.Controllers
//get list of recipients. //get list of recipients.
var userIds = _userAccessDataAccess.GetUserAccessByVehicleId(vehicleId).Select(x => x.Id.UserId); var userIds = _userAccessDataAccess.GetUserAccessByVehicleId(vehicleId).Select(x => x.Id.UserId);
List<string> emailRecipients = new List<string>(); List<string> emailRecipients = new List<string>();
if (!string.IsNullOrWhiteSpace(defaultEmailAddress))
{
emailRecipients.Add(defaultEmailAddress);
}
foreach (int userId in userIds) foreach (int userId in userIds)
{ {
var userData = _userRecordDataAccess.GetUserRecordById(userId); var userData = _userRecordDataAccess.GetUserRecordById(userId);
@@ -621,15 +676,19 @@ namespace CarCareTracker.Controllers
var result = _mailHelper.NotifyUserForReminders(vehicle, emailRecipients, results); var result = _mailHelper.NotifyUserForReminders(vehicle, emailRecipients, results);
operationResponses.Add(result); operationResponses.Add(result);
} }
if (operationResponses.All(x => x.Success)) if (!operationResponses.Any())
{ {
return Json(new OperationResponse { Success = true, Message = "Emails sent" }); return Json(new OperationResponse { Success = false, Message = "No Emails Sent, No Vehicles Available or No Recipients Configured" });
}
else if (operationResponses.All(x => x.Success))
{
return Json(new OperationResponse { Success = true, Message = $"Emails Sent({operationResponses.Count()})" });
} else if (operationResponses.All(x => !x.Success)) } else if (operationResponses.All(x => !x.Success))
{ {
return Json(new OperationResponse { Success = false, Message = "All emails failed, check SMTP settings" }); return Json(new OperationResponse { Success = false, Message = $"All Emails Failed({operationResponses.Count()}), Check SMTP Settings" });
} else } else
{ {
return Json(new OperationResponse { Success = true, Message = "Some emails sent, some failed, check recipient settings" }); return Json(new OperationResponse { Success = true, Message = $"Emails Sent({operationResponses.Count(x => x.Success)}), Emails Failed({operationResponses.Count(x => !x.Success)}), Check Recipient Settings" });
} }
} }
[Authorize(Roles = nameof(UserData.IsRootUser))] [Authorize(Roles = nameof(UserData.IsRootUser))]

View File

@@ -22,16 +22,47 @@ namespace CarCareTracker.Controllers
{ {
var viewModel = new AdminViewModel var viewModel = new AdminViewModel
{ {
Users = _loginLogic.GetAllUsers(), Users = _loginLogic.GetAllUsers().OrderBy(x=>x.Id).ToList(),
Tokens = _loginLogic.GetAllTokens() Tokens = _loginLogic.GetAllTokens()
}; };
return View(viewModel); 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) public IActionResult GenerateNewToken(string emailAddress, bool autoNotify)
{
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 = new OperationResponse { Success = true, Message = "Token Generated!" };
return Json(successResponse);
} else
{ {
var result = _loginLogic.GenerateUserToken(emailAddress, autoNotify); var result = _loginLogic.GenerateUserToken(emailAddress, autoNotify);
return Json(result); return Json(result);
} }
}
[HttpPost] [HttpPost]
public IActionResult DeleteToken(int tokenId) public IActionResult DeleteToken(int tokenId)
{ {

View File

@@ -92,5 +92,18 @@ namespace CarCareTracker.Controllers
} }
return Path.Combine("/", uploadDirectory, fileName); 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 IExtraFieldDataAccess _extraFieldDataAccess;
private readonly IReminderRecordDataAccess _reminderRecordDataAccess; private readonly IReminderRecordDataAccess _reminderRecordDataAccess;
private readonly IReminderHelper _reminderHelper; private readonly IReminderHelper _reminderHelper;
private readonly ITranslationHelper _translationHelper;
public HomeController(ILogger<HomeController> logger, public HomeController(ILogger<HomeController> logger,
IVehicleDataAccess dataAccess, IVehicleDataAccess dataAccess,
IUserLogic userLogic, IUserLogic userLogic,
@@ -31,7 +32,8 @@ namespace CarCareTracker.Controllers
IFileHelper fileHelper, IFileHelper fileHelper,
IExtraFieldDataAccess extraFieldDataAccess, IExtraFieldDataAccess extraFieldDataAccess,
IReminderRecordDataAccess reminderRecordDataAccess, IReminderRecordDataAccess reminderRecordDataAccess,
IReminderHelper reminderHelper) IReminderHelper reminderHelper,
ITranslationHelper translationHelper)
{ {
_logger = logger; _logger = logger;
_dataAccess = dataAccess; _dataAccess = dataAccess;
@@ -43,6 +45,7 @@ namespace CarCareTracker.Controllers
_reminderHelper = reminderHelper; _reminderHelper = reminderHelper;
_loginLogic = loginLogic; _loginLogic = loginLogic;
_vehicleLogic = vehicleLogic; _vehicleLogic = vehicleLogic;
_translationHelper = translationHelper;
} }
private int GetUserID() private int GetUserID()
{ {
@@ -52,6 +55,59 @@ namespace CarCareTracker.Controllers
{ {
return View(model: tab); 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() public IActionResult Garage()
{ {
var vehiclesStored = _dataAccess.GetVehicles(); var vehiclesStored = _dataAccess.GetVehicles();
@@ -59,7 +115,9 @@ namespace CarCareTracker.Controllers
{ {
vehiclesStored = _userLogic.FilterUserVehicles(vehiclesStored, GetUserID()); vehiclesStored = _userLogic.FilterUserVehicles(vehiclesStored, GetUserID());
} }
var vehicleViewModels = vehiclesStored.Select(x => new VehicleViewModel var vehicleViewModels = vehiclesStored.Select(x =>
{
var vehicleVM = new VehicleViewModel
{ {
Id = x.Id, Id = x.Id,
ImageLocation = x.ImageLocation, ImageLocation = x.ImageLocation,
@@ -71,10 +129,38 @@ namespace CarCareTracker.Controllers
IsElectric = x.IsElectric, IsElectric = x.IsElectric,
IsDiesel = x.IsDiesel, IsDiesel = x.IsDiesel,
UseHours = x.UseHours, UseHours = x.UseHours,
OdometerOptional = x.OdometerOptional,
ExtraFields = x.ExtraFields, ExtraFields = x.ExtraFields,
Tags = x.Tags, Tags = x.Tags,
LastReportedMileage = _vehicleLogic.GetMaxMileage(x.Id), DashboardMetrics = x.DashboardMetrics,
HasReminders = _vehicleLogic.GetVehicleHasUrgentOrPastDueReminders(x.Id) 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(); }).ToList();
return PartialView("_GarageDisplay", vehicleViewModels); return PartialView("_GarageDisplay", vehicleViewModels);
} }
@@ -85,19 +171,7 @@ namespace CarCareTracker.Controllers
{ {
vehiclesStored = _userLogic.FilterUserVehicles(vehiclesStored, GetUserID()); vehiclesStored = _userLogic.FilterUserVehicles(vehiclesStored, GetUserID());
} }
List<ReminderRecordViewModel> reminders = new List<ReminderRecordViewModel>(); var reminders = _vehicleLogic.GetReminders(vehiclesStored, true);
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);
}
}
return PartialView("_Calendar", reminders); return PartialView("_Calendar", reminders);
} }
public IActionResult ViewCalendarReminder(int reminderId) public IActionResult ViewCalendarReminder(int reminderId)
@@ -115,17 +189,21 @@ namespace CarCareTracker.Controllers
UserConfig = userConfig, UserConfig = userConfig,
UILanguages = languages UILanguages = languages
}; };
return PartialView("_Settings", viewModel);
}
public async Task<IActionResult> Sponsors()
{
try try
{ {
var httpClient = new HttpClient(); var httpClient = new HttpClient();
var sponsorsData = await httpClient.GetFromJsonAsync<Sponsors>(StaticHelper.SponsorsPath) ?? new Sponsors(); var sponsorsData = await httpClient.GetFromJsonAsync<Sponsors>(StaticHelper.SponsorsPath) ?? new Sponsors();
viewModel.Sponsors = sponsorsData; return PartialView("_Sponsors", sponsorsData);
} }
catch (Exception ex) catch (Exception ex)
{ {
_logger.LogError($"Unable to retrieve sponsors: {ex.Message}"); _logger.LogError($"Unable to retrieve sponsors: {ex.Message}");
return PartialView("_Sponsors", new Sponsors());
} }
return PartialView("_Settings", viewModel);
} }
[HttpPost] [HttpPost]
public IActionResult WriteToSettings(UserConfig userConfig) public IActionResult WriteToSettings(UserConfig userConfig)
@@ -147,10 +225,6 @@ namespace CarCareTracker.Controllers
var result = _config.SaveUserConfig(User, existingConfig); var result = _config.SaveUserConfig(User, existingConfig);
return Json(result); return Json(result);
} }
public IActionResult Privacy()
{
return View();
}
[Authorize(Roles = nameof(UserData.IsRootUser))] [Authorize(Roles = nameof(UserData.IsRootUser))]
public IActionResult GetExtraFieldsModal(int importMode = 0) public IActionResult GetExtraFieldsModal(int importMode = 0)
{ {
@@ -188,7 +262,8 @@ namespace CarCareTracker.Controllers
return Json(result); return Json(result);
} }
return Json(false); return Json(false);
} catch (Exception ex) }
catch (Exception ex)
{ {
_logger.LogError(ex.Message); _logger.LogError(ex.Message);
return Json(false); return Json(false);
@@ -205,7 +280,7 @@ namespace CarCareTracker.Controllers
var result = _loginLogic.UpdateUserDetails(userId, userAccount); var result = _loginLogic.UpdateUserDetails(userId, userAccount);
return Json(result); return Json(result);
} }
return Json(new OperationResponse { Success = false, Message = StaticHelper.GenericErrorMessage}); return Json(new OperationResponse { Success = false, Message = StaticHelper.GenericErrorMessage });
} }
catch (Exception ex) catch (Exception ex)
{ {
@@ -227,6 +302,260 @@ namespace CarCareTracker.Controllers
var userName = User.Identity.Name; var userName = User.Identity.Name;
return PartialView("_RootAccountModal", new UserData() { UserName = userName }); 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(new OperationResponse() { Success = true, Message = $"{translationsDownloaded} Translations Downloaded" });
} else
{
return Json(new OperationResponse() { Success = false, Message = "No Translations Downloaded" });
}
}
catch (Exception ex)
{
_logger.LogError($"Unable to retrieve translations: {ex.Message}");
return Json(new OperationResponse() { Success = false, Message = StaticHelper.GenericErrorMessage });
}
}
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)] [ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)]
public IActionResult Error() public IActionResult Error()
{ {

View File

@@ -51,6 +51,10 @@ namespace CarCareTracker.Controllers
} }
public IActionResult Registration() public IActionResult Registration()
{ {
if (_config.GetServerDisabledRegistration())
{
return RedirectToAction("Index");
}
return View(); return View();
} }
public IActionResult ForgotPassword() public IActionResult ForgotPassword()

View File

@@ -5,6 +5,7 @@ using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
using Npgsql; using Npgsql;
using System.IO.Compression; using System.IO.Compression;
using JsonSerializer=System.Text.Json.JsonSerializer;
namespace CarCareTracker.Controllers namespace CarCareTracker.Controllers
{ {
@@ -34,6 +35,7 @@ namespace CarCareTracker.Controllers
{ {
var cmds = new List<string> 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.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.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)", "CREATE TABLE IF NOT EXISTS app.upgraderecords (id INT GENERATED BY DEFAULT AS IDENTITY primary key, vehicleId INT not null, data jsonb not null)",
@@ -104,7 +106,7 @@ namespace CarCareTracker.Controllers
using (NpgsqlDataReader reader = ctext.ExecuteReader()) using (NpgsqlDataReader reader = ctext.ExecuteReader())
while (reader.Read()) 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); vehicles.Add(vehicle);
} }
} }
@@ -122,7 +124,7 @@ namespace CarCareTracker.Controllers
using (NpgsqlDataReader reader = ctext.ExecuteReader()) using (NpgsqlDataReader reader = ctext.ExecuteReader())
while (reader.Read()) 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) foreach (var record in repairrecords)
@@ -139,7 +141,7 @@ namespace CarCareTracker.Controllers
using (NpgsqlDataReader reader = ctext.ExecuteReader()) using (NpgsqlDataReader reader = ctext.ExecuteReader())
while (reader.Read()) 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) foreach (var record in upgraderecords)
@@ -156,7 +158,7 @@ namespace CarCareTracker.Controllers
using (NpgsqlDataReader reader = ctext.ExecuteReader()) using (NpgsqlDataReader reader = ctext.ExecuteReader())
while (reader.Read()) 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) foreach (var record in servicerecords)
@@ -175,7 +177,7 @@ namespace CarCareTracker.Controllers
using (NpgsqlDataReader reader = ctext.ExecuteReader()) using (NpgsqlDataReader reader = ctext.ExecuteReader())
while (reader.Read()) 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) foreach (var record in gasrecords)
@@ -192,7 +194,7 @@ namespace CarCareTracker.Controllers
using (NpgsqlDataReader reader = ctext.ExecuteReader()) using (NpgsqlDataReader reader = ctext.ExecuteReader())
while (reader.Read()) 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) foreach (var record in noterecords)
@@ -209,7 +211,7 @@ namespace CarCareTracker.Controllers
using (NpgsqlDataReader reader = ctext.ExecuteReader()) using (NpgsqlDataReader reader = ctext.ExecuteReader())
while (reader.Read()) 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) foreach (var record in odometerrecords)
@@ -226,7 +228,7 @@ namespace CarCareTracker.Controllers
using (NpgsqlDataReader reader = ctext.ExecuteReader()) using (NpgsqlDataReader reader = ctext.ExecuteReader())
while (reader.Read()) 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) foreach (var record in reminderrecords)
@@ -245,7 +247,7 @@ namespace CarCareTracker.Controllers
using (NpgsqlDataReader reader = ctext.ExecuteReader()) using (NpgsqlDataReader reader = ctext.ExecuteReader())
while (reader.Read()) 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) foreach (var record in planrecords)
@@ -262,7 +264,7 @@ namespace CarCareTracker.Controllers
using (NpgsqlDataReader reader = ctext.ExecuteReader()) using (NpgsqlDataReader reader = ctext.ExecuteReader())
while (reader.Read()) 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) foreach (var record in planrecordtemplates)
@@ -279,7 +281,7 @@ namespace CarCareTracker.Controllers
using (NpgsqlDataReader reader = ctext.ExecuteReader()) using (NpgsqlDataReader reader = ctext.ExecuteReader())
while (reader.Read()) 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) foreach (var record in supplyrecords)
@@ -296,7 +298,7 @@ namespace CarCareTracker.Controllers
using (NpgsqlDataReader reader = ctext.ExecuteReader()) using (NpgsqlDataReader reader = ctext.ExecuteReader())
while (reader.Read()) 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) foreach (var record in taxrecords)
@@ -359,7 +361,7 @@ namespace CarCareTracker.Controllers
using (NpgsqlDataReader reader = ctext.ExecuteReader()) using (NpgsqlDataReader reader = ctext.ExecuteReader())
while (reader.Read()) 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) foreach (var record in userconfigrecords)
@@ -403,7 +405,7 @@ namespace CarCareTracker.Controllers
using (NpgsqlDataReader reader = ctext.ExecuteReader()) using (NpgsqlDataReader reader = ctext.ExecuteReader())
while (reader.Read()) 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) foreach (var record in extrafields)
@@ -474,7 +476,7 @@ namespace CarCareTracker.Controllers
using (var ctext = pgDataSource.CreateCommand(cmd)) using (var ctext = pgDataSource.CreateCommand(cmd))
{ {
ctext.Parameters.AddWithValue("id", vehicle.Id); 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(); ctext.ExecuteNonQuery();
} }
} }
@@ -490,7 +492,7 @@ namespace CarCareTracker.Controllers
{ {
ctext.Parameters.AddWithValue("id", record.Id); ctext.Parameters.AddWithValue("id", record.Id);
ctext.Parameters.AddWithValue("vehicleId", record.VehicleId); 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(); ctext.ExecuteNonQuery();
} }
} }
@@ -506,7 +508,7 @@ namespace CarCareTracker.Controllers
{ {
ctext.Parameters.AddWithValue("id", record.Id); ctext.Parameters.AddWithValue("id", record.Id);
ctext.Parameters.AddWithValue("vehicleId", record.VehicleId); 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(); ctext.ExecuteNonQuery();
} }
} }
@@ -522,7 +524,7 @@ namespace CarCareTracker.Controllers
{ {
ctext.Parameters.AddWithValue("id", record.Id); ctext.Parameters.AddWithValue("id", record.Id);
ctext.Parameters.AddWithValue("vehicleId", record.VehicleId); 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(); ctext.ExecuteNonQuery();
} }
} }
@@ -540,7 +542,7 @@ namespace CarCareTracker.Controllers
{ {
ctext.Parameters.AddWithValue("id", record.Id); ctext.Parameters.AddWithValue("id", record.Id);
ctext.Parameters.AddWithValue("vehicleId", record.VehicleId); 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(); ctext.ExecuteNonQuery();
} }
} }
@@ -556,7 +558,7 @@ namespace CarCareTracker.Controllers
{ {
ctext.Parameters.AddWithValue("id", record.Id); ctext.Parameters.AddWithValue("id", record.Id);
ctext.Parameters.AddWithValue("vehicleId", record.VehicleId); 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(); ctext.ExecuteNonQuery();
} }
} }
@@ -572,7 +574,7 @@ namespace CarCareTracker.Controllers
{ {
ctext.Parameters.AddWithValue("id", record.Id); ctext.Parameters.AddWithValue("id", record.Id);
ctext.Parameters.AddWithValue("vehicleId", record.VehicleId); 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(); ctext.ExecuteNonQuery();
} }
} }
@@ -588,7 +590,7 @@ namespace CarCareTracker.Controllers
{ {
ctext.Parameters.AddWithValue("id", record.Id); ctext.Parameters.AddWithValue("id", record.Id);
ctext.Parameters.AddWithValue("vehicleId", record.VehicleId); 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(); ctext.ExecuteNonQuery();
} }
} }
@@ -606,7 +608,7 @@ namespace CarCareTracker.Controllers
{ {
ctext.Parameters.AddWithValue("id", record.Id); ctext.Parameters.AddWithValue("id", record.Id);
ctext.Parameters.AddWithValue("vehicleId", record.VehicleId); 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(); ctext.ExecuteNonQuery();
} }
} }
@@ -622,7 +624,7 @@ namespace CarCareTracker.Controllers
{ {
ctext.Parameters.AddWithValue("id", record.Id); ctext.Parameters.AddWithValue("id", record.Id);
ctext.Parameters.AddWithValue("vehicleId", record.VehicleId); 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(); ctext.ExecuteNonQuery();
} }
} }
@@ -638,7 +640,7 @@ namespace CarCareTracker.Controllers
{ {
ctext.Parameters.AddWithValue("id", record.Id); ctext.Parameters.AddWithValue("id", record.Id);
ctext.Parameters.AddWithValue("vehicleId", record.VehicleId); 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(); ctext.ExecuteNonQuery();
} }
} }
@@ -654,7 +656,7 @@ namespace CarCareTracker.Controllers
{ {
ctext.Parameters.AddWithValue("id", record.Id); ctext.Parameters.AddWithValue("id", record.Id);
ctext.Parameters.AddWithValue("vehicleId", record.VehicleId); 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(); ctext.ExecuteNonQuery();
} }
} }
@@ -705,7 +707,7 @@ namespace CarCareTracker.Controllers
using (var ctext = pgDataSource.CreateCommand(cmd)) using (var ctext = pgDataSource.CreateCommand(cmd))
{ {
ctext.Parameters.AddWithValue("id", record.Id); 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(); ctext.ExecuteNonQuery();
} }
} }
@@ -737,7 +739,7 @@ namespace CarCareTracker.Controllers
using (var ctext = pgDataSource.CreateCommand(cmd)) using (var ctext = pgDataSource.CreateCommand(cmd))
{ {
ctext.Parameters.AddWithValue("id", record.Id); 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(); ctext.ExecuteNonQuery();
} }
} }

View File

@@ -0,0 +1,187 @@
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);
}
[HttpPost]
public IActionResult DeleteGasRecordById(int gasRecordId)
{
var result = _gasRecordDataAccess.DeleteGasRecordById(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,486 @@
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)
{
if (mode == ImportMode.GasRecord)
{
//convert to gas model.
var convertedRecord = new GasRecord()
{
VehicleId = vehicleId,
Date = DateTime.Parse(importModel.Date),
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 = DateTime.Parse(importModel.Date),
Mileage = decimal.ToInt32(decimal.Parse(importModel.Odometer, NumberStyles.Any)),
Description = string.IsNullOrWhiteSpace(importModel.Description) ? $"Service Record on {importModel.Date}" : 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 = DateTime.Parse(importModel.Date),
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 = DateTime.Parse(importModel.Date),
Mileage = decimal.ToInt32(decimal.Parse(importModel.Odometer, NumberStyles.Any)),
Description = string.IsNullOrWhiteSpace(importModel.Description) ? $"Repair Record on {importModel.Date}" : 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 = DateTime.Parse(importModel.Date),
Mileage = decimal.ToInt32(decimal.Parse(importModel.Odometer, NumberStyles.Any)),
Description = string.IsNullOrWhiteSpace(importModel.Description) ? $"Upgrade Record on {importModel.Date}" : 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 = DateTime.Parse(importModel.Date),
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 = DateTime.Parse(importModel.Date),
Description = string.IsNullOrWhiteSpace(importModel.Description) ? $"Tax Record on {importModel.Date}" : 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,78 @@
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).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);
}
[HttpPost]
public IActionResult DeleteNoteById(int noteId)
{
var result = _noteDataAccess.DeleteNoteById(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,154 @@
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);
}
[HttpPost]
public IActionResult DeleteOdometerRecordById(int odometerRecordId)
{
var result = _odometerRecordDataAccess.DeleteOdometerRecordById(odometerRecordId);
if (result)
{
StaticHelper.NotifyAsync(_config.GetWebHookUrl(), 0, User.Identity.Name, $"Deleted Odometer Record - Id: {odometerRecordId}");
}
return Json(result);
}
}
}

View File

@@ -0,0 +1,265 @@
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 = RequisitionSupplyRecordsByUsage(planRecord.Supplies, DateTime.Parse(planRecord.DateCreated), planRecord.Description);
if (planRecord.CopySuppliesAttachment)
{
planRecord.Files.AddRange(GetSuppliesAttachments(planRecord.Supplies));
}
}
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(new OperationResponse { Success = false, Message = "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(new OperationResponse { Success = result, Message = result ? "Template Added" : StaticHelper.GenericErrorMessage });
}
[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(new OperationResponse { Success = false, Message = "Unable to find template" });
}
if (existingRecord.Supplies.Any())
{
var suppliesToOrder = CheckSupplyRecordsAvailability(existingRecord.Supplies);
return PartialView("_PlanOrderSupplies", suppliesToOrder);
}
else
{
return Json(new OperationResponse { Success = false, Message = "Template has No Supplies" });
}
}
[HttpPost]
public IActionResult ConvertPlanRecordTemplateToPlanRecord(int planRecordTemplateId)
{
var existingRecord = _planRecordTemplateDataAccess.GetPlanRecordTemplateById(planRecordTemplateId);
if (existingRecord.Id == default)
{
return Json(new OperationResponse { Success = false, Message = "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(new OperationResponse { Success = false, Message = "Missing Supplies, Please Delete This Template and Recreate It." });
}
else if (supplyAvailability.Any(x => x.Insufficient))
{
return Json(new OperationResponse { Success = false, Message = "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(new OperationResponse { Success = false, Message = "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(new OperationResponse { Success = result, Message = result ? "Plan Record Added" : StaticHelper.GenericErrorMessage });
}
[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)
{
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 result = _planRecordDataAccess.DeletePlanRecordById(planRecordId);
if (result)
{
StaticHelper.NotifyAsync(_config.GetWebHookUrl(), 0, User.Identity.Name, $"Deleted Plan Record - Id: {planRecordId}");
}
return Json(result);
}
}
}

View File

@@ -0,0 +1,163 @@
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, decimal? 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);
}
[HttpPost]
public IActionResult DeleteReminderRecordById(int reminderRecordId)
{
var result = _reminderRecordDataAccess.DeleteReminderRecordById(reminderRecordId);
if (result)
{
StaticHelper.NotifyAsync(_config.GetWebHookUrl(), 0, User.Identity.Name, $"Deleted Reminder - Id: {reminderRecordId}");
}
return Json(result);
}
}
}

View File

@@ -0,0 +1,101 @@
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 = RequisitionSupplyRecordsByUsage(collisionRecord.Supplies, DateTime.Parse(collisionRecord.Date), collisionRecord.Description);
if (collisionRecord.CopySuppliesAttachment)
{
collisionRecord.Files.AddRange(GetSuppliesAttachments(collisionRecord.Supplies));
}
}
//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);
}
[HttpPost]
public IActionResult DeleteCollisionRecordById(int collisionRecordId)
{
var result = _collisionRecordDataAccess.DeleteCollisionRecordById(collisionRecordId);
if (result)
{
StaticHelper.NotifyAsync(_config.GetWebHookUrl(), 0, User.Identity.Name, $"Deleted Repair Record - Id: {collisionRecordId}");
}
return Json(result);
}
}
}

View File

@@ -0,0 +1,568 @@
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(new OperationResponse { Success = false, Message = StaticHelper.GenericErrorMessage });
}
return Json(new OperationResponse { Success = true, Message = result });
}
else
{
return Json(new OperationResponse { Success = false, Message = "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("N1");
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("N1");
}
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("N1");
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);
}
[HttpGet]
public IActionResult GetAdditionalWidgets()
{
var widgets = _fileHelper.GetWidgets();
return PartialView("_ReportWidgets", widgets);
}
}
}

View File

@@ -0,0 +1,101 @@
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 = RequisitionSupplyRecordsByUsage(serviceRecord.Supplies, DateTime.Parse(serviceRecord.Date), serviceRecord.Description);
if (serviceRecord.CopySuppliesAttachment)
{
serviceRecord.Files.AddRange(GetSuppliesAttachments(serviceRecord.Supplies));
}
}
//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);
}
[HttpPost]
public IActionResult DeleteServiceRecordById(int serviceRecordId)
{
var result = _serviceRecordDataAccess.DeleteServiceRecordById(serviceRecordId);
if (result)
{
StaticHelper.NotifyAsync(_config.GetWebHookUrl(), 0, User.Identity.Name, $"Deleted Service Record - Id: {serviceRecordId}");
}
return Json(result);
}
}
}

View File

@@ -0,0 +1,194 @@
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
{
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;
}
[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);
//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);
}
[HttpPost]
public IActionResult DeleteSupplyRecordById(int supplyRecordId)
{
var result = _supplyRecordDataAccess.DeleteSupplyRecordById(supplyRecordId);
if (result)
{
StaticHelper.NotifyAsync(_config.GetWebHookUrl(), 0, User.Identity.Name, $"Deleted Supply Record - Id: {supplyRecordId}");
}
return Json(result);
}
}
}

View File

@@ -0,0 +1,124 @@
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);
}
[HttpPost]
public IActionResult DeleteTaxRecordById(int taxRecordId)
{
var result = _taxRecordDataAccess.DeleteTaxRecordById(taxRecordId);
if (result)
{
StaticHelper.NotifyAsync(_config.GetWebHookUrl(), 0, User.Identity.Name, $"Deleted Tax Record - Id: {taxRecordId}");
}
return Json(result);
}
}
}

View File

@@ -0,0 +1,101 @@
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 = RequisitionSupplyRecordsByUsage(upgradeRecord.Supplies, DateTime.Parse(upgradeRecord.Date), upgradeRecord.Description);
if (upgradeRecord.CopySuppliesAttachment)
{
upgradeRecord.Files.AddRange(GetSuppliesAttachments(upgradeRecord.Supplies));
}
}
//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);
}
[HttpPost]
public IActionResult DeleteUpgradeRecordById(int upgradeRecordId)
{
var result = _upgradeRecordDataAccess.DeleteUpgradeRecordById(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.External.Interfaces;
using CarCareTracker.Helper; using CarCareTracker.Helper;
using CarCareTracker.Models; using CarCareTracker.Models;
using LiteDB;
namespace CarCareTracker.External.Implementations namespace CarCareTracker.External.Implementations
{ {

View File

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

View File

@@ -18,7 +18,7 @@ namespace CarCareTracker.External.Implementations
try try
{ {
//create table if not exist. //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)) using (var ctext = pgDataSource.CreateCommand(initCMD))
{ {
ctext.ExecuteNonQuery(); ctext.ExecuteNonQuery();

View File

@@ -18,7 +18,7 @@ namespace CarCareTracker.External.Implementations
try try
{ {
//create table if not exist. //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)) using (var ctext = pgDataSource.CreateCommand(initCMD))
{ {
ctext.ExecuteNonQuery(); ctext.ExecuteNonQuery();

View File

@@ -17,7 +17,7 @@ namespace CarCareTracker.External.Implementations
try try
{ {
//create table if not exist. //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)) using (var ctext = pgDataSource.CreateCommand(initCMD))
{ {
ctext.ExecuteNonQuery(); ctext.ExecuteNonQuery();

View File

@@ -17,7 +17,7 @@ namespace CarCareTracker.External.Implementations
try try
{ {
//create table if not exist. //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)) using (var ctext = pgDataSource.CreateCommand(initCMD))
{ {
ctext.ExecuteNonQuery(); ctext.ExecuteNonQuery();

View File

@@ -17,7 +17,7 @@ namespace CarCareTracker.External.Implementations
try try
{ {
//create table if not exist. //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)) using (var ctext = pgDataSource.CreateCommand(initCMD))
{ {
ctext.ExecuteNonQuery(); ctext.ExecuteNonQuery();

View File

@@ -17,7 +17,7 @@ namespace CarCareTracker.External.Implementations
try try
{ {
//create table if not exist. //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)) using (var ctext = pgDataSource.CreateCommand(initCMD))
{ {
ctext.ExecuteNonQuery(); ctext.ExecuteNonQuery();

View File

@@ -17,7 +17,7 @@ namespace CarCareTracker.External.Implementations
try try
{ {
//create table if not exist. //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)) using (var ctext = pgDataSource.CreateCommand(initCMD))
{ {
ctext.ExecuteNonQuery(); ctext.ExecuteNonQuery();

View File

@@ -17,7 +17,7 @@ namespace CarCareTracker.External.Implementations
try try
{ {
//create table if not exist. //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)) using (var ctext = pgDataSource.CreateCommand(initCMD))
{ {
ctext.ExecuteNonQuery(); ctext.ExecuteNonQuery();

View File

@@ -17,7 +17,7 @@ namespace CarCareTracker.External.Implementations
try try
{ {
//create table if not exist. //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)) using (var ctext = pgDataSource.CreateCommand(initCMD))
{ {
ctext.ExecuteNonQuery(); ctext.ExecuteNonQuery();

View File

@@ -17,7 +17,7 @@ namespace CarCareTracker.External.Implementations
try try
{ {
//create table if not exist. //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)) using (var ctext = pgDataSource.CreateCommand(initCMD))
{ {
ctext.ExecuteNonQuery(); ctext.ExecuteNonQuery();

View File

@@ -17,7 +17,7 @@ namespace CarCareTracker.External.Implementations
try try
{ {
//create table if not exist. //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)) using (var ctext = pgDataSource.CreateCommand(initCMD))
{ {
ctext.ExecuteNonQuery(); ctext.ExecuteNonQuery();

View File

@@ -16,7 +16,7 @@ namespace CarCareTracker.External.Implementations
try try
{ {
//create table if not exist. //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)) using (var ctext = pgDataSource.CreateCommand(initCMD))
{ {
ctext.ExecuteNonQuery(); ctext.ExecuteNonQuery();

View File

@@ -17,7 +17,7 @@ namespace CarCareTracker.External.Implementations
try try
{ {
//create table if not exist. //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)) using (var ctext = pgDataSource.CreateCommand(initCMD))
{ {
ctext.ExecuteNonQuery(); ctext.ExecuteNonQuery();

View File

@@ -1,7 +1,6 @@
using CarCareTracker.External.Interfaces; using CarCareTracker.External.Interfaces;
using CarCareTracker.Models; using CarCareTracker.Models;
using Npgsql; using Npgsql;
using System.Net.Mail;
namespace CarCareTracker.External.Implementations namespace CarCareTracker.External.Implementations
{ {
@@ -17,7 +16,7 @@ namespace CarCareTracker.External.Implementations
try try
{ {
//create table if not exist. //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)) using (var ctext = pgDataSource.CreateCommand(initCMD))
{ {
ctext.ExecuteNonQuery(); ctext.ExecuteNonQuery();

View File

@@ -17,7 +17,7 @@ namespace CarCareTracker.External.Implementations
try try
{ {
//create table if not exist. //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)) using (var ctext = pgDataSource.CreateCommand(initCMD))
{ {
ctext.ExecuteNonQuery(); ctext.ExecuteNonQuery();

View File

@@ -16,7 +16,7 @@ namespace CarCareTracker.External.Implementations
try try
{ {
//create table if not exist. //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)) using (var ctext = pgDataSource.CreateCommand(initCMD))
{ {
ctext.ExecuteNonQuery(); ctext.ExecuteNonQuery();

View File

@@ -17,7 +17,7 @@ namespace CarCareTracker.External.Implementations
try try
{ {
//create table if not exist. //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)) using (var ctext = pgDataSource.CreateCommand(initCMD))
{ {
ctext.ExecuteNonQuery(); ctext.ExecuteNonQuery();

View File

@@ -2,6 +2,7 @@
using CarCareTracker.Models; using CarCareTracker.Models;
using Microsoft.Extensions.Caching.Memory; using Microsoft.Extensions.Caching.Memory;
using System.Security.Claims; using System.Security.Claims;
using System.Text.Json;
namespace CarCareTracker.Helper namespace CarCareTracker.Helper
{ {
@@ -12,10 +13,13 @@ namespace CarCareTracker.Helper
UserConfig GetUserConfig(ClaimsPrincipal user); UserConfig GetUserConfig(ClaimsPrincipal user);
bool SaveUserConfig(ClaimsPrincipal user, UserConfig configData); bool SaveUserConfig(ClaimsPrincipal user, UserConfig configData);
bool AuthenticateRootUser(string username, string password); bool AuthenticateRootUser(string username, string password);
bool AuthenticateRootUserOIDC(string email);
string GetWebHookUrl(); string GetWebHookUrl();
bool GetCustomWidgetsEnabled();
string GetMOTD(); string GetMOTD();
string GetLogoUrl(); string GetLogoUrl();
string GetServerLanguage(); string GetServerLanguage();
bool GetServerDisabledRegistration();
bool GetServerEnableShopSupplies(); bool GetServerEnableShopSupplies();
string GetServerPostgresConnection(); string GetServerPostgresConnection();
string GetAllowedFileUploadExtensions(); string GetAllowedFileUploadExtensions();
@@ -43,6 +47,10 @@ namespace CarCareTracker.Helper
} }
return webhook; return webhook;
} }
public bool GetCustomWidgetsEnabled()
{
return bool.Parse(_config["LUBELOGGER_CUSTOM_WIDGETS"] ?? "false");
}
public string GetMOTD() public string GetMOTD()
{ {
var motd = _config["LUBELOGGER_MOTD"]; var motd = _config["LUBELOGGER_MOTD"];
@@ -89,11 +97,26 @@ namespace CarCareTracker.Helper
} }
return username == rootUsername && password == rootPassword; return username == rootUsername && password == rootPassword;
} }
public bool AuthenticateRootUserOIDC(string email)
{
var rootEmail = _config[nameof(UserConfig.DefaultReminderEmail)] ?? string.Empty;
var rootUserOIDC = bool.Parse(_config[nameof(UserConfig.EnableRootUserOIDC)]);
if (!rootUserOIDC || string.IsNullOrWhiteSpace(rootEmail))
{
return false;
}
return email == rootEmail;
}
public string GetServerLanguage() public string GetServerLanguage()
{ {
var serverLanguage = _config[nameof(UserConfig.UserLanguage)] ?? "en_US"; var serverLanguage = _config[nameof(UserConfig.UserLanguage)] ?? "en_US";
return serverLanguage; return serverLanguage;
} }
public bool GetServerDisabledRegistration()
{
var registrationDisabled = bool.Parse(_config[nameof(UserConfig.DisableRegistration)]);
return registrationDisabled;
}
public string GetServerPostgresConnection() public string GetServerPostgresConnection()
{ {
if (!string.IsNullOrWhiteSpace(_config["POSTGRES_CONNECTION"])) if (!string.IsNullOrWhiteSpace(_config["POSTGRES_CONNECTION"]))
@@ -124,13 +147,13 @@ namespace CarCareTracker.Helper
if (!File.Exists(StaticHelper.UserConfigPath)) if (!File.Exists(StaticHelper.UserConfigPath))
{ {
//if file doesn't exist it might be because it's running on a mounted volume in docker. //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); var configFileContents = File.ReadAllText(StaticHelper.UserConfigPath);
configData.EnableAuth = bool.Parse(_config[nameof(UserConfig.EnableAuth)] ?? "false"); configData.EnableAuth = bool.Parse(_config[nameof(UserConfig.EnableAuth)] ?? "false");
configData.UserNameHash = _config[nameof(UserConfig.UserNameHash)] ?? string.Empty; configData.UserNameHash = _config[nameof(UserConfig.UserNameHash)] ?? string.Empty;
configData.UserPasswordHash = _config[nameof(UserConfig.UserPasswordHash)] ?? 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); _cache.Set<UserConfig>($"userConfig_{userId}", configData);
return true; return true;
} }
@@ -162,10 +185,13 @@ namespace CarCareTracker.Helper
{ {
EnableCsvImports = bool.Parse(_config[nameof(UserConfig.EnableCsvImports)]), EnableCsvImports = bool.Parse(_config[nameof(UserConfig.EnableCsvImports)]),
UseDarkMode = bool.Parse(_config[nameof(UserConfig.UseDarkMode)]), UseDarkMode = bool.Parse(_config[nameof(UserConfig.UseDarkMode)]),
UseSystemColorMode = bool.Parse(_config[nameof(UserConfig.UseSystemColorMode)]),
UseMPG = bool.Parse(_config[nameof(UserConfig.UseMPG)]), UseMPG = bool.Parse(_config[nameof(UserConfig.UseMPG)]),
UseDescending = bool.Parse(_config[nameof(UserConfig.UseDescending)]), UseDescending = bool.Parse(_config[nameof(UserConfig.UseDescending)]),
EnableAuth = bool.Parse(_config[nameof(UserConfig.EnableAuth)]), EnableAuth = bool.Parse(_config[nameof(UserConfig.EnableAuth)]),
EnableRootUserOIDC = bool.Parse(_config[nameof(UserConfig.EnableRootUserOIDC)]),
HideZero = bool.Parse(_config[nameof(UserConfig.HideZero)]), HideZero = bool.Parse(_config[nameof(UserConfig.HideZero)]),
AutomaticDecimalFormat = bool.Parse(_config[nameof(UserConfig.AutomaticDecimalFormat)]),
UseUKMPG = bool.Parse(_config[nameof(UserConfig.UseUKMPG)]), UseUKMPG = bool.Parse(_config[nameof(UserConfig.UseUKMPG)]),
UseMarkDownOnSavedNotes = bool.Parse(_config[nameof(UserConfig.UseMarkDownOnSavedNotes)]), UseMarkDownOnSavedNotes = bool.Parse(_config[nameof(UserConfig.UseMarkDownOnSavedNotes)]),
UseThreeDecimalGasCost = bool.Parse(_config[nameof(UserConfig.UseThreeDecimalGasCost)]), UseThreeDecimalGasCost = bool.Parse(_config[nameof(UserConfig.UseThreeDecimalGasCost)]),
@@ -178,9 +204,12 @@ namespace CarCareTracker.Helper
EnableShopSupplies = bool.Parse(_config[nameof(UserConfig.EnableShopSupplies)]), EnableShopSupplies = bool.Parse(_config[nameof(UserConfig.EnableShopSupplies)]),
EnableExtraFieldColumns = bool.Parse(_config[nameof(UserConfig.EnableExtraFieldColumns)]), EnableExtraFieldColumns = bool.Parse(_config[nameof(UserConfig.EnableExtraFieldColumns)]),
VisibleTabs = _config.GetSection(nameof(UserConfig.VisibleTabs)).Get<List<ImportMode>>(), VisibleTabs = _config.GetSection(nameof(UserConfig.VisibleTabs)).Get<List<ImportMode>>(),
TabOrder = _config.GetSection(nameof(UserConfig.TabOrder)).Get<List<ImportMode>>(),
UserColumnPreferences = _config.GetSection(nameof(UserConfig.UserColumnPreferences)).Get<List<UserColumnPreference>>() ?? new List<UserColumnPreference>(), UserColumnPreferences = _config.GetSection(nameof(UserConfig.UserColumnPreferences)).Get<List<UserColumnPreference>>() ?? new List<UserColumnPreference>(),
ReminderUrgencyConfig = _config.GetSection(nameof(UserConfig.ReminderUrgencyConfig)).Get<ReminderUrgencyConfig>() ?? new ReminderUrgencyConfig(), ReminderUrgencyConfig = _config.GetSection(nameof(UserConfig.ReminderUrgencyConfig)).Get<ReminderUrgencyConfig>() ?? new ReminderUrgencyConfig(),
DefaultTab = (ImportMode)int.Parse(_config[nameof(UserConfig.DefaultTab)]) DefaultTab = (ImportMode)int.Parse(_config[nameof(UserConfig.DefaultTab)]),
DefaultReminderEmail = _config[nameof(UserConfig.DefaultReminderEmail)],
DisableRegistration = bool.Parse(_config[nameof(UserConfig.DisableRegistration)])
}; };
int userId = 0; int userId = 0;
if (user != null) if (user != null)

View File

@@ -16,6 +16,10 @@ namespace CarCareTracker.Helper
int ClearTempFolder(); int ClearTempFolder();
int ClearUnlinkedThumbnails(List<string> linkedImages); int ClearUnlinkedThumbnails(List<string> linkedImages);
int ClearUnlinkedDocuments(List<string> linkedDocuments); int ClearUnlinkedDocuments(List<string> linkedDocuments);
string GetWidgets();
bool WidgetsExist();
bool SaveWidgets(string widgetsData);
bool DeleteWidgets();
} }
public class FileHelper : IFileHelper public class FileHelper : IFileHelper
{ {
@@ -100,6 +104,7 @@ namespace CarCareTracker.Helper
var documentPath = Path.Combine(tempPath, "documents"); var documentPath = Path.Combine(tempPath, "documents");
var translationPath = Path.Combine(tempPath, "translations"); var translationPath = Path.Combine(tempPath, "translations");
var dataPath = Path.Combine(tempPath, StaticHelper.DbName); var dataPath = Path.Combine(tempPath, StaticHelper.DbName);
var widgetPath = Path.Combine(tempPath, StaticHelper.AdditionalWidgetsPath);
var configPath = Path.Combine(tempPath, StaticHelper.UserConfigPath); var configPath = Path.Combine(tempPath, StaticHelper.UserConfigPath);
if (Directory.Exists(imagePath)) if (Directory.Exists(imagePath))
{ {
@@ -174,6 +179,10 @@ namespace CarCareTracker.Helper
//data path will always exist as it is created on startup if not. //data path will always exist as it is created on startup if not.
File.Move(dataPath, StaticHelper.DbName, true); File.Move(dataPath, StaticHelper.DbName, true);
} }
if (File.Exists(widgetPath))
{
File.Move(widgetPath, StaticHelper.AdditionalWidgetsPath, true);
}
if (File.Exists(configPath)) if (File.Exists(configPath))
{ {
//check if config folder exists. //check if config folder exists.
@@ -223,6 +232,7 @@ namespace CarCareTracker.Helper
var documentPath = Path.Combine(_webEnv.WebRootPath, "documents"); var documentPath = Path.Combine(_webEnv.WebRootPath, "documents");
var translationPath = Path.Combine(_webEnv.WebRootPath, "translations"); var translationPath = Path.Combine(_webEnv.WebRootPath, "translations");
var dataPath = StaticHelper.DbName; var dataPath = StaticHelper.DbName;
var widgetPath = StaticHelper.AdditionalWidgetsPath;
var configPath = StaticHelper.UserConfigPath; var configPath = StaticHelper.UserConfigPath;
if (!Directory.Exists(tempPath)) if (!Directory.Exists(tempPath))
Directory.CreateDirectory(tempPath); Directory.CreateDirectory(tempPath);
@@ -262,6 +272,12 @@ namespace CarCareTracker.Helper
Directory.CreateDirectory(newPath); Directory.CreateDirectory(newPath);
File.Copy(dataPath, $"{newPath}/{Path.GetFileName(dataPath)}"); 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)) if (File.Exists(configPath))
{ {
var newPath = Path.Combine(tempPath, "config"); var newPath = Path.Combine(tempPath, "config");
@@ -323,12 +339,20 @@ namespace CarCareTracker.Helper
var tempPath = GetFullFilePath("temp", false); var tempPath = GetFullFilePath("temp", false);
if (Directory.Exists(tempPath)) if (Directory.Exists(tempPath))
{ {
//delete files
var files = Directory.GetFiles(tempPath); var files = Directory.GetFiles(tempPath);
foreach (var file in files) foreach (var file in files)
{ {
File.Delete(file); File.Delete(file);
filesDeleted++; filesDeleted++;
} }
//delete folders
var folders = Directory.GetDirectories(tempPath);
foreach(var folder in folders)
{
Directory.Delete(folder, true);
filesDeleted++;
}
} }
return filesDeleted; return filesDeleted;
} }
@@ -368,5 +392,57 @@ namespace CarCareTracker.Helper
} }
return 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

@@ -36,9 +36,9 @@ namespace CarCareTracker.Helper
//need to order by to get correct results //need to order by to get correct results
result = result.OrderBy(x => x.Date).ThenBy(x => x.Mileage).ToList(); result = result.OrderBy(x => x.Date).ThenBy(x => x.Mileage).ToList();
var computedResults = new List<GasRecordViewModel>(); var computedResults = new List<GasRecordViewModel>();
int previousMileage = 0; decimal previousMileage = 0.00M;
decimal unFactoredConsumption = 0.00M; decimal unFactoredConsumption = 0.00M;
int unFactoredMileage = 0; decimal unFactoredMileage = 0.00M;
//perform computation. //perform computation.
for (int i = 0; i < result.Count; i++) for (int i = 0; i < result.Count; i++)
{ {
@@ -57,6 +57,10 @@ namespace CarCareTracker.Helper
if (i > 0) if (i > 0)
{ {
var deltaMileage = currentObject.Mileage - previousMileage; var deltaMileage = currentObject.Mileage - previousMileage;
if (deltaMileage < 0)
{
deltaMileage = 0;
}
var gasRecordViewModel = new GasRecordViewModel() var gasRecordViewModel = new GasRecordViewModel()
{ {
Id = currentObject.Id, Id = currentObject.Id,

View File

@@ -1,4 +1,5 @@
using LiteDB; using CarCareTracker.Models;
using LiteDB;
namespace CarCareTracker.Helper; namespace CarCareTracker.Helper;
@@ -15,6 +16,29 @@ public class LiteDBHelper: ILiteDBHelper
if (db == null) if (db == null)
{ {
db = new LiteDatabase(StaticHelper.DbName); db = new LiteDatabase(StaticHelper.DbName);
if (db.UserVersion == 0)
{
//migration required to convert ints to decimals
var collections = db.GetCollectionNames();
foreach (string collection in collections)
{
var documents = db.GetCollection(collection);
foreach (var document in documents.FindAll())
{
if (document.ContainsKey(nameof(GenericRecord.Mileage)))
{
document[nameof(GenericRecord.Mileage)] = Convert.ToDecimal(document[nameof(GenericRecord.Mileage)].AsInt32);
//check for initial mileage as well
if (document.ContainsKey(nameof(OdometerRecord.InitialMileage)))
{
document[nameof(OdometerRecord.InitialMileage)] = Convert.ToDecimal(document[nameof(OdometerRecord.InitialMileage)].AsInt32);
}
documents.Update(document);
}
}
}
db.UserVersion = 1;
}
} }
} }
public LiteDatabase GetLiteDB() public LiteDatabase GetLiteDB()

View File

@@ -1,6 +1,7 @@
using CarCareTracker.Models; using CarCareTracker.Models;
using MimeKit; using MimeKit;
using MailKit.Net.Smtp; using MailKit.Net.Smtp;
using MailKit.Security;
namespace CarCareTracker.Helper namespace CarCareTracker.Helper
{ {
@@ -109,18 +110,24 @@ namespace CarCareTracker.Helper
string emailSubject = $"Vehicle Reminders From LubeLogger - {DateTime.Now.ToShortDateString()}"; string emailSubject = $"Vehicle Reminders From LubeLogger - {DateTime.Now.ToShortDateString()}";
//construct html table. //construct html table.
string emailBody = File.ReadAllText(emailTemplatePath); 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 = ""; string tableBody = "";
foreach(ReminderRecordViewModel reminder in reminders) 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>"; tableBody += $"<tr class='{reminder.Urgency}'><td>{StaticHelper.GetTitleCaseReminderUrgency(reminder.Urgency)}</td><td>{reminder.Description}</td><td>{dueOn}</td></tr>";
} }
emailBody = emailBody.Replace("{TableBody}", tableBody); emailBody = emailBody.Replace("{TableBody}", tableBody);
try try
{ {
SendEmail(emailAddresses, emailSubject, emailBody); var result = SendEmail(emailAddresses, emailSubject, emailBody);
if (result)
{
return new OperationResponse { Success = true, Message = "Email Sent!" }; return new OperationResponse { Success = true, Message = "Email Sent!" };
} else
{
return new OperationResponse { Success = false, Message = StaticHelper.GenericErrorMessage };
}
} catch (Exception ex) } catch (Exception ex)
{ {
return new OperationResponse { Success = false, Message = ex.Message }; return new OperationResponse { Success = false, Message = ex.Message };
@@ -145,7 +152,7 @@ namespace CarCareTracker.Helper
using (var client = new SmtpClient()) using (var client = new SmtpClient())
{ {
client.Connect(server, mailConfig.Port, MailKit.Security.SecureSocketOptions.Auto); client.Connect(server, mailConfig.Port, SecureSocketOptions.Auto);
//perform authentication if either username or password is provided. //perform authentication if either username or password is provided.
//do not perform authentication if neither are provided. //do not perform authentication if neither are provided.
if (!string.IsNullOrWhiteSpace(mailConfig.Username) || !string.IsNullOrWhiteSpace(mailConfig.Password)) { if (!string.IsNullOrWhiteSpace(mailConfig.Username) || !string.IsNullOrWhiteSpace(mailConfig.Password)) {

View File

@@ -4,8 +4,8 @@ namespace CarCareTracker.Helper
{ {
public interface IReminderHelper public interface IReminderHelper
{ {
ReminderRecord GetUpdatedRecurringReminderRecord(ReminderRecord existingReminder, DateTime? currentDate, int? currentMileage); ReminderRecord GetUpdatedRecurringReminderRecord(ReminderRecord existingReminder, DateTime? currentDate, decimal? currentMileage);
List<ReminderRecordViewModel> GetReminderRecordViewModels(List<ReminderRecord> reminders, int currentMileage, DateTime dateCompare); List<ReminderRecordViewModel> GetReminderRecordViewModels(List<ReminderRecord> reminders, decimal currentMileage, DateTime dateCompare);
} }
public class ReminderHelper: IReminderHelper public class ReminderHelper: IReminderHelper
{ {
@@ -14,7 +14,7 @@ namespace CarCareTracker.Helper
{ {
_config = config; _config = config;
} }
public ReminderRecord GetUpdatedRecurringReminderRecord(ReminderRecord existingReminder, DateTime? currentDate, int? currentMileage) public ReminderRecord GetUpdatedRecurringReminderRecord(ReminderRecord existingReminder, DateTime? currentDate, decimal? currentMileage)
{ {
var newDate = currentDate ?? existingReminder.Date; var newDate = currentDate ?? existingReminder.Date;
var newMileage = currentMileage ?? existingReminder.Mileage; var newMileage = currentMileage ?? existingReminder.Mileage;
@@ -60,12 +60,16 @@ namespace CarCareTracker.Helper
} }
return existingReminder; return existingReminder;
} }
public List<ReminderRecordViewModel> GetReminderRecordViewModels(List<ReminderRecord> reminders, int currentMileage, DateTime dateCompare) public List<ReminderRecordViewModel> GetReminderRecordViewModels(List<ReminderRecord> reminders, decimal currentMileage, DateTime dateCompare)
{ {
List<ReminderRecordViewModel> reminderViewModels = new List<ReminderRecordViewModel>(); List<ReminderRecordViewModel> reminderViewModels = new List<ReminderRecordViewModel>();
var reminderUrgencyConfig = _config.GetReminderUrgencyConfig(); var reminderUrgencyConfig = _config.GetReminderUrgencyConfig();
foreach (var reminder in reminders) foreach (var reminder in reminders)
{ {
if (reminder.UseCustomThresholds)
{
reminderUrgencyConfig = reminder.CustomThresholds;
}
var reminderViewModel = new ReminderRecordViewModel() var reminderViewModel = new ReminderRecordViewModel()
{ {
Id = reminder.Id, Id = reminder.Id,

View File

@@ -9,13 +9,17 @@ namespace CarCareTracker.Helper
/// </summary> /// </summary>
public static class StaticHelper public static class StaticHelper
{ {
public static string VersionNumber = "1.3.6"; public static string VersionNumber = "1.4.0";
public static string DbName = "data/cartracker.db"; public static string DbName = "data/cartracker.db";
public static string UserConfigPath = "config/userConfig.json"; public static string UserConfigPath = "config/userConfig.json";
public static string AdditionalWidgetsPath = "data/widgets.html";
public static string GenericErrorMessage = "An error occurred, please try again later"; public static string GenericErrorMessage = "An error occurred, please try again later";
public static string ReminderEmailTemplate = "defaults/reminderemailtemplate.txt"; public static string ReminderEmailTemplate = "defaults/reminderemailtemplate.txt";
public static string DefaultAllowedFileExtensions = ".png,.jpg,.jpeg,.pdf,.xls,.xlsx,.docx"; public static string DefaultAllowedFileExtensions = ".png,.jpg,.jpeg,.pdf,.xls,.xlsx,.docx";
public static string SponsorsPath = "https://hargata.github.io/hargata/sponsors.json"; public static string SponsorsPath = "https://hargata.github.io/hargata/sponsors.json";
public static string TranslationPath = "https://hargata.github.io/lubelog_translations";
public static 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) public static string GetTitleCaseReminderUrgency(ReminderUrgency input)
{ {
switch (input) switch (input)
@@ -30,6 +34,66 @@ namespace CarCareTracker.Helper
return input.ToString(); 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) public static string TruncateStrings(string input, int maxLength = 25)
{ {
@@ -119,6 +183,10 @@ namespace CarCareTracker.Helper
new CostForVehicleByMonth { MonthId = 12, Cost = 0M} 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) public static ServiceRecord GenericToServiceRecord(GenericRecord input)
{ {
@@ -199,6 +267,8 @@ namespace CarCareTracker.Helper
recordExtraFields.Add(extraField); recordExtraFields.Add(extraField);
} }
} }
//re-order extra fields
recordExtraFields = recordExtraFields.OrderBy(x => templateExtraFields.FindIndex(y => y.Name == x.Name)).ToList();
return recordExtraFields; return recordExtraFields;
} }
@@ -244,6 +314,10 @@ namespace CarCareTracker.Helper
} }
var motd = config["LUBELOGGER_MOTD"] ?? "Not Configured"; var motd = config["LUBELOGGER_MOTD"] ?? "Not Configured";
Console.WriteLine($"Message Of The Day: {motd}"); 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) public static async void NotifyAsync(string webhookURL, int vehicleId, string username, string action)
{ {
@@ -288,6 +362,85 @@ namespace CarCareTracker.Helper
return "bi-file-bar-graph"; 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 //CSV Write Methods
public static void WriteGenericRecordExportModel(CsvWriter _csv, IEnumerable<GenericRecordExportModel> genericRecords) public static void WriteGenericRecordExportModel(CsvWriter _csv, IEnumerable<GenericRecordExportModel> genericRecords)
{ {

View File

@@ -1,4 +1,5 @@
using Microsoft.Extensions.Caching.Memory; using CarCareTracker.Models;
using Microsoft.Extensions.Caching.Memory;
using System.Text.Json; using System.Text.Json;
namespace CarCareTracker.Helper namespace CarCareTracker.Helper
@@ -6,17 +7,22 @@ namespace CarCareTracker.Helper
public interface ITranslationHelper public interface ITranslationHelper
{ {
string Translate(string userLanguage, string text); 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 public class TranslationHelper : ITranslationHelper
{ {
private readonly IFileHelper _fileHelper; private readonly IFileHelper _fileHelper;
private readonly IConfiguration _config; private readonly IConfiguration _config;
private readonly ILogger<ITranslationHelper> _logger;
private IMemoryCache _cache; private IMemoryCache _cache;
public TranslationHelper(IFileHelper fileHelper, IConfiguration config, IMemoryCache memoryCache) public TranslationHelper(IFileHelper fileHelper, IConfiguration config, IMemoryCache memoryCache, ILogger<ITranslationHelper> logger)
{ {
_fileHelper = fileHelper; _fileHelper = fileHelper;
_config = config; _config = config;
_cache = memoryCache; _cache = memoryCache;
_logger = logger;
} }
public string Translate(string userLanguage, string text) public string Translate(string userLanguage, string text)
{ {
@@ -36,11 +42,13 @@ namespace CarCareTracker.Helper
return translationDictionary ?? new Dictionary<string, string>(); return translationDictionary ?? new Dictionary<string, string>();
} catch (Exception ex) } catch (Exception ex)
{ {
_logger.LogError(ex.Message);
return new Dictionary<string, string>(); return new Dictionary<string, string>();
} }
} }
else else
{ {
_logger.LogError($"Could not find translation file for {userLanguage}");
return new Dictionary<string, string>(); return new Dictionary<string, string>();
} }
}); });
@@ -52,10 +60,138 @@ namespace CarCareTracker.Helper
{ {
//create entry //create entry
dictionary.Add(translationKey, text); dictionary.Add(translationKey, text);
_logger.LogInformation($"Translation key added to {userLanguage} for {translationKey} with value {text}");
File.WriteAllText(translationFilePath, JsonSerializer.Serialize(dictionary)); File.WriteAllText(translationFilePath, JsonSerializer.Serialize(dictionary));
return text; return text;
} }
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 new OperationResponse { Success = false, Message = "The translation file name en_US is reserved." };
}
if (string.IsNullOrWhiteSpace(userLanguage))
{
return new OperationResponse { Success = false, Message = "File name is not provided." };
}
if (!translations.Any())
{
return new OperationResponse { Success = false, Message = "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 new OperationResponse { Success = true, Message = "Translation Updated" };
}
catch (Exception ex)
{
_logger.LogError(ex.Message);
return new OperationResponse { Success = false, Message = StaticHelper.GenericErrorMessage };
}
}
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

@@ -245,14 +245,7 @@ namespace CarCareTracker.Logic
{ {
if (UserIsRoot(credentials)) if (UserIsRoot(credentials))
{ {
return new UserData() return GetRootUserData(credentials.UserName);
{
Id = -1,
UserName = credentials.UserName,
IsAdmin = true,
IsRootUser = true,
EmailAddress = string.Empty
};
} }
else else
{ {
@@ -271,6 +264,13 @@ namespace CarCareTracker.Logic
} }
public UserData ValidateOpenIDUser(LoginModel credentials) 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); var result = _userData.GetUserRecordByEmailAddress(credentials.EmailAddress);
if (result.Id != default) if (result.Id != default)
{ {
@@ -420,6 +420,17 @@ namespace CarCareTracker.Logic
var hashedPassword = GetHash(credentials.Password); var hashedPassword = GetHash(credentials.Password);
return _configHelper.AuthenticateRootUser(hashedUserName, hashedPassword); 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 #endregion
private static string GetHash(string value) private static string GetHash(string value)
{ {

View File

@@ -5,7 +5,7 @@ namespace CarCareTracker.Logic
{ {
public interface IOdometerLogic public interface IOdometerLogic
{ {
int GetLastOdometerRecordMileage(int vehicleId, List<OdometerRecord> odometerRecords); decimal GetLastOdometerRecordMileage(int vehicleId, List<OdometerRecord> odometerRecords);
bool AutoInsertOdometerRecord(OdometerRecord odometer); bool AutoInsertOdometerRecord(OdometerRecord odometer);
List<OdometerRecord> AutoConvertOdometerRecord(List<OdometerRecord> odometerRecords); List<OdometerRecord> AutoConvertOdometerRecord(List<OdometerRecord> odometerRecords);
} }
@@ -18,7 +18,7 @@ namespace CarCareTracker.Logic
_odometerRecordDataAccess = odometerRecordDataAccess; _odometerRecordDataAccess = odometerRecordDataAccess;
_logger = logger; _logger = logger;
} }
public int GetLastOdometerRecordMileage(int vehicleId, List<OdometerRecord> odometerRecords) public decimal GetLastOdometerRecordMileage(int vehicleId, List<OdometerRecord> odometerRecords)
{ {
if (!odometerRecords.Any()) if (!odometerRecords.Any())
{ {
@@ -33,6 +33,10 @@ namespace CarCareTracker.Logic
} }
public bool AutoInsertOdometerRecord(OdometerRecord odometer) public bool AutoInsertOdometerRecord(OdometerRecord odometer)
{ {
if (odometer.Mileage == default)
{
return false;
}
var lastReportedMileage = GetLastOdometerRecordMileage(odometer.VehicleId, new List<OdometerRecord>()); var lastReportedMileage = GetLastOdometerRecordMileage(odometer.VehicleId, new List<OdometerRecord>());
odometer.InitialMileage = lastReportedMileage != default ? lastReportedMileage : odometer.Mileage; odometer.InitialMileage = lastReportedMileage != default ? lastReportedMileage : odometer.Mileage;
@@ -43,7 +47,7 @@ namespace CarCareTracker.Logic
{ {
//perform ordering //perform ordering
odometerRecords = odometerRecords.OrderBy(x => x.Date).ThenBy(x => x.Mileage).ToList(); odometerRecords = odometerRecords.OrderBy(x => x.Date).ThenBy(x => x.Mileage).ToList();
int previousMileage = 0; decimal previousMileage = 0.00M;
for (int i = 0; i < odometerRecords.Count; i++) for (int i = 0; i < odometerRecords.Count; i++)
{ {
var currentObject = odometerRecords[i]; var currentObject = odometerRecords[i];

View File

@@ -6,9 +6,17 @@ namespace CarCareTracker.Logic
{ {
public interface IVehicleLogic public interface IVehicleLogic
{ {
int GetMaxMileage(int vehicleId); VehicleRecords GetVehicleRecords(int vehicleId);
int GetMinMileage(int vehicleId); decimal GetVehicleTotalCost(VehicleRecords vehicleRecords);
bool GetVehicleHasUrgentOrPastDueReminders(int vehicleId); decimal GetMaxMileage(int vehicleId);
decimal GetMaxMileage(VehicleRecords vehicleRecords);
decimal GetMinMileage(int vehicleId);
decimal 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, decimal 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 public class VehicleLogic: IVehicleLogic
{ {
@@ -16,29 +24,56 @@ namespace CarCareTracker.Logic
private readonly IGasRecordDataAccess _gasRecordDataAccess; private readonly IGasRecordDataAccess _gasRecordDataAccess;
private readonly ICollisionRecordDataAccess _collisionRecordDataAccess; private readonly ICollisionRecordDataAccess _collisionRecordDataAccess;
private readonly IUpgradeRecordDataAccess _upgradeRecordDataAccess; private readonly IUpgradeRecordDataAccess _upgradeRecordDataAccess;
private readonly ITaxRecordDataAccess _taxRecordDataAccess;
private readonly IOdometerRecordDataAccess _odometerRecordDataAccess; private readonly IOdometerRecordDataAccess _odometerRecordDataAccess;
private readonly IReminderRecordDataAccess _reminderRecordDataAccess; private readonly IReminderRecordDataAccess _reminderRecordDataAccess;
private readonly IPlanRecordDataAccess _planRecordDataAccess;
private readonly IReminderHelper _reminderHelper; private readonly IReminderHelper _reminderHelper;
public VehicleLogic( public VehicleLogic(
IServiceRecordDataAccess serviceRecordDataAccess, IServiceRecordDataAccess serviceRecordDataAccess,
IGasRecordDataAccess gasRecordDataAccess, IGasRecordDataAccess gasRecordDataAccess,
ICollisionRecordDataAccess collisionRecordDataAccess, ICollisionRecordDataAccess collisionRecordDataAccess,
IUpgradeRecordDataAccess upgradeRecordDataAccess, IUpgradeRecordDataAccess upgradeRecordDataAccess,
ITaxRecordDataAccess taxRecordDataAccess,
IOdometerRecordDataAccess odometerRecordDataAccess, IOdometerRecordDataAccess odometerRecordDataAccess,
IReminderRecordDataAccess reminderRecordDataAccess, IReminderRecordDataAccess reminderRecordDataAccess,
IPlanRecordDataAccess planRecordDataAccess,
IReminderHelper reminderHelper IReminderHelper reminderHelper
) { ) {
_serviceRecordDataAccess = serviceRecordDataAccess; _serviceRecordDataAccess = serviceRecordDataAccess;
_gasRecordDataAccess = gasRecordDataAccess; _gasRecordDataAccess = gasRecordDataAccess;
_collisionRecordDataAccess = collisionRecordDataAccess; _collisionRecordDataAccess = collisionRecordDataAccess;
_upgradeRecordDataAccess = upgradeRecordDataAccess; _upgradeRecordDataAccess = upgradeRecordDataAccess;
_taxRecordDataAccess = taxRecordDataAccess;
_odometerRecordDataAccess = odometerRecordDataAccess; _odometerRecordDataAccess = odometerRecordDataAccess;
_planRecordDataAccess = planRecordDataAccess;
_reminderRecordDataAccess = reminderRecordDataAccess; _reminderRecordDataAccess = reminderRecordDataAccess;
_reminderHelper = reminderHelper; _reminderHelper = reminderHelper;
} }
public int GetMaxMileage(int vehicleId) public VehicleRecords GetVehicleRecords(int vehicleId)
{ {
var numbersArray = new List<int>(); 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 decimal GetMaxMileage(int vehicleId)
{
var numbersArray = new List<decimal>();
var serviceRecords = _serviceRecordDataAccess.GetServiceRecordsByVehicleId(vehicleId); var serviceRecords = _serviceRecordDataAccess.GetServiceRecordsByVehicleId(vehicleId);
if (serviceRecords.Any()) if (serviceRecords.Any())
{ {
@@ -64,11 +99,36 @@ namespace CarCareTracker.Logic
{ {
numbersArray.Add(odometerRecords.Max(x => x.Mileage)); numbersArray.Add(odometerRecords.Max(x => x.Mileage));
} }
return numbersArray.Any() ? numbersArray.Max() : 0.00M;
}
public decimal GetMaxMileage(VehicleRecords vehicleRecords)
{
var numbersArray = new List<decimal>();
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; return numbersArray.Any() ? numbersArray.Max() : 0;
} }
public int GetMinMileage(int vehicleId) public decimal GetMinMileage(int vehicleId)
{ {
var numbersArray = new List<int>(); var numbersArray = new List<decimal>();
var serviceRecords = _serviceRecordDataAccess.GetServiceRecordsByVehicleId(vehicleId).Where(x => x.Mileage != default); var serviceRecords = _serviceRecordDataAccess.GetServiceRecordsByVehicleId(vehicleId).Where(x => x.Mileage != default);
if (serviceRecords.Any()) if (serviceRecords.Any())
{ {
@@ -96,12 +156,166 @@ namespace CarCareTracker.Logic
} }
return numbersArray.Any() ? numbersArray.Min() : 0; return numbersArray.Any() ? numbersArray.Min() : 0;
} }
public bool GetVehicleHasUrgentOrPastDueReminders(int vehicleId) public decimal GetMinMileage(VehicleRecords vehicleRecords)
{
var numbersArray = new List<decimal>();
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, decimal currentMileage)
{ {
var currentMileage = GetMaxMileage(vehicleId);
var reminders = _reminderRecordDataAccess.GetReminderRecordsByVehicleId(vehicleId); var reminders = _reminderRecordDataAccess.GetReminderRecordsByVehicleId(vehicleId);
var results = _reminderHelper.GetReminderRecordViewModels(reminders, currentMileage, DateTime.Now); var results = _reminderHelper.GetReminderRecordViewModels(reminders, currentMileage, DateTime.Now);
return results.Any(x => x.Urgency == ReminderUrgency.VeryUrgent || x.Urgency == ReminderUrgency.PastDue); 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 { 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

@@ -4,6 +4,7 @@ using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.DataProtection; using Microsoft.AspNetCore.DataProtection;
using Microsoft.Extensions.Options; using Microsoft.Extensions.Options;
using System.Security.Claims; using System.Security.Claims;
using System.Text;
using System.Text.Encodings.Web; using System.Text.Encodings.Web;
using System.Text.Json; using System.Text.Json;
@@ -42,7 +43,7 @@ namespace CarCareTracker.Middleware
new(ClaimTypes.Role, nameof(UserData.IsRootUser)) new(ClaimTypes.Role, nameof(UserData.IsRootUser))
}; };
appIdentity.AddClaims(userIdentity); 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); return AuthenticateResult.Success(ticket);
} }
else else
@@ -59,7 +60,7 @@ namespace CarCareTracker.Middleware
{ {
var cleanedHeader = request_header.ToString().Replace("Basic ", "").Trim(); var cleanedHeader = request_header.ToString().Replace("Basic ", "").Trim();
byte[] data = Convert.FromBase64String(cleanedHeader); byte[] data = Convert.FromBase64String(cleanedHeader);
string decodedString = System.Text.Encoding.UTF8.GetString(data); string decodedString = Encoding.UTF8.GetString(data);
var splitString = decodedString.Split(":"); var splitString = decodedString.Split(":");
if (splitString.Count() != 2) if (splitString.Count() != 2)
{ {
@@ -85,7 +86,7 @@ namespace CarCareTracker.Middleware
userIdentity.Add(new(ClaimTypes.Role, nameof(UserData.IsRootUser))); userIdentity.Add(new(ClaimTypes.Role, nameof(UserData.IsRootUser)));
} }
appIdentity.AddClaims(userIdentity); 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); return AuthenticateResult.Success(ticket);
} }
} }
@@ -133,7 +134,7 @@ namespace CarCareTracker.Middleware
userIdentity.Add(new(ClaimTypes.Role, nameof(UserData.IsRootUser))); userIdentity.Add(new(ClaimTypes.Role, nameof(UserData.IsRootUser)));
} }
appIdentity.AddClaims(userIdentity); 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); 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 decimal 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

@@ -6,7 +6,7 @@
public int VehicleId { get; set; } public int VehicleId { get; set; }
public List<int> ReminderRecordId { get; set; } = new List<int>(); public List<int> ReminderRecordId { get; set; } = new List<int>();
public string Date { get; set; } = DateTime.Now.ToShortDateString(); public string Date { get; set; } = DateTime.Now.ToShortDateString();
public int Mileage { get; set; } public decimal Mileage { get; set; }
public string Description { get; set; } public string Description { get; set; }
public decimal Cost { get; set; } public decimal Cost { get; set; }
public string Notes { get; set; } public string Notes { get; set; }

View File

@@ -8,7 +8,7 @@
/// <summary> /// <summary>
/// American moment /// American moment
/// </summary> /// </summary>
public int Mileage { get; set; } public decimal Mileage { get; set; }
/// <summary> /// <summary>
/// Wtf is a kilometer? /// Wtf is a kilometer?
/// </summary> /// </summary>

View File

@@ -8,7 +8,7 @@
/// <summary> /// <summary>
/// American moment /// American moment
/// </summary> /// </summary>
public int Mileage { get; set; } public decimal Mileage { get; set; }
/// <summary> /// <summary>
/// Wtf is a kilometer? /// Wtf is a kilometer?
/// </summary> /// </summary>

View File

@@ -9,13 +9,13 @@
/// <summary> /// <summary>
/// American moment /// American moment
/// </summary> /// </summary>
public int Mileage { get; set; } public decimal Mileage { get; set; }
/// <summary> /// <summary>
/// Wtf is a kilometer? /// Wtf is a kilometer?
/// </summary> /// </summary>
public decimal Gallons { get; set; } public decimal Gallons { get; set; }
public decimal Cost { get; set; } public decimal Cost { get; set; }
public int DeltaMileage { get; set; } public decimal DeltaMileage { get; set; }
public decimal MilesPerGallon { get; set; } public decimal MilesPerGallon { get; set; }
public decimal CostPerGallon { get; set; } public decimal CostPerGallon { get; set; }
public bool IsFillToFull { get; set; } public bool IsFillToFull { 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

@@ -5,9 +5,9 @@
public int Id { get; set; } public int Id { get; set; }
public int VehicleId { get; set; } public int VehicleId { get; set; }
public DateTime Date { get; set; } public DateTime Date { get; set; }
public int InitialMileage { get; set; } public decimal InitialMileage { get; set; }
public int Mileage { get; set; } public decimal Mileage { get; set; }
public int DistanceTraveled { get { return Mileage - InitialMileage; } } public decimal DistanceTraveled { get { return Mileage - InitialMileage; } }
public string Notes { get; set; } public string Notes { get; set; }
public List<string> Tags { get; set; } = new List<string>(); public List<string> Tags { get; set; } = new List<string>();
public List<UploadedFiles> Files { get; set; } = new List<UploadedFiles>(); public List<UploadedFiles> Files { get; set; } = new List<UploadedFiles>();

View File

@@ -5,8 +5,8 @@
public int Id { get; set; } public int Id { get; set; }
public int VehicleId { get; set; } public int VehicleId { get; set; }
public string Date { get; set; } = DateTime.Now.ToShortDateString(); public string Date { get; set; } = DateTime.Now.ToShortDateString();
public int InitialMileage { get; set; } public decimal InitialMileage { get; set; }
public int Mileage { get; set; } public decimal Mileage { get; set; }
public string Notes { get; set; } public string Notes { get; set; }
public List<UploadedFiles> Files { get; set; } = new List<UploadedFiles>(); public List<UploadedFiles> Files { get; set; } = new List<UploadedFiles>();
public List<string> Tags { get; set; } = new List<string>(); public List<string> Tags { get; set; } = new List<string>();

View File

@@ -5,10 +5,12 @@
public int Id { get; set; } public int Id { get; set; }
public int VehicleId { get; set; } public int VehicleId { get; set; }
public DateTime Date { get; set; } public DateTime Date { get; set; }
public int Mileage { get; set; } public decimal Mileage { get; set; }
public string Description { get; set; } public string Description { get; set; }
public string Notes { get; set; } public string Notes { get; set; }
public bool IsRecurring { get; set; } = false; 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 CustomMileageInterval { get; set; } = 0;
public int CustomMonthInterval { get; set; } = 0; public int CustomMonthInterval { get; set; } = 0;
public ReminderMileageInterval ReminderMileageInterval { get; set; } = ReminderMileageInterval.FiveThousandMiles; public ReminderMileageInterval ReminderMileageInterval { get; set; } = ReminderMileageInterval.FiveThousandMiles;

View File

@@ -5,10 +5,12 @@
public int Id { get; set; } public int Id { get; set; }
public int VehicleId { get; set; } public int VehicleId { get; set; }
public string Date { get; set; } = DateTime.Now.AddDays(1).ToShortDateString(); public string Date { get; set; } = DateTime.Now.AddDays(1).ToShortDateString();
public int Mileage { get; set; } public decimal Mileage { get; set; }
public string Description { get; set; } public string Description { get; set; }
public string Notes { get; set; } public string Notes { get; set; }
public bool IsRecurring { get; set; } = false; 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 CustomMileageInterval { get; set; } = 0;
public int CustomMonthInterval { get; set; } = 0; public int CustomMonthInterval { get; set; } = 0;
public ReminderMileageInterval ReminderMileageInterval { get; set; } = ReminderMileageInterval.FiveThousandMiles; public ReminderMileageInterval ReminderMileageInterval { get; set; } = ReminderMileageInterval.FiveThousandMiles;
@@ -26,6 +28,8 @@
Description = Description, Description = Description,
Metric = Metric, Metric = Metric,
IsRecurring = IsRecurring, IsRecurring = IsRecurring,
UseCustomThresholds = UseCustomThresholds,
CustomThresholds = CustomThresholds,
ReminderMileageInterval = ReminderMileageInterval, ReminderMileageInterval = ReminderMileageInterval,
ReminderMonthInterval = ReminderMonthInterval, ReminderMonthInterval = ReminderMonthInterval,
CustomMileageInterval = CustomMileageInterval, CustomMileageInterval = CustomMileageInterval,

View File

@@ -5,7 +5,7 @@
public int Id { get; set; } public int Id { get; set; }
public int VehicleId { get; set; } public int VehicleId { get; set; }
public DateTime Date { get; set; } public DateTime Date { get; set; }
public int Mileage { get; set; } public decimal Mileage { get; set; }
public string Description { get; set; } public string Description { get; set; }
public string Notes { get; set; } public string Notes { get; set; }
/// <summary> /// <summary>

View File

@@ -5,6 +5,6 @@
public int MonthId { get; set; } public int MonthId { get; set; }
public string MonthName { get; set; } public string MonthName { get; set; }
public decimal Cost { get; set; } public decimal Cost { get; set; }
public int DistanceTraveled { get; set; } public decimal DistanceTraveled { get; set; }
} }
} }

View File

@@ -0,0 +1,27 @@
namespace CarCareTracker.Models
{
public class CostTableForVehicle
{
public string DistanceUnit { get; set; } = "Cost Per Mile";
public decimal 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

@@ -7,10 +7,11 @@
{ {
public ImportMode DataType { get; set; } public ImportMode DataType { get; set; }
public DateTime Date { get; set; } public DateTime Date { get; set; }
public int Odometer { get; set; } public decimal Odometer { get; set; }
public string Description { get; set; } public string Description { get; set; }
public string Notes { get; set; } public string Notes { get; set; }
public decimal Cost { get; set; } public decimal Cost { get; set; }
public List<UploadedFiles> Files { get; set; } = new List<UploadedFiles>(); 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 class ReportViewModel
{ {
public List<CostForVehicleByMonth> CostForVehicleByMonth { get; set; } = new List<CostForVehicleByMonth>(); 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 CostMakeUpForVehicle CostMakeUpForVehicle { get; set; } = new CostMakeUpForVehicle();
public ReminderMakeUpForVehicle ReminderMakeUpForVehicle { get; set; } = new ReminderMakeUpForVehicle(); public ReminderMakeUpForVehicle ReminderMakeUpForVehicle { get; set; } = new ReminderMakeUpForVehicle();
public List<int> Years { get; set; } = new List<int>(); public List<int> Years { get; set; } = new List<int>();
public List<UserCollaborator> Collaborators { get; set; } = new List<UserCollaborator>(); 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 Vehicle VehicleData { get; set; }
public List<GenericReportModel> VehicleHistory { get; set; } public List<GenericReportModel> VehicleHistory { get; set; }
public ReportParameter ReportParameters { get; set; }
public string Odometer { get; set; } public string Odometer { get; set; }
public string MPG { get; set; } public string MPG { get; set; }
public decimal TotalCost { get; set; } public decimal TotalCost { get; set; }

View File

@@ -6,7 +6,7 @@
public int VehicleId { get; set; } public int VehicleId { get; set; }
public List<int> ReminderRecordId { get; set; } = new List<int>(); public List<int> ReminderRecordId { get; set; } = new List<int>();
public string Date { get; set; } = DateTime.Now.ToShortDateString(); public string Date { get; set; } = DateTime.Now.ToShortDateString();
public int Mileage { get; set; } public decimal Mileage { get; set; }
public string Description { get; set; } public string Description { get; set; }
public decimal Cost { get; set; } public decimal Cost { get; set; }
public string Notes { get; set; } public string Notes { get; set; }

View File

@@ -4,6 +4,5 @@ namespace CarCareTracker.Models
{ {
public UserConfig UserConfig { get; set; } public UserConfig UserConfig { get; set; }
public List<string> UILanguages { get; set; } public List<string> UILanguages { get; set; }
public Sponsors Sponsors { get; set; } = new Sponsors();
} }
} }

View File

@@ -5,7 +5,7 @@
public int Id { get; set; } public int Id { get; set; }
public int VehicleId { get; set; } public int VehicleId { get; set; }
public DateTime Date { get; set; } public DateTime Date { get; set; }
public int Mileage { get; set; } public decimal Mileage { get; set; }
public string Description { get; set; } public string Description { get; set; }
public decimal Cost { get; set; } public decimal Cost { get; set; }
public string Notes { get; set; } public string Notes { 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>();
}
}

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; } }
}
}

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

@@ -6,7 +6,7 @@
public int VehicleId { get; set; } public int VehicleId { get; set; }
public List<int> ReminderRecordId { get; set; } = new List<int>(); public List<int> ReminderRecordId { get; set; } = new List<int>();
public string Date { get; set; } = DateTime.Now.ToShortDateString(); public string Date { get; set; } = DateTime.Now.ToShortDateString();
public int Mileage { get; set; } public decimal Mileage { get; set; }
public string Description { get; set; } public string Description { get; set; }
public decimal Cost { get; set; } public decimal Cost { get; set; }
public string Notes { get; set; } public string Notes { get; set; }

View File

@@ -3,10 +3,13 @@
public class UserConfig public class UserConfig
{ {
public bool UseDarkMode { get; set; } public bool UseDarkMode { get; set; }
public bool UseSystemColorMode { get; set; }
public bool EnableCsvImports { get; set; } public bool EnableCsvImports { get; set; }
public bool UseMPG { get; set; } public bool UseMPG { get; set; }
public bool UseDescending { get; set; } public bool UseDescending { get; set; }
public bool EnableAuth { get; set; } public bool EnableAuth { get; set; }
public bool DisableRegistration { get; set; }
public bool EnableRootUserOIDC { get; set; }
public bool HideZero { get; set; } public bool HideZero { get; set; }
public bool UseUKMPG {get;set;} public bool UseUKMPG {get;set;}
public bool UseThreeDecimalGasCost { get; set; } public bool UseThreeDecimalGasCost { get; set; }
@@ -16,12 +19,14 @@
public bool EnableShopSupplies { get; set; } public bool EnableShopSupplies { get; set; }
public bool EnableExtraFieldColumns { get; set; } public bool EnableExtraFieldColumns { get; set; }
public bool HideSoldVehicles { get; set; } public bool HideSoldVehicles { get; set; }
public bool AutomaticDecimalFormat { get; set; }
public string PreferredGasUnit { get; set; } = string.Empty; public string PreferredGasUnit { get; set; } = string.Empty;
public string PreferredGasMileageUnit { get; set; } = string.Empty; public string PreferredGasMileageUnit { get; set; } = string.Empty;
public List<UserColumnPreference> UserColumnPreferences { get; set; } = new List<UserColumnPreference>(); public List<UserColumnPreference> UserColumnPreferences { get; set; } = new List<UserColumnPreference>();
public ReminderUrgencyConfig ReminderUrgencyConfig { get; set; } = new ReminderUrgencyConfig(); public ReminderUrgencyConfig ReminderUrgencyConfig { get; set; } = new ReminderUrgencyConfig();
public string UserNameHash { get; set; } public string UserNameHash { get; set; }
public string UserPasswordHash { get; set;} public string UserPasswordHash { get; set;}
public string DefaultReminderEmail { get; set; } = string.Empty;
public string UserLanguage { get; set; } = "en_US"; public string UserLanguage { get; set; } = "en_US";
public List<ImportMode> VisibleTabs { get; set; } = new List<ImportMode>() { public List<ImportMode> VisibleTabs { get; set; } = new List<ImportMode>() {
ImportMode.Dashboard, ImportMode.Dashboard,
@@ -31,7 +36,21 @@
ImportMode.UpgradeRecord, ImportMode.UpgradeRecord,
ImportMode.TaxRecord, ImportMode.TaxRecord,
ImportMode.ReminderRecord, ImportMode.ReminderRecord,
ImportMode.NoteRecord}; ImportMode.NoteRecord
};
public ImportMode DefaultTab { get; set; } = ImportMode.Dashboard; 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

@@ -15,6 +15,7 @@
public bool IsElectric { get; set; } = false; public bool IsElectric { get; set; } = false;
public bool IsDiesel { get; set; } = false; public bool IsDiesel { get; set; } = false;
public bool UseHours { 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<ExtraField> ExtraFields { get; set; } = new List<ExtraField>();
public List<string> Tags { get; set; } = new List<string>(); public List<string> Tags { get; set; } = new List<string>();
public bool HasOdometerAdjustment { get; set; } = false; public bool HasOdometerAdjustment { get; set; } = false;
@@ -26,5 +27,10 @@
/// Primarily used for vehicles where the odometer does not reflect actual mileage. /// Primarily used for vehicles where the odometer does not reflect actual mileage.
/// </summary> /// </summary>
public string OdometerDifference { get; set; } = "0"; 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

@@ -12,9 +12,16 @@
public bool IsElectric { get; set; } = false; public bool IsElectric { get; set; } = false;
public bool IsDiesel { get; set; } = false; public bool IsDiesel { get; set; } = false;
public bool UseHours { 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<ExtraField> ExtraFields { get; set; } = new List<ExtraField>();
public List<string> Tags { get; set; } = new List<string>(); public List<string> Tags { get; set; } = new List<string>();
public int LastReportedMileage; public string VehicleIdentifier { get; set; } = "LicensePlate";
public bool HasReminders = false; //Dashboard Metric Attributes
public List<DashboardMetric> DashboardMetrics { get; set; } = new List<DashboardMetric>();
public decimal 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

@@ -20,7 +20,7 @@ Try it out before you download it! The live demo resets every 20 minutes.
## Download ## Download
LubeLogger is available as both a Docker Image and a Windows Standalone Executable. 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
### Kubernetes Deployment ### Kubernetes Deployment
[Helm Chart](https://artifacthub.io/packages/helm/anza-labs/lubelogger) provided by [Anza-Labs](https://github.com/anza-labs) [Helm Chart](https://artifacthub.io/packages/helm/anza-labs/lubelogger) provided by [Anza-Labs](https://github.com/anza-labs)
@@ -28,7 +28,7 @@ Read this [Getting Started Guide](https://docs.lubelogger.com/Getting%20Started)
### Need Help? ### Need Help?
[Documentation](https://docs.lubelogger.com/) [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) [Search Existing Issues](https://github.com/hargata/lubelog/issues)
@@ -42,6 +42,7 @@ Read this [Getting Started Guide](https://docs.lubelogger.com/Getting%20Started)
- [Chart.js](https://github.com/chartjs/Chart.js) - [Chart.js](https://github.com/chartjs/Chart.js)
- [Drawdown](https://github.com/adamvleggett/drawdown) - [Drawdown](https://github.com/adamvleggett/drawdown)
- [MailKit](https://github.com/jstedfast/MailKit) - [MailKit](https://github.com/jstedfast/MailKit)
- [Masonry](https://github.com/desandro/masonry)
## License ## License
MIT MIT

View File

@@ -1,5 +1,5 @@
@{ @{
ViewData["Title"] = "LubeLogger API"; ViewData["Title"] = "API";
} }
<div class="row"> <div class="row">
<div class="d-flex justify-content-center"> <div class="d-flex justify-content-center">
@@ -26,7 +26,7 @@
<h6>Parameters</h6> <h6>Parameters</h6>
</div> </div>
</div> </div>
<div class="row"> <div class="row api-method">
<div class="col-1"> <div class="col-1">
GET GET
</div> </div>
@@ -40,7 +40,37 @@
No Params No Params
</div> </div>
</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"> <div class="col-1">
GET GET
</div> </div>
@@ -54,7 +84,7 @@
vehicleId - Id of Vehicle vehicleId - Id of Vehicle
</div> </div>
</div> </div>
<div class="row"> <div class="row api-method">
<div class="col-1"> <div class="col-1">
GET GET
</div> </div>
@@ -68,7 +98,7 @@
vehicleId - Id of Vehicle vehicleId - Id of Vehicle
</div> </div>
</div> </div>
<div class="row"> <div class="row api-method">
<div class="col-1"> <div class="col-1">
POST POST
</div> </div>
@@ -86,10 +116,12 @@
initialOdometer - Initial Odometer reading(optional)<br /> initialOdometer - Initial Odometer reading(optional)<br />
odometer - Odometer reading<br /> odometer - Odometer reading<br />
notes - notes(optional)<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> </div>
<div class="row"> <div class="row api-method">
<div class="col-1"> <div class="col-1">
GET GET
</div> </div>
@@ -103,7 +135,7 @@
vehicleId - Id of Vehicle vehicleId - Id of Vehicle
</div> </div>
</div> </div>
<div class="row"> <div class="row api-method">
<div class="col-1"> <div class="col-1">
POST POST
</div> </div>
@@ -122,10 +154,12 @@
description - Description<br/> description - Description<br/>
cost - Cost<br /> cost - Cost<br />
notes - notes(optional)<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> </div>
<div class="row"> <div class="row api-method">
<div class="col-1"> <div class="col-1">
GET GET
</div> </div>
@@ -139,7 +173,7 @@
vehicleId - Id of Vehicle vehicleId - Id of Vehicle
</div> </div>
</div> </div>
<div class="row"> <div class="row api-method">
<div class="col-1"> <div class="col-1">
POST POST
</div> </div>
@@ -158,10 +192,12 @@
description - Description<br /> description - Description<br />
cost - Cost<br /> cost - Cost<br />
notes - notes(optional)<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> </div>
<div class="row"> <div class="row api-method">
<div class="col-1"> <div class="col-1">
GET GET
</div> </div>
@@ -175,7 +211,7 @@
vehicleId - Id of Vehicle vehicleId - Id of Vehicle
</div> </div>
</div> </div>
<div class="row"> <div class="row api-method">
<div class="col-1"> <div class="col-1">
POST POST
</div> </div>
@@ -194,10 +230,12 @@
description - Description<br /> description - Description<br />
cost - Cost<br /> cost - Cost<br />
notes - notes(optional)<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> </div>
<div class="row"> <div class="row api-method">
<div class="col-1"> <div class="col-1">
GET GET
</div> </div>
@@ -211,7 +249,7 @@
vehicleId - Id of Vehicle vehicleId - Id of Vehicle
</div> </div>
</div> </div>
<div class="row"> <div class="row api-method">
<div class="col-1"> <div class="col-1">
POST POST
</div> </div>
@@ -229,10 +267,12 @@
description - Description<br /> description - Description<br />
cost - Cost<br /> cost - Cost<br />
notes - notes(optional)<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> </div>
<div class="row"> <div class="row api-method">
<div class="col-1"> <div class="col-1">
GET GET
</div> </div>
@@ -250,7 +290,7 @@
useUKMPG(bool) - Use UK Imperial Calculation useUKMPG(bool) - Use UK Imperial Calculation
</div> </div>
</div> </div>
<div class="row"> <div class="row api-method">
<div class="col-1"> <div class="col-1">
POST POST
</div> </div>
@@ -271,10 +311,12 @@
isFillToFull(bool) - Filled To Full<br /> isFillToFull(bool) - Filled To Full<br />
missedFuelUp(bool) - Missed Fuel Up<br /> missedFuelUp(bool) - Missed Fuel Up<br />
notes - notes(optional)<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> </div>
<div class="row"> <div class="row api-method">
<div class="col-1"> <div class="col-1">
GET GET
</div> </div>
@@ -290,7 +332,7 @@
</div> </div>
@if (User.IsInRole(nameof(UserData.IsRootUser))) @if (User.IsInRole(nameof(UserData.IsRootUser)))
{ {
<div class="row"> <div class="row api-method">
<div class="col-1"> <div class="col-1">
GET GET
</div> </div>
@@ -305,7 +347,7 @@
urgencies[]=[NotUrgent,Urgent,VeryUrgent,PastDue] urgencies[]=[NotUrgent,Urgent,VeryUrgent,PastDue]
</div> </div>
</div> </div>
<div class="row"> <div class="row api-method">
<div class="col-1"> <div class="col-1">
GET GET
</div> </div>
@@ -319,7 +361,7 @@
No Params(must be root user) No Params(must be root user)
</div> </div>
</div> </div>
<div class="row"> <div class="row api-method">
<div class="col-1"> <div class="col-1">
GET GET
</div> </div>
@@ -339,4 +381,11 @@
$('.copyable').on('click', function (e) { $('.copyable').on('click', function (e) {
copyToClipboard(e.currentTarget); 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> </script>

View File

@@ -1,6 +1,6 @@
@using CarCareTracker.Helper @using CarCareTracker.Helper
@{ @{
ViewData["Title"] = "Admin"; ViewData["Title"] = "Admin Panel";
} }
@inject IConfiguration config; @inject IConfiguration config;
@inject ITranslationHelper translator @inject ITranslationHelper translator
@@ -25,44 +25,17 @@
</div> </div>
<hr /> <hr />
<div class="row"> <div class="row">
<div class="col-md-5 col-12"> <div class="col-12">
<span class="lead">@translator.Translate(userLanguage, "Tokens")</span>
<hr />
<div class="row"> <div class="row">
<div class="col-6"> <div class="col-2 d-flex align-items-center">
<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>
<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>
</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> <span class="lead">@translator.Translate(userLanguage, "Users")</span>
</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>
<hr /> <hr />
<table class="table table-hover"> <table class="table table-hover">
<thead class="sticky-top"> <thead class="sticky-top">
@@ -73,58 +46,119 @@
<th scope="col" class="col-2">@translator.Translate(userLanguage, "Delete")</th> <th scope="col" class="col-2">@translator.Translate(userLanguage, "Delete")</th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody id="userTable">
@foreach (UserData userData in Model.Users) @await Html.PartialAsync("_Users", 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> </tbody>
</table> </table>
</div> </div>
</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> </div>
<script> <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"); var isChecked = $(sender).is(":checked");
$.post('/Admin/UpdateUserAdminStatus', { userId: userId, isAdmin: isChecked }, function (data) { $.post('/Admin/UpdateUserAdminStatus', { userId: userId, isAdmin: isChecked }, function (data) {
if (data){ if (data) {
reloadPage(); reloadUserTable();
} else { } else {
errorToast(genericErrorMessage()); errorToast(genericErrorMessage());
} }
}); });
} }
function reloadPage() { function toggleAutoNotify(e) {
window.location.reload(); if (!$("#enableAutoNotify").attr("disabled")) {
if ($(e.target).hasClass('btn')) {
$("#enableAutoNotify").trigger('click');
}
}
} }
function deleteToken(tokenId) { function deleteToken(tokenId) {
$.post(`/Admin/DeleteToken?tokenId=${tokenId}`, function (data) { $.post(`/Admin/DeleteToken?tokenId=${tokenId}`, function (data) {
if (data) { if (data) {
reloadPage(); reloadTokenTable();
} else { } else {
errorToast(genericErrorMessage()); errorToast(genericErrorMessage());
} }
}); });
} }
function deleteUser(userId) { function deleteUser(userId) {
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) { $.post(`/Admin/DeleteUser?userId=${userId}`, function (data) {
if (data) { if (data) {
reloadPage(); reloadUserTable();
} else { } else {
errorToast(genericErrorMessage()); errorToast(genericErrorMessage());
} }
}) });
}
});
} }
function generateNewToken() { function generateNewToken() {
Swal.fire({ Swal.fire({
title: 'Generate Token', title: 'Generate Token',
html: ` 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', confirmButtonText: 'Generate',
focusConfirm: false, focusConfirm: false,
@@ -140,7 +174,7 @@
var autoNotify = $("#enableAutoNotify").is(":checked"); var autoNotify = $("#enableAutoNotify").is(":checked");
$.get('/Admin/GenerateNewToken', { emailAddress: result.value.emailAddress, autoNotify: autoNotify }, function (data) { $.get('/Admin/GenerateNewToken', { emailAddress: result.value.emailAddress, autoNotify: autoNotify }, function (data) {
if (data.success) { if (data.success) {
reloadPage(); reloadTokenTable();
} else { } else {
errorToast(data.message) 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>
}

11
Views/Admin/_Users.cshtml Normal file
View File

@@ -0,0 +1,11 @@
@using CarCareTracker.Helper
@model List<UserData>
@foreach (UserData userData in Model)
{
<tr class="d-flex" style="cursor:pointer;">
<td class="col-4 d-flex align-items-center">@StaticHelper.TruncateStrings(userData.UserName)</td>
<td class="col-4 d-flex align-items-center">@StaticHelper.TruncateStrings(userData.EmailAddress)</td>
<td class="col-2 d-flex align-items-center"><input class="form-check-input" type="checkbox" value="" onchange="updateUserAdmin(@userData.Id, this)" @(userData.IsAdmin ? "checked" : "") /></td>
<td class="col-2 d-flex align-items-center"><button type="button" class="btn btn-danger" onclick="deleteUser(@userData.Id, this)"><i class="bi bi-trash"></i></button></td>
</tr>
}

View File

@@ -5,14 +5,14 @@
var userConfig = config.GetUserConfig(User); var userConfig = config.GetUserConfig(User);
var enableAuth = userConfig.EnableAuth; var enableAuth = userConfig.EnableAuth;
var userLanguage = userConfig.UserLanguage; var userLanguage = userConfig.UserLanguage;
var logoUrl = config.GetLogoUrl();
} }
@model string @model string
@{ @{
ViewData["Title"] = "LubeLogger"; ViewData["Title"] = "Garage";
} }
@section Scripts { @section Scripts {
<script src="~/js/garage.js?v=@StaticHelper.VersionNumber"></script> <script src="~/js/garage.js?v=@StaticHelper.VersionNumber"></script>
<script src="~/js/settings.js?v=@StaticHelper.VersionNumber"></script>
<script src="~/js/supplyrecord.js?v=@StaticHelper.VersionNumber"></script> <script src="~/js/supplyrecord.js?v=@StaticHelper.VersionNumber"></script>
<script src="~/lib/drawdown/drawdown.js"></script> <script src="~/lib/drawdown/drawdown.js"></script>
} }
@@ -61,33 +61,33 @@
<div class="container"> <div class="container">
<div class="row mt-2"> <div class="row mt-2">
<div class="d-flex lubelogger-navbar"> <div class="d-flex lubelogger-navbar">
<img src="@logoUrl" /> <img src="@config.GetLogoUrl()" class="lubelogger-logo" />
<div class="lubelogger-navbar-button"> <div class="lubelogger-navbar-button">
<button type="button" class="btn btn-dark" onclick="showMobileNav()"><i class="bi bi-list lubelogger-menu-icon"></i></button> <button type="button" class="btn btn-adaptive" onclick="showMobileNav()"><i class="bi bi-list lubelogger-menu-icon"></i></button>
</div> </div>
</div> </div>
</div> </div>
<hr /> <hr />
<ul class="nav nav-tabs lubelogger-tab" id="homeTab" role="tablist"> <ul class="nav nav-tabs lubelogger-tab" id="homeTab" role="tablist">
<li class="nav-item" role="presentation"> <li class="nav-item" role="presentation">
<button class="nav-link @(Model == "garage" ? "active" : "")" oncontextmenu="sortGarage(this)" id="garage-tab" data-bs-toggle="tab" data-bs-target="#garage-tab-pane" type="button" role="tab"><i class="bi bi-car-front me-2"></i>@translator.Translate(userLanguage,"Garage")</button> <button class="nav-link resizable-nav-link @(Model == "garage" ? "active" : "")" oncontextmenu="sortGarage(this)" id="garage-tab" data-bs-toggle="tab" data-bs-target="#garage-tab-pane" type="button" role="tab"><i class="bi bi-car-front"></i><span class="ms-2 d-sm-none d-md-inline">@translator.Translate(userLanguage, "Garage")</span></button>
</li> </li>
@if (config.GetServerEnableShopSupplies()) @if (config.GetServerEnableShopSupplies())
{ {
<li class="nav-item" role="presentation"> <li class="nav-item" role="presentation">
<button class="nav-link" id="supply-tab" data-bs-toggle="tab" data-bs-target="#supply-tab-pane" type="button" role="tab" aria-selected="false"><i class="bi bi-shop me-2"></i>@translator.Translate(userLanguage, "Supplies")</button> <button class="nav-link resizable-nav-link" id="supply-tab" data-bs-toggle="tab" data-bs-target="#supply-tab-pane" type="button" role="tab" aria-selected="false"><i class="bi bi-shop"></i><span class="ms-2 d-sm-none d-md-inline">@translator.Translate(userLanguage, "Supplies")</span></button>
</li> </li>
} }
<li class="nav-item" role="presentation"> <li class="nav-item" role="presentation">
<button class="nav-link" id="calendar-tab" data-bs-toggle="tab" data-bs-target="#calendar-tab-pane" type="button" role="tab" aria-selected="false"><i class="bi bi-calendar-week me-2"></i>@translator.Translate(userLanguage, "Calendar")</button> <button class="nav-link resizable-nav-link" id="calendar-tab" data-bs-toggle="tab" data-bs-target="#calendar-tab-pane" type="button" role="tab" aria-selected="false"><i class="bi bi-calendar-week"></i><span class="ms-2 d-sm-none d-md-inline">@translator.Translate(userLanguage, "Calendar")</span></button>
</li> </li>
<li class="nav-item ms-auto" role="presentation"> <li class="nav-item ms-auto" role="presentation">
<button class="nav-link @(Model == "settings" ? "active" : "")" id="settings-tab" data-bs-toggle="tab" data-bs-target="#settings-tab-pane" type="button" role="tab"><i class="bi bi-gear me-2"></i>@translator.Translate(userLanguage,"Settings")</button> <button class="nav-link resizable-nav-link @(Model == "settings" ? "active" : "")" id="settings-tab" data-bs-toggle="tab" data-bs-target="#settings-tab-pane" type="button" role="tab"><i class="bi bi-gear"></i><span class="ms-2 d-sm-none d-md-inline">@translator.Translate(userLanguage, "Settings")</span></button>
</li> </li>
@if (User.IsInRole("CookieAuth")) @if (User.IsInRole("CookieAuth"))
{ {
<li class="nav-item dropdown" role="presentation"> <li class="nav-item dropdown" role="presentation">
<a class="nav-link dropdown-toggle" data-bs-toggle="dropdown" href="#" role="button" aria-expanded="false"><i class="bi bi-person me-2"></i>@User.Identity.Name</a> <a class="nav-link resizable-nav-link dropdown-toggle" data-bs-toggle="dropdown" href="#" role="button" aria-expanded="false"><i class="bi bi-person"></i><span class="ms-2 d-sm-none d-md-inline">@User.Identity.Name</span></a>
<ul class="dropdown-menu"> <ul class="dropdown-menu">
@if (User.IsInRole(nameof(UserData.IsAdmin))) @if (User.IsInRole(nameof(UserData.IsAdmin)))
{ {

148
Views/Home/Kiosk.cshtml Normal file
View File

@@ -0,0 +1,148 @@
@{
ViewData["Title"] = "Kiosk";
}
@model KioskViewModel
@section Scripts {
<script src="~/lib/masonry/masonry.min.js"></script>
}
<div class="progress" role="progressbar" aria-label="Refresh Progress" aria-valuenow="25" aria-valuemin="0" aria-valuemax="100" style="height: 1px">
<div class="progress-bar" style="width: 0%"></div>
</div>
<div id="kioskContainer" class="container-fluid">
</div>
<script>
let refreshTimer;
let exceptionList = [];
let subtractAmount = 0;
let kioskMode = '@Model.KioskMode';
let currentKioskMode = 'Plan';
let kioskWakeLock;
@foreach(int exception in Model.Exclusions)
{
@:exceptionList.push(@exception);
}
function initKiosk() {
$("body > div").removeClass("container");
$("body > div").css('height', '100vh');
subtractAmount = parseInt($("#kioskContainer").width() * 0.0016); //remove 0.0016% of width every 100 ms which will approximate to one minute.
if (subtractAmount < 2) {
subtractAmount = 2;
}
retrieveKioskContent();
//acquire wakeLock;
try {
navigator.wakeLock.request('screen').then((wl) => {
kioskWakeLock = wl;
});
} catch (err) {
errorToast('Location Services not Enabled');
}
}
function setAccessToken(accessToken){
//use this function to never worry about user session expiring.
$.ajaxSetup({
headers: {
'Authorization': `Basic ${accessToken}`
}
});
console.log("Access Token for Kiosk Mode Configured!");
}
function retrieveKioskContent(){
clearInterval(refreshTimer);
if (kioskMode != 'Cycle'){
$.post('/Home/KioskContent', { exclusions: exceptionList, kioskMode: kioskMode }, function (data) {
$("#kioskContainer").html(data);
$(".kiosk-content").masonry();
if ($(".no-data-message").length == 0) {
$(".progress-bar").width($("#kioskContainer").width());
setTimeout(function () { startTimer() }, 500);
}
});
} else {
//cycle mode
switch (currentKioskMode) {
case "Vehicle":
currentKioskMode = "Reminder";
break;
case "Reminder":
currentKioskMode = "Plan";
break;
case "Plan":
currentKioskMode = "Vehicle";
break;
}
$.post('/Home/KioskContent', { exclusions: exceptionList, kioskMode: currentKioskMode }, function (data) {
$("#kioskContainer").html(data);
$(".kiosk-content").masonry();
if ($(".no-data-message").length > 0) {
//if no data on vehicle page
if (currentKioskMode == "Vehicle") {
return; //exit
} else {
retrieveKioskContent(); //skip until we hit a page with content.
}
} else {
$(".progress-bar").width($("#kioskContainer").width());
setTimeout(function () { startTimer() }, 500);
}
});
}
}
function startTimer() {
refreshTimer = setInterval(function () {
var currentWidth = $(".progress-bar").width();
if (currentWidth > 0) {
$(".progress-bar").width(currentWidth - subtractAmount);
} else {
retrieveKioskContent();
}
}, 100);
}
function addVehicleToExceptionList(vehicleId) {
Swal.fire({
title: "Remove Vehicle from Dashboard?",
text: "Removed vehicles can be restored by refreshing the page",
showCancelButton: true,
confirmButtonText: "Remove",
confirmButtonColor: "#dc3545"
}).then((result) => {
if (result.isConfirmed) {
exceptionList.push(vehicleId);
if (kioskMode == 'Cycle') {
//remove the vehicle programmatically.
$(`[data-vehicleId=${vehicleId}]`).remove();
} else {
//force a refresh
retrieveKioskContent();
}
}
});
}
function toggleReminderNote(sender){
var reminderNote = $(sender).find('.reminder-note');
if (reminderNote.text().trim() != ''){
if (reminderNote.hasClass('d-none')) {
reminderNote.removeClass('d-none');
} else {
reminderNote.addClass('d-none');
}
$(".kiosk-content").masonry();
}
}
function togglePlanDetails(sender) {
toggleReminderNote(sender);
var planSupplies = $(sender).find('.plan-supplies');
if (planSupplies.find('.plan-supply').length > 0) {
if (planSupplies.hasClass('d-none')) {
planSupplies.removeClass('d-none');
} else {
planSupplies.addClass('d-none');
}
$(".kiosk-content").masonry();
}
}
initKiosk();
</script>

View File

@@ -1,6 +0,0 @@
@{
ViewData["Title"] = "Privacy Policy";
}
<h1>@ViewData["Title"]</h1>
<p>Use this page to detail your site's privacy policy.</p>

View File

@@ -10,7 +10,7 @@
<h5 class="modal-title" id="updateAccountModalLabel">@translator.Translate(userLanguage, "Update Profile")</h5> <h5 class="modal-title" id="updateAccountModalLabel">@translator.Translate(userLanguage, "Update Profile")</h5>
<button type="button" class="btn-close" onclick="hideAccountInformationModal()" aria-label="Close"></button> <button type="button" class="btn-close" onclick="hideAccountInformationModal()" aria-label="Close"></button>
</div> </div>
<div class="modal-body"> <div class="modal-body" onkeydown="handleEnter(this)">
<form class="form-inline"> <form class="form-inline">
<div class="form-group"> <div class="form-group">
<label for="inputUsername">@translator.Translate(userLanguage, "Username")</label> <label for="inputUsername">@translator.Translate(userLanguage, "Username")</label>
@@ -18,7 +18,12 @@
<label for="inputEmail">@translator.Translate(userLanguage, "Email Address")</label> <label for="inputEmail">@translator.Translate(userLanguage, "Email Address")</label>
<input type="text" id="inputEmail" class="form-control" placeholder="@translator.Translate(userLanguage, "Email Address")" value="@Model.EmailAddress"> <input type="text" id="inputEmail" class="form-control" placeholder="@translator.Translate(userLanguage, "Email Address")" value="@Model.EmailAddress">
<label for="inputPassword">@translator.Translate(userLanguage, "New Password")</label> <label for="inputPassword">@translator.Translate(userLanguage, "New Password")</label>
<div class="input-group">
<input type="password" id="inputPassword" class="form-control" placeholder="@translator.Translate(userLanguage, "New Password")" value=""> <input type="password" id="inputPassword" class="form-control" placeholder="@translator.Translate(userLanguage, "New Password")" value="">
<div class="input-group-text">
<button type="button" class="btn btn-sm text-secondary password-visible-button" onclick="togglePasswordVisibility(this)"><i class="bi bi-eye"></i></button>
</div>
</div>
<label for="inputToken">@translator.Translate(userLanguage, "Token")</label> <label for="inputToken">@translator.Translate(userLanguage, "Token")</label>
<input type="text" id="inputToken" class="form-control" placeholder="@translator.Translate(userLanguage, "Token")" value=""> <input type="text" id="inputToken" class="form-control" placeholder="@translator.Translate(userLanguage, "Token")" value="">
<div class="row"> <div class="row">

View File

@@ -73,7 +73,7 @@
Swal.fire({ Swal.fire({
title: 'Field Name', title: 'Field Name',
html: ` html: `
<input type="text" id="inputExtraFieldName" class="swal2-input" placeholder="Field Name"> <input type="text" id="inputExtraFieldName" class="swal2-input" placeholder="Field Name" onkeydown="handleSwalEnter(event)">
`, `,
confirmButtonText: 'Add', confirmButtonText: 'Add',
focusConfirm: false, focusConfirm: false,

View File

@@ -30,18 +30,21 @@
{ {
@if (!(userConfig.HideSoldVehicles && !string.IsNullOrWhiteSpace(vehicle.SoldDate))) @if (!(userConfig.HideSoldVehicles && !string.IsNullOrWhiteSpace(vehicle.SoldDate)))
{ {
<div class="col-xl-2 col-lg-4 col-md-4 col-sm-4 col-4 user-select-none garage-item" ondragover="dragOver(event)" ondrop="dropBox(event, @vehicle.Id)" draggable="true" ondragstart="dragStart(event, @vehicle.Id)" data-tags='@string.Join(" ", vehicle.Tags)' id="gridVehicle_@vehicle.Id" data-bs-toggle="tooltip" data-bs-html="true" data-bs-title="@await Html.PartialAsync("_VehicleExtraFields", vehicle.ExtraFields)" data-bs-placement="bottom" data-bs-trigger="manual" onmouseenter="loadPinnedNotes(@vehicle.Id)" ontouchstart="loadPinnedNotes(@vehicle.Id)" ontouchcancel="hidePinnedNotes(@vehicle.Id)" ontouchend="hidePinnedNotes(@vehicle.Id)" onmouseleave="hidePinnedNotes(@vehicle.Id)"> <div class="col-xl-2 col-lg-3 col-md-4 col-sm-4 col-4 user-select-none garage-item" ondragover="dragOver(event)" ondrop="dropBox(event, @vehicle.Id)" draggable="true" ondragstart="dragStart(event, @vehicle.Id)" data-tags='@string.Join(" ", vehicle.Tags)' id="gridVehicle_@vehicle.Id" data-bs-toggle="tooltip" data-bs-html="true" data-bs-title="@await Html.PartialAsync("_VehicleExtraFields", vehicle.ExtraFields)" data-bs-placement="bottom" data-bs-trigger="manual" onmouseenter="loadPinnedNotes(@vehicle.Id)" ontouchstart="loadPinnedNotes(@vehicle.Id)" ontouchcancel="hidePinnedNotes(@vehicle.Id)" ontouchend="hidePinnedNotes(@vehicle.Id)" onmouseleave="hidePinnedNotes(@vehicle.Id)">
<div class="card" onclick="viewVehicle(@vehicle.Id)"> <div class="card" onclick="viewVehicle(@vehicle.Id)">
<img src="@vehicle.ImageLocation" style="height:145px; object-fit:scale-down; pointer-events:none; @(string.IsNullOrWhiteSpace(vehicle.SoldDate) ? "" : "filter: grayscale(100%);")" /> <img src="@vehicle.ImageLocation" style="height:145px; object-fit:scale-down; pointer-events:none; @(string.IsNullOrWhiteSpace(vehicle.SoldDate) ? "" : "filter: grayscale(100%);")" />
@if (!string.IsNullOrWhiteSpace(vehicle.SoldDate)) @if (!string.IsNullOrWhiteSpace(vehicle.SoldDate))
{ {
<div class="vehicle-sold-banner"><p class='display-6 mb-0'>@translator.Translate(userLanguage, "SOLD")</p></div> <div class="vehicle-sold-banner"><p class='display-6 mb-0'>@translator.Translate(userLanguage, "SOLD")</p></div>
} else if (vehicle.LastReportedMileage != default) } else if (vehicle.DashboardMetrics.Any())
{ {
<div class="vehicle-sold-banner"> <div class="vehicle-sold-banner">
@if (vehicle.DashboardMetrics.Contains(DashboardMetric.Default) && vehicle.LastReportedMileage != default)
{
<div class="d-flex justify-content-between"> <div class="d-flex justify-content-between">
<div> <div>
<span class="ms-2"><i class="bi bi-speedometer me-2"></i>@vehicle.LastReportedMileage.ToString("N0")</span> <span class="ms-2"><i class="bi bi-speedometer me-2"></i>@vehicle.LastReportedMileage.ToString("N1")</span>
</div> </div>
@if (vehicle.HasReminders) @if (vehicle.HasReminders)
{ {
@@ -50,20 +53,36 @@
</div> </div>
} }
</div> </div>
<span></span> }
@if (vehicle.DashboardMetrics.Contains(DashboardMetric.CostPerMile) && vehicle.CostPerMile != default)
{
<div class="d-flex justify-content-between">
<div>
<span class="ms-2"><i class="bi bi-cash-coin me-2"></i>@($"{vehicle.CostPerMile.ToString("C2")}/{vehicle.DistanceUnit}")</span>
</div>
</div>
}
@if (vehicle.DashboardMetrics.Contains(DashboardMetric.TotalCost) && vehicle.TotalCost != default)
{
<div class="d-flex justify-content-between">
<div>
<span class="ms-2"><i class="bi bi-cash-coin me-2"></i>@($"{vehicle.TotalCost.ToString("C2")}")</span>
</div>
</div>
}
</div> </div>
} }
<div class="card-body"> <div class="card-body">
<h5 class="card-title text-truncate garage-item-year" data-unit="@vehicle.Year">@($"{vehicle.Year}")</h5> <h5 class="card-title text-truncate garage-item-year" data-unit="@vehicle.Year">@($"{vehicle.Year}")</h5>
<h5 class="card-title text-truncate">@($"{vehicle.Make}")</h5> <h5 class="card-title text-truncate">@($"{vehicle.Make}")</h5>
<h5 class="card-title text-truncate">@($"{vehicle.Model}")</h5> <h5 class="card-title text-truncate">@($"{vehicle.Model}")</h5>
<p class="card-text text-truncate">@vehicle.LicensePlate</p> <p class="card-text text-truncate">@StaticHelper.GetVehicleIdentifier(vehicle)</p>
</div> </div>
</div> </div>
</div> </div>
} }
} }
<div class="col-xl-2 col-lg-4 col-md-4 col-sm-4 col-4 garage-item-add"> <div class="col-xl-2 col-lg-3 col-md-4 col-sm-4 col-4 garage-item-add">
<div class="card" onclick="showAddVehicleModal()" style="height:100%;"> <div class="card" onclick="showAddVehicleModal()" style="height:100%;">
<img src="/defaults/addnew_vehicle.png" style="object-fit:scale-down;height:100%;" /> <img src="/defaults/addnew_vehicle.png" style="object-fit:scale-down;height:100%;" />
</div> </div>

126
Views/Home/_Kiosk.cshtml Normal file
View File

@@ -0,0 +1,126 @@
@using CarCareTracker.Helper
@model List<VehicleInfo>
@inject IConfigHelper config
@inject ITranslationHelper translator
@{
var userConfig = config.GetUserConfig(User);
var userLanguage = userConfig.UserLanguage;
}
@if (Model.Any())
{
<div class="row row-cols-1 row-cols-md-3 g-4 mt-1 kiosk-content" data-masonry='{"percentPosition": true }'>
@foreach (VehicleInfo vehicle in Model)
{
<div class="col" data-vehicleId="@vehicle.VehicleData.Id">
<div class="card" onclick="addVehicleToExceptionList(@vehicle.VehicleData.Id)">
<div class="card-body" style="padding-top:0.25rem; padding-bottom:0.25rem;">
<h5 class="card-title">@($"{vehicle.VehicleData.Year} {vehicle.VehicleData.Make} {vehicle.VehicleData.Model} ({StaticHelper.GetVehicleIdentifier(vehicle.VehicleData)})")</h5>
<div class="row">
<div class="col-3 text-center">
<p class="display-7">@vehicle.ServiceRecordCount</p>
<p class="lead text-truncate">@translator.Translate(userLanguage, "Service")</p>
<p class="lead text-truncate">@vehicle.ServiceRecordCost.ToString("C0")</p>
</div>
<div class="col-3 text-center">
<p class="display-7">@vehicle.RepairRecordCount</p>
<p class="lead text-truncate">@translator.Translate(userLanguage, "Repairs")</p>
<p class="lead text-truncate">@vehicle.RepairRecordCost.ToString("C0")</p>
</div>
<div class="col-3 text-center">
<p class="display-7">@vehicle.UpgradeRecordCount</p>
<p class="lead text-truncate">@translator.Translate(userLanguage, "Upgrades")</p>
<p class="lead text-truncate">@vehicle.UpgradeRecordCost.ToString("C0")</p>
</div>
<div class="col-3 text-center">
<p class="display-7">@vehicle.GasRecordCount</p>
<p class="lead text-truncate">@translator.Translate(userLanguage, "Fuel")</p>
<p class="lead text-truncate">@vehicle.GasRecordCost.ToString("C0")</p>
</div>
</div>
</div>
@if (vehicle.PastDueReminderCount + vehicle.VeryUrgentReminderCount + vehicle.UrgentReminderCount + vehicle.NotUrgentReminderCount > 0)
{
<hr style="margin:0px;" />
<div class="card-body" style="padding-top:0.25rem; padding-bottom:0.25rem;">
<h5 class="card-title">@translator.Translate(userLanguage, "Reminders")</h5>
<div class="row">
<div class="col-3 text-center">
<p class="display-7">@vehicle.PastDueReminderCount</p>
<p class="lead text-wrap">@translator.Translate(userLanguage, "Past Due")</p>
</div>
<div class="col-3 text-center">
<p class="display-7">@vehicle.VeryUrgentReminderCount</p>
<p class="lead text-wrap">@translator.Translate(userLanguage, "Very Urgent")</p>
</div>
<div class="col-3 text-center">
<p class="display-7">@vehicle.UrgentReminderCount</p>
<p class="lead text-wrap">@translator.Translate(userLanguage, "Urgent")</p>
</div>
<div class="col-3 text-center">
<p class="display-7">@vehicle.NotUrgentReminderCount</p>
<p class="lead text-wrap">@translator.Translate(userLanguage, "Not Urgent")</p>
</div>
</div>
</div>
}
@if (vehicle.NextReminder != null)
{
<hr style="margin:0px;" />
<div class="card-body" style="padding-top:0.25rem; padding-bottom:0.25rem;">
<h5 class="card-title">@translator.Translate(userLanguage, "Upcoming Reminder")</h5>
<div class="row">
<div class="col-12">
<p class="display-7">@vehicle.NextReminder.Description</p>
<p class="lead text-wrap">@translator.Translate(userLanguage, StaticHelper.GetTitleCaseReminderUrgency(vehicle.NextReminder.Urgency))</p>
<div class="row">
@if (vehicle.NextReminder.Metric == "Date" || vehicle.NextReminder.Metric == "Both")
{
<div class="col-6"><i class='bi bi-calendar-event me-2'></i>@vehicle.NextReminder.DueDate</div>
}
@if (vehicle.NextReminder.Metric == "Odometer" || vehicle.NextReminder.Metric == "Both")
{
<div class="col-6"><i class='bi bi-speedometer me-2'></i>@vehicle.NextReminder.DueOdometer</div>
}
</div>
</div>
</div>
</div>
}
@if (vehicle.PlanRecordBackLogCount + vehicle.PlanRecordInProgressCount + vehicle.PlanRecordTestingCount + vehicle.PlanRecordBackLogCount > 0)
{
<hr style="margin:0px;" />
<div class="card-body" style="padding-top:0.25rem; padding-bottom:0.25rem;">
<h5 class="card-title">@translator.Translate(userLanguage, "Plans")</h5>
<div class="row">
<div class="col-3 text-center">
<p class="display-7">@vehicle.PlanRecordBackLogCount</p>
<p class="lead text-wrap">@translator.Translate(userLanguage, "Planned")</p>
</div>
<div class="col-3 text-center">
<p class="display-7">@vehicle.PlanRecordInProgressCount</p>
<p class="lead text-wrap">@translator.Translate(userLanguage, "Doing")</p>
</div>
<div class="col-3 text-center">
<p class="display-7">@vehicle.PlanRecordTestingCount</p>
<p class="lead text-wrap">@translator.Translate(userLanguage, "Testing")</p>
</div>
<div class="col-3 text-center">
<p class="display-7">@vehicle.PlanRecordDoneCount</p>
<p class="lead text-wrap">@translator.Translate(userLanguage, "Done")</p>
</div>
</div>
</div>
}
</div>
</div>
}
</div>
}
else
{
<div class="row no-data-message">
<div class="col">
<span class="display-3">@translator.Translate(userLanguage, "No records available to display")</span>
</div>
</div>
}

View File

@@ -0,0 +1,86 @@
@using CarCareTracker.Helper
@model List<PlanRecord>
@inject IConfigHelper config
@inject ITranslationHelper translator
@{
var userConfig = config.GetUserConfig(User);
var userLanguage = userConfig.UserLanguage;
}
@if (Model.Any())
{
<div class="row row-cols-1 row-cols-md-3 g-4 mt-1 kiosk-content" data-masonry='{"percentPosition": true }'>
@foreach (PlanRecord plan in Model)
{
<div class="col" onclick="togglePlanDetails(this)">
<div class="card @StaticHelper.GetPlanRecordColor(plan.Priority)">
<div class="card-body" style="padding-top:0.25rem; padding-bottom:0.25rem;">
<h5 class="card-title">@plan.Description</h5>
<div class="row">
<div class="col-12">
<p class="display-7 d-none reminder-note" style="white-space: pre-wrap">@plan.Notes</p>
<p class="lead text-wrap">@translator.Translate(userLanguage, StaticHelper.GetPlanRecordProgress(plan.Progress))</p>
<div class="row">
<div class="col-6">
@if (plan.ImportMode == ImportMode.ServiceRecord)
{
<span class="lead">@translator.Translate(userLanguage, "Service")</span>
}
else if (plan.ImportMode == ImportMode.UpgradeRecord)
{
<span class="lead">@translator.Translate(userLanguage, "Repairs")</span>
}
else if (plan.ImportMode == ImportMode.RepairRecord)
{
<span class="lead">@translator.Translate(userLanguage, "Upgrades")</span>
}
</div>
</div>
</div>
</div>
</div>
@if (plan.RequisitionHistory.Any())
{
<ul class="list-group list-group-flush plan-supplies d-none">
<li class="list-group-item">
<div class="row">
<div class="col-4">
@translator.Translate(userLanguage, "Part Number")
</div>
<div class="col-4">
@translator.Translate(userLanguage, "Description")
</div>
<div class="col-4">
@translator.Translate(userLanguage, "Quantity")
</div>
</div>
</li>
@foreach (SupplyUsageHistory supply in plan.RequisitionHistory)
{
<li class="list-group-item plan-supply">
<div class="row">
<div class="col-4">
@supply.PartNumber
</div>
<div class="col-4">
@supply.Description
</div>
<div class="col-4">
@supply.Quantity
</div>
</div>
</li>
}
</ul>
}
</div>
</div>
}
</div>
} else
{
<div class="row no-data-message">
<div class="col">
<span class="display-3">@translator.Translate(userLanguage, "No records available to display")</span>
</div>
</div>
}

View File

@@ -0,0 +1,47 @@
@using CarCareTracker.Helper
@model List<ReminderRecordViewModel>
@inject IConfigHelper config
@inject ITranslationHelper translator
@{
var userConfig = config.GetUserConfig(User);
var userLanguage = userConfig.UserLanguage;
}
@if (Model.Any())
{
<div class="row row-cols-1 row-cols-md-3 g-4 mt-1 kiosk-content" data-masonry='{"percentPosition": true }'>
@foreach (ReminderRecordViewModel reminder in Model)
{
<div class="col" onclick="toggleReminderNote(this)">
<div class="card @StaticHelper.GetReminderUrgencyColor(reminder.Urgency)">
<div class="card-body" style="padding-top:0.25rem; padding-bottom:0.25rem;">
<h5 class="card-title">@reminder.Description</h5>
<div class="row">
<div class="col-12">
<p class="display-7 d-none reminder-note" style="white-space: pre-wrap">@reminder.Notes</p>
<p class="lead text-wrap">@translator.Translate(userLanguage, StaticHelper.GetTitleCaseReminderUrgency(reminder.Urgency))</p>
<div class="row">
@if (reminder.Metric == ReminderMetric.Date || reminder.Metric == ReminderMetric.Both)
{
<div class="col-6"><i class='bi bi-calendar-event me-2'></i>@reminder.Date.ToShortDateString()</div>
}
@if (reminder.Metric == ReminderMetric.Odometer || reminder.Metric == ReminderMetric.Both)
{
<div class="col-6"><i class='bi bi-speedometer me-2'></i>@reminder.Mileage</div>
}
</div>
</div>
</div>
</div>
</div>
</div>
}
</div>
}
else
{
<div class="row no-data-message">
<div class="col">
<span class="display-3">@translator.Translate(userLanguage, "No records available to display")</span>
</div>
</div>
}

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