38240-vm/core/views.py
2026-02-06 11:27:38 +00:00

403 lines
14 KiB
Python

import os
import platform
import io
from datetime import timedelta
from django import get_version as django_version
from django.shortcuts import render, redirect, get_object_or_404
from django.utils import timezone
from django.db.models import Sum, Q
from django.contrib.auth.decorators import login_required
from django.contrib import messages
from django.http import JsonResponse, HttpResponse
from .models import Medicine, Batch, Category, StockTransaction, Supplier, Faktur
from .forms import SupplierForm, FakturForm, StockInForm, StockOutForm, CategoryForm, MedicineForm
from reportlab.lib import colors
from reportlab.lib.pagesizes import letter, A4
from reportlab.platypus import SimpleDocTemplate, Table, TableStyle, Paragraph, Spacer
from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle
@login_required
def home(request):
"""Render the medicine warehouse dashboard."""
now = timezone.now()
today = now.date()
# Stats
total_medicines = Medicine.objects.count()
# Total stock across all batches
total_stock = Batch.objects.aggregate(total=Sum('quantity'))['total'] or 0
# Expired batches
expired_batches_count = Batch.objects.filter(expiry_date__lte=today).count()
# Near expiry (next 90 days)
near_expiry_batches_count = Batch.objects.filter(
expiry_date__gt=today,
expiry_date__lte=today + timezone.timedelta(days=90)
).count()
# Low stock medicines
all_medicines = Medicine.objects.all()
low_stock_count = 0
for med in all_medicines:
if med.total_stock <= med.min_stock:
low_stock_count += 1
# Latest medicines for the table
recent_medicines = all_medicines.order_by('-created_at')[:5]
# Chart Data: Last 7 days movement
chart_labels = []
chart_data_in = []
chart_data_out = []
for i in range(6, -1, -1):
day = today - timedelta(days=i)
chart_labels.append(day.strftime('%d %b'))
in_sum = StockTransaction.objects.filter(
transaction_type='IN',
created_at__date=day
).aggregate(total=Sum('quantity'))['total'] or 0
out_sum = StockTransaction.objects.filter(
transaction_type='OUT',
created_at__date=day
).aggregate(total=Sum('quantity'))['total'] or 0
chart_data_in.append(in_sum)
chart_data_out.append(out_sum)
context = {
"project_name": "DN-WRS",
"total_medicines": total_medicines,
"total_stock": total_stock,
"expired_count": expired_batches_count,
"near_expiry_count": near_expiry_batches_count,
"low_stock_count": low_stock_count,
"recent_medicines": recent_medicines,
"chart_labels": chart_labels,
"chart_data_in": chart_data_in,
"chart_data_out": chart_data_out,
"django_version": django_version(),
"python_version": platform.python_version(),
"current_time": now,
}
return render(request, "core/index.html", context)
# --- MASTER DATA MANAGEMENT ---
@login_required
def category_list(request):
if request.method == 'POST':
form = CategoryForm(request.POST)
if form.is_valid():
form.save()
messages.success(request, "Kategori berhasil ditambahkan.")
return redirect('category_list')
else:
form = CategoryForm()
categories = Category.objects.all().order_by('name')
return render(request, 'core/categories.html', {
'categories': categories,
'form': form,
'project_name': 'DN-WRS'
})
@login_required
def supplier_list(request):
if request.method == 'POST':
form = SupplierForm(request.POST)
if form.is_valid():
form.save()
messages.success(request, "Supplier berhasil ditambahkan.")
return redirect('supplier_list')
else:
form = SupplierForm()
suppliers = Supplier.objects.all().order_by('name')
return render(request, 'core/suppliers.html', {
'suppliers': suppliers,
'form': form,
'project_name': 'DN-WRS'
})
@login_required
def medicine_list(request):
query = request.GET.get('q')
show_low_stock = request.GET.get('low_stock') == '1'
if request.method == 'POST':
form = MedicineForm(request.POST)
if form.is_valid():
form.save()
messages.success(request, "Data barang berhasil ditambahkan.")
return redirect('medicine_list')
else:
form = MedicineForm()
medicines_all = Medicine.objects.all().order_by('name')
# Calculate low stock count for the summary
low_stock_count = 0
for med in medicines_all:
if med.total_stock <= med.min_stock:
low_stock_count += 1
medicines = medicines_all
if query:
medicines = medicines.filter(
Q(name__icontains=query) |
Q(sku__icontains=query) |
Q(category__name__icontains=query)
)
if show_low_stock:
# Filter for low stock
low_stock_ids = [m.id for m in medicines if m.total_stock <= m.min_stock]
medicines = medicines.filter(id__in=low_stock_ids)
return render(request, 'core/medicines.html', {
'medicines': medicines,
'form': form,
'project_name': 'DN-WRS',
'query': query,
'show_low_stock': show_low_stock,
'low_stock_count': low_stock_count,
'total_count': medicines_all.count()
})
@login_required
def export_low_stock_pdf(request):
# Get low stock medicines
all_medicines = Medicine.objects.all().order_by('main_supplier__name', 'name')
low_stock_medicines = [m for m in all_medicines if m.total_stock <= m.min_stock]
# Group by main supplier
grouped = {}
for m in low_stock_medicines:
supplier_name = m.main_supplier.name if m.main_supplier else "Tanpa Supplier Utama"
if supplier_name not in grouped:
grouped[supplier_name] = []
grouped[supplier_name].append(m)
# Generate PDF
buffer = io.BytesIO()
doc = SimpleDocTemplate(buffer, pagesize=A4)
elements = []
styles = getSampleStyleSheet()
title_style = ParagraphStyle(
'Title',
parent=styles['Heading1'],
fontSize=18,
alignment=1,
spaceAfter=20,
textColor=colors.HexColor("#0d6efd")
)
elements.append(Paragraph("Laporan Daftar Stok Menipis", title_style))
elements.append(Paragraph(f"Tanggal Cetak: {timezone.now().strftime('%d %B %Y %H:%M')}", styles["Normal"]))
elements.append(Spacer(1, 20))
if not low_stock_medicines:
elements.append(Paragraph("Tidak ada stok yang menipis saat ini.", styles["Normal"]))
else:
for supplier, items in grouped.items():
elements.append(Paragraph(f"Supplier Utama: {supplier}", styles["Heading2"]))
elements.append(Spacer(1, 5))
data = [["Nama Barang", "SKU", "Stok", "Min.", "Satuan", "Supplier Alternatif"]]
for item in items:
alt_supplier = item.alternative_supplier.name if item.alternative_supplier else "-"
data.append([
item.name,
item.sku,
str(item.total_stock),
str(item.min_stock),
item.unit,
alt_supplier
])
table = Table(data, colWidths=[160, 60, 40, 40, 50, 110])
table.setStyle(TableStyle([
('BACKGROUND', (0, 0), (-1, 0), colors.HexColor("#343a40")),
('TEXTCOLOR', (0, 0), (-1, 0), colors.whitesmoke),
('ALIGN', (0, 0), (-1, -1), 'CENTER'),
('VALIGN', (0, 0), (-1, -1), 'MIDDLE'),
('FONTNAME', (0, 0), (-1, 0), 'Helvetica-Bold'),
('FONTSIZE', (0, 0), (-1, 0), 10),
('BOTTOMPADDING', (0, 0), (-1, 0), 10),
('TOPPADDING', (0, 0), (-1, 0), 10),
('GRID', (0, 0), (-1, -1), 0.5, colors.grey),
('FONTSIZE', (0, 1), (-1, -1), 9),
]))
elements.append(table)
elements.append(Spacer(1, 15))
doc.build(elements)
buffer.seek(0)
response = HttpResponse(buffer, content_type='application/pdf')
response['Content-Disposition'] = 'attachment; filename="laporan_stok_menipis.pdf"'
return response
# --- TRANSAKSI ---
@login_required
def input_faktur(request):
if request.method == 'POST':
faktur_form = FakturForm(request.POST)
if faktur_form.is_valid():
faktur = faktur_form.save()
return redirect('faktur_detail', pk=faktur.pk)
else:
faktur_form = FakturForm(initial={'faktur_type': 'MASUK', 'date': timezone.now().date()})
fakturs = Faktur.objects.all().order_by('-created_at')
return render(request, 'core/input_faktur.html', {
'faktur_form': faktur_form,
'fakturs': fakturs,
'project_name': 'DN-WRS'
})
@login_required
def faktur_detail(request, pk):
faktur = get_object_or_404(Faktur, pk=pk)
if request.method == 'POST':
form = StockInForm(request.POST)
if form.is_valid():
# Create Batch
batch = Batch.objects.create(
medicine=form.cleaned_data['medicine'],
faktur=faktur,
batch_number=form.cleaned_data['batch_number'],
expiry_date=form.cleaned_data['expiry_date'],
quantity=form.cleaned_data['quantity'],
buying_price=form.cleaned_data['buying_price'],
selling_price=form.cleaned_data['selling_price']
)
# Create Transaction
StockTransaction.objects.create(
medicine=batch.medicine,
batch=batch,
faktur=faktur,
transaction_type='IN',
quantity=batch.quantity,
note=f"Input dari Faktur {faktur.faktur_number}"
)
messages.success(request, f"Barang {batch.medicine.name} berhasil ditambahkan.")
return redirect('faktur_detail', pk=pk)
else:
form = StockInForm()
items = Batch.objects.filter(faktur=faktur)
return render(request, 'core/faktur_detail.html', {
'faktur': faktur,
'form': form,
'items': items,
'project_name': 'DN-WRS'
})
@login_required
def barang_keluar(request):
if request.method == 'POST':
form = StockOutForm(request.POST)
if form.is_valid():
medicine = form.cleaned_data['medicine']
batch = form.cleaned_data['batch']
qty = form.cleaned_data['quantity']
if batch.quantity < qty:
messages.error(request, f"Stok tidak mencukupi. Stok saat ini: {batch.quantity}")
else:
# Update Batch
batch.quantity -= qty
batch.save()
# Create Transaction
StockTransaction.objects.create(
medicine=medicine,
batch=batch,
transaction_type='OUT',
quantity=qty,
note=form.cleaned_data['note']
)
messages.success(request, f"Barang keluar berhasil dicatat.")
return redirect('barang_keluar')
else:
form = StockOutForm()
transactions = StockTransaction.objects.filter(transaction_type='OUT').order_by('-created_at')[:10]
return render(request, 'core/barang_keluar.html', {
'form': form,
'transactions': transactions,
'project_name': 'DN-WRS'
})
@login_required
def get_batches(request):
medicine_id = request.GET.get('medicine_id')
batches = Batch.objects.filter(medicine_id=medicine_id, quantity__gt=0).values('id', 'batch_number', 'quantity')
return JsonResponse(list(batches), safe=False)
@login_required
def laporan_transaksi(request):
transactions = StockTransaction.objects.all().order_by('-created_at')
return render(request, 'core/laporan_transaksi.html', {
'transactions': transactions,
'project_name': 'DN-WRS'
})
@login_required
def delete_transaksi(request, pk):
transaction = get_object_or_404(StockTransaction, pk=pk)
batch = transaction.batch
if batch:
if transaction.transaction_type == 'IN':
batch.quantity -= transaction.quantity
elif transaction.transaction_type == 'OUT':
batch.quantity += transaction.quantity
batch.save()
transaction.delete()
messages.success(request, "Transaksi berhasil dihapus dan stok telah diperbarui.")
return redirect('laporan_transaksi')
@login_required
def edit_transaksi(request, pk):
transaction = get_object_or_404(StockTransaction, pk=pk)
if request.method == 'POST':
new_qty = int(request.POST.get('quantity'))
old_qty = transaction.quantity
batch = transaction.batch
if batch:
if transaction.transaction_type == 'IN':
# Revert old, apply new
batch.quantity = batch.quantity - old_qty + new_qty
elif transaction.transaction_type == 'OUT':
# Revert old, apply new
batch.quantity = batch.quantity + old_qty - new_qty
if batch.quantity < 0:
messages.error(request, "Error: Stok tidak boleh negatif setelah perubahan.")
return redirect('laporan_transaksi')
batch.save()
transaction.quantity = new_qty
transaction.note = request.POST.get('note', transaction.note)
transaction.save()
messages.success(request, "Transaksi berhasil diperbarui.")
return redirect('laporan_transaksi')
return render(request, 'core/edit_transaksi.html', {
'transaction': transaction,
'project_name': 'DN-WRS'
})