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:
parent
9bee52dd03
commit
aaf86c2513
397
core/management/commands/import_production_data.py
Normal file
397
core/management/commands/import_production_data.py
Normal 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
|
||||
@ -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'),
|
||||
]
|
||||
|
||||
@ -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,
|
||||
)
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user