From 9d0d7a2230b28e71e04e339fe1c2775aaec86984 Mon Sep 17 00:00:00 2001 From: Nikhil Ravi Cybrosys <39847867+nikhilcybro@users.noreply.github.com> Date: Tue, 6 Jun 2023 12:27:41 +0530 Subject: [PATCH] [UPDT]summer note in recruitment (#9) * [UPDT]candidate update stage * [FIX]typo in notification - email sent * [UPDT]recruitment updates * [UPDT]onboarding updates * [UPDT]attendance updates * [UPDT]employee updates * [UPDT]updated static files for summernote --------- Co-authored-by: NIKHIL RAVI --- attendance/admin.py | 16 +- attendance/filters.py | 677 +- attendance/forms.py | 0 attendance/models.py | 333 +- .../attendance/attendance_filters.html | 6 +- .../attendance/attendance_activity/nav.html | 0 .../late_come_early_out_filters.html | 6 +- .../attendance/own_attendance/filters.html | 4 +- .../attendance/own_attendance/nav.html | 4 +- attendance/templatetags/attendancefilters.py | 153 +- attendance/urls.py | 0 attendance/views.py | 0 employee/admin.py | 10 +- employee/apps.py | 17 +- employee/filters.py | 77 +- employee/forms.py | 331 +- employee/imports.py | 13 - employee/models.py | 257 +- employee/templatetags/employeefilters.py | 67 - employee/urls.py | 232 +- employee/views.py | 1366 +- onboarding/decorators.py | 83 +- onboarding/filters.py | 65 +- onboarding/forms.py | 115 +- onboarding/models.py | 91 +- .../onboarding/candidate-filter.html | 85 +- .../templates/onboarding/candidate-task.html | 5 +- .../templates/onboarding/candidates-view.html | 237 +- .../templates/onboarding/candidates.html | 277 +- .../onboarding/employee-creation.html | 9 +- .../onboarding/onboarding-table.html | 11 +- .../templates/onboarding/profile-view.html | 14 +- .../templates/onboarding/user-creation.html | 2 +- onboarding/templatetags/onboardingfilters.py | 87 +- onboarding/urls.py | 91 +- onboarding/views.py | 573 +- recruitment/admin.py | 12 +- recruitment/apps.py | 13 + recruitment/decorators.py | 125 +- recruitment/filters.py | 263 +- recruitment/forms.py | 416 +- recruitment/methods.py | 27 +- recruitment/middleware.py | 6 - recruitment/models.py | 269 +- .../recruitment/widget/recruitmentAjax.js | 54 + .../widget/recruitment_widget_style.css | 0 .../templates/candidate/application_form.html | 282 +- .../templates/candidate/candidate_card.html | 2 +- .../candidate/candidate_create_form.html | 11 +- .../templates/candidate/candidate_list.html | 2 +- recruitment/templates/candidate/success.html | 122 + .../form/candidate_drop_down_form.html | 49 +- .../form/recruitment_drop_down_form.html | 34 +- .../pipeline/form/recruitment_update.html | 157 +- recruitment/templates/pipeline/pipeline.html | 33 +- .../templates/pipeline/pipeline_card.html | 34 +- .../recruitment/recruitment_component.html | 61 +- .../recruitment/recruitment_form.html | 170 +- .../recruitment/recruitment_update_form.html | 181 +- .../templates/stage/stage_component.html | 30 +- .../templatetags/recruitmentfilters.py | 82 +- recruitment/urls.py | 231 +- recruitment/views.py | 1460 +- recruitment/widgets.py | 35 + static/.gitattributes | 0 static/.gitignore | 0 static/admin/css/autocomplete.css | 0 static/admin/css/base.css | 0 static/admin/css/changelists.css | 0 static/admin/css/dark_mode.css | 0 static/admin/css/dashboard.css | 0 static/admin/css/fonts.css | 0 static/admin/css/forms.css | 0 static/admin/css/login.css | 0 static/admin/css/nav_sidebar.css | 0 static/admin/css/responsive.css | 0 static/admin/css/responsive_rtl.css | 0 static/admin/css/rtl.css | 0 .../css/vendor/select2/LICENSE-SELECT2.md | 0 static/admin/css/vendor/select2/select2.css | 0 .../admin/css/vendor/select2/select2.min.css | 0 static/admin/css/widgets.css | 0 static/admin/fonts/LICENSE.txt | 0 static/admin/fonts/README.txt | 0 static/admin/fonts/Roboto-Bold-webfont.woff | Bin static/admin/fonts/Roboto-Light-webfont.woff | Bin .../admin/fonts/Roboto-Regular-webfont.woff | Bin static/admin/img/LICENSE | 0 static/admin/img/README.txt | 0 static/admin/img/calendar-icons.svg | 0 static/admin/img/gis/move_vertex_off.svg | 0 static/admin/img/gis/move_vertex_on.svg | 0 static/admin/img/icon-addlink.svg | 0 static/admin/img/icon-alert.svg | 0 static/admin/img/icon-calendar.svg | 0 static/admin/img/icon-changelink.svg | 0 static/admin/img/icon-clock.svg | 0 static/admin/img/icon-deletelink.svg | 0 static/admin/img/icon-no.svg | 0 static/admin/img/icon-unknown-alt.svg | 0 static/admin/img/icon-unknown.svg | 0 static/admin/img/icon-viewlink.svg | 0 static/admin/img/icon-yes.svg | 0 static/admin/img/inline-delete.svg | 0 static/admin/img/search.svg | 0 static/admin/img/selector-icons.svg | 0 static/admin/img/sorting-icons.svg | 0 static/admin/img/tooltag-add.svg | 0 static/admin/img/tooltag-arrowright.svg | 0 static/admin/js/SelectBox.js | 0 static/admin/js/SelectFilter2.js | 0 static/admin/js/actions.js | 0 static/admin/js/admin/DateTimeShortcuts.js | 0 static/admin/js/admin/RelatedObjectLookups.js | 0 static/admin/js/autocomplete.js | 0 static/admin/js/calendar.js | 0 static/admin/js/cancel.js | 0 static/admin/js/change_form.js | 0 static/admin/js/collapse.js | 0 static/admin/js/core.js | 0 static/admin/js/filters.js | 0 static/admin/js/inlines.js | 0 static/admin/js/jquery.init.js | 0 static/admin/js/nav_sidebar.js | 0 static/admin/js/popup_response.js | 0 static/admin/js/prepopulate.js | 0 static/admin/js/prepopulate_init.js | 0 static/admin/js/urlify.js | 0 static/admin/js/vendor/jquery/LICENSE.txt | 0 static/admin/js/vendor/jquery/jquery.js | 0 static/admin/js/vendor/jquery/jquery.min.js | 0 static/admin/js/vendor/select2/LICENSE.md | 0 static/admin/js/vendor/select2/i18n/af.js | 0 static/admin/js/vendor/select2/i18n/ar.js | 0 static/admin/js/vendor/select2/i18n/az.js | 0 static/admin/js/vendor/select2/i18n/bg.js | 0 static/admin/js/vendor/select2/i18n/bn.js | 0 static/admin/js/vendor/select2/i18n/bs.js | 0 static/admin/js/vendor/select2/i18n/ca.js | 0 static/admin/js/vendor/select2/i18n/cs.js | 0 static/admin/js/vendor/select2/i18n/da.js | 0 static/admin/js/vendor/select2/i18n/de.js | 0 static/admin/js/vendor/select2/i18n/dsb.js | 0 static/admin/js/vendor/select2/i18n/el.js | 0 static/admin/js/vendor/select2/i18n/en.js | 0 static/admin/js/vendor/select2/i18n/es.js | 0 static/admin/js/vendor/select2/i18n/et.js | 0 static/admin/js/vendor/select2/i18n/eu.js | 0 static/admin/js/vendor/select2/i18n/fa.js | 0 static/admin/js/vendor/select2/i18n/fi.js | 0 static/admin/js/vendor/select2/i18n/fr.js | 0 static/admin/js/vendor/select2/i18n/gl.js | 0 static/admin/js/vendor/select2/i18n/he.js | 0 static/admin/js/vendor/select2/i18n/hi.js | 0 static/admin/js/vendor/select2/i18n/hr.js | 0 static/admin/js/vendor/select2/i18n/hsb.js | 0 static/admin/js/vendor/select2/i18n/hu.js | 0 static/admin/js/vendor/select2/i18n/hy.js | 0 static/admin/js/vendor/select2/i18n/id.js | 0 static/admin/js/vendor/select2/i18n/is.js | 0 static/admin/js/vendor/select2/i18n/it.js | 0 static/admin/js/vendor/select2/i18n/ja.js | 0 static/admin/js/vendor/select2/i18n/ka.js | 0 static/admin/js/vendor/select2/i18n/km.js | 0 static/admin/js/vendor/select2/i18n/ko.js | 0 static/admin/js/vendor/select2/i18n/lt.js | 0 static/admin/js/vendor/select2/i18n/lv.js | 0 static/admin/js/vendor/select2/i18n/mk.js | 0 static/admin/js/vendor/select2/i18n/ms.js | 0 static/admin/js/vendor/select2/i18n/nb.js | 0 static/admin/js/vendor/select2/i18n/ne.js | 0 static/admin/js/vendor/select2/i18n/nl.js | 0 static/admin/js/vendor/select2/i18n/pl.js | 0 static/admin/js/vendor/select2/i18n/ps.js | 0 static/admin/js/vendor/select2/i18n/pt-BR.js | 0 static/admin/js/vendor/select2/i18n/pt.js | 0 static/admin/js/vendor/select2/i18n/ro.js | 0 static/admin/js/vendor/select2/i18n/ru.js | 0 static/admin/js/vendor/select2/i18n/sk.js | 0 static/admin/js/vendor/select2/i18n/sl.js | 0 static/admin/js/vendor/select2/i18n/sq.js | 0 .../admin/js/vendor/select2/i18n/sr-Cyrl.js | 0 static/admin/js/vendor/select2/i18n/sr.js | 0 static/admin/js/vendor/select2/i18n/sv.js | 0 static/admin/js/vendor/select2/i18n/th.js | 0 static/admin/js/vendor/select2/i18n/tk.js | 0 static/admin/js/vendor/select2/i18n/tr.js | 0 static/admin/js/vendor/select2/i18n/uk.js | 0 static/admin/js/vendor/select2/i18n/vi.js | 0 static/admin/js/vendor/select2/i18n/zh-CN.js | 0 static/admin/js/vendor/select2/i18n/zh-TW.js | 0 .../admin/js/vendor/select2/select2.full.js | 0 .../js/vendor/select2/select2.full.min.js | 0 static/admin/js/vendor/xregexp/LICENSE.txt | 0 static/admin/js/vendor/xregexp/xregexp.js | 0 static/admin/js/vendor/xregexp/xregexp.min.js | 0 static/auth/forgot-password.html | 0 static/auth/login.html | 0 static/auth/reset.html | 0 static/bootstrap/bootsrtap.min.js | 0 static/bootstrap/bootstrap.min.css | 0 static/build/css/bootstrap.min.css | 6 + static/build/css/font/summernote.eot | Bin 0 -> 12072 bytes static/build/css/font/summernote.ttf | Bin 0 -> 11896 bytes static/build/css/font/summernote.woff | Bin 0 -> 7428 bytes static/build/css/font/summernote.woff2 | Bin 0 -> 6156 bytes static/build/css/style.min.css | 2 +- static/build/css/summernote-lite.min.css | 1 + static/build/js/htmxSelect2.js | 7 + .../themes/base/ui-icons_444444_256x240.png | Bin .../themes/base/ui-icons_555555_256x240.png | Bin .../themes/base/ui-icons_777620_256x240.png | Bin .../themes/base/ui-icons_777777_256x240.png | Bin .../themes/base/ui-icons_cc0000_256x240.png | Bin .../themes/base/ui-icons_ffffff_256x240.png | Bin static/build/js/jquery.min.js | 2 + static/build/js/mix-manifest.json | 0 static/build/js/summernote-lite.min.js | 48 + static/build/js/web.frontend.js | 215 +- static/build/js/web.frontend.min.js | 0 static/build/vendor/ionicons/.bin/stencil | 12 + static/build/vendor/ionicons/.bin/stencil.cmd | 17 + static/build/vendor/ionicons/.bin/stencil.ps1 | 28 + .../build/vendor/ionicons/.package-lock.json | 28 + .../vendor/ionicons/@stencil/core/LICENSE.md} | 8 +- .../vendor/ionicons/@stencil/core/bin/stencil | 57 + .../@stencil/core/cli/config-flags.d.ts | 122 + .../ionicons/@stencil/core/cli/index.cjs | 2609 + .../ionicons/@stencil/core/cli/index.d.ts | 19 + .../ionicons/@stencil/core/cli/index.js | 2583 + .../ionicons/@stencil/core/cli/package.json | 14 + .../ionicons/@stencil/core/compiler/lib.d.ts | 24 + .../@stencil/core/compiler/lib.dom.d.ts | 18505 ++++ .../core/compiler/lib.dom.iterable.d.ts | 341 + .../core/compiler/lib.es2015.collection.d.ts | 150 + .../core/compiler/lib.es2015.core.d.ts | 559 + .../@stencil/core/compiler/lib.es2015.d.ts | 30 + .../core/compiler/lib.es2015.generator.d.ts | 79 + .../core/compiler/lib.es2015.iterable.d.ts | 498 + .../core/compiler/lib.es2015.promise.d.ts | 83 + .../core/compiler/lib.es2015.proxy.d.ts | 130 + .../core/compiler/lib.es2015.reflect.d.ts | 146 + .../core/compiler/lib.es2015.symbol.d.ts | 48 + .../compiler/lib.es2015.symbol.wellknown.d.ts | 324 + .../compiler/lib.es2016.array.include.d.ts | 118 + .../@stencil/core/compiler/lib.es2016.d.ts | 22 + .../core/compiler/lib.es2016.full.d.ts | 25 + .../@stencil/core/compiler/lib.es2017.d.ts | 26 + .../core/compiler/lib.es2017.full.d.ts | 25 + .../core/compiler/lib.es2017.intl.d.ts | 47 + .../core/compiler/lib.es2017.object.d.ts | 51 + .../compiler/lib.es2017.sharedmemory.d.ts | 137 + .../core/compiler/lib.es2017.string.d.ts | 47 + .../core/compiler/lib.es2017.typedarrays.d.ts | 55 + .../compiler/lib.es2018.asyncgenerator.d.ts | 79 + .../compiler/lib.es2018.asynciterable.d.ts | 45 + .../@stencil/core/compiler/lib.es2018.d.ts | 26 + .../core/compiler/lib.es2018.full.d.ts | 25 + .../core/compiler/lib.es2018.intl.d.ts | 73 + .../core/compiler/lib.es2018.promise.d.ts | 32 + .../core/compiler/lib.es2018.regexp.d.ts | 39 + .../core/compiler/lib.es2019.array.d.ts | 85 + .../@stencil/core/compiler/lib.es2019.d.ts | 26 + .../core/compiler/lib.es2019.full.d.ts | 25 + .../core/compiler/lib.es2019.intl.d.ts | 25 + .../core/compiler/lib.es2019.object.d.ts | 35 + .../core/compiler/lib.es2019.string.d.ts | 39 + .../core/compiler/lib.es2019.symbol.d.ts | 26 + .../core/compiler/lib.es2020.bigint.d.ts | 730 + .../@stencil/core/compiler/lib.es2020.d.ts | 29 + .../core/compiler/lib.es2020.date.d.ts | 44 + .../core/compiler/lib.es2020.full.d.ts | 25 + .../core/compiler/lib.es2020.intl.d.ts | 433 + .../core/compiler/lib.es2020.number.d.ts | 30 + .../core/compiler/lib.es2020.promise.d.ts | 49 + .../compiler/lib.es2020.sharedmemory.d.ts | 99 + .../core/compiler/lib.es2020.string.d.ts | 30 + .../compiler/lib.es2020.symbol.wellknown.d.ts | 39 + .../@stencil/core/compiler/lib.es2021.d.ts | 25 + .../core/compiler/lib.es2021.full.d.ts | 25 + .../core/compiler/lib.es2021.intl.d.ts | 154 + .../core/compiler/lib.es2021.promise.d.ts | 50 + .../core/compiler/lib.es2021.string.d.ts | 35 + .../core/compiler/lib.es2021.weakref.d.ts | 75 + .../core/compiler/lib.es2022.array.d.ts | 123 + .../@stencil/core/compiler/lib.es2022.d.ts | 27 + .../core/compiler/lib.es2022.error.d.ts | 75 + .../core/compiler/lib.es2022.full.d.ts | 25 + .../core/compiler/lib.es2022.intl.d.ts | 111 + .../core/compiler/lib.es2022.object.d.ts | 28 + .../compiler/lib.es2022.sharedmemory.d.ts | 27 + .../core/compiler/lib.es2022.string.d.ts | 27 + .../@stencil/core/compiler/lib.es5.d.ts | 4526 + .../@stencil/core/compiler/lib.es6.d.ts | 25 + .../@stencil/core/compiler/lib.esnext.d.ts | 22 + .../core/compiler/lib.esnext.full.d.ts | 25 + .../core/compiler/lib.esnext.intl.d.ts | 30 + .../core/compiler/lib.esnext.promise.d.ts | 43 + .../core/compiler/lib.esnext.string.d.ts | 35 + .../core/compiler/lib.esnext.weakref.d.ts | 75 + .../core/compiler/lib.scripthost.d.ts | 327 + .../@stencil/core/compiler/lib.webworker.d.ts | 6192 ++ .../compiler/lib.webworker.importscripts.d.ts | 26 + .../core/compiler/lib.webworker.iterable.d.ts | 175 + .../@stencil/core/compiler/package.json | 8 + .../@stencil/core/compiler/stencil.d.ts | 73 + .../@stencil/core/compiler/stencil.js | 70673 ++++++++++++++++ .../@stencil/core/compiler/stencil.min.js | 4 + .../core/compiler/sys/in-memory-fs.d.ts | 218 + .../@stencil/core/compiler/transpile.d.ts | 32 + .../ionicons/@stencil/core/dependencies.json | 120 + .../core/dev-server/client/app-error.d.ts | 18 + .../core/dev-server/client/events.d.ts | 6 + .../dev-server/client/hmr-components.d.ts | 1 + .../client/hmr-external-styles.d.ts | 1 + .../core/dev-server/client/hmr-images.d.ts | 1 + .../dev-server/client/hmr-inline-styles.d.ts | 1 + .../core/dev-server/client/hmr-util.d.ts | 9 + .../core/dev-server/client/hmr-window.d.ts | 10 + .../core/dev-server/client/index.d.ts | 6 + .../@stencil/core/dev-server/client/index.js | 808 + .../core/dev-server/client/logger.d.ts | 5 + .../core/dev-server/client/package.json | 8 + .../core/dev-server/client/progress.d.ts | 3 + .../core/dev-server/client/status.d.ts | 4 + .../dev-server/client/test/hmr-util.spec.d.ts | 1 + .../dev-server/client/test/status.spec.d.ts | 1 + .../@stencil/core/dev-server/connector.html | 6 + .../@stencil/core/dev-server/index.d.ts | 3 + .../@stencil/core/dev-server/index.js | 264 + .../core/dev-server/open-in-editor-api.js | 1 + .../@stencil/core/dev-server/package.json | 8 + .../core/dev-server/server-process.js | 1799 + .../core/dev-server/server-worker-thread.js | 39 + .../core/dev-server/static/favicon.ico | Bin 0 -> 974 bytes .../dev-server/templates/directory-index.html | 132 + .../dev-server/templates/initial-load.html | 160 + .../@stencil/core/dev-server/visualstudio.vbs | 82 + .../ionicons/@stencil/core/dev-server/ws.js | 1 + .../@stencil/core/dev-server/xdg-open | 1066 + .../@stencil/core/internal/app-data/index.cjs | 92 + .../core/internal/app-data/index.d.ts | 4 + .../@stencil/core/internal/app-data/index.js | 88 + .../core/internal/app-data/package.json | 15 + .../@stencil/core/internal/client/css-shim.js | 4 + .../@stencil/core/internal/client/dom.js | 73 + .../@stencil/core/internal/client/index.js | 3371 + .../core/internal/client/package.json | 8 + .../core/internal/client/patch-browser.js | 142 + .../core/internal/client/patch-esm.js | 23 + .../core/internal/client/polyfills/core-js.js | 11 + .../internal/client/polyfills/css-shim.js | 1 + .../core/internal/client/polyfills/dom.js | 79 + .../client/polyfills/es5-html-element.js | 1 + .../core/internal/client/polyfills/index.js | 34 + .../core/internal/client/polyfills/system.js | 6 + .../core/internal/client/shadow-css.js | 387 + .../@stencil/core/internal/hydrate/index.js | 1143 + .../core/internal/hydrate/package.json | 7 + .../core/internal/hydrate/runner.d.ts | 217 + .../@stencil/core/internal/hydrate/runner.js | 777 + .../core/internal/hydrate/shadow-css.js | 143 + .../@stencil/core/internal/index.d.ts | 4 + .../ionicons/@stencil/core/internal/index.js | 2 + .../@stencil/core/internal/package.json | 9 + .../core/internal/stencil-core/index.cjs | 1 + .../core/internal/stencil-core/index.d.ts | 51 + .../core/internal/stencil-core/index.js | 16 + .../core/internal/stencil-ext-modules.d.ts | 41 + .../core/internal/stencil-private.d.ts | 2268 + .../internal/stencil-public-compiler.d.ts | 2363 + .../core/internal/stencil-public-docs.d.ts | 140 + .../core/internal/stencil-public-runtime.d.ts | 1637 + .../@stencil/core/internal/testing/index.js | 1105 + .../core/internal/testing/package.json | 7 + .../core/internal/testing/shadow-css.js | 143 + .../ionicons/@stencil/core/mock-doc/index.cjs | 4858 ++ .../@stencil/core/mock-doc/index.d.ts | 1020 + .../ionicons/@stencil/core/mock-doc/index.js | 4822 ++ .../@stencil/core/mock-doc/package.json | 15 + .../ionicons/@stencil/core/package.json | 155 + .../vendor/ionicons/@stencil/core/readme.md | 99 + .../screenshot/compare/assets/favicon.ico | Bin 0 -> 2918 bytes .../core/screenshot/compare/assets/logo.png | Bin 0 -> 5674 bytes .../core/screenshot/compare/build/app.css | 1 + .../core/screenshot/compare/build/app.esm.js | 1 + .../core/screenshot/compare/build/app.js | 33 + .../screenshot/compare/build/index.esm.js | 0 .../screenshot/compare/build/p-081b0641.js | 1 + .../compare/build/p-227a1e18.entry.js | 1 + .../compare/build/p-2c298727.entry.js | 1 + .../compare/build/p-5479268c.entry.js | 1 + .../compare/build/p-573ec8a4.entry.js | 1 + .../compare/build/p-6ba08604.entry.js | 1 + .../compare/build/p-6bc63295.entry.js | 1 + .../compare/build/p-7a3759fd.entry.js | 1 + .../screenshot/compare/build/p-7b4e3ba7.js | 1 + .../screenshot/compare/build/p-988eb362.css | 1 + .../screenshot/compare/build/p-9b6a9315.js | 1 + .../compare/build/p-b4cc611c.entry.js | 1 + .../compare/build/p-d1bf53f5.entry.js | 1 + .../screenshot/compare/build/p-e2efe0df.js | 1 + .../compare/build/p-e8ca6d97.entry.js | 1 + .../compare/build/p-ec2f13e0.entry.js | 1 + .../compare/build/p-f0b99977.entry.js | 1 + .../compare/build/p-f4745c2f.entry.js | 1 + .../screenshot/compare/build/p-fbbae598.js | 1 + .../core/screenshot/compare/host.config.json | 15 + .../core/screenshot/compare/index.html | 1 + .../core/screenshot/compare/manifest.json | 13 + .../core/screenshot/connector-base.d.ts | 42 + .../core/screenshot/connector-local.d.ts | 7 + .../@stencil/core/screenshot/connector.js | 2 + .../@stencil/core/screenshot/index.d.ts | 3 + .../@stencil/core/screenshot/index.js | 661 + .../core/screenshot/local-connector.js | 2 + .../@stencil/core/screenshot/package.json | 15 + .../@stencil/core/screenshot/pixel-match.d.ts | 1 + .../@stencil/core/screenshot/pixel-match.js | 2807 + .../core/screenshot/screenshot-compare.d.ts | 3 + .../core/screenshot/screenshot-fs.d.ts | 15 + .../@stencil/core/sys/node/autoprefixer.js | 8 + .../ionicons/@stencil/core/sys/node/glob.js | 1 + .../@stencil/core/sys/node/graceful-fs.js | 1 + .../@stencil/core/sys/node/index.d.ts | 22 + .../ionicons/@stencil/core/sys/node/index.js | 6124 ++ .../@stencil/core/sys/node/node-fetch.js | 1 + .../@stencil/core/sys/node/package.json | 8 + .../@stencil/core/sys/node/prompts.js | 1 + .../ionicons/@stencil/core/sys/node/worker.js | 52 + .../ionicons/@stencil/core/testing/index.d.ts | 12 + .../ionicons/@stencil/core/testing/index.js | 4155 + .../@stencil/core/testing/jest-environment.js | 3 + .../core/testing/jest-preprocessor.js | 3 + .../@stencil/core/testing/jest-preset.js | 37 + .../@stencil/core/testing/jest-runner.js | 3 + .../core/testing/jest-setuptestframework.js | 3 + .../core/testing/jest/jest-config.d.ts | 16 + .../core/testing/jest/jest-environment.d.ts | 15 + .../core/testing/jest/jest-preprocessor.d.ts | 59 + .../core/testing/jest/jest-runner.d.ts | 10 + .../core/testing/jest/jest-screenshot.d.ts | 2 + .../core/testing/jest/jest-serializer.d.ts | 4 + .../jest/jest-setup-test-framework.d.ts | 1 + .../testing/jest/test/jest-config.spec.d.ts | 1 + .../jest/test/jest-preprocessor.spec.d.ts | 1 + .../testing/jest/test/jest-runner.spec.d.ts | 1 + .../jest/test/jest-serializer.spec.d.ts | 1 + .../core/testing/matchers/attributes.d.ts | 14 + .../core/testing/matchers/class-list.d.ts | 12 + .../core/testing/matchers/events.d.ts | 21 + .../@stencil/core/testing/matchers/html.d.ts | 12 + .../@stencil/core/testing/matchers/index.d.ts | 23 + .../core/testing/matchers/screenshot.d.ts | 5 + .../@stencil/core/testing/matchers/text.d.ts | 4 + .../@stencil/core/testing/mock-fetch.d.ts | 11 + .../ionicons/@stencil/core/testing/mocks.d.ts | 56 + .../@stencil/core/testing/package.json | 8 + .../core/testing/puppeteer/index.d.ts | 2 + .../testing/puppeteer/puppeteer-browser.d.ts | 6 + .../puppeteer/puppeteer-declarations.d.ts | 429 + .../testing/puppeteer/puppeteer-element.d.ts | 67 + .../testing/puppeteer/puppeteer-emulate.d.ts | 2 + .../testing/puppeteer/puppeteer-events.d.ts | 21 + .../testing/puppeteer/puppeteer-page.d.ts | 2 + .../puppeteer/puppeteer-screenshot.d.ts | 4 + .../testing/reset-build-conditionals.d.ts | 2 + .../@stencil/core/testing/spec-page.d.ts | 2 + .../@stencil/core/testing/test-transpile.d.ts | 2 + .../core/testing/test/testing-utils.spec.d.ts | 1 + .../@stencil/core/testing/testing-logger.d.ts | 25 + .../@stencil/core/testing/testing-sys.d.ts | 6 + .../@stencil/core/testing/testing-utils.d.ts | 79 + .../@stencil/core/testing/testing.d.ts | 2 + static/htmx/htmx.min.js | 0 static/images/ui/assets.svg | 0 static/images/ui/at-circle.svg | 0 static/images/ui/attendances.svg | 0 static/images/ui/auth-logo.png | Bin static/images/ui/caret-down.svg | 0 static/images/ui/cog.svg | 0 static/images/ui/dashboard.svg | 0 static/images/ui/dots-v.svg | 0 static/images/ui/employees.svg | 0 static/images/ui/exciting.svg | 0 static/images/ui/filter.svg | 0 static/images/ui/grid.svg | 0 static/images/ui/happy.svg | 0 static/images/ui/leave.svg | 0 static/images/ui/menu.svg | 0 static/images/ui/not-found.svg | 1 + static/images/ui/notification.svg | 0 static/images/ui/online_party.svg | 0 static/images/ui/pms.svg | 0 static/images/ui/recruitment.svg | 0 static/images/ui/rocket.svg | 0 static/images/ui/search.svg | 0 static/images/ui/share.svg | 0 static/images/ui/trash.svg | 0 static/images/ui/user.jpg | Bin static/images/upload/sample-image.png | Bin static/images/upload/userphoto.png | Bin static/index/demo.js | 0 static/index/index.js | 0 static/ionicons/npm/components/index.cjs.js | 1 - static/ionicons/npm/components/index.d.ts | 22 - static/ionicons/npm/components/index.js | 2 - static/ionicons/npm/components/ion-icon.d.ts | 11 - static/ionicons/npm/components/ion-icon.js | 361 - static/ionicons/npm/components/package.json | 8 - static/ionicons/npm/icons/index.d.ts | 1340 - static/ionicons/npm/icons/index.js | 1340 - static/ionicons/npm/icons/index.mjs | 1340 - static/ionicons/npm/icons/package.json | 8 - static/ionicons/npm/package.json | 78 - static/ionicons/npm/readme.md | 121 - static/jquery/ajax.js | 0 static/jquery/jquery.min.js | 0 static/jquery/jqueryui.css | 0 static/jquery/jqueryui.js | 0 static/onboarding/onboarding-1.html | 0 static/onboarding/onboarding-2.html | 0 static/onboarding/onboarding-3.html | 0 static/onboarding/onboarding.html | 0 static/pipeline/pipeline.js | 0 static/proper/proper.js | 0 static/recruitment/candidate.js | 0 static/recruitment/recruitment copy.js | 0 static/recruitment/recruitment.js | 0 static/registration/registration.html | 0 static/src/js/index.js | 0 static/src/js/modules/dashboard/Calendar.js | 0 static/src/js/modules/dashboard/Dashboard.js | 0 .../src/js/modules/dashboard/ImageSelect.js | 0 .../src/js/modules/dashboard/ImageUpload.js | 0 static/src/js/modules/dashboard/Inputs.js | 0 static/src/js/modules/dashboard/Kanban.js | 0 static/src/js/modules/dashboard/LoadLayout.js | 0 .../src/js/modules/dashboard/ModalDialog.js | 0 .../src/js/modules/dashboard/Recruitment.js | 0 .../src/js/modules/dashboard/ResizeInput.js | 0 static/src/js/modules/dashboard/Specifics.js | 0 static/src/js/modules/dashboard/Tables.js | 0 static/src/js/modules/dashboard/Tabs.js | 0 static/src/js/modules/dashboard/Tooltip.js | 0 static/src/js/modules/vendors/niceSelect.js | 0 static/src/scss/abstracts/README.md | 0 static/src/scss/abstracts/_functions.scss | 0 static/src/scss/abstracts/_mixins.scss | 0 static/src/scss/abstracts/_variables.scss | 0 static/src/scss/base/README.md | 0 static/src/scss/base/_base.scss | 0 static/src/scss/base/_fonts.scss | 0 static/src/scss/base/_helpers.scss | 0 static/src/scss/base/_typography.scss | 0 static/src/scss/components/README.md | 0 static/src/scss/components/_accordion.scss | 0 static/src/scss/components/_alert.scss | 0 static/src/scss/components/_badges.scss | 0 static/src/scss/components/_button.scss | 0 static/src/scss/components/_card.scss | 0 static/src/scss/components/_dropdown.scss | 0 static/src/scss/components/_input.scss | 0 static/src/scss/components/_kanban.scss | 0 static/src/scss/components/_modal.scss | 0 static/src/scss/components/_pagination.scss | 0 static/src/scss/components/_profile.scss | 0 static/src/scss/components/_progress.scss | 0 static/src/scss/components/_sidebar.scss | 0 static/src/scss/components/_table.scss | 0 static/src/scss/components/_tabs.scss | 0 static/src/scss/components/_timeoff.scss | 0 static/src/scss/components/_tooltip.scss | 0 static/src/scss/layout/README.md | 0 static/src/scss/layout/_footer.scss | 0 static/src/scss/layout/_general.scss | 0 static/src/scss/layout/_header.scss | 0 static/src/scss/main.scss | 0 static/src/scss/overrides/_datepicker.scss | 0 static/src/scss/overrides/_fullcalendar.scss | 0 static/src/scss/pages/README.md | 0 static/src/scss/pages/_auth.scss | 0 static/src/scss/pages/_dashboard.scss | 0 static/src/scss/pages/_feedback.scss | 0 static/src/scss/pages/_onboarding.scss | 0 static/src/scss/pages/_profile.scss | 0 static/src/scss/prepros-6.config | 0 static/src/scss/prepros.config | 0 static/src/scss/themes/README.md | 0 static/src/scss/themes/_default.scss | 0 static/src/scss/vendors/README.md | 0 static/src/scss/vendors/_normalize.scss | 0 .../scss/vendors/bootstrap/_bootstrap.scss | 0 .../scss/vendors/bootstrap/_containers.scss | 0 .../scss/vendors/bootstrap/_functions.scss | 0 static/src/scss/vendors/bootstrap/_grid.scss | 0 .../src/scss/vendors/bootstrap/_helpers.scss | 0 .../src/scss/vendors/bootstrap/_mixins.scss | 0 .../src/scss/vendors/bootstrap/_reboot.scss | 0 static/src/scss/vendors/bootstrap/_root.scss | 0 static/src/scss/vendors/bootstrap/_type.scss | 0 .../scss/vendors/bootstrap/_utilities.scss | 0 .../scss/vendors/bootstrap/_variables.scss | 0 .../vendors/bootstrap/bootstrap-grid.scss | 0 .../vendors/bootstrap/bootstrap-reboot.scss | 0 .../bootstrap/bootstrap-utilities.scss | 0 .../vendors/bootstrap/helpers/_clearfix.scss | 0 .../bootstrap/helpers/_colored-links.scss | 0 .../vendors/bootstrap/helpers/_position.scss | 0 .../vendors/bootstrap/helpers/_ratio.scss | 0 .../bootstrap/helpers/_stretched-link.scss | 0 .../bootstrap/helpers/_text-truncation.scss | 0 .../bootstrap/helpers/_visually-hidden.scss | 0 .../scss/vendors/bootstrap/mixins/_alert.scss | 0 .../bootstrap/mixins/_border-radius.scss | 0 .../vendors/bootstrap/mixins/_box-shadow.scss | 0 .../bootstrap/mixins/_breakpoints.scss | 0 .../vendors/bootstrap/mixins/_buttons.scss | 0 .../scss/vendors/bootstrap/mixins/_caret.scss | 0 .../vendors/bootstrap/mixins/_clearfix.scss | 0 .../bootstrap/mixins/_color-scheme.scss | 0 .../vendors/bootstrap/mixins/_container.scss | 0 .../vendors/bootstrap/mixins/_deprecate.scss | 0 .../scss/vendors/bootstrap/mixins/_forms.scss | 0 .../vendors/bootstrap/mixins/_gradients.scss | 0 .../scss/vendors/bootstrap/mixins/_grid.scss | 0 .../scss/vendors/bootstrap/mixins/_image.scss | 0 .../vendors/bootstrap/mixins/_list-group.scss | 0 .../scss/vendors/bootstrap/mixins/_lists.scss | 0 .../vendors/bootstrap/mixins/_pagination.scss | 0 .../vendors/bootstrap/mixins/_reset-text.scss | 0 .../vendors/bootstrap/mixins/_resize.scss | 0 .../bootstrap/mixins/_table-variants.scss | 0 .../bootstrap/mixins/_text-truncate.scss | 0 .../vendors/bootstrap/mixins/_transition.scss | 0 .../vendors/bootstrap/mixins/_utilities.scss | 0 .../bootstrap/mixins/_visually-hidden.scss | 0 .../vendors/bootstrap/utilities/_api.scss | 0 .../scss/vendors/bootstrap/vendor/_rfs.scss | 0 static/static/images/ui/assets.svg | 0 static/static/images/ui/at-circle.svg | 0 static/static/images/ui/attendances.svg | 0 static/static/images/ui/caret-down.svg | 0 static/static/images/ui/cog.svg | 0 static/static/images/ui/dashboard.svg | 0 static/static/images/ui/dots-v.svg | 0 static/static/images/ui/employees.svg | 0 static/static/images/ui/exciting.svg | 0 static/static/images/ui/filter.svg | 0 static/static/images/ui/grid.svg | 0 static/static/images/ui/happy.svg | 0 static/static/images/ui/leave.svg | 0 static/static/images/ui/menu.svg | 0 static/static/images/ui/notification.svg | 0 static/static/images/ui/online_party.svg | 0 static/static/images/ui/pms.svg | 0 static/static/images/ui/recruitment.svg | 0 static/static/images/ui/rocket.svg | 0 static/static/images/ui/search.svg | 0 static/static/images/ui/share.svg | 0 static/static/images/ui/trash.svg | 0 static/static/images/ui/user.jpg | Bin static/static/images/upload/sample-image.png | Bin static/static/images/upload/userphoto.png | Bin 664 files changed, 163657 insertions(+), 8053 deletions(-) mode change 100755 => 100644 attendance/forms.py mode change 100755 => 100644 attendance/templates/attendance/attendance_activity/nav.html mode change 100755 => 100644 attendance/urls.py mode change 100755 => 100644 attendance/views.py delete mode 100644 employee/imports.py delete mode 100644 employee/templatetags/employeefilters.py delete mode 100644 recruitment/middleware.py create mode 100644 recruitment/static/recruitment/widget/recruitmentAjax.js rename employee/templatetags/__inti__.py => recruitment/static/recruitment/widget/recruitment_widget_style.css (100%) create mode 100644 recruitment/templates/candidate/success.html mode change 100755 => 100644 recruitment/views.py create mode 100644 recruitment/widgets.py mode change 100755 => 100644 static/.gitattributes mode change 100755 => 100644 static/.gitignore mode change 100755 => 100644 static/admin/css/autocomplete.css mode change 100755 => 100644 static/admin/css/base.css mode change 100755 => 100644 static/admin/css/changelists.css mode change 100755 => 100644 static/admin/css/dark_mode.css mode change 100755 => 100644 static/admin/css/dashboard.css mode change 100755 => 100644 static/admin/css/fonts.css mode change 100755 => 100644 static/admin/css/forms.css mode change 100755 => 100644 static/admin/css/login.css mode change 100755 => 100644 static/admin/css/nav_sidebar.css mode change 100755 => 100644 static/admin/css/responsive.css mode change 100755 => 100644 static/admin/css/responsive_rtl.css mode change 100755 => 100644 static/admin/css/rtl.css mode change 100755 => 100644 static/admin/css/vendor/select2/LICENSE-SELECT2.md mode change 100755 => 100644 static/admin/css/vendor/select2/select2.css mode change 100755 => 100644 static/admin/css/vendor/select2/select2.min.css mode change 100755 => 100644 static/admin/css/widgets.css mode change 100755 => 100644 static/admin/fonts/LICENSE.txt mode change 100755 => 100644 static/admin/fonts/README.txt mode change 100755 => 100644 static/admin/fonts/Roboto-Bold-webfont.woff mode change 100755 => 100644 static/admin/fonts/Roboto-Light-webfont.woff mode change 100755 => 100644 static/admin/fonts/Roboto-Regular-webfont.woff mode change 100755 => 100644 static/admin/img/LICENSE mode change 100755 => 100644 static/admin/img/README.txt mode change 100755 => 100644 static/admin/img/calendar-icons.svg mode change 100755 => 100644 static/admin/img/gis/move_vertex_off.svg mode change 100755 => 100644 static/admin/img/gis/move_vertex_on.svg mode change 100755 => 100644 static/admin/img/icon-addlink.svg mode change 100755 => 100644 static/admin/img/icon-alert.svg mode change 100755 => 100644 static/admin/img/icon-calendar.svg mode change 100755 => 100644 static/admin/img/icon-changelink.svg mode change 100755 => 100644 static/admin/img/icon-clock.svg mode change 100755 => 100644 static/admin/img/icon-deletelink.svg mode change 100755 => 100644 static/admin/img/icon-no.svg mode change 100755 => 100644 static/admin/img/icon-unknown-alt.svg mode change 100755 => 100644 static/admin/img/icon-unknown.svg mode change 100755 => 100644 static/admin/img/icon-viewlink.svg mode change 100755 => 100644 static/admin/img/icon-yes.svg mode change 100755 => 100644 static/admin/img/inline-delete.svg mode change 100755 => 100644 static/admin/img/search.svg mode change 100755 => 100644 static/admin/img/selector-icons.svg mode change 100755 => 100644 static/admin/img/sorting-icons.svg mode change 100755 => 100644 static/admin/img/tooltag-add.svg mode change 100755 => 100644 static/admin/img/tooltag-arrowright.svg mode change 100755 => 100644 static/admin/js/SelectBox.js mode change 100755 => 100644 static/admin/js/SelectFilter2.js mode change 100755 => 100644 static/admin/js/actions.js mode change 100755 => 100644 static/admin/js/admin/DateTimeShortcuts.js mode change 100755 => 100644 static/admin/js/admin/RelatedObjectLookups.js mode change 100755 => 100644 static/admin/js/autocomplete.js mode change 100755 => 100644 static/admin/js/calendar.js mode change 100755 => 100644 static/admin/js/cancel.js mode change 100755 => 100644 static/admin/js/change_form.js mode change 100755 => 100644 static/admin/js/collapse.js mode change 100755 => 100644 static/admin/js/core.js mode change 100755 => 100644 static/admin/js/filters.js mode change 100755 => 100644 static/admin/js/inlines.js mode change 100755 => 100644 static/admin/js/jquery.init.js mode change 100755 => 100644 static/admin/js/nav_sidebar.js mode change 100755 => 100644 static/admin/js/popup_response.js mode change 100755 => 100644 static/admin/js/prepopulate.js mode change 100755 => 100644 static/admin/js/prepopulate_init.js mode change 100755 => 100644 static/admin/js/urlify.js mode change 100755 => 100644 static/admin/js/vendor/jquery/LICENSE.txt mode change 100755 => 100644 static/admin/js/vendor/jquery/jquery.js mode change 100755 => 100644 static/admin/js/vendor/jquery/jquery.min.js mode change 100755 => 100644 static/admin/js/vendor/select2/LICENSE.md mode change 100755 => 100644 static/admin/js/vendor/select2/i18n/af.js mode change 100755 => 100644 static/admin/js/vendor/select2/i18n/ar.js mode change 100755 => 100644 static/admin/js/vendor/select2/i18n/az.js mode change 100755 => 100644 static/admin/js/vendor/select2/i18n/bg.js mode change 100755 => 100644 static/admin/js/vendor/select2/i18n/bn.js mode change 100755 => 100644 static/admin/js/vendor/select2/i18n/bs.js mode change 100755 => 100644 static/admin/js/vendor/select2/i18n/ca.js mode change 100755 => 100644 static/admin/js/vendor/select2/i18n/cs.js mode change 100755 => 100644 static/admin/js/vendor/select2/i18n/da.js mode change 100755 => 100644 static/admin/js/vendor/select2/i18n/de.js mode change 100755 => 100644 static/admin/js/vendor/select2/i18n/dsb.js mode change 100755 => 100644 static/admin/js/vendor/select2/i18n/el.js mode change 100755 => 100644 static/admin/js/vendor/select2/i18n/en.js mode change 100755 => 100644 static/admin/js/vendor/select2/i18n/es.js mode change 100755 => 100644 static/admin/js/vendor/select2/i18n/et.js mode change 100755 => 100644 static/admin/js/vendor/select2/i18n/eu.js mode change 100755 => 100644 static/admin/js/vendor/select2/i18n/fa.js mode change 100755 => 100644 static/admin/js/vendor/select2/i18n/fi.js mode change 100755 => 100644 static/admin/js/vendor/select2/i18n/fr.js mode change 100755 => 100644 static/admin/js/vendor/select2/i18n/gl.js mode change 100755 => 100644 static/admin/js/vendor/select2/i18n/he.js mode change 100755 => 100644 static/admin/js/vendor/select2/i18n/hi.js mode change 100755 => 100644 static/admin/js/vendor/select2/i18n/hr.js mode change 100755 => 100644 static/admin/js/vendor/select2/i18n/hsb.js mode change 100755 => 100644 static/admin/js/vendor/select2/i18n/hu.js mode change 100755 => 100644 static/admin/js/vendor/select2/i18n/hy.js mode change 100755 => 100644 static/admin/js/vendor/select2/i18n/id.js mode change 100755 => 100644 static/admin/js/vendor/select2/i18n/is.js mode change 100755 => 100644 static/admin/js/vendor/select2/i18n/it.js mode change 100755 => 100644 static/admin/js/vendor/select2/i18n/ja.js mode change 100755 => 100644 static/admin/js/vendor/select2/i18n/ka.js mode change 100755 => 100644 static/admin/js/vendor/select2/i18n/km.js mode change 100755 => 100644 static/admin/js/vendor/select2/i18n/ko.js mode change 100755 => 100644 static/admin/js/vendor/select2/i18n/lt.js mode change 100755 => 100644 static/admin/js/vendor/select2/i18n/lv.js mode change 100755 => 100644 static/admin/js/vendor/select2/i18n/mk.js mode change 100755 => 100644 static/admin/js/vendor/select2/i18n/ms.js mode change 100755 => 100644 static/admin/js/vendor/select2/i18n/nb.js mode change 100755 => 100644 static/admin/js/vendor/select2/i18n/ne.js mode change 100755 => 100644 static/admin/js/vendor/select2/i18n/nl.js mode change 100755 => 100644 static/admin/js/vendor/select2/i18n/pl.js mode change 100755 => 100644 static/admin/js/vendor/select2/i18n/ps.js mode change 100755 => 100644 static/admin/js/vendor/select2/i18n/pt-BR.js mode change 100755 => 100644 static/admin/js/vendor/select2/i18n/pt.js mode change 100755 => 100644 static/admin/js/vendor/select2/i18n/ro.js mode change 100755 => 100644 static/admin/js/vendor/select2/i18n/ru.js mode change 100755 => 100644 static/admin/js/vendor/select2/i18n/sk.js mode change 100755 => 100644 static/admin/js/vendor/select2/i18n/sl.js mode change 100755 => 100644 static/admin/js/vendor/select2/i18n/sq.js mode change 100755 => 100644 static/admin/js/vendor/select2/i18n/sr-Cyrl.js mode change 100755 => 100644 static/admin/js/vendor/select2/i18n/sr.js mode change 100755 => 100644 static/admin/js/vendor/select2/i18n/sv.js mode change 100755 => 100644 static/admin/js/vendor/select2/i18n/th.js mode change 100755 => 100644 static/admin/js/vendor/select2/i18n/tk.js mode change 100755 => 100644 static/admin/js/vendor/select2/i18n/tr.js mode change 100755 => 100644 static/admin/js/vendor/select2/i18n/uk.js mode change 100755 => 100644 static/admin/js/vendor/select2/i18n/vi.js mode change 100755 => 100644 static/admin/js/vendor/select2/i18n/zh-CN.js mode change 100755 => 100644 static/admin/js/vendor/select2/i18n/zh-TW.js mode change 100755 => 100644 static/admin/js/vendor/select2/select2.full.js mode change 100755 => 100644 static/admin/js/vendor/select2/select2.full.min.js mode change 100755 => 100644 static/admin/js/vendor/xregexp/LICENSE.txt mode change 100755 => 100644 static/admin/js/vendor/xregexp/xregexp.js mode change 100755 => 100644 static/admin/js/vendor/xregexp/xregexp.min.js mode change 100755 => 100644 static/auth/forgot-password.html mode change 100755 => 100644 static/auth/login.html mode change 100755 => 100644 static/auth/reset.html mode change 100755 => 100644 static/bootstrap/bootsrtap.min.js mode change 100755 => 100644 static/bootstrap/bootstrap.min.css create mode 100644 static/build/css/bootstrap.min.css create mode 100644 static/build/css/font/summernote.eot create mode 100644 static/build/css/font/summernote.ttf create mode 100644 static/build/css/font/summernote.woff create mode 100644 static/build/css/font/summernote.woff2 mode change 100755 => 100644 static/build/css/style.min.css create mode 100644 static/build/css/summernote-lite.min.css mode change 100755 => 100644 static/build/js/htmxSelect2.js mode change 100755 => 100644 static/build/js/images/vendor/jquery-ui/themes/base/ui-icons_444444_256x240.png mode change 100755 => 100644 static/build/js/images/vendor/jquery-ui/themes/base/ui-icons_555555_256x240.png mode change 100755 => 100644 static/build/js/images/vendor/jquery-ui/themes/base/ui-icons_777620_256x240.png mode change 100755 => 100644 static/build/js/images/vendor/jquery-ui/themes/base/ui-icons_777777_256x240.png mode change 100755 => 100644 static/build/js/images/vendor/jquery-ui/themes/base/ui-icons_cc0000_256x240.png mode change 100755 => 100644 static/build/js/images/vendor/jquery-ui/themes/base/ui-icons_ffffff_256x240.png create mode 100644 static/build/js/jquery.min.js mode change 100755 => 100644 static/build/js/mix-manifest.json create mode 100644 static/build/js/summernote-lite.min.js mode change 100755 => 100644 static/build/js/web.frontend.js mode change 100755 => 100644 static/build/js/web.frontend.min.js create mode 100644 static/build/vendor/ionicons/.bin/stencil create mode 100644 static/build/vendor/ionicons/.bin/stencil.cmd create mode 100644 static/build/vendor/ionicons/.bin/stencil.ps1 create mode 100644 static/build/vendor/ionicons/.package-lock.json rename static/{ionicons/npm/LICENSE => build/vendor/ionicons/@stencil/core/LICENSE.md} (73%) mode change 100755 => 100644 create mode 100644 static/build/vendor/ionicons/@stencil/core/bin/stencil create mode 100644 static/build/vendor/ionicons/@stencil/core/cli/config-flags.d.ts create mode 100644 static/build/vendor/ionicons/@stencil/core/cli/index.cjs create mode 100644 static/build/vendor/ionicons/@stencil/core/cli/index.d.ts create mode 100644 static/build/vendor/ionicons/@stencil/core/cli/index.js create mode 100644 static/build/vendor/ionicons/@stencil/core/cli/package.json create mode 100644 static/build/vendor/ionicons/@stencil/core/compiler/lib.d.ts create mode 100644 static/build/vendor/ionicons/@stencil/core/compiler/lib.dom.d.ts create mode 100644 static/build/vendor/ionicons/@stencil/core/compiler/lib.dom.iterable.d.ts create mode 100644 static/build/vendor/ionicons/@stencil/core/compiler/lib.es2015.collection.d.ts create mode 100644 static/build/vendor/ionicons/@stencil/core/compiler/lib.es2015.core.d.ts create mode 100644 static/build/vendor/ionicons/@stencil/core/compiler/lib.es2015.d.ts create mode 100644 static/build/vendor/ionicons/@stencil/core/compiler/lib.es2015.generator.d.ts create mode 100644 static/build/vendor/ionicons/@stencil/core/compiler/lib.es2015.iterable.d.ts create mode 100644 static/build/vendor/ionicons/@stencil/core/compiler/lib.es2015.promise.d.ts create mode 100644 static/build/vendor/ionicons/@stencil/core/compiler/lib.es2015.proxy.d.ts create mode 100644 static/build/vendor/ionicons/@stencil/core/compiler/lib.es2015.reflect.d.ts create mode 100644 static/build/vendor/ionicons/@stencil/core/compiler/lib.es2015.symbol.d.ts create mode 100644 static/build/vendor/ionicons/@stencil/core/compiler/lib.es2015.symbol.wellknown.d.ts create mode 100644 static/build/vendor/ionicons/@stencil/core/compiler/lib.es2016.array.include.d.ts create mode 100644 static/build/vendor/ionicons/@stencil/core/compiler/lib.es2016.d.ts create mode 100644 static/build/vendor/ionicons/@stencil/core/compiler/lib.es2016.full.d.ts create mode 100644 static/build/vendor/ionicons/@stencil/core/compiler/lib.es2017.d.ts create mode 100644 static/build/vendor/ionicons/@stencil/core/compiler/lib.es2017.full.d.ts create mode 100644 static/build/vendor/ionicons/@stencil/core/compiler/lib.es2017.intl.d.ts create mode 100644 static/build/vendor/ionicons/@stencil/core/compiler/lib.es2017.object.d.ts create mode 100644 static/build/vendor/ionicons/@stencil/core/compiler/lib.es2017.sharedmemory.d.ts create mode 100644 static/build/vendor/ionicons/@stencil/core/compiler/lib.es2017.string.d.ts create mode 100644 static/build/vendor/ionicons/@stencil/core/compiler/lib.es2017.typedarrays.d.ts create mode 100644 static/build/vendor/ionicons/@stencil/core/compiler/lib.es2018.asyncgenerator.d.ts create mode 100644 static/build/vendor/ionicons/@stencil/core/compiler/lib.es2018.asynciterable.d.ts create mode 100644 static/build/vendor/ionicons/@stencil/core/compiler/lib.es2018.d.ts create mode 100644 static/build/vendor/ionicons/@stencil/core/compiler/lib.es2018.full.d.ts create mode 100644 static/build/vendor/ionicons/@stencil/core/compiler/lib.es2018.intl.d.ts create mode 100644 static/build/vendor/ionicons/@stencil/core/compiler/lib.es2018.promise.d.ts create mode 100644 static/build/vendor/ionicons/@stencil/core/compiler/lib.es2018.regexp.d.ts create mode 100644 static/build/vendor/ionicons/@stencil/core/compiler/lib.es2019.array.d.ts create mode 100644 static/build/vendor/ionicons/@stencil/core/compiler/lib.es2019.d.ts create mode 100644 static/build/vendor/ionicons/@stencil/core/compiler/lib.es2019.full.d.ts create mode 100644 static/build/vendor/ionicons/@stencil/core/compiler/lib.es2019.intl.d.ts create mode 100644 static/build/vendor/ionicons/@stencil/core/compiler/lib.es2019.object.d.ts create mode 100644 static/build/vendor/ionicons/@stencil/core/compiler/lib.es2019.string.d.ts create mode 100644 static/build/vendor/ionicons/@stencil/core/compiler/lib.es2019.symbol.d.ts create mode 100644 static/build/vendor/ionicons/@stencil/core/compiler/lib.es2020.bigint.d.ts create mode 100644 static/build/vendor/ionicons/@stencil/core/compiler/lib.es2020.d.ts create mode 100644 static/build/vendor/ionicons/@stencil/core/compiler/lib.es2020.date.d.ts create mode 100644 static/build/vendor/ionicons/@stencil/core/compiler/lib.es2020.full.d.ts create mode 100644 static/build/vendor/ionicons/@stencil/core/compiler/lib.es2020.intl.d.ts create mode 100644 static/build/vendor/ionicons/@stencil/core/compiler/lib.es2020.number.d.ts create mode 100644 static/build/vendor/ionicons/@stencil/core/compiler/lib.es2020.promise.d.ts create mode 100644 static/build/vendor/ionicons/@stencil/core/compiler/lib.es2020.sharedmemory.d.ts create mode 100644 static/build/vendor/ionicons/@stencil/core/compiler/lib.es2020.string.d.ts create mode 100644 static/build/vendor/ionicons/@stencil/core/compiler/lib.es2020.symbol.wellknown.d.ts create mode 100644 static/build/vendor/ionicons/@stencil/core/compiler/lib.es2021.d.ts create mode 100644 static/build/vendor/ionicons/@stencil/core/compiler/lib.es2021.full.d.ts create mode 100644 static/build/vendor/ionicons/@stencil/core/compiler/lib.es2021.intl.d.ts create mode 100644 static/build/vendor/ionicons/@stencil/core/compiler/lib.es2021.promise.d.ts create mode 100644 static/build/vendor/ionicons/@stencil/core/compiler/lib.es2021.string.d.ts create mode 100644 static/build/vendor/ionicons/@stencil/core/compiler/lib.es2021.weakref.d.ts create mode 100644 static/build/vendor/ionicons/@stencil/core/compiler/lib.es2022.array.d.ts create mode 100644 static/build/vendor/ionicons/@stencil/core/compiler/lib.es2022.d.ts create mode 100644 static/build/vendor/ionicons/@stencil/core/compiler/lib.es2022.error.d.ts create mode 100644 static/build/vendor/ionicons/@stencil/core/compiler/lib.es2022.full.d.ts create mode 100644 static/build/vendor/ionicons/@stencil/core/compiler/lib.es2022.intl.d.ts create mode 100644 static/build/vendor/ionicons/@stencil/core/compiler/lib.es2022.object.d.ts create mode 100644 static/build/vendor/ionicons/@stencil/core/compiler/lib.es2022.sharedmemory.d.ts create mode 100644 static/build/vendor/ionicons/@stencil/core/compiler/lib.es2022.string.d.ts create mode 100644 static/build/vendor/ionicons/@stencil/core/compiler/lib.es5.d.ts create mode 100644 static/build/vendor/ionicons/@stencil/core/compiler/lib.es6.d.ts create mode 100644 static/build/vendor/ionicons/@stencil/core/compiler/lib.esnext.d.ts create mode 100644 static/build/vendor/ionicons/@stencil/core/compiler/lib.esnext.full.d.ts create mode 100644 static/build/vendor/ionicons/@stencil/core/compiler/lib.esnext.intl.d.ts create mode 100644 static/build/vendor/ionicons/@stencil/core/compiler/lib.esnext.promise.d.ts create mode 100644 static/build/vendor/ionicons/@stencil/core/compiler/lib.esnext.string.d.ts create mode 100644 static/build/vendor/ionicons/@stencil/core/compiler/lib.esnext.weakref.d.ts create mode 100644 static/build/vendor/ionicons/@stencil/core/compiler/lib.scripthost.d.ts create mode 100644 static/build/vendor/ionicons/@stencil/core/compiler/lib.webworker.d.ts create mode 100644 static/build/vendor/ionicons/@stencil/core/compiler/lib.webworker.importscripts.d.ts create mode 100644 static/build/vendor/ionicons/@stencil/core/compiler/lib.webworker.iterable.d.ts create mode 100644 static/build/vendor/ionicons/@stencil/core/compiler/package.json create mode 100644 static/build/vendor/ionicons/@stencil/core/compiler/stencil.d.ts create mode 100644 static/build/vendor/ionicons/@stencil/core/compiler/stencil.js create mode 100644 static/build/vendor/ionicons/@stencil/core/compiler/stencil.min.js create mode 100644 static/build/vendor/ionicons/@stencil/core/compiler/sys/in-memory-fs.d.ts create mode 100644 static/build/vendor/ionicons/@stencil/core/compiler/transpile.d.ts create mode 100644 static/build/vendor/ionicons/@stencil/core/dependencies.json create mode 100644 static/build/vendor/ionicons/@stencil/core/dev-server/client/app-error.d.ts create mode 100644 static/build/vendor/ionicons/@stencil/core/dev-server/client/events.d.ts create mode 100644 static/build/vendor/ionicons/@stencil/core/dev-server/client/hmr-components.d.ts create mode 100644 static/build/vendor/ionicons/@stencil/core/dev-server/client/hmr-external-styles.d.ts create mode 100644 static/build/vendor/ionicons/@stencil/core/dev-server/client/hmr-images.d.ts create mode 100644 static/build/vendor/ionicons/@stencil/core/dev-server/client/hmr-inline-styles.d.ts create mode 100644 static/build/vendor/ionicons/@stencil/core/dev-server/client/hmr-util.d.ts create mode 100644 static/build/vendor/ionicons/@stencil/core/dev-server/client/hmr-window.d.ts create mode 100644 static/build/vendor/ionicons/@stencil/core/dev-server/client/index.d.ts create mode 100644 static/build/vendor/ionicons/@stencil/core/dev-server/client/index.js create mode 100644 static/build/vendor/ionicons/@stencil/core/dev-server/client/logger.d.ts create mode 100644 static/build/vendor/ionicons/@stencil/core/dev-server/client/package.json create mode 100644 static/build/vendor/ionicons/@stencil/core/dev-server/client/progress.d.ts create mode 100644 static/build/vendor/ionicons/@stencil/core/dev-server/client/status.d.ts create mode 100644 static/build/vendor/ionicons/@stencil/core/dev-server/client/test/hmr-util.spec.d.ts create mode 100644 static/build/vendor/ionicons/@stencil/core/dev-server/client/test/status.spec.d.ts create mode 100644 static/build/vendor/ionicons/@stencil/core/dev-server/connector.html create mode 100644 static/build/vendor/ionicons/@stencil/core/dev-server/index.d.ts create mode 100644 static/build/vendor/ionicons/@stencil/core/dev-server/index.js create mode 100644 static/build/vendor/ionicons/@stencil/core/dev-server/open-in-editor-api.js create mode 100644 static/build/vendor/ionicons/@stencil/core/dev-server/package.json create mode 100644 static/build/vendor/ionicons/@stencil/core/dev-server/server-process.js create mode 100644 static/build/vendor/ionicons/@stencil/core/dev-server/server-worker-thread.js create mode 100644 static/build/vendor/ionicons/@stencil/core/dev-server/static/favicon.ico create mode 100644 static/build/vendor/ionicons/@stencil/core/dev-server/templates/directory-index.html create mode 100644 static/build/vendor/ionicons/@stencil/core/dev-server/templates/initial-load.html create mode 100644 static/build/vendor/ionicons/@stencil/core/dev-server/visualstudio.vbs create mode 100644 static/build/vendor/ionicons/@stencil/core/dev-server/ws.js create mode 100644 static/build/vendor/ionicons/@stencil/core/dev-server/xdg-open create mode 100644 static/build/vendor/ionicons/@stencil/core/internal/app-data/index.cjs create mode 100644 static/build/vendor/ionicons/@stencil/core/internal/app-data/index.d.ts create mode 100644 static/build/vendor/ionicons/@stencil/core/internal/app-data/index.js create mode 100644 static/build/vendor/ionicons/@stencil/core/internal/app-data/package.json create mode 100644 static/build/vendor/ionicons/@stencil/core/internal/client/css-shim.js create mode 100644 static/build/vendor/ionicons/@stencil/core/internal/client/dom.js create mode 100644 static/build/vendor/ionicons/@stencil/core/internal/client/index.js create mode 100644 static/build/vendor/ionicons/@stencil/core/internal/client/package.json create mode 100644 static/build/vendor/ionicons/@stencil/core/internal/client/patch-browser.js create mode 100644 static/build/vendor/ionicons/@stencil/core/internal/client/patch-esm.js create mode 100644 static/build/vendor/ionicons/@stencil/core/internal/client/polyfills/core-js.js create mode 100644 static/build/vendor/ionicons/@stencil/core/internal/client/polyfills/css-shim.js create mode 100644 static/build/vendor/ionicons/@stencil/core/internal/client/polyfills/dom.js create mode 100644 static/build/vendor/ionicons/@stencil/core/internal/client/polyfills/es5-html-element.js create mode 100644 static/build/vendor/ionicons/@stencil/core/internal/client/polyfills/index.js create mode 100644 static/build/vendor/ionicons/@stencil/core/internal/client/polyfills/system.js create mode 100644 static/build/vendor/ionicons/@stencil/core/internal/client/shadow-css.js create mode 100644 static/build/vendor/ionicons/@stencil/core/internal/hydrate/index.js create mode 100644 static/build/vendor/ionicons/@stencil/core/internal/hydrate/package.json create mode 100644 static/build/vendor/ionicons/@stencil/core/internal/hydrate/runner.d.ts create mode 100644 static/build/vendor/ionicons/@stencil/core/internal/hydrate/runner.js create mode 100644 static/build/vendor/ionicons/@stencil/core/internal/hydrate/shadow-css.js create mode 100644 static/build/vendor/ionicons/@stencil/core/internal/index.d.ts create mode 100644 static/build/vendor/ionicons/@stencil/core/internal/index.js create mode 100644 static/build/vendor/ionicons/@stencil/core/internal/package.json create mode 100644 static/build/vendor/ionicons/@stencil/core/internal/stencil-core/index.cjs create mode 100644 static/build/vendor/ionicons/@stencil/core/internal/stencil-core/index.d.ts create mode 100644 static/build/vendor/ionicons/@stencil/core/internal/stencil-core/index.js create mode 100644 static/build/vendor/ionicons/@stencil/core/internal/stencil-ext-modules.d.ts create mode 100644 static/build/vendor/ionicons/@stencil/core/internal/stencil-private.d.ts create mode 100644 static/build/vendor/ionicons/@stencil/core/internal/stencil-public-compiler.d.ts create mode 100644 static/build/vendor/ionicons/@stencil/core/internal/stencil-public-docs.d.ts create mode 100644 static/build/vendor/ionicons/@stencil/core/internal/stencil-public-runtime.d.ts create mode 100644 static/build/vendor/ionicons/@stencil/core/internal/testing/index.js create mode 100644 static/build/vendor/ionicons/@stencil/core/internal/testing/package.json create mode 100644 static/build/vendor/ionicons/@stencil/core/internal/testing/shadow-css.js create mode 100644 static/build/vendor/ionicons/@stencil/core/mock-doc/index.cjs create mode 100644 static/build/vendor/ionicons/@stencil/core/mock-doc/index.d.ts create mode 100644 static/build/vendor/ionicons/@stencil/core/mock-doc/index.js create mode 100644 static/build/vendor/ionicons/@stencil/core/mock-doc/package.json create mode 100644 static/build/vendor/ionicons/@stencil/core/package.json create mode 100644 static/build/vendor/ionicons/@stencil/core/readme.md create mode 100644 static/build/vendor/ionicons/@stencil/core/screenshot/compare/assets/favicon.ico create mode 100644 static/build/vendor/ionicons/@stencil/core/screenshot/compare/assets/logo.png create mode 100644 static/build/vendor/ionicons/@stencil/core/screenshot/compare/build/app.css create mode 100644 static/build/vendor/ionicons/@stencil/core/screenshot/compare/build/app.esm.js create mode 100644 static/build/vendor/ionicons/@stencil/core/screenshot/compare/build/app.js rename employee/templatetags/migrations/__init__.py => static/build/vendor/ionicons/@stencil/core/screenshot/compare/build/index.esm.js (100%) create mode 100644 static/build/vendor/ionicons/@stencil/core/screenshot/compare/build/p-081b0641.js create mode 100644 static/build/vendor/ionicons/@stencil/core/screenshot/compare/build/p-227a1e18.entry.js create mode 100644 static/build/vendor/ionicons/@stencil/core/screenshot/compare/build/p-2c298727.entry.js create mode 100644 static/build/vendor/ionicons/@stencil/core/screenshot/compare/build/p-5479268c.entry.js create mode 100644 static/build/vendor/ionicons/@stencil/core/screenshot/compare/build/p-573ec8a4.entry.js create mode 100644 static/build/vendor/ionicons/@stencil/core/screenshot/compare/build/p-6ba08604.entry.js create mode 100644 static/build/vendor/ionicons/@stencil/core/screenshot/compare/build/p-6bc63295.entry.js create mode 100644 static/build/vendor/ionicons/@stencil/core/screenshot/compare/build/p-7a3759fd.entry.js create mode 100644 static/build/vendor/ionicons/@stencil/core/screenshot/compare/build/p-7b4e3ba7.js create mode 100644 static/build/vendor/ionicons/@stencil/core/screenshot/compare/build/p-988eb362.css create mode 100644 static/build/vendor/ionicons/@stencil/core/screenshot/compare/build/p-9b6a9315.js create mode 100644 static/build/vendor/ionicons/@stencil/core/screenshot/compare/build/p-b4cc611c.entry.js create mode 100644 static/build/vendor/ionicons/@stencil/core/screenshot/compare/build/p-d1bf53f5.entry.js create mode 100644 static/build/vendor/ionicons/@stencil/core/screenshot/compare/build/p-e2efe0df.js create mode 100644 static/build/vendor/ionicons/@stencil/core/screenshot/compare/build/p-e8ca6d97.entry.js create mode 100644 static/build/vendor/ionicons/@stencil/core/screenshot/compare/build/p-ec2f13e0.entry.js create mode 100644 static/build/vendor/ionicons/@stencil/core/screenshot/compare/build/p-f0b99977.entry.js create mode 100644 static/build/vendor/ionicons/@stencil/core/screenshot/compare/build/p-f4745c2f.entry.js create mode 100644 static/build/vendor/ionicons/@stencil/core/screenshot/compare/build/p-fbbae598.js create mode 100644 static/build/vendor/ionicons/@stencil/core/screenshot/compare/host.config.json create mode 100644 static/build/vendor/ionicons/@stencil/core/screenshot/compare/index.html create mode 100644 static/build/vendor/ionicons/@stencil/core/screenshot/compare/manifest.json create mode 100644 static/build/vendor/ionicons/@stencil/core/screenshot/connector-base.d.ts create mode 100644 static/build/vendor/ionicons/@stencil/core/screenshot/connector-local.d.ts create mode 100644 static/build/vendor/ionicons/@stencil/core/screenshot/connector.js create mode 100644 static/build/vendor/ionicons/@stencil/core/screenshot/index.d.ts create mode 100644 static/build/vendor/ionicons/@stencil/core/screenshot/index.js create mode 100644 static/build/vendor/ionicons/@stencil/core/screenshot/local-connector.js create mode 100644 static/build/vendor/ionicons/@stencil/core/screenshot/package.json create mode 100644 static/build/vendor/ionicons/@stencil/core/screenshot/pixel-match.d.ts create mode 100644 static/build/vendor/ionicons/@stencil/core/screenshot/pixel-match.js create mode 100644 static/build/vendor/ionicons/@stencil/core/screenshot/screenshot-compare.d.ts create mode 100644 static/build/vendor/ionicons/@stencil/core/screenshot/screenshot-fs.d.ts create mode 100644 static/build/vendor/ionicons/@stencil/core/sys/node/autoprefixer.js create mode 100644 static/build/vendor/ionicons/@stencil/core/sys/node/glob.js create mode 100644 static/build/vendor/ionicons/@stencil/core/sys/node/graceful-fs.js create mode 100644 static/build/vendor/ionicons/@stencil/core/sys/node/index.d.ts create mode 100644 static/build/vendor/ionicons/@stencil/core/sys/node/index.js create mode 100644 static/build/vendor/ionicons/@stencil/core/sys/node/node-fetch.js create mode 100644 static/build/vendor/ionicons/@stencil/core/sys/node/package.json create mode 100644 static/build/vendor/ionicons/@stencil/core/sys/node/prompts.js create mode 100644 static/build/vendor/ionicons/@stencil/core/sys/node/worker.js create mode 100644 static/build/vendor/ionicons/@stencil/core/testing/index.d.ts create mode 100644 static/build/vendor/ionicons/@stencil/core/testing/index.js create mode 100644 static/build/vendor/ionicons/@stencil/core/testing/jest-environment.js create mode 100644 static/build/vendor/ionicons/@stencil/core/testing/jest-preprocessor.js create mode 100644 static/build/vendor/ionicons/@stencil/core/testing/jest-preset.js create mode 100644 static/build/vendor/ionicons/@stencil/core/testing/jest-runner.js create mode 100644 static/build/vendor/ionicons/@stencil/core/testing/jest-setuptestframework.js create mode 100644 static/build/vendor/ionicons/@stencil/core/testing/jest/jest-config.d.ts create mode 100644 static/build/vendor/ionicons/@stencil/core/testing/jest/jest-environment.d.ts create mode 100644 static/build/vendor/ionicons/@stencil/core/testing/jest/jest-preprocessor.d.ts create mode 100644 static/build/vendor/ionicons/@stencil/core/testing/jest/jest-runner.d.ts create mode 100644 static/build/vendor/ionicons/@stencil/core/testing/jest/jest-screenshot.d.ts create mode 100644 static/build/vendor/ionicons/@stencil/core/testing/jest/jest-serializer.d.ts create mode 100644 static/build/vendor/ionicons/@stencil/core/testing/jest/jest-setup-test-framework.d.ts create mode 100644 static/build/vendor/ionicons/@stencil/core/testing/jest/test/jest-config.spec.d.ts create mode 100644 static/build/vendor/ionicons/@stencil/core/testing/jest/test/jest-preprocessor.spec.d.ts create mode 100644 static/build/vendor/ionicons/@stencil/core/testing/jest/test/jest-runner.spec.d.ts create mode 100644 static/build/vendor/ionicons/@stencil/core/testing/jest/test/jest-serializer.spec.d.ts create mode 100644 static/build/vendor/ionicons/@stencil/core/testing/matchers/attributes.d.ts create mode 100644 static/build/vendor/ionicons/@stencil/core/testing/matchers/class-list.d.ts create mode 100644 static/build/vendor/ionicons/@stencil/core/testing/matchers/events.d.ts create mode 100644 static/build/vendor/ionicons/@stencil/core/testing/matchers/html.d.ts create mode 100644 static/build/vendor/ionicons/@stencil/core/testing/matchers/index.d.ts create mode 100644 static/build/vendor/ionicons/@stencil/core/testing/matchers/screenshot.d.ts create mode 100644 static/build/vendor/ionicons/@stencil/core/testing/matchers/text.d.ts create mode 100644 static/build/vendor/ionicons/@stencil/core/testing/mock-fetch.d.ts create mode 100644 static/build/vendor/ionicons/@stencil/core/testing/mocks.d.ts create mode 100644 static/build/vendor/ionicons/@stencil/core/testing/package.json create mode 100644 static/build/vendor/ionicons/@stencil/core/testing/puppeteer/index.d.ts create mode 100644 static/build/vendor/ionicons/@stencil/core/testing/puppeteer/puppeteer-browser.d.ts create mode 100644 static/build/vendor/ionicons/@stencil/core/testing/puppeteer/puppeteer-declarations.d.ts create mode 100644 static/build/vendor/ionicons/@stencil/core/testing/puppeteer/puppeteer-element.d.ts create mode 100644 static/build/vendor/ionicons/@stencil/core/testing/puppeteer/puppeteer-emulate.d.ts create mode 100644 static/build/vendor/ionicons/@stencil/core/testing/puppeteer/puppeteer-events.d.ts create mode 100644 static/build/vendor/ionicons/@stencil/core/testing/puppeteer/puppeteer-page.d.ts create mode 100644 static/build/vendor/ionicons/@stencil/core/testing/puppeteer/puppeteer-screenshot.d.ts create mode 100644 static/build/vendor/ionicons/@stencil/core/testing/reset-build-conditionals.d.ts create mode 100644 static/build/vendor/ionicons/@stencil/core/testing/spec-page.d.ts create mode 100644 static/build/vendor/ionicons/@stencil/core/testing/test-transpile.d.ts create mode 100644 static/build/vendor/ionicons/@stencil/core/testing/test/testing-utils.spec.d.ts create mode 100644 static/build/vendor/ionicons/@stencil/core/testing/testing-logger.d.ts create mode 100644 static/build/vendor/ionicons/@stencil/core/testing/testing-sys.d.ts create mode 100644 static/build/vendor/ionicons/@stencil/core/testing/testing-utils.d.ts create mode 100644 static/build/vendor/ionicons/@stencil/core/testing/testing.d.ts mode change 100755 => 100644 static/htmx/htmx.min.js mode change 100755 => 100644 static/images/ui/assets.svg mode change 100755 => 100644 static/images/ui/at-circle.svg mode change 100755 => 100644 static/images/ui/attendances.svg mode change 100755 => 100644 static/images/ui/auth-logo.png mode change 100755 => 100644 static/images/ui/caret-down.svg mode change 100755 => 100644 static/images/ui/cog.svg mode change 100755 => 100644 static/images/ui/dashboard.svg mode change 100755 => 100644 static/images/ui/dots-v.svg mode change 100755 => 100644 static/images/ui/employees.svg mode change 100755 => 100644 static/images/ui/exciting.svg mode change 100755 => 100644 static/images/ui/filter.svg mode change 100755 => 100644 static/images/ui/grid.svg mode change 100755 => 100644 static/images/ui/happy.svg mode change 100755 => 100644 static/images/ui/leave.svg mode change 100755 => 100644 static/images/ui/menu.svg create mode 100644 static/images/ui/not-found.svg mode change 100755 => 100644 static/images/ui/notification.svg mode change 100755 => 100644 static/images/ui/online_party.svg mode change 100755 => 100644 static/images/ui/pms.svg mode change 100755 => 100644 static/images/ui/recruitment.svg mode change 100755 => 100644 static/images/ui/rocket.svg mode change 100755 => 100644 static/images/ui/search.svg mode change 100755 => 100644 static/images/ui/share.svg mode change 100755 => 100644 static/images/ui/trash.svg mode change 100755 => 100644 static/images/ui/user.jpg mode change 100755 => 100644 static/images/upload/sample-image.png mode change 100755 => 100644 static/images/upload/userphoto.png mode change 100755 => 100644 static/index/demo.js mode change 100755 => 100644 static/index/index.js delete mode 100755 static/ionicons/npm/components/index.cjs.js delete mode 100755 static/ionicons/npm/components/index.d.ts delete mode 100755 static/ionicons/npm/components/index.js delete mode 100755 static/ionicons/npm/components/ion-icon.d.ts delete mode 100755 static/ionicons/npm/components/ion-icon.js delete mode 100755 static/ionicons/npm/components/package.json delete mode 100755 static/ionicons/npm/icons/index.d.ts delete mode 100755 static/ionicons/npm/icons/index.js delete mode 100755 static/ionicons/npm/icons/index.mjs delete mode 100755 static/ionicons/npm/icons/package.json delete mode 100755 static/ionicons/npm/package.json delete mode 100755 static/ionicons/npm/readme.md mode change 100755 => 100644 static/jquery/ajax.js mode change 100755 => 100644 static/jquery/jquery.min.js mode change 100755 => 100644 static/jquery/jqueryui.css mode change 100755 => 100644 static/jquery/jqueryui.js mode change 100755 => 100644 static/onboarding/onboarding-1.html mode change 100755 => 100644 static/onboarding/onboarding-2.html mode change 100755 => 100644 static/onboarding/onboarding-3.html mode change 100755 => 100644 static/onboarding/onboarding.html mode change 100755 => 100644 static/pipeline/pipeline.js mode change 100755 => 100644 static/proper/proper.js mode change 100755 => 100644 static/recruitment/candidate.js mode change 100755 => 100644 static/recruitment/recruitment copy.js mode change 100755 => 100644 static/recruitment/recruitment.js mode change 100755 => 100644 static/registration/registration.html mode change 100755 => 100644 static/src/js/index.js mode change 100755 => 100644 static/src/js/modules/dashboard/Calendar.js mode change 100755 => 100644 static/src/js/modules/dashboard/Dashboard.js mode change 100755 => 100644 static/src/js/modules/dashboard/ImageSelect.js mode change 100755 => 100644 static/src/js/modules/dashboard/ImageUpload.js mode change 100755 => 100644 static/src/js/modules/dashboard/Inputs.js mode change 100755 => 100644 static/src/js/modules/dashboard/Kanban.js mode change 100755 => 100644 static/src/js/modules/dashboard/LoadLayout.js mode change 100755 => 100644 static/src/js/modules/dashboard/ModalDialog.js mode change 100755 => 100644 static/src/js/modules/dashboard/Recruitment.js mode change 100755 => 100644 static/src/js/modules/dashboard/ResizeInput.js mode change 100755 => 100644 static/src/js/modules/dashboard/Specifics.js mode change 100755 => 100644 static/src/js/modules/dashboard/Tables.js mode change 100755 => 100644 static/src/js/modules/dashboard/Tabs.js mode change 100755 => 100644 static/src/js/modules/dashboard/Tooltip.js mode change 100755 => 100644 static/src/js/modules/vendors/niceSelect.js mode change 100755 => 100644 static/src/scss/abstracts/README.md mode change 100755 => 100644 static/src/scss/abstracts/_functions.scss mode change 100755 => 100644 static/src/scss/abstracts/_mixins.scss mode change 100755 => 100644 static/src/scss/abstracts/_variables.scss mode change 100755 => 100644 static/src/scss/base/README.md mode change 100755 => 100644 static/src/scss/base/_base.scss mode change 100755 => 100644 static/src/scss/base/_fonts.scss mode change 100755 => 100644 static/src/scss/base/_helpers.scss mode change 100755 => 100644 static/src/scss/base/_typography.scss mode change 100755 => 100644 static/src/scss/components/README.md mode change 100755 => 100644 static/src/scss/components/_accordion.scss mode change 100755 => 100644 static/src/scss/components/_alert.scss mode change 100755 => 100644 static/src/scss/components/_badges.scss mode change 100755 => 100644 static/src/scss/components/_button.scss mode change 100755 => 100644 static/src/scss/components/_card.scss mode change 100755 => 100644 static/src/scss/components/_dropdown.scss mode change 100755 => 100644 static/src/scss/components/_input.scss mode change 100755 => 100644 static/src/scss/components/_kanban.scss mode change 100755 => 100644 static/src/scss/components/_modal.scss mode change 100755 => 100644 static/src/scss/components/_pagination.scss mode change 100755 => 100644 static/src/scss/components/_profile.scss mode change 100755 => 100644 static/src/scss/components/_progress.scss mode change 100755 => 100644 static/src/scss/components/_sidebar.scss mode change 100755 => 100644 static/src/scss/components/_table.scss mode change 100755 => 100644 static/src/scss/components/_tabs.scss mode change 100755 => 100644 static/src/scss/components/_timeoff.scss mode change 100755 => 100644 static/src/scss/components/_tooltip.scss mode change 100755 => 100644 static/src/scss/layout/README.md mode change 100755 => 100644 static/src/scss/layout/_footer.scss mode change 100755 => 100644 static/src/scss/layout/_general.scss mode change 100755 => 100644 static/src/scss/layout/_header.scss mode change 100755 => 100644 static/src/scss/main.scss mode change 100755 => 100644 static/src/scss/overrides/_datepicker.scss mode change 100755 => 100644 static/src/scss/overrides/_fullcalendar.scss mode change 100755 => 100644 static/src/scss/pages/README.md mode change 100755 => 100644 static/src/scss/pages/_auth.scss mode change 100755 => 100644 static/src/scss/pages/_dashboard.scss mode change 100755 => 100644 static/src/scss/pages/_feedback.scss mode change 100755 => 100644 static/src/scss/pages/_onboarding.scss mode change 100755 => 100644 static/src/scss/pages/_profile.scss mode change 100755 => 100644 static/src/scss/prepros-6.config mode change 100755 => 100644 static/src/scss/prepros.config mode change 100755 => 100644 static/src/scss/themes/README.md mode change 100755 => 100644 static/src/scss/themes/_default.scss mode change 100755 => 100644 static/src/scss/vendors/README.md mode change 100755 => 100644 static/src/scss/vendors/_normalize.scss mode change 100755 => 100644 static/src/scss/vendors/bootstrap/_bootstrap.scss mode change 100755 => 100644 static/src/scss/vendors/bootstrap/_containers.scss mode change 100755 => 100644 static/src/scss/vendors/bootstrap/_functions.scss mode change 100755 => 100644 static/src/scss/vendors/bootstrap/_grid.scss mode change 100755 => 100644 static/src/scss/vendors/bootstrap/_helpers.scss mode change 100755 => 100644 static/src/scss/vendors/bootstrap/_mixins.scss mode change 100755 => 100644 static/src/scss/vendors/bootstrap/_reboot.scss mode change 100755 => 100644 static/src/scss/vendors/bootstrap/_root.scss mode change 100755 => 100644 static/src/scss/vendors/bootstrap/_type.scss mode change 100755 => 100644 static/src/scss/vendors/bootstrap/_utilities.scss mode change 100755 => 100644 static/src/scss/vendors/bootstrap/_variables.scss mode change 100755 => 100644 static/src/scss/vendors/bootstrap/bootstrap-grid.scss mode change 100755 => 100644 static/src/scss/vendors/bootstrap/bootstrap-reboot.scss mode change 100755 => 100644 static/src/scss/vendors/bootstrap/bootstrap-utilities.scss mode change 100755 => 100644 static/src/scss/vendors/bootstrap/helpers/_clearfix.scss mode change 100755 => 100644 static/src/scss/vendors/bootstrap/helpers/_colored-links.scss mode change 100755 => 100644 static/src/scss/vendors/bootstrap/helpers/_position.scss mode change 100755 => 100644 static/src/scss/vendors/bootstrap/helpers/_ratio.scss mode change 100755 => 100644 static/src/scss/vendors/bootstrap/helpers/_stretched-link.scss mode change 100755 => 100644 static/src/scss/vendors/bootstrap/helpers/_text-truncation.scss mode change 100755 => 100644 static/src/scss/vendors/bootstrap/helpers/_visually-hidden.scss mode change 100755 => 100644 static/src/scss/vendors/bootstrap/mixins/_alert.scss mode change 100755 => 100644 static/src/scss/vendors/bootstrap/mixins/_border-radius.scss mode change 100755 => 100644 static/src/scss/vendors/bootstrap/mixins/_box-shadow.scss mode change 100755 => 100644 static/src/scss/vendors/bootstrap/mixins/_breakpoints.scss mode change 100755 => 100644 static/src/scss/vendors/bootstrap/mixins/_buttons.scss mode change 100755 => 100644 static/src/scss/vendors/bootstrap/mixins/_caret.scss mode change 100755 => 100644 static/src/scss/vendors/bootstrap/mixins/_clearfix.scss mode change 100755 => 100644 static/src/scss/vendors/bootstrap/mixins/_color-scheme.scss mode change 100755 => 100644 static/src/scss/vendors/bootstrap/mixins/_container.scss mode change 100755 => 100644 static/src/scss/vendors/bootstrap/mixins/_deprecate.scss mode change 100755 => 100644 static/src/scss/vendors/bootstrap/mixins/_forms.scss mode change 100755 => 100644 static/src/scss/vendors/bootstrap/mixins/_gradients.scss mode change 100755 => 100644 static/src/scss/vendors/bootstrap/mixins/_grid.scss mode change 100755 => 100644 static/src/scss/vendors/bootstrap/mixins/_image.scss mode change 100755 => 100644 static/src/scss/vendors/bootstrap/mixins/_list-group.scss mode change 100755 => 100644 static/src/scss/vendors/bootstrap/mixins/_lists.scss mode change 100755 => 100644 static/src/scss/vendors/bootstrap/mixins/_pagination.scss mode change 100755 => 100644 static/src/scss/vendors/bootstrap/mixins/_reset-text.scss mode change 100755 => 100644 static/src/scss/vendors/bootstrap/mixins/_resize.scss mode change 100755 => 100644 static/src/scss/vendors/bootstrap/mixins/_table-variants.scss mode change 100755 => 100644 static/src/scss/vendors/bootstrap/mixins/_text-truncate.scss mode change 100755 => 100644 static/src/scss/vendors/bootstrap/mixins/_transition.scss mode change 100755 => 100644 static/src/scss/vendors/bootstrap/mixins/_utilities.scss mode change 100755 => 100644 static/src/scss/vendors/bootstrap/mixins/_visually-hidden.scss mode change 100755 => 100644 static/src/scss/vendors/bootstrap/utilities/_api.scss mode change 100755 => 100644 static/src/scss/vendors/bootstrap/vendor/_rfs.scss mode change 100755 => 100644 static/static/images/ui/assets.svg mode change 100755 => 100644 static/static/images/ui/at-circle.svg mode change 100755 => 100644 static/static/images/ui/attendances.svg mode change 100755 => 100644 static/static/images/ui/caret-down.svg mode change 100755 => 100644 static/static/images/ui/cog.svg mode change 100755 => 100644 static/static/images/ui/dashboard.svg mode change 100755 => 100644 static/static/images/ui/dots-v.svg mode change 100755 => 100644 static/static/images/ui/employees.svg mode change 100755 => 100644 static/static/images/ui/exciting.svg mode change 100755 => 100644 static/static/images/ui/filter.svg mode change 100755 => 100644 static/static/images/ui/grid.svg mode change 100755 => 100644 static/static/images/ui/happy.svg mode change 100755 => 100644 static/static/images/ui/leave.svg mode change 100755 => 100644 static/static/images/ui/menu.svg mode change 100755 => 100644 static/static/images/ui/notification.svg mode change 100755 => 100644 static/static/images/ui/online_party.svg mode change 100755 => 100644 static/static/images/ui/pms.svg mode change 100755 => 100644 static/static/images/ui/recruitment.svg mode change 100755 => 100644 static/static/images/ui/rocket.svg mode change 100755 => 100644 static/static/images/ui/search.svg mode change 100755 => 100644 static/static/images/ui/share.svg mode change 100755 => 100644 static/static/images/ui/trash.svg mode change 100755 => 100644 static/static/images/ui/user.jpg mode change 100755 => 100644 static/static/images/upload/sample-image.png mode change 100755 => 100644 static/static/images/upload/userphoto.png diff --git a/attendance/admin.py b/attendance/admin.py index 505a1861e..281e1ccee 100644 --- a/attendance/admin.py +++ b/attendance/admin.py @@ -1,8 +1,20 @@ +""" +admin.py + +This page is used to register attendance models with admins site. +""" from django.contrib import admin -from .models import Attendance, AttendanceActivity, AttendanceOverTime, AttendanceLateComeEarlyOut,AttendanceValidationCondition +from .models import ( + Attendance, + AttendanceActivity, + AttendanceOverTime, + AttendanceLateComeEarlyOut, + AttendanceValidationCondition, +) + # Register your models here. admin.site.register(Attendance) admin.site.register(AttendanceActivity) admin.site.register(AttendanceOverTime) admin.site.register(AttendanceLateComeEarlyOut) -admin.site.register(AttendanceValidationCondition) \ No newline at end of file +admin.site.register(AttendanceValidationCondition) diff --git a/attendance/filters.py b/attendance/filters.py index c0965c0b2..320642cfc 100644 --- a/attendance/filters.py +++ b/attendance/filters.py @@ -1,377 +1,508 @@ -import django_filters -from attendance.models import Attendance, AttendanceOverTime, AttendanceLateComeEarlyOut, AttendanceActivity +""" +filters.py + +This page is used to register filter for attendance models + +""" from django.forms import DateTimeInput from django import forms -from django.forms import TextInput,ModelChoiceField,Select +import django_filters from horilla.filters import filter_by_name -from django_filters import FilterSet, DateFilter +from attendance.models import ( + Attendance, + AttendanceOverTime, + AttendanceLateComeEarlyOut, + AttendanceActivity, +) class FilterSet(django_filters.FilterSet): + """ + Custom FilterSet class that applies specific CSS classes to filter + widgets. + + The class applies CSS classes to different types of filter widgets, + such as NumberInput, EmailInput, TextInput, Select, Textarea, + CheckboxInput, CheckboxSelectMultiple, and ModelChoiceField. The + CSS classes are applied to enhance the styling and behavior of the + filter widgets. + """ + def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) - for field_name, field in self.filters.items(): + for field_name, _ in self.filters.items(): filter_widget = self.filters[field_name] widget = filter_widget.field.widget if isinstance(self.filters[field_name], DurationInSecondsFilter): - filter_widget.field.widget.attrs.update({'class':'oh-input w-100','placeholder':'00:00'}) - elif isinstance(widget, (forms.NumberInput, forms.EmailInput,forms.TextInput)): - filter_widget.field.widget.attrs.update({'class': 'oh-input w-100'}) - elif isinstance(widget,(forms.Select,)): - filter_widget.field.widget.attrs.update({'class': 'oh-select oh-select-2 select2-hidden-accessible',}) - elif isinstance(widget,(forms.Textarea)): - filter_widget.field.widget.attrs.update({'class': 'oh-input w-100'}) - elif isinstance(widget, (forms.CheckboxInput,forms.CheckboxSelectMultiple,)): - filter_widget.field.widget.attrs.update({'class': 'oh-switch__checkbox'}) - elif isinstance(widget,(forms.ModelChoiceField)): - filter_widget.field.widget.attrs.update({'class': 'oh-select oh-select-2 select2-hidden-accessible',}) - + filter_widget.field.widget.attrs.update( + {"class": "oh-input w-100", "placeholder": "00:00"} + ) + elif isinstance( + widget, (forms.NumberInput, forms.EmailInput, forms.TextInput) + ): + filter_widget.field.widget.attrs.update({"class": "oh-input w-100"}) + elif isinstance(widget, (forms.Select,)): + filter_widget.field.widget.attrs.update( + { + "class": "oh-select oh-select-2 select2-hidden-accessible", + } + ) + elif isinstance(widget, (forms.Textarea)): + filter_widget.field.widget.attrs.update({"class": "oh-input w-100"}) + elif isinstance( + widget, + ( + forms.CheckboxInput, + forms.CheckboxSelectMultiple, + ), + ): + filter_widget.field.widget.attrs.update( + {"class": "oh-switch__checkbox"} + ) + elif isinstance(widget, (forms.ModelChoiceField)): + filter_widget.field.widget.attrs.update( + { + "class": "oh-select oh-select-2 select2-hidden-accessible", + } + ) class DurationInSecondsFilter(django_filters.CharFilter): + """ + Custom CharFilter class that applies specific filter process. + """ + def filter(self, qs, value): + """ + FilterSet filter method + + Args: + qs (self): FilterSet instance + value (str): duration formated string + + Returns: + qs: queryset object + """ if value: - ftr = [3600,60,1] - duration_sec = sum(a*b for a,b in zip(ftr, map(int,value.split(':')))) - lookup = self.lookup_expr or 'exact' + ftr = [3600, 60, 1] + duration_sec = sum(a * b for a, b in zip(ftr, map(int, value.split(":")))) + lookup = self.lookup_expr or "exact" return qs.filter(**{f"{self.field_name}__{lookup}": duration_sec}) return qs class AttendanceOverTimeFilter(FilterSet): + """ + Filter set class for AttendanceOverTime model + + Args: + FilterSet (class): custom filter set class to apply styling + """ + search = django_filters.CharFilter(method=filter_by_name) - hour_account__gte = DurationInSecondsFilter(field_name = 'hour_account_second',lookup_expr='gte') - hour_account__lte = DurationInSecondsFilter(field_name = 'hour_account_second',lookup_expr='lte') - overtime__gte = DurationInSecondsFilter(field_name = 'overtime_second',lookup_expr='gte') - overtime__lte = DurationInSecondsFilter(field_name = 'overtime_second',lookup_expr='lte') - month = django_filters.CharFilter(field_name='month',lookup_expr='icontains') + hour_account__gte = DurationInSecondsFilter( + field_name="hour_account_second", lookup_expr="gte" + ) + hour_account__lte = DurationInSecondsFilter( + field_name="hour_account_second", lookup_expr="lte" + ) + overtime__gte = DurationInSecondsFilter( + field_name="overtime_second", lookup_expr="gte" + ) + overtime__lte = DurationInSecondsFilter( + field_name="overtime_second", lookup_expr="lte" + ) + month = django_filters.CharFilter(field_name="month", lookup_expr="icontains") + class Meta: + """ + Meta class to add additional options + """ + model = AttendanceOverTime fields = [ - 'employee_id', - 'month', - 'overtime', - 'hour_account', - 'year', - 'employee_id__employee_work_info__department_id', - 'employee_id__employee_work_info__company_id', - 'employee_id__employee_work_info__job_position_id', - 'employee_id__employee_work_info__location', - 'employee_id__employee_work_info__reporting_manager_id', - 'employee_id__employee_work_info__shift_id', - 'employee_id__employee_work_info__work_type_id', - ] + "employee_id", + "month", + "overtime", + "hour_account", + "year", + "employee_id__employee_work_info__department_id", + "employee_id__employee_work_info__company_id", + "employee_id__employee_work_info__job_position_id", + "employee_id__employee_work_info__location", + "employee_id__employee_work_info__reporting_manager_id", + "employee_id__employee_work_info__shift_id", + "employee_id__employee_work_info__work_type_id", + ] def __init__(self, data=None, queryset=None, *, request=None, prefix=None): - super(AttendanceOverTimeFilter, self).__init__(data=data, queryset=queryset, request=request, prefix=prefix) - - + super().__init__(data=data, queryset=queryset, request=request, prefix=prefix) + + class LateComeEarlyOutFilter(FilterSet): search = django_filters.CharFilter(method=filter_by_name) - attendance_date__gte = django_filters.DateFilter( - field_name='attendance_id__attendance_date', - lookup_expr='gte', - widget=forms.DateInput(attrs={'type': 'date'}) + field_name="attendance_id__attendance_date", + lookup_expr="gte", + widget=forms.DateInput(attrs={"type": "date"}), ) attendance_date__lte = django_filters.DateFilter( - field_name='attendance_id__attendance_date', - lookup_expr='lte', - widget=forms.DateInput(attrs={'type': 'date'}) + field_name="attendance_id__attendance_date", + lookup_expr="lte", + widget=forms.DateInput(attrs={"type": "date"}), ) - - attendance_clock_in__lte = DateFilter( - field_name='attendance_id__attendance_clock_in', - widget=forms.DateInput(attrs={'type': 'time'}), - lookup_expr='lte', # add lookup expression here + attendance_clock_in__lte = django_filters.TimeFilter( + field_name="attendance_id__attendance_clock_in", + widget=forms.TimeInput(attrs={"type": "time"}), + lookup_expr="lte", ) - attendance_clock_in__gte = DateFilter( - field_name = 'attendance_id__attendance_clock_in', - widget=forms.DateInput(attrs={'type': 'time'}), - lookup_expr='gte', # add lookup expression here + attendance_clock_in__gte = django_filters.TimeFilter( + field_name="attendance_id__attendance_clock_in", + widget=forms.TimeInput(attrs={"type": "time"}), + lookup_expr="gte", ) - attendance_clock_out__gte = DateFilter( - field_name = 'attendance_id__attendance_clock_out', - widget=forms.DateInput(attrs={'type': 'time'}), - lookup_expr='gte', # add lookup expression here + attendance_clock_out__gte = django_filters.TimeFilter( + field_name="attendance_id__attendance_clock_out", + widget=forms.TimeInput(attrs={"type": "time"}), + lookup_expr="gte", ) - attendance_clock_out__lte = DateFilter( - field_name = 'attendance_id__attendance_clock_out', - widget=forms.DateInput(attrs={'type': 'time'}), - lookup_expr='lte', # add lookup expression here + attendance_clock_out__lte = django_filters.TimeFilter( + field_name="attendance_id__attendance_clock_out", + widget=forms.TimeInput(attrs={"type": "time"}), + lookup_expr="lte", ) - attendance_clock_in = DateFilter( - field_name = 'attendance_id__attendance_clock_in', - widget=forms.DateInput(attrs={'type': 'time'}), - # add lookup expression here + attendance_clock_in = django_filters.TimeFilter( + field_name="attendance_id__attendance_clock_in", + widget=forms.TimeInput(attrs={"type": "time"}), + lookup_expr="exact", ) - attendance_clock_out = DateFilter( - field_name = 'attendance_id__attendance_clock_out', - widget=forms.DateInput(attrs={'type': 'time'}), - # add lookup expression here + attendance_clock_out = django_filters.TimeFilter( + field_name="attendance_id__attendance_clock_out", + widget=forms.TimeInput(attrs={"type": "time"}), + lookup_expr="exact", ) - attendance_date = DateFilter( - field_name = 'attendance_id__attendance_date', - widget=forms.DateInput(attrs={'type': 'date'}), + attendance_date = django_filters.DateFilter( + field_name="attendance_id__attendance_date", + widget=forms.DateInput(attrs={"type": "date"}), + ) + overtime_second__lte = DurationInSecondsFilter( + field_name="overtime_second", lookup_expr="lte" + ) + overtime_second__gte = DurationInSecondsFilter( + field_name="overtime_second", lookup_expr="gte" + ) + at_work_second__lte = DurationInSecondsFilter( + field_name="at_work_second", lookup_expr="lte" + ) + at_work_second__gte = DurationInSecondsFilter( + field_name="at_work_second", lookup_expr="gte" ) - at_work_second__lte = DurationInSecondsFilter(field_name = 'at_work_second',lookup_expr='lte') - at_work_second__gte = DurationInSecondsFilter(field_name = 'at_work_second',lookup_expr='gte') - overtime_second__lte = DurationInSecondsFilter(field_name ='overtime_second',lookup_expr='lte') - overtime_second__gte = DurationInSecondsFilter(field_name ='overtime_second',lookup_expr='gte') class Meta: model = AttendanceLateComeEarlyOut fields = [ - 'employee_id', - 'type', - 'attendance_id__minimum_hour', - 'attendance_id__attendance_worked_hour', - 'attendance_id__attendance_overtime_approve', - 'attendance_id__attendance_validated', - 'employee_id__employee_work_info__department_id', - 'employee_id__employee_work_info__company_id', - 'employee_id__employee_work_info__job_position_id', - 'employee_id__employee_work_info__location', - 'employee_id__employee_work_info__reporting_manager_id', - 'attendance_id__shift_id', - 'attendance_id__work_type_id', - 'attendance_date__gte', - 'attendance_date__lte', - 'attendance_clock_in__lte', - 'attendance_clock_in__gte', - 'attendance_clock_out__gte', - 'attendance_clock_out__lte', - 'attendance_clock_in', - 'attendance_clock_out', - 'attendance_date', - + "employee_id", + "type", + "attendance_id__minimum_hour", + "attendance_id__attendance_worked_hour", + "attendance_id__attendance_overtime_approve", + "attendance_id__attendance_validated", + "employee_id__employee_work_info__department_id", + "employee_id__employee_work_info__company_id", + "employee_id__employee_work_info__job_position_id", + "employee_id__employee_work_info__location", + "employee_id__employee_work_info__reporting_manager_id", + "attendance_id__shift_id", + "attendance_id__work_type_id", + "attendance_date__gte", + "attendance_date__lte", + "attendance_clock_in__lte", + "attendance_clock_in__gte", + "attendance_clock_out__gte", + "attendance_clock_out__lte", + "attendance_clock_in", + "attendance_clock_out", + "attendance_date", ] - class AttendanceActivityFilter(FilterSet): + """ + Filter set class for AttendanceActivity model + + Args: + FilterSet (class): custom filter set class to apply styling + """ + search = django_filters.CharFilter(method=filter_by_name) - + attendance_date = django_filters.DateFilter( - field_name='attendance_date', - widget=forms.DateInput(attrs={'type': 'date'}) + field_name="attendance_date", widget=forms.DateInput(attrs={"type": "date"}) ) attendance_date_from = django_filters.DateFilter( - field_name='attendance_date', - lookup_expr='gte', - widget=forms.DateInput(attrs={'type': 'date'}) + field_name="attendance_date", + lookup_expr="gte", + widget=forms.DateInput(attrs={"type": "date"}), ) attendance_date_till = django_filters.DateFilter( - field_name='attendance_date', - lookup_expr='lte', - widget=forms.DateInput(attrs={'type': 'date'}) + field_name="attendance_date", + lookup_expr="lte", + widget=forms.DateInput(attrs={"type": "date"}), ) - in_form= django_filters.DateFilter( - field_name='clock_in', - lookup_expr='gte', - widget=forms.DateInput(attrs={'type': 'time'}) + in_form = django_filters.DateFilter( + field_name="clock_in", + lookup_expr="gte", + widget=forms.DateInput(attrs={"type": "time"}), ) - out_form= django_filters.DateFilter( - field_name='clock_out', - lookup_expr='gte', - widget=forms.DateInput(attrs={'type': 'time'}) + out_form = django_filters.DateFilter( + field_name="clock_out", + lookup_expr="gte", + widget=forms.DateInput(attrs={"type": "time"}), ) in_till = django_filters.DateFilter( - field_name='clock_in', - lookup_expr='lte', - widget=forms.DateInput(attrs={'type': 'time'}) + field_name="clock_in", + lookup_expr="lte", + widget=forms.DateInput(attrs={"type": "time"}), ) - out_till= django_filters.DateFilter( - field_name='clock_out', - lookup_expr='lte', - widget=forms.DateInput(attrs={'type': 'time'}) + out_till = django_filters.DateFilter( + field_name="clock_out", + lookup_expr="lte", + widget=forms.DateInput(attrs={"type": "time"}), ) clock_in_date = django_filters.DateFilter( - field_name='clock_in_date', - widget=forms.DateInput(attrs={'type': 'date'}) + field_name="clock_in_date", widget=forms.DateInput(attrs={"type": "date"}) ) clock_out_date = django_filters.DateFilter( - field_name='clock_out_date', - widget=forms.DateInput(attrs={'type': 'date'}) + field_name="clock_out_date", widget=forms.DateInput(attrs={"type": "date"}) ) class Meta: - fields = [ - 'employee_id', - 'attendance_date', - 'attendance_date_from', - 'attendance_date_till', - 'in_form', - 'in_till', - 'out_form', - 'shift_day', - 'out_till', - 'clock_in_date', - 'clock_out_date', - 'employee_id__employee_work_info__department_id', - 'employee_id__employee_work_info__company_id', - 'employee_id__employee_work_info__shift_id', - 'employee_id__employee_work_info__work_type_id', - 'employee_id__employee_work_info__job_position_id', - 'employee_id__employee_work_info__location', - 'employee_id__employee_work_info__reporting_manager_id', + """ + Meta class to add additional options + """ + fields = [ + "employee_id", + "attendance_date", + "attendance_date_from", + "attendance_date_till", + "in_form", + "in_till", + "out_form", + "shift_day", + "out_till", + "clock_in_date", + "clock_out_date", + "employee_id__employee_work_info__department_id", + "employee_id__employee_work_info__company_id", + "employee_id__employee_work_info__shift_id", + "employee_id__employee_work_info__work_type_id", + "employee_id__employee_work_info__job_position_id", + "employee_id__employee_work_info__location", + "employee_id__employee_work_info__reporting_manager_id", ] model = AttendanceActivity - - class AttendanceFilters(FilterSet): + """ + Filter set class for Attendance model + + Args: + FilterSet (class): custom filter set class to apply styling + """ + search = django_filters.CharFilter(method=filter_by_name) attendance_date__gte = django_filters.DateFilter( - field_name='attendance_date', - lookup_expr='gte', - widget=forms.DateInput(attrs={'type': 'date'}) + field_name="attendance_date", + lookup_expr="gte", + widget=forms.DateInput(attrs={"type": "date"}), ) attendance_date__lte = django_filters.DateFilter( - field_name='attendance_date', - lookup_expr='lte', - widget=forms.DateInput(attrs={'type': 'date'}) + field_name="attendance_date", + lookup_expr="lte", + widget=forms.DateInput(attrs={"type": "date"}), ) - attendance_clock_in__lte = DateFilter( - widget=forms.DateInput(attrs={'type': 'time'}), - lookup_expr='lte', # add lookup expression here + attendance_clock_in__lte = django_filters.TimeFilter( + field_name="attendance_clock_in", + widget=forms.TimeInput(attrs={"type": "time"}), + lookup_expr="lte", ) - attendance_clock_in__gte = DateFilter( - widget=forms.DateInput(attrs={'type': 'time'}), - lookup_expr='gte', # add lookup expression here + attendance_clock_in__gte = django_filters.TimeFilter( + field_name="attendance_clock_in", + widget=forms.TimeInput(attrs={"type": "time"}), + lookup_expr="gte", ) - attendance_clock_out__gte = DateFilter( - widget=forms.DateInput(attrs={'type': 'time'}), - lookup_expr='gte', # add lookup expression here + attendance_clock_out__gte = django_filters.TimeFilter( + field_name="attendance_clock_out", + widget=forms.TimeInput(attrs={"type": "time"}), + lookup_expr="gte", ) - attendance_clock_out__lte = DateFilter( - widget=forms.DateInput(attrs={'type': 'time'}), - lookup_expr='lte', # add lookup expression here + attendance_clock_out__lte = django_filters.TimeFilter( + field_name="attendance_clock_out", + widget=forms.TimeInput(attrs={"type": "time"}), + lookup_expr="lte", + ) + attendance_clock_in = django_filters.TimeFilter( + field_name="attendance_clock_in", + widget=forms.TimeInput(attrs={"type": "time"}), + ) + attendance_clock_out = django_filters.TimeFilter( + field_name="attendance_clock_out", + widget=forms.TimeInput(attrs={"type": "time"}), ) - attendance_clock_in = DateFilter( - widget=forms.DateInput(attrs={'type': 'time'}), - # add lookup expression here - ) - attendance_clock_out = DateFilter( - widget=forms.DateInput(attrs={'type': 'time'}), - # add lookup expression here - ) - attendance_date = DateFilter( - widget=forms.DateInput(attrs={'type': 'date'}), + attendance_date = django_filters.DateFilter( + widget=forms.DateInput(attrs={"type": "date"}), ) - at_work_second__lte = DurationInSecondsFilter(field_name = 'at_work_second',lookup_expr='lte') - at_work_second__gte = DurationInSecondsFilter(field_name = 'at_work_second',lookup_expr='gte') - overtime_second__lte = DurationInSecondsFilter(field_name ='overtime_second',lookup_expr='lte') - overtime_second__gte = DurationInSecondsFilter(field_name ='overtime_second',lookup_expr='gte') + at_work_second__lte = DurationInSecondsFilter( + field_name="at_work_second", lookup_expr="lte" + ) + at_work_second__gte = DurationInSecondsFilter( + field_name="at_work_second", lookup_expr="gte" + ) + overtime_second__lte = DurationInSecondsFilter( + field_name="overtime_second", lookup_expr="lte" + ) + overtime_second__gte = DurationInSecondsFilter( + field_name="overtime_second", lookup_expr="gte" + ) class Meta: + """ + Meta class to add additional options + """ + model = Attendance fields = [ - 'employee_id', - 'employee_id__employee_work_info__department_id', - 'employee_id__employee_work_info__company_id', - 'employee_id__employee_work_info__job_position_id', - 'employee_id__employee_work_info__location', - 'employee_id__employee_work_info__reporting_manager_id', - 'attendance_date', - 'work_type_id', - 'shift_id', - 'minimum_hour', - 'attendance_validated', - 'attendance_clock_in', - 'attendance_clock_out', - 'at_work_second', - 'overtime_second', - 'late_come_early_out__type', - 'attendance_overtime_approve', - 'attendance_validated', - 'at_work_second__lte', - 'at_work_second__gte', - 'overtime_second__lte', - 'overtime_second__gte', - 'overtime_second', - + "employee_id", + "employee_id__employee_work_info__department_id", + "employee_id__employee_work_info__company_id", + "employee_id__employee_work_info__job_position_id", + "employee_id__employee_work_info__location", + "employee_id__employee_work_info__reporting_manager_id", + "attendance_date", + "work_type_id", + "shift_id", + "minimum_hour", + "attendance_validated", + "attendance_clock_in", + "attendance_clock_out", + "at_work_second", + "overtime_second", + "late_come_early_out__type", + "attendance_overtime_approve", + "attendance_validated", + "at_work_second__lte", + "at_work_second__gte", + "overtime_second__lte", + "overtime_second__gte", + "overtime_second", ] widgets = { - 'attendance_date': DateTimeInput(attrs={'type': 'date'}), + "attendance_date": DateTimeInput(attrs={"type": "date"}), } - + def __init__(self, data=None, queryset=None, *, request=None, prefix=None): - super(AttendanceFilters, self).__init__(data=data, queryset=queryset, request=request, prefix=prefix) - + super().__init__(data=data, queryset=queryset, request=request, prefix=prefix) -class LateComeEarlyOutReGroup(): +class LateComeEarlyOutReGroup: + """ + Class to keep the field name for group by option + """ + fields = [ - ('','Select'), - ('employee_id','Employee'), - ('type','Type'), - ('attendance_id.attendance_date', 'Attendance Date'), - ('attendance_id.shift_id', 'Shift'), - ('attendance_id.work_type_id', 'Work Type'), - ('attendance_id.minimum_hour', 'Minimum Hour'), - ('attendance_id.employee_id.country', 'Country'), - ('attendance_id.employee_id.employee_work_info.reporting_manager_id', 'Reporting Manger'), - ('attendance_id.employee_id.employee_work_info.department_id', 'Department'), - ('attendance_id.employee_id.employee_work_info.job_position_id', 'Job Position'), - ('attendance_id.employee_id.employee_work_info.employee_type_id', 'Employment Type'), - ('attendance_id.employee_id.employee_work_info.company_id', 'Company'), + ("", "Select"), + ("employee_id", "Employee"), + ("type", "Type"), + ("attendance_id.attendance_date", "Attendance Date"), + ("attendance_id.shift_id", "Shift"), + ("attendance_id.work_type_id", "Work Type"), + ("attendance_id.minimum_hour", "Minimum Hour"), + ("attendance_id.employee_id.country", "Country"), + ( + "attendance_id.employee_id.employee_work_info.reporting_manager_id", + "Reporting Manger", + ), + ("attendance_id.employee_id.employee_work_info.department_id", "Department"), + ( + "attendance_id.employee_id.employee_work_info.job_position_id", + "Job Position", + ), + ( + "attendance_id.employee_id.employee_work_info.employee_type_id", + "Employment Type", + ), + ("attendance_id.employee_id.employee_work_info.company_id", "Company"), ] -class AttendanceReGroup(): - fields =[ - ('', 'Select'), - ('employee_id', 'Employee'), - ('attendance_date', 'Attendance Date'), - ('shift_id', 'Shift'), - ('work_type_id', 'Work Type'), - ('minimum_hour', 'Minimum Hour'), - ('employee_id.country', 'Country'), - ('employee_id.employee_work_info.reporting_manager_id', 'Reporting Manger'), - ('employee_id.employee_work_info.department_id', 'Department'), - ('employee_id.employee_work_info.job_position_id', 'Job Position'), - ('employee_id.employee_work_info.employee_type_id', 'Employment Type'), - ('employee_id.employee_work_info.company_id', 'Company'), - ] -class AttendanceOvertimeReGroup(): - fields =[ - ('', 'Select'), - ('employee_id', 'Employee'), - ('month', 'Month'), - ('year', 'Year'), - ('employee_id.country', 'Country'), - ('employee_id.employee_work_info.reporting_manager_id', 'Reporting Manger'), - ('employee_id.employee_work_info.shift_id', 'Shift'), - ('employee_id.employee_work_info.work_type_id', 'Work Type'), - ('employee_id.employee_work_info.department_id', 'Department'), - ('employee_id.employee_work_info.job_position_id', 'Job Position'), - ('employee_id.employee_work_info.employee_type_id', 'Employment Type'), - ('employee_id.employee_work_info.company_id', 'Company'), - ] +class AttendanceReGroup: + """ + Class to keep the field name for group by option + """ -class AttendanceActivityReGroup(): fields = [ - ('','Select'), - ('employee_id', 'Employee'), - ('attendance_date', 'Attendance Date'), - ('clock_in_date', 'In Date'), - ('clock_out_date', 'Out Date'), - ('shift_day', 'Shift Day'), - ('employee_id.country', 'Country'), - ('employee_id.employee_work_info.reporting_manager_id', 'Reporting Manger'), - ('employee_id.employee_work_info.shift_id', 'Shift'), - ('employee_id.employee_work_info.work_type_id', 'Work Type'), - ('employee_id.employee_work_info.department_id', 'Department'), - ('employee_id.employee_work_info.job_position_id', 'Job Position'), - ('employee_id.employee_work_info.employee_type_id', 'Employment Type'), - ('employee_id.employee_work_info.company_id', 'Company') - ] \ No newline at end of file + ("", "Select"), + ("employee_id", "Employee"), + ("attendance_date", "Attendance Date"), + ("shift_id", "Shift"), + ("work_type_id", "Work Type"), + ("minimum_hour", "Minimum Hour"), + ("employee_id.country", "Country"), + ("employee_id.employee_work_info.reporting_manager_id", "Reporting Manger"), + ("employee_id.employee_work_info.department_id", "Department"), + ("employee_id.employee_work_info.job_position_id", "Job Position"), + ("employee_id.employee_work_info.employee_type_id", "Employment Type"), + ("employee_id.employee_work_info.company_id", "Company"), + ] + + +class AttendanceOvertimeReGroup: + """ + Class to keep the field name for group by option + """ + + fields = [ + ("", "Select"), + ("employee_id", "Employee"), + ("month", "Month"), + ("year", "Year"), + ("employee_id.country", "Country"), + ("employee_id.employee_work_info.reporting_manager_id", "Reporting Manger"), + ("employee_id.employee_work_info.shift_id", "Shift"), + ("employee_id.employee_work_info.work_type_id", "Work Type"), + ("employee_id.employee_work_info.department_id", "Department"), + ("employee_id.employee_work_info.job_position_id", "Job Position"), + ("employee_id.employee_work_info.employee_type_id", "Employment Type"), + ("employee_id.employee_work_info.company_id", "Company"), + ] + + +class AttendanceActivityReGroup: + """ + Class to keep the field name for group by option + """ + + fields = [ + ("", "Select"), + ("employee_id", "Employee"), + ("attendance_date", "Attendance Date"), + ("clock_in_date", "In Date"), + ("clock_out_date", "Out Date"), + ("shift_day", "Shift Day"), + ("employee_id.country", "Country"), + ("employee_id.employee_work_info.reporting_manager_id", "Reporting Manger"), + ("employee_id.employee_work_info.shift_id", "Shift"), + ("employee_id.employee_work_info.work_type_id", "Work Type"), + ("employee_id.employee_work_info.department_id", "Department"), + ("employee_id.employee_work_info.job_position_id", "Job Position"), + ("employee_id.employee_work_info.employee_type_id", "Employment Type"), + ("employee_id.employee_work_info.company_id", "Company"), + ] diff --git a/attendance/forms.py b/attendance/forms.py old mode 100755 new mode 100644 diff --git a/attendance/models.py b/attendance/models.py index 44a2c35c9..522e3cec4 100644 --- a/attendance/models.py +++ b/attendance/models.py @@ -1,37 +1,46 @@ -from django.db import models -from base.models import EmployeeShift,EmployeeShiftDay, WorkType -from django.core.exceptions import ValidationError -from employee.models import Employee -from datetime import datetime -from django.utils.translation import gettext_lazy as _ +""" +models.py -# Create your models here. +This module is used to register models for recruitment app + +""" +import contextlib + +from datetime import datetime +from django.db import models +from django.core.exceptions import ValidationError +from django.utils.translation import gettext_lazy as _ +from base.models import EmployeeShift, EmployeeShiftDay, WorkType +from employee.models import Employee + +# Create your models here. def strtime_seconds(time): - ''' + """ this method is used reconvert time in H:M formate string back to seconds and return it args: time : time in H:M format - ''' - ftr = [3600,60,1] - return sum(a*b for a,b in zip(ftr, map(int,time.split(':')))) + """ + ftr = [3600, 60, 1] + return sum(a * b for a, b in zip(ftr, map(int, time.split(":")))) + def format_time(seconds): - '''this method is used to formate seconds to H:M and return it + """this method is used to formate seconds to H:M and return it args: seconds : seconds - ''' - hour=int(seconds//3600) - minutes=int((seconds%3600)//60) - seconds=int((seconds%3600)%60) - return "%02d:%02d" % (hour, minutes) + """ + hour = int(seconds // 3600) + minutes = int((seconds % 3600) // 60) + seconds = int((seconds % 3600) % 60) + return f"{hour:02d}:{minutes:02d}" def validate_time_format(value): - ''' + """ this method is used to validate the format of duration like fields. - ''' + """ if len(value) > 6: raise ValidationError(_("Invalid format, it should be HH:MM format")) try: @@ -40,66 +49,121 @@ def validate_time_format(value): minute = int(minute) if len(str(hour)) > 3 or minute not in range(60): raise ValidationError(_("Invalid time")) - except ValueError as e: - raise ValidationError(_("Invalid format")) from e + except ValueError as error: + raise ValidationError(_("Invalid format")) from error + def attendance_date_validate(date): + """ + Validates if the provided date is not a future date. + + :param date: The date to validate. + :raises ValidationError: If the provided date is in the future. + """ today = datetime.today().date() if date > today: - raise ValidationError(_('You cannot choose future date')) - - + raise ValidationError(_("You cannot choose a future date.")) class AttendanceActivity(models.Model): - employee_id = models.ForeignKey(Employee,on_delete=models.CASCADE, related_name='employee_attendance_activities',verbose_name="Employee") + """ + AttendanceActivity model + """ + employee_id = models.ForeignKey( + Employee, + on_delete=models.CASCADE, + related_name="employee_attendance_activities", + verbose_name="Employee", + ) attendance_date = models.DateField(null=True, validators=[attendance_date_validate]) - clock_in_date=models.DateField(null=True) - shift_day = models.ForeignKey(EmployeeShiftDay,null=True,on_delete=models.DO_NOTHING) + clock_in_date = models.DateField(null=True) + shift_day = models.ForeignKey( + EmployeeShiftDay, null=True, on_delete=models.DO_NOTHING + ) clock_in = models.TimeField() clock_out = models.TimeField(null=True) clock_out_date = models.DateField(null=True) class Meta: - ordering = ['-attendance_date','employee_id__employee_first_name','clock_in'] - + """ + Meta class to add some additional options + """ + ordering = ["-attendance_date", "employee_id__employee_first_name", "clock_in"] class Attendance(models.Model): + """ + Attendance model_ + """ employee_id = models.ForeignKey( - Employee, on_delete=models.CASCADE, null=True, related_name='employee_attendances',verbose_name="Employee") + Employee, + on_delete=models.CASCADE, + null=True, + related_name="employee_attendances", + verbose_name="Employee", + ) shift_id = models.ForeignKey( - EmployeeShift, on_delete=models.DO_NOTHING,null=True,verbose_name="Shift") - work_type_id = models.ForeignKey(WorkType,null=True,blank=True,on_delete=models.DO_NOTHING,verbose_name="Work Type") - attendance_date = models.DateField(null=False,validators=[attendance_date_validate]) - attendance_day = models.ForeignKey(EmployeeShiftDay,on_delete=models.DO_NOTHING,null= True) + EmployeeShift, on_delete=models.DO_NOTHING, null=True, verbose_name="Shift" + ) + work_type_id = models.ForeignKey( + WorkType, + null=True, + blank=True, + on_delete=models.DO_NOTHING, + verbose_name="Work Type", + ) + attendance_date = models.DateField( + null=False, validators=[attendance_date_validate] + ) + attendance_day = models.ForeignKey( + EmployeeShiftDay, on_delete=models.DO_NOTHING, null=True + ) attendance_clock_in = models.TimeField(null=True) attendance_clock_in_date = models.DateField(null=True) - attendance_clock_out = models.TimeField(null=True, ) + attendance_clock_out = models.TimeField( + null=True, + ) attendance_clock_out_date = models.DateField(null=True) - attendance_worked_hour = models.CharField(null=True,default='00:00',max_length=10,validators=[validate_time_format]) - minimum_hour = models.CharField(max_length = 10,default='00:00',validators=[validate_time_format]) - attendance_overtime = models.CharField(default='00:00',validators=[validate_time_format],max_length=10) + attendance_worked_hour = models.CharField( + null=True, default="00:00", max_length=10, validators=[validate_time_format] + ) + minimum_hour = models.CharField( + max_length=10, default="00:00", validators=[validate_time_format] + ) + attendance_overtime = models.CharField( + default="00:00", validators=[validate_time_format], max_length=10 + ) attendance_overtime_approve = models.BooleanField(default=False) attendance_validated = models.BooleanField(default=False) - at_work_second = models.IntegerField(null=True,blank=True) + at_work_second = models.IntegerField(null=True, blank=True) overtime_second = models.IntegerField(null=True, blank=True) approved_overtime_second = models.IntegerField(default=0) - class Meta: - unique_together= ('employee_id','attendance_date') - permissions = [('change_validateattendance','Validate Attendance'),('change_approveovertime','Change Approve Overtime')] - ordering = ['-attendance_date','employee_id__employee_first_name','attendance_clock_in'] - + """ + Meta class to add some additional options + """ + unique_together = ("employee_id", "attendance_date") + permissions = [ + ("change_validateattendance", "Validate Attendance"), + ("change_approveovertime", "Change Approve Overtime"), + ] + ordering = [ + "-attendance_date", + "employee_id__employee_first_name", + "attendance_clock_in", + ] def __str__(self) -> str: - return f'{self.employee_id.employee_first_name} {self.employee_id.employee_last_name} - {self.attendance_date}' + return f"{self.employee_id.employee_first_name} \ + {self.employee_id.employee_last_name} - {self.attendance_date}" def save(self, *args, **kwargs): self.at_work_second = strtime_seconds(self.attendance_worked_hour) self.overtime_second = strtime_seconds(self.attendance_overtime) - self.attendance_day = EmployeeShiftDay.objects.get(day = self.attendance_date.strftime('%A').lower()) + self.attendance_day = EmployeeShiftDay.objects.get( + day=self.attendance_date.strftime("%A").lower() + ) prev_attendance_approved = False condition = AttendanceValidationCondition.objects.first() @@ -110,20 +174,26 @@ class Attendance(models.Model): if overtime > cutoff_seconds: self.overtime_second = cutoff_seconds self.attendance_overtime = format_time(cutoff_seconds) - + if self.pk is not None: # Get the previous values of the boolean field prev_state = Attendance.objects.get(pk=self.pk) prev_attendance_approved = prev_state.attendance_overtime_approve - super(Attendance, self).save(*args, **kwargs) - employee_ot = self.employee_id.employee_overtime.filter(month=self.attendance_date.strftime('%B').lower(),year=self.attendance_date.strftime('%Y')) + super().save(*args, **kwargs) + employee_ot = self.employee_id.employee_overtime.filter( + month=self.attendance_date.strftime("%B").lower(), + year=self.attendance_date.strftime("%Y"), + ) if employee_ot.exists(): self.update_ot(employee_ot.first()) else: self.create_ot() approved = self.attendance_overtime_approve - attendance_account = self.employee_id.employee_overtime.filter(month=self.attendance_date.strftime('%B').lower(),year=self.attendance_date.year).first() + attendance_account = self.employee_id.employee_overtime.filter( + month=self.attendance_date.strftime("%B").lower(), + year=self.attendance_date.year, + ).first() total_ot_seconds = attendance_account.overtime_second if approved and prev_attendance_approved is False: self.approved_overtime_second = self.overtime_second @@ -133,38 +203,49 @@ class Attendance(models.Model): self.approved_overtime_second = 0 attendance_account.overtime = format_time(total_ot_seconds) attendance_account.save() - super(Attendance, self).save(*args, **kwargs) - - def delete(self, *args, **kwargs): - # Custom delete logic - # Perform additional operations before deleting the object - try: - AttendanceActivity.objects.filter(attendance_date=self.attendance_date,employee_id=self.employee_id).delete() - except: - pass - # Call the superclass delete() method to delete the object - super(Attendance, self).delete(*args, **kwargs) + super().save(*args, **kwargs) - # Perform additional operations after deleting the object - + def delete(self, *args, **kwargs): + # Custom delete logic + # Perform additional operations before deleting the object + with contextlib.suppress(Exception): + AttendanceActivity.objects.filter( + attendance_date=self.attendance_date, employee_id=self.employee_id + ).delete() + # Call the superclass delete() method to delete the object + super().delete(*args, **kwargs) + + # Perform additional operations after deleting the object def create_ot(self): - ''' - this method is used to create new AttendanceOvertime's instance if there is no existing for a specific month and year - ''' + """ + this method is used to create new AttendanceOvertime's instance if there + is no existing for a specific month and year + """ employee_ot = AttendanceOverTime() employee_ot.employee_id = self.employee_id - employee_ot.month = self.attendance_date.strftime('%B').lower() + employee_ot.month = self.attendance_date.strftime("%B").lower() employee_ot.year = self.attendance_date.year if self.attendance_overtime_approve: employee_ot.overtime = self.attendance_overtime if self.attendance_validated: employee_ot.hour_account = self.attendance_worked_hour employee_ot.save() - return + return self def update_ot(self, employee_ot): - month_attendances = Attendance.objects.filter(employee_id = self.employee_id, attendance_date__month=self.attendance_date.month,attendance_date__year=self.attendance_date.year,attendance_validated=True) + """ + This method is used to update the overtime + + Args: + employee_ot (obj): AttendanceOverTime instance + """ + month_attendances = Attendance.objects.filter( + employee_id=self.employee_id, + attendance_date__month=self.attendance_date.month, + attendance_date__year=self.attendance_date.year, + attendance_validated=True, + ) hour_balance = 0 for attendance in month_attendances: hour_balance = hour_balance + attendance.at_work_second @@ -172,52 +253,116 @@ class Attendance(models.Model): employee_ot.save() return employee_ot + class AttendanceOverTime(models.Model): - employee_id = models.ForeignKey(Employee,on_delete=models.CASCADE,related_name='employee_overtime',verbose_name="Employee") + """ + AttendanceOverTime model + """ + employee_id = models.ForeignKey( + Employee, + on_delete=models.CASCADE, + related_name="employee_overtime", + verbose_name="Employee", + ) month = models.CharField(max_length=10) month_sequence = models.PositiveSmallIntegerField(default=0) - year = models.CharField(default=datetime.now().strftime('%Y'),null=True,max_length=10) - hour_account = models.CharField(max_length=10,default='00:00',null=True,validators=[validate_time_format]) - overtime = models.CharField(max_length=20,default='00:00',validators=[validate_time_format]) - hour_account_second = models.IntegerField(default=0,null=True,) - overtime_second = models.IntegerField(default=0,null=True,) + year = models.CharField( + default=datetime.now().strftime("%Y"), null=True, max_length=10 + ) + hour_account = models.CharField( + max_length=10, default="00:00", null=True, validators=[validate_time_format] + ) + overtime = models.CharField( + max_length=20, default="00:00", validators=[validate_time_format] + ) + hour_account_second = models.IntegerField( + default=0, + null=True, + ) + overtime_second = models.IntegerField( + default=0, + null=True, + ) class Meta: - unique_together = [('employee_id'),('month'),('year')] - ordering = ['-year','-month_sequence'] + """ + Meta class to add some additional options + """ + unique_together = [("employee_id"), ("month"), ("year")] + ordering = ["-year", "-month_sequence"] - def save(self, *args, **kwargs): self.hour_account_second = strtime_seconds(self.hour_account) self.overtime_second = strtime_seconds(self.overtime) - month_name = self.month.split('-')[0] - months = ['january', 'february', 'march', 'april', 'may', 'june', 'july', 'august', 'september', 'october', 'november', 'december'] + month_name = self.month.split("-")[0] + months = [ + "january", + "february", + "march", + "april", + "may", + "june", + "july", + "august", + "september", + "october", + "november", + "december", + ] self.month_sequence = months.index(month_name) - super(AttendanceOverTime, self).save(*args, **kwargs) - + super().save(*args, **kwargs) -class AttendanceLateComeEarlyOut(models.Model): +class AttendanceLateComeEarlyOut(models.Model): + """ + AttendanceLateComeEarlyOut model + """ choices = [ - ('late_come',_('Late Come')), - ('early_out',_('Early Out')), + ("late_come", _("Late Come")), + ("early_out", _("Early Out")), ] - attendance_id = models.ForeignKey(Attendance,on_delete=models.CASCADE,related_name='late_come_early_out') - employee_id = models.ForeignKey(Employee,on_delete=models.DO_NOTHING,null=True,related_name='late_come_early_out',verbose_name="Employee") - type = models.CharField(max_length=20,choices=choices) + attendance_id = models.ForeignKey( + Attendance, on_delete=models.CASCADE, related_name="late_come_early_out" + ) + employee_id = models.ForeignKey( + Employee, + on_delete=models.DO_NOTHING, + null=True, + related_name="late_come_early_out", + verbose_name="Employee", + ) + type = models.CharField(max_length=20, choices=choices) + class Meta: - unique_together = [('attendance_id'),('type')] + """ + Meta class to add some additional options + """ + unique_together = [("attendance_id"), ("type")] def __str__(self) -> str: - return f'{self.attendance_id.employee_id.employee_first_name} {self.attendance_id.employee_id.employee_last_name} - {self.type}' + return f"{self.attendance_id.employee_id.employee_first_name} \ + {self.attendance_id.employee_id.employee_last_name} - {self.type}" class AttendanceValidationCondition(models.Model): - validation_at_work = models.CharField(default='09:00',max_length=10,validators=[validate_time_format]) - minimum_overtime_to_approve = models.CharField(default='00:30',null=True,max_length=10,validators=[validate_time_format]) - overtime_cutoff = models.CharField(default='02:00',null=True,max_length=10,validators=[validate_time_format]) + """ + AttendanceValidationCondition model + """ + validation_at_work = models.CharField( + default="09:00", max_length=10, validators=[validate_time_format] + ) + minimum_overtime_to_approve = models.CharField( + default="00:30", null=True, max_length=10, validators=[validate_time_format] + ) + overtime_cutoff = models.CharField( + default="02:00", null=True, max_length=10, validators=[validate_time_format] + ) + def clean(self): + """ + This method is used to perform some custom validations + """ super().clean() if not self.id and AttendanceValidationCondition.objects.exists(): - raise ValidationError(_('You cannot add more conditions.')) \ No newline at end of file + raise ValidationError(_("You cannot add more conditions.")) diff --git a/attendance/templates/attendance/attendance/attendance_filters.html b/attendance/templates/attendance/attendance/attendance_filters.html index 68d8ec118..12e440dc0 100644 --- a/attendance/templates/attendance/attendance/attendance_filters.html +++ b/attendance/templates/attendance/attendance/attendance_filters.html @@ -94,11 +94,11 @@
- {{f.form.attendance_clock_in__lte}} + {{f.form.attendance_clock_in__gte}}
- {{f.form.attendance_clock_out__lte}} + {{f.form.attendance_clock_out__gte}}
@@ -128,7 +128,7 @@
- {{f.form.overtime_second__gte}} + {{f.form.overtime_second__lte}}
diff --git a/attendance/templates/attendance/attendance_activity/nav.html b/attendance/templates/attendance/attendance_activity/nav.html old mode 100755 new mode 100644 diff --git a/attendance/templates/attendance/late_come_early_out/late_come_early_out_filters.html b/attendance/templates/attendance/late_come_early_out/late_come_early_out_filters.html index 3c2df8785..96b720f0b 100644 --- a/attendance/templates/attendance/late_come_early_out/late_come_early_out_filters.html +++ b/attendance/templates/attendance/late_come_early_out/late_come_early_out_filters.html @@ -77,7 +77,7 @@
- {{f.form.attendance_clock_out}} + {{f.form.attendance_clock_in}}
@@ -98,11 +98,11 @@
- {{f.form.attendance_clock_in__lte}} + {{f.form.attendance_clock_in__gte}}
- {{f.form.attendance_clock_out__lte}} + {{f.form.attendance_clock_out__gte}}
diff --git a/attendance/templates/attendance/own_attendance/filters.html b/attendance/templates/attendance/own_attendance/filters.html index 3a08d8544..9cd2de6fb 100644 --- a/attendance/templates/attendance/own_attendance/filters.html +++ b/attendance/templates/attendance/own_attendance/filters.html @@ -102,7 +102,7 @@
-
+ {% comment %}
{% trans "Group By" %}
@@ -128,7 +128,7 @@
-
+ {% endcomment %}
-
-
+
{% endcomment %}
diff --git a/attendance/templatetags/attendancefilters.py b/attendance/templatetags/attendancefilters.py index d55564255..c90da037f 100644 --- a/attendance/templatetags/attendancefilters.py +++ b/attendance/templatetags/attendancefilters.py @@ -1,47 +1,39 @@ -from django.template.defaultfilters import register -from django import template -from attendance.models import AttendanceValidationCondition, Attendance -from attendance.views import format_time, strtime_seconds -from employee.models import EmployeeWorkInformation -from django.contrib.auth.models import Permission -import datetime -from django.contrib.auth.models import User, Permission +""" +attendancefilters.py +This module is used to write custom template filters. + +""" from itertools import groupby +from django import template from django.template import TemplateSyntaxError +from django.template.defaultfilters import register +from attendance.views import strtime_seconds +from attendance.models import AttendanceValidationCondition + register = template.Library() -@register.filter(name='checkminimumot') + +@register.filter(name="checkminimumot") def checkminimumot(ot=None): """ - This filter method is used to check minimum overtime from the attendance validation condition - """ + This filter method is used to check minimum overtime from + the attendance validation condition + """ if ot is not None: condition = AttendanceValidationCondition.objects.all() if condition.exists(): minimum_overtime_to_approve = condition[0].minimum_overtime_to_approve overtime_second = strtime_seconds(ot) - minimum_ot_approve_seconds= strtime_seconds(minimum_overtime_to_approve) + minimum_ot_approve_seconds = strtime_seconds(minimum_overtime_to_approve) if overtime_second > minimum_ot_approve_seconds: return True return False -# @register.filter(name='is_reportingmanager') -# def is_reportingmanager(user): -# """ -# This method returns true if the user employee has corresponding related reporting manager object in EmployeeWorkInformation model -# args: -# user : request.user -# """ - -# employee =user.employee_get -# employee_manages = employee.reporting_manager.all() -# return employee_manages.exists() - -@register.filter(name='checkmanager') -def checkmanager(user,employee): +@register.filter(name="checkmanager") +def checkmanager(user, employee): """ This filter method is used to check request user is manager of the employee args: @@ -49,40 +41,44 @@ def checkmanager(user,employee): employee : employee instance """ - + employee_user = user.employee_get employee_manager = employee.employee_work_info.reporting_manager_id return bool( employee_user == employee_manager or user.is_superuser - or user.has_perm('attendance.change_attendance') + or user.has_perm("attendance.change_attendance") ) -@register.filter(name='is_clocked_in') +@register.filter(name="is_clocked_in") def is_clocked_in(user): """ This filter method is used to check the user is clocked in or not args: user : request.user """ - + try: employee = user.employee_get - employee.employee_work_info except: return False - date_today = datetime.date.today() - last_attendance = employee.employee_attendances.all().order_by('attendance_date','id').last() + last_attendance = ( + employee.employee_attendances.all().order_by("attendance_date", "id").last() + ) if last_attendance is not None: - last_activity = employee.employee_attendance_activities.filter(attendance_date = last_attendance.attendance_date).last() + last_activity = employee.employee_attendance_activities.filter( + attendance_date=last_attendance.attendance_date + ).last() return False if last_activity is None else last_activity.clock_out is None return False - - class DynamicRegroupNode(template.Node): + """ + DynamicRegroupNode + """ + def __init__(self, target, parser, expression, var_name): self.target = target self.expression = template.Variable(expression) @@ -91,18 +87,18 @@ class DynamicRegroupNode(template.Node): def render(self, context): obj_list = self.target.resolve(context, True) - if obj_list == None: + if obj_list is None: # target variable wasn't found in context; fail silently. context[self.var_name] = [] - return '' + return "" # List of dictionaries in the format: # {'grouper': 'key', 'list': [list of contents]}. - """ - Try to resolve the filter expression from the template context. - If the variable doesn't exist, accept the value that passed to the - template tag and convert it to a string - """ + # ---- + # Try to resolve the filter expression from the template context. + # If the variable doesn't exist, accept the value that passed to the + # template tag and convert it to a string + # ---- try: exp = self.expression.resolve(context) except template.VariableDoesNotExist: @@ -111,45 +107,68 @@ class DynamicRegroupNode(template.Node): filter_exp = self.parser.compile_filter(exp) context[self.var_name] = [ - {'grouper': key, 'list': list(val)} - for key, val in - groupby(obj_list, lambda v, f=filter_exp.resolve: f(v, True)) + {"grouper": key, "list": list(val)} + for key, val in groupby( + obj_list, lambda v, f=filter_exp.resolve: f(v, True) + ) ] - return '' + return "" + @register.tag def dynamic_regroup(parser, token): + """ + A template tag that allows dynamic grouping of objects based on a provided attribute. + + Usage: {% dynamic_regroup target by expression as var_name %} + + :param parser: The template parser. + :param token: The tokenized tag contents. + :return: A DynamicRegroupNode object. + :raises TemplateSyntaxError: If the tag is not properly formatted. + """ firstbits = token.contents.split(None, 3) if len(firstbits) != 4: raise TemplateSyntaxError("'regroup' tag takes five arguments") target = parser.compile_filter(firstbits[1]) - if firstbits[2] != 'by': + if firstbits[2] != "by": raise TemplateSyntaxError("second argument to 'regroup' tag must be 'by'") lastbits_reversed = firstbits[3][::-1].split(None, 2) - if lastbits_reversed[1][::-1] != 'as': - raise TemplateSyntaxError("next-to-last argument to 'regroup' tag must" - " be 'as'") + if lastbits_reversed[1][::-1] != "as": + raise TemplateSyntaxError( + "next-to-last argument to 'regroup' tag must" " be 'as'" + ) - """ - Django expects the value of `expression` to be an attribute available on - your objects. The value you pass to the template tag gets converted into a - FilterExpression object from the literal. - - Sometimes we need the attribute to group on to be dynamic. So, instead - of converting the value to a FilterExpression here, we're going to pass the - value as-is and convert it in the Node. - """ + # --- + # Django expects the value of `expression` to be an attribute available on + # your objects. The value you pass to the template tag gets converted into a + # FilterExpression object from the literal. + + # Sometimes we need the attribute to group on to be dynamic. So, instead + # of converting the value to a FilterExpression here, we're going to pass the + # value as-is and convert it in the Node. + # ---- expression = lastbits_reversed[2][::-1] var_name = lastbits_reversed[0][::-1] - """ - We also need to hand the parser to the node in order to convert the value - for `expression` to a FilterExpression. - """ + # ---- + # We also need to hand the parser to the node in order to convert the value + # for `expression` to a FilterExpression. + # ---- return DynamicRegroupNode(target, parser, expression, var_name) -@register.filter(name='any_permission') -def any_permission(user,app_label): - return user.has_module_perms(app_label) \ No newline at end of file +@register.filter(name="any_permission") +def any_permission(user, app_label): + """ + This method is used to check any on the module + + Args: + user (obj): Django user model instance + app_label (str): app label + + Returns: + bool: True if any permission on the module + """ + return user.has_module_perms(app_label) diff --git a/attendance/urls.py b/attendance/urls.py old mode 100755 new mode 100644 diff --git a/attendance/views.py b/attendance/views.py old mode 100755 new mode 100644 diff --git a/employee/admin.py b/employee/admin.py index 2a1b763fe..ffe89ea78 100644 --- a/employee/admin.py +++ b/employee/admin.py @@ -1,11 +1,13 @@ +""" +admin.py + +This page is used to register the model with admins site. +""" from django.contrib import admin -from employee.models import Employee, EmployeeWorkInformation,EmployeeBankDetails -from simple_history.admin import SimpleHistoryAdmin +from employee.models import Employee, EmployeeWorkInformation, EmployeeBankDetails # Register your models here. admin.site.register(Employee) admin.site.register(EmployeeBankDetails) admin.site.register(EmployeeWorkInformation) - - diff --git a/employee/apps.py b/employee/apps.py index b5ef14282..1e6a374ee 100644 --- a/employee/apps.py +++ b/employee/apps.py @@ -1,6 +1,19 @@ +""" +apps.py +""" from django.apps import AppConfig class EmployeeConfig(AppConfig): - default_auto_field = 'django.db.models.BigAutoField' - name = 'employee' + """ + AppConfig for the 'employee' app. + + This class represents the configuration for the 'employee' app. It provides + the necessary settings and metadata for the app. + + Attributes: + default_auto_field (str): The default auto field to use for model field IDs. + name (str): The name of the app. + """ + default_auto_field = "django.db.models.BigAutoField" + name = "employee" diff --git a/employee/filters.py b/employee/filters.py index 322dddbc7..a2ff8a8b2 100644 --- a/employee/filters.py +++ b/employee/filters.py @@ -1,18 +1,27 @@ -from horilla.filters import FilterSet, CharFilter +""" +filters.py + +This page is used to register filter for employee models + +""" import django_filters +from django.contrib.auth.models import Permission, Group +from horilla.filters import FilterSet from employee.models import Employee -from django.db import models -from django import forms -from django.contrib.auth.models import Permission, Group, User -from django.db.models import Q class EmployeeFilter(FilterSet): - search = CharFilter(method='filter_by_name') - - employee_first_name = django_filters.CharFilter(lookup_expr='icontains') - employee_last_name = django_filters.CharFilter(lookup_expr='icontains') - country = django_filters.CharFilter(lookup_expr='icontains') + """ + Filter set class for Candidate model + + Args: + FilterSet (class): custom filter set class to apply styling + """ + search = django_filters.CharFilter(method="filter_by_name") + + employee_first_name = django_filters.CharFilter(lookup_expr="icontains") + employee_last_name = django_filters.CharFilter(lookup_expr="icontains") + country = django_filters.CharFilter(lookup_expr="icontains") user_permissions = django_filters.ModelMultipleChoiceFilter( queryset=Permission.objects.all(), @@ -22,37 +31,43 @@ class EmployeeFilter(FilterSet): ) class Meta: + """ + Meta class to add the additional info + """ model = Employee fields = [ - 'employee_first_name', - 'employee_last_name', - 'email', - 'badge_id', - 'phone', - 'country', - 'gender', - 'is_active', - 'employee_work_info__job_position_id', - 'employee_work_info__department_id', - 'employee_work_info__work_type_id', - 'employee_work_info__employee_type_id', - 'employee_work_info__job_role_id', - 'employee_work_info__reporting_manager_id', - 'employee_work_info__company_id', - 'employee_work_info__shift_id', + "employee_first_name", + "employee_last_name", + "email", + "badge_id", + "phone", + "country", + "gender", + "is_active", + "employee_work_info__job_position_id", + "employee_work_info__department_id", + "employee_work_info__work_type_id", + "employee_work_info__employee_type_id", + "employee_work_info__job_role_id", + "employee_work_info__reporting_manager_id", + "employee_work_info__company_id", + "employee_work_info__shift_id", ] - def filter_by_name(self,queryset, name, value): + def filter_by_name(self, queryset, _, value): """ Filter queryset by first name or last name. """ # Split the search value into first name and last name parts = value.split() first_name = parts[0] - last_name = ' '.join(parts[1:]) if len(parts) > 1 else '' + last_name = " ".join(parts[1:]) if len(parts) > 1 else "" # Filter the queryset by first name and last name if first_name and last_name: - queryset = queryset.filter(employee_first_name__icontains=first_name, employee_last_name__icontains=last_name) + queryset = queryset.filter( + employee_first_name__icontains=first_name, + employee_last_name__icontains=last_name, + ) elif first_name: queryset = queryset.filter(employee_first_name__icontains=first_name) elif last_name: @@ -60,5 +75,5 @@ class EmployeeFilter(FilterSet): return queryset def __init__(self, data=None, queryset=None, *, request=None, prefix=None): - super(EmployeeFilter, self).__init__(data=data, queryset=queryset, request=request, prefix=prefix) - self.form.initial['is_active'] = True + super().__init__(data=data, queryset=queryset, request=request, prefix=prefix) + self.form.initial["is_active"] = True diff --git a/employee/forms.py b/employee/forms.py index 707faa101..38741e4ef 100644 --- a/employee/forms.py +++ b/employee/forms.py @@ -1,93 +1,159 @@ +""" +forms.py + +This module contains the form classes used in the application. + +Each form represents a specific functionality or data input in the +application. They are responsible for validating +and processing user input data. + +Classes: +- YourForm: Represents a form for handling specific data input. + +Usage: from django import forms -from django.contrib.auth.models import User -from employee.models import Employee, EmployeeWorkInformation, EmployeeBankDetails -from django.forms import DateInput,TextInput -from django.db import models -from django.core.exceptions import ValidationError -import json -from django.db.models import Q -from django.utils.translation import gettext_lazy as _ + +class YourForm(forms.Form): + field_name = forms.CharField() + + def clean_field_name(self): + # Custom validation logic goes here + pass +""" import re -import datetime +from django import forms +from django.db.models import Q +from django.contrib.auth.models import User +from django.forms import DateInput, TextInput +from django.utils.translation import gettext_lazy as trans +from employee.models import Employee, EmployeeWorkInformation, EmployeeBankDetails class ModelForm(forms.ModelForm): + """ + Overriding django default model form to apply some styles + """ + def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) - for field_name, field in self.fields.items(): + for _, field in self.fields.items(): widget = field.widget - if isinstance(widget, (forms.NumberInput, forms.EmailInput, forms.TextInput, forms.FileInput)): - label = _(field.label.title()) + if isinstance( + widget, + (forms.NumberInput, forms.EmailInput, forms.TextInput, forms.FileInput), + ): + label = trans(field.label.title()) field.widget.attrs.update( - {'class': 'oh-input w-100', 'placeholder': label}) - elif isinstance(widget,(forms.Select,)): - label = '' + {"class": "oh-input w-100", "placeholder": label} + ) + elif isinstance(widget, (forms.Select,)): + label = "" if field.label is not None: - label = _(field.label) - field.empty_label = _('---Choose {label}---').format(label=label) - field.widget.attrs.update({'class': 'oh-select oh-select-2 select2-hidden-accessible'}) - elif isinstance(widget,(forms.Textarea)): - field.widget.attrs.update({'class': 'oh-input w-100','placeholder':field.label,'rows':2,'cols':40}) - elif isinstance(widget, (forms.CheckboxInput,forms.CheckboxSelectMultiple,)): - field.widget.attrs.update({'class': 'oh-switch__checkbox'}) - + label = trans(field.label) + field.empty_label = trans("---Choose {label}---").format(label=label) + field.widget.attrs.update( + {"class": "oh-select oh-select-2 select2-hidden-accessible"} + ) + elif isinstance(widget, (forms.Textarea)): + field.widget.attrs.update( + { + "class": "oh-input w-100", + "placeholder": field.label, + "rows": 2, + "cols": 40, + } + ) + elif isinstance( + widget, + ( + forms.CheckboxInput, + forms.CheckboxSelectMultiple, + ), + ): + field.widget.attrs.update({"class": "oh-switch__checkbox"}) + class UserForm(ModelForm): + """ + Form for User model + """ + class Meta: - fields = ('groups',) + """ + Meta class to add the additional info + """ + + fields = ("groups",) model = User - def __init__(self, *args, **kwargs): - super(UserForm, self).__init__(*args, **kwargs) - class UserPermissionForm(ModelForm): + """ + Form for User model + """ + class Meta: - fields = ('groups','user_permissions') + """ + Meta class to add the additional info + """ + + fields = ("groups", "user_permissions") model = User - def __init__(self, *args, **kwargs): - super(UserPermissionForm, self).__init__(*args, **kwargs) - class EmployeeForm(ModelForm): - class Meta: - model = Employee - fields = '__all__' - exclude = ('employee_user_id',) - widgets = { - 'dob': TextInput(attrs={'type': 'date','id':'dob'}), + """ + Form for Employee model + """ + class Meta: + """ + Meta class to add the additional info + """ + + model = Employee + fields = "__all__" + exclude = ("employee_user_id",) + widgets = { + "dob": TextInput(attrs={"type": "date", "id": "dob"}), } + def __init__(self, *args, **kwargs): - super(EmployeeForm, self).__init__(*args, **kwargs) - if instance := kwargs.get('instance'): - ''' - django forms not showing value inside the date, time html element. - so here overriding default forms instance method to set initial value - ''' + super().__init__(*args, **kwargs) + if instance := kwargs.get("instance"): + # ---- + # django forms not showing value inside the date, time html element. + # so here overriding default forms instance method to set initial value + # ---- initial = {} if instance.dob is not None: - initial['dob'] = instance.dob.strftime('%H:%M') - kwargs['initial']=initial + initial["dob"] = instance.dob.strftime("%H:%M") + kwargs["initial"] = initial else: - self.initial = {"badge_id":self.get_next_badge_id()} + self.initial = {"badge_id": self.get_next_badge_id()} - def get_next_badge_id(self): + """ + This method is used to generate badge id + """ try: # total_employee_count = Employee.objects.count() - badge_ids = Employee.objects.filter(~Q(badge_id=None)).order_by('-badge_id') + badge_ids = Employee.objects.filter(~Q(badge_id=None)).order_by("-badge_id") greatest_id = badge_ids.first().badge_id - match = re.findall(r'\d+', greatest_id[::-1]) + match = re.findall(r"\d+", greatest_id[::-1]) total_employee_count = 0 if match: - total_employee_count = int(match[0][::-1]) - except: + total_employee_count = int(match[0][::-1]) + except Exception: total_employee_count = 0 try: - string = Employee.objects.filter(~Q(badge_id=None)).order_by('-badge_id').last().badge_id - except: + string = ( + Employee.objects.filter(~Q(badge_id=None)) + .order_by("-badge_id") + .last() + .badge_id + ) + except Exception: string = "DUDE" # Find the index of the last integer group in the string integer_group_index = None @@ -99,109 +165,130 @@ class EmployeeForm(ModelForm): if integer_group_index is None: # There is no integer group in the string, so just append #01 - return string + '#01' - else: - # Extract the integer group from the string - integer_group = string[integer_group_index:] - prefix = string[:integer_group_index] + return string + "#01" + # Extract the integer group from the string + integer_group = string[integer_group_index:] + prefix = string[:integer_group_index] - # Set the integer group to the total number of employees plus one - new_integer_group = str(total_employee_count + 1).zfill(len(integer_group)) + # Set the integer group to the total number of employees plus one + new_integer_group = str(total_employee_count + 1).zfill(len(integer_group)) + + # Return the new string + return prefix + new_integer_group - # Return the new string - return prefix + new_integer_group - def clean_badge_id(self): - badge_id = self.cleaned_data['badge_id'] + """ + This method is used to clean the badge id + """ + badge_id = self.cleaned_data["badge_id"] if badge_id: - qs = Employee.objects.filter(badge_id=badge_id).exclude(pk=self.instance.pk if self.instance else None) - if qs.exists(): - raise forms.ValidationError(_("Badge ID must be unique.")) - if not re.search(r'\d', badge_id): - raise forms.ValidationError(_("Badge ID must contain at least one digit.")) - + queryset = Employee.objects.filter(badge_id=badge_id).exclude( + pk=self.instance.pk if self.instance else None + ) + if queryset.exists(): + raise forms.ValidationError(trans("Badge ID must be unique.")) + if not re.search(r"\d", badge_id): + raise forms.ValidationError( + trans("Badge ID must contain at least one digit.") + ) return badge_id - - - + + class EmployeeWorkInformationForm(ModelForm): - employees = Employee.objects.filter(employee_work_info=None) + """ + Form for EmployeeWorkInformation model + """ + + employees = Employee.objects.filter(employee_work_info=None) employee_id = forms.ModelChoiceField(queryset=employees) + class Meta: + """ + Meta class to add the additional info + """ + model = EmployeeWorkInformation - fields = '__all__' + fields = "__all__" widgets = { - 'date_joining': DateInput(attrs={'type': 'date'}), - 'contract_end_date': DateInput(attrs={'type': 'date'}), + "date_joining": DateInput(attrs={"type": "date"}), + "contract_end_date": DateInput(attrs={"type": "date"}), } - def __init__(self, *args,disable=False, **kwargs): - super(EmployeeWorkInformationForm, self).__init__(*args, **kwargs) - for field in self.fields: - self.fields[field].widget.attrs['placeholder'] = self.fields[field].label + def __init__(self, *args, disable=False, **kwargs): + super().__init__(*args, **kwargs) + for field in self.fields: + self.fields[field].widget.attrs["placeholder"] = self.fields[field].label if disable: self.fields[field].disabled = True - def clean(self): cleaned_data = super().clean() - if 'employee_id' in self.errors: - del self.errors['employee_id'] - + if "employee_id" in self.errors: + del self.errors["employee_id"] + return cleaned_data class EmployeeWorkInformationUpdateForm(ModelForm): + """ + Form for EmployeeWorkInformation model + """ + class Meta: + """ + Meta class to add the additional info + """ + model = EmployeeWorkInformation - fields = '__all__' - exclude = ('employee_id',) + fields = "__all__" + exclude = ("employee_id",) widgets = { - 'date_joining': DateInput(attrs={'type': 'date'}), - 'contract_end_date': DateInput(attrs={'type': 'date'}), + "date_joining": DateInput(attrs={"type": "date"}), + "contract_end_date": DateInput(attrs={"type": "date"}), } - def __init__(self, *args, **kwargs): - super(EmployeeWorkInformationUpdateForm, self).__init__(*args, **kwargs) - class EmployeeBankDetailsForm(ModelForm): - address = forms.CharField(widget=forms.Textarea(attrs={'rows': 2, 'cols': 40})) + """ + Form for EmployeeBankDetails model + """ + + address = forms.CharField(widget=forms.Textarea(attrs={"rows": 2, "cols": 40})) class Meta: - model = EmployeeBankDetails - fields = '__all__' - exclude = ['employee_id',] - def __init__(self, *args, **kwargs): - super(EmployeeBankDetailsForm, self).__init__(*args, **kwargs) - for visible in self.visible_fields(): - visible.field.widget.attrs['class'] = 'oh-input w-100' - # for field in self.fields: - # self.fields[field].widget.attrs['placeholder'] = self.fields[field].label - - # self.fields['reporting_manager_id'].widget.attrs.update({'class': 'oh-select oh-select-2 select2-hidden-accessible'}) + """ + Meta class to add the additional info + """ + + model = EmployeeBankDetails + fields = "__all__" + exclude = [ + "employee_id", + ] + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + for visible in self.visible_fields(): + visible.field.widget.attrs["class"] = "oh-input w-100" + - class EmployeeBankDetailsUpdateForm(ModelForm): + """ + Form for EmployeeBankDetails model + """ + class Meta: + """ + Meta class to add the additional info + """ + model = EmployeeBankDetails - fields = '__all__' - exclude = ('employee_id',) + fields = "__all__" + exclude = ("employee_id",) def __init__(self, *args, **kwargs): - super(EmployeeBankDetailsUpdateForm, self).__init__(*args, **kwargs) + super().__init__(*args, **kwargs) for visible in self.visible_fields(): - visible.field.widget.attrs['class'] = 'oh-input w-100' - for field in self.fields: - self.fields[field].widget.attrs['placeholder'] = self.fields[field].label - -class EmployeeProfileBankDetailsForm(ModelForm): - class Meta: - model = EmployeeBankDetails - fields = '__all__' - exclude = ('employee_id',) - - def __init__(self, *args, **kwargs): - super(EmployeeProfileBankDetailsForm, self).__init__(*args, **kwargs) - for visible in self.visible_fields(): - visible.field.widget.attrs['class'] = 'oh-input w-100' + visible.field.widget.attrs["class"] = "oh-input w-100" + for field in self.fields: + self.fields[field].widget.attrs["placeholder"] = self.fields[field].label diff --git a/employee/imports.py b/employee/imports.py deleted file mode 100644 index 2a9e4bca1..000000000 --- a/employee/imports.py +++ /dev/null @@ -1,13 +0,0 @@ -from import_export import resources -from employee.models import Employee - -class EmployeeResource(resources.ModelResource): - - class Meta: - model = Employee - # exclude = ('id',) - fields = '__all__' - - def before_save_instance(self, instance, using_transactions, dry_run): - pass - diff --git a/employee/models.py b/employee/models.py index fa8a87978..09b9d58fc 100644 --- a/employee/models.py +++ b/employee/models.py @@ -1,66 +1,112 @@ +""" +models.py + +This module is used to register models for employee app + +""" +from datetime import date from django.db import models from django.contrib.auth.models import User, Permission -from base.models import Company, JobPosition, WorkType, EmployeeType, JobRole, Department, EmployeeShift +from django.utils.translation import gettext_lazy as trans from simple_history.models import HistoricalRecords -from datetime import date -from django.core.exceptions import ValidationError -from django.utils.translation import gettext_lazy as _ +from base.models import ( + Company, + JobPosition, + WorkType, + EmployeeType, + JobRole, + Department, + EmployeeShift, +) # create your model + def reporting_manager_validator(value): + """ + Method to implement reporting manager_validator + """ return value class Employee(models.Model): + """ + Employee model + """ + choice_gender = [ - ('male', _('Male')), - ('female', _('Female')), - ('other', _('Other')), + ("male", trans("Male")), + ("female", trans("Female")), + ("other", trans("Other")), ] choice_marital = ( - ('single', _('Single')), - ('married', _('Married')), - ('divorced', _('Divorced')), + ("single", trans("Single")), + ("married", trans("Married")), + ("divorced", trans("Divorced")), ) - badge_id = models.CharField(max_length=50,null=True,blank=True) + badge_id = models.CharField(max_length=50, null=True, blank=True) employee_user_id = models.OneToOneField( - User, on_delete=models.CASCADE, blank=True, null=True, related_name='employee_get') + User, + on_delete=models.CASCADE, + blank=True, + null=True, + related_name="employee_get", + ) employee_first_name = models.CharField( - max_length=200, null=False,) - employee_last_name = models.CharField( - max_length=200, null=True, blank=True) - employee_profile = models.ImageField(upload_to='employee/profile',null=True,blank=True) - email = models.EmailField(max_length=254,unique=True) - phone = models.CharField(max_length=15,) + max_length=200, + null=False, + ) + employee_last_name = models.CharField(max_length=200, null=True, blank=True) + employee_profile = models.ImageField( + upload_to="employee/profile", null=True, blank=True + ) + email = models.EmailField(max_length=254, unique=True) + phone = models.CharField( + max_length=15, + ) address = models.TextField(max_length=200, blank=True, null=True) country = models.CharField(max_length=30, blank=True, null=True) - state =models.CharField(max_length=30,null=True,blank=True) - zip = models.CharField(max_length=20,null=True,blank=True) + state = models.CharField(max_length=30, null=True, blank=True) + zip = models.CharField(max_length=20, null=True, blank=True) dob = models.DateField(null=True, blank=True) - gender = models.CharField(max_length=10, choices=choice_gender, default='male') + gender = models.CharField(max_length=10, choices=choice_gender, default="male") qualification = models.CharField(max_length=50, blank=True, null=True) experience = models.IntegerField(null=True, blank=True) marital_status = models.CharField( - max_length=50, blank=True, null=True, choices=choice_marital, default='single') - children = models.IntegerField(blank=True,null=True) - emergency_contact = models.CharField(max_length=15,null=True,blank=True) - emergency_contact_name = models.CharField(max_length=20,null=True,blank=True) - emergency_contact_relation = models.CharField(max_length=20,null=True,blank=True) + max_length=50, blank=True, null=True, choices=choice_marital, default="single" + ) + children = models.IntegerField(blank=True, null=True) + emergency_contact = models.CharField(max_length=15, null=True, blank=True) + emergency_contact_name = models.CharField(max_length=20, null=True, blank=True) + emergency_contact_relation = models.CharField(max_length=20, null=True, blank=True) is_active = models.BooleanField(default=True) - additional_info = models.JSONField(null=True,blank=True) - + additional_info = models.JSONField(null=True, blank=True) def __str__(self) -> str: - last_name = self.employee_last_name if self.employee_last_name is not None else '' - return f'{self.employee_first_name} {last_name}' + last_name = ( + self.employee_last_name if self.employee_last_name is not None else "" + ) + return f"{self.employee_first_name} {last_name}" class Meta: - unique_together = ('employee_first_name', 'employee_last_name') - permissions =(('change_ownprofile','Update own profile'),('view_ownprofile','View Own Profile')) - ordering = ['employee_first_name',] + """ + Recruitment model + """ + + unique_together = ("employee_first_name", "employee_last_name") + permissions = ( + ("change_ownprofile", "Update own profile"), + ("view_ownprofile", "View Own Profile"), + ) + ordering = [ + "employee_first_name", + ] constraints = [ - models.UniqueConstraint(fields=['badge_id'], condition=models.Q(badge_id__isnull=False), name='unique_badge_id') + models.UniqueConstraint( + fields=["badge_id"], + condition=models.Q(badge_id__isnull=False), + name="unique_badge_id", + ) ] def days_until_birthday(self): @@ -74,87 +120,136 @@ class Employee(models.Model): next_birthday = date(today.year + 1, birthday.month, birthday.day) return (next_birthday - today).days - - - def save(self, *args, **kwargs): # your custom code here # ... # call the parent class's save method to save the object - super(Employee, self).save(*args, **kwargs) + super().save(*args, **kwargs) employee = self if employee.employee_user_id is None: # Create user if no corresponding user exists username = self.email password = self.phone user = User.objects.create_user( - username=username, email=username, password=password) + username=username, email=username, password=password + ) self.employee_user_id = user - '''default permissions''' - change_ownprofile = Permission.objects.get(codename='change_ownprofile') - view_ownprofile = Permission.objects.get(codename='view_ownprofile') + # default permissions + change_ownprofile = Permission.objects.get(codename="change_ownprofile") + view_ownprofile = Permission.objects.get(codename="view_ownprofile") user.user_permissions.add(view_ownprofile) user.user_permissions.add(change_ownprofile) return self.save() + return self + class EmployeeWorkInformation(models.Model): - employee_id = models.OneToOneField( - Employee, on_delete=models.CASCADE, null=True,related_name='employee_work_info') - job_position_id = models.ForeignKey( - JobPosition, on_delete=models.DO_NOTHING, null=True,verbose_name="Job Position") - department_id = models.ForeignKey(Department, on_delete=models.DO_NOTHING,null=True,blank=True,verbose_name="Department") - work_type_id = models.ForeignKey( - WorkType, on_delete=models.DO_NOTHING, null=True, blank=True,verbose_name="Work Type") - employee_type_id = models.ForeignKey( - EmployeeType, on_delete=models.CASCADE, null=True, blank=True,verbose_name="Employee Type") - job_role_id = models.ForeignKey(JobRole,on_delete=models.DO_NOTHING,null=True,blank=True,verbose_name="Job Role") - reporting_manager_id = models.ForeignKey( - Employee, on_delete=models.DO_NOTHING, blank=True, null=True, related_name='reporting_manager',verbose_name="Reporting Manager") - company_id = models.ForeignKey( - Company, on_delete=models.CASCADE, blank=True, null=True,verbose_name="Company") - location = models.CharField(max_length=50,blank=True) - email = models.EmailField(max_length=254,blank=True,null=True) - mobile = models.CharField(max_length=254,blank=True,null=True) - shift_id = models.ForeignKey(EmployeeShift,on_delete=models.DO_NOTHING,null=True,verbose_name="Shift") - date_joining = models.DateField(null=True, blank=True) - contract_end_date = models.DateField(blank=True,null=True) - basic_salary = models.IntegerField(null=True,blank=True, default=0) - salary_hour = models.IntegerField(null=True,blank=True,default=0) - additional_info = models.JSONField(null=True,blank=True) + """ + EmployeeWorkInformation model + """ - # attachments = models.FileField(upload_to='employee/attachments',null=True,default=0,blank=True) + employee_id = models.OneToOneField( + Employee, + on_delete=models.CASCADE, + blank=True, + null=True, + related_name="employee_work_info", + ) + job_position_id = models.ForeignKey( + JobPosition, on_delete=models.DO_NOTHING, null=True, verbose_name="Job Position" + ) + department_id = models.ForeignKey( + Department, + on_delete=models.DO_NOTHING, + null=True, + blank=True, + verbose_name="Department", + ) + work_type_id = models.ForeignKey( + WorkType, + on_delete=models.DO_NOTHING, + null=True, + blank=True, + verbose_name="Work Type", + ) + employee_type_id = models.ForeignKey( + EmployeeType, + on_delete=models.CASCADE, + null=True, + blank=True, + verbose_name="Employee Type", + ) + job_role_id = models.ForeignKey( + JobRole, + on_delete=models.DO_NOTHING, + null=True, + blank=True, + verbose_name="Job Role", + ) + reporting_manager_id = models.ForeignKey( + Employee, + on_delete=models.DO_NOTHING, + blank=True, + null=True, + related_name="reporting_manager", + verbose_name="Reporting Manager", + ) + company_id = models.ForeignKey( + Company, on_delete=models.CASCADE, blank=True, null=True, verbose_name="Company" + ) + location = models.CharField(max_length=50, blank=True) + email = models.EmailField(max_length=254, blank=True, null=True) + mobile = models.CharField(max_length=254, blank=True, null=True) + shift_id = models.ForeignKey( + EmployeeShift, + on_delete=models.DO_NOTHING, + null=True, + blank=True, + verbose_name="Shift", + ) + date_joining = models.DateField(null=True, blank=True) + contract_end_date = models.DateField(blank=True, null=True) + basic_salary = models.IntegerField(null=True, blank=True, default=0) + salary_hour = models.IntegerField(null=True, blank=True, default=0) + additional_info = models.JSONField(null=True, blank=True) history = HistoricalRecords( - related_name='employee_work_info_history', + related_name="employee_work_info_history", ) def __str__(self) -> str: - return f'{self.employee_id} - {self.job_position_id}' - class Meta: - permissions = [('view_ownworkinfo','View Own Work Info'),('view_history','View Employee Work Information History')] + return f"{self.employee_id} - {self.job_position_id}" def save(self, *args, **kwargs): self.full_clean() super().save(*args, **kwargs) - - - - class EmployeeBankDetails(models.Model): - employee_id = models.OneToOneField(Employee, on_delete=models.CASCADE,null=True,related_name='employee_bank_details') + """ + EmployeeBankDetails model + """ + + employee_id = models.OneToOneField( + Employee, + on_delete=models.CASCADE, + null=True, + related_name="employee_bank_details", + ) bank_name = models.CharField(max_length=50) - account_number = models.CharField(max_length=50, null=False, blank=False,unique=True) + account_number = models.CharField( + max_length=50, null=False, blank=False, unique=True + ) branch = models.CharField(max_length=50) address = models.TextField(max_length=300) country = models.CharField(max_length=50, blank=True, null=True) state = models.CharField(max_length=50, blank=True) city = models.CharField(max_length=50, blank=True) - any_other_code1 = models.CharField(max_length=50,verbose_name="Bank Code #1") - any_other_code2 = models.CharField(max_length=50,null=True, blank=True,verbose_name="Bank Code #2") - additional_info = models.JSONField(null=True,blank=True) + any_other_code1 = models.CharField(max_length=50, verbose_name="Bank Code #1") + any_other_code2 = models.CharField( + max_length=50, null=True, blank=True, verbose_name="Bank Code #2" + ) + additional_info = models.JSONField(null=True, blank=True) def __str__(self) -> str: - return f'{self.employee_id}-{self.bank_name}' - + return f"{self.employee_id}-{self.bank_name}" diff --git a/employee/templatetags/employeefilters.py b/employee/templatetags/employeefilters.py deleted file mode 100644 index 26443c5ab..000000000 --- a/employee/templatetags/employeefilters.py +++ /dev/null @@ -1,67 +0,0 @@ -from django.template.defaultfilters import register -from django import template -from django.contrib.auth.models import User - -register = template.Library() - - - -def changed_field(fields): - changed_fields = '' - for field in fields: - changed_fields = changed_fields + f''' -
{field}
- '''.strip() - return changed_fields - -def old_value(fields,old_record): - old_values = [getattr(old_record, field) for field in fields] - values = '' - for value in old_values: - values = f'''{values} -
{value}
- ''' - return values - -def new_value(fields,new_value): - new_values = [getattr(new_value, field) for field in fields] - values = '' - for value in new_values: - values = f'''{values} -
{value}
- ''' - return values - -@register.filter(name='history') -def history(history): - length = len(history) - if length >= 2: - flag = 0 - html = [] - for hist in history: - if flag < length-1: - next_hist = history[flag+1] - delta = hist.diff_against(next_hist) - changed_fields= delta.changed_fields - old_record = delta.old_record - new_record = delta.new_record - html.append( - f''' - - {changed_field(changed_fields)} - {old_value(changed_fields,old_record)} - {new_value(changed_fields,new_record)} -
{hist.history_date}
-
{User.objects.get(id=hist.history_user_id).employee_get}
- - ''' - ) - flag = flag + 1 - return html - - - -@register.filter(name='options_get') -def options_get(opt): - return opt.__dict__['form'].fields[opt.name].__dict__['_queryset'] - diff --git a/employee/urls.py b/employee/urls.py index 93206a33a..fa4201b8d 100644 --- a/employee/urls.py +++ b/employee/urls.py @@ -1,92 +1,148 @@ -from django.urls import path, include +""" +urls.py + +This module is used to map url path with view methods. +""" +from django.urls import path from employee import views urlpatterns = [ - path('employee-profile', views.employee_profile,name='employee-profile'), - - path('employee-view//', views.employee_view_individual,name='employee-view-individual'), - - path('edit-profile',views.self_info_update,name='edit-profile'), - - path('update-profile-image//',views.update_profile_image,name='update-profile-image'), - - path('update-own-profile-image',views.update_own_profile_image,name='update-own-profile-image'), - - path('remove-profile-image//',views.remove_profile_image,name='remove-profile-image'), - - path('remove-own-profile-image',views.remove_own_profile_image,name='remove-own-profile-image'), - - path('employee-profile-bank-details',views.employee_profile_bank_details,name='employee-profile-bank-update'), - - path('employee-view', views.employee_view,name='employee-view'), - - path('employee-view-new',views.employee_view_new,name='employee-view-new'), - - path('employee-view-update//', views.employee_view_update,name='employee-view-update'), - - path('employee-create-personal-info', views.employee_update_personal_info,name='employee-create-personal-info'), - - path('employee-update-personal-info//', views.employee_update_personal_info,name='employee-update-personal-info'), - - path('employee-create-work-info', views.employee_update_work_info,name='employee-create-work-info'), - - path('employee-update-work-info//', views.employee_update_work_info,name='employee-update-work-info'), - - path('employee-create-bank-details', views.employee_update_bank_details,name='employee-create-bank-details'), - - path('employee-update-bank-details//', views.employee_update_bank_details,name='employee-update-bank-details'), - - path('employee-filter-view',views.employee_filter_view,name='employee-filter-view'), - - path('employee-view-card', views.employee_card,name='employee-view-card'), - - path('employee-view-list', views.employee_list,name='employee-view-list'), - - path('search-employee', views.employee_search,name='search-employee'), - - path('employee-create', views.employee_create,name='employee-create'), - - path('employee-update//', views.employee_update,name='employee-update'), - - path('employee-delete//', views.employee_delete,name='employee-delete'), - - path("employee-bulk-delete",views.employee_bulk_delete, name="employee-bulk-delete"), - - path("employee-bulk-archive",views.employee_bulk_archive, name="employee-bulk-archive"), - - path('employee-archive//', views.employee_archive,name='employee-archive'), - - path('employee-user-group-assign-delete//',views.employee_user_group_assign_delete,name='employee-user-group-assign-delete'), - - path('employee-work-info-view-create//',views.employee_work_info_view_create,name='employee-work-info-view-create'), - - path('employee-bank-details-view-create//',views.employee_bank_details_view_create,name='employee-bank-details-view-create'), - - path('employee-bank-details-view-update//',views.employee_bank_details_view_update,name='employee-bank-details-view-update'), - - path('employee-work-info-view-update//',views.employee_work_info_view_update,name='employee-work-info-view-update'), - - path('employee-work-information-delete//',views.employee_work_information_delete,name='employee-work-information-delete'), - - path('employee-import',views.employee_import,name='employee-import'), - - path('employee-export',views.employee_export,name='employee-export'), - - path('work-info-import',views.work_info_import,name='work-info-import'), - - path('work-info-export',views.work_info_export,name='work-info-export'), - - path('get-birthday',views.get_employees_birthday,name='get-birthday'), - - path('dashboard',views.dashboard,name='dashboard'), - - path('dashboard-employee',views.dashboard_employee,name='dashboard-employee'), - - path('dashboard-employee-gender',views.dashboard_employee_gender,name='dashboard-employee-gender'), - - path('dashboard-employee-department',views.dashboard_employee_department,name='dashboard-employee-department'), - - path('dashboard-employee-count',views.dashboard_employee_tiles,name='dashboard-employee-count'), - - + path("employee-profile", views.employee_profile, name="employee-profile"), + path( + "employee-view//", + views.employee_view_individual, + name="employee-view-individual", + ), + path("edit-profile", views.self_info_update, name="edit-profile"), + path( + "update-profile-image//", + views.update_profile_image, + name="update-profile-image", + ), + path( + "update-own-profile-image", + views.update_own_profile_image, + name="update-own-profile-image", + ), + path( + "remove-profile-image//", + views.remove_profile_image, + name="remove-profile-image", + ), + path( + "remove-own-profile-image", + views.remove_own_profile_image, + name="remove-own-profile-image", + ), + path( + "employee-profile-bank-details", + views.employee_profile_bank_details, + name="employee-profile-bank-update", + ), + path("employee-view", views.employee_view, name="employee-view"), + path("employee-view-new", views.employee_view_new, name="employee-view-new"), + path( + "employee-view-update//", + views.employee_view_update, + name="employee-view-update", + ), + path( + "employee-create-personal-info", + views.employee_update_personal_info, + name="employee-create-personal-info", + ), + path( + "employee-update-personal-info//", + views.employee_update_personal_info, + name="employee-update-personal-info", + ), + path( + "employee-create-work-info", + views.employee_update_work_info, + name="employee-create-work-info", + ), + path( + "employee-update-work-info//", + views.employee_update_work_info, + name="employee-update-work-info", + ), + path( + "employee-create-bank-details", + views.employee_update_bank_details, + name="employee-create-bank-details", + ), + path( + "employee-update-bank-details//", + views.employee_update_bank_details, + name="employee-update-bank-details", + ), + path( + "employee-filter-view", views.employee_filter_view, name="employee-filter-view" + ), + path("employee-view-card", views.employee_card, name="employee-view-card"), + path("employee-view-list", views.employee_list, name="employee-view-list"), + path("search-employee", views.employee_search, name="search-employee"), + path("employee-update//", views.employee_update, name="employee-update"), + path("employee-delete//", views.employee_delete, name="employee-delete"), + path( + "employee-bulk-delete", views.employee_bulk_delete, name="employee-bulk-delete" + ), + path( + "employee-bulk-archive", + views.employee_bulk_archive, + name="employee-bulk-archive", + ), + path("employee-archive//", views.employee_archive, name="employee-archive"), + path( + "employee-user-group-assign-delete//", + views.employee_user_group_assign_delete, + name="employee-user-group-assign-delete", + ), + path( + "employee-work-info-view-create//", + views.employee_work_info_view_create, + name="employee-work-info-view-create", + ), + path( + "employee-bank-details-view-create//", + views.employee_bank_details_view_create, + name="employee-bank-details-view-create", + ), + path( + "employee-bank-details-view-update//", + views.employee_bank_details_view_update, + name="employee-bank-details-view-update", + ), + path( + "employee-work-info-view-update//", + views.employee_work_info_view_update, + name="employee-work-info-view-update", + ), + path( + "employee-work-information-delete//", + views.employee_work_information_delete, + name="employee-work-information-delete", + ), + path("employee-import", views.employee_import, name="employee-import"), + path("employee-export", views.employee_export, name="employee-export"), + path("work-info-import", views.work_info_import, name="work-info-import"), + path("work-info-export", views.work_info_export, name="work-info-export"), + path("get-birthday", views.get_employees_birthday, name="get-birthday"), + path("dashboard", views.dashboard, name="dashboard"), + path("dashboard-employee", views.dashboard_employee, name="dashboard-employee"), + path( + "dashboard-employee-gender", + views.dashboard_employee_gender, + name="dashboard-employee-gender", + ), + path( + "dashboard-employee-department", + views.dashboard_employee_department, + name="dashboard-employee-department", + ), + path( + "dashboard-employee-count", + views.dashboard_employee_tiles, + name="dashboard-employee-count", + ), ] diff --git a/employee/views.py b/employee/views.py index 56de530bf..d3bc1c17e 100644 --- a/employee/views.py +++ b/employee/views.py @@ -1,31 +1,58 @@ -from datetime import date -from collections import defaultdict -from base.models import Department, JobPosition, JobRole, WorkType, EmployeeShift, EmployeeType, Company -from employee.filters import EmployeeFilter -from django.shortcuts import render, redirect -from django.contrib.auth.models import User, Permission -from horilla.decorators import permission_required, login_required, hx_request_required, manager_can_enter -from base.methods import choosesubordinates, filtersubordinates, is_reportingmanager, filtersubordinatesemployeemodel, choosesubordinatesemployeemodel, sortby -from employee.forms import EmployeeForm, EmployeeBankDetailsForm, EmployeeWorkInformationForm, EmployeeProfileBankDetailsForm, UserForm, UserPermissionForm, EmployeeWorkInformationUpdateForm, EmployeeBankDetailsUpdateForm -from employee.models import Employee, EmployeeWorkInformation, EmployeeBankDetails -from django.http import HttpResponse, HttpResponseRedirect, JsonResponse -from django.contrib import messages -from django.views.decorators.http import require_http_methods -from django.core.paginator import Paginator -from django.views.decorators.http import require_http_methods -import json -from notifications.signals import notify -from datetime import datetime, timedelta -import calendar -from django.db.models import F -from django.utils.translation import gettext_lazy as _ +""" +views.py +This module contains the view functions for handling HTTP requests and rendering +responses in your application. -import pandas as pd +Each view function corresponds to a specific URL route and performs the necessary +actions to handle the request, process data, and generate a response. +This module is part of the recruitment project and is intended to +provide the main entry points for interacting with the application's functionality. +""" -from django.conf import settings import os +import json +import calendar +from datetime import datetime, timedelta, date +from collections import defaultdict +import pandas as pd +from django.db.models import F +from django.conf import settings +from django.contrib import messages +from django.core.paginator import Paginator +from django.shortcuts import render, redirect +from django.utils.translation import gettext_lazy as _ +from django.contrib.auth.models import User +from django.views.decorators.http import require_http_methods +from django.http import HttpResponse, HttpResponseRedirect, JsonResponse +from notifications.signals import notify +from horilla.decorators import ( + permission_required, + login_required, + hx_request_required, + manager_can_enter, +) +from base.models import ( + Department, + JobPosition, + JobRole, + WorkType, + EmployeeShift, + EmployeeType, + Company, +) +from base.methods import filtersubordinates, filtersubordinatesemployeemodel, sortby +from employee.filters import EmployeeFilter +from employee.forms import ( + EmployeeForm, + EmployeeBankDetailsForm, + EmployeeWorkInformationForm, + EmployeeWorkInformationUpdateForm, + EmployeeBankDetailsUpdateForm, +) +from employee.models import Employee, EmployeeWorkInformation, EmployeeBankDetails + # Create your views here. @@ -37,7 +64,7 @@ def employee_profile(request): """ user = request.user employee = Employee.objects.filter(employee_user_id=user).first() - return render(request, 'employee/profile/profile_view.html', {'employee': employee}) + return render(request, "employee/profile/profile_view.html", {"employee": employee}) @login_required @@ -48,47 +75,55 @@ def self_info_update(request): user = request.user employee = Employee.objects.filter(employee_user_id=user).first() bank_form = EmployeeBankDetailsForm( - instance=EmployeeBankDetails.objects.filter(employee_id=employee).first()) - form = EmployeeForm(instance=Employee.objects.filter( - employee_user_id=user).first()) + instance=EmployeeBankDetails.objects.filter(employee_id=employee).first() + ) + form = EmployeeForm(instance=Employee.objects.filter(employee_user_id=user).first()) if request.POST: - if request.POST.get('employee_first_name') is not None: - instance = Employee.objects.filter( - employee_user_id=request.user).first() + if request.POST.get("employee_first_name") is not None: + instance = Employee.objects.filter(employee_user_id=request.user).first() form = EmployeeForm(request.POST, instance=instance) if form.is_valid(): instance = form.save(commit=False) instance.employee_user_id = user instance.save() - messages.success(request, _('Profile updated.')) - elif request.POST.get('any_other_code1') is not None: - instance = EmployeeBankDetails.objects.filter( - employee_id=employee).first() - bank_form = EmployeeBankDetailsForm( - request.POST, instance=instance) + messages.success(request, _("Profile updated.")) + elif request.POST.get("any_other_code1") is not None: + instance = EmployeeBankDetails.objects.filter(employee_id=employee).first() + bank_form = EmployeeBankDetailsForm(request.POST, instance=instance) if bank_form.is_valid(): instance = bank_form.save(commit=False) instance.employee_id = employee instance.save() - messages.success(request, _('Bank details updated.')) - return render(request, 'employee/profile/profile.html', {'form': form, 'bank_form': bank_form, }) + messages.success(request, _("Bank details updated.")) + return render( + request, + "employee/profile/profile.html", + { + "form": form, + "bank_form": bank_form, + }, + ) @login_required -@manager_can_enter('employee.view_employee') -def employee_view_individual(request, id): +@manager_can_enter("employee.view_employee") +def employee_view_individual(request, obj_id): """ This method is used to view profile of an employee. """ - employee = Employee.objects.get(id=id) + employee = Employee.objects.get(id=obj_id) user = Employee.objects.filter(employee_user_id=request.user).first() - if user.reporting_manager.filter(employee_id=employee).exists() or request.user.has_perm('employee.change_employee'): - return render(request, 'employee/view/individual.html', {'employee': employee}) - return HttpResponseRedirect(request.META.get('HTTP_REFERER', '/employee/employee-view')) + if user.reporting_manager.filter( + employee_id=employee + ).exists() or request.user.has_perm("employee.change_employee"): + return render(request, "employee/view/individual.html", {"employee": employee}) + return HttpResponseRedirect( + request.META.get("HTTP_REFERER", "/employee/employee-view") + ) @login_required -@require_http_methods(['POST']) +@require_http_methods(["POST"]) def employee_profile_bank_details(request): """ This method is used to fill self bank details @@ -100,12 +135,12 @@ def employee_profile_bank_details(request): bank_info = form.save(commit=False) bank_info.employee_id = employee bank_info.save() - messages.success(request, _('Bank details updated')) - return HttpResponseRedirect(request.META.get('HTTP_REFERER')) + messages.success(request, _("Bank details updated")) + return HttpResponseRedirect(request.META.get("HTTP_REFERER")) @login_required -@permission_required('employee.view_profile') +@permission_required("employee.view_profile") def employee_profile_update(request): """ This method is used update own profile of the requested employee @@ -113,71 +148,25 @@ def employee_profile_update(request): employee_user = request.user employee = Employee.objects.get(employee_user_id=employee_user) - if employee_user.has_perm('employee.change_profile'): - if request.method == 'POST': - form = EmployeeForm( - request.POST, request.FILES, instance=employee) + if employee_user.has_perm("employee.change_profile"): + if request.method == "POST": + form = EmployeeForm(request.POST, request.FILES, instance=employee) if form.is_valid(): form.save() - messages.success(request, _('Profile updated.')) - return redirect('/employee/employee-profile') + messages.success(request, _("Profile updated.")) + return redirect("/employee/employee-profile") @login_required -@permission_required('delete_group') -@require_http_methods(['POST']) -def employee_user_group_assign_delete(request, id): +@permission_required("delete_group") +@require_http_methods(["POST"]) +def employee_user_group_assign_delete(_, obj_id): """ This method is used to delete user group assign """ - user = User.objects.get(id=id) + user = User.objects.get(id=obj_id) user.groups.clear() - return redirect('/employee/employee-user-group-assign-view') - - -@login_required -@permission_required('employee.add_employee') -def employee_create(request): - """ - This method will render employee creation form template and save if the form is valid - """ - form = EmployeeForm() - permission_form = UserPermissionForm() - if request.method == 'POST': - form = EmployeeForm(request.POST, request.FILES) - if form.is_valid(): - username = request.POST['email'] - password = request.POST['phone'] - groups = request.POST.getlist('groups') - permissions = request.POST.getlist('user_permissions') - superuser_status = "superuser" in request.POST - users = User.objects.all() - username_exist = any(user.username == username for user in users) - if not username_exist: - form.save() - messages.success(request, _('Employee added.')) - new_user = User.objects.create_user( - username=username, email=username, password=password, is_superuser=superuser_status) - new_user.groups.set(groups) - new_user.user_permissions.set(permissions) - new_employee = Employee.objects.get(email=username) - new_employee.employee_user_id = new_user - - """ - default permissions - """ - - change_ownprofile = Permission.objects.get( - codename='change_ownprofile') - view_ownworkinfo = Permission.objects.get( - codename='view_ownworkinfo') - view_ownprofile = Permission.objects.get( - codename='view_ownprofile') - new_user.user_permissions.add(view_ownprofile) - new_user.user_permissions.add(change_ownprofile) - new_user.user_permissions.add(view_ownworkinfo) - new_employee.save() - return render(request, 'employee_personal_info/employee_create_form.html', {'form': form, 'perm_form': permission_form}) + return redirect("/employee/employee-user-group-assign-view") def paginator_qry(qryset, page_number): @@ -190,22 +179,31 @@ def paginator_qry(qryset, page_number): @login_required -@manager_can_enter('employee.view_employee') +@manager_can_enter("employee.view_employee") def employee_view(request): """ This method is used to render template for view all employee """ - previous_data = request.environ['QUERY_STRING'] + previous_data = request.environ["QUERY_STRING"] employees = Employee.objects.filter(is_active=True) - page_number = request.GET.get('page') - filter = EmployeeFilter(queryset=employees) + page_number = request.GET.get("page") + filter_obj = EmployeeFilter(queryset=employees) employees = filtersubordinatesemployeemodel( - request, filter.qs, 'employee.view_employee') - return render(request, 'employee_personal_info/employee_view.html', {'data': paginator_qry(employees, page_number), 'pd': previous_data, 'f': filter}) + request, filter_obj.qs, "employee.view_employee" + ) + return render( + request, + "employee_personal_info/employee_view.html", + { + "data": paginator_qry(employees, page_number), + "pd": previous_data, + "f": filter_obj, + }, + ) @login_required -@permission_required('employee.add_employee') +@permission_required("employee.add_employee") def employee_view_new(request): """ This method is used to render form to create a new employee. @@ -213,26 +211,36 @@ def employee_view_new(request): form = EmployeeForm() work_form = EmployeeWorkInformationForm() bank_form = EmployeeBankDetailsForm() - f = EmployeeFilter(queryset=Employee.objects.all()) - return render(request, 'employee/create_form/form_view.html', {'form': form, 'work_form': work_form, 'bank_form': bank_form, 'f': f}) + filter_obj = EmployeeFilter(queryset=Employee.objects.all()) + return render( + request, + "employee/create_form/form_view.html", + {"form": form, "work_form": work_form, "bank_form": bank_form, "f": filter_obj}, + ) @login_required -@manager_can_enter('employee.change_employee') -def employee_view_update(request, id): +@manager_can_enter("employee.change_employee") +def employee_view_update(request, obj_id): """ This method is used to render update form for employee. """ user = Employee.objects.filter(employee_user_id=request.user).first() - employee = Employee.objects.filter(id=id).first() - if user.reporting_manager.filter(employee_id=employee).exists() or request.user.has_perm('employee.change_employee'): + employee = Employee.objects.filter(id=obj_id).first() + if user.reporting_manager.filter( + employee_id=employee + ).exists() or request.user.has_perm("employee.change_employee"): form = EmployeeForm(instance=employee) work_form = EmployeeWorkInformationForm( - instance=EmployeeWorkInformation.objects.filter(employee_id=employee).first()) + instance=EmployeeWorkInformation.objects.filter( + employee_id=employee + ).first() + ) bank_form = EmployeeBankDetailsForm( - instance=EmployeeBankDetails.objects.filter(employee_id=employee).first()) + instance=EmployeeBankDetails.objects.filter(employee_id=employee).first() + ) if request.POST: - if request.POST.get('employee_first_name') is not None: + if request.POST.get("employee_first_name") is not None: form = EmployeeForm(request.POST, instance=employee) print("----------") print(request.POST) @@ -240,132 +248,186 @@ def employee_view_update(request, id): if form.is_valid(): form.save() messages.success( - request, _('Employee personal information updated.')) - elif request.POST.get('reporting_manager_id') is not None: + request, _("Employee personal information updated.") + ) + elif request.POST.get("reporting_manager_id") is not None: instance = EmployeeWorkInformation.objects.filter( - employee_id=employee).first() + employee_id=employee + ).first() work_form = EmployeeWorkInformationUpdateForm( - request.POST, instance=instance) + request.POST, instance=instance + ) if work_form.is_valid(): instance = work_form.save(commit=False) instance.employee_id = employee instance.save() - notify.send(request.user.employee_get, recipient=instance.employee_id.employee_user_id, - verb='Your work details has been updated.', redirect='/employee/employee-profile', icon='briefcase') - messages.success( - request, _('Employee work information updated.')) - elif request.POST.get('any_other_code1'): + notify.send( + request.user.employee_get, + recipient=instance.employee_id.employee_user_id, + verb="Your work details has been updated.", + redirect="/employee/employee-profile", + icon="briefcase", + ) + messages.success(request, _("Employee work information updated.")) + elif request.POST.get("any_other_code1"): instance = EmployeeBankDetails.objects.filter( - employee_id=employee).first() + employee_id=employee + ).first() bank_form = EmployeeBankDetailsUpdateForm( - request.POST, instance=instance) + request.POST, instance=instance + ) if bank_form.is_valid(): instance = bank_form.save(commit=False) instance.employee_id = employee instance.save() - messages.success(request, _('Employee bank details updated.')) - return render(request, 'employee/update_form/form_view.html', {'form': form, 'work_form': work_form, 'bank_form': bank_form}) - return HttpResponseRedirect(request.META.get('HTTP_REFERER', '/employee/employee-view')) + messages.success(request, _("Employee bank details updated.")) + return render( + request, + "employee/update_form/form_view.html", + {"form": form, "work_form": work_form, "bank_form": bank_form}, + ) + return HttpResponseRedirect( + request.META.get("HTTP_REFERER", "/employee/employee-view") + ) @login_required -@require_http_methods(['POST']) -@permission_required('employee.change_employee') -def update_profile_image(request, id): +@require_http_methods(["POST"]) +@permission_required("employee.change_employee") +def update_profile_image(request, obj_id): """ This method is used to upload a profile image """ try: - employee = Employee.objects.get(id=id) - img = request.FILES['employee_profile'] + employee = Employee.objects.get(id=obj_id) + img = request.FILES["employee_profile"] employee.employee_profile = img employee.save() - messages.success(request, _('Profile image updated.')) - except Exception as e: - messages.error(request, _('No image chosen.')) - response = render(request, 'employee/profile/profile_modal.html',) - return HttpResponse(response.content.decode('utf-8') + '') + messages.success(request, _("Profile image updated.")) + except Exception: + messages.error(request, _("No image chosen.")) + response = render( + request, + "employee/profile/profile_modal.html", + ) + return HttpResponse( + response.content.decode("utf-8") + "" + ) @login_required -@require_http_methods(['POST']) +@require_http_methods(["POST"]) def update_own_profile_image(request): """ This method is used to update own profile image from profile view form """ employee = request.user.employee_get - img = request.FILES.get('employee_profile') + img = request.FILES.get("employee_profile") employee.employee_profile = img employee.save() - messages.success(request, _('Profile image updated.')) - response = render(request, 'employee/profile/profile_modal.html',) - return HttpResponse(response.content.decode('utf-8') + '') + messages.success(request, _("Profile image updated.")) + response = render( + request, + "employee/profile/profile_modal.html", + ) + return HttpResponse( + response.content.decode("utf-8") + "" + ) @login_required -@require_http_methods(['DELETE']) -@permission_required('employee.change_employee') -def remove_profile_image(request, id): +@require_http_methods(["DELETE"]) +@permission_required("employee.change_employee") +def remove_profile_image(request, obj_id): """ This method is used to remove uploaded image - args: id Employee model instance id + Args: obj_id : Employee model instance id """ - employee = Employee.objects.get(id=id) - if employee.employee_profile.name == '': - messages.info(request, _('No profile image to remove.')) - response = render(request, 'employee/profile/profile_modal.html',) - return HttpResponse(response.content.decode('utf-8') + '') + employee = Employee.objects.get(id=obj_id) + if employee.employee_profile.name == "": + messages.info(request, _("No profile image to remove.")) + response = render( + request, + "employee/profile/profile_modal.html", + ) + return HttpResponse( + response.content.decode("utf-8") + "" + ) file_path = employee.employee_profile.path absolute_path = os.path.join(settings.MEDIA_ROOT, file_path) os.remove(absolute_path) employee.employee_profile = None employee.save() - messages.success(request, _('Profile image removed.')) - response = render(request, 'employee/profile/profile_modal.html',) - return HttpResponse(response.content.decode('utf-8') + '') + messages.success(request, _("Profile image removed.")) + response = render( + request, + "employee/profile/profile_modal.html", + ) + return HttpResponse( + response.content.decode("utf-8") + "" + ) @login_required -@require_http_methods(['DELETE']) +@require_http_methods(["DELETE"]) def remove_own_profile_image(request): """ This method is used to remove own profile image """ employee = request.user.employee_get - if employee.employee_profile.name == '': - messages.info(request, _('No profile image to remove.')) - response = render(request, 'employee/profile/profile_modal.html',) - return HttpResponse(response.content.decode('utf-8') + '') + if employee.employee_profile.name == "": + messages.info(request, _("No profile image to remove.")) + response = render( + request, + "employee/profile/profile_modal.html", + ) + return HttpResponse( + response.content.decode("utf-8") + "" + ) file_path = employee.employee_profile.path absolute_path = os.path.join(settings.MEDIA_ROOT, file_path) os.remove(absolute_path) employee.employee_profile = None employee.save() - messages.success(request, _('Profile image removed.')) - response = render(request, 'employee/profile/profile_modal.html',) - return HttpResponse(response.content.decode('utf-8') + '') + messages.success(request, _("Profile image removed.")) + response = render( + request, + "employee/profile/profile_modal.html", + ) + return HttpResponse( + response.content.decode("utf-8") + "" + ) @login_required -@manager_can_enter('employee.change_employee') -@require_http_methods(['POST']) -def employee_update_personal_info(request, id=None): +@manager_can_enter("employee.change_employee") +@require_http_methods(["POST"]) +def employee_update_personal_info(request, obj_id=None): """ This method is used to update employee's personal info. """ - employee = Employee.objects.filter(id=id).first() + employee = Employee.objects.filter(id=obj_id).first() form = EmployeeForm(request.POST, instance=employee) if form.is_valid(): form.save() if id is None: - messages.success(request, _('New Employee Added.')) + messages.success(request, _("New Employee Added.")) form = EmployeeForm(request.POST, instance=form.instance) work_form = EmployeeWorkInformationForm( - instance=EmployeeWorkInformation.objects.filter(employee_id=employee).first()) + instance=EmployeeWorkInformation.objects.filter( + employee_id=employee + ).first() + ) bank_form = EmployeeBankDetailsForm( - instance=EmployeeBankDetails.objects.filter(employee_id=employee).first()) - return redirect(f'employee-view-update/{form.instance.id}/', data={'form': form, 'work_form': work_form, 'bank_form': bank_form}) + instance=EmployeeBankDetails.objects.filter( + employee_id=employee + ).first() + ) + return redirect( + f"employee-view-update/{form.instance.id}/", + data={"form": form, "work_form": work_form, "bank_form": bank_form}, + ) return HttpResponse( """
@@ -377,25 +439,35 @@ def employee_update_personal_info(request, id=None): """ ) if id is None: - return render(request, 'employee/create_form/form_view.html', {'form': form, }) - errors = '\n'.join([ - f"
  • {form.fields.get(field, field).label}: {', '.join(errors)}
  • " - for field, errors in form.errors.items() - ]) + return render( + request, + "employee/create_form/form_view.html", + { + "form": form, + }, + ) + errors = "\n".join( + [ + f"
  • {form.fields.get(field, field).label}: {', '.join(errors)}
  • " + for field, errors in form.errors.items() + ] + ) return HttpResponse(f'
      {errors}
    ') @login_required -@manager_can_enter('employee.change_employeeworkinformation') -@require_http_methods(['POST']) -def employee_update_work_info(request, id=None): +@manager_can_enter("employee.change_employeeworkinformation") +@require_http_methods(["POST"]) +def employee_update_work_info(request, obj_id=None): """ This method is used to update employee work info """ - employee = Employee.objects.filter(id=id).first() + employee = Employee.objects.filter(id=obj_id).first() form = EmployeeWorkInformationForm( - request.POST, instance=EmployeeWorkInformation.objects.filter(employee_id=employee).first()) - form.fields['employee_id'].required = False + request.POST, + instance=EmployeeWorkInformation.objects.filter(employee_id=employee).first(), + ) + form.fields["employee_id"].required = False form.employee_id = employee if form.is_valid() and employee is not None: work_info = form.save(commit=False) @@ -412,23 +484,27 @@ def employee_update_work_info(request, id=None): """ ) - errors = '\n'.join([ - f"
  • {form.fields.get(field, field).label}: {', '.join(errors)}
  • " - for field, errors in form.errors.items() - ]) + errors = "\n".join( + [ + f"
  • {form.fields.get(field, field).label}: {', '.join(errors)}
  • " + for field, errors in form.errors.items() + ] + ) return HttpResponse(f'
      {errors}
    ') @login_required -@manager_can_enter('employee.change_employeebankdetails') -@require_http_methods(['POST']) -def employee_update_bank_details(request, id=None): +@manager_can_enter("employee.change_employeebankdetails") +@require_http_methods(["POST"]) +def employee_update_bank_details(request, obj_id=None): """ This method is used to render form to create employee's bank information. """ - employee = Employee.objects.filter(id=id).first() + employee = Employee.objects.filter(id=obj_id).first() form = EmployeeBankDetailsForm( - request.POST, instance=EmployeeBankDetails.objects.filter(employee_id=employee).first()) + request.POST, + instance=EmployeeBankDetails.objects.filter(employee_id=employee).first(), + ) if form.is_valid() and employee is not None: bank_info = form.save(commit=False) bank_info.employee_id = employee @@ -442,361 +518,439 @@ def employee_update_bank_details(request, id=None):
    """ ) - errors = '\n'.join([ - f"
  • {form.fields.get(field, field).label}: {', '.join(errors)}
  • " - for field, errors in form.errors.items() - ]) + errors = "\n".join( + [ + f"
  • {form.fields.get(field, field).label}: {', '.join(errors)}
  • " + for field, errors in form.errors.items() + ] + ) return HttpResponse(f'
      {errors}
    ') @login_required -@manager_can_enter('employee.view_employee') +@manager_can_enter("employee.view_employee") def employee_filter_view(request): """ This method is used to filter employee. """ - previous_data = request.environ['QUERY_STRING'] + previous_data = request.environ["QUERY_STRING"] employees = filtersubordinatesemployeemodel( - request, Employee.objects.all(), 'employee.view_employee') - filter = EmployeeFilter(request.GET, queryset=employees) - page_number = request.GET.get('page') - template = 'employee_personal_info/employee_card.html' - view = request.GET.get('view') - if view == 'list': - template = 'employee_personal_info/employee_list.html' - return render(request, template, {'data': paginator_qry(filter.qs, page_number), 'f': filter, 'pd': previous_data}) + request, Employee.objects.all(), "employee.view_employee" + ) + filter_obj = EmployeeFilter(request.GET, queryset=employees) + page_number = request.GET.get("page") + template = "employee_personal_info/employee_card.html" + view = request.GET.get("view") + if view == "list": + template = "employee_personal_info/employee_list.html" + return render( + request, + template, + { + "data": paginator_qry(filter_obj.qs, page_number), + "f": filter_obj, + "pd": previous_data, + }, + ) @login_required -@manager_can_enter('employee.view_employee') +@manager_can_enter("employee.view_employee") @hx_request_required def employee_card(request): """ This method renders card template to view all employees. """ - previous_data = request.environ['QUERY_STRING'] - search = request.GET.get('search') - if type(search) is type(None): - search = '' + previous_data = request.environ["QUERY_STRING"] + search = request.GET.get("search") + if isinstance(search,type(None)): + search = "" employees = filtersubordinatesemployeemodel( - request, Employee.objects.all(), 'employee.view_employee') - if request.GET.get('is_active') is None: - filter = EmployeeFilter(request.GET, queryset=employees.filter( - employee_first_name__icontains=search, is_active=True)) + request, Employee.objects.all(), "employee.view_employee" + ) + if request.GET.get("is_active") is None: + filter_obj = EmployeeFilter( + request.GET, + queryset=employees.filter( + employee_first_name__icontains=search, is_active=True + ), + ) else: - filter = EmployeeFilter(request.GET, queryset=employees.filter( - employee_first_name__icontains=search)) - page_number = request.GET.get('page') - employees = sortby(request, filter.qs, 'orderby') - return render(request, 'employee_personal_info/employee_card.html', {'data': paginator_qry(employees, page_number), 'f': filter, 'pd': previous_data}) + filter_obj = EmployeeFilter( + request.GET, + queryset=employees.filter(employee_first_name__icontains=search), + ) + page_number = request.GET.get("page") + employees = sortby(request, filter_obj.qs, "orderby") + return render( + request, + "employee_personal_info/employee_card.html", + { + "data": paginator_qry(employees, page_number), + "f": filter_obj, + "pd": previous_data, + }, + ) @login_required -@manager_can_enter('employee.view_employee') +@manager_can_enter("employee.view_employee") @hx_request_required def employee_list(request): """ - This method renders template to view all employees + This method renders template to view all employees """ - previous_data = request.environ['QUERY_STRING'] - search = request.GET.get('search') - if type(search) is type(None): - search = '' - if request.GET.get('is_active') is None: - filter = EmployeeFilter(request.GET, queryset=Employee.objects.filter( - employee_first_name__icontains=search, is_active=True)) + previous_data = request.environ["QUERY_STRING"] + search = request.GET.get("search") + if isinstance(search,type(None)): + search = "" + if request.GET.get("is_active") is None: + filter_obj = EmployeeFilter( + request.GET, + queryset=Employee.objects.filter( + employee_first_name__icontains=search, is_active=True + ), + ) else: - filter = EmployeeFilter(request.GET, queryset=Employee.objects.filter( - employee_first_name__icontains=search)) + filter_obj = EmployeeFilter( + request.GET, + queryset=Employee.objects.filter(employee_first_name__icontains=search), + ) employees = filtersubordinatesemployeemodel( - request, filter.qs, 'employee.view_employee') - employees = sortby(request, employees, 'orderby') - page_number = request.GET.get('page') - return render(request, 'employee_personal_info/employee_list.html', {'data': paginator_qry(employees, page_number), 'f': filter, 'pd': previous_data}) + request, filter_obj.qs, "employee.view_employee" + ) + employees = sortby(request, employees, "orderby") + page_number = request.GET.get("page") + return render( + request, + "employee_personal_info/employee_list.html", + { + "data": paginator_qry(employees, page_number), + "f": filter_obj, + "pd": previous_data, + }, + ) @login_required @hx_request_required -@manager_can_enter('employee.view_employee') -def employee_update(request, id): +@manager_can_enter("employee.view_employee") +def employee_update(request, obj_id): """ This method is used to update employee if the form is valid args: - id : employee id + obj_id : employee id """ - employee = Employee.objects.get(id=id) + employee = Employee.objects.get(id=obj_id) form = EmployeeForm(instance=employee) - work_info = EmployeeWorkInformation.objects.filter( - employee_id=employee).first() - bank_info = EmployeeBankDetails.objects.filter( - employee_id=employee).first() + work_info = EmployeeWorkInformation.objects.filter(employee_id=employee).first() + bank_info = EmployeeBankDetails.objects.filter(employee_id=employee).first() work_form = EmployeeWorkInformationForm() bank_form = EmployeeBankDetailsUpdateForm() if work_info is not None: work_form = EmployeeWorkInformationForm(instance=work_info) if bank_info is not None: bank_form = EmployeeBankDetailsUpdateForm(instance=bank_info) - if request.method == 'POST': - if request.user.has_perm('employee.change_employee'): - form = EmployeeForm( - request.POST, request.FILES, instance=employee) + if request.method == "POST": + if request.user.has_perm("employee.change_employee"): + form = EmployeeForm(request.POST, request.FILES, instance=employee) if form.is_valid(): form.save() - messages.success(request, _('Employee updated.')) - return render(request, 'employee_personal_info/employee_update_form.html', {'form': form, 'work_form': work_form, 'bank_form': bank_form}) + messages.success(request, _("Employee updated.")) + return render( + request, + "employee_personal_info/employee_update_form.html", + {"form": form, "work_form": work_form, "bank_form": bank_form}, + ) @login_required -@permission_required('employee.delete_employee') -@require_http_methods(['POST']) -def employee_delete(request, id): +@permission_required("employee.delete_employee") +@require_http_methods(["POST"]) +def employee_delete(request, obj_id): """ This method is used to delete employee args: id : employee id """ - view = request.POST.get('view') - employee = Employee.objects.get(id=id) + view = request.POST.get("view") + employee = Employee.objects.get(id=obj_id) user = employee.employee_user_id try: user.delete() - messages.success(request, _('Employee deleted')) - except Exception as e: - messages.error(request, e) - messages.error(request, _('You cannot delete this user/employee')) - return HttpResponseRedirect(request. META. get('HTTP_REFERER', f'/view={view}')) + messages.success(request, _("Employee deleted")) + except Exception as error: + messages.error(request, error) + messages.error(request, _("You cannot delete this user/employee")) + return HttpResponseRedirect(request.META.get("HTTP_REFERER", f"/view={view}")) @login_required -@permission_required('employee.delete_employee') +@permission_required("employee.delete_employee") def employee_bulk_delete(request): - ids = request.POST['ids'] + """ + This method is used to delete set of Employee instances + """ + ids = request.POST["ids"] ids = json.loads(ids) - for id in ids: - employee = Employee.objects.get(id=id) + for employee_id in ids: + employee = Employee.objects.get(id=employee_id) user = employee.employee_user_id try: user.delete() - messages.success(request, _('%(employee)s deleted.') % {'employee':employee}) - except Exception as e: - messages.error(request, e) - messages.error(request, _('You cannot delete %(employee)s.') % {'employee':employee}) + messages.success( + request, _("%(employee)s deleted.") % {"employee": employee} + ) + except Exception as error: + messages.error(request, error) + messages.error( + request, _("You cannot delete %(employee)s.") % {"employee": employee} + ) - return JsonResponse({'message': 'Success'}) + return JsonResponse({"message": "Success"}) @login_required -@permission_required('employee.delete_employee') -@require_http_methods(['POST']) +@permission_required("employee.delete_employee") +@require_http_methods(["POST"]) def employee_bulk_archive(request): - ids = request.POST['ids'] + """ + This method is used to archive bulk of Employee instances + """ + ids = request.POST["ids"] ids = json.loads(ids) is_active = False - if request.GET.get('is_active') == 'True': + if request.GET.get("is_active") == "True": is_active = True - for id in ids: - employee = Employee.objects.get(id=id) + for employee_id in ids: + employee = Employee.objects.get(id=employee_id) employee.is_active = is_active employee.employee_user_id.is_active = is_active employee.save() - message = _('archived') + message = _("archived") if is_active: - message = _('un-archived') - messages.success(request, f'{employee} is {message}') - return JsonResponse({'message': 'Success'}) + message = _("un-archived") + messages.success(request, f"{employee} is {message}") + return JsonResponse({"message": "Success"}) @login_required -@permission_required('employee.delete_employee') -def employee_archive(request, id): - employee = Employee.objects.get(id=id) +@permission_required("employee.delete_employee") +def employee_archive(request, obj_id): + """ + This method is used to archive employee instance + Args: + obj_id : Employee instance id + """ + employee = Employee.objects.get(id=obj_id) employee.is_active = not employee.is_active employee.employee_user_id.is_active = not employee.is_active employee.save() message = "Employee un-archived" if not employee.is_active: - message = _('Employee archived') + message = _("Employee archived") messages.success(request, message) - return HttpResponseRedirect(request.META.get('HTTP_REFERER')) + return HttpResponseRedirect(request.META.get("HTTP_REFERER")) @login_required -@permission_required('employee.change_employee') -def archive_employee(request, id): - """ - This method is used to archive employee - """ - employee = Employee.objects.get(id=id) - employee.is_active = False - messages.success(request, _('Employee archived successfully.')) - return - - -@login_required -@manager_can_enter('employee.view_employee') +@manager_can_enter("employee.view_employee") def employee_search(request): """ This method is used to search employee """ - search = request.GET['search'] - view = request.GET['view'] - previous_data = request.environ['QUERY_STRING'] + search = request.GET["search"] + view = request.GET["view"] + previous_data = request.environ["QUERY_STRING"] employees = EmployeeFilter(request.GET).qs - if search == '': + if search == "": employees = employees.filter(is_active=True) - page_number = request.GET.get('page') - template = 'employee_personal_info/employee_card.html' - if view == 'list': - template = 'employee_personal_info/employee_list.html' + page_number = request.GET.get("page") + template = "employee_personal_info/employee_card.html" + if view == "list": + template = "employee_personal_info/employee_list.html" employees = filtersubordinatesemployeemodel( - request, employees, 'employee.view_employee') - employees = sortby(request, employees, 'orderby') - return render(request, template, {'data': paginator_qry(employees, page_number), 'pd': previous_data}) + request, employees, "employee.view_employee" + ) + employees = sortby(request, employees, "orderby") + return render( + request, + template, + {"data": paginator_qry(employees, page_number), "pd": previous_data}, + ) @login_required -@manager_can_enter('employee.add_employeeworkinformation') -@require_http_methods(['POST']) -def employee_work_info_view_create(request, id): +@manager_can_enter("employee.add_employeeworkinformation") +@require_http_methods(["POST"]) +def employee_work_info_view_create(request, obj_id): """ This method is used to create employee work information from employee single view template args: - id : employee instance id + obj_id : employee instance id """ - employee = Employee.objects.get(id=id) + employee = Employee.objects.get(id=obj_id) form = EmployeeForm(instance=employee) work_form = EmployeeWorkInformationUpdateForm(request.POST) bank_form = EmployeeBankDetailsUpdateForm() bank_form_instance = EmployeeBankDetails.objects.filter( - employee_id=employee).first() + employee_id=employee + ).first() if bank_form_instance is not None: bank_form = EmployeeBankDetailsUpdateForm( - instance=employee.employee_bank_details) + instance=employee.employee_bank_details + ) if work_form.is_valid(): work_info = work_form.save(commit=False) work_info.employee_id = employee work_info.save() - messages.success(request, _('Created work information')) - return render(request, 'employee_personal_info/employee_update_form.html', {'form': form, 'work_form': work_form, 'bank_form': bank_form}) + messages.success(request, _("Created work information")) + return render( + request, + "employee_personal_info/employee_update_form.html", + {"form": form, "work_form": work_form, "bank_form": bank_form}, + ) @login_required -@manager_can_enter('employee.change_employeeworkinformation') -@require_http_methods(['POST']) -def employee_work_info_view_update(request, id): +@manager_can_enter("employee.change_employeeworkinformation") +@require_http_methods(["POST"]) +def employee_work_info_view_update(request, obj_id): """ This method is used to update employee work information from single view template args: - id : employee work information id + obj_id : employee work information id """ - work_information = EmployeeWorkInformation.objects.get(id=id) + work_information = EmployeeWorkInformation.objects.get(id=obj_id) form = EmployeeForm(instance=work_information.employee_id) bank_form = EmployeeBankDetailsUpdateForm( - instance=work_information.employee_id.employee_bank_details) + instance=work_information.employee_id.employee_bank_details + ) work_form = EmployeeWorkInformationUpdateForm( - request.POST, instance=work_information, + request.POST, + instance=work_information, ) if work_form.is_valid(): work_form.save() - messages.success(request, _('Work Information Updated Successfully')) - return render(request, 'employee_personal_info/employee_update_form.html', {'form': form, 'work_form': work_form, 'bank_form': bank_form}) + messages.success(request, _("Work Information Updated Successfully")) + return render( + request, + "employee_personal_info/employee_update_form.html", + {"form": form, "work_form": work_form, "bank_form": bank_form}, + ) @login_required -@manager_can_enter('employee.add_employeebankdetails') -@require_http_methods(['POST']) -def employee_bank_details_view_create(request, id): +@manager_can_enter("employee.add_employeebankdetails") +@require_http_methods(["POST"]) +def employee_bank_details_view_create(request, obj_id): """ This method used to create bank details object from the view template args: - id : employee instance id + obj_id : employee instance id """ - employee = Employee.objects.get(id=id) + employee = Employee.objects.get(id=obj_id) form = EmployeeForm(instance=employee) bank_form = EmployeeBankDetailsUpdateForm(request.POST) work_form_instance = EmployeeWorkInformation.objects.filter( - employee_id=employee).first() + employee_id=employee + ).first() work_form = EmployeeWorkInformationUpdateForm() if work_form_instance is not None: - work_form = EmployeeWorkInformationUpdateForm( - instance=work_form_instance) + work_form = EmployeeWorkInformationUpdateForm(instance=work_form_instance) if bank_form.is_valid(): bank_instance = bank_form.save(commit=False) bank_instance.employee_id = employee bank_instance.save() - messages.success(request, _('Bank Details Created Successfully')) - return render(request, 'employee_personal_info/employee_update_form.html', {'form': form, 'work_form': work_form, 'bank_form': bank_form}) + messages.success(request, _("Bank Details Created Successfully")) + return render( + request, + "employee_personal_info/employee_update_form.html", + {"form": form, "work_form": work_form, "bank_form": bank_form}, + ) @login_required -@manager_can_enter('employee.change_employeebankdetails') -@require_http_methods(['POST']) -def employee_bank_details_view_update(request, id): +@manager_can_enter("employee.change_employeebankdetails") +@require_http_methods(["POST"]) +def employee_bank_details_view_update(request, obj_id): """ This method is used to update employee bank details. """ - employee_bank_instance = EmployeeBankDetails.objects.get(id=id) + employee_bank_instance = EmployeeBankDetails.objects.get(id=obj_id) form = EmployeeForm(instance=employee_bank_instance.employee_id) work_form = EmployeeWorkInformationUpdateForm( - instance=employee_bank_instance.employee_id.employee_work_info) + instance=employee_bank_instance.employee_id.employee_work_info + ) bank_form = EmployeeBankDetailsUpdateForm( - request.POST, instance=employee_bank_instance) + request.POST, instance=employee_bank_instance + ) if bank_form.is_valid(): bank_instance = bank_form.save(commit=False) bank_instance.employee_id = employee_bank_instance.employee_id bank_instance.save() - messages.success(request, _('Bank Details Updated Successfully')) - return render(request, 'employee_personal_info/employee_update_form.html', {'form': form, 'work_form': work_form, 'bank_form': bank_form}) + messages.success(request, _("Bank Details Updated Successfully")) + return render( + request, + "employee_personal_info/employee_update_form.html", + {"form": form, "work_form": work_form, "bank_form": bank_form}, + ) @login_required -@permission_required('employee.delete_employeeworkinformation') -@require_http_methods(['POST', 'DELETE']) -def employee_work_information_delete(request, id): +@permission_required("employee.delete_employeeworkinformation") +@require_http_methods(["POST", "DELETE"]) +def employee_work_information_delete(_, obj_id): """ This method is used to delete employee work information args: - id : employee work information id + obj_id : employee work information id """ - work_information = EmployeeWorkInformation.objects.get(id=id).delete() - return redirect('/employee/employee-work-information-view') + EmployeeWorkInformation.objects.get(id=obj_id).delete() + return redirect("/employee/employee-work-information-view") @login_required -@permission_required('employee.add_employee') +@permission_required("employee.add_employee") def employee_import(request): """ This method is used to create employee and corresponding user. """ - if request.method == 'POST': - file = request.FILES['file'] + if request.method == "POST": + file = request.FILES["file"] # Read the Excel file into a Pandas DataFrame - df = pd.read_excel(file) + data_frame = pd.read_excel(file) # Convert the DataFrame to a list of dictionaries - employee_dicts = df.to_dict('records') + employee_dicts = data_frame.to_dict("records") # Create or update Employee objects from the list of dictionaries errolist = [] for employee_dict in employee_dicts: try: - phone = employee_dict['phone'] - email = employee_dict['email'] - employee_full_name = employee_dict['employee_full_name'] + phone = employee_dict["phone"] + email = employee_dict["email"] + employee_full_name = employee_dict["employee_full_name"] existing_user = User.objects.filter(username=email).first() if existing_user is None: employee_first_name = employee_full_name - employee_last_name = '' - if ' ' in employee_full_name: - employee_first_name, employee_last_name = employee_full_name.split( - ' ', 1) + employee_last_name = "" + if " " in employee_full_name: + ( + employee_first_name, + employee_last_name, + ) = employee_full_name.split(" ", 1) user = User.objects.create_user( - username=email, email=email, password=str(phone).strip(), is_superuser=False) + username=email, + email=email, + password=str(phone).strip(), + is_superuser=False, + ) employee = Employee() employee.employee_user_id = user employee.employee_first_name = employee_first_name @@ -804,7 +958,7 @@ def employee_import(request): employee.email = email employee.phone = phone employee.save() - except Exception as e: + except Exception: errolist.append(employee_dict) return HttpResponse( """ @@ -814,37 +968,36 @@ def employee_import(request): """ ) - df = pd.DataFrame(columns=['employee_full_name', 'email', 'phone']) + data_frame = pd.DataFrame(columns=["employee_full_name", "email", "phone"]) # Export the DataFrame to an Excel file - response = HttpResponse(content_type='application/ms-excel') - response['Content-Disposition'] = 'attachment; filename="employee_template.xlsx"' - df.to_excel(response, index=False) + response = HttpResponse(content_type="application/ms-excel") + response["Content-Disposition"] = 'attachment; filename="employee_template.xlsx"' + data_frame.to_excel(response, index=False) return response @login_required -@permission_required('employee.add_employee') -def employee_export(request): - """ +@permission_required("employee.add_employee") +def employee_export(_): + """ This method is used to export employee data to xlsx """ # Get the list of field names for your model - field_names = [f.name for f in Employee._meta.get_fields() - if not f.auto_created] - field_names.remove('employee_user_id') - field_names.remove('employee_profile') - field_names.remove('additional_info') - field_names.remove('is_active') + field_names = [f.name for f in Employee._meta.get_fields() if not f.auto_created] + field_names.remove("employee_user_id") + field_names.remove("employee_profile") + field_names.remove("additional_info") + field_names.remove("is_active") # Get the existing employee data and convert it to a DataFrame employee_data = Employee.objects.values_list(*field_names) - df = pd.DataFrame(list(employee_data), columns=field_names) + data_frame = pd.DataFrame(list(employee_data), columns=field_names) # Export the DataFrame to an Excel file - response = HttpResponse(content_type='application/ms-excel') - response['Content-Disposition'] = 'attachment; filename="employee_export.xlsx"' - df.to_excel(response, index=False) + response = HttpResponse(content_type="application/ms-excel") + response["Content-Disposition"] = 'attachment; filename="employee_export.xlsx"' + data_frame.to_excel(response, index=False) return response @@ -862,64 +1015,74 @@ def convert_nan(field, dicts): def work_info_import(request): - df = pd.DataFrame(columns=[ - 'badge_id', - 'first_name', - 'last_name', - 'phone', - 'email', - 'gender', - 'department', - 'job_position', - 'job_role', - 'work_type', - 'shift', - 'employee_type', - 'reporting_manager', - 'company', - 'location', - 'date_joining', - 'contract_end_date', - 'basic_salary', - 'salary_hour', - ]) + """ + This method is used to import Employee instances and creates related objects + """ + data_frame = pd.DataFrame( + columns=[ + "badge_id", + "first_name", + "last_name", + "phone", + "email", + "gender", + "department", + "job_position", + "job_role", + "work_type", + "shift", + "employee_type", + "reporting_manager", + "company", + "location", + "date_joining", + "contract_end_date", + "basic_salary", + "salary_hour", + ] + ) # Export the DataFrame to an Excel file - response = HttpResponse(content_type='application/ms-excel') - response['Content-Disposition'] = 'attachment; filename="work_info_template.xlsx"' - df.to_excel(response, index=False) + response = HttpResponse(content_type="application/ms-excel") + response["Content-Disposition"] = 'attachment; filename="work_info_template.xlsx"' + data_frame.to_excel(response, index=False) - if request.method == 'POST' and request.FILES.get('file') is not None: - file = request.FILES['file'] - df = pd.read_excel(file) - work_info_dicts = df.to_dict('records') + if request.method == "POST" and request.FILES.get("file") is not None: + file = request.FILES["file"] + data_frame = pd.read_excel(file) + work_info_dicts = data_frame.to_dict("records") error_lists = [] for work_info in work_info_dicts: try: - email = work_info['email'] - phone = work_info['phone'] - first_name = work_info['first_name'] - last_name = work_info['last_name'] - badge_id = convert_nan('badge_id', work_info) - department = convert_nan('department', work_info) - job_position = convert_nan('job_position', work_info) - job_role = convert_nan('job_role', work_info) - work_type = convert_nan('work_type', work_info) - employee_type = convert_nan('employee_type', work_info) - reporting_manager = convert_nan('reporting_manager', work_info) - company = convert_nan('company', work_info) - location = convert_nan('location', work_info) - shift = convert_nan('shift', work_info) - date_joining = convert_nan('date_joining', work_info) - contract_end_date = convert_nan('contract_end_date', work_info) - basic_salary = convert_nan('basic_salary', work_info) - gender = work_info.get('gender') + email = work_info["email"] + phone = work_info["phone"] + first_name = work_info["first_name"] + last_name = work_info["last_name"] + badge_id = convert_nan("badge_id", work_info) + department = convert_nan("department", work_info) + job_position = convert_nan("job_position", work_info) + job_role = convert_nan("job_role", work_info) + work_type = convert_nan("work_type", work_info) + employee_type = convert_nan("employee_type", work_info) + reporting_manager = convert_nan("reporting_manager", work_info) + company = convert_nan("company", work_info) + location = convert_nan("location", work_info) + shift = convert_nan("shift", work_info) + date_joining = convert_nan("date_joining", work_info) + contract_end_date = convert_nan("contract_end_date", work_info) + basic_salary = convert_nan("basic_salary", work_info) + gender = work_info.get("gender") user = User.objects.filter(username=email).first() if user is None: user = User.objects.create_user( - username=email, email=email, password=str(phone).strip(), is_superuser=False) + username=email, + email=email, + password=str(phone).strip(), + is_superuser=False, + ) employee_obj = Employee.objects.filter( - employee_first_name=first_name, employee_last_name=last_name).first() + employee_first_name=first_name, employee_last_name=last_name + ).first() if employee_obj is None: employee_obj = Employee() employee_obj.employee_user_id = user @@ -931,14 +1094,16 @@ def work_info_import(request): employee_obj.gender = gender.lower() employee_obj.save() department_obj = Department.objects.filter( - department=department).first() + department=department + ).first() if department_obj is None: department_obj = Department() department_obj.department = department department_obj.save() job_position_obj = JobPosition.objects.filter( - department_id=department_obj, job_position=job_position).first() + department_id=department_obj, job_position=job_position + ).first() if job_position_obj is None: job_position_obj = JobPosition() job_position_obj.department_id = department_obj @@ -946,42 +1111,45 @@ def work_info_import(request): job_position_obj.save() job_role_obj = JobRole.objects.filter( - job_role=job_role, job_position_id=job_position_obj).first() + job_role=job_role, job_position_id=job_position_obj + ).first() if job_role_obj is None: job_role_obj = JobRole() job_role_obj.job_position_id = job_position_obj job_role_obj.job_role = job_role job_role_obj.save() - work_type_obj = WorkType.objects.filter( - work_type=work_type).first() + work_type_obj = WorkType.objects.filter(work_type=work_type).first() if work_type_obj is None: work_type_obj = WorkType() work_type_obj.work_type = work_type work_type_obj.save() shift_obj = EmployeeShift.objects.filter( - employee_shift=shift).first() + employee_shift=shift + ).first() if shift_obj is None: shift_obj = EmployeeShift() shift_obj.employee_shift = shift shift_obj.save() employee_type_obj = EmployeeType.objects.filter( - employee_type=employee_type).first() + employee_type=employee_type + ).first() if employee_type_obj is None: employee_type_obj = EmployeeType() employee_type_obj.employee_type = employee_type employee_type_obj.save() - manager_fname, manager_lname = '', '' - if type(reporting_manager) is str and ' ' in reporting_manager: - manager_fname, manager_lname = reporting_manager.split( - ' ', 1) + manager_fname, manager_lname = "", "" + if isinstance(reporting_manager,str) and " " in reporting_manager: + manager_fname, manager_lname = reporting_manager.split(" ", 1) reporting_manager_obj = Employee.objects.filter( - employee_first_name=manager_fname, employee_last_name=manager_lname).first() - company_obj = Company.objects.filter( - company=company).first() + employee_first_name=manager_fname, + employee_last_name=manager_lname, + ).first() + company_obj = Company.objects.filter(company=company).first() employee_work_info = EmployeeWorkInformation.objects.filter( - employee_id=employee_obj).first() + employee_id=employee_obj + ).first() if employee_work_info is None: employee_work_info = EmployeeWorkInformation() employee_work_info.employee_id = employee_obj @@ -998,7 +1166,7 @@ def work_info_import(request): employee_work_info.contract_end_date = contract_end_date employee_work_info.basic_salary = basic_salary employee_work_info.save() - except Exception as e: + except Exception: error_lists.append(work_info) if error_lists: res = defaultdict(list) @@ -1006,85 +1174,99 @@ def work_info_import(request): for key in sub: res[key].append(sub[key]) # df = pd.DataFrame(res) - df = pd.DataFrame(error_lists, columns=error_lists[0].keys()) + data_frame = pd.DataFrame(error_lists, columns=error_lists[0].keys()) # Create an HTTP response object with the Excel file - response = HttpResponse(content_type='application/ms-excel') - response['Content-Disposition'] = 'attachment; filename="ImportError.xlsx"' - df.to_excel(response, index=False) + response = HttpResponse(content_type="application/ms-excel") + response["Content-Disposition"] = 'attachment; filename="ImportError.xlsx"' + data_frame.to_excel(response, index=False) return response - return HttpResponse('Imported successfully') + return HttpResponse("Imported successfully") return response -def work_info_export(request): +def work_info_export(_): """ This method is used to export employee data to xlsx """ # Add the fields from the related Employee,JobPosition,Department,WorkType,JobRole model field_names = [ - 'employee_id__badge_id', - 'employee_id__employee_first_name', - 'employee_id__employee_last_name', - 'employee_id__phone', - 'employee_id__email', - 'employee_id__gender', - 'department_id__department', - 'job_position_id__job_position', - 'job_role_id__job_role', - 'work_type_id__work_type', - 'shift_id__employee_shift', - 'employee_type_id__employee_type', - 'reporting_manager_id__employee_first_name', - 'company_id__company', - 'location', - 'date_joining', - 'contract_end_date', - 'basic_salary', - 'salary_hour', - + "employee_id__badge_id", + "employee_id__employee_first_name", + "employee_id__employee_last_name", + "employee_id__phone", + "employee_id__email", + "employee_id__gender", + "department_id__department", + "job_position_id__job_position", + "job_role_id__job_role", + "work_type_id__work_type", + "shift_id__employee_shift", + "employee_type_id__employee_type", + "reporting_manager_id__employee_first_name", + "company_id__company", + "location", + "date_joining", + "contract_end_date", + "basic_salary", + "salary_hour", ] # Get the list of field names from EmployeeWorkInformation model - field_names += [f.name for f in EmployeeWorkInformation._meta.get_fields() - if not f.auto_created] + field_names += [ + f.name for f in EmployeeWorkInformation._meta.get_fields() if not f.auto_created + ] - fields_to_remove = ['badge_id', 'employee_id', 'job_position_id', 'department_id', 'work_type_id', - 'job_role_id', 'reporting_manager_id', 'company_id', 'shift_id', 'employee_type_id', 'additional_info'] + fields_to_remove = [ + "badge_id", + "employee_id", + "job_position_id", + "department_id", + "work_type_id", + "job_role_id", + "reporting_manager_id", + "company_id", + "shift_id", + "employee_type_id", + "additional_info", + ] field_names = [ - field_name for field_name in field_names if field_name not in fields_to_remove] + field_name for field_name in field_names if field_name not in fields_to_remove + ] # Get the existing employee data with related Employee objects employee_data = EmployeeWorkInformation.objects.values_list(*field_names) - df = pd.DataFrame(employee_data, columns=field_names) + data_frame = pd.DataFrame(employee_data, columns=field_names) # Rename the columns for clarity - df = df.rename(columns={ - 'employee_id__badge_id': 'badge_id', - 'employee_id__employee_first_name': 'first_name', - 'employee_id__employee_last_name': 'last_name', - 'employee_id__phone': 'phone', - 'employee_id__email': 'email', - 'employee_id__gender': 'gender', - 'department_id__department': 'department', - 'job_position_id__job_position': 'job_position', - 'job_role_id__job_role': 'job_role', - 'work_type_id__work_type': 'work_type', - 'shift_id__employee_shift': 'shift', - 'employee_type_id__employee_type': 'employee_type', - 'reporting_manager_id__employee_first_name': 'reporting_manager', - 'company_id__company': 'company', - 'location': 'location', - 'date_joining': 'date_joining', - 'contract_end_date': 'contract_end_date', - 'basic_salary': 'basic_salary', - 'salary_hour': 'salary_hour', - }) + data_frame = data_frame.rename( + columns={ + "employee_id__badge_id": "badge_id", + "employee_id__employee_first_name": "first_name", + "employee_id__employee_last_name": "last_name", + "employee_id__phone": "phone", + "employee_id__email": "email", + "employee_id__gender": "gender", + "department_id__department": "department", + "job_position_id__job_position": "job_position", + "job_role_id__job_role": "job_role", + "work_type_id__work_type": "work_type", + "shift_id__employee_shift": "shift", + "employee_type_id__employee_type": "employee_type", + "reporting_manager_id__employee_first_name": "reporting_manager", + "company_id__company": "company", + "location": "location", + "date_joining": "date_joining", + "contract_end_date": "contract_end_date", + "basic_salary": "basic_salary", + "salary_hour": "salary_hour", + } + ) # Export the DataFrame to an Excel file - response = HttpResponse(content_type='application/ms-excel') - response['Content-Disposition'] = 'attachment; filename="employee_export.xlsx"' - df.to_excel(response, index=False) + response = HttpResponse(content_type="application/ms-excel") + response["Content-Disposition"] = 'attachment; filename="employee_export.xlsx"' + data_frame.to_excel(response, index=False) return response @@ -1099,107 +1281,134 @@ def birthday(): dob__day__gte=today.day, dob__month=today.month, dob__day__lte=last_day_of_month, - ).order_by(F('dob__day').asc(nulls_last=True)) + ).order_by(F("dob__day").asc(nulls_last=True)) for employee in employees: - employee.days_until_birthday = (employee.dob.day - today.day) + employee.days_until_birthday = employee.dob.day - today.day return employees @login_required -@manager_can_enter('employee.view_employee') -def get_employees_birthday(request): +@manager_can_enter("employee.view_employee") +def get_employees_birthday(_): """ This method is used to render all upcoming birthday employee details to fill the dashboard. """ employees = birthday() birthdays = [] for emp in employees: - name = emp.__str__() - dob = emp.dob.strftime('%d %b %Y') + name = f"{emp.employee_first_name} {emp.employee_last_name}" + dob = emp.dob.strftime("%d %b %Y") days_till_birthday = emp.days_until_birthday if days_till_birthday == 0: - days_till_birthday = 'Today' + days_till_birthday = "Today" elif days_till_birthday == 1: - days_till_birthday = 'Tomorrow' + days_till_birthday = "Tomorrow" else: - days_till_birthday = f'In {days_till_birthday} Days' + days_till_birthday = f"In {days_till_birthday} Days" try: path = emp.employee_profile.url except: - path = f'https://ui-avatars.com/api/?name={emp.employee_first_name}+{emp.employee_last_name}&background=random' - birthdays.append({ - 'profile': path, - 'name': name, - 'dob': dob, - 'daysUntilBirthday': days_till_birthday, - }) - return JsonResponse({'birthdays': birthdays}) + path = f"https://ui-avatars.com/api/?\ + name={emp.employee_first_name}+{emp.employee_last_name}&background=random" + birthdays.append( + { + "profile": path, + "name": name, + "dob": dob, + "daysUntilBirthday": days_till_birthday, + } + ) + return JsonResponse({"birthdays": birthdays}) @login_required -@manager_can_enter('employee.view_employee') +@manager_can_enter("employee.view_employee") def dashboard(request): """ This method is used to render individual dashboard for employee module """ upcoming_birthdays = birthday() employees = Employee.objects.all() - employees = filtersubordinates( - request, employees, 'employee.view_employee') + employees = filtersubordinates(request, employees, "employee.view_employee") active_employees = employees.filter(is_active=True) inactive_employees = employees.filter(is_active=False) active_ratio = 0 inactive_ratio = 0 if employees.exists(): - active_ratio = "%.1f" % ((len(active_employees)/len(employees))*100) - inactive_ratio = "%.1f" % ( - (len(inactive_employees)/len(employees))*100) + active_ratio = f"{(len(active_employees) / len(employees)) * 100:.1f}" + inactive_ratio = f"{(len(inactive_employees) / len(employees)) * 100:.1f}" - return render(request, 'employee/dashboard/dashboard_employee.html', { - 'birthdays': upcoming_birthdays, - 'active_employees': len(active_employees), - 'inactive_employees': len(inactive_employees), - 'total_employees': len(employees), - 'active_ratio': active_ratio, - 'inactive_ratio': inactive_ratio, - }) + return render( + request, + "employee/dashboard/dashboard_employee.html", + { + "birthdays": upcoming_birthdays, + "active_employees": len(active_employees), + "inactive_employees": len(inactive_employees), + "total_employees": len(employees), + "active_ratio": active_ratio, + "inactive_ratio": inactive_ratio, + }, + ) @login_required -@manager_can_enter('employee.view_employee') +@manager_can_enter("employee.view_employee") def dashboard_employee(request): """ Active and in-active employee dashboard """ - labels = ['Active', 'In-Avtive',] + labels = [ + "Active", + "In-Avtive", + ] employees = Employee.objects.all() - employees = filtersubordinates( - request, employees, 'employee.view_employee') - response = {'dataSet': [{'label': 'Employees', 'data': [len(employees.filter( - is_active=True)), len(employees.filter(is_active=False)),]}, ], 'labels': labels} + employees = filtersubordinates(request, employees, "employee.view_employee") + response = { + "dataSet": [ + { + "label": "Employees", + "data": [ + len(employees.filter(is_active=True)), + len(employees.filter(is_active=False)), + ], + }, + ], + "labels": labels, + } return JsonResponse(response) @login_required -@manager_can_enter('employee.view_employee') +@manager_can_enter("employee.view_employee") def dashboard_employee_gender(request): """ This method is used to filter out gender vise employees """ - labels = [' Male', 'Female', 'Others'] + labels = [" Male", "Female", "Others"] employees = Employee.objects.all() - employees = filtersubordinates( - request, employees, 'employee.view_employee') + employees = filtersubordinates(request, employees, "employee.view_employee") - response = {'dataSet': [{'label': 'Employees', 'data': [len(employees.filter(gender='male')), len( - employees.filter(gender='female')), len(employees.filter(gender='others')),]},], 'labels': labels} + response = { + "dataSet": [ + { + "label": "Employees", + "data": [ + len(employees.filter(gender="male")), + len(employees.filter(gender="female")), + len(employees.filter(gender="others")), + ], + }, + ], + "labels": labels, + } return JsonResponse(response) @login_required -@manager_can_enter('employee.view_employee') -def dashboard_employee_department(request): +@manager_can_enter("employee.view_employee") +def dashboard_employee_department(_): """ This method is used to find the count of employees corresponding to the departments """ @@ -1209,37 +1418,56 @@ def dashboard_employee_department(request): for i in departments: labels.append(i.department) for dept in departments: - count.append(len(Employee.objects.filter( - employee_work_info__department_id__department=dept))) - response = {'dataSet': [ - {'label': 'Department', 'data': count}], 'labels': labels} + count.append( + len( + Employee.objects.filter( + employee_work_info__department_id__department=dept + ) + ) + ) + response = {"dataSet": [{"label": "Department", "data": count}], "labels": labels} return JsonResponse(response) @login_required -def dashboard_employee_tiles(request): +def dashboard_employee_tiles(_): """ - This method returns json response. + This method returns json response. """ data = {} # active employees count - data['total_employees'] = Employee.objects.filter(is_active=True).count() + data["total_employees"] = Employee.objects.filter(is_active=True).count() # filtering newbies - data['newbies_today'] = EmployeeWorkInformation.objects.filter( - date_joining__range=[date.today(), date.today() + timedelta(days=1)]).count() + data["newbies_today"] = EmployeeWorkInformation.objects.filter( + date_joining__range=[date.today(), date.today() + timedelta(days=1)] + ).count() try: - data['newbies_today_percentage'] = "{:.2f}%".format(EmployeeWorkInformation.objects.filter(date_joining__range=[date.today( - ), date.today() + timedelta(days=1)]).count()/Employee.objects.filter(employee_work_info__isnull=False).count()) - except: - data['newbies_today_percentage'] = 0 + data[ + "newbies_today_percentage" + ] = f"""{ + (EmployeeWorkInformation.objects.filter( + date_joining__range=[date.today(), date.today() + timedelta(days=1)] + ).count() / Employee.objects.filter( + employee_work_info__isnull=False).count() + ) * 100:.2f}%""" + except Exception: + data["newbies_today_percentage"] = 0 # filtering newbies on this week - data['newbies_week'] = EmployeeWorkInformation.objects.filter( - date_joining__range=[date.today()-timedelta(days=7), date.today()]).count() + data["newbies_week"] = EmployeeWorkInformation.objects.filter( + date_joining__range=[date.today() - timedelta(days=7), date.today()] + ).count() try: - data['newbies_week_percentage'] = "{:.2f}%".format(EmployeeWorkInformation.objects.filter(date_joining__range=[date.today( - )-timedelta(days=7), date.today()]).count()/Employee.objects.filter(employee_work_info__isnull=False).count()) - except Exception as e: + data[ + "newbies_week_percentage" + ] = f"""{( + EmployeeWorkInformation.objects.filter( + date_joining__range=[date.today() - timedelta(days=7), date.today()] + ).count() / Employee.objects.filter( + employee_work_info__isnull=False + ).count() + ) * 100:.2f}%""" - data['newbies_week_percentage'] = 0 + except Exception: + data["newbies_week_percentage"] = 0 return JsonResponse(data) diff --git a/onboarding/decorators.py b/onboarding/decorators.py index 80c13b240..584905a1a 100644 --- a/onboarding/decorators.py +++ b/onboarding/decorators.py @@ -1,24 +1,76 @@ -from employee.models import Employee -from recruitment.models import Recruitment -from onboarding.models import * +""" +decorators.py + +Custom decorators for permission and manager checks in the application. +""" from django.contrib import messages from django.http import HttpResponseRedirect +from employee.models import Employee +from recruitment.models import Recruitment +from onboarding.models import OnboardingTask,OnboardingStage + + +def decorator_with_arguments(decorator): + """ + Decorator that allows decorators to accept arguments and keyword arguments. + + Args: + decorator (function): The decorator function to be wrapped. + + Returns: + function: The wrapper function. + + """ + + def wrapper(*args, **kwargs): + """ + Wrapper function that captures the arguments and keyword arguments. + + Args: + *args: Variable length argument list. + **kwargs: Arbitrary keyword arguments. + + Returns: + function: The inner wrapper function. + + """ + + def inner_wrapper(func): + """ + Inner wrapper function that applies the decorator to the function. + + Args: + func (function): The function to be decorated. + + Returns: + function: The decorated function. + + """ + return decorator(func, *args, **kwargs) + + return inner_wrapper + + return wrapper -decorator_with_arguments = lambda decorator: lambda *args, **kwargs: lambda func: decorator(func, *args, **kwargs) @decorator_with_arguments def all_manager_can_enter(function, perm): """ This method is used to check permission of employee for enter to the function """ + def _function(request, *args, **kwargs): user = request.user employee = Employee.objects.filter(employee_user_id=user).first() - is_manager = OnboardingTask.objects.filter(employee_id=employee).exists() or OnboardingStage.objects.filter(employee_id=employee) or Recruitment.objects.filter(recruitment_managers=employee).exists() + is_manager = ( + OnboardingTask.objects.filter(employee_id=employee).exists() + or OnboardingStage.objects.filter(employee_id=employee) + or Recruitment.objects.filter(recruitment_managers=employee).exists() + ) if user.has_perm(perm) or is_manager: return function(request, *args, **kwargs) - messages.info(request,'You dont have permission.') - return HttpResponseRedirect(request. META. get('HTTP_REFERER', '/')) + messages.info(request, "You dont have permission.") + return HttpResponseRedirect(request.META.get("HTTP_REFERER", "/")) return _function @@ -28,14 +80,18 @@ def stage_manager_can_enter(function, perm): """ This method is used to check permission of employee for enter to the function """ + def _function(request, *args, **kwargs): user = request.user employee = Employee.objects.filter(employee_user_id=user).first() - is_manager = OnboardingStage.objects.filter(employee_id=employee) or Recruitment.objects.filter(recruitment_managers=employee).exists() + is_manager = ( + OnboardingStage.objects.filter(employee_id=employee) + or Recruitment.objects.filter(recruitment_managers=employee).exists() + ) if user.has_perm(perm) or is_manager: return function(request, *args, **kwargs) - messages.info(request,'You dont have permission.') - return HttpResponseRedirect(request. META. get('HTTP_REFERER', '/')) + messages.info(request, "You dont have permission.") + return HttpResponseRedirect(request.META.get("HTTP_REFERER", "/")) return _function @@ -45,13 +101,14 @@ def recruitment_manager_can_enter(function, perm): """ This method is used to check permission of employee for enter to the function """ + def _function(request, *args, **kwargs): user = request.user employee = Employee.objects.filter(employee_user_id=user).first() is_manager = Recruitment.objects.filter(recruitment_managers=employee).exists() if user.has_perm(perm) or is_manager: return function(request, *args, **kwargs) - messages.info(request,'You dont have permission.') - return HttpResponseRedirect(request. META. get('HTTP_REFERER', '/')) + messages.info(request, "You dont have permission.") + return HttpResponseRedirect(request.META.get("HTTP_REFERER", "/")) - return _function \ No newline at end of file + return _function diff --git a/onboarding/filters.py b/onboarding/filters.py index 1191bfe53..9f480def0 100644 --- a/onboarding/filters.py +++ b/onboarding/filters.py @@ -1,38 +1,63 @@ -from .models import * +""" +filters.py +Used to register filter for onboarding models +""" from django import forms -from django_filters import FilterSet, DateFilter, filters +from django_filters import FilterSet as FilterSetOg, filters +from onboarding.models import Candidate + + +class FilterSet(FilterSetOg): + """ + Overriding FilterSet to add some default styles + """ -class FilterSet(FilterSet): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) - for field_name, field in self.filters.items(): + for field_name, _ in self.filters.items(): filter_widget = self.filters[field_name] widget = filter_widget.field.widget - if isinstance(widget, (forms.NumberInput, forms.EmailInput, forms.TextInput)): - filter_widget.field.widget.attrs.update( - {'class': 'oh-input w-100'}) + if isinstance( + widget, (forms.NumberInput, forms.EmailInput, forms.TextInput) + ): + filter_widget.field.widget.attrs.update({"class": "oh-input w-100"}) elif isinstance(widget, (forms.Select,)): filter_widget.field.widget.attrs.update( - {'class': 'oh-select oh-select-2 select2-hidden-accessible', }) + { + "class": "oh-select oh-select-2 select2-hidden-accessible", + } + ) elif isinstance(widget, (forms.Textarea)): + filter_widget.field.widget.attrs.update({"class": "oh-input w-100"}) + elif isinstance( + widget, + ( + forms.CheckboxInput, + forms.CheckboxSelectMultiple, + ), + ): filter_widget.field.widget.attrs.update( - {'class': 'oh-input w-100'}) - elif isinstance(widget, (forms.CheckboxInput, forms.CheckboxSelectMultiple,)): - filter_widget.field.widget.attrs.update( - {'class': 'oh-switch__checkbox'}) + {"class": "oh-switch__checkbox"} + ) elif isinstance(widget, (forms.ModelChoiceField)): filter_widget.field.widget.attrs.update( - {'class': 'oh-select oh-select-2 select2-hidden-accessible', }) - + { + "class": "oh-select oh-select-2 select2-hidden-accessible", + } + ) class CandidateFilter(FilterSet): - name = filters.CharFilter(field_name='name', lookup_expr='icontains') + """ + FilterSet class for Candidate model + """ + + name = filters.CharFilter(field_name="name", lookup_expr="icontains") class Meta: + """ + Meta class to add some additional options + """ + model = Candidate - fields = { - - - - } \ No newline at end of file + fields = {} diff --git a/onboarding/forms.py b/onboarding/forms.py index 5757f6aa3..890085c5f 100644 --- a/onboarding/forms.py +++ b/onboarding/forms.py @@ -1,17 +1,43 @@ +""" +forms.py + +This module contains the form classes used in the application. + +Each form represents a specific functionality or data input in the +application. They are responsible for validating +and processing user input data. + +Classes: +- YourForm: Represents a form for handling specific data input. + +Usage: +from django import forms + +class YourForm(forms.Form): + field_name = forms.CharField() + + def clean_field_name(self): + # Custom validation logic goes here + pass +""" from django import forms from django.forms import DateInput -from .models import OnboardingStage, OnboardingTask +from django.contrib.auth.models import User +from django.contrib.auth.forms import UserCreationForm as UserForm +from django.utils.translation import gettext_lazy as _ from employee.models import Employee, EmployeeBankDetails from recruitment.models import Candidate -from django.contrib.auth.models import User -from django.contrib.auth.forms import UserCreationForm -from django.utils.translation import gettext_lazy as _ +from onboarding.models import OnboardingStage, OnboardingTask class ModelForm(forms.ModelForm): + """ + Overriding django default model form to apply some styles + """ + def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) - for field_name, field in self.fields.items(): + for _, field in self.fields.items(): widget = field.widget if isinstance(widget, (forms.DateInput)): @@ -45,10 +71,14 @@ class ModelForm(forms.ModelForm): field.widget.attrs.update({"class": "oh-switch__checkbox"}) -class UserCreationFormCustom(UserCreationForm): +class UserCreationFormCustom(UserForm): + """ + Overriding user creation form to apply some styles + """ + def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) - for field_name, field in self.fields.items(): + for _, field in self.fields.items(): widget = field.widget if isinstance( widget, @@ -98,25 +128,49 @@ class UserCreationFormCustom(UserCreationForm): class OnboardingStageForm(ModelForm): + """ + Form for OnboardingStage model + """ + class Meta: + """ + Meta class to add additional info + """ + model = OnboardingStage fields = "__all__" exclude = ("sequence",) class OnboardingTaskForm(forms.ModelForm): + """ + Form for OnboardingTask Model + """ + class Meta: + """ + Meta class to apply some additional info + """ + model = OnboardingTask fields = "__all__" def __init__(self, *args, **kwargs): - super(OnboardingTaskForm, self).__init__(*args, **kwargs) + super().__init__(*args, **kwargs) for visible in self.visible_fields(): visible.field.widget.attrs["class"] = "form-control" class OnboardingCandidateForm(ModelForm): + """ + Form for Candidate model + """ + class Meta: + """ + Meta class for some additional options + """ + model = Candidate fields = "__all__" exclude = ( @@ -130,6 +184,7 @@ class OnboardingCandidateForm(ModelForm): "is_active", "resume", "schedule_date", + "job_position_id", ) widgets = { "joining_date": DateInput(attrs={"type": "date"}), @@ -142,13 +197,29 @@ class OnboardingCandidateForm(ModelForm): class UserCreationForm(UserCreationFormCustom): + """ + Form for User model + """ + class Meta: + """ + Meta class to add some additional options + """ + model = User fields = ["password1", "password2"] class OnboardingViewTaskForm(ModelForm): + """ + Form for OnboardingTask model + """ + class Meta: + """ + Meta class for some additional options + """ + model = OnboardingTask fields = "__all__" exclude = ("recruitment_id",) @@ -158,7 +229,15 @@ class OnboardingViewTaskForm(ModelForm): class OnboardingViewStageForm(ModelForm): + """ + Form for OnboardingStageModel + """ + class Meta: + """ + Meta class for add some additional options + """ + model = OnboardingStage fields = ["stage_title", "employee_id"] labels = { @@ -167,8 +246,12 @@ class OnboardingViewStageForm(ModelForm): class EmployeeCreationForm(ModelForm): + """ + Form for Employee Model + """ + employee_first_name = forms.CharField(required=True, label=_("First Name")) - employee_last_name = forms.CharField(required=True, label=_("Last Name")) + employee_last_name = forms.CharField(required=False, label=_("Last Name")) phone = forms.CharField(required=True, label=_("Phone")) address = forms.CharField(required=True, label=_("Address")) country = forms.CharField(required=True, label=_("Country")) @@ -188,10 +271,10 @@ class EmployeeCreationForm(ModelForm): ) class Meta: - model = Employee - fields = "__all__" + """ + Meta class to add some additional options + """ - class Meta: model = Employee fields = "__all__" exclude = ( @@ -207,6 +290,10 @@ class EmployeeCreationForm(ModelForm): class BankDetailsCreationForm(ModelForm): + """ + Form for BankDetailsCreationForm + """ + bank_name = forms.CharField(required=True, label="Bank Name") account_number = forms.CharField(required=True, label="Account Number") branch = forms.CharField(required=True, label="Branch") @@ -218,6 +305,10 @@ class BankDetailsCreationForm(ModelForm): any_other_code2 = forms.CharField(required=False, label="Code #2") class Meta: + """ + Meta class to add some additional options + """ + model = EmployeeBankDetails fields = "__all__" exclude = ("employee_id", "additional_info") diff --git a/onboarding/models.py b/onboarding/models.py index ab5c078c4..6ffb31c7e 100644 --- a/onboarding/models.py +++ b/onboarding/models.py @@ -1,60 +1,97 @@ +""" +models.py + +This module is used to register models for onboarding app + +""" from django.db import models +from django.utils.translation import gettext_lazy as _ from recruitment.models import Recruitment, Candidate from employee.models import Employee -from django.utils.translation import gettext_lazy as _ + class OnboardingStage(models.Model): + """ + OnboardingStage models + """ + stage_title = models.CharField(max_length=200) recruitment_id = models.ManyToManyField(Recruitment) employee_id = models.ManyToManyField(Employee) sequence = models.IntegerField(null=True) + def __str__(self): - return self.stage_title + return f"{self.stage_title}" class OnboardingTask(models.Model): + """ + OnboardingTask models + """ + task_title = models.CharField(max_length=200) - recruitment_id = models.ManyToManyField(Recruitment, related_name='onboarding_task') + recruitment_id = models.ManyToManyField(Recruitment, related_name="onboarding_task") employee_id = models.ManyToManyField(Employee) def __str__(self): - return self.task_title - + return f"{self.task_title}" + class CandidateStage(models.Model): - candidate_id = models.OneToOneField(Candidate, on_delete=models.CASCADE, related_name='onboarding_stage') + """ + CandidateStage model + """ + + candidate_id = models.OneToOneField( + Candidate, on_delete=models.CASCADE, related_name="onboarding_stage" + ) onboarding_stage_id = models.ForeignKey( - OnboardingStage, on_delete=models.CASCADE, related_name='candidate') + OnboardingStage, on_delete=models.CASCADE, related_name="candidate" + ) def __str__(self): - return f"{self.candidate_id} | {self.onboarding_stage_id}" + return f"{self.candidate_id} | {self.onboarding_stage_id}" -class CandidateTask(models.Model): - Choice = ( - ('', ''), - ('ongoing', _('Ongoing')), - ('stuck', _('Stuck')), - ('done', _('Done')), +class CandidateTask(models.Model): + """ + CandidateTask model + """ + + Choice = ( + ("", ""), + ("ongoing", _("Ongoing")), + ("stuck", _("Stuck")), + ("done", _("Done")), ) - candidate_id = models.ForeignKey(Candidate, on_delete=models.CASCADE, related_name='candidate_task') - status = models.CharField( - max_length=50, choices=Choice, blank=True, null=True) - onboarding_task_id = models.ForeignKey( - OnboardingTask, on_delete=models.CASCADE) + candidate_id = models.ForeignKey( + Candidate, on_delete=models.CASCADE, related_name="candidate_task" + ) + status = models.CharField(max_length=50, choices=Choice, blank=True, null=True) + onboarding_task_id = models.ForeignKey(OnboardingTask, on_delete=models.CASCADE) def __str__(self): return f"{self.candidate_id} | {self.onboarding_task_id} | {self.status}" - - class Meta: - unique_together = ('candidate_id','onboarding_task_id') - -class OnboardingPortal(models.Model): - candidate_id = models.OneToOneField(Candidate, on_delete=models.CASCADE, related_name='onboarding_portal') + class Meta: + """ + Meta class to add some additional options + """ + + unique_together = ("candidate_id", "onboarding_task_id") + + +class OnboardingPortal(models.Model): + """ + OnboardingPortal model + """ + + candidate_id = models.OneToOneField( + Candidate, on_delete=models.CASCADE, related_name="onboarding_portal" + ) token = models.CharField(max_length=200) - used = models.BooleanField(default=False) + used = models.BooleanField(default=False) count = models.IntegerField(default=0) def __str__(self): - return f'{self.candidate_id} | {self.token}' + return f"{self.candidate_id} | {self.token}" diff --git a/onboarding/templates/onboarding/candidate-filter.html b/onboarding/templates/onboarding/candidate-filter.html index a76d914f9..d63368108 100644 --- a/onboarding/templates/onboarding/candidate-filter.html +++ b/onboarding/templates/onboarding/candidate-filter.html @@ -1,42 +1,51 @@ {% load i18n %} -
    -
    - {% include 'onboarding/candidates.html' %} - + {% include 'onboarding/candidates.html' %}
    -{% endblock content %} \ No newline at end of file +{% endblock content %} diff --git a/onboarding/templates/onboarding/candidates.html b/onboarding/templates/onboarding/candidates.html index 908d8b1af..3072fbce9 100644 --- a/onboarding/templates/onboarding/candidates.html +++ b/onboarding/templates/onboarding/candidates.html @@ -1,104 +1,189 @@ {% load i18n %}
    -
    -
    -
    -
    -
    -
    - -
    - {% trans "Candidate" %} -
    -
    -
    {% trans "Email" %}
    -
    {% trans "Date of joining" %}
    -
    {% trans "Job position" %}
    -
    {% trans "Recruitment" %}
    -
    {% trans "Actions" %}
    - +
    +
    +
    +
    +
    +
    +
    + {% trans "Candidate" %} +
    - {% for candidate in candidates %} -
    -
    -
    -
    -
    - -
    -
    -
    - {% if candidate.profile %} - {{candidate.name}} - {% else %} - {{candidate.name}} - {% endif %} -
    - {{candidate.name}} -
    -
    -
    - {{candidate.email}} - {{candidate.joining_date}} - {{candidate.recruitment_id.job_position_id}} - {{candidate.recruitment_id}} -
    -
    - - - -
    -
    -
    -
    - {% endfor %} +
    {% trans "Email" %}
    +
    {% trans "Date of joining" %}
    +
    {% trans "Job position" %}
    +
    {% trans "Recruitment" %}
    +
    {% trans "Actions" %}
    +
    + {% for candidate in candidates %} +
    +
    +
    +
    +
    + +
    +
    +
    + {% if candidate.profile %} + {{candidate.name}} + {% else %} + {{candidate.name}} + {% endif %} +
    + {{candidate.name}} +
    +
    +
    + {{candidate.email}} + + {% comment %} {{candidate.joining_date}} {% endcomment %} + + {{candidate.recruitment_id.job_position_id}} + {{candidate.recruitment_id}} +
    +
    + + +
    +
    +
    +
    + {% endfor %} +
    - - {% trans "Page" %} {{ candidates.number }} {% trans "of" %} {{ candidates.paginator.num_pages }}. - - -
    \ No newline at end of file + + {% trans "Page" %} {{ candidates.number }} {% trans "of" %} {{ candidates.paginator.num_pages }}. + + +
    + diff --git a/onboarding/templates/onboarding/employee-creation.html b/onboarding/templates/onboarding/employee-creation.html index 2db135668..45ffd50a7 100644 --- a/onboarding/templates/onboarding/employee-creation.html +++ b/onboarding/templates/onboarding/employee-creation.html @@ -28,7 +28,7 @@
    - Acme, Inc. + {{employee}}
    • 1
      @@ -84,17 +84,14 @@
      - - {{form.country.errors}}
      - {{form.state.errors}}
      diff --git a/onboarding/templates/onboarding/onboarding-table.html b/onboarding/templates/onboarding/onboarding-table.html index 58765e582..e96ffff97 100644 --- a/onboarding/templates/onboarding/onboarding-table.html +++ b/onboarding/templates/onboarding/onboarding-table.html @@ -46,13 +46,14 @@
      {% trans "Candidate" %}
      {% trans "Email" %}
      +
      {% trans "Job Position" %}
      {% trans "Mobile" %}
      {% trans "Joining Date" %}
      {% trans "Stage" %}
      {% for task in recruitment.onboarding_task.all %} -
      +
      - {{task}} + {{task|truncatechars:20}} {% if request.user|stage_manages:stage or perms.onboarding.change_onboardingtask or perms.onboarding.delete_onboardingtask %}
      {{candidate.candidate_id.email}}
      +
      {{candidate.candidate_id.job_position_id}}
      {{candidate.candidate_id.mobile}}
      {{candidate.candidate_id.joining_date}}
      {% if request.user|stage_manages:stage or perms.onboarding.change_candidatestage %} -
      - Acme, Inc. + {{company}}
      • 1
        diff --git a/onboarding/templatetags/onboardingfilters.py b/onboarding/templatetags/onboardingfilters.py index f9063eabd..31d79ea8d 100644 --- a/onboarding/templatetags/onboardingfilters.py +++ b/onboarding/templatetags/onboardingfilters.py @@ -1,3 +1,9 @@ +""" +onboardingfilters.py + +This page is used to write custom template filters. +""" + from django.template.defaultfilters import register from django import template @@ -5,50 +11,97 @@ from django import template # from django.forms.boundfield register = template.Library() - - -@register.filter(name='is_taskmanager') + + +@register.filter(name="is_taskmanager") def is_taskmanager(user): - """ + """ + Method to check the user is task manager + + Args: + user (obj): User instance + + Returns: + bool: True if task manager """ try: employee = user.employee_get - return employee.onboardingtask_set.all().exists() or employee.onboardingstage_set.all().exists() or employee.recruitment_set.exists() + return ( + employee.onboardingtask_set.all().exists() + or employee.onboardingstage_set.all().exists() + or employee.recruitment_set.exists() + ) except Exception: return False - -@register.filter(name='task_manages') + +@register.filter(name="task_manages") def task_manages(user, recruitment): """ - + check weather the user manages any task, stage, and recruitment + + Args: + user (obj): django User instance + recruitment (obj): Recruitment instance + + Returns: + bool: returns true if user manges any them """ try: employee = user.employee_get - return recruitment.onboarding_task.filter(employee_id = employee).exists() or recruitment.onboardingstage_set.filter(employee_id = employee).exists() or recruitment.recruitment_managers.filter(id=user.employee_get.id).exists() + return ( + recruitment.onboarding_task.filter(employee_id=employee).exists() + or recruitment.onboardingstage_set.filter(employee_id=employee).exists() + or recruitment.recruitment_managers.filter(id=user.employee_get.id).exists() + ) except Exception: return False - -@register.filter(name='stage_manages') + +@register.filter(name="stage_manages") def stage_manages(user, stage): """ + check weather the user manages stage or not + + Args: + user (obj): django User model instance + stage (obj): OnboardingStage model instance + + Returns: + bool: true if the user in any manager in the stage. """ try: employee = user.employee_get - return stage.employee_id.filter(id=employee.id).exists() or stage.recruitment_id.filter(recruitment_managers=employee).exists() + return ( + stage.employee_id.filter(id=employee.id).exists() + or stage.recruitment_id.filter(recruitment_managers=employee).exists() + ) except Exception: return False - -@register.filter(name='task_manager') +@register.filter(name="task_manager") def task_manager(user, task): """ - + This method check weather the user manages onboarding task + + Args: + user (obj): django User model instance + task (obj): onboarding task instance + + Returns: + bool: returns true if the user in any manager in the task """ try: employee = user.employee_get - return task.onboarding_task_id.employee_id.filter(id = employee.id).exists() or task.candidate_id.onboarding_stage.onboarding_stage_id.employee_id.filter(id=employee.id).exists() or task.onboarding_task_id.recruitment_id.first().recruitment_managers.filter(id=user.employee_get.id).exists() + return ( + task.onboarding_task_id.employee_id.filter(id=employee.id).exists() + or task.candidate_id.onboarding_stage.onboarding_stage_id.employee_id.filter( + id=employee.id + ).exists() + or task.onboarding_task_id.recruitment_id.first() + .recruitment_managers.filter(id=user.employee_get.id) + .exists() + ) except Exception: - return False \ No newline at end of file + return False diff --git a/onboarding/urls.py b/onboarding/urls.py index 87f452c25..0a1a3a36c 100644 --- a/onboarding/urls.py +++ b/onboarding/urls.py @@ -1,34 +1,65 @@ +""" +urls.py + +This module is used to map url path with view methods. +""" from django.urls import path from onboarding import views -urlpatterns = [ - path('stage-creation/', views.stage_creation, name='stage-creation'), - path('stage-update//', views.stage_update, name='stage-update'), - path('stage-delete/', views.stage_delete, name='stage-delete'), - - path('task-creation/', views.task_creation, name='task-creation'), - path('task-delete/', views.task_delete, name='task-delete'), - path('task-update//', views.task_update, name='task-update'), - - path('candidate-creation', views.candidate_creation, name='candidate-creation'), - path('candidate-update/', views.candidate_update, name='candidate-update'), - path('candidate-delete/',views.candidate_delete, name='candidate-delete'), - path('candidates-view', views.candidates_view, name='candidates-view'), - path('candidate-filter', views.candidate_filter, name='candidate-filter'), - - path('email-send', views.email_send, name='email-send'), - - path('onboarding-view', views.onboarding_view, name='onboarding-view'), - path('candidate-task-update/', views.candidate_task_update ,name='candidate-task-update'), - path('candidate-stage-update///', views.candidate_stage_update, name='candidate-stage-update'), - - path('user-creation/', views.user_creation, name='user-creation'), - path('profile-view/', views.profile_view, name='profile-view'), - path('employee-creation/', views.employee_creation, name='employee-creation'), - path('employee-bank-details/', views.employee_bank_details,name='employee-bank-details'), - path('welcome-aboard', views.welcome_aboard, name='welcome-aboard'), - - path('hired-candidate-chart', views.hired_candidate_chart, name='hired-candidate-chart'), - path('onboard-candidate-chart', views.onboard_candidate_chart, name='onboard-candidate-chart'), - +urlpatterns = [ + path("stage-creation/", views.stage_creation, name="stage-creation"), + path( + "stage-update//", + views.stage_update, + name="stage-update", + ), + path("stage-delete/", views.stage_delete, name="stage-delete"), + path("task-creation/", views.task_creation, name="task-creation"), + path("task-delete/", views.task_delete, name="task-delete"), + path( + "task-update//", + views.task_update, + name="task-update", + ), + path("candidate-creation", views.candidate_creation, name="candidate-creation"), + path("candidate-update/", views.candidate_update, name="candidate-update"), + path("candidate-delete/", views.candidate_delete, name="candidate-delete"), + path("candidates-view", views.candidates_view, name="candidates-view"), + path("candidate-filter", views.candidate_filter, name="candidate-filter"), + path("email-send", views.email_send, name="email-send"), + path("onboarding-view", views.onboarding_view, name="onboarding-view"), + path( + "candidate-task-update/", + views.candidate_task_update, + name="candidate-task-update", + ), + path( + "candidate-stage-update///", + views.candidate_stage_update, + name="candidate-stage-update", + ), + path("user-creation/", views.user_creation, name="user-creation"), + path("profile-view/", views.profile_view, name="profile-view"), + path( + "employee-creation/", + views.employee_creation, + name="employee-creation", + ), + path( + "employee-bank-details/", + views.employee_bank_details, + name="employee-bank-details", + ), + path("welcome-aboard", views.welcome_aboard, name="welcome-aboard"), + path( + "hired-candidate-chart", + views.hired_candidate_chart, + name="hired-candidate-chart", + ), + path( + "onboard-candidate-chart", + views.onboard_candidate_chart, + name="onboard-candidate-chart", + ), + path("update-joining",views.update_joining,name="update-joining") ] diff --git a/onboarding/views.py b/onboarding/views.py index 2236884dc..eddf37a06 100644 --- a/onboarding/views.py +++ b/onboarding/views.py @@ -1,34 +1,64 @@ +""" +views.py + +This module contains the view functions for handling HTTP requests and rendering +responses in your application. + +Each view function corresponds to a specific URL route and performs the necessary +actions to handle the request, process data, and generate a response. + +This module is part of the recruitment project and is intended to +provide the main entry points for interacting with the application's functionality. +""" +import json +import random import secrets from django.core.mail import send_mail from django.utils.translation import gettext_lazy as _ from django.shortcuts import render, redirect from django.contrib.auth import login -from horilla.decorators import login_required, hx_request_required from django.http import HttpResponse, JsonResponse -from .forms import * -from recruitment.models import Candidate, Recruitment -from employee.models import Employee -from onboarding.models import OnboardingStage, OnboardingTask, CandidateStage, CandidateTask, OnboardingPortal -import json -from horilla.decorators import permission_required -from recruitment.filters import CandidateFilter from django.contrib import messages from django.core.paginator import Paginator -import random -from .decorators import all_manager_can_enter, stage_manager_can_enter, recruitment_manager_can_enter +from django.views.decorators.http import require_http_methods from notifications.signals import notify +from horilla.decorators import login_required, hx_request_required +from horilla.decorators import permission_required +from recruitment.models import Candidate, Recruitment +from recruitment.filters import CandidateFilter +from employee.models import Employee, EmployeeWorkInformation, EmployeeBankDetails +from onboarding.forms import ( + OnboardingCandidateForm, + UserCreationForm, + OnboardingViewTaskForm, + OnboardingViewStageForm, + EmployeeCreationForm, + BankDetailsCreationForm, +) +from onboarding.models import ( + OnboardingStage, + OnboardingTask, + CandidateStage, + CandidateTask, + OnboardingPortal, +) +from onboarding.decorators import ( + all_manager_can_enter, + stage_manager_can_enter, + recruitment_manager_can_enter, +) @login_required @hx_request_required -@recruitment_manager_can_enter('onboarding.add_onboardingstage') -def stage_creation(request, id): +@recruitment_manager_can_enter("onboarding.add_onboardingstage") +def stage_creation(request, obj_id): """ function used to create onboarding stage. Parameters: request (HttpRequest): The HTTP request object. - id : recruitment id + obj_id : recruitment id Returns: GET : return onboarding stage creation form template @@ -36,24 +66,24 @@ def stage_creation(request, id): """ form = OnboardingViewStageForm() if request.method == "POST": - recruitment = Recruitment.objects.get(id=id) + recruitment = Recruitment.objects.get(id=obj_id) form = OnboardingViewStageForm(request.POST) if form.is_valid(): - return stage_save(form, recruitment, request, id) - return render(request, 'onboarding/stage-form.html', {'form': form, 'id': id}) + return stage_save(form, recruitment, request, obj_id) + return render(request, "onboarding/stage-form.html", {"form": form, "id": obj_id}) -def stage_save(form, recruitment, request, id): +def stage_save(form, recruitment, request, rec_id): """ function used to save onboarding stage. Parameters: request (HttpRequest): The HTTP request object. recruitment : recruitment object - id : recruitment id + rec_id : recruitment id Returns: - GET : return onboarding view + GET : return onboarding view """ stage = form.save() stage.recruitment_id.add(recruitment) @@ -67,14 +97,17 @@ def stage_save(form, recruitment, request, id): icon="people-circle", redirect="/onboarding/onboarding-view", ) - response = render(request, 'onboarding/stage-form.html', - {'form': form, 'id': id}) - return HttpResponse(response.content.decode('utf-8') + '') + response = render( + request, "onboarding/stage-form.html", {"form": form, "id": rec_id} + ) + return HttpResponse( + response.content.decode("utf-8") + "" + ) @login_required @hx_request_required -@recruitment_manager_can_enter('onboarding.change_onboardingstage') +@recruitment_manager_can_enter("onboarding.change_onboardingstage") def stage_update(request, stage_id, recruitment_id): """ function used to update onboarding stage. @@ -82,7 +115,7 @@ def stage_update(request, stage_id, recruitment_id): Parameters: request (HttpRequest): The HTTP request object. stage_id : stage id - recruitment_id : recruitment id + recruitment_id : recruitment id Returns: GET : return onboarding stage update form template @@ -90,13 +123,12 @@ def stage_update(request, stage_id, recruitment_id): """ onboarding_stage = OnboardingStage.objects.get(id=stage_id) form = OnboardingViewStageForm(instance=onboarding_stage) - if request.method == 'POST': + if request.method == "POST": form = OnboardingViewStageForm(request.POST, instance=onboarding_stage) if form.is_valid(): stage = form.save() - messages.info(request, _('Stage is updated successfully..')) - users = [ - employee.employee_user_id for employee in stage.employee_id.all()] + messages.info(request, _("Stage is updated successfully..")) + users = [employee.employee_user_id for employee in stage.employee_id.all()] notify.send( request.user.employee_get, recipient=users, @@ -104,17 +136,26 @@ def stage_update(request, stage_id, recruitment_id): icon="people-circle", redirect="/onboarding/onboarding-view", ) - response = render(request, 'onboarding/stage-update.html', - {'form': form, 'stage_id': stage_id, 'recruitment_id': recruitment_id}) - return HttpResponse(response.content.decode('utf-8') + '') - return render(request, 'onboarding/stage-update.html', {'form': form, 'stage_id': stage_id, 'recruitment_id': recruitment_id}) + response = render( + request, + "onboarding/stage-update.html", + {"form": form, "stage_id": stage_id, "recruitment_id": recruitment_id}, + ) + return HttpResponse( + response.content.decode("utf-8") + "" + ) + return render( + request, + "onboarding/stage-update.html", + {"form": form, "stage_id": stage_id, "recruitment_id": recruitment_id}, + ) @login_required -@permission_required('onboarding.delete_onboardingstage') -@recruitment_manager_can_enter('onboarding.delete_onboardingstage') +@permission_required("onboarding.delete_onboardingstage") +@recruitment_manager_can_enter("onboarding.delete_onboardingstage") def stage_delete(request, stage_id): - """ + """ function used to delete onboarding stage. Parameters: @@ -135,14 +176,14 @@ def stage_delete(request, stage_id): @login_required @hx_request_required -@stage_manager_can_enter('onboarding.add_onboardingtask') -def task_creation(request, id): +@stage_manager_can_enter("onboarding.add_onboardingtask") +def task_creation(request, obj_id): """ function used to create onboarding task. Parameters: request (HttpRequest): The HTTP request object. - id : recruitment id + obj_id : recruitment id Returns: GET : return onboarding task creation form template @@ -150,19 +191,19 @@ def task_creation(request, id): """ form = OnboardingViewTaskForm() if request.method == "POST": - recruitment = Recruitment.objects.get(id=id) + recruitment = Recruitment.objects.get(id=obj_id) form_data = OnboardingViewTaskForm(request.POST) if form_data.is_valid(): task = form_data.save() task.recruitment_id.add(recruitment) task.save() - for candidate in recruitment.candidate.filter(hired=True, start_onboard=True): - CandidateTask(candidate_id=candidate, - onboarding_task_id=task).save() + for candidate in recruitment.candidate.filter( + hired=True, start_onboard=True + ): + CandidateTask(candidate_id=candidate, onboarding_task_id=task).save() messages.success(request, _("New task created successfully...")) - users = [ - employee.employee_user_id for employee in task.employee_id.all()] + users = [employee.employee_user_id for employee in task.employee_id.all()] notify.send( request.user.employee_get, recipient=users, @@ -170,15 +211,18 @@ def task_creation(request, id): icon="people-circle", redirect="/onboarding/onboarding-view", ) - response = render(request, 'onboarding/task-form.html', - {'form': form, 'id': id}) - return HttpResponse(response.content.decode('utf-8') + '') - return render(request, 'onboarding/task-form.html', {'form': form, 'id': id}) + response = render( + request, "onboarding/task-form.html", {"form": form, "id": obj_id} + ) + return HttpResponse( + response.content.decode("utf-8") + "" + ) + return render(request, "onboarding/task-form.html", {"form": form, "id": obj_id}) @login_required @hx_request_required -@stage_manager_can_enter('onboarding.change_onboardingtask') +@stage_manager_can_enter("onboarding.change_onboardingtask") def task_update(request, task_id, recruitment_id): """ function used to update onboarding task. @@ -186,20 +230,19 @@ def task_update(request, task_id, recruitment_id): Parameters: request (HttpRequest): The HTTP request object. task_id : task id - recruitment_id : recruitment id + recruitment_id : recruitment id Returns: GET : return onboarding task update form template POST : return onboarding view """ onboarding_task = OnboardingTask.objects.get(id=task_id) - if request.method == 'POST': + if request.method == "POST": form = OnboardingViewTaskForm(request.POST, instance=onboarding_task) if form.is_valid(): task = form.save() - messages.info(request, _('Task updated successfully..')) - users = [ - employee.employee_user_id for employee in task.employee_id.all()] + messages.info(request, _("Task updated successfully..")) + users = [employee.employee_user_id for employee in task.employee_id.all()] notify.send( request.user.employee_get, recipient=users, @@ -207,16 +250,25 @@ def task_update(request, task_id, recruitment_id): icon="people-circle", redirect="/onboarding/onboarding-view", ) - response = render(request, 'onboarding/task-update.html', - {'form': form, 'task_id': task_id, 'recruitment_id': recruitment_id}) - return HttpResponse(response.content.decode('utf-8') + '') + response = render( + request, + "onboarding/task-update.html", + {"form": form, "task_id": task_id, "recruitment_id": recruitment_id}, + ) + return HttpResponse( + response.content.decode("utf-8") + "" + ) form = OnboardingViewTaskForm(instance=onboarding_task) - return render(request, 'onboarding/task-update.html', {'form': form, 'task_id': task_id, 'recruitment_id': recruitment_id}) + return render( + request, + "onboarding/task-update.html", + {"form": form, "task_id": task_id, "recruitment_id": recruitment_id}, + ) @login_required -@permission_required('onboarding.delete_onboardingtask') -@stage_manager_can_enter('onboarding.delete_onboardingtask') +@permission_required("onboarding.delete_onboardingtask") +@stage_manager_can_enter("onboarding.delete_onboardingtask") def task_delete(request, task_id): """ function used to delete onboarding task. @@ -236,7 +288,7 @@ def task_delete(request, task_id): @login_required -@permission_required('recruitment.add_candidate') +@permission_required("recruitment.add_candidate") def candidate_creation(request): """ function used to create hired candidates . @@ -249,60 +301,57 @@ def candidate_creation(request): POST : return candidate view """ form = OnboardingCandidateForm() - if request.method == 'POST': + if request.method == "POST": form = OnboardingCandidateForm(request.POST, request.FILES) if form.is_valid(): candidate = form.save() candidate.hired = True candidate.save() - messages.success(request, _( - 'New candidate created successfully..')) + messages.success(request, _("New candidate created successfully..")) return redirect(candidates_view) - return render(request, 'onboarding/candidate-creation.html', {'form': form}) + return render(request, "onboarding/candidate-creation.html", {"form": form}) @login_required -@permission_required('recruitment.change_candidate') -def candidate_update(request, id): +@permission_required("recruitment.change_candidate") +def candidate_update(request, obj_id): """ function used to update hired candidates . Parameters: request (HttpRequest): The HTTP request object. - id : recruitment id + obj_id : recruitment id Returns: GET : return candidate update form template POST : return candidate view """ - candidate = Candidate.objects.get(id=id) + candidate = Candidate.objects.get(id=obj_id) form = OnboardingCandidateForm(instance=candidate) - if request.method == 'POST': - form = OnboardingCandidateForm( - request.POST, request.FILES, instance=candidate) + if request.method == "POST": + form = OnboardingCandidateForm(request.POST, request.FILES, instance=candidate) if form.is_valid(): form.save() - messages.info( - request, _('Candidate detail is updated successfully..')) + messages.info(request, _("Candidate detail is updated successfully..")) return redirect(candidates_view) - return render(request, 'onboarding/candidate-update.html', {'form': form}) + return render(request, "onboarding/candidate-update.html", {"form": form}) @login_required -@permission_required('recruitment.delete_candidate') -def candidate_delete(request, id): +@permission_required("recruitment.delete_candidate") +def candidate_delete(request, obj_id): """ function used to delete hired candidates . Parameters: request (HttpRequest): The HTTP request object. - id : recruitment id + obj_id : recruitment id Returns: GET : return candidate view """ - Candidate.objects.get(id=id).delete() - messages.success(request, _('Candidate deleted successfully..')) + Candidate.objects.get(id=obj_id).delete() + messages.success(request, _("Candidate deleted successfully..")) return redirect(candidates_view) @@ -316,7 +365,7 @@ def paginator_qry(qryset, page_number): @login_required -@permission_required('candidate.view_candidate') +@permission_required("candidate.view_candidate") def candidates_view(request): """ function used to view hired candidates . @@ -328,15 +377,23 @@ def candidates_view(request): GET : return candidate view template """ queryset = Candidate.objects.filter(hired=True, start_onboard=False) - candidate_filter = CandidateFilter() - previous_data = request.environ['QUERY_STRING'] - page_number = request.GET.get('page') + candidate_filter_obj = CandidateFilter() + previous_data = request.environ["QUERY_STRING"] + page_number = request.GET.get("page") page_obj = paginator_qry(queryset, page_number) - return render(request, 'onboarding/candidates-view.html', {'candidates': page_obj, 'form': candidate_filter.form, 'pd': previous_data}) + return render( + request, + "onboarding/candidates-view.html", + { + "candidates": page_obj, + "form": candidate_filter_obj.form, + "pd": previous_data, + }, + ) @login_required -@permission_required('candidate.view_candidate') +@permission_required("candidate.view_candidate") def candidate_filter(request): """ function used to filter hired candidates . @@ -345,14 +402,18 @@ def candidate_filter(request): request (HttpRequest): The HTTP request object. Returns: - GET : return candidate view template + GET : return candidate view template """ queryset = Candidate.objects.filter(hired=True, start_onboard=False) - candidate_filter = CandidateFilter(request.GET, queryset).qs - previous_data = request.environ['QUERY_STRING'] - page_number = request.GET.get('page') - page_obj = paginator_qry(candidate_filter, page_number) - return render(request, 'onboarding/candidates.html', {'candidates': page_obj, 'pd': previous_data}) + candidate_filter_obj = CandidateFilter(request.GET, queryset).qs + previous_data = request.environ["QUERY_STRING"] + page_number = request.GET.get("page") + page_obj = paginator_qry(candidate_filter_obj, page_number) + return render( + request, + "onboarding/candidates.html", + {"candidates": page_obj, "pd": previous_data}, + ) @login_required @@ -366,31 +427,31 @@ def email_send(request): Returns: GET : return json response """ - if request.method != 'POST': - return JsonResponse('', safe=False) - candidates = request.POST.get('candidates') + if request.method != "POST": + return JsonResponse("", safe=False) + candidates = request.POST.get("candidates") json_mylist = json.loads(candidates) if len(json_mylist) <= 0: - return JsonResponse({'message': _('No candidate choosed'), 'tags': 'danger'}) - for id in json_mylist: - candidate = Candidate.objects.get(id=id) - if candidate.start_onboard == False: + return JsonResponse({"message": _("No candidate choosed"), "tags": "danger"}) + for cand_id in json_mylist: + candidate = Candidate.objects.get(id=cand_id) + if candidate.start_onboard is False: token = secrets.token_hex(15) OnboardingPortal(candidate_id=candidate, token=token).save() send_mail( - 'Onboarding Portal', - f'{request.get_host()}/onboarding/user-creation/{token}', - 'from@example.com', + "Onboarding Portal", + f"{request.get_host()}/onboarding/user-creation/{token}", + "from@example.com", [candidate.email], fail_silently=False, ) candidate.start_onboard = True candidate.save() - return JsonResponse({'message': _('Email send successfully'), 'tags': 'success'}) + return JsonResponse({"message": _("Email send successfully"), "tags": "success"}) @login_required -@all_manager_can_enter('onboarding.view_candidatestage') +@all_manager_can_enter("onboarding.view_candidatestage") def onboarding_view(request): """ function used to view onboarding main view. @@ -406,23 +467,39 @@ def onboarding_view(request): if not CandidateStage.objects.filter(candidate_id=candidate).exists(): try: onboarding_stage = OnboardingStage.objects.filter( - recruitment_id=candidate.recruitment_id).order_by('sequence')[0] - CandidateStage(candidate_id=candidate, - onboarding_stage_id=onboarding_stage).save() - except Exception as e: - messages.error(request, _('%(recruitment)s has no stage..') % { - 'recruitment': candidate.recruitment_id}) + recruitment_id=candidate.recruitment_id + ).order_by("sequence")[0] + CandidateStage( + candidate_id=candidate, onboarding_stage_id=onboarding_stage + ).save() + except Exception: + messages.error( + request, + _("%(recruitment)s has no stage..") + % {"recruitment": candidate.recruitment_id}, + ) if tasks := OnboardingTask.objects.filter( recruitment_id=candidate.recruitment_id ): for task in tasks: - if not CandidateTask.objects.filter(candidate_id=candidate, onboarding_task_id=task).exists(): - CandidateTask(candidate_id=candidate, - onboarding_task_id=task).save() + if not CandidateTask.objects.filter( + candidate_id=candidate, onboarding_task_id=task + ).exists(): + CandidateTask( + candidate_id=candidate, onboarding_task_id=task + ).save() recruitments = Recruitment.objects.all() onboarding_stages = OnboardingStage.objects.all() choices = CandidateTask.Choice - return render(request, 'onboarding/onboarding-view.html', {'recruitments': recruitments, 'onboarding_stages': onboarding_stages, 'choices': choices}) + return render( + request, + "onboarding/onboarding-view.html", + { + "recruitments": recruitments, + "onboarding_stages": onboarding_stages, + "choices": choices, + }, + ) def user_creation(request, token): @@ -440,18 +517,25 @@ def user_creation(request, token): try: onboarding_portal = OnboardingPortal.objects.get(token=token) form = UserCreationForm() - if not onboarding_portal or onboarding_portal.used != False: - return HttpResponse('Denied') + if not onboarding_portal or onboarding_portal.used is True: + return HttpResponse("Denied") try: - if request.method == 'POST': + if request.method == "POST": form = UserCreationForm(request.POST) if form.is_valid(): return user_save(form, onboarding_portal, request, token) - except Exception as e: - messages.error(request, _('User with email-id already exists..')) - return render(request, 'onboarding/user-creation.html', {'form': form}) - except Exception as e: - return HttpResponse(e) + except Exception: + messages.error(request, _("User with email-id already exists..")) + return render( + request, + "onboarding/user-creation.html", + { + "form": form, + "company": onboarding_portal.candidate_id.recruitment_id.company_id, + }, + ) + except Exception as error: + return HttpResponse(error) def user_save(form, onboarding_portal, request, token): @@ -475,7 +559,7 @@ def user_save(form, onboarding_portal, request, token): onboarding_portal.count += 1 onboarding_portal.save() messages.success(request, _("Account created successfully..")) - return redirect('profile-view', token) + return redirect("profile-view", token) def profile_view(request, token): @@ -491,14 +575,21 @@ def profile_view(request, token): POST : update profile image of the user """ candidate = Candidate.objects.get(email=request.user) - if request.method == 'POST': - profile = request.FILES.get('profile') + if request.method == "POST": + profile = request.FILES.get("profile") if profile is not None: candidate.profile = profile candidate.save() - messages.success(request, _( - 'Profile picture updated successfully..')) - return render(request, 'onboarding/profile-view.html', {'candidate': candidate, 'token': token}) + messages.success(request, _("Profile picture updated successfully..")) + return render( + request, + "onboarding/profile-view.html", + { + "candidate": candidate, + "token": token, + "company": candidate.recruitment_id.company_id, + }, + ) def employee_creation(request, token): @@ -515,10 +606,16 @@ def employee_creation(request, token): """ candidate = Candidate.objects.get(email=request.user) onboarding_portal = OnboardingPortal.objects.get(token=token) - form = EmployeeCreationForm(initial={ - "employee_first_name": candidate.name, 'phone': candidate.mobile, 'address': candidate.address}) + form = EmployeeCreationForm( + initial={ + "employee_first_name": candidate.name, + "phone": candidate.mobile, + "address": candidate.address, + "dob": candidate.dob, + } + ) if not Employee.objects.filter(employee_user_id=request.user).exists(): - if request.method == 'POST': + if request.method == "POST": form_data = EmployeeCreationForm(request.POST) if form_data.is_valid(): employee_personal_info = form_data.save(commit=False) @@ -526,15 +623,25 @@ def employee_creation(request, token): employee_personal_info.email = candidate.email employee_personal_info.employee_profile = candidate.profile employee_personal_info.save() + EmployeeWorkInformation( + employee_id=employee_personal_info, + date_joining=candidate.joining_date, + job_position_id=candidate.recruitment_id.job_position_id, + ).save() onboarding_portal.count += 1 onboarding_portal.save() - messages.success(request, _( - "Employee personal details created successfully..")) - return redirect('employee-bank-details', token) + messages.success( + request, _("Employee personal details created successfully..") + ) + return redirect("employee-bank-details", token) onboarding_portal.count += 1 onboarding_portal.save() - return render(request, 'onboarding/employee-creation.html', {'form': form}) - return redirect('employee-bank-details', token) + return render( + request, + "onboarding/employee-creation.html", + {"form": form, "employee": candidate.recruitment_id.company_id}, + ) + return redirect("employee-bank-details", token) def employee_bank_details(request, token): @@ -553,17 +660,21 @@ def employee_bank_details(request, token): form = BankDetailsCreationForm() employee_id = request.user.employee_get if not EmployeeBankDetails.objects.filter(employee_id=employee_id).exists(): - if request.method == 'POST': + if request.method == "POST": form = BankDetailsCreationForm(request.POST) if form.is_valid(): - return employee_bank_details_save( - form, request, onboarding_portal - ) - return render(request, 'onboarding/employee-bank-details.html', {'form': form}) + return employee_bank_details_save(form, request, onboarding_portal) + return render( + request, + "onboarding/employee-bank-details.html", + { + "form": form, + "company": onboarding_portal.candidate_id.recruitment_id.company_id, + }, + ) return redirect(welcome_aboard) -# TODO Rename this here and in `employee_bank_details` def employee_bank_details_save(form, request, onboarding_portal): """ function used to save employee bank details. @@ -578,12 +689,12 @@ def employee_bank_details_save(form, request, onboarding_portal): """ employee_bank_detail = form.save(commit=False) employee_bank_detail.employee_id = Employee.objects.get( - employee_user_id=request.user) + employee_user_id=request.user + ) employee_bank_detail.save() onboarding_portal.count += 1 onboarding_portal.save() - messages.success(request, _( - "Employee bank details created successfully..")) + messages.success(request, _("Employee bank details created successfully..")) return redirect(welcome_aboard) @@ -598,44 +709,53 @@ def welcome_aboard(request): Returns: GET : return welcome onboard view """ - return render(request, 'onboarding/welcome-aboard.html') + return render(request, "onboarding/welcome-aboard.html") @login_required @hx_request_required -@all_manager_can_enter('onboarding.change_candidatetask') -def candidate_task_update(request, id): +@require_http_methods(["POST"]) +@all_manager_can_enter("onboarding.change_candidatetask") +def candidate_task_update(request, obj_id): """ function used to update candidate task. Parameters: request (HttpRequest): The HTTP request object. - id : candidate task id + obj_id : candidate task id Returns: POST : return candidate task template """ - if request.method == "POST": - status = request.POST.get('task') - candidate_task = CandidateTask.objects.get(id=id) - candidate_task.status = status - candidate_task.save() - users = [employee.employee_user_id for employee in candidate_task.onboarding_task_id.employee_id.all()] - notify.send( - request.user.employee_get, - recipient=users, - verb=f"The task {candidate_task.onboarding_task_id} of {candidate_task.candidate_id} was updated to {candidate_task.status}.", - icon="people-circle", - redirect="/onboarding/onboarding-view", - ) - messages.info(request, _('Candidate task updated successfully..')) - choices = CandidateTask.Choice - return render(request, 'onboarding/candidate-task.html', {'choices': choices, 'task': candidate_task}) + status = request.POST.get("task") + candidate_task = CandidateTask.objects.get(id=obj_id) + candidate_task.status = status + candidate_task.save() + users = [ + employee.employee_user_id + for employee in candidate_task.onboarding_task_id.employee_id.all() + ] + notify.send( + request.user.employee_get, + recipient=users, + verb=f"The task {candidate_task.onboarding_task_id} of\ + {candidate_task.candidate_id} was updated to {candidate_task.status}.", + icon="people-circle", + redirect="/onboarding/onboarding-view", + ) + messages.info(request, _("Candidate task updated successfully..")) + choices = CandidateTask.Choice + return render( + request, + "onboarding/candidate-task.html", + {"choices": choices, "task": candidate_task}, + ) @login_required @hx_request_required -@stage_manager_can_enter('onboarding.change_candidatestage') +@require_http_methods(["POST"]) +@stage_manager_can_enter("onboarding.change_candidatestage") def candidate_stage_update(request, candidate_id, recruitment_id): """ function used to update candidate stage. @@ -648,30 +768,41 @@ def candidate_stage_update(request, candidate_id, recruitment_id): Returns: POST : return candidate task template """ - if request.method == "POST": - stage_id = request.POST.get('stage') - recruitment = Recruitment.objects.get(id=recruitment_id) - stage = OnboardingStage.objects.get(id=stage_id) - candidate = Candidate.objects.get(id=candidate_id) - candidate_stage = CandidateStage.objects.get(candidate_id=candidate) - candidate_stage.onboarding_stage_id = stage - candidate_stage.save() - onboarding_stages = OnboardingStage.objects.all() - choices = CandidateTask.Choice - messages.info(request, _('Candidate stage updated successfully...')) - users = [employee.employee_user_id for employee in candidate_stage.onboarding_stage_id.employee_id.all()] - notify.send( - request.user.employee_get, - recipient=users, - verb=f"The stage of {candidate_stage.candidate_id} was updated to {candidate_stage.onboarding_stage_id}.", - icon="people-circle", - redirect="/onboarding/onboarding-view", - ) - return render(request, 'onboarding/onboarding-table.html', {'recruitment': recruitment, 'onboarding_stages': onboarding_stages, 'choices': choices}) + stage_id = request.POST.get("stage") + recruitment = Recruitment.objects.get(id=recruitment_id) + stage = OnboardingStage.objects.get(id=stage_id) + candidate = Candidate.objects.get(id=candidate_id) + candidate_stage = CandidateStage.objects.get(candidate_id=candidate) + candidate_stage.onboarding_stage_id = stage + candidate_stage.save() + onboarding_stages = OnboardingStage.objects.all() + choices = CandidateTask.Choice + messages.info(request, _("Candidate stage updated successfully...")) + users = [ + employee.employee_user_id + for employee in candidate_stage.onboarding_stage_id.employee_id.all() + ] + notify.send( + request.user.employee_get, + recipient=users, + verb=f"The stage of {candidate_stage.candidate_id} \ + was updated to {candidate_stage.onboarding_stage_id}.", + icon="people-circle", + redirect="/onboarding/onboarding-view", + ) + return render( + request, + "onboarding/onboarding-table.html", + { + "recruitment": recruitment, + "onboarding_stages": onboarding_stages, + "choices": choices, + }, + ) @login_required -def hired_candidate_chart(request): +def hired_candidate_chart(_): """ function used to show hired candidates in all recruitments. @@ -687,19 +818,26 @@ def hired_candidate_chart(request): border_color = [] recruitments = Recruitment.objects.all() for recruitment in recruitments: - r = random.randint(0, 255) - g = random.randint(0, 255) - b = random.randint(0, 255) - background_color.append(f"rgba({r}, {g}, {b}, 0.2") - border_color.append(f"rgb({r}, {g}, {b})") + red = random.randint(0, 255) + green = random.randint(0, 255) + blue = random.randint(0, 255) + background_color.append(f"rgba({red}, {green}, {blue}, 0.2") + border_color.append(f"rgb({red}, {green}, {blue})") labels.append(f"{recruitment}") - data.append(recruitment.candidate.filter( - hired=True).count()) - return JsonResponse({'labels': labels, 'data': data, 'background_color': background_color, 'border_color': border_color}, safe=False) + data.append(recruitment.candidate.filter(hired=True).count()) + return JsonResponse( + { + "labels": labels, + "data": data, + "background_color": background_color, + "border_color": border_color, + }, + safe=False, + ) @login_required -def onboard_candidate_chart(request): +def onboard_candidate_chart(_): """ function used to show onboard started candidates in recruitments. @@ -715,13 +853,38 @@ def onboard_candidate_chart(request): border_color = [] recruitments = Recruitment.objects.all() for recruitment in recruitments: - r = random.randint(0, 255) - g = random.randint(0, 255) - b = random.randint(0, 255) - background_color.append(f"rgba({r}, {g}, {b}, 0.2") - border_color.append(f"rgb({r}, {g}, {b})") + red = random.randint(0, 255) + green = random.randint(0, 255) + blue = random.randint(0, 255) + background_color.append(f"rgba({red}, {green}, {blue}, 0.2") + border_color.append(f"rgb({red}, {green}, {blue})") labels.append( - f"{recruitment.job_position_id.job_position} | {recruitment.start_date}") - data.append(recruitment.candidate.filter( - hired=True, start_onboard=True).count()) - return JsonResponse({'labels': labels, 'data': data, 'background_color': background_color, 'border_color': border_color}, safe=False) + f"{recruitment.job_position_id.job_position} | {recruitment.start_date}" + ) + data.append( + recruitment.candidate.filter(hired=True, start_onboard=True).count() + ) + return JsonResponse( + { + "labels": labels, + "data": data, + "background_color": background_color, + "border_color": border_color, + }, + safe=False, + ) + + +@login_required +@permission_required("candidate.view_candidate") +def update_joining(request): + """ + Ajax method to update joinng date + """ + print(request.POST) + cand_id = request.POST["candId"] + date = request.POST["date"] + candidate_obj = Candidate.objects.get(id=cand_id) + candidate_obj.joining_date = date + candidate_obj.save() + return JsonResponse({"message": "Success"}) diff --git a/recruitment/admin.py b/recruitment/admin.py index 9611ef43d..9be378d53 100644 --- a/recruitment/admin.py +++ b/recruitment/admin.py @@ -1,13 +1,15 @@ +""" +admin.py + +This page is used to register the model with admins site. +""" from django.contrib import admin -from .models import Stage, Recruitment, Candidate -from simple_history.admin import SimpleHistoryAdmin +from recruitment.models import Stage, Recruitment, Candidate # Register your models here. -class CandidateHistoryAdmin(SimpleHistoryAdmin): - list_display = ['name','stage_id'] admin.site.register(Stage) admin.site.register(Recruitment) -admin.site.register(Candidate,CandidateHistoryAdmin) +admin.site.register(Candidate) diff --git a/recruitment/apps.py b/recruitment/apps.py index d9396da8d..5b83e1988 100644 --- a/recruitment/apps.py +++ b/recruitment/apps.py @@ -1,6 +1,19 @@ +""" +apps.py +""" from django.apps import AppConfig class RecruitmentConfig(AppConfig): + """ + AppConfig for the 'recruitment' app. + + This class represents the configuration for the 'recruitment' app. It provides + the necessary settings and metadata for the app. + + Attributes: + default_auto_field (str): The default auto field to use for model field IDs. + name (str): The name of the app. + """ default_auto_field = 'django.db.models.BigAutoField' name = 'recruitment' diff --git a/recruitment/decorators.py b/recruitment/decorators.py index 03fdca378..55445fefe 100644 --- a/recruitment/decorators.py +++ b/recruitment/decorators.py @@ -1,44 +1,135 @@ -from employee.models import Employee -from recruitment.models import Recruitment,Stage +""" +decorators.py + +Custom decorators for permission and manager checks in the application. +""" from django.contrib import messages from django.http import HttpResponseRedirect +from employee.models import Employee +from recruitment.models import Recruitment, Stage + + +def decorator_with_arguments(decorator): + """ + Decorator that allows decorators to accept arguments and keyword arguments. + + Args: + decorator (function): The decorator function to be wrapped. + + Returns: + function: The wrapper function. + + """ + + def wrapper(*args, **kwargs): + """ + Wrapper function that captures the arguments and keyword arguments. + + Args: + *args: Variable length argument list. + **kwargs: Arbitrary keyword arguments. + + Returns: + function: The inner wrapper function. + + """ + + def inner_wrapper(func): + """ + Inner wrapper function that applies the decorator to the function. + + Args: + func (function): The function to be decorated. + + Returns: + function: The decorated function. + + """ + return decorator(func, *args, **kwargs) + + return inner_wrapper + + return wrapper + -decorator_with_arguments = lambda decorator: lambda *args, **kwargs: lambda func: decorator(func, *args, **kwargs) @decorator_with_arguments def manager_can_enter(function, perm): """ - This method is used to check permission to employee for enter to the function if the employee - do not have permission also checks, has reporting manager. + Decorator that checks if the user has the specified permission or is a manager. + + Args: + perm (str): The permission to check. + + Returns: + function: The decorated function. + + Raises: + None + """ + def _function(request, *args, **kwargs): + """ + Inner function that performs the permission and manager check. + + Args: + request (HttpRequest): The request object. + *args: Variable length argument list. + **kwargs: Arbitrary keyword arguments. + + Returns: + HttpResponse: The response from the decorated function. + + """ user = request.user employee = Employee.objects.filter(employee_user_id=user).first() - is_manager = Stage.objects.filter(stage_managers=employee).exists() or Recruitment.objects.filter(recruitment_managers=employee).exists() + is_manager = ( + Stage.objects.filter(stage_managers=employee).exists() + or Recruitment.objects.filter(recruitment_managers=employee).exists() + ) if user.has_perm(perm) or is_manager: return function(request, *args, **kwargs) - else: - messages.info(request,'You dont have permission.') - return HttpResponseRedirect(request. META. get('HTTP_REFERER', '/')) + messages.info(request, "You don't have permission.") + return HttpResponseRedirect(request.META.get("HTTP_REFERER", "/")) + return _function - - -decorator_with_arguments = lambda decorator: lambda *args, **kwargs: lambda func: decorator(func, *args, **kwargs) @decorator_with_arguments def recruitment_manager_can_enter(function, perm): """ - This method is used to check permission to employee for enter to the function if the employee - do not have permission also checks, has reporting manager. + Decorator that checks if the user has the specified permission or is a recruitment manager. + + Args: + perm (str): The permission to check. + + Returns: + function: The decorated function. + + Raises: + None + """ + def _function(request, *args, **kwargs): + """ + Inner function that performs the permission and manager check. + + Args: + request (HttpRequest): The request object. + *args: Variable length argument list. + **kwargs: Arbitrary keyword arguments. + + Returns: + HttpResponse: The response from the decorated function. + + """ user = request.user employee = Employee.objects.filter(employee_user_id=user).first() is_manager = Recruitment.objects.filter(recruitment_managers=employee).exists() if user.has_perm(perm) or is_manager: return function(request, *args, **kwargs) - messages.info(request,'You dont have permission.') - return HttpResponseRedirect(request. META. get('HTTP_REFERER', '/')) + messages.info(request, "You don't have permission.") + return HttpResponseRedirect(request.META.get("HTTP_REFERER", "/")) return _function - diff --git a/recruitment/filters.py b/recruitment/filters.py index 71fd45804..0dde86afa 100644 --- a/recruitment/filters.py +++ b/recruitment/filters.py @@ -1,176 +1,237 @@ +""" +filters.py + +This page is used to register filter for recruitment models + +""" import django_filters from django import forms - from recruitment.models import Candidate, Recruitment, Stage -from django import forms -# from django.forms.widgets import Boo -from django.db import models +# from django.forms.widgets import Boo + class FilterSet(django_filters.FilterSet): + """ + Custom FilterSet class that applies specific CSS classes to filter + widgets. + + The class applies CSS classes to different types of filter widgets, + such as NumberInput, EmailInput, TextInput, Select, Textarea, + CheckboxInput, CheckboxSelectMultiple, and ModelChoiceField. The + CSS classes are applied to enhance the styling and behavior of the + filter widgets. + """ + def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) - for field_name, field in self.filters.items(): - filter_widget = self.filters[field_name] + for _, filter_widget in self.filters.items(): widget = filter_widget.field.widget - if isinstance(widget, (forms.NumberInput, forms.EmailInput,forms.TextInput)): - filter_widget.field.widget.attrs.update({'class': 'oh-input w-100'}) - elif isinstance(widget,(forms.Select,)): - filter_widget.field.widget.attrs.update({'class': 'oh-select oh-select-2 select2-hidden-accessible',}) - elif isinstance(widget,(forms.Textarea)): - filter_widget.field.widget.attrs.update({'class': 'oh-input w-100'}) - elif isinstance(widget, (forms.CheckboxInput,forms.CheckboxSelectMultiple,)): - filter_widget.field.widget.attrs.update({'class': 'oh-switch__checkbox'}) - elif isinstance(widget,(forms.ModelChoiceField)): - filter_widget.field.widget.attrs.update({'class': 'oh-select oh-select-2 select2-hidden-accessible',}) - - + if isinstance(widget, (forms.NumberInput, forms.EmailInput, forms.TextInput)): + widget.attrs.update({"class": "oh-input w-100"}) + elif isinstance(widget, (forms.Select,)): + widget.attrs.update({"class": "oh-select oh-select-2 select2-hidden-accessible"}) + elif isinstance(widget, (forms.Textarea)): + widget.attrs.update({"class": "oh-input w-100"}) + elif isinstance(widget, (forms.CheckboxInput, forms.CheckboxSelectMultiple)): + widget.attrs.update({"class": "oh-switch__checkbox"}) + elif isinstance(widget, (forms.ModelChoiceField)): + widget.attrs.update({"class": "oh-select oh-select-2 select2-hidden-accessible"}) class CandidateFilter(FilterSet): - name = django_filters.CharFilter( - field_name='name', - lookup_expr='icontains' - ) + """ + Filter set class for Candidate model + + Args: + FilterSet (class): custom filter set class to apply styling + """ + + name = django_filters.CharFilter(field_name="name", lookup_expr="icontains") start_date = django_filters.DateFilter( - field_name='recruitment_id__start_date', - widget=forms.DateInput(attrs={'type': 'date'}) + field_name="recruitment_id__start_date", + widget=forms.DateInput(attrs={"type": "date"}), ) end_date = django_filters.DateFilter( - field_name='recruitment_id__end_date', - widget=forms.DateInput(attrs={'type': 'date'}) + field_name="recruitment_id__end_date", + widget=forms.DateInput(attrs={"type": "date"}), ) scheduled_from = django_filters.DateFilter( - field_name='schedule_date', - lookup_expr='gte', - widget=forms.DateInput(attrs={'type': 'date'}) + field_name="schedule_date", + lookup_expr="gte", + widget=forms.DateInput(attrs={"type": "date"}), ) scheduled_till = django_filters.DateFilter( - field_name='schedule_date', - lookup_expr='lte', - widget=forms.DateInput(attrs={'type': 'date'}) + field_name="schedule_date", + lookup_expr="lte", + widget=forms.DateInput(attrs={"type": "date"}), ) + class Meta: + """ + Meta class to add the additional info + """ model = Candidate - fields=[ - 'recruitment_id', - 'stage_id', - 'schedule_date', - 'email', - 'mobile', - 'country', - 'state', - 'city', - 'zip', - 'gender', - 'start_onboard', - 'hired', - 'canceled', - 'is_active', - 'recruitment_id__company_id', - 'recruitment_id__job_position_id', - 'recruitment_id__closed', - 'recruitment_id__is_active', - 'recruitment_id__job_position_id__department_id', - 'recruitment_id__recruitment_managers', - 'stage_id__stage_managers', - 'stage_id__stage_type', + fields = [ + "recruitment_id", + "stage_id", + "schedule_date", + "email", + "mobile", + "country", + "state", + "city", + "zip", + "gender", + "start_onboard", + "hired", + "canceled", + "is_active", + "recruitment_id__company_id", + "recruitment_id__job_position_id", + "recruitment_id__closed", + "recruitment_id__is_active", + "recruitment_id__job_position_id__department_id", + "recruitment_id__recruitment_managers", + "stage_id__stage_managers", + "stage_id__stage_type", ] + def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) - self.form.fields['is_active'].initial = True - + self.form.fields["is_active"].initial = True -BOOLEAN_CHOICES = (('',''),('false', 'False'), ('true', 'True'),) +BOOLEAN_CHOICES = ( + ("", ""), + ("false", "False"), + ("true", "True"), +) class RecruitmentFilter(FilterSet): - description = django_filters.CharFilter(lookup_expr='icontains') + """ + Filter set class for Recruitment model + + Args: + FilterSet (class): custom filter set class to apply styling + """ + + description = django_filters.CharFilter(lookup_expr="icontains") start_date = django_filters.DateFilter( - field_name='start_date', - widget=forms.DateInput(attrs={'type': 'date'}) + field_name="start_date", widget=forms.DateInput(attrs={"type": "date"}) ) end_date = django_filters.DateFilter( - field_name='end_date', - widget=forms.DateInput(attrs={'type': 'date'}) + field_name="end_date", widget=forms.DateInput(attrs={"type": "date"}) ) start_from = django_filters.DateFilter( - field_name='start_date', - lookup_expr='gte', - widget=forms.DateInput(attrs={'type': 'date'}) + field_name="start_date", + lookup_expr="gte", + widget=forms.DateInput(attrs={"type": "date"}), ) end_till = django_filters.DateFilter( - field_name='end_date', - lookup_expr='lte', - widget=forms.DateInput(attrs={'type': 'date'}) + field_name="end_date", + lookup_expr="lte", + widget=forms.DateInput(attrs={"type": "date"}), ) - search = django_filters.CharFilter(method='filter_by_name') - + search = django_filters.CharFilter(method="filter_by_name") + class Meta: + """ + Meta class to add the additional info + """ + model = Recruitment fields = [ - 'recruitment_managers', - 'company_id', - 'start_date', - 'end_date', - 'closed', - 'is_active', - 'job_position_id', + "recruitment_managers", + "company_id", + "title", + "is_event_based", + "start_date", + "end_date", + "closed", + "is_active", + "job_position_id", ] - - def filter_by_name(self,queryset, name, value): + def filter_by_name(self, queryset, _, value): """ Filter queryset by first name or last name. """ # Split the search value into first name and last name parts = value.split() first_name = parts[0] - last_name = ' '.join(parts[1:]) if len(parts) > 1 else '' + last_name = " ".join(parts[1:]) if len(parts) > 1 else "" - # Filter the queryset by first name and last name - job_queryset = queryset.filter(job_position_id__job_position__icontains = value) + job_queryset = queryset.filter( + open_positions__job_position__icontains=value + ) | queryset.filter(title__icontains=value) if first_name and last_name: - queryset = queryset.filter(recruitment_managers__employee_first_name__icontains=first_name, recruitment_managers__employee_last_name__icontains=last_name) + queryset = queryset.filter( + recruitment_managers__employee_first_name__icontains=first_name, + recruitment_managers__employee_last_name__icontains=last_name, + ) elif first_name: - queryset = queryset.filter(recruitment_managers__employee_first_name__icontains=first_name) + queryset = queryset.filter( + recruitment_managers__employee_first_name__icontains=first_name + ) elif last_name: - queryset = queryset.filter(recruitment_managers__employee_last_name__icontains=last_name) + queryset = queryset.filter( + recruitment_managers__employee_last_name__icontains=last_name + ) - return queryset | job_queryset + queryset = queryset | job_queryset + return queryset.distinct() class StageFilter(FilterSet): - search = django_filters.CharFilter(method='filter_by_name') + """ + Filter set class for Stage model + + Args: + FilterSet (class): custom filter set class to apply styling + """ + + search = django_filters.CharFilter(method="filter_by_name") class Meta: + """ + Meta class to add the additional info + """ model = Stage fields = [ - 'recruitment_id', - 'recruitment_id__job_position_id', - 'recruitment_id__job_position_id__department_id', - 'recruitment_id__company_id', - 'recruitment_id__recruitment_managers', - 'stage_managers', - 'stage_type', + "recruitment_id", + "recruitment_id__job_position_id", + "recruitment_id__job_position_id__department_id", + "recruitment_id__company_id", + "recruitment_id__recruitment_managers", + "stage_managers", + "stage_type", ] - def filter_by_name(self,queryset, name, value): + + def filter_by_name(self, queryset, _, value): """ Filter queryset by first name or last name. """ # Split the search value into first name and last name parts = value.split() first_name = parts[0] - last_name = ' '.join(parts[1:]) if len(parts) > 1 else '' + last_name = " ".join(parts[1:]) if len(parts) > 1 else "" # Filter the queryset by first name and last name - stage_queryset = queryset.filter(stage__icontains = value) + stage_queryset = queryset.filter(stage__icontains=value) if first_name and last_name: - queryset = queryset.filter(stage_managers__employee_first_name__icontains=first_name, stage_managers__employee_last_name__icontains=last_name) + queryset = queryset.filter( + stage_managers__employee_first_name__icontains=first_name, + stage_managers__employee_last_name__icontains=last_name, + ) elif first_name: - queryset = queryset.filter(stage_managers__employee_first_name__icontains=first_name) + queryset = queryset.filter( + stage_managers__employee_first_name__icontains=first_name + ) elif last_name: - queryset = queryset.filter(stage_managers__employee_last_name__icontains=last_name) + queryset = queryset.filter( + stage_managers__employee_last_name__icontains=last_name + ) return queryset | stage_queryset diff --git a/recruitment/forms.py b/recruitment/forms.py index f023a6706..a76c3faa4 100644 --- a/recruitment/forms.py +++ b/recruitment/forms.py @@ -1,127 +1,254 @@ +""" +forms.py + +This module contains the form classes used in the application. + +Each form represents a specific functionality or data input in the +application. They are responsible for validating +and processing user input data. + +Classes: +- YourForm: Represents a form for handling specific data input. + +Usage: from django import forms -from django import forms -from .models import Stage, Recruitment, Candidate, StageNote + +class YourForm(forms.Form): + field_name = forms.CharField() + + def clean_field_name(self): + # Custom validation logic goes here + pass +""" + import uuid -from employee.models import Employee +from django import forms from django.utils.translation import gettext_lazy as _ +from recruitment.models import Stage, Recruitment, Candidate, StageNote, JobPosition +from recruitment import widgets + class ModelForm(forms.ModelForm): + """ + Overriding django default model form to apply some styles + """ + def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) for field_name, field in self.fields.items(): widget = field.widget - if isinstance(widget, (forms.NumberInput, forms.EmailInput, forms.TextInput, forms.FileInput)): + if isinstance( + widget, + (forms.NumberInput, forms.EmailInput, forms.TextInput, forms.FileInput), + ): label = _(field.label) field.widget.attrs.update( - {'class': 'oh-input w-100', 'placeholder': label}) + {"class": "oh-input w-100", "placeholder": label} + ) elif isinstance(widget, forms.URLInput): field.widget.attrs.update( - {'class': 'oh-input w-100', 'placeholder': field.label}) + {"class": "oh-input w-100", "placeholder": field.label} + ) elif isinstance(widget, (forms.Select,)): - field.empty_label = _('---Choose {label}---').format(label=_(field.label)) + field.empty_label = _("---Choose {label}---").format( + label=_(field.label) + ) self.fields[field_name].widget.attrs.update( - {'id': uuid.uuid4, 'class': 'oh-select oh-select-2 w-100', 'style': 'height:50px;'}) + { + "id": uuid.uuid4, + "class": "oh-select oh-select-2 w-100", + "style": "height:50px;", + } + ) elif isinstance(widget, (forms.Textarea)): label = _(field.label) field.widget.attrs.update( - {'class': 'oh-input w-100', 'placeholder': label, 'rows': 2, 'cols': 40}) - elif isinstance(widget, (forms.CheckboxInput, forms.CheckboxSelectMultiple,)): - field.widget.attrs.update({'class': 'oh-switch__checkbox '}) + { + "class": "oh-input w-100", + "placeholder": label, + "rows": 2, + "cols": 40, + } + ) + elif isinstance( + widget, + ( + forms.CheckboxInput, + forms.CheckboxSelectMultiple, + ), + ): + field.widget.attrs.update({"class": "oh-switch__checkbox "}) class RegistrationForm(forms.ModelForm): + """ + Overriding django default model form to apply some styles + """ + def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) for field_name, field in self.fields.items(): widget = field.widget if isinstance(widget, (forms.Select,)): - label = '' + label = "" if field.label is not None: label = _(field.label) - field.empty_label = _('---Choose {label}---').format(label=label) + field.empty_label = _("---Choose {label}---").format(label=label) self.fields[field_name].widget.attrs.update( - {'id': uuid.uuid4, 'class': 'oh-select-2 oh-select--sm w-100'}) + {"id": uuid.uuid4, "class": "oh-select-2 oh-select--sm w-100"} + ) elif isinstance(widget, (forms.TextInput)): - field.widget.attrs.update({'class': 'oh-input w-100', }) - elif isinstance(widget, (forms.CheckboxInput, forms.CheckboxSelectMultiple,)): - field.widget.attrs.update({'class': 'oh-switch__checkbox '}) - + field.widget.attrs.update( + { + "class": "oh-input w-100", + } + ) + elif isinstance( + widget, + ( + forms.CheckboxInput, + forms.CheckboxSelectMultiple, + ), + ): + field.widget.attrs.update({"class": "oh-switch__checkbox "}) class DropDownForm(forms.ModelForm): + """ + Overriding django default model form to apply some styles + """ + def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) for field_name, field in self.fields.items(): widget = field.widget - if isinstance(widget, (forms.NumberInput, forms.EmailInput, forms.TextInput, forms.FileInput, forms.URLInput)): + if isinstance( + widget, + ( + forms.NumberInput, + forms.EmailInput, + forms.TextInput, + forms.FileInput, + forms.URLInput, + ), + ): if field.label is not None: label = _(field.label) field.widget.attrs.update( - {'class': 'oh-input oh-input--small oh-table__add-new-row d-block w-100', 'placeholder': label}) + { + "class": "oh-input oh-input--small oh-table__add-new-row d-block w-100", + "placeholder": label, + } + ) elif isinstance(widget, (forms.Select,)): - # label = field.label.replace('id','') - # field.empty_label = f'---Choose {label}---' self.fields[field_name].widget.attrs.update( - {'class': 'oh-select-2 oh-select--xs-forced ', 'id': uuid.uuid4(), }) + { + "class": "oh-select-2 oh-select--xs-forced ", + "id": uuid.uuid4(), + } + ) elif isinstance(widget, (forms.Textarea)): if field.label is not None: label = _(field.label) field.widget.attrs.update( - {'class': 'oh-input oh-input--small oh-input--textarea', 'placeholder': label, 'rows': 1, 'cols': 40}) - elif isinstance(widget, (forms.CheckboxInput, forms.CheckboxSelectMultiple,)): - field.widget.attrs.update({'class': 'oh-switch__checkbox '}) - + { + "class": "oh-input oh-input--small oh-input--textarea", + "placeholder": label, + "rows": 1, + "cols": 40, + } + ) + elif isinstance( + widget, + ( + forms.CheckboxInput, + forms.CheckboxSelectMultiple, + ), + ): + field.widget.attrs.update({"class": "oh-switch__checkbox "}) + class RecruitmentCreationForm(ModelForm): + """ + Form for Recruitment model + """ + class Meta: + """ + Meta class to add the additional info + """ + model = Recruitment - fields = '__all__' + fields = "__all__" widgets = { - 'start_date': forms.DateInput(attrs={'type': 'date'}), - 'end_date': forms.DateInput(attrs={'type': 'date'}), + "start_date": forms.DateInput(attrs={"type": "date"}), + "end_date": forms.DateInput(attrs={"type": "date"}), + "description": forms.Textarea(attrs={"data-summernote": ""}), } - labels = { - 'description': _('Description'), - 'vacancy': _('Vacancy') - } - def __init__(self, *args, **kwargs): - super(RecruitmentCreationForm, self).__init__(*args, **kwargs) - + labels = {"description": _("Description"), "vacancy": _("Vacancy")} class StageCreationForm(ModelForm): - class Meta: - model = Stage - fields = '__all__' - exclude = ('sequence',) - labels = { - 'stage': _('Stage'), - - } + """ + Form for Stage model + """ - def __init__(self, *args, **kwargs): - super(StageCreationForm, self).__init__(*args, **kwargs) - + class Meta: + """ + Meta class to add the additional info + """ + + model = Stage + fields = "__all__" + exclude = ("sequence",) + labels = { + "stage": _("Stage"), + } class CandidateCreationForm(ModelForm): + """ + Form for Candidate model + """ + + load = forms.CharField(widget=widgets.RecruitmentAjaxWidget, required=False) + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + if self.instance.recruitment_id is not None: + if self.instance is not None: + self.fields["job_position_id"] = forms.ModelChoiceField( + queryset=self.instance.recruitment_id.open_positions.all(), + # additional field options + ) + self.fields["recruitment_id"].widget.attrs = {"data-widget": "ajax-widget"} + self.fields["job_position_id"].widget.attrs = {"data-widget": "ajax-widget"} + class Meta: + """ + Meta class to add the additional info + """ + model = Candidate - fields = '__all__' - exclude = ('confirmation', 'scheduled_for', - 'schedule_date', 'joining_date', 'stage_id') + fields = "__all__" + exclude = ( + "confirmation", + "scheduled_for", + "schedule_date", + "joining_date", + "stage_id", + ) widgets = { - 'scheduled_date': forms.DateInput(attrs={'type': 'date'}), - 'dob': forms.DateInput(attrs={'type': 'date'}), + "scheduled_date": forms.DateInput(attrs={"type": "date"}), + "dob": forms.DateInput(attrs={"type": "date"}), } labels = { - 'name': _('Name'), - 'email': _('Email'), - 'mobile': _('Mobile'), - 'address': _('Address'), - 'zip': _('Zip'), + "name": _("Name"), + "email": _("Email"), + "mobile": _("Mobile"), + "address": _("Address"), + "zip": _("Zip"), } - def save(self, commit: bool = ...): candidate = self.instance @@ -130,86 +257,162 @@ class CandidateCreationForm(ModelForm): candidate.hired = False candidate.start_onboard = False if stage is not None: - if stage.stage_type == 'hired' and candidate.canceled is False: + if stage.stage_type == "hired" and candidate.canceled is False: candidate.hired = True candidate.start_onboard = True candidate.recruitment_id = recruitment candidate.stage_id = stage + job_id = self.data["job_position_id"] + job_position = JobPosition.objects.get(id=job_id) + self.instance.job_position_id = job_position return super().save(commit) + def clean(self): + if self.instance.name is not None: + self.errors.pop("job_position_id", None) + if ( + self.instance.job_position_id is None + or self.data["job_position_id"] == "" + ): + raise forms.ValidationError( + {"job_position_id": "This field is required"} + ) + if ( + self.instance.job_position_id + not in self.instance.recruitment_id.open_positions.all() + ): + raise forms.ValidationError({"job_position_id": "Choose valid choice"}) + return super().clean() + class ApplicationForm(RegistrationForm): - active_recruitment = Recruitment.objects.filter(is_active=True,closed=False) + """ + Form for create Candidate + """ + + load = forms.CharField(widget=widgets.RecruitmentAjaxWidget, required=False) + active_recruitment = Recruitment.objects.filter(is_active=True, closed=False) recruitment_id = forms.ModelChoiceField(queryset=active_recruitment) + class Meta: + """ + Meta class to add the additional info + """ + model = Candidate exclude = ( - 'stage_id', - 'schedule_date', - 'referral', - 'start_onboard', - 'hired', - 'is_active', - 'canceled', - 'joining_date', + "stage_id", + "schedule_date", + "referral", + "start_onboard", + "hired", + "is_active", + "canceled", + "joining_date", ) widgets = { - 'recruitment_id':forms.TextInput(attrs={'required':'required',}), - 'dob':forms.DateInput(attrs={'type':'date',}) + "recruitment_id": forms.TextInput( + attrs={ + "required": "required", + } + ), + "dob": forms.DateInput( + attrs={ + "type": "date", + } + ), } + def __init__(self, *args, **kwargs): - super(ApplicationForm, self).__init__(*args, **kwargs) - + super().__init__(*args, **kwargs) + self.fields["recruitment_id"].widget.attrs = {"data-widget": "ajax-widget"} + self.fields["job_position_id"].widget.attrs = {"data-widget": "ajax-widget"} + + class RecruitmentDropDownForm(DropDownForm): + """ + Form for Recruitment model + """ + class Meta: - fields = '__all__' + """ + Meta class to add the additional info + """ + + fields = "__all__" model = Recruitment widgets = { - 'start_date': forms.DateInput(attrs={'type': 'date'}), - 'end_date': forms.DateInput(attrs={'type': 'date'}), - } - labels = { - 'description': _('Description'), - 'vacancy': _('Vacancy') + "start_date": forms.DateInput(attrs={"type": "date"}), + "end_date": forms.DateInput(attrs={"type": "date"}), + "description": forms.Textarea(attrs={"data-summernote": ""}), } + labels = {"description": _("Description"), "vacancy": _("Vacancy")} + def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) - self.fields['job_position_id'].widget.attrs.update({'id':uuid.uuid4}) - self.fields['recruitment_managers'].widget.attrs.update({'id':uuid.uuid4}) - - field = self.fields['is_active'] + self.fields["job_position_id"].widget.attrs.update({"id": uuid.uuid4}) + self.fields["recruitment_managers"].widget.attrs.update({"id": uuid.uuid4}) + field = self.fields["is_active"] field.widget = field.hidden_widget() - class CandidateDropDownForm(DropDownForm): + """ + Form for Candidate model + """ + profile = forms.FileField(required=False) + class Meta: + """ + Meta class to add the additional info + """ + model = Candidate fields = [ - 'name','email','mobile','resume','stage_id','recruitment_id','profile' + "name", + "email", + "mobile", + "resume", + "stage_id", + "recruitment_id", + "job_position_id", + "profile", ] labels = { - 'name': _('Name'), - 'email': _('Email'), - 'mobile': _('Mobile'), + "name": _("Name"), + "email": _("Email"), + "mobile": _("Mobile"), + "job_position_id": _("Job Position"), } + def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) - self.fields['recruitment_id'].widget.attrs.update({'id':uuid.uuid4}) - self.fields['stage_id'].widget.attrs.update({'id':uuid.uuid4}) + self.fields["recruitment_id"].widget.attrs.update({"id": uuid.uuid4}) + self.fields["stage_id"].widget.attrs.update({"id": uuid.uuid4}) + class StageDropDownForm(DropDownForm): + """ + Form for Stage model + """ + class Meta: + """ + Meta class to add the additional info + """ + model = Stage - fields = '__all__' - exclude =['sequence',] + fields = "__all__" + exclude = [ + "sequence", + ] labels = { - 'stage': _('Stage'), + "stage": _("Stage"), } def __init__(self, *args, **kwargs): - super(StageDropDownForm, self).__init__(*args, **kwargs) + super().__init__(*args, **kwargs) stage = Stage.objects.last() if stage is not None and stage.sequence is not None: self.instance.sequence = stage.sequence + 1 @@ -217,21 +420,24 @@ class StageDropDownForm(DropDownForm): self.instance.sequence = 1 - class StageNoteForm(ModelForm): + """ + Form for StageNote model + """ + class Meta: + """ + Meta class to add the additional info + """ + model = StageNote exclude = ( - 'updated_by', - 'stage_id', - ) - fields = '__all__' + "updated_by", + "stage_id", + ) + fields = "__all__" def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) - field = self.fields['candidate_id'] + field = self.fields["candidate_id"] field.widget = field.hidden_widget() - - - - diff --git a/recruitment/methods.py b/recruitment/methods.py index 0caef4c9a..16c5ac0ee 100644 --- a/recruitment/methods.py +++ b/recruitment/methods.py @@ -1,15 +1,23 @@ +""" +methods.py + +This page is used to write reusable methods. + +""" def is_stagemanager(request): """ - This method is used to check stage manager, if the employee is also recruitment manager it returns true + This method is used to check stage manager, if the employee is also + recruitment manager it returns true """ try: employee = request.user.employee_get return employee.recruitment_set.exists() or employee.stage_set.exists() - except: + except Exception: return False + def is_recruitmentmanager(request): """ This method is used to check the employee is recruitment manager or not @@ -17,20 +25,25 @@ def is_recruitmentmanager(request): try: employee = request.user.employee_get return employee.recruitment_set.exists() - except: + except Exception: return False -def stage_manages(request,stage): + +def stage_manages(request, stage): """ This method is used to check the employee manager to this stage.""" try: employee = request.user.employee_get - return stage.stage_manager.filter(id = employee.id).exists() or stage.recruitment_id.recruitment_managers.filter(id=employee.id).exists() + return ( + stage.stage_manager.filter(id=employee.id).exists() + or stage.recruitment_id.recruitment_managers.filter(id=employee.id).exists() + ) except Exception: return False -def recruitment_manages(request,recruitment): + +def recruitment_manages(request, recruitment): """ This method is used to check the employee is manager to the current recruitment """ @@ -38,4 +51,4 @@ def recruitment_manages(request,recruitment): employee = request.user.employee_get return recruitment.recruitment_managers.filter(id=employee.id).exists() except Exception: - return False \ No newline at end of file + return False diff --git a/recruitment/middleware.py b/recruitment/middleware.py deleted file mode 100644 index 5181ab599..000000000 --- a/recruitment/middleware.py +++ /dev/null @@ -1,6 +0,0 @@ -from django.utils.deprecation import MiddlewareMixin - -class XFrameOptionsMiddleware(MiddlewareMixin): - def process_response(self, request, response): - response['X-Frame-Options'] = 'SAMEORIGIN' - return response \ No newline at end of file diff --git a/recruitment/models.py b/recruitment/models.py index 69b4baa68..f3ea64feb 100644 --- a/recruitment/models.py +++ b/recruitment/models.py @@ -1,125 +1,250 @@ +""" +models.py + +This module is used to register models for recruitment app + +""" +import os import django from django.db import models -from django.contrib.auth.models import User -from employee.models import Employee -from base.models import JobPosition,Company -from simple_history.models import HistoricalRecords from django.core.exceptions import ValidationError from django.utils.translation import gettext_lazy as _ +from simple_history.models import HistoricalRecords +from employee.models import Employee +from base.models import JobPosition, Company # Create your models here. -import os + def validate_pdf(value): """ This method is used to validate pdf """ ext = os.path.splitext(value.name)[1] # Get file extension - if ext.lower() != '.pdf': - raise ValidationError('File must be a PDF.') + if ext.lower() != ".pdf": + raise ValidationError("File must be a PDF.") + def validate_image(value): - return + """ + This method is used to validate the image + """ + return value + class Recruitment(models.Model): + """ + Recruitment model + """ + + title = models.CharField(max_length=30, null=True, blank=True) description = models.TextField(null=True) + is_event_based = models.BooleanField(default=False) + open_positions = models.ManyToManyField( + JobPosition, related_name="open_positions", blank=True + ) job_position_id = models.ForeignKey( - JobPosition, on_delete=models.CASCADE, null=False, db_constraint=False, related_name='recruitment',verbose_name="Job Position") + JobPosition, + on_delete=models.CASCADE, + null=True, + blank=True, + db_constraint=False, + related_name="recruitment", + verbose_name="Job Position", + ) vacancy = models.IntegerField(blank=True, null=True) recruitment_managers = models.ManyToManyField(Employee) - company_id = models.ForeignKey(Company,on_delete=models.DO_NOTHING,null=True,blank=True,verbose_name="Company") + company_id = models.ForeignKey( + Company, + on_delete=models.DO_NOTHING, + null=True, + blank=True, + verbose_name="Company", + ) start_date = models.DateField(default=django.utils.timezone.now) end_date = models.DateField(blank=True, null=True) closed = models.BooleanField(default=False) is_active = models.BooleanField(default=True) + class Meta: - unique_together = ('job_position_id','start_date',) + """ + Meta class to add the additional info + """ + + unique_together = [ + ( + "job_position_id", + "start_date", + ), + ("job_position_id", "start_date", "company_id"), + ] + permissions = (("archive_recruitment", "Archive Recruitment"),) + def __str__(self): - return f'{self.job_position_id.job_position} {self.start_date}' - class Meta: - permissions = (('archive_recruitment','Archive Recruitment'),) - unique_together = ('job_position_id','start_date','company_id') - unique_together = ('job_position_id','start_date',) - + title = ( + f"{self.job_position_id.job_position} {self.start_date}" + if self.title is None and self.job_position_id + else self.title + ) + + if not self.is_event_based and self.job_position_id is not None: + self.open_positions.add(self.job_position_id) + + return title + + def clean(self): + if self.title is None: + raise ValidationError({"title": "This field is required"}) + if not self.is_event_based and self.job_position_id is None: + raise ValidationError({"job_position_id": "This field is required"}) + return super().clean() + + def save(self, *args, **kwargs): + super().save(*args, **kwargs) # Save the Recruitment instance first + if self.is_event_based and self.open_positions is None: + raise ValidationError({"open_positions": "This field is required"}) + class Stage(models.Model): + """ + Stage model + """ + stage_types = [ - ('initial',_('Initial')), - ('test',_('Test')), - ('interview',_('Interview')), - ('hired',_('Hired')), + ("initial", _("Initial")), + ("test", _("Test")), + ("interview", _("Interview")), + ("hired", _("Hired")), ] - recruitment_id= models.ForeignKey( - Recruitment, on_delete=models.CASCADE,related_name='stage_set',verbose_name="Recruitment") - stage_managers= models.ManyToManyField(Employee,blank=True) - stage = models.CharField(max_length=50 ) - stage_type = models.CharField(max_length=20,choices=stage_types,default='interview') + recruitment_id = models.ForeignKey( + Recruitment, + on_delete=models.CASCADE, + related_name="stage_set", + verbose_name="Recruitment", + ) + stage_managers = models.ManyToManyField(Employee, blank=True) + stage = models.CharField(max_length=50) + stage_type = models.CharField( + max_length=20, choices=stage_types, default="interview" + ) sequence = models.IntegerField(null=True) is_active = models.BooleanField(default=True) - + def __str__(self): - return self.stage - + return f"{self.stage}" + class Meta: - permissions = (('archive_Stage','Archive Stage'),) - unique_together = ['recruitment_id','stage'] - - + """ + Meta class to add the additional info + """ + + permissions = (("archive_Stage", "Archive Stage"),) + unique_together = ["recruitment_id", "stage"] + + class Candidate(models.Model): - choices = [('male',_('Male')),('female',_('Female')),('other',_('Other'))] - name = models.CharField(max_length=100,null=True) - profile = models.ImageField(upload_to='recruitment/profile',null=True,validators=[validate_image,]) - portfolio = models.URLField(max_length=200,blank=True) - recruitment_id = models.ForeignKey(Recruitment, on_delete=models.CASCADE, blank=True, null=True,related_name='candidate',verbose_name="Recruitment") - stage_id = models.ForeignKey(Stage,on_delete=models.CASCADE,null=True,verbose_name="Stage") - schedule_date = models.DateTimeField(blank=True,null=True) - email = models.EmailField(max_length=254,) + """ + Candidate model + """ + + choices = [("male", _("Male")), ("female", _("Female")), ("other", _("Other"))] + name = models.CharField(max_length=100, null=True) + profile = models.ImageField( + upload_to="recruitment/profile", + null=True, + ) + portfolio = models.URLField(max_length=200, blank=True) + recruitment_id = models.ForeignKey( + Recruitment, + on_delete=models.CASCADE, + blank=True, + null=True, + related_name="candidate", + verbose_name="Recruitment", + ) + job_position_id = models.ForeignKey( + JobPosition, on_delete=models.CASCADE, null=True, blank=True + ) + stage_id = models.ForeignKey( + Stage, on_delete=models.CASCADE, null=True, verbose_name="Stage" + ) + schedule_date = models.DateTimeField(blank=True, null=True) + email = models.EmailField( + max_length=254, + unique=True, + ) mobile = models.CharField(max_length=15, blank=True) - resume = models.FileField(upload_to='recruitment/resume',validators = [validate_pdf,]) - referral = models.ForeignKey(Employee, on_delete= models.CASCADE, null= True, blank=True,related_name='candidate_referral') - address = models.TextField(null=True,blank=True) - country = models.CharField(max_length=30,null=True,blank=True) - dob = models.DateField(null=True,blank=True) - state = models.CharField(max_length=30,null=True,blank=True) - city = models.CharField(max_length=30,null=True,blank=True) - zip = models.CharField(max_length=30,null=True,blank=True) - gender =models.CharField(max_length=15,choices=choices,null=True) + resume = models.FileField( + upload_to="recruitment/resume", + validators=[ + validate_pdf, + ], + ) + referral = models.ForeignKey( + Employee, + on_delete=models.CASCADE, + null=True, + blank=True, + related_name="candidate_referral", + ) + address = models.TextField(null=True, blank=True) + country = models.CharField(max_length=30, null=True, blank=True) + dob = models.DateField(null=True, blank=True) + state = models.CharField(max_length=30, null=True, blank=True) + city = models.CharField(max_length=30, null=True, blank=True) + zip = models.CharField(max_length=30, null=True, blank=True) + gender = models.CharField(max_length=15, choices=choices, null=True) start_onboard = models.BooleanField(default=False) hired = models.BooleanField(default=False) canceled = models.BooleanField(default=False) is_active = models.BooleanField(default=True) - joining_date = models.DateField(blank=True,null=True) + joining_date = models.DateField(blank=True, null=True) history = HistoricalRecords( - related_name='candidate_history', + related_name="candidate_history", ) - def __str__(self): - return self.name + return f"{self.name}" def save(self, *args, **kwargs): - if self.stage_id is not None: - if self.stage_id.stage_type == 'hired': - self.hired = True - super().save(*args, **kwargs) - + # Check if the 'stage_id' attribute is not None + if self.stage_id is not None: + # Check if the stage type is 'hired' + if self.stage_id.stage_type == "hired": + self.hired = True + if not self.recruitment_id.is_event_based and self.job_position_id is None: + self.job_position_id = self.recruitment_id.job_position_id + if self.job_position_id not in self.recruitment_id.open_positions.all(): + raise ValidationError({"job_position_id": "Choose valid choice"}) + if self.recruitment_id.is_event_based and self.job_position_id is None: + raise ValidationError({"job_position_id": "This field is required."}) + super().save(*args, **kwargs) + class Meta: - unique_together = ('email','recruitment_id',) - permissions = (('view_history','View Candidate History'),('archive_candidate','Archive Candidate')) - - - + """ + Meta class to add the additional info + """ + unique_together = ( + "email", + "recruitment_id", + ) + permissions = ( + ("view_history", "View Candidate History"), + ("archive_candidate", "Archive Candidate"), + ) class StageNote(models.Model): - candidate_id = models.ForeignKey(Candidate,on_delete=models.CASCADE) - title = models.CharField(max_length=50,null=True) + """ + StageNote model + """ + + candidate_id = models.ForeignKey(Candidate, on_delete=models.CASCADE) + title = models.CharField(max_length=50, null=True) description = models.TextField() - stage_id = models.ForeignKey(Stage,on_delete=models.CASCADE) - updated_by = models.ForeignKey(Employee,on_delete=models.CASCADE) - + stage_id = models.ForeignKey(Stage, on_delete=models.CASCADE) + updated_by = models.ForeignKey(Employee, on_delete=models.CASCADE) + def __str__(self) -> str: - return self.description - - + return f"{self.description}" diff --git a/recruitment/static/recruitment/widget/recruitmentAjax.js b/recruitment/static/recruitment/widget/recruitmentAjax.js new file mode 100644 index 000000000..f38a8a950 --- /dev/null +++ b/recruitment/static/recruitment/widget/recruitmentAjax.js @@ -0,0 +1,54 @@ +$(document).ready(function () { + $(`[data-widget="ajax-widget"]select`).css({ + "width": "100% ", + "height": "50px", + "display": "flex", + "border": "1px solid hsl(213deg,22%,84%)", + "border-radius": "0rem", + "padding": "0.8rem 1.25rem", + "color": "hsl(0deg,0%,11%)", + }); + + function checkInitial() { + const initialValue = $("#id_recruitment_id").val(); + if (initialValue == "") { + $("#id_job_position_id").hide(); + } else { + $("#id_job_position_id").show(); + } + } + checkInitial(); + $("#id_recruitment_id").change(function (e) { + e.preventDefault(); + checkInitial(); + var recId = $(this).val(); + + $.ajax({ + type: "get", + url: "/recruitment/get-open-positions", + data: { recId: recId }, + success: function (response) { + var openPositions = JSON.parse(response.openPositions); + console.log(openPositions) + recruitmentInfo = JSON.parse(response.recruitmentInfo); + var selectElement = $("#id_job_position_id"); + + // Clear existing options + selectElement.empty(); + + // Add new options + openPositions.forEach(function (position) { + var option = $("