Django_Basic_Manufacturing/templates/manufacture/bom_form.html
2025-08-19 19:06:26 +07:00

338 lines
14 KiB
HTML

{% extends 'base.html' %}
{% load crispy_forms_tags %}
{% block title %}{{ title }}{% endblock %}
{% block content %}
<div class="d-flex justify-content-between align-items-center mb-4">
<h1 class="h3 mb-0">
<i class="bi bi-list-check me-2"></i>
{{ title }}
</h1>
<a href="{% url 'manufacture:bom_list' %}" class="btn btn-outline-secondary">
<i class="bi bi-arrow-left me-2"></i>
Back to BOM List
</a>
</div>
<div class="row">
<div class="col-md-8">
<div class="card">
<div class="card-header">
<h5 class="card-title mb-0">Bill of Materials Information</h5>
</div>
<div class="card-body">
<form method="post" id="bom-form">
{% csrf_token %}
{# Use dynamic form for both creation and editing #}
<div class="row">
<div class="col-md-12">
{% if main_form %}
{{ main_form.manufactured_product|as_crispy_field }}
{% else %}
{{ form.manufactured_product|as_crispy_field }}
{% endif %}
</div>
</div>
<h5 class="mt-4 mb-3">Components</h5>
{% if component_formset %}
{{ component_formset.management_form }}
<div id="component-forms-container">
{% for component_form in component_formset %}
<div class="card mb-3 component-form">
<div class="card-header d-flex justify-content-between align-items-center">
<h6 class="card-title mb-0">Component #{{ forloop.counter }}</h6>
{% if forloop.counter > 1 %}
<button type="button" class="btn btn-sm btn-outline-danger remove-component">
<i class="bi bi-trash"></i> Remove
</button>
{% endif %}
</div>
<div class="card-body">
<div class="row">
<div class="col-md-6">
{{ component_form.component|as_crispy_field }}
</div>
<div class="col-md-6">
{{ component_form.quantity|as_crispy_field }}
</div>
<div class="row">
<div class="col-md-6">
{{ component_form.unit|as_crispy_field }}
</div>
</div>
</div>
</div>
{% endfor %}
</div>
<div class="mb-3">
<button type="button" id="add-component" class="btn btn-outline-primary">
<i class="bi bi-plus-circle me-2"></i>
Add Another Component
</button>
</div>
{% else %}
{# Single BOM form for backward compatibility #}
<div class="row">
<div class="col-md-6">
{{ form.component|as_crispy_field }}
</div>
<div class="col-md-6">
{{ form.quantity|as_crispy_field }}
</div>
<div class="row">
<div class="col-md-6">
{{ form.unit|as_crispy_field }}
</div>
</div>
{% endif %}
<div class="mt-3">
<button type="submit" class="btn btn-primary">
<i class="bi bi-check-circle me-2"></i>
Save BOM Entry
</button>
<a href="{% url 'manufacture:bom_list' %}" class="btn btn-outline-secondary ms-2">
Cancel
</a>
</div>
</form>
</div>
</div>
<div class="col-md-4">
<div class="card">
<div class="card-header">
<h5 class="card-title mb-0">BOM Tips</h5>
</div>
<div class="card-body">
<ul class="list-unstyled mb-0">
<li class="mb-2">
<i class="bi bi-info-circle text-primary me-2"></i>
Select the manufactured product and component
</li>
<li class="mb-2">
<i class="bi bi-calculator text-success me-2"></i>
Enter the exact quantity needed
</li>
<li class="mb-2">
<i class="bi bi-rulers text-info me-2"></i>
Unit of measurement is automatically populated
</li>
{% if component_formset %}
<li class="mb-2">
<i class="bi bi-plus-circle text-warning me-2"></i>
Add multiple components dynamically
</li>
<li class="mb-2">
<i class="bi bi-dash-circle text-danger me-2"></i>
Remove components you don't need
</li>
{% endif %}
</ul>
</div>
</div>
</div>
</div>
{% endblock %}
{% block extra_js %}
<script>
document.addEventListener('DOMContentLoaded', function() {
// Get CSRF token
function getCookie(name) {
let cookieValue = null;
if (document.cookie && document.cookie !== '') {
const cookies = document.cookie.split(';');
for (let i = 0; i < cookies.length; i++) {
const cookie = cookies[i].trim();
if (cookie.substring(0, name.length + 1) === (name + '=')) {
cookieValue = decodeURIComponent(cookie.substring(name.length + 1));
break;
}
}
}
return cookieValue;
}
// Function to update unit field based on selected component
function updateUnitField(componentSelect, unitInput) {
const productId = componentSelect.value;
if (!productId) {
unitInput.value = '';
return;
}
// Make AJAX call to get product info
fetch(`/manufacture/bom/product/${productId}/info/`, {
method: 'GET',
headers: {
'X-CSRFToken': getCookie('csrftoken'),
'Content-Type': 'application/json',
},
credentials: 'same-origin'
})
.then(response => {
if (!response.ok) {
throw new Error('Network response was not ok');
}
return response.json();
})
.then(data => {
if (data.unit) {
unitInput.value = data.unit;
} else {
unitInput.value = '';
}
})
.catch(error => {
console.error('Error fetching product info:', error);
unitInput.value = '';
});
}
// Check if we have the dynamic form
if (document.getElementById('component-forms-container')) {
// Add component button
document.getElementById('add-component').addEventListener('click', function() {
var container = document.getElementById('component-forms-container');
var formCount = container.querySelectorAll('.component-form').length;
var totalForms = document.getElementById('id_component-TOTAL_FORMS');
// Clone the first form as a template
var firstForm = container.querySelector('.component-form');
var newForm = firstForm.cloneNode(true);
// Update form indices
var newFormIndex = formCount;
var formRegex = /component-\d+/g;
// Update all input names and IDs
newForm.innerHTML = newForm.innerHTML.replace(formRegex, 'component-' + newFormIndex);
// Clear input values
var inputs = newForm.querySelectorAll('input, select, textarea');
inputs.forEach(function(input) {
if (input.type === 'checkbox' || input.type === 'radio') {
input.checked = false;
} else if (input.type !== 'hidden') {
input.value = '';
}
});
// Update header
var header = newForm.querySelector('.card-header h6');
header.textContent = 'Component #' + (newFormIndex + 1);
// Add remove button if it doesn't exist
var removeButtonContainer = newForm.querySelector('.card-header');
if (!removeButtonContainer.querySelector('.remove-component')) {
var removeButton = document.createElement('button');
removeButton.type = 'button';
removeButton.className = 'btn btn-sm btn-outline-danger remove-component';
removeButton.innerHTML = '<i class="bi bi-trash"></i> Remove';
removeButtonContainer.appendChild(removeButton);
}
// Add event listener to the new remove button
var newRemoveButton = newForm.querySelector('.remove-component');
newRemoveButton.addEventListener('click', function() {
newForm.remove();
updateFormIndices();
});
// Add event listener to the new component select
var newComponentSelect = newForm.querySelector('select[id^="id_component-"][id$="-component"]');
var newUnitInput = newForm.querySelector('input[id^="id_component-"][id$="-unit"]');
if (newComponentSelect && newUnitInput) {
newComponentSelect.addEventListener('change', function() {
updateUnitField(this, newUnitInput);
});
}
// Append new form
container.appendChild(newForm);
// Update total forms count
totalForms.value = formCount + 1;
});
// Remove component buttons
document.querySelectorAll('.remove-component').forEach(function(button) {
button.addEventListener('click', function() {
var form = this.closest('.component-form');
form.remove();
updateFormIndices();
});
});
// Add event listeners to existing component selects
document.querySelectorAll('.component-form select[id$="-component"]').forEach(function(select) {
var unitInput = select.closest('.component-form').querySelector('input[id$="-unit"]');
if (unitInput) {
select.addEventListener('change', function() {
updateUnitField(this, unitInput);
});
// Initialize unit field if component is already selected
if (select.value) {
updateUnitField(select, unitInput);
}
}
});
// Update form indices function
function updateFormIndices() {
var forms = document.querySelectorAll('.component-form');
var totalForms = document.getElementById('id_component-TOTAL_FORMS');
forms.forEach(function(form, index) {
// Update header
var header = form.querySelector('.card-header h6');
header.textContent = 'Component #' + (index + 1);
// Update form indices in names and IDs
var formRegex = /component-\d+/g;
form.innerHTML = form.innerHTML.replace(formRegex, 'component-' + index);
// Update event listeners for component selects
var componentSelect = form.querySelector('select[id$="-component"]');
var unitInput = form.querySelector('input[id$="-unit"]');
if (componentSelect && unitInput) {
// Remove existing event listeners by cloning
var newSelect = componentSelect.cloneNode(true);
componentSelect.parentNode.replaceChild(newSelect, componentSelect);
// Add new event listener
newSelect.addEventListener('change', function() {
updateUnitField(this, unitInput);
});
}
// Update remove button event listener
var removeButton = form.querySelector('.remove-component');
if (removeButton) {
// Remove existing event listener by cloning
var newRemoveButton = removeButton.cloneNode(true);
removeButton.parentNode.replaceChild(newRemoveButton, removeButton);
// Add new event listener
newRemoveButton.addEventListener('click', function() {
form.remove();
updateFormIndices();
});
}
});
// Update total forms count
totalForms.value = forms.length;
}
}
});
</script>
{% endblock %}