Django_Basic_Manufacturing_3/templates/purchasing/po_form.html
2025-08-22 17:05:22 +07:00

383 lines
17 KiB
HTML

{% extends 'base.html' %}
{% load static %}
{% block title %}{{ module_title }} - Manufacturing App{% endblock %}
{% block extra_css %}
<style>
.formset-container {
border: 1px solid #dee2e6;
border-radius: 0.375rem;
padding: 1rem;
margin-bottom: 1rem;
}
.formset-header {
background-color: #f8f9fa;
padding: 0.5rem 1rem;
margin: -1rem -1rem 1rem -1rem;
border-bottom: 1px solid #dee2e6;
}
.delete-checkbox {
margin-top: 2rem;
}
</style>
{% endblock %}
{% block content %}
<div class="container-fluid mt-4">
<div class="row">
<div class="col-md-10 offset-md-1">
<div class="card">
<div class="card-header">
<h4 class="mb-0">
{% if is_create %}
<i class="fas fa-plus-circle"></i> Create Purchase Order
{% else %}
<i class="fas fa-edit"></i> Edit Purchase Order
{% endif %}
</h4>
</div>
<div class="card-body">
<form method="post" id="po-form">
{% csrf_token %}
<!-- Purchase Order Header -->
<div class="row">
<div class="col-md-6">
<div class="mb-3">
<label for="{{ form.supplier.id_for_label }}" class="form-label">
{{ form.supplier.label }}{% if form.supplier.field.required %}<span class="text-danger">*</span>{% endif %}
</label>
{{ form.supplier }}
{% if form.supplier.errors %}
<div class="text-danger small">
{% for error in form.supplier.errors %}{{ error }}{% endfor %}
</div>
{% endif %}
</div>
</div>
<div class="row">
<div class="col-md-6">
<div class="mb-3">
<label for="{{ form.order_date.id_for_label }}" class="form-label">
{{ form.order_date.label }}{% if form.order_date.field.required %}<span class="text-danger">*</span>{% endif %}
</label>
{{ form.order_date }}
{% if form.order_date.errors %}
<div class="text-danger small">
{% for error in form.order_date.errors %}{{ error }}{% endfor %}
</div>
{% endif %}
</div>
</div>
<div class="col-md-6">
<div class="mb-3">
<label for="{{ form.expected_delivery_date.id_for_label }}" class="form-label">
{{ form.expected_delivery_date.label }}
</label>
{{ form.expected_delivery_date }}
{% if form.expected_delivery_date.errors %}
<div class="text-danger small">
{% for error in form.expected_delivery_date.errors %}{{ error }}{% endfor %}
</div>
{% endif %}
</div>
</div>
</div>
<!-- Purchase Order Items -->
<div class="formset-container">
<div class="formset-header">
<h5 class="mb-0">
<i class="fas fa-list"></i> Order Items
<button type="button" class="btn btn-sm btn-outline-primary float-end" id="add-item">
<i class="fas fa-plus"></i> Add Item
</button>
</h5>
</div>
{{ formset.management_form }}
<div id="purchase-items-container">
{% for item_form in formset %}
<div class="item-form mb-3 p-3 border rounded {% if item_form.DELETE.value %}d-none deleted-item{% endif %}"
id="item-form-{{ forloop.counter0 }}">
<div class="row">
<div class="col-md-3">
<label class="form-label">Product</label>
{{ item_form.product }}
{% if item_form.product.errors %}
<div class="text-danger small">
{% for error in item_form.product.errors %}{{ error }}{% endfor %}
</div>
{% endif %}
</div>
<div class="col-md-3">
<label class="form-label">Quantity</label>
{{ item_form.quantity }}
{% if item_form.quantity.errors %}
<div class="text-danger small">
{% for error in item_form.quantity.errors %}{{ error }}{% endfor %}
</div>
{% endif %}
</div>
<div class="col-md-3">
<label class="form-label">Unit Price</label>
{{ item_form.unit_price }}
{% if item_form.unit_price.errors %}
<div class="text-danger small">
{% for error in item_form.unit_price.errors %}{{ error }}{% endfor %}
</div>
{% endif %}
</div>
<div class="col-md-3 d-flex align-items-end">
<div class="mb-3 me-2 flex-grow-1">
<label class="form-label">Total</label>
<div class="form-control-plaintext" id="item-total-{{ forloop.counter0 }}">
Rp 0,00
</div>
</div>
<div class="d-flex align-items-end">
<button type="button" class="btn btn-danger btn-sm mb-3" style="display: block;">
<i class="fas fa-trash"></i>
</button>
</div>
</div>
</div>
{{ item_form.id }}
{{ item_form.DELETE }}
</div>
{% endfor %}
</div>
</div>
<div class="mb-3">
<label for="{{ form.notes.id_for_label }}" class="form-label">
{{ form.notes.label }}
</label>
{{ form.notes }}
{% if form.notes.errors %}
<div class="text-danger small">
{% for error in form.notes.errors %}{{ error }}{% endfor %}
</div>
{% endif %}
</div>
<div class="d-flex justify-content-between">
<a href="{% url 'purchasing:po_list' %}" class="btn btn-secondary">
<i class="fas fa-times"></i> Cancel
</a>
<button type="submit" class="btn btn-primary">
{% if is_create %}
<i class="fas fa-save"></i> Create Purchase Order
{% else %}
<i class="fas fa-save"></i> Update Purchase Order
{% endif %}
</button>
</div>
</form>
</div>
</div>
</div>
</div>
</div>
{% block extra_js %}
<script>
document.addEventListener('DOMContentLoaded', function() {
// Auto-create first form if no forms exist
const container = document.getElementById('purchase-items-container');
if (container && container.children.length === 0) {
const totalForms = document.querySelector('input[name$="-TOTAL_FORMS"]');
totalForms.value = 1;
// Create the first form
const templateForm = document.createElement('div');
templateForm.className = 'item-form mb-3 p-3 border rounded';
templateForm.id = 'item-form-0';
templateForm.innerHTML = `
<div class="row">
<div class="col-md-3">
<label class="form-label">Product</label>
<select name="form-0-product" class="form-control" required>
<option value="">---------</option>
</select>
</div>
<div class="col-md-3">
<label class="form-label">Quantity</label>
<input type="number" name="form-0-quantity" class="form-control" step="0.01" min="0" required>
</div>
<div class="col-md-3">
<label class="form-label">Unit Price</label>
<input type="number" name="form-0-unit_price" class="form-control" step="0.01" min="0" required>
</div>
<div class="col-md-3 d-flex align-items-end">
<div class="mb-3 me-2 flex-grow-1">
<label class="form-label">Total</label>
<div class="form-control-plaintext" id="item-total-0">Rp 0,00</div>
</div>
<div class="d-flex align-items-end">
<button type="button" class="btn btn-danger btn-sm mb-3" style="display: block;">
<i class="fas fa-trash"></i>
</button>
</div>
</div>
</div>
<input type="hidden" name="form-0-id" id="id_form-0-id">
<input type="checkbox" name="form-0-DELETE" id="id_form-0-DELETE" style="display: none;">
`;
container.appendChild(templateForm);
attachFormEvents(templateForm);
}
// Auto-calculate totals when quantity or unit price changes
document.addEventListener('input', function(e) {
if (e.target.matches('input[name$="-quantity"], input[name$="-unit_price"]')) {
calculateItemTotal(e.target.closest('.item-form'));
}
});
// Add new item functionality
document.getElementById('add-item').addEventListener('click', function() {
const totalForms = document.querySelector('input[name$="-TOTAL_FORMS"]');
const maxForms = document.querySelector('input[name$="-MAX_NUM_FORMS"]');
const formNum = parseInt(totalForms.value);
// Check if we've reached the maximum number of forms
if (maxForms && parseInt(maxForms.value) > 0 && formNum >= parseInt(maxForms.value)) {
alert('Maximum number of items reached.');
return;
}
// Find the first non-deleted form to clone as template
const container = document.getElementById('purchase-items-container');
const forms = container.querySelectorAll('.item-form:not(.deleted-item)');
const templateForm = forms.length > 0 ? forms[0] : container.firstElementChild;
if (!templateForm) {
console.error('No template form found');
return;
}
const newForm = templateForm.cloneNode(true);
// Update form indices - replace all occurrences of -0- with the new form number
newForm.innerHTML = newForm.innerHTML.replace(
new RegExp(`-0-`, 'g'),
`-${formNum}-`
);
// Update the form ID
newForm.id = `item-form-${formNum}`;
// Clear form values and reset state
newForm.className = 'item-form mb-3 p-3 border rounded';
newForm.querySelectorAll('input, select, textarea').forEach(function(field) {
field.disabled = false;
if (field.type === 'checkbox') {
field.checked = false;
} else if (field.tagName === 'SELECT') {
field.selectedIndex = 0;
} else {
field.value = '';
}
// Re-enable required attribute if it was removed
if (field.name.includes('product') || field.name.includes('quantity') || field.name.includes('unit_price')) {
field.setAttribute('required', 'required');
}
});
// Reset total display
const totalDisplay = newForm.querySelector('[id^="item-total-"]');
if (totalDisplay) {
totalDisplay.id = `item-total-${formNum}`;
totalDisplay.textContent = 'Rp 0,00';
}
// Always show remove button for added items (never hide it)
const removeBtn = newForm.querySelector('.btn-danger');
if (removeBtn) {
removeBtn.style.display = 'block';
}
container.appendChild(newForm);
totalForms.value = formNum + 1;
// Re-attach event listeners to the new form
attachFormEvents(newForm);
});
});
// Function to attach events to form fields
function attachFormEvents(form) {
// Remove existing event listeners to prevent duplicates
form.querySelectorAll('input[name$="-quantity"], input[name$="-unit_price"]').forEach(function(input) {
const newInput = input.cloneNode(true);
input.parentNode.replaceChild(newInput, input);
newInput.addEventListener('input', function() {
calculateItemTotal(form);
});
});
const removeBtn = form.querySelector('.btn-danger');
if (removeBtn) {
const newBtn = removeBtn.cloneNode(true);
removeBtn.parentNode.replaceChild(newBtn, removeBtn);
newBtn.addEventListener('click', function() {
removeItem(this);
});
}
}
function calculateItemTotal(form) {
const quantity = parseFloat(form.querySelector('input[name$="-quantity"]').value) || 0;
const unitPrice = parseFloat(form.querySelector('input[name$="-unit_price"]').value) || 0;
const total = quantity * unitPrice;
// Update the total display
const totalDisplay = form.querySelector('[id^="item-total-"]');
if (totalDisplay) {
totalDisplay.textContent = formatCurrency(total);
}
}
function formatCurrency(amount) {
return new Intl.NumberFormat('id-ID', {
style: 'currency',
currency: 'IDR',
minimumFractionDigits: 2
}).format(amount);
}
function removeItem(button) {
// Show confirmation dialog
const confirmed = confirm('Are you sure you want to delete this item? This action cannot be undone.');
if (confirmed) {
const itemForm = button.closest('.item-form');
const deleteCheckbox = itemForm.querySelector('input[name$="-DELETE"]');
if (deleteCheckbox) {
// Mark for deletion in the form
deleteCheckbox.checked = true;
// Hide the item visually and disable all form inputs
itemForm.classList.add('d-none', 'deleted-item');
itemForm.querySelectorAll('input, select, textarea').forEach(function(field) {
field.disabled = true;
// Clear required attribute to prevent validation errors
field.removeAttribute('required');
});
}
}
}
</script>
{% endblock %}
{% endblock %}