Mam taki kod w appscript i moim programie do skanowania komponentów elektroniczych:

// Zmienne konfiguracyjne (łatwe do dostosowania)
const CONFIG = {
  sheetName: "Kompletacja",
  mpnCell: "D1", // Komórka do skanowania MPN
  quantityColumn: 3, // Kolumna C (ilość)
  targetColumn: 2, // Kolumna B (docelowa ilość)
  mpnColumn: 1, // Kolumna A (MPN)
  completionPercentageCell: "E1", // Komórka E1 dla procentu kompletacji

  // Kolory
  rowHighlightColor: "#F0F8FF", // Delikatny niebieski (AliceBlue) dla podświetlenia wiersza
  successColor: "#90EE90", // Jasnozielony (LightGreen) dla kompletacji
  pendingColor: "yellow", // Żółty dla niekompletacji
  errorColor: "red" // Czerwony dla błędów MPN
};

// =========================================
// FUNKCJA onEdit: Główny handler dla edycji
// =========================================
function onEdit(e) {
  if (!e || !e.range) return; // Zabezpieczenie przed brakującymi danymi eventu
  
  const sheet = e.range.getSheet();
  if (sheet.getName() !== CONFIG.sheetName) return; // Działamy tylko na wybranym arkuszu

  const editedRow = e.range.getRow();
  const editedCol = e.range.getColumn();
  const d1Cell = sheet.getRange(CONFIG.mpnCell);

  // Zapobieganie pętli - ignorujemy edycje po wyczyszczeniu komórki przez skrypt
  if (e.value === undefined || e.value === null || String(e.value).trim() === "") return;

  // Skanowanie MPN (komórka D1)
  if (e.range.getA1Notation() === CONFIG.mpnCell) {
    handleMPNScan(e, sheet, d1Cell);
    return;
  }

  // Skanowanie ilości (kolumna C)
  if (editedCol === CONFIG.quantityColumn && editedRow > 1) {
    handleQuantityScan(e, sheet, d1Cell);
    return;
  }
}

// =========================================
// FUNKCJA onSelectionChange: Podświetlanie wiersza po zmianie aktywnej komórki
// =========================================
function onSelectionChange(e) {
  if (!e || !e.range) return;
  const sheet = e.range.getSheet();
  if (sheet.getName() !== CONFIG.sheetName) return;

  const selectedRow = e.range.getRow();
  const selectedCol = e.range.getColumn();
  const props = PropertiesService.getDocumentProperties();
  const lastHighlightedRow = props.getProperty("lastHighlightedRow");

  // Czyść poprzednie podświetlenie
  if (lastHighlightedRow && sheet.getRange(lastHighlightedRow, 1).getRow() == lastHighlightedRow) {
    sheet.getRange(lastHighlightedRow, 1, 1, sheet.getLastColumn()).setBackground(null);
  }
  props.deleteProperty("lastHighlightedRow"); // Zawsze usuwaj, by nie podświetlało po przejściu na inny arkusz/zamknięciu

  // Podświetl aktualny wiersz, jeśli wybrano kolumnę C i wiersz danych (>1)
  if (selectedCol === CONFIG.quantityColumn && selectedRow > 1) {
    const rowRange = sheet.getRange(selectedRow, 1, 1, sheet.getLastColumn());
    rowRange.setBackground(CONFIG.rowHighlightColor);
    props.setProperty("lastHighlightedRow", selectedRow);
  }
}

// =========================================
// FUNKCJE POMOCNICZE
// =========================================

// Obsługa skanowania MPN
function handleMPNScan(e, sheet, d1Cell) {
  const mpnCode = String(e.value).trim(); // Upewnij się, że wartość jest stringiem i usuń białe znaki
  if (!mpnCode) return;

  const mpnRange = sheet.getRange(2, CONFIG.mpnColumn, sheet.getLastRow() - 1, 1);
  const mpnValues = mpnRange.getValues().flat().map(String); // Zmień na stringi, aby uniknąć problemów z typami
  const targetRow = mpnValues.indexOf(mpnCode);

  if (targetRow !== -1) { // Znaleziono MPN
    const actualTargetRow = targetRow + 2; // Bo dane zaczynają się od wiersza 2
    const qtyCell = sheet.getRange(actualTargetRow, CONFIG.quantityColumn);
    qtyCell.setBackground(CONFIG.pendingColor); // Podświetl żółto (oczekiwanie na ilość)
    qtyCell.activate();
  } else {
    // Błąd: MPN nie znaleziony
    SpreadsheetApp.getUi().alert(`BŁĄD: Nie znaleziono MPN "${mpnCode}" w kolumnie A.`);
    d1Cell.setBackground(CONFIG.errorColor); // Podświetl D1 na czerwono
    d1Cell.clearContent(); // Wyczyść D1
    d1Cell.activate(); // Wróć kursorem do D1
  }
  
  // Wyczyść D1 w każdym przypadku po przetworzeniu, aby umożliwić ponowny skan tego samego MPN
  sheet.getRange(CONFIG.mpnCell).clearContent();
}

// Obsługa skanowania ilości
function handleQuantityScan(e, sheet, d1Cell) {
  const scannedQty = parseFloat(e.value);
  const qtyCell = e.range;
  const editedRow = e.range.getRow();
  const targetQty = parseFloat(sheet.getRange(editedRow, CONFIG.targetColumn).getValue());

  // Obsługa błędów: nieprawidłowa wartość (nie liczba)
  if (isNaN(scannedQty)) {
    SpreadsheetApp.getUi().alert("BŁĄD: Wprowadź tylko liczby (ilość).");
    
    // Przywróć poprzednią wartość i jej formatowanie
    if (e.oldValue !== undefined && e.oldValue !== null && String(e.oldValue).trim() !== "") {
      qtyCell.setValue(e.oldValue);
      qtyCell.setNumberFormat("0"); // Przywróć format liczbowy
      const oldQty = parseFloat(e.oldValue);
      qtyCell.setBackground(oldQty >= targetQty ? CONFIG.successColor : CONFIG.pendingColor);
    } else {
      qtyCell.clearContent().setBackground(null); // Jeśli wcześniej była pusta, wyczyść i usuń tło
      qtyCell.setNumberFormat("0"); // Ustaw format na liczbę
    }
    qtyCell.activate(); // Wróć do tej samej komórki
    return;
  }

  // Kumulacja ilości (dodaj nową wartość do starej)
  const previousQty = e.oldValue ? parseFloat(e.oldValue) : 0;
  const newCumQty = previousQty + scannedQty;

  // Zapisz nową sumę i ustaw kolor
  qtyCell.setValue(newCumQty);
  qtyCell.setNumberFormat("0"); // Upewnij się, że format jest liczbowy
  if (!isNaN(targetQty)) {
    qtyCell.setBackground(newCumQty >= targetQty ? CONFIG.successColor : CONFIG.pendingColor);
  } else {
    qtyCell.setBackground(CONFIG.pendingColor); // Jeśli docelowa ilość nie jest liczbą, zawsze żółty
  }

  // Po poprawnym skanie ilości, wracamy do D1
  d1Cell.activate();
  
  // Aktualizuj procent kompletacji po każdej zmianie ilości
  updateCompletionPercentage(sheet);
}


// =========================================
// FUNKCJA: Obliczanie i aktualizacja procentu kompletacji w E1
// =========================================
function updateCompletionPercentage(sheet) {
  const lastRow = sheet.getLastRow();
  if (lastRow < 2) { // Brak danych
    sheet.getRange(CONFIG.completionPercentageCell).setValue("0.00%").setNumberFormat("0.00%");
    return;
  }

  const quantityRange = sheet.getRange(2, CONFIG.quantityColumn, lastRow - 1, 1);
  const backgrounds = quantityRange.getBackgrounds().flat(); // Pobierz kolory tła z kolumny ilości

  let completedCount = 0;
  for (let i = 0; i < backgrounds.length; i++) {
    // Sprawdź, czy tło komórki jest dokładnie kolorem sukcesu
    if (backgrounds[i].toLowerCase() === CONFIG.successColor.toLowerCase()) {
      completedCount++;
    }
  }

  const totalMpns = lastRow - 1; // Liczba wierszy z danymi MPN
  let percentage = 0;
  if (totalMpns > 0) {
    percentage = completedCount / totalMpns; // Procent jako ułamek
  }

  const percentageCell = sheet.getRange(CONFIG.completionPercentageCell);
  percentageCell.setValue(percentage);
  percentageCell.setNumberFormat("0.00%"); // Formatuj jako procent z dwoma miejscami po przecinku
}


// =========================================
// FUNKCJA onOpen: Tworzenie menu i inicjalizacja procentu po otwarciu arkusza
// =========================================
function onOpen() {
  const ui = SpreadsheetApp.getUi();
  ui.createMenu('Kompletacja')
      .addItem('Resetuj ilość w aktywnym wierszu', 'resetQtyInActiveRow')
      .addItem('Resetuj wszystkie ilości', 'resetAllQuantities')
      .addToUi();
  
  // Inicjalne wywołanie aktualizacji procentu kompletacji po otwarciu arkusza
  updateCompletionPercentage(SpreadsheetApp.getActiveSpreadsheet().getSheetByName(CONFIG.sheetName));
}

// =========================================
// FUNKCJA: Resetowanie ilości w aktywnym wierszu
// =========================================
function resetQtyInActiveRow() {
  const sheet = SpreadsheetApp.getActive().getActiveSheet();
  if (sheet.getName() !== CONFIG.sheetName) {
    SpreadsheetApp.getUi().alert("To narzędzie działa tylko w arkuszu 'Kompletacja'.");
    return;
  }
  
  const activeCell = sheet.getActiveCell();
  const row = activeCell.getRow();
  const col = activeCell.getColumn();
  
  // Resetujemy tylko w kolumnie C i tylko w wierszach danych (>1)
  if (col !== CONFIG.quantityColumn || row < 2) {
    SpreadsheetApp.getUi().alert("Wybierz komórkę w kolumnie C dla wiersza, który chcesz zresetować.");
    return;
  }
  
  const qtyCell = sheet.getRange(row, CONFIG.quantityColumn);
  qtyCell.setValue(0);
  qtyCell.setBackground(CONFIG.pendingColor); // Ustaw na żółto po zresetowaniu
  qtyCell.setNumberFormat("0");

  updateCompletionPercentage(sheet); // Aktualizuj procent po resecie
  SpreadsheetApp.getUi().alert(`Ilość w wierszu ${row} została zresetowana.`);
}

// =========================================
// FUNKCJA: Resetowanie wszystkich ilości
// =========================================
function resetAllQuantities() {
  const ui = SpreadsheetApp.getUi();
  const response = ui.alert('Potwierdzenie', 'Czy na pewno chcesz zresetować ilości we wszystkich wierszach? Tej operacji nie można cofnąć.', ui.ButtonSet.YES_NO);
  if (response === ui.Button.NO) return;

  const sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName(CONFIG.sheetName);
  if (!sheet) {
    SpreadsheetApp.getUi().alert("Arkusz 'Kompletacja' nie został znaleziony.");
    return;
  }
  
  const lastRow = sheet.getLastRow();
  if (lastRow < 2) { // Brak wierszy danych
    SpreadsheetApp.getUi().alert("Brak wierszy do zresetowania.");
    return;
  }
  
  const quantityRange = sheet.getRange(2, CONFIG.quantityColumn, lastRow - 1, 1);
  quantityRange.setValue(0);
  quantityRange.setBackground(CONFIG.pendingColor); // Ustaw wszystkie na żółto
  quantityRange.setNumberFormat("0");

  updateCompletionPercentage(sheet); // Aktualizuj procent po resecie
  SpreadsheetApp.getUi().alert("Wszystkie ilości zostały zresetowane.");
}

Teraz musimy zmienić koncept. Nie będziemy skanowali kodów kreskowych, tylko kody QR w których zawarte jest więcej informacji. Problem polega na tym, że każdy dostawca ma inną strukturę danych. Trzeba przekształcić program tak, żeby magazynier kliknął w komórkę D1 i tam wprowadzał dane. Skrypt musi rozpoznać jaka jest struktura danych (od jakiego dostawcy) i pobiera MPN po którym wyszukuje z listy oraz pobiera ilość i sam wpisuje automatycznie do Qk, przy wcześniejszym założeniu, że komponenty mogą przyjeżdżać w kilku częściach więc te ilości muszą się dodawać. Po wczytaniu danych, kursor wraca do komórki D1 aby magazynier mógł automatycznie skanować kolejne paczki.