* Personalzuweiser

Weist maximal mögliche Anzahl an Personal einem Fahrzeug zu. Originalskript von BOS-Ernie, angepasst und erweitert, zur Unterstützung der neusten Lehrgänge, durch leeSalami. Weitere Lehrgänge hinzugefügt von Manute1337.

您需要先安裝使用者腳本管理器擴展,如 TampermonkeyGreasemonkeyViolentmonkey 之後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyViolentmonkey 後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyViolentmonkey 後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyUserscripts 後才能安裝該腳本。

你需要先安裝一款使用者腳本管理器擴展,比如 Tampermonkey,才能安裝此腳本

您需要先安裝使用者腳本管理器擴充功能後才能安裝該腳本。

(我已經安裝了使用者腳本管理器,讓我安裝!)

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

(我已經安裝了使用者樣式管理器,讓我安裝!)

// ==UserScript==
// @name        * Personalzuweiser
// @namespace   bos-ernie.leitstellenspiel.de
// @version     2.2.3
// @license     BSD-3-Clause
// @author      BOS-Ernie, leeSalami, Manute1337
// @description Weist maximal mögliche Anzahl an Personal einem Fahrzeug zu. Originalskript von BOS-Ernie, angepasst und erweitert, zur Unterstützung der neusten Lehrgänge, durch leeSalami. Weitere Lehrgänge hinzugefügt von Manute1337.
// @match       https://*.leitstellenspiel.de/vehicles/*/zuweisung
// @icon        https://www.google.com/s2/favicons?sz=64&domain=leitstellenspiel.de
// @run-at      document-idle
// @grant       none
// ==/UserScript==

(async function () {
  'use strict';

  updatePersonalCount();
  const csrfToken = document.querySelector('meta[name=csrf-token]')?.content;

  if (!csrfToken) {
    return;
  }

  const loadingText = I18n.t('common.loading');
  addButtonGroup();

  async function assign() {
    const assignedPersonsElement = getAssignedPersonsElement();
    const numberOfAssignedPersonnel = parseInt(assignedPersonsElement.innerText);
    const vehicleCapacity = parseInt(assignedPersonsElement.parentElement.firstElementChild.innerText);

    let numberOfPersonnelToAssign = vehicleCapacity - numberOfAssignedPersonnel;
    const vehicleTypeId = getVehicleTypeId();

    if (numberOfPersonnelToAssign > 0 && vehicleTypeId !== null) {
      const vehicleTraining = getIdentifierByVehicleTypeId(vehicleTypeId);

      if (vehicleTraining === null) {
        return;
      }

      const trainingCount = Object.keys(vehicleTraining).length;

      if (trainingCount === 0) {
        return;
      }

      if (trainingCount !== 1 || !('no_training' in vehicleTraining)) {
        const rowsWithTraining = document.querySelectorAll('tbody tr:not([data-filterable-by="[]"]):has(a.btn-success):not(:has(span[data-education-key]))');
        const sortedRowsWithTraining = sortRows(rowsWithTraining);

        for (let i = 0, n = sortedRowsWithTraining.length; i < n; i++) {
          let hasIdentifier = true;
          let currentIdentifier = null;

          for (const educationIdentifier in vehicleTraining) {
            if (educationIdentifier === 'no_training') {
              continue;
            }

            const rowHasTraining = sortedRowsWithTraining[i].dataset.filterableBy.includes('"' + educationIdentifier + '"');

            if (vehicleTraining[educationIdentifier] === true && !rowHasTraining) {
              hasIdentifier = false;
            } else if (typeof vehicleTraining[educationIdentifier] === 'number') {
              if (!rowHasTraining || vehicleTraining[educationIdentifier] <= 0) {
                hasIdentifier = false;
              } else {
                hasIdentifier = true;
                currentIdentifier = educationIdentifier;
                break;
              }
            }
          }

          if (!hasIdentifier) {
            continue;
          }

          if (await changeAssignment(sortedRowsWithTraining[i].querySelector('a.btn-success'))) {
            numberOfPersonnelToAssign--;

            if (currentIdentifier) {
              vehicleTraining[currentIdentifier]--;
            }

            if (numberOfPersonnelToAssign === 0) {
              break;
            }

            await new Promise(r => setTimeout(r, 5));
          }
        }
      }
      if (numberOfPersonnelToAssign === 0) {
        return;
      }

      if ('no_training' in vehicleTraining) {
        if (typeof vehicleTraining['no_training'] === 'number') {
          numberOfPersonnelToAssign = Math.min(numberOfPersonnelToAssign, vehicleTraining['no_training']);
        }

        await assignPersonsWithoutTraining(numberOfPersonnelToAssign);
      }
    }
  }

  async function assignPersonsWithoutTraining(amount) {
    const rowsWithoutTraining = document.querySelectorAll('tbody tr[data-filterable-by="[]"]:has(a.btn-success):not(:has(span[data-education-key]))');
    const sortedRowsWithoutTraining = sortRows(rowsWithoutTraining);


    for (let i = 0, n = sortedRowsWithoutTraining.length; i < n; i++) {
      if (await changeAssignment(sortedRowsWithoutTraining[i].querySelector('a.btn-success'))) {
        amount--;

        if (amount === 0) {
          break;
        }

        await new Promise(r => setTimeout(r, 5));
      }
    }
  }

  async function changeAssignment(button) {
    if (button) {
      const personalId = button.getAttribute('personal_id');
      const personalElement = document.getElementById(`personal_${personalId}`);
      personalElement.innerHTML = `<td colspan="4">${loadingText}</td>`;

      try {
        const response = await fetch(button.href, {
          method: 'POST',
          headers: {
            'content-type': 'application/x-www-form-urlencoded; charset=UTF-8',
            'x-csrf-token': csrfToken,
            'x-requested-with': 'XMLHttpRequest',
          },
        });

        if (!response.ok) {
          return false;
        }

        personalElement.innerHTML = await response.text();
        updatePersonalCount();

        return true;
      } catch (e) {
        return false;
      }
    }

    return false
  }

  function updatePersonalCount() {
    const counterElement = document.getElementById('count_personal');

    if (!counterElement) {
      return;
    }

    const counter = document.querySelectorAll('.btn-assigned').length;
    const vehicleCapacity = parseInt(counterElement.parentElement.firstElementChild.innerText);
    counterElement.innerText = String(counter);

    if (counter !== vehicleCapacity) {
      counterElement.classList.remove('label-success');
      counterElement.classList.add('label-warning');
    } else {
      counterElement.classList.remove('label-warning');
      counterElement.classList.add('label-success');
    }
  }

  function sortRows(rows) {
    const vehicleId = getVehicleId();

    return Array.from(rows)
      .sort((a, b) => {
        const aInVehicle = a.querySelector('td:nth-child(3) a')?.href?.endsWith('/' + vehicleId);
        const bInVehicle = b.querySelector('td:nth-child(3) a')?.href?.endsWith('/' + vehicleId);

        if ((aInVehicle === true && !bInVehicle) || (aInVehicle === undefined && bInVehicle === false)) {
          return -1;
        } else if (aInVehicle === bInVehicle) {
          return 0;
        } else {
          return 1;
        }
      });
  }

  async function reset() {
    const selectButtons = document.getElementsByClassName('btn btn-default btn-assigned');

    // Since the click event removes the button from the DOM, only every second item would be clicked.
    // To prevent this, the loop is executed backwards.
    for (let i = selectButtons.length - 1; i >= 0; i--) {
      await changeAssignment(selectButtons[i]);
      await new Promise(r => setTimeout(r, 5));
    }
  }

  function assignClickEvent(event) {
    assign();
    event.preventDefault();
  }

  function resetClickEvent(event) {
    reset();
    event.preventDefault();
  }

  function getAssignedPersonsElement() {
    return document.getElementById("count_personal");
  }

  function addButtonGroup() {
    const okIcon = document.createElement("span");
    okIcon.className = "glyphicon glyphicon-ok";

    const assignButton = document.createElement("button");
    assignButton.type = "button";
    assignButton.className = "btn btn-default";
    assignButton.appendChild(okIcon);
    assignButton.addEventListener("click", assignClickEvent);

    const resetIcon = document.createElement("span");
    resetIcon.className = "glyphicon glyphicon-trash";

    const resetButton = document.createElement("button");
    resetButton.type = "button";
    resetButton.className = "btn btn-default";
    resetButton.appendChild(resetIcon);
    resetButton.addEventListener("click", resetClickEvent);

    const buttonGroup = document.createElement("div");
    buttonGroup.id = "vehicle-assigner-button-group";
    buttonGroup.className = "btn-group";
    buttonGroup.style.marginLeft = "5px";
    buttonGroup.appendChild(assignButton);
    buttonGroup.appendChild(resetButton);

    // Append button group to element with class "vehicles-education-filter-box"
    document.getElementsByClassName("vehicles-education-filter-box")[0].appendChild(buttonGroup);
  }

  function getVehicleId() {
    return window.location.pathname.split("/")[2];
  }

  /**
   * @return {number|null}
   */
  function getVehicleTypeId() {
    const vehicleId = getVehicleId();
    const request = new XMLHttpRequest();
    request.open("GET", `/api/v2/vehicles/${vehicleId}`, false);
    request.send();

    if (request.status === 200) {
      const vehicle = JSON.parse(request.responseText);
      return vehicle.result.vehicle_type;
    }

    return null;
  }



  /**
   * @return {{}|null}
   */
  function getIdentifierByVehicleTypeId(vehicleTypeId) {
    switch (vehicleTypeId) {
      case 0: //LF 20
        return {'no_training': true};
      case 1: //LF 10
        return {'no_training': true};
      case 2: //DLK 23
        return {'no_training': true};
      case 3: //ELW 1
        return {'no_training': true};
      case 4: //RW
        return {'no_training': true};
      case 5: //GW-A
        return {'no_training': true};
      case 6: //LF 8/6
        return {'no_training': true};
      case 7: //LF 20/16
        return {'no_training': true};
      case 8: //LF 10/6
        return {'no_training': true};
      case 9: //LF 16-TS
        return {'no_training': true};
      case 10: //GW-Öl
        return {'no_training': true};
      case 11: //GW-L2-Wasser
        return {'no_training': true};
      case 12: //GW-Messtechnik
        return {'gw_messtechnik': true};
      case 13: //SW 1000
        return {'no_training': true};
      case 14: //SW 2000
        return {'no_training': true};
      case 15: //SW 2000-Tr
        return {'no_training': true};
      case 16: //SW Kats
        return {'no_training': true};
      case 17: //TLF 2000
        return {'no_training': true};
      case 18: //TLF 3000
        return {'no_training': true};
      case 19: //TLF 8/8
        return {'no_training': true};
      case 20: //TLF 8/18
        return {'no_training': true};
      case 21: //TLF 16/24-Tr
        return {'no_training': true};
      case 22: //TLF 16/25
        return {'no_training': true};
      case 23: //TLF 16/45
        return {'no_training': true};
      case 24: //TLF 20/40
        return {'no_training': true};
      case 25: //TLF 20/40-SL
        return {'no_training': true};
      case 26: //TLF 16
        return {'no_training': true};
      case 27: //GW-Gefahrgut
        return {'gw_gefahrgut': true};
      case 28: //RTW
        return {'no_training': true};
      case 29: //NEF
        return {'notarzt': true};
      case 30: //HLF 20
        return {'no_training': true};
      case 31: //RTH
        return {'notarzt': true};
      case 32: //FuStW
        return {'no_training': true};
      case 33: //GW-Höhenrettung
        return {'gw_hoehenrettung': true};
      case 34: //ELW 2
        return {'elw2': true};
      case 35: //leBefKw
        return {'police_einsatzleiter': true};
      case 36: //MTW
        return {'no_training': true};
      case 37: //TSF-W
        return {'no_training': true};
      case 38: //KTW
        return {'no_training': true};
      case 39: //GKW
        return {'no_training': true};
      case 40: //MTW-TZ
        return {'thw_zugtrupp': true};
      case 41: //MzKW
        return {'no_training': true};
      case 42: //LKW K 9
        return {'thw_raumen': true};
      case 45: //MLW 5
        return {'thw_raumen': true};
      case 46: //WLF
        return {'wechsellader': true};
      case 50: //GruKw
        return {'no_training': true};
      case 51: //FüKw
        return {'police_fukw': true};
      case 52: //GefKw
        return {'no_training': true};
      case 53: //Dekon-P
        return {'dekon_p': true};
      case 55: //KdoW-LNA
        return {'lna': true};
      case 56: //KdoW-OrgL
        return {'orgl': true};
      case 57: //FwK
        return {'fwk': true};
      case 58: //KTW Typ B
        return {'no_training': true};
      case 59: //ELW 1 (SEG)
        return {'seg_elw': true};
      case 60: //GW-San
        return {'seg_gw_san': true};
      case 61: //Polizeihubschrauber
        return {'polizeihubschrauber': true};
      case 63: //GW-Taucher
        return {'gw_taucher': true};
      case 64: //GW-Wasserrettung
        return {'gw_wasserrettung': true};
      case 65: //LKW 7 Lkr 19 tm
        return {'no_training': true};
      case 69: //Tauchkraftwagen
        return {'gw_taucher': true};
      case 72: //WaWe 10
        return {'police_wasserwerfer': true};
      case 73: //GRTW
        return {'notarzt': 1, 'no_training': 5};
      case 74: //NAW
        return {'notarzt': 1, 'no_training': 2};
      case 75: //FLF
        return {'arff': true};
      case 76: //Rettungstreppe
        return {'rettungstreppe': true};
      case 79: //SEK - ZF
        return {'police_sek': true};
      case 80: //SEK - MTF
        return {'police_sek': true};
      case 81: //MEK - ZF
        return {'police_mek': true};
      case 82: //MEK - MTF
        return {'police_mek': true};
      case 83: //GW-Werkfeuerwehr
        return {'werkfeuerwehr': true};
      case 84: //ULF mit Löscharm
        return {'werkfeuerwehr': true};
      case 85: //TM 50
        return {'werkfeuerwehr': true};
      case 86: //Turbolöscher
        return {'werkfeuerwehr': true};
      case 87: //TLF 4000
        return {'no_training': true};
      case 88: //KLF
        return {'no_training': true};
      case 89: //MLF
        return {'no_training': true};
      case 90: //HLF 10
        return {'no_training': true};
      case 91: //Rettungshundefahrzeug
        return {'seg_rescue_dogs': true};
      case 93: //MTW-O
        return {'thw_rescue_dogs': true};
      case 94: //DHuFüKw
        return {'k9': true};
      case 95: //Polizeimotorrad
        return {'police_motorcycle': true};
      case 97: //ITW
        return {'intensive_care': 2, 'notarzt': 1};
      case 98: //Zivilstreifenwagen
        return {'criminal_investigation': true};
      case 99: //LKW 7 Lbw
        return {'water_damage_pump': true};
      case 100: //MLW 4
        return {'water_damage_pump': true};
      case 103: //FuStW (DGL)
        return {'police_service_group_leader': true};
      case 104: //GW-L1
        return {'no_training': true};
      case 105: //GW-L2
        return {'no_training': true};
      case 106: //MTF-L
        return {'no_training': true};
      case 107: //LF-L
        return {'no_training': true};
      case 109: //MzGW SB
        return {'heavy_rescue': true};
      case 114: //GW-Lüfter
        return {'no_training': true};
      case 121: //GTLF
        return {'no_training': true};
      case 122: //LKW 7 Lbw (FGr E)
        return {'thw_energy_supply': true};
      case 123: //LKW 7 Lbw (FGr WP)
        return {'water_damage_pump': true};
      case 124: //MTW-OV
        return {'no_training': true};
      case 125: //MTW-Tr UL
        return {'thw_drone': true};
      case 126: //MTF Drohne
        return {'fire_drone': true};
      case 127: //GW UAS
        return {'seg_drone': true};
      case 128: //ELW Drohne
        return {'fire_drone': true};
      case 129: //ELW2 Drohne
        return {'fire_drone': true, 'elw2': true};
      case 130: //GW-Bt
        return {'care_service': 1, 'care_service_equipment': 2};
      case 131: //Bt-Kombi
        return {'care_service': true};
      case 133: //Bt LKW
        return {'care_service': 1, 'care_service_equipment': 2};
      case 134: //Pferdetransporter klein
        return {'police_horse': true};
      case 135: //Pferdetransporter groß
        return {'police_horse': true};
      case 137: //Zugfahrzeug Pferdetransport
        return {'police_horse': true};
      case 140: //MTW-Verpflegung
        return {"fire_care_service": true};
      case 144: //FüKw (THW)
        return {"thw_command": true};
      case 145: //FüKomKW
        return {"thw_command": true};
      case 147: //FmKW
        return {"thw_command": true};
      case 148: //MTW Fgr K
        return {"thw_command": true};
      default:
        return null;
    }
  }
})();