[INITIAL COMMIT]
This commit is contained in:
326
README.md
326
README.md
@@ -1 +1,325 @@
|
||||
# horilla_
|
||||
|
||||
# **Horilla 🦍** [](http://www.gnu.org/licenses/agpl-3.0)
|
||||
Horilla is a free and open source software HRMS system.
|
||||
|
||||
## **Installation**
|
||||
____
|
||||
Horilla can be installed on your system by following the below commands.
|
||||
|
||||
You'll have to install python, django and the database you wish to use for the project as a prerequisites.
|
||||
|
||||
### **Python Installation**
|
||||
___
|
||||
|
||||
**Ubuntu**
|
||||
|
||||
Ubuntu comes with Python pre-installed, but if you need to install a specific version or if Python is not installed, you can use the terminal to install it.
|
||||
|
||||
Open the terminal and type the following command:
|
||||
```bash
|
||||
sudo apt-get install python3
|
||||
```
|
||||
This will install the latest version of Python 3.
|
||||
|
||||
To check if Python is installed correctly, type the following command:
|
||||
```bash
|
||||
python3 --version
|
||||
```
|
||||
This should output the version number of Python that you just installed.
|
||||
|
||||
**Windows**
|
||||
|
||||
To install Python on Windows, follow these steps:
|
||||
1. Download the latest version of Python from the official website: https://www.python.org/downloads/windows/ .
|
||||
2. Run the installer and select "Add Python to PATH" during the installation process.
|
||||
3. Choose the installation directory and complete the installation process.
|
||||
4. To check if Python is installed correctly, open the Command Prompt and type the following command:
|
||||
```bash
|
||||
python3 --version
|
||||
```
|
||||
This should output the version number of Python that you just installed.
|
||||
|
||||
**macOS**
|
||||
|
||||
macOS comes with Python pre-installed, but if you need to install a specific version or if Python is not installed, you can use Homebrew to install it.
|
||||
|
||||
Follow these steps:
|
||||
1. Install Homebrew by running the following command in the terminal:
|
||||
```bash
|
||||
/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"
|
||||
```
|
||||
2. Install Python by running the following command in the terminal:
|
||||
```bash
|
||||
brew install python
|
||||
```
|
||||
To check if Python is installed correctly, type the following command in the terminal:
|
||||
```bash
|
||||
python3 --version
|
||||
```
|
||||
This should output the version number of Python that you just installed.
|
||||
|
||||
Congratulations, you have successfully installed Python on Ubuntu, Windows, or macOS! You can now start using Python to build applications.
|
||||
|
||||
|
||||
### **Installing Django**
|
||||
___
|
||||
|
||||
Before installing Django, you must have Python installed on your machine.
|
||||
|
||||
To install Django, follow the following steps:
|
||||
1. Create a virtual environment:
|
||||
|
||||
It is highly recommended to create a virtual environment before installing Django.
|
||||
|
||||
A virtual environment allows you to isolate your Python environment and avoid conflicts with other Python packages that may be installed on your machine.
|
||||
|
||||
To create a virtual environment, open the terminal and navigate to the directory where you want to create the environment. Then type the following command:
|
||||
```bash
|
||||
python -m venv myenv
|
||||
```
|
||||
This will create a new virtual environment named "myenv".
|
||||
|
||||
To activate the virtual environment, type the following command:
|
||||
```bash
|
||||
source myenv/bin/activate
|
||||
```
|
||||
This will activate the virtual environment and you should see the name of the environment in the terminal prompt.
|
||||
|
||||
>Note that to activate your virtual environment on Widows, you will need to run the following code below (See this <a href="https://docs.python.org/3/library/venv.html">link</a> to fully understand the differences between platforms):
|
||||
```bash
|
||||
env/Scripts/activate.bat //In CMD
|
||||
env/Scripts/Activate.ps1 //In Powershel
|
||||
```
|
||||
2. Install Django:
|
||||
|
||||
With the virtual environment activated, you can now install Django using pip, the Python package manager. Type the following command:
|
||||
```bash
|
||||
pip install Django
|
||||
```
|
||||
This will download and install the latest stable version of Django.
|
||||
|
||||
3. Verify the installation:
|
||||
|
||||
To verify that Django is installed correctly, type the following command in the terminal:
|
||||
```bash
|
||||
python -m django --version
|
||||
```
|
||||
This should output the version number of Django that you just installed.
|
||||
|
||||
Congratulations, you have successfully installed Django on your machine!
|
||||
You can now start building web applications using Django.
|
||||
|
||||
### **Installing Horilla**
|
||||
___
|
||||
|
||||
For installing the Horilla, follow the following steps:
|
||||
1. Clone the project repository from GitHub:
|
||||
```bash
|
||||
git clone https://github.com/horilla-opensource/horilla.git
|
||||
```
|
||||
2. Install the required dependencies using pip:
|
||||
|
||||
For installing the python dependencies required for the project, run the following command by going into the project directory.
|
||||
```bash
|
||||
pip install -r requirements.txt
|
||||
```
|
||||
3. Set up the database by running the following commands:
|
||||
```bash
|
||||
python manage makemigrations
|
||||
python manage.py migrate
|
||||
```
|
||||
4. Create an admin employee account:
|
||||
```bash
|
||||
python manage.py createhorillauser
|
||||
```
|
||||
>Note: createhorillauser is a similar command to createsuperuser in Django, which creates an admin user along with a related admin employee into the database.
|
||||
|
||||
<br>
|
||||
Enter the details asked for creating the admin user for the project.
|
||||
5. Running the project
|
||||
To run the project locally, execute the following command:
|
||||
|
||||
```bash
|
||||
python manage.py runserver
|
||||
```
|
||||
If everything is configured correctly, you should be able to access your Horilla app at http://localhost:8000.
|
||||
|
||||
>Note:
|
||||
>>If you wish to run the Horilla application to any other port, you can specify the port number after the runserver command.
|
||||
|
||||
>>eg: *python manage.py runserver <port_number>*
|
||||
|
||||
>Note:
|
||||
>>By default a SQLite database will be setup for the project with demo data already loaded.
|
||||
|
||||
>>If you wish to start with a fresh database, remove the db.sqlite3 file from the project directory and run the migrate command followed by the createhorillauser command to start with a fresh database.
|
||||
|
||||
>>Or if you wish to change the database, refer the below section.
|
||||
|
||||
### **Database Setup**
|
||||
___
|
||||
|
||||
By default an SQLite database will be setup for the project, incase you wish to change the database of your choice, please use the below reference to do the same.
|
||||
|
||||
**PostgreSQL**
|
||||
|
||||
To setup postgresql database for the project, first you have to install the PostgreSQL and its python package ***psycopg2*** .
|
||||
1. Install the psycopg2 package using pip. This package is a PostgreSQL database adapter for Python.
|
||||
```bash
|
||||
pip install psycopg2
|
||||
```
|
||||
2. In the project settings file (settings.py), add the following database settings:
|
||||
```python
|
||||
DATABASES = {
|
||||
'default': {
|
||||
'ENGINE': 'django.db.backends.postgresql',
|
||||
'NAME': '<database_name>',
|
||||
'USER': '<database_user>',
|
||||
'PASSWORD': '<database_password>',
|
||||
'HOST': '<database_host>',
|
||||
'PORT': '<database_port>',
|
||||
}
|
||||
}
|
||||
```
|
||||
Replace *<database_name>, <database_user>, <database_password>, <database_host>, and <database_port>* with your PostgreSQL database settings.
|
||||
|
||||
3. Run migrations to create the necessary database tables.
|
||||
```bash
|
||||
python manage.py makemigrations
|
||||
python manage.py migrate
|
||||
```
|
||||
For more details:
|
||||
[Django PostgreSQL Database](https://docs.djangoproject.com/en/4.2/ref/databases/#postgresql-notes)
|
||||
|
||||
**MySQL**
|
||||
|
||||
To configure a MySQL database in Django, follow these steps:
|
||||
1. Install the ***mysqlclient*** package which will allow Django to interact with MySQL. You can install it using pip:
|
||||
```bash
|
||||
pip install mysqlclient
|
||||
```
|
||||
2. In the project settings file (settings.py), add the following database settings:
|
||||
```python
|
||||
DATABASES = {
|
||||
'default': {
|
||||
'ENGINE': 'django.db.backends.mysql',
|
||||
'NAME': '<database_name>',
|
||||
'USER': '<database_user>',
|
||||
'PASSWORD': '<database_password>',
|
||||
'HOST': '<database_host>',
|
||||
'PORT': '<database_port>',
|
||||
}
|
||||
}
|
||||
```
|
||||
Replace *<database_name>, <database_user>, <database_password>, <database_host>, and <database_port>* with the appropriate values for your MySQL installation.
|
||||
|
||||
|
||||
3. Run migrations to create the necessary database tables.
|
||||
```bash
|
||||
python manage.py makemigrations
|
||||
python manage.py migrate
|
||||
```
|
||||
For more details:
|
||||
[Django MySQL Database](https://docs.djangoproject.com/en/4.2/ref/databases/#mysql-notes)
|
||||
|
||||
**MariaDB**
|
||||
|
||||
To configure a MariaDB database with Django, you can follow the same steps used for MySQL database configuration as shown above.
|
||||
For more details:
|
||||
[Django MariaDB Database](https://docs.djangoproject.com/en/4.2/ref/databases/#mariadb-notes)
|
||||
|
||||
**SQLite**
|
||||
|
||||
To configure a SQLite database with Django, you can follow these steps:
|
||||
1. In the project settings file (settings.py), add the following database settings:
|
||||
```python
|
||||
DATABASES = {
|
||||
'default': {
|
||||
'ENGINE': 'django.db.backends.sqlite3',
|
||||
'NAME': BASE_DIR / 'db.sqlite3',
|
||||
}
|
||||
}
|
||||
```
|
||||
This will create a SQLite database in your project directory named db.sqlite3.
|
||||
|
||||
|
||||
|
||||
2. Run migrations to create the necessary database tables.
|
||||
```bash
|
||||
python manage.py makemigrations
|
||||
python manage.py migrate
|
||||
```
|
||||
>*Note that SQLite has some limitations compared to other databases, so you may need to consider these limitations if you have a large amount of data or a high level of concurrency in your application.*
|
||||
|
||||
For more details:
|
||||
[Django SQLite Database](https://docs.djangoproject.com/en/4.2/ref/databases/#sqlite-notes)
|
||||
|
||||
**Oracle**
|
||||
|
||||
To configure an Oracle database with Django, you can follow these steps:
|
||||
1. Install the cx_Oracle package which will allow Django to interact with Oracle. You can install it using pip:
|
||||
```bash
|
||||
pip install cx_Oracle
|
||||
```
|
||||
2. In the project settings file (settings.py), add the following database settings:
|
||||
```python
|
||||
DATABASES = {
|
||||
'default': {
|
||||
'ENGINE': 'django.db.backends.oracle',
|
||||
'NAME': '<database_name>',
|
||||
'USER': '<database_user>',
|
||||
'PASSWORD': '<database_password>',
|
||||
'HOST': '<database_host>',
|
||||
'PORT': '<database_port>',
|
||||
}
|
||||
}
|
||||
```
|
||||
Replace *<database_name>, <database_user>, <database_password>, <database_host>, and <database_port>* with the appropriate values for your Oracle installation.
|
||||
|
||||
|
||||
3. Run migrations to create the necessary database tables.
|
||||
```bash
|
||||
python manage.py makemigrations
|
||||
python manage.py migrate
|
||||
```
|
||||
>*Note that Oracle has some specific requirements for its database setup, so you may need to consult Oracle's documentation for more information on how to set up your database correctly.*
|
||||
|
||||
For more details:
|
||||
[Django Oracle Database](https://docs.djangoproject.com/en/4.2/ref/databases/#oracle-notes)
|
||||
|
||||
|
||||
### **Features**
|
||||
|
||||
- Recruitment
|
||||
- Onboarding
|
||||
- Employee
|
||||
- Attendance
|
||||
- Leave
|
||||
- Asset
|
||||
- Performance Management System
|
||||
|
||||
### **Roadmap**
|
||||
|
||||
- Payroll (On the way..Coming Soon)
|
||||
|
||||
- Calendar App
|
||||
|
||||
- Project Management
|
||||
|
||||
- More to come.....
|
||||
|
||||
___
|
||||
<br>
|
||||
|
||||
### **Laguages and Tools Used:**
|
||||
<br>
|
||||
<p align="left"> <a href="https://getbootstrap.com" target="_blank" rel="noreferrer"> <img src="https://raw.githubusercontent.com/devicons/devicon/master/icons/bootstrap/bootstrap-plain-wordmark.svg" alt="bootstrap" width="40" height="40"/> </a> <a href="https://www.chartjs.org" target="_blank" rel="noreferrer"> <img src="https://www.chartjs.org/media/logo-title.svg" alt="chartjs" width="40" height="40"/> </a> <a href="https://www.w3schools.com/css/" target="_blank" rel="noreferrer"> <img src="https://raw.githubusercontent.com/devicons/devicon/master/icons/css3/css3-original-wordmark.svg" alt="css3" width="40" height="40"/> </a> <a href="https://www.djangoproject.com/" target="_blank" rel="noreferrer"> <img src="https://cdn.worldvectorlogo.com/logos/django.svg" alt="django" width="40" height="40"/> </a> <a href="https://git-scm.com/" target="_blank" rel="noreferrer"> <img src="https://www.vectorlogo.zone/logos/git-scm/git-scm-icon.svg" alt="git" width="40" height="40"/> </a> <a href="https://www.w3.org/html/" target="_blank" rel="noreferrer"> <img src="https://raw.githubusercontent.com/devicons/devicon/master/icons/html5/html5-original-wordmark.svg" alt="html5" width="40" height="40"/> </a> <a href="https://www.linux.org/" target="_blank" rel="noreferrer"> <img src="https://raw.githubusercontent.com/devicons/devicon/master/icons/linux/linux-original.svg" alt="linux" width="40" height="40"/> </a> <a href="https://www.mysql.com/" target="_blank" rel="noreferrer"> <img src="https://raw.githubusercontent.com/devicons/devicon/master/icons/mysql/mysql-original-wordmark.svg" alt="mysql" width="40" height="40"/> </a> <a href="https://www.oracle.com/" target="_blank" rel="noreferrer"> <img src="https://raw.githubusercontent.com/devicons/devicon/master/icons/oracle/oracle-original.svg" alt="oracle" width="40" height="40"/> </a> <a href="https://www.postgresql.org" target="_blank" rel="noreferrer"> <img src="https://raw.githubusercontent.com/devicons/devicon/master/icons/postgresql/postgresql-original-wordmark.svg" alt="postgresql" width="40" height="40"/> </a> <a href="https://www.python.org" target="_blank" rel="noreferrer"> <img src="https://raw.githubusercontent.com/devicons/devicon/master/icons/python/python-original.svg" alt="python" width="40" height="40"/> </a> <a href="https://www.sqlite.org/" target="_blank" rel="noreferrer"> <img src="https://www.vectorlogo.zone/logos/sqlite/sqlite-icon.svg" alt="sqlite" width="40" height="40"/> </a> </p>
|
||||
<br>
|
||||
|
||||
___
|
||||
|
||||
### **AUTHORS**
|
||||
[Cybrosys Technologies](https://www.cybrosys.com/)
|
||||
|
||||
### **ABOUT**
|
||||
[Horilla](https://www.horilla.com/)
|
||||
BIN
TestDB_Horilla.sqlite3
Normal file
BIN
TestDB_Horilla.sqlite3
Normal file
Binary file not shown.
0
asset/__init__.py
Normal file
0
asset/__init__.py
Normal file
10
asset/admin.py
Normal file
10
asset/admin.py
Normal file
@@ -0,0 +1,10 @@
|
||||
from django.contrib import admin
|
||||
from .models import Asset,AssetCategory,AssetRequest,AssetAssignment,AssetLot
|
||||
# Register your models here.
|
||||
|
||||
|
||||
admin.site.register(Asset)
|
||||
admin.site.register(AssetRequest)
|
||||
admin.site.register(AssetCategory)
|
||||
admin.site.register(AssetAssignment)
|
||||
admin.site.register(AssetLot)
|
||||
6
asset/apps.py
Normal file
6
asset/apps.py
Normal file
@@ -0,0 +1,6 @@
|
||||
from django.apps import AppConfig
|
||||
|
||||
|
||||
class AssetConfig(AppConfig):
|
||||
default_auto_field = 'django.db.models.BigAutoField'
|
||||
name = 'asset'
|
||||
91
asset/filters.py
Normal file
91
asset/filters.py
Normal file
@@ -0,0 +1,91 @@
|
||||
from django_filters import FilterSet
|
||||
from django_filters import FilterSet, CharFilter
|
||||
import django_filters
|
||||
from .models import Asset,AssetAssignment,AssetCategory,AssetRequest,AssetLot
|
||||
from django import forms
|
||||
import uuid
|
||||
|
||||
|
||||
|
||||
class CustomFilterSet(FilterSet):
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
for field_name, field 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'})
|
||||
elif isinstance(widget,(forms.Select,)):
|
||||
filter_widget.field.widget.attrs.update({'class': 'oh-select oh-select-2',})
|
||||
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 ',})
|
||||
elif isinstance(widget,(forms.DateField)):
|
||||
filter_widget.field.widget.attrs.update({'type': 'date','class':'oh-input w-100'})
|
||||
if isinstance(field, django_filters.CharFilter):
|
||||
field.lookup_expr='icontains'
|
||||
|
||||
|
||||
class AssetExportFilter(CustomFilterSet):
|
||||
|
||||
class Meta:
|
||||
model = Asset
|
||||
fields = '__all__'
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(AssetExportFilter, self).__init__(*args, **kwargs)
|
||||
self.form.fields['asset_purchase_date'].widget.attrs.update({'type':'date'})
|
||||
|
||||
class AssetFilter(CustomFilterSet):
|
||||
class Meta:
|
||||
model = Asset
|
||||
fields = '__all__'
|
||||
def __init__(self,*args,**kwargs):
|
||||
super(AssetFilter,self).__init__(*args,**kwargs)
|
||||
for visible in self.form.visible_fields():
|
||||
visible.field.widget.attrs['id'] = str(uuid.uuid4())
|
||||
|
||||
|
||||
class CustomAssetFilter(CustomFilterSet):
|
||||
asset_id__asset_name = django_filters.CharFilter(lookup_expr='icontains')
|
||||
|
||||
class Meta:
|
||||
model = AssetAssignment
|
||||
fields =['asset_id__asset_name','asset_id__asset_status',]
|
||||
def __init__(self,*args,**kwargs):
|
||||
super(CustomAssetFilter,self).__init__(*args,**kwargs)
|
||||
for visible in self.form.visible_fields():
|
||||
visible.field.widget.attrs['id'] = str(uuid.uuid4())
|
||||
|
||||
|
||||
class AssetRequestFilter(CustomFilterSet):
|
||||
class Meta:
|
||||
model = AssetRequest
|
||||
fields = '__all__'
|
||||
def __init__(self,*args,**kwargs):
|
||||
super(AssetRequestFilter,self).__init__(*args,**kwargs)
|
||||
for visible in self.form.visible_fields():
|
||||
visible.field.widget.attrs['id'] = str(uuid.uuid4())
|
||||
|
||||
class AssetAllocationFilter(CustomFilterSet):
|
||||
class Meta:
|
||||
model = AssetAssignment
|
||||
fields = '__all__'
|
||||
def __init__(self,*args,**kwargs):
|
||||
super(AssetAllocationFilter,self).__init__(*args,**kwargs)
|
||||
for visible in self.form.visible_fields():
|
||||
visible.field.widget.attrs['id'] = str(uuid.uuid4())
|
||||
|
||||
class AssetCategoryFilter(CustomFilterSet):
|
||||
class Meta:
|
||||
model = AssetCategory
|
||||
fields = '__all__'
|
||||
def __init__(self,*args,**kwargs):
|
||||
super(AssetCategoryFilter,self).__init__(*args,**kwargs)
|
||||
for visible in self.form.visible_fields():
|
||||
visible.field.widget.attrs['id'] = str(uuid.uuid4())
|
||||
|
||||
121
asset/forms.py
Normal file
121
asset/forms.py
Normal file
@@ -0,0 +1,121 @@
|
||||
from .models import Asset, AssetRequest, AssetAssignment,AssetCategory,AssetLot
|
||||
from django.forms import ModelForm, DateInput
|
||||
from django.core.exceptions import ValidationError
|
||||
from django import forms
|
||||
import uuid
|
||||
from employee.models import Employee
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
|
||||
|
||||
def set_date_field_initial(instance):
|
||||
""" this is used to update change the date value format """
|
||||
initial = {}
|
||||
if instance.asset_purchase_date is not None:
|
||||
initial['asset_purchase_date'] = instance.asset_purchase_date.strftime('%Y-%m-%d')
|
||||
|
||||
return initial
|
||||
|
||||
|
||||
class AssetForm(ModelForm):
|
||||
|
||||
class Meta:
|
||||
model = Asset
|
||||
fields = '__all__'
|
||||
widgets = {
|
||||
'asset_name': forms.TextInput(attrs={'placeholder': 'Macbook Pro.', 'class': 'oh-input w-100'}),
|
||||
'asset_description': forms.Textarea(attrs={"type": "text","placeholder": _("A powerful laptop for business use."), "class": "oh-input oh-input--textarea oh-input--block","rows":3,"cols":40}),
|
||||
'asset_tracking_id': forms.TextInput(attrs={'placeholder': 'LPT001', 'class': 'oh-input w-100'}),
|
||||
'asset_purchase_date': forms.DateInput(attrs={"type": "date", "class": "oh-input w-100"}),
|
||||
'asset_purchase_cost':forms.NumberInput(attrs={'class': 'oh-input w-100',"placeholder": "1200.00."}),
|
||||
'asset_category_id':forms.Select(attrs={ "class":"oh-select oh-select-2 select2-hidden-accessible",},),
|
||||
'asset_status': forms.Select(attrs={"class": "oh-select oh-select--lg oh-select-no-search "}),
|
||||
'asset_lot_number_id': forms.Select(attrs={"class": "oh-select oh-select-2 select2-hidden-accessible ","placeholder":"LOT001",}),
|
||||
|
||||
}
|
||||
def __init__(self, *args, **kwargs):
|
||||
instance = kwargs.get('instance')
|
||||
if instance:
|
||||
kwargs['initial'] = set_date_field_initial(instance)
|
||||
super(AssetForm, self).__init__(*args, **kwargs)
|
||||
|
||||
self.fields['asset_category_id'].widget.attrs.update({'id':str(uuid.uuid4())})
|
||||
self.fields['asset_lot_number_id'].widget.attrs.update({'id':str(uuid.uuid4())})
|
||||
self.fields['asset_status'].widget.attrs.update({'id':str(uuid.uuid4())})
|
||||
|
||||
def clean(self):
|
||||
instance = self.instance
|
||||
if instance.pk:
|
||||
if asset_in_use := instance.assetassignment_set.filter(
|
||||
return_status__isnull=True
|
||||
):
|
||||
raise ValidationError('Asset in use you can"t change the status')
|
||||
class AssetCategoryForm(ModelForm):
|
||||
class Meta:
|
||||
model = AssetCategory
|
||||
fields = '__all__'
|
||||
widgets = {
|
||||
'asset_category_name': forms.TextInput(attrs={'placeholder': _('Computers.'), 'class': 'oh-input w-100'}),
|
||||
'asset_category_description': forms.Textarea(attrs={"type": "text", "placeholder": _("A category for all types of laptops."), "class": "oh-input oh-input--textarea oh-input--block","rows":3,"cols":40}),
|
||||
}
|
||||
|
||||
|
||||
class AssetRequestForm(ModelForm):
|
||||
|
||||
class Meta:
|
||||
model = AssetRequest
|
||||
fields = '__all__'
|
||||
widgets = {
|
||||
'requested_employee_id':forms.Select(attrs={ "class":"oh-select oh-select-2 select2-hidden-accessible",}),
|
||||
'asset_category_id':forms.Select(attrs={ "class":"oh-select oh-select-2 select2-hidden-accessible",}),
|
||||
'description': forms.Textarea(attrs={"type": "text", "id": "objective_description", "placeholder": _("Requesting a laptop for software development purposes."), "class": "oh-input oh-input--textarea oh-input--block","rows":3,"cols":40}),
|
||||
|
||||
}
|
||||
def __init__(self,*args, **kwargs):
|
||||
user = kwargs.pop('user',None)
|
||||
super(AssetRequestForm, self).__init__(*args, **kwargs,)
|
||||
if user is not None and user.has_perm('asset.add_assetrequest'):
|
||||
self.fields['requested_employee_id'].queryset = Employee.objects.all()
|
||||
self.fields['requested_employee_id'].initial = Employee.objects.filter(id=user.employee_get.id).first()
|
||||
else:
|
||||
self.fields['requested_employee_id'].queryset = Employee.objects.filter(employee_user_id = user)
|
||||
self.fields['requested_employee_id'].initial = user.employee_get
|
||||
|
||||
self.fields['asset_category_id'].widget.attrs.update({'id':str(uuid.uuid4())})
|
||||
|
||||
|
||||
|
||||
class AssetAllocationForm(ModelForm):
|
||||
|
||||
class Meta:
|
||||
model = AssetAssignment
|
||||
fields = '__all__'
|
||||
exclude=['return_date','return_condition','assigned_date']
|
||||
widgets = {
|
||||
'asset_id':forms.Select(attrs={ "class":"oh-select oh-select-2 "}),
|
||||
'assigned_to_employee_id':forms.Select(attrs={ "class":"oh-select oh-select-2 "}),
|
||||
'assigned_by_employee_id':forms.Select(attrs={ "class":"oh-select oh-select-2 ",},),
|
||||
}
|
||||
|
||||
|
||||
class AssetReturnForm(ModelForm):
|
||||
|
||||
class Meta:
|
||||
model = AssetAssignment
|
||||
fields=['return_date','return_condition','return_status']
|
||||
widgets = {
|
||||
'return_date':forms.DateInput(attrs={ "type":"date","class":"oh-input w-100","required":"true"}),
|
||||
'return_condition':forms.Textarea(attrs={ "class": "oh-input oh-input--textarea oh-input--block","rows":3,"cols":40,"placeholder":_("ohn returns the laptop. However, it has suffered minor damage.")}),
|
||||
'return_status':forms.Select(attrs={ "class":"oh-select oh-select-2","required":"true"},)
|
||||
}
|
||||
|
||||
|
||||
class AssetBatchForm(ModelForm):
|
||||
|
||||
class Meta:
|
||||
model = AssetLot
|
||||
fields='__all__'
|
||||
widgets = {
|
||||
'lot_number': forms.TextInput(attrs={'placeholder': 'A12345.', 'class': 'oh-input w-100'}),
|
||||
'lot_description': forms.Textarea(attrs={"type": "text","placeholder": _("A batch of 50 laptops, consisting of Lenovo ThinkPad T480s and Dell XPS 13."), "class": "oh-input oh-input--textarea oh-input--block","rows":3,"cols":40}),
|
||||
}
|
||||
|
||||
0
asset/migrations/__init__.py
Normal file
0
asset/migrations/__init__.py
Normal file
66
asset/models.py
Normal file
66
asset/models.py
Normal file
@@ -0,0 +1,66 @@
|
||||
from django.db import models
|
||||
from django.contrib.auth.models import User
|
||||
from employee.models import Employee
|
||||
from datetime import datetime
|
||||
import django
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
|
||||
|
||||
class AssetCategory(models.Model):
|
||||
asset_category_name = models.CharField(max_length=255,unique=True)
|
||||
asset_category_description = models.TextField()
|
||||
def __str__(self):
|
||||
return f'{self.asset_category_name}'
|
||||
|
||||
|
||||
class AssetLot(models.Model):
|
||||
lot_number = models.CharField(max_length=30,null=False,blank=False,unique=True)
|
||||
lot_description = models.TextField(null=True,blank=True)
|
||||
def __str__(self):
|
||||
return f'{self.lot_number}'
|
||||
|
||||
class Asset(models.Model):
|
||||
ASSET_STATUS = [
|
||||
('In use', _('In use')),
|
||||
('Available', _('Available')),
|
||||
('Not-Available', _('Not-Available')),
|
||||
]
|
||||
asset_name = models.CharField(max_length=255)
|
||||
asset_description = models.TextField(null=True,blank=True)
|
||||
asset_tracking_id = models.CharField(max_length=30, null=False, unique=True)
|
||||
asset_purchase_date = models.DateField()
|
||||
asset_purchase_cost = models.DecimalField(max_digits=10, decimal_places=2)
|
||||
asset_category_id = models.ForeignKey(AssetCategory, on_delete=models.CASCADE)
|
||||
asset_status = models.CharField(choices=ASSET_STATUS,default='Available',max_length=40)
|
||||
asset_lot_number_id = models.ForeignKey(AssetLot,on_delete=models.CASCADE,null=True,blank=True)
|
||||
def __str__(self):
|
||||
return f'{self.asset_name}-{self.asset_tracking_id}'
|
||||
|
||||
|
||||
class AssetAssignment(models.Model):
|
||||
STATUS = [
|
||||
('Minor damage', _('Minor damage')),
|
||||
('Major damage', _('Major damage')),
|
||||
('Healthy', _('Healthy')),
|
||||
]
|
||||
asset_id = models.ForeignKey(Asset, on_delete=models.CASCADE,limit_choices_to={'asset_status': 'Available'})
|
||||
assigned_to_employee_id = models.ForeignKey(Employee,on_delete=models.CASCADE,related_name="allocated_employeee")
|
||||
assigned_date = models.DateField(auto_now_add=True)
|
||||
assigned_by_employee_id = models.ForeignKey(Employee,on_delete=models.CASCADE,related_name='assigned_by')
|
||||
return_date = models.DateField(null=True, blank=True)
|
||||
return_condition = models.TextField(null=True, blank=True)
|
||||
return_status = models.CharField(choices=STATUS,max_length=30,null=True,blank=True)
|
||||
|
||||
|
||||
class AssetRequest(models.Model):
|
||||
STATUS = [
|
||||
('Requested', _('Requested')),
|
||||
('Approved',_('Approved')),
|
||||
('Rejected', _('Rejected')),
|
||||
]
|
||||
requested_employee_id = models.ForeignKey(Employee, on_delete=models.CASCADE,related_name='requested_employee',null=False,blank=False)
|
||||
asset_category_id = models.ForeignKey(AssetCategory, on_delete=models.CASCADE)
|
||||
asset_request_date = models.DateField(auto_now_add=True)
|
||||
description = models.TextField(null=True,blank=True)
|
||||
asset_request_status = models.CharField(max_length=30, choices=STATUS, default='Requested',null=True,blank=True)
|
||||
|
||||
6
asset/resources.py
Normal file
6
asset/resources.py
Normal file
@@ -0,0 +1,6 @@
|
||||
from import_export import resources
|
||||
from .models import Asset
|
||||
|
||||
class AssetResource(resources.ModelResource):
|
||||
class Meta:
|
||||
model = Asset
|
||||
0
asset/static/src/asset/assetList.js
Normal file
0
asset/static/src/asset/assetList.js
Normal file
45
asset/static/src/asset_category/assetCategoryView.js
Normal file
45
asset/static/src/asset_category/assetCategoryView.js
Normal file
@@ -0,0 +1,45 @@
|
||||
$(document).ready(function () {
|
||||
// asset category accordion
|
||||
$('.oh-accordion-meta__header--custom').on("click", function () {
|
||||
$(this).toggleClass('oh-accordion-meta__header--show');
|
||||
$(this).next().toggleClass('d-none');
|
||||
})
|
||||
|
||||
function updateAssetCount(assetCategoryId) {
|
||||
// used to update the count of asset in asset category
|
||||
var csrf_token = $('input[name="csrfmiddlewaretoken"]').attr('value');
|
||||
$.ajax({
|
||||
type: "POST",
|
||||
url: "asset-count-update",
|
||||
data: { 'asset_category_id': assetCategoryId, 'csrfmiddlewaretoken': csrf_token },
|
||||
dataType: "json",
|
||||
success: function (response) {
|
||||
$(`#asset-count${assetCategoryId}`).text(response);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// when created the count of the asset category
|
||||
$('.asset-create').on('click', function () {
|
||||
var assetCategoryId = $(this).attr('data-category-id').trim();
|
||||
setTimeout(function () {
|
||||
updateAssetCount(assetCategoryId);
|
||||
}, 1000);
|
||||
});
|
||||
|
||||
// search dropdown hiding
|
||||
|
||||
// Hide the select element initially
|
||||
$("select[name='type']").hide();
|
||||
|
||||
// Listen for changes to the search input field
|
||||
$("input[name='search']").on("input", function() {
|
||||
// If the input value is not empty, show the select element
|
||||
if ($(this).val().trim() !== "") {
|
||||
$("select[name='type']").show();
|
||||
} else {
|
||||
$("select[name='type']").hide();
|
||||
}
|
||||
});
|
||||
|
||||
});
|
||||
99
asset/templates/asset/asset_creation.html
Normal file
99
asset/templates/asset/asset_creation.html
Normal file
@@ -0,0 +1,99 @@
|
||||
{% load static i18n %}
|
||||
{% load i18n %}
|
||||
{% load widget_tweaks %}
|
||||
{% if asset_creation_form.asset_category_id.initial %}
|
||||
<!-- start of messages -->
|
||||
{% if messages %}
|
||||
<div class="oh-wrapper">
|
||||
{% for message in messages %}
|
||||
<div class="oh-alert-container">
|
||||
<div class="oh-alert oh-alert--animated {{message.tags}}">
|
||||
{{ message }}
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
<script>
|
||||
setTimeout(function () {
|
||||
$('.oh-modal__close').click()
|
||||
}, 1000);
|
||||
</script>
|
||||
</div>
|
||||
{% endif %}
|
||||
<!-- end of messages -->
|
||||
|
||||
<!-- checking if the category id is present -->
|
||||
<div class="oh-modal__dialog-header">
|
||||
<button type="button" class="oh-modal__close" data-dismiss="oh-modal" aria-label="Close"
|
||||
hx-get="{%url 'asset-list' id=asset_creation_form.asset_category_id.initial %}?{{pg}}"
|
||||
hx-target="#assetList{{asset_creation_form.asset_category_id.initial}}">
|
||||
<ion-icon name="close-outline"></ion-icon>
|
||||
</button>
|
||||
<span class="oh-modal__dialog-title ml-5" id="addEmployeeObjectiveModalLabel">
|
||||
<h5>{% trans "Asset Creation" %}</h5>
|
||||
</span>
|
||||
</div>
|
||||
<div class="oh-modal__dialog-body">
|
||||
<form hx-post="{%url 'asset-creation' id=asset_creation_form.asset_category_id.initial %}" hx-target="#AssetModal">
|
||||
{% csrf_token %}
|
||||
{{asset_creation_form.asset_category_id.as_hidden }}
|
||||
<div id="AssetCreationFormContainer">
|
||||
<div class="oh-input__group ">
|
||||
<label class="oh-input__label" for="objective">{% trans "Asset Name" %}</label>
|
||||
{{asset_creation_form.asset_name}}
|
||||
{{asset_creation_form.asset_name.errors}}
|
||||
</div>
|
||||
<div class="oh-input__group">
|
||||
<label class="oh-input__label" for="lastname"> {% trans "Description" %}</label>
|
||||
{{asset_creation_form.asset_description}}
|
||||
{{asset_creation_form.asset_description.errors}}
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-12 col-sm-12 col-md-4 col-lg-4">
|
||||
<div class="oh-input__group">
|
||||
<label class="oh-input__label" for="keyType">{% trans "Tracking Id" %}</label>
|
||||
{{asset_creation_form.asset_tracking_id}}
|
||||
{{asset_creation_form.asset_tracking_id.errors}}
|
||||
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-12 col-sm-12 col-md-4 col-lg-4">
|
||||
<div class="oh-input__group">
|
||||
<label class="oh-input__label" for="startDate">{% trans "Purchase Date" %}</label>
|
||||
{{asset_creation_form.asset_purchase_date |attr:"type:date" }}
|
||||
{{asset_creation_form.asset_purchase_date.errors }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-12 col-sm-12 col-md-4 col-lg-4">
|
||||
<div class="oh-input__group">
|
||||
<label class="oh-input__label" for="endDate">{% trans "Cost" %}</label>
|
||||
{{asset_creation_form.asset_purchase_cost}}
|
||||
{{asset_creation_form.asset_purchase_cost.errors}}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-12 col-sm-12 col-md-6 col-lg-6">
|
||||
<div class="oh-input__group">
|
||||
<label class="oh-input__label" for="endDate">{% trans "Status" %}</label>
|
||||
{{asset_creation_form.asset_status }}
|
||||
{{asset_creation_form.asset_status.errors }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-12 col-sm-12 col-md-6 col-lg-6">
|
||||
<div class="oh-input__group">
|
||||
<label class="oh-input__label" for="endDate">{% trans "Batch No" %}</label>
|
||||
{{asset_creation_form.asset_lot_number_id }}
|
||||
{{asset_creation_form.asset_lot_number_id.errors}}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="oh-modal__dialog-footer">
|
||||
<button type="submit" class=" oh-btn oh-btn--secondary oh-btn--shadow mt-3">
|
||||
{% trans "Save" %}
|
||||
</button>
|
||||
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
{% endif %}
|
||||
9
asset/templates/asset/asset_import.html
Normal file
9
asset/templates/asset/asset_import.html
Normal file
@@ -0,0 +1,9 @@
|
||||
{% extends 'index.html' %}
|
||||
{% load i18n %}
|
||||
{% block content %}
|
||||
<form method="post" enctype="multipart/form-data">
|
||||
{% csrf_token %}
|
||||
<input type="file" name="file">
|
||||
<button type="submit">{% trans "Import Excel file" %}</button>
|
||||
</form>
|
||||
{% endblock %}
|
||||
60
asset/templates/asset/asset_information.html
Normal file
60
asset/templates/asset/asset_information.html
Normal file
@@ -0,0 +1,60 @@
|
||||
{% load i18n %}
|
||||
<div class="oh-modal__section-head">{% trans "Asset Information" %}</div>
|
||||
<div class="row">
|
||||
<div class="col-12 col-md-12 col-lg-12">
|
||||
<div class="oh-modal__group mt-2">
|
||||
<label class="oh-modal__label oh-text--small">{% trans "Asset Name" %}</label>
|
||||
<label class="oh-modal__value">{{asset.asset_name}}</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-12 col-md-12 col-lg-12 mt-3">
|
||||
<div class="oh-modal__group">
|
||||
<label class="oh-modal__label oh-text--small">{% trans "Description" %}</label>
|
||||
<div class="oh-modal__description">
|
||||
{{asset.asset_description}}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-12 col-md-12 col-lg-6 mt-3">
|
||||
<div class="oh-modal__group">
|
||||
<label class="oh-modal__label oh-text--small">{% trans "Tracking Id" %}</label>
|
||||
<label class="oh-modal__value">{{asset.asset_tracking_id}}</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-12 col-md-12 col-lg-6 mt-3">
|
||||
<div class="oh-modal__group">
|
||||
<label class="oh-modal__label oh-text--small">{% trans "Purchase Date" %}</label>
|
||||
<label class="oh-modal__value">{{asset.asset_purchase_date}}</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-12 col-md-12 col-lg-6 mt-3">
|
||||
<div class="oh-modal__group">
|
||||
<label class="oh-modal__label oh-text--small">{% trans "Cost" %}</label>
|
||||
<label class="oh-modal__value">{{asset.asset_purchase_cost}}</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-12 col-md-12 col-lg-6 mt-3">
|
||||
<div class="oh-modal__group">
|
||||
<label class="oh-modal__label oh-text--small">{% trans "Status" %}</label>
|
||||
<label class="oh-modal__value">{{asset.get_asset_status_display}}</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-12 col-md-12 col-lg-6 mt-3">
|
||||
<div class="oh-modal__group">
|
||||
<label class="oh-modal__label oh-text--small">{% trans "Batch No" %}</label>
|
||||
<label class="oh-modal__value">{{asset.asset_lot_number_id}}</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-12 col-md-12 col-lg-6 mt-3">
|
||||
<div class="oh-modal__group">
|
||||
<label class="oh-modal__label oh-text--small">{% trans "Category" %}</label>
|
||||
<label class="oh-modal__value">{{asset.asset_category_id}}</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
172
asset/templates/asset/asset_list.html
Normal file
172
asset/templates/asset/asset_list.html
Normal file
@@ -0,0 +1,172 @@
|
||||
{% load i18n %}
|
||||
{% load static %}
|
||||
|
||||
<!-- start of messages -->
|
||||
{% if messages %}
|
||||
<div class="oh-wrapper">
|
||||
{% for message in messages %}
|
||||
<div class="oh-alert-container">
|
||||
<div class="oh-alert oh-alert--animated {{message.tags}}">
|
||||
{{ message }}
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% endif %}
|
||||
<!-- end of messages -->
|
||||
|
||||
<div class="oh-sticky-table__table" data-count="{{total_count}}">
|
||||
<div class="oh-sticky-table__thead">
|
||||
<div class="oh-sticky-table__tr">
|
||||
<div class="oh-sticky-table__th">{% trans "Asset" %}</div>
|
||||
<div class="oh-sticky-table__th">{% trans "Status" %}</div>
|
||||
<div class="oh-sticky-table__th">{% trans "Tracking Id" %}</div>
|
||||
<div class="oh-sticky-table__th">{% trans "Batch No" %}</div>
|
||||
<div class="oh-sticky-table__th"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="oh-sticky-table__tbody" id="assetPaginatorTarget">
|
||||
{% for asset in assets %}
|
||||
<div class="oh-sticky-table__tr oh-multiple-table-sort__movable" id="assetDelete{{asset.id}}">
|
||||
<div class="oh-sticky-table__sd"
|
||||
data-toggle="oh-modal-toggle"
|
||||
data-target="#assetInfoModal"
|
||||
hx-get="{%url 'asset-information' id=asset.id %}"
|
||||
hx-target="#assetInfomation">
|
||||
<div class="oh-profile oh-profile--md">
|
||||
<div class="oh-profile__avatar mr-1">
|
||||
<img src="https://ui-avatars.com/api/?name={{asset.asset_name}}&background=random" class="oh-profile__image"
|
||||
alt="Mary Magdalene" />
|
||||
</div>
|
||||
<span class="oh-profile__name oh-text--dark">{{asset.asset_name}}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="oh-sticky-table__td">{% trans asset.asset_status %} </div>
|
||||
<div class="oh-sticky-table__td">{{asset.asset_tracking_id}}</div>
|
||||
<div class="oh-sticky-table__td">{{asset.asset_lot_number_id}}</div>
|
||||
<div class="oh-sticky-table__td">
|
||||
<div class="oh-btn-group">
|
||||
|
||||
{% if asset_under == 'asset_filter' %}
|
||||
<a
|
||||
class="oh-btn oh-btn--light-bkg w-100"
|
||||
data-toggle="oh-modal-toggle"
|
||||
data-target="#AssetCategoryModal"
|
||||
hx-get="{% url 'asset-update' id=asset.id %}?asset_under=asset_filter&{{pg}}"
|
||||
title="{% trans 'Update' %}"
|
||||
hx-target="#AssetModal"
|
||||
id="oh-btn-asset-update-modal">
|
||||
<ion-icon name="create-outline" role="img" class="md hydrated" aria-label="create outline"></ion-icon>
|
||||
</a>
|
||||
<a hx-get="{%url 'asset-delete' id=asset.id %}?asset_list=asset_filter&{{pg}}"
|
||||
hx-target="#assetCategoryList"
|
||||
onclick=" return originalConfirm('Do you want to delete this asset?')"
|
||||
class="oh-btn oh-btn--danger-outline oh-btn--light-bkg w-100"
|
||||
title="Remove">
|
||||
<ion-icon name="trash-outline" role="img" class="md hydrated" aria-label="trash outline"></ion-icon>
|
||||
</a>
|
||||
{% else %}
|
||||
<a
|
||||
class="oh-btn oh-btn--light-bkg w-100 "
|
||||
data-toggle="oh-modal-toggle"
|
||||
data-target="#AssetCategoryModal"
|
||||
hx-get="{% url 'asset-update' id=asset.id %}?{{pg}}"
|
||||
title="{% trans 'Update' %}"
|
||||
hx-target="#AssetModal"
|
||||
id="oh-btn-asset-update-modal">
|
||||
<ion-icon name="create-outline" role="img" class="md hydrated" aria-label="create outline"></ion-icon>
|
||||
</a>
|
||||
<a hx-post="{%url 'asset-delete' id=asset.id %}?{{pg}}"
|
||||
hx-target="#assetList{{asset.asset_category_id.id}}"
|
||||
onclick=" return originalConfirm('Do you want to delete this asset?')"
|
||||
class="oh-btn oh-btn--danger-outline oh-btn--light-bkg w-100 asset-delete" title="{% trans 'Remove' %}"
|
||||
data-category-id="{{asset.asset_category_id.id}}">
|
||||
<ion-icon name="trash-outline" role="img" class="md hydrated" aria-label="trash outline"></ion-icon>
|
||||
</a>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- pagination start -->
|
||||
<div class="oh-pagination">
|
||||
<span class="oh-pagination__page" data-toggle="modal" data-target="#addEmployeeModal"></span>
|
||||
<nav class="oh-pagination__nav">
|
||||
<div class="oh-pagination__input-container me-3">
|
||||
<span class="oh-pagination__label me-1">{% trans "Page" %}</span>
|
||||
<input type="number" name="page" class="oh-pagination__input" value="{{assets.number }}" min="1"
|
||||
hx-get="{% url 'asset-list' id=asset_category_id %}?{{pg}}" hx-target="#assetList{{asset_category_id}}">
|
||||
<span class="oh-pagination__label">{% trans "of" %} {{ assets.paginator.num_pages }}</span>
|
||||
</div>
|
||||
<ul class="oh-pagination__items">
|
||||
{% if assets.has_previous %}
|
||||
<li class="oh-pagination__item oh-pagination__item--wide">
|
||||
<a hx-get="{% url 'asset-list' id=asset_category_id %}?{{pg}}&page=1" class='oh-pagination__link'
|
||||
hx-target="#assetList{{asset_category_id}}">{% trans "First" %}</a>
|
||||
</li>
|
||||
<li class="oh-pagination__item oh-pagination__item--wide">
|
||||
<a hx-get="{% url 'asset-list' id=asset_category_id %}?{{pg}}&page={{ assets.previous_page_number }}"
|
||||
class='oh-pagination__link' hx-target="#assetList{{asset_category_id}}">{% trans "Previous" %}</a>
|
||||
</li>
|
||||
{%endif %}
|
||||
{% if assets.has_next %}
|
||||
<li class="oh-pagination__item oh-pagination__item--wide">
|
||||
<a hx-get="{% url 'asset-list' id=asset_category_id %}?{{pg}}&page={{ assets.next_page_number }}"
|
||||
class='btn btn-outline-secondary' hx-target="#assetList{{asset_category_id}}">{% trans "Next" %}</a>
|
||||
</li>
|
||||
<li class="oh-pagination__item oh-pagination__item--wide">
|
||||
<a hx-get="{% url 'asset-list' id=asset_category_id %}?{{pg}}&page={{ assets.paginator.num_pages }}"
|
||||
hx-target="#assetList{{asset_category_id}}" class="oh-pagination__link">{% trans "Last" %}</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
</ul>
|
||||
</nav>
|
||||
</div>
|
||||
|
||||
<!-- this tag is used to identify if asset is under category or filter -->
|
||||
<p style="display: none;" id="asset_under"> {{asset_under}}</p>
|
||||
<p style="display: none;" id="asset-count-updated"> {{ asset_count }}</p>
|
||||
<p style="display: none;" id="asset-category"> {{ asset_category_id }}</p>
|
||||
|
||||
|
||||
<!-- end of pagination -->
|
||||
<script>
|
||||
$(document).ready(function () {
|
||||
// asset delete
|
||||
$('.asset-delete').on('click', function () {
|
||||
var assetCategoryId = $(this).attr('data-category-id').trim();
|
||||
setTimeout(function () {
|
||||
updateAssetCount(assetCategoryId);
|
||||
}, 1000);
|
||||
});
|
||||
|
||||
// asset pagination target change
|
||||
var asset_under = $('#asset_under').text().trim()
|
||||
// if the asset is filter the hx-target will be changed
|
||||
if (asset_under === 'asset_filter') {
|
||||
// the asset list is loaded in filter so the pagination target is
|
||||
$('.oh-pagination *[hx-target]').each(function () {
|
||||
$(this).attr('hx-target', '#assetCategoryList');
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
// asset count update
|
||||
function updateAssetCount(assetCategoryId) {
|
||||
var csrf_token = $('input[name="csrfmiddlewaretoken"]').attr('value');
|
||||
$.ajax({
|
||||
type: "POST",
|
||||
url: "asset-count-update",
|
||||
data: { 'asset_category_id': assetCategoryId, 'csrfmiddlewaretoken': csrf_token },
|
||||
dataType: "json",
|
||||
success: function (response) {
|
||||
$(`#asset-count${assetCategoryId}`).text(response);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
</script>
|
||||
24
asset/templates/asset/asset_return_form.html
Normal file
24
asset/templates/asset/asset_return_form.html
Normal file
@@ -0,0 +1,24 @@
|
||||
{% load i18n %}
|
||||
<div class="oh-modal__dialog-title">{% trans "Asset Return Form" %}</div>
|
||||
<form action="{%url 'asset-allocate-return' id=asset_id %}" method="post">
|
||||
{% csrf_token %}
|
||||
<div class="m-3">
|
||||
<div class="oh-input__group ">
|
||||
<label class="oh-input__label" for="objective">{% trans "Return Status" %}</label>
|
||||
{{asset_return_form.return_status}}
|
||||
</div>
|
||||
<div class="oh-input__group ">
|
||||
<label class="oh-input__label" for="objective">{% trans "Return Date" %}</label>
|
||||
{{asset_return_form.return_date}}
|
||||
</div>
|
||||
<div class="oh-input__group ">
|
||||
<label class="oh-input__label" for="objective">{% trans "Return Condition" %}</label>
|
||||
{{asset_return_form.return_condition}}
|
||||
</div>
|
||||
</div>
|
||||
<div class="oh-modal__dialog-footer">
|
||||
<button type="submit" class="oh-btn oh-btn--secondary oh-btn--shadow ">
|
||||
{% trans "Save" %}
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
142
asset/templates/asset/asset_update.html
Normal file
142
asset/templates/asset/asset_update.html
Normal file
@@ -0,0 +1,142 @@
|
||||
{% load static i18n %}
|
||||
{% if asset_form.instance.id %}
|
||||
<!-- start of messages -->
|
||||
{% if messages %}
|
||||
<div class="oh-wrapper">
|
||||
{% for message in messages %}
|
||||
<div class="oh-alert-container">
|
||||
<div class="oh-alert oh-alert--animated {{message.tags}}">
|
||||
{{ message }}
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
<script>
|
||||
setTimeout(function () {
|
||||
$('.oh-modal__close').click()
|
||||
}, 1000);
|
||||
</script>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% if asset_form.errors %}
|
||||
<div class="oh-wrapper">
|
||||
{% for error in asset_form.non_field_errors %}
|
||||
<div class="oh-alert-container">
|
||||
<div class="oh-alert oh-alert--animated oh-alert--danger">
|
||||
{{ error }}
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% endif %}
|
||||
<!-- end of messages -->
|
||||
|
||||
<div class="oh-modal__dialog-header">
|
||||
{% if asset_under == 'asset_filter' %}
|
||||
<button
|
||||
type="button" class="oh-modal__close "
|
||||
id="asset_update_close_btn_asset_list"
|
||||
data-dismiss="oh-modal"
|
||||
aria-label="Close"
|
||||
hx-get="{%url 'asset-list' id=0 %}?asset_list=asset_filter&{{pg}}"
|
||||
hx-target="#assetCategoryList">
|
||||
<ion-icon name="close-outline"></ion-icon>
|
||||
</button>
|
||||
{% else %}
|
||||
|
||||
<button
|
||||
type="button"
|
||||
class="oh-modal__close asset_update_close_btn"
|
||||
id="asset_update_close_btn_category"
|
||||
data-dismiss="oh-modal"
|
||||
aria-label="Close"
|
||||
hx-get="{%url 'asset-list' id=asset_form.instance.asset_category_id.id %}?{{pg}}"
|
||||
hx-target="#assetList{{asset_form.instance.asset_category_id.id}}">
|
||||
<ion-icon name="close-outline"></ion-icon>
|
||||
</button>
|
||||
{% endif %}
|
||||
<span class="oh-modal__dialog-title " id="addEmployeeObjectiveModalLabel"> {% trans "Asset Update" %}</span>
|
||||
</div>
|
||||
<div class="oh-modal__dialog-body">
|
||||
<form hx-post="{%url 'asset-update' id=asset_form.instance.id %}?{{pg}}" hx-target="#AssetModal">
|
||||
{% if asset_under == 'asset_filter' %}
|
||||
<input type="hidden" name="asset_under" value="asset_filter">
|
||||
{%endif %}
|
||||
{% csrf_token %}
|
||||
<div class="oh-modal__dialog-body">
|
||||
<section>
|
||||
<div id="ObjecitveContainer">
|
||||
<div class="my-3" id="keyResultCard">
|
||||
<div class=" " id="assetUpdateFormContainer">
|
||||
<div class="oh-input__group ">
|
||||
<label class="oh-input__label" for="objective">{% trans "Asset Name" %}</label>
|
||||
{{asset_form.asset_name}}
|
||||
{{asset_form.asset_name.errors}}
|
||||
</div>
|
||||
<div class="oh-input__group ">
|
||||
<label class="oh-input__label" for="objective">{% trans "Asset Description" %}</label>
|
||||
{{asset_form.asset_description}}
|
||||
{{asset_form.asset_description.errors}}
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-12 col-sm-12 col-md-6 col-lg-6">
|
||||
<div class="oh-input__group">
|
||||
<label class="oh-input__label" for="keyType">{% trans "Tracking Id" %}</label>
|
||||
{{asset_form.asset_tracking_id}}
|
||||
{{asset_form.asset_tracking_id.errors}}
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-12 col-sm-12 col-md-6 col-lg-6">
|
||||
<div class="oh-input__group">
|
||||
<label class="oh-input__label" for="startDate">{% trans "Category" %}</label>
|
||||
{{asset_form.asset_category_id }}
|
||||
{{asset_form.asset_category_id.errors }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-12 col-sm-12 col-md-6 col-lg-6">
|
||||
<div class="oh-input__group">
|
||||
<label class="oh-input__label" for="startDate">{% trans "Purchase Date" %}</label>
|
||||
{{asset_form.asset_purchase_date }}
|
||||
{{asset_form.asset_purchase_date.errors }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-12 col-sm-12 col-md-6 col-lg-6">
|
||||
<div class="oh-input__group">
|
||||
<label class="oh-input__label" for="endDate">{% trans "Cost" %}</label>
|
||||
{{asset_form.asset_purchase_cost}}
|
||||
{{asset_form.asset_purchase_cost.errors}}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-12 col-sm-12 col-md-6 col-lg-6">
|
||||
<div class="oh-input__group">
|
||||
<label class="oh-input__label" for="keyType">{% trans "Status" %}</label>
|
||||
{{asset_form.asset_status}}
|
||||
{{asset_form.asset_status.errors}}
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-12 col-sm-12 col-md-6 col-lg-6">
|
||||
<div class="oh-input__group">
|
||||
<label class="oh-input__label" for="keyType">{% trans "Batch No" %}</label>
|
||||
{{asset_form.asset_lot_number_id}}
|
||||
{{asset_form.asset_lot_number_id.errors}}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
<div class="oh-modal__dialog-footer">
|
||||
<button type="submit" class="oh-btn oh-btn--secondary oh-btn--shadow">
|
||||
{% trans "Save" %}
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
{% endif %}
|
||||
31
asset/templates/batch/asset_batch_number_creation.html
Normal file
31
asset/templates/batch/asset_batch_number_creation.html
Normal file
@@ -0,0 +1,31 @@
|
||||
{% load static i18n %}
|
||||
{% load i18n %}
|
||||
<div class="oh-modal__dialog-header">
|
||||
<span class="oh-modal__dialog-title" id="addEmployeeObjectiveModalLabel">
|
||||
<h5>{% trans "Batch Number" %}</h5>
|
||||
</span>
|
||||
<button type="button" class="oh-modal__close" data-dismiss="oh-modal" aria-label="Close"
|
||||
hx-get="{%url 'asset-batch-number-search'%}" hx-target=#AssetBatchList>
|
||||
<ion-icon name="close-outline"></ion-icon>
|
||||
</button>
|
||||
</div>
|
||||
<form hx-post="{%url 'asset-batch-number-creation' %}" hx-target="#AssetFormTarget">
|
||||
{% csrf_token %}
|
||||
<div class="oh-modal__dialog-body">
|
||||
<div class="oh-input__group ">
|
||||
<label class="oh-input__label" for="objective">{% trans "Batch Number" %}</label>
|
||||
{{asset_batch_form.lot_number}}
|
||||
{{asset_batch_form.lot_number.errors}}
|
||||
</div>
|
||||
<div class="oh-input__group ">
|
||||
<label class="oh-input__label" for="objective">{% trans "Description" %}</label>
|
||||
{{asset_batch_form.lot_description}}
|
||||
{{asset_batch_form.lot_description.errors}}
|
||||
</div>
|
||||
<div class="oh-modal__dialog-footer">
|
||||
<button type="submit" class="oh-btn oh-btn--secondary oh-btn--shadow ">
|
||||
{% trans "Save" %}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
79
asset/templates/batch/asset_batch_number_list.html
Normal file
79
asset/templates/batch/asset_batch_number_list.html
Normal file
@@ -0,0 +1,79 @@
|
||||
{% load i18n %}
|
||||
<div class="oh-wrapper">
|
||||
<table class="oh-table ">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>{% trans "Batch Number" %}</th>
|
||||
<th>{% trans "Description" %}</th>
|
||||
<th></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for batch_number in batch_numbers %}
|
||||
<tr draggable="false">
|
||||
<td>{{batch_number.lot_number}}</td>
|
||||
{% if batch_number.lot_description %}
|
||||
<td>{{batch_number.lot_description}}</td>
|
||||
{% else %}
|
||||
<td class="link-danger">{% trans "Batch Description is not added" %}</td>
|
||||
{% endif %}
|
||||
<td>
|
||||
<div class="oh-btn-group">
|
||||
<a class="oh-btn oh-btn--light-bkg w-100 " data-toggle="oh-modal-toggle" data-target="#AssetBatchModal"
|
||||
hx-put="{% url 'asset-batch-update' id=batch_number.id %}" hx-target="#AssetFormTarget">
|
||||
<ion-icon name="create-outline" role="img" class="md hydrated" aria-label="create outline" title="{% trans 'Update' %}">
|
||||
</ion-icon>
|
||||
</a>
|
||||
<form
|
||||
action="{% url 'asset-batch-number-delete' id=batch_number.id %}"
|
||||
onsubmit="return confirm('Do you want to delete this batch number ?')"
|
||||
method="post"
|
||||
style="display: contents">
|
||||
{% csrf_token %}
|
||||
<button class="oh-btn oh-btn--danger-outline w-100" title="{% trans 'Delete' %}">
|
||||
<ion-icon name="trash-outline" role="img" class="md hydrated" aria-label="trash outline"></ion-icon>
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<!-- pagination start -->
|
||||
<div class="oh-pagination">
|
||||
<span class="oh-pagination__page" data-toggle="modal" data-target="#addEmployeeModal"></span>
|
||||
<nav class="oh-pagination__nav">
|
||||
<div class="oh-pagination__input-container me-3">
|
||||
<span class="oh-pagination__label me-1">{% trans "Page" %}</span>
|
||||
<input type="number" name="page" class="oh-pagination__input" value="{{batch_numbers.number }}" min="1"
|
||||
hx-get="{% url 'asset-batch-number-search' %}?{{pg}}" hx-target="#AssetBatchList">
|
||||
<span class="oh-pagination__label">{% trans "of" %} {{ batch_numbers.paginator.num_pages }}</span>
|
||||
</div>
|
||||
<ul class="oh-pagination__items">
|
||||
{% if batch_numbers.has_previous %}
|
||||
<li class="oh-pagination__item oh-pagination__item--wide">
|
||||
<a hx-get="{% url 'asset-batch-number-search' %}?{{pg}}&page=1" class='oh-pagination__link'
|
||||
hx-target="#AssetBatchList">{% trans "First" %}</a>
|
||||
</li>
|
||||
<li class="oh-pagination__item oh-pagination__item--wide">
|
||||
<a hx-get="{% url 'asset-batch-number-search' %}?{{pg}}&page={{ batch_numbers.previous_page_number }}"
|
||||
class='oh-pagination__link' hx-target="#AssetBatchList">{% trans "Previous" %}</a>
|
||||
</li>
|
||||
{%endif %}
|
||||
{% if batch_numbers.has_next %}
|
||||
<li class="oh-pagination__item oh-pagination__item--wide">
|
||||
<a hx-get="{% url 'asset-batch-number-search' %}?{{pg}}&page={{ batch_numbers.next_page_number }}"
|
||||
class='btn btn-outline-secondary' hx-target="#AssetBatchList">{% trans "Next" %}</a>
|
||||
</li>
|
||||
<li class="oh-pagination__item oh-pagination__item--wide">
|
||||
<a hx-get="{% url 'asset-batch-number-search' %}?{{pg}}&page={{ batch_numbers.paginator.num_pages }}"
|
||||
hx-target="#AssetBatchList" class="oh-pagination__link">{% trans "Last" %}</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
</ul>
|
||||
</nav>
|
||||
</div>
|
||||
<!-- end of pagination -->
|
||||
</div>
|
||||
44
asset/templates/batch/asset_batch_number_update.html
Normal file
44
asset/templates/batch/asset_batch_number_update.html
Normal file
@@ -0,0 +1,44 @@
|
||||
{% load static i18n %}
|
||||
{% load i18n %}
|
||||
<!-- start of messages -->
|
||||
{% if in_use_message %}
|
||||
<div class="oh-wrapper">
|
||||
<div class="oh-alert-container">
|
||||
<div class="oh-alert oh-alert--animated oh-alert--warning">
|
||||
{{ in_use_message }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
<!-- end of messages -->
|
||||
|
||||
<div class="oh-modal__dialog-header">
|
||||
<span class="oh-modal__dialog-title" id="addEmployeeObjectiveModalLabel">{% trans "Batch Number Update" %}</span>
|
||||
<button
|
||||
type="button"
|
||||
class="oh-modal__close"
|
||||
data-dismiss="oh-modal"
|
||||
aria-label="Close"
|
||||
hx-get="{%url 'asset-batch-number-search'%}"
|
||||
hx-target=#AssetBatchList>
|
||||
<ion-icon name="close-outline"></ion-icon>
|
||||
</button>
|
||||
</div>
|
||||
<form hx-post="{%url 'asset-batch-update' id=asset_batch_update_form.instance.id %}" hx-target="#AssetFormTarget">
|
||||
{% csrf_token %}
|
||||
<div class="oh-modal__dialog-body">
|
||||
<div class="oh-input__group ">
|
||||
<label class="oh-input__label" for="objective">{% trans "Batch Number" %}</label>
|
||||
{{asset_batch_update_form.lot_number}}
|
||||
</div>
|
||||
<div class="oh-input__group ">
|
||||
<label class="oh-input__label" for="objective">{% trans "Description" %}</label>
|
||||
{{asset_batch_update_form.lot_description}}
|
||||
</div>
|
||||
</div>
|
||||
<div class="oh-modal__dialog-footer">
|
||||
<button type="submit" class="oh-btn oh-btn--secondary oh-btn--shadow ">
|
||||
{% trans "Save" %}
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
63
asset/templates/batch/asset_batch_number_view.html
Normal file
63
asset/templates/batch/asset_batch_number_view.html
Normal file
@@ -0,0 +1,63 @@
|
||||
{% extends 'index.html' %}
|
||||
{% block content %}
|
||||
{% load static i18n %}
|
||||
{% load i18n %}
|
||||
{% load widget_tweaks %}
|
||||
|
||||
<!-- start of messages -->
|
||||
{% if messages %}
|
||||
<div class="oh-wrapper">
|
||||
{% for message in messages %}
|
||||
<div class="oh-alert-container">
|
||||
<div class="oh-alert oh-alert--animated {{message.tags}}">
|
||||
{{ message }}
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% endif %}
|
||||
<!-- end of messages -->
|
||||
|
||||
<main :class="sidebarOpen ? 'oh-main__sidebar-visible' : ''">
|
||||
<section class="oh-wrapper oh-main__topbar" x-data="{searchShow: false}">
|
||||
<div class="oh-main__titlebar oh-main__titlebar--left">
|
||||
<h1 class="oh-main__titlebar-title fw-bold">{% trans "Asset Batch Number" %}</h1>
|
||||
<a class="oh-main__titlebar-search-toggle" role="button" aria-label="Toggle Search"
|
||||
@click="searchShow = !searchShow">
|
||||
<ion-icon name="search-outline" class="oh-main__titlebar-serach-icon"></ion-icon>
|
||||
</a>
|
||||
</div>
|
||||
<div class="oh-main__titlebar oh-main__titlebar--right">
|
||||
<div class="oh-input-group oh-input__search-group"
|
||||
:class="searchShow ? 'oh-input__search-group--show' : ''">
|
||||
<ion-icon name="search-outline" class="oh-input-group__icon oh-input-group__icon--left"></ion-icon>
|
||||
<input name="search" hx-get="{% url 'asset-batch-number-search' %}" hx-target="#AssetBatchList"
|
||||
hx-trigger='keyup delay:500ms' type="text" class="oh-input oh-input__icon" aria-label="Search Input"
|
||||
placeholder="{% trans 'Search' %}" />
|
||||
</div>
|
||||
<div class="oh-main__titlebar-button-container">
|
||||
<div class="oh-btn-group ml-2">
|
||||
<div>
|
||||
<a class="oh-btn oh-btn--secondary oh-btn--shadow"
|
||||
data-toggle="oh-modal-toggle"
|
||||
data-target="#AssetBatchModal"
|
||||
hx-get="{% url 'asset-batch-number-creation' %}"
|
||||
hx-target="#AssetFormTarget">
|
||||
<ion-icon class="me-2" name="add-outline"></ion-icon>{% trans "Create" %}</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
<div id="AssetBatchList">
|
||||
<!-- including asset category -->
|
||||
{% include 'batch/asset_batch_number_list.html' %}
|
||||
</div>
|
||||
</main>
|
||||
|
||||
<div class="oh-modal" id="AssetBatchModal" role="dialog" aria-labelledby="AssetBatchModal" aria-hidden="true">
|
||||
<div class="oh-modal__dialog " style="max-width: 550px;" id="AssetFormTarget">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% endblock %}
|
||||
122
asset/templates/category/asset_category.html
Normal file
122
asset/templates/category/asset_category.html
Normal file
@@ -0,0 +1,122 @@
|
||||
{% load assets_custom_filter %}
|
||||
{% load static i18n %}
|
||||
{% load i18n %}
|
||||
|
||||
<!-- start of messages -->
|
||||
{% if messages %}
|
||||
<div class="oh-wrapper">
|
||||
{% for message in messages %}
|
||||
<div class="oh-alert-container">
|
||||
<div class="oh-alert oh-alert--animated {{message.tags}}" >
|
||||
{{ message }}
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% endif %}
|
||||
<!-- end of messages -->
|
||||
<!-- new acordian -->
|
||||
|
||||
<div class="oh-card" id="assetFiltered">
|
||||
<div class="oh-accordion-meta">
|
||||
<div class="">
|
||||
{% for asset_category in asset_categorys %}
|
||||
<div
|
||||
class="oh-accordion-meta__header oh-accordion-meta__header--custom"
|
||||
data-target="#assetCategory{{asset_category.id}}"
|
||||
hx-get="{%url 'asset-list' id=asset_category.id %}"
|
||||
hx-target="#assetList{{asset_category.id}}">
|
||||
<div class="d-flex">
|
||||
<span
|
||||
class="oh-badge oh-badge--secondary oh-badge--small oh-badge--round ms-2 mr-2"
|
||||
id="asset-count{{asset_category.id}}"
|
||||
data-category-id="{{asset_category.id}}">
|
||||
{{asset_category.asset_set.count}}
|
||||
</span>
|
||||
<span class="oh-accordion-meta__title">{{asset_category}}</span>
|
||||
</div>
|
||||
|
||||
<div class="oh-accordion-meta__actions" id="assetActions">
|
||||
<div class="oh-dropdown" x-data="{open: false}" >
|
||||
<button class="oh-btn oh-stop-prop oh-accordion-meta__btn" @click="open = !open"
|
||||
@click.outside="open = false">
|
||||
{% trans "Actions" %}
|
||||
<ion-icon class="ms-2 oh-accordion-meta__btn-icon" name="caret-down-outline"></ion-icon>
|
||||
</button>
|
||||
<div class="oh-dropdown__menu oh-dropdown__menu--right" x-show="open" style="display: none;">
|
||||
<ul class="oh-dropdown__items">
|
||||
<li class="oh-dropdown__item">
|
||||
<a href="#" class="oh-dropdown__link asset-create" data-toggle="oh-modal-toggle"
|
||||
data-target="#AssetCategoryModal" hx-get="{% url 'asset-creation' id=asset_category.id %}?{{pg}}"
|
||||
hx-target="#AssetModal" data-category-id="{{asset_category.id}}">{% trans "Create" %}</a>
|
||||
</li>
|
||||
<li class="oh-dropdown__item">
|
||||
<a href="#" class="oh-dropdown__link " data-toggle="oh-modal-toggle"
|
||||
data-target="#AssetCategoryModal" hx-get="{% url 'asset-category-update' id=asset_category.id %}?{{pg}}"
|
||||
hx-target="#AssetModal">{% trans "Update" %}</a>
|
||||
</li>
|
||||
<li class="oh-dropdown__item">
|
||||
<a hx-post="{% url 'asset-category-delete' id=asset_category.id %}?{{pg}}"
|
||||
class="oh-dropdown__link oh-dropdown__link--danger "
|
||||
hx-target="#assetCategoryList"
|
||||
hx-confirm="Do you want to delete Asset Category?">{% trans "Delete" %}</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="oh-accordion-meta__body d-none" id="assetCategory{{asset_category.id}}">
|
||||
<!-- htmx asset list loading here -->
|
||||
<div class="oh-sticky-table oh-sticky-table--no-overflow mb-5" id="assetList{{asset_category.id}}">
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
|
||||
<!-- pagination start -->
|
||||
<div class="oh-pagination">
|
||||
<span class="oh-pagination__page" data-toggle="modal" data-target="#addEmployeeModal"></span>
|
||||
<nav class="oh-pagination__nav">
|
||||
<div class="oh-pagination__input-container me-3">
|
||||
<span class="oh-pagination__label me-1">{% trans "Page" %} </span>
|
||||
<input type="number" name="page" class="oh-pagination__input" value="{{asset_categorys.number }}" min="1"
|
||||
hx-get="{% url 'asset-category-view-search-filter' %}" hx-target="#assetCategoryList">
|
||||
<span class="oh-pagination__label">{% trans "of" %} {{ asset_categorys.paginator.num_pages }}</span>
|
||||
</div>
|
||||
<ul class="oh-pagination__items">
|
||||
{% if asset_categorys.has_previous %}
|
||||
<li class="oh-pagination__item oh-pagination__item--wide">
|
||||
<a hx-get="{% url 'asset-category-view-search-filter' %}?{{pg}}&page=1" class='oh-pagination__link'
|
||||
hx-target="#assetCategoryList">{% trans "First" %}</a>
|
||||
</li>
|
||||
<li class="oh-pagination__item oh-pagination__item--wide">
|
||||
<a hx-get="{% url 'asset-category-view-search-filter' %}?{{pg}}&page={{ asset_categorys.previous_page_number }}"
|
||||
class='oh-pagination__link' hx-target="#assetCategoryList">{% trans "Previous" %}</a>
|
||||
</li>
|
||||
{%endif %}
|
||||
{% if asset_categorys.has_next %}
|
||||
<li class="oh-pagination__item oh-pagination__item--wide">
|
||||
<a hx-get="{% url 'asset-category-view-search-filter' %}?{{pg}}&page={{ asset_categorys.next_page_number}}"
|
||||
class='btn btn-outline-secondary' hx-target="#assetCategoryList">{% trans "Next" %}</a>
|
||||
</li>
|
||||
<li class="oh-pagination__item oh-pagination__item--wide">
|
||||
<a hx-get="{% url 'asset-category-view-search-filter' %}?{{pg}}&page={{ asset_categorys.paginator.num_pages }}"
|
||||
hx-target="#assetCategoryList" class="oh-pagination__link">{% trans "Last" %}</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
</ul>
|
||||
</nav>
|
||||
</div>
|
||||
<!-- end of pagination -->
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<script>
|
||||
// action button seperating from the acordion
|
||||
$('#assetActions').on('click',function (e) {
|
||||
e.stopPropagation();
|
||||
});
|
||||
</script>
|
||||
|
||||
|
||||
52
asset/templates/category/asset_category_creation.html
Normal file
52
asset/templates/category/asset_category_creation.html
Normal file
@@ -0,0 +1,52 @@
|
||||
{% load static i18n %}
|
||||
<!-- start of messages -->
|
||||
{% if messages %}
|
||||
<div class="oh-wrapper">
|
||||
{% for message in messages %}
|
||||
<div class="oh-alert-container">
|
||||
<div class="oh-alert oh-alert--animated {{message.tags}}">
|
||||
{{ message }}
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
<script>
|
||||
setTimeout(function () {
|
||||
$('.oh-modal__close').click()
|
||||
}, 1000);
|
||||
</script>
|
||||
</div>
|
||||
{% endif %}
|
||||
<!-- end of messages -->
|
||||
|
||||
<div class="oh-modal__dialog-header">
|
||||
<button
|
||||
type="button"
|
||||
class="oh-modal__close"
|
||||
data-dismiss="oh-modal"
|
||||
aria-label="Close"
|
||||
hx-get="{%url 'asset-category-view-search-filter' %}"
|
||||
hx-target="#assetCategoryList">
|
||||
<ion-icon name="close-outline"></ion-icon>
|
||||
</button>
|
||||
<span class="oh-modal__dialog-title ml-5" id="addEmployeeObjectiveModalLabel">
|
||||
<h5>{% trans "Asset Category Creation" %}</h5>
|
||||
</span>
|
||||
</div>
|
||||
<div class="oh-modal__dialog-body">
|
||||
<form hx-post="{%url 'asset-category-creation' %}" hx-target="#AssetModal">
|
||||
{% csrf_token %}
|
||||
<div class="oh-input__group ">
|
||||
<label class="oh-input__label" for="objective">{% trans "Name" %}</label>
|
||||
{{asset_category_form.asset_category_name}}
|
||||
</div>
|
||||
<div class="oh-input__group ">
|
||||
<label class="oh-input__label" for="objective">{% trans "Description" %}</label>
|
||||
{{asset_category_form.asset_category_description}}
|
||||
</div>
|
||||
<div class="oh-modal__dialog-footer">
|
||||
<button type="submit" class="oh-btn oh-btn--secondary oh-btn--shadow ">
|
||||
{% trans "Save" %}
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
50
asset/templates/category/asset_category_update.html
Normal file
50
asset/templates/category/asset_category_update.html
Normal file
@@ -0,0 +1,50 @@
|
||||
{% load static i18n %}
|
||||
{% load i18n %}
|
||||
<!-- start of messages -->
|
||||
{% if messages %}
|
||||
<div class="oh-wrapper">
|
||||
{% for message in messages %}
|
||||
<div class="oh-alert-container">
|
||||
<div class="oh-alert oh-alert--animated {{message.tags}}">
|
||||
{{ message }}
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
<script>
|
||||
setTimeout(function () {
|
||||
$('.oh-modal__close').click()
|
||||
}, 1000);
|
||||
</script>
|
||||
</div>
|
||||
{% endif %}
|
||||
<!-- end of messages -->
|
||||
<!-- asset cretion form -->
|
||||
<div class="oh-modal__dialog-header">
|
||||
<button type="button" class="oh-modal__close" data-dismiss="oh-modal" aria-label="Close"
|
||||
hx-get="{%url 'asset-category-view-search-filter'%}?{{pg}}" hx-target="#assetCategoryList">
|
||||
<ion-icon name="close-outline"></ion-icon>
|
||||
</button>
|
||||
<span class="oh-modal__dialog-title ml-5" id="addEmployeeObjectiveModalLabel">
|
||||
<h5>{% trans "Asset Category Update" %}</h5>
|
||||
</span>
|
||||
</div>
|
||||
<div class="oh-modal__dialog-body">
|
||||
<form hx-post="{%url 'asset-category-update' id=asset_category_update_form.instance.id %}?{{pg}}"
|
||||
hx-target="#AssetModal">
|
||||
{% csrf_token %}
|
||||
<div class="oh-input__group ">
|
||||
<label class="oh-input__label" for="objective">{% trans "Name" %}</label>
|
||||
{{asset_category_update_form.asset_category_name}}
|
||||
</div>
|
||||
<div class="oh-input__group ">
|
||||
<label class="oh-input__label" for="objective">{% trans "Description" %}</label>
|
||||
{{asset_category_update_form.asset_category_description}}
|
||||
</div>
|
||||
|
||||
<div class="oh-modal__dialog-footer">
|
||||
<button type="submit" class="oh-btn oh-btn--secondary oh-btn--shadow ">
|
||||
{% trans "Save" %}
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
268
asset/templates/category/asset_category_view.html
Normal file
268
asset/templates/category/asset_category_view.html
Normal file
@@ -0,0 +1,268 @@
|
||||
{% extends 'index.html' %}
|
||||
{% block content %}
|
||||
{% load static i18n %}
|
||||
{% load i18n %}
|
||||
{% load widget_tweaks %}
|
||||
{% load assets_custom_filter %}
|
||||
|
||||
<!-- start of messages -->
|
||||
{% if messages %}
|
||||
<div class="oh-wrapper">
|
||||
{% for message in messages %}
|
||||
<div class="oh-alert-container">
|
||||
<div class="oh-alert oh-alert--animated oh-alert--warning">
|
||||
{{ message }}
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% endif %}
|
||||
<!-- end of messages -->
|
||||
|
||||
<main :class="sidebarOpen ? 'oh-main__sidebar-visible' : ''">
|
||||
<section class="oh-wrapper oh-main__topbar" x-data="{searchShow: false}">
|
||||
<div class="oh-main__titlebar oh-main__titlebar--left">
|
||||
<h1 class="oh-main__titlebar-title fw-bold">{% trans "Asset Category" %}</h1>
|
||||
<a class="oh-main__titlebar-search-toggle" role="button" aria-label="Toggle Search"
|
||||
@click="searchShow = !searchShow">
|
||||
<ion-icon name="search-outline" class="oh-main__titlebar-serach-icon"></ion-icon>
|
||||
</a>
|
||||
</div>
|
||||
<div class="oh-main__titlebar oh-main__titlebar--right">
|
||||
<div class="oh-input-group oh-input__search-group "
|
||||
:class="searchShow ? 'oh-input__search-group--show' : ''">
|
||||
<!-- search form start -->
|
||||
<form id="searchForm" hx-get="{% url 'asset-category-view-search-filter' %}?asset_list=asset" hx-target="#assetCategoryList">
|
||||
<ion-icon name="search-outline" class="oh-input-group__icon oh-input-group__icon--left"></ion-icon>
|
||||
<input name="search" type="text" class="oh-input oh-input__icon " aria-label="Search Input" placeholder="{% trans 'Search' %}" />
|
||||
<select size="2" name="type" class='oh-input__icon'
|
||||
onclick="document.getElementById('searchForm').dispatchEvent(new Event('submit'));"
|
||||
style="border: none;overflow: hidden; display: flex; position: absolute; z-index: 999; margin-left:8%;"
|
||||
>
|
||||
<option value="asset">Search in :Asset</option>
|
||||
<option value="category" >Search in :Asset Category</option>
|
||||
</select>
|
||||
</form>
|
||||
<!-- end of search -->
|
||||
</div>
|
||||
<div class="oh-main__titlebar-button-container">
|
||||
<a href="{% url 'asset-batch-view'%}">
|
||||
<button class="oh-btn ml-2"> <ion-icon name="list-outline" class="me-1"></ion-icon>{% trans "Batch No" %}</button>
|
||||
</a>
|
||||
<!-- import asset start -->
|
||||
<div class="oh-dropdown" x-data="{open: false}" @click.outside="open = false">
|
||||
<a href="{% url 'asset-excel' %}" onclick="return originalConfirm('Do you want to download excel file')">
|
||||
<button class="oh-btn ml-2" @click="open = !open">
|
||||
<ion-icon name="arrow-down-outline" class="me-1"></ion-icon> {% trans "Import" %}
|
||||
</button>
|
||||
</a>
|
||||
<div class="oh-dropdown__import oh-dropdown__import--right" x-show="open" style="display: none;">
|
||||
<div id="AssetImportResponse"></div>
|
||||
<form action="{%url 'asset-import' %}" enctype="multipart/form-data" method="post">
|
||||
{% csrf_token %}
|
||||
<div class="oh-dropdown__import-form">
|
||||
<label class="oh-dropdown__import-label" for="uploadFile">
|
||||
<ion-icon name="cloud-upload" class="oh-dropdown__import-form-icon"></ion-icon>
|
||||
<span class="oh-dropdown__import-form-title">{% trans "Upload a File" %}</span>
|
||||
<span class="oh-dropdown__import-form-text">{% trans "Drag and drop files here" %}</span>
|
||||
</label>
|
||||
<input type="file" name="asset_import" id="" />
|
||||
</div>
|
||||
<button type="submit"
|
||||
class="oh-btn oh-btn--small oh-btn--secondary w-100 mt-3">{% trans "Upload" %}</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
<!-- import asset end -->
|
||||
<!-- asset export start -->
|
||||
<div class="oh-dropdown" x-data="{open: false}">
|
||||
<button class="oh-btn ml-2" @click="open = !open">
|
||||
<ion-icon name="arrow-up-outline" class="me-1"></ion-icon> {% trans "Export" %}
|
||||
</button>
|
||||
<div class="oh-dropdown__menu oh-dropdown__menu--right oh-dropdown__filter p-4" x-show="open"
|
||||
@click.outside="open = false" style="display: none;">
|
||||
<form action="{% url 'asset-export-excel' %}" method="post">
|
||||
{% csrf_token %}
|
||||
<div class="oh-dropdown__filter-body">
|
||||
<div class="oh-accordion">
|
||||
<div class="oh-accordion-header ">{% trans "Asset" %}</div>
|
||||
<div class="oh-accordion-body">
|
||||
<div class="row">
|
||||
<div class="col-sm-12 col-md-12 col-lg-6">
|
||||
<div class="oh-input-group">
|
||||
<label class="oh-label">{% trans "Asset Name" %}</label>
|
||||
{{asset_export_filter.form.asset_name}}
|
||||
</div>
|
||||
<div class="oh-input-group">
|
||||
<label class="oh-label">{% trans "Status" %}</label>
|
||||
{{asset_export_filter.form.asset_status}}
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-sm-12 col-md-12 col-lg-6">
|
||||
<div class="oh-input-group">
|
||||
<label class="oh-label">{% trans "Tracking Id" %}</label>
|
||||
{{asset_export_filter.form.asset_tracking_id}}
|
||||
</div>
|
||||
<div class="oh-input-group">
|
||||
<label class="oh-label">{% trans "Purchased Date" %}</label>
|
||||
{{ asset_export_filter.form.asset_purchase_date | attr:"type:date"}}
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-sm-12 col-md-12 col-lg-6">
|
||||
<div class="oh-input-group">
|
||||
<label class="oh-label">{% trans "Category" %}</label>
|
||||
{{asset_export_filter.form.asset_category_id}}
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<div class="col-sm-12 col-md-12 col-lg-6">
|
||||
<div class="oh-input-group">
|
||||
<label class="oh-label">{% trans "Batch Number" %}</label>
|
||||
{{asset_export_filter.form.asset_lot_number_id}}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="oh-dropdown__filter-footer">
|
||||
<button class="oh-btn oh-btn--secondary oh-btn--small ">{% trans "Export" %}</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
<!-- asset export end -->
|
||||
<!-- asset filter -->
|
||||
<div class="oh-dropdown" x-data="{open: false}">
|
||||
<button class="oh-btn ml-2" @click="open = !open">
|
||||
<ion-icon name="filter" class="mr-1"></ion-icon>{% trans "Filter" %}
|
||||
</button>
|
||||
<div class="oh-dropdown__menu oh-dropdown__menu--right oh-dropdown__filter p-4" x-show="open"
|
||||
@click.outside="open = false" style="display: none;">
|
||||
<div class="oh-dropdown__filter-body">
|
||||
<div class="oh-accordion">
|
||||
<div class="oh-accordion-header">{% trans "Asset Category" %}</div>
|
||||
<div class="oh-accordion-body">
|
||||
<form hx-get="{%url 'asset-category-view-search-filter' %}" hx-target="#assetCategoryList" hx-swap="innerHTML">
|
||||
<div class="row">
|
||||
<div class="col-sm-12 col-md-12 col-lg-12">
|
||||
<div class="oh-input-group">
|
||||
<label class="oh-label">{% trans "Category Name" %}</label>
|
||||
{{asset_category_filter_form.asset_category_name}}
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-sm-12 col-md-12 col-lg-12">
|
||||
<div class="oh-input-group">
|
||||
<label class="oh-label">{% trans "Description" %}</label>
|
||||
{{asset_category_filter_form.asset_category_description}}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="oh-dropdown__filter-footer">
|
||||
<button class="oh-btn oh-btn--secondary oh-btn--small w-100">{% trans "Filter" %}</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
<div class="oh-accordion">
|
||||
<div class="oh-accordion-header">{% trans "Asset" %}</div>
|
||||
<div class="oh-accordion-body">
|
||||
<form hx-get="{%url 'asset-list' id=0 %}?asset_list='asset'" name="asset_list" hx-target="#assetCategoryList" hx-swap="innerHTML">
|
||||
<div class="row">
|
||||
<div class="col-sm-12 col-md-12 col-lg-6">
|
||||
<div class="oh-input-group">
|
||||
<label class="oh-label">{% trans "Asset Name" %}</label>
|
||||
{{asset_filter_form.asset_name}}
|
||||
</div>
|
||||
<div class="oh-input-group">
|
||||
<label class="oh-label">{% trans "Tracking Id" %}</label>
|
||||
{{asset_filter_form.asset_tracking_id}}
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<div class="col-sm-12 col-md-12 col-lg-6">
|
||||
<div class="oh-input-group">
|
||||
<label class="oh-label">{% trans "Purchase Date" %}</label>
|
||||
{{asset_filter_form.asset_purchase_date |attr:"type:date"}}
|
||||
</div>
|
||||
<div class="oh-input-group">
|
||||
<label class="oh-label">{% trans "Purchase Cost" %}</label>
|
||||
{{asset_filter_form.asset_purchase_cost}}
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-sm-12 col-md-12 col-lg-6">
|
||||
<div class="oh-input-group">
|
||||
<label class="oh-label">{% trans "Asset Batch Number" %}</label>
|
||||
{{asset_filter_form.asset_lot_number_id}}
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-sm-12 col-md-12 col-lg-6">
|
||||
<div class="oh-input-group">
|
||||
<label class="oh-label">{% trans "Category" %}</label>
|
||||
{{asset_filter_form.asset_category_id}}
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-sm-12 col-md-12 col-lg-12">
|
||||
<div class="oh-input-group">
|
||||
<label class="oh-label">{% trans "Status" %}</label>
|
||||
{{asset_filter_form.asset_status}}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="oh-dropdown__filter-footer">
|
||||
<button class="oh-btn oh-btn--secondary oh-btn--small w-100">{% trans "Filter" %}</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- asset filter end -->
|
||||
<div class="oh-btn-group ml-2">
|
||||
<div>
|
||||
<a href="#" class="oh-btn oh-btn--secondary oh-btn--shadow"
|
||||
data-toggle="oh-modal-toggle"
|
||||
data-target="#AssetCategoryModal"
|
||||
hx-get="{%url 'asset-category-creation' %}"
|
||||
hx-target="#AssetModal">
|
||||
<ion-icon class="me-2" name="add-outline"></ion-icon>
|
||||
{% trans "Create" %}
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
<div class="oh-wrapper">
|
||||
<div id="assetCategoryList">
|
||||
<!-- including asset category -->
|
||||
{% include 'category/asset_category.html' %}
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
|
||||
<div class="oh-modal" id="AssetCategoryModal" role="dialog" aria-labelledby="AssetCategoryModal" aria-hidden="true">
|
||||
<div class="oh-modal__dialog" id="AssetModal">
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="oh-modal" id="assetInfoModal" role="dialog" aria-labelledby="assetModalLabel" aria-hidden="true">
|
||||
<div class="oh-modal__dialog">
|
||||
<div class="oh-modal__dialog-header">
|
||||
<span class="oh-modal__dialog-title" id="assetModalLabel">{{asset.asset_name}}</span>
|
||||
<button class="oh-modal__close" aria-label="Close" >
|
||||
<ion-icon name="close-outline"></ion-icon>
|
||||
</button>
|
||||
</div>
|
||||
<div class="oh-modal__dialog-body">
|
||||
<section id="assetInfomation">
|
||||
</section>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<script src="{% static 'src/asset_category/assetCategoryView.js' %}"></script>
|
||||
{% endblock %}
|
||||
224
asset/templates/future_update/asset_list_view.html
Normal file
224
asset/templates/future_update/asset_list_view.html
Normal file
@@ -0,0 +1,224 @@
|
||||
{% comment %} {% extends 'index.html' %}
|
||||
{% load static i18n %}
|
||||
{% block content %}
|
||||
|
||||
<!-- start of messages -->
|
||||
{% if messages %}
|
||||
<div class="oh-wrapper">
|
||||
{% for message in messages %}
|
||||
<div class="oh-alert-container">
|
||||
<div class="oh-alert oh-alert--animated {{message.tags}}">
|
||||
{{ message }}
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% endif %}
|
||||
<!-- end of messages -->
|
||||
|
||||
<main :class="sidebarOpen ? 'oh-main__sidebar-visible' : ''">
|
||||
<section class="oh-wrapper oh-main__topbar" x-data="{searchShow: false}">
|
||||
<div class="oh-main__titlebar oh-main__titlebar--left">
|
||||
<h1 class="oh-main__titlebar-title fw-bold">{% trans "Assets" %}</h1>
|
||||
|
||||
<a class="oh-main__titlebar-search-toggle" role="button" aria-label="Toggle Search"
|
||||
@click="searchShow = !searchShow">
|
||||
<ion-icon name="search-outline" class="oh-main__titlebar-serach-icon"></ion-icon>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div class="oh-main__titlebar oh-main__titlebar--right">
|
||||
<div class="oh-input-group oh-input__search-group"
|
||||
:class="searchShow ? 'oh-input__search-group--show' : ''">
|
||||
<ion-icon name="search-outline" class="oh-input-group__icon oh-input-group__icon--left"></ion-icon>
|
||||
<input name="search" hx-get=""
|
||||
hx-target="#assetCategoryList" hx-trigger='keyup delay:500ms' type="text"
|
||||
class="oh-input oh-input__icon" aria-label="Search Input" placeholder="Search" />
|
||||
</div>
|
||||
<div class="oh-main__titlebar-button-container">
|
||||
|
||||
|
||||
|
||||
<div class="oh-dropdown" x-data="{open: false}">
|
||||
<button class="oh-btn ml-2" @click="open = !open">
|
||||
<ion-icon name="filter" class="mr-1"></ion-icon>{% trans "Filter" %}
|
||||
</button>
|
||||
<div class="oh-dropdown__menu oh-dropdown__menu--right oh-dropdown__filter p-4" x-show="open"
|
||||
@click.outside="open = false" style="display: none;">
|
||||
|
||||
|
||||
<div class="oh-dropdown__filter-body">
|
||||
|
||||
|
||||
<div class="oh-accordion">
|
||||
<div class="oh-accordion-header">{% trans "Asset" %} </div>
|
||||
<div class="oh-accordion-body">
|
||||
<form hx-get="{%url 'asset-list' id=0 %}?asset_list='asset'" name="asset_list" hx-target="#assetCategoryList" hx-swap="innerHTML">
|
||||
<div class="row">
|
||||
<div class="col-sm-12 col-md-12 col-lg-6">
|
||||
<div class="oh-input-group">
|
||||
<label class="oh-label">{% trans "Asset Name" %}</label>
|
||||
{{asset_filter_form.asset_name}}
|
||||
</div>
|
||||
<div class="oh-input-group">
|
||||
<label class="oh-label">{% trans "Tracking Id" %}</label>
|
||||
{{asset_filter_form.asset_tracking_id}}
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<div class="col-sm-12 col-md-12 col-lg-6">
|
||||
<div class="oh-input-group">
|
||||
<label class="oh-label">{% trans "Purchase Date" %}</label>
|
||||
{{asset_filter_form.asset_purchase_date}}
|
||||
</div>
|
||||
<div class="oh-input-group">
|
||||
<label class="oh-label">{% trans "Purchase Cost" %}</label>
|
||||
{{asset_filter_form.asset_purchase_cost}}
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="col-sm-12 col-md-12 col-lg-6">
|
||||
<div class="oh-input-group">
|
||||
<label class="oh-label">{% trans "Asset Lot Number" %}</label>
|
||||
{{asset_filter_form.asset_lot_number_id}}
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-sm-12 col-md-12 col-lg-6">
|
||||
<div class="oh-input-group">
|
||||
<label class="oh-label">{% trans "Category" %}</label>
|
||||
{{asset_filter_form.asset_category_id}}
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<div class="col-sm-12 col-md-12 col-lg-12">
|
||||
<div class="oh-input-group">
|
||||
<label class="oh-label">{% trans "Status" %}</label>
|
||||
{{asset_filter_form.asset_status}}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<div class="oh-dropdown__filter-footer">
|
||||
<button class="oh-btn oh-btn--secondary oh-btn--small ">{% trans "Filter" %}</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="oh-btn-group ml-2">
|
||||
<div>
|
||||
<a href="#" class="oh-btn oh-btn--secondary oh-btn--shadow" data-toggle="oh-modal-toggle"
|
||||
data-target="#AssetCategoryModal" hx-get=""
|
||||
hx-target="#AssetModal"> <ion-icon class="me-2" name="add-outline"></ion-icon>{% trans "Create" %} </a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
<div class="oh-wrapper">
|
||||
<div id="assetCategoryList">
|
||||
<!-- list -->
|
||||
|
||||
<div class="oh-sticky-table__table" data-count="{{total_count}}">
|
||||
<div class="oh-sticky-table__thead">
|
||||
<div class="oh-sticky-table__tr">
|
||||
<div class="oh-sticky-table__th">{% trans "Asset" %}</div>
|
||||
<div class="oh-sticky-table__th">{% trans "Status" %}</div>
|
||||
<div class="oh-sticky-table__th">{% trans "Tracking Id" %}</div>
|
||||
<div class="oh-sticky-table__th">{% trans "Batch No" %}</div>
|
||||
<div class="oh-sticky-table__th"></div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="oh-sticky-table__tbody" id="assetPaginatorTarget">
|
||||
{% for asset in assets %}
|
||||
|
||||
<div class="oh-sticky-table__tr oh-multiple-table-sort__movable" id="assetDelete{{asset.id}}">
|
||||
<div class="oh-sticky-table__sd" data-toggle="oh-modal-toggle" data-target="#assetInfoModal"
|
||||
hx-get="{%url 'asset-information' id=asset.id %}" hx-target="#assetInfomation">
|
||||
<div class="oh-profile oh-profile--md">
|
||||
<div class="oh-profile__avatar mr-1">
|
||||
<img src="https://ui-avatars.com/api/?name={{asset.asset_name}}&background=random" class="oh-profile__image"
|
||||
alt="Mary Magdalene" />
|
||||
</div>
|
||||
<span class="oh-profile__name oh-text--dark">{{asset.asset_name}}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="oh-sticky-table__td">{{asset.asset_status}}</div>
|
||||
<div class="oh-sticky-table__td">{{asset.asset_tracking_id}}</div>
|
||||
<div class="oh-sticky-table__td">{{asset.asset_lot_number_id}}</div>
|
||||
<div class="oh-sticky-table__td">
|
||||
<div class="oh-btn-group">
|
||||
|
||||
|
||||
<a class="oh-btn oh-btn--light-bkg w-100 " data-toggle="oh-modal-toggle" data-target="#AssetCategoryModal"
|
||||
hx-get="{% url 'asset-update' id=asset.id %}" title="{% trans 'Update' %}" hx-target="#AssetModal" id="oh-btn-asset-update-modal"> <ion-icon
|
||||
name="create-outline" role="img" class="md hydrated" aria-label="create outline"></ion-icon></a>
|
||||
<a hx-post="{%url 'asset-delete' id=asset.id %}" hx-target="#assetList{{asset.asset_category_id.id}}"
|
||||
hx-confirm="Do you want to delete this asset?"
|
||||
class="oh-btn oh-btn--danger-outline oh-btn--light-bkg w-100 asset-delete" title="{% trans 'Remove' %}" data-category-id="{{asset.asset_category_id.id}}"> <ion-icon name="trash-outline"
|
||||
role="img" class="md hydrated" aria-label="trash outline"></ion-icon></a>
|
||||
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<!-- pagination start -->
|
||||
<div class="oh-pagination">
|
||||
<span class="oh-pagination__page" data-toggle="modal" data-target="#addEmployeeModal"></span>
|
||||
|
||||
<nav class="oh-pagination__nav">
|
||||
<div class="oh-pagination__input-container me-3">
|
||||
|
||||
<span class="oh-pagination__label me-1">{% trans "Page" %}</span>
|
||||
|
||||
<input type="number" name="page" class="oh-pagination__input" value="{{assets.number }}" min="1"
|
||||
hx-get="" hx-target="">
|
||||
<span class="oh-pagination__label">{% trans "of" %} {{ assets.paginator.num_pages }}</span>
|
||||
</div>
|
||||
|
||||
<ul class="oh-pagination__items">
|
||||
|
||||
{% if assets.has_previous %}
|
||||
<li class="oh-pagination__item oh-pagination__item--wide">
|
||||
<a hx-get="" class='oh-pagination__link'
|
||||
hx-target="">{% trans "First" %}</a>
|
||||
|
||||
</li>
|
||||
<li class="oh-pagination__item oh-pagination__item--wide">
|
||||
<a hx-get=""
|
||||
class='oh-pagination__link' hx-target="">{% trans "Previous" %}</a>
|
||||
</li>
|
||||
{%endif %}
|
||||
{% if assets.has_next %}
|
||||
<li class="oh-pagination__item oh-pagination__item--wide">
|
||||
<a hx-get=""
|
||||
class='btn btn-outline-secondary' hx-target="">{% trans "Next" %}</a>
|
||||
</li>
|
||||
<li class="oh-pagination__item oh-pagination__item--wide">
|
||||
<a hx-get=""
|
||||
hx-target="" class="oh-pagination__link">{% trans "Last" %}</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
</ul>
|
||||
</nav>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
|
||||
|
||||
{% endblock %} {% endcomment %}
|
||||
@@ -0,0 +1,27 @@
|
||||
{% load i18n %}
|
||||
<h5 >{% trans "Asset Allocation" %}</h5>
|
||||
<form hx-post="{%url 'asset-allocate-creation' %}" hx-target="#asset-request-allocation-modal-target">
|
||||
{% csrf_token %}
|
||||
<div class=" m-3">
|
||||
<div class="oh-input__group ">
|
||||
<label class="oh-input__label" for="objective">{% trans "Assigned To" %}</label>
|
||||
{{asset_allocation_form.assigned_to_employee_id}}
|
||||
{{asset_allocation_form.assigned_to_employee_id.errors}}
|
||||
</div>
|
||||
<div class="oh-input__group ">
|
||||
<label class="oh-input__label" for="objective">{% trans "Asset" %}</label>
|
||||
{{asset_allocation_form.asset_id}}
|
||||
{{asset_allocation_form.asset_id.errors}}
|
||||
</div>
|
||||
<div class="oh-input__group ">
|
||||
<label class="oh-input__label" for="objective">{% trans "Assigned By" %}</label>
|
||||
{{asset_allocation_form.assigned_by_employee_id}}
|
||||
{{asset_allocation_form.assigned_by_employee_id.errors}}
|
||||
</div>
|
||||
</div>
|
||||
<div class="oh-modal__dialog-footer">
|
||||
<button type="submit" class="oh-btn oh-btn--secondary oh-btn--shadow">
|
||||
{% trans "Save" %}
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
27
asset/templates/request_allocation/asset_approve.html
Normal file
27
asset/templates/request_allocation/asset_approve.html
Normal file
@@ -0,0 +1,27 @@
|
||||
{% load i18n %}
|
||||
<h5 >{% trans "Asset Approve" %}</h5>
|
||||
<form hx-post="{%url 'asset-request-approve' id=id %}" hx-target="#asset-request-allocation-modal-target">
|
||||
{% csrf_token %}
|
||||
<div class=" m-3">
|
||||
<!-- <div class="oh-input__group ">
|
||||
<label class="oh-input__label" for="objective">{% trans "Requesting User" %}</label>
|
||||
{{asset_allocation_form.assigned_to_employee_id}}
|
||||
{{asset_allocation_form.assigned_to_employee_id.errors}}
|
||||
</div> -->
|
||||
<div class="oh-input__group ">
|
||||
<label class="oh-input__label" for="objective">{% trans "Asset" %}</label>
|
||||
{{asset_allocation_form.asset_id}}
|
||||
{{asset_allocation_form.asset_id.errors}}
|
||||
</div>
|
||||
<!-- <div class="oh-input__group ">
|
||||
<label class="oh-input__label" for="objective">{% trans "Assigned By" %}</label>
|
||||
{{asset_allocation_form.assigned_by_employee_id}}
|
||||
{{asset_allocation_form.assigned_by_employee_id.errors}}
|
||||
</div> -->
|
||||
</div>
|
||||
<div class="oh-modal__dialog-footer">
|
||||
<button type="submit" class="oh-btn oh-btn--secondary oh-btn--shadow">
|
||||
{% trans "Save" %}
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
@@ -0,0 +1,474 @@
|
||||
{% load i18n %}
|
||||
<div class="oh-tabs__contents">
|
||||
<div class="oh-tabs__content " id="tab_3">
|
||||
<!-- Sticky Table for own objective-->
|
||||
<div class="oh-sticky-table">
|
||||
<div class="oh-sticky-table__table ">
|
||||
<div class="oh-sticky-table__thead">
|
||||
<div class="oh-sticky-table__tr">
|
||||
<div class="oh-sticky-table__th">{% trans "Asset" %}</div>
|
||||
<div class="oh-sticky-table__th">{% trans "Status" %}</div>
|
||||
<div class="oh-sticky-table__th"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="oh-sticky-table__tbody">
|
||||
<div id="assetRequestAllocationTarget"></div>
|
||||
{% for asset in assets %}
|
||||
<!-- asset request looping -->
|
||||
<div class="oh-sticky-table__tr" draggable="true">
|
||||
<div class="oh-sticky-table__sd">
|
||||
<div class="oh-profile oh-profile--md">
|
||||
<div class="oh-profile__avatar mr-1">
|
||||
<img src="https://ui-avatars.com/api/?name={{asset.asset_id.asset_name}}&background=random" class="oh-profile__image"
|
||||
alt="Mary Magdalene" />
|
||||
</div>
|
||||
<span class="oh-profile__name oh-text--dark">{{asset.asset_id.asset_name}}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="oh-sticky-table__td">
|
||||
<span class="oh-dot oh-dot--small me-1 oh-dot--color oh-dot--warning"></span>
|
||||
<span class="link-warning">
|
||||
In use
|
||||
</span>
|
||||
</div>
|
||||
<div class="oh-sticky-table__td">
|
||||
<button href="#" class="oh-btn oh-btn--secondary" role="button" data-toggle="oh-modal-toggle"
|
||||
data-target="#asset-request-allocation-modal" hx-get="{%url 'asset-allocate-return' id=asset.asset_id.id %}" hx-target="#asset-request-allocation-modal-target"><ion-icon name="return-down-back-sharp"></ion-icon>{% trans "Return" %}</button>
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- End of Sticky Table -->
|
||||
|
||||
<!-- pagination start -->
|
||||
<div class="oh-pagination">
|
||||
<span class="oh-pagination__page" data-toggle="modal" data-target="#addEmployeeModal"></span>
|
||||
<nav class="oh-pagination__nav">
|
||||
<div class="oh-pagination__input-container me-3">
|
||||
<span class="oh-pagination__label me-1">{% trans "Page" %}</span>
|
||||
<input type="number" name="page" class="oh-pagination__input" value="{{assets.number }}" min="1"
|
||||
hx-get="{% url 'asset-request-allocation-view-search-filter' %}?{{pg}}" hx-target="#asset_request_allocation_list">
|
||||
<span class="oh-pagination__label">{% trans "of" %} {{ assets.paginator.num_pages }}</span>
|
||||
</div>
|
||||
<ul class="oh-pagination__items">
|
||||
{% if assets.has_previous %}
|
||||
<li class="oh-pagination__item oh-pagination__item--wide">
|
||||
<a hx-get="{% url 'asset-request-allocation-view-search-filter' %}?{{pg}}&page=1" class='oh-pagination__link'
|
||||
hx-target="#asset_request_allocation_list">{% trans "First" %}</a>
|
||||
</li>
|
||||
<li class="oh-pagination__item oh-pagination__item--wide">
|
||||
<a hx-get="{% url 'asset-request-allocation-view-search-filter' %}?{{pg}}&page={{ assets.previous_page_number }}"
|
||||
class='oh-pagination__link' hx-target="#asset_request_allocation_list">{% trans "Previous" %}</a>
|
||||
</li>
|
||||
{%endif %}
|
||||
{% if assets.has_next %}
|
||||
<li class="oh-pagination__item oh-pagination__item--wide">
|
||||
<a hx-get="{% url 'asset-request-allocation-view-search-filter' %}?{{pg}}&page={{ assets.next_page_number }}"
|
||||
class='btn btn-outline-secondary' hx-target="#asset_request_allocation_list">{% trans "Next" %}</a>
|
||||
</li>
|
||||
<li class="oh-pagination__item oh-pagination__item--wide">
|
||||
<a hx-get="{% url 'asset-request-allocation-view-search-filter' %}?{{pg}}&page={{ assets.paginator.num_pages }}"
|
||||
hx-target="#asset_request_allocation_list" class="oh-pagination__link">{% trans "Last" %}</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
</ul>
|
||||
</nav>
|
||||
</div>
|
||||
<!-- end of pagination -->
|
||||
|
||||
</div>
|
||||
|
||||
<div class="oh-tabs__content " id="tab_1">
|
||||
<!-- Sticky Table for own objective-->
|
||||
<div class="oh-sticky-table">
|
||||
<div class="oh-sticky-table__table ">
|
||||
<div class="oh-sticky-table__thead">
|
||||
<div class="oh-sticky-table__tr">
|
||||
<div class="oh-sticky-table__th">{% trans "Request User" %}</div>
|
||||
<div class="oh-sticky-table__th">{% trans "Asset Category" %}</div>
|
||||
<div class="oh-sticky-table__th">{% trans "Request Date" %}</div>
|
||||
<div class="oh-sticky-table__th">{% trans "Status" %}</div>
|
||||
<div class="oh-sticky-table__th"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="oh-sticky-table__tbody">
|
||||
<div id="assetRequestAllocationTarget"></div>
|
||||
{% for asset_request in asset_requests %}
|
||||
<!-- asset request looping -->
|
||||
<div class="oh-sticky-table__tr" draggable="true">
|
||||
<div class="oh-sticky-table__sd" data-toggle="oh-modal-toggle" data-target="#requestStatus{{asset_request.id}}" >
|
||||
<div class="oh-profile oh-profile--md">
|
||||
<div class="oh-profile__avatar mr-1">
|
||||
<img src="https://ui-avatars.com/api/?name=Magdalene&background=random" class="oh-profile__image"
|
||||
alt="Mary Magdalene" />
|
||||
</div>
|
||||
<span class="oh-profile__name oh-text--dark">{{asset_request.requested_employee_id}}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="oh-sticky-table__td">{{asset_request.asset_category_id}}</div>
|
||||
<div class="oh-sticky-table__td">{{ asset_request.asset_request_date }}</div>
|
||||
<div class="oh-sticky-table__td">
|
||||
<div class="d-flex align-items-center">
|
||||
<span
|
||||
class="oh-dot oh-dot--small me-1 oh-dot--color
|
||||
{% if asset_request.asset_request_status == 'Approved' %}
|
||||
oh-dot--success
|
||||
{% elif asset_request.asset_request_status == 'Rejected' %}
|
||||
oh-dot--danger
|
||||
{% elif asset_request.asset_request_status == 'Requested' %}
|
||||
oh-dot--info
|
||||
{% endif %}
|
||||
"
|
||||
></span><span class="
|
||||
{% if asset_request.asset_request_status == 'Approved' %}
|
||||
link-success
|
||||
{% elif asset_request.asset_request_status == 'Rejected' %}
|
||||
link-danger
|
||||
{% elif asset_request.asset_request_status == 'Requested' %}
|
||||
link-info
|
||||
{% endif %}
|
||||
">{% trans asset_request.asset_request_status %}</span>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
{% if perms.asset.add_assetassignment %}
|
||||
{% if asset_request.asset_request_status == 'Requested' %}
|
||||
<div class="oh-sticky-table__td">
|
||||
<div class="oh-btn-group" >
|
||||
<a class="oh-btn oh-btn--success" role="button" data-toggle="oh-modal-toggle"
|
||||
data-target="#asset-request-allocation-modal" hx-get="{%url 'asset-request-approve' id=asset_request.id %}" hx-target="#asset-request-allocation-modal-target" >
|
||||
<ion-icon name="checkmark-outline"></ion-icon>
|
||||
{% trans "Approve" %}
|
||||
</a>
|
||||
<form action="{% url 'asset-request-reject' id=asset_request.id %}" method="post">
|
||||
{% csrf_token %}
|
||||
<button class="oh-btn oh-btn--danger"><ion-icon
|
||||
name="close-outline"></ion-icon>{% trans "Reject" %}</button>
|
||||
</form>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
<!-- Asset request Status -->
|
||||
<div
|
||||
class="oh-modal"
|
||||
id="requestStatus{{asset_request.id}}"
|
||||
role="dialog"
|
||||
aria-labelledby="tableTimeOffModal"
|
||||
aria-hidden="true"
|
||||
>
|
||||
<div class="oh-modal__dialog oh-modal__dialog--timeoff oh-timeoff-modal">
|
||||
<div class="oh-modal__dialog-header">
|
||||
|
||||
<button class="oh-modal__close" aria-label="Close">
|
||||
<ion-icon name="close-outline"></ion-icon>
|
||||
</button>
|
||||
</div>
|
||||
<div class="oh-modal__dialog-body oh-timeoff-modal__body pb-2">
|
||||
<div class="oh-timeoff-modal__profile-content">
|
||||
|
||||
<div class="oh-timeoff-modal__profile-info">
|
||||
<span class="oh-timeoff-modal__user fw-bold">{{asset_request.requested_employee_id}}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="oh-timeoff-modal__stats-container">
|
||||
<div class="oh-timeoff-modal__stat">
|
||||
<span class="oh-timeoff-modal__stat-title">{% trans "Requested Date" %}</span>
|
||||
<span class="oh-timeoff-modal__stat-count">{{asset_request.asset_request_date}}</span>
|
||||
</div>
|
||||
<div class="oh-timeoff-modal__stat">
|
||||
<span class="oh-timeoff-modal__stat-title">{% trans "Category" %}</span>
|
||||
<span class="oh-timeoff-modal__stat-count">{{asset_request.asset_category_id}}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="oh-timeoff-modal__stats mt-3 w-100">
|
||||
<div class="oh-timeoff-modal__stat">
|
||||
<span class="oh-timeoff-modal__stat-title">{% trans "Request Description" %}</span>
|
||||
<div class="oh-timeoff-modal__stat-description ">
|
||||
{{asset_request.description}}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="oh-modal__dialog-footer ">
|
||||
{% if perms.asset.add_assetassignment %}
|
||||
{% if asset_request.asset_request_status == 'Requested' %}
|
||||
<a class="oh-btn oh-btn--success " role="button" data-toggle="oh-modal-toggle"
|
||||
data-target="#asset-request-allocation-modal" hx-get="{%url 'asset-request-approve' id=asset_request.id %}" hx-target="#asset-request-allocation-modal-target" title="{% trans 'Approve' %}">
|
||||
<ion-icon name="checkmark-outline"></ion-icon>
|
||||
</a>
|
||||
<form action="{% url 'asset-request-reject' id=asset_request.id %}" method="post" class="">
|
||||
{% csrf_token %}
|
||||
<button class="oh-btn oh-btn--danger " title="{% trans 'Reject' %}"><ion-icon
|
||||
name="close-outline"></ion-icon></button>
|
||||
</form>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- End asset return status -->
|
||||
{% endfor %}
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- End of Sticky Table -->
|
||||
|
||||
<!-- pagination start -->
|
||||
<div class="oh-pagination">
|
||||
<span class="oh-pagination__page" data-toggle="modal" data-target="#addEmployeeModal"></span>
|
||||
<nav class="oh-pagination__nav">
|
||||
<div class="oh-pagination__input-container me-3">
|
||||
<span class="oh-pagination__label me-1">{% trans "Page" %}</span>
|
||||
<input type="number" name="page" class="oh-pagination__input" value="{{asset_requests.number }}" min="1"
|
||||
hx-get="{% url 'asset-request-allocation-view-search-filter' %}?{{pg}}" hx-target="#asset_request_allocation_list">
|
||||
<span class="oh-pagination__label">{% trans "of" %} {{ asset_requests.paginator.num_pages }}</span>
|
||||
</div>
|
||||
<ul class="oh-pagination__items">
|
||||
{% if asset_requests.has_previous %}
|
||||
<li class="oh-pagination__item oh-pagination__item--wide">
|
||||
<a hx-get="{% url 'asset-request-allocation-view-search-filter' %}?{{pg}}&page=1" class='oh-pagination__link'
|
||||
hx-target="#asset_request_allocation_list">{% trans "First" %}</a>
|
||||
</li>
|
||||
<li class="oh-pagination__item oh-pagination__item--wide">
|
||||
<a hx-get="{% url 'asset-request-allocation-view-search-filter' %}?{{pg}}&page={{ asset_requests.previous_page_number }}"
|
||||
class='oh-pagination__link' hx-target="#asset_request_allocation_list">{% trans "Previous" %}</a>
|
||||
</li>
|
||||
{%endif %}
|
||||
{% if asset_requests.has_next %}
|
||||
<li class="oh-pagination__item oh-pagination__item--wide">
|
||||
<a hx-get="{% url 'asset-request-allocation-view-search-filter' %}?{{pg}}&page={{ asset_requests.next_page_number }}"
|
||||
class='btn btn-outline-secondary' hx-target="#asset_request_allocation_list">{% trans "Next" %}</a>
|
||||
</li>
|
||||
<li class="oh-pagination__item oh-pagination__item--wide">
|
||||
<a hx-get="{% url 'asset-request-allocation-view-search-filter' %}?{{pg}}&page={{ asset_requests.paginator.num_pages }}"
|
||||
hx-target="#asset_request_allocation_list" class="oh-pagination__link">{% trans "Last" %}</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
</ul>
|
||||
</nav>
|
||||
</div>
|
||||
<!-- end of pagination -->
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
<div class="oh-tabs__content" id="tab_2">
|
||||
<!-- stivky table for all objectives -->
|
||||
<div class="oh-sticky-table">
|
||||
<div class="oh-sticky-table__table">
|
||||
<div class="oh-sticky-table__thead">
|
||||
<div class="oh-sticky-table__tr">
|
||||
<div class="oh-sticky-table__th">{% trans "Allocated User" %}</div>
|
||||
<div class="oh-sticky-table__th">{% trans "Asset" %}</div>
|
||||
<div class="oh-sticky-table__th">{% trans "Assigned Date" %}</div>
|
||||
<div class="oh-sticky-table__th">{% trans "Return Date" %}</div>
|
||||
<div class="oh-sticky-table__th"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="oh-sticky-table__tbody">
|
||||
{% for asset_allocation in asset_allocations %}
|
||||
<div class="oh-sticky-table__tr" draggable="true">
|
||||
<div class="oh-sticky-table__sd" data-toggle="oh-modal-toggle" data-target="#returnStatus{{asset_allocation.id}}">
|
||||
<div class="oh-profile oh-profile--md">
|
||||
<div class="oh-profile__avatar mr-1">
|
||||
<img src="https://ui-avatars.com/api/?name=OV+Vijayan&background=random" class="oh-profile__image"
|
||||
alt="O.V. Vijayan" />
|
||||
</div>
|
||||
<span class="oh-profile__name oh-text--dark">{{asset_allocation.assigned_to_employee_id}}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="oh-sticky-table__td" data-toggle="oh-modal-toggle" data-target="#returnStatus{{asset_allocation.id}}">{{asset_allocation.asset_id}}</div>
|
||||
<div class="oh-sticky-table__td" data-toggle="oh-modal-toggle" data-target="#returnStatus{{asset_allocation.id}}">{{asset_allocation.assigned_date}}</div>
|
||||
{% if asset_allocation.return_date %}
|
||||
<div class="oh-sticky-table__td" data-toggle="oh-modal-toggle" data-target="#returnStatus{{asset_allocation.id}}">{{asset_allocation.return_date}}</div>
|
||||
{% else %}
|
||||
<div class="oh-sticky-table__td" data-toggle="oh-modal-toggle" data-target="#returnStatus{{asset_allocation.id}}">
|
||||
<span class="oh-dot oh-dot--small me-1 oh-dot--color oh-dot--warning"></span>
|
||||
<span class="link-warning">
|
||||
In use
|
||||
</span></div>
|
||||
{% endif %}
|
||||
{% if not asset_allocation.return_status %}
|
||||
<div class="oh-sticky-table__td" >
|
||||
<button href="#" class="oh-btn oh-btn--secondary" role="button" data-toggle="oh-modal-toggle"
|
||||
data-target="#asset-request-allocation-modal" hx-get="{%url 'asset-allocate-return' id=asset_allocation.asset_id.id %}" hx-target="#asset-request-allocation-modal-target"><ion-icon name="return-down-back-sharp"></ion-icon>{% trans "Return" %}</button>
|
||||
</div>
|
||||
{% else %}
|
||||
<div class="oh-sticky-table__td" >
|
||||
<div class="d-flex align-items-center">
|
||||
<span class="oh-dot oh-dot--small me-1 oh-dot--color oh-dot--info"></span>
|
||||
<span class="link-primary">
|
||||
{% trans "Returned" %}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
<!-- Asset Return Status -->
|
||||
<div
|
||||
class="oh-modal"
|
||||
id="returnStatus{{asset_allocation.id}}"
|
||||
role="dialog"
|
||||
aria-labelledby="tableTimeOffModal"
|
||||
aria-hidden="true"
|
||||
>
|
||||
<div class="oh-modal__dialog oh-modal__dialog--timeoff oh-timeoff-modal">
|
||||
<div class="oh-modal__dialog-header">
|
||||
|
||||
<button class="oh-modal__close" aria-label="Close">
|
||||
<ion-icon name="close-outline"></ion-icon>
|
||||
</button>
|
||||
</div>
|
||||
<div class="oh-modal__dialog-body oh-timeoff-modal__body pb-2">
|
||||
<div class="oh-timeoff-modal__profile-content">
|
||||
|
||||
<div class="oh-timeoff-modal__profile-info">
|
||||
<span class="oh-timeoff-modal__user fw-bold">{{asset_allocation.assigned_to_employee_id}}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="oh-timeoff-modal__stats-container">
|
||||
<div class="oh-timeoff-modal__stat">
|
||||
<span class="oh-timeoff-modal__stat-title">Returned Status </span>
|
||||
<span class="oh-timeoff-modal__stat-count">{{asset_allocation.return_status}}</span>
|
||||
</div>
|
||||
<div class="oh-timeoff-modal__stat">
|
||||
<span class="oh-timeoff-modal__stat-title">Allocated User</span>
|
||||
<span class="oh-timeoff-modal__stat-count">{{asset_allocation.assigned_by_employee_id}}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="oh-timeoff-modal__stats-container mt-3">
|
||||
<div class="oh-timeoff-modal__stat">
|
||||
<span class="oh-timeoff-modal__stat-title">Allocated Date </span>
|
||||
<span class="oh-timeoff-modal__stat-count">{{asset_allocation.assigned_date}}</span>
|
||||
</div>
|
||||
<div class="oh-timeoff-modal__stat">
|
||||
<span class="oh-timeoff-modal__stat-title">Returned Date </span>
|
||||
<span class="oh-timeoff-modal__stat-count">{{asset_allocation.return_date}}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="oh-timeoff-modal__stats-container mt-3">
|
||||
<div class="oh-timeoff-modal__stat w-100">
|
||||
<span class="oh-timeoff-modal__stat-title">Asset</span>
|
||||
<span class="oh-timeoff-modal__stat-count">{{asset_allocation.asset_id}}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="oh-timeoff-modal__stats mt-3 w-100">
|
||||
<div class="oh-timeoff-modal__stat">
|
||||
<span class="oh-timeoff-modal__stat-title">Return Description</span>
|
||||
<div class="oh-timeoff-modal__stat-description ">
|
||||
{{asset_allocation.return_condition}}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="oh-modal__dialog-footer ">
|
||||
{% if not asset_allocation.return_status %}
|
||||
<button
|
||||
class="oh-btn oh-btn--secondary w-100"
|
||||
role="button"
|
||||
data-toggle="oh-modal-toggle"
|
||||
data-target="#asset-request-allocation-modal"
|
||||
hx-get="{%url 'asset-allocate-return' id=asset_allocation.asset_id.id %}"
|
||||
hx-target="#asset-request-allocation-modal-target">
|
||||
<ion-icon name="return-down-back-sharp"></ion-icon>{% trans "Return" %}</button>
|
||||
{%endif %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- End asset return status -->
|
||||
{% endfor %}
|
||||
|
||||
</div>
|
||||
</div>
|
||||
<!-- end of sticky table -->
|
||||
<!-- asset return form start -->
|
||||
<div class="oh-modal" id="AssetReturnModal" role="dialog" aria-labelledby="AssetReturnModal"
|
||||
aria-hidden="true">
|
||||
<div class="oh-modal__dialog" >
|
||||
<div class="oh-modal__dialog-header">
|
||||
<span class="oh-modal__dialog-title" id="addEmployeeObjectiveModalLabel"> {% trans "Asset Return" %}</span>
|
||||
<button type="button" class="oh-modal__close" aria-label="Close">
|
||||
<ion-icon name="close-outline"></ion-icon>
|
||||
</button>
|
||||
</div>
|
||||
<div class="oh-modal__dialog-body" id="assetReturnForm">
|
||||
</div>
|
||||
</div>
|
||||
<!-- end asset return form start -->
|
||||
</div>
|
||||
<!-- pagination start -->
|
||||
<div class="oh-pagination">
|
||||
<span class="oh-pagination__page" data-toggle="modal" data-target="#addEmployeeModal"></span>
|
||||
<nav class="oh-pagination__nav">
|
||||
<div class="oh-pagination__input-container me-3">
|
||||
<span class="oh-pagination__label me-1">{% trans "Page" %}</span>
|
||||
<input type="number" name="page" class="oh-pagination__input" value="{{asset_allocations.number }}" min="1"
|
||||
hx-get="{% url 'asset-request-allocation-view-search-filter' %}?{{pg}}" hx-target="#asset_request_allocation_list">
|
||||
<span class="oh-pagination__label">{% trans "of" %} {{ asset_allocations.paginator.num_pages }}</span>
|
||||
</div>
|
||||
<ul class="oh-pagination__items">
|
||||
{% if asset_allocations.has_previous %}
|
||||
<li class="oh-pagination__item oh-pagination__item--wide">
|
||||
<a hx-get="{% url 'asset-request-allocation-view-search-filter' %}?{{pg}}&page=1" class='oh-pagination__link'
|
||||
hx-target="#asset_request_allocation_list">{% trans "First" %}</a>
|
||||
</li>
|
||||
<li class="oh-pagination__item oh-pagination__item--wide">
|
||||
<a hx-get="{% url 'asset-request-allocation-view-search-filter' %}?{{pg}}&page={{ asset_allocations.previous_page_number }}"
|
||||
class='oh-pagination__link' hx-target="#asset_request_allocation_list">{% trans "Previous" %}</a>
|
||||
</li>
|
||||
{%endif %}
|
||||
{% if asset_allocations.has_next %}
|
||||
<li class="oh-pagination__item oh-pagination__item--wide">
|
||||
<a hx-get="{% url 'asset-request-allocation-view-search-filter' %}?{{pg}}&page={{ asset_allocations.next_page_number }}"
|
||||
class='btn btn-outline-secondary' hx-target="#asset_request_allocation_list">{% trans "Next" %}</a>
|
||||
</li>
|
||||
<li class="oh-pagination__item oh-pagination__item--wide">
|
||||
<a hx-get="{% url 'asset-request-allocation-view-search-filter' %}?{{pg}}&page={{ asset_allocations.paginator.num_pages }}"
|
||||
hx-target="#asset_request_allocation_list" class="oh-pagination__link">{% trans "Last" %}</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
</ul>
|
||||
</nav>
|
||||
</div>
|
||||
<!-- end of pagination -->
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
<script>
|
||||
$(document).ready(function () {
|
||||
var activeTab = localStorage.getItem('activeTabPms')
|
||||
if (activeTab != null) {
|
||||
var tab = $(`[data-target="${activeTab}"]`)
|
||||
var tabContent = $(activeTab)
|
||||
$(tab).attr('class', 'oh-tabs__tab oh-tabs__tab--active');
|
||||
$(tabContent).attr('class', 'oh-tabs__content oh-tabs__content--active');
|
||||
}
|
||||
else {
|
||||
$('[data-target="#tab_1"]').attr('class', 'oh-tabs__tab oh-tabs__tab--active');
|
||||
$('#tab_1').attr('class', 'oh-tabs__content oh-tabs__content--active');
|
||||
}
|
||||
$('.oh-tabs__tab').click(function (e) {
|
||||
var activeTab = $(this).attr('data-target');
|
||||
localStorage.setItem('activeTabPms', activeTab)
|
||||
});
|
||||
});
|
||||
</script>
|
||||
@@ -0,0 +1,228 @@
|
||||
{% extends 'index.html' %} {% block content %}
|
||||
{% load static i18n %}
|
||||
{% load i18n %}
|
||||
{% load mathfilters %}
|
||||
{% load widget_tweaks %}
|
||||
|
||||
<main :class="sidebarOpen ? 'oh-main__sidebar-visible' : ''">
|
||||
<section class="oh-wrapper oh-main__topbar" x-data="{searchShow: false}">
|
||||
<div class="oh-main__titlebar oh-main__titlebar--left">
|
||||
<h1 class="oh-main__titlebar-title fw-bold">{% trans "Asset" %}</h1>
|
||||
|
||||
<a class="oh-main__titlebar-search-toggle" role="button" aria-label="Toggle Search"
|
||||
@click="searchShow = !searchShow">
|
||||
<ion-icon name="search-outline" class="oh-main__titlebar-serach-icon"></ion-icon>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div class="oh-main__titlebar oh-main__titlebar--right">
|
||||
<div class="oh-input-group oh-input__search-group"
|
||||
:class="searchShow ? 'oh-input__search-group--show' : ''">
|
||||
<ion-icon name="search-outline" class="oh-input-group__icon oh-input-group__icon--left"></ion-icon>
|
||||
<input name="search" hx-get="{% url 'asset-request-allocation-view-search-filter' %}"
|
||||
hx-target="#asset_request_allocation_list" hx-trigger='keyup delay:500ms' type="text"
|
||||
class="oh-input oh-input__icon" aria-label="Search Input" placeholder="{% trans 'Search' %}" />
|
||||
</div>
|
||||
<div class="oh-main__titlebar-button-container">
|
||||
|
||||
<div class="oh-dropdown" x-data="{open: false}">
|
||||
<button class="oh-btn ml-2" @click="open = !open">
|
||||
<ion-icon name="filter" class="mr-1"></ion-icon>{% trans "Filter" %}
|
||||
</button>
|
||||
<div class="oh-dropdown__menu oh-dropdown__menu--right oh-dropdown__filter p-4" x-show="open"
|
||||
@click.outside="open = false" style="display: none;">
|
||||
<form hx-get="{% url 'asset-request-allocation-view-search-filter' %}"
|
||||
hx-target="#asset_request_allocation_list">
|
||||
|
||||
<div class="oh-dropdown__filter-body">
|
||||
|
||||
<div class="oh-accordion">
|
||||
<div class="oh-accordion-header">{% trans "Asset" %}</div>
|
||||
<div class="oh-accordion-body">
|
||||
<div class="row">
|
||||
<div class="col-sm-12 col-md-12 col-lg-12">
|
||||
<div class="oh-input-group">
|
||||
<label class="oh-label">{% trans "Asset Name" %}</label>
|
||||
{{assets_filter_form.asset_id__asset_name}}
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-sm-12 col-md-12 col-lg-12">
|
||||
<div class="oh-input-group">
|
||||
<label class="oh-label">{% trans "Status" %}</label>
|
||||
{{assets_filter_form.asset_id__asset_status}}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="oh-accordion">
|
||||
<div class="oh-accordion-header">{% trans "Asset Request" %}</div>
|
||||
<div class="oh-accordion-body">
|
||||
<div class="row">
|
||||
<div class="col-sm-12 col-md-12 col-lg-6">
|
||||
<div class="oh-input-group">
|
||||
<label class="oh-label">{% trans "Requested Employee" %}</label>
|
||||
{{asset_request_filter_form.requested_employee_id}}
|
||||
</div>
|
||||
<div class="oh-input-group">
|
||||
<label class="oh-label">{% trans "Asset Category" %}</label>
|
||||
{{asset_request_filter_form.asset_category_id}}
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-sm-12 col-md-12 col-lg-6">
|
||||
<div class="oh-input-group">
|
||||
<label class="oh-label">{% trans "Asset Request Date" %}</label>
|
||||
{{ asset_request_filter_form.asset_request_date|attr:"type:date" }}
|
||||
</div>
|
||||
<div class="oh-input-group">
|
||||
<label class="oh-label">{% trans "Status" %}</label>
|
||||
{{asset_request_filter_form.asset_request_status}}
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-sm-12 col-md-12 col-lg-12">
|
||||
<div class="oh-input-group">
|
||||
<label class="oh-label">{% trans "Description" %}</label>
|
||||
{{ asset_request_filter_form.description| attr:"class:oh-input oh-input--textarea oh-input--block" }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="oh-accordion">
|
||||
<div class="oh-accordion-header">{% trans "Asset Allocation" %}</div>
|
||||
<div class="oh-accordion-body">
|
||||
<div class="row">
|
||||
<div class="col-sm-12 col-md-12 col-lg-6">
|
||||
<div class="oh-input-group">
|
||||
<label class="oh-label">{% trans "Allocated User" %}</label>
|
||||
{{asset_allocation_filter_form.assigned_to_employee_id}}
|
||||
</div>
|
||||
<div class="oh-input-group">
|
||||
<label class="oh-label">{% trans "Asset" %}</label>
|
||||
{{asset_allocation_filter_form.asset_id}}
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-sm-12 col-md-12 col-lg-6">
|
||||
<div class="oh-input-group">
|
||||
<label class="oh-label">{% trans "Asset Allocated Date" %}</label>
|
||||
{{ asset_allocation_filter_form.assigned_date | attr:"type:date" }}
|
||||
</div>
|
||||
<div class="oh-input-group">
|
||||
<label class="oh-label">{% trans "Status" %}</label>
|
||||
{{asset_allocation_filter_form.return_status}}
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-sm-12 col-md-12 col-lg-6">
|
||||
<div class="oh-input-group">
|
||||
<label class="oh-label">{% trans "Return Date" %}</label>
|
||||
{{ asset_allocation_filter_form.assigned_date | attr:"type:date" }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-sm-12 col-md-12 col-lg-6">
|
||||
<div class="oh-input-group">
|
||||
<label class="oh-label">{% trans "Allocated By" %}</label>
|
||||
{{asset_allocation_filter_form.assigned_by_employee_id}}
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-sm-12 col-md-12 col-lg-12">
|
||||
<div class="oh-input-group">
|
||||
<label class="oh-label">{% trans "Return Condition" %}</label>
|
||||
{{ asset_allocation_filter_form.return_condition| attr:"class:oh-input oh-input--textarea oh-input--block" }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="oh-dropdown__filter-footer">
|
||||
<button class="oh-btn oh-btn--secondary oh-btn--small w-100">{% trans "Filter" %}</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<div class="oh-wrapper">
|
||||
<div class="oh-tabs">
|
||||
<ul class="oh-tabs__tablist">
|
||||
<li class="oh-tabs__tab " data-target="#tab_3">
|
||||
{% trans "Asset" %}
|
||||
|
||||
</li>
|
||||
<li class="oh-tabs__tab " data-target="#tab_1">
|
||||
{% trans "Asset Request" %}
|
||||
<a href="#" class="link-danger oh-btn oh-btn--secondary-outline" role="" data-toggle="oh-modal-toggle"
|
||||
data-target="#asset-request-allocation-modal" hx-get="{%url 'asset-request-creation'%}" hx-target="#asset-request-allocation-modal-target">
|
||||
<ion-icon name="add-outline" role="img" class="md hydrated" aria-label="add outline"></ion-icon>
|
||||
</a>
|
||||
</li>
|
||||
{% if perms.asset.view_assetassignment %}
|
||||
<li class="oh-tabs__tab" data-target="#tab_2">
|
||||
{% trans "Asset Allocation" %}
|
||||
<a href="#" class="oh-btn oh-btn--secondary-outline" role="button" data-toggle="oh-modal-toggle"
|
||||
data-target="#asset-request-allocation-modal" hx-get="{%url 'asset-allocate-creation' %}" hx-target="#asset-request-allocation-modal-target">
|
||||
<ion-icon name="add-outline" role="img" class="md hydrated" aria-label="add outline"></ion-icon>
|
||||
</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
</ul>
|
||||
<div id="asset_request_allocation_list">
|
||||
{% include 'request_allocation/asset_request_allocation_list.html' %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</main>
|
||||
|
||||
<!-- asset request and allocation modal start -->
|
||||
<div class="oh-modal" id="asset-request-allocation-modal" role="dialog" aria-labelledby="AssetRequestModal" aria-hidden="true">
|
||||
|
||||
<div class="oh-modal__dialog " style="max-width:550px">
|
||||
<div class="oh-modal__dialog-header">
|
||||
<button type="button" class="oh-modal__close" aria-label="Close">
|
||||
<ion-icon name="close-outline"></ion-icon>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- htmx form -->
|
||||
<div class="oh-modal__dialog-body" id="asset-request-allocation-modal-target">
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- end of asset request modal start -->
|
||||
<!--
|
||||
|
||||
<script>
|
||||
$(document).on('htmx:load','#asset-request-allocation-modal-target',function () {
|
||||
{% include "select2.js" %}
|
||||
// select2 for modal
|
||||
var selectEl = $('#asset-request-allocation-modal-target').find('.oh-select')
|
||||
selectEl.select2()
|
||||
|
||||
});
|
||||
$(document).on('htmx:load','#asset_request_allocation_list',function () {
|
||||
// modal js after pagination
|
||||
$("[data-toggle='oh-modal-toggle']").on('click', function () {
|
||||
|
||||
let clickedEl = $(this).closest('[data-toggle = "oh-modal-toggle"]');
|
||||
if (clickedEl != null) {
|
||||
const targetEl = clickedEl.data('target');
|
||||
$(targetEl).addClass('oh-modal--show');
|
||||
}
|
||||
});
|
||||
|
||||
$('.oh-modal__close').on('click', function () {
|
||||
$('.oh-modal--show').removeClass('oh-modal--show');
|
||||
});
|
||||
$('.oh-accordion-meta__header').on('click',function(){
|
||||
|
||||
})
|
||||
|
||||
});
|
||||
</script> -->
|
||||
|
||||
|
||||
{% endblock content %}
|
||||
@@ -0,0 +1,38 @@
|
||||
{% load i18n %}
|
||||
<h5 >{% trans "Asset Request" %}</h5>
|
||||
{% if asset_request_form.errors %}
|
||||
<div class="oh-wrapper">
|
||||
{% for error in asset_request_form.non_field_errors %}
|
||||
<div class="oh-alert-container">
|
||||
<div class="oh-alert oh-alert--animated oh-alert--danger">
|
||||
{{ error }}
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% endif %}
|
||||
<form hx-post="{%url 'asset-request-creation' %}" hx-target="#asset-request-allocation-modal-target">
|
||||
{% csrf_token %}
|
||||
<div class="m-3">
|
||||
<div class="oh-input__group ">
|
||||
<label class="oh-input__label" for="objective">{% trans "Requesting User" %}</label>
|
||||
{{asset_request_form.requested_employee_id}}
|
||||
{{asset_request_form.requested_employee_id.errors}}
|
||||
</div>
|
||||
<div class="oh-input__group ">
|
||||
<label class="oh-input__label" for="objective">{% trans "Asset Category" %}</label>
|
||||
{{asset_request_form.asset_category_id}}
|
||||
{{asset_request_form.asset_category_id.errors}}
|
||||
</div>
|
||||
<div class="oh-input__group ">
|
||||
<label class="oh-input__label" for="objective">{% trans "Description" %}</label>
|
||||
{{asset_request_form.description}}
|
||||
{{asset_request_form.description.errors}}
|
||||
</div>
|
||||
</div>
|
||||
<div class="oh-modal__dialog-footer">
|
||||
<button type="submit" class="oh-btn oh-btn--secondary oh-btn--shadow ">
|
||||
{% trans "Save" %}
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
0
asset/templatetags/__init__.py
Normal file
0
asset/templatetags/__init__.py
Normal file
7
asset/templatetags/assets_custom_filter.py
Normal file
7
asset/templatetags/assets_custom_filter.py
Normal file
@@ -0,0 +1,7 @@
|
||||
|
||||
from django import template
|
||||
register = template.Library()
|
||||
|
||||
@register.filter(name='get_item')
|
||||
def get_item(dictionary, key):
|
||||
return dictionary.get(key)
|
||||
3
asset/tests.py
Normal file
3
asset/tests.py
Normal file
@@ -0,0 +1,3 @@
|
||||
from django.test import TestCase
|
||||
|
||||
# Create your tests here.
|
||||
42
asset/urls.py
Normal file
42
asset/urls.py
Normal file
@@ -0,0 +1,42 @@
|
||||
from django.urls import path
|
||||
from django import views
|
||||
from . import views
|
||||
|
||||
|
||||
urlpatterns = [
|
||||
|
||||
path('asset-creation/<int:id>/', views.asset_creation,name='asset-creation'),
|
||||
path('asset-list/<int:id>', views.asset_list,name='asset-list'),
|
||||
path('asset-update/<int:id>/', views.asset_update,name='asset-update'),
|
||||
path('asset-delete/<int:id>/', views.asset_delete,name='asset-delete'),
|
||||
path('asset-information/<int:id>/', views.asset_information,name='asset-information'),
|
||||
|
||||
path('asset-category-view', views.asset_category_view,name='asset-category-view'),
|
||||
path('asset-category-view-search-filter', views.asset_category_view_search_filter,name='asset-category-view-search-filter'),
|
||||
path('asset-category-creation', views.asset_category_creation,name='asset-category-creation'),
|
||||
path('asset-category-update/<int:id>', views.asset_category_update,name='asset-category-update'),
|
||||
path('asset-category-delete/<int:id>', views.asset_category_delete,name='asset-category-delete'),
|
||||
|
||||
path('asset-request-creation', views.asset_request_creation,name='asset-request-creation'),
|
||||
path('asset-request-allocation-view', views.asset_request_alloaction_view,name='asset-request-allocation-view'),
|
||||
path('asset-request-allocation-view-search-filter', views.asset_request_alloaction_view_search_filter,name='asset-request-allocation-view-search-filter'),
|
||||
path('asset-request-approve/<int:id>/', views.asset_request_approve,name='asset-request-approve'),
|
||||
path('asset-request-reject/<int:id>/', views.asset_request_reject,name='asset-request-reject'),
|
||||
|
||||
path('asset-allocate-creation', views.asset_allocate_creation,name='asset-allocate-creation'),
|
||||
path('asset-allocate-return/<int:id>/',views.asset_allocate_return,name='asset-allocate-return'),
|
||||
path('asset-excel', views.asset_excel, name='asset-excel'),
|
||||
|
||||
path('asset-import', views.asset_import, name='asset-import'),
|
||||
path('asset-export-excel', views.asset_export_excel, name='asset-export-excel'),
|
||||
|
||||
path('asset-batch-number-creation', views.asset_batch_number_creation, name='asset-batch-number-creation'),
|
||||
path('asset-batch-view', views.asset_batch_view, name='asset-batch-view'),
|
||||
path('asset-batch-number-search', views.asset_batch_number_search, name='asset-batch-number-search'),
|
||||
path('asset-batch-update/<int:id>', views.asset_batch_update, name='asset-batch-update'),
|
||||
path('asset-batch-number-delete/<int:id>', views.asset_batch_number_delete, name='asset-batch-number-delete'),
|
||||
|
||||
path('asset-count-update', views.asset_count_update, name='asset-count-update'),
|
||||
|
||||
|
||||
]
|
||||
875
asset/views.py
Normal file
875
asset/views.py
Normal file
@@ -0,0 +1,875 @@
|
||||
from django.shortcuts import render, redirect
|
||||
from .forms import AssetBatchForm, AssetForm, AssetRequestForm, AssetAllocationForm, AssetCategoryForm, AssetReturnForm
|
||||
from .models import Asset, AssetRequest, AssetAssignment, AssetCategory, AssetLot
|
||||
from django.http import HttpResponse, HttpResponseRedirect
|
||||
import pandas as pd
|
||||
from .filters import AssetExportFilter, AssetFilter
|
||||
from django.contrib import messages
|
||||
from django.core.paginator import Paginator
|
||||
from .filters import AssetAllocationFilter, AssetExportFilter, AssetRequestFilter, CustomAssetFilter, AssetCategoryFilter
|
||||
from horilla.decorators import login_required,hx_request_required
|
||||
from horilla.decorators import permission_required
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
from notifications.signals import notify
|
||||
from employee.models import Employee
|
||||
|
||||
@login_required
|
||||
@hx_request_required
|
||||
@permission_required('asset.add_asset')
|
||||
def asset_creation(request, id):
|
||||
"""
|
||||
View function for creating a new asset object.
|
||||
Args:
|
||||
request (HttpRequest): A Django HttpRequest object that contains information about the current request.
|
||||
id (int): An integer representing the ID of the asset category for which the asset is being created.
|
||||
|
||||
Returns:
|
||||
If the request method is 'POST' and the form is valid, the function saves the new asset object to the database
|
||||
and redirects to the asset creation page with a success message.
|
||||
If the form is not valid, the function returns the asset creation page with the form containing the invalid data.
|
||||
If the request method is not 'POST', the function renders the asset creation page with the form initialized with
|
||||
the ID of the asset category for which the asset is being created.
|
||||
Raises:
|
||||
None
|
||||
"""
|
||||
initial_data = {'asset_category_id': id}
|
||||
form = AssetForm(initial=initial_data)
|
||||
context = {'asset_creation_form': form}
|
||||
if request.method == 'POST':
|
||||
form = AssetForm(request.POST, initial=initial_data)
|
||||
if form.is_valid():
|
||||
form.save()
|
||||
messages.success(request, _('Asset created successfully'))
|
||||
return redirect('asset-creation', id=id)
|
||||
else:
|
||||
context['asset_creation_form'] = form
|
||||
return render(request, 'asset/asset_creation.html', context)
|
||||
|
||||
|
||||
@login_required
|
||||
@hx_request_required
|
||||
@permission_required('asset.delete_asset')
|
||||
def asset_update(request, id):
|
||||
"""
|
||||
Updates an asset with the given ID.
|
||||
If the request method is GET, it displays the form to update the asset. If the
|
||||
request method is POST and the form is valid, it updates the asset and
|
||||
redirects to the asset list view for the asset's category.
|
||||
Args:
|
||||
- request: the HTTP request object
|
||||
- id (int): the ID of the asset to be updated
|
||||
Returns:
|
||||
- If the request method is GET, the rendered 'asset_update.html' template
|
||||
with the form to update the asset.
|
||||
- If the request method is POST and the form is valid, a redirect to the asset
|
||||
list view for the asset's category.
|
||||
"""
|
||||
if request.method == 'GET':
|
||||
# modal form get
|
||||
asset_under = request.GET.get('asset_under')
|
||||
elif request.method == 'POST':
|
||||
# modal form post
|
||||
asset_under = request.POST.get('asset_under')
|
||||
|
||||
if not asset_under:
|
||||
# if asset there is no asset_under data that means the request is form the category list
|
||||
asset_under = 'asset_category'
|
||||
instance = Asset.objects.get(id=id)
|
||||
asset_form = AssetForm(instance=instance)
|
||||
previous_data = request.environ['QUERY_STRING']
|
||||
context = {'asset_form': asset_form,
|
||||
'asset_under':asset_under,
|
||||
'pg':previous_data}
|
||||
if request.method == 'POST':
|
||||
asset_form = AssetForm(request.POST, instance=instance)
|
||||
if asset_form.is_valid():
|
||||
asset_form.save()
|
||||
messages.success(request,_('Asset Updated'))
|
||||
context['asset_form'] = asset_form
|
||||
return render(request, 'asset/asset_update.html', context)
|
||||
|
||||
|
||||
@login_required
|
||||
@hx_request_required
|
||||
def asset_information(request, id):
|
||||
"""
|
||||
Display information about a specific Asset object.
|
||||
Args:
|
||||
request: the HTTP request object
|
||||
id (int): the ID of the Asset object to retrieve
|
||||
Returns:
|
||||
A rendered HTML template displaying the information about the requested Asset object.
|
||||
"""
|
||||
|
||||
asset = Asset.objects.get(id=id)
|
||||
context = {'asset': asset}
|
||||
return render(request, 'asset/asset_information.html', context)
|
||||
|
||||
|
||||
@login_required
|
||||
@permission_required('asset.delete_asset')
|
||||
def asset_delete(request, id):
|
||||
"""Delete the asset with the given id.
|
||||
If the asset is currently in use, display an info message and redirect to the asset list.
|
||||
Otherwise, delete the asset and display a success message.
|
||||
Args:
|
||||
request: HttpRequest object representing the current request.
|
||||
id: int representing the id of the asset to be deleted.
|
||||
Returns:
|
||||
If the asset is currently in use or the asset list filter is applied, render the asset list template
|
||||
with the corresponding context.
|
||||
Otherwise, redirect to the asset list view for the asset category of the deleted asset.
|
||||
"""
|
||||
|
||||
asset = Asset.objects.get(id=id)
|
||||
status = asset.asset_status
|
||||
asset_list_filter = request.GET.get('asset_list')
|
||||
asset_allocation = AssetAssignment.objects.filter(asset_id=asset).first()
|
||||
if asset_list_filter :
|
||||
# if the asset deleted is from the filterd list of asset
|
||||
asset_under = "asset_filter"
|
||||
assets = Asset.objects.all()
|
||||
previous_data = request.environ['QUERY_STRING']
|
||||
asset_filtered = AssetFilter(request.GET, queryset=assets)
|
||||
asset_list = asset_filtered.qs
|
||||
paginator = Paginator(asset_list, 20)
|
||||
page_number = request.GET.get('page')
|
||||
page_obj = paginator.get_page(page_number)
|
||||
context = { "assets": page_obj,
|
||||
"pg": previous_data,
|
||||
"asset_category_id": asset.asset_category_id.id,
|
||||
"asset_under":asset_under
|
||||
}
|
||||
if status =='In use':
|
||||
messages.info(request,_('Asset is in use'))
|
||||
return render(request, 'asset/asset_list.html', context)
|
||||
elif asset_allocation:
|
||||
# if this asset is used in any allocation
|
||||
messages.error(request,_('Asset is used in allocation!.'))
|
||||
return render(request, 'asset/asset_list.html', context)
|
||||
|
||||
asset.delete()
|
||||
messages.success(request,_('Asset deleted successfully'))
|
||||
return render(request, 'asset/asset_list.html', context)
|
||||
else:
|
||||
# if the asset is deleted under the category
|
||||
if status =='In use':
|
||||
#if asset under the category
|
||||
messages.info(request,_('Asset is in use'))
|
||||
return redirect('asset-list',id=asset.asset_category_id.id)
|
||||
elif asset_allocation:
|
||||
# if this asset is used in any allocation
|
||||
messages.error(request,_('Asset is used in allocation!.'))
|
||||
return redirect('asset-list',id=asset.asset_category_id.id)
|
||||
asset.delete()
|
||||
messages.success(request,_('Asset deleted successfully'))
|
||||
return redirect('asset-list',id=asset.asset_category_id.id)
|
||||
|
||||
|
||||
@login_required
|
||||
@hx_request_required
|
||||
def asset_list(request, id):
|
||||
"""
|
||||
View function is used as asset list inside a category and also in filterd asset list
|
||||
Args:
|
||||
request (HttpRequest): A Django HttpRequest object that contains information about the current request.
|
||||
id (int): An integer representing the id of the asset category to list assets for.
|
||||
Returns:
|
||||
A rendered HTML template that displays a paginated list of assets in the given asset category.
|
||||
Raises:
|
||||
None
|
||||
"""
|
||||
asset_list_filter = request.GET.get('asset_list')
|
||||
context = {}
|
||||
if asset_list_filter:
|
||||
# if the data is present means that it is for asset filtered list
|
||||
query = request.GET.get('query')
|
||||
asset_under = "asset_filter"
|
||||
if query:
|
||||
assets_in_category = Asset.objects.filter(asset_name__icontains = query)
|
||||
else:
|
||||
assets_in_category = Asset.objects.all()
|
||||
else:
|
||||
# if the data is not present means that it is for asset category list
|
||||
asset_under = "asset_category"
|
||||
asset_category = AssetCategory.objects.get(id=id)
|
||||
assets_in_category = Asset.objects.filter(asset_category_id=asset_category)
|
||||
|
||||
previous_data = request.environ['QUERY_STRING']
|
||||
asset_filtered = AssetFilter(request.GET, queryset=assets_in_category)
|
||||
asset_list = asset_filtered.qs
|
||||
# Change 20 to the desired number of items per page
|
||||
paginator = Paginator(asset_list, 20)
|
||||
page_number = request.GET.get('page')
|
||||
page_obj = paginator.get_page(page_number)
|
||||
|
||||
context = {"assets": page_obj,
|
||||
"pg": previous_data,
|
||||
"asset_category_id": id,
|
||||
"asset_under": asset_under,
|
||||
"asset_count": len(assets_in_category) or None}
|
||||
return render(request, 'asset/asset_list.html', context)
|
||||
|
||||
|
||||
|
||||
@login_required
|
||||
@hx_request_required
|
||||
@permission_required('asset.add_assetcategory')
|
||||
def asset_category_creation(request):
|
||||
"""
|
||||
Allow a user to create a new AssetCategory object using a form.
|
||||
Args:
|
||||
request: the HTTP request object
|
||||
Returns:
|
||||
A rendered HTML template displaying the AssetCategory creation form.
|
||||
"""
|
||||
asset_category_form = AssetCategoryForm()
|
||||
context = {"asset_category_form": asset_category_form}
|
||||
|
||||
if request.method == 'POST':
|
||||
asset_category_form = AssetCategoryForm(request.POST)
|
||||
if asset_category_form.is_valid():
|
||||
asset_category_form.save()
|
||||
messages.success(request, _('Asset category created successfully'))
|
||||
else:
|
||||
context['asset_category_form'] = asset_category_form
|
||||
return render(request, 'category/asset_category_creation.html', context)
|
||||
|
||||
|
||||
@login_required
|
||||
@hx_request_required
|
||||
@permission_required('asset.change_assetcategory')
|
||||
def asset_category_update(request, id):
|
||||
"""
|
||||
This view is used to update an existing asset category.
|
||||
Args:
|
||||
request: HttpRequest object.
|
||||
id: int value representing the id of the asset category to update.
|
||||
Returns:
|
||||
Rendered HTML template.
|
||||
"""
|
||||
|
||||
previous_data = request.environ['QUERY_STRING']
|
||||
asset_category = AssetCategory.objects.get(id=id)
|
||||
asset_category_form = AssetCategoryForm(instance=asset_category)
|
||||
context = {
|
||||
'asset_category_update_form': asset_category_form,
|
||||
'pg': previous_data
|
||||
}
|
||||
if request.method == 'POST':
|
||||
asset_category_form = AssetCategoryForm(request.POST, instance=asset_category)
|
||||
if asset_category_form.is_valid():
|
||||
asset_category_form.save()
|
||||
messages.success(request, _('Asset category updated successfully'))
|
||||
else:
|
||||
context['asset_category_form'] = asset_category_form
|
||||
|
||||
return render(request, 'category/asset_category_update.html', context)
|
||||
|
||||
|
||||
@permission_required('asset.delete_assetcategory')
|
||||
def asset_category_delete(request, id):
|
||||
"""
|
||||
Deletes an asset category and redirects to the asset category view.
|
||||
Args:
|
||||
request (HttpRequest): The HTTP request object.
|
||||
id (int): The ID of the asset category to be deleted.
|
||||
Returns:
|
||||
HttpResponseRedirect: A redirect to the asset category view.
|
||||
Raises:
|
||||
None.
|
||||
"""
|
||||
asset_category = AssetCategory.objects.get(id=id)
|
||||
asset_status = Asset.objects.filter(asset_category_id=asset_category).filter(asset_status='In use')
|
||||
|
||||
if asset_status:
|
||||
messages.info(request, _('There are assets in use in the %(asset_category)s category.') % {'asset_category':asset_category})
|
||||
return redirect(asset_category_view_search_filter)
|
||||
|
||||
asset_category.delete()
|
||||
messages.success(request, _('Asset Category Deleted'))
|
||||
return redirect(asset_category_view_search_filter)
|
||||
|
||||
|
||||
def filter_pagination_asset_category(request):
|
||||
search = request.GET.get('search')
|
||||
if search is None:
|
||||
search = ""
|
||||
|
||||
previous_data = request.environ['QUERY_STRING']
|
||||
asset_category_queryset = AssetCategory.objects.all().filter(asset_category_name__icontains=search)
|
||||
asset_category_filtered = AssetCategoryFilter(request.GET, queryset=asset_category_queryset)
|
||||
asset_export_filter = AssetExportFilter(request.GET, queryset=Asset.objects.all())
|
||||
asset_category_paginator = Paginator(asset_category_filtered.qs, 20)
|
||||
page_number = request.GET.get('page')
|
||||
asset_categorys = asset_category_paginator.get_page(page_number)
|
||||
|
||||
asset_creation_form = AssetForm()
|
||||
asset_category_form = AssetCategoryForm()
|
||||
asset_filter_form = AssetFilter()
|
||||
return {
|
||||
"asset_creation_form": asset_creation_form,
|
||||
"asset_category_form": asset_category_form,
|
||||
"asset_export_filter": asset_export_filter,
|
||||
"asset_categorys": asset_categorys,
|
||||
|
||||
"asset_category_filter_form": asset_category_filtered.form,
|
||||
"asset_filter_form": asset_filter_form.form,
|
||||
"pg": previous_data,
|
||||
}
|
||||
|
||||
|
||||
@login_required
|
||||
@permission_required('asset.view_assetcategory')
|
||||
def asset_category_view(request):
|
||||
"""
|
||||
View function for rendering a paginated list of asset categories.
|
||||
Args:
|
||||
request (HttpRequest): A Django HttpRequest object that contains information about the current request.
|
||||
Returns:
|
||||
A rendered HTML template that displays a paginated list of asset categories.
|
||||
Raises:
|
||||
None
|
||||
"""
|
||||
context = filter_pagination_asset_category(request)
|
||||
return render(request, 'category/asset_category_view.html', context)
|
||||
|
||||
|
||||
@login_required
|
||||
@permission_required('asset.view_assetcategory')
|
||||
def asset_category_view_search_filter(request):
|
||||
"""
|
||||
View function for rendering a paginated list of asset categories with search and filter options.
|
||||
Args:
|
||||
request (HttpRequest): A Django HttpRequest object that contains information about the current request.
|
||||
Returns:
|
||||
A rendered HTML template that displays a paginated list of asset categories with search and filter options.
|
||||
Raises:
|
||||
None
|
||||
"""
|
||||
|
||||
search_type = request.GET.get('type')
|
||||
query = request.GET.get('search')
|
||||
if search_type =='asset':
|
||||
# searching asset will redirect to asset list and pass the query
|
||||
return redirect(f'/asset/asset-list/0?asset_list=asset&query={query}')
|
||||
context = filter_pagination_asset_category(request)
|
||||
return render(request, 'category/asset_category.html', context)
|
||||
|
||||
|
||||
@login_required
|
||||
def asset_request_creation(request):
|
||||
"""
|
||||
Creates a new AssetRequest object and saves it to the database.
|
||||
Renders the asset_request_creation.html template if the request method is GET.
|
||||
If the request method is POST and the form data is valid, the new AssetRequest is saved to the database and
|
||||
the user is redirected to the asset_request_view_search_filter view.
|
||||
If the form data is invalid, or if the request method is POST but the form data is not present, the user is
|
||||
presented with the asset_request_creation.html template with error messages displayed.
|
||||
"""
|
||||
# intitial = {'requested_employee_id':request.user.employee_get}
|
||||
form = AssetRequestForm(user=request.user)
|
||||
context = {'asset_request_form':form}
|
||||
if request.method == 'POST':
|
||||
form = AssetRequestForm(request.POST,user=request.user)
|
||||
if form.is_valid():
|
||||
form.save()
|
||||
messages.success(request,_('Asset request created!'))
|
||||
return HttpResponse('<script>window.location.reload()</script>')
|
||||
context['asset_request_form'] = form
|
||||
|
||||
return render(request,'request_allocation/asset_request_creation.html',context)
|
||||
|
||||
|
||||
@login_required
|
||||
@permission_required('asset.add_asset')
|
||||
def asset_request_approve(request, id):
|
||||
"""
|
||||
Approves an asset request with the given ID and updates the corresponding asset record
|
||||
to mark it as allocated.
|
||||
Args:
|
||||
request: The HTTP request object.
|
||||
id (int): The ID of the asset request to be approved.
|
||||
Returns:
|
||||
A redirect response to the asset request allocation view, or an error message if the
|
||||
request with the given ID cannot be found or its asset has already been allocated.
|
||||
"""
|
||||
|
||||
asset_request = AssetRequest.objects.filter(id=id).first()
|
||||
asset_category = asset_request.asset_category_id
|
||||
assets = Asset.objects.filter(asset_category_id=asset_category).filter(asset_status='Available')
|
||||
|
||||
form = AssetAllocationForm(initial={'asset_id':assets})
|
||||
context = {'asset_allocation_form':form,
|
||||
'id':id}
|
||||
if request.method == 'POST':
|
||||
post_data = request.POST.dict()
|
||||
# Add additional fields to the dictionary
|
||||
post_data['assigned_to_employee_id'] = asset_request.requested_employee_id
|
||||
post_data['assigned_by_employee_id'] = request.user.employee_get
|
||||
form = AssetAllocationForm(post_data)
|
||||
if form.is_valid():
|
||||
asset = form.instance.asset_id.id
|
||||
asset =Asset.objects.filter(id=asset).first()
|
||||
asset.asset_status = 'In use'
|
||||
asset.save()
|
||||
form = form.save()
|
||||
asset_request.asset_request_status = 'Approved'
|
||||
asset_request.save()
|
||||
messages.success(request,_('Asset request approved successfully!.'))
|
||||
notify.send(request.user.employee_get, recipient=form.assigned_to_employee_id.employee_user_id, verb='Your asset request appoved!.',redirect='/asset/asset-request-allocation-view',icon='bag-check')
|
||||
response = render(request,'request_allocation/asset_approve.html',{'asset_allocation_form':form,'id':id})
|
||||
return HttpResponse(response.content.decode('utf-8') +'<script>location.reload();</script>')
|
||||
context['asset_allocation_form'] = form
|
||||
|
||||
return render(request,'request_allocation/asset_approve.html',context)
|
||||
|
||||
|
||||
@login_required
|
||||
@permission_required('asset.add_asset')
|
||||
def asset_request_reject(request, id):
|
||||
"""
|
||||
View function to reject an asset request.
|
||||
Parameters:
|
||||
request (HttpRequest): the request object sent by the client
|
||||
id (int): the id of the AssetRequest object to reject
|
||||
|
||||
Returns:
|
||||
HttpResponse: a redirect to the asset request list view with a success message if the asset request is rejected successfully, or a redirect to the asset request detail view with an error message if the asset request is not found or already rejected
|
||||
"""
|
||||
asset_request = AssetRequest.objects.get(id=id)
|
||||
asset_request.asset_request_status = 'Rejected'
|
||||
asset_request.save()
|
||||
messages.info(request,_('Asset request rejected'))
|
||||
notify.send(request.user.employee_get, recipient=asset_request.requested_employee_id.employee_user_id, verb='Your asset request rejected!.',redirect='/asset/asset-request-allocation-view',icon='bag-check')
|
||||
return HttpResponseRedirect(request. META. get('HTTP_REFERER', '/'))
|
||||
|
||||
|
||||
@login_required
|
||||
@permission_required('asset.add_asset')
|
||||
def asset_allocate_creation(request):
|
||||
"""
|
||||
View function to create asset allocation.
|
||||
Returns:
|
||||
- to allocated view.
|
||||
"""
|
||||
|
||||
form = AssetAllocationForm()
|
||||
context = {'asset_allocation_form':form}
|
||||
if request.method == 'POST':
|
||||
form = AssetAllocationForm(request.POST)
|
||||
if form.is_valid():
|
||||
asset = form.instance.asset_id.id
|
||||
asset =Asset.objects.filter(id=asset).first()
|
||||
asset.asset_status = 'In use'
|
||||
asset.save()
|
||||
form.save()
|
||||
messages.success(request,_('Asset allocated successfully!.'))
|
||||
return HttpResponse('<script>window.location.reload()</script>')
|
||||
context['asset_allocation_form'] = form
|
||||
|
||||
return render(request,'request_allocation/asset_allocation_creation.html',context)
|
||||
|
||||
|
||||
@login_required
|
||||
def asset_allocate_return(request, id):
|
||||
"""
|
||||
View function to return asset.
|
||||
Args:
|
||||
- id: integer value representing the ID of the asset
|
||||
Returns:
|
||||
- message of the return
|
||||
"""
|
||||
|
||||
context = {'asset_return_form' : AssetReturnForm(),
|
||||
'asset_id':id}
|
||||
if request.method == 'POST':
|
||||
asset = Asset.objects.filter(id=id).first()
|
||||
asset_return_status = request.POST.get('return_status')
|
||||
asset_return_date = request.POST.get('return_date')
|
||||
asset_return_condition = request.POST.get('return_condition')
|
||||
if asset_return_status == 'Healthy':
|
||||
asset_allocation = AssetAssignment.objects.filter(asset_id=id,return_status__isnull=True).first()
|
||||
asset_allocation.return_date = asset_return_date
|
||||
asset_allocation.return_status = asset_return_status
|
||||
asset_allocation.return_condition = asset_return_condition
|
||||
asset_allocation.save()
|
||||
asset.asset_status = 'Available'
|
||||
asset.save()
|
||||
messages.info(request,_('Asset Return Successful !.'))
|
||||
return HttpResponseRedirect(request. META. get('HTTP_REFERER', '/'))
|
||||
asset.asset_status = 'Not-Available'
|
||||
asset.save()
|
||||
asset_allocation = AssetAssignment.objects.filter(asset_id=id,return_status__isnull=True).first()
|
||||
asset_allocation.return_date = asset_return_date
|
||||
asset_allocation.return_status = asset_return_status
|
||||
asset_allocation.return_condition = asset_return_condition
|
||||
asset_allocation.save()
|
||||
messages.info(request,_('Asset Return Successful!.'))
|
||||
return HttpResponseRedirect(request. META. get('HTTP_REFERER', '/'))
|
||||
return render(request,'asset/asset_return_form.html',context)
|
||||
|
||||
|
||||
def filter_pagination_asset_request_allocation(request):
|
||||
|
||||
asset_request_alloaction_search = request.GET.get('search')
|
||||
if asset_request_alloaction_search is None:
|
||||
asset_request_alloaction_search = ""
|
||||
employee = request.user.employee_get
|
||||
|
||||
assets = AssetAssignment.objects.filter(assigned_to_employee_id=employee).exclude(return_status__isnull=False).filter(asset_id__asset_name__icontains=asset_request_alloaction_search)
|
||||
if request.user.has_perm(('asset.view_assetrequest','asset.view_assetassignment')):
|
||||
asset_allocations_queryset = AssetAssignment.objects.all().filter(assigned_to_employee_id__employee_first_name__icontains=asset_request_alloaction_search)
|
||||
asset_requests_queryset = AssetRequest.objects.all().filter(requested_employee_id__employee_first_name__icontains=asset_request_alloaction_search)
|
||||
else:
|
||||
asset_allocations_queryset = AssetAssignment.objects.filter(assigned_to_employee_id=employee).filter(assigned_to_employee_id__employee_first_name__icontains=asset_request_alloaction_search)
|
||||
asset_requests_queryset = AssetRequest.objects.filter(requested_employee_id=employee).filter(requested_employee_id__employee_first_name__icontains=asset_request_alloaction_search)
|
||||
|
||||
previous_data = request.environ['QUERY_STRING']
|
||||
assets_filtered = CustomAssetFilter(request.GET, queryset=assets)
|
||||
asset_request_filtered = AssetRequestFilter(request.GET, queryset=asset_requests_queryset)
|
||||
asset_allocation_filtered = AssetAllocationFilter(request.GET, queryset=asset_allocations_queryset)
|
||||
asset_paginator = Paginator(assets_filtered.qs, 20)
|
||||
asset_request_paginator = Paginator(asset_request_filtered.qs, 20)
|
||||
asset_allocation_paginator = Paginator(asset_allocation_filtered.qs, 20)
|
||||
page_number = request.GET.get('page')
|
||||
assets = asset_paginator.get_page(page_number)
|
||||
asset_requests = asset_request_paginator.get_page(page_number)
|
||||
asset_allocations = asset_allocation_paginator.get_page(page_number)
|
||||
|
||||
return {
|
||||
"assets": assets,
|
||||
"asset_requests": asset_requests,
|
||||
"asset_allocations": asset_allocations,
|
||||
"assets_filter_form": assets_filtered.form,
|
||||
"asset_request_filter_form": asset_request_filtered.form,
|
||||
"asset_allocation_filter_form": asset_allocation_filtered.form,
|
||||
"pg": previous_data,
|
||||
}
|
||||
|
||||
|
||||
@login_required
|
||||
def asset_request_alloaction_view(request):
|
||||
"""
|
||||
This view is used to display a paginated list of asset allocation requests.
|
||||
Args:
|
||||
request (HttpRequest): The HTTP request object.
|
||||
Returns:
|
||||
HttpResponse: The HTTP response object with the rendered HTML template.
|
||||
"""
|
||||
context = filter_pagination_asset_request_allocation(request)
|
||||
return render(request, 'request_allocation/asset_request_allocation_view.html', context)
|
||||
|
||||
|
||||
def asset_request_alloaction_view_search_filter(request):
|
||||
"""
|
||||
This view handles the search and filter functionality for the asset request allocation list.
|
||||
Args:
|
||||
request: HTTP request object.
|
||||
Returns:
|
||||
Rendered HTTP response with the filtered and paginated asset request allocation list.
|
||||
"""
|
||||
context = filter_pagination_asset_request_allocation(request)
|
||||
return render(request, 'request_allocation/asset_request_allocation_list.html', context)
|
||||
|
||||
|
||||
def convert_nan(val):
|
||||
if pd.isna(val):
|
||||
return None
|
||||
else:
|
||||
return val
|
||||
|
||||
@login_required
|
||||
@permission_required('asset.add_asset')
|
||||
def asset_import(request):
|
||||
""" asset import view"""
|
||||
|
||||
try:
|
||||
if request.method == 'POST':
|
||||
file = request.FILES.get('asset_import')
|
||||
|
||||
if file is not None:
|
||||
try:
|
||||
df = pd.read_excel(file)
|
||||
except KeyError as e:
|
||||
messages.error(request, f'{e}')
|
||||
return redirect(asset_category_view)
|
||||
|
||||
# Create Asset objects from the DataFrame and save them to the database
|
||||
for index, row in df.iterrows():
|
||||
asset_name = convert_nan(row['Asset name'])
|
||||
asset_description = convert_nan(row['Description'])
|
||||
asset_tracking_id = convert_nan(row['Tracking id'])
|
||||
purchase_date = convert_nan(row['Purchase date'])
|
||||
purchase_cost = convert_nan(row['Purchase cost'])
|
||||
category_name = convert_nan(row['Category'])
|
||||
lot_number = convert_nan(row['lot number'])
|
||||
status = convert_nan(row['Status'])
|
||||
|
||||
asset_category, create = AssetCategory.objects.get_or_create(asset_category_name=category_name)
|
||||
asset_lot_number, create = AssetLot.objects.get_or_create(lot_number=lot_number)
|
||||
Asset.objects.create(asset_name=asset_name, asset_description=asset_description, asset_tracking_id=asset_tracking_id,
|
||||
asset_purchase_date=purchase_date, asset_purchase_cost=purchase_cost,
|
||||
asset_category_id=asset_category, asset_status=status, asset_lot_number_id=asset_lot_number)
|
||||
|
||||
messages.success(request, _('Successfully imported Assets'))
|
||||
return redirect(asset_category_view)
|
||||
messages.error(request, _('File Error'))
|
||||
return redirect(asset_category_view)
|
||||
except Exception as e:
|
||||
messages.error(request, f'{e}')
|
||||
return redirect(asset_category_view)
|
||||
|
||||
|
||||
@login_required
|
||||
def asset_excel(request):
|
||||
"""asset excel download view"""
|
||||
|
||||
try:
|
||||
columns = ['Asset name', 'Description', 'Tracking id',
|
||||
'Purchase date', 'Purchase cost', 'Category', 'Status', 'lot number']
|
||||
# Create a pandas DataFrame with columns but no data
|
||||
df = pd.DataFrame(columns=columns)
|
||||
# Write the DataFrame to an Excel file
|
||||
response = HttpResponse(content_type='application/ms-excel')
|
||||
response['Content-Disposition'] = 'attachment; filename="my_excel_file.xlsx"'
|
||||
df.to_excel(response, index=False)
|
||||
return response
|
||||
except Exception as e:
|
||||
return HttpResponse(e)
|
||||
|
||||
|
||||
@login_required
|
||||
@permission_required('asset.add_asset')
|
||||
def asset_export_excel(request):
|
||||
"""asset export view """
|
||||
|
||||
queryset_all = Asset.objects.all()
|
||||
if not queryset_all:
|
||||
messages.warning(request, _('There are no assets to export.'))
|
||||
return redirect('asset-category-view') # or some other URL
|
||||
|
||||
queryset = AssetExportFilter(request.POST, queryset=queryset_all).qs
|
||||
|
||||
# Convert the queryset to a Pandas DataFrame
|
||||
data = {
|
||||
'asset_name': [],
|
||||
'asset_description': [],
|
||||
'asset_tracking_id': [],
|
||||
'asset_purchase_date': [],
|
||||
'asset_purchase_cost': [],
|
||||
'asset_category_id': [],
|
||||
'asset_status': [],
|
||||
'asset_lot_number_id': [],
|
||||
}
|
||||
|
||||
for asset in queryset:
|
||||
try:
|
||||
data['asset_name'].append(asset.asset_name)
|
||||
except AttributeError:
|
||||
data['asset_name'].append(None)
|
||||
|
||||
try:
|
||||
data['asset_description'].append(asset.asset_description)
|
||||
except AttributeError:
|
||||
data['asset_description'].append(None)
|
||||
|
||||
try:
|
||||
data['asset_tracking_id'].append(asset.asset_tracking_id)
|
||||
except AttributeError:
|
||||
data['asset_tracking_id'].append(None)
|
||||
|
||||
try:
|
||||
data['asset_purchase_date'].append(asset.asset_purchase_date)
|
||||
except AttributeError:
|
||||
data['asset_purchase_date'].append(None)
|
||||
|
||||
try:
|
||||
data['asset_purchase_cost'].append(asset.asset_purchase_cost)
|
||||
except AttributeError:
|
||||
data['asset_purchase_cost'].append(None)
|
||||
|
||||
try:
|
||||
data['asset_category_id'].append(asset.asset_category_id.asset_category_name)
|
||||
except AttributeError:
|
||||
data['asset_category_id'].append(None)
|
||||
|
||||
try:
|
||||
data['asset_status'].append(asset.asset_status)
|
||||
except AttributeError:
|
||||
data['asset_status'].append(None)
|
||||
|
||||
try:
|
||||
data['asset_lot_number_id'].append(asset.asset_lot_number_id.lot_number)
|
||||
except AttributeError:
|
||||
data['asset_lot_number_id'].append(None)
|
||||
|
||||
df = pd.DataFrame(data)
|
||||
|
||||
# Convert any date fields to the desired format
|
||||
# Rename the columns as needed
|
||||
df = df.rename(columns={
|
||||
'asset_name': 'Asset name',
|
||||
'asset_description': 'Description',
|
||||
'asset_tracking_id': 'Tracking id',
|
||||
'asset_purchase_date': 'Purchase date',
|
||||
'asset_purchase_cost': 'Purchase cost',
|
||||
'asset_category_id': 'Category',
|
||||
'asset_status': 'Status',
|
||||
'asset_lot_number_id': 'lot number',
|
||||
})
|
||||
|
||||
# Write the DataFrame to an Excel file
|
||||
response = HttpResponse(content_type='application/vnd.ms-excel')
|
||||
response['Content-Disposition'] = 'attachment; filename="assets.xlsx"'
|
||||
df.to_excel(response, index=False)
|
||||
return response
|
||||
|
||||
|
||||
@login_required
|
||||
def asset_batch_number_creation(request):
|
||||
"""asset batch number creation view """
|
||||
asset_batch_form = AssetBatchForm()
|
||||
context = {
|
||||
'asset_batch_form': asset_batch_form,
|
||||
}
|
||||
if request.method == 'POST':
|
||||
asset_batch_form = AssetBatchForm(request.POST)
|
||||
if asset_batch_form.is_valid():
|
||||
asset_batch_form.save()
|
||||
messages.success(request,_('Batch number created successfully.'))
|
||||
response = render(request,'batch/asset_batch_number_creation.html',context)
|
||||
return HttpResponse(response.content.decode('utf-8') +'<script>location.reload();</script>')
|
||||
else:
|
||||
context = {
|
||||
'asset_batch_form': asset_batch_form,
|
||||
}
|
||||
return render(request,'batch/asset_batch_number_creation.html',context)
|
||||
return render(request,'batch/asset_batch_number_creation.html',context)
|
||||
|
||||
|
||||
@login_required
|
||||
@permission_required('asset.add_assetlot')
|
||||
def asset_batch_view(request):
|
||||
"""
|
||||
View function to display details of all batch numbers.
|
||||
|
||||
Returns:
|
||||
- all asset batch numbers based on page
|
||||
"""
|
||||
|
||||
asset_batchs = AssetLot.objects.all()
|
||||
previous_data = request.environ['QUERY_STRING']
|
||||
asset_batch_numbers_search_paginator = Paginator(asset_batchs, 20)
|
||||
page_number = request.GET.get('page')
|
||||
asset_batch_numbers = asset_batch_numbers_search_paginator.get_page(
|
||||
page_number)
|
||||
asset_batch_form = AssetBatchForm()
|
||||
|
||||
context = {
|
||||
'batch_numbers': asset_batch_numbers,
|
||||
'asset_batch_form': asset_batch_form,
|
||||
'pg': previous_data}
|
||||
return render(request, 'batch/asset_batch_number_view.html', context)
|
||||
|
||||
|
||||
@login_required
|
||||
@permission_required('asset.change_assetlot')
|
||||
def asset_batch_update(request, id):
|
||||
"""
|
||||
View function to return asset.
|
||||
Args:
|
||||
- id: integer value representing the ID of the asset
|
||||
Returns:
|
||||
- message of the return
|
||||
"""
|
||||
asset_batch_number = AssetLot.objects.get(id=id)
|
||||
asset_batch = AssetLot.objects.get(id=id)
|
||||
asset_batch_form = AssetBatchForm(instance=asset_batch)
|
||||
context = {
|
||||
'asset_batch_update_form': asset_batch_form,
|
||||
}
|
||||
assigned_batch_number = Asset.objects.filter(
|
||||
asset_lot_number_id=asset_batch_number)
|
||||
if assigned_batch_number:
|
||||
asset_batch_form = AssetBatchForm(instance=asset_batch)
|
||||
asset_batch_form['lot_number'].field.widget.attrs.update({'readonly': 'readonly'})
|
||||
context['asset_batch_update_form'] = asset_batch_form
|
||||
context['in_use_message'] = 'This batch number is already in-use'
|
||||
if request.method == 'POST':
|
||||
asset_batch_form = AssetBatchForm(request.POST, instance=asset_batch_number)
|
||||
if asset_batch_form.is_valid():
|
||||
asset_batch_form.save()
|
||||
messages.info(request,_('Batch updated successfully.'))
|
||||
response = render(request, 'batch/asset_batch_number_update.html', context)
|
||||
return HttpResponse(response.content.decode('utf-8') +'<script>location.reload();</script>')
|
||||
context['asset_batch_update_form'] = asset_batch_form
|
||||
return render(request, 'batch/asset_batch_number_update.html', context)
|
||||
|
||||
|
||||
@login_required
|
||||
@permission_required('asset.delete_assetlot')
|
||||
def asset_batch_number_delete(request, id):
|
||||
"""
|
||||
View function to return asset.
|
||||
Args:
|
||||
- id: integer value representing the ID of the asset
|
||||
Returns:
|
||||
- message of the return
|
||||
"""
|
||||
asset_batch_number = AssetLot.objects.get(id=id)
|
||||
assigned_batch_number = Asset.objects.filter(asset_lot_number_id=asset_batch_number)
|
||||
if assigned_batch_number:
|
||||
messages.error(request,_('Batch number in-use'))
|
||||
return redirect(asset_batch_view)
|
||||
asset_batch_number.delete()
|
||||
messages.success(request,_('Batch number deleted'))
|
||||
return redirect(asset_batch_view)
|
||||
|
||||
@login_required
|
||||
@hx_request_required
|
||||
@permission_required('asset.delete_assetlot')
|
||||
def asset_batch_number_search(request):
|
||||
"""
|
||||
View function to return search data of asset batch number.
|
||||
|
||||
Args:
|
||||
- id: integer value representing the ID of the asset
|
||||
|
||||
Returns:
|
||||
- message of the return
|
||||
"""
|
||||
asset_batch_number_search = request.GET.get('search')
|
||||
if asset_batch_number_search is None:
|
||||
asset_batch_number_search = ""
|
||||
|
||||
asset_batchs = AssetLot.objects.all().filter(lot_number__icontains=asset_batch_number_search)
|
||||
previous_data = request.environ['QUERY_STRING']
|
||||
asset_batch_numbers_search_paginator = Paginator(asset_batchs, 20)
|
||||
page_number = request.GET.get('page')
|
||||
asset_batch_numbers = asset_batch_numbers_search_paginator.get_page(page_number)
|
||||
|
||||
context = {
|
||||
"batch_numbers": asset_batch_numbers,
|
||||
"pg": previous_data,
|
||||
}
|
||||
|
||||
return render(request, 'batch/asset_batch_number_list.html', context)
|
||||
|
||||
@login_required
|
||||
def asset_count_update(request):
|
||||
"""
|
||||
View function to return update asset count at asset category.
|
||||
Args:
|
||||
- id: integer value representing the ID of the asset category
|
||||
Returns:
|
||||
- count of asset inside the category
|
||||
"""
|
||||
if request.method=='POST':
|
||||
category_id = request.POST.get('asset_category_id')
|
||||
if category_id is not None:
|
||||
category = AssetCategory.objects.get(id=category_id)
|
||||
asset_count = category.asset_set.count()
|
||||
return HttpResponse(asset_count)
|
||||
return HttpResponse('error')
|
||||
|
||||
0
attendance/__init__.py
Normal file
0
attendance/__init__.py
Normal file
8
attendance/admin.py
Normal file
8
attendance/admin.py
Normal file
@@ -0,0 +1,8 @@
|
||||
from django.contrib import admin
|
||||
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)
|
||||
7
attendance/apps.py
Normal file
7
attendance/apps.py
Normal file
@@ -0,0 +1,7 @@
|
||||
from django.apps import AppConfig
|
||||
|
||||
|
||||
|
||||
class AttendanceConfig(AppConfig):
|
||||
default_auto_field = 'django.db.models.BigAutoField'
|
||||
name = 'attendance'
|
||||
377
attendance/filters.py
Normal file
377
attendance/filters.py
Normal file
@@ -0,0 +1,377 @@
|
||||
import django_filters
|
||||
from attendance.models import Attendance, AttendanceOverTime, AttendanceLateComeEarlyOut, AttendanceActivity
|
||||
from django.forms import DateTimeInput
|
||||
from django import forms
|
||||
from django.forms import TextInput,ModelChoiceField,Select
|
||||
from horilla.filters import filter_by_name
|
||||
from django_filters import FilterSet, DateFilter
|
||||
|
||||
|
||||
class FilterSet(django_filters.FilterSet):
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
for field_name, field 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',})
|
||||
|
||||
|
||||
|
||||
class DurationInSecondsFilter(django_filters.CharFilter):
|
||||
def filter(self, qs, value):
|
||||
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'
|
||||
return qs.filter(**{f"{self.field_name}__{lookup}": duration_sec})
|
||||
return qs
|
||||
|
||||
|
||||
class AttendanceOverTimeFilter(FilterSet):
|
||||
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')
|
||||
class Meta:
|
||||
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',
|
||||
]
|
||||
|
||||
def __init__(self, data=None, queryset=None, *, request=None, prefix=None):
|
||||
super(AttendanceOverTimeFilter, self).__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'})
|
||||
)
|
||||
attendance_date__lte = django_filters.DateFilter(
|
||||
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__gte = DateFilter(
|
||||
field_name = 'attendance_id__attendance_clock_in',
|
||||
widget=forms.DateInput(attrs={'type': 'time'}),
|
||||
lookup_expr='gte', # add lookup expression here
|
||||
)
|
||||
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__lte = DateFilter(
|
||||
field_name = 'attendance_id__attendance_clock_out',
|
||||
widget=forms.DateInput(attrs={'type': 'time'}),
|
||||
lookup_expr='lte', # add lookup expression here
|
||||
)
|
||||
attendance_clock_in = DateFilter(
|
||||
field_name = 'attendance_id__attendance_clock_in',
|
||||
widget=forms.DateInput(attrs={'type': 'time'}),
|
||||
# add lookup expression here
|
||||
)
|
||||
attendance_clock_out = DateFilter(
|
||||
field_name = 'attendance_id__attendance_clock_out',
|
||||
widget=forms.DateInput(attrs={'type': 'time'}),
|
||||
# add lookup expression here
|
||||
)
|
||||
attendance_date = DateFilter(
|
||||
field_name = 'attendance_id__attendance_date',
|
||||
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')
|
||||
|
||||
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',
|
||||
|
||||
]
|
||||
|
||||
|
||||
|
||||
class AttendanceActivityFilter(FilterSet):
|
||||
search = django_filters.CharFilter(method=filter_by_name)
|
||||
|
||||
attendance_date = django_filters.DateFilter(
|
||||
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'})
|
||||
)
|
||||
attendance_date_till = django_filters.DateFilter(
|
||||
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'})
|
||||
)
|
||||
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'})
|
||||
)
|
||||
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'})
|
||||
)
|
||||
clock_out_date = django_filters.DateFilter(
|
||||
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',
|
||||
|
||||
]
|
||||
model = AttendanceActivity
|
||||
|
||||
|
||||
|
||||
|
||||
class AttendanceFilters(FilterSet):
|
||||
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'})
|
||||
)
|
||||
attendance_date__lte = django_filters.DateFilter(
|
||||
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__gte = DateFilter(
|
||||
widget=forms.DateInput(attrs={'type': 'time'}),
|
||||
lookup_expr='gte', # add lookup expression here
|
||||
)
|
||||
attendance_clock_out__gte = DateFilter(
|
||||
widget=forms.DateInput(attrs={'type': 'time'}),
|
||||
lookup_expr='gte', # add lookup expression here
|
||||
)
|
||||
attendance_clock_out__lte = DateFilter(
|
||||
widget=forms.DateInput(attrs={'type': 'time'}),
|
||||
lookup_expr='lte', # add lookup expression here
|
||||
)
|
||||
|
||||
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'}),
|
||||
)
|
||||
|
||||
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 = 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',
|
||||
|
||||
]
|
||||
|
||||
widgets = {
|
||||
'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)
|
||||
|
||||
|
||||
|
||||
class LateComeEarlyOutReGroup():
|
||||
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'),
|
||||
]
|
||||
|
||||
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 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')
|
||||
]
|
||||
211
attendance/forms.py
Normal file
211
attendance/forms.py
Normal file
@@ -0,0 +1,211 @@
|
||||
from django import forms
|
||||
from .models import Attendance, AttendanceOverTime, AttendanceActivity,AttendanceLateComeEarlyOut,AttendanceValidationCondition
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
from django.core.exceptions import ValidationError
|
||||
from django.db import models
|
||||
from django.forms import DateTimeInput
|
||||
from employee.models import Employee
|
||||
from calendar import month_name
|
||||
import uuid
|
||||
|
||||
class ModelForm(forms.ModelForm):
|
||||
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)):
|
||||
field.widget.attrs.update({'class': 'oh-input w-100','placeholder':field.label})
|
||||
elif isinstance(widget,(forms.Select,)):
|
||||
label = ''
|
||||
if field.label is not None:
|
||||
label = field.label.replace('id',' ')
|
||||
field.empty_label = f'---Choose {label}---'
|
||||
self.fields[field_name].widget.attrs.update({'class':'oh-select oh-select-2 w-100','id': uuid.uuid4(),'style':'height:50px;border-radius:0;'})
|
||||
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 AttendanceUpdateForm(ModelForm):
|
||||
"""
|
||||
This model form is used to direct save the validated query dict to attendance model
|
||||
from AttendanceForm. This form can be used to update existing attendance.
|
||||
"""
|
||||
class Meta:
|
||||
fields='__all__'
|
||||
exclude = ['overtime_second','at_work_second','attendance_day','approved_overtime_second']
|
||||
model = Attendance
|
||||
widgets = {
|
||||
'attendance_clock_in': DateTimeInput(attrs={'type': 'time'}),
|
||||
'attendance_clock_out': DateTimeInput(attrs={'type': 'time'}),
|
||||
'attendance_clock_out_date':DateTimeInput(attrs={'type':'date'}),
|
||||
'attendance_date':DateTimeInput(attrs={'type':'date'}),
|
||||
'attendance_clock_in_date':DateTimeInput(attrs={'type':'date'}),
|
||||
}
|
||||
|
||||
def __init__(self, *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={
|
||||
'attendance_date':instance.attendance_date.strftime('%Y-%m-%d'),
|
||||
'attendance_clock_in':instance.attendance_clock_in.strftime('%H:%M'),
|
||||
'attendance_clock_in_date':instance.attendance_clock_in_date.strftime('%Y-%m-%d'),
|
||||
}
|
||||
if instance.attendance_clock_out_date is not None:
|
||||
initial['attendance_clock_out'] = instance.attendance_clock_out.strftime('%H:%M')
|
||||
initial['attendance_clock_out_date'] = instance.attendance_clock_out_date.strftime('%Y-%m-%d')
|
||||
kwargs['initial']=initial
|
||||
super(AttendanceUpdateForm, self).__init__(*args, **kwargs)
|
||||
|
||||
|
||||
class AttendanceForm(ModelForm):
|
||||
employee_id = forms.ModelMultipleChoiceField(queryset=Employee.objects.filter(employee_work_info__isnull=False),)
|
||||
class Meta:
|
||||
model=Attendance
|
||||
fields = '__all__'
|
||||
exclude = ('attendance_overtime_approve','attendance_overtime_calculation','at_work_second','overtime_second','attendance_day','approved_overtime_second')
|
||||
widgets = {
|
||||
'attendance_clock_in': DateTimeInput(attrs={'type': 'time'}),
|
||||
'attendance_clock_out': DateTimeInput(attrs={'type': 'time'}),
|
||||
'attendance_clock_out_date':DateTimeInput(attrs={'type':'date'}),
|
||||
'attendance_date':DateTimeInput(attrs={'type':'date'}),
|
||||
'attendance_clock_in_date':DateTimeInput(attrs={'type':'date'}),
|
||||
}
|
||||
|
||||
def __init__(self, *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={
|
||||
'attendance_date':instance.attendance_date.strftime('%Y-%m-%d'),
|
||||
'attendance_clock_in':instance.attendance_clock_in.strftime('%H:%M'),
|
||||
'attendance_clock_in_date':instance.attendance_clock_in_date.strftime('%Y-%m-%d'),
|
||||
}
|
||||
if instance.attendance_clock_out_date is not None:
|
||||
initial['attendance_clock_out'] = instance.attendance_clock_out.strftime('%H:%M')
|
||||
initial['attendance_clock_out_date'] = instance.attendance_clock_out_date.strftime('%Y-%m-%d')
|
||||
kwargs['initial']=initial
|
||||
super(AttendanceForm, self).__init__(*args, **kwargs)
|
||||
self.fields['employee_id'].widget.attrs.update({'id': str(uuid.uuid4())})
|
||||
self.fields['shift_id'].widget.attrs.update({'id': str(uuid.uuid4())})
|
||||
self.fields['work_type_id'].widget.attrs.update({'id': str(uuid.uuid4())})
|
||||
|
||||
|
||||
def save(self, commit=True):
|
||||
instance = super().save(commit=False)
|
||||
for emp_id in self.data.getlist('employee_id'):
|
||||
if int(emp_id) != int(instance.employee_id.id):
|
||||
data_copy = self.data.copy()
|
||||
data_copy.update({'employee_id':str(emp_id)})
|
||||
attendance = AttendanceUpdateForm(data_copy).save(commit=False)
|
||||
attendance.save()
|
||||
if commit:
|
||||
instance.save()
|
||||
return instance
|
||||
|
||||
def clean_employee_id(self):
|
||||
employee = self.cleaned_data['employee_id']
|
||||
for emp in employee:
|
||||
attendance = Attendance.objects.filter(employee_id=emp,attendance_date=self.data['attendance_date']).first()
|
||||
if attendance is not None:
|
||||
raise ValidationError(_('Attendance for the date is already exist for %(emp)s' % {'emp':emp}))
|
||||
if employee.first() is None:
|
||||
raise ValidationError(_('Employee not chosen'))
|
||||
|
||||
return employee.first()
|
||||
|
||||
|
||||
class AttendanceActivityForm(ModelForm):
|
||||
class Meta:
|
||||
model=AttendanceActivity
|
||||
fields = '__all__'
|
||||
widgets = {
|
||||
'clock_in': DateTimeInput(attrs={'type': 'time'}),
|
||||
'clock_out': DateTimeInput(attrs={'type': 'time'}),
|
||||
'clock_in_date':DateTimeInput(attrs={'type':'date'}),
|
||||
'clock_out_date':DateTimeInput(attrs={'type':'date'})
|
||||
}
|
||||
|
||||
def __init__(self, *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 = {
|
||||
'attendance_date':instance.attendance_date.strftime('%Y-%m-%d'),
|
||||
'clock_in_date':instance.clock_in_date.strftime('%Y-%m-%d'),
|
||||
'clock_in':instance.clock_in.strftime('%H:%M'),
|
||||
}
|
||||
if instance.clock_out is not None:
|
||||
initial['clock_out'] = instance.clock_out.strftime('%H:%M')
|
||||
initial['clock_out_date'] = instance.clock_out_date.strftime('%Y-%m-%d')
|
||||
kwargs['initial']=initial
|
||||
super(AttendanceActivityForm, self).__init__(*args, **kwargs)
|
||||
|
||||
|
||||
|
||||
class MonthSelectField(forms.ChoiceField):
|
||||
def __init__(self, *args, **kwargs):
|
||||
choices = [(month_name[i].lower(), month_name[i]) for i in range(1, 13)]
|
||||
super(MonthSelectField, self).__init__(choices=choices, *args, **kwargs)
|
||||
|
||||
|
||||
class AttendanceOverTimeForm(ModelForm):
|
||||
month = MonthSelectField(label=_("Month"))
|
||||
|
||||
class Meta:
|
||||
model = AttendanceOverTime
|
||||
fields = '__all__'
|
||||
exclude = ['hour_account_second','overtime_second','month_sequence']
|
||||
labels = {
|
||||
"employee_id":_("Employee"),
|
||||
"year":_("Year"),
|
||||
"hour_account":_("Hour Account"),
|
||||
"overtime":_("Overtime"),
|
||||
}
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(AttendanceOverTimeForm, self).__init__(*args, **kwargs)
|
||||
self.fields['employee_id'].widget.attrs.update({'id': str(uuid.uuid4())})
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
class AttendanceLateComeEarlyOutForm(ModelForm):
|
||||
class Meta:
|
||||
model = AttendanceLateComeEarlyOut
|
||||
fields = '__all__'
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(AttendanceLateComeEarlyOutForm, self).__init__(*args, **kwargs)
|
||||
|
||||
|
||||
class AttendanceValidationConditionForm(ModelForm):
|
||||
class Meta:
|
||||
model = AttendanceValidationCondition
|
||||
validation_at_work = forms.DurationField( )
|
||||
approve_overtime_after = forms.DurationField( )
|
||||
overtime_cutoff = forms.DurationField( )
|
||||
|
||||
labels = {
|
||||
'validation_at_work': _('Do not Auto Validate Attendance if an Employee Works More Than this Amount of Duration'),
|
||||
|
||||
'minimum_overtime_to_approve': _('Minimum Hour to Approve Overtime'),
|
||||
'overtime_cutoff': _('Maximum Allowed Overtime Per Day'),
|
||||
}
|
||||
fields = '__all__'
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(AttendanceValidationConditionForm, self).__init__(*args, **kwargs)
|
||||
0
attendance/migrations/__init__.py
Normal file
0
attendance/migrations/__init__.py
Normal file
201
attendance/models.py
Normal file
201
attendance/models.py
Normal file
@@ -0,0 +1,201 @@
|
||||
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 _
|
||||
|
||||
# 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(':'))))
|
||||
|
||||
def format_time(seconds):
|
||||
'''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)
|
||||
|
||||
|
||||
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:
|
||||
hour, minute = value.split(":")
|
||||
hour = int(hour)
|
||||
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
|
||||
|
||||
def attendance_date_validate(date):
|
||||
today = datetime.today().date()
|
||||
if date > today:
|
||||
raise ValidationError(_('You cannot choose future date'))
|
||||
|
||||
|
||||
|
||||
|
||||
class AttendanceActivity(models.Model):
|
||||
employee_id = models.ForeignKey(Employee,on_delete=models.CASCADE, related_name='employee_attendance_activities')
|
||||
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 = 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']
|
||||
|
||||
|
||||
|
||||
class Attendance(models.Model):
|
||||
employee_id = models.ForeignKey(
|
||||
Employee, on_delete=models.CASCADE, null=True, related_name='employee_attendances')
|
||||
shift_id = models.ForeignKey(
|
||||
EmployeeShift, on_delete=models.DO_NOTHING,null=True)
|
||||
work_type_id = models.ForeignKey(WorkType,null=True,blank=True,on_delete=models.DO_NOTHING)
|
||||
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_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_overtime_approve = models.BooleanField(default=False)
|
||||
attendance_validated = models.BooleanField(default=False)
|
||||
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']
|
||||
|
||||
|
||||
def __str__(self) -> str:
|
||||
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())
|
||||
prev_attendance_approved = False
|
||||
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'))
|
||||
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()
|
||||
total_ot_seconds = attendance_account.overtime_second
|
||||
if approved and prev_attendance_approved is False:
|
||||
self.approved_overtime_second = self.overtime_second
|
||||
total_ot_seconds = total_ot_seconds + self.approved_overtime_second
|
||||
elif not approved:
|
||||
total_ot_seconds = total_ot_seconds - self.approved_overtime_second
|
||||
self.approved_overtime_second = 0
|
||||
attendance_account.overtime = format_time(total_ot_seconds)
|
||||
attendance_account.save()
|
||||
super(Attendance, self).save(*args, **kwargs)
|
||||
|
||||
|
||||
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
|
||||
'''
|
||||
employee_ot = AttendanceOverTime()
|
||||
employee_ot.employee_id = self.employee_id
|
||||
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
|
||||
|
||||
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)
|
||||
hour_balance = 0
|
||||
for attendance in month_attendances:
|
||||
hour_balance = hour_balance + attendance.at_work_second
|
||||
employee_ot.hour_account = format_time(hour_balance)
|
||||
employee_ot.save()
|
||||
return employee_ot
|
||||
|
||||
class AttendanceOverTime(models.Model):
|
||||
employee_id = models.ForeignKey(Employee,on_delete=models.CASCADE,related_name='employee_overtime')
|
||||
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,)
|
||||
|
||||
class Meta:
|
||||
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']
|
||||
self.month_sequence = months.index(month_name)
|
||||
super(AttendanceOverTime, self).save(*args, **kwargs)
|
||||
|
||||
|
||||
|
||||
class AttendanceLateComeEarlyOut(models.Model):
|
||||
choices = [
|
||||
('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')
|
||||
type = models.CharField(max_length=20,choices=choices)
|
||||
class Meta:
|
||||
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}'
|
||||
|
||||
|
||||
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])
|
||||
def clean(self):
|
||||
super().clean()
|
||||
if not self.id and AttendanceValidationCondition.objects.exists():
|
||||
raise ValidationError(_('You cannot add more conditions.'))
|
||||
129
attendance/static/attendance/actions.js
Normal file
129
attendance/static/attendance/actions.js
Normal file
@@ -0,0 +1,129 @@
|
||||
$(document).ready(function () {
|
||||
$(".validate").change(function (e) {
|
||||
var is_checked = $(this).is(":checked");
|
||||
if (is_checked) {
|
||||
$(".validate-row").prop("checked", true);
|
||||
} else {
|
||||
$(".validate-row").prop("checked", false);
|
||||
}
|
||||
});
|
||||
|
||||
$(".all-attendances").change(function (e) {
|
||||
var is_checked = $(this).is(":checked");
|
||||
if (is_checked) {
|
||||
$(".all-attendance-row").prop("checked", true);
|
||||
} else {
|
||||
$(".all-attendance-row").prop("checked", false);
|
||||
}
|
||||
});
|
||||
|
||||
$(".ot-attendances").change(function (e) {
|
||||
var is_checked = $(this).is(":checked");
|
||||
if (is_checked) {
|
||||
$(".ot-attendance-row").prop("checked", true);
|
||||
} else {
|
||||
$(".ot-attendance-row").prop("checked", false);
|
||||
}
|
||||
});
|
||||
|
||||
function getCookie(name) {
|
||||
let cookieValue = null;
|
||||
if (document.cookie && document.cookie !== "") {
|
||||
const cookies = document.cookie.split(";");
|
||||
for (let i = 0; i < cookies.length; i++) {
|
||||
const cookie = cookies[i].trim();
|
||||
// Does this cookie string begin with the name we want?
|
||||
if (cookie.substring(0, name.length + 1) === name + "=") {
|
||||
cookieValue = decodeURIComponent(cookie.substring(name.length + 1));
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return cookieValue;
|
||||
}
|
||||
$("#validateAttendances").click(function (e) {
|
||||
e.preventDefault();
|
||||
choice = originalConfirm(
|
||||
"Do you really want to validate all the selected attendances? "
|
||||
);
|
||||
if (choice) {
|
||||
var checkedRows = $(".validate-row").filter(":checked");
|
||||
ids = [];
|
||||
checkedRows.each(function () {
|
||||
ids.push($(this).attr("id"));
|
||||
});
|
||||
$.ajax({
|
||||
type: "POST",
|
||||
url: "/attendance/validate-bulk-attendance",
|
||||
data: {
|
||||
csrfmiddlewaretoken: getCookie("csrftoken"),
|
||||
ids: JSON.stringify(ids),
|
||||
},
|
||||
success: function (response, textStatus, jqXHR) {
|
||||
if (jqXHR.status === 200) {
|
||||
location.reload(); // Reload the current page
|
||||
} else {
|
||||
}
|
||||
},
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
$("#approveOt").click(function (e) {
|
||||
e.preventDefault();
|
||||
choice = originalConfirm(
|
||||
"Do you really want to approve OT for all the selected attendances? "
|
||||
);
|
||||
if (choice) {
|
||||
var checkedRows = $(".ot-attendance-row").filter(":checked");
|
||||
ids = [];
|
||||
checkedRows.each(function () {
|
||||
ids.push($(this).attr("id"));
|
||||
});
|
||||
$.ajax({
|
||||
type: "POST",
|
||||
url: "/attendance/approve-bulk-overtime",
|
||||
data: {
|
||||
csrfmiddlewaretoken: getCookie("csrftoken"),
|
||||
ids: JSON.stringify(ids),
|
||||
},
|
||||
success: function (response, textStatus, jqXHR) {
|
||||
if (jqXHR.status === 200) {
|
||||
location.reload(); // Reload the current page
|
||||
} else {
|
||||
// console.log("Unexpected HTTP status:", jqXHR.status);
|
||||
}
|
||||
},
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
$("#bulkDelete").click(function (e) {
|
||||
e.preventDefault();
|
||||
choice = originalConfirm(
|
||||
"Do you really want to delete all the selected attendances? "
|
||||
);
|
||||
if (choice) {
|
||||
var checkedRows = $(".attendance-checkbox").filter(":checked");
|
||||
ids = [];
|
||||
checkedRows.each(function () {
|
||||
ids.push($(this).attr("id"));
|
||||
});
|
||||
$.ajax({
|
||||
type: "POST",
|
||||
url: "/attendance/attendance-bulk-delete",
|
||||
data: {
|
||||
csrfmiddlewaretoken: getCookie("csrftoken"),
|
||||
ids: JSON.stringify(ids),
|
||||
},
|
||||
success: function (response, textStatus, jqXHR) {
|
||||
if (jqXHR.status === 200) {
|
||||
location.reload(); // Reload the current page
|
||||
} else {
|
||||
// console.log("Unexpected HTTP status:", jqXHR.status);
|
||||
}
|
||||
},
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
45
attendance/static/dashboard/attendanceChart.js
Normal file
45
attendance/static/dashboard/attendanceChart.js
Normal file
@@ -0,0 +1,45 @@
|
||||
$(document).ready(function () {
|
||||
function attendanceChart(dataSet, labels) {
|
||||
const data = {
|
||||
labels: labels,
|
||||
datasets: dataSet,
|
||||
};
|
||||
// Create chart using the Chart.js library
|
||||
window['myChart'] = {}
|
||||
const ctx = document.getElementById("dailyAnalytic").getContext("2d");
|
||||
myChart = new Chart(ctx, {
|
||||
type: 'doughnut',
|
||||
data: data,
|
||||
options: {
|
||||
},
|
||||
});
|
||||
}
|
||||
$.ajax({
|
||||
url: "/attendance/dashboard-attendance",
|
||||
type: "GET",
|
||||
success: function (response) {
|
||||
// Code to handle the response
|
||||
dataSet = response.dataSet;
|
||||
labels = response.labels;
|
||||
|
||||
attendanceChart(dataSet, labels);
|
||||
},
|
||||
});
|
||||
|
||||
$('.oh-card-dashboard__title').click(function (e) {
|
||||
var chartType = myChart.config.type
|
||||
if (chartType === 'line') {
|
||||
chartType = 'bar';
|
||||
} else if(chartType==='bar') {
|
||||
chartType = 'doughnut';
|
||||
} else if(chartType==='doughnut'){
|
||||
chartType = 'pie'
|
||||
}else if(chartType==='pie'){
|
||||
chartType = 'line'
|
||||
}
|
||||
myChart.config.type = chartType;
|
||||
myChart.update();
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
@@ -0,0 +1,170 @@
|
||||
{% load static %}
|
||||
{% load i18n %}
|
||||
<form hx-get='{% url "attendance-search" %}' id="filterForm" hx-swap='innerHTML' hx-target='#tab_contents' >
|
||||
<div class="oh-dropdown__filter-body">
|
||||
<div class="oh-accordion">
|
||||
<div class="oh-accordion-header">{% trans "Work Info" %}</div>
|
||||
<div class="oh-accordion-body">
|
||||
<div class="row">
|
||||
<div class="col-sm-12 col-md-12 col-lg-6">
|
||||
<div class="oh-input-group">
|
||||
<label class="oh-label">{% trans "Employee" %}</label>
|
||||
{{f.form.employee_id}}
|
||||
</div>
|
||||
<div class="oh-input-group">
|
||||
<label class="oh-label">{% trans "Department" %}</label>
|
||||
{{f.form.employee_id__employee_work_info__department_id}}
|
||||
</div>
|
||||
<div class="oh-input-group">
|
||||
<label class="oh-label">{% trans "Shift" %}</label>
|
||||
{{f.form.shift_id}}
|
||||
</div>
|
||||
<div class="oh-input-group">
|
||||
<label class="oh-label">{% trans "Reporting Manager" %}</label>
|
||||
{{f.form.employee_id__employee_work_info__reporting_manager_id}}
|
||||
</div>
|
||||
|
||||
|
||||
</div>
|
||||
<div class="col-sm-12 col-md-12 col-lg-6">
|
||||
<div class="oh-input-group">
|
||||
<label class="oh-label">{% trans "Company" %}</label>
|
||||
{{f.form.employee_id__employee_work_info__company_id}}
|
||||
</div>
|
||||
<div class="oh-input-group">
|
||||
<label class="oh-label">{% trans "Job Position" %}</label>
|
||||
{{f.form.employee_id__employee_work_info__job_position_id}}
|
||||
</div>
|
||||
<div class="oh-input-group">
|
||||
<label class="oh-label">{% trans "Work Type" %}</label>
|
||||
{{f.form.work_type_id}}
|
||||
</div>
|
||||
<div class="oh-input-group">
|
||||
<label class="oh-label">{% trans "Work Location" %}</label>
|
||||
{{f.form.employee_id__employee_work_info__location}}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% comment %} {{f.form}} {% endcomment %}
|
||||
<div class="oh-accordion">
|
||||
<div class="oh-accordion-header">{% trans "Attendance" %}</div>
|
||||
<div class="oh-accordion-body">
|
||||
<div class="row">
|
||||
<div class="col-sm-12 col-md-12 col-lg-6">
|
||||
<div class="oh-input-group">
|
||||
<label class="oh-label">{% trans "Attendance Date" %}</label>
|
||||
{{f.form.attendance_date}}
|
||||
</div>
|
||||
<div class="oh-input-group">
|
||||
<label class="oh-label">{% trans "In Time" %}</label>
|
||||
{{f.form.attendance_clock_in}}
|
||||
</div>
|
||||
<div class="oh-input-group">
|
||||
<label class="oh-label">{% trans "Validated?" %}</label>
|
||||
{{f.form.attendance_validated}}
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-sm-12 col-md-12 col-lg-6">
|
||||
<div class="oh-input-group">
|
||||
<label class="oh-label">{% trans "Min Hour" %}</label>
|
||||
{{f.form.minimum_hour}}
|
||||
</div>
|
||||
<div class="oh-input-group">
|
||||
<label class="oh-label">{% trans "Out Time" %}</label>
|
||||
{{f.form.attendance_clock_out}}
|
||||
</div>
|
||||
<div class="oh-input-group">
|
||||
<label class="oh-label">{% trans "OT Approved?" %}</label>
|
||||
{{f.form.attendance_overtime_approve}}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="oh-accordion">
|
||||
<div class="oh-accordion-header">{% trans "Advanced" %}</div>
|
||||
<div class="oh-accordion-body">
|
||||
<div class="row">
|
||||
<div class="col-sm-12 col-md-12 col-lg-6">
|
||||
<div class="oh-input-group">
|
||||
<label class="oh-label">{% trans "Attendance From" %}</label>
|
||||
{{f.form.attendance_date__gte}}
|
||||
</div>
|
||||
<div class="oh-input-group">
|
||||
<label class="oh-label">{% trans "In From" %}</label>
|
||||
{{f.form.attendance_clock_in__lte}}
|
||||
</div>
|
||||
<div class="oh-input-group">
|
||||
<label class="oh-label">{% trans "Out From" %}</label>
|
||||
{{f.form.attendance_clock_out__lte}}
|
||||
</div>
|
||||
<div class="oh-input-group">
|
||||
<label class="oh-label">{% trans "At Work Greater or Equal" %}</label>
|
||||
{{f.form.at_work_second__gte}}
|
||||
</div>
|
||||
<div class="oh-input-group">
|
||||
<label class="oh-label">{% trans "OT Greater or Equal" %}</label>
|
||||
{{f.form.overtime_second__gte}}
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-sm-12 col-md-12 col-lg-6">
|
||||
<div class="oh-input-group">
|
||||
<label class="oh-label">{% trans "Attendance Till" %}</label>
|
||||
{{f.form.attendance_date__lte}}
|
||||
</div>
|
||||
<div class="oh-input-group">
|
||||
<label class="oh-label">{% trans "In Till" %}</label>
|
||||
{{f.form.attendance_clock_in__lte}}
|
||||
</div>
|
||||
<div class="oh-input-group">
|
||||
<label class="oh-label">{% trans "Out Till" %}</label>
|
||||
{{f.form.attendance_clock_out__lte}}
|
||||
</div>
|
||||
<div class="oh-input-group">
|
||||
<label class="oh-label">{% trans "At Work Lessthan or Equal" %}</label>
|
||||
{{f.form.at_work_second__lte}}
|
||||
</div>
|
||||
<div class="oh-input-group">
|
||||
<label class="oh-label">{% trans "OT Lessthan or Equal" %}</label>
|
||||
{{f.form.overtime_second__gte}}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="oh-accordion">
|
||||
<div class="oh-accordion-header">{% trans "Group By" %}</div>
|
||||
<div class="oh-accordion-body">
|
||||
<div class="row">
|
||||
<div class="col-sm-12 col-md-12 col-lg-6">
|
||||
<div class="oh-input-group">
|
||||
<label class="oh-label">{% trans "Field" %}</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-sm-12 col-md-12 col-lg-6">
|
||||
<div class="oh-input-group">
|
||||
<select class="oh-select mt-1 " id="field" name="field" class="select2-selection select2-selection--single" id="gp">
|
||||
{% for field in gp_fields %}
|
||||
<option value="{{ field.0 }}">{{ field.1 }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="oh-dropdown__filter-footer">
|
||||
<button class="oh-btn oh-btn--secondary oh-btn--small w-100" id="filterSubmit">{% trans "Filter" %}</button>
|
||||
</div>
|
||||
|
||||
</form>
|
||||
|
||||
<script>
|
||||
$('#filterForm').submit(function (e) {
|
||||
// var formData = $(this).serialize();
|
||||
// $('#field').attr('hx-vals',`{'data':${formData}}` );
|
||||
});
|
||||
</script>
|
||||
132
attendance/templates/attendance/attendance/attendance_nav.html
Normal file
132
attendance/templates/attendance/attendance/attendance_nav.html
Normal file
@@ -0,0 +1,132 @@
|
||||
{% load static %}
|
||||
{% load i18n %}
|
||||
{% load basefilters %}
|
||||
{% if perms.attendance.add_attendance or request.user|is_reportingmanager %}
|
||||
|
||||
<div
|
||||
class="oh-modal"
|
||||
id="addAttendance"
|
||||
role="dialog"
|
||||
aria-labelledby="addAttendance"
|
||||
aria-hidden="true"
|
||||
>
|
||||
<div class="oh-modal__dialog">
|
||||
<div class="oh-modal__dialog-header">
|
||||
<h2 class="oh-modal__dialog-title" id="addEmployeeModalLabel">
|
||||
{% trans "Add Attendances" %}
|
||||
</h2>
|
||||
<button
|
||||
class="oh-modal__close"
|
||||
aria-label="Close"
|
||||
|
||||
>
|
||||
<ion-icon name="close-outline"></ion-icon>
|
||||
</button>
|
||||
</div>
|
||||
<div class="oh-modal__dialog-body" id="addAttendanceModalBody"></div>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<section class="oh-wrapper oh-main__topbar" x-data="{searchShow: false}">
|
||||
<div class="oh-main__titlebar oh-main__titlebar--left">
|
||||
<h1 class="oh-main__titlebar-title fw-bold">
|
||||
<a href="{% url 'attendance-view' %}" class="text-dark"> {% trans "Attendances" %} </a>
|
||||
</h1>
|
||||
<a
|
||||
class="oh-main__titlebar-search-toggle"
|
||||
role="button"
|
||||
aria-label="Toggle Search"
|
||||
@click="searchShow = !searchShow"
|
||||
>
|
||||
<ion-icon
|
||||
name="search-outline"
|
||||
class="oh-main__titlebar-serach-icon"
|
||||
></ion-icon>
|
||||
</a>
|
||||
</div>
|
||||
<div class="oh-main__titlebar oh-main__titlebar--right">
|
||||
<div
|
||||
class="oh-input-group oh-input__search-group"
|
||||
:class="searchShow ? 'oh-input__search-group--show' : ''"
|
||||
>
|
||||
<ion-icon
|
||||
name="search-outline"
|
||||
class="oh-input-group__icon oh-input-group__icon--left"
|
||||
></ion-icon>
|
||||
<input
|
||||
type="text"
|
||||
class="oh-input oh-input__icon"
|
||||
aria-label="Search Input"
|
||||
id="attendance-search"
|
||||
name="search"
|
||||
placeholder="{% trans 'Search' %}"
|
||||
hx-get="{% url 'attendance-search' %}"
|
||||
hx-trigger="keyup changed delay:500ms, search"
|
||||
hx-target="#tab_contents"
|
||||
hx-swap="innerHTML"
|
||||
/>
|
||||
</div>
|
||||
<div class="oh-main__titlebar-button-container">
|
||||
<div class="oh-dropdown" x-data="{open: false}">
|
||||
<button class="oh-btn ml-2" @click="open = !open">
|
||||
<ion-icon name="filter" class="mr-1"></ion-icon>{% trans "Filter" %}
|
||||
</button>
|
||||
<div
|
||||
class="oh-dropdown__menu oh-dropdown__menu--right oh-dropdown__filter p-4"
|
||||
x-show="open"
|
||||
@click.outside="open = false"
|
||||
style="display: none;"
|
||||
>
|
||||
{% include 'attendance/attendance/attendance_filters.html' %}
|
||||
</div>
|
||||
</div>
|
||||
<div class="oh-dropdown ml-2" x-data="{open: false}">
|
||||
<button
|
||||
class="oh-btn oh-btn--dropdown"
|
||||
@click="open = !open"
|
||||
@click.outside="open = false"
|
||||
>
|
||||
{% trans "Actions" %}
|
||||
</button>
|
||||
<div class="oh-dropdown__menu oh-dropdown__menu--right" x-show="open" style="display: none;">
|
||||
<ul class="oh-dropdown__items">
|
||||
{% if perms.attendance.change_attendance or request.user|is_reportingmanager %}
|
||||
<li class="oh-dropdown__item">
|
||||
<a href="#" id="validateAttendances" class="oh-dropdown__link"
|
||||
>{% trans "Validate" %}</a
|
||||
>
|
||||
</li>
|
||||
{% endif %} {% if perms.attendance.change_attendance or request.user|is_reportingmanager %}
|
||||
<li class="oh-dropdown__item">
|
||||
<a href="#" id="approveOt" class="oh-dropdown__link">{% trans "Approve OT" %}</a
|
||||
>
|
||||
</li>
|
||||
{% endif %} {% if perms.attendance.delete_attendance %}
|
||||
<li class="oh-dropdown__item">
|
||||
<a href="#" id="bulkDelete" class="oh-dropdown__link oh-dropdown__link--danger" >{% trans "Delete" %}</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
{% if perms.attendance.add_attendance or request.user|is_reportingmanager%}
|
||||
<button
|
||||
class="oh-btn oh-btn--secondary ml-2"
|
||||
data-toggle="oh-modal-toggle"
|
||||
data-target="#addAttendance"
|
||||
hx-get="{% url 'attendance-create' %}"
|
||||
hx-target="#addAttendanceModalBody"
|
||||
>
|
||||
<ion-icon name="add-sharp" class="mr-1"></ion-icon>{% trans "Create" %}
|
||||
</button>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
<script>
|
||||
$("#attendance-search").keydown(function (e) {
|
||||
var val = $(this).val();
|
||||
$(".pg").attr("hx-vals", `{"search":${val}}`);
|
||||
});
|
||||
</script>
|
||||
</section>
|
||||
@@ -0,0 +1,31 @@
|
||||
{% extends 'index.html' %}
|
||||
{% block content %}
|
||||
{% load static %}
|
||||
{% load i18n %}
|
||||
{% include 'attendance/attendance/attendance_nav.html' %}
|
||||
<div class="oh-wrapper" id='attendance-container'>
|
||||
<div class="oh-tabs">
|
||||
<ul class="oh-tabs__tablist">
|
||||
<li class="oh-tabs__tab" data-target="#tab_1">
|
||||
{% trans "Validate Attendances" %}
|
||||
|
||||
</li>
|
||||
<li class="oh-tabs__tab oh-tabs__tab" data-target="#tab_3">
|
||||
{% trans "OT Attendances" %}
|
||||
|
||||
</li>
|
||||
<li class="oh-tabs__tab" data-target="#tab_2">
|
||||
{% trans "Validated Attendances" %}
|
||||
</li>
|
||||
</ul>
|
||||
<div class="oh-tabs__contents" id='tab_contents'>
|
||||
{% include 'attendance/attendance/tab_content.html' %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
{% endblock content %}
|
||||
86
attendance/templates/attendance/attendance/form.html
Normal file
86
attendance/templates/attendance/attendance/form.html
Normal file
@@ -0,0 +1,86 @@
|
||||
{% load i18n %}
|
||||
<form hx-post="{% url 'attendance-create' %}" hx-target="#addAttendanceModalBody" id="addForm">
|
||||
<div class="row">
|
||||
<div class="col-12">
|
||||
<label class="oh-label" for="firstname">{% trans "Employee" %}</label>
|
||||
{{form.employee_id}}
|
||||
{{form.employee_id.errors}}
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-12 col-sm-12 col-md-12 col-lg-6">
|
||||
<label class="oh-label" for="lastname">{% trans "Shift" %}</label>
|
||||
{{form.shift_id}}
|
||||
{{form.shift_id.errors}}
|
||||
</div>
|
||||
<div class="col-12 col-sm-12 col-md-12 col-lg-6">
|
||||
<label class="oh-label" for="lastname">{% trans "Work Type" %}</label>
|
||||
{{form.work_type_id}}
|
||||
{{form.work_type_id.errors}}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-12 col-sm-12 col-md-12 col-lg-6">
|
||||
<label class="oh-label" for="lastname">{% trans "Attendance Date" %}</label>
|
||||
{{form.attendance_date}}
|
||||
{{form.attendance_date.errors}}
|
||||
</div>
|
||||
<div class="col-12 col-sm-12 col-md-12 col-lg-6">
|
||||
<label class="oh-label" for="lastname">{% trans "Minimum Hour" %}</label>
|
||||
{{form.minimum_hour}}
|
||||
{{form.minimum_hour.errors}}
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-12 col-sm-12 col-md-12 col-lg-6">
|
||||
<label class="oh-label" for="lastname">{% trans "Check-In Date" %}</label>
|
||||
{{form.attendance_clock_in_date}}
|
||||
{{form.attendance_clock_in_date.errors}}
|
||||
</div>
|
||||
<div class="col-12 col-sm-12 col-md-12 col-lg-6">
|
||||
<label class="oh-label" for="lastname">{% trans "In Time" %}</label>
|
||||
{{form.attendance_clock_in}}
|
||||
{{form.attendance_clock_in.errors}}
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-12 col-sm-12 col-md-12 col-lg-6">
|
||||
<label class="oh-label" for="lastname">{% trans "Check-Out Date" %}</label>
|
||||
{{form.attendance_clock_out_date}}
|
||||
{{form.attendance_clock_out_date.errors}}
|
||||
</div>
|
||||
<div class="col-12 col-sm-12 col-md-12 col-lg-6">
|
||||
<label class="oh-label" for="lastname">{% trans "Out Time" %}</label>
|
||||
{{form.attendance_clock_out}}
|
||||
{{form.attendance_clock_out.errors}}
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-12 col-sm-12 col-md-12 col-lg-6">
|
||||
<label class="oh-label" for="lastname">{% trans "At Work" %}</label>
|
||||
{{form.attendance_worked_hour}}
|
||||
{{form.attendance_worked_hour.errors}}
|
||||
</div>
|
||||
<div class="col-12 col-sm-12 col-md-12 col-lg-6">
|
||||
<label class="oh-label" for="lastname">{% trans "Overtime" %}</label>
|
||||
{{form.attendance_overtime}}
|
||||
{{form.attendance_overtime.errors}}
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-12 col-sm-12 col-md-12 col-lg-6">
|
||||
<div class="row">
|
||||
<div class="col-12 col-sm-12 col-md-12 col-lg-6">
|
||||
<label class="oh-label" for="lastname">{% trans "Validated" %}</label><br>
|
||||
{{form.attendance_validated}}
|
||||
{{form.attendance_validated.errors}}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="oh-modal__dialog-footer">
|
||||
<input type="submit" value='{% trans "Add Attendance" %}' class="oh-btn oh-btn--secondary oh-btn--shadow">
|
||||
</div>
|
||||
</form>
|
||||
549
attendance/templates/attendance/attendance/group_by.html
Normal file
549
attendance/templates/attendance/attendance/group_by.html
Normal file
@@ -0,0 +1,549 @@
|
||||
{% load attendancefilters %}
|
||||
{% load basefilters %}
|
||||
{% load static %}
|
||||
{% load i18n %}
|
||||
|
||||
|
||||
|
||||
<div class="oh-tabs__content" id="tab_2">
|
||||
|
||||
<div class="oh-sticky-table">
|
||||
<div class="oh-sticky-table__table oh-table__sticky-collaspable-sort">
|
||||
<div class="oh-sticky-table__thead">
|
||||
<div class="oh-sticky-table__tr">
|
||||
<div class="oh-sticky-table__th">{% trans "Employee" %}</div>
|
||||
<div class="oh-sticky-table__th">{% trans "Date" %}</div>
|
||||
<div class="oh-sticky-table__th">{% trans "Day" %}</div>
|
||||
<div class="oh-sticky-table__th">{% trans "Clock In" %}</div>
|
||||
<div class="oh-sticky-table__th">{% trans "In Date" %}</div>
|
||||
<div class="oh-sticky-table__th">{% trans "Clock Out" %}</div>
|
||||
<div class="oh-sticky-table__th">{% trans "Out Date" %}</div>
|
||||
<div class="oh-sticky-table__th">{% trans "Shift" %}</div>
|
||||
<div class="oh-sticky-table__th">{% trans "Min Hour" %}</div>
|
||||
<div class="oh-sticky-table__th">{% trans "At Work" %}</div>
|
||||
<div class="oh-sticky-table__th">{% trans "Overtime" %}</div>
|
||||
<div class="oh-sticky-table__th"></div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- grouper -->
|
||||
{% dynamic_regroup attendances by field as attendances_grouper %}
|
||||
{% for attendance_list in attendances_grouper %}
|
||||
<div class="oh-sticky-table__tbody" draggable="true">
|
||||
<div class="oh-sticky-table__tr oh-table__toggle-parent" data-target="{{attendance_list.grouper}}">
|
||||
<div class="oh-sticky-table__sd">
|
||||
<div class="d-flex align-items-center">
|
||||
<button class="oh-btn oh-btn--transparent oh-table__toggle-button"></button>
|
||||
<span class="ms-2">
|
||||
|
||||
<div class="oh-tabs__input-badge-container">
|
||||
<span class="oh-badge oh-badge--secondary oh-badge--small oh-badge--round mr-1">
|
||||
{{attendance_list.list|length}}
|
||||
</span>
|
||||
{{attendance_list.grouper}}
|
||||
</div>
|
||||
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="oh-sticky-table__td"></div>
|
||||
<div class="oh-sticky-table__td"></div>
|
||||
<div class="oh-sticky-table__td"></div>
|
||||
<div class="oh-sticky-table__td"></div>
|
||||
<div class="oh-sticky-table__td"></div>
|
||||
<div class="oh-sticky-table__td"></div>
|
||||
<div class="oh-sticky-table__td"></div>
|
||||
<div class="oh-sticky-table__td"></div>
|
||||
<div class="oh-sticky-table__td"></div>
|
||||
<div class="oh-sticky-table__td"></div>
|
||||
<div class="oh-sticky-table__td"></div>
|
||||
</div>
|
||||
<!-- data -->
|
||||
{% for attendance in attendance_list.list %}
|
||||
<div class="oh-sticky-table__tr oh-table__toggle-child" data-group="{{attendance_list.grouper}}">
|
||||
|
||||
<div class="oh-sticky-table__sd">
|
||||
<div class="oh-profile oh-profile--md">
|
||||
<div class="oh-profile__avatar mr-1">
|
||||
<img
|
||||
src="https://ui-avatars.com/api/?name={{attendance.employee_id.employee_first_name}}+{{attendance.employee_id.employee_last_name}}&background=random"
|
||||
class="oh-profile__image"
|
||||
alt="Mary Magdalene"
|
||||
/>
|
||||
</div>
|
||||
<span class="oh-profile__name oh-text--dark"
|
||||
>{{attendance.employee_id}}</span
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
<div class="oh-sticky-table__td">
|
||||
{{attendance.attendance_date}}
|
||||
</div>
|
||||
<div class="oh-sticky-table__td">
|
||||
{{attendance.attendance_day|title}}
|
||||
</div>
|
||||
<div class="oh-sticky-table__td">
|
||||
{{attendance.attendance_clock_in}}
|
||||
</div>
|
||||
<div class="oh-sticky-table__td">
|
||||
{{attendance.attendance_clock_in_date}}
|
||||
</div>
|
||||
<div class="oh-sticky-table__td">
|
||||
{{attendance.attendance_clock_out}}
|
||||
</div>
|
||||
<div class="oh-sticky-table__td">
|
||||
{{attendance.attendance_clock_out_date}}
|
||||
</div>
|
||||
<div class="oh-sticky-table__td">{{attendance.shift_id}}</div>
|
||||
<div class="oh-sticky-table__td">
|
||||
{{attendance.work_type_id}}
|
||||
</div>
|
||||
<div class="oh-sticky-table__td">
|
||||
{{attendance.attendance_worked_hour}}
|
||||
</div>
|
||||
<div class="oh-sticky-table__td">
|
||||
{{attendance.attendance_overtime}}
|
||||
</div>
|
||||
<div class="oh-sticky-table__td">
|
||||
<div class="oh-btn-group">
|
||||
{% if perms.attendance.change_attendance or request.user|is_reportingmanager %}
|
||||
<a hx-get="{% url 'attendance-update' attendance.id %}" hx-target='#updateAttendanceModalBody' hx-swap='innerHTML' data-toggle='oh-modal-toggle' data-target='#updateAttendanceModal' class="oh-btn oh-btn--light-bkg w-100" title="{% trans 'Edit' %}"><ion-icon name="create-outline"></ion-icon></a>
|
||||
{% endif %}
|
||||
{% if perms.attendance.delete_attendance %}
|
||||
<form hx-post="{% url 'attendance-delete' attendance.id %}?{{pd}}&opage={{overtime_attendances.number}}&vpage={{validate_attendances.number}}&page={{attendances.number}}" hx-confirm="Are you sure want to delete this attendance?" hx-target="#tab_contents" method='post'>
|
||||
{% csrf_token %}
|
||||
<button type='submit' class="oh-btn oh-btn--danger-outline oh-btn--light-bkg w-100" title="{% trans 'Remove' %}"><ion-icon name="trash-outline"></ion-icon></button>
|
||||
</form>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="oh-pagination">
|
||||
<span
|
||||
class="oh-pagination__page"
|
||||
>
|
||||
{% trans "Page" %} {{ attendances.number }} {% trans "of" %} {{ attendances.paginator.num_pages }}.
|
||||
</span
|
||||
>
|
||||
<nav class="oh-pagination__nav">
|
||||
<div class="oh-pagination__input-container me-3">
|
||||
<span class="oh-pagination__label me-1">{% trans "Page" %}</span>
|
||||
<input
|
||||
type="number"
|
||||
name="page"
|
||||
class="oh-pagination__input"
|
||||
value="{{attendances.number}}"
|
||||
hx-get="{% url 'attendance-search' %}?{{pd}}"
|
||||
hx-target="#tab_contents"
|
||||
min="1"
|
||||
/>
|
||||
<span class="oh-pagination__label">{% trans "of" %} {{attendances.paginator.num_pages}}</span>
|
||||
</div>
|
||||
<ul class="oh-pagination__items">
|
||||
{% if attendances.has_previous %}
|
||||
<li class="oh-pagination__item oh-pagination__item--wide">
|
||||
<a hx-target='#tab_contents' hx-get="{% url 'attendance-search' %}?{{pd}}&page=1" class="oh-pagination__link">{% trans "First" %}</a>
|
||||
</li>
|
||||
<li class="oh-pagination__item oh-pagination__item--wide">
|
||||
<a hx-target='#tab_contents' hx-get="{% url 'attendance-search' %}?{{pd}}&page={{ attendances.previous_page_number }}" class="oh-pagination__link">{% trans "Previous" %}</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
{% if attendances.has_next %}
|
||||
<li class="oh-pagination__item oh-pagination__item--wide">
|
||||
<a hx-target='#tab_contents' hx-get="{% url 'attendance-search' %}?{{pd}}&page={{ attendances.next_page_number }}" class="oh-pagination__link">{% trans "Next" %}</a>
|
||||
</li>
|
||||
<li class="oh-pagination__item oh-pagination__item--wide">
|
||||
<a hx-target='#tab_contents' hx-get="{% url 'attendance-search' %}?{{pd}}&page={{ attendances.paginator.num_pages }}" class="oh-pagination__link">{% trans "Last" %}</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
</ul>
|
||||
</nav>
|
||||
</div>
|
||||
<!-- End of Sticky Table -->
|
||||
</div>
|
||||
<div class="oh-tabs__content" id="tab_1">
|
||||
|
||||
<div class="oh-sticky-table">
|
||||
<div class="oh-sticky-table__table oh-table__sticky-collaspable-sort">
|
||||
<div class="oh-sticky-table__thead">
|
||||
<div class="oh-sticky-table__tr">
|
||||
<div class="oh-sticky-table__th">{% trans "Employee" %}</div>
|
||||
<div class="oh-sticky-table__th">{% trans "Date" %}</div>
|
||||
<div class="oh-sticky-table__th">{% trans "Day" %}</div>
|
||||
<div class="oh-sticky-table__th">{% trans "Clock In" %}</div>
|
||||
<div class="oh-sticky-table__th">{% trans "In Date" %}</div>
|
||||
<div class="oh-sticky-table__th">{% trans "Clock Out" %}</div>
|
||||
<div class="oh-sticky-table__th">{% trans "Out Date" %}</div>
|
||||
<div class="oh-sticky-table__th">{% trans "Shift" %}</div>
|
||||
<div class="oh-sticky-table__th">{% trans "Min Hour" %}</div>
|
||||
<div class="oh-sticky-table__th">{% trans "At Work" %}</div>
|
||||
<div class="oh-sticky-table__th">{% trans "Overtime" %}</div>
|
||||
<div class="oh-sticky-table__th"></div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- grouper -->
|
||||
{% dynamic_regroup validate_attendances by field as attendances_grouper %}
|
||||
{% for attendance_list in attendances_grouper %}
|
||||
<div class="oh-sticky-table__tbody" draggable="true">
|
||||
<div class="oh-sticky-table__tr oh-table__toggle-parent" data-target="{{attendance_list.grouper}}validate">
|
||||
<div class="oh-sticky-table__sd">
|
||||
<div class="d-flex align-items-center">
|
||||
<button class="oh-btn oh-btn--transparent oh-table__toggle-button"></button>
|
||||
<span class="ms-2">
|
||||
<div class="oh-tabs__input-badge-container">
|
||||
<span class="oh-badge oh-badge--secondary oh-badge--small oh-badge--round mr-1">
|
||||
{{attendance_list.list|length}}
|
||||
</span>
|
||||
{{attendance_list.grouper}}
|
||||
</div></span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="oh-sticky-table__td"></div>
|
||||
<div class="oh-sticky-table__td"></div>
|
||||
<div class="oh-sticky-table__td"></div>
|
||||
<div class="oh-sticky-table__td"></div>
|
||||
<div class="oh-sticky-table__td"></div>
|
||||
<div class="oh-sticky-table__td"></div>
|
||||
<div class="oh-sticky-table__td"></div>
|
||||
<div class="oh-sticky-table__td"></div>
|
||||
<div class="oh-sticky-table__td"></div>
|
||||
<div class="oh-sticky-table__td"></div>
|
||||
<div class="oh-sticky-table__td"></div>
|
||||
</div>
|
||||
<!-- data -->
|
||||
{% for attendance in attendance_list.list %}
|
||||
<div class="oh-sticky-table__tr oh-table__toggle-child" data-group="{{attendance_list.grouper}}validate">
|
||||
|
||||
<div class="oh-sticky-table__sd">
|
||||
<div class="oh-profile oh-profile--md">
|
||||
<div class="oh-profile__avatar mr-1">
|
||||
<img
|
||||
src="https://ui-avatars.com/api/?name={{attendance.employee_id.employee_first_name}}+{{attendance.employee_id.employee_last_name}}&background=random"
|
||||
class="oh-profile__image"
|
||||
alt="Mary Magdalene"
|
||||
/>
|
||||
</div>
|
||||
<span class="oh-profile__name oh-text--dark"
|
||||
>{{attendance.employee_id}}</span
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
<div class="oh-sticky-table__td">
|
||||
{{attendance.attendance_date}}
|
||||
</div>
|
||||
<div class="oh-sticky-table__td">
|
||||
{{attendance.attendance_day|title}}
|
||||
</div>
|
||||
<div class="oh-sticky-table__td">
|
||||
{{attendance.attendance_clock_in}}
|
||||
</div>
|
||||
<div class="oh-sticky-table__td">
|
||||
{{attendance.attendance_clock_in_date}}
|
||||
</div>
|
||||
<div class="oh-sticky-table__td">
|
||||
{{attendance.attendance_clock_out}}
|
||||
</div>
|
||||
<div class="oh-sticky-table__td">
|
||||
{{attendance.attendance_clock_out_date}}
|
||||
</div>
|
||||
<div class="oh-sticky-table__td">{{attendance.shift_id}}</div>
|
||||
<div class="oh-sticky-table__td">
|
||||
{{attendance.work_type_id}}
|
||||
</div>
|
||||
<div class="oh-sticky-table__td">
|
||||
{{attendance.attendance_worked_hour}}
|
||||
</div>
|
||||
<div class="oh-sticky-table__td">
|
||||
{{attendance.attendance_overtime}}
|
||||
</div>
|
||||
<div class="oh-sticky-table__td">
|
||||
<div class="oh-btn-group">
|
||||
{% if perms.attendance.change_attendance or request.user|is_reportingmanager %}
|
||||
<a hx-get="{% url 'attendance-update' attendance.id %}" hx-target='#updateAttendanceModalBody' hx-swap='innerHTML' data-toggle='oh-modal-toggle' data-target='#updateAttendanceModal' class="oh-btn oh-btn--light-bkg w-100" title="{% trans 'Edit' %}"><ion-icon name="create-outline"></ion-icon></a>
|
||||
{% endif %}
|
||||
{% if perms.attendance.delete_attendance %}
|
||||
<form hx-post="{% url 'attendance-delete' attendance.id %}?{{pd}}&opage={{overtime_attendances.number}}&vpage={{validate_attendances.number}}&page={{attendances.number}}" hx-confirm="Are you sure want to delete this attendance?" hx-target="#tab_contents" method='post'>
|
||||
{% csrf_token %}
|
||||
<button type='submit' class="oh-btn oh-btn--danger-outline oh-btn--light-bkg w-100" title="{% trans 'Remove' %}"><ion-icon name="trash-outline"></ion-icon></button>
|
||||
</form>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="oh-pagination">
|
||||
<span
|
||||
class="oh-pagination__page"
|
||||
>
|
||||
{% trans "Page" %} {{ validate_attendances.number }} {% trans "of" %} {{ validate_attendances.paginator.num_pages }}.
|
||||
</span
|
||||
>
|
||||
<nav class="oh-pagination__nav">
|
||||
<div class="oh-pagination__input-container me-3">
|
||||
<span class="oh-pagination__label me-1">{% trans "Page" %}</span>
|
||||
<input
|
||||
type="number"
|
||||
name="vpage"
|
||||
class="oh-pagination__input"
|
||||
value="{{validate_attendances.number}}"
|
||||
hx-get="{% url 'attendance-search' %}?{{pd}}"
|
||||
hx-target="#tab_contents"
|
||||
min="1"
|
||||
/>
|
||||
<span class="oh-pagination__label">{% trans "of" %} {{validate_attendances.paginator.num_pages}}</span>
|
||||
</div>
|
||||
<ul class="oh-pagination__items">
|
||||
{% if validate_attendances.has_previous %}
|
||||
<li class="oh-pagination__item oh-pagination__item--wide">
|
||||
<a hx-target='#tab_contents' hx-get="{% url 'attendance-search' %}?{{pd}}&vpage=1" class="oh-pagination__link">{% trans "First" %}</a>
|
||||
</li>
|
||||
<li class="oh-pagination__item oh-pagination__item--wide">
|
||||
<a hx-target='#tab_contents' hx-get="{% url 'attendance-search' %}?{{pd}}&vpage={{ validate_attendances.previous_page_number }}" class="oh-pagination__link">{% trans "Previous" %}</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
{% if validate_attendances.has_next %}
|
||||
<li class="oh-pagination__item oh-pagination__item--wide">
|
||||
<a hx-target='#tab_contents' hx-get="{% url 'attendance-search' %}?{{pd}}&vpage={{ validate_attendances.next_page_number }}" class="oh-pagination__link">{% trans "Next" %}</a>
|
||||
</li>
|
||||
<li class="oh-pagination__item oh-pagination__item--wide">
|
||||
<a hx-target='#tab_contents' hx-get="{% url 'attendance-search' %}?{{pd}}&vpage={{ validate_attendances.paginator.num_pages }}" class="oh-pagination__link">{% trans "Last" %}</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
</ul>
|
||||
</nav>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<div class="oh-tabs__content" id="tab_3">
|
||||
|
||||
<div class="oh-sticky-table">
|
||||
<div class="oh-sticky-table__table oh-table__sticky-collaspable-sort">
|
||||
<div class="oh-sticky-table__thead">
|
||||
<div class="oh-sticky-table__tr">
|
||||
<div class="oh-sticky-table__th">{% trans "Employee" %}</div>
|
||||
<div class="oh-sticky-table__th">{% trans "Date" %}</div>
|
||||
<div class="oh-sticky-table__th">{% trans "Day" %}</div>
|
||||
<div class="oh-sticky-table__th">{% trans "Clock In" %}</div>
|
||||
<div class="oh-sticky-table__th">{% trans "In Date" %}</div>
|
||||
<div class="oh-sticky-table__th">{% trans "Clock Out" %}</div>
|
||||
<div class="oh-sticky-table__th">{% trans "Out Date" %}</div>
|
||||
<div class="oh-sticky-table__th">{% trans "Shift" %}</div>
|
||||
<div class="oh-sticky-table__th">{% trans "Min Hour" %}</div>
|
||||
<div class="oh-sticky-table__th">{% trans "At Work" %}</div>
|
||||
<div class="oh-sticky-table__th">{% trans "Overtime" %}</div>
|
||||
<div class="oh-sticky-table__th"></div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- grouper -->
|
||||
{% dynamic_regroup overtime_attendances by field as attendances_grouper %}
|
||||
{% for attendance_list in attendances_grouper %}
|
||||
<div class="oh-sticky-table__tbody" draggable="true">
|
||||
<div class="oh-sticky-table__tr oh-table__toggle-parent" data-target="{{attendance_list.grouper}}ot">
|
||||
<div class="oh-sticky-table__sd">
|
||||
<div class="d-flex align-items-center">
|
||||
<button class="oh-btn oh-btn--transparent oh-table__toggle-button"></button>
|
||||
<span class="ms-2">
|
||||
<div class="oh-tabs__input-badge-container">
|
||||
<span class="oh-badge oh-badge--secondary oh-badge--small oh-badge--round mr-1">
|
||||
{{attendance_list.list|length}}
|
||||
</span>
|
||||
{{attendance_list.grouper}}
|
||||
</div>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="oh-sticky-table__td"></div>
|
||||
<div class="oh-sticky-table__td"></div>
|
||||
<div class="oh-sticky-table__td"></div>
|
||||
<div class="oh-sticky-table__td"></div>
|
||||
<div class="oh-sticky-table__td"></div>
|
||||
<div class="oh-sticky-table__td"></div>
|
||||
<div class="oh-sticky-table__td"></div>
|
||||
<div class="oh-sticky-table__td"></div>
|
||||
<div class="oh-sticky-table__td"></div>
|
||||
<div class="oh-sticky-table__td"></div>
|
||||
<div class="oh-sticky-table__td"></div>
|
||||
</div>
|
||||
<!-- data -->
|
||||
{% for attendance in attendance_list.list %}
|
||||
<div class="oh-sticky-table__tr oh-table__toggle-child" data-group="{{attendance_list.grouper}}ot">
|
||||
|
||||
<div class="oh-sticky-table__sd">
|
||||
<div class="oh-profile oh-profile--md">
|
||||
<div class="oh-profile__avatar mr-1">
|
||||
<img
|
||||
src="https://ui-avatars.com/api/?name={{attendance.employee_id.employee_first_name}}+{{attendance.employee_id.employee_last_name}}&background=random"
|
||||
class="oh-profile__image"
|
||||
alt="Mary Magdalene"
|
||||
/>
|
||||
</div>
|
||||
<span class="oh-profile__name oh-text--dark"
|
||||
>{{attendance.employee_id}}</span
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
<div class="oh-sticky-table__td">
|
||||
{{attendance.attendance_date}}
|
||||
</div>
|
||||
<div class="oh-sticky-table__td">
|
||||
{{attendance.attendance_day|title}}
|
||||
</div>
|
||||
<div class="oh-sticky-table__td">
|
||||
{{attendance.attendance_clock_in}}
|
||||
</div>
|
||||
<div class="oh-sticky-table__td">
|
||||
{{attendance.attendance_clock_in_date}}
|
||||
</div>
|
||||
<div class="oh-sticky-table__td">
|
||||
{{attendance.attendance_clock_out}}
|
||||
</div>
|
||||
<div class="oh-sticky-table__td">
|
||||
{{attendance.attendance_clock_out_date}}
|
||||
</div>
|
||||
<div class="oh-sticky-table__td">{{attendance.shift_id}}</div>
|
||||
<div class="oh-sticky-table__td">
|
||||
{{attendance.work_type_id}}
|
||||
</div>
|
||||
<div class="oh-sticky-table__td">
|
||||
{{attendance.attendance_worked_hour}}
|
||||
</div>
|
||||
<div class="oh-sticky-table__td">
|
||||
{{attendance.attendance_overtime}}
|
||||
</div>
|
||||
<div class="oh-sticky-table__td">
|
||||
<div class="oh-btn-group">
|
||||
{% if perms.attendance.change_attendance or request.user|is_reportingmanager %}
|
||||
<a hx-get="{% url 'attendance-update' attendance.id %}" hx-target='#updateAttendanceModalBody' hx-swap='innerHTML' data-toggle='oh-modal-toggle' data-target='#updateAttendanceModal' class="oh-btn oh-btn--light-bkg w-100" title="{% trans 'Edit' %}"><ion-icon name="create-outline"></ion-icon></a>
|
||||
{% endif %}
|
||||
{% if perms.attendance.delete_attendance %}
|
||||
<form hx-post="{% url 'attendance-delete' attendance.id %}?{{pd}}&opage={{overtime_attendances.number}}&vpage={{validate_attendances.number}}&page={{attendances.number}}" hx-confirm="Are you sure want to delete this attendance?" hx-target="#tab_contents" method='post'>
|
||||
{% csrf_token %}
|
||||
<button type='submit' class="oh-btn oh-btn--danger-outline oh-btn--light-bkg w-100" title="{% trans 'Remove' %}"><ion-icon name="trash-outline"></ion-icon></button>
|
||||
</form>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="oh-pagination">
|
||||
<span
|
||||
class="oh-pagination__page"
|
||||
>
|
||||
{% trans "Page" %} {{ overtime_attendances.number }} {% trans "of" %} {{ overtime_attendances.paginator.num_pages }}.
|
||||
</span
|
||||
>
|
||||
<nav class="oh-pagination__nav">
|
||||
<div class="oh-pagination__input-container me-3">
|
||||
<span class="oh-pagination__label me-1">{% trans "Page" %}</span>
|
||||
<input
|
||||
type="number"
|
||||
name="opage"
|
||||
class="oh-pagination__input"
|
||||
value="{{overtime_attendances.number}}"
|
||||
hx-get="{% url 'attendance-search' %}?{{pd}}"
|
||||
hx-target="#tab_contents"
|
||||
min="1"
|
||||
/>
|
||||
<span class="oh-pagination__label">{% trans "of" %} {{overtime_attendances.paginator.num_pages}}</span>
|
||||
</div>
|
||||
<ul class="oh-pagination__items">
|
||||
{% if overtime_attendances.has_previous %}
|
||||
<li class="oh-pagination__item oh-pagination__item--wide">
|
||||
<a hx-target='#tab_contents' hx-get="{% url 'attendance-search' %}?{{pd}}&opage=1" class="oh-pagination__link">{% trans "First" %}</a>
|
||||
</li>
|
||||
<li class="oh-pagination__item oh-pagination__item--wide">
|
||||
<a hx-target='#tab_contents' hx-get="{% url 'attendance-search' %}?{{pd}}&opage={{ overtime_attendances.previous_page_number }}" class="oh-pagination__link">{% trans "Previous" %}</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
{% if overtime_attendances.has_next %}
|
||||
<li class="oh-pagination__item oh-pagination__item--wide">
|
||||
<a hx-target='#tab_contents' hx-get="{% url 'attendance-search' %}?{{pd}}&opage={{ overtime_attendances.next_page_number }}" class="oh-pagination__link">{% trans "Next" %}</a>
|
||||
</li>
|
||||
<li class="oh-pagination__item oh-pagination__item--wide">
|
||||
<a hx-target='#tab_contents' hx-get="{% url 'attendance-search' %}?{{pd}}&opage={{ overtime_attendances.paginator.num_pages }}" class="oh-pagination__link">{% trans "Last" %}</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
</ul>
|
||||
</nav>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
{% if perms.attendance.change_attendance %}
|
||||
<div
|
||||
class="oh-modal"
|
||||
id="updateAttendanceModal"
|
||||
role="dialog"
|
||||
aria-labelledby="updateAttendanceModal"
|
||||
aria-hidden="true"
|
||||
>
|
||||
<div class="oh-modal__dialog">
|
||||
<div class="oh-modal__dialog-header">
|
||||
<h2 class="oh-modal__dialog-title" id="updateAttendanceModalLabel">
|
||||
Edit Attendance
|
||||
</h2>
|
||||
<button class="oh-modal__close" aria-label="Close" hx-get="{% url 'attendance-search' %}?{{pd}}&opage={{overtime_attendances.number}}&vpage={{validate_attendances.number}}&page={{attendances.number}}" hx-target="#tab_contents">
|
||||
<ion-icon name="close-outline"></ion-icon>
|
||||
</button>
|
||||
</div>
|
||||
<div id="updateAttendanceModalBody">
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<script>
|
||||
|
||||
$('.oh-table__sticky-collaspable-sort').click(function (e) {
|
||||
e.preventDefault();
|
||||
let clickedEl = $(e.target).closest(".oh-table__toggle-parent");
|
||||
let targetSelector = clickedEl.data("target");
|
||||
let toggleBtn = clickedEl.find(".oh-table__toggle-button");
|
||||
$(`[data-group='${targetSelector}']`).toggleClass(
|
||||
"oh-table__toggle-child--show"
|
||||
);
|
||||
if (toggleBtn) {
|
||||
toggleBtn.toggleClass("oh-table__toggle-button--show");
|
||||
}
|
||||
});
|
||||
$(document).ready(function () {
|
||||
var activeTab = localStorage.getItem('activeTabAttendance')
|
||||
if (activeTab != null) {
|
||||
var tab = $(`[data-target="${activeTab}"]`)
|
||||
var tabContent = $(activeTab)
|
||||
$(tab).attr('class', 'oh-tabs__tab oh-tabs__tab--active');
|
||||
$(tabContent).attr('class', 'oh-tabs__content oh-tabs__content--active');
|
||||
}
|
||||
else{
|
||||
$('[data-target="#tab_1"]').attr('class', 'oh-tabs__tab oh-tabs__tab--active');
|
||||
$('#tab_1').attr('class', 'oh-tabs__content oh-tabs__content--active');
|
||||
}
|
||||
$('.oh-tabs__tab').click(function (e) {
|
||||
var activeTab = $(this).attr('data-target');
|
||||
localStorage.setItem('activeTabAttendance',activeTab)
|
||||
|
||||
});
|
||||
});
|
||||
|
||||
</script>
|
||||
496
attendance/templates/attendance/attendance/tab_content.html
Normal file
496
attendance/templates/attendance/attendance/tab_content.html
Normal file
@@ -0,0 +1,496 @@
|
||||
{% load i18n %}
|
||||
{% load attendancefilters %}
|
||||
{% load basefilters %}
|
||||
|
||||
<div class="oh-tabs__content" id="tab_2">
|
||||
<!-- Sticky Table -->
|
||||
<div class="oh-sticky-table">
|
||||
<div class="oh-sticky-table__table oh-table--sortable">
|
||||
<div class="oh-sticky-table__thead">
|
||||
<div class="oh-sticky-table__tr">
|
||||
<div class="oh-sticky-table__th" >
|
||||
<div class="d-flex">
|
||||
<div class="">
|
||||
<input type="checkbox" class="oh-input oh-input__checkbox mt-1 mr-2 all-attendances" />
|
||||
</div>
|
||||
<div hx-get="{% url 'attendance-search' %}?{{pd}}&sortby=employee_id__employee_first_name" hx-target="#tab_contents">
|
||||
{% trans "Employee" %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="oh-sticky-table__th" hx-get="{% url 'attendance-search' %}?{{pd}}&sortby=attendance_date" hx-target="#tab_contents">{% trans "Date" %}</div>
|
||||
<div class="oh-sticky-table__th">{% trans "Day" %}</div>
|
||||
<div class="oh-sticky-table__th" >{% trans "Clock In" %}</div>
|
||||
<div class="oh-sticky-table__th" hx-get="{% url 'attendance-search' %}?{{pd}}&sortby=attendance_clock_in_date" hx-target="#tab_contents">{% trans "In Date" %}</div>
|
||||
<div class="oh-sticky-table__th">{% trans "Clock Out" %}</div>
|
||||
<div class="oh-sticky-table__th" hx-get="{% url 'attendance-search' %}?{{pd}}&sortby=attendance_clock_out_date" hx-target="#tab_contents">{% trans "Out Date" %}</div>
|
||||
<div class="oh-sticky-table__th">{% trans "Shift" %}</div>
|
||||
<div class="oh-sticky-table__th">{% trans "Min Hour" %}</div>
|
||||
<div class="oh-sticky-table__th" hx-get="{% url 'attendance-search' %}?{{pd}}&sortby=at_work_second" hx-target="#tab_contents">{% trans "At Work" %}</div>
|
||||
<div class="oh-sticky-table__th" hx-get="{% url 'attendance-search' %}?{{pd}}&sortby=overtime_second" hx-target="#tab_contents">{% trans "Overtime" %}</div>
|
||||
<div class="oh-sticky-table__th"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="oh-sticky-table__tbody">
|
||||
{% for attendance in attendances %}
|
||||
<div class="oh-sticky-table__tr"
|
||||
draggable="false">
|
||||
<div class="oh-sticky-table__sd">
|
||||
<div class="d-flex">
|
||||
|
||||
<div class="">
|
||||
<input type="checkbox" id="{{attendance.id}}" class="oh-input attendance-checkbox oh-input__checkbox mt-2 mr-2 all-attendance-row" />
|
||||
</div>
|
||||
<div class="oh-profile oh-profile--md">
|
||||
<div class="oh-profile__avatar mr-1">
|
||||
<img
|
||||
src="https://ui-avatars.com/api/?name={{attendance.employee_id.employee_first_name}}+{{attendance.employee_id.employee_last_name}}&background=random"
|
||||
class="oh-profile__image"
|
||||
alt="Mary Magdalene"
|
||||
/>
|
||||
</div>
|
||||
<span class="oh-profile__name oh-text--dark"
|
||||
>{{attendance.employee_id}}</span
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="oh-sticky-table__td">
|
||||
{{attendance.attendance_date}}
|
||||
</div>
|
||||
<div class="oh-sticky-table__td">
|
||||
{{attendance.attendance_day|capfirst}}
|
||||
</div>
|
||||
<div class="oh-sticky-table__td">
|
||||
{{attendance.attendance_clock_in}}
|
||||
</div>
|
||||
<div class="oh-sticky-table__td">
|
||||
{{attendance.attendance_clock_in_date}}
|
||||
</div>
|
||||
<div class="oh-sticky-table__td">
|
||||
{{attendance.attendance_clock_out}}
|
||||
</div>
|
||||
<div class="oh-sticky-table__td">
|
||||
{{attendance.attendance_clock_out_date}}
|
||||
</div>
|
||||
<div class="oh-sticky-table__td">{{attendance.shift_id}}</div>
|
||||
<div class="oh-sticky-table__td">
|
||||
{{attendance.work_type_id}}
|
||||
</div>
|
||||
<div class="oh-sticky-table__td">
|
||||
{{attendance.attendance_worked_hour}}
|
||||
</div>
|
||||
<div class="oh-sticky-table__td">
|
||||
{{attendance.attendance_overtime}}
|
||||
</div>
|
||||
|
||||
<div class="oh-sticky-table__td">
|
||||
<div class="oh-btn-group">
|
||||
{% if perms.attendance.change_attendance or request.user|is_reportingmanager %}
|
||||
<a hx-get="{% url 'attendance-update' attendance.id %}" hx-target='#updateAttendanceModalBody' hx-swap='innerHTML' data-toggle='oh-modal-toggle' data-target='#updateAttendanceModal' class="oh-btn oh-btn--light-bkg w-100" title="{% trans 'Edit' %}"><ion-icon name="create-outline"></ion-icon></a>
|
||||
{% endif %}
|
||||
{% if perms.attendance.delete_attendance %}
|
||||
<form action="{% url 'attendance-delete' attendance.id %}" onsubmit="return confirm('{% trans "Are you sure want to delete this attendance?" %}')" hx-target="#tab_contents" method='post'>
|
||||
{% csrf_token %}
|
||||
<button type='submit' class="oh-btn oh-btn--danger-outline oh-btn--light-bkg w-100" title="{% trans 'Remove' %}"><ion-icon name="trash-outline"></ion-icon></button>
|
||||
</form>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="oh-pagination">
|
||||
<span
|
||||
class="oh-pagination__page"
|
||||
>
|
||||
{% trans "Page" %} {{ attendances.number }} {% trans "of" %} {{ attendances.paginator.num_pages }}.
|
||||
</span
|
||||
>
|
||||
<nav class="oh-pagination__nav">
|
||||
<div class="oh-pagination__input-container me-3">
|
||||
<span class="oh-pagination__label me-1">{% trans "Page" %}</span>
|
||||
<input
|
||||
type="number"
|
||||
name="page"
|
||||
class="oh-pagination__input"
|
||||
value="{{attendances.number}}"
|
||||
hx-get="{% url 'attendance-search' %}?{{pd}}"
|
||||
hx-target="#tab_contents"
|
||||
min="1"
|
||||
/>
|
||||
<span class="oh-pagination__label">{% trans "of" %} {{attendances.paginator.num_pages}}</span>
|
||||
</div>
|
||||
<ul class="oh-pagination__items">
|
||||
{% if attendances.has_previous %}
|
||||
<li class="oh-pagination__item oh-pagination__item--wide">
|
||||
<a hx-target='#tab_contents' hx-get="{% url 'attendance-search' %}?{{pd}}&page=1" class="oh-pagination__link">{% trans "First" %}</a>
|
||||
</li>
|
||||
<li class="oh-pagination__item oh-pagination__item--wide">
|
||||
<a hx-target='#tab_contents' hx-get="{% url 'attendance-search' %}?{{pd}}&page={{ attendances.previous_page_number }}" class="oh-pagination__link">{% trans "Previous" %}</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
{% if attendances.has_next %}
|
||||
<li class="oh-pagination__item oh-pagination__item--wide">
|
||||
<a hx-target='#tab_contents' hx-get="{% url 'attendance-search' %}?{{pd}}&page={{ attendances.next_page_number }}" class="oh-pagination__link">{% trans "Next" %}</a>
|
||||
</li>
|
||||
<li class="oh-pagination__item oh-pagination__item--wide">
|
||||
<a hx-target='#tab_contents' hx-get="{% url 'attendance-search' %}?{{pd}}&page={{ attendances.paginator.num_pages }}" class="oh-pagination__link">{% trans "Last" %}</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
</ul>
|
||||
</nav>
|
||||
</div>
|
||||
|
||||
<!-- End of Sticky Table -->
|
||||
</div>
|
||||
<div class="oh-tabs__content" id="tab_1">
|
||||
<div class="oh-sticky-table">
|
||||
<div class="oh-sticky-table__table oh-table--sortable">
|
||||
<div class="oh-sticky-table__thead">
|
||||
<div class="oh-sticky-table__tr">
|
||||
<div class="oh-sticky-table__th" >
|
||||
<div class="d-flex">
|
||||
<div class="">
|
||||
<input type="checkbox" class="oh-input oh-input__checkbox mt-1 mr-2 validate" />
|
||||
</div>
|
||||
<div hx-get="{% url 'attendance-search' %}?{{pd}}&sortby=employee_id__employee_first_name" hx-target="#tab_contents">
|
||||
{% trans "Employee" %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="oh-sticky-table__th" hx-get="{% url 'attendance-search' %}?{{pd}}&sortby=attendance_date" hx-target="#tab_contents">{% trans "Date" %}</div>
|
||||
<div class="oh-sticky-table__th">{% trans "Day" %}</div>
|
||||
<div class="oh-sticky-table__th" >{% trans "Clock In" %}</div>
|
||||
<div class="oh-sticky-table__th" hx-get="{% url 'attendance-search' %}?{{pd}}&sortby=attendance_clock_in_date" hx-target="#tab_contents">{% trans "In Date" %}</div>
|
||||
<div class="oh-sticky-table__th">{% trans "Clock Out" %}</div>
|
||||
<div class="oh-sticky-table__th" hx-get="{% url 'attendance-search' %}?{{pd}}&sortby=attendance_clock_out_date" hx-target="#tab_contents">{% trans "Out Date" %}</div>
|
||||
<div class="oh-sticky-table__th">{% trans "Shift" %}</div>
|
||||
<div class="oh-sticky-table__th">{% trans "Min Hour" %}</div>
|
||||
<div class="oh-sticky-table__th" hx-get="{% url 'attendance-search' %}?{{pd}}&sortby=at_work_second" hx-target="#tab_contents">{% trans "At Work" %}</div>
|
||||
<div class="oh-sticky-table__th" hx-get="{% url 'attendance-search' %}?{{pd}}&sortby=overtime_second" hx-target="#tab_contents">{% trans "Overtime" %}</div>
|
||||
<div class="oh-sticky-table__th"></div>
|
||||
<div class="oh-sticky-table__th"></div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
{% for attendance in validate_attendances %}
|
||||
<div class="oh-sticky-table__tbody">
|
||||
<div class="oh-sticky-table__tr"
|
||||
draggable="false">
|
||||
<div class="oh-sticky-table__sd">
|
||||
<div class="d-flex">
|
||||
|
||||
<div class="">
|
||||
<input type="checkbox" id="{{attendance.id}}" class="oh-input attendance-checkbox oh-input__checkbox mt-2 mr-2 validate-row" />
|
||||
</div>
|
||||
<div class="oh-profile oh-profile--md">
|
||||
<div class="oh-profile__avatar mr-1">
|
||||
<img
|
||||
src="https://ui-avatars.com/api/?name={{attendance.employee_id.employee_first_name}}+{{attendance.employee_id.employee_last_name}}&background=random"
|
||||
class="oh-profile__image"
|
||||
alt="Mary Magdalene"
|
||||
/>
|
||||
</div>
|
||||
<span class="oh-profile__name oh-text--dark"
|
||||
>{{attendance.employee_id}}</span
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<div class="oh-sticky-table__td">
|
||||
{{attendance.attendance_date}}
|
||||
</div>
|
||||
<div class="oh-sticky-table__td">
|
||||
{{attendance.attendance_day|capfirst}}
|
||||
</div>
|
||||
<div class="oh-sticky-table__td">
|
||||
{{attendance.attendance_clock_in}}
|
||||
</div>
|
||||
<div class="oh-sticky-table__td">
|
||||
{{attendance.attendance_clock_in_date}}
|
||||
</div>
|
||||
<div class="oh-sticky-table__td">
|
||||
{{attendance.attendance_clock_out}}
|
||||
</div>
|
||||
<div class="oh-sticky-table__td">
|
||||
{{attendance.attendance_clock_out_date}}
|
||||
</div>
|
||||
<div class="oh-sticky-table__td">{{attendance.shift_id}}</div>
|
||||
<div class="oh-sticky-table__td">
|
||||
{{attendance.work_type_id}}
|
||||
</div>
|
||||
<div class="oh-sticky-table__td">
|
||||
{{attendance.attendance_worked_hour}}
|
||||
</div>
|
||||
<div class="oh-sticky-table__td">
|
||||
{{attendance.attendance_overtime}}
|
||||
</div>
|
||||
<div class="oh-sticky-table__td">
|
||||
<div class="oh-btn-group">
|
||||
{% if perms.attendance.change_attendance or request.user|is_reportingmanager %}
|
||||
<a hx-get="{% url 'attendance-update' attendance.id %}" hx-target='#updateAttendanceModalBody' hx-swap='innerHTML' data-toggle='oh-modal-toggle' data-target='#updateAttendanceModal' class="oh-btn oh-btn--light-bkg w-100" title="{% trans 'Edit' %}"><ion-icon name="create-outline"></ion-icon></a>
|
||||
{% endif %}
|
||||
{% if perms.attendance.delete_attendance %}
|
||||
<form action="{% url 'attendance-delete' attendance.id %}" onsubmit="return confirm('{% trans "Are you sure want to delete this attendance?" %}')" hx-target="#tab_contents" method='post'>
|
||||
{% csrf_token %}
|
||||
<button type='submit' class="oh-btn oh-btn--danger-outline oh-btn--light-bkg w-100" title="{% trans 'Remove' %}"><ion-icon name="trash-outline"></ion-icon></button>
|
||||
</form>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
<div class="oh-sticky-table__td">
|
||||
{% if request.user|is_reportingmanager or perms.attendance.change_attendance %}
|
||||
<a
|
||||
href = '{% url "validate-this-attendance" attendance.id %}'
|
||||
hx-target='#updateAttendanceBody'
|
||||
class="oh-btn oh-btn--info">
|
||||
{% trans "Validate" %}
|
||||
</a>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
<div class="oh-pagination">
|
||||
<span
|
||||
class="oh-pagination__page"
|
||||
>
|
||||
{% trans "Page" %} {{ validate_attendances.number }} {% trans "of" %} {{ validate_attendances.paginator.num_pages }}.
|
||||
</span
|
||||
>
|
||||
<nav class="oh-pagination__nav">
|
||||
<div class="oh-pagination__input-container me-3">
|
||||
<span class="oh-pagination__label me-1">{% trans "Page" %}</span>
|
||||
<input
|
||||
type="number"
|
||||
name="vpage"
|
||||
class="oh-pagination__input"
|
||||
value="{{validate_attendances.number}}"
|
||||
hx-get="{% url 'attendance-search' %}?{{pd}}"
|
||||
hx-target="#tab_contents"
|
||||
min="1"
|
||||
/>
|
||||
<span class="oh-pagination__label">{% trans "of" %} {{validate_attendances.paginator.num_pages}}</span>
|
||||
</div>
|
||||
<ul class="oh-pagination__items">
|
||||
{% if validate_attendances.has_previous %}
|
||||
<li class="oh-pagination__item oh-pagination__item--wide">
|
||||
<a hx-target='#tab_contents' hx-get="{% url 'attendance-search' %}?{{pd}}&vpage=1" class="oh-pagination__link">{% trans "First" %}</a>
|
||||
</li>
|
||||
<li class="oh-pagination__item oh-pagination__item--wide">
|
||||
<a hx-target='#tab_contents' hx-get="{% url 'attendance-search' %}?{{pd}}&vpage={{ validate_attendances.previous_page_number }}" class="oh-pagination__link">{% trans "Previous" %}</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
{% if validate_attendances.has_next %}
|
||||
<li class="oh-pagination__item oh-pagination__item--wide">
|
||||
<a hx-target='#tab_contents' hx-get="{% url 'attendance-search' %}?{{pd}}&vpage={{ validate_attendances.next_page_number }}" class="oh-pagination__link">{% trans "Next" %}</a>
|
||||
</li>
|
||||
<li class="oh-pagination__item oh-pagination__item--wide">
|
||||
<a hx-target='#tab_contents' hx-get="{% url 'attendance-search' %}?{{pd}}&vpage={{ validate_attendances.paginator.num_pages }}" class="oh-pagination__link">{% trans "Last" %}</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
</ul>
|
||||
</nav>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<div class="oh-tabs__content" id="tab_3">
|
||||
<div class="oh-sticky-table">
|
||||
<div class="oh-sticky-table__table oh-table--sortable">
|
||||
<div class="oh-sticky-table__thead">
|
||||
<div class="oh-sticky-table__tr">
|
||||
<div class="oh-sticky-table__th" >
|
||||
<div class="d-flex">
|
||||
<div class="">
|
||||
<input type="checkbox" class="oh-input oh-input__checkbox mt-1 mr-2 ot-attendances" />
|
||||
</div>
|
||||
<div hx-get="{% url 'attendance-search' %}?{{pd}}&sortby=employee_id__employee_first_name" hx-target="#tab_contents">
|
||||
{% trans "Employee" %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="oh-sticky-table__th" hx-get="{% url 'attendance-search' %}?{{pd}}&sortby=attendance_date" hx-target="#tab_contents">{% trans "Date" %}</div>
|
||||
<div class="oh-sticky-table__th">{% trans "Day" %}</div>
|
||||
<div class="oh-sticky-table__th" >{% trans "Clock In" %}</div>
|
||||
<div class="oh-sticky-table__th" hx-get="{% url 'attendance-search' %}?{{pd}}&sortby=attendance_clock_in_date" hx-target="#tab_contents">{% trans "In Date" %}</div>
|
||||
<div class="oh-sticky-table__th">{% trans "Clock Out" %}</div>
|
||||
<div class="oh-sticky-table__th" hx-get="{% url 'attendance-search' %}?{{pd}}&sortby=attendance_clock_out_date" hx-target="#tab_contents">{% trans "Out Date" %}</div>
|
||||
<div class="oh-sticky-table__th">{% trans "Shift" %}</div>
|
||||
<div class="oh-sticky-table__th">{% trans "Min Hour" %}</div>
|
||||
<div class="oh-sticky-table__th" hx-get="{% url 'attendance-search' %}?{{pd}}&sortby=at_work_second" hx-target="#tab_contents">{% trans "At Work" %}</div>
|
||||
<div class="oh-sticky-table__th" hx-get="{% url 'attendance-search' %}?{{pd}}&sortby=overtime_second" hx-target="#tab_contents">{% trans "Overtime" %}</div>
|
||||
<div class="oh-sticky-table__th"></div>
|
||||
<div class="oh-sticky-table__th"></div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
{% for attendance in overtime_attendances %}
|
||||
<div class="oh-sticky-table__tbody">
|
||||
<div class="oh-sticky-table__tr"
|
||||
draggable="false">
|
||||
<div class="oh-sticky-table__sd">
|
||||
<div class="d-flex">
|
||||
<div class="">
|
||||
<input type="checkbox" id="{{attendance.id}}" class="oh-input attendance-checkbox oh-input__checkbox mt-2 mr-2 ot-attendance-row" />
|
||||
</div>
|
||||
<div class="oh-profile oh-profile--md">
|
||||
<div class="oh-profile__avatar mr-1">
|
||||
<img
|
||||
src="https://ui-avatars.com/api/?name={{attendance.employee_id.employee_first_name}}+{{attendance.employee_id.employee_last_name}}&background=random"
|
||||
class="oh-profile__image"
|
||||
alt="Mary Magdalene"
|
||||
/>
|
||||
</div>
|
||||
<span class="oh-profile__name oh-text--dark"
|
||||
>{{attendance.employee_id}}</span
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="oh-sticky-table__td">
|
||||
{{attendance.attendance_date}}
|
||||
</div>
|
||||
<div class="oh-sticky-table__td">
|
||||
{{attendance.attendance_day|capfirst}}
|
||||
</div>
|
||||
<div class="oh-sticky-table__td">
|
||||
{{attendance.attendance_clock_in}}
|
||||
</div>
|
||||
<div class="oh-sticky-table__td">
|
||||
{{attendance.attendance_clock_in_date}}
|
||||
</div>
|
||||
<div class="oh-sticky-table__td">
|
||||
{{attendance.attendance_clock_out}}
|
||||
</div>
|
||||
<div class="oh-sticky-table__td">
|
||||
{{attendance.attendance_clock_out_date}}
|
||||
</div>
|
||||
<div class="oh-sticky-table__td">{{attendance.shift_id}}</div>
|
||||
<div class="oh-sticky-table__td">
|
||||
{{attendance.work_type_id}}
|
||||
</div>
|
||||
<div class="oh-sticky-table__td">
|
||||
{{attendance.attendance_worked_hour}}
|
||||
</div>
|
||||
<div class="oh-sticky-table__td">
|
||||
{{attendance.attendance_overtime}}
|
||||
</div>
|
||||
<div class="oh-sticky-table__td">
|
||||
<a href="{% url 'approve-overtime' attendance.id %}" class="oh-btn oh-btn--info">
|
||||
{% trans "Approve" %}
|
||||
</a>
|
||||
</div>
|
||||
<div class="oh-sticky-table__td">
|
||||
<div class="oh-btn-group">
|
||||
{% if perms.attendance.change_attendance or request.user|is_reportingmanager %}
|
||||
<a hx-get="{% url 'attendance-update' attendance.id %}" hx-target='#updateAttendanceModalBody' hx-swap='innerHTML' data-toggle='oh-modal-toggle' data-target='#updateAttendanceModal' class="oh-btn oh-btn--light-bkg w-100" title="{% trans 'Edit' %}"><ion-icon name="create-outline"></ion-icon></a>
|
||||
{% endif %}
|
||||
{% if perms.attendance.delete_attendance %}
|
||||
<form action="{% url 'attendance-delete' attendance.id %}" onsubmit="return confirm('{% trans "Are you sure want to delete this attendance?" %}')" hx-target="#tab_contents" method='post'>
|
||||
{% csrf_token %}
|
||||
<button type='submit' class="oh-btn oh-btn--danger-outline oh-btn--light-bkg w-100" title="{% trans 'Remove' %}"><ion-icon name="trash-outline"></ion-icon></button>
|
||||
</form>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
<div class="oh-pagination">
|
||||
<span
|
||||
class="oh-pagination__page"
|
||||
>
|
||||
{% trans "Page" %} {{ overtime_attendances.number }} {% trans "of" %} {{ overtime_attendances.paginator.num_pages }}.
|
||||
</span
|
||||
>
|
||||
<nav class="oh-pagination__nav">
|
||||
<div class="oh-pagination__input-container me-3">
|
||||
<span class="oh-pagination__label me-1">{% trans "Page" %}</span>
|
||||
<input
|
||||
type="number"
|
||||
name="opage"
|
||||
class="oh-pagination__input"
|
||||
value="{{overtime_attendances.number}}"
|
||||
hx-get="{% url 'attendance-search' %}?{{pd}}"
|
||||
hx-target="#tab_contents"
|
||||
min="1"
|
||||
/>
|
||||
<span class="oh-pagination__label">{% trans "of" %} {{overtime_attendances.paginator.num_pages}}</span>
|
||||
</div>
|
||||
<ul class="oh-pagination__items">
|
||||
{% if overtime_attendances.has_previous %}
|
||||
<li class="oh-pagination__item oh-pagination__item--wide">
|
||||
<a hx-target='#tab_contents' hx-get="{% url 'attendance-search' %}?{{pd}}&opage=1" class="oh-pagination__link">{% trans "First" %}</a>
|
||||
</li>
|
||||
<li class="oh-pagination__item oh-pagination__item--wide">
|
||||
<a hx-target='#tab_contents' hx-get="{% url 'attendance-search' %}?{{pd}}&opage={{ overtime_attendances.previous_page_number }}" class="oh-pagination__link">{% trans "Previous" %}</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
{% if overtime_attendances.has_next %}
|
||||
<li class="oh-pagination__item oh-pagination__item--wide">
|
||||
<a hx-target='#tab_contents' hx-get="{% url 'attendance-search' %}?{{pd}}&opage={{ overtime_attendances.next_page_number }}" class="oh-pagination__link">{% trans "Next" %}</a>
|
||||
</li>
|
||||
<li class="oh-pagination__item oh-pagination__item--wide">
|
||||
<a hx-target='#tab_contents' hx-get="{% url 'attendance-search' %}?{{pd}}&opage={{ overtime_attendances.paginator.num_pages }}" class="oh-pagination__link">{% trans "Last" %}</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
</ul>
|
||||
</nav>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% if perms.attendance.change_attendance or request.user|is_reportingmanager %}
|
||||
<div
|
||||
class="oh-modal"
|
||||
id="updateAttendanceModal"
|
||||
role="dialog"
|
||||
aria-labelledby="updateAttendanceModal"
|
||||
aria-hidden="true"
|
||||
>
|
||||
<div class="oh-modal__dialog">
|
||||
<div class="oh-modal__dialog-header">
|
||||
<h2 class="oh-modal__dialog-title" id="updateAttendanceModalLabel">
|
||||
{% trans "Edit Attendance" %}
|
||||
</h2>
|
||||
<button class="oh-modal__close" aria-label="Close" >
|
||||
<ion-icon name="close-outline"></ion-icon>
|
||||
</button>
|
||||
</div>
|
||||
<div id="updateAttendanceModalBody">
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<script>
|
||||
$(document).ready(function () {
|
||||
var activeTab = localStorage.getItem('activeTabAttendance')
|
||||
if (activeTab != null) {
|
||||
var tab = $(`[data-target="${activeTab}"]`)
|
||||
var tabContent = $(activeTab)
|
||||
$(tab).attr('class', 'oh-tabs__tab oh-tabs__tab--active');
|
||||
$(tabContent).attr('class', 'oh-tabs__content oh-tabs__content--active');
|
||||
}
|
||||
else{
|
||||
$('[data-target="#tab_1"]').attr('class', 'oh-tabs__tab oh-tabs__tab--active');
|
||||
$('#tab_1').attr('class', 'oh-tabs__content oh-tabs__content--active');
|
||||
}
|
||||
$('.oh-tabs__tab').click(function (e) {
|
||||
var activeTab = $(this).attr('data-target');
|
||||
localStorage.setItem('activeTabAttendance',activeTab)
|
||||
|
||||
});
|
||||
});
|
||||
</script>
|
||||
92
attendance/templates/attendance/attendance/update_form.html
Normal file
92
attendance/templates/attendance/attendance/update_form.html
Normal file
@@ -0,0 +1,92 @@
|
||||
{% load i18n %}
|
||||
<form hx-post="{% url 'attendance-update' form.instance.id %}" hx-swap="#updateAttendanceModalBody" method="post" id="updateForm">
|
||||
<div class="oh-modal__dialog-body">
|
||||
{% csrf_token %}
|
||||
<div class="row">
|
||||
<div class="col-12">
|
||||
<label class="oh-label" for="firstname">{% trans "Employee" %}</label>
|
||||
{{form.employee_id}}
|
||||
{{form.employee_id.errors}}
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-12 col-sm-12 col-md-12 col-lg-6">
|
||||
<label class="oh-label" for="lastname">{% trans "Shift" %}</label>
|
||||
{{form.shift_id}}
|
||||
{{form.shift_id.errors}}
|
||||
</div>
|
||||
<div class="col-12 col-sm-12 col-md-12 col-lg-6">
|
||||
<label class="oh-label" for="lastname">{% trans "Work Type" %}</label>
|
||||
{{form.work_type_id}}
|
||||
{{form.work_type_id.errors}}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-12 col-sm-12 col-md-12 col-lg-6">
|
||||
<label class="oh-label" for="lastname">{% trans "Attendance Date" %}</label>
|
||||
{{form.attendance_date}}
|
||||
{{form.attendance_date.errors}}
|
||||
</div>
|
||||
<div class="col-12 col-sm-12 col-md-12 col-lg-6">
|
||||
<label class="oh-label" for="lastname">{% trans "Minimum Hour" %}</label>
|
||||
{{form.minimum_hour}}
|
||||
{{form.minimum_hour.errors}}
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-12 col-sm-12 col-md-12 col-lg-6">
|
||||
<label class="oh-label" for="lastname">{% trans "Check-In Date" %}</label>
|
||||
{{form.attendance_clock_in_date}}
|
||||
{{form.attendance_clock_in_date.errors}}
|
||||
</div>
|
||||
<div class="col-12 col-sm-12 col-md-12 col-lg-6">
|
||||
<label class="oh-label" for="lastname">{% trans "In Time" %}</label>
|
||||
{{form.attendance_clock_in}}
|
||||
{{form.attendance_clock_in.errors}}
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-12 col-sm-12 col-md-12 col-lg-6">
|
||||
<label class="oh-label" for="lastname">{% trans "Check-Out Date" %}</label>
|
||||
{{form.attendance_clock_out_date}}
|
||||
{{form.attendance_clock_out_date.errors}}
|
||||
</div>
|
||||
<div class="col-12 col-sm-12 col-md-12 col-lg-6">
|
||||
<label class="oh-label" for="lastname">{% trans "Out Time" %}</label>
|
||||
{{form.attendance_clock_out}}
|
||||
{{form.attendance_clock_out.errors}}
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-12 col-sm-12 col-md-12 col-lg-6">
|
||||
<label class="oh-label" for="lastname">{% trans "At Work" %}</label>
|
||||
{{form.attendance_worked_hour}}
|
||||
{{form.attendance_worked_hour.errors}}
|
||||
</div>
|
||||
<div class="col-12 col-sm-12 col-md-12 col-lg-6">
|
||||
<label class="oh-label" for="lastname">{% trans "Overtime" %}</label>
|
||||
{{form.attendance_overtime}}
|
||||
{{form.attendance_overtime.errors}}
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-12 col-sm-12 col-md-12 col-lg-6">
|
||||
<div class="row">
|
||||
<div class="col-12 col-sm-12 col-md-12 col-lg-6">
|
||||
<label class="oh-label" for="lastname">{% trans "Validated" %}</label><br>
|
||||
{{form.attendance_validated}}
|
||||
{{form.attendance_validated.errors}}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="oh-modal__dialog-footer">
|
||||
<input type="submit" value='Save' class="pl-5 pr-5 oh-btn oh-btn--secondary oh-btn--shadow">
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
|
||||
</script>
|
||||
@@ -0,0 +1 @@
|
||||
{{form}}
|
||||
@@ -0,0 +1,129 @@
|
||||
{% load static %}
|
||||
{% load i18n %}
|
||||
<form hx-get='{% url "attendance-ot-search" %}' hx-swap='innerHTML' hx-target='#ot-table' >
|
||||
<div class="oh-dropdown__filter-body">
|
||||
<div class="oh-accordion">
|
||||
<div class="oh-accordion-header">{% trans "Work Info" %}</div>
|
||||
<div class="oh-accordion-body">
|
||||
<div class="row">
|
||||
<div class="col-sm-12 col-md-12 col-lg-6">
|
||||
<div class="oh-input-group">
|
||||
<label class="oh-label">{% trans "Employee" %}</label>
|
||||
{{f.form.employee_id}}
|
||||
</div>
|
||||
<div class="oh-input-group">
|
||||
<label class="oh-label">{% trans "Department" %}</label>
|
||||
{{f.form.employee_id__employee_work_info__department_id}}
|
||||
</div>
|
||||
<div class="oh-input-group">
|
||||
<label class="oh-label">{% trans "Shift" %}</label>
|
||||
{{f.form.employee_id__employee_work_info__shift_id}}
|
||||
</div>
|
||||
<div class="oh-input-group">
|
||||
<label class="oh-label">{% trans "Reporting Manager" %}</label>
|
||||
{{f.form.employee_id__employee_work_info__reporting_manager_id}}
|
||||
</div>
|
||||
|
||||
|
||||
</div>
|
||||
<div class="col-sm-12 col-md-12 col-lg-6">
|
||||
<div class="oh-input-group">
|
||||
<label class="oh-label">{% trans "Company" %}</label>
|
||||
{{f.form.employee_id__employee_work_info__company_id}}
|
||||
</div>
|
||||
<div class="oh-input-group">
|
||||
<label class="oh-label">{% trans "Job Position" %}</label>
|
||||
{{f.form.employee_id__employee_work_info__job_position_id}}
|
||||
</div>
|
||||
<div class="oh-input-group">
|
||||
<label class="oh-label">{% trans "Work Type" %}</label>
|
||||
{{f.form.employee_id__employee_work_info__work_type_id}}
|
||||
</div>
|
||||
<div class="oh-input-group">
|
||||
<label class="oh-label">{% trans "Work Location" %}</label>
|
||||
{{f.form.employee_id__employee_work_info__location}}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="oh-accordion">
|
||||
<div class="oh-accordion-header">{% trans "Hour Account" %}</div>
|
||||
<div class="oh-accordion-body">
|
||||
<div class="row">
|
||||
<div class="col-sm-12 col-md-12 col-lg-6">
|
||||
<div class="oh-input-group">
|
||||
<label class="oh-label">{% trans "Month" %}</label>
|
||||
{{f.form.month}}
|
||||
</div>
|
||||
<div class="oh-input-group">
|
||||
<label class="oh-label">{% trans "Overtime" %}</label>
|
||||
{{f.form.overtime}}
|
||||
</div>
|
||||
<div class="oh-input-group">
|
||||
<label class="oh-label">{% trans "Year" %}</label>
|
||||
{{f.form.year}}
|
||||
</div>
|
||||
<div class="oh-input-group">
|
||||
<label class="oh-label">{% trans "Hour Account" %}</label>
|
||||
{{f.form.hour_account}}
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="oh-accordion">
|
||||
<div class="oh-accordion-header">{% trans "Advanced" %}</div>
|
||||
<div class="oh-accordion-body">
|
||||
<div class="row">
|
||||
<div class="col-sm-12 col-md-12 col-lg-6">
|
||||
<div class="oh-input-group">
|
||||
<label class="oh-label">{% trans "Hour Account Greater or Equal" %}</label>
|
||||
{{f.form.hour_account__gte}}
|
||||
</div>
|
||||
<div class="oh-input-group">
|
||||
<label class="oh-label">{% trans "OT Account Greater or Equal" %}</label>
|
||||
{{f.form.hour_account__gte}}
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-sm-12 col-md-12 col-lg-6">
|
||||
<div class="oh-input-group">
|
||||
<label class="oh-label">{% trans "Hour Account Lessthan or Equal" %}</label>
|
||||
{{f.form.hour_account__lte}}
|
||||
</div>
|
||||
<div class="oh-input-group">
|
||||
<label class="oh-label">{% trans "OT Account Lessthan or Equal" %}</label>
|
||||
{{f.form.overtime__lte}}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="oh-accordion">
|
||||
<div class="oh-accordion-header">{% trans "Group By" %}</div>
|
||||
<div class="oh-accordion-body">
|
||||
<div class="row">
|
||||
<div class="col-sm-12 col-md-12 col-lg-6">
|
||||
<div class="oh-input-group">
|
||||
<label class="oh-label">{% trans "Field" %}</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-sm-12 col-md-12 col-lg-6">
|
||||
<div class="oh-input-group">
|
||||
<select class="oh-select mt-1 " id="field" name="field" class="select2-selection select2-selection--single" id="gp">
|
||||
{% for field in gp_fields %}
|
||||
<option value="{{ field.0 }}">{{ field.1 }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="oh-dropdown__filter-footer">
|
||||
<button class="oh-btn oh-btn--secondary oh-btn--small w-100">{% trans "Filter" %}</button>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
{% extends 'index.html' %}{% block content %}
|
||||
{% include 'attendance/attendance_account/nav.html' %}
|
||||
<div class="oh-wrapper">
|
||||
{% include 'attendance/attendance_account/overtime_list.html' %}
|
||||
</div>
|
||||
|
||||
|
||||
{% endblock %}
|
||||
@@ -0,0 +1,8 @@
|
||||
|
||||
<form id="add-form"
|
||||
hx-post="{% url 'attendance-overtime-create' %}" hx-target = "#addAttendanceOtModalBody"hx-target="#attendanceAddMessage" hx-swap = 'innerHTML' method='post'>
|
||||
{{form.as_p}}
|
||||
</div>
|
||||
<div class="oh-modal__dialog-footer">
|
||||
<input type="submit" value='Save' class="oh-btn oh-btn--secondary oh-btn--shadow mt-4 pl-5 pr-5">
|
||||
</form>
|
||||
168
attendance/templates/attendance/attendance_account/group_by.html
Normal file
168
attendance/templates/attendance/attendance_account/group_by.html
Normal file
@@ -0,0 +1,168 @@
|
||||
{% load i18n %}
|
||||
{% load attendancefilters %}
|
||||
{% load basefilters %}
|
||||
<div class="oh-sticky-table">
|
||||
<div class="oh-sticky-table__table oh-table__sticky-collaspable-sort">
|
||||
<div class="oh-sticky-table__thead">
|
||||
<div class="oh-sticky-table__tr">
|
||||
<div class="oh-sticky-table__th">{% trans "Employee" %}</div>
|
||||
<div class="oh-sticky-table__th">{% trans "Month" %}</div>
|
||||
<div class="oh-sticky-table__th">{% trans "Year" %}</div>
|
||||
<div class="oh-sticky-table__th">{% trans "Hour Account" %}</div>
|
||||
<div class="oh-sticky-table__th">{% trans "Overtime" %}</div>
|
||||
<div class="oh-sticky-table__th"></div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- grouper -->
|
||||
{% dynamic_regroup accounts by field as accounts_grouper %}
|
||||
{% for attendance_list in accounts_grouper %}
|
||||
<div class="oh-sticky-table__tbody" draggable="true">
|
||||
<div class="oh-sticky-table__tr oh-table__toggle-parent" data-target="{{attendance_list.grouper}}">
|
||||
<div class="oh-sticky-table__sd">
|
||||
<div class="d-flex align-items-center">
|
||||
<button class="oh-btn oh-btn--transparent oh-table__toggle-button"></button>
|
||||
<span class="ms-2">
|
||||
<div class="oh-tabs__input-badge-container">
|
||||
<span class="oh-badge oh-badge--secondary oh-badge--small oh-badge--round mr-1">
|
||||
{{attendance_list.list|length}}
|
||||
</span>
|
||||
{{attendance_list.grouper}}
|
||||
</div>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="oh-sticky-table__td"></div>
|
||||
<div class="oh-sticky-table__td"></div>
|
||||
<div class="oh-sticky-table__td"></div>
|
||||
<div class="oh-sticky-table__td"></div>
|
||||
<div class="oh-sticky-table__td"></div>
|
||||
</div>
|
||||
<!-- data -->
|
||||
{% for ot in attendance_list.list %}
|
||||
<div class="oh-sticky-table__tr oh-table__toggle-child" data-group="{{attendance_list.grouper}}">
|
||||
|
||||
<div class="oh-sticky-table__sd">
|
||||
<div class="oh-profile oh-profile--md">
|
||||
<div class="oh-profile__avatar mr-1">
|
||||
<img
|
||||
src="https://ui-avatars.com/api/?name={{ot.employee_id.employee_first_name}}+{{ot.employee_id.employee_last_name}}&background=random"
|
||||
class="oh-profile__image"
|
||||
alt="Mary Magdalene"
|
||||
/>
|
||||
</div>
|
||||
<span class="oh-profile__name oh-text--dark"
|
||||
>{{ot.employee_id}}</span
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
<div class="oh-sticky-table__td">{{ot.month|capfirst}}</div>
|
||||
<div class="oh-sticky-table__td">{{ot.year}}</div>
|
||||
<div class="oh-sticky-table__td">{{ot.hour_account}}</div>
|
||||
<div class="oh-sticky-table__td">{{ot.overtime}}</div>
|
||||
<div class="oh-sticky-table__td">
|
||||
<div class="oh-btn-group">
|
||||
{% if perms.recruitment.change_attendanceovertime or request.user|is_reportingmanager %}
|
||||
<a hx-get="{% url 'attendance-overtime-update' ot.id %}" hx-target='#updateAttendanceOTModalBody' data-toggle='oh-modal-toggle' data-target='#updateOtModal' class="oh-btn oh-btn--light-bkg w-100" title="{% trans 'Edit' %}"><ion-icon name="create-outline"></ion-icon></a>
|
||||
{% endif %}
|
||||
{% if perms.recruitment.delete_attendance %}
|
||||
<form hx-post="{% url 'attendance-overtime-delete' ot.id %}?page={{accounts.number}}" hx-confirm="Are you sure want to delete this attendance?" hx-target="#ot-table" method='post'>
|
||||
{% csrf_token %}
|
||||
<button type='submit' class="oh-btn oh-btn--danger-outline oh-btn--light-bkg w-100" title="{% trans 'Remove' %}"><ion-icon name="trash-outline"></ion-icon></button>
|
||||
</form>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="oh-pagination">
|
||||
<span
|
||||
class="oh-pagination__page"
|
||||
>
|
||||
{% trans "Page" %} {{ accounts.number }} {% trans "of" %} {{ accounts.paginator.num_pages }}.
|
||||
</span
|
||||
>
|
||||
<nav class="oh-pagination__nav">
|
||||
<div class="oh-pagination__input-container me-3">
|
||||
<span class="oh-pagination__label me-1">{% trans "Page" %}</span>
|
||||
<input
|
||||
type="number"
|
||||
name="page"
|
||||
class="oh-pagination__input"
|
||||
value="{{accounts.number}}"
|
||||
hx-get="{% url 'attendance-ot-search' %}?{{pd}}"
|
||||
hx-target="#ot-table"
|
||||
min="1"
|
||||
/>
|
||||
<span class="oh-pagination__label">{% trans "of" %} {{accounts.paginator.num_pages}}</span>
|
||||
</div>
|
||||
<ul class="oh-pagination__items">
|
||||
{% if accounts.has_previous %}
|
||||
<li class="oh-pagination__item oh-pagination__item--wide">
|
||||
<a hx-target='#ot-table' hx-get="{% url 'attendance-ot-search' %}?{{pd}}&page=1" class="oh-pagination__link">{% trans "First" %}</a>
|
||||
</li>
|
||||
<li class="oh-pagination__item oh-pagination__item--wide">
|
||||
<a hx-target='#ot-table' hx-get="{% url 'attendance-ot-search' %}?{{pd}}&page={{ accounts.previous_page_number }}" class="oh-pagination__link">{% trans "Previous" %}</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
{% if accounts.has_next %}
|
||||
<li class="oh-pagination__item oh-pagination__item--wide">
|
||||
<a hx-target='#ot-table' hx-get="{% url 'attendance-ot-search' %}?{{pd}}&page={{ accounts.next_page_number }}" class="oh-pagination__link">{% trans "Next" %}</a>
|
||||
</li>
|
||||
<li class="oh-pagination__item oh-pagination__item--wide">
|
||||
<a hx-target='#ot-table' hx-get="{% url 'attendance-ot-search' %}?{{pd}}&page={{ accounts.paginator.num_pages }}" class="oh-pagination__link">{% trans "Last" %}</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
</ul>
|
||||
</nav>
|
||||
</div>
|
||||
|
||||
{% if perms.attendance.change_attendanceovertime or request.user|is_reportingmanager %}
|
||||
<div
|
||||
class="oh-modal"
|
||||
id="updateOtModal"
|
||||
role="dialog"
|
||||
aria-labelledby="updateOtModal"
|
||||
aria-hidden="true"
|
||||
>
|
||||
<div class="oh-modal__dialog">
|
||||
<div class="oh-modal__dialog-header">
|
||||
<h2 class="oh-modal__dialog-title" id="updateOtModalLabel">
|
||||
{% trans "Update Account" %}
|
||||
</h2>
|
||||
<button class="oh-modal__close" aria-label="Close" hx-get="{% url 'attendance-ot-search' %}?{{pd}}&page={{accounts.number}}" hx-target="#ot-table">
|
||||
<ion-icon name="close-outline"></ion-icon>
|
||||
</button>
|
||||
<div class="oh-modal__dialog-body p-0 pt-2" id='updateAttendanceOTModalBody'>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
|
||||
|
||||
<script>
|
||||
$(document).load('htmx:afterswap', function () {
|
||||
|
||||
$('.oh-table__sticky-collaspable-sort').click(function (e) {
|
||||
e.preventDefault();
|
||||
let clickedEl = $(e.target).closest(".oh-table__toggle-parent");
|
||||
let targetSelector = clickedEl.data("target");
|
||||
let toggleBtn = clickedEl.find(".oh-table__toggle-button");
|
||||
$(`[data-group='${targetSelector}']`).toggleClass(
|
||||
"oh-table__toggle-child--show"
|
||||
);
|
||||
if (toggleBtn) {
|
||||
toggleBtn.toggleClass("oh-table__toggle-button--show");
|
||||
}
|
||||
});
|
||||
});
|
||||
</script>
|
||||
108
attendance/templates/attendance/attendance_account/nav.html
Normal file
108
attendance/templates/attendance/attendance_account/nav.html
Normal file
@@ -0,0 +1,108 @@
|
||||
|
||||
{% load i18n %}
|
||||
{% load basefilters %}
|
||||
{% if perms.attendance.add_attendanceovertime or request.user|is_reportingmanager %}
|
||||
<div
|
||||
class="oh-modal"
|
||||
id="addAttendanceOtModal"
|
||||
role="dialog"
|
||||
aria-labelledby="addAttendanceOtModal"
|
||||
aria-hidden="true"
|
||||
>
|
||||
<div class="oh-modal__dialog">
|
||||
<div class="oh-modal__dialog-header">
|
||||
<h2 class="oh-modal__dialog-title" id="addAttendanceOtModalLabel">
|
||||
{% trans "Add OT" %}
|
||||
</h2>
|
||||
<button class="oh-modal__close" aria-label="Close" >
|
||||
<ion-icon name="close-outline"></ion-icon>
|
||||
</button>
|
||||
</div>
|
||||
<div class="oh-modal__dialog-body" id='addAttendanceOtModalBody'>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
|
||||
<section class="oh-wrapper oh-main__topbar" x-data="{searchShow: false}">
|
||||
<div class="oh-main__titlebar oh-main__titlebar--left">
|
||||
<h1 class="oh-main__titlebar-title fw-bold">
|
||||
<a href="{% url 'attendance-overtime-view' %}" class='text-dark'>
|
||||
{% trans "Hour Account" %}
|
||||
</a>
|
||||
</h1>
|
||||
<a
|
||||
class="oh-main__titlebar-search-toggle"
|
||||
role="button"
|
||||
aria-label="Toggle Search"
|
||||
@click="searchShow = !searchShow"
|
||||
>
|
||||
<ion-icon
|
||||
name="search-outline"
|
||||
class="oh-main__titlebar-serach-icon"
|
||||
></ion-icon>
|
||||
</a>
|
||||
</div>
|
||||
<div class="oh-main__titlebar oh-main__titlebar--right">
|
||||
<div
|
||||
class="oh-input-group oh-input__search-group"
|
||||
:class="searchShow ? 'oh-input__search-group--show' : ''"
|
||||
>
|
||||
<ion-icon
|
||||
name="search-outline"
|
||||
class="oh-input-group__icon oh-input-group__icon--left"
|
||||
></ion-icon>
|
||||
<input
|
||||
type="text"
|
||||
class="oh-input oh-input__icon"
|
||||
aria-label="Search Input"
|
||||
id="attendance-search"
|
||||
name='search'
|
||||
placeholder="{% trans 'Search' %}"
|
||||
hx-get="{% url 'attendance-ot-search' %}"
|
||||
hx-trigger="keyup changed delay:500ms, search"
|
||||
hx-target="#ot-table"
|
||||
hx-swap="innerHTML"
|
||||
/>
|
||||
</div>
|
||||
<div class="oh-main__titlebar-button-container">
|
||||
<div class="oh-dropdown" x-data="{open: false}">
|
||||
<button class="oh-btn ml-2" @click="open = !open">
|
||||
<ion-icon name="filter" class="mr-1"></ion-icon>{% trans "Filter" %}
|
||||
</button>
|
||||
<div
|
||||
class="oh-dropdown__menu oh-dropdown__menu--right oh-dropdown__filter p-4"
|
||||
x-show="open"
|
||||
@click.outside="open = false"
|
||||
style="display: none;"
|
||||
>
|
||||
{% include 'attendance/attendance_account/attendance_account_filter.html' %}
|
||||
</div>
|
||||
</div>
|
||||
{% if perms.attendance.add_attendanceovertime or request.user|is_reportingmanager %}
|
||||
<div class="oh-btn-group ml-2">
|
||||
<div class="oh-dropdown" >
|
||||
<button class="oh-btn oh-btn--secondary"
|
||||
data-toggle="oh-modal-toggle"
|
||||
data-target="#addAttendanceOtModal"
|
||||
hx-get="{% url 'attendance-overtime-create' %}"
|
||||
hx-target="#addAttendanceOtModalBody"
|
||||
>
|
||||
<ion-icon name="add-sharp" class="mr-2"></ion-icon>
|
||||
{% trans "Create" %}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
</div>
|
||||
</div>
|
||||
<script>
|
||||
$('#attendance-search').keydown(function (e) {
|
||||
var val = $(this).val();
|
||||
$('.pg').attr('hx-vals', `{"search":${val}}`);
|
||||
});
|
||||
</script>
|
||||
</section>
|
||||
@@ -0,0 +1,120 @@
|
||||
{% load i18n %}
|
||||
{% load basefilters %}
|
||||
<div class="oh-sticky-table" id='ot-table'>
|
||||
<div class="oh-sticky-table__table oh-table--sortable">
|
||||
<div class="oh-sticky-table__thead">
|
||||
<div class="oh-sticky-table__tr">
|
||||
<div class="oh-sticky-table__th" hx-get="{% url 'attendance-ot-search' %}?{{pd}}&sortby=employee_id__employee_first_name" hx-target="#ot-table">{% trans "Employee" %}</div>
|
||||
<div class="oh-sticky-table__th" hx-get="{% url 'attendance-ot-search' %}?{{pd}}&sortby=month" hx-target="#ot-table">{% trans "Month" %}</div>
|
||||
<div class="oh-sticky-table__th" hx-get="{% url 'attendance-ot-search' %}?{{pd}}&sortby=year" hx-target="#ot-table">{% trans "Year" %}</div>
|
||||
<div class="oh-sticky-table__th" hx-get="{% url 'attendance-ot-search' %}?{{pd}}&sortby=hour_account_second" hx-target="#ot-table">{% trans "Hour Account" %}</div>
|
||||
<div class="oh-sticky-table__th" hx-get="{% url 'attendance-ot-search' %}?{{pd}}&sortby=overtime_second" hx-target="#ot-table">{% trans "Overtime" %}</div>
|
||||
<div class="oh-sticky-table__th"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="oh-sticky-table__tbody">
|
||||
{% for ot in accounts %}
|
||||
<div class="oh-sticky-table__tr" draggable="true" >
|
||||
<div class="oh-sticky-table__sd">
|
||||
<div class="oh-profile oh-profile--md">
|
||||
<div class="oh-profile__avatar mr-1">
|
||||
<img
|
||||
src="https://ui-avatars.com/api/?name={{ot.employee_id.employee_first_name}}+{{ot.employee_id.employee_last_name}}&background=random"
|
||||
class="oh-profile__image"
|
||||
alt="Mary Magdalene"
|
||||
/>
|
||||
</div>
|
||||
<span class="oh-profile__name oh-text--dark"
|
||||
>{{ot.employee_id}}</span
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
<div class="oh-sticky-table__td">{{ot.month|capfirst}}</div>
|
||||
<div class="oh-sticky-table__td">{{ot.year}}</div>
|
||||
<div class="oh-sticky-table__td">{{ot.hour_account}}</div>
|
||||
<div class="oh-sticky-table__td">{{ot.overtime}}</div>
|
||||
<div class="oh-sticky-table__td">
|
||||
<div class="oh-btn-group">
|
||||
{% if perms.recruitment.change_attendanceovertime or request.user|is_reportingmanager %}
|
||||
<a hx-get="{% url 'attendance-overtime-update' ot.id %}" hx-target='#updateAttendanceOTModalBody' data-toggle='oh-modal-toggle' data-target='#updateOtModal' class="oh-btn oh-btn--light-bkg w-100" title="{% trans 'Edit' %}"><ion-icon name="create-outline"></ion-icon></a>
|
||||
{% endif %}
|
||||
{% if perms.recruitment.delete_attendance %}
|
||||
<form action="{% url 'attendance-overtime-delete' ot.id %}" onsubmit="return confirm('{% trans "Are you sure want to delete this attendance?" %}')" hx-target="#ot-table" method='post'>
|
||||
{% csrf_token %}
|
||||
<button type='submit' class="oh-btn oh-btn--danger-outline oh-btn--light-bkg w-100" title="{% trans 'Remove' %}"><ion-icon name="trash-outline"></ion-icon></button>
|
||||
</form>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="oh-pagination">
|
||||
<span
|
||||
class="oh-pagination__page"
|
||||
>
|
||||
{% trans "Page" %} {{ accounts.number }} {% trans "of" %} {{ accounts.paginator.num_pages }}.
|
||||
</span
|
||||
>
|
||||
<nav class="oh-pagination__nav">
|
||||
<div class="oh-pagination__input-container me-3">
|
||||
<span class="oh-pagination__label me-1">{% trans "Page" %}</span>
|
||||
<input
|
||||
type="number"
|
||||
name="page"
|
||||
class="oh-pagination__input"
|
||||
value="{{accounts.number}}"
|
||||
hx-get="{% url 'attendance-ot-search' %}?{{pd}}"
|
||||
hx-target="#ot-table"
|
||||
min="1"
|
||||
/>
|
||||
<span class="oh-pagination__label">{% trans "of" %} {{accounts.paginator.num_pages}}</span>
|
||||
</div>
|
||||
<ul class="oh-pagination__items">
|
||||
{% if accounts.has_previous %}
|
||||
<li class="oh-pagination__item oh-pagination__item--wide">
|
||||
<a hx-target='#ot-table' hx-get="{% url 'attendance-ot-search' %}?{{pd}}&page=1" class="oh-pagination__link">{% trans "First" %}</a>
|
||||
</li>
|
||||
<li class="oh-pagination__item oh-pagination__item--wide">
|
||||
<a hx-target='#ot-table' hx-get="{% url 'attendance-ot-search' %}?{{pd}}&page={{ accounts.previous_page_number }}" class="oh-pagination__link">{% trans "Previous" %}</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
{% if accounts.has_next %}
|
||||
<li class="oh-pagination__item oh-pagination__item--wide">
|
||||
<a hx-target='#ot-table' hx-get="{% url 'attendance-ot-search' %}?{{pd}}&page={{ accounts.next_page_number }}" class="oh-pagination__link">{% trans "Next" %}</a>
|
||||
</li>
|
||||
<li class="oh-pagination__item oh-pagination__item--wide">
|
||||
<a hx-target='#ot-table' hx-get="{% url 'attendance-ot-search' %}?{{pd}}&page={{ accounts.paginator.num_pages }}" class="oh-pagination__link">{% trans "Last" %}</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
</ul>
|
||||
</nav>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
{% if perms.attendance.change_attendanceovertime or request.user|is_reportingmanager %}
|
||||
<div
|
||||
class="oh-modal"
|
||||
id="updateOtModal"
|
||||
role="dialog"
|
||||
aria-labelledby="updateOtModal"
|
||||
aria-hidden="true"
|
||||
>
|
||||
<div class="oh-modal__dialog">
|
||||
<div class="oh-modal__dialog-header">
|
||||
<h2 class="oh-modal__dialog-title" id="updateOtModalLabel">
|
||||
{% trans "Update Account" %}
|
||||
</h2>
|
||||
<button class="oh-modal__close" aria-label="Close" >
|
||||
<ion-icon name="close-outline"></ion-icon>
|
||||
</button>
|
||||
<div class="oh-modal__dialog-body p-0 pt-2" id='updateAttendanceOTModalBody'>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
@@ -0,0 +1,13 @@
|
||||
{% load i18n %}
|
||||
<form
|
||||
id="update-form"
|
||||
hx-post="{% url 'attendance-overtime-update' form.instance.id %}"
|
||||
id="update-form"
|
||||
hx-target = "#updateAttendanceOTModalBody"
|
||||
hx-target="#attendanceAddMessage"
|
||||
hx-swap = 'innerHTML' >
|
||||
{{form.as_p}}
|
||||
<div class="oh-modal__dialog-footer">
|
||||
<input type="submit" value="{% trans 'Save' %}" class="oh-btn oh-btn--secondary oh-btn--shadow mt-4 pl-5 pr-5">
|
||||
</div>
|
||||
</form>
|
||||
@@ -0,0 +1,151 @@
|
||||
{% load static %}
|
||||
{% load i18n %}
|
||||
<form hx-get='{% url "attendance-activity-search" %}' id="filterForm" hx-swap='innerHTML' hx-target='#activity-table' >
|
||||
<div class="oh-dropdown__filter-body">
|
||||
<div class="oh-accordion">
|
||||
<div class="oh-accordion-header">{% trans "Work Info" %}</div>
|
||||
<div class="oh-accordion-body">
|
||||
<div class="row">
|
||||
<div class="col-sm-12 col-md-12 col-lg-6">
|
||||
<div class="oh-input-group">
|
||||
<label class="oh-label">{% trans "Employee" %}</label>
|
||||
{{f.form.employee_id}}
|
||||
</div>
|
||||
<div class="oh-input-group">
|
||||
<label class="oh-label">{% trans "Department" %}</label>
|
||||
{{f.form.employee_id__employee_work_info__department_id}}
|
||||
</div>
|
||||
<div class="oh-input-group">
|
||||
<label class="oh-label">{% trans "Shift" %}</label>
|
||||
{{f.form.employee_id__employee_work_info__shift_id}}
|
||||
</div>
|
||||
<div class="oh-input-group">
|
||||
<label class="oh-label">{% trans "Reporting Manager" %}</label>
|
||||
{{f.form.employee_id__employee_work_info__reporting_manager_id}}
|
||||
</div>
|
||||
|
||||
|
||||
</div>
|
||||
<div class="col-sm-12 col-md-12 col-lg-6">
|
||||
<div class="oh-input-group">
|
||||
<label class="oh-label">{% trans "Company" %}</label>
|
||||
{{f.form.employee_id__employee_work_info__company_id}}
|
||||
</div>
|
||||
<div class="oh-input-group">
|
||||
<label class="oh-label">{% trans "Job Position" %}</label>
|
||||
{{f.form.employee_id__employee_work_info__job_position_id}}
|
||||
</div>
|
||||
<div class="oh-input-group">
|
||||
<label class="oh-label">{% trans "Work Type" %}</label>
|
||||
{{f.form.employee_id__employee_work_info__work_type_id}}
|
||||
</div>
|
||||
<div class="oh-input-group">
|
||||
<label class="oh-label">{% trans "Work Location" %}</label>
|
||||
{{f.form.employee_id__employee_work_info__location}}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% comment %} {{f.form}} {% endcomment %}
|
||||
<div class="oh-accordion">
|
||||
<div class="oh-accordion-header">{% trans "Attendance Activity" %}</div>
|
||||
<div class="oh-accordion-body">
|
||||
<div class="row">
|
||||
<div class="col-sm-12 col-md-12 col-lg-6">
|
||||
<div class="oh-input-group">
|
||||
<label class="oh-label">{% trans "Attendance Date" %}</label>
|
||||
{{f.form.attendance_date}}
|
||||
</div>
|
||||
<div class="oh-input-group">
|
||||
<label class="oh-label">{% trans "Out Date" %}</label>
|
||||
{{f.form.clock_out_date}}
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-sm-12 col-md-12 col-lg-6">
|
||||
<div class="oh-input-group">
|
||||
<label class="oh-label">{% trans "In Date" %}</label>
|
||||
{{f.form.clock_in_date}}
|
||||
</div>
|
||||
<div class="oh-input-group">
|
||||
<label class="oh-label">{% trans "Shift Day" %}</label>
|
||||
{{f.form.shift_day}}
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="oh-accordion">
|
||||
<div class="oh-accordion-header">{% trans "Advanced" %}</div>
|
||||
<div class="oh-accordion-body">
|
||||
<div class="row">
|
||||
<div class="col-sm-12 col-md-12 col-lg-6">
|
||||
<div class="oh-input-group">
|
||||
<label class="oh-label">{% trans "Attendance From" %}</label>
|
||||
{{f.form.attendance_date_from}}
|
||||
</div>
|
||||
<div class="oh-input-group">
|
||||
<label class="oh-label">{% trans "In From" %}</label>
|
||||
{{f.form.in_form}}
|
||||
</div>
|
||||
<div class="oh-input-group">
|
||||
<label class="oh-label">{% trans "Out From" %}</label>
|
||||
{{f.form.out_form}}
|
||||
</div>
|
||||
<div class="oh-input-group">
|
||||
<label class="oh-label">{% trans "OT Greater or Equal" %}</label>
|
||||
{{f.form.overtime_second__gte}}
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-sm-12 col-md-12 col-lg-6">
|
||||
<div class="oh-input-group">
|
||||
<label class="oh-label">{% trans "Attendance Till" %}</label>
|
||||
{{f.form.attendance_date_till}}
|
||||
</div>
|
||||
<div class="oh-input-group">
|
||||
<label class="oh-label">{% trans "In Till" %}</label>
|
||||
{{f.form.in_till}}
|
||||
</div>
|
||||
<div class="oh-input-group">
|
||||
<label class="oh-label">{% trans "Out Till" %}</label>
|
||||
{{f.form.out_till}}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="oh-accordion">
|
||||
<div class="oh-accordion-header">{% trans "Group By" %}</div>
|
||||
<div class="oh-accordion-body">
|
||||
<div class="row">
|
||||
<div class="col-sm-12 col-md-12 col-lg-6">
|
||||
<div class="oh-input-group">
|
||||
<label class="oh-label">{% trans "Field" %}</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-sm-12 col-md-12 col-lg-6">
|
||||
<div class="oh-input-group">
|
||||
<select class="oh-select mt-1 " id="field" name="field" class="select2-selection select2-selection--single" id="gp">
|
||||
{% for field in gp_fields %}
|
||||
<option value="{{ field.0 }}">{{ field.1 }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="oh-dropdown__filter-footer">
|
||||
<button class="oh-btn oh-btn--secondary oh-btn--small w-100" id="filterSubmit">{% trans "Filter" %}</button>
|
||||
</div>
|
||||
|
||||
</form>
|
||||
|
||||
<script>
|
||||
$('#filterForm').submit(function (e) {
|
||||
// var formData = $(this).serialize();
|
||||
// $('#field').attr('hx-vals',`{'data':${formData}}` );
|
||||
});
|
||||
</script>
|
||||
@@ -0,0 +1,85 @@
|
||||
{% load i18n %}
|
||||
<div id="activity-table">
|
||||
<div class="oh-sticky-table" >
|
||||
<div class="oh-sticky-table__table oh-table--sortable">
|
||||
<div class="oh-sticky-table__thead">
|
||||
<div class="oh-sticky-table__tr">
|
||||
<div class="oh-sticky-table__th"hx-target='#activity-table' hx-get="{% url 'attendance-activity-search' %}?{{pd}}&orderby=employee_id__employee_first_name">{% trans "Employee" %}</div>
|
||||
<div class='oh-sticky-table__th'hx-target='#activity-table' hx-get="{% url 'attendance-activity-search' %}?{{pd}}&orderby=attendance_date">{% trans "Attendnace Date" %}</div>
|
||||
<div class='oh-sticky-table__th'hx-target='#activity-table' hx-get="{% url 'attendance-activity-search' %}?{{pd}}&orderby=clock_in_date">{% trans "In Date" %}</div>
|
||||
<div class='oh-sticky-table__th'hx-target='#activity-table' hx-get="{% url 'attendance-activity-search' %}?{{pd}}&orderby=">{% trans "Clock In" %}</div>
|
||||
<div class='oh-sticky-table__th'hx-target='#activity-table' hx-get="{% url 'attendance-activity-search' %}?{{pd}}&orderby=">{% trans "Clock Out" %}</div>
|
||||
<div class='oh-sticky-table__th'hx-target='#activity-table' hx-get="{% url 'attendance-activity-search' %}?{{pd}}&orderby=clock_out_date">{% trans "Out Date" %}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="oh-sticky-table__tbody">
|
||||
{% for activity in data %}
|
||||
<div class="oh-sticky-table__tr" draggable="true">
|
||||
<div class="oh-sticky-table__sd">
|
||||
<div class="oh-profile oh-profile--md">
|
||||
<div class="oh-profile__avatar mr-1">
|
||||
<img
|
||||
src="https://ui-avatars.com/api/?name={{activity.employee_id.employee_first_name}}+{{activity.employee_id.employee_last_name}}&background=random"
|
||||
class="oh-profile__image"
|
||||
alt="Mary Magdalene"
|
||||
/>
|
||||
</div>
|
||||
<span class="oh-profile__name oh-text--dark"
|
||||
>{{activity.employee_id}}</span
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
<div class="oh-sticky-table__td">{{activity.attendance_date}}</div>
|
||||
<div class="oh-sticky-table__td">{{activity.clock_in_date}}</div>
|
||||
<div class="oh-sticky-table__td">{{activity.clock_in}}</div>
|
||||
<div class="oh-sticky-table__td">{{activity.clock_out}}</div>
|
||||
<div class="oh-sticky-table__td">{{activity.clock_out_date}}</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<div class="oh-pagination">
|
||||
<span
|
||||
class="oh-pagination__page"
|
||||
>
|
||||
{% trans "Page" %} {{ data.number }} {% trans "of" %} {{ data.paginator.num_pages }}.
|
||||
</span
|
||||
>
|
||||
<nav class="oh-pagination__nav">
|
||||
<div class="oh-pagination__input-container me-3">
|
||||
<span class="oh-pagination__label me-1">{% trans "Page" %}</span>
|
||||
<input
|
||||
type="number"
|
||||
name="page"
|
||||
class="oh-pagination__input"
|
||||
value="{{data.number}}"
|
||||
hx-get="{% url 'attendance-activity-search' %}?{{pd}}"
|
||||
hx-target="#activity-table"
|
||||
min="1"
|
||||
/>
|
||||
<span class="oh-pagination__label">{% trans "of" %} {{data.paginator.num_pages}}</span>
|
||||
</div>
|
||||
<ul class="oh-pagination__items">
|
||||
{% if data.has_previous %}
|
||||
<li class="oh-pagination__item oh-pagination__item--wide">
|
||||
<a hx-target='#activity-table' hx-get="{% url 'attendance-activity-search' %}?{{pd}}&page=1" class="oh-pagination__link">{% trans "First" %}</a>
|
||||
</li>
|
||||
<li class="oh-pagination__item oh-pagination__item--wide">
|
||||
<a hx-target='#activity-table' hx-get="{% url 'attendance-activity-search' %}?{{pd}}&page={{ data.previous_page_number }}" class="oh-pagination__link">{% trans "Previous" %}</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
{% if data.has_next %}
|
||||
<li class="oh-pagination__item oh-pagination__item--wide">
|
||||
<a hx-target='#activity-table' hx-get="{% url 'attendance-activity-search' %}?{{pd}}&page={{ data.next_page_number }}" class="oh-pagination__link">{% trans "Next" %}</a>
|
||||
</li>
|
||||
<li class="oh-pagination__item oh-pagination__item--wide">
|
||||
<a hx-target='#activity-table' hx-get="{% url 'attendance-activity-search' %}?{{pd}}&page={{ data.paginator.num_pages }}" class="oh-pagination__link">{% trans "Last" %}</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
</ul>
|
||||
</nav>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
{% extends 'index.html' %}{% block content %}
|
||||
|
||||
<div class="oh-wrapper">
|
||||
{% include 'attendance/attendance_activity/nav.html' %}
|
||||
{% include 'attendance/attendance_activity/activity_list.html' %}
|
||||
</div>
|
||||
|
||||
|
||||
{% endblock %}
|
||||
@@ -0,0 +1,121 @@
|
||||
{% load i18n %}
|
||||
{% load attendancefilters %}
|
||||
<div class="oh-sticky-table">
|
||||
<div class="oh-sticky-table__table oh-table__sticky-collaspable-sort">
|
||||
<div class="oh-sticky-table__thead">
|
||||
<div class="oh-sticky-table__tr">
|
||||
<div class="oh-sticky-table__th">{% trans "Employee" %}</div>
|
||||
<div class='oh-sticky-table__th'>{% trans "Attendnace Date" %}</div>
|
||||
<div class='oh-sticky-table__th'>{% trans "In Date" %}</div>
|
||||
<div class='oh-sticky-table__th'>{% trans "Clock In" %}</div>
|
||||
<div class='oh-sticky-table__th'>{% trans "Clock Out" %}</div>
|
||||
<div class='oh-sticky-table__th'>{% trans "Out Date" %}</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- grouper -->
|
||||
{% dynamic_regroup data by field as attendances_grouper %}
|
||||
{% for attendance_list in attendances_grouper %}
|
||||
<div class="oh-sticky-table__tbody" draggable="true">
|
||||
<div class="oh-sticky-table__tr oh-table__toggle-parent" data-target="{{attendance_list.grouper}}">
|
||||
<div class="oh-sticky-table__sd">
|
||||
<div class="d-flex align-items-center">
|
||||
<button class="oh-btn oh-btn--transparent oh-table__toggle-button"></button>
|
||||
<span class="ms-2">{{attendance_list.grouper}}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="oh-sticky-table__td"></div>
|
||||
<div class="oh-sticky-table__td"></div>
|
||||
<div class="oh-sticky-table__td"></div>
|
||||
<div class="oh-sticky-table__td"></div>
|
||||
<div class="oh-sticky-table__td"></div>
|
||||
</div>
|
||||
<!-- data -->
|
||||
{% for activity in attendance_list.list %}
|
||||
<div class="oh-sticky-table__tr oh-table__toggle-child" data-group="{{attendance_list.grouper}}">
|
||||
|
||||
<div class="oh-sticky-table__sd">
|
||||
<div class="oh-profile oh-profile--md">
|
||||
<div class="oh-profile__avatar mr-1">
|
||||
<img
|
||||
src="https://ui-avatars.com/api/?name={{activity.employee_id.employee_first_name}}+{{activity.employee_id.employee_last_name}}&background=random"
|
||||
class="oh-profile__image"
|
||||
alt="Mary Magdalene"
|
||||
/>
|
||||
</div>
|
||||
<span class="oh-profile__name oh-text--dark"
|
||||
>{{activity.employee_id}}</span
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
<div class="oh-sticky-table__td">{{activity.attendance_date}}</div>
|
||||
<div class="oh-sticky-table__td">{{activity.clock_in_date}}</div>
|
||||
<div class="oh-sticky-table__td">{{activity.clock_in}}</div>
|
||||
<div class="oh-sticky-table__td">{{activity.clock_out}}</div>
|
||||
<div class="oh-sticky-table__td">{{activity.clock_out_date}}</div>
|
||||
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
</div>
|
||||
<div class="oh-pagination">
|
||||
<span
|
||||
class="oh-pagination__page"
|
||||
>
|
||||
{% trans "Page" %} {{ data.number }} {% trans "of" %} {{ data.paginator.num_pages }}.
|
||||
</span
|
||||
>
|
||||
<nav class="oh-pagination__nav">
|
||||
<div class="oh-pagination__input-container me-3">
|
||||
<span class="oh-pagination__label me-1">{% trans "Page" %}</span>
|
||||
<input
|
||||
type="number"
|
||||
name="page"
|
||||
class="oh-pagination__input"
|
||||
value="{{data.number}}"
|
||||
hx-get="{% url 'attendance-activity-search' %}?{{pd}}"
|
||||
hx-target="#activity-table"
|
||||
min="1"
|
||||
/>
|
||||
<span class="oh-pagination__label">{% trans "of" %} {{data.paginator.num_pages}}</span>
|
||||
</div>
|
||||
<ul class="oh-pagination__items">
|
||||
{% if data.has_previous %}
|
||||
<li class="oh-pagination__item oh-pagination__item--wide">
|
||||
<a hx-target='#activity-table' hx-get="{% url 'attendance-activity-search' %}?{{pd}}&page=1" class="oh-pagination__link">{% trans "First" %}</a>
|
||||
</li>
|
||||
<li class="oh-pagination__item oh-pagination__item--wide">
|
||||
<a hx-target='#activity-table' hx-get="{% url 'attendance-activity-search' %}?{{pd}}&page={{ data.previous_page_number }}" class="oh-pagination__link">{% trans "Previous" %}</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
{% if data.has_next %}
|
||||
<li class="oh-pagination__item oh-pagination__item--wide">
|
||||
<a hx-target='#activity-table' hx-get="{% url 'attendance-activity-search' %}?{{pd}}&page={{ data.next_page_number }}" class="oh-pagination__link">{% trans "Next" %}</a>
|
||||
</li>
|
||||
<li class="oh-pagination__item oh-pagination__item--wide">
|
||||
<a hx-target='#activity-table' hx-get="{% url 'attendance-activity-search' %}?{{pd}}&page={{ data.paginator.num_pages }}" class="oh-pagination__link">{% trans "Last" %}</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
</ul>
|
||||
</nav>
|
||||
</div>
|
||||
|
||||
|
||||
<script>
|
||||
$('.oh-table__sticky-collaspable-sort').click(function (e) {
|
||||
e.preventDefault();
|
||||
let clickedEl = $(e.target).closest(".oh-table__toggle-parent");
|
||||
let targetSelector = clickedEl.data("target");
|
||||
let toggleBtn = clickedEl.find(".oh-table__toggle-button");
|
||||
$(`[data-group='${targetSelector}']`).toggleClass(
|
||||
"oh-table__toggle-child--show"
|
||||
);
|
||||
if (toggleBtn) {
|
||||
toggleBtn.toggleClass("oh-table__toggle-button--show");
|
||||
}
|
||||
});
|
||||
</script>
|
||||
79
attendance/templates/attendance/attendance_activity/nav.html
Normal file
79
attendance/templates/attendance/attendance_activity/nav.html
Normal file
@@ -0,0 +1,79 @@
|
||||
{% load i18n %}
|
||||
<section class="oh-wrapper oh-main__topbar " x-data="{searchShow: false}">
|
||||
<div class="oh-main__titlebar oh-main__titlebar--left">
|
||||
<h1 class="oh-main__titlebar-title fw-bold">
|
||||
<a href="{% url 'attendance-activity-view' %}" class='text-dark'>
|
||||
{% trans "Attendance Activity" %}
|
||||
</a>
|
||||
</h1>
|
||||
<a
|
||||
class="oh-main__titlebar-search-toggle"
|
||||
role="button"
|
||||
aria-label="Toggle Search"
|
||||
@click="searchShow = !searchShow"
|
||||
>
|
||||
<ion-icon
|
||||
name="search-outline"
|
||||
class="oh-main__titlebar-serach-icon"
|
||||
></ion-icon>
|
||||
</a>
|
||||
</div>
|
||||
<div class="oh-main__titlebar oh-main__titlebar--right">
|
||||
<div
|
||||
class="oh-input-group oh-input__search-group"
|
||||
:class="searchShow ? 'oh-input__search-group--show' : ''"
|
||||
>
|
||||
<ion-icon
|
||||
name="search-outline"
|
||||
class="oh-input-group__icon oh-input-group__icon--left"
|
||||
></ion-icon>
|
||||
<input
|
||||
type="text"
|
||||
class="oh-input oh-input__icon"
|
||||
aria-label="Search Input"
|
||||
id="attendance-search"
|
||||
name='search'
|
||||
placeholder="{% trans 'Search' %}"
|
||||
hx-get="{% url 'attendance-activity-search' %}"
|
||||
hx-trigger="keyup changed delay:500ms, search"
|
||||
hx-target="#activity-table"
|
||||
hx-swap="innerHTML"
|
||||
/>
|
||||
</div>
|
||||
<div class="oh-main__titlebar-button-container">
|
||||
<div class="oh-dropdown" x-data="{open: false}">
|
||||
<button class="oh-btn ml-2" @click="open = !open">
|
||||
<ion-icon name="filter" class="mr-1"></ion-icon>{% trans "Filter" %}
|
||||
</button>
|
||||
<div
|
||||
class="oh-dropdown__menu oh-dropdown__menu--right oh-dropdown__filter p-4"
|
||||
x-show="open"
|
||||
@click.outside="open = false"
|
||||
style="display: none;"
|
||||
>
|
||||
{% include 'attendance/attendance_activity/activity_filters.html' %}
|
||||
</div>
|
||||
</div>
|
||||
<div class="oh-btn-group ml-2">
|
||||
<div class="oh-dropdown" x-data="{open: false}">
|
||||
<button
|
||||
class="oh-btn oh-btn--dropdown oh-btn--secondary oh-btn--shadow"
|
||||
@click="open = !open"
|
||||
@click.outside="open = false"
|
||||
>
|
||||
{% trans "Actions" %}
|
||||
</button>
|
||||
<div class="oh-dropdown__menu oh-dropdown__menu--right" x-show="open" style="display: none;">
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<script>
|
||||
$('#attendance-search').keydown(function (e) {
|
||||
var val = $(this).val();
|
||||
$('.pg').attr('hx-vals', `{"search":${val}}`);
|
||||
});
|
||||
</script>
|
||||
</section>
|
||||
26
attendance/templates/attendance/break_point/condition.html
Normal file
26
attendance/templates/attendance/break_point/condition.html
Normal file
@@ -0,0 +1,26 @@
|
||||
{% extends 'settings.html' %}
|
||||
{% load i18n %}
|
||||
{% block settings %}
|
||||
<div class="oh-inner-sidebar-content">
|
||||
|
||||
<form action="" method="post">
|
||||
{% csrf_token %}
|
||||
<div class="oh-inner-sidebar-content__header">
|
||||
<h2 class="oh-inner-sidebar-content__title">{% trans "Attendance Condition" %}</h2>
|
||||
</div>
|
||||
<div class="oh-inner-sidebar-content__body">
|
||||
<div class="oh-input-group">
|
||||
{{form}}
|
||||
</div>
|
||||
</div>
|
||||
<button type='submit' class="oh-btn oh-btn--secondary mt-2 mr-0 oh-btn--w-100-resp">
|
||||
{% trans "Save Changes" %}
|
||||
</button>
|
||||
<div class="oh-inner-sidebar-content__footer">
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
{% include 'attendance/break_point/condition_view.html' %}
|
||||
</div>
|
||||
{% endblock settings %}
|
||||
@@ -0,0 +1,32 @@
|
||||
{% load i18n %}
|
||||
<div class="oh-sticky-table">
|
||||
<div class="oh-sticky-table__table oh-table--sortable">
|
||||
<div class="oh-sticky-table__thead">
|
||||
<div class="oh-sticky-table__tr">
|
||||
<div class="oh-sticky-table__th">{% trans "Auto Validate Till" %}</div>
|
||||
<div class="oh-sticky-table__th">{% trans "Min Hour To Approve OT" %}</div>
|
||||
<div class="oh-sticky-table__th">{% trans "OT Cut-Off/Day" %}</div>
|
||||
<div class="oh-sticky-table__th"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="oh-sticky-table__tbody">
|
||||
{% if condition != None %}
|
||||
<div class="oh-sticky-table__tr" draggable="true">
|
||||
|
||||
<div class="oh-sticky-table__td">
|
||||
{{condition.validation_at_work}}
|
||||
</div>
|
||||
<div class="oh-sticky-table__td">
|
||||
{{condition.minimum_overtime_to_approve}}
|
||||
</div>
|
||||
<div class="oh-sticky-table__td">
|
||||
{{condition.overtime_cutoff}}
|
||||
</div>
|
||||
<div class="oh-sticky-table__td">
|
||||
<a href="{% url 'validation-condition-update' condition.id %}" type="button" class="oh-btn oh-btn--info"> {% trans "Edit" %}</a>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
181
attendance/templates/attendance/dashboard/dashboard.html
Normal file
181
attendance/templates/attendance/dashboard/dashboard.html
Normal file
@@ -0,0 +1,181 @@
|
||||
{% extends 'index.html' %}
|
||||
{% block content %}
|
||||
{% load static %}
|
||||
<div class="oh-wrapper">
|
||||
<div class="oh-dashboard row">
|
||||
<div class="oh-dashboard__left col-12 col-sm-12 col-md-12 col-lg-9">
|
||||
<div class="oh-dashboard__cards row">
|
||||
<div class="col-12 col-sm-12 col-md-6 col-lg-4">
|
||||
<div class="oh-card-dashboard oh-card-dashboard oh-card-dashboard--success">
|
||||
<div class="oh-card-dashboard__header">
|
||||
<span class="oh-card-dashboard__title">On Time</span>
|
||||
</div>
|
||||
<div class="oh-card-dashboard__body">
|
||||
<div class="oh-card-dashboard__counts">
|
||||
<span class="oh-card-dashboard__sign">
|
||||
{% if on_time < late_come %}
|
||||
<ion-icon name="caret-down-outline"></ion-icon>
|
||||
{% else %}
|
||||
<ion-icon name="caret-up-outline"></ion-icon>
|
||||
{% endif %}
|
||||
</span>
|
||||
<span class="oh-card-dashboard__count">{{on_time}}</span>
|
||||
</div>
|
||||
<span class="oh-badge oh-card-dashboard__badge"
|
||||
>{{on_time_ratio}}%</span
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-12 col-sm-12 col-md-6 col-lg-4">
|
||||
<div class="oh-card-dashboard oh-card-dashboard--neutral">
|
||||
<div class="oh-card-dashboard__header">
|
||||
<span class="oh-card-dashboard__title"
|
||||
>Today's Attendances</span
|
||||
>
|
||||
</div>
|
||||
<div class="oh-card-dashboard__body">
|
||||
<div class="oh-card-dashboard__counts">
|
||||
{% comment %} <span class="oh-card-dashboard__count">100%</span> {% endcomment %}
|
||||
<span class="oh-card-dashboard__count">{{marked_attendances_ratio}}%</span>
|
||||
</div>
|
||||
<span class="oh-badge oh-card-dashboard__badge"
|
||||
>{{marked_attendances}}/{{expected_attendances}}</span
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-12 col-sm-12 col-md-6 col-lg-4">
|
||||
<div class="oh-card-dashboard oh-card-dashboard--danger">
|
||||
<div class="oh-card-dashboard__header">
|
||||
<span class="oh-card-dashboard__title"
|
||||
>Late Come</span
|
||||
>
|
||||
</div>
|
||||
<div class="oh-card-dashboard__body">
|
||||
<div class="oh-card-dashboard__counts">
|
||||
<span class="oh-card-dashboard__sign">
|
||||
{% if late_come < on_time %}
|
||||
<ion-icon name="caret-down-outline"></ion-icon>
|
||||
{% else %}
|
||||
<ion-icon name="caret-up-outline"></ion-icon>
|
||||
{% endif %}
|
||||
|
||||
</span>
|
||||
<span class="oh-card-dashboard__count">{{late_come}}</span>
|
||||
</div>
|
||||
<span class="oh-badge oh-card-dashboard__badge"
|
||||
>{{late_come_ratio}}%</span
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="oh-dashboard__movable-cards row mt-4">
|
||||
<div class="col-12 col-sm-12 col-md-12 col-lg-6 oh-card-dashboard--moveable">
|
||||
<div
|
||||
class="oh-card-dashboard oh-card-dashboard--no-scale oh-card-dashboard--transparent"
|
||||
>
|
||||
<div class="oh-card-dashboard__header oh-card-dashboard__header--divider">
|
||||
<span class="oh-card-dashboard__title">Day Analytic</span>
|
||||
</div>
|
||||
<div class="oh-card-dashboard__body">
|
||||
<canvas id="dailyAnalytic"></canvas>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-12 col-sm-12 col-md-12 col-lg-6 oh-card-dashboard--moveable">
|
||||
<div
|
||||
class="oh-card-dashboard oh-card-dashboard--no-scale oh-card-dashboard--transparent"
|
||||
>
|
||||
<div class="oh-card-dashboard__header oh-card-dashboard__header--divider">
|
||||
<span class="oh-card-dashboard__title">Overall Conversions</span>
|
||||
</div>
|
||||
<div class="oh-card-dashboard__body">
|
||||
<canvas id="myChart2"></canvas>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="oh-dashboard__right col-12 col-sm-12 col-md-12 col-lg-3">
|
||||
<div class="oh-dashboard__events">
|
||||
<div class="oh-dashbaord__events-reel">
|
||||
<div class="oh-dashboard__event oh-dashboard__event--purple">
|
||||
<div class="oh-dasboard__event-photo">
|
||||
<img src="/static/images/upload/userphoto.png" class="oh-dashboard__event-userphoto"/>
|
||||
</div>
|
||||
<div class="oh-dasboard__event-details">
|
||||
<span class="oh-dashboard__event-title">Birthday</span>
|
||||
<span class="oh-dashboard__event-main">Katie Melua</span>
|
||||
<span class="oh-dashboard__event-date">29/03/2023</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="oh-dashboard__event oh-dashboard__event--crimson">
|
||||
<div class="oh-dasboard__event-photo">
|
||||
<img src="/static/images/upload/userphoto.png" class="oh-dashboard__event-userphoto"/>
|
||||
</div>
|
||||
|
||||
<div class="oh-dasboard__event-details">
|
||||
<span class="oh-dashboard__event-title">Birthday</span>
|
||||
<span class="oh-dashboard__event-main">Katie Melua</span>
|
||||
<span class="oh-dashboard__event-date">29/03/2023</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="oh-dashboard__event oh-dashboard__event--purple">
|
||||
<div class="oh-dasboard__event-photo">
|
||||
<img src="/static/images/upload/userphoto.png" class="oh-dashboard__event-userphoto"/>
|
||||
</div>
|
||||
<div class="oh-dasboard__event-details">
|
||||
<span class="oh-dashboard__event-title">Birthday</span>
|
||||
<span class="oh-dashboard__event-main">Katie Melua</span>
|
||||
<span class="oh-dashboard__event-date">29/03/2023</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<ul class="oh-dashboard__events-nav">
|
||||
<li class="oh-dashboard__events-nav-item oh-dashboard__events-nav-item--active" data-target="0"></li>
|
||||
<li class="oh-dashboard__events-nav-item" data-target="1"></li>
|
||||
<li class="oh-dashboard__events-nav-item" data-target="2"></li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="oh-card-dashboard oh-card-dashboard--no-scale oh-card-dashboard--transparent mt-3">
|
||||
<div class="oh-card-dashboard__header oh-card-dashboard__header--divider">
|
||||
<span class="oh-card-dashboard__title">On Break</span>
|
||||
</div>
|
||||
|
||||
<div class="oh-card-dashboard__body">
|
||||
<ul class="oh-card-dashboard__user-list">
|
||||
{% for emp in on_break %}
|
||||
<li class="oh-card-dashboard__user-item">
|
||||
<div class="oh-profile oh-profile--md">
|
||||
<div class="oh-profile__avatar mr-1">
|
||||
<img
|
||||
src="https://ui-avatars.com/api/?name={{emp.employee_id}}&background=random"
|
||||
class="oh-profile__image"
|
||||
alt="Amy Winehouse"
|
||||
/>
|
||||
</div>
|
||||
<span class="oh-profile__name oh-text--dark"
|
||||
>{{emp.employee_id}}</span
|
||||
>
|
||||
</div>
|
||||
</li>
|
||||
{% endfor %}
|
||||
|
||||
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
|
||||
|
||||
<script src="{% static 'dashboard/attendanceChart.js' %}"></script>
|
||||
|
||||
{% endblock content %}
|
||||
@@ -0,0 +1,134 @@
|
||||
{% load i18n %}
|
||||
{% load attendancefilters %}
|
||||
<div class="oh-sticky-table">
|
||||
<div class="oh-sticky-table__table oh-table__sticky-collaspable-sort">
|
||||
<div class="oh-sticky-table__thead">
|
||||
<div class="oh-sticky-table__tr">
|
||||
<div class='oh-sticky-table__th' scope="col">{% trans "Employee" %}</div>
|
||||
<div class='oh-sticky-table__th' scope="col">{% trans "Type" %}</div>
|
||||
<div class='oh-sticky-table__th' scope="col">{% trans "Attendance Date" %}</div>
|
||||
<div class='oh-sticky-table__th' scope="col">{% trans "Clock In" %}</div>
|
||||
<div class='oh-sticky-table__th' scope="col">{% trans "In Date" %}</div>
|
||||
<div class='oh-sticky-table__th' scope="col">{% trans "Clock Out" %}</div>
|
||||
<div class='oh-sticky-table__th' scope="col">{% trans "Out Date" %}</div>
|
||||
<div class='oh-sticky-table__th' scope="col">{% trans "Min Hour" %}</div>
|
||||
<div class='oh-sticky-table__th' scope="col">{% trans "At Work" %}</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- grouper -->
|
||||
{% dynamic_regroup data by field as reports_grouper %}
|
||||
{% for attendance_list in reports_grouper %}
|
||||
<div class="oh-sticky-table__tbody" draggable="true">
|
||||
<div class="oh-sticky-table__tr oh-table__toggle-parent" data-target="{{attendance_list.grouper}}">
|
||||
<div class="oh-sticky-table__sd">
|
||||
<div class="d-flex align-items-center">
|
||||
<button class="oh-btn oh-btn--transparent oh-table__toggle-button"></button>
|
||||
<span class="ms-2">
|
||||
<div class="oh-tabs__input-badge-container">
|
||||
<span class="oh-badge oh-badge--secondary oh-badge--small oh-badge--round mr-1">
|
||||
{{attendance_list.list|length}}
|
||||
</span>
|
||||
{{attendance_list.grouper}}
|
||||
</div>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="oh-sticky-table__td"></div>
|
||||
<div class="oh-sticky-table__td"></div>
|
||||
<div class="oh-sticky-table__td"></div>
|
||||
<div class="oh-sticky-table__td"></div>
|
||||
<div class="oh-sticky-table__td"></div>
|
||||
<div class="oh-sticky-table__td"></div>
|
||||
<div class="oh-sticky-table__td"></div>
|
||||
<div class="oh-sticky-table__td"></div>
|
||||
</div>
|
||||
<!-- data -->
|
||||
{% for late_in_early_out in attendance_list.list %}
|
||||
<div class="oh-sticky-table__tr oh-table__toggle-child" data-group="{{attendance_list.grouper}}">
|
||||
|
||||
<div class="oh-sticky-table__sd">
|
||||
<div class="oh-profile oh-profile--md">
|
||||
<div class="oh-profile__avatar mr-1">
|
||||
<img
|
||||
src="https://ui-avatars.com/api/?name={{late_in_early_out.employee_id.employee_first_name}}+{{late_in_early_out.employee_id.employee_last_name}}&background=random"
|
||||
class="oh-profile__image"
|
||||
alt="Mary Magdalene"
|
||||
/>
|
||||
</div>
|
||||
<span class="oh-profile__name oh-text--dark"
|
||||
>{{late_in_early_out.employee_id}}</span
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
<div class='oh-sticky-table__td'>{{late_in_early_out.type}}</div>
|
||||
<div class='oh-sticky-table__td'>{{late_in_early_out.attendance_id.attendance_date}}</div>
|
||||
<div class='oh-sticky-table__td'>{{late_in_early_out.attendance_id.attendance_clock_in}}</div>
|
||||
<div class='oh-sticky-table__td'>{{late_in_early_out.attendance_id.attendance_clock_in_date}}</div>
|
||||
<div class='oh-sticky-table__td'>{{late_in_early_out.attendance_id.attendance_clock_out}}</div>
|
||||
<div class='oh-sticky-table__td'>{{late_in_early_out.attendance_id.attendance_clock_out_date}}</div>
|
||||
<div class='oh-sticky-table__td'>{{late_in_early_out.attendance_id.minimum_hour}}</div>
|
||||
<div class='oh-sticky-table__td'>{{late_in_early_out.attendance_id.attendance_worked_hour}}</div>
|
||||
|
||||
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="oh-pagination">
|
||||
<span
|
||||
class="oh-pagination__page"
|
||||
>
|
||||
{% trans "Page" %} {{ data.number }} {% trans "of" %} {{ data.paginator.num_pages }}.
|
||||
</span
|
||||
>
|
||||
<nav class="oh-pagination__nav">
|
||||
<div class="oh-pagination__input-container me-3">
|
||||
<span class="oh-pagination__label me-1">{% trans "Page" %}</span>
|
||||
<input
|
||||
type="number"
|
||||
name="page"
|
||||
class="oh-pagination__input"
|
||||
value="{{data.number}}"
|
||||
hx-get="{% url 'late-come-early-out-search' %}?{{pd}}"
|
||||
hx-target="#report-container"
|
||||
min="1"
|
||||
/>
|
||||
<span class="oh-pagination__label">{% trans "of" %} {{data.paginator.num_pages}}</span>
|
||||
</div>
|
||||
<ul class="oh-pagination__items">
|
||||
{% if data.has_previous %}
|
||||
<li class="oh-pagination__item oh-pagination__item--wide">
|
||||
<a hx-target='#report-container' hx-get="{% url 'late-come-early-out-search' %}?{{pd}}&page=1" class="oh-pagination__link">{% trans "First" %}</a>
|
||||
</li>
|
||||
<li class="oh-pagination__item oh-pagination__item--wide">
|
||||
<a hx-target='#report-container' hx-get="{% url 'late-come-early-out-search' %}?{{pd}}&page={{ data.previous_page_number }}" class="oh-pagination__link">{% trans "Previous" %}</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
{% if data.has_next %}
|
||||
<li class="oh-pagination__item oh-pagination__item--wide">
|
||||
<a hx-target='#report-container' hx-get="{% url 'late-come-early-out-search' %}?{{pd}}&page={{ data.next_page_number }}" class="oh-pagination__link">{% trans "Next" %}</a>
|
||||
</li>
|
||||
<li class="oh-pagination__item oh-pagination__item--wide">
|
||||
<a hx-target='#report-container' hx-get="{% url 'late-come-early-out-search' %}?{{pd}}&page={{ data.paginator.num_pages }}" class="oh-pagination__link">{% trans "Last" %}</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
</ul>
|
||||
</nav>
|
||||
</div>
|
||||
<script>
|
||||
$('.oh-table__sticky-collaspable-sort').click(function (e) {
|
||||
e.preventDefault();
|
||||
let clickedEl = $(e.target).closest(".oh-table__toggle-parent");
|
||||
let targetSelector = clickedEl.data("target");
|
||||
let toggleBtn = clickedEl.find(".oh-table__toggle-button");
|
||||
$(`[data-group='${targetSelector}']`).toggleClass(
|
||||
"oh-table__toggle-child--show"
|
||||
);
|
||||
if (toggleBtn) {
|
||||
toggleBtn.toggleClass("oh-table__toggle-button--show");
|
||||
}
|
||||
});
|
||||
</script>
|
||||
@@ -0,0 +1,174 @@
|
||||
{% load static %}
|
||||
{% load i18n %}
|
||||
<form hx-get='{% url "late-come-early-out-search" %}' id="filterForm" hx-swap='innerHTML' hx-target='#report-container' >
|
||||
<div class="oh-dropdown__filter-body">
|
||||
<div class="oh-accordion">
|
||||
<div class="oh-accordion-header">{% trans "Work Info" %}</div>
|
||||
<div class="oh-accordion-body">
|
||||
<div class="row">
|
||||
<div class="col-sm-12 col-md-12 col-lg-6">
|
||||
<div class="oh-input-group">
|
||||
<label class="oh-label">{% trans "Employee" %}</label>
|
||||
{{f.form.employee_id}}
|
||||
</div>
|
||||
<div class="oh-input-group">
|
||||
<label class="oh-label">{% trans "Department" %}</label>
|
||||
{{f.form.employee_id__employee_work_info__department_id}}
|
||||
</div>
|
||||
<div class="oh-input-group">
|
||||
<label class="oh-label">{% trans "Shift" %}</label>
|
||||
{{f.form.attendance_id__shift_id}}
|
||||
</div>
|
||||
<div class="oh-input-group">
|
||||
<label class="oh-label">{% trans "Reporting Manager" %}</label>
|
||||
{{f.form.employee_id__employee_work_info__reporting_manager_id}}
|
||||
</div>
|
||||
|
||||
|
||||
</div>
|
||||
<div class="col-sm-12 col-md-12 col-lg-6">
|
||||
<div class="oh-input-group">
|
||||
<label class="oh-label">{% trans "Company" %}</label>
|
||||
{{f.form.employee_id__employee_work_info__company_id}}
|
||||
</div>
|
||||
<div class="oh-input-group">
|
||||
<label class="oh-label">{% trans "Job Position" %}</label>
|
||||
{{f.form.employee_id__employee_work_info__job_position_id}}
|
||||
</div>
|
||||
<div class="oh-input-group">
|
||||
<label class="oh-label">{% trans "Work Type" %}</label>
|
||||
{{f.form.attendance_id__work_type_id}}
|
||||
</div>
|
||||
<div class="oh-input-group">
|
||||
<label class="oh-label">{% trans "Work Location" %}</label>
|
||||
{{f.form.employee_id__employee_work_info__location}}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% comment %} {{f.form}} {% endcomment %}
|
||||
<div class="oh-accordion">
|
||||
<div class="oh-accordion-header">{% trans "Late Come/Early Out" %}</div>
|
||||
<div class="oh-accordion-body">
|
||||
<div class="row">
|
||||
<div class="col-sm-12 col-md-12 col-lg-6">
|
||||
<div class="oh-input-group">
|
||||
<label class="oh-label">{% trans "Type" %}</label>
|
||||
{{f.form.type}}
|
||||
</div>
|
||||
<div class="oh-input-group">
|
||||
<label class="oh-label">{% trans "Attendance Date" %}</label>
|
||||
{{f.form.attendance_date}}
|
||||
</div>
|
||||
<div class="oh-input-group">
|
||||
<label class="oh-label">{% trans "Out Time" %}</label>
|
||||
{{f.form.attendance_clock_out}}
|
||||
</div>
|
||||
<div class="oh-input-group">
|
||||
<label class="oh-label">{% trans "Validated?" %}</label>
|
||||
{{f.form.attendance_id__attendance_validated}}
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-sm-12 col-md-12 col-lg-6">
|
||||
<div class="oh-input-group">
|
||||
<label class="oh-label">{% trans "Min Hour" %}</label>
|
||||
{{f.form.attendance_id__minimum_hour}}
|
||||
</div>
|
||||
<div class="oh-input-group">
|
||||
<label class="oh-label">{% trans "In Time" %}</label>
|
||||
{{f.form.attendance_clock_out}}
|
||||
</div>
|
||||
<div class="oh-input-group">
|
||||
<label class="oh-label">{% trans "OT Approved?" %}</label>
|
||||
{{f.form.attendance_id__attendance_overtime_approve}}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="oh-accordion">
|
||||
<div class="oh-accordion-header">{% trans "Advanced" %}</div>
|
||||
<div class="oh-accordion-body">
|
||||
<div class="row">
|
||||
<div class="col-sm-12 col-md-12 col-lg-6">
|
||||
<div class="oh-input-group">
|
||||
<label class="oh-label">{% trans "Attendance From" %}</label>
|
||||
{{f.form.attendance_date__gte}}
|
||||
</div>
|
||||
<div class="oh-input-group">
|
||||
<label class="oh-label">{% trans "In From" %}</label>
|
||||
{{f.form.attendance_clock_in__lte}}
|
||||
</div>
|
||||
<div class="oh-input-group">
|
||||
<label class="oh-label">{% trans "Out From" %}</label>
|
||||
{{f.form.attendance_clock_out__lte}}
|
||||
</div>
|
||||
<div class="oh-input-group">
|
||||
<label class="oh-label">{% trans "At Work Greater or Equal" %}</label>
|
||||
{{f.form.at_work_second__gte}}
|
||||
</div>
|
||||
<div class="oh-input-group">
|
||||
<label class="oh-label">{% trans "OT Greater or Equal" %}</label>
|
||||
{{f.form.overtime_second__gte}}
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-sm-12 col-md-12 col-lg-6">
|
||||
<div class="oh-input-group">
|
||||
<label class="oh-label">{% trans "Attendance Till" %}</label>
|
||||
{{f.form.attendance_date__lte}}
|
||||
</div>
|
||||
<div class="oh-input-group">
|
||||
<label class="oh-label">{% trans "In Till" %}</label>
|
||||
{{f.form.attendance_clock_in__lte}}
|
||||
</div>
|
||||
<div class="oh-input-group">
|
||||
<label class="oh-label">{% trans "Out Till" %}</label>
|
||||
{{f.form.attendance_clock_out__lte}}
|
||||
</div>
|
||||
<div class="oh-input-group">
|
||||
<label class="oh-label">{% trans "At Work Lessthan or Equal" %}</label>
|
||||
{{f.form.at_work_second__lte}}
|
||||
</div>
|
||||
<div class="oh-input-group">
|
||||
<label class="oh-label">{% trans "OT Lessthan or Equal" %}</label>
|
||||
{{f.form.overtime_second__gte}}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="oh-accordion">
|
||||
<div class="oh-accordion-header">{% trans "Group By" %}</div>
|
||||
<div class="oh-accordion-body">
|
||||
<div class="row">
|
||||
<div class="col-sm-12 col-md-12 col-lg-6">
|
||||
<div class="oh-input-group">
|
||||
<label class="oh-label">{% trans "Field" %}</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-sm-12 col-md-12 col-lg-6">
|
||||
<div class="oh-input-group">
|
||||
<select class="oh-select mt-1 " id="field" name="field" class="select2-selection select2-selection--single" id="gp">
|
||||
{% for field in gp_fields %}
|
||||
<option value="{{ field.0 }}">{{ field.1 }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="oh-dropdown__filter-footer">
|
||||
<button class="oh-btn oh-btn--secondary oh-btn--small w-100" id="filterSubmit">{% trans "Filter" %}</button>
|
||||
</div>
|
||||
|
||||
</form>
|
||||
|
||||
<script>
|
||||
$('#filterForm').submit(function (e) {
|
||||
// var formData = $(this).serialize();
|
||||
// $('#field').attr('hx-vals',`{'data':${formData}}` );
|
||||
});
|
||||
</script>
|
||||
95
attendance/templates/attendance/late_come_early_out/nav.html
Normal file
95
attendance/templates/attendance/late_come_early_out/nav.html
Normal file
@@ -0,0 +1,95 @@
|
||||
{% load i18n %}
|
||||
<section class="oh-wrapper oh-main__topbar " x-data="{searchShow: false}">
|
||||
<div class="oh-main__titlebar oh-main__titlebar--left">
|
||||
<h1 class="oh-main__titlebar-title fw-bold">
|
||||
<a href="{% url 'late-come-early-out-view' %}" class='text-dark'>
|
||||
{% trans "Late Come/Early Out" %}
|
||||
</a>
|
||||
|
||||
</h1>
|
||||
<a
|
||||
class="oh-main__titlebar-search-toggle"
|
||||
role="button"
|
||||
aria-label="Toggle Search"
|
||||
@click="searchShow = !searchShow"
|
||||
>
|
||||
<ion-icon
|
||||
name="search-outline"
|
||||
class="oh-main__titlebar-serach-icon"
|
||||
></ion-icon>
|
||||
</a>
|
||||
</div>
|
||||
<div class="oh-main__titlebar oh-main__titlebar--right">
|
||||
<div
|
||||
class="oh-input-group oh-input__search-group"
|
||||
:class="searchShow ? 'oh-input__search-group--show' : ''"
|
||||
>
|
||||
<ion-icon
|
||||
name="search-outline"
|
||||
class="oh-input-group__icon oh-input-group__icon--left"
|
||||
></ion-icon>
|
||||
<input
|
||||
type="text"
|
||||
class="oh-input oh-input__icon"
|
||||
aria-label="Search Input"
|
||||
id="report-search"
|
||||
name='search'
|
||||
placeholder="{% trans 'Search' %}"
|
||||
hx-get="{% url 'late-come-early-out-search' %}"
|
||||
hx-trigger="keyup changed delay:500ms, search"
|
||||
hx-target="#report-container"
|
||||
hx-swap="innerHTML"
|
||||
/>
|
||||
</div>
|
||||
<div class="oh-main__titlebar-button-container">
|
||||
<div class="oh-dropdown" x-data="{open: false}">
|
||||
<button class="oh-btn ml-2" @click="open = !open">
|
||||
<ion-icon name="filter" class="mr-1"></ion-icon>{% trans "Filter" %}
|
||||
</button>
|
||||
<div
|
||||
class="oh-dropdown__menu oh-dropdown__menu--right oh-dropdown__filter p-4"
|
||||
x-show="open"
|
||||
@click.outside="open = false"
|
||||
style="display: none;"
|
||||
>
|
||||
{% include 'attendance/late_come_early_out/late_come_early_out_filters.html' %}
|
||||
</div>
|
||||
</div>
|
||||
<div class="oh-btn-group ml-2">
|
||||
{% comment %} <div class="oh-dropdown" x-data="{open: false}">
|
||||
<button
|
||||
class="oh-btn oh-btn--dropdown oh-btn--secondary oh-btn--shadow"
|
||||
@click="open = !open"
|
||||
@click.outside="open = false"
|
||||
>
|
||||
Actions
|
||||
</button>
|
||||
<div class="oh-dropdown__menu oh-dropdown__menu--right" x-show="open">
|
||||
<ul class="oh-dropdown__items">
|
||||
<li class="oh-dropdown__item">
|
||||
<a href="#" class="oh-dropdown__link">Arrange</a>
|
||||
</li>
|
||||
<li class="oh-dropdown__item">
|
||||
<a href="#" class="oh-dropdown__link">Forward</a>
|
||||
</li>
|
||||
<li class="oh-dropdown__item">
|
||||
<a href="#" class="oh-dropdown__link">Archive</a>
|
||||
</li>
|
||||
<li class="oh-dropdown__item">
|
||||
<a href="#" class="oh-dropdown__link oh-dropdown__link--danger"
|
||||
>Delete</a
|
||||
>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div> {% endcomment %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<script>
|
||||
$('#attendance-search').keydown(function (e) {
|
||||
var val = $(this).val();
|
||||
$('.pg').attr('hx-vals', `{"search":${val}}`);
|
||||
});
|
||||
</script>
|
||||
</section>
|
||||
@@ -0,0 +1,94 @@
|
||||
{% load i18n %}
|
||||
<div div class="oh-sticky-table" >
|
||||
<div class="oh-sticky-table__table oh-table--sortable">
|
||||
<div class="oh-sticky-table__thead">
|
||||
<div class="oh-sticky-table__tr">
|
||||
<div class='oh-sticky-table__th' scope="col" hx-target='#report-container' hx-get="{% url 'late-come-early-out-search' %}?{{pd}}&sortby=employee_id__employee_first_name">{% trans "Employee" %}</div>
|
||||
<div class='oh-sticky-table__th' scope="col" hx-target='#report-container' hx-get="{% url 'late-come-early-out-search' %}?{{pd}}&sortby=type">{% trans "Type" %}</div>
|
||||
<div class='oh-sticky-table__th' scope="col" hx-target='#report-container' hx-get="{% url 'late-come-early-out-search' %}?{{pd}}&sortby=attendance_id__attendance_date">{% trans "Attendance Date" %}</div>
|
||||
<div class='oh-sticky-table__th' scope="col" hx-target='#report-container' hx-get="{% url 'late-come-early-out-search' %}?{{pd}}&sortby=">{% trans "Clock In" %}</div>
|
||||
<div class='oh-sticky-table__th' scope="col" hx-target='#report-container' hx-get="{% url 'late-come-early-out-search' %}?{{pd}}&sortby=attendance_id__attendance_clock_in_date">{% trans "In Date" %}</div>
|
||||
<div class='oh-sticky-table__th' scope="col" hx-target='#report-container' hx-get="{% url 'late-come-early-out-search' %}?{{pd}}&sortby=">{% trans "Clock Out" %}</div>
|
||||
<div class='oh-sticky-table__th' scope="col" hx-target='#report-container' hx-get="{% url 'late-come-early-out-search' %}?{{pd}}&sortby=attendance_id__attendance_clock_out_date">{% trans "Out Date" %}</div>
|
||||
<div class='oh-sticky-table__th' scope="col" hx-target='#report-container' hx-get="{% url 'late-come-early-out-search' %}?{{pd}}&sortby=">{% trans "Min Hour" %}</div>
|
||||
<div class='oh-sticky-table__th' scope="col" hx-target='#report-container' hx-get="{% url 'late-come-early-out-search' %}?{{pd}}&sortby=attendance_id__at_work_second">{% trans "At Work" %}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="oh-sticky-table__tbody">
|
||||
{% for late_in_early_out in data %}
|
||||
<div class="oh-sticky-table__tr" draggable="true">
|
||||
<div class="oh-sticky-table__sd">
|
||||
<div class="oh-profile oh-profile--md">
|
||||
<div class="oh-profile__avatar mr-1">
|
||||
<img
|
||||
src="https://ui-avatars.com/api/?name={{late_in_early_out.employee_id.employee_first_name}}+{{late_in_early_out.employee_id.employee_last_name}}&background=random"
|
||||
class="oh-profile__image"
|
||||
alt="Mary Magdalene"
|
||||
/>
|
||||
</div>
|
||||
<span class="oh-profile__name oh-text--dark"
|
||||
>{{late_in_early_out.employee_id}}</span
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
<div class='oh-sticky-table__td'>
|
||||
{% if late_in_early_out.type == 'late_come' %}
|
||||
{% trans "Late Come" %}
|
||||
{% else %}
|
||||
{% trans "Early Out" %}
|
||||
{% endif %}
|
||||
</div>
|
||||
<div class='oh-sticky-table__td'>{{late_in_early_out.attendance_id.attendance_date}}</div>
|
||||
<div class='oh-sticky-table__td'>{{late_in_early_out.attendance_id.attendance_clock_in}}</div>
|
||||
<div class='oh-sticky-table__td'>{{late_in_early_out.attendance_id.attendance_clock_in_date}}</div>
|
||||
<div class='oh-sticky-table__td'>{{late_in_early_out.attendance_id.attendance_clock_out}}</div>
|
||||
<div class='oh-sticky-table__td'>{{late_in_early_out.attendance_id.attendance_clock_out_date}}</div>
|
||||
<div class='oh-sticky-table__td'>{{late_in_early_out.attendance_id.minimum_hour}}</div>
|
||||
<div class='oh-sticky-table__td'>{{late_in_early_out.attendance_id.attendance_worked_hour}}</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="oh-pagination">
|
||||
<span
|
||||
class="oh-pagination__page"
|
||||
>
|
||||
{% trans "Page" %} {{ data.number }} {% trans "of" %} {{ data.paginator.num_pages }}.
|
||||
</span
|
||||
>
|
||||
<nav class="oh-pagination__nav">
|
||||
<div class="oh-pagination__input-container me-3">
|
||||
<span class="oh-pagination__label me-1">{% trans "Page" %}</span>
|
||||
<input
|
||||
type="number"
|
||||
name="page"
|
||||
class="oh-pagination__input"
|
||||
value="{{data.number}}"
|
||||
hx-get="{% url 'late-come-early-out-search' %}?{{pd}}"
|
||||
hx-target="#report-container"
|
||||
min="1"
|
||||
/>
|
||||
<span class="oh-pagination__label">{% trans "of" %} {{data.paginator.num_pages}}</span>
|
||||
</div>
|
||||
<ul class="oh-pagination__items">
|
||||
{% if data.has_previous %}
|
||||
<li class="oh-pagination__item oh-pagination__item--wide">
|
||||
<a hx-target='#report-container' hx-get="{% url 'late-come-early-out-search' %}?{{pd}}&page=1" class="oh-pagination__link">{% trans "First" %}</a>
|
||||
</li>
|
||||
<li class="oh-pagination__item oh-pagination__item--wide">
|
||||
<a hx-target='#report-container' hx-get="{% url 'late-come-early-out-search' %}?{{pd}}&page={{ data.previous_page_number }}" class="oh-pagination__link">{% trans "Previous" %}</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
{% if data.has_next %}
|
||||
<li class="oh-pagination__item oh-pagination__item--wide">
|
||||
<a hx-target='#report-container' hx-get="{% url 'late-come-early-out-search' %}?{{pd}}&page={{ data.next_page_number }}" class="oh-pagination__link">{% trans "Next" %}</a>
|
||||
</li>
|
||||
<li class="oh-pagination__item oh-pagination__item--wide">
|
||||
<a hx-target='#report-container' hx-get="{% url 'late-come-early-out-search' %}?{{pd}}&page={{ data.paginator.num_pages }}" class="oh-pagination__link">{% trans "Last" %}</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
</ul>
|
||||
</nav>
|
||||
</div>
|
||||
@@ -0,0 +1,11 @@
|
||||
{% extends 'index.html' %}
|
||||
{% block content %}
|
||||
|
||||
{% include 'attendance/late_come_early_out/nav.html' %}
|
||||
<div class="oh-wrapper">
|
||||
<di id='report-container'>
|
||||
{% include 'attendance/late_come_early_out/report_list.html' %}
|
||||
</di>
|
||||
</div>
|
||||
|
||||
{% endblock %}
|
||||
120
attendance/templates/attendance/own_attendance/attendances.html
Normal file
120
attendance/templates/attendance/own_attendance/attendances.html
Normal file
@@ -0,0 +1,120 @@
|
||||
{% load i18n %}
|
||||
<div class="oh-sticky-table">
|
||||
<div class="oh-sticky-table__table oh-table--sortable">
|
||||
<div class="oh-sticky-table__thead">
|
||||
<div class="oh-sticky-table__tr">
|
||||
<div class="oh-sticky-table__th" hx-target='#attendance-container' hx-get="{% url 'own-attendance-filter' %}?{{pd}}&orderby=employee_id__employee_first_name">{% trans "Employee" %}</div>
|
||||
<div class="oh-sticky-table__th" hx-target='#attendance-container' hx-get="{% url 'own-attendance-filter' %}?{{pd}}&orderby=attendance_date">{% trans "Date" %}</div>
|
||||
<div class="oh-sticky-table__th" hx-target='#attendance-container' hx-get="{% url 'own-attendance-filter' %}?{{pd}}&orderby=">{% trans "Day" %}</div>
|
||||
<div class="oh-sticky-table__th" hx-target='#attendance-container' hx-get="{% url 'own-attendance-filter' %}?{{pd}}&orderby=">{% trans "Clock In" %}</div>
|
||||
<div class="oh-sticky-table__th" hx-target='#attendance-container' hx-get="{% url 'own-attendance-filter' %}?{{pd}}&orderby=attendance_clock_in_date">{% trans "In Date" %}</div>
|
||||
<div class="oh-sticky-table__th" hx-target='#attendance-container' hx-get="{% url 'own-attendance-filter' %}?{{pd}}&orderby=">{% trans "Clock Out" %}</div>
|
||||
<div class="oh-sticky-table__th" hx-target='#attendance-container' hx-get="{% url 'own-attendance-filter' %}?{{pd}}&orderby=attendance_clock_out_date">{% trans "Out Date" %}</div>
|
||||
<div class="oh-sticky-table__th" hx-target='#attendance-container' hx-get="{% url 'own-attendance-filter' %}?{{pd}}&orderby=">{% trans "Shift" %}</div>
|
||||
<div class="oh-sticky-table__th" hx-target='#attendance-container' hx-get="{% url 'own-attendance-filter' %}?{{pd}}&orderby=">{% trans "Min Hour" %}</div>
|
||||
<div class="oh-sticky-table__th" hx-target='#attendance-container' hx-get="{% url 'own-attendance-filter' %}?{{pd}}&orderby=at_work_second">{% trans "At Work" %}</div>
|
||||
<div class="oh-sticky-table__th" hx-target='#attendance-container' hx-get="{% url 'own-attendance-filter' %}?{{pd}}&orderby=overtime_second">{% trans "Overtime" %}</div>
|
||||
<div class="oh-sticky-table__th"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="oh-sticky-table__tbody">
|
||||
{% for attendance in attendances %}
|
||||
<div class="oh-sticky-table__tr" data-toggle="oh-modal-toggle"
|
||||
data-target="#addAttendance" draggable="false">
|
||||
<div class="oh-sticky-table__sd">
|
||||
<div class="oh-profile oh-profile--md">
|
||||
<div class="oh-profile__avatar mr-1">
|
||||
<img
|
||||
src="https://ui-avatars.com/api/?name={{attendance.employee_id.employee_first_name}}+{{attendance.employee_id.employee_last_name}}&background=random"
|
||||
class="oh-profile__image"
|
||||
alt="Mary Magdalene"
|
||||
/>
|
||||
</div>
|
||||
<span class="oh-profile__name oh-text--dark"
|
||||
>{{attendance.employee_id}}</span
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
<div class="oh-sticky-table__td">
|
||||
{{attendance.attendance_date}}
|
||||
</div>
|
||||
<div class="oh-sticky-table__td">
|
||||
{{attendance.attendance_day|capfirst}}
|
||||
</div>
|
||||
<div class="oh-sticky-table__td">
|
||||
{{attendance.attendance_clock_in}}
|
||||
</div>
|
||||
<div class="oh-sticky-table__td">
|
||||
{{attendance.attendance_clock_in_date}}
|
||||
</div>
|
||||
<div class="oh-sticky-table__td">
|
||||
{{attendance.attendance_clock_out}}
|
||||
</div>
|
||||
<div class="oh-sticky-table__td">
|
||||
{{attendance.attendance_clock_out_date}}
|
||||
</div>
|
||||
<div class="oh-sticky-table__td">{{attendance.shift_id}}</div>
|
||||
<div class="oh-sticky-table__td">
|
||||
{{attendance.work_type_id}}
|
||||
</div>
|
||||
<div class="oh-sticky-table__td">
|
||||
{{attendance.attendance_worked_hour}}
|
||||
</div>
|
||||
<div class="oh-sticky-table__td">
|
||||
{{attendance.attendance_overtime}}
|
||||
</div>
|
||||
<div class="oh-sticky-table__td">
|
||||
{% if attendance.attendance_validated == True %}
|
||||
<a href="/attendance/revalidate-this-attendance/{{attendance.id}}/"class='oh-btn oh-btn--info'>{% trans "Revalidate" %}</a>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
<div class="oh-pagination">
|
||||
<span
|
||||
class="oh-pagination__page"
|
||||
>
|
||||
{% trans "Page" %} {{ attendances.number }} {% trans "of" %} {{ attendances.paginator.num_pages }}.
|
||||
</span
|
||||
>
|
||||
<nav class="oh-pagination__nav">
|
||||
<div class="oh-pagination__input-container me-3">
|
||||
<span class="oh-pagination__label me-1">{% trans "Page" %}</span>
|
||||
<input
|
||||
type="number"
|
||||
name="page"
|
||||
class="oh-pagination__input"
|
||||
value="{{attendances.number}}"
|
||||
hx-get="{% url 'own-attendance-filter' %}?{{pd}}"
|
||||
hx-target="#attendance-container"
|
||||
min="1"
|
||||
/>
|
||||
<span class="oh-pagination__label">{% trans "of" %} {{attendances.paginator.num_pages}}</span>
|
||||
</div>
|
||||
<ul class="oh-pagination__items">
|
||||
{% if attendances.has_previous %}
|
||||
<li class="oh-pagination__item oh-pagination__item--wide">
|
||||
<a hx-target='#attendance-container' hx-get="{% url 'own-attendance-filter' %}?{{pd}}&page=1" class="oh-pagination__link">{% trans "First" %}</a>
|
||||
</li>
|
||||
<li class="oh-pagination__item oh-pagination__item--wide">
|
||||
<a hx-target='#attendance-container' hx-get="{% url 'own-attendance-filter' %}?{{pd}}&page={{ attendances.previous_page_number }}" class="oh-pagination__link">{% trans "Previous" %}</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
{% if attendances.has_next %}
|
||||
<li class="oh-pagination__item oh-pagination__item--wide">
|
||||
<a hx-target='#attendance-container' hx-get="{% url 'own-attendance-filter' %}?{{pd}}&page={{ attendances.next_page_number }}" class="oh-pagination__link">{% trans "Next" %}</a>
|
||||
</li>
|
||||
<li class="oh-pagination__item oh-pagination__item--wide">
|
||||
<a hx-target='#attendance-container' hx-get="{% url 'own-attendance-filter' %}?{{pd}}&page={{ attendances.paginator.num_pages }}" class="oh-pagination__link">{% trans "Last" %}</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
</ul>
|
||||
</nav>
|
||||
</div>
|
||||
143
attendance/templates/attendance/own_attendance/filters.html
Normal file
143
attendance/templates/attendance/own_attendance/filters.html
Normal file
@@ -0,0 +1,143 @@
|
||||
{% load i18n %}
|
||||
<div class="oh-dropdown" x-data="{open: false}">
|
||||
<button class="oh-btn ml-2" @click="open = !open">
|
||||
<ion-icon name="filter" class="mr-1"></ion-icon>{% trans "Filter" %}
|
||||
</button>
|
||||
<div
|
||||
class="oh-dropdown__menu oh-dropdown__menu--right oh-dropdown__filter p-4"
|
||||
x-show="open"
|
||||
@click.outside="open = false"
|
||||
style="display: none;"
|
||||
>
|
||||
<form
|
||||
hx-get='{% url "filter-own-attendance" %}'
|
||||
id="filterForm"
|
||||
hx-swap="innerHTML"
|
||||
hx-target="#attendance-container"
|
||||
>
|
||||
<div class="oh-dropdown__filter-body">
|
||||
<div class="oh-accordion">
|
||||
<div class="oh-accordion-header">{% trans "Attendance" %}</div>
|
||||
<div class="oh-accordion-body">
|
||||
<div class="row">
|
||||
<div class="col-sm-12 col-md-12 col-lg-6">
|
||||
<div class="oh-input-group">
|
||||
<label class="oh-label">{% trans "Attendance Date" %}</label>
|
||||
{{f.form.attendance_date}}
|
||||
</div>
|
||||
<div class="oh-input-group">
|
||||
<label class="oh-label">{% trans "In Time" %}</label>
|
||||
{{f.form.attendance_clock_in}}
|
||||
</div>
|
||||
<div class="oh-input-group">
|
||||
<label class="oh-label">{% trans "Validated?" %}</label>
|
||||
{{f.form.attendance_validated}}
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-sm-12 col-md-12 col-lg-6">
|
||||
<div class="oh-input-group">
|
||||
<label class="oh-label">{% trans "Min Hour" %}</label>
|
||||
{{f.form.minimum_hour}}
|
||||
</div>
|
||||
<div class="oh-input-group">
|
||||
<label class="oh-label">{% trans "Out Time" %}</label>
|
||||
{{f.form.attendance_clock_out}}
|
||||
</div>
|
||||
<div class="oh-input-group">
|
||||
<label class="oh-label">{% trans "OT Approved?" %}</label>
|
||||
{{f.form.attendance_overtime_approve}}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="oh-accordion">
|
||||
<div class="oh-accordion-header">{% trans "Advanced" %}</div>
|
||||
<div class="oh-accordion-body">
|
||||
<div class="row">
|
||||
<div class="col-sm-12 col-md-12 col-lg-6">
|
||||
<div class="oh-input-group">
|
||||
<label class="oh-label">{% trans "Attendance From" %}</label>
|
||||
{{f.form.attendance_date__gte}}
|
||||
</div>
|
||||
<div class="oh-input-group">
|
||||
<label class="oh-label">{% trans "In From" %}</label>
|
||||
{{f.form.attendance_clock_in__lte}}
|
||||
</div>
|
||||
<div class="oh-input-group">
|
||||
<label class="oh-label">{% trans "Out From" %}</label>
|
||||
{{f.form.attendance_clock_out__lte}}
|
||||
</div>
|
||||
<div class="oh-input-group">
|
||||
<label class="oh-label">{% trans "At Work Greater or Equal" %}</label>
|
||||
{{f.form.at_work_second__gte}}
|
||||
</div>
|
||||
<div class="oh-input-group">
|
||||
<label class="oh-label">{% trans "OT Greater or Equal" %}</label>
|
||||
{{f.form.overtime_second__gte}}
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-sm-12 col-md-12 col-lg-6">
|
||||
<div class="oh-input-group">
|
||||
<label class="oh-label">{% trans "Attendance Till" %}</label>
|
||||
{{f.form.attendance_date__lte}}
|
||||
</div>
|
||||
<div class="oh-input-group">
|
||||
<label class="oh-label">{% trans "In Till" %}</label>
|
||||
{{f.form.attendance_clock_in__lte}}
|
||||
</div>
|
||||
<div class="oh-input-group">
|
||||
<label class="oh-label">{% trans "Out Till" %}</label>
|
||||
{{f.form.attendance_clock_out__lte}}
|
||||
</div>
|
||||
<div class="oh-input-group">
|
||||
<label class="oh-label">{% trans "At Work Lessthan or Equal" %}</label>
|
||||
{{f.form.at_work_second__lte}}
|
||||
</div>
|
||||
<div class="oh-input-group">
|
||||
<label class="oh-label">{% trans "OT Lessthan or Equal" %}</label>
|
||||
{{f.form.overtime_second__gte}}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="oh-accordion">
|
||||
<div class="oh-accordion-header">{% trans "Group By" %}</div>
|
||||
<div class="oh-accordion-body">
|
||||
<div class="row">
|
||||
<div class="col-sm-12 col-md-12 col-lg-6">
|
||||
<div class="oh-input-group">
|
||||
<label class="oh-label">{% trans "Field" %}</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-sm-12 col-md-12 col-lg-6">
|
||||
<div class="oh-input-group">
|
||||
<select
|
||||
class="oh-select mt-1"
|
||||
id="field"
|
||||
name="field"
|
||||
class="select2-selection select2-selection--single"
|
||||
id="gp"
|
||||
>
|
||||
{% for field in gp_fields %}
|
||||
<option value="{{ field.0 }}">{{ field.1 }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="oh-dropdown__filter-footer">
|
||||
<button
|
||||
class="oh-btn oh-btn--secondary oh-btn--small w-100"
|
||||
id="filterSubmit"
|
||||
>
|
||||
{% trans "Filter" %}
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
23
attendance/templates/attendance/own_attendance/nav.html
Normal file
23
attendance/templates/attendance/own_attendance/nav.html
Normal file
@@ -0,0 +1,23 @@
|
||||
{% load i18n %}
|
||||
<section class="oh-wrapper oh-main__topbar" x-data="{searchShow: false}">
|
||||
<div class="oh-main__titlebar oh-main__titlebar--left">
|
||||
<h1 class="oh-main__titlebar-title fw-bold">{% trans "My Attendances" %}</h1>
|
||||
<a class="oh-main__titlebar-search-toggle" role="button" aria-label="Toggle Search" @click="searchShow = !searchShow">
|
||||
<ion-icon name="search-outline" class="oh-main__titlebar-serach-icon"></ion-icon>
|
||||
</a>
|
||||
</div>
|
||||
<div class="oh-main__titlebar oh-main__titlebar--right">
|
||||
<div class="oh-input-group oh-input__search-group"
|
||||
:class="searchShow ? 'oh-input__search-group--show' : ''">
|
||||
<ion-icon name="search-outline" class="oh-input-group__icon oh-input-group__icon--left"></ion-icon>
|
||||
<input type="text" class="oh-input oh-input__icon" aria-label="Search Input" placeholder="{% trans 'Search' %}" />
|
||||
</div>
|
||||
<div class="oh-main__titlebar-button-container">
|
||||
|
||||
<div class="oh-btn-group ml-2">
|
||||
{% include 'attendance/own_attendance/filters.html' %}
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
@@ -0,0 +1,7 @@
|
||||
{% extends 'index.html' %}
|
||||
{% block content %}
|
||||
{% include 'attendance/own_attendance/nav.html' %}
|
||||
<div id="attendance-container" class='oh-wrapper'>
|
||||
{% include 'attendance/own_attendance/attendances.html' %}
|
||||
</div>
|
||||
{% endblock content %}
|
||||
0
attendance/templatetags/__init__.py
Normal file
0
attendance/templatetags/__init__.py
Normal file
155
attendance/templatetags/attendancefilters.py
Normal file
155
attendance/templatetags/attendancefilters.py
Normal file
@@ -0,0 +1,155 @@
|
||||
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
|
||||
|
||||
from itertools import groupby
|
||||
from django.template import TemplateSyntaxError
|
||||
|
||||
register = template.Library()
|
||||
|
||||
@register.filter(name='checkminimumot')
|
||||
def checkminimumot(ot=None):
|
||||
"""
|
||||
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)
|
||||
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):
|
||||
"""
|
||||
This filter method is used to check request user is manager of the employee
|
||||
args:
|
||||
user : request.user
|
||||
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')
|
||||
)
|
||||
|
||||
|
||||
@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()
|
||||
if last_attendance is not None:
|
||||
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):
|
||||
def __init__(self, target, parser, expression, var_name):
|
||||
self.target = target
|
||||
self.expression = template.Variable(expression)
|
||||
self.var_name = var_name
|
||||
self.parser = parser
|
||||
|
||||
def render(self, context):
|
||||
obj_list = self.target.resolve(context, True)
|
||||
if obj_list == None:
|
||||
# target variable wasn't found in context; fail silently.
|
||||
context[self.var_name] = []
|
||||
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:
|
||||
exp = self.expression.resolve(context)
|
||||
except template.VariableDoesNotExist:
|
||||
exp = str(self.expression)
|
||||
|
||||
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))
|
||||
]
|
||||
|
||||
return ''
|
||||
|
||||
@register.tag
|
||||
def dynamic_regroup(parser, token):
|
||||
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':
|
||||
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'")
|
||||
|
||||
"""
|
||||
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.
|
||||
"""
|
||||
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)
|
||||
0
attendance/templatetags/migrations/__init__.py
Normal file
0
attendance/templatetags/migrations/__init__.py
Normal file
3
attendance/tests.py
Normal file
3
attendance/tests.py
Normal file
@@ -0,0 +1,3 @@
|
||||
from django.test import TestCase
|
||||
|
||||
# Create your tests here.
|
||||
69
attendance/urls.py
Normal file
69
attendance/urls.py
Normal file
@@ -0,0 +1,69 @@
|
||||
from django.urls import path
|
||||
from . import views
|
||||
|
||||
urlpatterns = [
|
||||
path('attendance-create',views.attendance_create,name='attendance-create'),
|
||||
|
||||
path('attendance-view',views.attendance_view,name='attendance-view'),
|
||||
|
||||
path('attendance-search',views.attendance_search,name='attendance-search'),
|
||||
|
||||
path('attendance-update/<int:id>/',views.attendance_update,name='attendance-update'),
|
||||
|
||||
path('attendance-delete/<int:id>/',views.attendance_delete,name='attendance-delete'),
|
||||
|
||||
path('attendance-bulk-delete',views.attendance_bulk_delete,name='attendance-bulk-delete'),
|
||||
|
||||
path('attendance-overtime-create',views.attendance_overtime_create,name='attendance-overtime-create'),
|
||||
|
||||
path('attendance-overtime-view',views.attendance_overtime_view,name='attendance-overtime-view'),
|
||||
|
||||
path('attendance-overtime-search',views.attendance_overtime_search,name='attendance-ot-search'),
|
||||
|
||||
path('attendance-overtime-update/<int:id>/',views.attendance_overtime_update,name='attendance-overtime-update'),
|
||||
|
||||
path('attendance-overtime-delete/<int:id>/',views.attendance_overtime_delete,name='attendance-overtime-delete'),
|
||||
|
||||
path('attendance-activity-view',views.attendance_activity_view,name='attendance-activity-view'),
|
||||
|
||||
path('attendance-activity-search',views.attendance_activity_search,name='attendance-activity-search'),
|
||||
|
||||
path('attendance-activity-delete/<int:id>/',views.attendance_activity_delete,name='attendance-activity-delete'),
|
||||
|
||||
path('view-my-attendance',views.view_my_attendance,name='view-my-attendance'),
|
||||
|
||||
path('filter-own-attendance',views.filter_own_attendance,name='filter-own-attendance'),
|
||||
|
||||
path('own-attendance-filter',views.own_attendance_sort,name='own-attendance-filter'),
|
||||
|
||||
path('clock-in',views.clock_in,name='clock-in'),
|
||||
|
||||
path('clock-out',views.clock_out,name='clock-out'),
|
||||
|
||||
path('late-come-early-out-view',views.late_come_early_out_view,name='late-come-early-out-view'),
|
||||
|
||||
path('late-come-early-out-search',views.late_come_early_out_search,name='late-come-early-out-search'),
|
||||
|
||||
path('late-come-early-out-delete/<int:id>/',views.late_come_early_out_delete,name='late-come-early-out-delete'),
|
||||
|
||||
path('validation-condition-create',views.validation_condition_create,name='validation-condition-create'),
|
||||
|
||||
path('validation-condition-update/<int:id>/',views.validation_condition_update,name='validation-condition-update'),
|
||||
|
||||
path('validation-condition-delete/<int:id>/',views.validation_condition_delete,name='validation-condition-delete'),
|
||||
|
||||
path('validate-bulk-attendance',views.validate_bulk_attendance,name='validate-bulk-attendance'),
|
||||
|
||||
path('validate-this-attendance/<int:id>/',views.validate_this_attendance,name='validate-this-attendance'),
|
||||
|
||||
path('revalidate-this-attendance/<int:id>/',views.revalidate_this_attendance,name='revalidate-this-attendance'),
|
||||
|
||||
path('approve-overtime/<int:id>/',views.approve_overtime,name='approve-overtime'),
|
||||
|
||||
path('approve-bulk-overtime',views.approve_bulk_overtime,name='approve-bulk-overtime'),
|
||||
|
||||
path('dashboard',views.dashboard,name='dashboard'),
|
||||
|
||||
path('dashboard-attendance',views.dashboard_attendance,name='dashboard-attendance'),
|
||||
|
||||
]
|
||||
1251
attendance/views.py
Normal file
1251
attendance/views.py
Normal file
File diff suppressed because it is too large
Load Diff
44
auth/forgot-password.html
Normal file
44
auth/forgot-password.html
Normal file
@@ -0,0 +1,44 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Login - OpenHRMS Dashboard</title>
|
||||
<link rel="stylesheet" href="./../build/css/style.min.css" />
|
||||
</head>
|
||||
<body>
|
||||
<div id="main">
|
||||
<main class="oh-auth">
|
||||
<div class="oh-auth-card">
|
||||
<h1 class="oh-onboarding-card__title oh-onboarding-card__title--h2 text-center my-3">Forgot Password?</h1>
|
||||
<p class="text-muted text-center">Type in your email to reset the password</p>
|
||||
<div class="oh-form-group">
|
||||
<div class="oh-input-group">
|
||||
<label class="oh-label" for="email">E-mail</label>
|
||||
<input type="email" id="email" class="oh-input w-100" placeholder="e.g. jane.doe@acme.com" autofocus required />
|
||||
</div>
|
||||
|
||||
<button
|
||||
href="#"
|
||||
class="oh-btn oh-onboarding-card__button mt-4 oh-btn--secondary oh-btn--shadow w-100 mb-4"
|
||||
role="button"
|
||||
>
|
||||
Change Password
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
</div>
|
||||
<script src="./../build/js/web.frontend.min.js"></script>
|
||||
<script
|
||||
type="module"
|
||||
src="https://unpkg.com/ionicons@5.5.2/dist/ionicons/ionicons.esm.js"
|
||||
></script>
|
||||
<script
|
||||
nomodule
|
||||
src="https://unpkg.com/ionicons@5.5.2/dist/ionicons/ionicons.js"
|
||||
></script>
|
||||
<script></script>
|
||||
</body>
|
||||
</html>
|
||||
70
auth/login.html
Normal file
70
auth/login.html
Normal file
@@ -0,0 +1,70 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Login - OpenHRMS Dashboard</title>
|
||||
<link rel="stylesheet" href="./../build/css/style.min.css" />
|
||||
</head>
|
||||
<body>
|
||||
<div id="main">
|
||||
<main class="oh-auth">
|
||||
<div class="oh-auth-card">
|
||||
<h1 class="oh-onboarding-card__title oh-onboarding-card__title--h2 text-center my-3">Sign In</h1>
|
||||
<p class="text-muted text-center">Please login to access the dashboard.</p>
|
||||
<div class="oh-form-group">
|
||||
<div class="oh-input-group">
|
||||
<label class="oh-label" for="email">E-mail</label>
|
||||
<input type="email" id="email" class="oh-input w-100" placeholder="e.g. jane.doe@acme.com" />
|
||||
</div>
|
||||
<div class="oh-input-group">
|
||||
<label class="oh-label" for="password">Password</label>
|
||||
<div class="oh-password-input-container">
|
||||
<input
|
||||
type="password"
|
||||
id="password"
|
||||
class="oh-input oh-input--password w-100"
|
||||
placeholder="Use alphanumeric characters"
|
||||
/>
|
||||
<button
|
||||
class="oh-btn oh-btn--transparent oh-password-input--toggle"
|
||||
>
|
||||
<ion-icon
|
||||
class="oh-passowrd-input__show-icon"
|
||||
title="Show Password"
|
||||
name="eye-outline"
|
||||
></ion-icon>
|
||||
<ion-icon
|
||||
class="oh-passowrd-input__hide-icon d-none"
|
||||
title="Hide Password"
|
||||
name="eye-off-outline"
|
||||
></ion-icon>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<button
|
||||
href="#"
|
||||
class="oh-btn oh-onboarding-card__button mt-4 oh-btn--secondary oh-btn--shadow w-100 mb-4"
|
||||
role="button"
|
||||
>
|
||||
<ion-icon class="me-2" name="lock-closed-outline"></ion-icon>
|
||||
Secure Sign-in
|
||||
</button>
|
||||
<small class="text-center"><a href="#" class="oh-link oh-link--secondary justify-content-center">Forgot password?</a></small>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
</div>
|
||||
<script src="./../build/js/web.frontend.min.js"></script>
|
||||
<script
|
||||
type="module"
|
||||
src="https://unpkg.com/ionicons@5.5.2/dist/ionicons/ionicons.esm.js"
|
||||
></script>
|
||||
<script
|
||||
nomodule
|
||||
src="https://unpkg.com/ionicons@5.5.2/dist/ionicons/ionicons.js"
|
||||
></script>
|
||||
<script></script>
|
||||
</body>
|
||||
</html>
|
||||
91
auth/reset.html
Normal file
91
auth/reset.html
Normal file
@@ -0,0 +1,91 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Reset Password - OpenHRMS Dashboard</title>
|
||||
<link rel="stylesheet" href="./../build/css/style.min.css" />
|
||||
</head>
|
||||
<body>
|
||||
<div id="main">
|
||||
<main class="oh-auth">
|
||||
<div class="oh-auth-card">
|
||||
<h1 class="oh-onboarding-card__title oh-onboarding-card__title--h2 text-center my-3">Reset Password</h1>
|
||||
<p class="text-muted text-center">Enter your new password to reset</p>
|
||||
<div class="oh-form-group">
|
||||
<div class="oh-input-group">
|
||||
<label class="oh-label" for="password">New Password</label>
|
||||
<div class="oh-password-input-container">
|
||||
<input
|
||||
type="password"
|
||||
id="password"
|
||||
class="oh-input oh-input--password w-100"
|
||||
placeholder="Use alphanumeric characters"
|
||||
/>
|
||||
<button
|
||||
class="oh-btn oh-btn--transparent oh-password-input--toggle"
|
||||
>
|
||||
<ion-icon
|
||||
class="oh-passowrd-input__show-icon"
|
||||
title="Show Password"
|
||||
name="eye-outline"
|
||||
></ion-icon>
|
||||
<ion-icon
|
||||
class="oh-passowrd-input__hide-icon d-none"
|
||||
title="Hide Password"
|
||||
name="eye-off-outline"
|
||||
></ion-icon>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="oh-input-group">
|
||||
<label class="oh-label" for="confirmPassword">Confirm New Password</label>
|
||||
<div class="oh-password-input-container">
|
||||
<input
|
||||
type="password"
|
||||
id="confirmPassword"
|
||||
class="oh-input oh-input--password w-100"
|
||||
placeholder="Use alphanumeric characters"
|
||||
/>
|
||||
<button
|
||||
class="oh-btn oh-btn--transparent oh-password-input--toggle"
|
||||
>
|
||||
<ion-icon
|
||||
class="oh-passowrd-input__show-icon"
|
||||
title="Show Password"
|
||||
name="eye-outline"
|
||||
></ion-icon>
|
||||
<ion-icon
|
||||
class="oh-passowrd-input__hide-icon d-none"
|
||||
title="Hide Password"
|
||||
name="eye-off-outline"
|
||||
></ion-icon>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<button
|
||||
href="#"
|
||||
class="oh-btn oh-onboarding-card__button mt-4 oh-btn--secondary oh-btn--shadow w-100 mb-4"
|
||||
role="button"
|
||||
>
|
||||
Reset my password
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
</div>
|
||||
<script src="./../build/js/web.frontend.min.js"></script>
|
||||
<script
|
||||
type="module"
|
||||
src="https://unpkg.com/ionicons@5.5.2/dist/ionicons/ionicons.esm.js"
|
||||
></script>
|
||||
<script
|
||||
nomodule
|
||||
src="https://unpkg.com/ionicons@5.5.2/dist/ionicons/ionicons.js"
|
||||
></script>
|
||||
<script></script>
|
||||
</body>
|
||||
</html>
|
||||
BIN
base/.DS_Store
vendored
Normal file
BIN
base/.DS_Store
vendored
Normal file
Binary file not shown.
1
base/__init__.py
Normal file
1
base/__init__.py
Normal file
@@ -0,0 +1 @@
|
||||
from . import scheduler
|
||||
22
base/admin.py
Normal file
22
base/admin.py
Normal file
@@ -0,0 +1,22 @@
|
||||
from django.contrib import admin
|
||||
from simple_history.admin import SimpleHistoryAdmin
|
||||
|
||||
from base.models import Company, Department, JobPosition, JobRole, EmployeeShiftSchedule, EmployeeShift, EmployeeShiftDay, EmployeeType, WorkType, RotatingWorkType,RotatingWorkTypeAssign ,RotatingShift,RotatingShiftAssign, ShiftRequest,WorkTypeRequest
|
||||
|
||||
# Register your models here.
|
||||
|
||||
admin.site.register(Company)
|
||||
admin.site.register(Department, SimpleHistoryAdmin)
|
||||
admin.site.register(JobPosition)
|
||||
admin.site.register(JobRole)
|
||||
admin.site.register(EmployeeShift)
|
||||
admin.site.register(EmployeeShiftSchedule)
|
||||
admin.site.register(EmployeeShiftDay)
|
||||
admin.site.register(EmployeeType)
|
||||
admin.site.register(WorkType)
|
||||
admin.site.register(RotatingWorkType)
|
||||
admin.site.register(RotatingWorkTypeAssign)
|
||||
admin.site.register(RotatingShift)
|
||||
admin.site.register(RotatingShiftAssign)
|
||||
admin.site.register(ShiftRequest)
|
||||
admin.site.register(WorkTypeRequest)
|
||||
6
base/apps.py
Normal file
6
base/apps.py
Normal file
@@ -0,0 +1,6 @@
|
||||
from django.apps import AppConfig
|
||||
|
||||
|
||||
class BaseConfig(AppConfig):
|
||||
default_auto_field = 'django.db.models.BigAutoField'
|
||||
name = 'base'
|
||||
0
base/decorators.py
Normal file
0
base/decorators.py
Normal file
177
base/filters.py
Normal file
177
base/filters.py
Normal file
@@ -0,0 +1,177 @@
|
||||
import django_filters
|
||||
from horilla.filters import FilterSet, filter_by_name
|
||||
from django_filters import CharFilter
|
||||
from django import forms
|
||||
from base.models import ShiftRequest,WorkTypeRequest, RotatingShiftAssign, RotatingWorkTypeAssign
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
class ShiftRequestFilter(FilterSet):
|
||||
requested_date = django_filters.DateFilter(
|
||||
field_name='requested_date',
|
||||
widget=forms.DateInput(attrs={'type': 'date'})
|
||||
)
|
||||
requested_date__gte = django_filters.DateFilter(
|
||||
field_name='requested_date',
|
||||
lookup_expr='gte',
|
||||
widget=forms.DateInput(attrs={'type': 'date'})
|
||||
)
|
||||
requested_date__lte = django_filters.DateFilter(
|
||||
field_name='requested_date',
|
||||
lookup_expr='lte',
|
||||
widget=forms.DateInput(attrs={'type': 'date'})
|
||||
)
|
||||
search = CharFilter(method=filter_by_name)
|
||||
|
||||
class Meta:
|
||||
fields = '__all__'
|
||||
model = ShiftRequest
|
||||
fields = [
|
||||
|
||||
'employee_id',
|
||||
'requested_date',
|
||||
'previous_shift_id',
|
||||
'shift_id',
|
||||
'approved',
|
||||
'canceled',
|
||||
'employee_id__employee_first_name',
|
||||
'employee_id__employee_last_name',
|
||||
'employee_id__is_active',
|
||||
'employee_id__gender',
|
||||
'employee_id__employee_work_info__job_position_id',
|
||||
'employee_id__employee_work_info__department_id',
|
||||
'employee_id__employee_work_info__work_type_id',
|
||||
'employee_id__employee_work_info__employee_type_id',
|
||||
'employee_id__employee_work_info__job_role_id',
|
||||
'employee_id__employee_work_info__reporting_manager_id',
|
||||
'employee_id__employee_work_info__company_id',
|
||||
'employee_id__employee_work_info__shift_id',
|
||||
]
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
class WorkTypeRequestFilter(FilterSet):
|
||||
requested_date = django_filters.DateFilter(
|
||||
field_name='requested_date',
|
||||
widget=forms.DateInput(attrs={'type': 'date'})
|
||||
)
|
||||
requested_date__gte = django_filters.DateFilter(
|
||||
field_name='requested_till',
|
||||
lookup_expr='gte',
|
||||
widget=forms.DateInput(attrs={'type': 'date'})
|
||||
)
|
||||
requested_date__lte = django_filters.DateFilter(
|
||||
field_name='requested_till',
|
||||
lookup_expr='lte',
|
||||
widget=forms.DateInput(attrs={'type': 'date'})
|
||||
)
|
||||
search = CharFilter(method=filter_by_name)
|
||||
|
||||
class Meta:
|
||||
fields = '__all__'
|
||||
model = WorkTypeRequest
|
||||
fields = [
|
||||
|
||||
'employee_id',
|
||||
'requested_date',
|
||||
'previous_work_type_id',
|
||||
'approved',
|
||||
'work_type_id',
|
||||
'canceled',
|
||||
'employee_id__employee_first_name',
|
||||
'employee_id__employee_last_name',
|
||||
'employee_id__is_active',
|
||||
'employee_id__gender',
|
||||
'employee_id__employee_work_info__job_position_id',
|
||||
'employee_id__employee_work_info__department_id',
|
||||
'employee_id__employee_work_info__work_type_id',
|
||||
'employee_id__employee_work_info__employee_type_id',
|
||||
'employee_id__employee_work_info__job_role_id',
|
||||
'employee_id__employee_work_info__reporting_manager_id',
|
||||
'employee_id__employee_work_info__company_id',
|
||||
'employee_id__employee_work_info__shift_id',
|
||||
]
|
||||
|
||||
|
||||
|
||||
|
||||
class RotatingShiftAssignFilters(FilterSet):
|
||||
search = CharFilter(method=filter_by_name)
|
||||
|
||||
next_change_date = django_filters.DateFilter(
|
||||
field_name='next_change_date',
|
||||
widget=forms.DateInput(attrs={'type': 'date'})
|
||||
)
|
||||
start_date = django_filters.DateFilter(
|
||||
field_name='start_date',
|
||||
widget=forms.DateInput(attrs={'type': 'date'})
|
||||
)
|
||||
class Meta:
|
||||
fields = '__all__'
|
||||
model = RotatingShiftAssign
|
||||
fields = [
|
||||
'employee_id',
|
||||
'rotating_shift_id',
|
||||
'next_change_date',
|
||||
'start_date',
|
||||
'based_on',
|
||||
'rotate_after_day',
|
||||
'rotate_every_weekend',
|
||||
'rotate_every',
|
||||
'current_shift',
|
||||
'next_shift',
|
||||
'is_active',
|
||||
'employee_id__employee_work_info__job_position_id',
|
||||
'employee_id__employee_work_info__department_id',
|
||||
'employee_id__employee_work_info__work_type_id',
|
||||
'employee_id__employee_work_info__employee_type_id',
|
||||
'employee_id__employee_work_info__job_role_id',
|
||||
'employee_id__employee_work_info__reporting_manager_id',
|
||||
'employee_id__employee_work_info__company_id',
|
||||
'employee_id__employee_work_info__shift_id',
|
||||
|
||||
]
|
||||
|
||||
|
||||
|
||||
class RotatingWorkTypeAssignFilter(FilterSet):
|
||||
search = CharFilter(method=filter_by_name)
|
||||
|
||||
next_change_date = django_filters.DateFilter(
|
||||
field_name='next_change_date',
|
||||
widget=forms.DateInput(attrs={'type': 'date'})
|
||||
)
|
||||
start_date = django_filters.DateFilter(
|
||||
field_name='start_date',
|
||||
widget=forms.DateInput(attrs={'type': 'date'})
|
||||
)
|
||||
class Meta:
|
||||
fields = '__all__'
|
||||
model = RotatingWorkTypeAssign
|
||||
fields = [
|
||||
'employee_id',
|
||||
'rotating_work_type_id',
|
||||
'next_change_date',
|
||||
'start_date',
|
||||
'based_on',
|
||||
'rotate_after_day',
|
||||
'rotate_every_weekend',
|
||||
'rotate_every',
|
||||
'current_work_type',
|
||||
'next_work_type',
|
||||
'is_active',
|
||||
'employee_id__employee_work_info__job_position_id',
|
||||
'employee_id__employee_work_info__department_id',
|
||||
'employee_id__employee_work_info__work_type_id',
|
||||
'employee_id__employee_work_info__employee_type_id',
|
||||
'employee_id__employee_work_info__job_role_id',
|
||||
'employee_id__employee_work_info__reporting_manager_id',
|
||||
'employee_id__employee_work_info__company_id',
|
||||
'employee_id__employee_work_info__shift_id',
|
||||
|
||||
]
|
||||
754
base/forms.py
Normal file
754
base/forms.py
Normal file
@@ -0,0 +1,754 @@
|
||||
|
||||
import datetime
|
||||
from typing import Any
|
||||
from django.forms import widgets
|
||||
from django.core.exceptions import ValidationError
|
||||
from django import forms
|
||||
from django.contrib.auth.models import Group, Permission
|
||||
from base.models import Company, Department, JobPosition, JobRole, WorkType, EmployeeType, EmployeeShift, EmployeeShiftSchedule, RotatingShift, RotatingShiftAssign, RotatingWorkType, RotatingWorkTypeAssign, WorkTypeRequest, ShiftRequest, EmployeeShiftDay
|
||||
from django.forms import DateInput
|
||||
from django.core.exceptions import ValidationError
|
||||
from employee.models import Employee
|
||||
import uuid
|
||||
import re
|
||||
import calendar
|
||||
from notifications.signals import notify
|
||||
from datetime import timedelta
|
||||
from django.utils.translation import gettext as _
|
||||
|
||||
# your form here
|
||||
|
||||
|
||||
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:
|
||||
hour, minute = value.split(":")
|
||||
hour = int(hour)
|
||||
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
|
||||
|
||||
|
||||
BASED_ON = [
|
||||
('after', 'After'),
|
||||
('weekly', 'Weekend'),
|
||||
('monthly', 'Monthly'),
|
||||
]
|
||||
|
||||
|
||||
def get_next_week_date(target_day, start_date):
|
||||
"""
|
||||
Calculates the date of the next occurrence of the target day within the next week.
|
||||
|
||||
Parameters:
|
||||
target_day (int): The target day of the week (0-6, where Monday is 0 and Sunday is 6).
|
||||
start_date (datetime.date): The starting date.
|
||||
|
||||
Returns:
|
||||
datetime.date: The date of the next occurrence of the target day within the next week.
|
||||
"""
|
||||
if start_date.weekday() == target_day:
|
||||
return start_date
|
||||
days_until_target_day = (target_day - start_date.weekday()) % 7
|
||||
if days_until_target_day == 0:
|
||||
days_until_target_day = 7
|
||||
return start_date + timedelta(days=days_until_target_day)
|
||||
|
||||
|
||||
def get_next_monthly_date(start_date, rotate_every):
|
||||
"""
|
||||
Given a start date and a rotation day (specified as an integer between 1 and 31, or the string 'last'), calculates the
|
||||
next rotation date for a monthly rotation schedule.
|
||||
|
||||
If the rotation day has not yet occurred in the current month, the next rotation date will be on the rotation day
|
||||
of the current month. If the rotation day has already occurred in the current month, the next rotation date will be on
|
||||
the rotation day of the next month.
|
||||
|
||||
If 'last' is specified as the rotation day, the next rotation date will be on the last day of the current month.
|
||||
|
||||
Parameters:
|
||||
- start_date: The start date of the rotation schedule, as a datetime.date object.
|
||||
- rotate_every: The rotation day, specified as an integer between 1 and 31, or the string 'last'.
|
||||
|
||||
Returns:
|
||||
- A datetime.date object representing the next rotation date.
|
||||
"""
|
||||
|
||||
if rotate_every == 'last':
|
||||
# Set rotate_every to the last day of the current month
|
||||
last_day = calendar.monthrange(start_date.year, start_date.month)[1]
|
||||
rotate_every = str(last_day)
|
||||
rotate_every = int(rotate_every)
|
||||
|
||||
# Calculate the next change date
|
||||
if start_date.day <= rotate_every or rotate_every == 0:
|
||||
# If the rotation day has not occurred yet this month, or if it's the last day of the month, set the next change date to the rotation day of this month
|
||||
try:
|
||||
next_change = datetime.date(
|
||||
start_date.year, start_date.month, rotate_every)
|
||||
except ValueError:
|
||||
next_change = datetime.date(
|
||||
start_date.year, start_date.month + 1, 1) # Advance to next month
|
||||
# Set day to rotate_every
|
||||
next_change = datetime.date(
|
||||
next_change.year, next_change.month, rotate_every)
|
||||
else:
|
||||
# If the rotation day has already occurred this month, set the next change date to the rotation day of the next month
|
||||
last_day = calendar.monthrange(start_date.year, start_date.month)[1]
|
||||
next_month_start = start_date.replace(day=last_day) + timedelta(days=1)
|
||||
try:
|
||||
next_change = next_month_start.replace(day=rotate_every)
|
||||
except ValueError:
|
||||
next_change = (next_month_start.replace(
|
||||
month=next_month_start.month + 1) + timedelta(days=1)).replace(day=rotate_every)
|
||||
|
||||
return next_change
|
||||
|
||||
|
||||
class ModelForm(forms.ModelForm):
|
||||
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)):
|
||||
field.widget.attrs.update(
|
||||
{'class': 'oh-input w-100', 'placeholder': field.label})
|
||||
elif isinstance(widget, (forms.Select,)):
|
||||
label = ''
|
||||
if field.label is not None:
|
||||
label = field.label.replace('id', ' ')
|
||||
field.empty_label = f'---Choose {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 w-100'})
|
||||
|
||||
|
||||
class Form(forms.Form):
|
||||
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)):
|
||||
field.widget.attrs.update(
|
||||
{'class': 'oh-input w-100', 'placeholder': field.label})
|
||||
elif isinstance(widget, (forms.Select,)):
|
||||
label = ''
|
||||
if field.label is not None:
|
||||
label = field.label.replace('id', ' ')
|
||||
field.empty_label = f'---Choose {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 UserGroupForm(ModelForm):
|
||||
class Meta:
|
||||
model = Group
|
||||
fields = '__all__'
|
||||
|
||||
|
||||
class AssignUserGroup(Form):
|
||||
employee = forms.ModelMultipleChoiceField(queryset=Employee.objects.all())
|
||||
group = forms.ModelMultipleChoiceField(queryset=Group.objects.all())
|
||||
|
||||
def save(self):
|
||||
employees = self.cleaned_data['employee']
|
||||
group = self.cleaned_data['group']
|
||||
for employee in employees:
|
||||
employee.employee_user_id.groups.add(*group)
|
||||
|
||||
return group
|
||||
|
||||
|
||||
class AssignPermission(Form):
|
||||
employee = forms.ModelMultipleChoiceField(queryset=Employee.objects.all())
|
||||
permission = forms.ModelMultipleChoiceField(
|
||||
queryset=Permission.objects.all())
|
||||
|
||||
def save(self):
|
||||
employees = self.cleaned_data['employee']
|
||||
permissions = self.cleaned_data['permission']
|
||||
for emp in employees:
|
||||
user = emp.employee_user_id
|
||||
user.user_permissions.add(*permissions)
|
||||
return
|
||||
|
||||
|
||||
class CompanyForm(ModelForm):
|
||||
class Meta:
|
||||
model = Company
|
||||
fields = '__all__'
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(CompanyForm, self).__init__(*args, **kwargs)
|
||||
|
||||
|
||||
class DepartmentForm(ModelForm):
|
||||
class Meta:
|
||||
model = Department
|
||||
fields = '__all__'
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(DepartmentForm, self).__init__(*args, **kwargs)
|
||||
|
||||
|
||||
class JobPositionForm(ModelForm):
|
||||
class Meta:
|
||||
model = JobPosition
|
||||
fields = '__all__'
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(JobPositionForm, self).__init__(*args, **kwargs)
|
||||
|
||||
|
||||
class JobRoleForm(ModelForm):
|
||||
class Meta:
|
||||
model = JobRole
|
||||
fields = '__all__'
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(JobRoleForm, self).__init__(*args, **kwargs)
|
||||
|
||||
|
||||
class WorkTypeForm(ModelForm):
|
||||
class Meta:
|
||||
model = WorkType
|
||||
fields = '__all__'
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(WorkTypeForm, self).__init__(*args, **kwargs)
|
||||
|
||||
|
||||
class RotatingWorkTypeForm(ModelForm):
|
||||
class Meta:
|
||||
model = RotatingWorkType
|
||||
fields = '__all__'
|
||||
exclude = ('employee_id',)
|
||||
widgets = {
|
||||
'start_date': DateInput(attrs={'type': 'date'}),
|
||||
}
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(RotatingWorkTypeForm, self).__init__(*args, **kwargs)
|
||||
|
||||
|
||||
class RotatingWorkTypeAssignForm(forms.ModelForm):
|
||||
employee_id = forms.ModelMultipleChoiceField(
|
||||
label="Employee", queryset=Employee.objects.filter(employee_work_info__isnull=False))
|
||||
based_on = forms.ChoiceField(choices=BASED_ON, initial='daily')
|
||||
rotate_after_day = forms.IntegerField(initial=5,)
|
||||
start_date = forms.DateField(
|
||||
initial=datetime.date.today, widget=forms.DateInput)
|
||||
|
||||
class Meta:
|
||||
model = RotatingWorkTypeAssign
|
||||
fields = '__all__'
|
||||
exclude = ('next_change_date', 'current_work_type', 'next_work_type')
|
||||
widgets = {
|
||||
'start_date': DateInput(attrs={'type': 'date'}),
|
||||
}
|
||||
labels = {
|
||||
'rotating_work_type_id': 'Rotating work type',
|
||||
}
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(RotatingWorkTypeAssignForm, self).__init__(*args, **kwargs)
|
||||
|
||||
self.fields['rotate_every_weekend'].widget.attrs.update(
|
||||
{'class': 'w-100', 'style': 'display:none; height:50px; border-radius:0;border:1px solid hsl(213deg,22%,84%);', 'data-hidden': True})
|
||||
self.fields['rotate_every'].widget.attrs.update(
|
||||
{'class': 'w-100', 'style': 'display:none; height:50px; border-radius:0;border:1px solid hsl(213deg,22%,84%);', 'data-hidden': True})
|
||||
self.fields['rotate_after_day'].widget.attrs.update(
|
||||
{'class': 'w-100 oh-input', 'style': ' height:50px; border-radius:0;', })
|
||||
self.fields['based_on'].widget.attrs.update(
|
||||
{'class': 'w-100', 'style': ' height:50px; border-radius:0;border:1px solid hsl(213deg,22%,84%);', })
|
||||
self.fields['start_date'].widget = forms.DateInput(
|
||||
attrs={'class': 'w-100 oh-input', 'type': 'date', 'style': ' height:50px; border-radius:0;', })
|
||||
self.fields['rotating_work_type_id'].widget.attrs.update(
|
||||
{'class': 'oh-select oh-select-2', })
|
||||
self.fields['employee_id'].widget.attrs.update(
|
||||
{'class': 'oh-select oh-select-2', })
|
||||
|
||||
def clean_employee_id(self):
|
||||
employee_ids = self.cleaned_data.get('employee_id')
|
||||
if employee_ids:
|
||||
return employee_ids[0]
|
||||
else:
|
||||
return ValidationError('This field is required')
|
||||
|
||||
def clean(self):
|
||||
cleaned_data = super().clean()
|
||||
if 'rotate_after_day' in self.errors:
|
||||
del self.errors['rotate_after_day']
|
||||
return cleaned_data
|
||||
|
||||
def save(self, commit=False, manager=None):
|
||||
employee_ids = self.data.getlist('employee_id')
|
||||
rotating_work_type = RotatingWorkType.objects.get(
|
||||
id=self.data['rotating_work_type_id'])
|
||||
|
||||
day_name = self.cleaned_data['rotate_every_weekend']
|
||||
day_names = ["monday", "tuesday", "wednesday",
|
||||
"thursday", "friday", "saturday", "sunday"]
|
||||
target_day = day_names.index(day_name.lower())
|
||||
|
||||
for employee_id in employee_ids:
|
||||
employee = Employee.objects.filter(id=employee_id).first()
|
||||
rotating_work_type_assign = RotatingWorkTypeAssign()
|
||||
rotating_work_type_assign.rotating_work_type_id = rotating_work_type
|
||||
rotating_work_type_assign.employee_id = employee
|
||||
rotating_work_type_assign.based_on = self.cleaned_data['based_on']
|
||||
rotating_work_type_assign.start_date = self.cleaned_data['start_date']
|
||||
rotating_work_type_assign.next_change_date = self.cleaned_data['start_date']
|
||||
rotating_work_type_assign.rotate_after_day = self.data.get(
|
||||
'rotate_after_day')
|
||||
rotating_work_type_assign.rotate_every = self.cleaned_data['rotate_every']
|
||||
rotating_work_type_assign.rotate_every_weekend = self.cleaned_data[
|
||||
'rotate_every_weekend']
|
||||
rotating_work_type_assign.next_change_date = self.cleaned_data['start_date']
|
||||
rotating_work_type_assign.current_work_type = employee.employee_work_info.work_type_id
|
||||
rotating_work_type_assign.next_work_type = rotating_work_type.work_type2
|
||||
based_on = self.cleaned_data['based_on']
|
||||
start_date = self.cleaned_data['start_date']
|
||||
if based_on == "weekly":
|
||||
next_date = get_next_week_date(target_day, start_date)
|
||||
rotating_work_type_assign.next_change_date = next_date
|
||||
elif based_on == "monthly":
|
||||
# 0, 1, 2, ..., 31, or "last"
|
||||
rotate_every = self.cleaned_data['rotate_every']
|
||||
start_date = self.cleaned_data['start_date']
|
||||
next_date = get_next_monthly_date(start_date, rotate_every)
|
||||
rotating_work_type_assign.next_change_date = next_date
|
||||
elif based_on == "after":
|
||||
rotating_work_type_assign.next_change_date = rotating_work_type_assign.start_date + \
|
||||
datetime.timedelta(
|
||||
days=int(self.data.get('rotate_after_day')))
|
||||
|
||||
rotating_work_type_assign.save()
|
||||
|
||||
|
||||
class RotatingWorkTypeAssignUpdateForm(forms.ModelForm):
|
||||
class Meta:
|
||||
model = RotatingWorkTypeAssign
|
||||
fields = '__all__'
|
||||
exclude = ('next_change_date', 'current_work_type', 'next_work_type')
|
||||
widgets = {
|
||||
'start_date': DateInput(attrs={'type': 'date'}),
|
||||
}
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(RotatingWorkTypeAssignUpdateForm, self).__init__(*args, **kwargs)
|
||||
|
||||
self.fields['rotate_every_weekend'].widget.attrs.update(
|
||||
{'class': 'w-100', 'style': 'display:none; height:50px; border-radius:0;border:1px solid hsl(213deg,22%,84%);', 'data-hidden': True})
|
||||
self.fields['rotate_every'].widget.attrs.update(
|
||||
{'class': 'w-100', 'style': 'display:none; height:50px; border-radius:0;border:1px solid hsl(213deg,22%,84%);', 'data-hidden': True})
|
||||
self.fields['rotate_after_day'].widget.attrs.update(
|
||||
{'class': 'w-100 oh-input', 'style': ' height:50px; border-radius:0;', })
|
||||
self.fields['based_on'].widget.attrs.update(
|
||||
{'class': 'w-100', 'style': ' height:50px; border-radius:0; border:1px solid hsl(213deg,22%,84%);', })
|
||||
self.fields['start_date'].widget = forms.DateInput(
|
||||
attrs={'class': 'w-100 oh-input', 'type': 'date', 'style': ' height:50px; border-radius:0;', })
|
||||
self.fields['rotating_work_type_id'].widget.attrs.update(
|
||||
{'class': 'oh-select oh-select-2', })
|
||||
self.fields['employee_id'].widget.attrs.update(
|
||||
{'class': 'oh-select oh-select-2', })
|
||||
|
||||
def save(self, *args, **kwargs):
|
||||
|
||||
day_name = self.cleaned_data['rotate_every_weekend']
|
||||
day_names = ["monday", "tuesday", "wednesday",
|
||||
"thursday", "friday", "saturday", "sunday"]
|
||||
target_day = day_names.index(day_name.lower())
|
||||
|
||||
based_on = self.cleaned_data['based_on']
|
||||
start_date = self.instance.start_date
|
||||
if based_on == "weekly":
|
||||
next_date = get_next_week_date(target_day, start_date)
|
||||
self.instance.next_change_date = next_date
|
||||
elif based_on == "monthly":
|
||||
rotate_every = self.instance.rotate_every # 0, 1, 2, ..., 31, or "last"
|
||||
start_date = self.instance.start_date
|
||||
next_date = get_next_monthly_date(start_date, rotate_every)
|
||||
self.instance.next_change_date = next_date
|
||||
elif based_on == "after":
|
||||
self.instance.next_change_date = self.instance.start_date + \
|
||||
datetime.timedelta(
|
||||
days=int(self.data.get('rotate_after_day')))
|
||||
return super().save()
|
||||
|
||||
|
||||
class EmployeeTypeForm(ModelForm):
|
||||
class Meta:
|
||||
model = EmployeeType
|
||||
fields = '__all__'
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(EmployeeTypeForm, self).__init__(*args, **kwargs)
|
||||
|
||||
|
||||
class EmployeeShiftForm(ModelForm):
|
||||
class Meta:
|
||||
model = EmployeeShift
|
||||
fields = '__all__'
|
||||
exclude = ('days',)
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(EmployeeShiftForm, self).__init__(*args, **kwargs)
|
||||
|
||||
def clean_full_time(self):
|
||||
full_time = self.cleaned_data['full_time']
|
||||
validate_time_format(full_time)
|
||||
return full_time
|
||||
|
||||
|
||||
class EmployeeShiftScheduleUpdateForm(ModelForm):
|
||||
|
||||
class Meta:
|
||||
fields = '__all__'
|
||||
widgets = {
|
||||
'start_time': DateInput(attrs={'type': 'time'}),
|
||||
'end_time': DateInput(attrs={'type': 'time'}),
|
||||
|
||||
}
|
||||
model = EmployeeShiftSchedule
|
||||
|
||||
def __init__(self, *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 = {
|
||||
'start_time': instance.start_time.strftime('%H:%M'),
|
||||
'end_time': instance.end_time.strftime('%H:%M'),
|
||||
}
|
||||
kwargs['initial'] = initial
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
|
||||
class EmployeeShiftScheduleForm(ModelForm):
|
||||
day = forms.ModelMultipleChoiceField(
|
||||
queryset=EmployeeShiftDay.objects.all(),)
|
||||
|
||||
class Meta:
|
||||
|
||||
model = EmployeeShiftSchedule
|
||||
fields = '__all__'
|
||||
widgets = {
|
||||
'start_time': DateInput(attrs={'type': 'time'}),
|
||||
'end_time': DateInput(attrs={'type': 'time'}),
|
||||
|
||||
}
|
||||
|
||||
def __init__(self, *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 = {
|
||||
'start_time': instance.start_time.strftime('%H:%M'),
|
||||
'end_time': instance.end_time.strftime('%H:%M'),
|
||||
}
|
||||
kwargs['initial'] = initial
|
||||
super(EmployeeShiftScheduleForm, self).__init__(*args, **kwargs)
|
||||
self.fields['day'].widget.attrs.update({'id': str(uuid.uuid4())})
|
||||
self.fields['shift_id'].widget.attrs.update({'id': str(uuid.uuid4())})
|
||||
|
||||
def save(self, commit=True):
|
||||
instance = super().save(commit=False)
|
||||
for day in self.data.getlist('day'):
|
||||
if int(day) != int(instance.day.id):
|
||||
data_copy = self.data.copy()
|
||||
data_copy.update({'day': str(day)})
|
||||
shift_schedule = EmployeeShiftScheduleUpdateForm(
|
||||
data_copy).save(commit=False)
|
||||
shift_schedule.save()
|
||||
if commit:
|
||||
instance.save()
|
||||
return instance
|
||||
|
||||
def clean_day(self):
|
||||
days = self.cleaned_data['day']
|
||||
for day in days:
|
||||
attendance = EmployeeShiftSchedule.objects.filter(
|
||||
day=day, shift_id=self.data['shift_id']).first()
|
||||
if attendance is not None:
|
||||
raise ValidationError(
|
||||
f'Shift schedule is already exist for {day}')
|
||||
if days.first() is None:
|
||||
raise ValidationError('Employee not chosen')
|
||||
|
||||
return days.first()
|
||||
|
||||
|
||||
class RotatingShiftForm(ModelForm):
|
||||
class Meta:
|
||||
model = RotatingShift
|
||||
fields = '__all__'
|
||||
exclude = ('employee_id',)
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(RotatingShiftForm, self).__init__(*args, **kwargs)
|
||||
|
||||
|
||||
class RotatingShiftAssignForm(forms.ModelForm):
|
||||
employee_id = forms.ModelMultipleChoiceField(
|
||||
label="Employee", queryset=Employee.objects.filter(employee_work_info__isnull=False))
|
||||
based_on = forms.ChoiceField(choices=BASED_ON, initial='daily')
|
||||
rotate_after_day = forms.IntegerField(initial=5,)
|
||||
start_date = forms.DateField(
|
||||
initial=datetime.date.today, widget=forms.DateInput)
|
||||
|
||||
class Meta:
|
||||
model = RotatingShiftAssign
|
||||
fields = '__all__'
|
||||
exclude = ('next_change_date', 'current_shift', 'next_shift')
|
||||
widgets = {
|
||||
'start_date': DateInput(attrs={'type': 'date'}),
|
||||
}
|
||||
labels = {
|
||||
'rotating_shift_id': 'Rotating shift',
|
||||
}
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(RotatingShiftAssignForm, self).__init__(*args, **kwargs)
|
||||
self.fields['rotate_every_weekend'].widget.attrs.update(
|
||||
{'class': 'w-100 ', 'style': 'display:none; height:50px; border-radius:0;border:1px solid hsl(213deg,22%,84%);', 'data-hidden': True})
|
||||
self.fields['rotate_every'].widget.attrs.update(
|
||||
{'class': 'w-100 ', 'style': 'display:none; height:50px; border-radius:0;border:1px solid hsl(213deg,22%,84%);', 'data-hidden': True})
|
||||
self.fields['rotate_after_day'].widget.attrs.update(
|
||||
{'class': 'w-100 oh-input', 'style': ' height:50px; border-radius:0;', })
|
||||
self.fields['based_on'].widget.attrs.update(
|
||||
{'class': 'w-100', 'style': ' height:50px; border-radius:0;border:1px solid hsl(213deg,22%,84%);', })
|
||||
self.fields['start_date'].widget = forms.DateInput(
|
||||
attrs={'class': 'w-100 oh-input', 'type': 'date', 'style': ' height:50px; border-radius:0;', })
|
||||
self.fields['rotating_shift_id'].widget.attrs.update(
|
||||
{'class': 'oh-select oh-select-2', })
|
||||
self.fields['employee_id'].widget.attrs.update(
|
||||
{'class': 'oh-select oh-select-2', })
|
||||
|
||||
def clean_employee_id(self):
|
||||
employee_ids = self.cleaned_data.get('employee_id')
|
||||
if employee_ids:
|
||||
return employee_ids[0]
|
||||
else:
|
||||
return ValidationError('This field is required')
|
||||
|
||||
def clean(self):
|
||||
cleaned_data = super().clean()
|
||||
if 'rotate_after_day' in self.errors:
|
||||
del self.errors['rotate_after_day']
|
||||
return cleaned_data
|
||||
|
||||
def save(self, commit=False,):
|
||||
employee_ids = self.data.getlist('employee_id')
|
||||
rotating_shift = RotatingShift.objects.get(
|
||||
id=self.data['rotating_shift_id'])
|
||||
|
||||
day_name = self.cleaned_data['rotate_every_weekend']
|
||||
day_names = ["monday", "tuesday", "wednesday",
|
||||
"thursday", "friday", "saturday", "sunday"]
|
||||
target_day = day_names.index(day_name.lower())
|
||||
|
||||
for employee_id in employee_ids:
|
||||
employee = Employee.objects.filter(id=employee_id).first()
|
||||
rotating_shift_assign = RotatingShiftAssign()
|
||||
rotating_shift_assign.rotating_shift_id = rotating_shift
|
||||
rotating_shift_assign.employee_id = employee
|
||||
rotating_shift_assign.based_on = self.cleaned_data['based_on']
|
||||
rotating_shift_assign.start_date = self.cleaned_data['start_date']
|
||||
rotating_shift_assign.next_change_date = self.cleaned_data['start_date']
|
||||
rotating_shift_assign.rotate_after_day = self.data.get(
|
||||
'rotate_after_day')
|
||||
rotating_shift_assign.rotate_every = self.cleaned_data['rotate_every']
|
||||
rotating_shift_assign.rotate_every_weekend = self.cleaned_data['rotate_every_weekend']
|
||||
rotating_shift_assign.next_change_date = self.cleaned_data['start_date']
|
||||
rotating_shift_assign.current_shift = employee.employee_work_info.shift_id
|
||||
rotating_shift_assign.next_shift = rotating_shift.shift2
|
||||
based_on = self.cleaned_data['based_on']
|
||||
start_date = self.cleaned_data['start_date']
|
||||
if based_on == "weekly":
|
||||
next_date = get_next_week_date(target_day, start_date)
|
||||
rotating_shift_assign.next_change_date = next_date
|
||||
elif based_on == "monthly":
|
||||
# 0, 1, 2, ..., 31, or "last"
|
||||
rotate_every = self.cleaned_data['rotate_every']
|
||||
start_date = self.cleaned_data['start_date']
|
||||
next_date = get_next_monthly_date(start_date, rotate_every)
|
||||
rotating_shift_assign.next_change_date = next_date
|
||||
elif based_on == "after":
|
||||
rotating_shift_assign.next_change_date = rotating_shift_assign.start_date + \
|
||||
datetime.timedelta(
|
||||
days=int(self.data.get('rotate_after_day')))
|
||||
|
||||
rotating_shift_assign.save()
|
||||
|
||||
|
||||
class RotatingShiftAssignUpdateForm(forms.ModelForm):
|
||||
|
||||
class Meta:
|
||||
model = RotatingShiftAssign
|
||||
fields = '__all__'
|
||||
exclude = ('next_change_date', 'current_shift', 'next_shift')
|
||||
widgets = {
|
||||
'start_date': DateInput(attrs={'type': 'date'}),
|
||||
}
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(RotatingShiftAssignUpdateForm, self).__init__(*args, **kwargs)
|
||||
self.fields['rotate_every_weekend'].widget.attrs.update(
|
||||
{'class': 'w-100 ', 'style': 'display:none; height:50px; border-radius:0; border:1px solid hsl(213deg,22%,84%);', 'data-hidden': True})
|
||||
self.fields['rotate_every'].widget.attrs.update(
|
||||
{'class': 'w-100 ', 'style': 'display:none; height:50px; border-radius:0; border:1px solid hsl(213deg,22%,84%);', 'data-hidden': True})
|
||||
self.fields['rotate_after_day'].widget.attrs.update(
|
||||
{'class': 'w-100 oh-input', 'style': ' height:50px; border-radius:0;', })
|
||||
self.fields['based_on'].widget.attrs.update(
|
||||
{'class': 'w-100', 'style': ' height:50px; border-radius:0; border:1px solid hsl(213deg,22%,84%);', })
|
||||
self.fields['start_date'].widget = forms.DateInput(
|
||||
attrs={'class': 'w-100 oh-input', 'type': 'date', 'style': ' height:50px; border-radius:0;', })
|
||||
self.fields['rotating_shift_id'].widget.attrs.update(
|
||||
{'class': 'oh-select oh-select-2', })
|
||||
self.fields['employee_id'].widget.attrs.update(
|
||||
{'class': 'oh-select oh-select-2', })
|
||||
|
||||
def save(self, *args, **kwargs):
|
||||
|
||||
day_name = self.cleaned_data['rotate_every_weekend']
|
||||
day_names = ["monday", "tuesday", "wednesday",
|
||||
"thursday", "friday", "saturday", "sunday"]
|
||||
target_day = day_names.index(day_name.lower())
|
||||
|
||||
based_on = self.cleaned_data['based_on']
|
||||
start_date = self.instance.start_date
|
||||
if based_on == "weekly":
|
||||
next_date = get_next_week_date(target_day, start_date)
|
||||
self.instance.next_change_date = next_date
|
||||
elif based_on == "monthly":
|
||||
rotate_every = self.instance.rotate_every # 0, 1, 2, ..., 31, or "last"
|
||||
start_date = self.instance.start_date
|
||||
next_date = get_next_monthly_date(start_date, rotate_every)
|
||||
self.instance.next_change_date = next_date
|
||||
elif based_on == "after":
|
||||
self.instance.next_change_date = self.instance.start_date + \
|
||||
datetime.timedelta(
|
||||
days=int(self.data.get('rotate_after_day')))
|
||||
return super().save()
|
||||
|
||||
|
||||
class ShiftRequestForm(ModelForm):
|
||||
|
||||
class Meta:
|
||||
model = ShiftRequest
|
||||
fields = '__all__'
|
||||
exclude = ('approved', 'canceled', 'previous_shift_id',
|
||||
'is_active', 'shift_changed')
|
||||
widgets = {
|
||||
'requested_date': DateInput(attrs={'type': 'date'}),
|
||||
'requested_till': DateInput(attrs={'type': 'date'}),
|
||||
}
|
||||
labels = {
|
||||
'employee_id': 'Employee',
|
||||
'shift_id': 'Shift'
|
||||
}
|
||||
|
||||
def save(self, commit: bool = ...):
|
||||
if not self.instance.approved:
|
||||
employee = self.instance.employee_id
|
||||
self.instance.previous_shift_id = employee.employee_work_info.shift_id
|
||||
return super().save(commit)
|
||||
|
||||
# here set default filter for all the employees those have work information filled.
|
||||
|
||||
|
||||
class WorkTypeRequestForm(ModelForm):
|
||||
class Meta:
|
||||
model = WorkTypeRequest
|
||||
fields = '__all__'
|
||||
exclude = ('approved', 'canceled', 'previous_work_type_id',
|
||||
'is_active', 'work_type_changed')
|
||||
widgets = {
|
||||
'requested_date': DateInput(attrs={'type': 'date'}),
|
||||
'requested_till': DateInput(attrs={'type': 'date'}),
|
||||
}
|
||||
labels = {
|
||||
'employee_id': 'Employee',
|
||||
'work_type_id': 'Work type'
|
||||
}
|
||||
|
||||
def save(self, commit: bool = ...):
|
||||
if not self.instance.approved:
|
||||
employee = self.instance.employee_id
|
||||
self.instance.previous_work_type_id = employee.employee_work_info.work_type_id
|
||||
return super().save(commit)
|
||||
|
||||
|
||||
class ResetPasswordForm(forms.Form):
|
||||
password = forms.CharField(
|
||||
label="New password",
|
||||
strip=False,
|
||||
widget=forms.PasswordInput(attrs={'autocomplete': 'new-password',
|
||||
'placeholder': 'Enter Strong Password', 'class': 'oh-input oh-input--password w-100 mb-2'}),
|
||||
help_text="Enter your new password.",
|
||||
)
|
||||
confirm_password = forms.CharField(
|
||||
label="New password confirmation",
|
||||
strip=False,
|
||||
widget=forms.PasswordInput(attrs={'autocomplete': 'new-password',
|
||||
'placeholder': 'Re-Enter Password', 'class': 'oh-input oh-input--password w-100 mb-2'}),
|
||||
help_text="Enter the same password as before, for verification.",
|
||||
)
|
||||
|
||||
def clean_password(self):
|
||||
password = self.cleaned_data.get('password')
|
||||
try:
|
||||
|
||||
if len(password) < 7:
|
||||
raise ValidationError(
|
||||
'Password must contain at least 8 characters.')
|
||||
elif not any(char.isupper() for char in password):
|
||||
raise ValidationError(
|
||||
'Password must contain at least one uppercase letter.')
|
||||
elif not any(char.islower() for char in password):
|
||||
raise ValidationError(
|
||||
'Password must contain at least one lowercase letter.')
|
||||
elif not any(char.isdigit() for char in password):
|
||||
raise ValidationError(
|
||||
'Password must contain at least one digit.')
|
||||
elif all(
|
||||
char not in '!@#$%^&*()_+-=[]{}|;:,.<>?\'\"`~\\/'
|
||||
for char in password
|
||||
):
|
||||
raise ValidationError(
|
||||
'Password must contain at least one special character.')
|
||||
except ValidationError as e:
|
||||
raise forms.ValidationError(list(e)[0])
|
||||
return password
|
||||
|
||||
def clean_confirm_password(self):
|
||||
password = self.cleaned_data.get('password')
|
||||
confirm_password = self.cleaned_data.get('confirm_password')
|
||||
if password == confirm_password:
|
||||
return confirm_password
|
||||
raise forms.ValidationError('Password must be same.')
|
||||
|
||||
def save(self, *args, user=None, **kwargs):
|
||||
if user is not None:
|
||||
user.set_password(self.data['password'])
|
||||
user.save()
|
||||
37
base/management/commands/createhorillauser.py
Normal file
37
base/management/commands/createhorillauser.py
Normal file
@@ -0,0 +1,37 @@
|
||||
from django.core.management.base import BaseCommand, CommandError
|
||||
from django.contrib.auth.models import User
|
||||
from employee.models import Employee
|
||||
|
||||
class Command(BaseCommand):
|
||||
help = 'Creates a new user'
|
||||
|
||||
def add_arguments(self, parser):
|
||||
pass
|
||||
|
||||
def handle(self, *args, **options):
|
||||
first_name = input('Enter first name: ')
|
||||
last_name = input('Enter last name: ')
|
||||
username = input('Enter username: ')
|
||||
password = input('Enter password: ')
|
||||
email = input('Enter email: ')
|
||||
phone = input('Enter phone number: ')
|
||||
try:
|
||||
user = User.objects.create_superuser(username=username, email=email, password=password)
|
||||
employee = Employee()
|
||||
employee.employee_user_id =user
|
||||
employee.employee_first_name = first_name
|
||||
employee.employee_last_name = last_name
|
||||
employee.email = email
|
||||
employee.phone = phone
|
||||
employee.save()
|
||||
bot = User.objects.filter(username="Horilla Bot").first()
|
||||
if bot is None:
|
||||
User.objects.create_user(
|
||||
username="Horilla Bot",
|
||||
password="#HorillaBot!!(*&*&^(33))",
|
||||
)
|
||||
|
||||
self.stdout.write(self.style.SUCCESS('Employee "%s" created successfully' % employee))
|
||||
except Exception as e:
|
||||
user.delete()
|
||||
raise CommandError('Error creating user "%s": %s' % (username, e))
|
||||
99
base/methods.py
Normal file
99
base/methods.py
Normal file
@@ -0,0 +1,99 @@
|
||||
from employee.models import Employee
|
||||
|
||||
|
||||
def filtersubordinates(request,queryset,perm=None):
|
||||
"""
|
||||
This method is used to filter out subordinates queryset element.
|
||||
"""
|
||||
user = request.user
|
||||
if user.has_perm(perm):
|
||||
return queryset
|
||||
manager = Employee.objects.filter(employee_user_id=user).first()
|
||||
queryset = queryset.filter(employee_id__employee_work_info__reporting_manager_id=manager)
|
||||
return queryset
|
||||
|
||||
|
||||
def filtersubordinatesemployeemodel(request,queryset,perm=None):
|
||||
"""
|
||||
This method is used to filter out subordinates queryset element.
|
||||
"""
|
||||
user = request.user
|
||||
if user.has_perm(perm):
|
||||
return queryset
|
||||
manager = Employee.objects.filter(employee_user_id=user).first()
|
||||
queryset = queryset.filter(employee_work_info__reporting_manager_id=manager)
|
||||
return queryset
|
||||
|
||||
|
||||
|
||||
def is_reportingmanager(request):
|
||||
"""
|
||||
This method is used to check weather the employee is reporting manager or not.
|
||||
"""
|
||||
try:
|
||||
user = request.user
|
||||
return user.employee_get.reporting_manager.all().exists()
|
||||
except:
|
||||
return False
|
||||
|
||||
|
||||
def choosesubordinates(request,form,perm,):
|
||||
user = request.user
|
||||
if user.has_perm(perm):
|
||||
return form
|
||||
manager = Employee.objects.filter(employee_user_id=user).first()
|
||||
queryset = Employee.objects.filter(employee_work_info__reporting_manager_id=manager)
|
||||
form.fields['employee_id'].queryset = queryset
|
||||
return form
|
||||
|
||||
|
||||
def choosesubordinatesemployeemodel(request,form,perm):
|
||||
user = request.user
|
||||
if user.has_perm(perm):
|
||||
return form
|
||||
manager = Employee.objects.filter(employee_user_id=user).first()
|
||||
queryset = Employee.objects.filter(employee_work_info__reporting_manager_id=manager)
|
||||
|
||||
form.fields['employee_id'].queryset = queryset
|
||||
return form
|
||||
|
||||
|
||||
|
||||
orderingList = [{
|
||||
'id':'',
|
||||
'field':'',
|
||||
'ordering':'',
|
||||
}]
|
||||
|
||||
def sortby(request,queryset,key):
|
||||
"""
|
||||
This method is used to sort query set by asc or desc
|
||||
"""
|
||||
global orderingList
|
||||
id = request.user.id
|
||||
# here will create dictionary object to the global orderingList if not exists,
|
||||
# if exists then method will switch corresponding object ordering.
|
||||
filtered_list = [x for x in orderingList if x['id'] ==id]
|
||||
ordering = filtered_list[0] if filtered_list else None
|
||||
if ordering is None:
|
||||
ordering = {
|
||||
'id':id,
|
||||
'field':None,
|
||||
'ordering':'-',
|
||||
}
|
||||
orderingList.append(ordering)
|
||||
sortby = request.GET.get(key)
|
||||
if sortby is not None and sortby != '':
|
||||
# here will update the orderingList
|
||||
ordering['field']=sortby
|
||||
if queryset.query.order_by == queryset.query.order_by:
|
||||
queryset = queryset.order_by(f'{ordering["ordering"]}{sortby}')
|
||||
if ordering['ordering']=='-':
|
||||
ordering['ordering']=''
|
||||
else:
|
||||
ordering["ordering"]='-'
|
||||
orderingList = [item for item in orderingList if item["id"] != id]
|
||||
orderingList.append(ordering)
|
||||
|
||||
return queryset
|
||||
|
||||
0
base/migrations/__init__.py
Normal file
0
base/migrations/__init__.py
Normal file
270
base/models.py
Normal file
270
base/models.py
Normal file
@@ -0,0 +1,270 @@
|
||||
import django
|
||||
from django.db import models
|
||||
from simple_history.models import HistoricalRecords
|
||||
from django.core.exceptions import ValidationError
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
|
||||
# Create your models here.
|
||||
|
||||
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:
|
||||
hour, minute = value.split(":")
|
||||
hour = int(hour)
|
||||
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
|
||||
|
||||
|
||||
|
||||
class Company(models.Model):
|
||||
company = models.CharField(max_length=50)
|
||||
hq = models.BooleanField(default=False)
|
||||
address = models.TextField()
|
||||
country = models.CharField(max_length=50)
|
||||
state = models.CharField(max_length=50)
|
||||
city = models.CharField(max_length=50)
|
||||
zip = models.CharField(max_length=20)
|
||||
icon = models.FileField(upload_to='base/icon',null=True)
|
||||
class Meta:
|
||||
unique_together = ['company','address']
|
||||
|
||||
def __str__(self) -> str:
|
||||
return self.company
|
||||
|
||||
|
||||
class Department(models.Model):
|
||||
department = models.CharField(max_length=50, blank=False,unique=True)
|
||||
history = HistoricalRecords()
|
||||
|
||||
def __str__(self):
|
||||
return self.department
|
||||
|
||||
|
||||
class JobPosition(models.Model):
|
||||
job_position = models.CharField(max_length=50, blank=False, null=False, unique=True)
|
||||
department_id = models.ForeignKey(
|
||||
Department, on_delete=models.CASCADE, blank=True, related_name='job_position')
|
||||
def __str__(self):
|
||||
return self.job_position
|
||||
|
||||
|
||||
class JobRole(models.Model):
|
||||
job_position_id = models.ForeignKey(JobPosition, on_delete=models.CASCADE)
|
||||
job_role = models.CharField(max_length=50,blank=False,null=True)
|
||||
class Meta:
|
||||
unique_together = ('job_position_id','job_role')
|
||||
def __str__(self):
|
||||
return f'{self.job_role} - {self.job_position_id.job_position}'
|
||||
|
||||
class WorkType(models.Model):
|
||||
work_type = models.CharField(max_length=50)
|
||||
def __str__(self) -> str:
|
||||
return self.work_type
|
||||
|
||||
class RotatingWorkType(models.Model):
|
||||
name = models.CharField(max_length=50)
|
||||
work_type1 = models.ForeignKey(WorkType,on_delete=models.CASCADE,related_name='work_type1')
|
||||
work_type2 = models.ForeignKey(WorkType,on_delete=models.CASCADE,related_name='work_type2')
|
||||
employee_id = models.ManyToManyField('employee.Employee', through='RotatingWorkTypeAssign')
|
||||
|
||||
def __str__(self) -> str:
|
||||
return self.name
|
||||
|
||||
def clean(self):
|
||||
if self.work_type1 == self.work_type2:
|
||||
raise ValidationError(_('Choose different work type'))
|
||||
|
||||
|
||||
DAY_DATE = [(str(i), str(i)) for i in range(1, 32)]
|
||||
DAY_DATE.append(('last', _('Last Day')))
|
||||
DAY = [
|
||||
('monday', _('Monday')),
|
||||
('tuesday', _('Tuesday')),
|
||||
('wednesday', _('Wednesday')),
|
||||
('thursday', _('Thursday')),
|
||||
('friday', _('Friday')),
|
||||
('saturday', _('Saturday')),
|
||||
('sunday', _('Sunday')),
|
||||
]
|
||||
BASED_ON = [
|
||||
('after',_('After')),
|
||||
('weekly',_('Weekend')),
|
||||
('monthly',_('Monthly')),
|
||||
]
|
||||
|
||||
class RotatingWorkTypeAssign(models.Model):
|
||||
|
||||
employee_id = models.ForeignKey('employee.Employee',on_delete=models.CASCADE,null=True)
|
||||
rotating_work_type_id = models.ForeignKey(RotatingWorkType,on_delete=models.CASCADE)
|
||||
next_change_date = models.DateField(null=True)
|
||||
start_date = models.DateField(default= django.utils.timezone.now)
|
||||
based_on = models.CharField(max_length=10,choices=BASED_ON,null=False,blank=False)
|
||||
rotate_after_day = models.IntegerField(default=7)
|
||||
rotate_every_weekend = models.CharField(max_length=10,default='monday',choices=DAY,blank=True,null=True)
|
||||
rotate_every = models.CharField(max_length=10,default='1',choices=DAY_DATE)
|
||||
current_work_type = models.ForeignKey(WorkType,null=True,on_delete=models.DO_NOTHING,related_name='current_work_type')
|
||||
next_work_type = models.ForeignKey(WorkType,null=True,on_delete=models.DO_NOTHING,related_name='next_work_type')
|
||||
is_active = models.BooleanField(default=True)
|
||||
|
||||
class Meta:
|
||||
ordering = ['-next_change_date','-employee_id__employee_first_name']
|
||||
|
||||
def clean(self):
|
||||
if self.is_active and self.employee_id is not None:
|
||||
# Check if any other active record with the same parent already exists
|
||||
siblings = RotatingWorkTypeAssign.objects.filter(is_active=True, employee_id=self.employee_id)
|
||||
if siblings.exists() and siblings.first().id != self.id:
|
||||
raise ValidationError(_('Only one active record allowed per employee'))
|
||||
if self.start_date < django.utils.timezone.now().date():
|
||||
raise ValidationError(_('Date must be greater than or equal to today'))
|
||||
|
||||
|
||||
class EmployeeType(models.Model):
|
||||
employee_type = models.CharField(max_length=50)
|
||||
|
||||
def __str__(self) -> str:
|
||||
return self.employee_type
|
||||
|
||||
class EmployeeShiftDay(models.Model):
|
||||
|
||||
day = models.CharField(max_length=20, choices=DAY)
|
||||
def __str__(self) -> str:
|
||||
return self.day
|
||||
|
||||
class EmployeeShift(models.Model):
|
||||
employee_shift = models.CharField(max_length=50, null=False, blank=False,)
|
||||
days = models.ManyToManyField(
|
||||
EmployeeShiftDay, through='EmployeeShiftSchedule')
|
||||
weekly_full_time = models.CharField(max_length=6,default='40:00',null=True,blank=True)
|
||||
full_time = models.CharField(max_length=6,default='200:00',validators=[validate_time_format])
|
||||
|
||||
|
||||
def __str__(self) -> str:
|
||||
return self.employee_shift
|
||||
|
||||
class EmployeeShiftSchedule(models.Model):
|
||||
day = models.ForeignKey(EmployeeShiftDay,
|
||||
on_delete=models.CASCADE,related_name='day_schedule')
|
||||
|
||||
shift_id = models.ForeignKey(
|
||||
EmployeeShift, on_delete=models.CASCADE)
|
||||
minimum_working_hour = models.CharField(default='08:15',max_length=5,validators=[validate_time_format])
|
||||
start_time = models.TimeField(null=True)
|
||||
end_time = models.TimeField(null=True)
|
||||
|
||||
class Meta:
|
||||
unique_together = [
|
||||
['shift_id', 'day']
|
||||
]
|
||||
|
||||
def __str__(self) -> str:
|
||||
return f'{self.shift_id.employee_shift} {self.day}'
|
||||
|
||||
class RotatingShift(models.Model):
|
||||
name =models.CharField(max_length=50)
|
||||
employee_id = models.ManyToManyField('employee.Employee',through='RotatingShiftAssign')
|
||||
shift1 = models.ForeignKey(EmployeeShift,related_name='shift1',on_delete=models.CASCADE)
|
||||
shift2 = models.ForeignKey(EmployeeShift,related_name='shift2',on_delete=models.CASCADE)
|
||||
|
||||
def __str__(self) -> str:
|
||||
return self.name
|
||||
def clean(self):
|
||||
if self.shift1 == self.shift2:
|
||||
raise ValidationError(_('Choose different shifts'))
|
||||
|
||||
|
||||
class RotatingShiftAssign(models.Model):
|
||||
|
||||
# employee_id = models.OneToOneField('employee.Employee',on_delete=models.CASCADE)
|
||||
employee_id = models.ForeignKey('employee.Employee',on_delete=models.CASCADE)
|
||||
rotating_shift_id = models.ForeignKey(RotatingShift,on_delete=models.CASCADE)
|
||||
next_change_date = models.DateField(null=True)
|
||||
start_date = models.DateField(default=django.utils.timezone.now)
|
||||
based_on = models.CharField(max_length=10,choices=BASED_ON,null=False,blank=False)
|
||||
rotate_after_day = models.IntegerField(null=True,blank=True,default=7)
|
||||
rotate_every_weekend = models.CharField(max_length=10,default='monday',choices=DAY,blank=True,null=True)
|
||||
rotate_every = models.CharField(max_length=10,blank=True,null=True,default='1',choices=DAY_DATE)
|
||||
current_shift = models.ForeignKey(EmployeeShift,on_delete=models.DO_NOTHING,null=True,related_name='current_shift')
|
||||
next_shift = models.ForeignKey(EmployeeShift,on_delete=models.DO_NOTHING,null=True,related_name='next_shift')
|
||||
is_active=models.BooleanField(default=True)
|
||||
|
||||
class Meta:
|
||||
ordering = ['-next_change_date','-employee_id__employee_first_name']
|
||||
|
||||
def clean(self):
|
||||
if self.is_active and self.employee_id is not None:
|
||||
# Check if any other active record with the same parent already exists
|
||||
siblings = RotatingShiftAssign.objects.filter(is_active=True, employee_id=self.employee_id)
|
||||
if siblings.exists() and siblings.first().id != self.id:
|
||||
raise ValidationError(_('Only one active record allowed per employee'))
|
||||
if self.start_date < django.utils.timezone.now().date():
|
||||
raise ValidationError(_('Date must be greater than or equal to today'))
|
||||
|
||||
|
||||
class WorkTypeRequest(models.Model):
|
||||
|
||||
def save(self, *args, **kwargs):
|
||||
|
||||
super(WorkTypeRequest,self).save(*args, **kwargs)
|
||||
|
||||
employee_id = models.ForeignKey('employee.Employee',on_delete=models.CASCADE,null=True,related_name='work_type_request')
|
||||
requested_date = models.DateField(null=True,default=django.utils.timezone.now)
|
||||
requested_till = models.DateField(null=True,blank=True,default=django.utils.timezone.now)
|
||||
work_type_id = models.ForeignKey(WorkType,on_delete=models.CASCADE,related_name='requested_work_type')
|
||||
previous_work_type_id = models.ForeignKey(WorkType,on_delete=models.DO_NOTHING,null=True,blank=True,related_name='previous_work_type')
|
||||
description = models.TextField(null=True)
|
||||
approved = models.BooleanField(default=False)
|
||||
canceled = models.BooleanField(default=False)
|
||||
work_type_changed = models.BooleanField(default=False)
|
||||
is_active= models.BooleanField(default=True)
|
||||
|
||||
class Meta:
|
||||
permissions = (('approve_worktyperequest','Approve Work Type Request'),('cancel_worktyperequest','Cancel Work Type Request'))
|
||||
ordering = ['requested_date',]
|
||||
unique_together = ['employee_id','requested_date']
|
||||
|
||||
def clean(self):
|
||||
if self.requested_date < django.utils.timezone.now().date():
|
||||
raise ValidationError(_('Date must be greater than or equal to today'))
|
||||
if self.requested_till and self.requested_till < self.requested_date:
|
||||
raise ValidationError(_('End date must be greater than or equal to start date'))
|
||||
|
||||
|
||||
class ShiftRequest(models.Model):
|
||||
|
||||
employee_id = models.ForeignKey('employee.Employee',on_delete=models.CASCADE,null=True,related_name='shift_request')
|
||||
requested_date = models.DateField(null=True,default=django.utils.timezone.now)
|
||||
requested_till = models.DateField(null=True,blank=True,default=django.utils.timezone.now)
|
||||
shift_id = models.ForeignKey(EmployeeShift,on_delete=models.CASCADE,related_name='requested_shift')
|
||||
previous_shift_id = models.ForeignKey(EmployeeShift,on_delete=models.DO_NOTHING,null=True,blank=True,related_name='previous_shift')
|
||||
description = models.TextField(null=True)
|
||||
approved = models.BooleanField(default=False)
|
||||
canceled = models.BooleanField(default=False)
|
||||
shift_changed = models.BooleanField(default=False)
|
||||
is_active= models.BooleanField(default=True)
|
||||
|
||||
class Meta:
|
||||
permissions = (('approve_shiftrequest','Approve Shift Request'),('cancel_shiftrequest','Cancel Shift Request'))
|
||||
ordering = ['requested_date',]
|
||||
unique_together = ['employee_id','requested_date']
|
||||
|
||||
def clean(self):
|
||||
if self.requested_date < django.utils.timezone.now().date():
|
||||
raise ValidationError(_('Date must be greater than or equal to today'))
|
||||
if self.requested_till and self.requested_till < self.requested_date:
|
||||
raise ValidationError(_('End date must be greater than or equal to start date'))
|
||||
|
||||
|
||||
def save(self, *args, **kwargs):
|
||||
|
||||
super(ShiftRequest,self).save(*args, **kwargs)
|
||||
|
||||
def __str__(self):
|
||||
return f"{self.employee_id}"
|
||||
297
base/scheduler.py
Normal file
297
base/scheduler.py
Normal file
@@ -0,0 +1,297 @@
|
||||
from apscheduler.schedulers.background import BackgroundScheduler
|
||||
from datetime import datetime, timedelta, date
|
||||
import calendar
|
||||
from notifications.signals import notify
|
||||
|
||||
|
||||
def update_rotating_work_type_assign(rotating_work_type,new_date):
|
||||
"""
|
||||
Here will update the employee work information details and send notification
|
||||
"""
|
||||
from django.contrib.auth.models import User
|
||||
employee = rotating_work_type.employee_id
|
||||
employee_work_info = employee.employee_work_info
|
||||
work_type1 = rotating_work_type.rotating_work_type_id.work_type1
|
||||
work_type2 = rotating_work_type.rotating_work_type_id.work_type2
|
||||
new = rotating_work_type.next_work_type
|
||||
next = work_type2
|
||||
if new == next:
|
||||
next = work_type1
|
||||
employee_work_info.work_type_id = new
|
||||
employee_work_info.save()
|
||||
rotating_work_type.next_change_date = new_date
|
||||
rotating_work_type.current_work_type = new
|
||||
rotating_work_type.next_work_type = next
|
||||
rotating_work_type.save()
|
||||
bot = User.objects.filter(username="Horilla Bot").first()
|
||||
if bot is not None:
|
||||
employee = rotating_work_type.employee_id
|
||||
notify.send(bot,recipient=employee.employee_user_id,verb="Your Work Type has been changed.",icon="infinite",redirect="/employee/employee-profile")
|
||||
return
|
||||
|
||||
def work_type_rotate_after(rotating_work_work_type):
|
||||
"""
|
||||
This method for rotate work type based on after day
|
||||
"""
|
||||
date_today = datetime.now()
|
||||
switch_date = rotating_work_work_type.next_change_date
|
||||
if switch_date.strftime('%Y-%m-%d') == date_today.strftime('%Y-%m-%d'):
|
||||
# calculate the next work type switch date
|
||||
new_date = date_today + timedelta(days=rotating_work_work_type.rotate_after_day)
|
||||
update_rotating_work_type_assign(rotating_work_work_type,new_date)
|
||||
return
|
||||
|
||||
|
||||
def work_type_rotate_weekend(rotating_work_type):
|
||||
"""
|
||||
This method for rotate work type based on weekend
|
||||
"""
|
||||
date_today = datetime.now()
|
||||
switch_date = rotating_work_type.next_change_date
|
||||
if switch_date.strftime('%Y-%m-%d') == date_today.strftime('%Y-%m-%d'):
|
||||
# calculate the next work type switch date
|
||||
day = datetime.now().strftime('%A').lower()
|
||||
switch_day = rotating_work_type.rotate_every_weekend
|
||||
if day == switch_day:
|
||||
new_date = date_today + timedelta(days=7)
|
||||
update_rotating_work_type_assign(rotating_work_type,new_date)
|
||||
return
|
||||
|
||||
def work_type_rotate_every(rotating_work_type):
|
||||
"""
|
||||
This method for rotate work type based on every month
|
||||
"""
|
||||
date_today = datetime.now()
|
||||
switch_date = rotating_work_type.next_change_date
|
||||
day_date = rotating_work_type.rotate_every
|
||||
if switch_date.strftime('%Y-%m-%d') == date_today.strftime('%Y-%m-%d'):
|
||||
# calculate the next work type switch date
|
||||
if day_date == switch_date.strftime('%d').lstrip('0'):
|
||||
new_date = date_today.replace(month=date_today.month + 1)
|
||||
update_rotating_work_type_assign(rotating_work_type, new_date)
|
||||
elif day_date == 'last':
|
||||
year = date_today.strftime('%Y')
|
||||
month = date_today.strftime('%m')
|
||||
last_day = calendar.monthrange(int(year), int(month) + 1)[1]
|
||||
new_date = datetime(int(year), int(month) + 1, last_day)
|
||||
update_rotating_work_type_assign(rotating_work_type, new_date)
|
||||
return
|
||||
|
||||
def rotate_work_type():
|
||||
"""
|
||||
This method will identify the based on condition to the rotating shift assign
|
||||
and redirect to the chunk method to execute.
|
||||
"""
|
||||
from base.models import RotatingWorkTypeAssign
|
||||
rotating_work_types = RotatingWorkTypeAssign.objects.filter(is_active=True)
|
||||
for rotating_work_type in rotating_work_types:
|
||||
based_on = rotating_work_type.based_on
|
||||
if based_on =='after':
|
||||
work_type_rotate_after(rotating_work_type)
|
||||
elif based_on == 'weekly':
|
||||
work_type_rotate_weekend(rotating_work_type)
|
||||
elif based_on == 'monthly':
|
||||
work_type_rotate_every(rotating_work_type)
|
||||
return
|
||||
|
||||
|
||||
|
||||
def update_rotating_shift_assign(rotating_shift,new_date):
|
||||
"""
|
||||
Here will update the employee work information and send notification
|
||||
"""
|
||||
from django.contrib.auth.models import User
|
||||
employee = rotating_shift.employee_id
|
||||
employee_work_info = employee.employee_work_info
|
||||
shift1 = rotating_shift.rotating_shift_id.shift1
|
||||
shift2 = rotating_shift.rotating_shift_id.shift2
|
||||
new = rotating_shift.next_shift
|
||||
next = shift2
|
||||
if new == next:
|
||||
next = shift1
|
||||
employee_work_info.shift_id = new
|
||||
employee_work_info.save()
|
||||
rotating_shift.next_change_date = new_date
|
||||
rotating_shift.current_shift = new
|
||||
rotating_shift.next_shift = next
|
||||
rotating_shift.save()
|
||||
bot = User.objects.filter(username='Horilla Bot').first()
|
||||
if bot is not None:
|
||||
employee = rotating_shift.employee_id
|
||||
notify.send(bot,recipient=employee.employee_user_id,verb="Your shift has been changed.",icon="infinite",redirect="/employee/employee-profile")
|
||||
return
|
||||
|
||||
def shift_rotate_after_day(rotating_shift):
|
||||
"""
|
||||
This method for rotate shift based on after day
|
||||
"""
|
||||
date_today = datetime.now()
|
||||
switch_date = rotating_shift.next_change_date
|
||||
if switch_date.strftime('%Y-%m-%d') == date_today.strftime('%Y-%m-%d'):
|
||||
# calculate the next work type switch date
|
||||
new_date = date_today + timedelta(days=rotating_shift.rotate_after_day)
|
||||
update_rotating_shift_assign(rotating_shift,new_date)
|
||||
return
|
||||
|
||||
def shift_rotate_weekend(rotating_shift):
|
||||
"""
|
||||
This method for rotate shift based on weekend
|
||||
"""
|
||||
date_today = datetime.now()
|
||||
switch_date = rotating_shift.next_change_date
|
||||
if switch_date.strftime('%Y-%m-%d') == date_today.strftime('%Y-%m-%d'):
|
||||
# calculate the next work type switch date
|
||||
day = datetime.now().strftime('%A').lower()
|
||||
switch_day = rotating_shift.rotate_every_weekend
|
||||
if day == switch_day:
|
||||
new_date = date_today + timedelta(days=7)
|
||||
update_rotating_shift_assign(rotating_shift,new_date)
|
||||
return
|
||||
|
||||
def shift_rotate_every(rotating_shift):
|
||||
"""
|
||||
This method for rotate shift based on every month
|
||||
"""
|
||||
date_today = datetime.now()
|
||||
switch_date = rotating_shift.next_change_date
|
||||
day_date = rotating_shift.rotate_every
|
||||
if switch_date.strftime('%Y-%m-%d') == date_today.strftime('%Y-%m-%d'):
|
||||
# calculate the next work type switch date
|
||||
if day_date == switch_date.strftime('%d').lstrip('0'):
|
||||
new_date = date_today.replace(month=date_today.month + 1)
|
||||
update_rotating_shift_assign(rotating_shift, new_date)
|
||||
elif day_date == 'last':
|
||||
year = date_today.strftime('%Y')
|
||||
month = date_today.strftime('%m')
|
||||
last_day = calendar.monthrange(int(year), int(month) + 1)[1]
|
||||
new_date = datetime(int(year), int(month) + 1, last_day)
|
||||
update_rotating_shift_assign(rotating_shift, new_date)
|
||||
return
|
||||
|
||||
|
||||
def rotate_shift():
|
||||
"""
|
||||
This method will identify the based on condition to the rotating shift assign
|
||||
and redirect to the chunk method to execute.
|
||||
"""
|
||||
from base.models import RotatingShiftAssign
|
||||
rotating_shifts = RotatingShiftAssign.objects.filter(is_active=True)
|
||||
for rotating_shift in rotating_shifts:
|
||||
based_on = rotating_shift.based_on
|
||||
# after day condition
|
||||
if based_on =='after':
|
||||
shift_rotate_after_day(rotating_shift)
|
||||
# weekly condition
|
||||
elif based_on == 'weekly':
|
||||
shift_rotate_weekend(rotating_shift)
|
||||
# monthly condition
|
||||
elif based_on == 'monthly':
|
||||
shift_rotate_every(rotating_shift)
|
||||
|
||||
return
|
||||
|
||||
|
||||
|
||||
def switch_shift():
|
||||
"""
|
||||
This method change employees shift information regards to the shift request
|
||||
"""
|
||||
from base.models import ShiftRequest
|
||||
from django.contrib.auth.models import User
|
||||
|
||||
today =date.today()
|
||||
shift_requests = ShiftRequest.objects.filter(canceled=False,approved=True,requested_date__exact=today,shift_changed=False)
|
||||
for request in shift_requests:
|
||||
work_info = request.employee_id.employee_work_info
|
||||
# updating requested shift to the employee work information.
|
||||
work_info.shift_id = request.shift_id
|
||||
work_info.save()
|
||||
request.approved = True
|
||||
request.shift_changed = True
|
||||
request.save()
|
||||
bot = User.objects.filter(username='Horilla Bot').first()
|
||||
if bot is not None:
|
||||
employee = request.employee_id
|
||||
notify.send(bot,recipient=employee.employee_user_id,verb="Shift Changes notification",icon="refresh",redirect="/employee/employee-profile")
|
||||
return
|
||||
|
||||
def undo_shift():
|
||||
"""
|
||||
This method undo previous employees shift information regards to the shift request
|
||||
"""
|
||||
from base.models import ShiftRequest
|
||||
from django.contrib.auth.models import User
|
||||
|
||||
today =date.today()
|
||||
# here will get all the active shift requests
|
||||
shift_requests = ShiftRequest.objects.filter(canceled=False,approved=True,requested_till__lt=today,is_active=True,shift_changed=True)
|
||||
for request in shift_requests:
|
||||
work_info = request.employee_id.employee_work_info
|
||||
work_info.shift_id = request.previous_shift_id
|
||||
work_info.save()
|
||||
# making the instance in-active
|
||||
request.is_active = False
|
||||
request.save()
|
||||
bot = User.objects.filter(username='Horilla Bot').first()
|
||||
if bot is not None:
|
||||
employee = request.employee_id
|
||||
notify.send(bot,recipient=employee.employee_user_id,verb="Shift changes notification, Requested date expired.",icon="refresh",redirect="/employee/employee-profile")
|
||||
return
|
||||
|
||||
|
||||
|
||||
def switch_work_type():
|
||||
"""
|
||||
This method change employees work type information regards to the work type request
|
||||
"""
|
||||
from django.contrib.auth.models import User
|
||||
from base.models import WorkTypeRequest
|
||||
today =date.today()
|
||||
work_type_requests = WorkTypeRequest.objects.filter(canceled=False,approved=True,requested_date__exact=today,work_type_changed=False)
|
||||
for request in work_type_requests:
|
||||
work_info = request.employee_id.employee_work_info
|
||||
# updating requested work type to the employee work information.
|
||||
work_info.work_type_id = request.work_type_id
|
||||
work_info.save()
|
||||
request.approved = True
|
||||
request.work_type_changed = True
|
||||
request.save()
|
||||
bot = User.objects.filter(username='Horilla Bot').first()
|
||||
if bot is not None:
|
||||
employee = request.employee_id
|
||||
notify.send(bot,recipient=employee.employee_user_id,verb="Work Type Changes notification",icon="swap-horizontal",redirect="/employee/employee-profile")
|
||||
return
|
||||
|
||||
def undo_work_type():
|
||||
"""
|
||||
This method undo previous employees work type information regards to the work type request
|
||||
"""
|
||||
from base.models import WorkTypeRequest
|
||||
from django.contrib.auth.models import User
|
||||
|
||||
today =date.today()
|
||||
# here will get all the active work type requests
|
||||
work_type_requests = WorkTypeRequest.objects.filter(canceled=False,approved=True,requested_till__lt=today,is_active=True,work_type_changed=True)
|
||||
for request in work_type_requests:
|
||||
work_info = request.employee_id.employee_work_info
|
||||
# updating employee work information's work type to previous work type
|
||||
work_info.work_type_id = request.previous_work_type_id
|
||||
work_info.save()
|
||||
# making the instance is in-active
|
||||
request.is_active = False
|
||||
request.save()
|
||||
bot = User.objects.filter(username='Horilla Bot').first()
|
||||
if bot is not None:
|
||||
employee = request.employee_id
|
||||
notify.send(bot,recipient=employee.employee_user_id,verb="Work type changes notification, Requested date expired.",icon="swap-horizontal",redirect="/employee/employee-profile")
|
||||
return
|
||||
|
||||
|
||||
scheduler = BackgroundScheduler()
|
||||
scheduler.add_job(rotate_shift, 'interval', seconds=10)
|
||||
scheduler.add_job(rotate_work_type, 'interval', seconds=10)
|
||||
scheduler.add_job(switch_shift, 'interval', seconds=10)
|
||||
scheduler.add_job(undo_shift, 'interval', seconds=10)
|
||||
scheduler.add_job(switch_work_type, 'interval', seconds=10)
|
||||
scheduler.add_job(undo_work_type, 'interval', seconds=10)
|
||||
scheduler.start()
|
||||
8
base/signals.py
Normal file
8
base/signals.py
Normal file
@@ -0,0 +1,8 @@
|
||||
# from django.db.models.signals import post_save
|
||||
# from notifications.signals import notify
|
||||
# from base.models import *
|
||||
|
||||
# def my_handler(sender, instance, created, **kwargs):
|
||||
# notify.send(instance, verb='was saved')
|
||||
|
||||
# post_save.connect(my_handler, sender=ShiftRequest)
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user