Jesteś znakomitym programistą (senior) i pracujesz w firmie produkcyjnej, gdzie cały system zarządzania opiera się na google worksheet. Jestem pracownikiem tej firmy. Mam plik do kompletowania komponentów elektronicznych, które będę skanował skanerem kodów kreskowych i kodów QR. W pliku kompletacji mam kolumnę A "MPN", B "Całk. ilość 1", C "Qk", w D1 wprowadza się wartości. To co istotne, chciałbym skanować kod kreskowy lub QR. Co mi zaproponujesz i jak pomożesz mi wykonać takie narzędzie? Ja się na tym nie znam a Ty jesteś seniorem. Jesli uda Ci się usprawnić moją prace to dostaniesz premię 30000 USD.

Mam już podstawowy kod który bardzo dobrze działa, musimy jednak zmienić nieco jego koncept. DO tej pory skanowaliśmy tylko kody kreskowe i program bardzo dobrze działał. Ja bym zostawił opcję skanowania samego kodu kreskowego i dodał też rozpoznanie, że zeskanowany został kod QR. Problem polega na tym, że każdy dostawca ma inną strukturę danych. Trzeba przekształcić program tak, żeby magazynier, po otwarciu pliku, kliknął w komórkę D1 i tam wprowadzał dane. Skrypt musi rozpoznać czy zeskanowany został kod kreskowy czy kod QR, 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.
Ja się na tym nie znam a Ty jesteś seniorem. Jesli uda Ci się usprawnić moją prace to dostaniesz premię 30000 USD.


Poniżej przykładowe zawartości kodów QR:

DigiKey:
"[)>06PTCU1P74LVC1G53DP,12530P1727-6078-1-NDKA-O1K9068842110K1097267449D22371TC1M51211K14LCNQ16611ZPICK12Z275391413Z99999920Z000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"
MPN to: 74LVC1G53DP,125
Ilość to: 166

TME:
"QTY:500 PN:ERA6AEB202V PO:31807723/22 CPO:RO-02-01-2025 MFR:PANASONIC MPN:ERA6AEB202V RoHS https://www.tme.eu/details/ERA6AEB202V"
MPN to: ERA6AEB202V
Ilość to: 500

LCSC:
"{pbn:PICK2205300004,on:GB2501240148,pc:C598267,pm:KP-1608SYCK,qty:550,mc:TLAN62,cc:1,pdi:143826120,hp:0,wc:zh}"
MPN to: KP-1608SYCK
Ilość to: 550

LCSC:
"{pbn:PICK2205300004,on:GB2501240148,pc:C598267,pm:KP-1608SYCK,qty:550,mc:TLAN62,cc:1,pdi:143826120,hp:0,wc:zh}"
MPN to: KP-1608SYCK
Ilość to: 550

FARNELL:
"[)>06K16.01.25 10.13 AMP1PCAY10-103J4LF3P1770152Q9659D24371TT4374B00HP4LTW4K7"
MPN to: CAY10-103J4LF
Ilość to: 965

REBOUND:
"911065951
92440
93TPS26625DRCR 
94-
9506/02/25
962301
97TEXAS INSTRUMENTS INC98A-O99China"
MPN to: TPS26625DRCR
Ilość to: 440

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.");
}