Add production data import from V2 CSV backup

Imports 57 rows of real data: 14 workers, 2 projects, 2 supervisors,
38 work logs (Jan 23 - Feb 21), 19 adjustments (deductions, bonuses,
overtime, loan repayments, advance payments). Includes PayrollRecords
for paid entries. Visit /import-data/ to trigger from browser.

Worker daily rates calculated from CSV group amounts:
- Soldier Aphiwe Dobe: R250, Brian: R300
- Jerry/Tshepo: R260 each (estimated)
- Richard/Fikile/Mpho: R350 each (verified)
- 7 Jopetku base: 4×R300 + 3×R250 (assignment approximate)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Konrad du Plessis 2026-02-22 19:47:47 +02:00
parent 9bee52dd03
commit aaf86c2513
3 changed files with 438 additions and 0 deletions

View File

@ -0,0 +1,397 @@
# === IMPORT PRODUCTION DATA ===
# Imports the real work logs, adjustments, workers, projects, and supervisors
# from the V2 Flatlogic backup CSV into the V5 database.
#
# Run: python manage.py import_production_data
#
# This command is safe to re-run — it skips data that already exists.
import datetime
from decimal import Decimal
from django.core.management.base import BaseCommand
from django.contrib.auth.models import User
from django.utils import timezone
from core.models import Project, Worker, Team, WorkLog, PayrollRecord, PayrollAdjustment, Loan
class Command(BaseCommand):
help = 'Imports production work logs and adjustments from V2 backup'
def handle(self, *args, **options):
self.stdout.write(self.style.NOTICE('Starting production data import...'))
self.stdout.write('')
# =============================================
# 1. CREATE USERS (admin + supervisors)
# =============================================
admin_user = self._create_user('admin', 'admin123', is_staff=True, is_superuser=True, first_name='Admin')
christiaan = self._create_user('Christiaan', 'super123', first_name='Christiaan')
fitz = self._create_user('Fitz', 'super123', first_name='Fitz')
supervisor_map = {
'Christiaan': christiaan,
'Fitz': fitz,
'admin': admin_user,
}
# =============================================
# 2. CREATE PROJECTS
# =============================================
plot, _ = Project.objects.get_or_create(name='Plot', defaults={'active': True})
jopetku, _ = Project.objects.get_or_create(name='Jopetku', defaults={'active': True})
# Assign supervisors to projects
plot.supervisors.add(christiaan)
jopetku.supervisors.add(christiaan, fitz)
project_map = {
'Plot': plot,
'Jopetku': jopetku,
}
self.stdout.write(self.style.SUCCESS(' Projects: Plot, Jopetku'))
# =============================================
# 3. CREATE WORKERS
# =============================================
# Daily rates calculated from CSV group amounts:
# - Soldier Aphiwe Dobe: R250/day (verified: 770 - 520 = 250)
# - Brian: R300/day (verified: 550 - 250 = 300)
# - Jerry: R260/day (assumed: 520/2 = 260)
# - Tshepo Isrom Moganedi: R260/day (assumed: 520/2 = 260)
# - Richard Moleko, Fikile Oupa Masimula, Mpho Gift Nkoana: R350/day
# (verified: 3000 - 1950 = 1050, 1050/3 = 350)
# - 7 Jopetku base workers: 4 at R300/day + 3 at R250/day
# (verified: 4×300 + 3×250 = 1950)
# Note: assignment of which 4 vs 3 is approximate — adjust in admin
# if needed.
worker_data = [
# (name, monthly_salary, id_placeholder)
('Soldier Aphiwe Dobe', Decimal('5000.00'), '0000000000001'),
('Brian', Decimal('6000.00'), '0000000000002'),
('Jerry', Decimal('5200.00'), '0000000000003'),
('Tshepo Isrom Moganedi', Decimal('5200.00'), '0000000000004'),
('Richard Moleko', Decimal('7000.00'), '0000000000005'),
('Fikile Oupa Masimula', Decimal('7000.00'), '0000000000006'),
('Mpho Gift Nkoana', Decimal('7000.00'), '0000000000007'),
# 4 at R300/day = R6,000/month
('Clifford Jan Bobby Selemela', Decimal('6000.00'), '0000000000008'),
('Goitsimang Rasta Moleko', Decimal('6000.00'), '0000000000009'),
('Jimmy Moleko', Decimal('6000.00'), '0000000000010'),
('Johannes Laka', Decimal('6000.00'), '0000000000011'),
# 3 at R250/day = R5,000/month
('Shane Malobela', Decimal('5000.00'), '0000000000012'),
('Sello Lloyed Matloa', Decimal('5000.00'), '0000000000013'),
('Tumelo Faith Sinugo', Decimal('5000.00'), '0000000000014'),
]
worker_map = {}
for name, salary, id_num in worker_data:
worker, created = Worker.objects.get_or_create(
name=name,
defaults={
'monthly_salary': salary,
'id_number': id_num,
'active': True,
'employment_date': datetime.date(2024, 1, 15),
}
)
worker_map[name] = worker
if created:
self.stdout.write(f' Created worker: {name} (R{salary}/month, R{worker.daily_rate}/day)')
self.stdout.write(self.style.SUCCESS(f' Workers: {len(worker_map)} total'))
# =============================================
# 4. CREATE TEAMS
# =============================================
plot_team, created = Team.objects.get_or_create(
name='Plot Team',
defaults={'supervisor': christiaan, 'active': True}
)
if created:
plot_workers = [worker_map[n] for n in ['Soldier Aphiwe Dobe', 'Brian', 'Jerry', 'Tshepo Isrom Moganedi']]
plot_team.workers.set(plot_workers)
self.stdout.write(self.style.SUCCESS(f' Created Plot Team ({len(plot_workers)} workers)'))
jopetku_team, created = Team.objects.get_or_create(
name='Jopetku Team',
defaults={'supervisor': fitz, 'active': True}
)
if created:
jopetku_workers = [worker_map[n] for n in [
'Richard Moleko', 'Fikile Oupa Masimula', 'Mpho Gift Nkoana',
'Clifford Jan Bobby Selemela', 'Goitsimang Rasta Moleko',
'Johannes Laka', 'Jimmy Moleko', 'Shane Malobela',
'Sello Lloyed Matloa', 'Tumelo Faith Sinugo',
]]
jopetku_team.workers.set(jopetku_workers)
self.stdout.write(self.style.SUCCESS(f' Created Jopetku Team ({len(jopetku_workers)} workers)'))
team_map = {
'Plot': plot_team,
'Jopetku': jopetku_team,
}
# =============================================
# 5. EMBEDDED CSV DATA
# =============================================
# Format: (date, description, workers_str, amount, status, supervisor)
# From: work_logs_and_adjustments.csv (V2 Flatlogic backup)
csv_rows = [
('2026-02-21', 'Plot', 'Soldier Aphiwe Dobe, Brian', '550.00', 'Pending', 'Christiaan'),
('2026-02-21', 'Advance Payment - Advance', 'Brian', '-300.00', 'Pending', 'System'),
('2026-02-20', 'Overtime - Overtime Hour buyback', 'Fikile Oupa Masimula', '1750.00', 'Paid', 'System'),
('2026-02-20', 'Overtime - Overtime Hour buyback', 'Mpho Gift Nkoana', '1750.00', 'Paid', 'System'),
('2026-02-20', 'Overtime - Overtime Hour buyback', 'Richard Moleko', '1750.00', 'Paid', 'System'),
('2026-02-19', 'Jopetku', 'Clifford Jan Bobby Selemela, Goitsimang Rasta Moleko, Johannes Laka, Jimmy Moleko, Shane Malobela, Sello Lloyed Matloa, Tumelo Faith Sinugo', '1950.00', 'Paid', 'Fitz'),
('2026-02-18', 'Jopetku', 'Clifford Jan Bobby Selemela, Goitsimang Rasta Moleko, Johannes Laka, Jimmy Moleko, Shane Malobela, Sello Lloyed Matloa, Tumelo Faith Sinugo', '1950.00', 'Paid', 'Fitz'),
('2026-02-17', 'Jopetku', 'Clifford Jan Bobby Selemela, Goitsimang Rasta Moleko, Johannes Laka, Jimmy Moleko, Shane Malobela, Sello Lloyed Matloa, Tumelo Faith Sinugo', '1950.00', 'Paid', 'Fitz'),
('2026-02-16', 'Jopetku', 'Clifford Jan Bobby Selemela, Goitsimang Rasta Moleko, Johannes Laka, Jimmy Moleko, Shane Malobela, Sello Lloyed Matloa, Tumelo Faith Sinugo', '1950.00', 'Paid', 'Fitz'),
('2026-02-14', 'Jopetku', 'Clifford Jan Bobby Selemela, Goitsimang Rasta Moleko, Johannes Laka, Jimmy Moleko, Shane Malobela, Sello Lloyed Matloa, Tumelo Faith Sinugo', '1950.00', 'Paid', 'Fitz'),
('2026-02-13', 'Plot', 'Soldier Aphiwe Dobe, Brian, Jerry, Tshepo Isrom Moganedi', '1070.00', 'Paid', 'Christiaan'),
('2026-02-13', 'Jopetku', 'Richard Moleko, Fikile Oupa Masimula, Mpho Gift Nkoana, Clifford Jan Bobby Selemela, Goitsimang Rasta Moleko, Johannes Laka, Jimmy Moleko, Shane Malobela, Sello Lloyed Matloa, Tumelo Faith Sinugo', '3000.00', 'Paid', 'Fitz'),
('2026-02-13', 'Bonus - Advance d', 'Brian', '300.00', 'Paid', 'System'),
('2026-02-13', 'Loan Repayment - Advance deduction', 'Brian', '-300.00', 'Paid', 'System'),
('2026-02-13', 'Loan Repayment - Advance deduction', 'Jerry', '-200.00', 'Paid', 'System'),
('2026-02-13', 'Loan Repayment - Advance deduction', 'Tshepo Isrom Moganedi', '-200.00', 'Paid', 'System'),
('2026-02-13', 'Loan Repayment - Advance deduction', 'Brian', '-300.00', 'Paid', 'System'),
('2026-02-12', 'Plot', 'Soldier Aphiwe Dobe, Brian, Jerry, Tshepo Isrom Moganedi', '1070.00', 'Paid', 'Christiaan'),
('2026-02-12', 'Jopetku', 'Richard Moleko, Fikile Oupa Masimula, Mpho Gift Nkoana, Clifford Jan Bobby Selemela, Goitsimang Rasta Moleko, Johannes Laka, Jimmy Moleko, Shane Malobela, Sello Lloyed Matloa, Tumelo Faith Sinugo', '3000.00', 'Paid', 'Fitz'),
('2026-02-11', 'Plot', 'Soldier Aphiwe Dobe, Brian, Jerry, Tshepo Isrom Moganedi', '1070.00', 'Paid', 'Christiaan'),
('2026-02-11', 'Jopetku', 'Richard Moleko, Fikile Oupa Masimula, Mpho Gift Nkoana, Clifford Jan Bobby Selemela, Goitsimang Rasta Moleko, Johannes Laka, Jimmy Moleko, Shane Malobela, Sello Lloyed Matloa, Tumelo Faith Sinugo', '3000.00', 'Paid', 'Fitz'),
('2026-02-10', 'Plot', 'Soldier Aphiwe Dobe, Brian, Jerry, Tshepo Isrom Moganedi', '1070.00', 'Paid', 'Christiaan'),
('2026-02-10', 'Jopetku', 'Richard Moleko, Fikile Oupa Masimula, Mpho Gift Nkoana, Clifford Jan Bobby Selemela, Goitsimang Rasta Moleko, Johannes Laka, Jimmy Moleko, Shane Malobela, Sello Lloyed Matloa, Tumelo Faith Sinugo', '3000.00', 'Paid', 'Fitz'),
('2026-02-09', 'Plot', 'Soldier Aphiwe Dobe, Jerry, Tshepo Isrom Moganedi', '770.00', 'Paid', 'Christiaan'),
('2026-02-09', 'Jopetku', 'Richard Moleko, Fikile Oupa Masimula, Mpho Gift Nkoana, Clifford Jan Bobby Selemela, Goitsimang Rasta Moleko, Johannes Laka, Jimmy Moleko, Shane Malobela, Sello Lloyed Matloa, Tumelo Faith Sinugo', '3000.00', 'Paid', 'Fitz'),
('2026-02-07', 'Plot', 'Soldier Aphiwe Dobe, Brian', '550.00', 'Paid', 'Christiaan'),
('2026-02-06', 'Plot', 'Soldier Aphiwe Dobe, Brian, Jerry, Tshepo Isrom Moganedi', '1070.00', 'Paid', 'Christiaan'),
('2026-02-06', 'Jopetku', 'Richard Moleko, Fikile Oupa Masimula, Mpho Gift Nkoana, Clifford Jan Bobby Selemela, Goitsimang Rasta Moleko, Johannes Laka, Jimmy Moleko, Shane Malobela, Sello Lloyed Matloa, Tumelo Faith Sinugo', '3000.00', 'Paid', 'Fitz'),
('2026-02-05', 'Plot', 'Soldier Aphiwe Dobe, Brian, Jerry, Tshepo Isrom Moganedi', '1070.00', 'Paid', 'Christiaan'),
('2026-02-05', 'Jopetku', 'Richard Moleko, Fikile Oupa Masimula, Mpho Gift Nkoana, Clifford Jan Bobby Selemela, Goitsimang Rasta Moleko, Johannes Laka, Jimmy Moleko, Shane Malobela, Sello Lloyed Matloa, Tumelo Faith Sinugo', '3000.00', 'Paid', 'Christiaan'),
('2026-02-04', 'Plot', 'Soldier Aphiwe Dobe, Brian, Jerry, Tshepo Isrom Moganedi', '1070.00', 'Paid', 'Christiaan'),
('2026-02-04', 'Jopetku', 'Richard Moleko, Fikile Oupa Masimula, Mpho Gift Nkoana, Clifford Jan Bobby Selemela, Goitsimang Rasta Moleko, Johannes Laka, Jimmy Moleko, Shane Malobela, Sello Lloyed Matloa, Tumelo Faith Sinugo', '3000.00', 'Paid', 'Christiaan'),
('2026-02-04', 'Deduction - Tripod replacement', 'Fikile Oupa Masimula', '-250.00', 'Paid', 'System'),
('2026-02-04', 'Deduction - Tripod replacement', 'Goitsimang Rasta Moleko', '-250.00', 'Paid', 'System'),
('2026-02-04', 'Deduction - Tripod replacement', 'Jimmy Moleko', '-250.00', 'Paid', 'System'),
('2026-02-04', 'Deduction - Tripod replacement', 'Clifford Jan Bobby Selemela', '-250.00', 'Paid', 'System'),
('2026-02-04', 'Deduction - Tripod replacement', 'Johannes Laka', '-250.00', 'Paid', 'System'),
('2026-02-04', 'Deduction - Tripod replacement', 'Mpho Gift Nkoana', '-250.00', 'Paid', 'System'),
('2026-02-04', 'Deduction - Tripod replacement', 'Richard Moleko', '-250.00', 'Paid', 'System'),
('2026-02-04', 'Deduction - Tripod replacement', 'Sello Lloyed Matloa', '-250.00', 'Paid', 'System'),
('2026-02-04', 'Deduction - Tripod replacement', 'Shane Malobela', '-250.00', 'Paid', 'System'),
('2026-02-04', 'Deduction - Tripod replacement', 'Tumelo Faith Sinugo', '-250.00', 'Paid', 'System'),
('2026-02-03', 'Plot', 'Soldier Aphiwe Dobe, Brian', '550.00', 'Paid', 'Christiaan'),
('2026-02-03', 'Plot', 'Jerry, Tshepo Isrom Moganedi', '520.00', 'Paid', 'Christiaan'),
('2026-02-03', 'Jopetku', 'Richard Moleko, Fikile Oupa Masimula, Mpho Gift Nkoana, Clifford Jan Bobby Selemela, Goitsimang Rasta Moleko, Johannes Laka, Jimmy Moleko, Shane Malobela, Sello Lloyed Matloa, Tumelo Faith Sinugo', '3000.00', 'Paid', 'Christiaan'),
('2026-02-02', 'Plot', 'Soldier Aphiwe Dobe, Brian, Jerry, Tshepo Isrom Moganedi', '1070.00', 'Paid', 'Christiaan'),
('2026-02-02', 'Jopetku', 'Richard Moleko, Fikile Oupa Masimula, Mpho Gift Nkoana, Clifford Jan Bobby Selemela, Goitsimang Rasta Moleko, Johannes Laka, Jimmy Moleko, Shane Malobela, Sello Lloyed Matloa, Tumelo Faith Sinugo', '3000.00', 'Paid', 'Christiaan'),
('2026-02-01', 'Jopetku', 'Richard Moleko, Fikile Oupa Masimula, Mpho Gift Nkoana, Clifford Jan Bobby Selemela, Goitsimang Rasta Moleko, Johannes Laka, Jimmy Moleko, Shane Malobela, Sello Lloyed Matloa, Tumelo Faith Sinugo', '3000.00', 'Paid', 'Christiaan'),
('2026-01-31', 'Jopetku', 'Richard Moleko, Fikile Oupa Masimula, Mpho Gift Nkoana, Clifford Jan Bobby Selemela, Goitsimang Rasta Moleko, Johannes Laka, Jimmy Moleko, Shane Malobela, Sello Lloyed Matloa, Tumelo Faith Sinugo', '3000.00', 'Paid', 'Christiaan'),
('2026-01-30', 'Jopetku', 'Richard Moleko, Fikile Oupa Masimula, Mpho Gift Nkoana, Clifford Jan Bobby Selemela, Goitsimang Rasta Moleko, Johannes Laka, Jimmy Moleko, Shane Malobela, Sello Lloyed Matloa, Tumelo Faith Sinugo', '3000.00', 'Paid', 'Christiaan'),
('2026-01-29', 'Jopetku', 'Richard Moleko, Fikile Oupa Masimula, Mpho Gift Nkoana, Clifford Jan Bobby Selemela, Goitsimang Rasta Moleko, Johannes Laka, Jimmy Moleko, Shane Malobela, Sello Lloyed Matloa, Tumelo Faith Sinugo', '3000.00', 'Paid', 'Christiaan'),
('2026-01-28', 'Jopetku', 'Richard Moleko, Fikile Oupa Masimula, Mpho Gift Nkoana, Clifford Jan Bobby Selemela, Goitsimang Rasta Moleko, Johannes Laka, Jimmy Moleko, Shane Malobela, Sello Lloyed Matloa, Tumelo Faith Sinugo', '3000.00', 'Paid', 'Christiaan'),
('2026-01-27', 'Jopetku', 'Richard Moleko, Fikile Oupa Masimula, Mpho Gift Nkoana, Clifford Jan Bobby Selemela, Goitsimang Rasta Moleko, Johannes Laka, Jimmy Moleko, Shane Malobela, Sello Lloyed Matloa, Tumelo Faith Sinugo', '3000.00', 'Paid', 'Christiaan'),
('2026-01-26', 'Jopetku', 'Richard Moleko, Fikile Oupa Masimula, Mpho Gift Nkoana, Clifford Jan Bobby Selemela, Goitsimang Rasta Moleko, Johannes Laka, Jimmy Moleko, Shane Malobela, Sello Lloyed Matloa, Tumelo Faith Sinugo', '3000.00', 'Paid', 'Christiaan'),
('2026-01-25', 'Jopetku', 'Richard Moleko, Fikile Oupa Masimula, Mpho Gift Nkoana, Clifford Jan Bobby Selemela, Goitsimang Rasta Moleko, Johannes Laka, Jimmy Moleko, Shane Malobela, Sello Lloyed Matloa, Tumelo Faith Sinugo', '3000.00', 'Paid', 'Christiaan'),
('2026-01-24', 'Jopetku', 'Richard Moleko, Fikile Oupa Masimula, Mpho Gift Nkoana, Clifford Jan Bobby Selemela, Goitsimang Rasta Moleko, Johannes Laka, Jimmy Moleko, Shane Malobela, Sello Lloyed Matloa, Tumelo Faith Sinugo', '3000.00', 'Paid', 'Christiaan'),
('2026-01-23', 'Jopetku', 'Richard Moleko, Fikile Oupa Masimula, Mpho Gift Nkoana, Clifford Jan Bobby Selemela, Goitsimang Rasta Moleko, Johannes Laka, Jimmy Moleko, Shane Malobela, Sello Lloyed Matloa, Tumelo Faith Sinugo', '3000.00', 'Paid', 'Christiaan'),
]
# =============================================
# 6. PROCESS EACH ROW
# =============================================
logs_created = 0
adjs_created = 0
payments_created = 0
for date_str, description, workers_str, amount_str, status, supervisor_name in csv_rows:
row_date = datetime.datetime.strptime(date_str, '%Y-%m-%d').date()
amount = Decimal(amount_str)
is_paid = (status == 'Paid')
worker_names = [n.strip() for n in workers_str.split(',')]
# --- Is this a work log or an adjustment? ---
if supervisor_name != 'System' and ' - ' not in description:
# WORK LOG ROW
project_name = description
project = project_map.get(project_name)
if not project:
self.stdout.write(self.style.WARNING(f' Unknown project: {project_name}, skipping'))
continue
supervisor = supervisor_map.get(supervisor_name)
team = team_map.get(project_name)
workers = [worker_map[n] for n in worker_names if n in worker_map]
if not workers:
continue
# Check if this exact work log already exists
existing = WorkLog.objects.filter(
date=row_date,
project=project,
).first()
# If same date + project exists, check if it has the same workers
# (Feb 3 has two separate Plot logs with different workers)
if existing:
existing_worker_ids = set(existing.workers.values_list('id', flat=True))
new_worker_ids = set(w.id for w in workers)
if existing_worker_ids == new_worker_ids:
# Already imported, skip
worklog = existing
elif existing_worker_ids & new_worker_ids:
# Overlapping workers — merge by adding new workers
existing.workers.add(*workers)
worklog = existing
else:
# Different workers, same date+project — create new log
worklog = WorkLog.objects.create(
date=row_date,
project=project,
team=team,
supervisor=supervisor,
)
worklog.workers.set(workers)
logs_created += 1
else:
worklog = WorkLog.objects.create(
date=row_date,
project=project,
team=team,
supervisor=supervisor,
)
worklog.workers.set(workers)
logs_created += 1
# Create PayrollRecords for paid work logs (one per worker)
if is_paid:
for worker in workers:
# Check if already paid
already_paid = PayrollRecord.objects.filter(
worker=worker,
work_logs=worklog,
).exists()
if not already_paid:
pr = PayrollRecord.objects.create(
worker=worker,
date=row_date,
amount_paid=worker.daily_rate,
)
pr.work_logs.add(worklog)
payments_created += 1
else:
# ADJUSTMENT ROW
# Parse "Type - Description" format
if ' - ' in description:
adj_type_raw, adj_desc = description.split(' - ', 1)
else:
adj_type_raw = description
adj_desc = ''
# Map CSV type names to V5 TYPE_CHOICES
type_map = {
'Bonus': 'Bonus',
'Deduction': 'Deduction',
'Loan Repayment': 'Loan Repayment',
'Overtime': 'Overtime',
'Advance Payment': 'Advance Payment',
}
adj_type = type_map.get(adj_type_raw.strip(), adj_type_raw.strip())
# Amount: CSV uses negative for deductions, V5 stores positive
adj_amount = abs(amount)
for name in worker_names:
name = name.strip()
worker = worker_map.get(name)
if not worker:
self.stdout.write(self.style.WARNING(f' Unknown worker for adjustment: {name}'))
continue
# Check for duplicate
existing_adj = PayrollAdjustment.objects.filter(
worker=worker,
type=adj_type,
amount=adj_amount,
date=row_date,
description=adj_desc,
).first()
if existing_adj:
continue
# Find project for the adjustment (match by date)
adj_project = None
if adj_type in ('Deduction', 'Bonus', 'Overtime'):
# Try to find which project this worker was on that date
day_log = WorkLog.objects.filter(
date=row_date,
workers=worker,
).first()
if day_log:
adj_project = day_log.project
# Create PayrollRecord for paid adjustments
payroll_record = None
if is_paid:
payroll_record = PayrollRecord.objects.create(
worker=worker,
date=row_date,
amount_paid=adj_amount if adj_type in ('Bonus', 'Overtime', 'New Loan') else -adj_amount,
)
adj = PayrollAdjustment.objects.create(
worker=worker,
type=adj_type,
amount=adj_amount,
date=row_date,
description=adj_desc,
project=adj_project,
payroll_record=payroll_record,
)
adjs_created += 1
# =============================================
# 7. SUMMARY
# =============================================
self.stdout.write('')
self.stdout.write(self.style.SUCCESS('=== Production data import complete! ==='))
self.stdout.write(f' Admin login: admin / admin123')
self.stdout.write(f' Supervisor login: Christiaan / super123')
self.stdout.write(f' Supervisor login: Fitz / super123')
self.stdout.write(f' Projects: {Project.objects.filter(active=True).count()}')
self.stdout.write(f' Workers: {Worker.objects.filter(active=True).count()}')
self.stdout.write(f' Teams: {Team.objects.filter(active=True).count()}')
self.stdout.write(f' WorkLogs created: {logs_created}')
self.stdout.write(f' Adjustments created: {adjs_created}')
self.stdout.write(f' PayrollRecords created: {payments_created}')
self.stdout.write(f' Total WorkLogs: {WorkLog.objects.count()}')
self.stdout.write(f' Total Payments: {PayrollRecord.objects.count()}')
def _create_user(self, username, password, is_staff=False, is_superuser=False, first_name=''):
"""Create a user or update their flags if they already exist."""
user, created = User.objects.get_or_create(
username=username,
defaults={
'is_staff': is_staff,
'is_superuser': is_superuser,
'first_name': first_name,
}
)
if created:
user.set_password(password)
user.save()
role = 'admin' if is_superuser else 'supervisor'
self.stdout.write(self.style.SUCCESS(f' Created {role} user: {username}'))
else:
if is_staff and not user.is_staff:
user.is_staff = True
if is_superuser and not user.is_superuser:
user.is_superuser = True
user.save()
return user

View File

@ -42,4 +42,8 @@ urlpatterns = [
# Preview a worker's payslip (AJAX — returns JSON)
path('payroll/preview/<int:worker_id>/', views.preview_payslip, name='preview_payslip'),
# === TEMPORARY: Import production data from browser ===
# Visit /import-data/ once to populate the database. Remove after use.
path('import-data/', views.import_data, name='import_data'),
]

View File

@ -1099,3 +1099,40 @@ def preview_payslip(request, worker_id):
'net_pay': log_amount + adj_total,
'logs': unpaid_logs,
})
# =============================================================================
# === IMPORT DATA (TEMPORARY) ===
# Runs the import_production_data command from the browser.
# Visit /import-data/ once to populate the database. Safe to re-run.
# REMOVE THIS VIEW once data is imported.
# =============================================================================
def import_data(request):
"""Runs the import_production_data management command from the browser."""
from django.core.management import call_command
from io import StringIO
output = StringIO()
try:
call_command('import_production_data', stdout=output)
result = output.getvalue()
lines = result.replace('\n', '<br>')
return HttpResponse(
'<html><body style="font-family: monospace; padding: 20px;">'
'<h2>Import Complete!</h2>'
'<div>' + lines + '</div>'
'<br><br>'
'<a href="/admin/">Go to Admin Panel</a> | '
'<a href="/payroll/">Go to Payroll Dashboard</a> | '
'<a href="/">Go to Dashboard</a>'
'</body></html>'
)
except Exception as e:
return HttpResponse(
'<html><body style="font-family: monospace; padding: 20px; color: red;">'
'<h2>Import Error</h2>'
'<pre>' + str(e) + '</pre>'
'</body></html>',
status=500,
)