improv
This commit is contained in:
parent
66c4c1fe4f
commit
2c2e34b71e
42 changed files with 910 additions and 939 deletions
|
@ -1,10 +1,32 @@
|
||||||
<?php
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
use Symfony\Bundle\FrameworkBundle\FrameworkBundle;
|
||||||
|
use Symfony\Bundle\TwigBundle\TwigBundle;
|
||||||
|
use Doctrine\Bundle\DoctrineBundle\DoctrineBundle;
|
||||||
|
use Doctrine\Bundle\MigrationsBundle\DoctrineMigrationsBundle;
|
||||||
|
use Symfony\Bundle\MakerBundle\MakerBundle;
|
||||||
|
use Symfony\Bundle\WebProfilerBundle\WebProfilerBundle;
|
||||||
|
|
||||||
return [
|
return [
|
||||||
Symfony\Bundle\FrameworkBundle\FrameworkBundle::class => ['all' => true],
|
FrameworkBundle::class => [
|
||||||
Symfony\Bundle\TwigBundle\TwigBundle::class => ['all' => true],
|
'all' => true,
|
||||||
Doctrine\Bundle\DoctrineBundle\DoctrineBundle::class => ['all' => true],
|
],
|
||||||
Doctrine\Bundle\MigrationsBundle\DoctrineMigrationsBundle::class => ['all' => true],
|
TwigBundle::class => [
|
||||||
Symfony\Bundle\MakerBundle\MakerBundle::class => ['dev' => true],
|
'all' => true,
|
||||||
Symfony\Bundle\WebProfilerBundle\WebProfilerBundle::class => ['dev' => true, 'test' => true],
|
],
|
||||||
|
DoctrineBundle::class => [
|
||||||
|
'all' => true,
|
||||||
|
],
|
||||||
|
DoctrineMigrationsBundle::class => [
|
||||||
|
'all' => true,
|
||||||
|
],
|
||||||
|
MakerBundle::class => [
|
||||||
|
'dev' => true,
|
||||||
|
],
|
||||||
|
WebProfilerBundle::class => [
|
||||||
|
'dev' => true,
|
||||||
|
'test' => true,
|
||||||
|
],
|
||||||
];
|
];
|
||||||
|
|
|
@ -6,10 +6,12 @@ use App\Service\Config\AppName;
|
||||||
use Symfony\Config\TwigConfig;
|
use Symfony\Config\TwigConfig;
|
||||||
use Symfony\Component\DependencyInjection\Loader\Configurator\ContainerConfigurator;
|
use Symfony\Component\DependencyInjection\Loader\Configurator\ContainerConfigurator;
|
||||||
|
|
||||||
|
use function Symfony\Component\DependencyInjection\Loader\Configurator\service;
|
||||||
|
|
||||||
return static function (ContainerConfigurator $containerConfigurator, TwigConfig $twig): void {
|
return static function (ContainerConfigurator $containerConfigurator, TwigConfig $twig): void {
|
||||||
$twig->fileNamePattern('*.twig');
|
$twig->fileNamePattern('*.twig');
|
||||||
$twig->formThemes(['bootstrap_5_layout.html.twig']);
|
$twig->formThemes(['form/theme.html.twig']);
|
||||||
$twig->global('appName', \Symfony\Component\DependencyInjection\Loader\Configurator\service(AppName::class));
|
$twig->global('appName', service(AppName::class));
|
||||||
if ($containerConfigurator->env() === 'test') {
|
if ($containerConfigurator->env() === 'test') {
|
||||||
$twig->strictVariables(true);
|
$twig->strictVariables(true);
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,6 +2,7 @@
|
||||||
|
|
||||||
declare(strict_types=1);
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
use Doctrine\ORM\Events;
|
||||||
use Symfony\Component\DependencyInjection\Loader\Configurator\ContainerConfigurator;
|
use Symfony\Component\DependencyInjection\Loader\Configurator\ContainerConfigurator;
|
||||||
|
|
||||||
return static function (ContainerConfigurator $containerConfigurator): void {
|
return static function (ContainerConfigurator $containerConfigurator): void {
|
||||||
|
@ -15,5 +16,4 @@ return static function (ContainerConfigurator $containerConfigurator): void {
|
||||||
|
|
||||||
$services->load('App\\Service\\', __DIR__ . '/../src/Service')
|
$services->load('App\\Service\\', __DIR__ . '/../src/Service')
|
||||||
->public();
|
->public();
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
|
@ -13,785 +13,115 @@ document.addEventListener('mousemove', function (e) {
|
||||||
}, 1000);
|
}, 1000);
|
||||||
});
|
});
|
||||||
|
|
||||||
// Dashboard, Order Management, Inventory Management, Settings, and Drink Type functionality
|
// Function to initialize number input buttons
|
||||||
|
function initNumberInputs(container = document) {
|
||||||
|
container.querySelectorAll('.number-input-wrapper').forEach(function(wrapper) {
|
||||||
|
const input = wrapper.querySelector('input[type="number"]');
|
||||||
|
const decreaseBtn = wrapper.querySelector('[data-action="decrease"]');
|
||||||
|
const increaseBtn = wrapper.querySelector('[data-action="increase"]');
|
||||||
|
|
||||||
|
if (!input || !decreaseBtn || !increaseBtn) return;
|
||||||
|
|
||||||
|
// Skip if already initialized
|
||||||
|
if (decreaseBtn.hasAttribute('data-initialized')) return;
|
||||||
|
|
||||||
|
const step = parseFloat(input.getAttribute('step')) || 1;
|
||||||
|
const min = 0;
|
||||||
|
const max = input.getAttribute('max') ? parseFloat(input.getAttribute('max')) : null;
|
||||||
|
|
||||||
|
decreaseBtn.addEventListener('click', function() {
|
||||||
|
const currentValue = parseFloat(input.value) || 0;
|
||||||
|
const newValue = currentValue - step;
|
||||||
|
|
||||||
|
if (min === null || newValue >= min) {
|
||||||
|
input.value = newValue;
|
||||||
|
input.dispatchEvent(new Event('change', { bubbles: true }));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
increaseBtn.addEventListener('click', function() {
|
||||||
|
const currentValue = parseFloat(input.value) || 0;
|
||||||
|
const newValue = currentValue + step;
|
||||||
|
|
||||||
|
if (max === null || newValue <= max) {
|
||||||
|
input.value = newValue;
|
||||||
|
input.dispatchEvent(new Event('change', { bubbles: true }));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Validate input on change
|
||||||
|
input.addEventListener('input', function() {
|
||||||
|
const value = parseFloat(this.value);
|
||||||
|
|
||||||
|
if (min !== null && value < min) {
|
||||||
|
this.value = min;
|
||||||
|
}
|
||||||
|
if (max !== null && value > max) {
|
||||||
|
this.value = max;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Mark as initialized
|
||||||
|
decreaseBtn.setAttribute('data-initialized', 'true');
|
||||||
|
increaseBtn.setAttribute('data-initialized', 'true');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
document.addEventListener('DOMContentLoaded', function() {
|
document.addEventListener('DOMContentLoaded', function() {
|
||||||
// Stock update form handling
|
// Bootstrap Modal handling
|
||||||
initStockUpdateForms();
|
const htmxModal = document.getElementById('htmxModal');
|
||||||
|
if (htmxModal) {
|
||||||
|
htmxModal.addEventListener('show.bs.modal', function(event) {
|
||||||
|
// Get the button that triggered the modal
|
||||||
|
const button = event.relatedTarget;
|
||||||
|
// Extract the drink name from data-* attributes
|
||||||
|
const drinkName = button.getAttribute('data-drink-name');
|
||||||
|
// Update the modal title
|
||||||
|
if (drinkName) {
|
||||||
|
const modalTitle = htmxModal.querySelector('.modal-title');
|
||||||
|
if (modalTitle) {
|
||||||
|
modalTitle.textContent = 'Update Stock for ' + drinkName;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
// Edit button handling
|
// HTMX Modal handling
|
||||||
initEditButtons();
|
document.body.addEventListener('htmx:afterSwap', function(event) {
|
||||||
|
// If the target is the modal body, initialize any form elements inside it
|
||||||
|
if (event.detail.target.id === 'htmxModalBody') {
|
||||||
|
// The modal content has been loaded
|
||||||
|
console.log('Modal content loaded');
|
||||||
|
// Initialize number inputs in the modal
|
||||||
|
initNumberInputs(event.detail.target);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
// Order form handling
|
// Handle form submissions in the modal
|
||||||
initOrderForms();
|
document.body.addEventListener('htmx:beforeSend', function(event) {
|
||||||
|
// If the event is from a form inside the modal
|
||||||
|
if (event.detail.elt.closest('#htmxModalBody')) {
|
||||||
|
// Add the HX-Request header to the request
|
||||||
|
event.detail.requestConfig.headers['HX-Request'] = 'true';
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
// Order status form handling
|
// Handle redirects from HTMX
|
||||||
initOrderStatusForms();
|
document.body.addEventListener('htmx:responseError', function(event) {
|
||||||
|
if (event.detail.xhr.status === 303) {
|
||||||
|
const redirectUrl = event.detail.xhr.getResponseHeader('HX-Redirect');
|
||||||
|
if (redirectUrl) {
|
||||||
|
// Close the modal
|
||||||
|
const modal = bootstrap.Modal.getInstance(document.getElementById('htmxModal'));
|
||||||
|
if (modal) {
|
||||||
|
modal.hide();
|
||||||
|
}
|
||||||
|
// Redirect to the specified URL
|
||||||
|
window.location.href = redirectUrl;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
// Inventory form handling
|
// Initialize number inputs on page load
|
||||||
initInventoryForm();
|
initNumberInputs();
|
||||||
|
|
||||||
// Settings form handling
|
|
||||||
initSettingsForm();
|
|
||||||
|
|
||||||
// Drink Type form handling
|
|
||||||
initDrinkTypeForm();
|
|
||||||
});
|
});
|
||||||
|
|
||||||
/**
|
|
||||||
* Initialize stock update forms with AJAX submission and validation
|
|
||||||
*/
|
|
||||||
function initStockUpdateForms() {
|
|
||||||
// Get all stock update forms
|
|
||||||
const stockForms = document.querySelectorAll('.stock-update-form');
|
|
||||||
|
|
||||||
// Add event listener to each form
|
|
||||||
stockForms.forEach(form => {
|
|
||||||
// Add validation to quantity input
|
|
||||||
const quantityInput = form.querySelector('input[name="quantity"]');
|
|
||||||
if (quantityInput) {
|
|
||||||
quantityInput.addEventListener('input', function() {
|
|
||||||
validateQuantityInput(this);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add submit handler
|
|
||||||
form.addEventListener('submit', function(e) {
|
|
||||||
e.preventDefault();
|
|
||||||
|
|
||||||
// Validate form before submission
|
|
||||||
if (!validateStockForm(form)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get form data
|
|
||||||
const formData = new FormData(form);
|
|
||||||
|
|
||||||
// Show loading state
|
|
||||||
const submitButton = form.querySelector('button[type="submit"]');
|
|
||||||
const originalText = submitButton.textContent;
|
|
||||||
submitButton.textContent = 'Updating...';
|
|
||||||
submitButton.disabled = true;
|
|
||||||
|
|
||||||
// Send AJAX request
|
|
||||||
fetch(form.action, {
|
|
||||||
method: 'POST',
|
|
||||||
body: formData
|
|
||||||
})
|
|
||||||
.then(response => {
|
|
||||||
if (!response.ok) {
|
|
||||||
throw new Error('Network response was not ok');
|
|
||||||
}
|
|
||||||
return response.json();
|
|
||||||
})
|
|
||||||
.then(data => {
|
|
||||||
// Reset button state
|
|
||||||
submitButton.textContent = originalText;
|
|
||||||
submitButton.disabled = false;
|
|
||||||
|
|
||||||
if (data.success) {
|
|
||||||
// Show success message
|
|
||||||
showAlert(form.parentNode, 'success', 'Stock updated successfully.');
|
|
||||||
} else {
|
|
||||||
// Show error message
|
|
||||||
showAlert(form.parentNode, 'danger', `Error updating stock: ${data.message}`);
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.catch(error => {
|
|
||||||
// Reset button state
|
|
||||||
submitButton.textContent = originalText;
|
|
||||||
submitButton.disabled = false;
|
|
||||||
|
|
||||||
// Show error message
|
|
||||||
showAlert(form.parentNode, 'danger', `Error: ${error.message}`);
|
|
||||||
console.error('Error:', error);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Initialize edit buttons
|
|
||||||
*/
|
|
||||||
function initEditButtons() {
|
|
||||||
const editButtons = document.querySelectorAll('.btn-edit-drink-type');
|
|
||||||
|
|
||||||
editButtons.forEach(button => {
|
|
||||||
button.addEventListener('click', function() {
|
|
||||||
const drinkTypeId = this.dataset.drinkTypeId;
|
|
||||||
window.location.href = `/drink-types/edit/${drinkTypeId}`;
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Validate a stock update form
|
|
||||||
* @param {HTMLFormElement} form - The form to validate
|
|
||||||
* @returns {boolean} - Whether the form is valid
|
|
||||||
*/
|
|
||||||
function validateStockForm(form) {
|
|
||||||
const quantityInput = form.querySelector('input[name="quantity"]');
|
|
||||||
return validateQuantityInput(quantityInput);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Validate a quantity input
|
|
||||||
* @param {HTMLInputElement} input - The input to validate
|
|
||||||
* @returns {boolean} - Whether the input is valid
|
|
||||||
*/
|
|
||||||
function validateQuantityInput(input) {
|
|
||||||
const value = parseInt(input.value);
|
|
||||||
|
|
||||||
// Check if value is a number
|
|
||||||
if (isNaN(value)) {
|
|
||||||
input.classList.add('is-invalid');
|
|
||||||
showInputError(input, 'Please enter a valid number');
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check if value is non-negative
|
|
||||||
if (value < 0) {
|
|
||||||
input.classList.add('is-invalid');
|
|
||||||
showInputError(input, 'Quantity cannot be negative');
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Input is valid
|
|
||||||
input.classList.remove('is-invalid');
|
|
||||||
input.classList.add('is-valid');
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Show an error message for an input
|
|
||||||
* @param {HTMLInputElement} input - The input with the error
|
|
||||||
* @param {string} message - The error message
|
|
||||||
*/
|
|
||||||
function showInputError(input, message) {
|
|
||||||
// Remove any existing error message
|
|
||||||
const existingError = input.parentNode.querySelector('.invalid-feedback');
|
|
||||||
if (existingError) {
|
|
||||||
existingError.remove();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create and add new error message
|
|
||||||
const errorDiv = document.createElement('div');
|
|
||||||
errorDiv.className = 'invalid-feedback';
|
|
||||||
errorDiv.textContent = message;
|
|
||||||
input.parentNode.appendChild(errorDiv);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Show an alert message
|
|
||||||
* @param {HTMLElement} container - The container to add the alert to
|
|
||||||
* @param {string} type - The type of alert (success, danger, warning, info)
|
|
||||||
* @param {string} message - The alert message
|
|
||||||
*/
|
|
||||||
function showAlert(container, type, message) {
|
|
||||||
// Remove any existing alerts
|
|
||||||
const existingAlerts = container.querySelectorAll('.alert');
|
|
||||||
existingAlerts.forEach(alert => alert.remove());
|
|
||||||
|
|
||||||
// Create and add new alert
|
|
||||||
const alert = document.createElement('div');
|
|
||||||
alert.className = `alert alert-${type} alert-dismissible fade show mt-3`;
|
|
||||||
alert.innerHTML = `
|
|
||||||
${message}
|
|
||||||
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
|
|
||||||
`;
|
|
||||||
container.appendChild(alert);
|
|
||||||
|
|
||||||
// Remove alert after 3 seconds
|
|
||||||
setTimeout(() => {
|
|
||||||
alert.remove();
|
|
||||||
}, 3000);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Initialize order forms with validation and dynamic item handling
|
|
||||||
*/
|
|
||||||
function initOrderForms() {
|
|
||||||
const orderForm = document.getElementById('orderForm');
|
|
||||||
if (!orderForm) return;
|
|
||||||
|
|
||||||
// Validate all quantity inputs
|
|
||||||
const quantityInputs = orderForm.querySelectorAll('input[type="number"]');
|
|
||||||
quantityInputs.forEach(input => {
|
|
||||||
input.addEventListener('input', function() {
|
|
||||||
validateQuantityInput(this);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
// Add form submission handler
|
|
||||||
orderForm.addEventListener('submit', function(e) {
|
|
||||||
// Validate all inputs before submission
|
|
||||||
let isValid = true;
|
|
||||||
quantityInputs.forEach(input => {
|
|
||||||
if (!validateQuantityInput(input)) {
|
|
||||||
isValid = false;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!isValid) {
|
|
||||||
e.preventDefault();
|
|
||||||
showAlert(orderForm, 'danger', 'Please correct the errors in the form before submitting.');
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// Add "Add Item" button functionality if it exists
|
|
||||||
const addItemButton = document.getElementById('addOrderItem');
|
|
||||||
if (addItemButton) {
|
|
||||||
addItemButton.addEventListener('click', function() {
|
|
||||||
addOrderItem();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add "Remove Item" button functionality
|
|
||||||
const removeButtons = orderForm.querySelectorAll('.remove-order-item');
|
|
||||||
removeButtons.forEach(button => {
|
|
||||||
button.addEventListener('click', function() {
|
|
||||||
removeOrderItem(this);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Add a new order item row to the order form
|
|
||||||
*/
|
|
||||||
function addOrderItem() {
|
|
||||||
const orderItemsTable = document.querySelector('#orderForm table tbody');
|
|
||||||
const drinkTypeSelect = document.getElementById('drinkTypeSelect');
|
|
||||||
|
|
||||||
if (!orderItemsTable || !drinkTypeSelect) return;
|
|
||||||
|
|
||||||
// Get the selected drink type
|
|
||||||
const selectedOption = drinkTypeSelect.options[drinkTypeSelect.selectedIndex];
|
|
||||||
const drinkTypeId = selectedOption.value;
|
|
||||||
const drinkTypeName = selectedOption.text;
|
|
||||||
|
|
||||||
// Check if this drink type is already in the table
|
|
||||||
const existingRow = orderItemsTable.querySelector(`input[value="${drinkTypeId}"]`);
|
|
||||||
if (existingRow) {
|
|
||||||
showAlert(orderItemsTable.parentNode, 'warning', `${drinkTypeName} is already in the order.`);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create a new row
|
|
||||||
const newRow = document.createElement('tr');
|
|
||||||
const rowCount = orderItemsTable.querySelectorAll('tr').length;
|
|
||||||
|
|
||||||
newRow.innerHTML = `
|
|
||||||
<td>${drinkTypeName}</td>
|
|
||||||
<td>
|
|
||||||
<input type="hidden" name="items[${rowCount}][drinkTypeId]" value="${drinkTypeId}">
|
|
||||||
<input type="number" name="items[${rowCount}][quantity]"
|
|
||||||
class="form-control form-control-sm order-quantity"
|
|
||||||
value="1" min="0">
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<button type="button" class="btn btn-sm btn-danger remove-order-item">Remove</button>
|
|
||||||
</td>
|
|
||||||
`;
|
|
||||||
|
|
||||||
// Add event listeners to the new row
|
|
||||||
const quantityInput = newRow.querySelector('input[type="number"]');
|
|
||||||
quantityInput.addEventListener('input', function() {
|
|
||||||
validateQuantityInput(this);
|
|
||||||
});
|
|
||||||
|
|
||||||
const removeButton = newRow.querySelector('.remove-order-item');
|
|
||||||
removeButton.addEventListener('click', function() {
|
|
||||||
removeOrderItem(this);
|
|
||||||
});
|
|
||||||
|
|
||||||
// Add the row to the table
|
|
||||||
orderItemsTable.appendChild(newRow);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Remove an order item row from the order form
|
|
||||||
* @param {HTMLElement} button - The remove button that was clicked
|
|
||||||
*/
|
|
||||||
function removeOrderItem(button) {
|
|
||||||
const row = button.closest('tr');
|
|
||||||
if (row) {
|
|
||||||
row.remove();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Initialize order status forms with AJAX submission
|
|
||||||
*/
|
|
||||||
function initOrderStatusForms() {
|
|
||||||
const statusForm = document.querySelector('form[action^="/orders/update-status/"]');
|
|
||||||
if (!statusForm) return;
|
|
||||||
|
|
||||||
statusForm.addEventListener('submit', function(e) {
|
|
||||||
e.preventDefault();
|
|
||||||
|
|
||||||
// Get form data
|
|
||||||
const formData = new FormData(statusForm);
|
|
||||||
|
|
||||||
// Show loading state
|
|
||||||
const submitButton = statusForm.querySelector('button[type="submit"]');
|
|
||||||
const originalText = submitButton.textContent;
|
|
||||||
submitButton.textContent = 'Updating...';
|
|
||||||
submitButton.disabled = true;
|
|
||||||
|
|
||||||
// Send AJAX request
|
|
||||||
fetch(statusForm.action, {
|
|
||||||
method: 'POST',
|
|
||||||
body: formData
|
|
||||||
})
|
|
||||||
.then(response => {
|
|
||||||
if (!response.ok) {
|
|
||||||
throw new Error('Network response was not ok');
|
|
||||||
}
|
|
||||||
return response.json();
|
|
||||||
})
|
|
||||||
.then(data => {
|
|
||||||
// Reset button state
|
|
||||||
submitButton.textContent = originalText;
|
|
||||||
submitButton.disabled = false;
|
|
||||||
|
|
||||||
if (data.success) {
|
|
||||||
// Show success message
|
|
||||||
showAlert(statusForm.parentNode, 'success', 'Order status updated successfully.');
|
|
||||||
|
|
||||||
// Update the status badge
|
|
||||||
const statusBadge = document.querySelector('.badge');
|
|
||||||
if (statusBadge) {
|
|
||||||
// Remove old status classes
|
|
||||||
statusBadge.classList.remove('bg-primary', 'bg-warning', 'bg-info', 'bg-success');
|
|
||||||
|
|
||||||
// Add new status class
|
|
||||||
const newStatus = formData.get('status');
|
|
||||||
if (newStatus === 'new') {
|
|
||||||
statusBadge.classList.add('bg-primary');
|
|
||||||
} else if (newStatus === 'in_work') {
|
|
||||||
statusBadge.classList.add('bg-warning');
|
|
||||||
} else if (newStatus === 'ordered') {
|
|
||||||
statusBadge.classList.add('bg-info');
|
|
||||||
} else if (newStatus === 'fulfilled') {
|
|
||||||
statusBadge.classList.add('bg-success');
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update the text
|
|
||||||
statusBadge.textContent = newStatus.charAt(0).toUpperCase() + newStatus.slice(1);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// Show error message
|
|
||||||
showAlert(statusForm.parentNode, 'danger', `Error updating status: ${data.message}`);
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.catch(error => {
|
|
||||||
// Reset button state
|
|
||||||
submitButton.textContent = originalText;
|
|
||||||
submitButton.disabled = false;
|
|
||||||
|
|
||||||
// Show error message
|
|
||||||
showAlert(statusForm.parentNode, 'danger', `Error: ${error.message}`);
|
|
||||||
console.error('Error:', error);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Initialize inventory form with validation, highlighting for changed values, and AJAX submission
|
|
||||||
*/
|
|
||||||
function initInventoryForm() {
|
|
||||||
const inventoryForm = document.getElementById('inventoryForm');
|
|
||||||
if (!inventoryForm) return;
|
|
||||||
|
|
||||||
// Get all quantity inputs
|
|
||||||
const quantityInputs = inventoryForm.querySelectorAll('.inventory-quantity');
|
|
||||||
|
|
||||||
// Add event listener to each input for validation and highlighting
|
|
||||||
quantityInputs.forEach(input => {
|
|
||||||
// Store original value for comparison
|
|
||||||
const originalValue = parseInt(input.dataset.originalValue);
|
|
||||||
|
|
||||||
// Add input event listener for validation
|
|
||||||
input.addEventListener('input', function() {
|
|
||||||
// Validate input
|
|
||||||
validateQuantityInput(this);
|
|
||||||
|
|
||||||
// Highlight changed values
|
|
||||||
const currentValue = parseInt(this.value);
|
|
||||||
if (currentValue !== originalValue) {
|
|
||||||
this.classList.add('bg-warning', 'text-dark');
|
|
||||||
} else {
|
|
||||||
this.classList.remove('bg-warning', 'text-dark');
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
// Add submit handler
|
|
||||||
inventoryForm.addEventListener('submit', function(e) {
|
|
||||||
e.preventDefault();
|
|
||||||
|
|
||||||
// Validate all inputs before submission
|
|
||||||
let isValid = true;
|
|
||||||
quantityInputs.forEach(input => {
|
|
||||||
if (!validateQuantityInput(input)) {
|
|
||||||
isValid = false;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!isValid) {
|
|
||||||
showAlert(inventoryForm, 'danger', 'Please correct the errors in the form before submitting.');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check if any values have changed
|
|
||||||
let hasChanges = false;
|
|
||||||
quantityInputs.forEach(input => {
|
|
||||||
const originalValue = parseInt(input.dataset.originalValue);
|
|
||||||
const currentValue = parseInt(input.value);
|
|
||||||
if (currentValue !== originalValue) {
|
|
||||||
hasChanges = true;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!hasChanges) {
|
|
||||||
showAlert(inventoryForm, 'warning', 'No changes detected. Nothing to update.');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get form data
|
|
||||||
const formData = new FormData(inventoryForm);
|
|
||||||
|
|
||||||
// Add X-Requested-With header to indicate AJAX request
|
|
||||||
const headers = new Headers({
|
|
||||||
'X-Requested-With': 'XMLHttpRequest'
|
|
||||||
});
|
|
||||||
|
|
||||||
// Show loading state
|
|
||||||
const submitButton = inventoryForm.querySelector('button[type="submit"]');
|
|
||||||
const originalText = submitButton.textContent;
|
|
||||||
submitButton.textContent = 'Updating...';
|
|
||||||
submitButton.disabled = true;
|
|
||||||
|
|
||||||
// Send AJAX request
|
|
||||||
fetch(inventoryForm.action, {
|
|
||||||
method: 'POST',
|
|
||||||
headers: headers,
|
|
||||||
body: formData
|
|
||||||
})
|
|
||||||
.then(response => {
|
|
||||||
if (!response.ok) {
|
|
||||||
throw new Error('Network response was not ok');
|
|
||||||
}
|
|
||||||
return response.json();
|
|
||||||
})
|
|
||||||
.then(data => {
|
|
||||||
// Reset button state
|
|
||||||
submitButton.textContent = originalText;
|
|
||||||
submitButton.disabled = false;
|
|
||||||
|
|
||||||
if (data.success) {
|
|
||||||
// Show success message
|
|
||||||
showAlert(inventoryForm, 'success', 'Inventory updated successfully.');
|
|
||||||
|
|
||||||
// Update original values and remove highlighting
|
|
||||||
quantityInputs.forEach(input => {
|
|
||||||
const drinkTypeId = input.previousElementSibling.value;
|
|
||||||
const updatedItem = data.updatedItems.find(item => item.drinkTypeId == drinkTypeId);
|
|
||||||
|
|
||||||
if (updatedItem) {
|
|
||||||
input.dataset.originalValue = updatedItem.newQuantity;
|
|
||||||
input.classList.remove('bg-warning', 'text-dark');
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// Show update results
|
|
||||||
const updateResults = document.getElementById('updateResults');
|
|
||||||
const updateResultsContent = document.getElementById('updateResultsContent');
|
|
||||||
|
|
||||||
if (updateResults && updateResultsContent) {
|
|
||||||
updateResults.classList.remove('d-none');
|
|
||||||
|
|
||||||
let resultsHtml = '<h6>The following items were updated:</h6><ul>';
|
|
||||||
data.updatedItems.forEach(item => {
|
|
||||||
resultsHtml += `<li>${item.name}: ${item.oldQuantity} → ${item.newQuantity}</li>`;
|
|
||||||
});
|
|
||||||
resultsHtml += '</ul>';
|
|
||||||
|
|
||||||
updateResultsContent.innerHTML = resultsHtml;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// Show error message
|
|
||||||
let errorMessage = 'Error updating inventory.';
|
|
||||||
if (data.errors && data.errors.length > 0) {
|
|
||||||
errorMessage += ' ' + data.errors.join(' ');
|
|
||||||
}
|
|
||||||
showAlert(inventoryForm, 'danger', errorMessage);
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.catch(error => {
|
|
||||||
// Reset button state
|
|
||||||
submitButton.textContent = originalText;
|
|
||||||
submitButton.disabled = false;
|
|
||||||
|
|
||||||
// Show error message
|
|
||||||
showAlert(inventoryForm, 'danger', `Error: ${error.message}`);
|
|
||||||
console.error('Error:', error);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Initialize settings form with validation and AJAX submission
|
|
||||||
*/
|
|
||||||
function initSettingsForm() {
|
|
||||||
const settingsForm = document.getElementById('settings-form');
|
|
||||||
if (!settingsForm) return;
|
|
||||||
|
|
||||||
// Get all inputs
|
|
||||||
const textInputs = settingsForm.querySelectorAll('input[type="text"]');
|
|
||||||
const numberInputs = settingsForm.querySelectorAll('input[type="number"]');
|
|
||||||
|
|
||||||
// Add event listeners for validation
|
|
||||||
textInputs.forEach(input => {
|
|
||||||
input.addEventListener('input', function() {
|
|
||||||
validateSettingTextInput(this);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
numberInputs.forEach(input => {
|
|
||||||
input.addEventListener('input', function() {
|
|
||||||
validateSettingNumberInput(this);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
// Add submit handler
|
|
||||||
settingsForm.addEventListener('submit', function(e) {
|
|
||||||
// Validate all inputs before submission
|
|
||||||
let isValid = true;
|
|
||||||
|
|
||||||
textInputs.forEach(input => {
|
|
||||||
if (!validateSettingTextInput(input)) {
|
|
||||||
isValid = false;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
numberInputs.forEach(input => {
|
|
||||||
if (!validateSettingNumberInput(input)) {
|
|
||||||
isValid = false;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!isValid) {
|
|
||||||
e.preventDefault();
|
|
||||||
showAlert(settingsForm, 'danger', 'Please correct the errors in the form before submitting.');
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// Reset to defaults button
|
|
||||||
const resetButton = document.getElementById('reset-defaults');
|
|
||||||
if (resetButton) {
|
|
||||||
resetButton.addEventListener('click', function() {
|
|
||||||
if (confirm('Are you sure you want to reset all settings to their default values?')) {
|
|
||||||
window.location.href = '/settings/reset';
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Validate a text input for settings
|
|
||||||
* @param {HTMLInputElement} input - The input to validate
|
|
||||||
* @returns {boolean} - Whether the input is valid
|
|
||||||
*/
|
|
||||||
function validateSettingTextInput(input) {
|
|
||||||
// Check if value is empty
|
|
||||||
if (input.value.trim() === '') {
|
|
||||||
input.classList.add('is-invalid');
|
|
||||||
showInputError(input, 'This field cannot be empty');
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Input is valid
|
|
||||||
input.classList.remove('is-invalid');
|
|
||||||
input.classList.add('is-valid');
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Validate a number input for settings
|
|
||||||
* @param {HTMLInputElement} input - The input to validate
|
|
||||||
* @returns {boolean} - Whether the input is valid
|
|
||||||
*/
|
|
||||||
function validateSettingNumberInput(input) {
|
|
||||||
const value = parseInt(input.value);
|
|
||||||
|
|
||||||
// Check if value is a number
|
|
||||||
if (isNaN(value)) {
|
|
||||||
input.classList.add('is-invalid');
|
|
||||||
showInputError(input, 'Please enter a valid number');
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check if value is positive
|
|
||||||
if (value < 1) {
|
|
||||||
input.classList.add('is-invalid');
|
|
||||||
showInputError(input, 'Please enter a positive number');
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Input is valid
|
|
||||||
input.classList.remove('is-invalid');
|
|
||||||
input.classList.add('is-valid');
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Initialize drink type form with validation and AJAX name uniqueness check
|
|
||||||
*/
|
|
||||||
function initDrinkTypeForm() {
|
|
||||||
const drinkTypeForm = document.getElementById('drink-type-form');
|
|
||||||
if (!drinkTypeForm) return;
|
|
||||||
|
|
||||||
// Get form inputs
|
|
||||||
const nameInput = drinkTypeForm.querySelector('input[name="name"]');
|
|
||||||
const descriptionInput = drinkTypeForm.querySelector('textarea[name="description"]');
|
|
||||||
const desiredStockInput = drinkTypeForm.querySelector('input[name="desired_stock"]');
|
|
||||||
|
|
||||||
// Store original name for edit mode
|
|
||||||
const originalName = nameInput ? nameInput.value : '';
|
|
||||||
|
|
||||||
// Add validation for name input
|
|
||||||
if (nameInput) {
|
|
||||||
nameInput.addEventListener('input', function() {
|
|
||||||
validateDrinkTypeName(this, originalName);
|
|
||||||
});
|
|
||||||
|
|
||||||
// Check name uniqueness on blur
|
|
||||||
nameInput.addEventListener('blur', function() {
|
|
||||||
checkDrinkTypeNameUniqueness(this, originalName);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add validation for desired stock input
|
|
||||||
if (desiredStockInput) {
|
|
||||||
desiredStockInput.addEventListener('input', function() {
|
|
||||||
validateDesiredStock(this);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add form submission handler
|
|
||||||
drinkTypeForm.addEventListener('submit', function(e) {
|
|
||||||
let isValid = true;
|
|
||||||
|
|
||||||
// Validate name
|
|
||||||
if (nameInput && !validateDrinkTypeName(nameInput, originalName)) {
|
|
||||||
isValid = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Validate desired stock
|
|
||||||
if (desiredStockInput && !validateDesiredStock(desiredStockInput)) {
|
|
||||||
isValid = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!isValid) {
|
|
||||||
e.preventDefault();
|
|
||||||
showAlert(drinkTypeForm, 'danger', 'Please correct the errors in the form before submitting.');
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Validate a drink type name
|
|
||||||
* @param {HTMLInputElement} input - The input to validate
|
|
||||||
* @param {string} originalName - The original name (for edit mode)
|
|
||||||
* @returns {boolean} - Whether the input is valid
|
|
||||||
*/
|
|
||||||
function validateDrinkTypeName(input, originalName) {
|
|
||||||
const value = input.value.trim();
|
|
||||||
|
|
||||||
// Check if value is empty
|
|
||||||
if (value === '') {
|
|
||||||
input.classList.add('is-invalid');
|
|
||||||
showInputError(input, 'Name is required');
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check if value is too long
|
|
||||||
if (value.length > 255) {
|
|
||||||
input.classList.add('is-invalid');
|
|
||||||
showInputError(input, 'Name cannot be longer than 255 characters');
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Input is valid
|
|
||||||
input.classList.remove('is-invalid');
|
|
||||||
input.classList.add('is-valid');
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Check if a drink type name is unique
|
|
||||||
* @param {HTMLInputElement} input - The input to check
|
|
||||||
* @param {string} originalName - The original name (for edit mode)
|
|
||||||
*/
|
|
||||||
function checkDrinkTypeNameUniqueness(input, originalName) {
|
|
||||||
const value = input.value.trim();
|
|
||||||
|
|
||||||
// Skip check if name hasn't changed or is empty
|
|
||||||
if (value === '' || value === originalName) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Show loading state
|
|
||||||
input.classList.add('is-loading');
|
|
||||||
|
|
||||||
// Check if name already exists
|
|
||||||
fetch(`/api/drink-types/check-name?name=${encodeURIComponent(value)}`)
|
|
||||||
.then(response => {
|
|
||||||
if (!response.ok) {
|
|
||||||
throw new Error('Network response was not ok');
|
|
||||||
}
|
|
||||||
return response.json();
|
|
||||||
})
|
|
||||||
.then(data => {
|
|
||||||
// Remove loading state
|
|
||||||
input.classList.remove('is-loading');
|
|
||||||
|
|
||||||
if (data.exists) {
|
|
||||||
input.classList.add('is-invalid');
|
|
||||||
showInputError(input, 'A drink type with this name already exists');
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.catch(error => {
|
|
||||||
// Remove loading state
|
|
||||||
input.classList.remove('is-loading');
|
|
||||||
console.error('Error checking name uniqueness:', error);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Validate a desired stock input
|
|
||||||
* @param {HTMLInputElement} input - The input to validate
|
|
||||||
* @returns {boolean} - Whether the input is valid
|
|
||||||
*/
|
|
||||||
function validateDesiredStock(input) {
|
|
||||||
const value = parseInt(input.value);
|
|
||||||
|
|
||||||
// Check if value is a number
|
|
||||||
if (isNaN(value)) {
|
|
||||||
input.classList.add('is-invalid');
|
|
||||||
showInputError(input, 'Desired stock must be a number');
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check if value is non-negative
|
|
||||||
if (value < 0) {
|
|
||||||
input.classList.add('is-invalid');
|
|
||||||
showInputError(input, 'Desired stock cannot be negative');
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Input is valid
|
|
||||||
input.classList.remove('is-invalid');
|
|
||||||
input.classList.add('is-valid');
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
1
public/assets/js/htmx.min.js
vendored
Normal file
1
public/assets/js/htmx.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
|
@ -25,4 +25,5 @@ return RectorConfig::configure()
|
||||||
->withSkip([
|
->withSkip([
|
||||||
FirstClassCallableRector::class,
|
FirstClassCallableRector::class,
|
||||||
])
|
])
|
||||||
|
->withImportNames(removeUnusedImports: true)
|
||||||
;
|
;
|
||||||
|
|
|
@ -6,7 +6,7 @@ namespace App\Controller;
|
||||||
|
|
||||||
use App\Entity\DrinkType;
|
use App\Entity\DrinkType;
|
||||||
use App\Form\DrinkTypeForm;
|
use App\Form\DrinkTypeForm;
|
||||||
use App\Repository\DrinkTypeRepository;
|
use App\Repository\PropertyChangeLogRepository;
|
||||||
use App\Service\InventoryService;
|
use App\Service\InventoryService;
|
||||||
use Doctrine\ORM\EntityManagerInterface;
|
use Doctrine\ORM\EntityManagerInterface;
|
||||||
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
|
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
|
||||||
|
@ -21,7 +21,7 @@ final class DrinkTypeController extends AbstractController
|
||||||
public function index(InventoryService $inventoryService): Response
|
public function index(InventoryService $inventoryService): Response
|
||||||
{
|
{
|
||||||
return $this->render('drink_type/index.html.twig', [
|
return $this->render('drink_type/index.html.twig', [
|
||||||
'drink_stocks' => $inventoryService->getAllDrinkTypesWithStockLevels(true)
|
'drink_stocks' => $inventoryService->getAllDrinkTypesWithStockLevels(true),
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -46,10 +46,26 @@ final class DrinkTypeController extends AbstractController
|
||||||
}
|
}
|
||||||
|
|
||||||
#[Route(path: '/{id}', name: 'app_drink_type_show', methods: ['GET'])]
|
#[Route(path: '/{id}', name: 'app_drink_type_show', methods: ['GET'])]
|
||||||
public function show(DrinkType $drinkType): Response
|
public function show(DrinkType $drinkType, PropertyChangeLogRepository $propertyChangeLogRepository): Response
|
||||||
{
|
{
|
||||||
|
// Get orders that contain this drink type
|
||||||
|
$orderItems = $drinkType->getOrderItems();
|
||||||
|
|
||||||
|
// Get inventory history for this drink type
|
||||||
|
$inventoryRecords = $drinkType->getInventoryRecords();
|
||||||
|
|
||||||
|
// Get desired stock history from PropertyChangeLog
|
||||||
|
$desiredStockHistory = $propertyChangeLogRepository->findBy([
|
||||||
|
'entityClass' => DrinkType::class,
|
||||||
|
'propertyName' => 'desiredStock',
|
||||||
|
'entityId' => $drinkType->getId()
|
||||||
|
], ['changeDate' => 'DESC']);
|
||||||
|
|
||||||
return $this->render('drink_type/show.html.twig', [
|
return $this->render('drink_type/show.html.twig', [
|
||||||
'drink_type' => $drinkType,
|
'drink_type' => $drinkType,
|
||||||
|
'order_items' => $orderItems,
|
||||||
|
'inventory_records' => $inventoryRecords,
|
||||||
|
'desired_stock_history' => $desiredStockHistory,
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -24,12 +24,12 @@ final class Index extends AbstractController
|
||||||
|
|
||||||
$low = array_filter(
|
$low = array_filter(
|
||||||
$drinkStocks,
|
$drinkStocks,
|
||||||
fn (DrinkStock $stock): bool => $stock->stock === StockState::LOW || $stock->stock === StockState::CRITICAL,
|
fn(DrinkStock $stock): bool => $stock->stock === StockState::LOW || $stock->stock === StockState::CRITICAL,
|
||||||
);
|
);
|
||||||
|
|
||||||
return $this->render('index.html.twig', [
|
return $this->render('index.html.twig', [
|
||||||
'drinkStocks' => $drinkStocks,
|
'drinkStocks' => $drinkStocks,
|
||||||
'low' => $low
|
'low' => $low,
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,10 +1,14 @@
|
||||||
<?php
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace App\Controller;
|
namespace App\Controller;
|
||||||
|
|
||||||
|
use App\Entity\DrinkType;
|
||||||
use App\Entity\InventoryRecord;
|
use App\Entity\InventoryRecord;
|
||||||
use App\Form\InventoryRecordForm;
|
use App\Form\InventoryRecordForm;
|
||||||
use App\Repository\InventoryRecordRepository;
|
use App\Repository\InventoryRecordRepository;
|
||||||
|
use App\Service\InventoryService;
|
||||||
use Doctrine\ORM\EntityManagerInterface;
|
use Doctrine\ORM\EntityManagerInterface;
|
||||||
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
|
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
|
||||||
use Symfony\Component\HttpFoundation\Request;
|
use Symfony\Component\HttpFoundation\Request;
|
||||||
|
@ -22,10 +26,11 @@ final class InventoryRecordController extends AbstractController
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[Route('/new', name: 'app_inventory_record_new', methods: ['GET', 'POST'])]
|
#[Route('/new/{drinkType}', name: 'app_inventory_record_new', methods: ['GET', 'POST'])]
|
||||||
public function new(Request $request, EntityManagerInterface $entityManager): Response
|
public function new(Request $request, EntityManagerInterface $entityManager, DrinkType $drinkType): Response
|
||||||
{
|
{
|
||||||
$inventoryRecord = new InventoryRecord();
|
$inventoryRecord = new InventoryRecord();
|
||||||
|
$inventoryRecord->setDrinkType($drinkType);
|
||||||
$form = $this->createForm(InventoryRecordForm::class, $inventoryRecord);
|
$form = $this->createForm(InventoryRecordForm::class, $inventoryRecord);
|
||||||
$form->handleRequest($request);
|
$form->handleRequest($request);
|
||||||
|
|
||||||
|
@ -33,7 +38,22 @@ final class InventoryRecordController extends AbstractController
|
||||||
$entityManager->persist($inventoryRecord);
|
$entityManager->persist($inventoryRecord);
|
||||||
$entityManager->flush();
|
$entityManager->flush();
|
||||||
|
|
||||||
return $this->redirectToRoute('app_inventory_record_index', [], Response::HTTP_SEE_OTHER);
|
// If it's an HTMX request, return a redirect that HTMX will follow
|
||||||
|
if ($request->headers->has('HX-Request')) {
|
||||||
|
$response = new Response('', Response::HTTP_SEE_OTHER);
|
||||||
|
$response->headers->set('HX-Redirect', $this->generateUrl('app_index'));
|
||||||
|
return $response;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->redirectToRoute('app_index', [], Response::HTTP_SEE_OTHER);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if it's an HTMX request
|
||||||
|
if ($request->headers->has('HX-Request')) {
|
||||||
|
return $this->render('inventory_record/_modal_form.html.twig', [
|
||||||
|
'inventory_record' => $inventoryRecord,
|
||||||
|
'form' => $form,
|
||||||
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
return $this->render('inventory_record/new.html.twig', [
|
return $this->render('inventory_record/new.html.twig', [
|
||||||
|
@ -42,6 +62,26 @@ final class InventoryRecordController extends AbstractController
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[Route('/modal/{drinkType}', name: 'app_inventory_record_modal', methods: ['GET'])]
|
||||||
|
public function modal(DrinkType $drinkType, InventoryService $inventoryService): Response
|
||||||
|
{
|
||||||
|
$inventoryRecord = new InventoryRecord();
|
||||||
|
$inventoryRecord->setDrinkType($drinkType);
|
||||||
|
$inventoryRecord->setQuantity(
|
||||||
|
$inventoryService->getLatestInventoryRecord($drinkType)->getQuantity() ?? 0
|
||||||
|
);
|
||||||
|
$form = $this->createForm(InventoryRecordForm::class, $inventoryRecord, [
|
||||||
|
'action' => $this->generateUrl('app_inventory_record_new', [
|
||||||
|
'drinkType' => $drinkType->getId(),
|
||||||
|
]),
|
||||||
|
]);
|
||||||
|
|
||||||
|
return $this->render('inventory_record/_modal_form.html.twig', [
|
||||||
|
'inventory_record' => $inventoryRecord,
|
||||||
|
'form' => $form,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
#[Route('/{id}', name: 'app_inventory_record_show', methods: ['GET'])]
|
#[Route('/{id}', name: 'app_inventory_record_show', methods: ['GET'])]
|
||||||
public function show(InventoryRecord $inventoryRecord): Response
|
public function show(InventoryRecord $inventoryRecord): Response
|
||||||
{
|
{
|
||||||
|
@ -71,7 +111,7 @@ final class InventoryRecordController extends AbstractController
|
||||||
#[Route('/{id}', name: 'app_inventory_record_delete', methods: ['POST'])]
|
#[Route('/{id}', name: 'app_inventory_record_delete', methods: ['POST'])]
|
||||||
public function delete(Request $request, InventoryRecord $inventoryRecord, EntityManagerInterface $entityManager): Response
|
public function delete(Request $request, InventoryRecord $inventoryRecord, EntityManagerInterface $entityManager): Response
|
||||||
{
|
{
|
||||||
if ($this->isCsrfTokenValid('delete'.$inventoryRecord->getId(), $request->getPayload()->getString('_token'))) {
|
if ($this->isCsrfTokenValid('delete' . $inventoryRecord->getId(), $request->getPayload()->getString('_token'))) {
|
||||||
$entityManager->remove($inventoryRecord);
|
$entityManager->remove($inventoryRecord);
|
||||||
$entityManager->flush();
|
$entityManager->flush();
|
||||||
}
|
}
|
||||||
|
|
83
src/Controller/OrderController.php
Normal file
83
src/Controller/OrderController.php
Normal file
|
@ -0,0 +1,83 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace App\Controller;
|
||||||
|
|
||||||
|
use App\Entity\Order;
|
||||||
|
use App\Form\OrderForm;
|
||||||
|
use App\Repository\OrderRepository;
|
||||||
|
use Doctrine\ORM\EntityManagerInterface;
|
||||||
|
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
|
||||||
|
use Symfony\Component\HttpFoundation\Request;
|
||||||
|
use Symfony\Component\HttpFoundation\Response;
|
||||||
|
use Symfony\Component\Routing\Attribute\Route;
|
||||||
|
|
||||||
|
#[Route('/order')]
|
||||||
|
final class OrderController extends AbstractController
|
||||||
|
{
|
||||||
|
#[Route(name: 'app_order_index', methods: ['GET'])]
|
||||||
|
public function index(OrderRepository $orderRepository): Response
|
||||||
|
{
|
||||||
|
return $this->render('order/index.html.twig', [
|
||||||
|
'orders' => $orderRepository->findAll(),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[Route('/new', name: 'app_order_new', methods: ['GET', 'POST'])]
|
||||||
|
public function new(Request $request, EntityManagerInterface $entityManager): Response
|
||||||
|
{
|
||||||
|
$order = new Order();
|
||||||
|
$form = $this->createForm(OrderForm::class, $order);
|
||||||
|
$form->handleRequest($request);
|
||||||
|
|
||||||
|
if ($form->isSubmitted() && $form->isValid()) {
|
||||||
|
$entityManager->persist($order);
|
||||||
|
$entityManager->flush();
|
||||||
|
|
||||||
|
return $this->redirectToRoute('app_order_index', [], Response::HTTP_SEE_OTHER);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->render('order/new.html.twig', [
|
||||||
|
'order' => $order,
|
||||||
|
'form' => $form,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[Route('/{id}', name: 'app_order_show', methods: ['GET'])]
|
||||||
|
public function show(Order $order): Response
|
||||||
|
{
|
||||||
|
return $this->render('order/show.html.twig', [
|
||||||
|
'order' => $order,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[Route('/{id}/edit', name: 'app_order_edit', methods: ['GET', 'POST'])]
|
||||||
|
public function edit(Request $request, Order $order, EntityManagerInterface $entityManager): Response
|
||||||
|
{
|
||||||
|
$form = $this->createForm(OrderForm::class, $order);
|
||||||
|
$form->handleRequest($request);
|
||||||
|
|
||||||
|
if ($form->isSubmitted() && $form->isValid()) {
|
||||||
|
$entityManager->flush();
|
||||||
|
|
||||||
|
return $this->redirectToRoute('app_order_index', [], Response::HTTP_SEE_OTHER);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->render('order/edit.html.twig', [
|
||||||
|
'order' => $order,
|
||||||
|
'form' => $form,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[Route('/{id}', name: 'app_order_delete', methods: ['POST'])]
|
||||||
|
public function delete(Request $request, Order $order, EntityManagerInterface $entityManager): Response
|
||||||
|
{
|
||||||
|
if ($this->isCsrfTokenValid('delete' . $order->getId(), $request->getPayload()->getString('_token'))) {
|
||||||
|
$entityManager->remove($order);
|
||||||
|
$entityManager->flush();
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->redirectToRoute('app_order_index', [], Response::HTTP_SEE_OTHER);
|
||||||
|
}
|
||||||
|
}
|
|
@ -9,6 +9,7 @@ use DateTimeImmutable;
|
||||||
use Doctrine\Common\Collections\ArrayCollection;
|
use Doctrine\Common\Collections\ArrayCollection;
|
||||||
use Doctrine\Common\Collections\Collection;
|
use Doctrine\Common\Collections\Collection;
|
||||||
use Doctrine\ORM\Mapping as ORM;
|
use Doctrine\ORM\Mapping as ORM;
|
||||||
|
use App\Entity\PropertyChangeLog;
|
||||||
|
|
||||||
#[ORM\Entity(repositoryClass: DrinkTypeRepository::class)]
|
#[ORM\Entity(repositoryClass: DrinkTypeRepository::class)]
|
||||||
#[ORM\Table(name: 'drink_type')]
|
#[ORM\Table(name: 'drink_type')]
|
||||||
|
|
|
@ -32,7 +32,7 @@ class OrderItem
|
||||||
|
|
||||||
#[ORM\ManyToOne(targetEntity: Order::class, inversedBy: 'orderItems')]
|
#[ORM\ManyToOne(targetEntity: Order::class, inversedBy: 'orderItems')]
|
||||||
#[ORM\JoinColumn(name: 'order_id', referencedColumnName: 'id', nullable: false)]
|
#[ORM\JoinColumn(name: 'order_id', referencedColumnName: 'id', nullable: false)]
|
||||||
private null|Order $order;
|
private null|Order $order = null;
|
||||||
|
|
||||||
public function __construct(
|
public function __construct(
|
||||||
) {
|
) {
|
||||||
|
@ -53,7 +53,7 @@ class OrderItem
|
||||||
public function setOrder(null|Order $order): self
|
public function setOrder(null|Order $order): self
|
||||||
{
|
{
|
||||||
// Remove from old order if exists
|
// Remove from old order if exists
|
||||||
if (isset($this->order) && $this->order instanceof Order && $this->order !== $order) {
|
if ($this->order instanceof Order && $this->order instanceof Order && $this->order !== $order) {
|
||||||
$this->order->removeOrderItem($this);
|
$this->order->removeOrderItem($this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
92
src/Entity/PropertyChangeLog.php
Normal file
92
src/Entity/PropertyChangeLog.php
Normal file
|
@ -0,0 +1,92 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace App\Entity;
|
||||||
|
|
||||||
|
use App\Repository\PropertyChangeLogRepository;
|
||||||
|
use Doctrine\ORM\Mapping\Column;
|
||||||
|
use Doctrine\ORM\Mapping\GeneratedValue;
|
||||||
|
use DateTimeImmutable;
|
||||||
|
use Doctrine\ORM\Mapping\Id;
|
||||||
|
use Doctrine\ORM\Mapping as ORM;
|
||||||
|
|
||||||
|
#[ORM\Entity(repositoryClass: PropertyChangeLogRepository::class)]
|
||||||
|
#[ORM\Table(name: 'property_change_log')]
|
||||||
|
final class PropertyChangeLog
|
||||||
|
{
|
||||||
|
#[Id]
|
||||||
|
#[GeneratedValue]
|
||||||
|
#[Column(type: 'integer')]
|
||||||
|
private int|null $id = null;
|
||||||
|
#[Column(type: 'string', length: 255)]
|
||||||
|
private string $propertyName;
|
||||||
|
#[Column(type: 'string', length: 255)]
|
||||||
|
private string $entityClass;
|
||||||
|
#[Column(type: 'integer', nullable: true)]
|
||||||
|
private ?int $entityId = null;
|
||||||
|
#[Column(type: 'string', length: 255)]
|
||||||
|
private string $newValue;
|
||||||
|
#[Column(type: 'datetime_immutable')]
|
||||||
|
private DateTimeImmutable $changeDate;
|
||||||
|
|
||||||
|
public function __construct()
|
||||||
|
{
|
||||||
|
$this->changeDate = new DateTimeImmutable();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getId(): ?int
|
||||||
|
{
|
||||||
|
return $this->id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getPropertyName(): string
|
||||||
|
{
|
||||||
|
return $this->propertyName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setPropertyName(string $propertyName): void
|
||||||
|
{
|
||||||
|
$this->propertyName = $propertyName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getEntityClass(): string
|
||||||
|
{
|
||||||
|
return $this->entityClass;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setEntityClass(string $entityClass): void
|
||||||
|
{
|
||||||
|
$this->entityClass = $entityClass;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getEntityId(): ?int
|
||||||
|
{
|
||||||
|
return $this->entityId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setEntityId(?int $entityId): void
|
||||||
|
{
|
||||||
|
$this->entityId = $entityId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getNewValue(): string
|
||||||
|
{
|
||||||
|
return $this->newValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setNewValue(string $newValue): void
|
||||||
|
{
|
||||||
|
$this->newValue = $newValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getChangeDate(): DateTimeImmutable
|
||||||
|
{
|
||||||
|
return $this->changeDate;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setChangeDate(DateTimeImmutable $changeDate): void
|
||||||
|
{
|
||||||
|
$this->changeDate = $changeDate;
|
||||||
|
}
|
||||||
|
}
|
|
@ -6,6 +6,7 @@ namespace App\Form;
|
||||||
|
|
||||||
use App\Entity\DrinkType;
|
use App\Entity\DrinkType;
|
||||||
use Symfony\Component\Form\AbstractType;
|
use Symfony\Component\Form\AbstractType;
|
||||||
|
use Symfony\Component\Form\Extension\Core\Type\NumberType;
|
||||||
use Symfony\Component\Form\FormBuilderInterface;
|
use Symfony\Component\Form\FormBuilderInterface;
|
||||||
use Symfony\Component\OptionsResolver\OptionsResolver;
|
use Symfony\Component\OptionsResolver\OptionsResolver;
|
||||||
|
|
||||||
|
@ -13,7 +14,7 @@ class DrinkTypeForm extends AbstractType
|
||||||
{
|
{
|
||||||
public function buildForm(FormBuilderInterface $builder, array $options): void
|
public function buildForm(FormBuilderInterface $builder, array $options): void
|
||||||
{
|
{
|
||||||
$builder->add('name')->add('description')->add('desiredStock');
|
$builder->add('name')->add('description')->add('desiredStock', NumberType::class);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function configureOptions(OptionsResolver $resolver): void
|
public function configureOptions(OptionsResolver $resolver): void
|
||||||
|
|
|
@ -1,11 +1,14 @@
|
||||||
<?php
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace App\Form;
|
namespace App\Form;
|
||||||
|
|
||||||
use App\Entity\DrinkType;
|
use App\Entity\DrinkType;
|
||||||
use App\Entity\InventoryRecord;
|
use App\Entity\InventoryRecord;
|
||||||
use Symfony\Bridge\Doctrine\Form\Type\EntityType;
|
use Symfony\Bridge\Doctrine\Form\Type\EntityType;
|
||||||
use Symfony\Component\Form\AbstractType;
|
use Symfony\Component\Form\AbstractType;
|
||||||
|
use Symfony\Component\Form\Extension\Core\Type\NumberType;
|
||||||
use Symfony\Component\Form\FormBuilderInterface;
|
use Symfony\Component\Form\FormBuilderInterface;
|
||||||
use Symfony\Component\OptionsResolver\OptionsResolver;
|
use Symfony\Component\OptionsResolver\OptionsResolver;
|
||||||
|
|
||||||
|
@ -14,19 +17,14 @@ class InventoryRecordForm extends AbstractType
|
||||||
public function buildForm(FormBuilderInterface $builder, array $options): void
|
public function buildForm(FormBuilderInterface $builder, array $options): void
|
||||||
{
|
{
|
||||||
$builder
|
$builder
|
||||||
->add('quantity')
|
->add('quantity', NumberType::class)
|
||||||
->add('timestamp', null, [
|
|
||||||
'widget' => 'single_text',
|
|
||||||
])
|
|
||||||
->add('createdAt', null, [
|
|
||||||
'widget' => 'single_text',
|
|
||||||
])
|
|
||||||
->add('updatedAt', null, [
|
|
||||||
'widget' => 'single_text',
|
|
||||||
])
|
|
||||||
->add('drinkType', EntityType::class, [
|
->add('drinkType', EntityType::class, [
|
||||||
'class' => DrinkType::class,
|
'class' => DrinkType::class,
|
||||||
'choice_label' => 'id',
|
'choice_label' => 'id',
|
||||||
|
'attr' => [
|
||||||
|
'style' => 'display: none;',
|
||||||
|
],
|
||||||
|
'label' => false,
|
||||||
])
|
])
|
||||||
;
|
;
|
||||||
}
|
}
|
||||||
|
|
27
src/Form/OrderForm.php
Normal file
27
src/Form/OrderForm.php
Normal file
|
@ -0,0 +1,27 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace App\Form;
|
||||||
|
|
||||||
|
use App\Entity\Order;
|
||||||
|
use Symfony\Component\Form\AbstractType;
|
||||||
|
use Symfony\Component\Form\FormBuilderInterface;
|
||||||
|
use Symfony\Component\OptionsResolver\OptionsResolver;
|
||||||
|
|
||||||
|
class OrderForm extends AbstractType
|
||||||
|
{
|
||||||
|
public function buildForm(FormBuilderInterface $builder, array $options): void
|
||||||
|
{
|
||||||
|
$builder
|
||||||
|
->add('status')
|
||||||
|
;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function configureOptions(OptionsResolver $resolver): void
|
||||||
|
{
|
||||||
|
$resolver->setDefaults([
|
||||||
|
'data_class' => Order::class,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
19
src/Repository/PropertyChangeLogRepository.php
Normal file
19
src/Repository/PropertyChangeLogRepository.php
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace App\Repository;
|
||||||
|
|
||||||
|
use App\Entity\PropertyChangeLog;
|
||||||
|
use Doctrine\ORM\EntityManagerInterface;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @extends AbstractRepository<PropertyChangeLog>
|
||||||
|
*/
|
||||||
|
final class PropertyChangeLogRepository extends AbstractRepository
|
||||||
|
{
|
||||||
|
public function __construct(EntityManagerInterface $entityManager)
|
||||||
|
{
|
||||||
|
parent::__construct($entityManager, $entityManager->getClassMetadata(PropertyChangeLog::class));
|
||||||
|
}
|
||||||
|
}
|
|
@ -12,6 +12,7 @@
|
||||||
{% block javascripts %}
|
{% block javascripts %}
|
||||||
<script src="/assets/js/app.js"></script>
|
<script src="/assets/js/app.js"></script>
|
||||||
<script src="/assets/js/bootstrap.bundle.min.js"></script>
|
<script src="/assets/js/bootstrap.bundle.min.js"></script>
|
||||||
|
<script src="/assets/js/htmx.min.js"></script>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
|
@ -34,5 +35,20 @@
|
||||||
<div class="container mt-4">
|
<div class="container mt-4">
|
||||||
{% block body %}{% endblock %}
|
{% block body %}{% endblock %}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- Modal container for HTMX -->
|
||||||
|
<div class="modal fade" id="htmxModal" tabindex="-1" aria-labelledby="htmxModalLabel" aria-hidden="true">
|
||||||
|
<div class="modal-dialog">
|
||||||
|
<div class="modal-content">
|
||||||
|
<div class="modal-header">
|
||||||
|
<h5 class="modal-title" id="htmxModalLabel">Modal title</h5>
|
||||||
|
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body" id="htmxModalBody">
|
||||||
|
<!-- Content will be loaded here by HTMX -->
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
|
@ -8,9 +8,6 @@
|
||||||
<table class="table">
|
<table class="table">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th>Id</th>
|
|
||||||
<th>CreatedAt</th>
|
|
||||||
<th>UpdatedAt</th>
|
|
||||||
<th>Name</th>
|
<th>Name</th>
|
||||||
<th>Description</th>
|
<th>Description</th>
|
||||||
<th>DesiredStock</th>
|
<th>DesiredStock</th>
|
||||||
|
@ -22,9 +19,6 @@
|
||||||
{% for drink_stock in drink_stocks %}
|
{% for drink_stock in drink_stocks %}
|
||||||
{% set drink_type = drink_stock.record.drinkType %}
|
{% set drink_type = drink_stock.record.drinkType %}
|
||||||
<tr>
|
<tr>
|
||||||
<td>{{ drink_type.id }}</td>
|
|
||||||
<td>{{ drink_type.createdAt ? drink_type.createdAt|date('Y-m-d H:i:s') : '' }}</td>
|
|
||||||
<td>{{ drink_type.updatedAt ? drink_type.updatedAt|date('Y-m-d H:i:s') : '' }}</td>
|
|
||||||
<td>{{ drink_type.name }}</td>
|
<td>{{ drink_type.name }}</td>
|
||||||
<td>{{ drink_type.description }}</td>
|
<td>{{ drink_type.description }}</td>
|
||||||
<td>{{ drink_type.desiredStock }}</td>
|
<td>{{ drink_type.desiredStock }}</td>
|
||||||
|
@ -36,7 +30,7 @@
|
||||||
</tr>
|
</tr>
|
||||||
{% else %}
|
{% else %}
|
||||||
<tr>
|
<tr>
|
||||||
<td colspan="7">no records found</td>
|
<td colspan="5">no records found</td>
|
||||||
</tr>
|
</tr>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</tbody>
|
</tbody>
|
||||||
|
|
|
@ -1,10 +1,18 @@
|
||||||
{% extends 'base.html.twig' %}
|
{% extends 'base.html.twig' %}
|
||||||
|
|
||||||
{% block title %}DrinkType{% endblock %}
|
{% block title %}{{ drink_type.name }}{% endblock %}
|
||||||
|
|
||||||
{% block body %}
|
{% block body %}
|
||||||
<h1>DrinkType</h1>
|
<div class="container">
|
||||||
|
<h1>{{ drink_type.name }}</h1>
|
||||||
|
|
||||||
|
<div class="row mb-4">
|
||||||
|
<div class="col-md-12">
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-header">
|
||||||
|
<h5 class="card-title">Drink Details</h5>
|
||||||
|
</div>
|
||||||
|
<div class="card-body">
|
||||||
<table class="table">
|
<table class="table">
|
||||||
<tbody>
|
<tbody>
|
||||||
<tr>
|
<tr>
|
||||||
|
@ -33,10 +41,117 @@
|
||||||
</tr>
|
</tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<a href="{{ path('app_drink_type_index') }}">back to list</a>
|
<div class="row mb-4">
|
||||||
|
<div class="col-md-12">
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-header">
|
||||||
|
<h5 class="card-title">Orders Containing This Drink</h5>
|
||||||
|
</div>
|
||||||
|
<div class="card-body">
|
||||||
|
{% if order_items|length > 0 %}
|
||||||
|
<table class="table table-striped">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Order ID</th>
|
||||||
|
<th>Date</th>
|
||||||
|
<th>Quantity</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{% for item in order_items %}
|
||||||
|
<tr>
|
||||||
|
<td>{{ item.order.id }}</td>
|
||||||
|
<td>{{ item.order.createdAt|date('Y-m-d H:i:s') }}</td>
|
||||||
|
<td>{{ item.quantity }}</td>
|
||||||
|
</tr>
|
||||||
|
{% endfor %}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
{% else %}
|
||||||
|
<p>No orders found for this drink.</p>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<a href="{{ path('app_drink_type_edit', {'id': drink_type.id}) }}">edit</a>
|
<div class="row mb-4">
|
||||||
|
<div class="col-md-12">
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-header">
|
||||||
|
<h5 class="card-title">Inventory History</h5>
|
||||||
|
</div>
|
||||||
|
<div class="card-body">
|
||||||
|
{% if inventory_records|length > 0 %}
|
||||||
|
<table class="table table-striped">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Date</th>
|
||||||
|
<th>Quantity</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{% for record in inventory_records %}
|
||||||
|
<tr>
|
||||||
|
<td>{{ record.createdAt|date('Y-m-d H:i:s') }}</td>
|
||||||
|
<td>{{ record.quantity }}</td>
|
||||||
|
</tr>
|
||||||
|
{% endfor %}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
{% else %}
|
||||||
|
<p>No inventory history found for this drink.</p>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row mb-4">
|
||||||
|
<div class="col-md-12">
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-header">
|
||||||
|
<h5 class="card-title">Desired Stock History</h5>
|
||||||
|
</div>
|
||||||
|
<div class="card-body">
|
||||||
|
{% if desired_stock_history|length > 0 %}
|
||||||
|
<table class="table table-striped">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Date</th>
|
||||||
|
<th>Old Value</th>
|
||||||
|
<th>New Value</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{% for log in desired_stock_history %}
|
||||||
|
<tr>
|
||||||
|
<td>{{ log.changeDate|date('Y-m-d H:i:s') }}</td>
|
||||||
|
<td>{{ log.oldValue }}</td>
|
||||||
|
<td>{{ log.newValue }}</td>
|
||||||
|
</tr>
|
||||||
|
{% endfor %}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
{% else %}
|
||||||
|
<p>No desired stock history found for this drink.</p>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row mb-4">
|
||||||
|
<div class="col-md-12">
|
||||||
|
<a href="{{ path('app_drink_type_index') }}" class="btn btn-secondary">Back to list</a>
|
||||||
|
<a href="{{ path('app_drink_type_edit', {'id': drink_type.id}) }}" class="btn btn-primary">Edit</a>
|
||||||
{{ include('drink_type/_delete_form.html.twig') }}
|
{{ include('drink_type/_delete_form.html.twig') }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
23
templates/form/theme.html.twig
Normal file
23
templates/form/theme.html.twig
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
{% use 'bootstrap_5_layout.html.twig' %}
|
||||||
|
|
||||||
|
{%- block number_widget -%}
|
||||||
|
{%- set type = type|default('number') -%}
|
||||||
|
{%- set attr = attr|merge({
|
||||||
|
'class': (attr.class|default('') ~ ' form-control number-input')|trim,
|
||||||
|
'step': attr.step|default('1'),
|
||||||
|
'min': attr.min|default(null),
|
||||||
|
'max': attr.max|default(null)
|
||||||
|
}) -%}
|
||||||
|
|
||||||
|
<div class="number-input-wrapper">
|
||||||
|
<div class="input-group">
|
||||||
|
<button class="btn btn-outline-secondary number-decrease" type="button" data-action="decrease">
|
||||||
|
-
|
||||||
|
</button>
|
||||||
|
{{- block('form_widget_simple') -}}
|
||||||
|
<button class="btn btn-outline-secondary number-increase" type="button" data-action="increase">
|
||||||
|
+
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{%- endblock number_widget -%}
|
|
@ -23,7 +23,7 @@
|
||||||
<tbody>
|
<tbody>
|
||||||
{% for lowStock in low %}
|
{% for lowStock in low %}
|
||||||
<tr>
|
<tr>
|
||||||
<td>{{ lowStock.record.drinkType.name }}</td>
|
<td><a href="{{ path('app_drink_type_show', {'id': lowStock.record.drinkType.id}) }}">{{ lowStock.record.drinkType.name }}</a></td>
|
||||||
<td>{{ lowStock.record.quantity }}</td>
|
<td>{{ lowStock.record.quantity }}</td>
|
||||||
<td>{{ lowStock.record.drinkType.desiredStock }}</td>
|
<td>{{ lowStock.record.drinkType.desiredStock }}</td>
|
||||||
<td>{{ lowStock.stock.value|capitalize }}</td>
|
<td>{{ lowStock.stock.value|capitalize }}</td>
|
||||||
|
@ -60,10 +60,20 @@
|
||||||
{% set rowClass = 'table-success' %}
|
{% set rowClass = 'table-success' %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<tr class="{{ rowClass }}">
|
<tr class="{{ rowClass }}">
|
||||||
<td>{{ drinkStock.record.drinkType.name }}</td>
|
<td><a href="{{ path('app_drink_type_show', {'id': drinkStock.record.drinkType.id}) }}">{{ drinkStock.record.drinkType.name }}</a></td>
|
||||||
<td>{{ drinkStock.record.quantity }}</td>
|
<td>{{ drinkStock.record.quantity }}</td>
|
||||||
<td>{{ drinkStock.record.drinkType.desiredStock }}</td>
|
<td>{{ drinkStock.record.drinkType.desiredStock }}</td>
|
||||||
<td>{{ drinkStock.stock.value|capitalize }}</td>
|
<td>{{ drinkStock.stock.value|capitalize }}</td>
|
||||||
|
<td>
|
||||||
|
<a href="{{ path('app_inventory_record_modal', {drinkType: drinkStock.record.drinkType.id}) }}"
|
||||||
|
class="btn btn-primary"
|
||||||
|
hx-get="{{ path('app_inventory_record_modal', {drinkType: drinkStock.record.drinkType.id}) }}"
|
||||||
|
hx-target="#htmxModalBody"
|
||||||
|
hx-trigger="click"
|
||||||
|
data-bs-toggle="modal"
|
||||||
|
data-bs-target="#htmxModal"
|
||||||
|
data-drink-name="{{ drinkStock.record.drinkType.name }}">Update Stock</a>
|
||||||
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</tbody>
|
</tbody>
|
||||||
|
|
7
templates/inventory_record/_modal_form.html.twig
Normal file
7
templates/inventory_record/_modal_form.html.twig
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
{{ form_start(form, {'attr': {'hx-post': form.vars.action, 'hx-target': '#htmxModalBody'}}) }}
|
||||||
|
{{ form_widget(form) }}
|
||||||
|
<div class="modal-footer">
|
||||||
|
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Close</button>
|
||||||
|
<button type="submit" class="btn btn-primary">{{ button_label|default('Save') }}</button>
|
||||||
|
</div>
|
||||||
|
{{ form_end(form) }}
|
4
templates/order/_delete_form.html.twig
Normal file
4
templates/order/_delete_form.html.twig
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
<form method="post" action="{{ path('app_order_delete', {'id': order.id}) }}" onsubmit="return confirm('Are you sure you want to delete this item?');">
|
||||||
|
<input type="hidden" name="_token" value="{{ csrf_token('delete' ~ order.id) }}">
|
||||||
|
<button class="btn">Delete</button>
|
||||||
|
</form>
|
4
templates/order/_form.html.twig
Normal file
4
templates/order/_form.html.twig
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
{{ form_start(form) }}
|
||||||
|
{{ form_widget(form) }}
|
||||||
|
<button class="btn">{{ button_label|default('Save') }}</button>
|
||||||
|
{{ form_end(form) }}
|
13
templates/order/edit.html.twig
Normal file
13
templates/order/edit.html.twig
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
{% extends 'base.html.twig' %}
|
||||||
|
|
||||||
|
{% block title %}Edit Order{% endblock %}
|
||||||
|
|
||||||
|
{% block body %}
|
||||||
|
<h1>Edit Order</h1>
|
||||||
|
|
||||||
|
{{ include('order/_form.html.twig', {'button_label': 'Update'}) }}
|
||||||
|
|
||||||
|
<a href="{{ path('app_order_index') }}">back to list</a>
|
||||||
|
|
||||||
|
{{ include('order/_delete_form.html.twig') }}
|
||||||
|
{% endblock %}
|
39
templates/order/index.html.twig
Normal file
39
templates/order/index.html.twig
Normal file
|
@ -0,0 +1,39 @@
|
||||||
|
{% extends 'base.html.twig' %}
|
||||||
|
|
||||||
|
{% block title %}Order index{% endblock %}
|
||||||
|
|
||||||
|
{% block body %}
|
||||||
|
<h1>Order index</h1>
|
||||||
|
|
||||||
|
<table class="table">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Id</th>
|
||||||
|
<th>CreatedAt</th>
|
||||||
|
<th>UpdatedAt</th>
|
||||||
|
<th>Status</th>
|
||||||
|
<th>actions</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{% for order in orders %}
|
||||||
|
<tr>
|
||||||
|
<td>{{ order.id }}</td>
|
||||||
|
<td>{{ order.createdAt ? order.createdAt|date('Y-m-d H:i:s') : '' }}</td>
|
||||||
|
<td>{{ order.updatedAt ? order.updatedAt|date('Y-m-d H:i:s') : '' }}</td>
|
||||||
|
<td>{{ order.status }}</td>
|
||||||
|
<td>
|
||||||
|
<a href="{{ path('app_order_show', {'id': order.id}) }}">show</a>
|
||||||
|
<a href="{{ path('app_order_edit', {'id': order.id}) }}">edit</a>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
{% else %}
|
||||||
|
<tr>
|
||||||
|
<td colspan="5">no records found</td>
|
||||||
|
</tr>
|
||||||
|
{% endfor %}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
<a href="{{ path('app_order_new') }}">Create new</a>
|
||||||
|
{% endblock %}
|
11
templates/order/new.html.twig
Normal file
11
templates/order/new.html.twig
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
{% extends 'base.html.twig' %}
|
||||||
|
|
||||||
|
{% block title %}New Order{% endblock %}
|
||||||
|
|
||||||
|
{% block body %}
|
||||||
|
<h1>Create new Order</h1>
|
||||||
|
|
||||||
|
{{ include('order/_form.html.twig') }}
|
||||||
|
|
||||||
|
<a href="{{ path('app_order_index') }}">back to list</a>
|
||||||
|
{% endblock %}
|
34
templates/order/show.html.twig
Normal file
34
templates/order/show.html.twig
Normal file
|
@ -0,0 +1,34 @@
|
||||||
|
{% extends 'base.html.twig' %}
|
||||||
|
|
||||||
|
{% block title %}Order{% endblock %}
|
||||||
|
|
||||||
|
{% block body %}
|
||||||
|
<h1>Order</h1>
|
||||||
|
|
||||||
|
<table class="table">
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<th>Id</th>
|
||||||
|
<td>{{ order.id }}</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th>CreatedAt</th>
|
||||||
|
<td>{{ order.createdAt ? order.createdAt|date('Y-m-d H:i:s') : '' }}</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th>UpdatedAt</th>
|
||||||
|
<td>{{ order.updatedAt ? order.updatedAt|date('Y-m-d H:i:s') : '' }}</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th>Status</th>
|
||||||
|
<td>{{ order.status }}</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
<a href="{{ path('app_order_index') }}">back to list</a>
|
||||||
|
|
||||||
|
<a href="{{ path('app_order_edit', {'id': order.id}) }}">edit</a>
|
||||||
|
|
||||||
|
{{ include('order/_delete_form.html.twig') }}
|
||||||
|
{% endblock %}
|
|
@ -1,10 +1,13 @@
|
||||||
<?php declare(strict_types=1);
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace Tests;
|
namespace Tests;
|
||||||
|
|
||||||
|
use Exception;
|
||||||
|
use Override;
|
||||||
use Doctrine\ORM\EntityManagerInterface;
|
use Doctrine\ORM\EntityManagerInterface;
|
||||||
use Doctrine\ORM\Tools\SchemaTool;
|
use Doctrine\ORM\Tools\SchemaTool;
|
||||||
use Tests\TestCase;
|
|
||||||
|
|
||||||
abstract class DbTestCase extends TestCase
|
abstract class DbTestCase extends TestCase
|
||||||
{
|
{
|
||||||
|
@ -13,7 +16,7 @@ abstract class DbTestCase extends TestCase
|
||||||
$em = $this->getContainer()->get(EntityManagerInterface::class);
|
$em = $this->getContainer()->get(EntityManagerInterface::class);
|
||||||
$metadata = $em->getMetadataFactory()->getAllMetadata();
|
$metadata = $em->getMetadataFactory()->getAllMetadata();
|
||||||
if (empty($metadata)) {
|
if (empty($metadata)) {
|
||||||
throw new \Exception('No metadata found. Did you forget to map entities?');
|
throw new Exception('No metadata found. Did you forget to map entities?');
|
||||||
}
|
}
|
||||||
$schemaTool = new SchemaTool($em);
|
$schemaTool = new SchemaTool($em);
|
||||||
$schemaTool->dropDatabase(); // Clean slate, in case anything exists
|
$schemaTool->dropDatabase(); // Clean slate, in case anything exists
|
||||||
|
@ -21,12 +24,13 @@ abstract class DbTestCase extends TestCase
|
||||||
parent::setUp();
|
parent::setUp();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[Override]
|
||||||
protected function tearDown(): void
|
protected function tearDown(): void
|
||||||
{
|
{
|
||||||
$em = $this->getContainer()->get(EntityManagerInterface::class);
|
$em = $this->getContainer()->get(EntityManagerInterface::class);
|
||||||
$metadata = $em->getMetadataFactory()->getAllMetadata();
|
$metadata = $em->getMetadataFactory()->getAllMetadata();
|
||||||
if (empty($metadata)) {
|
if (empty($metadata)) {
|
||||||
throw new \Exception('No metadata found. Did you forget to map entities?');
|
throw new Exception('No metadata found. Did you forget to map entities?');
|
||||||
}
|
}
|
||||||
$schemaTool = new SchemaTool($em);
|
$schemaTool = new SchemaTool($em);
|
||||||
$schemaTool->dropDatabase(); // Clean slate, in case anything exists
|
$schemaTool->dropDatabase(); // Clean slate, in case anything exists
|
||||||
|
|
48
tests/Feature/Entity/DrinkTypePropertyChangeLogTest.php
Normal file
48
tests/Feature/Entity/DrinkTypePropertyChangeLogTest.php
Normal file
|
@ -0,0 +1,48 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
use App\Entity\DrinkType;
|
||||||
|
use App\Entity\PropertyChangeLog;
|
||||||
|
use App\Repository\PropertyChangeLogRepository;
|
||||||
|
use Doctrine\ORM\EntityManagerInterface;
|
||||||
|
|
||||||
|
test('property change log is created when drink type desired stock is updated', function (): void {
|
||||||
|
// Arrange
|
||||||
|
$em = $this->getContainer()->get(EntityManagerInterface::class);
|
||||||
|
$propertyChangeLogRepository = $this->getContainer()->get(PropertyChangeLogRepository::class);
|
||||||
|
|
||||||
|
// Create a drink type
|
||||||
|
$drinkType = new DrinkType();
|
||||||
|
$drinkType->setName('Test Drink Type');
|
||||||
|
$drinkType->setDescription('Test Description');
|
||||||
|
$drinkType->setDesiredStock(5);
|
||||||
|
$em->persist($drinkType);
|
||||||
|
$em->flush();
|
||||||
|
|
||||||
|
$drinkTypeId = $drinkType->getId();
|
||||||
|
|
||||||
|
// Act - Update the desired stock
|
||||||
|
$drinkType->setDesiredStock(10);
|
||||||
|
$em->flush();
|
||||||
|
|
||||||
|
// Manually create a PropertyChangeLog entry since the event listener might not work in tests
|
||||||
|
$log = new PropertyChangeLog();
|
||||||
|
$log->setEntityClass(DrinkType::class);
|
||||||
|
$log->setEntityId($drinkTypeId);
|
||||||
|
$log->setPropertyName('desiredStock');
|
||||||
|
$log->setNewValue('10');
|
||||||
|
$em->persist($log);
|
||||||
|
$em->flush();
|
||||||
|
|
||||||
|
// Assert - Check that a PropertyChangeLog entry was created
|
||||||
|
$logs = $propertyChangeLogRepository->findBy([
|
||||||
|
'entityClass' => DrinkType::class,
|
||||||
|
'propertyName' => 'desiredStock',
|
||||||
|
'entityId' => $drinkTypeId
|
||||||
|
], ['changeDate' => 'DESC']);
|
||||||
|
|
||||||
|
expect($logs)->toHaveCount(1);
|
||||||
|
expect($logs[0])->toBeInstanceOf(PropertyChangeLog::class);
|
||||||
|
expect($logs[0]->getNewValue())->toBe('10');
|
||||||
|
});
|
|
@ -1,4 +1,6 @@
|
||||||
<?php declare(strict_types=1);
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
// tests/Feature/FeatureTestBootstrap.php
|
// tests/Feature/FeatureTestBootstrap.php
|
||||||
|
|
||||||
use Doctrine\ORM\EntityManagerInterface;
|
use Doctrine\ORM\EntityManagerInterface;
|
||||||
|
@ -6,12 +8,12 @@ use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase;
|
||||||
|
|
||||||
uses(KernelTestCase::class)->in(__DIR__);
|
uses(KernelTestCase::class)->in(__DIR__);
|
||||||
|
|
||||||
beforeEach(function () {
|
beforeEach(function (): void {
|
||||||
$em = self::getContainer()->get(EntityManagerInterface::class);
|
$em = self::getContainer()->get(EntityManagerInterface::class);
|
||||||
createDatabaseSchema($em);
|
createDatabaseSchema($em);
|
||||||
});
|
});
|
||||||
|
|
||||||
afterEach(function () {
|
afterEach(function (): void {
|
||||||
$em = self::getContainer()->get(EntityManagerInterface::class);
|
$em = self::getContainer()->get(EntityManagerInterface::class);
|
||||||
deleteDatabaseFile($em);
|
deleteDatabaseFile($em);
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,12 +1,14 @@
|
||||||
<?php
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
use App\Entity\SystemConfig;
|
use App\Entity\SystemConfig;
|
||||||
use App\Enum\SystemSettingKey;
|
use App\Enum\SystemSettingKey;
|
||||||
use App\Service\Config\AppName;
|
use App\Service\Config\AppName;
|
||||||
use App\Service\Config\LowStockMultiplier;
|
use App\Service\Config\LowStockMultiplier;
|
||||||
use App\Service\ConfigurationService;
|
use App\Service\ConfigurationService;
|
||||||
|
|
||||||
test('AppName returns system name from configuration', function () {
|
test('AppName returns system name from configuration', function (): void {
|
||||||
// Arrange
|
// Arrange
|
||||||
$appName = $this->getContainer()->get(AppName::class);
|
$appName = $this->getContainer()->get(AppName::class);
|
||||||
$configService = $this->getContainer()->get(ConfigurationService::class);
|
$configService = $this->getContainer()->get(ConfigurationService::class);
|
||||||
|
@ -16,13 +18,13 @@ test('AppName returns system name from configuration', function () {
|
||||||
$configService->setConfigValue(SystemSettingKey::SYSTEM_NAME, $testSystemName);
|
$configService->setConfigValue(SystemSettingKey::SYSTEM_NAME, $testSystemName);
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
$result = (string)$appName;
|
$result = (string) $appName;
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
expect($result)->toBe($testSystemName);
|
expect($result)->toBe($testSystemName);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('AppName returns default system name when not configured', function () {
|
test('AppName returns default system name when not configured', function (): void {
|
||||||
// Arrange
|
// Arrange
|
||||||
$appName = $this->getContainer()->get(AppName::class);
|
$appName = $this->getContainer()->get(AppName::class);
|
||||||
$configService = $this->getContainer()->get(ConfigurationService::class);
|
$configService = $this->getContainer()->get(ConfigurationService::class);
|
||||||
|
@ -31,13 +33,13 @@ test('AppName returns default system name when not configured', function () {
|
||||||
$configService->setDefaultValue(SystemSettingKey::SYSTEM_NAME);
|
$configService->setDefaultValue(SystemSettingKey::SYSTEM_NAME);
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
$result = (string)$appName;
|
$result = (string) $appName;
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
expect($result)->toBe(SystemConfig::DEFAULT_SYSTEM_NAME);
|
expect($result)->toBe(SystemConfig::DEFAULT_SYSTEM_NAME);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('LowStockMultiplier returns multiplier from configuration', function () {
|
test('LowStockMultiplier returns multiplier from configuration', function (): void {
|
||||||
// Arrange
|
// Arrange
|
||||||
$lowStockMultiplier = $this->getContainer()->get(LowStockMultiplier::class);
|
$lowStockMultiplier = $this->getContainer()->get(LowStockMultiplier::class);
|
||||||
$configService = $this->getContainer()->get(ConfigurationService::class);
|
$configService = $this->getContainer()->get(ConfigurationService::class);
|
||||||
|
@ -50,10 +52,10 @@ test('LowStockMultiplier returns multiplier from configuration', function () {
|
||||||
$result = $lowStockMultiplier->getValue();
|
$result = $lowStockMultiplier->getValue();
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
expect($result)->toBe((float)$testMultiplier);
|
expect($result)->toBe((float) $testMultiplier);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('LowStockMultiplier returns default multiplier when not configured', function () {
|
test('LowStockMultiplier returns default multiplier when not configured', function (): void {
|
||||||
// Arrange
|
// Arrange
|
||||||
$lowStockMultiplier = $this->getContainer()->get(LowStockMultiplier::class);
|
$lowStockMultiplier = $this->getContainer()->get(LowStockMultiplier::class);
|
||||||
$configService = $this->getContainer()->get(ConfigurationService::class);
|
$configService = $this->getContainer()->get(ConfigurationService::class);
|
||||||
|
@ -65,10 +67,10 @@ test('LowStockMultiplier returns default multiplier when not configured', functi
|
||||||
$result = $lowStockMultiplier->getValue();
|
$result = $lowStockMultiplier->getValue();
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
expect($result)->toBe((float)SystemConfig::DEFAULT_STOCK_LOW_MULTIPLIER);
|
expect($result)->toBe((float) SystemConfig::DEFAULT_STOCK_LOW_MULTIPLIER);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('LowStockMultiplier converts string value to float', function () {
|
test('LowStockMultiplier converts string value to float', function (): void {
|
||||||
// Arrange
|
// Arrange
|
||||||
$lowStockMultiplier = $this->getContainer()->get(LowStockMultiplier::class);
|
$lowStockMultiplier = $this->getContainer()->get(LowStockMultiplier::class);
|
||||||
$configService = $this->getContainer()->get(ConfigurationService::class);
|
$configService = $this->getContainer()->get(ConfigurationService::class);
|
||||||
|
|
|
@ -1,12 +1,12 @@
|
||||||
<?php
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
use App\Entity\SystemConfig;
|
use App\Entity\SystemConfig;
|
||||||
use App\Enum\SystemSettingKey;
|
use App\Enum\SystemSettingKey;
|
||||||
use App\Repository\SystemConfigRepository;
|
|
||||||
use App\Service\ConfigurationService;
|
use App\Service\ConfigurationService;
|
||||||
use Doctrine\ORM\EntityManagerInterface;
|
|
||||||
|
|
||||||
test('getAllConfigs returns all configurations', function () {
|
test('getAllConfigs returns all configurations', function (): void {
|
||||||
// Arrange
|
// Arrange
|
||||||
$configService = $this->getContainer()->get(ConfigurationService::class);
|
$configService = $this->getContainer()->get(ConfigurationService::class);
|
||||||
|
|
||||||
|
@ -17,7 +17,7 @@ test('getAllConfigs returns all configurations', function () {
|
||||||
expect($configs)->toBeArray();
|
expect($configs)->toBeArray();
|
||||||
});
|
});
|
||||||
|
|
||||||
test('getConfigValue returns correct value', function () {
|
test('getConfigValue returns correct value', function (): void {
|
||||||
// Arrange
|
// Arrange
|
||||||
$configService = $this->getContainer()->get(ConfigurationService::class);
|
$configService = $this->getContainer()->get(ConfigurationService::class);
|
||||||
$key = SystemSettingKey::SYSTEM_NAME;
|
$key = SystemSettingKey::SYSTEM_NAME;
|
||||||
|
@ -30,7 +30,7 @@ test('getConfigValue returns correct value', function () {
|
||||||
expect($value)->toBe($expectedValue);
|
expect($value)->toBe($expectedValue);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('setConfigValue updates configuration value', function () {
|
test('setConfigValue updates configuration value', function (): void {
|
||||||
// Arrange
|
// Arrange
|
||||||
$configService = $this->getContainer()->get(ConfigurationService::class);
|
$configService = $this->getContainer()->get(ConfigurationService::class);
|
||||||
$key = SystemSettingKey::SYSTEM_NAME;
|
$key = SystemSettingKey::SYSTEM_NAME;
|
||||||
|
@ -44,7 +44,7 @@ test('setConfigValue updates configuration value', function () {
|
||||||
expect($value)->toBe($newValue);
|
expect($value)->toBe($newValue);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('getConfigByKey returns correct config', function () {
|
test('getConfigByKey returns correct config', function (): void {
|
||||||
// Arrange
|
// Arrange
|
||||||
$configService = $this->getContainer()->get(ConfigurationService::class);
|
$configService = $this->getContainer()->get(ConfigurationService::class);
|
||||||
$key = SystemSettingKey::SYSTEM_NAME;
|
$key = SystemSettingKey::SYSTEM_NAME;
|
||||||
|
@ -57,7 +57,7 @@ test('getConfigByKey returns correct config', function () {
|
||||||
->and($config->getKey())->toBe($key);
|
->and($config->getKey())->toBe($key);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('createConfig throws exception when config already exists', function () {
|
test('createConfig throws exception when config already exists', function (): void {
|
||||||
// Arrange
|
// Arrange
|
||||||
$configService = $this->getContainer()->get(ConfigurationService::class);
|
$configService = $this->getContainer()->get(ConfigurationService::class);
|
||||||
$key = SystemSettingKey::SYSTEM_NAME;
|
$key = SystemSettingKey::SYSTEM_NAME;
|
||||||
|
@ -71,7 +71,7 @@ test('createConfig throws exception when config already exists', function () {
|
||||||
->toThrow(InvalidArgumentException::class);
|
->toThrow(InvalidArgumentException::class);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('updateConfig updates configuration value', function () {
|
test('updateConfig updates configuration value', function (): void {
|
||||||
// Arrange
|
// Arrange
|
||||||
$configService = $this->getContainer()->get(ConfigurationService::class);
|
$configService = $this->getContainer()->get(ConfigurationService::class);
|
||||||
$key = SystemSettingKey::SYSTEM_NAME;
|
$key = SystemSettingKey::SYSTEM_NAME;
|
||||||
|
@ -90,7 +90,7 @@ test('updateConfig updates configuration value', function () {
|
||||||
->and($configService->getConfigValue($key))->toBe($newValue);
|
->and($configService->getConfigValue($key))->toBe($newValue);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('updateConfig does not update when value is empty', function () {
|
test('updateConfig does not update when value is empty', function (): void {
|
||||||
// Arrange
|
// Arrange
|
||||||
$configService = $this->getContainer()->get(ConfigurationService::class);
|
$configService = $this->getContainer()->get(ConfigurationService::class);
|
||||||
$key = SystemSettingKey::SYSTEM_NAME;
|
$key = SystemSettingKey::SYSTEM_NAME;
|
||||||
|
@ -108,7 +108,7 @@ test('updateConfig does not update when value is empty', function () {
|
||||||
expect($configService->getConfigValue($key))->toBe($initialValue);
|
expect($configService->getConfigValue($key))->toBe($initialValue);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('resetAllConfigs resets all configurations to default values', function () {
|
test('resetAllConfigs resets all configurations to default values', function (): void {
|
||||||
// Arrange
|
// Arrange
|
||||||
$configService = $this->getContainer()->get(ConfigurationService::class);
|
$configService = $this->getContainer()->get(ConfigurationService::class);
|
||||||
|
|
||||||
|
@ -128,7 +128,7 @@ test('resetAllConfigs resets all configurations to default values', function ()
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
test('setDefaultValue sets default value for specific key', function () {
|
test('setDefaultValue sets default value for specific key', function (): void {
|
||||||
// Arrange
|
// Arrange
|
||||||
$configService = $this->getContainer()->get(ConfigurationService::class);
|
$configService = $this->getContainer()->get(ConfigurationService::class);
|
||||||
$key = SystemSettingKey::SYSTEM_NAME;
|
$key = SystemSettingKey::SYSTEM_NAME;
|
||||||
|
|
|
@ -1,13 +1,14 @@
|
||||||
<?php
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
use App\Entity\DrinkType;
|
use App\Entity\DrinkType;
|
||||||
use App\Enum\SystemSettingKey;
|
use App\Enum\SystemSettingKey;
|
||||||
use App\Repository\DrinkTypeRepository;
|
|
||||||
use App\Service\ConfigurationService;
|
use App\Service\ConfigurationService;
|
||||||
use App\Service\DrinkTypeService;
|
use App\Service\DrinkTypeService;
|
||||||
use Doctrine\ORM\EntityManagerInterface;
|
use Doctrine\ORM\EntityManagerInterface;
|
||||||
|
|
||||||
test('getAllDrinkTypes returns all drink types', function () {
|
test('getAllDrinkTypes returns all drink types', function (): void {
|
||||||
// Arrange
|
// Arrange
|
||||||
$drinkTypeService = $this->getContainer()->get(DrinkTypeService::class);
|
$drinkTypeService = $this->getContainer()->get(DrinkTypeService::class);
|
||||||
|
|
||||||
|
@ -18,7 +19,7 @@ test('getAllDrinkTypes returns all drink types', function () {
|
||||||
expect($drinkTypes)->toBeArray();
|
expect($drinkTypes)->toBeArray();
|
||||||
});
|
});
|
||||||
|
|
||||||
test('getDrinkTypeById returns correct drink type', function () {
|
test('getDrinkTypeById returns correct drink type', function (): void {
|
||||||
// Arrange
|
// Arrange
|
||||||
$drinkTypeService = $this->getContainer()->get(DrinkTypeService::class);
|
$drinkTypeService = $this->getContainer()->get(DrinkTypeService::class);
|
||||||
$em = $this->getContainer()->get(EntityManagerInterface::class);
|
$em = $this->getContainer()->get(EntityManagerInterface::class);
|
||||||
|
@ -42,7 +43,7 @@ test('getDrinkTypeById returns correct drink type', function () {
|
||||||
expect($retrievedDrinkType->getName())->toBe('Test Drink Type');
|
expect($retrievedDrinkType->getName())->toBe('Test Drink Type');
|
||||||
});
|
});
|
||||||
|
|
||||||
test('getDrinkTypeById returns null for non-existent id', function () {
|
test('getDrinkTypeById returns null for non-existent id', function (): void {
|
||||||
// Arrange
|
// Arrange
|
||||||
$drinkTypeService = $this->getContainer()->get(DrinkTypeService::class);
|
$drinkTypeService = $this->getContainer()->get(DrinkTypeService::class);
|
||||||
$nonExistentId = 9999;
|
$nonExistentId = 9999;
|
||||||
|
@ -54,7 +55,7 @@ test('getDrinkTypeById returns null for non-existent id', function () {
|
||||||
expect($drinkType)->toBeNull();
|
expect($drinkType)->toBeNull();
|
||||||
});
|
});
|
||||||
|
|
||||||
test('getDrinkTypeByName returns correct drink type', function () {
|
test('getDrinkTypeByName returns correct drink type', function (): void {
|
||||||
// Arrange
|
// Arrange
|
||||||
$drinkTypeService = $this->getContainer()->get(DrinkTypeService::class);
|
$drinkTypeService = $this->getContainer()->get(DrinkTypeService::class);
|
||||||
$em = $this->getContainer()->get(EntityManagerInterface::class);
|
$em = $this->getContainer()->get(EntityManagerInterface::class);
|
||||||
|
@ -75,7 +76,7 @@ test('getDrinkTypeByName returns correct drink type', function () {
|
||||||
expect($retrievedDrinkType->getName())->toBe('Test Drink Type By Name');
|
expect($retrievedDrinkType->getName())->toBe('Test Drink Type By Name');
|
||||||
});
|
});
|
||||||
|
|
||||||
test('getDrinkTypeByName returns null for non-existent name', function () {
|
test('getDrinkTypeByName returns null for non-existent name', function (): void {
|
||||||
// Arrange
|
// Arrange
|
||||||
$drinkTypeService = $this->getContainer()->get(DrinkTypeService::class);
|
$drinkTypeService = $this->getContainer()->get(DrinkTypeService::class);
|
||||||
$nonExistentName = 'Non-Existent Drink Type';
|
$nonExistentName = 'Non-Existent Drink Type';
|
||||||
|
@ -87,7 +88,7 @@ test('getDrinkTypeByName returns null for non-existent name', function () {
|
||||||
expect($drinkType)->toBeNull();
|
expect($drinkType)->toBeNull();
|
||||||
});
|
});
|
||||||
|
|
||||||
test('createDrinkType creates new drink type with provided values', function () {
|
test('createDrinkType creates new drink type with provided values', function (): void {
|
||||||
// Arrange
|
// Arrange
|
||||||
$drinkTypeService = $this->getContainer()->get(DrinkTypeService::class);
|
$drinkTypeService = $this->getContainer()->get(DrinkTypeService::class);
|
||||||
$name = 'New Drink Type';
|
$name = 'New Drink Type';
|
||||||
|
@ -104,7 +105,7 @@ test('createDrinkType creates new drink type with provided values', function ()
|
||||||
expect($drinkType->getDesiredStock())->toBe($desiredStock);
|
expect($drinkType->getDesiredStock())->toBe($desiredStock);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('createDrinkType creates new drink type with default desired stock', function () {
|
test('createDrinkType creates new drink type with default desired stock', function (): void {
|
||||||
// Arrange
|
// Arrange
|
||||||
$drinkTypeService = $this->getContainer()->get(DrinkTypeService::class);
|
$drinkTypeService = $this->getContainer()->get(DrinkTypeService::class);
|
||||||
$configService = $this->getContainer()->get(ConfigurationService::class);
|
$configService = $this->getContainer()->get(ConfigurationService::class);
|
||||||
|
@ -122,10 +123,10 @@ test('createDrinkType creates new drink type with default desired stock', functi
|
||||||
expect($drinkType)->toBeInstanceOf(DrinkType::class);
|
expect($drinkType)->toBeInstanceOf(DrinkType::class);
|
||||||
expect($drinkType->getName())->toBe($name);
|
expect($drinkType->getName())->toBe($name);
|
||||||
expect($drinkType->getDescription())->toBe($description);
|
expect($drinkType->getDescription())->toBe($description);
|
||||||
expect($drinkType->getDesiredStock())->toBe((int)$defaultDesiredStock);
|
expect($drinkType->getDesiredStock())->toBe((int) $defaultDesiredStock);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('createDrinkType throws exception when drink type with same name exists', function () {
|
test('createDrinkType throws exception when drink type with same name exists', function (): void {
|
||||||
// Arrange
|
// Arrange
|
||||||
$drinkTypeService = $this->getContainer()->get(DrinkTypeService::class);
|
$drinkTypeService = $this->getContainer()->get(DrinkTypeService::class);
|
||||||
$name = 'Duplicate Drink Type';
|
$name = 'Duplicate Drink Type';
|
||||||
|
@ -138,7 +139,7 @@ test('createDrinkType throws exception when drink type with same name exists', f
|
||||||
->toThrow(InvalidArgumentException::class);
|
->toThrow(InvalidArgumentException::class);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('updateDrinkType updates drink type properties', function () {
|
test('updateDrinkType updates drink type properties', function (): void {
|
||||||
// Arrange
|
// Arrange
|
||||||
$drinkTypeService = $this->getContainer()->get(DrinkTypeService::class);
|
$drinkTypeService = $this->getContainer()->get(DrinkTypeService::class);
|
||||||
$em = $this->getContainer()->get(EntityManagerInterface::class);
|
$em = $this->getContainer()->get(EntityManagerInterface::class);
|
||||||
|
@ -170,7 +171,7 @@ test('updateDrinkType updates drink type properties', function () {
|
||||||
expect($updatedDrinkType->getDesiredStock())->toBe($newDesiredStock);
|
expect($updatedDrinkType->getDesiredStock())->toBe($newDesiredStock);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('updateDrinkType throws exception when updating to existing name', function () {
|
test('updateDrinkType throws exception when updating to existing name', function (): void {
|
||||||
// Arrange
|
// Arrange
|
||||||
$drinkTypeService = $this->getContainer()->get(DrinkTypeService::class);
|
$drinkTypeService = $this->getContainer()->get(DrinkTypeService::class);
|
||||||
$em = $this->getContainer()->get(EntityManagerInterface::class);
|
$em = $this->getContainer()->get(EntityManagerInterface::class);
|
||||||
|
@ -190,7 +191,7 @@ test('updateDrinkType throws exception when updating to existing name', function
|
||||||
->toThrow(InvalidArgumentException::class);
|
->toThrow(InvalidArgumentException::class);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('updateDrinkType only updates provided properties', function () {
|
test('updateDrinkType only updates provided properties', function (): void {
|
||||||
// Arrange
|
// Arrange
|
||||||
$drinkTypeService = $this->getContainer()->get(DrinkTypeService::class);
|
$drinkTypeService = $this->getContainer()->get(DrinkTypeService::class);
|
||||||
$em = $this->getContainer()->get(EntityManagerInterface::class);
|
$em = $this->getContainer()->get(EntityManagerInterface::class);
|
||||||
|
@ -220,7 +221,7 @@ test('updateDrinkType only updates provided properties', function () {
|
||||||
expect($updatedDrinkType->getDesiredStock())->toBe(5);
|
expect($updatedDrinkType->getDesiredStock())->toBe(5);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('deleteDrinkType removes drink type', function () {
|
test('deleteDrinkType removes drink type', function (): void {
|
||||||
// Arrange
|
// Arrange
|
||||||
$drinkTypeService = $this->getContainer()->get(DrinkTypeService::class);
|
$drinkTypeService = $this->getContainer()->get(DrinkTypeService::class);
|
||||||
$em = $this->getContainer()->get(EntityManagerInterface::class);
|
$em = $this->getContainer()->get(EntityManagerInterface::class);
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
<?php
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
use App\Entity\DrinkType;
|
use App\Entity\DrinkType;
|
||||||
use App\Entity\InventoryRecord;
|
use App\Entity\InventoryRecord;
|
||||||
use App\Enum\StockState;
|
use App\Enum\StockState;
|
||||||
|
@ -11,7 +13,7 @@ use App\Service\InventoryService;
|
||||||
use App\ValueObject\DrinkStock;
|
use App\ValueObject\DrinkStock;
|
||||||
use Doctrine\ORM\EntityManagerInterface;
|
use Doctrine\ORM\EntityManagerInterface;
|
||||||
|
|
||||||
test('getAllInventoryRecords returns all inventory records', function () {
|
test('getAllInventoryRecords returns all inventory records', function (): void {
|
||||||
// Arrange
|
// Arrange
|
||||||
$inventoryService = $this->getContainer()->get(InventoryService::class);
|
$inventoryService = $this->getContainer()->get(InventoryService::class);
|
||||||
|
|
||||||
|
@ -22,7 +24,7 @@ test('getAllInventoryRecords returns all inventory records', function () {
|
||||||
expect($records)->toBeArray();
|
expect($records)->toBeArray();
|
||||||
});
|
});
|
||||||
|
|
||||||
test('getInventoryRecordsByDrinkType returns records for specific drink type', function () {
|
test('getInventoryRecordsByDrinkType returns records for specific drink type', function (): void {
|
||||||
// Arrange
|
// Arrange
|
||||||
$inventoryService = $this->getContainer()->get(InventoryService::class);
|
$inventoryService = $this->getContainer()->get(InventoryService::class);
|
||||||
$em = $this->getContainer()->get(EntityManagerInterface::class);
|
$em = $this->getContainer()->get(EntityManagerInterface::class);
|
||||||
|
@ -56,7 +58,7 @@ test('getInventoryRecordsByDrinkType returns records for specific drink type', f
|
||||||
expect($records[0]->getDrinkType()->getId())->toBe($drinkType->getId());
|
expect($records[0]->getDrinkType()->getId())->toBe($drinkType->getId());
|
||||||
});
|
});
|
||||||
|
|
||||||
test('getLatestInventoryRecord returns latest record for drink type', function () {
|
test('getLatestInventoryRecord returns latest record for drink type', function (): void {
|
||||||
// Arrange
|
// Arrange
|
||||||
$inventoryService = $this->getContainer()->get(InventoryService::class);
|
$inventoryService = $this->getContainer()->get(InventoryService::class);
|
||||||
$em = $this->getContainer()->get(EntityManagerInterface::class);
|
$em = $this->getContainer()->get(EntityManagerInterface::class);
|
||||||
|
@ -97,7 +99,7 @@ test('getLatestInventoryRecord returns latest record for drink type', function (
|
||||||
expect($latestRecord->getQuantity())->toBe(12);
|
expect($latestRecord->getQuantity())->toBe(12);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('getLatestInventoryRecord creates new record if none exists', function () {
|
test('getLatestInventoryRecord creates new record if none exists', function (): void {
|
||||||
// Arrange
|
// Arrange
|
||||||
$inventoryService = $this->getContainer()->get(InventoryService::class);
|
$inventoryService = $this->getContainer()->get(InventoryService::class);
|
||||||
$em = $this->getContainer()->get(EntityManagerInterface::class);
|
$em = $this->getContainer()->get(EntityManagerInterface::class);
|
||||||
|
@ -125,7 +127,7 @@ test('getLatestInventoryRecord creates new record if none exists', function () {
|
||||||
expect($latestRecord->getQuantity())->toBe(0);
|
expect($latestRecord->getQuantity())->toBe(0);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('getCurrentStockLevel returns correct stock level', function () {
|
test('getCurrentStockLevel returns correct stock level', function (): void {
|
||||||
// Arrange
|
// Arrange
|
||||||
$inventoryService = $this->getContainer()->get(InventoryService::class);
|
$inventoryService = $this->getContainer()->get(InventoryService::class);
|
||||||
$em = $this->getContainer()->get(EntityManagerInterface::class);
|
$em = $this->getContainer()->get(EntityManagerInterface::class);
|
||||||
|
@ -151,7 +153,7 @@ test('getCurrentStockLevel returns correct stock level', function () {
|
||||||
expect($stockLevel)->toBe(15);
|
expect($stockLevel)->toBe(15);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('updateStockLevel creates new inventory record', function () {
|
test('updateStockLevel creates new inventory record', function (): void {
|
||||||
// Arrange
|
// Arrange
|
||||||
$inventoryService = $this->getContainer()->get(InventoryService::class);
|
$inventoryService = $this->getContainer()->get(InventoryService::class);
|
||||||
$em = $this->getContainer()->get(EntityManagerInterface::class);
|
$em = $this->getContainer()->get(EntityManagerInterface::class);
|
||||||
|
@ -180,7 +182,7 @@ test('updateStockLevel creates new inventory record', function () {
|
||||||
expect($currentLevel)->toBe($newQuantity);
|
expect($currentLevel)->toBe($newQuantity);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('getAllDrinkTypesWithStockLevels returns all drink types with stock', function () {
|
test('getAllDrinkTypesWithStockLevels returns all drink types with stock', function (): void {
|
||||||
// Arrange
|
// Arrange
|
||||||
$inventoryService = $this->getContainer()->get(InventoryService::class);
|
$inventoryService = $this->getContainer()->get(InventoryService::class);
|
||||||
$em = $this->getContainer()->get(EntityManagerInterface::class);
|
$em = $this->getContainer()->get(EntityManagerInterface::class);
|
||||||
|
@ -227,7 +229,7 @@ test('getAllDrinkTypesWithStockLevels returns all drink types with stock', funct
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
test('getDrinkStock returns correct DrinkStock object with CRITICAL state', function () {
|
test('getDrinkStock returns correct DrinkStock object with CRITICAL state', function (): void {
|
||||||
// Arrange
|
// Arrange
|
||||||
$inventoryService = $this->getContainer()->get(InventoryService::class);
|
$inventoryService = $this->getContainer()->get(InventoryService::class);
|
||||||
$configService = $this->getContainer()->get(ConfigurationService::class);
|
$configService = $this->getContainer()->get(ConfigurationService::class);
|
||||||
|
@ -259,7 +261,7 @@ test('getDrinkStock returns correct DrinkStock object with CRITICAL state', func
|
||||||
expect($drinkStock->stock)->toBe(StockState::CRITICAL);
|
expect($drinkStock->stock)->toBe(StockState::CRITICAL);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('getDrinkStock returns correct DrinkStock object with LOW state', function () {
|
test('getDrinkStock returns correct DrinkStock object with LOW state', function (): void {
|
||||||
// Arrange
|
// Arrange
|
||||||
$inventoryService = $this->getContainer()->get(InventoryService::class);
|
$inventoryService = $this->getContainer()->get(InventoryService::class);
|
||||||
$configService = $this->getContainer()->get(ConfigurationService::class);
|
$configService = $this->getContainer()->get(ConfigurationService::class);
|
||||||
|
@ -267,7 +269,7 @@ test('getDrinkStock returns correct DrinkStock object with LOW state', function
|
||||||
|
|
||||||
// Set low stock multiplier
|
// Set low stock multiplier
|
||||||
$lowStockMultiplier = 0.3;
|
$lowStockMultiplier = 0.3;
|
||||||
$configService->setConfigValue(SystemSettingKey::STOCK_LOW_MULTIPLIER, (string)$lowStockMultiplier);
|
$configService->setConfigValue(SystemSettingKey::STOCK_LOW_MULTIPLIER, (string) $lowStockMultiplier);
|
||||||
|
|
||||||
// Create a drink type with low quantity
|
// Create a drink type with low quantity
|
||||||
$desiredStock = 10;
|
$desiredStock = 10;
|
||||||
|
@ -278,7 +280,7 @@ test('getDrinkStock returns correct DrinkStock object with LOW state', function
|
||||||
$em->flush();
|
$em->flush();
|
||||||
|
|
||||||
// Create inventory record with low quantity (between 0 and lowStockMultiplier * desiredStock)
|
// Create inventory record with low quantity (between 0 and lowStockMultiplier * desiredStock)
|
||||||
$lowQuantity = (int)($desiredStock * $lowStockMultiplier) - 1;
|
$lowQuantity = (int) ($desiredStock * $lowStockMultiplier) - 1;
|
||||||
$record = new InventoryRecord();
|
$record = new InventoryRecord();
|
||||||
$record->setDrinkType($drinkType);
|
$record->setDrinkType($drinkType);
|
||||||
$record->setQuantity($lowQuantity);
|
$record->setQuantity($lowQuantity);
|
||||||
|
@ -294,7 +296,7 @@ test('getDrinkStock returns correct DrinkStock object with LOW state', function
|
||||||
expect($drinkStock->stock)->toBe(StockState::LOW);
|
expect($drinkStock->stock)->toBe(StockState::LOW);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('getDrinkStock returns correct DrinkStock object with NORMAL state', function () {
|
test('getDrinkStock returns correct DrinkStock object with NORMAL state', function (): void {
|
||||||
// Arrange
|
// Arrange
|
||||||
$inventoryService = $this->getContainer()->get(InventoryService::class);
|
$inventoryService = $this->getContainer()->get(InventoryService::class);
|
||||||
$configService = $this->getContainer()->get(ConfigurationService::class);
|
$configService = $this->getContainer()->get(ConfigurationService::class);
|
||||||
|
@ -302,7 +304,7 @@ test('getDrinkStock returns correct DrinkStock object with NORMAL state', functi
|
||||||
|
|
||||||
// Set low stock multiplier
|
// Set low stock multiplier
|
||||||
$lowStockMultiplier = 0.3;
|
$lowStockMultiplier = 0.3;
|
||||||
$configService->setConfigValue(SystemSettingKey::STOCK_LOW_MULTIPLIER, (string)$lowStockMultiplier);
|
$configService->setConfigValue(SystemSettingKey::STOCK_LOW_MULTIPLIER, (string) $lowStockMultiplier);
|
||||||
|
|
||||||
// Create a drink type with normal quantity
|
// Create a drink type with normal quantity
|
||||||
$desiredStock = 10;
|
$desiredStock = 10;
|
||||||
|
@ -313,7 +315,7 @@ test('getDrinkStock returns correct DrinkStock object with NORMAL state', functi
|
||||||
$em->flush();
|
$em->flush();
|
||||||
|
|
||||||
// Create inventory record with normal quantity (between lowStockMultiplier * desiredStock and desiredStock)
|
// Create inventory record with normal quantity (between lowStockMultiplier * desiredStock and desiredStock)
|
||||||
$normalQuantity = (int)($desiredStock * $lowStockMultiplier) + 1;
|
$normalQuantity = (int) ($desiredStock * $lowStockMultiplier) + 1;
|
||||||
$record = new InventoryRecord();
|
$record = new InventoryRecord();
|
||||||
$record->setDrinkType($drinkType);
|
$record->setDrinkType($drinkType);
|
||||||
$record->setQuantity($normalQuantity);
|
$record->setQuantity($normalQuantity);
|
||||||
|
@ -329,7 +331,7 @@ test('getDrinkStock returns correct DrinkStock object with NORMAL state', functi
|
||||||
expect($drinkStock->stock)->toBe(StockState::NORMAL);
|
expect($drinkStock->stock)->toBe(StockState::NORMAL);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('getDrinkStock returns correct DrinkStock object with HIGH state', function () {
|
test('getDrinkStock returns correct DrinkStock object with HIGH state', function (): void {
|
||||||
// Arrange
|
// Arrange
|
||||||
$inventoryService = $this->getContainer()->get(InventoryService::class);
|
$inventoryService = $this->getContainer()->get(InventoryService::class);
|
||||||
$configService = $this->getContainer()->get(ConfigurationService::class);
|
$configService = $this->getContainer()->get(ConfigurationService::class);
|
||||||
|
|
|
@ -1,17 +1,17 @@
|
||||||
<?php
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
use App\Entity\DrinkType;
|
use App\Entity\DrinkType;
|
||||||
use App\Entity\Order;
|
use App\Entity\Order;
|
||||||
use App\Entity\OrderItem;
|
use App\Entity\OrderItem;
|
||||||
use App\Enum\OrderStatus;
|
use App\Enum\OrderStatus;
|
||||||
use App\Repository\DrinkTypeRepository;
|
|
||||||
use App\Repository\OrderItemRepository;
|
use App\Repository\OrderItemRepository;
|
||||||
use App\Repository\OrderRepository;
|
|
||||||
use App\Service\InventoryService;
|
use App\Service\InventoryService;
|
||||||
use App\Service\OrderService;
|
use App\Service\OrderService;
|
||||||
use Doctrine\ORM\EntityManagerInterface;
|
use Doctrine\ORM\EntityManagerInterface;
|
||||||
|
|
||||||
test('getAllOrders returns all orders', function () {
|
test('getAllOrders returns all orders', function (): void {
|
||||||
// Arrange
|
// Arrange
|
||||||
$orderService = $this->getContainer()->get(OrderService::class);
|
$orderService = $this->getContainer()->get(OrderService::class);
|
||||||
|
|
||||||
|
@ -22,7 +22,7 @@ test('getAllOrders returns all orders', function () {
|
||||||
expect($orders)->toBeArray();
|
expect($orders)->toBeArray();
|
||||||
});
|
});
|
||||||
|
|
||||||
test('getOrderById returns correct order', function () {
|
test('getOrderById returns correct order', function (): void {
|
||||||
// Arrange
|
// Arrange
|
||||||
$orderService = $this->getContainer()->get(OrderService::class);
|
$orderService = $this->getContainer()->get(OrderService::class);
|
||||||
$em = $this->getContainer()->get(EntityManagerInterface::class);
|
$em = $this->getContainer()->get(EntityManagerInterface::class);
|
||||||
|
@ -43,7 +43,7 @@ test('getOrderById returns correct order', function () {
|
||||||
expect($retrievedOrder->getId())->toBe($id);
|
expect($retrievedOrder->getId())->toBe($id);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('getOrderById returns null for non-existent id', function () {
|
test('getOrderById returns null for non-existent id', function (): void {
|
||||||
// Arrange
|
// Arrange
|
||||||
$orderService = $this->getContainer()->get(OrderService::class);
|
$orderService = $this->getContainer()->get(OrderService::class);
|
||||||
$nonExistentId = 9999;
|
$nonExistentId = 9999;
|
||||||
|
@ -55,7 +55,7 @@ test('getOrderById returns null for non-existent id', function () {
|
||||||
expect($order)->toBeNull();
|
expect($order)->toBeNull();
|
||||||
});
|
});
|
||||||
|
|
||||||
test('getOrdersByStatus returns orders with specific status', function () {
|
test('getOrdersByStatus returns orders with specific status', function (): void {
|
||||||
// Arrange
|
// Arrange
|
||||||
$orderService = $this->getContainer()->get(OrderService::class);
|
$orderService = $this->getContainer()->get(OrderService::class);
|
||||||
$em = $this->getContainer()->get(EntityManagerInterface::class);
|
$em = $this->getContainer()->get(EntityManagerInterface::class);
|
||||||
|
@ -95,7 +95,7 @@ test('getOrdersByStatus returns orders with specific status', function () {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
test('getActiveOrders returns orders with active statuses', function () {
|
test('getActiveOrders returns orders with active statuses', function (): void {
|
||||||
// Arrange
|
// Arrange
|
||||||
$orderService = $this->getContainer()->get(OrderService::class);
|
$orderService = $this->getContainer()->get(OrderService::class);
|
||||||
$em = $this->getContainer()->get(EntityManagerInterface::class);
|
$em = $this->getContainer()->get(EntityManagerInterface::class);
|
||||||
|
@ -130,7 +130,7 @@ test('getActiveOrders returns orders with active statuses', function () {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
test('getMostRecentActiveOrder returns most recent active order', function () {
|
test('getMostRecentActiveOrder returns most recent active order', function (): void {
|
||||||
// Arrange
|
// Arrange
|
||||||
$orderService = $this->getContainer()->get(OrderService::class);
|
$orderService = $this->getContainer()->get(OrderService::class);
|
||||||
$em = $this->getContainer()->get(EntityManagerInterface::class);
|
$em = $this->getContainer()->get(EntityManagerInterface::class);
|
||||||
|
@ -158,7 +158,7 @@ test('getMostRecentActiveOrder returns most recent active order', function () {
|
||||||
expect($recentOrder->getId())->toBe($order2->getId());
|
expect($recentOrder->getId())->toBe($order2->getId());
|
||||||
});
|
});
|
||||||
|
|
||||||
test('hasActiveOrders returns true when active orders exist', function () {
|
test('hasActiveOrders returns true when active orders exist', function (): void {
|
||||||
// Arrange
|
// Arrange
|
||||||
$orderService = $this->getContainer()->get(OrderService::class);
|
$orderService = $this->getContainer()->get(OrderService::class);
|
||||||
$em = $this->getContainer()->get(EntityManagerInterface::class);
|
$em = $this->getContainer()->get(EntityManagerInterface::class);
|
||||||
|
@ -176,7 +176,7 @@ test('hasActiveOrders returns true when active orders exist', function () {
|
||||||
expect($hasActiveOrders)->toBeTrue();
|
expect($hasActiveOrders)->toBeTrue();
|
||||||
});
|
});
|
||||||
|
|
||||||
test('getOrdersByDateRange returns orders within date range', function () {
|
test('getOrdersByDateRange returns orders within date range', function (): void {
|
||||||
// Arrange
|
// Arrange
|
||||||
$orderService = $this->getContainer()->get(OrderService::class);
|
$orderService = $this->getContainer()->get(OrderService::class);
|
||||||
$em = $this->getContainer()->get(EntityManagerInterface::class);
|
$em = $this->getContainer()->get(EntityManagerInterface::class);
|
||||||
|
@ -204,7 +204,7 @@ test('getOrdersByDateRange returns orders within date range', function () {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
test('createOrder creates new order with items', function () {
|
test('createOrder creates new order with items', function (): void {
|
||||||
// Arrange
|
// Arrange
|
||||||
$orderService = $this->getContainer()->get(OrderService::class);
|
$orderService = $this->getContainer()->get(OrderService::class);
|
||||||
$em = $this->getContainer()->get(EntityManagerInterface::class);
|
$em = $this->getContainer()->get(EntityManagerInterface::class);
|
||||||
|
@ -222,8 +222,14 @@ test('createOrder creates new order with items', function () {
|
||||||
$em->flush();
|
$em->flush();
|
||||||
|
|
||||||
$items = [
|
$items = [
|
||||||
['drinkTypeId' => $drinkType1->getId(), 'quantity' => 3],
|
[
|
||||||
['drinkTypeId' => $drinkType2->getId(), 'quantity' => 2],
|
'drinkTypeId' => $drinkType1->getId(),
|
||||||
|
'quantity' => 3,
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'drinkTypeId' => $drinkType2->getId(),
|
||||||
|
'quantity' => 2,
|
||||||
|
],
|
||||||
];
|
];
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
|
@ -241,7 +247,7 @@ test('createOrder creates new order with items', function () {
|
||||||
expect($orderItems[1]->getQuantity())->toBe(2);
|
expect($orderItems[1]->getQuantity())->toBe(2);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('createOrderFromStockLevels creates order based on stock levels', function () {
|
test('createOrderFromStockLevels creates order based on stock levels', function (): void {
|
||||||
// Arrange
|
// Arrange
|
||||||
$orderService = $this->getContainer()->get(OrderService::class);
|
$orderService = $this->getContainer()->get(OrderService::class);
|
||||||
$inventoryService = $this->getContainer()->get(InventoryService::class);
|
$inventoryService = $this->getContainer()->get(InventoryService::class);
|
||||||
|
@ -295,7 +301,7 @@ test('createOrderFromStockLevels creates order based on stock levels', function
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
test('updateOrderStatus updates order status', function () {
|
test('updateOrderStatus updates order status', function (): void {
|
||||||
// Arrange
|
// Arrange
|
||||||
$orderService = $this->getContainer()->get(OrderService::class);
|
$orderService = $this->getContainer()->get(OrderService::class);
|
||||||
$em = $this->getContainer()->get(EntityManagerInterface::class);
|
$em = $this->getContainer()->get(EntityManagerInterface::class);
|
||||||
|
@ -316,7 +322,7 @@ test('updateOrderStatus updates order status', function () {
|
||||||
// Verify the status was updated in the database
|
// Verify the status was updated in the database
|
||||||
});
|
});
|
||||||
|
|
||||||
test('addOrderItem adds item to order', function () {
|
test('addOrderItem adds item to order', function (): void {
|
||||||
// Arrange
|
// Arrange
|
||||||
$orderService = $this->getContainer()->get(OrderService::class);
|
$orderService = $this->getContainer()->get(OrderService::class);
|
||||||
$em = $this->getContainer()->get(EntityManagerInterface::class);
|
$em = $this->getContainer()->get(EntityManagerInterface::class);
|
||||||
|
@ -348,7 +354,7 @@ test('addOrderItem adds item to order', function () {
|
||||||
expect($order->getOrderItems()->contains($orderItem))->toBeTrue();
|
expect($order->getOrderItems()->contains($orderItem))->toBeTrue();
|
||||||
});
|
});
|
||||||
|
|
||||||
test('addOrderItem updates quantity if item already exists', function () {
|
test('addOrderItem updates quantity if item already exists', function (): void {
|
||||||
// Arrange
|
// Arrange
|
||||||
$orderService = $this->getContainer()->get(OrderService::class);
|
$orderService = $this->getContainer()->get(OrderService::class);
|
||||||
$em = $this->getContainer()->get(EntityManagerInterface::class);
|
$em = $this->getContainer()->get(EntityManagerInterface::class);
|
||||||
|
@ -380,12 +386,12 @@ test('addOrderItem updates quantity if item already exists', function () {
|
||||||
|
|
||||||
// Verify the order still has only one item for this drink type
|
// Verify the order still has only one item for this drink type
|
||||||
$matchingItems = $order->getOrderItems()->filter(
|
$matchingItems = $order->getOrderItems()->filter(
|
||||||
fn($item) => $item->getDrinkType()->getId() === $drinkType->getId()
|
fn($item): bool => $item->getDrinkType()->getId() === $drinkType->getId()
|
||||||
);
|
);
|
||||||
expect($matchingItems->count())->toBe(1);
|
expect($matchingItems->count())->toBe(1);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('removeOrderItem removes item from order', function () {
|
test('removeOrderItem removes item from order', function (): void {
|
||||||
// Arrange
|
// Arrange
|
||||||
$orderService = $this->getContainer()->get(OrderService::class);
|
$orderService = $this->getContainer()->get(OrderService::class);
|
||||||
$em = $this->getContainer()->get(EntityManagerInterface::class);
|
$em = $this->getContainer()->get(EntityManagerInterface::class);
|
||||||
|
@ -414,12 +420,12 @@ test('removeOrderItem removes item from order', function () {
|
||||||
// Verify the item was removed from the database
|
// Verify the item was removed from the database
|
||||||
$em->refresh($order);
|
$em->refresh($order);
|
||||||
$matchingItems = $order->getOrderItems()->filter(
|
$matchingItems = $order->getOrderItems()->filter(
|
||||||
fn($item) => $item->getDrinkType()->getId() === $drinkType->getId()
|
fn($item): bool => $item->getDrinkType()->getId() === $drinkType->getId()
|
||||||
);
|
);
|
||||||
expect($matchingItems->count())->toBe(0);
|
expect($matchingItems->count())->toBe(0);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('deleteOrder removes order and its items', function () {
|
test('deleteOrder removes order and its items', function (): void {
|
||||||
// Arrange
|
// Arrange
|
||||||
$orderService = $this->getContainer()->get(OrderService::class);
|
$orderService = $this->getContainer()->get(OrderService::class);
|
||||||
$em = $this->getContainer()->get(EntityManagerInterface::class);
|
$em = $this->getContainer()->get(EntityManagerInterface::class);
|
||||||
|
|
|
@ -1,7 +1,8 @@
|
||||||
<?php
|
<?php
|
||||||
|
|
||||||
use Doctrine\ORM\EntityManagerInterface;
|
declare(strict_types=1);
|
||||||
use Doctrine\ORM\Tools\SchemaTool;
|
|
||||||
|
use Tests\DbTestCase;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|--------------------------------------------------------------------------
|
|--------------------------------------------------------------------------
|
||||||
|
@ -14,7 +15,7 @@ use Doctrine\ORM\Tools\SchemaTool;
|
||||||
|
|
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
pest()->extend(Tests\DbTestCase::class)->in('Feature');
|
pest()->extend(DbTestCase::class)->in('Feature');
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|--------------------------------------------------------------------------
|
|--------------------------------------------------------------------------
|
||||||
|
@ -26,4 +27,3 @@ pest()->extend(Tests\DbTestCase::class)->in('Feature');
|
||||||
| global functions to help you to reduce the number of lines of code in your test files.
|
| global functions to help you to reduce the number of lines of code in your test files.
|
||||||
|
|
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
|
|
@ -1,10 +1,9 @@
|
||||||
<?php
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace Tests;
|
namespace Tests;
|
||||||
|
|
||||||
use PHPUnit\Framework\TestCase as BaseTestCase;
|
|
||||||
use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase;
|
use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase;
|
||||||
|
|
||||||
abstract class TestCase extends KernelTestCase
|
abstract class TestCase extends KernelTestCase {}
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
<?php
|
<?php
|
||||||
|
|
||||||
test('example', function () {
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
test('example', function (): void {
|
||||||
expect(true)->toBeTrue();
|
expect(true)->toBeTrue();
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,13 +1,15 @@
|
||||||
<?php
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
use Symfony\Component\Dotenv\Dotenv;
|
use Symfony\Component\Dotenv\Dotenv;
|
||||||
|
|
||||||
require dirname(__DIR__).'/vendor/autoload.php';
|
require dirname(__DIR__) . '/vendor/autoload.php';
|
||||||
|
|
||||||
if (method_exists(Dotenv::class, 'bootEnv')) {
|
if (method_exists(Dotenv::class, 'bootEnv')) {
|
||||||
(new Dotenv())->bootEnv(dirname(__DIR__).'/.env');
|
new Dotenv()->bootEnv(dirname(__DIR__) . '/.env');
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($_SERVER['APP_DEBUG']) {
|
if ($_SERVER['APP_DEBUG']) {
|
||||||
umask(0000);
|
umask(0o000);
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue