diff --git a/core/management/commands/import_production_data.py b/core/management/commands/import_production_data.py new file mode 100644 index 0000000..0f92f6d --- /dev/null +++ b/core/management/commands/import_production_data.py @@ -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 diff --git a/core/urls.py b/core/urls.py index 8676818..0183234 100644 --- a/core/urls.py +++ b/core/urls.py @@ -42,4 +42,8 @@ urlpatterns = [ # Preview a worker's payslip (AJAX — returns JSON) path('payroll/preview//', 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'), ] diff --git a/core/views.py b/core/views.py index f447a1d..a5c4b83 100644 --- a/core/views.py +++ b/core/views.py @@ -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', '
') + return HttpResponse( + '' + '

Import Complete!

' + '
' + lines + '
' + '

' + 'Go to Admin Panel | ' + 'Go to Payroll Dashboard | ' + 'Go to Dashboard' + '' + ) + except Exception as e: + return HttpResponse( + '' + '

Import Error

' + '
' + str(e) + '
' + '', + status=500, + )