feat(CustomerController): Ajoute route pour création de commande client

Ajoute une route pour créer des commandes (devis, avis de paiement, facture) pour un client spécifique. Crée template associé.
This commit is contained in:
Serreau Jovann
2025-07-24 08:54:04 +02:00
parent d4d9d47bd1
commit a18b89b790
8 changed files with 229 additions and 16 deletions

3
.env
View File

@@ -57,3 +57,6 @@ INSEE_KEYAPI=f941bdf0-b8a3-4e4c-81bd-f0b8a35e4cc8
OVH_KEY=34bc2c2eb416b67d
OVH_SECRET=12239d273975b5ab53318907fb66d355
OVH_CUSTOMER=56c387eb9ca4b9a2de4d4d97fd3d7f22
DOCUSIGN_URL=signature.esy-web.dev
DOCUSIGN_KEY=52u82oCoiG79awGsuxLfJqhxYjg8mrJfAsJJHejRMFa

View File

@@ -14,7 +14,7 @@ label,span,input,{
color:var(--color-red-700);
}
select,
input {
background: oklch(21% 0.034 264.665);
color: white;

View File

@@ -0,0 +1,133 @@
/**
* Repeater Component - Duplicates the first element in its `rows` ref up to `maxRows` times.
*
* Suggested structure (Emmet):
* [data-component="repeater"]>(ol[data-ref="rows"][tabindex="0"]>li>((fieldset.your-fields>input)+button[data-ref="removeButton"]))+button[data-ref="addButton"]
*
* Refs:
* {HTMLElement | HTMLOListElement | HTMLUListElement} rows - The element that holds the repeated fields. Should be focusable.
* {HTMLButtonElement} removeButton - Button for removing a row. Place inside the default row.
* {HTMLButtonElement} addButton - Button for adding a row.
*
* Props:
* {?number} maxRows - The most rows the repeater will let the user add.
*/
export function repeaterComponent($el) {
const $props = getProps($el, { maxRows: 5 });
const $refs = getRefs($el);
let rowHTML = $refs.rows.children[0].outerHTML;
// Hook up events for the row.
function setUpRow(row) {
const rowRefs = getRefs(row);
rowRefs.removeButton.onclick = (e) => {
e.preventDefault();
removeRow(row);
};
}
// Enable or disable addButton as necessary.
function updateAddButton() {
if ($refs.rows.children.length >= $props.maxRows) {
$refs.addButton.setAttribute('disabled', '');
return;
}
$refs.addButton.removeAttribute('disabled');
}
// Update array key values to the row number
function updateFieldNames() {
[...$refs.rows.children]
.forEach((el, index) => {
el.querySelectorAll('[name]')
.forEach(el => {
const newName = el.getAttribute('name').replace(/\[\d\]/gm, `[${index}]`);
el.setAttribute('name', newName);
});
});
}
function addRow() {
if (
!rowHTML ||
$refs.rows.children.length >= $props.maxRows
) return;
let newRow = createFromHTML(rowHTML);
newRow.removeAttribute('id');
setUpRow(newRow);
$refs.rows.appendChild(newRow);
newRow.querySelector('input,textarea,select').focus();
updateFieldNames();
updateAddButton();
}
function removeRow(row) {
if ($refs.rows.children.length <= 1) return;
row.remove();
$refs.rows.focus();
updateFieldNames();
updateAddButton();
}
function init() {
setUpRow($refs.rows.children[0]);
$refs.addButton.onclick = (e) => {
e.preventDefault();
addRow();
}
updateFieldNames();
}
init();
}
// Return an object that contains references to DOM objects.
function getRefs(el) {
let result = {};
[...el.querySelectorAll('[data-ref]')]
.forEach(ref => {
result[ref.dataset.ref] = ref;
});
return result;
}
function setDefaults(obj, defaults) {
let results = obj;
for (const prop in defaults) {
if (!obj.hasOwnProperty(prop)) {
results[prop] = defaults[prop];
}
}
return results;
}
// Get initial component data from the `data-props` attribute in JSON format.
function getProps(el, defaults={}) {
return setDefaults(
JSON.parse(el.dataset.props ?? '{}'),
defaults
);
}
// Create a new element from an HTML string.
function createFromHTML(html='') {
let element = document.createElement(null);
element.innerHTML = html;
return element.firstElementChild;
}

BIN
bun.lockb

Binary file not shown.

View File

@@ -25,6 +25,7 @@
"@sentry/browser": "^9.34.0",
"@tailwindcss/vite": "^4.1.10",
"autoprefixer": "^10.4.21",
"sortablejs": "^1.15.6",
"tailwindcss": "^4.1.10"
}
}

View File

@@ -169,4 +169,12 @@ class CustomerController extends AbstractController
'form' => $form->createView()
]);
}
#[Route(path: '/artemis/intranet/customer/{id}/orderAdd',name: 'artemis_intranet_customer_orderAdd',methods: ['GET', 'POST'])]
public function customerOrder(?Customer $customer,Request $request,LoggerService $loggerService,EntityManagerInterface $entityManager,EventDispatcherInterface $eventDispatcher): Response
{
return $this->render('artemis/intranet/customer/order-add.twig',[
'customer' => $customer,
]);
}
}

View File

@@ -0,0 +1,9 @@
{% extends 'artemis/base.twig' %}
{% block title %}Intranet - Client - {{ customer.raisonSocial }} - Création devis / avis de paiement / facture{% endblock %}
{% block content %}
<div class="flex justify-between items-center mb-6">
<h2 class="text-3xl font-semibold text-gray-800 dark:text-gray-200">Client - {{ customer.raisonSocial }} - Création devis / avis de paiement / facture</h2>
</div>
{% endblock %}

View File

@@ -1,16 +1,75 @@
<div class="flex space-x-4 mb-6 border-b border-gray-700">
{% set active = "text-sm border-b-2 border-purple-500" %}
{% set desactive = "text-sm text-gray-400 hover:text-white" %}
<a href="{{ path('artemis_intranet_customer_view',{id:customer.id,current:'order',currentOrder:'f'}) }}" class="px-4 py-2 font-semibold {% if currentOrder == "f" %}{{ active }}{% else %}{{ desactive }}{% endif %}">
<i class="fad fa-home"></i>
Facture
</a>
<a href="{{ path('artemis_intranet_customer_view',{id:customer.id,current:'order',currentOrder:'a'}) }}" class="px-4 py-2 font-semibold {% if currentOrder == "a" %}{{ active }}{% else %}{{ desactive }}{% endif %}">
<i class="fad fa-file-pdf"></i>
Avis de paiement
</a>
<a href="{{ path('artemis_intranet_customer_view',{id:customer.id,current:'order',currentOrder:'d'}) }}" class="px-4 py-2 font-semibold {% if currentOrder == "d" %}{{ active }}{% else %}{{ desactive }}{% endif %}">
<i class="fad fa-spider-web"></i>
Devis
</a>
<div class="flex justify-between items-center mb-6">
<h2 class="text-3xl font-semibold text-gray-800 dark:text-gray-200">Factures / Devis / Avis de Paiement</h2>
<div>
<a href="{{ path('artemis_intranet_customer_orderAdd',{id:customer.id}) }}" class="px-4 py-2 bg-blue-600 text-white font-medium rounded-md shadow-md hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2 dark:focus:ring-offset-gray-900">
+ Crée un devis / avis de paiement / facture
</a>
</div>
</div>
<!-- Barre de recherche + filtre type -->
<div class="bg-gray-800 rounded-lg shadow p-6 mb-6">
<form method="get" action="" class="grid grid-cols-1 md:grid-cols-2 gap-4">
<div>
<label for="searchInput" class="block mb-1 font-semibold">Rechercher :</label>
<input
type="search"
id="searchInput"
name="search"
placeholder="Nom ou numéro de document"
class="w-full px-4 py-2 rounded-md text-gray-900 focus:outline-none focus:ring-2 focus:ring-blue-500"
/>
</div>
<div>
<label for="typeFilter" class="block mb-1 font-semibold">Filtrer par type :</label>
<select
id="typeFilter"
name="type"
class="w-full px-4 py-2 rounded-md text-gray-900 focus:outline-none focus:ring-2 focus:ring-blue-500"
>
<option value="">Tous</option>
<option value="facture">Facture</option>
<option value="devis">Devis</option>
<option value="avis">Avis de paiement</option>
</select>
</div>
<div class="md:col-span-2">
<button
type="submit"
class="mt-3 bg-blue-600 hover:bg-blue-700 text-white px-4 py-2 rounded"
>
Filtrer
</button>
</div>
</form>
</div>
<!-- Tableau -->
<div class="overflow-x-auto bg-gray-800 rounded-lg shadow">
<table class="min-w-full divide-y divide-gray-700">
<thead class="bg-gray-700">
<tr>
<th class="px-6 py-3 text-left text-xs font-medium uppercase tracking-wider">Type</th>
<th class="px-6 py-3 text-left text-xs font-medium uppercase tracking-wider">N°</th>
<th class="px-6 py-3 text-left text-xs font-medium uppercase tracking-wider">Date</th>
<th class="px-6 py-3 text-left text-xs font-medium uppercase tracking-wider">Montant</th>
<th class="px-6 py-3 text-left text-xs font-medium uppercase tracking-wider">Statut</th>
<th class="px-6 py-3 text-center text-xs font-medium uppercase tracking-wider">Actions</th>
</tr>
</thead>
<tbody>
<tr class="hover:bg-gray-700">
<td class="px-6 py-4">Facture</td>
<td class="px-6 py-4">F2025-001</td>
<td class="px-6 py-4">2025-07-01</td>
<td class="px-6 py-4">1 200,00 €</td>
<td class="px-6 py-4 text-green-400">Payé</td>
<td class="px-6 py-4 text-center">
<button class="bg-blue-600 hover:bg-blue-700 text-white px-3 py-1 rounded">Modifier</button>
<button class="bg-blue-600 hover:bg-blue-700 text-white px-3 py-1 rounded">Télécharger</button>
<button class="bg-blue-600 hover:bg-blue-700 text-white px-3 py-1 rounded">Annulée</button>
</td>
</tr>
</tbody>
</table>
</div>