window.updateSaveButtonState = window.updateSaveButtonState || function () {}; window.markOrderDirty = window.markOrderDirty || function () {}; (function () { function initWorkspace() { const wrap = document.getElementById('ass-client-workspace'); if (!wrap) return; const ajaxUrl = wrap.dataset.ajaxUrl; const nonce = wrap.dataset.nonce; const screen = wrap.dataset.screen || 'workspace'; const initialOrderId = parseInt(wrap.dataset.orderId || '0', 10); const initialOrderMode = ['view', 'edit', 'repeat', 'new'].includes(wrap.dataset.orderMode || 'edit') ? (wrap.dataset.orderMode || 'edit') : 'edit'; const initialCustomerId = parseInt(wrap.dataset.customerId || '0', 10); const workspaceBaseUrl = wrap.dataset.workspaceUrl || (window.location.origin + window.location.pathname); const workspaceScreen = document.getElementById('asscw-workspace-screen'); const orderScreen = document.getElementById('asscw-order-screen'); const orderScreenContent = document.getElementById('asscw-order-screen-content'); const orderTitle = document.getElementById('asscw-order-title'); const saveOrderButton = document.getElementById('asscw-save-order-button'); const noticeBox = document.getElementById('asscw-notice'); const backToCustomerBtn = document.getElementById('asscw-back-to-customer-button'); const backToWorkspaceLink = document.getElementById('asscw-back-to-workspace-link'); const input = document.getElementById('asscw-search-input'); const clearBtn = document.getElementById('asscw-clear-search'); const resultsBox = document.getElementById('asscw-search-results'); const detailBox = document.getElementById('asscw-client-detail'); const ordersBox = document.getElementById('asscw-orders'); const selectedClientBar = document.getElementById('asscw-selected-client-bar'); const selectedClientName = document.getElementById('asscw-selected-client-name'); const changeClientBtn = document.getElementById('asscw-change-client-btn'); let timer = null; let productTimer = null; let selectedCustomerId = 0; let selectedCustomerLabel = ''; let keyboardIndex = -1; let currentOrderData = null; let currentOrderMode = initialOrderMode; let currentCustomerId = initialCustomerId || 0; let orderHasUnsavedChanges = false; let isSavingOrder = false; let productSearchRequestId = 0; let currentProductPreviewId = 0; let clientSearchRequestId = 0; let lastSearchTerm = ''; let lastSearchResults = []; const searchStateStorageKey = 'asscw_last_search_state'; const defaultDetailHtml = 'Sélectionne un client pour afficher sa fiche.'; const defaultOrdersHtml = 'Aucune commande à afficher.'; function escapeHtml(value) { return String(value || '') .replace(/&/g, '&') .replace(//g, '>') .replace(/"/g, '"') .replace(/'/g, '''); } function showNotice(message, type) { if (!noticeBox) return; noticeBox.className = 'asscw-notice ' + (type === 'error' ? 'is-error' : 'is-success'); noticeBox.textContent = message || ''; noticeBox.style.display = 'block'; window.clearTimeout(showNotice._timer); showNotice._timer = window.setTimeout(function () { noticeBox.style.display = 'none'; }, 5000); } function updateSaveButtonState() { if (!saveOrderButton) return; if (currentOrderMode === 'view' || !currentOrderData) { saveOrderButton.style.display = 'none'; return; } saveOrderButton.style.display = 'inline-flex'; if (isSavingOrder) { saveOrderButton.disabled = true; saveOrderButton.textContent = 'Enregistrement...'; return; } saveOrderButton.disabled = !orderHasUnsavedChanges; if (currentOrderMode === 'repeat' || currentOrderMode === 'new') { saveOrderButton.textContent = orderHasUnsavedChanges ? 'Créer la commande' : 'Enregistré'; return; } saveOrderButton.textContent = orderHasUnsavedChanges ? 'Enregistrer' : 'Enregistré'; } function markOrderDirty() { if (currentOrderMode === 'view') return; orderHasUnsavedChanges = true; updateSaveButtonState(); } window.updateSaveButtonState = updateSaveButtonState; window.markOrderDirty = markOrderDirty; function post(action, payload) { const form = new URLSearchParams(); form.append('action', action); form.append('nonce', nonce); Object.keys(payload || {}).forEach(function (key) { const value = payload[key]; if (typeof value === 'object') { form.append(key, JSON.stringify(value)); } else { form.append(key, value); } }); return fetch(ajaxUrl, { method: 'POST', headers: { 'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8' }, body: form.toString(), credentials: 'same-origin' }).then(async function (response) { const text = await response.text(); try { return JSON.parse(text); } catch (e) { throw new Error('La requête a renvoyé une réponse invalide.'); } }); } function buildCustomerUrl(customerId) { const url = new URL(workspaceBaseUrl, window.location.origin); url.searchParams.delete('asscw_screen'); url.searchParams.delete('asscw_order'); url.searchParams.delete('asscw_mode'); url.searchParams.delete('asscw_customer'); if (customerId) { url.searchParams.set('asscw_customer', String(customerId)); } return url.toString(); } function buildWorkspaceUrl() { const url = new URL(workspaceBaseUrl, window.location.origin); url.searchParams.delete('asscw_screen'); url.searchParams.delete('asscw_order'); url.searchParams.delete('asscw_mode'); url.searchParams.delete('asscw_customer'); return url.toString(); } function updateWorkspaceUrl(customerId) { window.history.replaceState({}, '', customerId ? buildCustomerUrl(customerId) : buildWorkspaceUrl()); } function updateClearButton() { if (!clearBtn || !input) return; clearBtn.style.display = input.value.trim().length ? 'block' : 'none'; } function showResultsBox() { if (resultsBox) resultsBox.style.display = 'block'; } function hideResultsBox() { if (resultsBox) resultsBox.style.display = 'none'; } function showSelectedClientBar(label) { if (!selectedClientBar || !selectedClientName) return; selectedClientName.textContent = label || ''; selectedClientBar.style.display = 'flex'; } function hideSelectedClientBar() { if (!selectedClientBar || !selectedClientName) return; selectedClientName.textContent = ''; selectedClientBar.style.display = 'none'; } function getResultItems() { return Array.from(resultsBox.querySelectorAll('.asscw-result-item[data-customer-id]')); } function resetKeyboardSelection() { keyboardIndex = -1; clearSelectionHighlight(); } function applyKeyboardHighlight() { const items = getResultItems(); items.forEach(function (item, index) { if (index === keyboardIndex) { item.classList.add('asscw-selected'); item.scrollIntoView({ block: 'nearest' }); } else if (parseInt(item.getAttribute('data-customer-id'), 10) !== parseInt(selectedCustomerId, 10)) { item.classList.remove('asscw-selected'); } }); } function setKeyboardIndex(index) { const items = getResultItems(); if (!items.length) { keyboardIndex = -1; return; } if (index < 0) index = items.length - 1; if (index >= items.length) index = 0; keyboardIndex = index; applyKeyboardHighlight(); } function clearSelectionHighlight() { resultsBox.querySelectorAll('.asscw-result-item.asscw-selected').forEach(function (item) { item.classList.remove('asscw-selected'); }); } function highlightSelectedCustomer() { clearSelectionHighlight(); if (!selectedCustomerId) return; const item = resultsBox.querySelector('.asscw-result-item[data-customer-id="' + selectedCustomerId + '"]'); if (item) item.classList.add('asscw-selected'); } function resetWorkspace() { selectedCustomerId = 0; selectedCustomerLabel = ''; clearLastSearchState(); resultsBox.innerHTML = ''; showResultsBox(); hideSelectedClientBar(); resetKeyboardSelection(); detailBox.innerHTML = '
' + defaultDetailHtml + '
'; ordersBox.innerHTML = '
' + defaultOrdersHtml + '
'; updateClearButton(); updateWorkspaceUrl(0); updateBackButtons(0); } function switchToWorkspace() { if (workspaceScreen) workspaceScreen.style.display = ''; if (orderScreen) orderScreen.style.display = 'none'; } function switchToOrderScreen() { if (workspaceScreen) workspaceScreen.style.display = 'none'; if (orderScreen) orderScreen.style.display = ''; } function updateBackButtons(customerId) { currentCustomerId = parseInt(customerId || '0', 10) || 0; if (backToCustomerBtn) backToCustomerBtn.style.display = currentCustomerId ? '' : 'none'; if (backToWorkspaceLink) backToWorkspaceLink.href = buildWorkspaceUrl(); } function renderStockBadge(product) { const status = product && product.stock_status ? product.stock_status : ''; if (!status) return ''; const labelMap = { instock: 'En stock', onbackorder: 'Sur commande', outofstock: 'Rupture' }; return '' + escapeHtml(labelMap[status] || status) + ''; } function renderProductPreviewButton(product) { if (!product || !product.id) return ''; return ''; } function renderProductThumb(product, extraClass) { if (!product) return ''; const className = extraClass ? 'asscw-product-thumb ' + extraClass : 'asscw-product-thumb'; const productId = escapeHtml(product.id || product.product_id || ''); if (product.image_url) { return ''; } return ''; } function renderConditioningBadge(product) { if (!product || !product.conditioning_label) return ''; return '
' + escapeHtml(product.conditioning_label) + '
'; } function getProductPreviewHost() { const workspaceHost = document.getElementById('asscw-workspace-product-preview-host'); const orderHost = document.getElementById('asscw-product-preview-host') || orderScreenContent; const isWorkspaceVisible = workspaceScreen && workspaceScreen.style.display !== 'none'; if (isWorkspaceVisible && workspaceHost) return workspaceHost; return orderHost || workspaceHost || null; } function isLandscapeTabletLayout() { return window.matchMedia('(min-width: 981px)').matches && window.matchMedia('(orientation: landscape)').matches; } function syncProductPreviewLayout() { const panel = document.getElementById('asscw-product-preview-panel'); if (!panel) return; panel.classList.toggle('is-side-drawer', isLandscapeTabletLayout()); panel.classList.toggle('is-bottom-sheet', !isLandscapeTabletLayout()); } function closeProductPreview() { const preview = document.getElementById('asscw-product-preview-panel'); if (preview) preview.remove(); const orderLayout = document.querySelector('.asscw-order-layout'); if (orderLayout) orderLayout.classList.remove('has-product-preview'); currentProductPreviewId = 0; } function renderProductPreview(product) { closeProductPreview(); if (!product) return; currentProductPreviewId = parseInt(product.id || '0', 10) || 0; const attributes = Array.isArray(product.attributes) ? product.attributes : []; const qtyText = (product.stock_qty === null || typeof product.stock_qty === 'undefined') ? '' : String(product.stock_qty); const imageHtml = product.image_url ? '
' + escapeHtml(product.name || '') + '
' : ''; const links = [ product.permalink ? 'Voir la page' : '', product.edit_url ? 'Éditer' : '' ].filter(Boolean).join(' '); const panel = document.createElement('div'); panel.id = 'asscw-product-preview-panel'; panel.className = 'asscw-panel asscw-product-preview-panel'; panel.innerHTML = '

Fiche produit

' + imageHtml + '
' + '
' + escapeHtml(product.name || '') + '
' + (product.sku ? '
SKU : ' + escapeHtml(product.sku) + '
' : '') + '
' + (product.price_html ? '
Prix : ' + escapeHtml(product.price_html) + '
' : '') + (renderStockBadge(product) ? '
Stock : ' + renderStockBadge(product) + (qtyText !== '' ? ' · Qté : ' + escapeHtml(qtyText) : '') + '
' : '') + (product.categories ? '
Catégories : ' + escapeHtml(product.categories) + '
' : '') + (product.conditioning_label ? '
Conditionnement : ' + escapeHtml(product.conditioning_label) + '
' : '') + (product.weight ? '
Poids : ' + escapeHtml(product.weight) + '
' : '') + (product.dimensions ? '
Dimensions : ' + escapeHtml(product.dimensions) + '
' : '') + '
' + (attributes.length ? '
' + attributes.map(function(attr){ return '
' + escapeHtml(attr.label || '') + ' : ' + escapeHtml(attr.value || '') + '
'; }).join('') + '
' : '') + (product.short_description ? '
Résumé :
' + escapeHtml(product.short_description) + '
' : '') + (product.description ? '
Description :
' + escapeHtml(product.description) + '
' : '') + (links ? '
' + links + '
' : '') + '
'; const host = getProductPreviewHost(); if (!host) return; host.appendChild(panel); const orderLayout = document.querySelector('.asscw-order-layout'); if (orderLayout) orderLayout.classList.add('has-product-preview'); syncProductPreviewLayout(); const closeBtn = panel.querySelector('.asscw-product-preview-close'); if (closeBtn) closeBtn.addEventListener('click', closeProductPreview); } function openProductPreview(productId) { productId = parseInt(productId || '0', 10) || 0; if (!productId) return; if (currentProductPreviewId === productId) { closeProductPreview(); return; } closeProductPreview(); const loading = document.createElement('div'); loading.id = 'asscw-product-preview-panel'; loading.className = 'asscw-panel asscw-product-preview-panel'; loading.innerHTML = '
Chargement de la fiche produit...
'; const host = getProductPreviewHost(); if (!host) return; host.appendChild(loading); const orderLayout = document.querySelector('.asscw-order-layout'); if (orderLayout) orderLayout.classList.add('has-product-preview'); syncProductPreviewLayout(); post('ass_get_product_detail', { product_id: productId }) .then(function(data){ if (!data || !data.success) throw new Error(data && data.data && data.data.message ? data.data.message : 'Erreur de chargement produit.'); renderProductPreview(data.data.product || null); }) .catch(function(error){ const panel = document.getElementById('asscw-product-preview-panel'); if (panel) panel.innerHTML = '
' + escapeHtml(error.message || 'Erreur de chargement produit.') + '
'; currentProductPreviewId = productId; }); } function bindProductPreviewButtons(scope) { if (!scope) return; scope.querySelectorAll('.asscw-view-product-button').forEach(function(button){ button.addEventListener('click', function(){ openProductPreview(button.getAttribute('data-product-id')); }); }); } function persistLastSearchState() { try { window.sessionStorage.setItem(searchStateStorageKey, JSON.stringify({ term: lastSearchTerm || '', results: Array.isArray(lastSearchResults) ? lastSearchResults : [], selected_customer_id: selectedCustomerId || 0, selected_customer_label: selectedCustomerLabel || '' })); } catch (error) {} } function hydrateLastSearchState() { try { const raw = window.sessionStorage.getItem(searchStateStorageKey); if (!raw) return; const parsed = JSON.parse(raw); if (parsed && typeof parsed.term === 'string') { lastSearchTerm = parsed.term; } if (parsed && Array.isArray(parsed.results)) { lastSearchResults = parsed.results; } if (!selectedCustomerId && parsed && parsed.selected_customer_id) { selectedCustomerId = parseInt(parsed.selected_customer_id, 10) || 0; } if (!selectedCustomerLabel && parsed && typeof parsed.selected_customer_label === 'string') { selectedCustomerLabel = parsed.selected_customer_label; } } catch (error) {} } function clearLastSearchState() { lastSearchTerm = ''; lastSearchResults = []; try { window.sessionStorage.removeItem(searchStateStorageKey); } catch (error) {} } function renderSearchResults(items) { resetKeyboardSelection(); showResultsBox(); if (!items || !items.length) { resultsBox.innerHTML = '
Aucun client trouvé.
'; return; } resultsBox.innerHTML = items.map(function (item) { const selectedClass = parseInt(item.id, 10) === parseInt(selectedCustomerId, 10) ? ' asscw-selected' : ''; return `
${escapeHtml(item.label)}
${item.contact_name ? 'Contact : ' + escapeHtml(item.contact_name) + ' · ' : ''} ${item.phone ? 'Tél : ' + escapeHtml(item.phone) + ' · ' : ''} ${item.email ? 'Email : ' + escapeHtml(item.email) : ''}
`; }).join(''); } function renderFrequentProducts(products) { if (!products || !products.length) return ''; return `

Produits fréquents

${products.map(function (product) { return `
${renderProductThumb(product, 'is-inline')}${renderProductPreviewButton(product)}${renderConditioningBadge(product)}
`; }).join('')}
`; } function buildClassificationHtml(workspaceDetail, customer) { const master = workspaceDetail && workspaceDetail.master ? workspaceDetail.master : null; const parts = []; if (master) { if (master.category_label) parts.push(master.category_label); if (master.subcategory_label) parts.push(master.subcategory_label); if (master.subsubcategory_label) parts.push(master.subsubcategory_label); } if (!parts.length) { if (customer && customer.customer_category) parts.push(customer.customer_category); if (customer && customer.customer_subcategory) parts.push(customer.customer_subcategory); } if (!parts.length) { return ''; } return escapeHtml(parts.join(' → ')); } function buildCustomerTabsNav() { return `
`; } function bindCustomerTabs(defaultTab) { const tabWrap = document.getElementById('asscw-customer-tabs'); if (!tabWrap) return; const buttons = Array.from(tabWrap.querySelectorAll('.asscw-tab-button')); const panels = Array.from(document.querySelectorAll('.asscw-tab-panel')); const firstTab = defaultTab || 'summary'; function activate(tabName) { buttons.forEach(function (button) { button.classList.toggle('is-active', button.getAttribute('data-tab') === tabName); }); panels.forEach(function (panel) { panel.style.display = panel.getAttribute('data-tab-panel') === tabName ? '' : 'none'; }); } buttons.forEach(function (button) { button.addEventListener('click', function () { activate(button.getAttribute('data-tab')); }); }); activate(firstTab); } function buildEditableContactRowsHtml(contacts) { if (!contacts || !contacts.length) { return '
Aucun contact opérationnel.
'; } return `
${contacts.map(function (contact) { const id = parseInt(contact.id || '0', 10) || 0; return `
`; }).join('')}
`; } function buildEditableAddressRowsHtml(addresses) { if (!addresses || !addresses.length) { return '
Aucune adresse de livraison.
'; } return `
${addresses.map(function (address) { const id = parseInt(address.id || '0', 10) || 0; return `
`; }).join('')}
`; } function buildAddContactFormHtml() { return `

Ajouter un contact

`; } function buildAddAddressFormHtml() { return `

Ajouter une adresse de livraison

`; } function renderCustomerDetail(customer, frequentProducts, workspaceDetail, activeTab) { const billingLines = (customer.billing_lines || []).map(function (line) { return '
  • ' + escapeHtml(line) + '
  • '; }).join(''); const shippingLines = (customer.shipping_lines || []).map(function (line) { return '
  • ' + escapeHtml(line) + '
  • '; }).join(''); const pricelist = customer.pricelist_label || ''; const category = customer.customer_category || ''; const subcategory = customer.customer_subcategory || ''; const shippingAddresses = Array.isArray(customer.shipping_addresses) ? customer.shipping_addresses : []; const workspaceContacts = workspaceDetail && Array.isArray(workspaceDetail.contacts) ? workspaceDetail.contacts : []; const workspaceAddresses = workspaceDetail && Array.isArray(workspaceDetail.addresses) ? workspaceDetail.addresses : []; let shippingSelectorHtml = ''; if (shippingAddresses.length > 0) { shippingSelectorHtml = `

    Choisir l’adresse de livraison

    ${shippingAddresses.map(function (address, index) { const addressId = String(address.id || ''); const label = escapeHtml(address.label || ('Adresse ' + (index + 1))); const company = escapeHtml(address.company || ''); const address1 = escapeHtml(address.address_1 || ''); const address2 = escapeHtml(address.address_2 || ''); const postcode = escapeHtml(address.postcode || ''); const city = escapeHtml(address.city || ''); const country = escapeHtml(address.country || ''); const phone = escapeHtml(address.phone || ''); const isDefault = !!address.is_default; const checked = isDefault || index === 0 ? 'checked' : ''; const cityLine = [postcode, city].filter(Boolean).join(' '); const parts = [ company, address1, address2, cityLine, country, phone ? ('Téléphone : ' + phone) : '' ].filter(Boolean); return ` `; }).join('')}
    `; } detailBox.innerHTML = ` ${buildCustomerTabsNav()}
    Client
    ${escapeHtml(customer.label || '')}
    Contact
    ${escapeHtml(customer.contact_name || '') || ''}
    Téléphone
    ${escapeHtml(customer.phone || '') || ''}
    Email
    ${escapeHtml(customer.email || '') || ''}
    TVA
    ${escapeHtml(customer.vat_number || '') || ''}
    ID Odoo
    ${escapeHtml(customer.odoo_partner_id || '') || ''}
    Source
    ${escapeHtml(customer.client_source || '') || ''}
    Dernière synchro
    ${escapeHtml(customer.odoo_last_sync || '') || ''}
    Tarif
    ${escapeHtml(pricelist) || ''}
    Catégorie
    ${escapeHtml(category) || ''}
    Sous-catégorie
    ${escapeHtml(subcategory) || ''}
    Classification
    ${buildClassificationHtml(workspaceDetail, customer)}
    Adresse facturation
    ${billingLines ? '
      ' + billingLines + '
    ' : ''}
    Adresse livraison par défaut
    ${shippingLines ? '
      ' + shippingLines + '
    ' : ''}
    ${shippingSelectorHtml}
    ${renderFrequentProducts(frequentProducts)}
    `; bindCustomerTabs(activeTab || 'summary'); const createBtn = document.getElementById('asscw-create-order-button'); bindProductPreviewButtons(detailBox); if (createBtn) { createBtn.addEventListener('click', function () { let selectedAddressId = ''; const selectedAddressInput = document.querySelector('input[name="asscw_shipping_address"]:checked'); if (selectedAddressInput) { selectedAddressId = selectedAddressInput.value || ''; } openNewOrderScreen(customer, frequentProducts, selectedAddressId); }); } const addContactBtn = document.getElementById('asscw-add-contact-button'); if (addContactBtn) { addContactBtn.addEventListener('click', function () { post('ass_add_customer_contact', { customer_id: customer.id, contact_type: document.getElementById('asscw-new-contact-type').value || 'other', first_name: document.getElementById('asscw-new-contact-first-name').value || '', last_name: document.getElementById('asscw-new-contact-last-name').value || '', job_title: document.getElementById('asscw-new-contact-job-title').value || '', email: document.getElementById('asscw-new-contact-email').value || '', phone: document.getElementById('asscw-new-contact-phone').value || '', is_primary: document.getElementById('asscw-new-contact-primary').checked ? 1 : 0 }) .then(function (data) { if (!data || !data.success) { throw new Error(data && data.data && data.data.message ? data.data.message : 'Erreur lors de l’ajout du contact.'); } showNotice(data.data.message || 'Contact ajouté.', 'success'); renderCustomerDetail(customer, frequentProducts, data.data.workspace_detail || workspaceDetail, 'contacts'); }) .catch(function (error) { showNotice(error.message || 'Erreur lors de l’ajout du contact.', 'error'); }); }); } detailBox.querySelectorAll('.asscw-save-contact-button').forEach(function (button) { button.addEventListener('click', function () { const contactId = parseInt(button.getAttribute('data-contact-id'), 10); const box = button.closest('[data-contact-id]'); if (!contactId || !box) return; post('ass_update_customer_contact', { customer_id: customer.id, contact_id: contactId, contact_type: (box.querySelector('.asscw-edit-contact-type') || {}).value || 'other', first_name: (box.querySelector('.asscw-edit-contact-first-name') || {}).value || '', last_name: (box.querySelector('.asscw-edit-contact-last-name') || {}).value || '', job_title: (box.querySelector('.asscw-edit-contact-job-title') || {}).value || '', email: (box.querySelector('.asscw-edit-contact-email') || {}).value || '', phone: (box.querySelector('.asscw-edit-contact-phone') || {}).value || '', is_primary: (box.querySelector('.asscw-edit-contact-primary') || {}).checked ? 1 : 0 }) .then(function (data) { if (!data || !data.success) { throw new Error(data && data.data && data.data.message ? data.data.message : 'Erreur lors de la mise à jour du contact.'); } showNotice(data.data.message || 'Contact mis à jour.', 'success'); renderCustomerDetail(customer, frequentProducts, data.data.workspace_detail || workspaceDetail, 'contacts'); }) .catch(function (error) { showNotice(error.message || 'Erreur lors de la mise à jour du contact.', 'error'); }); }); }); const addAddressBtn = document.getElementById('asscw-add-address-button'); if (addAddressBtn) { addAddressBtn.addEventListener('click', function () { post('ass_add_delivery_address', { customer_id: customer.id, label: document.getElementById('asscw-new-address-label').value || '', company: document.getElementById('asscw-new-address-company').value || '', attention_to: document.getElementById('asscw-new-address-attention').value || '', address_1: document.getElementById('asscw-new-address-1').value || '', address_2: document.getElementById('asscw-new-address-2').value || '', postcode: document.getElementById('asscw-new-address-postcode').value || '', city: document.getElementById('asscw-new-address-city').value || '', country_code: document.getElementById('asscw-new-address-country').value || '', phone: document.getElementById('asscw-new-address-phone').value || '', email: document.getElementById('asscw-new-address-email').value || '', instructions: document.getElementById('asscw-new-address-instructions').value || '', is_default: document.getElementById('asscw-new-address-default').checked ? 1 : 0 }) .then(function (data) { if (!data || !data.success) { throw new Error(data && data.data && data.data.message ? data.data.message : 'Erreur lors de l’ajout de l’adresse.'); } showNotice(data.data.message || 'Adresse ajoutée.', 'success'); renderCustomerDetail(customer, frequentProducts, data.data.workspace_detail || workspaceDetail, 'addresses'); }) .catch(function (error) { showNotice(error.message || 'Erreur lors de l’ajout de l’adresse.', 'error'); }); }); } detailBox.querySelectorAll('.asscw-save-address-button').forEach(function (button) { button.addEventListener('click', function () { const addressId = parseInt(button.getAttribute('data-address-id'), 10); const box = button.closest('[data-address-id]'); if (!addressId || !box) return; post('ass_update_delivery_address', { customer_id: customer.id, address_id: addressId, label: (box.querySelector('.asscw-edit-address-label') || {}).value || '', company: (box.querySelector('.asscw-edit-address-company') || {}).value || '', attention_to: (box.querySelector('.asscw-edit-address-attention') || {}).value || '', address_1: (box.querySelector('.asscw-edit-address-1') || {}).value || '', address_2: (box.querySelector('.asscw-edit-address-2') || {}).value || '', postcode: (box.querySelector('.asscw-edit-address-postcode') || {}).value || '', city: (box.querySelector('.asscw-edit-address-city') || {}).value || '', country_code: (box.querySelector('.asscw-edit-address-country') || {}).value || '', phone: (box.querySelector('.asscw-edit-address-phone') || {}).value || '', email: (box.querySelector('.asscw-edit-address-email') || {}).value || '', instructions: (box.querySelector('.asscw-edit-address-instructions') || {}).value || '', is_default: (box.querySelector('.asscw-edit-address-default') || {}).checked ? 1 : 0 }) .then(function (data) { if (!data || !data.success) { throw new Error(data && data.data && data.data.message ? data.data.message : 'Erreur lors de la mise à jour de l’adresse.'); } showNotice(data.data.message || 'Adresse mise à jour.', 'success'); renderCustomerDetail(customer, frequentProducts, data.data.workspace_detail || workspaceDetail, 'addresses'); }) .catch(function (error) { showNotice(error.message || 'Erreur lors de la mise à jour de l’adresse.', 'error'); }); }); }); } function openNewOrderScreen(customer, frequentProducts, selectedAddressId) { if (!customer || !customer.id) { showNotice('Client invalide.', 'error'); return; } const payload = { order_id: 0, order_number: 'Nouveau', status: 'Brouillon local', status_key: 'local-new', currency: 'EUR', date_created: '', customer_id: customer.id, customer: customer, frequent_products: frequentProducts || [], items: [], fees: [], shipping: [], discount_total: '', total_tax: '', total: '', total_amount: 0, customer_note: '', selected_shipping_address_id: selectedAddressId || '' }; switchToOrderScreen(); renderOrderScreen(payload, 'new'); } function bindRepeatButtons() { ordersBox.querySelectorAll('.asscw-repeat-order-button').forEach(function (button) { button.addEventListener('click', function () { const orderId = parseInt(button.getAttribute('data-order-id'), 10); if (!orderId) { showNotice('Commande invalide.', 'error'); return; } button.disabled = true; button.textContent = 'Reprise...'; persistLastSearchState(); const url = new URL(workspaceBaseUrl, window.location.origin); url.searchParams.set('asscw_screen', 'order'); url.searchParams.set('asscw_order', String(orderId)); url.searchParams.set('asscw_mode', 'repeat'); if (selectedCustomerId) url.searchParams.set('asscw_customer', String(selectedCustomerId)); window.location.href = url.toString(); }); }); } function renderOrders(orders) { if (!orders || !orders.length) { ordersBox.innerHTML = '
    Aucune commande trouvée pour ce client.
    '; return; } ordersBox.innerHTML = ` ${orders.map(function (order) { const viewUrl = order.frontend_view_url || order.view_url || order.edit_url || '#'; return ` `; }).join('')}
    CommandeDateStatutActions
    #${escapeHtml(order.number || '')} ${escapeHtml(order.date || '')} ${escapeHtml(order.status || '')}
    Voir
    `; bindRepeatButtons(); } function loadCustomer(customerId, options) { options = options || {}; selectedCustomerId = customerId; highlightSelectedCustomer(); detailBox.innerHTML = '
    Chargement du client...
    '; ordersBox.innerHTML = '
    Chargement des commandes...
    '; if (!options.keepResultsOpen) { hideResultsBox(); } post('ass_get_client_detail', { customer_id: customerId, workspace_url: workspaceBaseUrl }) .then(function (data) { if (!data || !data.success) throw new Error(data && data.data && data.data.message ? data.data.message : 'Erreur de chargement.'); selectedCustomerLabel = (data.data.customer && data.data.customer.label) ? data.data.customer.label : selectedCustomerLabel; if (selectedCustomerLabel) { input.value = selectedCustomerLabel; showSelectedClientBar(selectedCustomerLabel); } renderCustomerDetail( data.data.customer, data.data.frequent_products || [], data.data.workspace_detail || null, options.activeTab || 'summary' ); renderOrders(data.data.orders || []); persistLastSearchState(); highlightSelectedCustomer(); updateBackButtons(customerId); if (!options.skipHistory) updateWorkspaceUrl(customerId); }) .catch(function (error) { showResultsBox(); detailBox.innerHTML = '
    ' + escapeHtml(error.message || 'Erreur de chargement.') + '
    '; ordersBox.innerHTML = '
    Impossible de charger les commandes.
    '; showNotice(error.message || 'Erreur de chargement du client.', 'error'); }); } function renderOrderScreen(order, mode) { currentOrderData = order; currentOrderMode = ['view', 'edit', 'repeat', 'new'].includes(mode) ? mode : 'edit'; updateBackButtons(order.customer_id || 0); const isReadOnly = currentOrderMode === 'view'; const isRepeatMode = currentOrderMode === 'repeat'; const isNewMode = currentOrderMode === 'new'; if (orderTitle) orderTitle.innerHTML = (isNewMode ? 'Nouvelle commande' : (isRepeatMode ? 'Reprise de la commande #' : 'Commande #') + escapeHtml(order.order_number || '')) + ' ' + escapeHtml(order.status || '') + ''; orderHasUnsavedChanges = false; isSavingOrder = false; updateSaveButtonState(); const customer = order.customer || {}; const billingLines = (customer.billing_lines || []).map(function (line) { return '
  • ' + escapeHtml(line) + '
  • '; }).join(''); const shippingLines = (customer.shipping_lines || []).map(function (line) { return '
  • ' + escapeHtml(line) + '
  • '; }).join(''); orderScreenContent.innerHTML = `

    Lignes de commande

    ${isReadOnly ? '' : ''} ${(order.items || []).map(function (item) { const metaHtml = (item.meta_lines || []).length ? '
    ' + item.meta_lines.map(function (line) { return escapeHtml(line); }).join('
    ') + '
    ' : ''; return ` ${isReadOnly ? '' : ''} `; }).join('')}
    ProduitQtéTotal ligneAction
    ${renderProductThumb({id: item.product_id || '', image_url: item.image_url || '', name: item.name || ''}, 'is-inline')}
    ${escapeHtml(item.name || '')}
    ${item.sku ? '
    SKU : ' + escapeHtml(item.sku) + '
    ' : ''}${renderConditioningBadge(item)}${metaHtml}
    ${isReadOnly ? escapeHtml(item.qty) : ''} ${item.line_total || ''}
    ' + renderProductPreviewButton({id: item.product_id || ''}) + '
    ${!isReadOnly && order.frequent_products && order.frequent_products.length ? `

    Produits fréquents du client

    ${order.frequent_products.map(function (product) { return `
    ${renderProductThumb(product, 'is-inline') }${renderProductPreviewButton(product)}${renderConditioningBadge(product)}
    `; }).join('')}
    ` : ''} ${isReadOnly ? '' : '

    Ajouter un produit simple

    '}

    Informations commande

    Numéro :
    ${isNewMode ? '-' : '#' + escapeHtml(order.order_number || '')}
    Date :
    ${escapeHtml(order.date_created || '') || '-'}
    Total :
    ${order.total || ''}${(typeof order.total_amount === 'number' && order.total_amount < 250) ? '
    + frais de port
    ' : ''}
    Taxes :
    ${order.total_tax || ''}
    Bonus acquis (avoir) :
    -
    Statut :
    ${escapeHtml(order.status || '')}

    Client

    ${escapeHtml(customer.label || '')}
    ${customer.contact_name ? '
    Contact : ' + escapeHtml(customer.contact_name) + '
    ' : ''}${customer.phone ? '
    Téléphone : ' + escapeHtml(customer.phone) + '
    ' : ''}${customer.email ? '
    Email : ' + escapeHtml(customer.email) + '
    ' : ''}

    Facturation

    ${billingLines ? '
      ' + billingLines + '
    ' : ''}

    Livraison

    ${shippingLines ? '
      ' + shippingLines + '
    ' : ''}
    ${(order.fees || []).length ? '

    Frais

    ' + order.fees.map(function (fee) { return '
    ' + escapeHtml(fee.name || '') + ' : ' + fee.total + '
    '; }).join('') + '
    ' : ''} ${(order.shipping || []).length ? '

    Expédition

    ' + order.shipping.map(function (row) { return '
    ' + escapeHtml(row.name || '') + ' : ' + row.total + '
    '; }).join('') + '
    ' : ''}

    Note client

    ${isReadOnly ? ((order.customer_note && order.customer_note.length) ? '
    ' + escapeHtml(order.customer_note) + '
    ' : 'Aucune note.') : ''}
    `; if (!isReadOnly) bindOrderEditorEvents(); } function bindOrderEditorEvents() { orderScreenContent.querySelectorAll('.asscw-item-qty, .asscw-new-product-qty, #asscw-order-note').forEach(function (field) { field.addEventListener('input', markOrderDirty); field.addEventListener('change', markOrderDirty); }); orderScreenContent.querySelectorAll('.asscw-remove-item-button').forEach(function (button) { button.addEventListener('click', function () { const itemId = parseInt(button.getAttribute('data-item-id'), 10); if (!itemId) return; const row = orderScreenContent.querySelector('tr[data-item-id="' + itemId + '"]'); if (row) { const qtyInput = row.querySelector('.asscw-item-qty'); if (qtyInput) qtyInput.value = '0'; row.style.opacity = '0.45'; markOrderDirty(); } }); }); bindProductPreviewButtons(orderScreenContent); orderScreenContent.querySelectorAll('.asscw-frequent-product').forEach(function (button) { button.addEventListener('click', function () { appendNewProductRow({ product_id: parseInt(button.getAttribute('data-product-id'), 10), name: button.getAttribute('data-product-name') || '', sku: button.getAttribute('data-product-sku') || '', price_html: button.getAttribute('data-price-html') || '', image_url: button.getAttribute('data-product-image') || '', conditioning_label: button.getAttribute('data-conditioning-label') || '' }); markOrderDirty(); }); }); const productInput = document.getElementById('asscw-product-search-input'); const productResults = document.getElementById('asscw-product-results'); if (productInput && productResults) { productInput.addEventListener('input', function () { const term = productInput.value.trim(); clearTimeout(productTimer); if (term.length < 2) { productResults.innerHTML = ''; markOrderDirty(); return; } const requestId = ++productSearchRequestId; productTimer = setTimeout(function () { productResults.innerHTML = '
    Recherche...
    '; post('ass_search_products', { term: term }) .then(function (data) { if (requestId !== productSearchRequestId) return; if (!data || !data.success) throw new Error(data && data.data && data.data.message ? data.data.message : 'Erreur produit.'); const items = data.data.items || []; if (!items.length) { productResults.innerHTML = '
    Aucun produit trouvé.
    '; return; } productResults.innerHTML = items.map(function (item) { const qtyText = (item.stock_qty === null || typeof item.stock_qty === 'undefined') ? '' : ' · Qté : ' + escapeHtml(item.stock_qty); return `
    ${renderProductThumb(item, 'is-inline')}
    ${escapeHtml(item.name || '')}
    ${renderConditioningBadge(item)}
    ${item.sku ? 'SKU : ' + escapeHtml(item.sku) + ' · ' : ''}${escapeHtml(item.price_html || '')}${qtyText}${renderStockBadge(item)}
    ${renderProductPreviewButton(item)}
    `; }).join(''); bindProductPreviewButtons(productResults); productResults.querySelectorAll('.asscw-add-product-button').forEach(function (button) { button.addEventListener('click', function () { const productId = parseInt(button.getAttribute('data-product-id'), 10); if (!productId) return; appendNewProductRow({ product_id: productId, name: button.getAttribute('data-product-name') || '', sku: button.getAttribute('data-product-sku') || '', price_html: button.getAttribute('data-price-html') || '', image_url: button.getAttribute('data-product-image') || '', conditioning_label: button.getAttribute('data-conditioning-label') || '' }); productInput.value = ''; productResults.innerHTML = ''; }); }); }) .catch(function (error) { if (requestId !== productSearchRequestId) return; productResults.innerHTML = '
    ' + escapeHtml(error.message || 'Erreur de recherche produit.') + '
    '; }); }, 150); }); } } function appendNewProductRow(product) { const tbody = document.getElementById('asscw-editor-items-body'); if (!tbody || !product || !product.product_id) return; const productId = String(product.product_id || ''); const existingOrderQtyInput = tbody.querySelector('tr[data-product-id="' + productId + '"] .asscw-item-qty'); if (existingOrderQtyInput) { const currentValue = parseInt(existingOrderQtyInput.value || '0', 10); existingOrderQtyInput.value = String((isNaN(currentValue) ? 0 : currentValue) + 1); return; } const existingNewQtyInput = tbody.querySelector('.asscw-new-product-qty[data-product-id="' + productId + '"]'); if (existingNewQtyInput) { const currentValue = parseInt(existingNewQtyInput.value || '0', 10); existingNewQtyInput.value = String((isNaN(currentValue) ? 0 : currentValue) + 1); return; } const row = document.createElement('tr'); row.setAttribute('data-new-product-id', String(product.product_id || '')); row.innerHTML = `
    ${renderProductThumb({id: product.product_id || '', image_url: product.image_url || '', name: product.name || ''}, 'is-inline')}
    ${escapeHtml(product.name || '')}
    ${product.sku ? '
    SKU : ' + escapeHtml(product.sku) + '
    ' : ''}${renderConditioningBadge(product)}
    Nouveau produit simple
    ${escapeHtml(product.price_html || '')}
    ${renderProductPreviewButton({id: product.product_id || ''})}
    `; tbody.appendChild(row); const removeBtn = row.querySelector('.asscw-remove-new-item-button'); if (removeBtn) removeBtn.addEventListener('click', function () { row.remove(); markOrderDirty(); }); bindProductPreviewButtons(row); } function gatherOrderPayloadForSave() { const items = []; orderScreenContent.querySelectorAll('.asscw-item-qty').forEach(function (inputField) { const itemId = parseInt(inputField.getAttribute('data-item-id'), 10); if (itemId) items.push({ item_id: itemId, qty: String(inputField.value || '0').replace(',', '.') }); }); const newProducts = []; orderScreenContent.querySelectorAll('.asscw-new-product-qty').forEach(function (inputField) { const productId = parseInt(inputField.getAttribute('data-product-id'), 10); const qty = parseInt(String(inputField.value || '0').replace(',', '.'), 10); if (productId && qty > 0) newProducts.push({ product_id: productId, qty: String(qty) }); }); const noteField = document.getElementById('asscw-order-note'); return { order_id: currentOrderData ? currentOrderData.order_id : 0, mode: currentOrderMode, workspace_url: workspaceBaseUrl, customer_id: currentOrderData ? currentOrderData.customer_id : 0, shipping_address_id: currentOrderData ? (currentOrderData.selected_shipping_address_id || '') : '', items: items, new_products: newProducts, customer_note: noteField ? noteField.value : '' }; } function loadFrontOrder(orderId, mode) { switchToOrderScreen(); if (orderScreenContent) orderScreenContent.innerHTML = '
    Chargement de la commande...
    '; if (saveOrderButton) saveOrderButton.style.display = 'none'; post('ass_get_front_order', { order_id: orderId, mode: mode, workspace_url: workspaceBaseUrl }) .then(function (data) { if (!data || !data.success) throw new Error(data && data.data && data.data.message ? data.data.message : 'Erreur de chargement de commande.'); renderOrderScreen(data.data.order, mode); }) .catch(function (error) { if (orderScreenContent) orderScreenContent.innerHTML = '
    ' + escapeHtml(error.message || 'Erreur de chargement de commande.') + '
    '; showNotice(error.message || 'Erreur de chargement de la commande.', 'error'); }); } if (saveOrderButton) { saveOrderButton.addEventListener('click', function () { if (!currentOrderData || currentOrderMode === 'view') return; const payload = gatherOrderPayloadForSave(); if (payload.mode !== 'new' && !payload.order_id) { showNotice('Commande invalide.', 'error'); return; } isSavingOrder = true; updateSaveButtonState(); post(payload.mode === 'repeat' ? 'ass_create_repeat_order' : (payload.mode === 'new' ? 'ass_create_front_order' : 'ass_save_front_order'), payload) .then(function (data) { if (!data || !data.success) throw new Error(data && data.data && data.data.message ? data.data.message : 'Erreur d’enregistrement.'); if (payload.mode === 'repeat' || payload.mode === 'new') { if (!data.data || !data.data.order_id) throw new Error('Commande créée mais impossible de récupérer son identifiant.'); showNotice('Commande créée.', 'success'); currentOrderMode = 'edit'; orderHasUnsavedChanges = false; return post('ass_get_front_order', { order_id: data.data.order_id, mode: 'edit', workspace_url: workspaceBaseUrl }).then(function (refreshData) { if (!refreshData || !refreshData.success) throw new Error(refreshData && refreshData.data && refreshData.data.message ? refreshData.data.message : 'Erreur de rechargement.'); renderOrderScreen(refreshData.data.order, 'edit'); }); } showNotice('Commande enregistrée.', 'success'); orderHasUnsavedChanges = false; return post('ass_get_front_order', { order_id: payload.order_id, mode: 'edit', workspace_url: workspaceBaseUrl }).then(function (refreshData) { if (!refreshData || !refreshData.success) throw new Error(refreshData && refreshData.data && refreshData.data.message ? refreshData.data.message : 'Erreur de rechargement.'); renderOrderScreen(refreshData.data.order, 'edit'); }); }) .catch(function (error) { showNotice(error.message || 'Erreur lors de l’enregistrement.', 'error'); }) .finally(function () { isSavingOrder = false; updateSaveButtonState(); }); }); } if (backToCustomerBtn) backToCustomerBtn.addEventListener('click', function () { switchToWorkspace(); if (currentCustomerId) { loadCustomer(currentCustomerId, { skipHistory: true }); } else { updateWorkspaceUrl(0); updateBackButtons(0); } }); updateBackButtons(initialCustomerId || 0); if (input) { input.addEventListener('input', function () { const term = input.value.trim(); clearTimeout(timer); updateClearButton(); if (selectedCustomerLabel && term !== selectedCustomerLabel) { hideSelectedClientBar(); selectedCustomerId = 0; showResultsBox(); } if (term.length < 2) { resetWorkspace(); return; } const requestId = ++clientSearchRequestId; timer = setTimeout(function () { resultsBox.innerHTML = '
    Recherche...
    '; post('ass_search_clients', { term: term }) .then(function (data) { if (requestId !== clientSearchRequestId) return; if (!data || !data.success) throw new Error(data && data.data && data.data.message ? data.data.message : 'Erreur de recherche.'); lastSearchTerm = term; lastSearchResults = data.data.items || []; persistLastSearchState(); renderSearchResults(lastSearchResults); highlightSelectedCustomer(); }) .catch(function (error) { if (requestId !== clientSearchRequestId) return; resultsBox.innerHTML = '
    ' + escapeHtml(error.message || 'Erreur de recherche.') + '
    '; }); }, 220); }); } if (input) { input.addEventListener('keydown', function (event) { const items = getResultItems(); if (event.key === 'ArrowDown') { if (!items.length || resultsBox.style.display === 'none') return; event.preventDefault(); setKeyboardIndex(keyboardIndex + 1); return; } if (event.key === 'ArrowUp') { if (!items.length || resultsBox.style.display === 'none') return; event.preventDefault(); setKeyboardIndex(keyboardIndex <= 0 ? items.length - 1 : keyboardIndex - 1); return; } if (event.key === 'Enter') { if (keyboardIndex >= 0 && items[keyboardIndex]) { event.preventDefault(); const item = items[keyboardIndex]; const customerId = parseInt(item.getAttribute('data-customer-id'), 10); if (customerId) { selectedCustomerLabel = (item.querySelector('.asscw-result-title') || {}).textContent || ''; loadCustomer(customerId); } } return; } if (event.key === 'Escape') { event.preventDefault(); input.value = ''; resetWorkspace(); input.focus(); } }); } if (clearBtn) clearBtn.addEventListener('click', function () { input.value = ''; resetWorkspace(); input.focus(); }); if (changeClientBtn) changeClientBtn.addEventListener('click', function () { hydrateLastSearchState(); selectedCustomerId = 0; selectedCustomerLabel = ''; hideSelectedClientBar(); showResultsBox(); resetKeyboardSelection(); if (lastSearchTerm.length >= 2 && Array.isArray(lastSearchResults) && lastSearchResults.length) { input.value = lastSearchTerm; renderSearchResults(lastSearchResults); } else { resultsBox.innerHTML = ''; input.value = ''; } updateClearButton(); input.focus(); }); if (resultsBox) resultsBox.addEventListener('click', function (event) { const item = event.target.closest('.asscw-result-item[data-customer-id]'); if (!item) return; const customerId = parseInt(item.getAttribute('data-customer-id'), 10); if (!customerId) return; selectedCustomerLabel = ((item.querySelector('.asscw-result-title') || {}).textContent || '').trim(); clearSelectionHighlight(); item.classList.add('asscw-selected'); loadCustomer(customerId); }); hydrateLastSearchState(); hideSelectedClientBar(); updateClearButton(); window.addEventListener('resize', syncProductPreviewLayout); window.addEventListener('orientationchange', syncProductPreviewLayout); if (screen === 'order' && initialOrderId > 0) { switchToOrderScreen(); loadFrontOrder(initialOrderId, initialOrderMode); } else { switchToWorkspace(); if (initialCustomerId > 0) { loadCustomer(initialCustomerId, { skipHistory: true }); } } } if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', initWorkspace); } else { initWorkspace(); } })();