[INITIAL COMMIT]

This commit is contained in:
Nikhil
2023-05-10 15:06:57 +05:30
parent ad897ada98
commit 2e18244716
10107 changed files with 1663194 additions and 1 deletions

326
README.md
View File

@@ -1 +1,325 @@
# horilla_
# **Horilla 🦍** [![AGPL License](https://img.shields.io/badge/license-AGPL-blue.svg)](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

Binary file not shown.

0
asset/__init__.py Normal file
View File

10
asset/admin.py Normal file
View 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
View 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
View 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
View 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}),
}

View File

66
asset/models.py Normal file
View 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
View File

@@ -0,0 +1,6 @@
from import_export import resources
from .models import Asset
class AssetResource(resources.ModelResource):
class Meta:
model = Asset

View File

View 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();
}
});
});

View 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 %}

View 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 %}

View 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>

View 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>

View 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>

View 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 %}

View 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>

View 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>

View 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>

View 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 %}

View 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>

View 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>

View 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>

View 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 %}

View 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 %}

View File

@@ -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>

View 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>

View File

@@ -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>

View File

@@ -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 %}

View File

@@ -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>

View File

View 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
View File

@@ -0,0 +1,3 @@
from django.test import TestCase
# Create your tests here.

42
asset/urls.py Normal file
View 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
View 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
View File

8
attendance/admin.py Normal file
View 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
View 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
View 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
View 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)

View File

201
attendance/models.py Normal file
View 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.'))

View 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);
}
},
});
}
});
});

View 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();
});
});

View File

@@ -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>

View 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>

View File

@@ -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 %}

View 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>

View 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>

View 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>

View 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>

View File

@@ -0,0 +1 @@
{{form}}

View File

@@ -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>

View File

@@ -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 %}

View File

@@ -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>

View 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>

View 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>

View File

@@ -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 %}

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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 %}

View File

@@ -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>

View 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>

View 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 %}

View File

@@ -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>

View 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 %}

View File

@@ -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>

View File

@@ -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>

View 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>

View File

@@ -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>

View File

@@ -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 %}

View 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>

View 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>

View 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>

View File

@@ -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 %}

View File

View 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)

3
attendance/tests.py Normal file
View File

@@ -0,0 +1,3 @@
from django.test import TestCase
# Create your tests here.

69
attendance/urls.py Normal file
View 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

File diff suppressed because it is too large Load Diff

44
auth/forgot-password.html Normal file
View 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
View 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
View 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

Binary file not shown.

1
base/__init__.py Normal file
View File

@@ -0,0 +1 @@
from . import scheduler

22
base/admin.py Normal file
View 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
View 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
View File

177
base/filters.py Normal file
View 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
View 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()

View 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
View 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

View File

270
base/models.py Normal file
View 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
View 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
View 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