Implemented financial tracking
This commit is contained in:
4
.antigravityrules
Normal file
4
.antigravityrules
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
# Django Virtual Environment Rule
|
||||||
|
|
||||||
|
Always use the virtual environment located at `.venv` when running any Python scripts, pip commands, or Django management commands.
|
||||||
|
You can run commands using the virtual environment's Python executable directly (e.g., `.venv/bin/python manage.py ...`) or activate it first.
|
||||||
4
.cursorrules
Normal file
4
.cursorrules
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
# Django Virtual Environment Rule
|
||||||
|
|
||||||
|
Always use the virtual environment located at `.venv` when running any Python scripts, pip commands, or Django management commands.
|
||||||
|
You can run commands using the virtual environment's Python executable directly (e.g., `.venv/bin/python manage.py ...`) or activate it first.
|
||||||
@@ -1,20 +1,90 @@
|
|||||||
|
import datetime
|
||||||
from django import forms
|
from django import forms
|
||||||
|
|
||||||
from django.forms import ModelForm
|
from django.forms import ModelForm
|
||||||
from .models import Employee, Contract, ChargeNumber
|
from .models import Employee, Contract, ChargeNumber, TimeCardCell, AddressModel
|
||||||
|
|
||||||
|
class NewEmployeeForm(ModelForm):
|
||||||
|
first_name = forms.CharField(max_length=30, required=False, label="First Name")
|
||||||
|
last_name = forms.CharField(max_length=30, required=False, label="Last Name")
|
||||||
|
address_1 = forms.CharField(max_length=128, label="Address Line 1")
|
||||||
|
address_2 = forms.CharField(max_length=128, required=False, label="Address Line 2")
|
||||||
|
city = forms.CharField(max_length=64, label="City")
|
||||||
|
state = forms.CharField(max_length=2, label="State (e.g. NY)")
|
||||||
|
zip_code = forms.CharField(max_length=5, label="ZIP Code")
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = Employee
|
||||||
|
fields = ["user", "manager", "phoneNumber", "slary"]
|
||||||
|
|
||||||
|
def save(self, commit=True):
|
||||||
|
employee = super().save(commit=False)
|
||||||
|
|
||||||
|
if self.cleaned_data.get('first_name') or self.cleaned_data.get('last_name'):
|
||||||
|
if self.cleaned_data.get('first_name'):
|
||||||
|
employee.user.first_name = self.cleaned_data.get('first_name')
|
||||||
|
if self.cleaned_data.get('last_name'):
|
||||||
|
employee.user.last_name = self.cleaned_data.get('last_name')
|
||||||
|
employee.user.save()
|
||||||
|
|
||||||
|
address = AddressModel.objects.create(
|
||||||
|
address_1=self.cleaned_data['address_1'],
|
||||||
|
address_2=self.cleaned_data['address_2'],
|
||||||
|
city=self.cleaned_data['city'],
|
||||||
|
state=self.cleaned_data['state'],
|
||||||
|
zip_code=self.cleaned_data['zip_code'],
|
||||||
|
)
|
||||||
|
employee.primaryAddress = address
|
||||||
|
employee.workAddress = address
|
||||||
|
if commit:
|
||||||
|
employee.save()
|
||||||
|
return employee
|
||||||
|
|
||||||
class EmployeeForm(ModelForm):
|
class EmployeeForm(ModelForm):
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Employee
|
model = Employee
|
||||||
# TODO: fix slary to be salary
|
fields = ["user", "manager", "primaryAddress", "workAddress", "phoneNumber", "slary"]
|
||||||
fields = ["primaryAddress","workAddress", "slary"]
|
|
||||||
|
|
||||||
class ContractForm(ModelForm):
|
class ContractForm(ModelForm):
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Contract
|
model = Contract
|
||||||
fields = ["contract_type","name","proposed_amount","baseline_amount","funded_amount","baseline_start","baseline_end"]
|
fields = ["contract_type","name","proposed_amount","baseline_amount","funded_amount","budget_hours","baseline_start","baseline_end"]
|
||||||
|
|
||||||
class ChargeNumberForm(ModelForm):
|
class ChargeNumberForm(ModelForm):
|
||||||
class Meta:
|
class Meta:
|
||||||
model = ChargeNumber
|
model = ChargeNumber
|
||||||
fields = ["charge_number_type","amount", "start_date","end_date"]
|
fields = ["charge_number_type","amount", "start_date","end_date"]
|
||||||
|
|
||||||
|
class TimeLogForm(ModelForm):
|
||||||
|
start_time = forms.TimeField(required=False, widget=forms.TimeInput(attrs={'type': 'time'}))
|
||||||
|
end_time = forms.TimeField(required=False, widget=forms.TimeInput(attrs={'type': 'time'}))
|
||||||
|
hour = forms.FloatField(required=False, label="Duration (hours)")
|
||||||
|
date = forms.DateField(initial=datetime.date.today, widget=forms.DateInput(attrs={'type': 'date'}))
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = TimeCardCell
|
||||||
|
fields = ["contract", "date", "start_time", "end_time", "hour"]
|
||||||
|
|
||||||
|
def clean(self):
|
||||||
|
cleaned_data = super().clean()
|
||||||
|
start = cleaned_data.get('start_time')
|
||||||
|
end = cleaned_data.get('end_time')
|
||||||
|
duration = cleaned_data.get('hour')
|
||||||
|
|
||||||
|
if start and end:
|
||||||
|
dt_start = datetime.datetime.combine(datetime.date.today(), start)
|
||||||
|
dt_end = datetime.datetime.combine(datetime.date.today(), end)
|
||||||
|
diff = (dt_end - dt_start).total_seconds() / 3600.0
|
||||||
|
if diff < 0:
|
||||||
|
diff += 24.0
|
||||||
|
cleaned_data['hour'] = round(diff, 2)
|
||||||
|
elif start and duration:
|
||||||
|
dt_start = datetime.datetime.combine(datetime.date.today(), start)
|
||||||
|
dt_end = dt_start + datetime.timedelta(hours=duration)
|
||||||
|
cleaned_data['end_time'] = (dt_end).time()
|
||||||
|
elif not duration and not (start and end):
|
||||||
|
raise forms.ValidationError("You must provide either (Start Time and End Time) OR (Start Time and Duration) OR (Duration).")
|
||||||
|
|
||||||
|
if not cleaned_data.get('hour') and duration:
|
||||||
|
cleaned_data['hour'] = duration
|
||||||
|
|
||||||
|
return cleaned_data
|
||||||
@@ -0,0 +1,76 @@
|
|||||||
|
# Generated by Django 5.0 on 2026-03-20 11:40
|
||||||
|
|
||||||
|
import django.db.models.deletion
|
||||||
|
from django.conf import settings
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('financial', '0005_chargenumber_financial_chargenumber_charge_number_type_chargenumbertypeenum_and_more'),
|
||||||
|
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='chargenumber',
|
||||||
|
name='created_by',
|
||||||
|
field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='+', to=settings.AUTH_USER_MODEL),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='chargenumber',
|
||||||
|
name='last_modified_BY',
|
||||||
|
field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='+', to=settings.AUTH_USER_MODEL),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='contract',
|
||||||
|
name='budget_hours',
|
||||||
|
field=models.FloatField(default=0.0),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='contract',
|
||||||
|
name='created_by',
|
||||||
|
field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='+', to=settings.AUTH_USER_MODEL),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='contract',
|
||||||
|
name='last_modified_BY',
|
||||||
|
field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='+', to=settings.AUTH_USER_MODEL),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='employee',
|
||||||
|
name='created_by',
|
||||||
|
field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='+', to=settings.AUTH_USER_MODEL),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='employee',
|
||||||
|
name='last_modified_BY',
|
||||||
|
field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='+', to=settings.AUTH_USER_MODEL),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='timecard',
|
||||||
|
name='created_by',
|
||||||
|
field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='+', to=settings.AUTH_USER_MODEL),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='timecard',
|
||||||
|
name='last_modified_BY',
|
||||||
|
field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='+', to=settings.AUTH_USER_MODEL),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='timecardcell',
|
||||||
|
name='contract',
|
||||||
|
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='financial.contract'),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='timecardcell',
|
||||||
|
name='created_by',
|
||||||
|
field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='+', to=settings.AUTH_USER_MODEL),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='timecardcell',
|
||||||
|
name='last_modified_BY',
|
||||||
|
field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='+', to=settings.AUTH_USER_MODEL),
|
||||||
|
),
|
||||||
|
]
|
||||||
@@ -0,0 +1,34 @@
|
|||||||
|
# Generated by Django 5.0 on 2026-03-20 14:35
|
||||||
|
|
||||||
|
import django_enum.fields
|
||||||
|
from django.conf import settings
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('financial', '0006_chargenumber_created_by_and_more'),
|
||||||
|
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.RemoveConstraint(
|
||||||
|
model_name='contract',
|
||||||
|
name='financial_Contract_contract_type_ContractTypeEnum',
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='contract',
|
||||||
|
name='award_amount',
|
||||||
|
field=models.FloatField(default=0.0),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='contract',
|
||||||
|
name='contract_type',
|
||||||
|
field=django_enum.fields.EnumCharField(choices=[('FFP', 'FIRM_FIX_PRICED'), ('CPFF', 'COST_PLUS_FIXED_FEE'), ('MAX', 'MAX_NUM_CONTRACT_TYPES')], max_length=4),
|
||||||
|
),
|
||||||
|
migrations.AddConstraint(
|
||||||
|
model_name='contract',
|
||||||
|
constraint=models.CheckConstraint(check=models.Q(('contract_type__in', ['FFP', 'CPFF', 'MAX'])), name='financial_Contract_contract_type_ContractTypeEnum'),
|
||||||
|
),
|
||||||
|
]
|
||||||
@@ -0,0 +1,38 @@
|
|||||||
|
# Generated by Django 5.0 on 2026-03-20 14:43
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('financial', '0007_remove_contract_financial_contract_contract_type_contracttypeenum_and_more'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='chargenumber',
|
||||||
|
name='slug',
|
||||||
|
field=models.SlugField(blank=True, null=True, unique=True),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='contract',
|
||||||
|
name='slug',
|
||||||
|
field=models.SlugField(blank=True, null=True, unique=True),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='employee',
|
||||||
|
name='slug',
|
||||||
|
field=models.SlugField(blank=True, null=True, unique=True),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='timecard',
|
||||||
|
name='slug',
|
||||||
|
field=models.SlugField(blank=True, null=True, unique=True),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='timecardcell',
|
||||||
|
name='slug',
|
||||||
|
field=models.SlugField(blank=True, null=True, unique=True),
|
||||||
|
),
|
||||||
|
]
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
# Generated by Django 5.0 on 2026-03-20 15:18
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('financial', '0008_alter_chargenumber_slug_alter_contract_slug_and_more'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='timecardcell',
|
||||||
|
name='end_time',
|
||||||
|
field=models.TimeField(blank=True, null=True),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='timecardcell',
|
||||||
|
name='start_time',
|
||||||
|
field=models.TimeField(blank=True, null=True),
|
||||||
|
),
|
||||||
|
]
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
# Generated by Django 5.0 on 2026-03-20 15:30
|
||||||
|
|
||||||
|
import django.db.models.deletion
|
||||||
|
import phonenumber_field.modelfields
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('financial', '0009_timecardcell_end_time_timecardcell_start_time'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='employee',
|
||||||
|
name='manager',
|
||||||
|
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='manager_employee', to='financial.employee'),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='employee',
|
||||||
|
name='phoneNumber',
|
||||||
|
field=phonenumber_field.modelfields.PhoneNumberField(blank=True, max_length=128, null=True, region=None, unique=True),
|
||||||
|
),
|
||||||
|
]
|
||||||
@@ -6,25 +6,36 @@ from django_enum import EnumField
|
|||||||
from django.utils.text import slugify
|
from django.utils.text import slugify
|
||||||
import datetime
|
import datetime
|
||||||
|
|
||||||
|
def user_str(self):
|
||||||
|
if self.first_name and self.last_name:
|
||||||
|
return f"{self.first_name} {self.last_name}"
|
||||||
|
if self.username:
|
||||||
|
return self.username
|
||||||
|
if self.email:
|
||||||
|
return self.email
|
||||||
|
return str(self.id)
|
||||||
|
|
||||||
|
User.__str__ = user_str
|
||||||
|
|
||||||
# Abstract Model Classes
|
# Abstract Model Classes
|
||||||
class TimeMixin(models.Model):
|
class TimeMixin(models.Model):
|
||||||
|
|
||||||
created = models.DateTimeField(default=timezone.now)
|
created = models.DateTimeField(default=timezone.now)
|
||||||
last_modified = models.DateTimeField(default=timezone.now)
|
last_modified = models.DateTimeField(default=timezone.now)
|
||||||
created_by = models.ForeignKey
|
created_by = models.ForeignKey(User, on_delete=models.SET_NULL, null=True, related_name='+')
|
||||||
last_modified_BY = models.ForeignKey
|
last_modified_BY = models.ForeignKey(User, on_delete=models.SET_NULL, null=True, related_name='+')
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
abstract = True
|
abstract = True
|
||||||
|
|
||||||
class IdMixin(models.Model):
|
class IdMixin(models.Model):
|
||||||
slug = models.SlugField()
|
slug = models.SlugField(blank=True, unique=True, null=True)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
abstract = True
|
abstract = True
|
||||||
|
|
||||||
def save(self, *args, **kwargs):
|
def save(self, *args, **kwargs):
|
||||||
if self.slug is None:
|
if not self.slug:
|
||||||
self.slug = slugify(datetime.datetime.now().time())
|
self.slug = slugify(datetime.datetime.now().time())
|
||||||
super(IdMixin, self).save(*args, **kwargs)
|
super(IdMixin, self).save(*args, **kwargs)
|
||||||
|
|
||||||
@@ -32,6 +43,7 @@ class IdMixin(models.Model):
|
|||||||
class Contract(IdMixin, TimeMixin):
|
class Contract(IdMixin, TimeMixin):
|
||||||
class ContractTypeEnum(models.TextChoices):
|
class ContractTypeEnum(models.TextChoices):
|
||||||
FIRM_FIX_PRICED = "FFP", "FIRM_FIX_PRICED"
|
FIRM_FIX_PRICED = "FFP", "FIRM_FIX_PRICED"
|
||||||
|
COST_PLUS_FIXED_FEE = "CPFF", "COST_PLUS_FIXED_FEE"
|
||||||
MAX_NUM_CONRACT_TYPES = "MAX", "MAX_NUM_CONTRACT_TYPES"
|
MAX_NUM_CONRACT_TYPES = "MAX", "MAX_NUM_CONTRACT_TYPES"
|
||||||
|
|
||||||
contract_type = EnumField(ContractTypeEnum)
|
contract_type = EnumField(ContractTypeEnum)
|
||||||
@@ -40,11 +52,16 @@ class Contract(IdMixin, TimeMixin):
|
|||||||
proposed_amount = models.FloatField(default=0.0)
|
proposed_amount = models.FloatField(default=0.0)
|
||||||
baseline_amount = models.FloatField(default=0.0)
|
baseline_amount = models.FloatField(default=0.0)
|
||||||
funded_amount = models.FloatField(default=0.0)
|
funded_amount = models.FloatField(default=0.0)
|
||||||
|
award_amount = models.FloatField(default=0.0)
|
||||||
|
budget_hours = models.FloatField(default=0.0)
|
||||||
|
|
||||||
baseline_start = models.DateField(null=True, blank=True, default=None)
|
baseline_start = models.DateField(null=True, blank=True, default=None)
|
||||||
baseline_end = models.DateField(null=True, blank=True, default=None)
|
baseline_end = models.DateField(null=True, blank=True, default=None)
|
||||||
linkedContracts = models.ForeignKey("self", on_delete=models.CASCADE, null=True, blank=True)
|
linkedContracts = models.ForeignKey("self", on_delete=models.CASCADE, null=True, blank=True)
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return f"{self.name} - {self.contract_type}"
|
||||||
|
|
||||||
# TODO: make calc ev func
|
# TODO: make calc ev func
|
||||||
|
|
||||||
class ChargeNumber(IdMixin, TimeMixin):
|
class ChargeNumber(IdMixin, TimeMixin):
|
||||||
@@ -73,14 +90,19 @@ class AddressModel(models.Model):
|
|||||||
zip_code = models.CharField(max_length=5)
|
zip_code = models.CharField(max_length=5)
|
||||||
|
|
||||||
class Employee(IdMixin, TimeMixin):
|
class Employee(IdMixin, TimeMixin):
|
||||||
manager = models.ForeignKey("self", on_delete=models.CASCADE, related_name="manager_employee")
|
manager = models.ForeignKey("self", on_delete=models.CASCADE, related_name="manager_employee", null=True, blank=True)
|
||||||
user = models.OneToOneField(User, on_delete=models.CASCADE)
|
user = models.OneToOneField(User, on_delete=models.CASCADE)
|
||||||
primaryAddress = models.ForeignKey(AddressModel, on_delete=models.CASCADE, related_name="primary_address_employee")
|
primaryAddress = models.ForeignKey(AddressModel, on_delete=models.CASCADE, related_name="primary_address_employee")
|
||||||
workAddress = models.ForeignKey(AddressModel, on_delete=models.CASCADE, related_name="work_address_employee")
|
workAddress = models.ForeignKey(AddressModel, on_delete=models.CASCADE, related_name="work_address_employee")
|
||||||
phoneNumber = PhoneNumberField(null=False, blank=False, unique=True)
|
phoneNumber = PhoneNumberField(null=True, blank=True, unique=True)
|
||||||
slary= models.FloatField(default=0.0)
|
slary= models.FloatField(default=0.0)
|
||||||
|
|
||||||
# TODO: get hourly salary
|
def __str__(self):
|
||||||
|
return str(self.user)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def hourly_salary(self):
|
||||||
|
return (self.slary or 0.0) / 2080.0
|
||||||
# TODO: roles, jpbTitles
|
# TODO: roles, jpbTitles
|
||||||
|
|
||||||
|
|
||||||
@@ -97,7 +119,10 @@ class TimeCard(IdMixin, TimeMixin):
|
|||||||
class TimeCardCell(IdMixin, TimeMixin):
|
class TimeCardCell(IdMixin, TimeMixin):
|
||||||
timeCard = models.ForeignKey(TimeCard, on_delete=models.CASCADE)
|
timeCard = models.ForeignKey(TimeCard, on_delete=models.CASCADE)
|
||||||
date = models.DateField(null=True, default = None)
|
date = models.DateField(null=True, default = None)
|
||||||
|
start_time = models.TimeField(null=True, blank=True)
|
||||||
|
end_time = models.TimeField(null=True, blank=True)
|
||||||
hour = models.FloatField(default = 0.0)
|
hour = models.FloatField(default = 0.0)
|
||||||
|
contract = models.ForeignKey(Contract, on_delete=models.CASCADE, null=True, blank=True)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,56 +1,65 @@
|
|||||||
<!-- a lot of stuff-->
|
{% extends "base.html" %}
|
||||||
|
|
||||||
<!doctype html>
|
|
||||||
{% load static %}
|
{% load static %}
|
||||||
<html lang="en"">
|
|
||||||
<head>
|
|
||||||
<title>Contract Detail</title>
|
|
||||||
<link href=" {% static 'financial/css/material-dashboard.css' %}" rel="stylesheet">
|
|
||||||
<link rel="icon" type="image/xicon" href="{% static 'public/img/favicon.jpg' %}">
|
|
||||||
|
|
||||||
<body class="g-sidenav-show bg-gray-200">
|
{% block title %}Contract Detail - AI ML Operations{% endblock %}
|
||||||
|
|
||||||
<main class="main-content position-relative max-height-vh-100 h-100 border-radius-lg">
|
{% block content %}
|
||||||
|
<div class="section">
|
||||||
|
<div class="container">
|
||||||
|
<div style="margin-bottom: 2rem;">
|
||||||
|
<a href="{% url 'contracts' %}" class="btn" style="padding: 0.5rem 1.5rem; font-size: 0.9rem;">Back to
|
||||||
|
Contracts</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
{% if is_new %}
|
{% if is_new %}
|
||||||
<h1>New Contract</h1>
|
<h1 class="section-title" style="text-align: left;">New Contract</h1>
|
||||||
|
<div class="card" style="max-width: 600px;">
|
||||||
<form method="post" action='{% url "new_contract" %}'>
|
<form method="post" action='{% url "new_contract" %}'>
|
||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
{{ form }}
|
{{ form.as_p }}
|
||||||
<button type="" button" value="submit">Create</button>
|
<button type="submit" class="btn" style="margin-top: 1rem;">Create</button>
|
||||||
</form>
|
</form>
|
||||||
|
</div>
|
||||||
|
|
||||||
{% else %}
|
{% else %}
|
||||||
<h1> {{ contract.name }}</h1>
|
<h1 class="section-title" style="text-align: left;">{{ contract.name }}</h1>
|
||||||
|
<div class="card" style="max-width: 600px;">
|
||||||
<form method="post" action='{% url "contract_detail" contract.slug %}'>
|
<form method="post" action='{% url "contract_detail" contract.slug %}'>
|
||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
{{ form }}
|
{{ form.as_p }}
|
||||||
<button type="" button" value="submit">Update</button>
|
<button type="submit" class="btn" style="margin-top: 1rem;">Update</button>
|
||||||
</form>
|
</form>
|
||||||
{% endif %}
|
</div>
|
||||||
|
|
||||||
|
<hr style="margin: 40px 0; border: 0; border-top: 1px solid rgba(255,255,255,0.1);">
|
||||||
|
|
||||||
|
<h2 class="section-title" style="text-align: left; font-size: 2rem;">Charge Numbers</h2>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
{% if is_new %}
|
|
||||||
{% else %}
|
|
||||||
<h2> Charge Numbers</h2>
|
|
||||||
{% if charge_numbes %}
|
{% if charge_numbes %}
|
||||||
<p> put charge number table here</p>
|
<div class="card" style="margin-bottom: 2rem;">
|
||||||
<p> Create a new charge number</p>
|
<p style="color: var(--text-muted);">put charge number table here</p>
|
||||||
{{ charge_number_form }}
|
</div>
|
||||||
{% else %}
|
|
||||||
<p> There are no charge numbers for this contract</p>
|
<div class="card" style="max-width: 600px;">
|
||||||
|
<h3 style="margin-bottom: 1rem;">Create a new charge number</h3>
|
||||||
<form method="post" action='{% url "new_charge_number" %}'>
|
<form method="post" action='{% url "new_charge_number" %}'>
|
||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
{{ charge_number_form }}
|
{{ charge_number_form.as_p }}
|
||||||
<button type="" button" value="submit">Update</button>
|
<button type="submit" class="btn" style="margin-top: 1rem;">Add Charge Number</button>
|
||||||
</form>
|
</form>
|
||||||
{% endif %}
|
</div>
|
||||||
|
{% else %}
|
||||||
|
<div class="card" style="max-width: 600px;">
|
||||||
|
<p style="color: var(--text-muted); margin-bottom: 1.5rem;">There are no charge numbers for this contract.
|
||||||
|
</p>
|
||||||
|
<form method="post" action='{% url "new_charge_number" %}'>
|
||||||
|
{% csrf_token %}
|
||||||
|
{{ charge_number_form.as_p }}
|
||||||
|
<button type="submit" class="btn" style="margin-top: 1rem;">Add Charge Number</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
|
{% endif %}
|
||||||
</body>
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
||||||
@@ -1,21 +1,33 @@
|
|||||||
<!-- a lot of stuff-->
|
{% extends "base.html" %}
|
||||||
|
|
||||||
<!doctype html>
|
|
||||||
{% load static %}
|
{% load static %}
|
||||||
<html lang="en"">
|
|
||||||
<head>
|
|
||||||
<title>Profile</title>
|
|
||||||
<link href=" {% static 'financial/css/material-dashboard.css' %}" rel="stylesheet">
|
|
||||||
<link rel="icon" type="image/xicon" href="{% static 'public/img/favicon.jpg' %}">
|
|
||||||
|
|
||||||
<body class="g-sidenav-show bg-gray-200">
|
{% block title %}Contracts - AI ML Operations{% endblock %}
|
||||||
|
|
||||||
<main class="main-content position-relative max-height-vh-100 h-100 border-radius-lg">
|
{% block content %}
|
||||||
|
<div class="section">
|
||||||
|
<div class="container">
|
||||||
|
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 2rem;">
|
||||||
|
<h1 class="section-title" style="margin-bottom: 0;">Contracts</h1>
|
||||||
|
<a href="{% url 'financial_index' %}" class="btn" style="padding: 0.5rem 1.5rem; font-size: 0.9rem;">Back to
|
||||||
|
Dashboard</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{% if chart_data_json %}
|
||||||
|
<h2 class="section-title" style="font-size: 2rem; margin-bottom: 2rem;">Burn Rate Charts</h2>
|
||||||
|
<div style="display: flex; flex-wrap: wrap; gap: 20px; margin-bottom: 3rem;">
|
||||||
|
{% for c in contracts %}
|
||||||
|
<div
|
||||||
|
style="flex: 1; min-width: 300px; max-width: 400px; background: var(--surface-color); border: 1px solid rgba(255,255,255,0.1); padding: 15px; border-radius: 8px;">
|
||||||
|
<canvas id="chart_{{ forloop.counter }}"></canvas>
|
||||||
|
</div>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
<h1>Contracts</h1>
|
<div class="table-responsive">
|
||||||
{% if contracts %}
|
{% if contracts %}
|
||||||
<table>
|
<table class="table" style="margin-bottom: 2rem;">
|
||||||
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th>Name</th>
|
<th>Name</th>
|
||||||
<th>Identifier</th>
|
<th>Identifier</th>
|
||||||
@@ -23,28 +35,117 @@
|
|||||||
<th>Start Date</th>
|
<th>Start Date</th>
|
||||||
<th>End Date</th>
|
<th>End Date</th>
|
||||||
<th>Proposed Amount</th>
|
<th>Proposed Amount</th>
|
||||||
<th>Baseline Amount</th>
|
<th>Budget Hours</th>
|
||||||
<th>Funded Amount</th>
|
<th>Hours Charged</th>
|
||||||
|
<th>Money Spent</th>
|
||||||
|
<th>Money Remaining</th>
|
||||||
|
<th>Projected End Date</th>
|
||||||
</tr>
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
{% for contract in contracts %}
|
{% for contract in contracts %}
|
||||||
<tr>
|
<tr>
|
||||||
<td> <a href="{% url 'contract_detail' contract.slug %}">{{ contract.name }}</a></td>
|
<td><a href="{% url 'contract_detail' contract.slug %}">{{ contract.name }}</a></td>
|
||||||
<td> {{ contract.slug }}</td>
|
<td>{{ contract.slug }}</td>
|
||||||
<td> {{ contract.contract_type }}</td>
|
<td>{{ contract.contract_type }}</td>
|
||||||
<td> {{ contract.baseline_start }}</td>
|
<td>{{ contract.baseline_start }}</td>
|
||||||
<td> {{ contract.baseline_end }}</td>
|
<td>{{ contract.baseline_end }}</td>
|
||||||
<td> {{ contract.proposed_amount }}</td>
|
<td>{{ contract.proposed_amount }}</td>
|
||||||
<td> {{ contract.baseline_amount }}</td>
|
<td>{{ contract.budget_hours }}</td>
|
||||||
<td> {{ contract.funded_amount }}</td>
|
<td>{{ contract.total_hours_spent }}</td>
|
||||||
|
<td>${{ contract.total_money_spent|floatformat:2 }}</td>
|
||||||
|
<td>${{ contract.remaining_money|floatformat:2 }}</td>
|
||||||
|
<td>{{ contract.projected_end_date }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
<hr>
|
|
||||||
<p> Create <a href="{% url 'new_contract' %}">new</a> contract.</p>
|
|
||||||
{% else %}
|
|
||||||
<p> There are no contracts. <a href="{% url 'new_contract' %}">Please make one</a></p>
|
|
||||||
|
|
||||||
|
<p>Create a <a href="{% url 'new_contract' %}" class="text-cyber-cyan"
|
||||||
|
style="text-decoration: underline;">new contract</a>.</p>
|
||||||
|
{% else %}
|
||||||
|
<p style="color: var(--text-muted);">There are no contracts. <a href="{% url 'new_contract' %}"
|
||||||
|
class="text-cyber-cyan" style="text-decoration: underline;">Please make one</a>.</p>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</body>
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
|
||||||
|
<script src="https://cdn.jsdelivr.net/npm/moment@2.29.4/moment.min.js"></script>
|
||||||
|
<script src="https://cdn.jsdelivr.net/npm/chartjs-adapter-moment@1.0.1/dist/chartjs-adapter-moment.min.js"></script>
|
||||||
|
<script>
|
||||||
|
document.addEventListener("DOMContentLoaded", function () {
|
||||||
|
const chartDataRaw = '{{ chart_data_json|escapejs }}';
|
||||||
|
if (chartDataRaw) {
|
||||||
|
Chart.defaults.color = '#e0e0e0';
|
||||||
|
Chart.defaults.borderColor = 'rgba(255, 255, 255, 0.1)';
|
||||||
|
|
||||||
|
const chartData = JSON.parse(chartDataRaw);
|
||||||
|
chartData.forEach((data, index) => {
|
||||||
|
const canvas = document.getElementById('chart_' + (index + 1));
|
||||||
|
if (!canvas) return;
|
||||||
|
const ctx = canvas.getContext('2d');
|
||||||
|
new Chart(ctx, {
|
||||||
|
type: 'line',
|
||||||
|
data: {
|
||||||
|
datasets: [
|
||||||
|
{
|
||||||
|
label: 'Historical Burn',
|
||||||
|
data: [
|
||||||
|
{ x: data.start_date, y: data.budget },
|
||||||
|
{ x: data.today, y: data.remaining }
|
||||||
|
],
|
||||||
|
borderColor: '#00f3ff', /* Cyber cyan */
|
||||||
|
backgroundColor: '#00f3ff',
|
||||||
|
fill: false,
|
||||||
|
tension: 0.1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Projected Burn',
|
||||||
|
data: [
|
||||||
|
{ x: data.today, y: data.remaining },
|
||||||
|
{ x: data.projected_end, y: 0 }
|
||||||
|
],
|
||||||
|
borderColor: '#bc13fe', /* Neon purple */
|
||||||
|
borderDash: [5, 5],
|
||||||
|
backgroundColor: '#bc13fe',
|
||||||
|
fill: false,
|
||||||
|
tension: 0.1
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
options: {
|
||||||
|
responsive: true,
|
||||||
|
plugins: {
|
||||||
|
title: {
|
||||||
|
display: true,
|
||||||
|
text: data.name + ' Burn Rate ($)',
|
||||||
|
color: '#ffffff'
|
||||||
|
},
|
||||||
|
legend: {
|
||||||
|
labels: { color: '#e0e0e0' }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
scales: {
|
||||||
|
x: {
|
||||||
|
type: 'time',
|
||||||
|
time: { unit: 'month' },
|
||||||
|
title: { display: true, text: 'Date', color: '#a0a0a0' },
|
||||||
|
grid: { color: 'rgba(255, 255, 255, 0.05)' },
|
||||||
|
ticks: { color: '#a0a0a0' }
|
||||||
|
},
|
||||||
|
y: {
|
||||||
|
beginAtZero: true,
|
||||||
|
title: { display: true, text: 'Remaining Budget ($)', color: '#a0a0a0' },
|
||||||
|
grid: { color: 'rgba(255, 255, 255, 0.05)' },
|
||||||
|
ticks: { color: '#a0a0a0' }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
{% endblock %}
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
{% extends "base.html" %}
|
||||||
|
{% load static %}
|
||||||
|
|
||||||
|
{% block title %}Edit Time Log - AI ML Operations{% endblock %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<div class="section">
|
||||||
|
<div class="container">
|
||||||
|
<h1 class="section-title" style="text-align: left;">Edit Time Log</h1>
|
||||||
|
|
||||||
|
<div class="card" style="max-width: 600px;">
|
||||||
|
<form method="POST">
|
||||||
|
{% csrf_token %}
|
||||||
|
{{ form.as_p }}
|
||||||
|
<div style="margin-top: 1.5rem; display: flex; gap: 1rem; align-items: center;">
|
||||||
|
<button type="submit" class="btn">Update Record</button>
|
||||||
|
<a href="{% url 'time_logs' %}" class="btn"
|
||||||
|
style="background: var(--surface-color); color: var(--text-color); border: 1px solid rgba(255,255,255,0.1);">Cancel</a>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
||||||
@@ -1,118 +1,95 @@
|
|||||||
<!doctype html>
|
{% extends "base.html" %}
|
||||||
{% load static %}
|
{% load static %}
|
||||||
<html lang="en"">
|
|
||||||
<head>
|
|
||||||
<title>Accounting</title>
|
|
||||||
<link href=" {% static 'financial/css/material-dashboard.css' %}" rel="stylesheet">
|
|
||||||
<link rel="icon" type="image/xicon" href="{% static 'public/img/favicon.jpg' %}">
|
|
||||||
|
|
||||||
<body class="g-sidenav-show bg-gray-200">
|
{% block title %}Accounting Dashboard{% endblock %}
|
||||||
<div class="sidenav-header">
|
|
||||||
<ul class="navbar-item">
|
|
||||||
<li class="nav-item">
|
|
||||||
Dashboard
|
|
||||||
</li>
|
|
||||||
<li class="nav-item">
|
|
||||||
Settings
|
|
||||||
</li>
|
|
||||||
|
|
||||||
</ul>
|
{% block content %}
|
||||||
</div>
|
<div class="section">
|
||||||
<main class="main-content position-relative max-height-vh-100 h-100 border-radius-lg">
|
<div class="container">
|
||||||
<nav class="navbar navbar-main navbar-expand-lg px-0 mx-4 shadow-none border-radius-xl" id="navbarBlur"
|
<h1 class="section-title">Dashboard</h1>
|
||||||
data-scroll="true">
|
|
||||||
<div class="container-fluid py-1 px-3">
|
<div class="card-grid" style="margin-bottom: 3rem;">
|
||||||
<nav aria-label="breadcrumb">
|
<a href="{% url 'contracts' %}" class="card">
|
||||||
<h6>Dashboard</h6>
|
<span class="card-title">View Contracts</span>
|
||||||
</nav>
|
<p class="card-text">Overview of all active and past contracts and their scopes.</p>
|
||||||
</div>
|
</a>
|
||||||
</nav>
|
<a href="{% url 'new_contract' %}" class="card">
|
||||||
<div class="container-fluid py-4">
|
<span class="card-title">New Contract</span>
|
||||||
<div class="row">
|
<p class="card-text">Establish a new project contract to track budgets.</p>
|
||||||
<div class="col-xl-3 col-sm-4 mb-xl-o mb-4">
|
</a>
|
||||||
<div class="card">
|
<a href="{% url 'new_employee' %}" class="card">
|
||||||
<div class="card-header p-3 pt-2">
|
<span class="card-title">New Employee</span>
|
||||||
<a href="{% url 'contracts' %}">
|
<p class="card-text">Add a new personnel member to your organization.</p>
|
||||||
Contracts
|
</a>
|
||||||
|
<a href="{% url 'Timekeeping' %}" class="card">
|
||||||
|
<span class="card-title">Log Time</span>
|
||||||
|
<p class="card-text">Record work hours against specific contracts.</p>
|
||||||
|
</a>
|
||||||
|
<a href="{% url 'time_logs' %}" class="card">
|
||||||
|
<span class="card-title">Manage Time Logs</span>
|
||||||
|
<p class="card-text">Review and edit submitted time entries.</p>
|
||||||
|
</a>
|
||||||
|
<a href="{% url 'client_reports' %}" class="card">
|
||||||
|
<span class="card-title">Client Reports</span>
|
||||||
|
<p class="card-text">Generate comprehensive reports for billing.</p>
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div class="card-body">
|
<h2 class="section-title" style="font-size: 2rem; margin-bottom: 2rem;">Contracts Overview</h2>
|
||||||
<p> put picture here</p>
|
<div class="table-responsive">
|
||||||
</div>
|
{% if contracts %}
|
||||||
</div>
|
<table class="table">
|
||||||
</div>
|
<thead>
|
||||||
{% if is_worker %}
|
<tr>
|
||||||
<div class="col-xl-3 col-sm-4 mb-xl-o mb-4">
|
<th>Contract Name</th>
|
||||||
<div class="card">
|
<th>Budget Hours</th>
|
||||||
<div class="card-header p-3 pt-2">
|
<th>Logged Hours</th>
|
||||||
<a href="{% url 'profile' %}">
|
</tr>
|
||||||
Profile
|
</thead>
|
||||||
</a>
|
<tbody>
|
||||||
|
{% for c in contracts %}
|
||||||
|
<tr>
|
||||||
|
<td><a href="{% url 'contract_detail' c.slug %}">{{ c.name }}</a></td>
|
||||||
|
<td>{{ c.budget_hours }}</td>
|
||||||
|
<td>{{ c.total_logged }}</td>
|
||||||
|
</tr>
|
||||||
|
{% endfor %}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
{% else %}
|
||||||
|
<p style="color: var(--text-muted);">No contracts available.</p>
|
||||||
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div class="card-body">
|
<hr style="margin: 40px 0; border: 0; border-top: 1px solid rgba(255,255,255,0.1);">
|
||||||
<p> put picture here</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{% endif %}
|
|
||||||
{% if is_worker %}
|
|
||||||
<div class="col-xl-3 col-sm-4 mb-xl-o mb-4">
|
|
||||||
<div class="card">
|
|
||||||
<div class="card-header p-3 pt-2">
|
|
||||||
<a href="{% url 'Timekeeping' %}">
|
|
||||||
Timekeeping
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</div class="card-body">
|
<h2 class="section-title" style="font-size: 2rem; margin-bottom: 2rem;">Employee Hours per Contract</h2>
|
||||||
<p> put picture here</p>
|
<div class="table-responsive">
|
||||||
</div>
|
{% if employee_data %}
|
||||||
</div>
|
<table class="table">
|
||||||
</div>
|
<thead>
|
||||||
{% endif %}
|
<tr>
|
||||||
{% if is_manager %}
|
<th>Employee Name</th>
|
||||||
<div class="col-xl-3 col-sm-4 mb-xl-o mb-4">
|
{% for c in contracts %}
|
||||||
<div class="card">
|
<th>{{ c.name }}</th>
|
||||||
<div class="card-header p-3 pt-2">
|
{% endfor %}
|
||||||
<a href="{% url 'Timeapproval' %}">
|
</tr>
|
||||||
Time Approval
|
</thead>
|
||||||
</a>
|
<tbody>
|
||||||
</div>
|
{% for row in employee_data %}
|
||||||
<div class="card-body">
|
<tr>
|
||||||
<p> put picture here</p>
|
<td>{{ row.employee }}</td>
|
||||||
</div>
|
{% for ch in row.contract_hours %}
|
||||||
</div>
|
<td>{{ ch.hours }}</td>
|
||||||
</div>
|
{% endfor %}
|
||||||
{% endif %}
|
</tr>
|
||||||
{% if is_procurment_officer %}
|
{% endfor %}
|
||||||
<div class="col-xl-3 col-sm-4 mb-xl-o mb-4">
|
</tbody>
|
||||||
<div class="card">
|
</table>
|
||||||
<div class="card-header p-3 pt-2">
|
{% else %}
|
||||||
</div>
|
<p style="color: var(--text-muted);">No employees available.</p>
|
||||||
<div class="card-body">
|
|
||||||
<p> put picture here</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{% endif %}
|
|
||||||
{% if is_finance %}
|
|
||||||
<div class="col-xl-3 col-sm-4 mb-xl-o mb-4">
|
|
||||||
<div class="card">
|
|
||||||
<div class="card-header p-3 pt-2">
|
|
||||||
<a href="{% url 'contracts' %}">
|
|
||||||
Contracts
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
<div class="card-body">
|
|
||||||
<p> put picture here</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</main>
|
</div>
|
||||||
<h1>Accouting</h1>
|
{% endblock %}
|
||||||
</body>
|
|
||||||
25
company_site/financial/templates/financial/new_employee.html
Normal file
25
company_site/financial/templates/financial/new_employee.html
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
{% extends "base.html" %}
|
||||||
|
{% load static %}
|
||||||
|
|
||||||
|
{% block title %}New Employee - AI ML Operations{% endblock %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<div class="section">
|
||||||
|
<div class="container">
|
||||||
|
<div style="margin-bottom: 2rem;">
|
||||||
|
<a href="{% url 'financial_index' %}" class="btn" style="padding: 0.5rem 1.5rem; font-size: 0.9rem;">Back to
|
||||||
|
Dashboard</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h1 class="section-title" style="text-align: left;">New Employee</h1>
|
||||||
|
|
||||||
|
<div class="card" style="max-width: 600px;">
|
||||||
|
<form method="post" action="{% url 'new_employee' %}">
|
||||||
|
{% csrf_token %}
|
||||||
|
{{ form.as_p }}
|
||||||
|
<button type="submit" class="btn" style="margin-top: 1rem;">Create Employee</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
||||||
@@ -1 +1,13 @@
|
|||||||
<p> this page has not been created yet</p>
|
{% extends "base.html" %}
|
||||||
|
|
||||||
|
{% block title %}Page Not Found{% endblock %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<div class="section">
|
||||||
|
<div class="container" style="text-align: center; padding: 4rem 0;">
|
||||||
|
<h1 class="section-title" style="margin-bottom: 1.5rem;">Coming Soon</h1>
|
||||||
|
<p style="color: var(--text-muted); font-size: 1.2rem;">This page has not been created yet.</p>
|
||||||
|
<a href="{% url 'financial_index' %}" class="btn" style="margin-top: 2rem;">Return to Dashboard</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
||||||
@@ -1,18 +1,19 @@
|
|||||||
<!-- a lot of stuff-->
|
{% extends "base.html" %}
|
||||||
|
|
||||||
<!doctype html>
|
|
||||||
{% load static %}
|
{% load static %}
|
||||||
<html lang="en"">
|
|
||||||
<head>
|
|
||||||
<title>Profile</title>
|
|
||||||
<link href=" {% static 'financial/css/material-dashboard.css' %}" rel="stylesheet">
|
|
||||||
<link rel="icon" type="image/xicon" href="{% static 'public/img/favicon.jpg' %}">
|
|
||||||
|
|
||||||
<body class="g-sidenav-show bg-gray-200">
|
{% block title %}Profile - AI ML Operations{% endblock %}
|
||||||
|
|
||||||
<main class="main-content position-relative max-height-vh-100 h-100 border-radius-lg">
|
{% block content %}
|
||||||
|
<div class="section">
|
||||||
|
<div class="container">
|
||||||
<h1>Profile</h1>
|
<h1 class="section-title" style="text-align: left;">Profile</h1>
|
||||||
{{ form }}
|
<div class="card" style="max-width: 600px;">
|
||||||
</body>
|
<form method="post">
|
||||||
|
{% csrf_token %}
|
||||||
|
{{ form.as_p }}
|
||||||
|
<button type="submit" class="btn" style="margin-top: 1rem;">Save Profile</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
||||||
44
company_site/financial/templates/financial/reports.html
Normal file
44
company_site/financial/templates/financial/reports.html
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
{% extends "base.html" %}
|
||||||
|
{% load static %}
|
||||||
|
|
||||||
|
{% block title %}Client Reports - AI ML Operations{% endblock %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<div class="section">
|
||||||
|
<div class="container">
|
||||||
|
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 2rem;">
|
||||||
|
<h1 class="section-title" style="margin-bottom: 0;">Client Reports</h1>
|
||||||
|
<a href="{% url 'financial_index' %}" class="btn" style="padding: 0.5rem 1.5rem; font-size: 0.9rem;">Back to
|
||||||
|
Dashboard</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="table-responsive">
|
||||||
|
{% if contracts %}
|
||||||
|
<table class="table">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Contract Name</th>
|
||||||
|
<th>Total Budget (Hours)</th>
|
||||||
|
<th>Total Logged (Hours)</th>
|
||||||
|
<th>Remaining Budget (Hours)</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{% for c in contracts %}
|
||||||
|
<tr>
|
||||||
|
<td>{{ c.name }}</td>
|
||||||
|
<td>{{ c.budget_hours }}</td>
|
||||||
|
<td>{{ c.total_logged }}</td>
|
||||||
|
<td style="{% if c.remaining_budget < 0 %}color: #ff4444; font-weight: bold;{% endif %}">{{
|
||||||
|
c.remaining_budget }}</td>
|
||||||
|
</tr>
|
||||||
|
{% endfor %}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
{% else %}
|
||||||
|
<p style="color: var(--text-muted); text-align: center; padding: 2rem;">No data available to report.</p>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
||||||
64
company_site/financial/templates/financial/time_logs.html
Normal file
64
company_site/financial/templates/financial/time_logs.html
Normal file
@@ -0,0 +1,64 @@
|
|||||||
|
{% extends "base.html" %}
|
||||||
|
{% load static %}
|
||||||
|
|
||||||
|
{% block title %}Time Logs - AI ML Operations{% endblock %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<div class="section">
|
||||||
|
<div class="container">
|
||||||
|
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 2rem;">
|
||||||
|
<h1 class="section-title" style="margin-bottom: 0;">All Time Logs</h1>
|
||||||
|
<div>
|
||||||
|
<a href="{% url 'financial_index' %}" class="btn"
|
||||||
|
style="padding: 0.5rem 1.5rem; font-size: 0.9rem; margin-right: 1rem; background: var(--surface-color); color: var(--text-color); border: 1px solid rgba(255,255,255,0.1);">Back
|
||||||
|
to Dashboard</a>
|
||||||
|
<a href="{% url 'Timekeeping' %}" class="btn" style="padding: 0.5rem 1.5rem; font-size: 0.9rem;">Log New
|
||||||
|
Time</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="table-responsive">
|
||||||
|
<table class="table">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Employee</th>
|
||||||
|
<th>Contract</th>
|
||||||
|
<th>Date</th>
|
||||||
|
<th>Start Time</th>
|
||||||
|
<th>End Time</th>
|
||||||
|
<th>Duration (hrs)</th>
|
||||||
|
<th>Actions</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{% for log in logs %}
|
||||||
|
<tr>
|
||||||
|
<td>{{ log.timeCard.employee }}</td>
|
||||||
|
<td>{{ log.contract }}</td>
|
||||||
|
<td>{{ log.date }}</td>
|
||||||
|
<td>{{ log.start_time|default_if_none:"" }}</td>
|
||||||
|
<td>{{ log.end_time|default_if_none:"" }}</td>
|
||||||
|
<td>{{ log.hour }}</td>
|
||||||
|
<td>
|
||||||
|
<a href="{% url 'edit_time_log' log.id %}" class="text-cyber-cyan"
|
||||||
|
style="margin-right: 10px;">Edit</a>
|
||||||
|
<form action="{% url 'delete_time_log' log.id %}" method="POST" style="display:inline;"
|
||||||
|
onsubmit="return confirm('Are you sure you want to delete this time log?');">
|
||||||
|
{% csrf_token %}
|
||||||
|
<button type="submit"
|
||||||
|
style="background:none; border:none; color: #ff4444; cursor:pointer; font-size: 0.95rem; font-family: var(--font-main);">Delete</button>
|
||||||
|
</form>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
{% empty %}
|
||||||
|
<tr>
|
||||||
|
<td colspan="7" style="color: var(--text-muted); text-align: center; padding: 2rem;">No time
|
||||||
|
logs found.</td>
|
||||||
|
</tr>
|
||||||
|
{% endfor %}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
||||||
31
company_site/financial/templates/financial/timekeeping.html
Normal file
31
company_site/financial/templates/financial/timekeeping.html
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
{% extends "base.html" %}
|
||||||
|
{% load static %}
|
||||||
|
|
||||||
|
{% block title %}Timekeeping - AI ML Operations{% endblock %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<div class="section">
|
||||||
|
<div class="container">
|
||||||
|
<div style="margin-bottom: 2rem;">
|
||||||
|
<a href="{% url 'financial_index' %}" class="btn" style="padding: 0.5rem 1.5rem; font-size: 0.9rem;">Back to
|
||||||
|
Dashboard</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h1 class="section-title" style="text-align: left;">Log Time</h1>
|
||||||
|
|
||||||
|
{% if error %}
|
||||||
|
<div class="alert alert-danger" style="max-width: 600px; margin-bottom: 1.5rem;">
|
||||||
|
{{ error }}
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
<div class="card" style="max-width: 600px;">
|
||||||
|
<form method="post" action="{% url 'Timekeeping' %}">
|
||||||
|
{% csrf_token %}
|
||||||
|
{{ form.as_p }}
|
||||||
|
<button type="submit" class="btn" style="margin-top: 1rem;">Log Time</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
||||||
@@ -5,13 +5,18 @@ from . import views
|
|||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
path("", views.index, name="financial_index"),
|
path("", views.index, name="financial_index"),
|
||||||
path("timekeeping", views.timekeeping, name="Timekeeping"),
|
path("timekeeping", views.timekeeping, name="Timekeeping"),
|
||||||
|
path("time_logs", views.time_logs, name="time_logs"),
|
||||||
|
path("time_logs/<int:log_id>/edit", views.edit_time_log, name="edit_time_log"),
|
||||||
|
path("time_logs/<int:log_id>/delete", views.delete_time_log, name="delete_time_log"),
|
||||||
path("timeapproval", views.timeapproval, name="Timeapproval"),
|
path("timeapproval", views.timeapproval, name="Timeapproval"),
|
||||||
path("contracts", views.contracts, name="contracts"),
|
path("contracts", views.contracts, name="contracts"),
|
||||||
path("<str:contract_slug>/contract_detail", views.contract_detail, name="contract_detail"),
|
path("<str:contract_slug>/contract_detail", views.contract_detail, name="contract_detail"),
|
||||||
path("new_contract", views.new_contract, name="new_contract"),
|
path("new_contract", views.new_contract, name="new_contract"),
|
||||||
|
path("new_employee", views.new_employee, name="new_employee"),
|
||||||
path("new_charge_number", views.new_charge_number, name="new_charge_number"),
|
path("new_charge_number", views.new_charge_number, name="new_charge_number"),
|
||||||
path("<str:charge_number_slug>/update_charge_number", views.update_charge_number, name="update_charge_number"),
|
path("<str:charge_number_slug>/update_charge_number", views.update_charge_number, name="update_charge_number"),
|
||||||
#path("contracts/<int:contract_id>/", views.contract_detail, name="contract"),
|
#path("contracts/<int:contract_id>/", views.contract_detail, name="contract"),
|
||||||
path("procurements", views.procurement, name="procurements"),
|
path("procurements", views.procurement, name="procurements"),
|
||||||
path("profile", views.profile, name="profile"),
|
path("profile", views.profile, name="profile"),
|
||||||
|
path("client_reports", views.client_reports, name="client_reports"),
|
||||||
]
|
]
|
||||||
@@ -1,48 +1,121 @@
|
|||||||
from django.shortcuts import render, redirect
|
from django.shortcuts import render, redirect
|
||||||
from .forms import EmployeeForm, ContractForm, ChargeNumberForm
|
from django.contrib.auth.decorators import user_passes_test
|
||||||
from .models import Contract
|
from .forms import EmployeeForm, ContractForm, ChargeNumberForm, TimeLogForm, NewEmployeeForm
|
||||||
# Create your views here.
|
from .models import Contract, TimeCard, TimeCardCell, Employee
|
||||||
|
from django.utils import timezone
|
||||||
|
from django.db.models import Sum
|
||||||
|
from datetime import timedelta
|
||||||
|
import json
|
||||||
|
|
||||||
# PAGES TO CREATE
|
def is_admin(user):
|
||||||
# dashboard
|
return user.is_active and user.is_superuser
|
||||||
# log in
|
|
||||||
# log out
|
|
||||||
# password reset
|
|
||||||
# employee timecard
|
|
||||||
# time card approval
|
|
||||||
# contract
|
|
||||||
# charge number
|
|
||||||
# user management (?)
|
|
||||||
|
|
||||||
|
@user_passes_test(is_admin)
|
||||||
def index(request):
|
def index(request):
|
||||||
permissions = []
|
|
||||||
context = {
|
|
||||||
'is_procurment_officer':True,
|
|
||||||
'is_worker':True,
|
|
||||||
'is_manager':True,
|
|
||||||
'is_finance': True
|
|
||||||
|
|
||||||
}
|
|
||||||
return render(request, "financial/index.html", context)
|
|
||||||
|
|
||||||
def contracts(request):
|
|
||||||
contracts = Contract.objects.all()
|
contracts = Contract.objects.all()
|
||||||
return render(request, 'financial/contracts.html', {'contracts':contracts})
|
for c in contracts:
|
||||||
|
total = TimeCardCell.objects.filter(contract=c).aggregate(Sum('hour'))['hour__sum']
|
||||||
|
c.total_logged = total if total else 0.0
|
||||||
|
|
||||||
|
employees = Employee.objects.all()
|
||||||
|
employee_data = []
|
||||||
|
for e in employees:
|
||||||
|
contract_hours = []
|
||||||
|
for c in contracts:
|
||||||
|
total_e_c = TimeCardCell.objects.filter(contract=c, timeCard__employee=e).aggregate(Sum('hour'))['hour__sum']
|
||||||
|
contract_hours.append({'contract': c, 'hours': total_e_c if total_e_c else 0.0})
|
||||||
|
employee_data.append({'employee': e, 'contract_hours': contract_hours})
|
||||||
|
|
||||||
|
return render(request, "financial/index.html", {
|
||||||
|
'contracts': contracts,
|
||||||
|
'employee_data': employee_data
|
||||||
|
})
|
||||||
|
|
||||||
|
@user_passes_test(is_admin)
|
||||||
|
def new_employee(request):
|
||||||
|
if request.method == "POST":
|
||||||
|
form = NewEmployeeForm(request.POST)
|
||||||
|
if form.is_valid():
|
||||||
|
form.save()
|
||||||
|
return redirect('financial_index')
|
||||||
|
else:
|
||||||
|
form = NewEmployeeForm()
|
||||||
|
return render(request, 'financial/new_employee.html', {"form": form})
|
||||||
|
|
||||||
|
@user_passes_test(is_admin)
|
||||||
|
def contracts(request):
|
||||||
|
contracts_list = Contract.objects.all()
|
||||||
|
today = timezone.now().date()
|
||||||
|
|
||||||
|
chart_data_list = []
|
||||||
|
|
||||||
|
for c in contracts_list:
|
||||||
|
cells = TimeCardCell.objects.filter(contract=c).select_related('timeCard__employee').order_by('date')
|
||||||
|
|
||||||
|
total_hours = sum((cell.hour or 0.0) for cell in cells)
|
||||||
|
total_money = sum((cell.hour or 0.0) * cell.timeCard.employee.hourly_salary for cell in cells)
|
||||||
|
|
||||||
|
c.total_hours_spent = total_hours
|
||||||
|
c.total_money_spent = total_money
|
||||||
|
c.remaining_hours = max(0, c.budget_hours - total_hours)
|
||||||
|
|
||||||
|
budget_amt = c.funded_amount if c.funded_amount > 0 else c.proposed_amount
|
||||||
|
c.remaining_money = max(0, budget_amt - total_money)
|
||||||
|
|
||||||
|
proj_end = None
|
||||||
|
|
||||||
|
if cells.exists():
|
||||||
|
first_date = cells.first().date or c.baseline_start or today
|
||||||
|
last_date = cells.last().date or today
|
||||||
|
days_elapsed = (last_date - first_date).days
|
||||||
|
if days_elapsed <= 0:
|
||||||
|
days_elapsed = 1
|
||||||
|
|
||||||
|
daily_hour_burn = total_hours / days_elapsed
|
||||||
|
daily_money_burn = total_money / days_elapsed
|
||||||
|
|
||||||
|
days_out_hours = (c.remaining_hours / daily_hour_burn) if daily_hour_burn > 0 else 9999
|
||||||
|
days_out_money = (c.remaining_money / daily_money_burn) if daily_money_burn > 0 else 9999
|
||||||
|
|
||||||
|
days_out = max(days_out_hours, days_out_money)
|
||||||
|
|
||||||
|
if days_out < 9999:
|
||||||
|
proj_end = last_date + timedelta(days=int(days_out))
|
||||||
|
|
||||||
|
c.projected_end_date = proj_end if proj_end else "N/A"
|
||||||
|
|
||||||
|
start_dt = str(c.baseline_start or today)
|
||||||
|
end_dt = str(proj_end or (today + timedelta(days=30)))
|
||||||
|
|
||||||
|
chart_data_list.append({
|
||||||
|
'name': c.name,
|
||||||
|
'start_date': start_dt,
|
||||||
|
'today': str(today),
|
||||||
|
'projected_end': end_dt,
|
||||||
|
'budget': float(budget_amt),
|
||||||
|
'spent': float(total_money),
|
||||||
|
'remaining': float(c.remaining_money)
|
||||||
|
})
|
||||||
|
|
||||||
|
return render(request, 'financial/contracts.html', {
|
||||||
|
'contracts': contracts_list,
|
||||||
|
'chart_data_json': json.dumps(chart_data_list)
|
||||||
|
})
|
||||||
|
|
||||||
|
@user_passes_test(is_admin)
|
||||||
def contract_detail(request, contract_slug):
|
def contract_detail(request, contract_slug):
|
||||||
contract = Contract.objects.filter(slug=contract_slug)
|
contract = Contract.objects.filter(slug=contract_slug).first()
|
||||||
if request.method == 'POST':
|
if request.method == 'POST':
|
||||||
form = ContractForm(request.POST, instance=contract[0])
|
form = ContractForm(request.POST, instance=contract)
|
||||||
if form.is_valid():
|
if form.is_valid():
|
||||||
form.save()
|
form.save()
|
||||||
return redirect('contracts')
|
return redirect('contracts')
|
||||||
else:
|
else:
|
||||||
form = ContractForm(instance = contract[0])
|
form = ContractForm(instance=contract)
|
||||||
charge_number_form = ChargeNumberForm()
|
charge_number_form = ChargeNumberForm()
|
||||||
# TODO: handle multiple better but we can assume there is only one
|
return render(request, 'financial/contract_detail.html', {'is_new': False, 'form': form, 'charge_number_form':charge_number_form, 'contract': contract})
|
||||||
|
|
||||||
return render(request, 'financial/contract_detail.html', {'is_new': False, 'form': form, 'charge_number_form':charge_number_form, 'contract': contract[0]})
|
|
||||||
|
|
||||||
|
@user_passes_test(is_admin)
|
||||||
def new_contract(request):
|
def new_contract(request):
|
||||||
if request.method == "POST":
|
if request.method == "POST":
|
||||||
form = ContractForm(request.POST)
|
form = ContractForm(request.POST)
|
||||||
@@ -50,30 +123,88 @@ def new_contract(request):
|
|||||||
form.save()
|
form.save()
|
||||||
return redirect('contracts')
|
return redirect('contracts')
|
||||||
else:
|
else:
|
||||||
return render(request, 'financial/contract_detail.html', {"form": ContractForm(), 'is_new': True})
|
form = ContractForm()
|
||||||
|
return render(request, 'financial/contract_detail.html', {"form": form, 'is_new': True})
|
||||||
|
|
||||||
|
@user_passes_test(is_admin)
|
||||||
|
def timekeeping(request):
|
||||||
|
if request.method == "POST":
|
||||||
|
form = TimeLogForm(request.POST)
|
||||||
|
if form.is_valid():
|
||||||
|
employee = Employee.objects.filter(user=request.user).first()
|
||||||
|
if not employee:
|
||||||
|
employee = Employee.objects.first()
|
||||||
|
if not employee:
|
||||||
|
return render(request, 'financial/timekeeping.html', {"form": form, "error": "No Employee exists. Cannot auto-create TimeCard. Please create an Employee first."})
|
||||||
|
|
||||||
|
time_card, _ = TimeCard.objects.get_or_create(employee=employee, startDate=timezone.now().date(), endDate=timezone.now().date())
|
||||||
|
cell = form.save(commit=False)
|
||||||
|
cell.timeCard = time_card
|
||||||
|
cell.save()
|
||||||
|
return redirect('financial_index')
|
||||||
else:
|
else:
|
||||||
return render(request, 'financial/contract_detail.html', {"form": ContractForm(), 'is_new': True})
|
form = TimeLogForm()
|
||||||
|
return render(request, 'financial/timekeeping.html', {'form': form})
|
||||||
|
|
||||||
|
@user_passes_test(is_admin)
|
||||||
|
def time_logs(request):
|
||||||
|
logs = TimeCardCell.objects.all().order_by('-date', '-created')
|
||||||
|
return render(request, 'financial/time_logs.html', {'logs': logs})
|
||||||
|
|
||||||
|
@user_passes_test(is_admin)
|
||||||
|
def edit_time_log(request, log_id):
|
||||||
|
log_entry = TimeCardCell.objects.filter(id=log_id).first()
|
||||||
|
if not log_entry:
|
||||||
|
return redirect('time_logs')
|
||||||
|
|
||||||
|
if request.method == "POST":
|
||||||
|
form = TimeLogForm(request.POST, instance=log_entry)
|
||||||
|
if form.is_valid():
|
||||||
|
form.save()
|
||||||
|
return redirect('time_logs')
|
||||||
|
else:
|
||||||
|
form = TimeLogForm(instance=log_entry)
|
||||||
|
|
||||||
|
return render(request, 'financial/edit_time_log.html', {'form': form, 'log': log_entry})
|
||||||
|
|
||||||
|
@user_passes_test(is_admin)
|
||||||
|
def delete_time_log(request, log_id):
|
||||||
|
if request.method == "POST":
|
||||||
|
log_entry = TimeCardCell.objects.filter(id=log_id).first()
|
||||||
|
if log_entry:
|
||||||
|
log_entry.delete()
|
||||||
|
return redirect('time_logs')
|
||||||
|
|
||||||
|
@user_passes_test(is_admin)
|
||||||
|
def client_reports(request):
|
||||||
|
contracts = Contract.objects.all()
|
||||||
|
for c in contracts:
|
||||||
|
total = TimeCardCell.objects.filter(contract=c).aggregate(Sum('hour'))['hour__sum']
|
||||||
|
c.total_logged = total if total else 0.0
|
||||||
|
c.remaining_budget = c.budget_hours - c.total_logged
|
||||||
|
return render(request, 'financial/reports.html', {'contracts': contracts})
|
||||||
|
|
||||||
|
@user_passes_test(is_admin)
|
||||||
def update_charge_number(request, charge_number_slug):
|
def update_charge_number(request, charge_number_slug):
|
||||||
return render(request, 'financial/not_created.html', {})
|
return render(request, 'financial/not_created.html', {})
|
||||||
|
|
||||||
|
@user_passes_test(is_admin)
|
||||||
def new_charge_number(request, charge_number_slug):
|
def new_charge_number(request, charge_number_slug):
|
||||||
return render(request, 'financial/not_created.html', {})
|
return render(request, 'financial/not_created.html', {})
|
||||||
|
|
||||||
def timekeeping(request):
|
@user_passes_test(is_admin)
|
||||||
return render(request, 'financial/not_created.html', {})
|
|
||||||
|
|
||||||
def timeapproval(request):
|
def timeapproval(request):
|
||||||
return render(request, 'financial/not_created.html', {})
|
return render(request, 'financial/not_created.html', {})
|
||||||
|
|
||||||
|
@user_passes_test(is_admin)
|
||||||
def chargenumber(request):
|
def chargenumber(request):
|
||||||
return render(request, 'financial/not_created.html', {})
|
return render(request, 'financial/not_created.html', {})
|
||||||
|
|
||||||
|
@user_passes_test(is_admin)
|
||||||
def procurement(request):
|
def procurement(request):
|
||||||
return render(request, 'financial/procurement.html', {})
|
return render(request, 'financial/procurement.html', {})
|
||||||
|
|
||||||
|
@user_passes_test(is_admin)
|
||||||
def profile(request):
|
def profile(request):
|
||||||
form = EmployeeForm()
|
form = EmployeeForm()
|
||||||
return render(request, 'financial/profile.html', {'form': form})
|
return render(request, 'financial/profile.html', {'form': form})
|
||||||
# def contract_detail(request, contract_id):
|
|
||||||
@@ -459,3 +459,68 @@ nav {
|
|||||||
transform: none;
|
transform: none;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Table Styles */
|
||||||
|
.table {
|
||||||
|
width: 100%;
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
color: var(--text-color);
|
||||||
|
border-collapse: collapse;
|
||||||
|
}
|
||||||
|
|
||||||
|
.table th,
|
||||||
|
.table td {
|
||||||
|
padding: 1rem;
|
||||||
|
vertical-align: top;
|
||||||
|
border-top: 1px solid rgba(255, 255, 255, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.table thead th {
|
||||||
|
vertical-align: bottom;
|
||||||
|
border-bottom: 2px solid rgba(255, 255, 255, 0.1);
|
||||||
|
color: var(--primary-color);
|
||||||
|
text-align: left;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
.table-responsive {
|
||||||
|
display: block;
|
||||||
|
width: 100%;
|
||||||
|
overflow-x: auto;
|
||||||
|
-webkit-overflow-scrolling: touch;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Django Form Styling defaults */
|
||||||
|
input[type="text"],
|
||||||
|
input[type="number"],
|
||||||
|
input[type="email"],
|
||||||
|
input[type="password"],
|
||||||
|
input[type="date"],
|
||||||
|
input[type="time"],
|
||||||
|
input[type="datetime-local"],
|
||||||
|
select,
|
||||||
|
textarea {
|
||||||
|
width: 100%;
|
||||||
|
padding: 0.75rem 1rem;
|
||||||
|
background: var(--bg-color);
|
||||||
|
border: 1px solid rgba(255, 255, 255, 0.1);
|
||||||
|
border-radius: 8px;
|
||||||
|
color: white;
|
||||||
|
font-family: var(--font-main);
|
||||||
|
font-size: 1rem;
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
transition: border-color var(--transition-speed);
|
||||||
|
}
|
||||||
|
|
||||||
|
input:focus, select:focus, textarea:focus {
|
||||||
|
outline: none;
|
||||||
|
border-color: var(--primary-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.helptext {
|
||||||
|
color: var(--text-muted);
|
||||||
|
font-size: 0.85rem;
|
||||||
|
display: block;
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
margin-top: -0.5rem;
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user