338 lines
14 KiB
HTML
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 %} |