{"id":4349,"date":"2026-08-23T21:41:53","date_gmt":"2026-08-23T21:41:53","guid":{"rendered":"https:\/\/maroczain.com\/?p=4349"},"modified":"2026-08-23T21:49:31","modified_gmt":"2026-08-23T21:49:31","slug":"4349","status":"publish","type":"post","link":"https:\/\/maroczain.com\/en\/2026\/08\/23\/4349\/","title":{"rendered":"OMRA CAPACITY MANAGER"},"content":{"rendered":"\n<!DOCTYPE html>\n<html lang=\"fr\">\n<head>\n<meta charset=\"UTF-8\">\n<meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n<title>OMRA Capacity Manager<\/title>\n\n<style>\n:root{\n  --navy:#082b52;\n  --blue:#0e5fa8;\n  --gold:#d4af37;\n  --bg:#f5f8fc;\n  --white:#fff;\n  --text:#1f2937;\n  --green:#168a4f;\n  --orange:#e58a00;\n  --red:#c62828;\n  --border:#dbe3ec;\n}\n*{box-sizing:border-box}\nbody{\n  margin:0;\n  font-family:Arial,Helvetica,sans-serif;\n  background:var(--bg);\n  color:var(--text);\n}\n.omra-app{\n  max-width:1500px;\n  margin:auto;\n  padding:20px;\n}\n.hero{\n  background:linear-gradient(135deg,#051d37,#0e5fa8);\n  color:white;\n  padding:40px 30px;\n  border-radius:22px;\n}\n.hero h1{\n  margin:0 0 10px;\n  font-size:40px;\n}\n.hero p{\n  margin:0;\n  opacity:.9;\n}\n.toolbar{\n  margin-top:20px;\n  display:flex;\n  gap:10px;\n  flex-wrap:wrap;\n}\nbutton{\n  border:0;\n  padding:11px 16px;\n  border-radius:8px;\n  cursor:pointer;\n  font-weight:bold;\n}\n.btn-gold{background:var(--gold);color:#11263c}\n.btn-blue{background:var(--blue);color:white}\n.btn-red{background:var(--red);color:white}\n.btn-light{background:white;color:var(--navy)}\n.kpis{\n  display:grid;\n  grid-template-columns:repeat(auto-fit,minmax(180px,1fr));\n  gap:15px;\n  margin:20px 0;\n}\n.kpi{\n  background:white;\n  padding:20px;\n  border-radius:16px;\n  box-shadow:0 6px 20px rgba(0,0,0,.06);\n}\n.kpi small{\n  color:#64748b;\n}\n.kpi strong{\n  display:block;\n  font-size:28px;\n  color:var(--navy);\n  margin-top:6px;\n}\n.panel{\n  background:white;\n  border-radius:18px;\n  padding:22px;\n  margin-bottom:20px;\n  box-shadow:0 6px 20px rgba(0,0,0,.05);\n}\n.panel h2{\n  margin-top:0;\n  color:var(--navy);\n}\n.grid{\n  display:grid;\n  grid-template-columns:repeat(auto-fit,minmax(180px,1fr));\n  gap:14px;\n}\nlabel{\n  display:block;\n  font-size:13px;\n  color:#52606f;\n  margin-bottom:4px;\n}\ninput,select{\n  width:100%;\n  padding:10px;\n  border:1px solid var(--border);\n  border-radius:8px;\n}\ntable{\n  width:100%;\n  border-collapse:collapse;\n  margin-top:15px;\n}\nth,td{\n  border-bottom:1px solid var(--border);\n  padding:10px;\n  text-align:center;\n  font-size:14px;\n}\nth{\n  background:#edf4fb;\n  color:var(--navy);\n}\n.status{\n  display:inline-block;\n  padding:5px 10px;\n  border-radius:20px;\n  color:white;\n  font-size:12px;\n  font-weight:bold;\n}\n.green{background:var(--green)}\n.orange{background:var(--orange)}\n.red{background:var(--red)}\n.alert-box{\n  padding:15px;\n  border-radius:10px;\n  margin:8px 0;\n}\n.alert-green{background:#e8f7ef;border-left:5px solid var(--green)}\n.alert-orange{background:#fff3df;border-left:5px solid var(--orange)}\n.alert-red{background:#fdeaea;border-left:5px solid var(--red)}\n.summary{\n  display:grid;\n  grid-template-columns:repeat(auto-fit,minmax(220px,1fr));\n  gap:14px;\n}\n.summary div{\n  background:#f0f6fc;\n  border-radius:10px;\n  padding:14px;\n}\n.actions{\n  display:flex;\n  gap:8px;\n  flex-wrap:wrap;\n  margin-top:12px;\n}\n@media(max-width:800px){\n  .hero h1{font-size:30px}\n  table{font-size:12px}\n  th,td{padding:7px}\n}\n<\/style>\n<\/head>\n\n<body>\n<div class=\"omra-app\">\n\n<div class=\"hero\">\n  <h1>\ud83d\udd4b OMRA CAPACITY MANAGER<\/h1>\n  <p>Pilotage des capacit\u00e9s a\u00e9riennes, ventes, h\u00f4tels, marges et risques d&rsquo;invendus<\/p>\n\n  <div class=\"toolbar\">\n    <button class=\"btn-gold\" onclick=\"scrollToForm()\">+ Nouveau d\u00e9part<\/button>\n    <button class=\"btn-light\" onclick=\"exportCSV()\">Exporter CSV<\/button>\n    <button class=\"btn-light\" onclick=\"saveData()\">Sauvegarder<\/button>\n    <button class=\"btn-light\" onclick=\"loadData()\">Restaurer<\/button>\n  <\/div>\n<\/div>\n\n<div class=\"kpis\">\n  <div class=\"kpi\"><small>Capacit\u00e9 totale<\/small><strong id=\"kpiCapacity\">0<\/strong><\/div>\n  <div class=\"kpi\"><small>Vendues<\/small><strong id=\"kpiSold\">0<\/strong><\/div>\n  <div class=\"kpi\"><small>Options<\/small><strong id=\"kpiOptions\">0<\/strong><\/div>\n  <div class=\"kpi\"><small>Disponibles<\/small><strong id=\"kpiAvailable\">0<\/strong><\/div>\n  <div class=\"kpi\"><small>Load Factor<\/small><strong id=\"kpiLF\">0%<\/strong><\/div>\n  <div class=\"kpi\"><small>CA pr\u00e9visionnel<\/small><strong id=\"kpiRevenue\">0 MAD<\/strong><\/div>\n  <div class=\"kpi\"><small>Marge pr\u00e9visionnelle<\/small><strong id=\"kpiMargin\">0 MAD<\/strong><\/div>\n<\/div>\n\n<div class=\"panel\" id=\"formPanel\">\n  <h2>Cr\u00e9er \/ Ajouter un d\u00e9part Omra<\/h2>\n\n  <div class=\"grid\">\n    <div>\n      <label>R\u00e9f\u00e9rence<\/label>\n      <input id=\"ref\" placeholder=\"OMR-001\">\n    <\/div>\n    <div>\n      <label>Date d\u00e9part<\/label>\n      <input id=\"date\" type=\"date\">\n    <\/div>\n    <div>\n      <label>Compagnie<\/label>\n      <input id=\"airline\" placeholder=\"RAM\">\n    <\/div>\n    <div>\n      <label>Vol<\/label>\n      <input id=\"flight\" placeholder=\"ATxxx\">\n    <\/div>\n    <div>\n      <label>Origine<\/label>\n      <input id=\"origin\" placeholder=\"CMN\">\n    <\/div>\n    <div>\n      <label>Destination<\/label>\n      <input id=\"destination\" placeholder=\"MED\">\n    <\/div>\n    <div>\n      <label>Capacit\u00e9 contract\u00e9e<\/label>\n      <input id=\"capacity\" type=\"number\" min=\"0\" value=\"180\">\n    <\/div>\n    <div>\n      <label>Confirm\u00e9s<\/label>\n      <input id=\"sold\" type=\"number\" min=\"0\" value=\"0\">\n    <\/div>\n    <div>\n      <label>Options<\/label>\n      <input id=\"options\" type=\"number\" min=\"0\" value=\"0\">\n    <\/div>\n    <div>\n      <label>Prix package \/ p\u00e8lerin<\/label>\n      <input id=\"price\" type=\"number\" min=\"0\" value=\"15900\">\n    <\/div>\n    <div>\n      <label>Co\u00fbt total d\u00e9part<\/label>\n      <input id=\"cost\" type=\"number\" min=\"0\" value=\"0\">\n    <\/div>\n    <div>\n      <label>Release dans X jours<\/label>\n      <input id=\"release\" type=\"number\" min=\"0\" value=\"15\">\n    <\/div>\n  <\/div>\n\n  <div class=\"actions\">\n    <button class=\"btn-blue\" onclick=\"addDeparture()\">Ajouter le d\u00e9part<\/button>\n    <button class=\"btn-light\" onclick=\"resetForm()\">R\u00e9initialiser<\/button>\n  <\/div>\n<\/div>\n\n<div class=\"panel\">\n  <h2>Tableau de pilotage des d\u00e9parts<\/h2>\n\n  <div style=\"overflow:auto\">\n    <table id=\"departuresTable\">\n      <thead>\n        <tr>\n          <th>R\u00e9f.<\/th>\n          <th>Date<\/th>\n          <th>Vol<\/th>\n          <th>Capacit\u00e9<\/th>\n          <th>Confirm\u00e9s<\/th>\n          <th>Options<\/th>\n          <th>Dispo<\/th>\n          <th>LF<\/th>\n          <th>CA<\/th>\n          <th>Marge<\/th>\n          <th>Release<\/th>\n          <th>Statut<\/th>\n          <th>Action<\/th>\n        <\/tr>\n      <\/thead>\n      <tbody><\/tbody>\n    <\/table>\n  <\/div>\n<\/div>\n\n<div class=\"panel\">\n  <h2>Alertes commerciales<\/h2>\n  <div id=\"alerts\"><\/div>\n<\/div>\n\n<div class=\"panel\">\n  <h2>Analyse financi\u00e8re<\/h2>\n  <div class=\"summary\">\n    <div><strong>Point mort moyen<\/strong><br><span id=\"breakEven\">0 p\u00e8lerin(s)<\/span><\/div>\n    <div><strong>Recette moyenne par p\u00e8lerin<\/strong><br><span id=\"avgPrice\">0 MAD<\/span><\/div>\n    <div><strong>Co\u00fbt moyen par d\u00e9part<\/strong><br><span id=\"avgCost\">0 MAD<\/span><\/div>\n    <div><strong>Marge moyenne<\/strong><br><span id=\"avgMargin\">0 MAD<\/span><\/div>\n  <\/div>\n<\/div>\n\n<\/div>\n\n<script>\nlet departures = [];\n\nfunction money(n){\n  return new Intl.NumberFormat('fr-FR').format(Math.round(n)) + \" MAD\";\n}\n\nfunction scrollToForm(){\n  document.getElementById('formPanel').scrollIntoView({behavior:'smooth'});\n}\n\nfunction addDeparture(){\n  const d = {\n    ref: document.getElementById('ref').value || 'OMR-' + (departures.length+1),\n    date: document.getElementById('date').value,\n    airline: document.getElementById('airline').value,\n    flight: document.getElementById('flight').value,\n    origin: document.getElementById('origin').value,\n    destination: document.getElementById('destination').value,\n    capacity: +document.getElementById('capacity').value,\n    sold: +document.getElementById('sold').value,\n    options: +document.getElementById('options').value,\n    price: +document.getElementById('price').value,\n    cost: +document.getElementById('cost').value,\n    release: +document.getElementById('release').value\n  };\n\n  if(d.capacity <= 0){\n    alert(\"La capacit\u00e9 doit \u00eatre sup\u00e9rieure \u00e0 z\u00e9ro.\");\n    return;\n  }\n\n  if(d.sold + d.options > d.capacity){\n    alert(\"Attention : confirm\u00e9s + options d\u00e9passent la capacit\u00e9.\");\n    return;\n  }\n\n  departures.push(d);\n  render();\n  resetForm();\n}\n\nfunction resetForm(){\n  document.getElementById('ref').value='';\n  document.getElementById('date').value='';\n  document.getElementById('airline').value='';\n  document.getElementById('flight').value='';\n  document.getElementById('origin').value='';\n  document.getElementById('destination').value='';\n  document.getElementById('capacity').value=180;\n  document.getElementById('sold').value=0;\n  document.getElementById('options').value=0;\n  document.getElementById('price').value=15900;\n  document.getElementById('cost').value=0;\n  document.getElementById('release').value=15;\n}\n\nfunction getStatus(d){\n  const lf = d.capacity ? (d.sold\/d.capacity)*100 : 0;\n\n  if(d.release <= 7 &#038;&#038; lf < 75) return ['ROUGE','red'];\n  if(lf < 65) return ['ORANGE','orange'];\n  return ['VERT','green'];\n}\n\nfunction render(){\n  const tbody = document.querySelector('#departuresTable tbody');\n  tbody.innerHTML='';\n\n  let totalCapacity=0,totalSold=0,totalOptions=0,totalRevenue=0,totalCost=0;\n\n  departures.forEach((d,i)=>{\n    const available = d.capacity - d.sold - d.options;\n    const lf = d.capacity ? (d.sold\/d.capacity)*100 : 0;\n    const revenue = d.sold * d.price;\n    const margin = revenue - d.cost;\n    const status = getStatus(d);\n\n    totalCapacity += d.capacity;\n    totalSold += d.sold;\n    totalOptions += d.options;\n    totalRevenue += revenue;\n    totalCost += d.cost;\n\n    const row = document.createElement('tr');\n\n    row.innerHTML = `\n      <td>${d.ref}<\/td>\n      <td>${d.date || '-'}<\/td>\n      <td>${d.airline} ${d.flight}<\/td>\n      <td>${d.capacity}<\/td>\n      <td>${d.sold}<\/td>\n      <td>${d.options}<\/td>\n      <td>${available}<\/td>\n      <td>${lf.toFixed(1)}%<\/td>\n      <td>${money(revenue)}<\/td>\n      <td>${money(margin)}<\/td>\n      <td>J-${d.release}<\/td>\n      <td><span class=\"status ${status[1]}\">${status[0]}<\/span><\/td>\n      <td><button class=\"btn-red\" onclick=\"removeDeparture(${i})\">Supprimer<\/button><\/td>\n    `;\n\n    tbody.appendChild(row);\n  });\n\n  const totalAvailable = totalCapacity-totalSold-totalOptions;\n  const totalLF = totalCapacity ? (totalSold\/totalCapacity)*100 : 0;\n  const totalMargin = totalRevenue-totalCost;\n\n  document.getElementById('kpiCapacity').textContent=totalCapacity;\n  document.getElementById('kpiSold').textContent=totalSold;\n  document.getElementById('kpiOptions').textContent=totalOptions;\n  document.getElementById('kpiAvailable').textContent=totalAvailable;\n  document.getElementById('kpiLF').textContent=totalLF.toFixed(1)+'%';\n  document.getElementById('kpiRevenue').textContent=money(totalRevenue);\n  document.getElementById('kpiMargin').textContent=money(totalMargin);\n\n  renderAlerts();\n  renderFinancials();\n}\n\nfunction renderAlerts(){\n  const box = document.getElementById('alerts');\n  box.innerHTML='';\n\n  if(departures.length===0){\n    box.innerHTML='<div class=\"alert-box alert-green\">Aucun d\u00e9part enregistr\u00e9.<\/div>';\n    return;\n  }\n\n  departures.forEach(d=>{\n    const lf = d.capacity ? (d.sold\/d.capacity)*100 : 0;\n    const available = d.capacity-d.sold-d.options;\n\n    let cls='alert-green';\n    let text='Situation ma\u00eetris\u00e9e';\n\n    if(d.release<=7 &#038;&#038; lf<75){\n      cls='alert-red';\n      text=`ALERTE CRITIQUE : ${available} si\u00e8ges disponibles, release \u00e0 J-${d.release}. Action commerciale imm\u00e9diate recommand\u00e9e.`;\n    }else if(lf<65){\n      cls='alert-orange';\n      text=`Sous-remplissage : taux confirm\u00e9 ${lf.toFixed(1)}%. Intensifier les ventes et revoir les options.`;\n    }else{\n      text=`Remplissage satisfaisant : ${lf.toFixed(1)}% confirm\u00e9. Continuer le suivi.`;\n    }\n\n    box.innerHTML += `\n      <div class=\"alert-box ${cls}\">\n        <strong>${d.ref} - ${d.airline} ${d.flight}<\/strong><br>\n        ${text}\n      <\/div>\n    `;\n  });\n}\n\nfunction renderFinancials(){\n  if(departures.length===0){\n    document.getElementById('breakEven').textContent='0 p\u00e8lerin(s)';\n    document.getElementById('avgPrice').textContent='0 MAD';\n    document.getElementById('avgCost').textContent='0 MAD';\n    document.getElementById('avgMargin').textContent='0 MAD';\n    return;\n  }\n\n  let totalPrice=0,totalCost=0,totalMargin=0,totalBreakEven=0;\n\n  departures.forEach(d=>{\n    const revenue=d.sold*d.price;\n    totalPrice+=d.price;\n    totalCost+=d.cost;\n    totalMargin+=(revenue-d.cost);\n\n    if(d.price>0){\n      totalBreakEven += Math.ceil(d.cost\/d.price);\n    }\n  });\n\n  document.getElementById('breakEven').textContent =\n    Math.round(totalBreakEven\/departures.length) + ' p\u00e8lerin(s)';\n\n  document.getElementById('avgPrice').textContent =\n    money(totalPrice\/departures.length);\n\n  document.getElementById('avgCost').textContent =\n    money(totalCost\/departures.length);\n\n  document.getElementById('avgMargin').textContent =\n    money(totalMargin\/departures.length);\n}\n\nfunction removeDeparture(i){\n  departures.splice(i,1);\n  render();\n}\n\nfunction saveData(){\n  localStorage.setItem('mza_omra_capacity',JSON.stringify(departures));\n  alert('Donn\u00e9es sauvegard\u00e9es dans ce navigateur.');\n}\n\nfunction loadData(){\n  const raw=localStorage.getItem('mza_omra_capacity');\n\n  if(!raw){\n    alert('Aucune sauvegarde trouv\u00e9e.');\n    return;\n  }\n\n  departures=JSON.parse(raw);\n  render();\n}\n\nfunction exportCSV(){\n  if(departures.length===0){\n    alert('Aucune donn\u00e9e \u00e0 exporter.');\n    return;\n  }\n\n  let csv =\n    'Reference;Date;Compagnie;Vol;Origine;Destination;Capacite;Confirmes;Options;Disponibles;Prix;Cout;Release\\\\n';\n\n  departures.forEach(d=>{\n    csv += [\n      d.ref,\n      d.date,\n      d.airline,\n      d.flight,\n      d.origin,\n      d.destination,\n      d.capacity,\n      d.sold,\n      d.options,\n      d.capacity-d.sold-d.options,\n      d.price,\n      d.cost,\n      d.release\n    ].join(';')+'\\\\n';\n  });\n\n  const blob=new Blob([csv],{type:'text\/csv;charset=utf-8;'});\n  const url=URL.createObjectURL(blob);\n  const a=document.createElement('a');\n\n  a.href=url;\n  a.download='omra-capacity-manager.csv';\n  a.click();\n\n  URL.revokeObjectURL(url);\n}\n\nrender();\n<\/script>\n\n<\/body>\n<\/html>\n","protected":false},"excerpt":{"rendered":"<p>OMRA Capacity Manager \ud83d\udd4b OMRA CAPACITY MANAGER Pilotage des capacit\u00e9s a\u00e9riennes, ventes, h\u00f4tels, marges et risques d&rsquo;invendus + Nouveau d\u00e9part Exporter CSV Sauvegarder Restaurer Capacit\u00e9 totale0 Vendues0 Options0 Disponibles0 Load Factor0% CA pr\u00e9visionnel0 MAD Marge pr\u00e9visionnelle0 MAD Cr\u00e9er \/ Ajouter un d\u00e9part Omra R\u00e9f\u00e9rence Date d\u00e9part Compagnie Vol Origine Destination Capacit\u00e9 contract\u00e9e Confirm\u00e9s Options Prix [&hellip;]<\/p>\n","protected":false},"author":2,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"_jetpack_newsletter_access":"","_jetpack_dont_email_post_to_subs":false,"_jetpack_newsletter_tier_id":0,"_jetpack_memberships_contains_paywalled_content":false,"_jetpack_feature_clip_id":0,"_jetpack_memberships_contains_paid_content":false,"footnotes":"","jetpack_post_was_ever_published":false},"categories":[1],"tags":[],"class_list":["post-4349","post","type-post","status-publish","format-standard","hentry","category-uncategorized"],"jetpack_sharing_enabled":true,"jetpack_featured_media_url":"","_links":{"self":[{"href":"https:\/\/maroczain.com\/en\/wp-json\/wp\/v2\/posts\/4349","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/maroczain.com\/en\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/maroczain.com\/en\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/maroczain.com\/en\/wp-json\/wp\/v2\/users\/2"}],"replies":[{"embeddable":true,"href":"https:\/\/maroczain.com\/en\/wp-json\/wp\/v2\/comments?post=4349"}],"version-history":[{"count":2,"href":"https:\/\/maroczain.com\/en\/wp-json\/wp\/v2\/posts\/4349\/revisions"}],"predecessor-version":[{"id":4357,"href":"https:\/\/maroczain.com\/en\/wp-json\/wp\/v2\/posts\/4349\/revisions\/4357"}],"wp:attachment":[{"href":"https:\/\/maroczain.com\/en\/wp-json\/wp\/v2\/media?parent=4349"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/maroczain.com\/en\/wp-json\/wp\/v2\/categories?post=4349"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/maroczain.com\/en\/wp-json\/wp\/v2\/tags?post=4349"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}