Compare commits

...

116 Commits

Author SHA1 Message Date
54ff167124 improve tables layout
All checks were successful
Builder / Build-Project (push) Successful in 2m44s
2024-11-17 17:15:58 -05:00
2a561a925c change list page size
All checks were successful
Builder / Build-Project (push) Successful in 2m45s
2024-11-17 16:37:57 -05:00
0695223c31 fix file upload
All checks were successful
Builder / Build-Project (push) Successful in 2m43s
2024-11-17 16:15:08 -05:00
4de84d7320 fix packages.json
All checks were successful
Builder / Build-Project (push) Successful in 2m47s
2024-11-15 11:42:46 -05:00
1857dcb5a9 Merge pull request 'registro-horas' (#76) from registro-horas into main
Some checks failed
Builder / Build-Project (push) Has been cancelled
Reviewed-on: #76
Reviewed-by: alex <alex@primefactorsolutions.com>
2024-11-15 16:40:49 +00:00
Melina Gutierrez
7b4953a3aa Añadir correcion de service y repository
All checks were successful
PR Builder / Build-PR (pull_request) Successful in 2m44s
2024-11-15 12:34:43 -04:00
Melina Gutierrez
03146b1b9b Añadir correcion de formato
Some checks failed
PR Builder / Build-PR (pull_request) Failing after 2m40s
2024-11-15 12:26:47 -04:00
Melina Gutierrez
e8612aa5df Añadir correcion a Employee repository y service
Some checks failed
PR Builder / Build-PR (pull_request) Failing after 24s
2024-11-15 12:18:44 -04:00
Melina Gutierrez
f46d2cac03 Añadir correcion a Employee 2024-11-15 12:08:06 -04:00
Melina Gutierrez
886a19f2cf Añadir correcion a Employee 2024-11-15 11:54:52 -04:00
Melina Gutierrez
c58b37bbaf Formulario de registro de horas trabajadas mas el formulario 2024-11-15 11:51:32 -04:00
50fd791449 add version in login page
All checks were successful
Builder / Build-Project (push) Successful in 2m47s
2024-11-14 23:15:17 -05:00
b9320a3d6a adding git version in build
All checks were successful
Builder / Build-Project (push) Successful in 2m50s
2024-11-14 22:47:11 -05:00
439eab3541 Merge pull request 'correcciones' (#73) from En-desarrollo into main
All checks were successful
Builder / Build-Project (push) Successful in 2m46s
Reviewed-on: #73
Reviewed-by: alex <alex@primefactorsolutions.com>
2024-11-15 02:52:45 +00:00
005f0235fa correcciones vacacion gestion anterior
All checks were successful
PR Builder / Build-PR (pull_request) Successful in 2m42s
2024-11-14 22:42:42 -04:00
305a5c53f3 correcciones
All checks were successful
PR Builder / Build-PR (pull_request) Successful in 2m38s
2024-11-14 22:31:29 -04:00
97b9e515bf correcciones
All checks were successful
PR Builder / Build-PR (pull_request) Successful in 2m38s
2024-11-14 22:19:43 -04:00
1c93e9c20c Merge pull request 'solicitud vacaciones priorizar primero de gestion anterior' (#72) from En-desarrollo into main
All checks were successful
Builder / Build-Project (push) Successful in 2m44s
Reviewed-on: #72
Reviewed-by: alex <alex@primefactorsolutions.com>
2024-11-15 01:15:32 +00:00
ddefc54e9e solicitud vacaciones priorizar primero de gestion anterior
All checks were successful
PR Builder / Build-PR (pull_request) Successful in 2m38s
2024-11-14 20:58:17 -04:00
bdfaa7da85 Merge pull request 'En-desarrollo' (#71) from En-desarrollo into main
All checks were successful
Builder / Build-Project (push) Successful in 2m45s
Reviewed-on: #71
2024-11-15 00:24:15 +00:00
5ba432079b Resolver errores de checkstyle
All checks were successful
PR Builder / Build-PR (pull_request) Successful in 2m36s
2024-11-14 04:15:29 -04:00
1c638ff27b Lista de Solicitudes de empleado - actualizar resumen
Some checks failed
PR Builder / Build-PR (pull_request) Failing after 24s
2024-11-14 04:09:26 -04:00
a3e94040ff correcciones
All checks were successful
PR Builder / Build-PR (pull_request) Successful in 2m38s
2024-11-14 03:17:26 -04:00
b1e95cf8a5 Resolver errores de checkstyle
All checks were successful
PR Builder / Build-PR (pull_request) Successful in 2m36s
2024-11-13 19:58:33 -04:00
8e1f0fdd7d Lista de Solicitudes de empleado - actualizar solicitudes permisos de salud
Some checks failed
PR Builder / Build-PR (pull_request) Failing after 22s
2024-11-13 19:54:36 -04:00
9579b33acc Lista de Solicitudes de empleado - Actualizar resumen de empleado 2024-11-13 18:34:42 -04:00
efca245c1d Corregir datos de prueba
All checks were successful
PR Builder / Build-PR (pull_request) Successful in 2m40s
2024-11-13 14:47:28 -04:00
ca5fb122fb Lista de Solicitudes de empleado - Actualizar estado
All checks were successful
PR Builder / Build-PR (pull_request) Successful in 2m40s
2024-11-13 14:10:09 -04:00
6d60fd00cc Lista de Solicitudes de empleado - Refactorizar 2024-11-13 12:38:25 -04:00
51a1d22be9 Lista de Solicitudes de empleado - Calcular total disponibilidad de dias 2024-11-12 21:54:28 -04:00
3b0e5833f8 Merge pull request '#45-Registro Semanal de Horas Trabajadas' (#69) from hoursworked into main
All checks were successful
Builder / Build-Project (push) Successful in 2m47s
Reviewed-on: #69
2024-11-11 19:21:21 +00:00
Melina Gutierrez
21d1313b69 #45-Registro Semanal de Horas Trabajadas corregir employee solucionar error
All checks were successful
PR Builder / Build-PR (pull_request) Successful in 2m45s
2024-11-11 15:00:45 -04:00
Melina Gutierrez
249a8cb1ee #45-Registro Semanal de Horas Trabajadas corregir employee
Some checks failed
PR Builder / Build-PR (pull_request) Failing after 2m47s
2024-11-09 13:03:06 -04:00
Melina Gutierrez
b58a833444 Merge remote-tracking branch 'origin/hoursworked' into hoursworked
Some checks failed
PR Builder / Build-PR (pull_request) Failing after 21s
# Conflicts:
#	src/main/java/com/primefactorsolutions/model/Actividad.java
#	src/main/java/com/primefactorsolutions/service/HoursWorkedService.java
#	src/main/java/com/primefactorsolutions/views/EmployeeView.java
#	src/main/java/com/primefactorsolutions/views/HoursWorkedView.java
#	src/main/java/com/primefactorsolutions/views/ReporteView.java
2024-11-09 12:58:42 -04:00
Melina Gutierrez
ca2cd583a0 #45-Registro Semanal de Horas Trabajadas mas Reporte Excel 2024-11-09 12:43:31 -04:00
Melina Gutierrez
36287611b8 hoursworked sin reporte vista 2024-11-09 11:43:42 -04:00
Melina Gutierrez
5a0aa27382 primer commit 2024-11-09 11:42:54 -04:00
1ab10e286e Merge pull request 'En-desarrollo' (#65) from En-desarrollo into main
All checks were successful
Builder / Build-Project (push) Successful in 2m37s
Reviewed-on: #65
2024-11-09 12:15:26 +00:00
Melina Gutierrez
40d0cef2a1 hoursworked sin reporte vista 2024-11-08 08:36:22 -04:00
3922220439 Resolver errores de checkstyle
All checks were successful
PR Builder / Build-PR (pull_request) Successful in 2m33s
2024-11-07 18:53:47 -04:00
6024b3fdd0 Resolved merge conflict in RequestsListView.java
Some checks failed
PR Builder / Build-PR (pull_request) Failing after 17s
2024-11-07 18:45:01 -04:00
5e37cbbcd0 Perfil de Personal Administrativo - Listado Vacaciones de empleado (Calcular automadicamente dias de las vacaciones gestion actual y anterior) 2024-11-07 18:40:34 -04:00
f7e7dea7cb #37 Perfil de Personal Administrativo - Listado General de Vacaciones (Calcular automadicamente dias de las vacaciones gestion actual y anterior)
Some checks failed
PR Builder / Build-PR (pull_request) Failing after 21s
2024-11-07 18:31:05 -04:00
cb34868b9c Descarte funcionalidad generar automaticamente solicitud vacacion
All checks were successful
PR Builder / Build-PR (pull_request) Successful in 2m42s
2024-11-07 15:42:42 -04:00
d3fdb128b7 Merge branch 'En-desarrollo' of https://git.primefactorsolutions.com/PFS/pfs-intra into En-desarrollo
All checks were successful
PR Builder / Build-PR (pull_request) Successful in 2m37s
2024-11-07 02:18:18 -04:00
91aad85146 #5, #6 Perfil de Empleado - GUARDAR y EDITAR Información de Empleados (Refactorizacion generar solicitudes vacaciones) 2024-11-07 02:17:27 -04:00
9a29f91994 Merge pull request '#5, #6 Perfil de Empleado - GUARDAR y EDITAR Información de Empleados (Asignar o actualizar vacaciones)' (#68) from Vacaciones into En-desarrollo
Some checks failed
PR Builder / Build-PR (pull_request) Failing after 19s
Reviewed-on: #68
2024-11-06 18:56:16 +00:00
ed88936683 Resolviendo conflictos y finalizando fusión entre ramas Vacaciones y En-desarrollo
All checks were successful
PR Builder / Build-PR (pull_request) Successful in 0s
2024-11-06 14:55:53 -04:00
a0bd00fba4 #5, #6 Perfil de Empleado - GUARDAR y EDITAR Información de Empleados (Asignar o actualizar vacaciones)
All checks were successful
PR Builder / Build-PR (pull_request) Successful in 0s
2024-11-06 14:49:02 -04:00
81acc686e6 Merge pull request 'Correccion registro solicitud vacaciones actual y anterior' (#67) from Vacaciones into En-desarrollo
Some checks failed
PR Builder / Build-PR (pull_request) Failing after 19s
Reviewed-on: #67
2024-11-05 18:59:37 +00:00
9fdf1c08b5 Correccion registro solicitud vacaciones actual y anterior
All checks were successful
PR Builder / Build-PR (pull_request) Successful in 0s
2024-11-05 14:58:28 -04:00
4f128fd02e Merge pull request 'Vacaciones' (#66) from Vacaciones into En-desarrollo
Some checks failed
PR Builder / Build-PR (pull_request) Failing after 22s
Reviewed-on: #66
2024-11-05 18:32:42 +00:00
88e80a7dcc traducir a español y cambiar uso de grid a paging grid de RequestEmploye
All checks were successful
PR Builder / Build-PR (pull_request) Successful in 1s
2024-11-05 13:33:00 -04:00
7e415ae837 cambiar team string a un team class 2024-11-05 10:47:16 -04:00
Melina Gutierrez
4561a43866 primer commit 2024-11-04 21:45:02 -04:00
2b6400fe24 Merge branch 'En-desarrollo' of https://git.primefactorsolutions.com/PFS/pfs-intra into En-desarrollo
All checks were successful
PR Builder / Build-PR (pull_request) Successful in 2m35s
2024-11-04 00:49:43 -04:00
4615febec3 Actualizar BD - Actualizar el estado de los registros de solicitudes de vacaciones 2024-11-04 00:48:59 -04:00
9d87493f7d Merge pull request 'Vacaciones' (#64) from Vacaciones into En-desarrollo
All checks were successful
PR Builder / Build-PR (pull_request) Successful in 2m27s
Reviewed-on: #64
2024-11-04 04:25:22 +00:00
eb6183df19 Correcciones
All checks were successful
PR Builder / Build-PR (pull_request) Successful in 0s
2024-11-04 00:24:34 -04:00
45bf76c3d8 Actualizar BD - Actualizar el estado de los registros de solicitudes de vacaciones 2024-11-04 00:19:12 -04:00
cfd5599d32 #55 Perfil de Personal Administrativo - Añadir Vacaciones del Empleado (No aceptar fechas pasadas) 2024-11-03 23:40:13 -04:00
4421c6fe45 #55 Perfil de Personal Administrativo - Añadir Vacaciones del Empleado (correcciones) 2024-11-03 23:22:09 -04:00
71cb2d67c9 Perfil de Personal Administrativo - Listado solicitudes pendientes de Vacaciones 2024-11-02 00:51:21 -04:00
3efa8c73d5 Traducir a epañol las sentencias de incercion de datos 2024-11-01 13:42:23 -04:00
c0cc1e86de Categoria vacaciones - traducirlo al español 2024-10-31 01:42:33 -04:00
a02499d53f Estado de la solicitud - traducirlo al español 2024-10-31 01:34:18 -04:00
ca630ef5c6 #37 Perfil de Personal Administrativo - Listado General de Vacaciones (Tracducir a español) 2024-10-31 01:29:59 -04:00
f7ab9f8421 #37 Perfil de Personal Administrativo - Listado General de Vacaciones (Modificar calcular total general) 2024-10-31 01:16:48 -04:00
1e22223f27 Merge pull request 'En-desarrollo' (#62) from En-desarrollo into main
All checks were successful
Builder / Build-Project (push) Successful in 2m40s
Reviewed-on: #62
2024-10-30 20:14:57 +00:00
2f4d1ab130 adding default jwt secret
All checks were successful
Builder / Build-Project (push) Successful in 2m37s
2024-10-30 16:10:57 -04:00
94e7432551 #37 Perfil de Personal Administrativo - Listado General de Vacaciones (Modificar estado del empleado) 2024-10-30 15:10:05 -04:00
83ba9f7478 Merge pull request 'Vacaciones' (#63) from Vacaciones into En-desarrollo
All checks were successful
PR Builder / Build-PR (pull_request) Successful in 2m28s
Reviewed-on: #63
2024-10-30 18:56:02 +00:00
68c94e6193 Agregar solicitud vacaciones a la bd para pruebas
All checks were successful
PR Builder / Build-PR (pull_request) Successful in 0s
2024-10-30 14:55:22 -04:00
860b453138 #37 Perfil de Personal Administrativo - Listado General de Vacaciones (Correccion filtrado) 2024-10-30 14:46:51 -04:00
4f6935341d Merge pull request 'Vacaciones' (#61) from Vacaciones into En-desarrollo
All checks were successful
PR Builder / Build-PR (pull_request) Successful in 2m32s
Reviewed-on: #61
2024-10-30 18:31:55 +00:00
335e895197 Merge pull request '#10-Registro-Informacion-Profesional' (#60) from #10-Registro-Informacion-Profesional into main
All checks were successful
Builder / Build-Project (push) Successful in 2m38s
Reviewed-on: #60
2024-10-30 01:52:11 +00:00
Melina Gutierrez
937b1ef531 \#46 Perfil de Empleado - Registro Semanal mas reporte, falta mejorar el reporte semanal
All checks were successful
PR Builder / Build-PR (pull_request) Successful in 2m35s
#46 Perfil de Empleado - Registro Semanal

\#46 Perfil de Empleado - Registro Semanal Y Mensual falta bd

\#46 Perfil de Empleado - Registro Mensual

\#46 Perfil de Empleado - Registro Semanal de Horas trabajadas con el numero de semanas pero sin la bd

\#46 Perfil de Empleado - Registro Semanal de Horas trabajadas con el numero de semanas

\#46 Perfil de Empleado - Registro Semanal de Horas trabajadas

#46 Perfil de Empleado - Registro Semanal Y Mensual falta bd
2024-10-29 21:46:25 -04:00
98ed64dfda Listado Solicitudes de Empleado - Calcular totales
All checks were successful
PR Builder / Build-PR (pull_request) Successful in 0s
2024-10-29 15:28:20 -04:00
476884408a #37 Perfil de Personal Administrativo - Listado General de Vacaciones (Agregar total general) 2024-10-28 19:12:24 -04:00
f0c0811f0d #55 Perfil de Personal Administrativo - Añadir Vacaciones del Empleado (Verificacion obtencion de datos) 2024-10-28 18:16:44 -04:00
7d20703a7d #55 Perfil de Personal Administrativo - Añadir Vacaciones del Empleado (Verificacion de validaciones) 2024-10-28 00:55:41 -04:00
73915b1364 #55 Perfil de Personal Administrativo - Añadir Vacaciones del Empleado (Correccion) 2024-10-27 20:05:31 -04:00
6dfa5c0ec2 updated vaadin version
All checks were successful
Builder / Build-Project (push) Successful in 2m36s
2024-10-27 14:43:45 -04:00
6a1d3e70c6 #37 Perfil de Personal Administrativo - Listado General de Vacaciones (Correcciones) 2024-10-27 00:19:56 -04:00
82232d23c7 fix build
All checks were successful
Builder / Build-Project (push) Successful in 1m28s
2024-10-23 15:25:45 -04:00
807555a2dd #55 Perfil de Personal Administrativo - Añadir Vacaciones del Empleado (Configurar habilitacion categoria si previa solicitud expira) 2024-10-23 15:09:36 -04:00
cb3ed95c0c #55 Perfil de Personal Administrativo - Añadir Vacaciones del Empleado (Configurar vacacion por antiguedad del empleado) 2024-10-23 08:02:12 -04:00
52bed61ae8 #55 Perfil de Personal Administrativo - Añadir Vacaciones del Empleado (Configurar categoria Permiso de Salud) 2024-10-23 07:28:15 -04:00
3ddacfc771 fix reset password view
Some checks failed
Builder / Build-Project (push) Failing after 2m22s
2024-10-22 22:30:08 -04:00
292825d1a8 reset password views
Some checks failed
Builder / Build-Project (push) Failing after 2m25s
2024-10-22 22:13:31 -04:00
0117915b9d #55 Perfil de Personal Administrativo - Añadir Vacaciones del Empleado (Configurar si DaysBalance sea mayor a cero) 2024-10-22 00:20:13 -04:00
623b5b0ee6 Merge pull request 'En-desarrollo' (#59) from En-desarrollo into main
All checks were successful
Builder / Build-Project (push) Successful in 2m29s
Reviewed-on: #59
2024-10-22 01:01:10 +00:00
6806bdc24b Merge pull request 'Vacaciones' (#58) from Vacaciones into En-desarrollo
All checks were successful
PR Builder / Build-PR (pull_request) Successful in 2m31s
Reviewed-on: #58
2024-10-21 16:43:11 +00:00
a5e166351a #55 Perfil de Personal Administrativo - Añadir Vacaciones del Empleado (Refactorizacion configuracion form)
All checks were successful
PR Builder / Build-PR (pull_request) Successful in 0s
2024-10-21 12:39:28 -04:00
69c61e093e #55 Perfil de Personal Administrativo - Añadir Vacaciones del Empleado (Configuara fecha manual) 2024-10-21 12:14:26 -04:00
0858566e8e #55 Perfil de Personal Administrativo - Añadir Vacaciones del Empleado (Inactivar categoria por genero) 2024-10-20 22:49:47 -04:00
1c18cd566f #55 Perfil de Personal Administrativo - Añadir Vacaciones del Empleado (Excluir las categorias que cuenten con un registro) 2024-10-20 18:24:40 -04:00
2582c5e903 #55 Perfil de Personal Administrativo - Añadir Vacaciones del Empleado (Para seleccionar categoria requerir empleado) 2024-10-20 15:16:34 -04:00
2c30bed91f #55 Perfil de Personal Administrativo - Añadir Vacaciones del Empleado (Actualizar automaticamente fechas) 2024-10-20 15:11:24 -04:00
a37b93591f #55 Perfil de Personal Administrativo - Añadir Vacaciones del Empleado (Guardar solicitud) 2024-10-20 15:05:05 -04:00
8ab5016757 #55 Perfil de Personal Administrativo - Añadir Vacaciones del Empleado (Crear base BD vacaciones) 2024-10-19 00:01:15 -04:00
dac11494ea Merge pull request 'Vacaciones' (#57) from Vacaciones into En-desarrollo
Reviewed-on: #57
2024-10-18 17:35:12 +00:00
85dad4900c #55 Perfil de Personal Administrativo - Añadir Vacaciones del Empleado
All checks were successful
PR Builder / Build-PR (pull_request) Successful in 0s
2024-10-18 13:33:47 -04:00
34940ff794 Lista de Vacaciones - Calcular totales de vacaciones 2024-10-16 21:01:18 -04:00
3b085e835b Merge pull request 'En-desarrollo' (#53) from En-desarrollo into main
All checks were successful
Builder / Build-Project (push) Successful in 2m28s
Reviewed-on: #53
2024-10-16 01:30:59 +00:00
5a270d6795 Merge pull request '#37, #41#, 42, #43, #44 Listado General Vacaciones - Vista y Filtros' (#56) from Vacaciones into En-desarrollo
All checks were successful
PR Builder / Build-PR (pull_request) Successful in 2m38s
Reviewed-on: #56
2024-10-15 19:06:19 +00:00
22963adad9 #37, #41#, 42, #43, #44 Listado General Vacaciones - Vista y Filtros
All checks were successful
PR Builder / Build-PR (pull_request) Successful in 0s
2024-10-15 15:04:26 -04:00
7d3b63f6a8 Merge pull request 'Vacaciones' (#54) from Vacaciones into En-desarrollo
Some checks failed
PR Builder / Build-PR (pull_request) Failing after 20s
Reviewed-on: #54
2024-10-14 21:43:44 +00:00
696481a695 Resolver conflictos entre las Ramas Vacaciones y En-desarrollo
All checks were successful
PR Builder / Build-PR (pull_request) Successful in 0s
2024-10-14 17:42:31 -04:00
0d9e0755b0 #49, #50 Perfil de Personal Administrativo - Ver/Editar Vacaciones del Empleado
All checks were successful
PR Builder / Build-PR (pull_request) Successful in 0s
2024-10-14 01:17:23 -04:00
81b3003d1b Modelo empleado - Unir bd Team a la bd Employee 2024-10-13 20:25:39 -04:00
602ab686f5 Vacaciones del Empleado - Refactorizar 2024-10-13 20:25:38 -04:00
62d678c407 Vacaciones del Empleado - Filtrar por categoria y estado 2024-10-13 20:25:37 -04:00
3768a69cf4 adding user avatar and logout menu
All checks were successful
Builder / Build-Project (push) Successful in 2m33s
2024-10-11 22:45:51 -04:00
2a47bb8d9a Merge pull request 'Listado-General-Vacaciones' (#52) from Listado-General-Vacaciones into En-desarrollo
All checks were successful
PR Builder / Build-PR (pull_request) Successful in 2m30s
Reviewed-on: #52
2024-10-11 19:30:22 +00:00
f90efef23c Merge pull request '#37, #41, #42, #43, #44 Perfil de Personal Administrativo - Listado General de Vacaciones y Filtrado' (#47) from Listado-General-Vacaciones into En-desarrollo
Reviewed-on: #47
2024-10-08 18:48:54 +00:00
51 changed files with 4861 additions and 1751 deletions

1
.gitignore vendored
View File

@ -19,3 +19,4 @@ drivers/
# Error screenshots generated by TestBench for failed integration tests
error-screenshots/
webpack.generated.js
*.env

2089
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -6,22 +6,22 @@
"@f0rce/ace-widget": "1.0.2",
"@polymer/polymer": "3.5.1",
"@vaadin-component-factory/vcf-pdf-viewer": "2.0.1",
"@vaadin/bundles": "24.4.2",
"@vaadin/bundles": "24.5.1",
"@vaadin/common-frontend": "0.0.19",
"@vaadin/polymer-legacy-adapter": "24.4.2",
"@vaadin/react-components": "24.4.2",
"@vaadin/react-components-pro": "24.4.2",
"@vaadin/router": "1.7.5",
"@vaadin/polymer-legacy-adapter": "24.5.1",
"@vaadin/react-components": "24.5.1",
"@vaadin/react-components-pro": "24.5.1",
"@vaadin/router": "2.0.0",
"@vaadin/vaadin-development-mode-detector": "2.0.7",
"@vaadin/vaadin-lumo-styles": "24.4.2",
"@vaadin/vaadin-material-styles": "24.4.2",
"@vaadin/vaadin-themable-mixin": "24.4.2",
"@vaadin/vaadin-usage-statistics": "2.1.2",
"@vaadin/vaadin-lumo-styles": "24.5.1",
"@vaadin/vaadin-material-styles": "24.5.1",
"@vaadin/vaadin-themable-mixin": "24.5.1",
"@vaadin/vaadin-usage-statistics": "2.1.3",
"construct-style-sheets-polyfill": "3.1.0",
"date-fns": "2.29.3",
"lit": "3.1.4",
"print-js": "1.6.0",
"proj4": "2.11.0",
"proj4": "2.12.1",
"react": "18.3.1",
"react-dom": "18.3.1",
"react-router-dom": "6.23.1"
@ -51,22 +51,22 @@
"@f0rce/ace-widget": "1.0.2",
"@polymer/polymer": "3.5.1",
"@vaadin-component-factory/vcf-pdf-viewer": "2.0.1",
"@vaadin/bundles": "24.4.2",
"@vaadin/bundles": "24.5.1",
"@vaadin/common-frontend": "0.0.19",
"@vaadin/polymer-legacy-adapter": "24.4.2",
"@vaadin/react-components": "24.4.2",
"@vaadin/react-components-pro": "24.4.2",
"@vaadin/router": "1.7.5",
"@vaadin/polymer-legacy-adapter": "24.5.1",
"@vaadin/react-components": "24.5.1",
"@vaadin/react-components-pro": "24.5.1",
"@vaadin/router": "2.0.0",
"@vaadin/vaadin-development-mode-detector": "2.0.7",
"@vaadin/vaadin-lumo-styles": "24.4.2",
"@vaadin/vaadin-material-styles": "24.4.2",
"@vaadin/vaadin-themable-mixin": "24.4.2",
"@vaadin/vaadin-usage-statistics": "2.1.2",
"@vaadin/vaadin-lumo-styles": "24.5.1",
"@vaadin/vaadin-material-styles": "24.5.1",
"@vaadin/vaadin-themable-mixin": "24.5.1",
"@vaadin/vaadin-usage-statistics": "2.1.3",
"construct-style-sheets-polyfill": "3.1.0",
"date-fns": "2.29.3",
"lit": "3.1.4",
"print-js": "1.6.0",
"proj4": "2.11.0",
"proj4": "2.12.1",
"react": "18.3.1",
"react-dom": "18.3.1",
"react-router-dom": "6.23.1"
@ -91,7 +91,7 @@
"workbox-core": "7.1.0",
"workbox-precaching": "7.1.0"
},
"hash": "0962b593830d75a70657cde2e956e8c49b704d39c45bfd150cda9fdac99f1c6e"
"hash": "1a0f17d48b329307b5862bc57499307d1b89f7d89260121c2b7189f76957c436"
},
"overrides": {
"@vaadin/bundles": "$@vaadin/bundles",

59
pom.xml
View File

@ -11,7 +11,8 @@
<properties>
<java.version>21</java.version>
<vaadin.version>24.4.6</vaadin.version>
<vaadin.version>24.5.1</vaadin.version>
<vaadin-maven-plugin.version>24.4.6</vaadin-maven-plugin.version>
</properties>
<parent>
@ -119,6 +120,15 @@
<artifactId>commons-beanutils</artifactId>
<version>1.9.4</version>
</dependency>
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<version>2.17.0</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-core</artifactId>
</dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
@ -181,6 +191,16 @@
<artifactId>viritin</artifactId>
<version>2.8.22</version>
</dependency>
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi-ooxml</artifactId>
<version>5.2.3</version>
</dependency>
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi-ooxml-schemas</artifactId>
<version>4.1.2</version>
</dependency>
<dependency>
<groupId>com.flowingcode.addons</groupId>
<artifactId>simple-timer</artifactId>
@ -240,16 +260,49 @@
<artifactId>freemarker</artifactId>
<version>2.3.32</version>
</dependency>
<dependency>
<groupId>com.auth0</groupId>
<artifactId>java-jwt</artifactId>
<version>4.4.0</version>
</dependency>
<dependency>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>3.3.1</version>
</dependency>
<dependency>
<groupId>io.github.git-commit-id</groupId>
<artifactId>git-commit-id-maven-plugin</artifactId>
<version>9.0.1</version>
</dependency>
</dependencies>
<build>
<defaultGoal>spring-boot:run</defaultGoal>
<plugins>
<plugin>
<groupId>io.github.git-commit-id</groupId>
<artifactId>git-commit-id-maven-plugin</artifactId>
<version>9.0.1</version>
<executions>
<execution>
<id>get-the-git-infos</id>
<goals>
<goal>revision</goal>
</goals>
<phase>initialize</phase>
</execution>
</executions>
<configuration>
<generateGitPropertiesFile>true</generateGitPropertiesFile>
<generateGitPropertiesFilename>${project.build.outputDirectory}/git.properties</generateGitPropertiesFilename>
<includeOnlyProperties>
<includeOnlyProperty>^git.build.(time|version)$</includeOnlyProperty>
<includeOnlyProperty>^git.commit.id.(abbrev|full)$</includeOnlyProperty>
</includeOnlyProperties>
<commitIdGenerationMode>full</commitIdGenerationMode>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-checkstyle-plugin</artifactId>
@ -279,7 +332,7 @@
<plugin>
<groupId>com.vaadin</groupId>
<artifactId>vaadin-maven-plugin</artifactId>
<version>${vaadin.version}</version>
<version>${vaadin-maven-plugin.version}</version>
<executions>
<execution>
<goals>
@ -340,7 +393,7 @@
<plugin>
<groupId>com.vaadin</groupId>
<artifactId>vaadin-maven-plugin</artifactId>
<version>${vaadin.version}</version>
<version>${vaadin-maven-plugin.version}</version>
<executions>
<execution>
<goals>

Binary file not shown.

Binary file not shown.

View File

@ -4,7 +4,6 @@ import com.vaadin.flow.component.page.AppShellConfigurator;
import com.vaadin.flow.theme.Theme;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
/**
* The entry point of the Spring Boot application.
*

View File

@ -0,0 +1,19 @@
package com.primefactorsolutions.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.support.PropertySourcesPlaceholderConfigurer;
import org.springframework.core.io.ClassPathResource;
@Configuration
public class PropertiesConfig {
@Bean
public static PropertySourcesPlaceholderConfigurer placeholderConfigurer() {
final PropertySourcesPlaceholderConfigurer propsConfig = new PropertySourcesPlaceholderConfigurer();
propsConfig.setLocation(new ClassPathResource("git.properties"));
propsConfig.setIgnoreResourceNotFound(true);
propsConfig.setIgnoreUnresolvablePlaceholders(true);
return propsConfig;
}
}

View File

@ -1,147 +1,171 @@
package com.primefactorsolutions.model;
import com.google.common.collect.Lists;
import jakarta.persistence.*;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
package com.primefactorsolutions.model;
import java.time.LocalDate;
import java.util.Collection;
import com.google.common.collect.Lists;
import jakarta.persistence.*;
import jakarta.validation.constraints.*;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
@Data
@Entity
@AllArgsConstructor
@NoArgsConstructor
@EqualsAndHashCode(callSuper = true)
public class Employee extends BaseEntity implements UserDetails {
private String username;
private String firstName;
private String lastName;
private LocalDate birthday;
private String birthCity;
private String age;
import java.time.LocalDate;
import java.util.Collection;
private String residenceAddress;
private String localAddress;
private String phoneNumber;
private String personalEmail;
private String position;
@ManyToOne
@JoinColumn(name = "team_id", nullable = false)
private Team team;
private String emergencyCName;
private String emergencyCAddress;
private String emergencyCPhone;
private String emergencyCEmail;
private String numberOfChildren;
@Data
@Entity
@AllArgsConstructor
@NoArgsConstructor
@EqualsAndHashCode(callSuper = true)
public class Employee extends BaseEntity implements UserDetails {
private String ci;
private String issuedIn;
private String username;
@NotNull(message = "El nombre no puede estar vacío")
@Pattern(regexp = "^[a-zA-Z ]+$", message = "El nombre solo debe contener letras")
private String firstName;
@NotNull(message = "El apellido no puede estar vacío")
@Pattern(regexp = "^[a-zA-Z ]+$", message = "El apellido solo debe contener letras")
private String lastName;
private LocalDate birthday;
@Pattern(regexp = "^[a-zA-Z ,]+$", message = "La ciudad de nacimiento solo debe contener letras, espacios o comas")
private String birthCity;
private String age;
@Size(max = 50, message = "La dirección de residencia no debe exceder 50 caracteres")
private String residenceAddress;
@Size(max = 30, message = "La dirección local no debe exceder 100 caracteres")
@Pattern(regexp = "^[a-zA-Z -]+$", message = "La dirección local solo debe contener letras y guion")
private String localAddress;
@Pattern(regexp = "^[0-9]+$", message = "El número de teléfono debe contener solo números")
private String phoneNumber;
@Email(message = "El correo personal no tiene un formato válido")
private String personalEmail;
private String pTitle1;
private String pTitle2;
private String pTitle3;
@Pattern(regexp = "^[a-zA-Z ]+$", message = "El cargo solo debe contener letras")
private String position;
@ManyToOne
@JoinColumn(name = "team_id", nullable = false)
private Team team;
private String pStudy1;
private String pStudy2;
private String pStudy3;
@Pattern(regexp = "^[a-zA-Z ]+$", message = "El nombre y apellido de contacto"
+ " de emergencia solo debe contener letras")
private String emergencyCName;
private String emergencyCAddress;
@Pattern(regexp = "^[0-9]+$", message = "El teléfono de contacto de emergencia "
+ " debe contener solo números")
private String emergencyCPhone;
@Email(message = "El correo de contacto de emergencia no tiene un formato válido")
private String emergencyCEmail;
private String certification1;
private String certification2;
private String certification3;
private String certification4;
@Max(value = 10, message = "El número de hijos no puede exceder a 10")
@Pattern(regexp = "^[0-9]+$", message = "La cantidad de hijos debe contener solo números")
private String numberOfChildren;
private String recognition;
private String achievements;
@Pattern(regexp = "^[0-9]+$", message = "El CI debe contener solo números")
private String ci;
private String issuedIn;
private String pTitle1;
private String pTitle2;
private String pTitle3;
private String language;
private String languageLevel;
private String pStudy1;
private String pStudy2;
private String pStudy3;
private String cod;
private String leadManager;
private String project;
private String certification1;
private String certification2;
private String certification3;
private String certification4;
private String recognition;
private String achievements;
private LocalDate dateOfEntry;
private LocalDate dateOfExit;
private String language;
private String languageLevel;
@Pattern(regexp = "^[A-Za-z0-9]+$", message = "El código debe contener solo letras y números")
private String cod;
@Pattern(regexp = "^[a-zA-Z ]+$", message = "El lead manager solo debe contener letras")
private String leadManager;
@Pattern(regexp = "^[a-zA-Z ]+$", message = "El proyecto solo debe contener letras")
private String project;
private String contractType;
private String seniority;
private String salary;
private LocalDate dateOfEntry;
private LocalDate dateOfExit;
@Pattern(regexp = "^[a-zA-Z ]+$", message = "El tipo de contrato solo debe contener letras")
private String contractType;
@Pattern(regexp = "^[0-9]+$", message = "La antigüedad debe contener solo números")
private String seniority;
@Pattern(regexp = "^[0-9]+(\\.[0-9]{1,2})?$", message = "El salario debe ser un número con hasta dos decimales")
private String salary;
@Pattern(regexp = "^[a-zA-Z ]+$", message = "El nombre del banco solo debe contener letras")
private String bankName;
@Pattern(regexp = "^[0-9]+$", message = "El número de cuenta debe contener solo números")
private String accountNumber;
private String bankName;
private String accountNumber;
private String gpss;
private String sss;
@Pattern(regexp = "^[a-zA-Z ]+$", message = "Los derechohabientes solo deben contener letras")
private String beneficiaries;
private String gpss;
private String sss;
private String beneficiaries;
@Column(columnDefinition = "TEXT")
private String profileImage;
@Enumerated(EnumType.STRING)
private Status status;
@Column(columnDefinition = "TEXT")
private String profileImage;
@Enumerated(EnumType.STRING)
private Status status;
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return Lists.newArrayList();
}
@Override
public String getPassword() {
return null;
}
@Override
public String getUsername() {
return this.username;
}
@Override
public boolean isAccountNonExpired() {
return true;
}
@Override
public boolean isAccountNonLocked() {
return true;
}
@Override
public boolean isCredentialsNonExpired() {
return true;
}
@Override
public boolean isEnabled() {
return true;
}
public enum Status {
ACTIVE,
INACTIVE
}
@Enumerated(EnumType.STRING)
private MaritalStatus maritalStatus;
public enum MaritalStatus {
SINGLE,
MARRIED,
WIDOWED,
DIVORCED
}
@Enumerated(EnumType.STRING)
private Gender gender;
public enum Gender {
MALE,
FEMALE
}
public Status getStatus() {
return status;
}
public void setStatus(final Status status) {
this.status = status;
}
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return Lists.newArrayList();
}
@Override
public String getPassword() {
return null;
}
@Override
public String getUsername() {
return this.username;
}
@Override
public boolean isAccountNonExpired() {
return true;
}
@Override
public boolean isAccountNonLocked() {
return true;
}
@Override
public boolean isCredentialsNonExpired() {
return true;
}
@Override
public boolean isEnabled() {
return true;
}
public enum Status {
ACTIVE,
INACTIVE
}
@Enumerated(EnumType.STRING)
private MaritalStatus maritalStatus;
public enum MaritalStatus {
SINGLE,
MARRIED,
WIDOWED,
DIVORCED
}
@Enumerated(EnumType.STRING)
private Gender gender;
public enum Gender {
MALE,
FEMALE
}
}

View File

@ -0,0 +1,149 @@
package com.primefactorsolutions.model;
import jakarta.persistence.*;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor;
import java.time.LocalDate;
import java.time.temporal.WeekFields;
import java.util.List;
import java.util.Locale;
@Data
@Entity
@AllArgsConstructor
@NoArgsConstructor
@EqualsAndHashCode(callSuper = true)
public class HoursWorked extends BaseEntity {
@ManyToOne
@JoinColumn(name = "employee_id", nullable = true)
private Employee employee;
@ManyToOne
@JoinColumn(name = "team_id", nullable = true)
private Team team;
private int weekNumber;
private LocalDate date;
private String actividad;
private String tareasEspecificas;
private double hours;
private double horasTareasEspecificas;
private double horaspendientes;
private double totalHours;
public static double calculateTotalHours(final List<HoursWorked> activities) {
return activities.stream()
.mapToDouble(activity -> activity.hours + activity.horasTareasEspecificas)
.sum();
}
public static double calculatePendingHours(final List<HoursWorked> activities) {
double totalHoursWorked = calculateTotalHours(activities);
return Math.max(0, 40 - totalHoursWorked);
}
public Employee getEmployee() {
return employee;
}
public void setEmployee(final Employee employee) {
this.employee = employee;
}
public int getWeekNumber() {
return weekNumber;
}
public void setWeekNumber(final int weekNumber) {
this.weekNumber = weekNumber;
}
public LocalDate getDate() {
return date;
}
public void setDate(final LocalDate date) {
this.date = date;
if (date != null) {
WeekFields weekFields = WeekFields.of(Locale.getDefault());
this.weekNumber = date.get(weekFields.weekOfWeekBasedYear());
}
}
public String getActividad() {
return actividad;
}
public void setActividad(final String actividad) {
this.actividad = actividad;
}
public double getHours() {
return hours;
}
public void setHours(final double hours) {
this.hours = hours;
}
public double getTotalHours() {
double total = this.getHours();
return totalHours + total;
}
public void setTotalHours(final double totalHours) {
this.totalHours = totalHours;
}
public Team getTeam() {
return team;
}
public void setTeam(final Team team) {
this.team = team;
}
public String getTareasEspecificas() {
return tareasEspecificas;
}
public void setTareasEspecificas(final String tareasEspecificas) {
this.tareasEspecificas = tareasEspecificas;
}
public double getHorasTareasEspecificas() {
return horasTareasEspecificas;
}
public void setHorasTareasEspecificas(final double horasTareasEspecificas) {
this.tareasEspecificas = tareasEspecificas;
}
public double getHoraspendientes() {
//double horasTrabajadas = this.getTotalHours() + this.getHorasTareasEspecificas();
return 40;
}
public void setHoraspendientes(final double horaspendientes) {
this.horaspendientes = horaspendientes;
}
public double getHoursWorked() {
return hours;
}
public void setHoursWorked(final double hoursWorked) {
this.hours = hoursWorked;
}
public double getTotalHoursWorked() {
return totalHours;
}
public void setTotalHoursWorked(final double totalHoursWorked) {
this.totalHours = totalHoursWorked;
}
}

View File

@ -6,7 +6,7 @@ import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor;
import java.util.Date;
import java.time.LocalDate;
@Data
@Entity
@ -20,24 +20,11 @@ public class TimeOffRequest extends BaseEntity {
@Enumerated(EnumType.STRING)
private TimeOffRequestType category;
@Enumerated(EnumType.STRING)
private Status state;
private TimeOffRequestStatus state;
private Double availableDays;
private Date expiration;
private Date startDate;
private Date endDate;
private LocalDate expiration;
private LocalDate startDate;
private LocalDate endDate;
private Double daysToBeTake;
private Double daysBalance;
public enum Status {
ALL,
TAKEN,
REQUESTED,
APPROVED,
IN_USE,
UNDER_REVIEW,
PENDING,
REJECTED,
COMPLETED,
CANCELLED,
EXPIRED,
}
}

View File

@ -0,0 +1,16 @@
package com.primefactorsolutions.model;
public enum TimeOffRequestStatus {
TODOS,
TOMADO,
APROBADO,
EN_USO,
PENDIENTE,
RECHAZADO,
VENCIDO,
SOLICITADO,
EN_REVISION,
COMPLETADO,
CANCELADO,
}

View File

@ -1,10 +1,31 @@
package com.primefactorsolutions.model;
public enum TimeOffRequestType {
ALL,
VACATION,
MATERNITY,
BIRTHDAY,
FIXED_HOLIDAY,
OTHER
TODOS,
AÑO_NUEVO,
LUNES_CARNAVAL,
MARTES_CARNAVAL,
VIERNES_SANTO,
DIA_DEL_TRABAJADOR,
DIA_DE_LA_INDEPENDENCIA,
NAVIDAD,
DIA_DEL_ESTADO_PLURINACIONAL,
CORPUS_CHRISTI,
AÑO_NUEVO_ANDINO,
ANIVERSARIO_DEPARTAMENTAL,
DIA_DE_TODOS_LOS_DIFUNTOS,
CUMPLEAÑOS,
MATERNIDAD,
PATERNIDAD,
MATRIMONIO,
DUELO_1ER_GRADO,
DUELO_2ER_GRADO,
DIA_DEL_PADRE,
DIA_DE_LA_MADRE,
DIA_DE_LA_MUJER_INTERNACIONAL,
DIA_DE_LA_MUJER_NACIONAL,
PERMISOS_DE_SALUD,
VACACION_GESTION_ACTUAL,
VACACION_GESTION_ANTERIOR,
}

View File

@ -0,0 +1,30 @@
package com.primefactorsolutions.model;
import jakarta.persistence.Entity;
import jakarta.persistence.EnumType;
import jakarta.persistence.Enumerated;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor;
@Data
@Entity
@AllArgsConstructor
@NoArgsConstructor
@EqualsAndHashCode(callSuper = true)
public class Vacation extends BaseEntity {
@Enumerated(EnumType.STRING)
private TimeOffRequestType category;
private Integer monthOfYear;
private Integer dayOfMonth;
private Double duration;
private Double expiration;
@Enumerated(EnumType.STRING)
private Type type;
public enum Type {
FIXED,
MOVABLE,
OTHER
}
}

View File

@ -3,9 +3,15 @@ package com.primefactorsolutions.repositories;
import com.primefactorsolutions.model.Employee;
import org.springframework.data.jpa.repository.JpaRepository;
import java.util.List;
import java.util.Optional;
import java.util.UUID;
public interface EmployeeRepository extends JpaRepository<Employee, UUID> {
Optional<Employee> findByUsername(String username);
}
Optional<Employee> findByPersonalEmail(String personalEmail);
Optional<Employee> findByTeamId(UUID teamId);
List<Employee> findByTeamName(String teamName);
}

View File

@ -0,0 +1,16 @@
package com.primefactorsolutions.repositories;
import com.primefactorsolutions.model.HoursWorked;
import org.springframework.data.jpa.repository.JpaRepository;
import java.time.LocalDate;
import java.util.List;
import java.util.UUID;
public interface HoursWorkedRepository extends JpaRepository<HoursWorked, UUID> {
List<HoursWorked> findByWeekNumber(int weekNumber);
List<HoursWorked> findByDate(LocalDate date);
List<HoursWorked> findByEmployeeIdAndWeekNumber(UUID employeeId, int weekNumber);
}

View File

@ -1,11 +1,18 @@
package com.primefactorsolutions.repositories;
import com.primefactorsolutions.model.TimeOffRequest;
import com.primefactorsolutions.model.TimeOffRequestStatus;
import com.primefactorsolutions.model.TimeOffRequestType;
import org.springframework.data.jpa.repository.JpaRepository;
import java.util.List;
import java.util.Optional;
import java.util.UUID;
public interface TimeOffRequestRepository extends JpaRepository<TimeOffRequest, UUID> {
List<TimeOffRequest> findByEmployeeId(UUID idEmployee);
Optional<TimeOffRequest> findByEmployeeIdAndState(UUID employeeId, TimeOffRequestStatus state);
List<TimeOffRequest> findByEmployeeIdAndCategory(UUID employeeId, TimeOffRequestType category);
List<TimeOffRequest> findByState(TimeOffRequestStatus state);
void deleteByEmployeeIdAndCategory(UUID employeeId, TimeOffRequestType category);
}

View File

@ -0,0 +1,11 @@
package com.primefactorsolutions.repositories;
import com.primefactorsolutions.model.TimeOffRequestType;
import com.primefactorsolutions.model.Vacation;
import org.springframework.data.jpa.repository.JpaRepository;
import java.util.UUID;
public interface VacationRepository extends JpaRepository<Vacation, UUID> {
Vacation findByCategory(TimeOffRequestType category);
}

View File

@ -0,0 +1,101 @@
package com.primefactorsolutions.service;
import com.auth0.jwt.JWT;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.exceptions.JWTCreationException;
import com.auth0.jwt.exceptions.JWTVerificationException;
import com.auth0.jwt.interfaces.DecodedJWT;
import com.auth0.jwt.interfaces.JWTVerifier;
import com.primefactorsolutions.model.Employee;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import java.time.Instant;
import java.time.temporal.ChronoUnit;
@Service
@Slf4j
public class AccountService {
private final EmailService emailService;
private final EmployeeService employeeService;
private final String secret;
public AccountService(final EmailService emailService, final EmployeeService employeeService,
@Value("${application.jwtSecret}") final String secret) {
this.emailService = emailService;
this.employeeService = employeeService;
this.secret = secret;
}
public void sendResetPasswordEmail(final String personalEmail) {
final Employee employee = employeeService.getEmployeeByPersonalEmail(personalEmail);
if (employee == null) {
log.warn("Could not find employee for email {}", personalEmail);
return;
}
final String link = createResetPasswordLink(employee.getUsername());
final String content = "Visit this link to reset your password: " + link;
emailService.sendEmail(personalEmail, "PFS - Reset Password", content);
}
public void resetPassword(final String username, final String newPassword, final String token) {
DecodedJWT decodedJWT;
try {
Algorithm algorithm = Algorithm.HMAC512(secret);
JWTVerifier verifier = JWT.require(algorithm)
.withIssuer("pfs")
.build();
decodedJWT = verifier.verify(token);
final Instant expiry = decodedJWT.getExpiresAtAsInstant();
final String claim = decodedJWT.getClaim("username").asString();
if (expiry.isBefore(Instant.now())
|| !username.equals(claim)) {
log.warn("token invalid {} {} {}", username, claim, expiry);
return;
}
} catch (JWTVerificationException e) {
log.warn("error updating password", e);
return;
}
final Employee employee = employeeService.getDetachedEmployeeByUsername(username);
if (employee == null) {
log.warn("Could not find employee for username {}", username);
return;
}
if (StringUtils.isBlank(newPassword) || newPassword.length() < 8) {
throw new IllegalArgumentException("New password should be at least 8 chars long");
}
employeeService.updatePassword(employee, newPassword);
log.info("updated password for {}", username);
}
private String createResetPasswordLink(final String username) {
String token = "";
try {
Algorithm algorithm = Algorithm.HMAC512(secret);
token = JWT.create()
.withIssuer("pfs")
.withClaim("username", username)
.withExpiresAt(Instant.now().plus(1, ChronoUnit.HOURS))
.sign(algorithm);
} catch (JWTCreationException e) {
throw new RuntimeException(e);
}
return String.format("https://intra.primefactorsolutions.com/reset-password?username=%s&token=%s", username,
token);
}
}

View File

@ -0,0 +1,32 @@
package com.primefactorsolutions.service;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.mail.SimpleMailMessage;
import org.springframework.mail.javamail.JavaMailSender;
import org.springframework.stereotype.Service;
@Service
@AllArgsConstructor
@Slf4j
public class EmailService {
public static final String NO_REPLY_PRIMEFACTORSOLUTIONS_COM = "no-reply@primefactorsolutions.com";
private final JavaMailSender emailSender;
public void sendEmail(final String email, final String title, final String messageContent) {
try {
final SimpleMailMessage message = new SimpleMailMessage();
message.setFrom(NO_REPLY_PRIMEFACTORSOLUTIONS_COM);
message.setBcc(NO_REPLY_PRIMEFACTORSOLUTIONS_COM);
message.setTo(email);
message.setSubject(title);
message.setText(messageContent);
emailSender.send(message);
log.info("Sent email to {}", email);
} catch (Exception e) {
log.error("Error sending email to {}", email, e);
throw e;
}
}
}

View File

@ -18,12 +18,18 @@ import java.util.Collections;
@Service
@AllArgsConstructor
public class EmployeeService {
private static final String USERPASSWORD = "userPassword";
private static final String OBJECTCLASS = "objectclass";
private static final String ORGANIZATIONAL_PERSON = "organizationalPerson";
private static final String INET_ORG_PERSON = "inetOrgPerson";
private static final String TOP = "top";
private static final String PERSON = "person";
public static final String BASE_DN = "dc=primefactorsolutions,dc=com";
private final EmployeeRepository employeeRepository;
private final LdapTemplate ldapTemplate;
private final EntityManager entityManager;
public static final String BASE_DN = "dc=primefactorsolutions,dc=com";
protected Name buildDn(final Employee employee) {
return LdapNameBuilder.newInstance(BASE_DN)
.add("ou", "users")
@ -42,6 +48,13 @@ public class EmployeeService {
return null;
}
public String getTeamLeadName(final UUID teamId) {
// Encuentra al empleado con el rol de lead_manager en el equipo especificado
Optional<Employee> leadManager = employeeRepository.findByTeamId(teamId);
return leadManager.map(employee -> employee.getFirstName() + " " + employee.getLastName())
.orElse("No asignado");
}
public List<Employee> findEmployees(
final int start, final int pageSize, final String sortProperty, final boolean asc) {
List<Employee> employees = employeeRepository.findAll();
@ -63,6 +76,10 @@ public class EmployeeService {
return employees.subList(start, end);
}
public Employee getEmployeeByPersonalEmail(final String email) {
return employeeRepository.findByPersonalEmail(email).orElse(null);
}
public Employee createOrUpdate(final Employee employee) {
if (employee.getId() == null) {
final Name dn = buildDn(employee);
@ -74,28 +91,29 @@ public class EmployeeService {
}
public Employee getEmployee(final UUID id) {
Optional<Employee> employee = employeeRepository.findById(id);
final Optional<Employee> employee = employeeRepository.findById(id);
return employee.orElse(null);
}
private Attributes buildAttributes(final Employee employee) {
final Attributes attrs = new BasicAttributes();
final BasicAttribute ocattr = new BasicAttribute("objectclass");
ocattr.add("top");
ocattr.add("person");
ocattr.add("organizationalPerson");
ocattr.add("inetOrgPerson");
final BasicAttribute ocattr = new BasicAttribute(OBJECTCLASS);
ocattr.add(TOP);
ocattr.add(PERSON);
ocattr.add(ORGANIZATIONAL_PERSON);
ocattr.add(INET_ORG_PERSON);
attrs.put(ocattr);
attrs.put("cn", String.format("%s %s", employee.getFirstName(), employee.getLastName()));
attrs.put("sn", String.format("%s %s", employee.getFirstName(), employee.getLastName()));
attrs.put("uid", employee.getUsername());
attrs.put("userpassword", String.format("%s%s", employee.getUsername(), 123));
attrs.put(USERPASSWORD, String.format("%s%s", employee.getUsername(), 123));
return attrs;
}
public void updatePassword(final Employee employee) {
final Attribute attr = new BasicAttribute("userpassword", employee.getUsername() + "123");
public void updatePassword(final Employee employee, final String newPassword) {
final Attribute attr = new BasicAttribute(USERPASSWORD, newPassword);
final ModificationItem item = new ModificationItem(DirContext.REPLACE_ATTRIBUTE, attr);
ldapTemplate.modifyAttributes(buildDn(employee), new ModificationItem[] {item});
@ -104,4 +122,8 @@ public class EmployeeService {
public List<Employee> findAllEmployees() {
return employeeRepository.findAll();
}
public List<Employee> findEmployeesByTeam(final String teamName) {
return employeeRepository.findByTeamName(teamName);
}
}

View File

@ -0,0 +1,99 @@
package com.primefactorsolutions.service;
import com.primefactorsolutions.model.HoursWorked;
import com.primefactorsolutions.repositories.HoursWorkedRepository;
import org.apache.commons.beanutils.BeanComparator;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.time.LocalDate;
import java.util.*;
@Service
public class HoursWorkedService {
private final HoursWorkedRepository hoursWorkedRepository;
@Autowired
public HoursWorkedService(final HoursWorkedRepository hoursWorkedRepository) {
this.hoursWorkedRepository = hoursWorkedRepository;
}
public List<HoursWorked> findAll() {
return hoursWorkedRepository.findAll();
}
public double getTotalHoursWorkedByEmployeeForWeek(final UUID employeeId, final int weekNumber) {
List<HoursWorked> hoursWorkedList = hoursWorkedRepository.findByWeekNumber(weekNumber);
return hoursWorkedList.stream()
.filter(hw -> hw.getEmployee().getId().equals(employeeId))
.mapToDouble(HoursWorked::getTotalHours)
.sum();
}
public HoursWorked findHoursWorked(final UUID id) {
Optional<HoursWorked> hoursWorked = hoursWorkedRepository.findById(id);
HoursWorked hw = hoursWorked.get();
return hw;
}
public HoursWorked saveHoursWorked(final HoursWorked hoursWorked) {
return hoursWorkedRepository.save(hoursWorked);
}
public HoursWorked save(final HoursWorked hoursWorked) {
return hoursWorkedRepository.save(hoursWorked);
}
public double getTotalHoursForEmployee(final UUID employeeId, final int weekNumber) {
List<HoursWorked> activities = hoursWorkedRepository.findByEmployeeIdAndWeekNumber(employeeId, weekNumber);
return HoursWorked.calculateTotalHours(activities);
}
public double getPendingHoursForEmployee(final UUID employeeId, final int weekNumber) {
List<HoursWorked> activities = hoursWorkedRepository.findByEmployeeIdAndWeekNumber(employeeId, weekNumber);
return HoursWorked.calculatePendingHours(activities);
}
public List<HoursWorked> findByWeekNumber(final int weekNumber) {
return hoursWorkedRepository.findByWeekNumber(weekNumber);
}
public List<HoursWorked> findByDate(final LocalDate date) {
return hoursWorkedRepository.findByDate(date);
}
public List<HoursWorked> findByDateAndWeekNumber(final LocalDate date, final int weekNumber) {
return hoursWorkedRepository.findByDate(date);
}
public List<HoursWorked> findHoursWorkeds(
final int start, final int pageSize, final String sortProperty, final boolean asc) {
List<HoursWorked> hoursWorkeds = hoursWorkedRepository.findAll();
int end = Math.min(start + pageSize, hoursWorkeds.size());
hoursWorkeds.sort(new BeanComparator<>(sortProperty));
if (!asc) {
Collections.reverse(hoursWorkeds);
}
return hoursWorkeds.subList(start, end);
}
public List<HoursWorked> findHoursWorkeds(final int start, final int pageSize) {
List<HoursWorked> hoursWorkeds = hoursWorkedRepository.findAll();
int end = Math.min(start + pageSize, hoursWorkeds.size());
return hoursWorkeds.subList(start, end);
}
public HoursWorked getHoursWorked(final UUID id) {
final Optional<HoursWorked> hoursWorked = hoursWorkedRepository.findById(id);
return hoursWorked.get();
}
public List<HoursWorked> findListHoursWorkedEmployee(final UUID employeeId, final int weekNumber) {
return hoursWorkedRepository.findByEmployeeIdAndWeekNumber(employeeId, weekNumber);
}
}

View File

@ -2,6 +2,7 @@ package com.primefactorsolutions.service;
import com.openhtmltopdf.pdfboxout.PdfBoxRenderer;
import com.openhtmltopdf.pdfboxout.PdfRendererBuilder;
import com.primefactorsolutions.repositories.HoursWorkedRepository;
import freemarker.template.Configuration;
import freemarker.template.DefaultObjectWrapper;
import freemarker.template.Template;
@ -9,15 +10,119 @@ import freemarker.template.TemplateExceptionHandler;
import lombok.SneakyThrows;
import org.apache.pdfbox.io.MemoryUsageSetting;
import org.apache.pdfbox.pdmodel.PDDocument;
import org.apache.poi.ss.usermodel.*;
import org.apache.poi.ss.util.CellRangeAddress;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
import org.jetbrains.annotations.NotNull;
import org.springframework.stereotype.Service;
import java.io.*;
import java.nio.charset.StandardCharsets;
import java.util.List;
import java.util.Map;
import java.util.TimeZone;
@Service
public class ReportService {
private final HoursWorkedRepository hoursWorkedRepository;
public ReportService(final HoursWorkedRepository hoursWorkedRepository) {
this.hoursWorkedRepository = hoursWorkedRepository;
}
// Este método ahora solo crea el archivo Excel a partir de los datos que recibe.
public byte[] writeAsExcel(final String reportName, final List<String> headers,
final List<Map<String, Object>> data, final String selectedTeam,
final int weekNumber, final int currentYear)
throws IOException {
return createExcelFile(reportName, headers, data, selectedTeam, weekNumber, currentYear);
}
private byte[] createExcelFile(final String reportName, final List<String> headers,
final List<Map<String, Object>> data, final String selectedTeam,
final int weekNumber, final int currentYear)
throws IOException {
try (Workbook workbook = new XSSFWorkbook();
ByteArrayOutputStream os = new ByteArrayOutputStream()) {
Sheet sheet = workbook.createSheet(reportName);
// Crear encabezados
// Crear una fila para el rótulo "Reporte por equipo"
Row titleRow = sheet.createRow(0); // Fila 0 para el rótulo
Cell titleCell = titleRow.createCell(0);
// Concatenar el nombre del equipo al rótulo
String titleText = "Informe: " + weekNumber + "/" + currentYear;
titleCell.setCellValue(titleText);
// Estilo del rótulo
CellStyle titleStyle = workbook.createCellStyle();
Font titleFont = workbook.createFont();
titleFont.setBold(true);
titleFont.setFontHeightInPoints((short) 14); // Tamaño de la fuente
titleStyle.setFont(titleFont);
titleCell.setCellStyle(titleStyle);
// Fusionar celdas para el rótulo
sheet.addMergedRegion(new CellRangeAddress(0, 0, 0, headers.size() - 1)); // Ajusta el rango de celdas
// Crear filas adicionales con la información solicitada
Row asuntoRow = sheet.createRow(1); // Fila 1: Asunto
asuntoRow.createCell(0).setCellValue("Asunto: Informe semanal de horas trabajadas");
sheet.addMergedRegion(new CellRangeAddress(1, 1, 0, headers.size() - 1));
Row semanaRow = sheet.createRow(2); // Fila 2: Semana
semanaRow.createCell(0).setCellValue("Semana: " + weekNumber); // Puedes insertar una fecha real aquí
sheet.addMergedRegion(new CellRangeAddress(2, 2, 0, headers.size() - 1));
Row horasCumplirRow = sheet.createRow(3); // Fila 3: Horas a cumplir
horasCumplirRow.createCell(0).setCellValue("Horas a cumplir: 40 horas"); // Puedes agregar las horas reales
sheet.addMergedRegion(new CellRangeAddress(3, 3, 0, headers.size() - 1));
Row teamLeadRow = sheet.createRow(4); // Fila 4: Team Lead
teamLeadRow.createCell(0).setCellValue("Team Lead: "); // Solo texto
sheet.addMergedRegion(new CellRangeAddress(4, 4, 0, headers.size() - 1));
// Crear encabezados (fila 5)
Row headerRow = sheet.createRow(5); // Los encabezados empiezan en la fila 5
CellStyle headerStyle = workbook.createCellStyle();
Font headerFont = workbook.createFont();
headerFont.setBold(true);
headerStyle.setFont(headerFont);
for (int i = 0; i < headers.size(); i++) {
Cell cell = headerRow.createCell(i);
cell.setCellValue(headers.get(i));
cell.setCellStyle(headerStyle);
}
// Crear filas de datos (a partir de la fila 6)
for (int i = 0; i < data.size(); i++) {
Row dataRow = sheet.createRow(i + 6); // Los datos empiezan después de la fila de encabezados
Map<String, Object> rowData = data.get(i);
int cellIndex = 0;
for (String key : headers) {
Cell cell = dataRow.createCell(cellIndex++);
Object value = rowData.get(key);
if (value != null) {
if (value instanceof String) {
cell.setCellValue((String) value);
} else if (value instanceof Number) {
cell.setCellValue(((Number) value).doubleValue());
}
} else {
cell.setCellValue(""); // Manejo de valores nulos
}
}
}
workbook.write(os);
return os.toByteArray();
} catch (IOException e) {
System.err.println("Error al generar el archivo Excel: " + e.getMessage());
throw e; // Propagar la excepción después de registrarla
}
}
@SneakyThrows
public byte[] writeAsPdf(final String reportName, final Object model) {
@ -69,4 +174,4 @@ public class ReportService {
return cfg;
}
}
}

View File

@ -5,6 +5,7 @@ import com.primefactorsolutions.repositories.TimeOffRequestRepository;
import lombok.AllArgsConstructor;
import org.springframework.stereotype.Service;
import java.time.LocalDate;
import java.util.List;
import java.util.UUID;
import java.util.Optional;
@ -18,6 +19,14 @@ public class TimeOffRequestService {
timeOffRequestRepository.save(newTimeOffRequest);
}
public void deleteTimeOffRequestByEmployeeAndCategory(final UUID employeeId, final TimeOffRequestType category) {
timeOffRequestRepository.deleteByEmployeeIdAndCategory(employeeId, category);
}
public void saveAll(final List<TimeOffRequest> requests) {
timeOffRequestRepository.saveAll(requests);
}
public void deleteTimeOffRequest(final UUID id) {
timeOffRequestRepository.deleteById(id);
}
@ -31,7 +40,49 @@ public class TimeOffRequestService {
return timeOffRequest.orElse(null);
}
public List<TimeOffRequest> findRequestsByState(final TimeOffRequestStatus state) {
return timeOffRequestRepository.findByState(state);
}
public List<TimeOffRequest> findRequestsByEmployeeId(final UUID idEmployee) {
return timeOffRequestRepository.findByEmployeeId(idEmployee);
}
public Optional<TimeOffRequest> findByEmployeeAndState(final UUID employeeId, final TimeOffRequestStatus state) {
return timeOffRequestRepository.findByEmployeeIdAndState(employeeId, state);
}
public List<TimeOffRequest> findByEmployeeAndCategory(final UUID employeeId, final TimeOffRequestType category) {
return timeOffRequestRepository.findByEmployeeIdAndCategory(employeeId, category);
}
public void updateRequestStatuses() {
List<TimeOffRequest> requests = findAllTimeOffRequests();
LocalDate now = LocalDate.now();
LocalDate startOfYear = LocalDate.of(now.getYear(), 1, 1);
for (TimeOffRequest request : requests) {
if (request.getCategory() == TimeOffRequestType.VACACION_GESTION_ACTUAL && now.isEqual(startOfYear)) {
deleteTimeOffRequestByEmployeeAndCategory(
request.getEmployee().getId(),
TimeOffRequestType.VACACION_GESTION_ANTERIOR
);
request.setCategory(TimeOffRequestType.VACACION_GESTION_ANTERIOR);
}
if (request.getState() == TimeOffRequestStatus.APROBADO
|| request.getState() == TimeOffRequestStatus.EN_USO) {
LocalDate startDate = request.getStartDate();
LocalDate endDate = request.getEndDate();
if (now.isAfter(endDate)) {
request.setState(TimeOffRequestStatus.TOMADO);
} else if (now.isEqual(startDate) || (now.isAfter(startDate) && now.isBefore(endDate))) {
request.setState(TimeOffRequestStatus.EN_USO);
}
}
}
saveAll(requests);
}
}

View File

@ -0,0 +1,23 @@
package com.primefactorsolutions.service;
import com.primefactorsolutions.model.TimeOffRequestType;
import com.primefactorsolutions.model.Vacation;
import com.primefactorsolutions.repositories.VacationRepository;
import lombok.AllArgsConstructor;
import org.springframework.stereotype.Service;
import java.util.List;
@Service
@AllArgsConstructor
public class VacationService {
private final VacationRepository vacationRepository;
public Vacation findVacationByCategory(final TimeOffRequestType category) {
return vacationRepository.findByCategory(category);
}
public List<Vacation> findVacations() {
return vacationRepository.findAll();
}
}

View File

@ -37,7 +37,7 @@ public class AssessmentsListView extends Main {
final HorizontalLayout hl = new HorizontalLayout();
final Button addAssessment = new Button("Add Assessment");
addAssessment.addClickListener((ComponentEventListener<ClickEvent<Button>>) buttonClickEvent -> {
this.getUI().get().navigate(AssessmentView.class, "new");
this.getUI().flatMap(ui -> ui.navigate(AssessmentView.class, "new"));
});
hl.add(addAssessment);
@ -51,7 +51,7 @@ public class AssessmentsListView extends Main {
grid.addComponentColumn((ValueProvider<Assessment, Component>) assessment -> {
var result = new Button("Result", event ->
this.getUI().get().navigate(SubmissionView.class, assessment.getId().toString())
this.getUI().flatMap(ui -> ui.navigate(SubmissionView.class, assessment.getId().toString()))
);
result.setEnabled(assessment.isCompleted());
@ -95,6 +95,7 @@ public class AssessmentsListView extends Main {
return assessmentService.getAssessments().size();
}
@SuppressWarnings("unused")
@Override
public Stream<Assessment> fetch(final Query<Assessment, Object> query) {
int limit = query.getLimit();

View File

@ -0,0 +1,16 @@
package com.primefactorsolutions.views;
import com.vaadin.flow.component.html.Main;
import com.vaadin.flow.component.orderedlayout.VerticalLayout;
import lombok.Getter;
@Getter
public class BaseView extends Main {
private final VerticalLayout currentPageLayout;
public BaseView() {
currentPageLayout = new VerticalLayout();
add(currentPageLayout);
}
}

View File

@ -28,15 +28,13 @@ import java.util.stream.Stream;
@Route(value = "/candidates", layout = MainLayout.class)
@PermitAll
public class CandidatesListView extends Main {
private final CandidateService candidateService;
public CandidatesListView(final CandidateService candidateService) {
this.candidateService = candidateService;
final HorizontalLayout hl = new HorizontalLayout();
final Button addCandidate = new Button("Add Candidate");
addCandidate.addClickListener((ComponentEventListener<ClickEvent<Button>>) buttonClickEvent -> {
this.getUI().get().navigate(CandidateView.class, "new");
this.getUI().flatMap(ui -> ui.navigate(CandidateView.class, "new"));
});
hl.add(addCandidate);
@ -46,7 +44,7 @@ public class CandidatesListView extends Main {
grid.addComponentColumn((ValueProvider<Candidate, Component>) candidate -> {
final Button edit = new Button("Edit");
edit.addClickListener((ComponentEventListener<ClickEvent<Button>>) buttonClickEvent ->
this.getUI().get().navigate(CandidateView.class, candidate.getId().toString()));
this.getUI().flatMap(ui -> ui.navigate(CandidateView.class, candidate.getId().toString())));
return edit;
});
@ -61,6 +59,7 @@ public class CandidatesListView extends Main {
return candidateService.getCandidates().size();
}
@SuppressWarnings("unused")
@Override
public Stream<Candidate> fetch(final Query<Candidate, Object> query) {
int limit = query.getLimit();

View File

@ -0,0 +1,5 @@
package com.primefactorsolutions.views;
public class Constants {
public static final int PAGE_SIZE = 10;
}

View File

@ -9,8 +9,8 @@ import com.vaadin.flow.component.UI;
import com.vaadin.flow.component.button.Button;
import com.vaadin.flow.component.combobox.ComboBox;
import com.vaadin.flow.component.grid.GridSortOrder;
import com.vaadin.flow.component.html.Main;
import com.vaadin.flow.component.html.Span;
import com.vaadin.flow.component.orderedlayout.HorizontalLayout;
import com.vaadin.flow.data.provider.SortDirection;
import com.vaadin.flow.router.PageTitle;
import com.vaadin.flow.router.Route;
@ -24,13 +24,14 @@ import org.vaadin.firitin.components.grid.PagingGrid;
import java.io.ByteArrayInputStream;
import java.util.List;
import static com.primefactorsolutions.views.Constants.PAGE_SIZE;
@SpringComponent
@Scope("prototype")
@PageTitle("Documents")
@Route(value = "/documents", layout = MainLayout.class)
@PermitAll
public class DocumentsListView extends Main {
public class DocumentsListView extends BaseView {
private final DocumentService documentService;
private final EmployeeService employeeService;
@ -46,11 +47,16 @@ public class DocumentsListView extends Main {
}
private void initializeView() {
getCurrentPageLayout().add(createActionButton("Add Document", this::navigateToAddDocumentView));
final HorizontalLayout hl = new HorizontalLayout();
hl.add(createDocumentTypeFilter());
hl.add(createEmployeeFilter());
getCurrentPageLayout().add(hl);
configureDocumentGrid();
add(createActionButton("Add Document", this::navigateToAddDocumentView));
add(createDocumentTypeFilter());
add(createEmployeeFilter());
add(documentGrid);
getCurrentPageLayout().add(documentGrid);
}
private void configureDocumentGrid() {
@ -133,7 +139,7 @@ public class DocumentsListView extends Main {
private void configurePagination() {
documentGrid.setPaginationBarMode(PagingGrid.PaginationBarMode.BOTTOM);
documentGrid.setPageSize(5);
documentGrid.setPageSize(PAGE_SIZE);
}
private void updateDocumentGrid(final DocumentType documentType, final Employee employee) {

View File

@ -1,8 +1,12 @@
package com.primefactorsolutions.views;
import com.primefactorsolutions.model.Employee;
import com.primefactorsolutions.model.Team;
import com.primefactorsolutions.model.*;
import com.primefactorsolutions.service.EmployeeService;
import com.primefactorsolutions.service.ReportService;
import com.primefactorsolutions.service.TeamService;
import com.primefactorsolutions.service.TimeOffRequestService;
import com.vaadin.componentfactory.pdfviewer.PdfViewer;
import com.vaadin.flow.component.ClickEvent;
import com.vaadin.flow.component.Component;
@ -34,6 +38,8 @@ import org.vaadin.firitin.form.BeanValidationForm;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.time.LocalDate;
import java.time.temporal.ChronoUnit;
import java.util.Base64;
import java.util.List;
import java.util.UUID;
@ -47,6 +53,8 @@ public class EmployeeView extends BeanValidationForm<Employee> implements HasUrl
private final EmployeeService employeeService;
private final ReportService reportService;
private final TimeOffRequestService requestService;
private final TeamService teamService;
// TODO: campo usado para registrar al empleado en LDAP. Este campo podria estar en otro form eventualmente.
private final TextField username = createTextField("Username: ", 30, true);
@ -56,24 +64,21 @@ public class EmployeeView extends BeanValidationForm<Employee> implements HasUrl
private final ComboBox<Employee.Gender> gender = createGenderComboBox();
private final VDatePicker birthday = new VDatePicker("Fecha de Nacimiento");
private final TextField age = createTextField("Edad", 3, false);
private final TextField birthCity = createTextField("Ciudad y País de Nacimiento", 20, false);
private final TextField birthCity = createTextField("Ciudad y País de Nacimiento ejemplo: (Ciudad, País) ",
30, false);
private final TextField residenceAddress = createTextField("Dirección de Residencia", 50, false);
private final TextField localAddress = createTextField("Dep/Provincia de Residencia", 10, false);
private final TextField localAddress = createTextField("Departamento y Provincia de Residencia "
+ " ejemplo: (Departamento-Provincia)", 30, false);
private final ComboBox<Employee.MaritalStatus> maritalStatus = createMaritalStatusComboBox();
private final TextField numberOfChildren = createTextField("Numero de Hijos", 3, false);
private final TextField ci = createTextField("CI", 30, false);
private final TextField issuedIn = createTextField("Expedido en ", 30, false);
private final TextField numberOfChildren = createTextField("Numero de Hijos", 2, false);
private final TextField ci = createTextField("CI", 10, false);
private final TextField issuedIn = createTextField("Expedido en ", 10, false);
private final TextField phoneNumber = createTextField("Teléfono", 8, false);
private final EmailField personalEmail = createEmailField("E-mail");
private final TextField cod = createTextField("Codigo de Empleado", 30, false);
private final TextField position = createTextField("Cargo", 30, false);
private final TextField team = createTextField("Equipo", 30, false);
private final TextField leadManager = createTextField("Lead/Manager", 30, false);
private final TextField project = createTextField("Proyecto", 30, false);
private final EmailField personalEmail = createEmailField("E-mail ejemplo: (ejemplo@gmail.com)");
private final TextField emergencyCName = createTextField("Nombres y Apellidos de Contacto", 50, false);
private final TextField emergencyCAddress = createTextField("Dirección de Contacto", 50, false);
private final TextField emergencyCPhone = createTextField("Teléfono de Contacto", 8, false);
private final EmailField emergencyCEmail = createEmailField("Email de Contacto");
private final EmailField emergencyCEmail = createEmailField("Email de Contacto ejemplo: (ejemplo@gmail.com)");
private final MemoryBuffer buffer = new MemoryBuffer();
private final Upload upload = new Upload(buffer);
@ -92,10 +97,15 @@ public class EmployeeView extends BeanValidationForm<Employee> implements HasUrl
private final TextField certification4 = createTextField("Certificación 4", 30, false);
private final TextField recognition = createTextField("Reconocimientos", 30, false);
private final TextField achievements = createTextField("Logros Profesionales", 30, false);
private final TextField language = createTextField("Idioma", 30, false);
private final TextField language = createTextField("Idioma", 50, false);
private final TextField languageLevel = createTextField("Nivel de Idioma", 30, false);
//INFORMACION DE CONTRATACION
//INFORMACION ADMINISTRATIVA
private final TextField cod = createTextField("Codigo de Empleado", 20, false);
private final TextField position = createTextField("Cargo", 30, false);
private final ComboBox<Team> team = new ComboBox<>("Equipo");
private final TextField leadManager = createTextField("Lead/Manager", 30, false);
private final TextField project = createTextField("Proyecto", 30, false);
private final VDatePicker dateOfEntry = new VDatePicker("Fecha de Ingreso");
private final VDatePicker dateOfExit = new VDatePicker("Fecha de Retiro");
private final TextField contractType = createTextField("Tipo de Contratación", 30, false);
@ -132,13 +142,18 @@ public class EmployeeView extends BeanValidationForm<Employee> implements HasUrl
//TITULOS PARA INFORMACIÓN ADMINISTRATIVA
private final H2 infoAdm = new H2("Información Administrativa");
private final H3 infoCont = new H3("Información de Contratación");
private final H3 datBanc = new H3("Datos Bancados");
private final H3 datBanc = new H3("Datos Bancarios");
private final H3 datGest = new H3("Datos Gestora Pública y Seguro Social");
public EmployeeView(final EmployeeService employeeService, final ReportService reportService) {
public EmployeeView(final EmployeeService employeeService,
final ReportService reportService,
final TeamService teamService,
final TimeOffRequestService requestService) {
super(Employee.class);
this.employeeService = employeeService;
this.reportService = reportService;
this.requestService = requestService;
this.teamService = teamService;
saveButton.addThemeVariants(ButtonVariant.LUMO_PRIMARY);
configureComponents();
@ -150,11 +165,21 @@ public class EmployeeView extends BeanValidationForm<Employee> implements HasUrl
phoneNumber.addValueChangeListener(e -> validatePhoneNumber(phoneNumber, e.getValue()));
emergencyCPhone.setValueChangeMode(ValueChangeMode.EAGER);
emergencyCPhone.addValueChangeListener(e -> validatePhoneNumber(emergencyCPhone, e.getValue()));
firstName.setValueChangeMode(ValueChangeMode.EAGER);
firstName.addValueChangeListener(e -> validateNameField(firstName, e.getValue()));
lastName.setValueChangeMode(ValueChangeMode.EAGER);
lastName.addValueChangeListener(e -> validateNameField(lastName, e.getValue()));
createTeamComboBox();
configureUpload();
saveButton.setVisible(true);
editButton.setVisible(true);
reportButton.setVisible(true);
birthday.addValueChangeListener(event -> calculateAge());
birthday.setMax(java.time.LocalDate.now());
dateOfEntry.addValueChangeListener(event -> calculateSeniority());
dateOfEntry.addValueChangeListener(event -> calculateSeniority());
reportButton.addClickListener((ComponentEventListener<ClickEvent<Button>>) buttonClickEvent -> {
var employee = getEntity();
@ -167,16 +192,45 @@ public class EmployeeView extends BeanValidationForm<Employee> implements HasUrl
initDialog();
}
private void validateNameField(final TextField textField, final String value) {
if (!value.matches("^[a-zA-ZáéíóúÁÉÍÓÚñÑ\\s]*$")) {
textField.setInvalid(true);
textField.setErrorMessage("Este campo solo debe contener letras.");
} else {
textField.setInvalid(false);
}
}
private void calculateAge() {
if (birthday.getValue() != null) {
int currentYear = java.time.LocalDate.now().getYear();
int birthYear = birthday.getValue().getYear();
int ages = currentYear - birthYear;
age.setValue(String.valueOf(ages));
if (ages < 18) {
birthday.setInvalid(true);
birthday.setErrorMessage("La edad no puede ser menor a 18 años.");
Notification.show("La edad ingresada no es válida, debe ser mayor o igual a 18 años.");
} else {
birthday.setInvalid(false);
}
System.out.println(age);
}
}
private void calculateSeniority() {
LocalDate entryDate = dateOfEntry.getValue();
LocalDate exitDate = dateOfExit.getValue() != null ? dateOfExit.getValue() : LocalDate.now();
if (entryDate != null) {
long yearsOfService = ChronoUnit.YEARS.between(entryDate, exitDate);
String seniorityValue = yearsOfService + " años ";
seniority.setValue(seniorityValue);
} else {
seniority.setValue("No disponible");
}
}
private void configureUpload() {
upload.setAcceptedFileTypes("image/jpeg", "image/png");
upload.setMaxFileSize(1024 * 1024);
@ -188,11 +242,15 @@ public class EmployeeView extends BeanValidationForm<Employee> implements HasUrl
getEntity().setProfileImage(base64Image);
profileImagePreview.setSrc("data:image/jpeg;base64," + base64Image);
profileImagePreview.setSrc("data:image/png;base64," + base64Image);
profileImagePreview.setMaxWidth("150px");
profileImagePreview.setMaxHeight("150px");
} catch (IOException e) {
Notification.show("Error al subir la imagen.");
Notification.show("Error al subir la imagen: " + e.getMessage());
e.printStackTrace();
} catch (Exception e) {
Notification.show("Error en el servidor al procesar la imagen.");
e.printStackTrace();
}
});
}
@ -237,7 +295,7 @@ public class EmployeeView extends BeanValidationForm<Employee> implements HasUrl
ComboBox<Employee.Status> comboBox = new ComboBox<>("Estado");
comboBox.setItems(Employee.Status.values());
comboBox.setItemLabelGenerator(Employee.Status::name);
comboBox.setRequiredIndicatorVisible(true); // Indicador de campo requerido
comboBox.setRequiredIndicatorVisible(true);
return comboBox;
}
@ -262,6 +320,13 @@ public class EmployeeView extends BeanValidationForm<Employee> implements HasUrl
return emailField;
}
private void createTeamComboBox() {
List<Team> teams = teamService.findAllTeams();
team.setItems(teams);
team.setItemLabelGenerator(Team::getName);
team.setWidthFull();
}
private <T> ComboBox<T> createComboBox(final String label, final T[] items) {
ComboBox<T> comboBox = new ComboBox<>(label);
comboBox.setItems(items);
@ -282,6 +347,26 @@ public class EmployeeView extends BeanValidationForm<Employee> implements HasUrl
return !firstName.isEmpty() && !lastName.isEmpty() && status.getValue() != null;
}
private void setVacationDuration(
final Employee employee,
final TimeOffRequest request,
final LocalDate referenceDate) {
double yearsOfService = ChronoUnit.YEARS.between(employee.getDateOfEntry(), referenceDate);
request.setAvailableDays(calculateAvailableDays(yearsOfService));
}
private double calculateAvailableDays(final double yearsOfService) {
if (yearsOfService > 10) {
return 30.0;
} else if (yearsOfService > 5) {
return 20.0;
} else if (yearsOfService > 1) {
return 15.0;
} else {
return 0.0;
}
}
private void saveEmployee() {
if (validateForm()) {
Employee employee = getEntity();
@ -302,7 +387,6 @@ public class EmployeeView extends BeanValidationForm<Employee> implements HasUrl
editButton.setVisible(false);
}
@Override
public void setParameter(final BeforeEvent beforeEvent, final String action) {
final RouteParameters params = beforeEvent.getRouteParameters();
@ -337,12 +421,12 @@ public class EmployeeView extends BeanValidationForm<Employee> implements HasUrl
if (employee.getProfileImage() != null && !employee.getProfileImage().isEmpty()) {
profileImagePreview.setSrc("data:image/jpeg;base64," + employee.getProfileImage());
profileImagePreview.setVisible(true);
profileImagePreview.setMaxWidth("150px");
profileImagePreview.setMaxHeight("150px");
profileImagePreview.setMaxWidth("250px");
profileImagePreview.setMaxHeight("250px");
upload.setVisible(false);
upload.setVisible(true);
} else {
profileImagePreview.setVisible(false);
profileImagePreview.setVisible(true);
upload.setVisible(true);
}
}
@ -422,6 +506,7 @@ public class EmployeeView extends BeanValidationForm<Employee> implements HasUrl
emergencyCPhone.setReadOnly(false);
emergencyCEmail.setReadOnly(false);
upload.setVisible(false);
profileImagePreview.setVisible(true);
age.setReadOnly(false);
gender.setReadOnly(false);
status.setReadOnly(false);
@ -469,7 +554,6 @@ public class EmployeeView extends BeanValidationForm<Employee> implements HasUrl
birthCity, residenceAddress, localAddress,
maritalStatus, ci, issuedIn, numberOfChildren,
phoneNumber, personalEmail,
cod, position, team, leadManager, project,
contEmerg, emergencyCName, emergencyCAddress, emergencyCPhone, emergencyCEmail,
infProf,
titulos, pTitle1, pTitle2, pTitle3, pStudy1, pStudy2, pStudy3,
@ -477,11 +561,11 @@ public class EmployeeView extends BeanValidationForm<Employee> implements HasUrl
logros, recognition, achievements,
idioma, language, languageLevel,
infoAdm,
cod, position, team, leadManager, project,
infoCont, dateOfEntry, dateOfExit, contractType, seniority, salary,
datBanc, bankName, accountNumber,
datGest, gpss, sss, beneficiaries,
saveButton, editButton, reportButton, dialog
);
}
}
}

View File

@ -4,7 +4,6 @@ import com.primefactorsolutions.model.Employee;
import com.primefactorsolutions.service.EmployeeService;
import com.vaadin.flow.component.button.Button;
import com.vaadin.flow.component.html.H2;
import com.vaadin.flow.component.html.Main;
import com.vaadin.flow.router.PageTitle;
import com.vaadin.flow.router.Route;
import com.vaadin.flow.spring.annotation.SpringComponent;
@ -16,13 +15,12 @@ import org.springframework.context.annotation.Scope;
import java.util.List;
@SpringComponent
@Scope("prototype")
@PageTitle("Employees")
@Route(value = "/employees", layout = MainLayout.class)
@PermitAll
public class EmployeesListView extends Main {
public class EmployeesListView extends BaseView {
private final EmployeeService employeeService;
private final PagingGrid<Employee> table = new PagingGrid<>(Employee.class);
@ -34,10 +32,10 @@ public class EmployeesListView extends Main {
}
private void setupView() {
add(new H2("Employee List"));
getCurrentPageLayout().add(new H2("Employee List"));
configureTable();
add(createAddEmployeeButton());
add(table);
getCurrentPageLayout().add(createAddEmployeeButton());
getCurrentPageLayout().add(table);
}
private void configureTable() {
@ -75,7 +73,7 @@ public class EmployeesListView extends Main {
private void setupPagingGrid() {
table.setPaginationBarMode(PagingGrid.PaginationBarMode.BOTTOM);
table.setPageSize(5);
table.setPageSize(Constants.PAGE_SIZE);
}
private void refreshGrid() {

View File

@ -196,7 +196,6 @@ public class EvaluationView extends Main implements HasUrlParameter<String> {
final MenuBar navMenuBar = new MenuBar();
prev = navMenuBar.addItem("Anterior pregunta",
(ComponentEventListener<ClickEvent<MenuItem>>) menuItemClickEvent -> {
log.info(">>> prev");
this.currSubmission.setResponse(this.questionEditor.getValue());
this.assessmentService.saveSubmission(assessment.getId(), this.currSubmission);
this.currSubmission = this.assessmentService.getPrevSubmission(assessment.getId(), this.currSubmission);
@ -253,7 +252,6 @@ public class EvaluationView extends Main implements HasUrlParameter<String> {
start = new Button("Empezar");
start.addThemeVariants(ButtonVariant.LUMO_PRIMARY, ButtonVariant.LUMO_SUCCESS);
start.addClickListener((ComponentEventListener<ClickEvent<Button>>) buttonClickEvent -> {
log.info(">>> start");
this.assessment = this.assessmentService.startAssessment(this.assessment.getId());
if (tf.getValue().trim().equalsIgnoreCase(this.assessment.getCandidate().getEmail())) {
@ -318,7 +316,6 @@ public class EvaluationView extends Main implements HasUrlParameter<String> {
}
private void goToNext() {
log.info(">>> next");
Submission found = this.assessmentService.getNextSubmission(assessment.getId(),
this.currSubmission.getId());

View File

@ -0,0 +1,256 @@
package com.primefactorsolutions.views;
import com.primefactorsolutions.model.Employee;
import com.primefactorsolutions.model.HoursWorked;
import com.primefactorsolutions.model.Team;
import com.primefactorsolutions.service.EmployeeService;
import com.primefactorsolutions.service.HoursWorkedService;
import com.primefactorsolutions.service.TeamService;
import com.vaadin.flow.component.button.Button;
import com.vaadin.flow.component.combobox.ComboBox;
import com.vaadin.flow.component.notification.Notification;
import com.vaadin.flow.component.orderedlayout.HorizontalLayout;
import com.vaadin.flow.router.PageTitle;
import com.vaadin.flow.router.Route;
import com.vaadin.flow.spring.annotation.SpringComponent;
import jakarta.annotation.security.PermitAll;
import org.springframework.context.annotation.Scope;
import org.vaadin.firitin.components.grid.PagingGrid;
import java.time.LocalDate;
import java.time.temporal.IsoFields;
import java.util.*;
import java.util.stream.Collectors;
import static com.primefactorsolutions.views.Constants.PAGE_SIZE;
@SpringComponent
@PermitAll
@Scope("prototype")
@PageTitle("Registro de Horas Trabajadas")
@Route(value = "/hours-worked-list", layout = MainLayout.class)
public class HoursWorkedListView extends BaseView {
private final HoursWorkedService hoursWorkedService;
private final EmployeeService employeeService;
private final TeamService teamService;
private final PagingGrid<HoursWorked> hoursWorkedGrid = new PagingGrid<>();
private ComboBox<Employee> employeeFilter;
private ComboBox<Team> teamFilter;
private UUID selectedEmployeeId;
public HoursWorkedListView(final HoursWorkedService hoursWorkedService,
final EmployeeService employeeService,
final TeamService teamService) {
this.hoursWorkedService = hoursWorkedService;
this.employeeService = employeeService;
this.teamService = teamService;
initializeView();
refreshGridListHoursWorked(null, null);
}
private void refreshGridListHoursWorked(final Employee employee,
final Team team) {
hoursWorkedGrid.setPagingDataProvider((page, pageSize) -> {
int start = (int) (page * hoursWorkedGrid.getPageSize());
List<HoursWorked> hoursWorkedList = fetchFilteredHoursWorked(start, pageSize, employee, team);
double totalHours = hoursWorkedList.stream()
.mapToDouble(HoursWorked::getTotalHours)
.sum();
Notification.show("Total de horas trabajadas: " + totalHours,
3000, Notification.Position.BOTTOM_CENTER);
return hoursWorkedList;
});
hoursWorkedGrid.getDataProvider().refreshAll();
}
private List<HoursWorked> fetchFilteredHoursWorked(final int start,
final int pageSize,
final Employee employee,
final Team team) {
List<HoursWorked> filteredHoursWorked = hoursWorkedService.findAll();
if (employee != null && !"TODOS".equals(employee.getFirstName())) {
filteredHoursWorked = filteredHoursWorked.stream()
.filter(hw -> hw.getEmployee().getId().equals(employee.getId()))
.collect(Collectors.toList());
}
if (team != null && !"TODOS".equals(team.getName())) {
filteredHoursWorked = filteredHoursWorked.stream()
.filter(hw -> hw.getEmployee().getTeam() != null
&& hw.getEmployee().getTeam().getId().equals(team.getId()))
.collect(Collectors.toList());
}
for (HoursWorked hoursWorked : filteredHoursWorked) {
if (employee != null && hoursWorked.getEmployee().getId().equals(employee.getId())) {
LocalDate date = hoursWorked.getDate();
int currentWeek = date.get(IsoFields.WEEK_OF_WEEK_BASED_YEAR);
double totalWorkedInSameWeek = filteredHoursWorked.stream()
.filter(hw -> hw.getEmployee().getId().equals(employee.getId())
&&
hw.getDate().get(IsoFields.WEEK_OF_WEEK_BASED_YEAR) == currentWeek)
.mapToDouble(HoursWorked::getHours)
.sum();
double updatedPendingHours = totalWorkedInSameWeek - hoursWorked.getHours();
hoursWorked.setHoraspendientes(updatedPendingHours);
}
}
int end = Math.min(start + pageSize, filteredHoursWorked.size());
return filteredHoursWorked.subList(start, end);
}
private void initializeView() {
getCurrentPageLayout().add(createAddHoursWorked());
setupFilters();
setupListHoursWorkedGrid();
getCurrentPageLayout().add(hoursWorkedGrid);
getCurrentPageLayout().add(createActionButtons());
}
private void setupFilters() {
final HorizontalLayout hl = new HorizontalLayout();
hl.add(createEmployeeFilter());
hl.add(createTeamFilter());
getCurrentPageLayout().add(hl);
}
private void setupListHoursWorkedGrid() {
hoursWorkedGrid.addColumn(hw -> hw.getDate() != null ? hw.getDate().toString() : "")
.setHeader("Fecha")
.setSortable(true);
hoursWorkedGrid.addColumn(HoursWorked::getWeekNumber)
.setHeader("Semana")
.setSortable(true);
hoursWorkedGrid.addColumn(hw -> hw.getEmployee().getFirstName() + " " + hw.getEmployee().getLastName())
.setHeader("Empleado");
hoursWorkedGrid.addColumn(hw -> hw.getEmployee().getTeam() != null ? hw.getEmployee().getTeam()
.getName() : "Sin asignar")
.setHeader("Equipo");
hoursWorkedGrid.addColumn(HoursWorked::getActividad).setHeader("Actividad");
hoursWorkedGrid.addColumn(HoursWorked::getHours).setHeader("Total Horas").setSortable(true);
hoursWorkedGrid.addColumn(hw -> hw.getHoraspendientes() - calcularTotal(hw)).setHeader("Horas Pendientes")
.setSortable(true);
hoursWorkedGrid.setPaginationBarMode(PagingGrid.PaginationBarMode.BOTTOM);
hoursWorkedGrid.setPageSize(PAGE_SIZE);
hoursWorkedGrid.asSingleSelect().addValueChangeListener(event -> {
HoursWorked selectedHoursWorked = event.getValue();
if (selectedHoursWorked != null) {
selectedEmployeeId = selectedHoursWorked.getEmployee().getId();
}
});
}
private double calcularTotal(final HoursWorked hoursWorked) {
List<HoursWorked> listHoursworkedemploye = hoursWorkedService.findListHoursWorkedEmployee(
hoursWorked.getEmployee().getId(), hoursWorked.getWeekNumber());
return calculateTotalUtilized(listHoursworkedemploye);
}
private double calculateTotalUtilized(final List<HoursWorked> employeeRequests) {
return employeeRequests.stream()
.filter(Objects::nonNull)
.mapToDouble(HoursWorked::getHours)
.sum();
}
private HorizontalLayout createActionButtons() {
Button viewButton = new Button("Ver", event -> {
if (selectedEmployeeId != null) {
navigateToHoursWorkedView(selectedEmployeeId);
} else {
Notification.show("Seleccione una solicitud.", 3000, Notification.Position.MIDDLE);
}
});
Button closeButton = new Button("Salir", event -> navigateToListView());
return new HorizontalLayout(viewButton, closeButton);
}
private void navigateToListView() {
getUI().ifPresent(ui -> ui.navigate(MainView.class));
}
private void navigateToHoursWorkedView(final UUID idEmployee) {
getUI().ifPresent(ui -> ui.navigate("hours-worked-list/" + idEmployee.toString()));
}
private Button createButton(final String label, final Runnable onClickAction) {
final Button button = new Button(label);
button.addClickListener(event -> onClickAction.run());
return button;
}
private Button createAddHoursWorked() {
return createButton("Agregar Actividad", this::navigateToHours);
}
private void navigateToHours() {
getUI().ifPresent(ui -> ui.navigate(HoursWorkedView.class, "new"));
}
private ComboBox<Employee> createEmployeeFilter() {
employeeFilter = new ComboBox<>("Empleado");
final List<Employee> employees = new ArrayList<>(employeeService.findAllEmployees());
employees.addFirst(createAllEmployeesOption());
employeeFilter.setItems(employees);
employeeFilter.setItemLabelGenerator(this::getEmployeeFullName);
employeeFilter.setValue(employees.getFirst());
employeeFilter.addValueChangeListener(event ->
refreshGridListHoursWorked(
event.getValue(),
teamFilter.getValue()
)
);
return employeeFilter;
}
private String getEmployeeFullName(final Employee employee) {
return "TODOS".equals(employee.getFirstName())
? "TODOS" : employee.getFirstName() + " " + employee.getLastName();
}
private ComboBox<Team> createTeamFilter() {
teamFilter = new ComboBox<>("Equipo");
List<Team> teams = new ArrayList<>(teamService.findAllTeams());
teams.addFirst(createAllTeamsOption());
teamFilter.setItems(teams);
teamFilter.setItemLabelGenerator(this::getTeamLabel);
teamFilter.setValue(teams.getFirst());
teamFilter.addValueChangeListener(event ->
refreshGridListHoursWorked(
employeeFilter.getValue(),
event.getValue()
)
);
return teamFilter;
}
private String getTeamLabel(final Team team) {
return team != null && !"TODOS".equals(team.getName()) ? team.getName() : "TODOS";
}
private Employee createAllEmployeesOption() {
Employee allEmployeesOption = new Employee();
allEmployeesOption.setFirstName("TODOS");
return allEmployeesOption;
}
private Team createAllTeamsOption() {
Team allTeamsOption = new Team();
allTeamsOption.setName("TODOS");
return allTeamsOption;
}
}

View File

@ -1,197 +1,256 @@
package com.primefactorsolutions.views;
import com.vaadin.flow.component.datepicker.DatePicker;
import com.vaadin.flow.component.orderedlayout.HorizontalLayout;
import com.primefactorsolutions.model.Employee;
import com.primefactorsolutions.model.HoursWorked;
import com.primefactorsolutions.model.Team;
import com.primefactorsolutions.service.EmployeeService;
import com.primefactorsolutions.service.HoursWorkedService;
import com.primefactorsolutions.service.TeamService;
import com.vaadin.flow.component.Component;
import com.vaadin.flow.component.html.H2;
import com.vaadin.flow.component.html.Label;
import com.vaadin.flow.component.notification.Notification;
import com.vaadin.flow.component.textfield.TextField;
import com.vaadin.flow.component.button.Button;
import com.vaadin.flow.component.combobox.ComboBox;
import com.vaadin.flow.component.grid.Grid;
import com.vaadin.flow.component.orderedlayout.VerticalLayout;
import com.vaadin.flow.component.html.H2;
import com.vaadin.flow.router.PageTitle;
import com.vaadin.flow.router.Route;
import com.vaadin.flow.router.*;
import com.vaadin.flow.spring.annotation.SpringComponent;
import jakarta.annotation.security.PermitAll;
import org.springframework.context.annotation.Scope;
import org.vaadin.firitin.components.datepicker.VDatePicker;
import org.vaadin.firitin.form.BeanValidationForm;
import java.time.LocalDate;
import java.time.YearMonth;
import java.time.temporal.IsoFields;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
@SpringComponent
@PermitAll
@Scope("prototype")
@PageTitle("Hours Worked")
@Route(value = "/hours-worked/me", layout = MainLayout.class)
public class HoursWorkedView extends VerticalLayout {
public HoursWorkedView() {
H2 title = new H2("Registro de Horas Trabajadas");
@PageTitle("Horas Trabajadas")
@Route(value = "/hours-worked-list/:hours-workedId?/:action?", layout = MainLayout.class)
public class HoursWorkedView extends BeanValidationForm<HoursWorked> implements HasUrlParameter<String> {
private final VDatePicker dateField = new VDatePicker("Fecha");
private final ComboBox<Team> teamField = new ComboBox<>("Equipo");
private ComboBox<Employee> employeeField;
private final ComboBox<String> tareasEspecificasDropdown = new ComboBox<>("Tarea Específica");
private final TextField tareaEspecificaInput = new TextField("Otra Tarea Específica");
private final TextField horasTareaEspecificaField = new TextField("Horas Tarea Específica");
private final TextField activityField = new TextField("Actividad");
private final TextField hoursField = new TextField("Horas");
DatePicker datePicker = new DatePicker("Selecciona una fecha");
datePicker.setValue(LocalDate.now());
private final H2 equipoLabel = new H2("Tareas del Cliente/Equipo");
private final H2 empresaLabel = new H2("Tareas de la Empresa");
private final Label totalCompletadoLabel = new Label();
ComboBox<String> equipoDropdown = new ComboBox<>("Equipo");
equipoDropdown.setItems("Equipo 1", "Equipo 2", "Equipo 3"); // Ejemplo de datos
private final HoursWorkedService hoursWorkedService;
private final EmployeeService employeeService;
private final TeamService teamService;
private HoursWorked hoursWorked;
private Employee employee;
TextField empleadoSearch = new TextField("Empleado (Search)");
private Button saveButton;
HorizontalLayout filtersLayout = new HorizontalLayout(equipoDropdown, empleadoSearch);
public HoursWorkedView(final HoursWorkedService hoursWorkedService,
final EmployeeService employeeService,
final TeamService teamService) {
super(HoursWorked.class);
this.hoursWorkedService = hoursWorkedService;
this.employeeService = employeeService;
this.teamService = teamService;
Grid<Actividad> grid = new Grid<>(Actividad.class, false);
grid.addColumn(Actividad::getNombre).setHeader("Actividad");
grid.addColumn(Actividad::getLunes).setHeader("Lunes");
grid.addColumn(Actividad::getMartes).setHeader("Martes");
grid.addColumn(Actividad::getMiercoles).setHeader("Miércoles");
grid.addColumn(Actividad::getJueves).setHeader("Jueves");
grid.addColumn(Actividad::getViernes).setHeader("Viernes");
grid.addColumn(Actividad::getSabado).setHeader("Sábado");
grid.addColumn(Actividad::getDomingo).setHeader("Domingo");
initializeDateField();
initializeTeamField();
initializeEmployeeField();
configureTareasEspecificas();
grid.setItems(
new Actividad.Builder()
.nombre("Actividad 1")
.lunes(3)
.martes(3)
.miercoles(3)
.jueves(3)
.viernes(3)
.sabado(1)
.domingo(2)
.build(),
new Actividad.Builder()
.nombre("Actividad 2")
.lunes(2)
.martes(2)
.miercoles(2)
.jueves(2)
.viernes(2)
.sabado(0)
.domingo(1)
.build(),
new Actividad.Builder()
.nombre("Meeting 1")
.lunes(0)
.martes(0.5)
.miercoles(0.5)
.jueves(0)
.viernes(0)
.sabado(0.5)
.domingo(0)
.build()
);
Button actualizarButton = new Button("Actualizar");
Button guardarButton = new Button("Guardar");
Button cerrarButton = new Button("Cerrar");
HorizontalLayout buttonsLayout = new HorizontalLayout(actualizarButton, guardarButton, cerrarButton);
add(title, datePicker, filtersLayout, grid, buttonsLayout);
}
public static final class Actividad {
private final String nombre;
private final double lunes;
private final double martes;
private final double miercoles;
private final double jueves;
private final double viernes;
private final double sabado;
private final double domingo;
@Override
public void setParameter(final BeforeEvent beforeEvent, final String action) {
final RouteParameters params = beforeEvent.getRouteParameters();
final String s = params.get("hours-workedId").orElse(null);
private Actividad(final Builder builder) {
this.nombre = builder.nombre;
this.lunes = builder.lunes;
this.martes = builder.martes;
this.miercoles = builder.miercoles;
this.jueves = builder.jueves;
this.viernes = builder.viernes;
this.sabado = builder.sabado;
this.domingo = builder.domingo;
}
if ("new".equals(action)) {
setEntityWithEnabledSave(new HoursWorked());
} else {
UUID hoursWorkedId = UUID.fromString(s);
var hoursWorked = hoursWorkedService.getHoursWorked(hoursWorkedId);
setEntityWithEnabledSave(hoursWorked);
public static class Builder {
private String nombre;
private double lunes;
private double martes;
private double miercoles;
private double jueves;
private double viernes;
private double sabado;
private double domingo;
public Builder nombre(final String nombre) {
this.nombre = nombre;
return this;
}
public Builder lunes(final double lunes) {
this.lunes = lunes;
return this;
}
public Builder martes(final double martes) {
this.martes = martes;
return this;
}
public Builder miercoles(final double miercoles) {
this.miercoles = miercoles;
return this;
}
public Builder jueves(final double jueves) {
this.jueves = jueves;
return this;
}
public Builder viernes(final double viernes) {
this.viernes = viernes;
return this;
}
public Builder sabado(final double sabado) {
this.sabado = sabado;
return this;
}
public Builder domingo(final double domingo) {
this.domingo = domingo;
return this;
}
public Actividad build() {
return new Actividad(this);
if ("edit".equals(action) && !s.isEmpty()) {
saveButton.setVisible(true);
} else if ("view".equals(action) && !s.isEmpty()) {
saveButton.setVisible(false);
}
}
}
public String getNombre() {
return nombre;
}
@Override
protected List<Component> getFormComponents() {
return List.of(
dateField,
teamField,
employeeField,
equipoLabel,
activityField,
hoursField,
empresaLabel,
tareasEspecificasDropdown,
tareaEspecificaInput,
horasTareaEspecificaField,
createCloseButton()
);
}
public double getLunes() {
return lunes;
}
private void configureTareasEspecificas() {
tareasEspecificasDropdown.setItems("Entrevistas", "Reuniones",
"Colaboraciones", "Aprendizajes", "Proyectos PFS", "Otros");
tareasEspecificasDropdown.setPlaceholder("Selecciona una tarea...");
public double getMartes() {
return martes;
}
tareasEspecificasDropdown.addValueChangeListener(event -> {
String selected = event.getValue();
boolean isOtros = "Otros".equals(selected);
tareaEspecificaInput.setVisible(isOtros);
horasTareaEspecificaField.setVisible(true);
if (!isOtros) {
tareaEspecificaInput.clear();
horasTareaEspecificaField.clear();
}
});
tareaEspecificaInput.setVisible(false);
horasTareaEspecificaField.setVisible(false);
}
public double getMiercoles() {
return miercoles;
}
protected Button createSaveButton() {
saveButton = new Button("Guardar");
saveButton.addClickListener(event -> saveHoursWorked());
return saveButton;
}
public double getJueves() {
return jueves;
}
protected Button createCloseButton() {
Button closeButton = new Button("Cerrar");
closeButton.addClickListener(event -> closeForm());
return closeButton;
}
public double getViernes() {
return viernes;
}
private void initializeTeamField() {
List<Team> teams = new ArrayList<>(teamService.findAllTeams());
teamField.setItems(teamService.findAllTeams());
teamField.setItemLabelGenerator(Team::getName);
teamField.setValue(teams.getFirst());
teamField.addValueChangeListener(event -> {
if (teams != null) {
employeeField.getValue();
event.getValue();
}
}
);
}
public double getSabado() {
return sabado;
}
private ComboBox<Employee> initializeEmployeeField() {
employeeField = new ComboBox<>("Empleado");
List<Employee> employees = new ArrayList<>(employeeService.findAllEmployees());
employeeField.setItems(employees);
employeeField.setItemLabelGenerator(this::getEmployeeFullName);
employeeField.setValue(employees.getFirst());
return employeeField;
}
public double getDomingo() {
return domingo;
private String getEmployeeFullName(final Employee employee) {
return "TODOS".equals(employee.getFirstName())
? "TODOS" : employee.getFirstName() + " " + employee.getLastName();
}
private void initializeDateField() {
LocalDate today = LocalDate.now();
YearMonth currentMonth = YearMonth.of(today.getYear(), today.getMonth());
LocalDate startOfMonth = currentMonth.atDay(1);
LocalDate maxSelectableDate = today;
dateField.setMin(startOfMonth);
dateField.setMax(maxSelectableDate);
dateField.setValue(today);
dateField.addValueChangeListener(event -> {
LocalDate selectedDate = event.getValue();
if (selectedDate != null) {
int weekNumber = selectedDate.get(IsoFields.WEEK_OF_WEEK_BASED_YEAR);
Notification.show("Número de la semana: " + weekNumber, 3000, Notification.Position.BOTTOM_CENTER);
if (hoursWorked != null) {
hoursWorked.setWeekNumber(weekNumber);
}
}
});
}
private void saveHoursWorked() {
if (isFormValid()) {
HoursWorked hoursWorked = getEntity();
setFieldValues(hoursWorked);
hoursWorkedService.save(hoursWorked);
Notification.show("Horas trabajadas guardadas correctamente.");
closeForm();
}
}
private void setFieldValues(final HoursWorked hoursWorked) {
hoursWorked.setDate(dateField.getValue());
hoursWorked.setTeam(teamField.getValue());
hoursWorked.setEmployee(employeeField.getValue());
hoursWorked.setActividad(activityField.getValue());
try {
double hours = Double.parseDouble(hoursField.getValue());
hoursWorked.setHours(hours);
} catch (NumberFormatException e) {
Notification.show("Por favor, ingrese un número válido para las horas.");
}
if ("Otros".equals(tareasEspecificasDropdown.getValue())) {
hoursWorked.setTareasEspecificas(tareaEspecificaInput.getValue());
try {
double horasEspecifica = Double.parseDouble(horasTareaEspecificaField.getValue());
hoursWorked.setHorasTareasEspecificas(horasEspecifica);
double totalHoras = hoursWorked.getHours() + horasEspecifica;
hoursWorked.setTotalHours(totalHoras);
} catch (NumberFormatException e) {
Notification.show("Por favor, ingrese un número válido para las horas de la tarea específica.");
}
}
}
private void closeForm() {
getUI().ifPresent(ui -> ui.navigate("hours-worked-list/" + hoursWorked.getId().toString()));
}
private boolean isFormValid() {
return dateField.getValue() != null
&&
teamField.getValue() != null
&&
employeeField.getValue() != null
&&
!activityField.isEmpty();
}
private void configureViewOrEditAction(final String action) {
if ("edit".equals(action) && hoursWorked != null) {
setFieldsReadOnly(false);
} else if ("view".equals(action) && hoursWorked != null) {
setFieldsReadOnly(true);
saveButton.setEnabled(false);
}
}
private void setFieldsReadOnly(final boolean readOnly) {
dateField.setReadOnly(readOnly);
teamField.setReadOnly(readOnly);
employeeField.setReadOnly(readOnly);
activityField.setReadOnly(readOnly);
}
}

View File

@ -0,0 +1,54 @@
package com.primefactorsolutions.views;
import com.vaadin.flow.component.button.Button;
import com.vaadin.flow.component.button.ButtonVariant;
import com.vaadin.flow.component.formlayout.FormLayout;
import com.vaadin.flow.component.html.H3;
import com.vaadin.flow.component.orderedlayout.HorizontalLayout;
import com.vaadin.flow.component.orderedlayout.VerticalLayout;
import com.vaadin.flow.component.textfield.PasswordField;
import com.vaadin.flow.router.BeforeEnterEvent;
import com.vaadin.flow.router.BeforeEnterObserver;
import com.vaadin.flow.router.PageTitle;
import com.vaadin.flow.router.Route;
import com.vaadin.flow.server.auth.AnonymousAllowed;
@SuppressWarnings("unused")
@Route("init-account")
@PageTitle("PFS Intra")
@AnonymousAllowed
public class InitAccountView extends VerticalLayout implements BeforeEnterObserver {
public InitAccountView() {
setSizeFull();
setAlignItems(Alignment.CENTER);
setJustifyContentMode(JustifyContentMode.CENTER);
final VerticalLayout vl = new VerticalLayout();
vl.setJustifyContentMode(JustifyContentMode.CENTER);
vl.setWidth("400px");
final PasswordField password = new PasswordField("Password");
final PasswordField confirmPassword = new PasswordField("Confirm Password");
final FormLayout formLayout = new FormLayout(password, confirmPassword);
formLayout.setColspan(password, 3);
formLayout.setColspan(confirmPassword, 3);
final Button primaryButton = new Button("Submit");
primaryButton.addThemeVariants(ButtonVariant.LUMO_PRIMARY);
final Button secondaryButton = new Button("Cancel");
HorizontalLayout hl = new HorizontalLayout(secondaryButton, primaryButton);
vl.add(new H3("Set Account Password"));
vl.add(formLayout);
vl.add(hl);
add(vl);
}
@Override
public void beforeEnter(final BeforeEnterEvent beforeEnterEvent) {
}
}

View File

@ -1,6 +1,8 @@
package com.primefactorsolutions.views;
import com.vaadin.flow.component.html.Anchor;
import com.vaadin.flow.component.html.H1;
import com.vaadin.flow.component.html.Span;
import com.vaadin.flow.component.login.LoginForm;
import com.vaadin.flow.component.orderedlayout.VerticalLayout;
import com.vaadin.flow.router.BeforeEnterEvent;
@ -8,6 +10,9 @@ import com.vaadin.flow.router.BeforeEnterObserver;
import com.vaadin.flow.router.PageTitle;
import com.vaadin.flow.router.Route;
import com.vaadin.flow.server.auth.AnonymousAllowed;
import com.vaadin.flow.theme.lumo.LumoUtility;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
@Route("login")
@PageTitle("PFS Intra")
@ -16,16 +21,22 @@ public class LoginView extends VerticalLayout implements BeforeEnterObserver {
private final LoginForm login = new LoginForm();
public LoginView() {
public LoginView(@Autowired @Value("${git.commit.id.abbrev}") final String commitId) {
addClassName("login-view");
setSizeFull();
setAlignItems(Alignment.CENTER);
setJustifyContentMode(JustifyContentMode.CENTER);
login.setAction("login");
login.setForgotPasswordButtonVisible(false);
add(new H1("PFS Intra"));
add(login);
add(new Anchor("/password-recovery", "Reset password?"));
final Span version = new Span(String.format("v.%s", commitId));
version.addClassName(LumoUtility.FontSize.XSMALL);
add(version);
}
@Override

View File

@ -1,22 +1,29 @@
package com.primefactorsolutions.views;
import com.primefactorsolutions.model.Employee;
import com.vaadin.flow.component.Component;
import com.vaadin.flow.component.Text;
import com.vaadin.flow.component.applayout.AppLayout;
import com.vaadin.flow.component.applayout.DrawerToggle;
import com.vaadin.flow.component.button.Button;
import com.vaadin.flow.component.html.Footer;
import com.vaadin.flow.component.html.H1;
import com.vaadin.flow.component.html.Header;
import com.vaadin.flow.component.html.Span;
import com.vaadin.flow.component.avatar.Avatar;
import com.vaadin.flow.component.contextmenu.HasMenuItems;
import com.vaadin.flow.component.contextmenu.MenuItem;
import com.vaadin.flow.component.contextmenu.SubMenu;
import com.vaadin.flow.component.html.*;
import com.vaadin.flow.component.icon.Icon;
import com.vaadin.flow.component.icon.VaadinIcon;
import com.vaadin.flow.component.menubar.MenuBar;
import com.vaadin.flow.component.menubar.MenuBarVariant;
import com.vaadin.flow.component.orderedlayout.FlexComponent;
import com.vaadin.flow.component.orderedlayout.HorizontalLayout;
import com.vaadin.flow.component.orderedlayout.Scroller;
import com.vaadin.flow.component.shared.Tooltip;
import com.vaadin.flow.component.sidenav.SideNav;
import com.vaadin.flow.component.sidenav.SideNavItem;
import com.vaadin.flow.router.PageTitle;
import com.vaadin.flow.spring.security.AuthenticationContext;
import com.vaadin.flow.theme.lumo.LumoUtility;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.core.userdetails.UserDetails;
import org.vaadin.lineawesome.LineAwesomeIcon;
@ -30,67 +37,98 @@ public class MainLayout extends AppLayout {
private H1 viewTitle;
public MainLayout(final AuthenticationContext authContext) {
public MainLayout(final AuthenticationContext authContext,
@Autowired @Value("${git.commit.id.abbrev}") final String commitId) {
this.authContext = authContext;
setPrimarySection(Section.DRAWER);
addDrawerContent();
addDrawerContent(commitId);
addHeaderContent();
}
private void addHeaderContent() {
DrawerToggle toggle = new DrawerToggle();
final DrawerToggle toggle = new DrawerToggle();
toggle.setAriaLabel("Menu toggle");
viewTitle = new H1();
viewTitle.addClassNames(LumoUtility.FontSize.LARGE, LumoUtility.Margin.NONE);
HorizontalLayout
header =
authContext.getAuthenticatedUser(UserDetails.class)
.map(user -> {
final Button logout = new Button("Logout", click -> this.authContext.logout());
final Span loggedUser = new Span("Welcome " + user.getUsername());
String employeeId = "N/A";
final HorizontalLayout header = authContext.getAuthenticatedUser(UserDetails.class)
.map(user -> {
String employeeId = "N/A";
if (user instanceof Employee) {
final UUID uuid = ((Employee) user).getId();
if (user instanceof Employee) {
final UUID uuid = ((Employee) user).getId();
if (uuid != null) {
employeeId = uuid.toString();
}
}
if (uuid != null) {
employeeId = uuid.toString();
}
}
final Tooltip tooltip = Tooltip.forComponent(loggedUser)
.withText("Employee id: " + employeeId)
.withPosition(Tooltip.TooltipPosition.TOP_START);
final Avatar loggedUser = new Avatar(user.getUsername());
loggedUser.getStyle().set("display", "block");
loggedUser.getElement().setAttribute("tabindex", "-1");
final HorizontalLayout hl = new HorizontalLayout(loggedUser, logout);
hl.setJustifyContentMode(FlexComponent.JustifyContentMode.END);
final MenuBar menuBar = new MenuBar();
menuBar.addThemeVariants(MenuBarVariant.LUMO_TERTIARY_INLINE);
final MenuItem actions = createIconItem(menuBar, loggedUser, null, employeeId);
final SubMenu actionsSubMenu = actions.getSubMenu();
final MenuItem signOutMenuItem = createIconItem(actionsSubMenu,
createIcon(VaadinIcon.EXIT, true), "Sign-out", null);
signOutMenuItem.addClickListener(c -> this.authContext.logout());
return hl;
}).orElseGet(HorizontalLayout::new);
final HorizontalLayout hl = new HorizontalLayout(menuBar);
hl.setJustifyContentMode(FlexComponent.JustifyContentMode.END);
return hl;
}).orElseGet(HorizontalLayout::new);
header.setAlignItems(FlexComponent.Alignment.STRETCH);
header.setWidthFull();
addToNavbar(true, toggle, viewTitle, header);
}
private void addDrawerContent() {
Span appName = new Span("pfs-intra");
private MenuItem createIconItem(final HasMenuItems menu, final Component component,
final String label, final String ariaLabel) {
final MenuItem item = menu.addItem(component, e -> {
});
if (ariaLabel != null) {
item.setAriaLabel(ariaLabel);
}
if (label != null) {
item.add(new Text(label));
}
return item;
}
private Icon createIcon(final VaadinIcon iconName, final boolean isChild) {
final Icon icon = new Icon(iconName);
if (isChild) {
icon.getStyle().set("width", "var(--lumo-icon-size-s)");
icon.getStyle().set("height", "var(--lumo-icon-size-s)");
icon.getStyle().set("marginRight", "var(--lumo-space-s)");
}
return icon;
}
private void addDrawerContent(final String commitId) {
final Span appName = new Span("pfs-intra");
appName.addClassNames(LumoUtility.FontWeight.SEMIBOLD, LumoUtility.FontSize.LARGE);
Header header = new Header(appName);
Scroller scroller = new Scroller(createNavigation());
addToDrawer(header, scroller, createFooter());
final Header header = new Header(appName);
final Scroller scroller = new Scroller(createNavigation());
addToDrawer(header, scroller, createFooter(commitId));
}
private SideNav createNavigation() {
SideNav nav = new SideNav();
final SideNav nav = new SideNav();
authContext.getAuthenticatedUser(UserDetails.class).ifPresent(u -> {
SideNavItem recruiting = new SideNavItem("Recruiting", MainView.class,
LineAwesomeIcon.BUSINESS_TIME_SOLID.create());
SideNavItem recruiting = new SideNavItem("Recruiting");
recruiting.setPrefixComponent(LineAwesomeIcon.BUSINESS_TIME_SOLID.create());
recruiting.addItem(new SideNavItem("Assessments", AssessmentsListView.class,
LineAwesomeIcon.RIBBON_SOLID.create()));
recruiting.addItem(new SideNavItem("Candidates", CandidatesListView.class,
@ -98,8 +136,8 @@ public class MainLayout extends AppLayout {
recruiting.addItem(new SideNavItem("Questions", QuestionsListView.class,
LineAwesomeIcon.QUESTION_SOLID.create()));
SideNavItem admin = new SideNavItem("Admin", MainView.class,
LineAwesomeIcon.SUPERSCRIPT_SOLID.create());
SideNavItem admin = new SideNavItem("Admin");
admin.setPrefixComponent(LineAwesomeIcon.BUILDING.create());
admin.addItem(new SideNavItem("Employees", EmployeesListView.class,
LineAwesomeIcon.USER_EDIT_SOLID.create()));
admin.addItem(new SideNavItem("Documents", DocumentsListView.class,
@ -108,10 +146,16 @@ public class MainLayout extends AppLayout {
SideNavItem timeOff = new SideNavItem("My Time-off", TimeoffView.class,
LineAwesomeIcon.PLANE_DEPARTURE_SOLID.create());
timeOff.addItem(new SideNavItem("Vacations", RequestsListView.class,
LineAwesomeIcon.SUN.create()));
LineAwesomeIcon.UMBRELLA_BEACH_SOLID.create()));
timeOff.addItem(new SideNavItem("Add Vacation", RequestRegisterView.class,
LineAwesomeIcon.CALENDAR_PLUS.create()));
timeOff.addItem(new SideNavItem("Pending Requests", PendingRequestsListView.class,
LineAwesomeIcon.LIST_ALT.create()));
SideNavItem timesheet = new SideNavItem("My Timesheet", TimesheetView.class,
LineAwesomeIcon.HOURGLASS_START_SOLID.create());
timesheet.addItem(new SideNavItem("Hours Worked", HoursWorkedView.class,
timesheet.addItem(new SideNavItem("Registro de Horas Trabajadas", HoursWorkedListView.class,
LineAwesomeIcon.ID_CARD_SOLID.create()));
timesheet.addItem(new SideNavItem("Reporte Horas Trabajadas", ReporteView.class,
LineAwesomeIcon.ID_CARD_SOLID.create()));
SideNavItem profile = new SideNavItem("My Profile", ProfileView.class,
@ -128,10 +172,8 @@ public class MainLayout extends AppLayout {
return nav;
}
private Footer createFooter() {
Footer layout = new Footer();
return layout;
private Footer createFooter(final String commitId) {
return new Footer(new Text(String.format("v.%s", commitId)));
}
@Override

View File

@ -10,7 +10,8 @@ import jakarta.annotation.security.PermitAll;
@Route(value = "", layout = MainLayout.class)
@PermitAll
public class MainView extends Main {
public MainView() {
add(new Text("welcome"));
add(new Text("Welcome"));
}
}

View File

@ -0,0 +1,67 @@
package com.primefactorsolutions.views;
import com.primefactorsolutions.service.AccountService;
import com.vaadin.flow.component.ClickEvent;
import com.vaadin.flow.component.ComponentEventListener;
import com.vaadin.flow.component.button.Button;
import com.vaadin.flow.component.button.ButtonVariant;
import com.vaadin.flow.component.formlayout.FormLayout;
import com.vaadin.flow.component.html.H3;
import com.vaadin.flow.component.orderedlayout.HorizontalLayout;
import com.vaadin.flow.component.orderedlayout.VerticalLayout;
import com.vaadin.flow.component.textfield.EmailField;
import com.vaadin.flow.router.BeforeEnterEvent;
import com.vaadin.flow.router.BeforeEnterObserver;
import com.vaadin.flow.router.PageTitle;
import com.vaadin.flow.router.Route;
import com.vaadin.flow.server.auth.AnonymousAllowed;
@Route("password-recovery")
@PageTitle("PFS Intra")
@AnonymousAllowed
public class PasswordRecoveryView extends VerticalLayout implements BeforeEnterObserver {
public PasswordRecoveryView(final AccountService accountService) {
setSizeFull();
setAlignItems(Alignment.CENTER);
setJustifyContentMode(JustifyContentMode.CENTER);
final VerticalLayout vl = new VerticalLayout();
vl.setJustifyContentMode(JustifyContentMode.CENTER);
vl.setWidth("400px");
final EmailField personalEmail = new EmailField("Personal Email");
personalEmail.setRequired(true);
final EmailField confirmPersonalEmail = new EmailField("Confirm Personal Email");
confirmPersonalEmail.setRequired(true);
final FormLayout formLayout = new FormLayout(personalEmail, confirmPersonalEmail);
formLayout.setColspan(personalEmail, 3);
formLayout.setColspan(confirmPersonalEmail, 3);
final Button primaryButton = new Button("Submit");
primaryButton.addThemeVariants(ButtonVariant.LUMO_PRIMARY);
primaryButton.addClickListener((ComponentEventListener<ClickEvent<Button>>) buttonClickEvent -> {
if (personalEmail.getValue().equals(confirmPersonalEmail.getValue())) {
accountService.sendResetPasswordEmail(personalEmail.getValue());
getUI().ifPresent(ui -> ui.navigate(MainView.class));
}
});
final Button secondaryButton = new Button("Cancel");
final HorizontalLayout hl = new HorizontalLayout(secondaryButton, primaryButton);
secondaryButton.addClickListener((ComponentEventListener<ClickEvent<Button>>) buttonClickEvent ->
getUI().ifPresent(ui -> ui.navigate(MainView.class)));
vl.add(new H3("PFS - Password Recovery"));
vl.add(formLayout);
vl.add(hl);
add(vl);
}
@Override
public void beforeEnter(final BeforeEnterEvent beforeEnterEvent) {
}
}

View File

@ -0,0 +1,230 @@
package com.primefactorsolutions.views;
import com.primefactorsolutions.model.*;
import com.primefactorsolutions.service.EmployeeService;
import com.primefactorsolutions.service.TeamService;
import com.primefactorsolutions.service.TimeOffRequestService;
import com.vaadin.flow.component.button.Button;
import com.vaadin.flow.component.combobox.ComboBox;
import com.vaadin.flow.component.notification.Notification;
import com.vaadin.flow.component.orderedlayout.HorizontalLayout;
import com.vaadin.flow.router.PageTitle;
import com.vaadin.flow.router.Route;
import com.vaadin.flow.spring.annotation.SpringComponent;
import jakarta.annotation.security.PermitAll;
import org.springframework.context.annotation.Scope;
import org.vaadin.firitin.components.grid.PagingGrid;
import java.util.*;
import java.util.stream.Collectors;
import static com.primefactorsolutions.views.Constants.PAGE_SIZE;
@SpringComponent
@Scope("prototype")
@PageTitle("PendingRequests")
@Route(value = "/pending-requests", layout = MainLayout.class)
@PermitAll
public class PendingRequestsListView extends BaseView {
private final TimeOffRequestService requestService;
private final EmployeeService employeeService;
private final TeamService teamService;
private final PagingGrid<TimeOffRequest> pendingRequestsGrid = new PagingGrid<>();
private ComboBox<Employee> employeeFilter;
private ComboBox<Team> teamFilter;
private ComboBox<TimeOffRequestType> categoryFilter;
private UUID selectedRequestId;
public PendingRequestsListView(final TimeOffRequestService requestService,
final EmployeeService employeeService,
final TeamService teamService) {
this.requestService = requestService;
this.employeeService = employeeService;
this.teamService = teamService;
initializeView();
refreshGeneralPendingRequestsGrid(null, null, null);
}
private void initializeView() {
setupFilters();
setupPendingRequestsGrid();
createActionButtons();
}
private void setupFilters() {
final HorizontalLayout hl = new HorizontalLayout();
hl.add(createEmployeeFilter());
hl.add(createTeamFilter());
hl.add(createCategoryFilter());
getCurrentPageLayout().add(hl);
}
private void setupPendingRequestsGrid() {
pendingRequestsGrid.addColumn(this::getEmployeeFullName).setHeader("Empleado");
pendingRequestsGrid.addColumn(this::getTeamName).setHeader("Equipo");
pendingRequestsGrid.addColumn(this::getCategory).setHeader("Categoría");
pendingRequestsGrid.setPaginationBarMode(PagingGrid.PaginationBarMode.BOTTOM);
pendingRequestsGrid.setPageSize(PAGE_SIZE);
pendingRequestsGrid.asSingleSelect().addValueChangeListener(event -> {
TimeOffRequest selectedRequest = event.getValue();
if (selectedRequest != null) {
selectedRequestId = selectedRequest.getId();
}
});
getCurrentPageLayout().add(pendingRequestsGrid);
}
private void createActionButtons() {
final Button approveButton = createActionButton("Aprobar", TimeOffRequestStatus.APROBADO);
final Button rejectButton = createActionButton("Rechazar", TimeOffRequestStatus.RECHAZADO);
final Button closeButton = new Button("Salir", event -> navigateToMainView());
getCurrentPageLayout().add(new HorizontalLayout(approveButton, rejectButton, closeButton));
}
private Button createActionButton(final String caption, final TimeOffRequestStatus status) {
return new Button(caption, event -> {
if (selectedRequestId != null) {
TimeOffRequest request = requestService.findTimeOffRequest(selectedRequestId);
request.setState(status);
requestService.saveTimeOffRequest(request);
refreshGeneralPendingRequestsGrid(null, null, null);
} else {
Notification.show("Seleccione una solicitud.", 3000, Notification.Position.MIDDLE);
}
});
}
private void refreshGeneralPendingRequestsGrid(final Employee employee,
final Team team,
final TimeOffRequestType category) {
pendingRequestsGrid.setPagingDataProvider((page, pageSize) -> {
int start = (int) (page * pendingRequestsGrid.getPageSize());
return fetchFilteredPendingRequests(start, pageSize, employee, team, category);
});
pendingRequestsGrid.getDataProvider().refreshAll();
}
private List<TimeOffRequest> fetchFilteredPendingRequests(final int start,
final int pageSize,
final Employee employee,
final Team team,
final TimeOffRequestType category) {
List<TimeOffRequest> filteredPendingRequests
= requestService.findRequestsByState(TimeOffRequestStatus.SOLICITADO);
if (employee != null && !"TODOS".equals(employee.getFirstName())) {
filteredPendingRequests = filteredPendingRequests.stream()
.filter(emp -> emp.getEmployee().getId().equals(employee.getId()))
.collect(Collectors.toList());
}
if (team != null && !"TODOS".equals(team.getName())) {
filteredPendingRequests = filteredPendingRequests.stream()
.filter(emp -> emp.getEmployee().getTeam() != null
&& emp.getEmployee().getTeam().getId().equals(team.getId()))
.collect(Collectors.toList());
}
if (category != null && category != TimeOffRequestType.TODOS) {
filteredPendingRequests = filteredPendingRequests.stream()
.filter(emp -> emp.getCategory().equals(category))
.collect(Collectors.toList());
}
int end = Math.min(start + pageSize, filteredPendingRequests.size());
return filteredPendingRequests.subList(start, end);
}
private String getEmployeeFullName(final TimeOffRequest request) {
Employee employee = request.getEmployee();
return getEmployeeFullNameLabel(employee);
}
private String getEmployeeFullNameLabel(final Employee employee) {
return "TODOS".equals(employee.getFirstName())
? "TODOS" : employee.getFirstName() + " " + employee.getLastName();
}
private String getTeamName(final TimeOffRequest request) {
Team team = request.getEmployee().getTeam();
return team != null ? team.getName() : "Sin asignar";
}
private String getTeamLabel(final Team team) {
return "TODOS".equals(team.getName()) ? "TODOS" : team.getName();
}
private String getCategory(final TimeOffRequest request) {
return String.valueOf(request.getCategory());
}
private ComboBox<Employee> createEmployeeFilter() {
employeeFilter = new ComboBox<>("Empleado");
List<Employee> employees = new ArrayList<>(employeeService.findAllEmployees());
employees.addFirst(createAllEmployeesOption());
employeeFilter.setItems(employees);
employeeFilter.setItemLabelGenerator(this::getEmployeeFullNameLabel);
employeeFilter.setValue(employees.getFirst());
employeeFilter.addValueChangeListener(event ->
refreshGeneralPendingRequestsGrid(
event.getValue(),
teamFilter.getValue(),
categoryFilter.getValue()
)
);
return employeeFilter;
}
private ComboBox<Team> createTeamFilter() {
teamFilter = new ComboBox<>("Equipo");
List<Team> teams = new ArrayList<>(teamService.findAllTeams());
teams.addFirst(createAllTeamsOption());
teamFilter.setItems(teams);
teamFilter.setItemLabelGenerator(this::getTeamLabel);
teamFilter.setValue(teams.getFirst());
teamFilter.addValueChangeListener(event ->
refreshGeneralPendingRequestsGrid(
employeeFilter.getValue(),
event.getValue(),
categoryFilter.getValue()
)
);
return teamFilter;
}
private ComboBox<TimeOffRequestType> createCategoryFilter() {
categoryFilter = new ComboBox<>("Categoría");
categoryFilter.setItems(TimeOffRequestType.values());
categoryFilter.setValue(TimeOffRequestType.values()[0]);
categoryFilter.addValueChangeListener(event ->
refreshGeneralPendingRequestsGrid(
employeeFilter.getValue(),
teamFilter.getValue(),
event.getValue()
)
);
return categoryFilter;
}
private Employee createAllEmployeesOption() {
Employee allEmployeesOption = new Employee();
allEmployeesOption.setFirstName("TODOS");
return allEmployeesOption;
}
private Team createAllTeamsOption() {
Team allTeamsOption = new Team();
allTeamsOption.setName("TODOS");
return allTeamsOption;
}
private void navigateToMainView() {
getUI().ifPresent(ui -> ui.navigate(MainView.class));
}
}

View File

@ -28,16 +28,13 @@ import java.util.stream.Stream;
@Route(value = "/questions", layout = MainLayout.class)
@PermitAll
public class QuestionsListView extends Main {
private final QuestionService questionService;
public QuestionsListView(final QuestionService questionService) {
this.questionService = questionService;
final HorizontalLayout hl = new HorizontalLayout();
final Button addQuestion = new Button("Add Question");
addQuestion.addClickListener((ComponentEventListener<ClickEvent<Button>>) buttonClickEvent -> {
this.getUI().get().navigate(QuestionView.class, "new");
});
addQuestion.addClickListener((ComponentEventListener<ClickEvent<Button>>) buttonClickEvent ->
this.getUI().flatMap(ui -> ui.navigate(QuestionView.class, "new")));
hl.add(addQuestion);
final VGrid<Question> grid = new VGrid<>(Question.class);
@ -45,7 +42,7 @@ public class QuestionsListView extends Main {
grid.addComponentColumn((ValueProvider<Question, Component>) question -> {
final Button edit = new Button("Edit");
edit.addClickListener((ComponentEventListener<ClickEvent<Button>>) buttonClickEvent ->
this.getUI().get().navigate(QuestionView.class, question.getId().toString()));
this.getUI().flatMap(ui -> ui.navigate(QuestionView.class, question.getId().toString())));
return edit;
});
grid.setDataProvider(new DataProvider<>() {
@ -59,6 +56,7 @@ public class QuestionsListView extends Main {
return questionService.getQuestions().size();
}
@SuppressWarnings("unused")
@Override
public Stream<Question> fetch(final Query<Question, Object> query) {
int limit = query.getLimit();

View File

@ -0,0 +1,234 @@
package com.primefactorsolutions.views;
import com.primefactorsolutions.model.HoursWorked;
import com.primefactorsolutions.model.Team;
import com.primefactorsolutions.service.HoursWorkedService;
import com.primefactorsolutions.service.ReportService;
import com.primefactorsolutions.service.TeamService;
import com.vaadin.flow.component.button.Button;
import com.vaadin.flow.component.combobox.ComboBox;
import com.vaadin.flow.component.grid.Grid;
import com.vaadin.flow.component.html.Anchor;
import com.vaadin.flow.component.html.H2;
import com.vaadin.flow.component.html.Span;
import com.vaadin.flow.component.notification.Notification;
import com.vaadin.flow.component.orderedlayout.HorizontalLayout;
import com.vaadin.flow.component.orderedlayout.VerticalLayout;
import com.vaadin.flow.router.PageTitle;
import com.vaadin.flow.router.Route;
import com.vaadin.flow.server.StreamResource;
import jakarta.annotation.security.PermitAll;
import org.springframework.beans.factory.annotation.Autowired;
import com.primefactorsolutions.service.EmployeeService;
import java.io.ByteArrayInputStream;
import java.time.DayOfWeek;
import java.time.LocalDate;
import java.time.format.TextStyle;
import java.time.temporal.WeekFields;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.stream.Collectors;
@PermitAll
@Route(value = "/reportes", layout = MainLayout.class)
@PageTitle("Reporte de Horas Trabajadas")
public class ReporteView extends VerticalLayout {
private final EmployeeService employeeService;
private final HoursWorkedService hoursWorkedService;
private final ReportService reportService;
private final TeamService teamService;
private final ComboBox<Team> equipoComboBox = new ComboBox<>("Seleccionar Equipo");
private final ComboBox<String> semanaComboBox = new ComboBox<>("Seleccionar Semana");
private final Grid<Map<String, Object>> grid = new Grid<>();
private final VerticalLayout headerLayout = new VerticalLayout();
private Anchor downloadLink;
private final Span semanaInfoSpan = new Span();
// Obtener el año actual
private int currentYear = LocalDate.now().getYear();
@Autowired
public ReporteView(final HoursWorkedService hoursWorkedService,
final ReportService reportService, final TeamService teamService,
final EmployeeService employeeService) {
this.hoursWorkedService = hoursWorkedService;
this.reportService = reportService;
this.teamService = teamService;
this.employeeService = employeeService;
H2 title = new H2("Reporte de Horas Trabajadas");
add(title);
List<Team> teams = teamService.findAllTeams();
equipoComboBox.setItems(teams);
equipoComboBox.setItemLabelGenerator(Team::getName);
// Configurar el ComboBox de semanas
initializeSemanaComboBox();
// Listener para actualizar `semanaInfoSpan` con la selección del usuario en `semanaComboBox`
semanaComboBox.addValueChangeListener(event -> {
String selectedWeek = event.getValue();
semanaInfoSpan.setText(selectedWeek != null ? selectedWeek : "Selecciona una semana");
});
Button reportButton = new Button("Generar Reporte de Horas Trabajadas",
event -> generateHoursWorkedReport());
HorizontalLayout filtersLayout = new HorizontalLayout(equipoComboBox, semanaComboBox, reportButton);
add(filtersLayout);
// Añadir `headerLayout` al diseño principal para el encabezado dinámico
add(headerLayout);
updateHeaderLayout(null, null);
grid.addColumn(map -> map.get("Empleado")).setHeader("Empleado");
grid.addColumn(map -> map.get("Horas Trabajadas")).setHeader("Horas Trabajadas");
grid.addColumn(map -> map.get("Horas Pendientes")).setHeader("Horas Pendientes");
grid.addColumn(map -> map.get("Observaciones")).setHeader("Observaciones");
add(grid);
}
private void initializeSemanaComboBox() {
int year = LocalDate.now().getYear();
LocalDate startOfYear = LocalDate.of(year, 1, 5); // Suponemos que la semana comienza el 5 de enero.
List<String> semanas = startOfYear.datesUntil(LocalDate.of(year + 1, 1, 1),
java.time.Period.ofWeeks(1))
.map(date -> {
int weekNumber = date.get(WeekFields.of(DayOfWeek.MONDAY, 1)
.weekOfWeekBasedYear());
LocalDate startOfWeek = date;
LocalDate endOfWeek = startOfWeek.plusDays(6);
return String.format("Semana %d: %s - %s",
weekNumber,
startOfWeek.getDayOfMonth() + " de " + startOfWeek.getMonth()
.getDisplayName(TextStyle.FULL, Locale.getDefault()),
endOfWeek.getDayOfMonth() + " de " + endOfWeek.getMonth()
.getDisplayName(TextStyle.FULL, Locale.getDefault())
);
})
.collect(Collectors.toList());
semanaComboBox.setItems(semanas);
semanaComboBox.setPlaceholder("Seleccione una semana");
}
private void generateHoursWorkedReport() {
Team selectedEquipo = equipoComboBox.getValue();
String selectedWeek = semanaComboBox.getValue();
if (selectedEquipo == null || selectedWeek == null) {
Notification.show("Por favor, selecciona un equipo y una semana para generar el reporte.",
3000, Notification.Position.MIDDLE);
return;
}
int weekNumber = Integer.parseInt(selectedWeek.split(" ")[1].replace(":", ""));
LocalDate selectedDate = LocalDate.now().with(WeekFields.of(DayOfWeek.FRIDAY, 1)
.weekOfWeekBasedYear(), weekNumber);
updateHeaderLayout(selectedEquipo, selectedDate);
List<HoursWorked> hoursWorkedList = hoursWorkedService.findAll().stream()
.filter(hw -> hw.getEmployee().getTeam().getId().equals(selectedEquipo
.getId()) && hw.getWeekNumber() == weekNumber)
.collect(Collectors.toList());
System.out.println(hoursWorkedList);
if (hoursWorkedList.isEmpty()) {
Notification.show("No hay horas trabajadas disponibles para generar el reporte.",
3000, Notification.Position.MIDDLE);
return;
}
List<Map<String, Object>> data = hoursWorkedList.stream()
.map(hoursWorked -> {
Map<String, Object> map = new HashMap<>();
map.put("ID", hoursWorked.getId().toString());
map.put("Employee ID", hoursWorked.getEmployee().getId().toString());
map.put("Empleado", hoursWorked.getEmployee().getFirstName() + " "
+ hoursWorked.getEmployee().getLastName());
map.put("Horas Trabajadas", hoursWorked.getTotalHours());
map.put("Horas Pendientes", 40 - hoursWorked.getTotalHours());
map.put("Observaciones", "");
return map;
})
.collect(Collectors.toList());
grid.setItems(data);
generateExcelDownloadLink(data, weekNumber);
}
private void updateHeaderLayout(final Team team, final LocalDate dateInWeek) {
headerLayout.removeAll();
if (team != null && dateInWeek != null) {
LocalDate startOfWeek = dateInWeek.with(DayOfWeek.FRIDAY);
LocalDate endOfWeek = dateInWeek.with(DayOfWeek.THURSDAY);
int weekNumber = getWeekOfYear(dateInWeek);
String formattedStartDate = startOfWeek.getDayOfMonth() + " de "
+ startOfWeek.getMonth().getDisplayName(TextStyle.FULL, Locale.getDefault());
String formattedEndDate = endOfWeek.getDayOfMonth() + " de "
+ endOfWeek.getMonth().getDisplayName(TextStyle.FULL, Locale.getDefault());
headerLayout.add(new Span("Informe "
+ String.format("%03d", weekNumber) + "/" + currentYear) {{
getStyle().set("font-size", "24px");
getStyle().set("font-weight", "bold");
}});
String teamLeadName = employeeService.getTeamLeadName(team.getId());
headerLayout.add(
new Span("Asunto: Informe Semanal de Horas Trabajadas") {{
getStyle().set("font-size", "18px");
}},
semanaInfoSpan,
new Span("Horas a cumplir: 40 horas") {{
getStyle().set("font-size", "18px");
}},
new Span("Equipo: " + team.getName()) {{
getStyle().set("font-size", "18px");
}},
new Span("Team Lead: " + teamLeadName) {{
getStyle().set("font-size", "18px");
}}
);
}
}
private void generateExcelDownloadLink(final List<Map<String, Object>> data, final int weekNumber) {
try {
List<String> headers = List.of("Empleado",
"Horas Trabajadas", "Horas Pendientes", "Observaciones");
String selectedTeam = equipoComboBox.getValue().getName();
byte[] excelBytes = reportService.writeAsExcel(
"hours_worked_report", headers, data, selectedTeam, weekNumber, currentYear);
StreamResource excelResource = new StreamResource("hours_worked_report.xlsx",
() -> new ByteArrayInputStream(excelBytes));
if (downloadLink == null) {
downloadLink = new Anchor(excelResource, "Descargar Reporte en Excel");
downloadLink.getElement().setAttribute("download", true);
add(downloadLink);
} else {
downloadLink.setHref(excelResource);
}
} catch (Exception e) {
Notification.show("Error al generar el reporte de horas trabajadas en Excel.",
3000, Notification.Position.MIDDLE);
}
}
private int getWeekOfYear(final LocalDate date) {
return date.get(WeekFields.of(Locale.getDefault()).weekOfWeekBasedYear());
}
}

View File

@ -0,0 +1,423 @@
package com.primefactorsolutions.views;
import com.primefactorsolutions.model.*;
import com.primefactorsolutions.service.EmployeeService;
import com.primefactorsolutions.service.TimeOffRequestService;
import com.primefactorsolutions.service.VacationService;
import com.vaadin.flow.component.button.Button;
import com.vaadin.flow.component.combobox.ComboBox;
import com.vaadin.flow.component.html.Div;
import com.vaadin.flow.component.html.H3;
import com.vaadin.flow.component.html.Span;
import com.vaadin.flow.component.notification.Notification;
import com.vaadin.flow.component.orderedlayout.HorizontalLayout;
import com.vaadin.flow.component.orderedlayout.VerticalLayout;
import com.vaadin.flow.router.BeforeEvent;
import com.vaadin.flow.router.HasUrlParameter;
import com.vaadin.flow.router.PageTitle;
import com.vaadin.flow.router.Route;
import com.vaadin.flow.spring.annotation.SpringComponent;
import jakarta.annotation.security.PermitAll;
import org.springframework.context.annotation.Scope;
import org.vaadin.firitin.components.grid.PagingGrid;
import java.time.LocalDate;
import java.time.Period;
import java.util.*;
import java.util.stream.Collectors;
import static com.primefactorsolutions.views.Constants.PAGE_SIZE;
@SpringComponent
@PermitAll
@Scope("prototype")
@PageTitle("RequestEmployee")
@Route(value = "/requests", layout = MainLayout.class)
public class RequestEmployeeView extends Div implements HasUrlParameter<String> {
private final TimeOffRequestService requestService;
private final EmployeeService employeeService;
private final VacationService vacationService;
private final PagingGrid<TimeOffRequest> requestGrid = new PagingGrid<>(TimeOffRequest.class);
private List<TimeOffRequest> requests = Collections.emptyList();
private ComboBox<TimeOffRequestType> categoryFilter;
private ComboBox<TimeOffRequestStatus> stateFilter;
private UUID employeeId;
private TimeOffRequest request;
public RequestEmployeeView(final TimeOffRequestService requestService,
final EmployeeService employeeService,
final VacationService vacationService) {
this.requestService = requestService;
this.employeeService = employeeService;
this.vacationService = vacationService;
}
private void initializeView() {
requestService.updateRequestStatuses();
setupFilters();
setupGrid();
add(requestGrid, createActionButtons(), createSummaryLayout());
refreshRequestGrid(null, null);
}
private void setupFilters() {
categoryFilter = createCategoryFilter();
stateFilter = createStateFilter();
add(categoryFilter, stateFilter);
}
private ComboBox<TimeOffRequestType> createCategoryFilter() {
categoryFilter = new ComboBox<>("Categoría");
categoryFilter.setItems(TimeOffRequestType.values());
categoryFilter.setValue(TimeOffRequestType.values()[0]);
categoryFilter.addValueChangeListener(event -> refreshRequestGrid(event.getValue(), stateFilter.getValue()));
return categoryFilter;
}
private ComboBox<TimeOffRequestStatus> createStateFilter() {
stateFilter = new ComboBox<>("Estado de la solicitud");
stateFilter.setItems(TimeOffRequestStatus.values());
stateFilter.setValue(TimeOffRequestStatus.values()[0]);
stateFilter.addValueChangeListener(event -> refreshRequestGrid(categoryFilter.getValue(), event.getValue()));
return stateFilter;
}
private void setupGrid() {
requestGrid.setColumns(
"category",
"state",
"startDate",
"endDate",
"daysToBeTake");
requestGrid.getColumnByKey("category").setHeader("Categoría");
requestGrid.getColumnByKey("state").setHeader("Estado");
requestGrid.getColumnByKey("startDate").setHeader("Fecha de Inicio");
requestGrid.getColumnByKey("endDate").setHeader("Fecha de Fin");
requestGrid.getColumnByKey("daysToBeTake").setHeader("Días a Tomar");
requestGrid.setPaginationBarMode(PagingGrid.PaginationBarMode.BOTTOM);
requestGrid.setPageSize(PAGE_SIZE);
requestGrid.asSingleSelect().addValueChangeListener(event -> {
TimeOffRequest selectedRequest = event.getValue();
if (selectedRequest != null) {
request = selectedRequest;
}
});
}
private Set<TimeOffRequestType> getStandardExclusions() {
return Set.of(
TimeOffRequestType.MATERNIDAD,
TimeOffRequestType.PATERNIDAD,
TimeOffRequestType.MATRIMONIO,
TimeOffRequestType.DUELO_1ER_GRADO,
TimeOffRequestType.DUELO_2ER_GRADO,
TimeOffRequestType.DIA_DEL_PADRE,
TimeOffRequestType.DIA_DE_LA_MADRE
);
}
private Set<TimeOffRequestType> getMaleSpecificExclusions() {
return Set.of(
TimeOffRequestType.DIA_DE_LA_MUJER_INTERNACIONAL,
TimeOffRequestType.DIA_DE_LA_MUJER_NACIONAL
);
}
private VerticalLayout createSummaryLayout() {
Employee employee = employeeService.getEmployee(employeeId);
boolean isMale = employee.getGender() == Employee.Gender.MALE;
int currentYear = LocalDate.now().getYear();
List<Vacation> vacations = vacationService.findVacations();
double healthLicence = 2;
List<TimeOffRequest> healthRequests = requestService
.findByEmployeeAndCategory(employeeId, TimeOffRequestType.PERMISOS_DE_SALUD);
if (healthRequests != null && !healthRequests.isEmpty()) {
healthLicence = healthRequests.getLast().getDaysBalance();
}
double totalFixedAndMovableHolidays = calculateHolidayDays(vacations);
double totalPersonalDays = calculatePersonalDays(vacations, isMale);
List<Double> vacationDays = calculateVacationDays(employee);
double utilizedVacationCurrentDays = vacationDays.get(1);
List<TimeOffRequest> vacationCurrentRequests = requestService
.findByEmployeeAndCategory(employeeId, TimeOffRequestType.VACACION_GESTION_ACTUAL);
if (vacationCurrentRequests != null && !vacationCurrentRequests.isEmpty()) {
utilizedVacationCurrentDays = vacationCurrentRequests.getLast().getDaysBalance();
}
double totalVacationCurrentDays = vacationDays.get(1) - (vacationDays.get(1) - utilizedVacationCurrentDays);
double utilizedVacationPreviousDays = vacationDays.get(0);
List<TimeOffRequest> vacationPreviousRequests = requestService
.findByEmployeeAndCategory(employeeId, TimeOffRequestType.VACACION_GESTION_ANTERIOR);
if (vacationPreviousRequests != null && !vacationPreviousRequests.isEmpty()) {
utilizedVacationPreviousDays = vacationPreviousRequests.getLast().getDaysBalance();
}
double totalVacationPreviousDays = vacationDays.getFirst()
- (vacationDays.getFirst() - utilizedVacationPreviousDays);
double utilizedFixedAndMovableHolidays = calculateHolidayUtilizedDays(currentYear);
double utilizedPersonalDays = calculatePersonalDaysUtilized(isMale, currentYear);
double remainingHolidayDays = totalFixedAndMovableHolidays - utilizedFixedAndMovableHolidays;
double remainingPersonalDays = (totalPersonalDays - utilizedPersonalDays) + healthLicence;
double remainingVacationDays = totalVacationCurrentDays + totalVacationPreviousDays;
double totalAvailableDays = remainingHolidayDays + remainingPersonalDays + remainingVacationDays;
return new VerticalLayout(
new Span("Total feriados fijos y movibles: " + remainingHolidayDays),
new Span("Total días libres personales: " + remainingPersonalDays),
new Span("Total vacaciones pendientes de uso: " + remainingVacationDays),
new Span("TOTAL GENERAL DE DÍAS DISPONIBLES: " + totalAvailableDays)
);
}
private double calculateHolidayDays(final List<Vacation> vacations) {
return vacations.stream()
.filter(req -> req.getType() != Vacation.Type.OTHER)
.mapToDouble(Vacation::getDuration)
.sum();
}
private double calculatePersonalDays(final List<Vacation> vacations, final boolean isMale) {
return vacations.stream()
.filter(req -> req.getType() == Vacation.Type.OTHER)
.filter(req -> !getStandardExclusions().contains(req.getCategory()))
.filter(req -> !(isMale && getMaleSpecificExclusions().contains(req.getCategory())))
.filter(req -> !req.getCategory().name().startsWith("VACACION"))
.filter(req -> req.getCategory() != TimeOffRequestType.PERMISOS_DE_SALUD)
.mapToDouble(Vacation::getDuration)
.sum();
}
private List<Double> calculateVacationDays(final Employee employee) {
List<Double> vacationDays = new ArrayList<>();
if (employee.getDateOfEntry() != null) {
LocalDate entryDate = employee.getDateOfEntry();
LocalDate today = LocalDate.now();
boolean hasAnniversaryPassed = entryDate.getMonthValue() < today.getMonthValue()
|| (entryDate.getMonthValue() == today.getMonthValue() && entryDate.getDayOfMonth()
<= today.getDayOfMonth());
LocalDate previousVacationYearDate;
LocalDate currentVacationYearDate;
if (hasAnniversaryPassed) {
previousVacationYearDate = LocalDate.of(
today.getYear() - 1,
entryDate.getMonth(),
entryDate.getDayOfMonth()
);
currentVacationYearDate = LocalDate.of(
today.getYear(),
entryDate.getMonth(),
entryDate.getDayOfMonth()
);
} else {
previousVacationYearDate = LocalDate.of(
today.getYear() - 2,
entryDate.getMonth(),
entryDate.getDayOfMonth()
);
currentVacationYearDate = LocalDate.of(
today.getYear() - 1,
entryDate.getMonth(),
entryDate.getDayOfMonth()
);
}
vacationDays.add(calculateVacationDaysSinceEntry(entryDate, previousVacationYearDate));
vacationDays.add(calculateVacationDaysSinceEntry(entryDate, currentVacationYearDate));
} else {
vacationDays.add(0.0);
vacationDays.add(0.0);
}
return vacationDays;
}
private double calculateHolidayUtilizedDays(final int year) {
return requests.stream()
.filter(this::verificationIsHoliday)
.filter(req -> req.getState() == TimeOffRequestStatus.TOMADO)
.filter(req -> getStartDateYear(req) == year)
.mapToDouble(TimeOffRequest::getDaysToBeTake)
.sum();
}
private double calculatePersonalDaysUtilized(final boolean isMale, final int year) {
return requests.stream()
.filter(req -> !verificationIsHoliday(req))
.filter(req -> req.getState() == TimeOffRequestStatus.TOMADO)
.filter(req -> !getStandardExclusions().contains(req.getCategory()))
.filter(req -> !(isMale && getMaleSpecificExclusions().contains(req.getCategory())))
.filter(req -> !req.getCategory().name().startsWith("VACACION"))
.filter(req -> req.getCategory() != TimeOffRequestType.PERMISOS_DE_SALUD)
.filter(req -> getStartDateYear(req) == year)
.mapToDouble(TimeOffRequest::getDaysToBeTake)
.sum();
}
private int getStartDateYear(final TimeOffRequest request) {
if (request.getStartDate() != null) {
return request.getStartDate().getYear();
}
return 0;
}
private double calculateVacationDaysSinceEntry(final LocalDate dateOfEntry, final LocalDate date) {
int yearsOfService = dateOfEntry != null ? Period.between(dateOfEntry, date).getYears() : 0;
if (yearsOfService > 10) {
return 30;
}
if (yearsOfService > 5) {
return 20;
}
if (yearsOfService > 1) {
return 15;
}
return 0;
}
private Boolean verificationIsHoliday(final TimeOffRequest request) {
Vacation vacation = vacationService.findVacationByCategory(request.getCategory());
return vacation.getType() != Vacation.Type.OTHER;
}
private HorizontalLayout createActionButtons() {
Button viewButton = createButton("Ver", () -> navigateToViewRequest(request));
Button editButton = createButton("Editar", () -> navigateToEditRequest(request));
Button closeButton = new Button("Salir", event -> navigateToRequestsListView());
return new HorizontalLayout(viewButton, editButton, closeButton);
}
private Button createButton(final String caption, final Runnable action) {
return new Button(caption, event -> {
if (request != null) {
action.run();
} else {
Notification.show("Seleccione una solicitud.", 3000, Notification.Position.MIDDLE);
}
});
}
private void navigateToRequestsListView() {
getUI().ifPresent(ui -> ui.navigate(RequestsListView.class));
}
private void navigateToEditRequest(final TimeOffRequest request) {
navigateToRequestView(request, "edit");
}
private void navigateToViewRequest(final TimeOffRequest request) {
navigateToRequestView(request, "view");
}
private void navigateToRequestView(final TimeOffRequest request, final String action) {
getUI().ifPresent(ui -> ui.navigate(RequestView.class, request.getId().toString() + "/" + action));
}
private void refreshRequestGrid(final TimeOffRequestType category, final TimeOffRequestStatus state) {
requestGrid.setPagingDataProvider((page, pageSize) -> {
int start = (int) (page * requestGrid.getPageSize());
return fetchFilteredTimeOffRequests(start, pageSize, category, state);
});
requestGrid.getDataProvider().refreshAll();
}
private List<TimeOffRequest> fetchFilteredTimeOffRequests(final int start,
final int pageSize,
final TimeOffRequestType category,
final TimeOffRequestStatus state) {
requests = requestService.findRequestsByEmployeeId(employeeId);
generateRequests();
if (category != null && !"TODOS".equals(category.name())) {
requests = requests.stream()
.filter(req -> req.getCategory().equals(category))
.collect(Collectors.toList());
}
if (state != null && !"TODOS".equals(state.name())) {
requests = requests.stream()
.filter(req -> req.getState().equals(state))
.collect(Collectors.toList());
}
int end = Math.min(start + pageSize, requests.size());
return requests.subList(start, end);
}
public void generateRequests() {
boolean isMale = isEmployeeMale();
for (TimeOffRequestType type : TimeOffRequestType.values()) {
if (shouldIncludeRequest(type) && isValidRequestType(type, isMale)) {
TimeOffRequest request = createRequest(type);
if (isVacationExpired(request)) {
request.setState(TimeOffRequestStatus.VENCIDO);
} else {
request.setState(TimeOffRequestStatus.PENDIENTE);
}
requests.add(request);
}
}
}
private boolean isEmployeeMale() {
return employeeService.getEmployee(employeeId).getGender() == Employee.Gender.MALE;
}
private boolean isValidRequestType(final TimeOffRequestType type, final boolean isMale) {
return !getStandardExclusions().contains(type)
&& !(isMale && getMaleSpecificExclusions().contains(type))
&& type != TimeOffRequestType.TODOS;
}
private TimeOffRequest createRequest(final TimeOffRequestType type) {
TimeOffRequest request = new TimeOffRequest();
request.setCategory(type);
return request;
}
private boolean isVacationExpired(final TimeOffRequest request) {
Vacation vacation = vacationService.findVacationByCategory(request.getCategory());
if (vacation != null && vacation.getMonthOfYear() != null && vacation.getDayOfMonth() != null) {
int vacationMonth = vacation.getMonthOfYear();
int vacationDay = vacation.getDayOfMonth();
int currentMonth = LocalDate.now().getMonthValue();
int currentDay = LocalDate.now().getDayOfMonth();
return vacationMonth < currentMonth || (vacationMonth == currentMonth && vacationDay < currentDay);
}
return false;
}
private boolean shouldIncludeRequest(final TimeOffRequestType type) {
List<TimeOffRequest> existingRequest = requestService.findByEmployeeAndCategory(employeeId, type);
return existingRequest.isEmpty();
}
@Override
public void setParameter(final BeforeEvent event, final String parameter) {
employeeId = UUID.fromString(parameter);
Employee employee = employeeService.getEmployee(employeeId);
requests = requestService.findRequestsByEmployeeId(employeeId);
setViewTitle(employee.getFirstName() + " " + employee.getLastName(), employee.getTeam().getName());
requestGrid.setItems(requests);
initializeView();
}
private void setViewTitle(final String employeeName, final String employeeTeam) {
addComponentAsFirst(new H3("Nombre del empleado: " + employeeName));
addComponentAtIndex(1, new H3("Equipo: " + employeeTeam));
}
}

View File

@ -0,0 +1,511 @@
package com.primefactorsolutions.views;
import com.primefactorsolutions.model.*;
import com.primefactorsolutions.service.EmployeeService;
import com.primefactorsolutions.service.TimeOffRequestService;
import com.primefactorsolutions.service.VacationService;
import com.vaadin.flow.component.button.Button;
import com.vaadin.flow.component.combobox.ComboBox;
import com.vaadin.flow.component.datepicker.DatePicker;
import com.vaadin.flow.component.html.H3;
import com.vaadin.flow.component.notification.Notification;
import com.vaadin.flow.component.orderedlayout.HorizontalLayout;
import com.vaadin.flow.component.orderedlayout.VerticalLayout;
import com.vaadin.flow.component.textfield.NumberField;
import com.vaadin.flow.data.binder.Binder;
import com.vaadin.flow.router.*;
import com.vaadin.flow.spring.annotation.SpringComponent;
import jakarta.annotation.security.PermitAll;
import org.springframework.context.annotation.Scope;
import java.time.LocalDate;
import java.time.temporal.ChronoUnit;
import java.util.Arrays;
import java.util.Comparator;
import java.util.List;
import java.util.UUID;
@SpringComponent
@PermitAll
@Scope("prototype")
@PageTitle("Request")
@Route(value = "/requests/new", layout = MainLayout.class)
public class RequestRegisterView extends VerticalLayout {
private final ComboBox<Employee> employeeComboBox = new ComboBox<>("Empleado");
private final ComboBox<TimeOffRequestType> categoryComboBox = new ComboBox<>("Categoría");
private final NumberField availableDaysField = new NumberField("Días disponibles");
private final DatePicker startDatePicker = new DatePicker("Fecha de inicio");
private final DatePicker endDatePicker = new DatePicker("Fecha final");
private final NumberField daysToBeTakenField = new NumberField("Días a tomar");
private final NumberField balanceDaysField = new NumberField("Días de saldo");
private final TimeOffRequestService requestService;
private final EmployeeService employeeService;
private final VacationService vacationService;
private final Binder<TimeOffRequest> binder;
private Vacation vacation;
private Employee employee;
private LocalDate endDate;
private Button saveButton;
private Button closeButton;
public RequestRegisterView(final TimeOffRequestService requestService,
final EmployeeService employeeService,
final VacationService vacationService) {
this.requestService = requestService;
this.employeeService = employeeService;
this.vacationService = vacationService;
this.binder = new Binder<>(TimeOffRequest.class);
initializeView();
}
private void initializeView() {
requestService.updateRequestStatuses();
configureFormFields();
configureButtons();
configureBinder();
setupFormLayout();
configureInitialFieldStates();
}
private void configureInitialFieldStates() {
categoryComboBox.setEnabled(false);
startDatePicker.setEnabled(false);
endDatePicker.setEnabled(false);
availableDaysField.setReadOnly(true);
daysToBeTakenField.setReadOnly(true);
balanceDaysField.setReadOnly(true);
}
private void configureFormFields() {
employeeComboBox.setItems(employeeService.findAllEmployees());
employeeComboBox.setItemLabelGenerator(emp -> emp.getFirstName() + " " + emp.getLastName());
employeeComboBox.addValueChangeListener(event -> {
employee = event.getValue();
handleEmployeeSelection(event.getValue());
});
categoryComboBox.addValueChangeListener(event -> {
onCategoryChange(event.getValue());
handleCategorySelection(event.getValue());
});
startDatePicker.addValueChangeListener(event -> updateDatePickerMinValues());
endDatePicker.addValueChangeListener(event -> calculateDays());
}
private void configureBinder() {
binder.forField(employeeComboBox)
.bind(TimeOffRequest::getEmployee, TimeOffRequest::setEmployee);
binder.forField(categoryComboBox)
.bind(TimeOffRequest::getCategory, TimeOffRequest::setCategory);
binder.forField(availableDaysField)
.bind(TimeOffRequest::getAvailableDays, TimeOffRequest::setAvailableDays);
binder.forField(startDatePicker)
.bind(TimeOffRequest::getStartDate, TimeOffRequest::setStartDate);
binder.forField(endDatePicker)
.bind(TimeOffRequest::getEndDate, TimeOffRequest::setEndDate);
binder.forField(daysToBeTakenField)
.bind(TimeOffRequest::getDaysToBeTake, TimeOffRequest::setDaysToBeTake);
binder.forField(balanceDaysField)
.bind(TimeOffRequest::getDaysBalance, TimeOffRequest::setDaysBalance);
binder.setBean(new TimeOffRequest());
}
private void handleEmployeeSelection(final Employee selectedEmployee) {
if (selectedEmployee != null) {
categoryComboBox.clear();
availableDaysField.clear();
startDatePicker.clear();
endDatePicker.clear();
daysToBeTakenField.clear();
balanceDaysField.clear();
categoryComboBox.setEnabled(true);
startDatePicker.setEnabled(false);
endDatePicker.setEnabled(false);
filterCategories(selectedEmployee);
}
}
private void filterCategories(final Employee employee) {
categoryComboBox.clear();
List<TimeOffRequest> employeeRequests = requestService.findRequestsByEmployeeId(employee.getId());
List<TimeOffRequestType> allCategories = Arrays.asList(TimeOffRequestType.values());
List<TimeOffRequestType> availableCategories = allCategories.stream()
.filter(category -> isCategoryAvailable(employeeRequests, category))
.filter(category -> isCategoryAllowedByGender(category, employee.getGender()))
.filter(category -> category != TimeOffRequestType.TODOS)
.filter(category -> shouldIncludeVacationGestionActual(employeeRequests, category))
.filter(category -> shouldIncludeVacationGestionAnterior(employeeRequests, category))
.toList();
categoryComboBox.setItems(availableCategories);
}
private boolean shouldIncludeVacationGestionActual(final List<TimeOffRequest> employeeRequests,
final TimeOffRequestType category) {
if (category != TimeOffRequestType.VACACION_GESTION_ACTUAL) {
return true;
}
return employeeRequests.stream()
.anyMatch(request -> request.getCategory() == TimeOffRequestType.VACACION_GESTION_ANTERIOR
&& request.getDaysBalance() == 0
&& request.getState() == TimeOffRequestStatus.TOMADO);
}
private boolean shouldIncludeVacationGestionAnterior(final List<TimeOffRequest> employeeRequests,
final TimeOffRequestType category) {
if (category != TimeOffRequestType.VACACION_GESTION_ANTERIOR) {
return true;
}
return employeeRequests.stream()
.noneMatch(request -> request.getCategory() == TimeOffRequestType.VACACION_GESTION_ANTERIOR
&& request.getDaysBalance() == 0);
}
private void onCategoryChange(final TimeOffRequestType selectedCategory) {
if (selectedCategory == TimeOffRequestType.VACACION_GESTION_ACTUAL
|| selectedCategory == TimeOffRequestType.VACACION_GESTION_ANTERIOR) {
startDatePicker.setEnabled(true);
endDatePicker.setEnabled(true);
} else {
startDatePicker.setEnabled(true);
endDatePicker.setEnabled(false);
}
}
private boolean isCategoryAvailable(final List<TimeOffRequest> employeeRequests,
final TimeOffRequestType category) {
List<TimeOffRequest> requestsByCategory = employeeRequests.stream()
.filter(request -> request.getCategory() == category)
.toList();
if (requestsByCategory.isEmpty()) {
return true;
}
TimeOffRequest latestRequest = requestsByCategory.stream()
.max(Comparator.comparing(TimeOffRequest::getStartDate))
.orElse(null);
boolean isSpecialCategory = category == TimeOffRequestType.PERMISOS_DE_SALUD
|| category == TimeOffRequestType.VACACION_GESTION_ACTUAL
|| category == TimeOffRequestType.VACACION_GESTION_ANTERIOR;
if (isSpecialCategory) {
return (latestRequest.getState() == TimeOffRequestStatus.TOMADO
&& latestRequest.getDaysBalance() > 0)
|| latestRequest.getState() == TimeOffRequestStatus.RECHAZADO
|| (latestRequest.getState() == TimeOffRequestStatus.TOMADO
&& latestRequest.getDaysBalance() == 0
&& latestRequest.getExpiration().isBefore(LocalDate.now()));
} else {
return (latestRequest.getState() == TimeOffRequestStatus.TOMADO
&& latestRequest.getExpiration().isBefore(LocalDate.now()))
|| latestRequest.getState() == TimeOffRequestStatus.RECHAZADO;
}
}
private boolean isCategoryAllowedByGender(final TimeOffRequestType category, final Employee.Gender gender) {
if (gender == Employee.Gender.MALE) {
return category != TimeOffRequestType.MATERNIDAD
&& category != TimeOffRequestType.DIA_DE_LA_MADRE
&& category != TimeOffRequestType.DIA_DE_LA_MUJER_INTERNACIONAL
&& category != TimeOffRequestType.DIA_DE_LA_MUJER_NACIONAL;
} else {
return category != TimeOffRequestType.DIA_DEL_PADRE
&& category != TimeOffRequestType.PATERNIDAD;
}
}
private void handleCategorySelection(final TimeOffRequestType selectedCategory) {
if (selectedCategory != null) {
updateAvailableDays(selectedCategory);
}
}
private void updateAvailableDays(final TimeOffRequestType selectedCategory) {
vacation = vacationService.findVacationByCategory(selectedCategory);
UUID employeeId = employeeComboBox.getValue().getId();
List<TimeOffRequest> requests = requestService.findByEmployeeAndCategory(employeeId, selectedCategory);
if (vacation != null) {
TimeOffRequest requestWithBalance = requests.stream()
.filter(request -> request.getDaysBalance() > 0
&& request.getState() != TimeOffRequestStatus.VENCIDO
&& request.getState() != TimeOffRequestStatus.RECHAZADO)
.max(Comparator.comparing(TimeOffRequest::getStartDate))
.orElse(null);
if (requestWithBalance != null) {
if (requestWithBalance.getState() == TimeOffRequestStatus.TOMADO
&& requestWithBalance.getDaysBalance() > 0) {
availableDaysField.setValue(requestWithBalance.getDaysBalance());
} else {
availableDaysField.setValue(vacation.getDuration());
}
} else if (selectedCategory == TimeOffRequestType.VACACION_GESTION_ACTUAL
|| selectedCategory == TimeOffRequestType.VACACION_GESTION_ANTERIOR) {
LocalDate dateOfEntry = employeeComboBox.getValue().getDateOfEntry();
LocalDate currentDate = LocalDate.now();
long yearsOfService = ChronoUnit.YEARS.between(dateOfEntry, currentDate);
if (selectedCategory == TimeOffRequestType.VACACION_GESTION_ANTERIOR) {
yearsOfService -= 1;
}
if (yearsOfService > 10) {
availableDaysField.setValue(30.0);
} else if (yearsOfService > 5) {
availableDaysField.setValue(20.0);
} else if (yearsOfService > 1) {
availableDaysField.setValue(15.0);
} else {
availableDaysField.setValue(0.0);
}
} else {
availableDaysField.setValue(vacation.getDuration());
}
setDatePickerLimits(vacation);
}
}
private void setDatePickerLimits(final Vacation vacation) {
LocalDate startDate;
endDate = null;
UUID employeeId = employee.getId();
List<TimeOffRequest> previousRequests
= requestService.findByEmployeeAndCategory(employeeId, vacation.getCategory());
int startYear = calculateStartYear(previousRequests);
startDate = determineStartDate(vacation, startYear);
if (startDate.isBefore(LocalDate.now())) {
startDate = determineStartDate(vacation, startYear + 1);
}
if (startDate != null) {
if (vacation.getExpiration() != null) {
endDate = startDate.plusDays(vacation.getExpiration().intValue() - 1);
} else {
endDate = LocalDate.of(startDate.getYear(), 12, 31);
}
} else {
startDate = LocalDate.now();
}
setPickerValues(vacation, startDate);
setPickerLimits(startDate, endDate);
}
private int calculateStartYear(final List<TimeOffRequest> previousRequests) {
if (previousRequests.isEmpty()) {
return LocalDate.now().getYear();
}
int lastRequestYear = previousRequests.stream()
.max(Comparator.comparing(TimeOffRequest::getStartDate))
.map(request -> request.getStartDate().getYear())
.orElse(LocalDate.now().getYear());
if (previousRequests.getLast().getState() != TimeOffRequestStatus.RECHAZADO) {
lastRequestYear = lastRequestYear + 1;
}
int currentYear = LocalDate.now().getYear();
return Math.max(lastRequestYear, currentYear);
}
private LocalDate determineStartDate(final Vacation vacation, final int startYear) {
if (vacation.getCategory() == TimeOffRequestType.CUMPLEAÑOS && employee.getBirthday() != null) {
return LocalDate.of(startYear, employee.getBirthday().getMonth(), employee.getBirthday().getDayOfMonth());
}
if (vacation.getMonthOfYear() != null && vacation.getDayOfMonth() != null) {
return LocalDate.of(startYear, vacation.getMonthOfYear().intValue(), vacation.getDayOfMonth().intValue());
}
if (vacation.getCategory() == TimeOffRequestType.PERMISOS_DE_SALUD) {
return LocalDate.now();
}
return LocalDate.now();
}
private void setPickerValues(final Vacation vacation, final LocalDate startDate) {
startDatePicker.setValue(startDate);
if ((vacation.getDuration() != null && vacation.getDuration() == 0.5)
|| vacation.getCategory() == TimeOffRequestType.PERMISOS_DE_SALUD
|| vacation.getCategory() == TimeOffRequestType.CUMPLEAÑOS) {
endDatePicker.setValue(startDate);
} else {
int durationDays = (vacation.getDuration() != null ? vacation.getDuration().intValue() - 1 : 0);
endDatePicker.setValue(startDate.plusDays(durationDays));
}
}
private void setPickerLimits(final LocalDate startDate, final LocalDate endDate) {
startDatePicker.setMin(startDate);
startDatePicker.setMax(endDate);
endDatePicker.setMin(startDate);
endDatePicker.setMax(endDate);
}
private void updateDatePickerMinValues() {
LocalDate startDate = startDatePicker.getValue();
if (availableDaysField.getValue() != null) {
if (availableDaysField.getValue() == 0.5) {
endDatePicker.setValue(startDate.plusDays(0));
} else {
endDatePicker.setValue(startDate.plusDays(availableDaysField.getValue().intValue() - 1));
}
calculateDays();
}
}
private void calculateDays() {
LocalDate startDate = startDatePicker.getValue();
LocalDate endDate = endDatePicker.getValue();
Double availableDays = availableDaysField.getValue();
if (areDatesValid(startDate, endDate)) {
double daysToBeTaken = calculateDaysBetween(startDate, endDate);
setDaysToBeTakenField(daysToBeTaken);
double balanceDays = calculateBalanceDays(availableDays, daysToBeTakenField.getValue());
balanceDaysField.setValue(balanceDays);
if (balanceDays < 0.0) {
clearFields();
}
}
}
private boolean areDatesValid(final LocalDate startDate, final LocalDate endDate) {
return startDate != null && endDate != null;
}
private double calculateDaysBetween(final LocalDate startDate, final LocalDate endDate) {
return java.time.temporal.ChronoUnit.DAYS.between(startDate, endDate) + 1;
}
private void setDaysToBeTakenField(final double daysToBeTaken) {
if (vacation.getCategory() == TimeOffRequestType.PERMISOS_DE_SALUD
|| vacation.getCategory() == TimeOffRequestType.CUMPLEAÑOS
|| vacation.getCategory() == TimeOffRequestType.DIA_DEL_PADRE
|| vacation.getCategory() == TimeOffRequestType.DIA_DE_LA_MADRE
|| vacation.getCategory() == TimeOffRequestType.DIA_DE_LA_MUJER_INTERNACIONAL
|| vacation.getCategory() == TimeOffRequestType.DIA_DE_LA_MUJER_NACIONAL) {
daysToBeTakenField.setValue(0.5);
} else {
daysToBeTakenField.setValue(daysToBeTaken);
}
}
private double calculateBalanceDays(final double availableDays, final double daysToBeTaken) {
return availableDays - daysToBeTaken;
}
private void clearFields() {
daysToBeTakenField.clear();
balanceDaysField.clear();
endDatePicker.clear();
}
private void configureButtons() {
saveButton = new Button("Guardar", event -> saveRequest());
closeButton = new Button("Salir", event -> closeForm());
}
private void setupFormLayout() {
add(
new H3("Añadir solicitud de vacaciones"),
employeeComboBox,
categoryComboBox,
availableDaysField,
startDatePicker,
endDatePicker,
daysToBeTakenField,
balanceDaysField,
new HorizontalLayout(saveButton, closeButton)
);
}
private void saveRequest() {
if (!binder.validate().isOk()) {
Notification.show("Rellene correctamente todos los campos obligatorios.");
return;
}
if (!validateForm()) {
Notification.show("Por favor, complete los campos antes de guardar");
return;
}
TimeOffRequest request = prepareRequest();
if (request.getCategory() == TimeOffRequestType.VACACION_GESTION_ACTUAL) {
handleVacationRequest(request);
} else {
handleExistingRequests(request);
}
requestService.saveTimeOffRequest(request);
Notification.show("Solicitud guardada correctamente.");
closeForm();
}
private TimeOffRequest prepareRequest() {
TimeOffRequest request = binder.getBean();
request.setStartDate(startDatePicker.getValue());
request.setAvailableDays(availableDaysField.getValue());
request.setExpiration(endDate != null ? endDate : endDatePicker.getValue());
request.setState(TimeOffRequestStatus.SOLICITADO);
return request;
}
private void handleExistingRequests(final TimeOffRequest request) {
List<TimeOffRequest> existingRequests =
requestService.findByEmployeeAndCategory(employee.getId(), request.getCategory());
int maxRequests = request.getCategory() != TimeOffRequestType.PERMISOS_DE_SALUD
&& !request.getCategory().name().startsWith("VACACION")
&& request.getCategory() != TimeOffRequestType.CUMPLEAÑOS
? 2 : 1;
if (existingRequests.size() >= maxRequests) {
existingRequests.stream()
.min(Comparator.comparing(TimeOffRequest::getStartDate))
.ifPresent(oldestRequest -> requestService.deleteTimeOffRequest(oldestRequest.getId()));
}
}
private void handleVacationRequest(final TimeOffRequest request) {
List<TimeOffRequest> existingRequests = requestService.findByEmployeeAndCategory(
employee.getId(),
TimeOffRequestType.VACACION_GESTION_ACTUAL
);
if (!existingRequests.isEmpty()) {
TimeOffRequest existingRequest = existingRequests.getFirst();
existingRequest.setCategory(TimeOffRequestType.VACACION_GESTION_ANTERIOR);
requestService.saveTimeOffRequest(existingRequest);
}
}
private boolean validateForm() {
return employeeComboBox.getValue() != null
&& categoryComboBox.getValue() != null
&& startDatePicker.getValue() != null
&& endDatePicker.getValue() != null;
}
private void closeForm() {
getUI().ifPresent(ui -> ui.navigate(RequestsListView.class));
}
}

View File

@ -1,156 +1,160 @@
package com.primefactorsolutions.views;
import com.primefactorsolutions.model.Employee;
import com.primefactorsolutions.model.TimeOffRequest;
import com.primefactorsolutions.model.TimeOffRequestType;
import com.primefactorsolutions.model.*;
import com.primefactorsolutions.service.EmployeeService;
import com.primefactorsolutions.service.TeamService;
import com.primefactorsolutions.service.TimeOffRequestService;
import com.vaadin.flow.component.Component;
import com.vaadin.flow.component.button.Button;
import com.vaadin.flow.component.combobox.ComboBox;
import com.vaadin.flow.component.grid.Grid;
import com.vaadin.flow.component.html.Div;
import com.vaadin.flow.component.datepicker.DatePicker;
import com.vaadin.flow.component.html.H3;
import com.vaadin.flow.component.html.Span;
import com.vaadin.flow.component.orderedlayout.HorizontalLayout;
import com.vaadin.flow.component.orderedlayout.VerticalLayout;
import com.vaadin.flow.router.BeforeEvent;
import com.vaadin.flow.router.HasUrlParameter;
import com.vaadin.flow.router.PageTitle;
import com.vaadin.flow.router.Route;
import com.vaadin.flow.component.notification.Notification;
import com.vaadin.flow.component.textfield.NumberField;
import com.vaadin.flow.router.*;
import com.vaadin.flow.spring.annotation.SpringComponent;
import jakarta.annotation.security.PermitAll;
import org.springframework.context.annotation.Scope;
import org.vaadin.firitin.form.BeanValidationForm;
import java.util.Collections;
import java.util.List;
import java.util.UUID;
import java.util.stream.Collectors;
@SpringComponent
@PermitAll
@Scope("prototype")
@PageTitle("Request")
@Route(value = "/requests", layout = MainLayout.class)
public class RequestView extends Div implements HasUrlParameter<String> {
@Route(value = "/requests/:requestId?/:action?", layout = MainLayout.class)
public class RequestView extends BeanValidationForm<TimeOffRequest> implements HasUrlParameter<String> {
private final ComboBox<TimeOffRequestStatus> state = new ComboBox<>("Estado de la solicitud");
private final DatePicker expiration = new DatePicker("Vencimiento");
private final DatePicker startDate = new DatePicker("Fecha de inicio");
private final DatePicker endDate = new DatePicker("Fecha de fin");
private final NumberField availableDays = new NumberField("Días disponibles");
private final NumberField daysToBeTake = new NumberField("Días a tomar");
private final TimeOffRequestService requestService;
private final EmployeeService employeeService;
private final Grid<TimeOffRequest> requestGrid = new Grid<>(TimeOffRequest.class);
private List<TimeOffRequest> requests = Collections.emptyList();
private ComboBox<TimeOffRequestType> categoryFilter;
private ComboBox<TimeOffRequest.Status> stateFilter;
private UUID employeeId;
private TimeOffRequest request;
private Employee employee;
public RequestView(final TimeOffRequestService requestService, final EmployeeService employeeService) {
private Button saveButton;
public RequestView(final TimeOffRequestService requestService,
final EmployeeService employeeService,
final TeamService teamService) {
super(TimeOffRequest.class);
this.requestService = requestService;
this.employeeService = employeeService;
initializeView();
}
private void initializeView() {
setupFilters();
setupGrid();
add(requestGrid, createSummaryLayout(), createActionButtons());
refreshRequestGrid(null, null);
}
private void setupFilters() {
categoryFilter = createCategoryFilter();
stateFilter = createStateFilter();
add(categoryFilter, stateFilter);
}
private ComboBox<TimeOffRequestType> createCategoryFilter() {
categoryFilter = new ComboBox<>("Category");
categoryFilter.setItems(TimeOffRequestType.values());
categoryFilter.setValue(TimeOffRequestType.values()[0]);
categoryFilter.addValueChangeListener(event -> refreshRequestGrid(event.getValue(), stateFilter.getValue()));
return categoryFilter;
}
private ComboBox<TimeOffRequest.Status> createStateFilter() {
stateFilter = new ComboBox<>("State");
stateFilter.setItems(TimeOffRequest.Status.values());
stateFilter.setValue(TimeOffRequest.Status.values()[0]);
stateFilter.addValueChangeListener(event -> refreshRequestGrid(categoryFilter.getValue(), event.getValue()));
return stateFilter;
}
private void setupGrid() {
requestGrid.setColumns(
"category",
"state",
"availableDays",
"expiration",
"startDate",
"endDate",
"daysToBeTake",
"daysBalance"
);
requestGrid.setAllRowsVisible(true);
}
private VerticalLayout createSummaryLayout() {
int totalVacations = 15;
int totalTimeOff = 2;
int totalAvailableDays = totalVacations + totalTimeOff;
return new VerticalLayout(
new Span("TOTAL HOLIDAYS: " + totalVacations),
new Span("TOTAL TIME OFF: " + totalTimeOff),
new Span("TOTAL AVAILABLE DAYS: " + totalAvailableDays)
);
}
private HorizontalLayout createActionButtons() {
Button viewButton = new Button("View");
Button editButton = new Button("Edit");
Button saveButton = new Button("Save");
Button closeButton = new Button("Close", event -> navigateToRequestsListView());
return new HorizontalLayout(viewButton, editButton, saveButton, closeButton);
}
private void navigateToRequestsListView() {
getUI().ifPresent(ui -> ui.navigate(RequestsListView.class));
}
private void refreshRequestGrid(final TimeOffRequestType category, final TimeOffRequest.Status state) {
List<TimeOffRequest> filteredRequests = allFiltersAreNull(category, state)
? requestService.findRequestsByEmployeeId(employeeId)
: fetchFilteredTimeOffRequests(category, state);
requestGrid.setItems(filteredRequests);
}
private boolean allFiltersAreNull(final TimeOffRequestType category, final TimeOffRequest.Status state) {
return category == null && state == null;
}
private List<TimeOffRequest> fetchFilteredTimeOffRequests(final TimeOffRequestType category,
final TimeOffRequest.Status state) {
requests = requestService.findRequestsByEmployeeId(employeeId);
if (category != null && !"ALL".equals(category.name())) {
requests = requests.stream()
.filter(req -> req.getCategory().equals(category))
.collect(Collectors.toList());
}
if (state != null && !"ALL".equals(state.name())) {
requests = requests.stream()
.filter(req -> req.getState().equals(state))
.collect(Collectors.toList());
}
return requests;
state.setItems(List.of(TimeOffRequestStatus.values()));
}
@Override
public void setParameter(final BeforeEvent event, final String parameter) {
employeeId = UUID.fromString(parameter);
Employee employee = employeeService.getEmployee(employeeId);
requests = requestService.findRequestsByEmployeeId(employeeId);
setViewTitle(employee.getFirstName() + " " + employee.getLastName(), employee.getTeam().getName());
requestGrid.setItems(requests);
public void setParameter(final BeforeEvent beforeEvent, final String action) {
final RouteParameters params = beforeEvent.getRouteParameters();
final String requestIdString = params.get("requestId").orElse(null);
if ("new".equals(action)) {
setEntityWithEnabledSave(new TimeOffRequest());
} else {
assert requestIdString != null;
UUID requestId = UUID.fromString(requestIdString);
request = requestService.findTimeOffRequest(requestId);
UUID employeeId = request.getEmployee().getId();
employee = employeeService.getEmployee(employeeId);
setEntity(request);
configureViewOrEditAction(action);
}
}
private void setViewTitle(final String employeeName, final String employeeTeam) {
addComponentAsFirst(new H3("Name: " + employeeName));
addComponentAtIndex(1, new H3("Team: " + employeeTeam));
@Override
protected List<Component> getFormComponents() {
return List.of(
createEmployeeHeader(),
createTeamHeader(),
createCategoryHeader(),
state,
expiration,
startDate,
endDate,
availableDays,
daysToBeTake,
createCloseButton()
);
}
protected Button createSaveButton() {
saveButton = new Button("Guardar");
saveButton.addClickListener(event -> saveRequest());
return saveButton;
}
protected Button createCloseButton() {
Button closeButton = new Button("Salir");
closeButton.addClickListener(event -> closeForm());
return closeButton;
}
private void setFieldsReadOnly(final boolean option) {
state.setReadOnly(option);
expiration.setReadOnly(option);
startDate.setReadOnly(option);
endDate.setReadOnly(option);
availableDays.setReadOnly(option);
daysToBeTake.setReadOnly(option);
}
private void saveRequest() {
if (isFormValid()) {
TimeOffRequest request = getEntity();
setRequestFieldValues(request);
requestService.saveTimeOffRequest(request);
Notification.show("Solicitud guardada correctamente.");
closeForm();
}
}
private void setRequestFieldValues(final TimeOffRequest request) {
request.setState(state.getValue());
request.setExpiration(expiration.getValue());
request.setStartDate(startDate.getValue());
request.setEndDate(endDate.getValue());
request.setAvailableDays(availableDays.getValue());
request.setDaysToBeTake(daysToBeTake.getValue());
}
private void closeForm() {
getUI().ifPresent(ui -> ui.navigate("requests/" + employee.getId().toString()));
}
private boolean isFormValid() {
return !state.isEmpty()
&& expiration.getValue() != null
&& startDate.getValue() != null
&& endDate.getValue() != null
&& availableDays.getValue() != null
&& daysToBeTake.getValue() != null;
}
private void configureViewOrEditAction(final String action) {
if ("edit".equals(action) && !request.getId().toString().isEmpty()) {
setFieldsReadOnly(false);
} else if ("view".equals(action) && !request.getId().toString().isEmpty()) {
setFieldsReadOnly(true);
saveButton.setEnabled(false);
}
}
private H3 createEmployeeHeader() {
return new H3("Empleado: " + employee.getFirstName() + " " + employee.getLastName());
}
private H3 createTeamHeader() {
return new H3("Equipo: " + employee.getTeam().getName());
}
private H3 createCategoryHeader() {
return new H3("Categoría: " + request.getCategory());
}
}

View File

@ -4,10 +4,9 @@ import com.primefactorsolutions.model.*;
import com.primefactorsolutions.service.EmployeeService;
import com.primefactorsolutions.service.TeamService;
import com.primefactorsolutions.service.TimeOffRequestService;
import com.primefactorsolutions.service.VacationService;
import com.vaadin.flow.component.button.Button;
import com.vaadin.flow.component.combobox.ComboBox;
import com.vaadin.flow.component.html.Main;
import com.vaadin.flow.component.html.Span;
import com.vaadin.flow.component.notification.Notification;
import com.vaadin.flow.component.orderedlayout.HorizontalLayout;
import com.vaadin.flow.router.PageTitle;
@ -17,178 +16,338 @@ import jakarta.annotation.security.PermitAll;
import org.springframework.context.annotation.Scope;
import org.vaadin.firitin.components.grid.PagingGrid;
import java.util.Collections;
import java.util.List;
import java.util.UUID;
import java.time.LocalDate;
import java.time.Period;
import java.util.*;
import java.util.stream.Collectors;
import static com.primefactorsolutions.views.Constants.PAGE_SIZE;
@SpringComponent
@Scope("prototype")
@PageTitle("Requests")
@Route(value = "/requests", layout = MainLayout.class)
@PermitAll
public class RequestsListView extends Main {
public class RequestsListView extends BaseView {
private final TimeOffRequestService requestService;
private final EmployeeService employeeService;
private final TeamService teamService;
private final PagingGrid<TimeOffRequest> requestGrid = new PagingGrid<>(TimeOffRequest.class);
private List<TimeOffRequest> requests = Collections.emptyList();
private final VacationService vacationService;
private final PagingGrid<Employee> requestGrid = new PagingGrid<>();
private ComboBox<Employee> employeeFilter;
private ComboBox<Team> teamFilter;
private ComboBox<TimeOffRequestType> categoryFilter;
private ComboBox<TimeOffRequest.Status> stateFilter;
private ComboBox<Status> stateFilter;
private UUID selectedEmployeeId;
public RequestsListView(final TimeOffRequestService requestService,
final EmployeeService employeeService,
final TeamService teamService) {
final TeamService teamService,
final VacationService vacationService) {
this.requestService = requestService;
this.employeeService = employeeService;
this.teamService = teamService;
this.requests = requestService.findAllTimeOffRequests();
this.vacationService = vacationService;
initializeView();
refreshRequestGrid(null, null, null, null);
refreshGeneralRequestGrid(null, null, null);
}
private void initializeView() {
requestService.updateRequestStatuses();
setupFilters();
setupRequestGrid();
add(requestGrid);
add(createActionButtons());
getCurrentPageLayout().add(requestGrid);
getCurrentPageLayout().add(createActionButtons());
}
private void setupFilters() {
add(createEmployeeFilter());
add(createTeamFilter());
add(createCategoryFilter());
add(createStateFilter());
final HorizontalLayout hl = new HorizontalLayout();
hl.add(createEmployeeFilter());
hl.add(createTeamFilter());
hl.add(createStateFilter());
getCurrentPageLayout().add(hl);
}
private void setupRequestGrid() {
requestGrid.setColumns(
"category",
"state",
"availableDays",
"expiration",
"startDate",
"endDate",
"daysToBeTake",
"daysBalance"
);
requestGrid.addComponentColumn(this::createEmployeeSpan).setHeader("Employee");
requestGrid.addComponentColumn(this::createTeamSpan).setHeader("Team");
requestGrid.addColumn(this::getEmployeeFullName).setHeader("Empleado");
requestGrid.addColumn(this::getTeamName).setHeader("Equipo");
requestGrid.addColumn(this::getEmployeeStatus).setHeader("Estado del empleado");
requestGrid.addColumn(this::getGeneralTotal).setHeader("Total general");
requestGrid.setPaginationBarMode(PagingGrid.PaginationBarMode.BOTTOM);
requestGrid.setPageSize(5);
requestGrid.setPageSize(PAGE_SIZE);
requestGrid.asSingleSelect().addValueChangeListener(event -> {
TimeOffRequest selectedRequest = event.getValue();
Employee selectedRequest = event.getValue();
if (selectedRequest != null) {
selectedEmployeeId = selectedRequest.getEmployee().getId();
selectedEmployeeId = selectedRequest.getId();
}
});
}
private HorizontalLayout createActionButtons() {
Button viewButton = new Button("View", event -> {
Button viewButton = new Button("Ver", event -> {
if (selectedEmployeeId != null) {
navigateToTimeOffRequestView(selectedEmployeeId);
} else {
Notification.show("Please select a request to view.", 3000, Notification.Position.MIDDLE);
Notification.show("Seleccione una solicitud.", 3000, Notification.Position.MIDDLE);
}
});
Button editButton = new Button("Edit");
Button saveButton = new Button("Save");
Button closeButton = new Button("Close", event -> navigateToMainView());
return new HorizontalLayout(viewButton, editButton, saveButton, closeButton);
Button closeButton = new Button("Salir", event -> navigateToMainView());
return new HorizontalLayout(viewButton, closeButton);
}
private void refreshRequestGrid(final Employee employee,
final Team team,
final TimeOffRequestType category,
final TimeOffRequest.Status state) {
private void refreshGeneralRequestGrid(final Employee employee,
final Team team,
final Status state) {
requestGrid.setPagingDataProvider((page, pageSize) -> {
requests = requestService.findAllTimeOffRequests();
int start = (int) (page * requestGrid.getPageSize());
if (allFiltersAreNull(employee, team, category, state)) {
return fetchTimeOffRequests(start, pageSize);
} else {
return fetchFilteredTimeOffRequests(start, pageSize, employee, team, category, state);
}
return fetchFilteredEmployees(start, pageSize, employee, team, state);
});
requestGrid.getDataProvider().refreshAll();
}
private boolean allFiltersAreNull(final Employee employee,
final Team team,
final TimeOffRequestType category,
final TimeOffRequest.Status state) {
return employee == null && team == null && category == null && state == null;
}
private List<Employee> fetchFilteredEmployees(final int start,
final int pageSize,
final Employee employee,
final Team team,
final Status state) {
List<Employee> filteredEmployees = employeeService.findAllEmployees();
private List<TimeOffRequest> fetchTimeOffRequests(final int start, final int pageSize) {
int end = start + pageSize;
if (end > requests.size()) {
end = requests.size();
}
return requests.subList(start, end);
}
private List<TimeOffRequest> fetchFilteredTimeOffRequests(final int start,
final int pageSize,
final Employee employee,
final Team team,
final TimeOffRequestType category,
final TimeOffRequest.Status state) {
if (employee != null && !"ALL".equals(employee.getFirstName())) {
requests = requests.stream()
.filter(request -> request.getEmployee().getId().equals(employee.getId()))
if (employee != null && !"TODOS".equals(employee.getFirstName())) {
filteredEmployees = filteredEmployees.stream()
.filter(emp -> emp.getId().equals(employee.getId()))
.collect(Collectors.toList());
}
if (team != null && !"ALL".equals(team.getName())) {
requests = requests.stream()
.filter(request -> request.getEmployee().getTeam().getId().equals(team.getId()))
if (team != null && !"TODOS".equals(team.getName())) {
filteredEmployees = filteredEmployees.stream()
.filter(emp -> emp.getTeam() != null && emp.getTeam().getId().equals(team.getId()))
.collect(Collectors.toList());
}
if (category != null && !"ALL".equals(category.name())) {
requests = requests.stream()
.filter(request -> request.getCategory().equals(category))
if (state != null && state != Status.TODOS) {
filteredEmployees = filteredEmployees.stream()
.filter(emp -> {
Optional<TimeOffRequest> request = requestService
.findByEmployeeAndState(emp.getId(), TimeOffRequestStatus.EN_USO);
return state == Status.EN_DESCANSO ? request.isPresent() : request.isEmpty();
})
.collect(Collectors.toList());
}
if (state != null && !"ALL".equals(state.name())) {
requests = requests.stream()
.filter(request -> request.getState().equals(state))
.collect(Collectors.toList());
}
int end = start + pageSize;
if (end > requests.size()) {
end = requests.size();
}
return requests.subList(start, end);
int end = Math.min(start + pageSize, filteredEmployees.size());
return filteredEmployees.subList(start, end);
}
private Span createEmployeeSpan(final TimeOffRequest timeOffRequest) {
Employee employee = timeOffRequest.getEmployee();
return new Span(employee.getFirstName() + " " + employee.getLastName());
private String getEmployeeFullName(final Employee employee) {
return "TODOS".equals(employee.getFirstName())
? "TODOS" : employee.getFirstName() + " " + employee.getLastName();
}
private Span createTeamSpan(final TimeOffRequest timeOffRequest) {
return new Span(timeOffRequest.getEmployee().getTeam().getName());
private String getTeamName(final Employee employee) {
Team team = employee.getTeam();
return team != null ? team.getName() : "Sin asignar";
}
private String getTeamLabel(final Team team) {
return "TODOS".equals(team.getName()) ? "TODOS" : team.getName();
}
private String getEmployeeStatus(final Employee employee) {
Optional<TimeOffRequest> activeRequest = requestService
.findByEmployeeAndState(employee.getId(), TimeOffRequestStatus.EN_USO);
return activeRequest.isPresent() ? "EN_DESCANSO" : "ACTIVO";
}
private String getGeneralTotal(final Employee employee) {
List<TimeOffRequest> employeeRequests = requestService.findRequestsByEmployeeId(employee.getId());
List<Vacation> vacations = vacationService.findVacations();
List<Double> vacationDays = calculateVacationDays(employee);
double utilizedVacationCurrentDays = vacationDays.get(1);
List<TimeOffRequest> vacationCurrentRequests = requestService
.findByEmployeeAndCategory(employee.getId(), TimeOffRequestType.VACACION_GESTION_ACTUAL);
if (vacationCurrentRequests != null && !vacationCurrentRequests.isEmpty()) {
utilizedVacationCurrentDays = vacationCurrentRequests.getLast().getDaysBalance();
}
double totalVacationCurrentDays = vacationDays.get(1) - (vacationDays.get(1) - utilizedVacationCurrentDays);
double utilizedVacationPreviousDays = vacationDays.get(0);
List<TimeOffRequest> vacationPreviousRequests = requestService
.findByEmployeeAndCategory(employee.getId(), TimeOffRequestType.VACACION_GESTION_ANTERIOR);
if (vacationPreviousRequests != null && !vacationPreviousRequests.isEmpty()) {
utilizedVacationPreviousDays = vacationPreviousRequests.getLast().getDaysBalance();
}
double totalVacationPreviousDays = vacationDays.getFirst()
- (vacationDays.getFirst() - utilizedVacationPreviousDays);
double totalUtilized = calculateTotalUtilized(employeeRequests);
double totalVacations = totalVacationCurrentDays + totalVacationPreviousDays;
double totalAvailable = calculateTotalAvailable(vacations, employeeRequests, employee);
double generalTotal = totalAvailable + totalVacations - totalUtilized;
return String.valueOf(generalTotal);
}
private Set<TimeOffRequestType> getExcludedCategories() {
return Set.of(
TimeOffRequestType.MATERNIDAD,
TimeOffRequestType.PATERNIDAD,
TimeOffRequestType.MATRIMONIO,
TimeOffRequestType.DUELO_1ER_GRADO,
TimeOffRequestType.DUELO_2ER_GRADO,
TimeOffRequestType.DIA_DEL_PADRE,
TimeOffRequestType.DIA_DE_LA_MADRE
);
}
private Set<TimeOffRequestType> getGenderSpecificExclusions() {
return Set.of(
TimeOffRequestType.DIA_DE_LA_MUJER_INTERNACIONAL,
TimeOffRequestType.DIA_DE_LA_MUJER_NACIONAL
);
}
private double calculateTotalUtilized(final List<TimeOffRequest> employeeRequests) {
int currentYear = LocalDate.now().getYear();
return employeeRequests.stream()
.filter(Objects::nonNull)
.filter(request -> request.getCategory() != TimeOffRequestType.PERMISOS_DE_SALUD)
.filter(request -> request.getCategory() != TimeOffRequestType.VACACION_GESTION_ACTUAL)
.filter(request -> request.getCategory() != TimeOffRequestType.VACACION_GESTION_ANTERIOR)
.filter(request -> request.getStartDate() != null && (
request.getStartDate().getYear() == currentYear
|| (request.getCategory().name().startsWith("VACACION")
&& request.getStartDate().getYear() == currentYear - 1)
))
.mapToDouble(request -> request.getDaysToBeTake() != null ? request.getDaysToBeTake() : 0.0)
.sum();
}
private List<Double> calculateVacationDays(final Employee employee) {
List<Double> vacationDays = new ArrayList<>();
if (employee.getDateOfEntry() != null) {
LocalDate entryDate = employee.getDateOfEntry();
LocalDate today = LocalDate.now();
boolean hasAnniversaryPassed = entryDate.getMonthValue() < today.getMonthValue()
|| (entryDate.getMonthValue() == today.getMonthValue() && entryDate.getDayOfMonth()
<= today.getDayOfMonth());
LocalDate previousVacationYearDate;
LocalDate currentVacationYearDate;
if (hasAnniversaryPassed) {
previousVacationYearDate = LocalDate.of(
today.getYear() - 1,
entryDate.getMonth(),
entryDate.getDayOfMonth()
);
currentVacationYearDate = LocalDate.of(
today.getYear(),
entryDate.getMonth(),
entryDate.getDayOfMonth()
);
} else {
previousVacationYearDate = LocalDate.of(
today.getYear() - 2,
entryDate.getMonth(),
entryDate.getDayOfMonth()
);
currentVacationYearDate = LocalDate.of(
today.getYear() - 1,
entryDate.getMonth(),
entryDate.getDayOfMonth()
);
}
vacationDays.add(calculateVacationDaysSinceEntry(entryDate, previousVacationYearDate));
vacationDays.add(calculateVacationDaysSinceEntry(entryDate, currentVacationYearDate));
} else {
vacationDays.add(0.0);
vacationDays.add(0.0);
}
return vacationDays;
}
private double calculateTotalAvailable(final List<Vacation> vacations, final List<TimeOffRequest> employeeRequests,
final Employee employee) {
Set<TimeOffRequestType> excludedCategories = getExcludedCategories();
Set<TimeOffRequestType> genderSpecificExclusions = getGenderSpecificExclusions();
Set<TimeOffRequestType> employeeRequestCategories = employeeRequests.stream()
.map(TimeOffRequest::getCategory)
.collect(Collectors.toSet());
double healthLicence = 2;
List<TimeOffRequest> healthRequests = requestService
.findByEmployeeAndCategory(employee.getId(), TimeOffRequestType.PERMISOS_DE_SALUD);
if (healthRequests != null && !healthRequests.isEmpty()) {
healthLicence = healthRequests.getLast().getDaysBalance();
}
double totalAvailable = vacations.stream()
.filter(Objects::nonNull)
.filter(vacation -> vacation.getCategory() != TimeOffRequestType.PERMISOS_DE_SALUD)
.filter(vacation -> shouldIncludeVacation(
vacation,
excludedCategories,
genderSpecificExclusions,
employee, employeeRequestCategories
))
.mapToDouble(vacation -> vacation.getDuration() != null ? vacation.getDuration() : 0.0)
.sum();
return totalAvailable + healthLicence;
}
private double calculateVacationDaysSinceEntry(final LocalDate dateOfEntry, final LocalDate date) {
int yearsOfService = dateOfEntry != null ? Period.between(dateOfEntry, date).getYears() : 0;
if (yearsOfService > 10) {
return 30;
}
if (yearsOfService > 5) {
return 20;
}
if (yearsOfService > 1) {
return 15;
}
return 0;
}
private boolean shouldIncludeVacation(final Vacation vacation,
final Set<TimeOffRequestType> excludedCategories,
final Set<TimeOffRequestType> genderSpecificExclusions,
final Employee employee,
final Set<TimeOffRequestType> employeeRequestCategories) {
if (excludedCategories.contains(vacation.getCategory())
&& !employeeRequestCategories.contains(vacation.getCategory())) {
return false;
}
return isFemale(employee) || !genderSpecificExclusions.contains(vacation.getCategory());
}
private boolean isFemale(final Employee employee) {
return employee.getGender() == Employee.Gender.FEMALE;
}
private ComboBox<Employee> createEmployeeFilter() {
employeeFilter = new ComboBox<>("Employee");
List<Employee> employees = employeeService.findAllEmployees();
employeeFilter = new ComboBox<>("Empleado");
List<Employee> employees = new ArrayList<>(employeeService.findAllEmployees());
employees.addFirst(createAllEmployeesOption());
employeeFilter.setItems(employees);
employeeFilter.setItemLabelGenerator(this::getEmployeeLabel);
employeeFilter.setItemLabelGenerator(this::getEmployeeFullName);
employeeFilter.setValue(employees.getFirst());
employeeFilter.addValueChangeListener(event ->
refreshRequestGrid(
refreshGeneralRequestGrid(
event.getValue(),
teamFilter.getValue(),
categoryFilter.getValue(),
stateFilter.getValue()
)
);
@ -196,73 +355,54 @@ public class RequestsListView extends Main {
}
private ComboBox<Team> createTeamFilter() {
teamFilter = new ComboBox<>("Team");
List<Team> teams = teamService.findAllTeams();
teamFilter = new ComboBox<>("Equipo");
List<Team> teams = new ArrayList<>(teamService.findAllTeams());
teams.addFirst(createAllTeamsOption());
teamFilter.setItems(teams);
teamFilter.setItemLabelGenerator(this::getTeamLabel);
teamFilter.setValue(teams.getFirst());
teamFilter.addValueChangeListener(event ->
refreshRequestGrid(
refreshGeneralRequestGrid(
employeeFilter.getValue(),
event.getValue(),
categoryFilter.getValue(),
stateFilter.getValue()
)
);
return teamFilter;
}
private ComboBox<TimeOffRequestType> createCategoryFilter() {
categoryFilter = new ComboBox<>("Category");
categoryFilter.setItems(TimeOffRequestType.values());
categoryFilter.setValue(TimeOffRequestType.values()[0]);
categoryFilter.addValueChangeListener(event ->
refreshRequestGrid(
employeeFilter.getValue(),
teamFilter.getValue(),
event.getValue(),
stateFilter.getValue()
)
);
return categoryFilter;
}
private ComboBox<TimeOffRequest.Status> createStateFilter() {
stateFilter = new ComboBox<>("State");
stateFilter.setItems(TimeOffRequest.Status.values());
stateFilter.setValue(TimeOffRequest.Status.values()[0]);
private ComboBox<Status> createStateFilter() {
stateFilter = new ComboBox<>("Estado del empleado");
stateFilter.setItems(Status.values());
stateFilter.setValue(Status.values()[0]);
stateFilter.addValueChangeListener(event ->
refreshRequestGrid(
refreshGeneralRequestGrid(
employeeFilter.getValue(),
teamFilter.getValue(),
categoryFilter.getValue(),
event.getValue()
)
);
return stateFilter;
}
private enum Status {
TODOS,
EN_DESCANSO,
ACTIVO
}
private Employee createAllEmployeesOption() {
Employee allEmployeesOption = new Employee();
allEmployeesOption.setFirstName("ALL");
allEmployeesOption.setFirstName("TODOS");
return allEmployeesOption;
}
private Team createAllTeamsOption() {
Team allTeamsOption = new Team();
allTeamsOption.setName("ALL");
allTeamsOption.setName("TODOS");
return allTeamsOption;
}
private String getEmployeeLabel(final Employee employee) {
return "ALL".equals(employee.getFirstName()) ? "ALL" : employee.getFirstName() + " " + employee.getLastName();
}
private String getTeamLabel(final Team team) {
return "ALL".equals(team.getName()) ? "ALL" : team.getName();
}
private void navigateToMainView() {
getUI().ifPresent(ui -> ui.navigate(MainView.class));
}

View File

@ -0,0 +1,71 @@
package com.primefactorsolutions.views;
import com.primefactorsolutions.service.AccountService;
import com.vaadin.flow.component.ClickEvent;
import com.vaadin.flow.component.ComponentEventListener;
import com.vaadin.flow.component.button.Button;
import com.vaadin.flow.component.button.ButtonVariant;
import com.vaadin.flow.component.formlayout.FormLayout;
import com.vaadin.flow.component.html.H3;
import com.vaadin.flow.component.orderedlayout.HorizontalLayout;
import com.vaadin.flow.component.orderedlayout.VerticalLayout;
import com.vaadin.flow.component.textfield.PasswordField;
import com.vaadin.flow.router.*;
import com.vaadin.flow.server.auth.AnonymousAllowed;
import java.util.List;
@Route("reset-password")
@PageTitle("PFS Intra")
@AnonymousAllowed
public class ResetPasswordView extends VerticalLayout implements BeforeEnterObserver {
private String username;
private String token;
public ResetPasswordView(final AccountService accountService) {
setSizeFull();
setAlignItems(Alignment.CENTER);
setJustifyContentMode(JustifyContentMode.CENTER);
final VerticalLayout vl = new VerticalLayout();
vl.setJustifyContentMode(JustifyContentMode.CENTER);
vl.setWidth("400px");
final PasswordField password = new PasswordField("Password");
final PasswordField confirmPassword = new PasswordField("Confirm Password");
final FormLayout formLayout = new FormLayout(password, confirmPassword);
formLayout.setColspan(password, 3);
formLayout.setColspan(confirmPassword, 3);
final Button primaryButton = new Button("Submit");
primaryButton.addThemeVariants(ButtonVariant.LUMO_PRIMARY);
primaryButton.addClickListener((ComponentEventListener<ClickEvent<Button>>) buttonClickEvent -> {
accountService.resetPassword(username, password.getValue(), token);
getUI().ifPresent(ui -> ui.navigate(MainView.class));
});
final Button secondaryButton = new Button("Cancel");
secondaryButton.addClickListener((ComponentEventListener<ClickEvent<Button>>) buttonClickEvent ->
getUI().ifPresent(ui -> ui.navigate(MainView.class)));
HorizontalLayout hl = new HorizontalLayout(secondaryButton, primaryButton);
vl.add(new H3("PFS - Reset Password"));
vl.add(formLayout);
vl.add(hl);
add(vl);
}
@Override
public void beforeEnter(final BeforeEnterEvent beforeEnterEvent) {
final Location location = beforeEnterEvent.getLocation();
final QueryParameters queryParameters = location.getQueryParameters();
this.username = queryParameters.getParameters().getOrDefault("username", List.of()).stream()
.findFirst().orElse(null);
this.token = queryParameters.getParameters().getOrDefault("token", List.of()).stream()
.findFirst().orElse(null);
}
}

View File

@ -1,2 +1,3 @@
spring.ldap.url=ldap://localhost:8391
spring.ldap.embedded.port=8391
application.jwtSecret=test123

View File

@ -44,3 +44,5 @@ spring.sql.init.mode=${SQL_INIT:embedded}
spring.h2.console.enabled=true
spring.h2.console.settings.web-allow-others=true
application.jwtSecret=${JWT_SECRET:changeme}

View File

@ -16,53 +16,80 @@ INSERT INTO team (id, version, name) VALUES ('c3a8a7b1-f2d9-48c0-86ea-f215c2e6b3
INSERT INTO team (id, version, name) VALUES ('8f6b61e7-efb2-4de7-b8ed-7438c9d8babe', 1, 'GHI');
insert into employee (id, version, username, first_name, last_name, status, team_id) values ('5c6f11fe-c341-4be7-a9a6-bba0081ad7c6', 1, 'bob', 'Bob', 'Test', 'ACTIVE','b0e8f394-78c1-4d8a-9c57-dc6e8b36a5fa');
insert into employee (id, version, username, first_name, last_name, status, team_id) values ('cba3efb7-32bc-44be-9fdc-fc5e4f211254', 1, 'ben', 'Ben', 'Test', 'ACTIVE', '6d63bc15-3f8b-46f7-9cf1-7e9b0b9a2b28');
insert into employee (id, version, username, first_name, last_name, status, team_id) values ('e99b7af5-7d3a-4c0f-b8bc-e8d0388d8fc4', 1, 'jperez', 'Juan', 'Perez Condori', 'INACTIVE', 'c3a8a7b1-f2d9-48c0-86ea-f215c2e6b3a3');
insert into employee (id, version, username, first_name, last_name, status, team_id) values ('f6ab3c6d-7078-45f6-9b22-4e37637bfec6', 1, 'agarcia', 'Ana', 'Garcia Rojas', 'ACTIVE', '8f6b61e7-efb2-4de7-b8ed-7438c9d8babe');
insert into employee (id, version, username, first_name, last_name, status, team_id) values ('2e2293b1-3f9a-4f3d-abc8-32639b0a5e15', 1, 'clopez', 'Carlos', 'Lopez Mendoza', 'INACTIVE', 'b0e8f394-78c1-4d8a-9c57-dc6e8b36a5fa');
insert into employee (id, version, username, first_name, last_name, status, team_id) values ('4b1c6c35-4627-4b35-b6e9-dc75c68b2c31', 1, 'mfernandez', 'Maria', 'Fernandez Villca', 'ACTIVE', '6d63bc15-3f8b-46f7-9cf1-7e9b0b9a2b28');
insert into employee (id, version, username, first_name, last_name, status, team_id) values ('afc5c741-f70a-4394-853b-39d51b118927', 1, 'lgutierrez', 'Luis', 'Gutierrez Mamani', 'ACTIVE', 'c3a8a7b1-f2d9-48c0-86ea-f215c2e6b3a3');
insert into employee (id, version, username, first_name, last_name, status, team_id) values ('b2436b82-7b9f-4f0d-9463-f2c3173a45c3', 1, 'lmartinez', 'Laura', 'Martinez Paredes', 'INACTIVE', '8f6b61e7-efb2-4de7-b8ed-7438c9d8babe');
insert into employee (id, version, username, first_name, last_name, status, team_id) values ('6e6a8a4e-9f6b-44eb-8c69-40acfdc86756', 1, 'rsantos', 'Roberto', 'Santos Escobar', 'ACTIVE','b0e8f394-78c1-4d8a-9c57-dc6e8b36a5fa');
insert into employee (id, version, username, first_name, last_name, status, team_id) values ('36b0d1c6-bdc0-4d98-94bb-08b9bce3f0d5', 1, 'vmorales', 'Valeria', 'Morales Ochoa', 'INACTIVE', '6d63bc15-3f8b-46f7-9cf1-7e9b0b9a2b28');
insert into employee (id, version, username, first_name, last_name, status, team_id) values ('5a1c6d80-58b3-43e3-a5a5-24b4a2d1d54a', 1, 'jramirez', 'Jorge', 'Ramirez Tapia', 'ACTIVE', 'c3a8a7b1-f2d9-48c0-86ea-f215c2e6b3a3');
insert into employee (id, version, username, first_name, last_name, status, team_id) values ('9d6a5b2e-6d0b-4b89-8d6a-d3f3d1bfc047', 1, 'storres', 'Sandra', 'Torres Huanca', 'ACTIVE', '8f6b61e7-efb2-4de7-b8ed-7438c9d8babe');
insert into employee (id, version, username, first_name, last_name, status, team_id) values ('f8b3e0c0-0d5a-4e5c-bf9d-207b9b5e8279', 1, 'fquispe', 'Felipe', 'Quispe Huanca', 'INACTIVE','b0e8f394-78c1-4d8a-9c57-dc6e8b36a5fa');
insert into employee (id, version, username, first_name, last_name, status, team_id) values ('cd80e1d0-9a08-44a6-bd63-2c63eaa003d4', 1, 'grivas', 'Gabriela', 'Rivas Arana', 'ACTIVE', '6d63bc15-3f8b-46f7-9cf1-7e9b0b9a2b28');
insert into employee (id, version, username, first_name, last_name, status, team_id) values ('62d3c1b7-815e-4e96-8d7e-f8c4236bca55', 1, 'oflores', 'Oscar', 'Flores Quiroga', 'INACTIVE', 'c3a8a7b1-f2d9-48c0-86ea-f215c2e6b3a3');
insert into employee (id, version, username, first_name, last_name, status, team_id) values ('f20b7c5a-5a67-44f0-9ec1-4c1b8e80de05', 1, 'mvargas', 'Marta', 'Vargas Soria', 'ACTIVE', '8f6b61e7-efb2-4de7-b8ed-7438c9d8babe');
insert into employee (id, version, username, first_name, last_name, status, team_id) values ('19b5a76e-d7b1-4b76-8b02-4d0748e85809', 1, 'aespinoza', 'Andres', 'Espinoza Chura', 'INACTIVE','b0e8f394-78c1-4d8a-9c57-dc6e8b36a5fa');
insert into employee (id, version, username, first_name, last_name, status, team_id) values ('5c1a7b82-832d-4f24-8377-54b77b91b6a8', 1, 'cvillanueva', 'Carla', 'Villanueva Arce', 'ACTIVE', '6d63bc15-3f8b-46f7-9cf1-7e9b0b9a2b28');
INSERT INTO vacation (id, version, category, month_of_year, day_of_month, duration, expiration, type) VALUES ('123e4567-e89b-12d3-a456-426614174000', 1, 'AÑO_NUEVO', 1, 1, 1, 1, 'FIXED');
INSERT INTO vacation (id, version, category, month_of_year, day_of_month, duration, expiration, type) VALUES ('223e4567-e89b-12d3-a456-426614174001', 1, 'LUNES_CARNAVAL', 2, 12, 1, 1, 'FIXED');
INSERT INTO vacation (id, version, category, month_of_year, day_of_month, duration, expiration, type) VALUES ('323e4567-e89b-12d3-a456-426614174002', 1, 'MARTES_CARNAVAL', 2, 13, 1, 1, 'FIXED');
INSERT INTO vacation (id, version, category, month_of_year, day_of_month, duration, expiration, type) VALUES ('423e4567-e89b-12d3-a456-426614174003', 1, 'VIERNES_SANTO', 3, 29, 1, 1, 'FIXED');
INSERT INTO vacation (id, version, category, month_of_year, day_of_month, duration, expiration, type) VALUES ('523e4567-e89b-12d3-a456-426614174004', 1, 'DIA_DEL_TRABAJADOR', 5, 1, 1, 1, 'FIXED');
INSERT INTO vacation (id, version, category, month_of_year, day_of_month, duration, expiration, type) VALUES ('623e4567-e89b-12d3-a456-426614174005', 1, 'DIA_DE_LA_INDEPENDENCIA', 8, 6, 1, 1, 'FIXED');
INSERT INTO vacation (id, version, category, month_of_year, day_of_month, duration, expiration, type) VALUES ('723e4567-e89b-12d3-a456-426614174006', 1, 'NAVIDAD', 12, 25, 1, 1, 'FIXED');
INSERT INTO vacation (id, version, category, month_of_year, day_of_month, duration, expiration, type) VALUES ('823e4567-e89b-12d3-a456-426614174007', 1, 'DIA_DEL_ESTADO_PLURINACIONAL', 1, 21, 1, 30, 'MOVABLE');
INSERT INTO vacation (id, version, category, month_of_year, day_of_month, duration, expiration, type) VALUES ('923e4567-e89b-12d3-a456-426614174008', 1, 'CORPUS_CHRISTI', 5, 30, 1, 30, 'MOVABLE');
INSERT INTO vacation (id, version, category, month_of_year, day_of_month, duration, expiration, type) VALUES ('a23e4567-e89b-12d3-a456-426614174009', 1, 'AÑO_NUEVO_ANDINO', 6, 21, 1, 30, 'MOVABLE');
INSERT INTO vacation (id, version, category, month_of_year, day_of_month, duration, expiration, type) VALUES ('b23e4567-e89b-12d3-a456-42661417400a', 1, 'ANIVERSARIO_DEPARTAMENTAL', 9, 14, 1, 30, 'MOVABLE');
INSERT INTO vacation (id, version, category, month_of_year, day_of_month, duration, expiration, type) VALUES ('c23e4567-e89b-12d3-a456-42661417400b', 1, 'DIA_DE_TODOS_LOS_DIFUNTOS', 11, 2, 1, 30, 'MOVABLE');
INSERT INTO vacation (id, version, category, month_of_year, day_of_month, duration, type) VALUES ('c23e4567-e89b-12d3-a456-42661417400c', 1, 'CUMPLEAÑOS', 12, 31, 0.5, 'OTHER');
INSERT INTO vacation (id, version, category, duration, expiration, type) VALUES ('c23e4567-e89b-12d3-a456-42661417400d', 1, 'MATERNIDAD', 90, 90, 'OTHER');
INSERT INTO vacation (id, version, category, duration, expiration, type) VALUES ('c23e4567-e89b-12d3-a456-42661417400e', 1, 'PATERNIDAD', 3, 3, 'OTHER');
INSERT INTO vacation (id, version, category, duration, expiration, type) VALUES ('c23e4567-e89b-12d3-a456-42661417400f', 1, 'MATRIMONIO', 3, 3, 'OTHER');
INSERT INTO vacation (id, version, category, duration, expiration, type) VALUES ('550e8400-e29b-41d4-a716-446655440000', 1, 'DUELO_1ER_GRADO', 3, 3, 'OTHER');
INSERT INTO vacation (id, version, category, duration, expiration, type) VALUES ('c23e4567-e89b-12d3-a456-42661417400a', 1, 'DUELO_2ER_GRADO', 2, 2, 'OTHER');
INSERT INTO vacation (id, version, category, month_of_year, day_of_month, duration, expiration, type) VALUES ('c23e4567-e89b-12d3-a456-42661417401a', 1, 'DIA_DEL_PADRE', 3, 19, 0.5, 30, 'OTHER');
INSERT INTO vacation (id, version, category, month_of_year, day_of_month, duration, expiration, type) VALUES ('c23e4567-e89b-12d3-a456-42661417401b', 1, 'DIA_DE_LA_MADRE', 5, 27, 0.5, 30, 'OTHER');
INSERT INTO vacation (id, version, category, month_of_year, day_of_month, duration, expiration, type) VALUES ('c23e4567-e89b-12d3-a456-42661417401c', 1, 'DIA_DE_LA_MUJER_INTERNACIONAL', 3, 8, 0.5, 30, 'OTHER');
INSERT INTO vacation (id, version, category, month_of_year, day_of_month, duration, expiration, type) VALUES ('c23e4567-e89b-12d3-a456-42661417401d', 1, 'DIA_DE_LA_MUJER_NACIONAL', 10, 11, 0.5, 30, 'OTHER');
INSERT INTO vacation (id, version, category, duration, type) VALUES ('c23e4567-e89b-12d3-a456-42661417401e', 1, 'PERMISOS_DE_SALUD', 2, 'OTHER');
INSERT INTO vacation (id, version, category, expiration, type) VALUES ('490e5fbe-895b-42f8-b914-95437f7b39c0', 1, 'VACACION_GESTION_ACTUAL', 360, 'OTHER');
INSERT INTO vacation (id, version, category, expiration, type) VALUES ('c23e4567-e89b-12d3-a456-4266141740ff', 1, 'VACACION_GESTION_ANTERIOR', 360, 'OTHER');
insert into employee (id, version, username, first_name, last_name, status, team_id, gender, birthday, date_of_entry) values ('5c6f11fe-c341-4be7-a9a6-bba0081ad7c6', 1, 'bob', 'Bob', 'Test', 'ACTIVE','b0e8f394-78c1-4d8a-9c57-dc6e8b36a5fa', 'MALE', '2024-02-20', '2013-10-22');
insert into employee (id, version, username, first_name, last_name, status, team_id, gender, date_of_entry) values ('cba3efb7-32bc-44be-9fdc-fc5e4f211254', 1, 'ben', 'Ben', 'Test', 'ACTIVE', '6d63bc15-3f8b-46f7-9cf1-7e9b0b9a2b28', 'MALE', '2016-10-23');
insert into employee (id, version, username, first_name, last_name, status, team_id, gender, date_of_entry) values ('e99b7af5-7d3a-4c0f-b8bc-e8d0388d8fc4', 1, 'jperez', 'Juan', 'Perez Condori', 'INACTIVE', 'c3a8a7b1-f2d9-48c0-86ea-f215c2e6b3a3', 'MALE', '2022-10-22');
insert into employee (id, version, username, first_name, last_name, status, team_id, gender, date_of_entry) values ('f6ab3c6d-7078-45f6-9b22-4e37637bfec6', 1, 'agarcia', 'Ana', 'Garcia Rojas', 'ACTIVE', '8f6b61e7-efb2-4de7-b8ed-7438c9d8babe', 'FEMALE', '2024-10-24');
insert into employee (id, version, username, first_name, last_name, status, team_id, gender) values ('2e2293b1-3f9a-4f3d-abc8-32639b0a5e15', 1, 'clopez', 'Carlos', 'Lopez Mendoza', 'INACTIVE', 'b0e8f394-78c1-4d8a-9c57-dc6e8b36a5fa', 'MALE');
insert into employee (id, version, username, first_name, last_name, status, team_id, gender) values ('4b1c6c35-4627-4b35-b6e9-dc75c68b2c31', 1, 'mfernandez', 'Maria', 'Fernandez Villca', 'ACTIVE', '6d63bc15-3f8b-46f7-9cf1-7e9b0b9a2b28', 'FEMALE');
insert into employee (id, version, username, first_name, last_name, status, team_id, gender) values ('afc5c741-f70a-4394-853b-39d51b118927', 1, 'lgutierrez', 'Luis', 'Gutierrez Mamani', 'ACTIVE', 'c3a8a7b1-f2d9-48c0-86ea-f215c2e6b3a3', 'MALE');
insert into employee (id, version, username, first_name, last_name, status, team_id, gender) values ('b2436b82-7b9f-4f0d-9463-f2c3173a45c3', 1, 'lmartinez', 'Laura', 'Martinez Paredes', 'INACTIVE', '8f6b61e7-efb2-4de7-b8ed-7438c9d8babe', 'FEMALE');
insert into employee (id, version, username, first_name, last_name, status, team_id, gender) values ('6e6a8a4e-9f6b-44eb-8c69-40acfdc86756', 1, 'rsantos', 'Roberto', 'Santos Escobar', 'ACTIVE','b0e8f394-78c1-4d8a-9c57-dc6e8b36a5fa', 'MALE');
insert into employee (id, version, username, first_name, last_name, status, team_id, gender) values ('36b0d1c6-bdc0-4d98-94bb-08b9bce3f0d5', 1, 'vmorales', 'Valeria', 'Morales Ochoa', 'INACTIVE', '6d63bc15-3f8b-46f7-9cf1-7e9b0b9a2b28', 'FEMALE');
insert into employee (id, version, username, first_name, last_name, status, team_id, gender) values ('5a1c6d80-58b3-43e3-a5a5-24b4a2d1d54a', 1, 'jramirez', 'Jorge', 'Ramirez Tapia', 'ACTIVE', 'c3a8a7b1-f2d9-48c0-86ea-f215c2e6b3a3', 'MALE');
insert into employee (id, version, username, first_name, last_name, status, team_id, gender) values ('9d6a5b2e-6d0b-4b89-8d6a-d3f3d1bfc047', 1, 'storres', 'Sandra', 'Torres Huanca', 'ACTIVE', '8f6b61e7-efb2-4de7-b8ed-7438c9d8babe', 'FEMALE');
insert into employee (id, version, username, first_name, last_name, status, team_id, gender) values ('f8b3e0c0-0d5a-4e5c-bf9d-207b9b5e8279', 1, 'fquispe', 'Felipe', 'Quispe Huanca', 'INACTIVE','b0e8f394-78c1-4d8a-9c57-dc6e8b36a5fa', 'MALE');
insert into employee (id, version, username, first_name, last_name, status, team_id, gender) values ('cd80e1d0-9a08-44a6-bd63-2c63eaa003d4', 1, 'grivas', 'Gabriela', 'Rivas Arana', 'ACTIVE', '6d63bc15-3f8b-46f7-9cf1-7e9b0b9a2b28', 'FEMALE');
insert into employee (id, version, username, first_name, last_name, status, team_id, gender) values ('62d3c1b7-815e-4e96-8d7e-f8c4236bca55', 1, 'oflores', 'Oscar', 'Flores Quiroga', 'INACTIVE', 'c3a8a7b1-f2d9-48c0-86ea-f215c2e6b3a3', 'MALE');
insert into employee (id, version, username, first_name, last_name, status, team_id, gender) values ('f20b7c5a-5a67-44f0-9ec1-4c1b8e80de05', 1, 'mvargas', 'Marta', 'Vargas Soria', 'ACTIVE', '8f6b61e7-efb2-4de7-b8ed-7438c9d8babe', 'FEMALE');
insert into employee (id, version, username, first_name, last_name, status, team_id, gender) values ('19b5a76e-d7b1-4b76-8b02-4d0748e85809', 1, 'aespinoza', 'Andres', 'Espinoza Chura', 'INACTIVE','b0e8f394-78c1-4d8a-9c57-dc6e8b36a5fa', 'MALE');
insert into employee (id, version, username, first_name, last_name, status, team_id, gender) values ('5c1a7b82-832d-4f24-8377-54b77b91b6a8', 1, 'cvillanueva', 'Carla', 'Villanueva Arce', 'ACTIVE', '6d63bc15-3f8b-46f7-9cf1-7e9b0b9a2b28', 'FEMALE');
insert into time_off_request (id, version, employee_id, category, state, available_days, expiration, start_date, end_date, days_to_be_take, days_balance)
values ('9d6f12ba-e341-4e7a-b8a6-cab0982bd8c1', 1, '5c6f11fe-c341-4be7-a9a6-bba0081ad7c6', 'VACATION', 'TAKEN', 15, '2025-12-31', '2024-10-01', '2024-10-10', 5, 10);
values ('9d6f12ba-e341-4e7a-b8a6-cab0982bd8c1', 1, '5c6f11fe-c341-4be7-a9a6-bba0081ad7c6', 'PATERNIDAD', 'APROBADO', 3, '2024-10-03', '2024-10-01', '2024-10-03', 3, 0);
insert into time_off_request (id, version, employee_id, category, state, available_days, expiration, start_date, end_date, days_to_be_take, days_balance)
values ('2fa314bc-f547-4b12-a8b6-bb789feabc12', 1, '19b5a76e-d7b1-4b76-8b02-4d0748e85809', 'BIRTHDAY', 'APPROVED', 15, '2025-12-31', '2024-12-01', '2024-12-15', 7, 8);
values ('9a6f12ba-e111-4e7a-b8a6-caa0982bd8a1', 1, '5c6f11fe-c341-4be7-a9a6-bba0081ad7c6', 'AÑO_NUEVO', 'APROBADO', 1, '2024-01-01', '2024-01-01', '2024-01-01', 1, 0);
insert into time_off_request (id, version, employee_id, category, state, available_days, expiration, start_date, end_date, days_to_be_take, days_balance)
values ('d5f6341a-913d-4e7f-a0b2-cfe0786acd34', 1, 'cba3efb7-32bc-44be-9fdc-fc5e4f211254', 'FIXED_HOLIDAY', 'IN_USE', 20, '2025-11-30', '2024-11-10', '2024-11-20', 10, 10);
values ('9b6f12ba-e222-4e7a-b8a6-caa0982bd8b2', 1, '5c6f11fe-c341-4be7-a9a6-bba0081ad7c6', 'LUNES_CARNAVAL', 'APROBADO', 1, '2025-02-12', '2025-02-12', '2025-02-12', 1, 0);
insert into time_off_request (id, version, employee_id, category, state, available_days, expiration, start_date, end_date, days_to_be_take, days_balance)
values ('4f913b23-ff23-4527-bcd6-adfe01234567', 1, 'e99b7af5-7d3a-4c0f-b8bc-e8d0388d8fc4', 'MATERNITY', 'TAKEN', 18, '2025-06-30', '2024-07-01', '2024-07-15', 10, 8);
values ('9c6f12ba-e333-4e7a-b8a6-caa0982bd8c3', 1, '5c6f11fe-c341-4be7-a9a6-bba0081ad7c6', 'MARTES_CARNAVAL', 'SOLICITADO', 1, '2025-02-13', '2025-02-13', '2025-02-13', 1, 0);
insert into time_off_request (id, version, employee_id, category, state, available_days, expiration, start_date, end_date, days_to_be_take, days_balance)
values ('8c653f2a-f9a3-4d67-b3b6-12ad98fe0983', 1, 'f6ab3c6d-7078-45f6-9b22-4e37637bfec6', 'VACATION', 'REQUESTED', 10, '2025-10-31', '2024-09-15', '2024-09-20', 5, 5);
values ('9d6f12ba-e444-4e7a-b8a6-caa0982bd8d4', 1, '5c6f11fe-c341-4be7-a9a6-bba0081ad7c6', 'VIERNES_SANTO', 'APROBADO', 1, '2024-03-29', '2024-03-29', '2024-03-29', 1, 0);
insert into time_off_request (id, version, employee_id, category, state, available_days, expiration, start_date, end_date, days_to_be_take, days_balance)
values ('fb9d9d75-b2ab-4ea4-b8b3-0a8f89e5c123', 1, '2e2293b1-3f9a-4f3d-abc8-32639b0a5e15', 'FIXED_HOLIDAY', 'IN_USE', 12, '2025-08-31', '2024-08-05', '2024-08-15', 6, 6);
values ('9e6f12ba-e555-4e7a-b8a6-caa0982bd8e5', 1, '5c6f11fe-c341-4be7-a9a6-bba0081ad7c6', 'VACACION_GESTION_ANTERIOR', 'APROBADO', 20, '2024-11-01', '2022-11-01', '2022-11-30', 20, 0);
insert into time_off_request (id, version, employee_id, category, state, available_days, expiration, start_date, end_date, days_to_be_take, days_balance)
values ('1c913a12-46e9-47b7-9e31-ab903fedc789', 1, '4b1c6c35-4627-4b35-b6e9-dc75c68b2c31', 'BIRTHDAY', 'TAKEN', 14, '2025-12-31', '2024-10-20', '2024-10-25', 5, 9);
values ('8c653f2a-f9a3-4d67-b3b6-12ad98fe0983', 1, 'f6ab3c6d-7078-45f6-9b22-4e37637bfec6', 'DIA_DEL_TRABAJADOR', 'APROBADO', 1, '2024-05-01', '2024-05-01', '2024-05-01', 1, 0);
insert into time_off_request (id, version, employee_id, category, state, available_days, expiration, start_date, end_date, days_to_be_take, days_balance)
values ('b1249d3a-cc34-4954-88d9-1e4f67fe2436', 1, 'afc5c741-f70a-4394-853b-39d51b118927', 'MATERNITY', 'APPROVED', 20, '2025-11-30', '2024-11-05', '2024-11-12', 7, 13);
values ('fb9d9d75-b2ab-4ea4-b8b3-0a8f89e5c123', 1, '2e2293b1-3f9a-4f3d-abc8-32639b0a5e15', 'DIA_DE_LA_INDEPENDENCIA', 'APROBADO', 1, '2024-08-06', '2024-08-06', '2024-08-06', 1, 0);
insert into time_off_request (id, version, employee_id, category, state, available_days, expiration, start_date, end_date, days_to_be_take, days_balance)
values ('6fdc47a8-127b-41c4-8d12-7fc12098ab12', 1, 'b2436b82-7b9f-4f0d-9463-f2c3173a45c3', 'VACATION', 'TAKEN', 18, '2025-06-30', '2024-07-10', '2024-07-20', 8, 10);
values ('6fdc47a8-127b-41c4-8d12-7fc12098ab12', 1, 'b2436b82-7b9f-4f0d-9463-f2c3173a45c3', 'DIA_DE_TODOS_LOS_DIFUNTOS', 'SOLICITADO', 1, '2025-12-01', '2025-11-02', '2025-11-02', 1, 0);
insert into time_off_request (id, version, employee_id, category, state, available_days, expiration, start_date, end_date, days_to_be_take, days_balance)
values ('12ec8b74-983d-4a17-b67e-134f45ae904c', 1, '5c1a7b82-832d-4f24-8377-54b77b91b6a8', 'FIXED_HOLIDAY', 'PENDING', 15, '2025-12-31', '2024-09-01', '2024-09-05', 4, 11);
values ('12ec8b74-983d-4a17-b67e-134f45ae904c', 1, '5c1a7b82-832d-4f24-8377-54b77b91b6a8', 'AÑO_NUEVO', 'SOLICITADO', 1, '2025-01-01', '2025-01-01', '2025-01-01', 1, 0);
insert into time_off_request (id, version, employee_id, category, state, available_days, expiration, start_date, end_date, days_to_be_take, days_balance)
values ('89bc4b2a-943f-487c-a9f3-bacf78145e67', 1, 'cba3efb7-32bc-44be-9fdc-fc5e4f211254', 'VACATION', 'APPROVED', 20, '2025-11-30', '2024-10-25', '2024-11-05', 9, 11);
insert into time_off_request (id, version, employee_id, category, state, available_days, expiration, start_date, end_date, days_to_be_take, days_balance)
values ('37adfc2a-7463-4b2d-a7c1-fae04567cdef', 1, 'e99b7af5-7d3a-4c0f-b8bc-e8d0388d8fc4', 'BIRTHDAY', 'TAKEN', 18, '2025-06-30', '2024-06-01', '2024-06-10', 6, 12);
insert into time_off_request (id, version, employee_id, category, state, available_days, expiration, start_date, end_date, days_to_be_take, days_balance)
values ('2bc138ea-12db-4b89-a0b4-78e045e34b4e', 1, 'f6ab3c6d-7078-45f6-9b22-4e37637bfec6', 'MATERNITY', 'REQUESTED', 10, '2025-10-31', '2024-10-01', '2024-10-10', 3, 7);
insert into time_off_request (id, version, employee_id, category, state, available_days, expiration, start_date, end_date, days_to_be_take, days_balance)
values ('14de1a56-6893-4e12-90f3-4faec457f002', 1, 'cd80e1d0-9a08-44a6-bd63-2c63eaa003d4', 'FIXED_HOLIDAY', 'PENDING', 22, '2025-08-31', '2024-07-15', '2024-07-25', 8, 14);
insert into time_off_request (id, version, employee_id, category, state, available_days, expiration, start_date, end_date, days_to_be_take, days_balance)
values ('fb08a6c9-cd17-42e8-b9e2-734ec834cae2', 1, '4b1c6c35-4627-4b35-b6e9-dc75c68b2c31', 'VACATION', 'TAKEN', 16, '2025-12-31', '2024-09-30', '2024-10-05', 4, 12);
values ('89bc4b2a-943f-487c-a9f3-bacf78145e67', 1, 'cba3efb7-32bc-44be-9fdc-fc5e4f211254', 'LUNES_CARNAVAL', 'APROBADO', 1, '2024-02-12', '2024-02-12', '2024-02-12', 1, 0);
INSERT INTO HOURS_WORKED (ID, VERSION, ACTIVIDAD, DATE, HORAS_TAREAS_ESPECIFICAS, HORASPENDIENTES, HOURS, TAREAS_ESPECIFICAS, TOTAL_HOURS, WEEK_NUMBER, EMPLOYEE_ID, TEAM_ID)
VALUES ('6d6b3a71-9b11-4526-8335-b089627a8cd6', 0, 'Scrum Meeting', '2024-11-15', 0.0, 0.0, 2.0, NULL, 8, 46, '5c6f11fe-c341-4be7-a9a6-bba0081ad7c6', 'b0e8f394-78c1-4d8a-9c57-dc6e8b36a5fa');