Implemented financial tracking

This commit is contained in:
2026-03-20 13:07:28 -05:00
parent 6fa4fc1f7c
commit c88e344198
22 changed files with 1058 additions and 270 deletions

4
.antigravityrules Normal file
View 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
View 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.

View File

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

View File

@@ -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),
),
]

View File

@@ -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'),
),
]

View File

@@ -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),
),
]

View File

@@ -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),
),
]

View File

@@ -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),
),
]

View File

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

View File

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

View File

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

View File

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

View File

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

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

View File

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

View File

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

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

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

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

View File

@@ -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"),
] ]

View File

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

View File

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