Option Explicit ' Исправленная версия: пулы item-кодов хранятся как массив + pointer. Sub DistributeItemsToSets_TextOutput_Fix() Dim wsMap As Worksheet, wsItems As Worksheet, wsSets As Worksheet Dim wsOut As Worksheet, wsErr As Worksheet Dim lastRow As Long Dim dictItems As Object ' ITEM_GTIN -> array of codes (Variant) Dim dictPointers As Object ' ITEM_GTIN -> Long (next index to take, 1-based) Dim dictSets As Object ' SET_GTIN -> Collection of set codes Dim dictMap As Object ' SET_GTIN -> Collection of "ITEMGTIN|COUNT" Dim r As Long Dim key As Variant Application.ScreenUpdating = False Application.Calculation = xlCalculationManual ' Листы (поменяйте названия при необходимости) Set wsMap = ThisWorkbook.Worksheets("Map") Set wsItems = ThisWorkbook.Worksheets("Items") Set wsSets = ThisWorkbook.Worksheets("Sets") ' Удалить старые выходные листы, если есть On Error Resume Next Application.DisplayAlerts = False ThisWorkbook.Worksheets("Assignments").Delete ThisWorkbook.Worksheets("Errors").Delete Application.DisplayAlerts = True On Error GoTo 0 ' Создать выходные листы и установить текстовый формат Set wsOut = ThisWorkbook.Worksheets.Add wsOut.Name = "Assignments" wsOut.Cells.NumberFormat = "@" wsOut.Range("A1:D1").Value = Array("SET_GTIN", "SET_CODE", "ITEM_GTIN", "ITEM_CODE") Set wsErr = ThisWorkbook.Worksheets.Add wsErr.Name = "Errors" wsErr.Cells.NumberFormat = "@" wsErr.Range("A1:D1").Value = Array("ITEM_GTIN", "NEEDED", "AVAILABLE", "MISSING") ' Инициализация структур Set dictItems = CreateObject("Scripting.Dictionary") Set dictPointers = CreateObject("Scripting.Dictionary") Set dictSets = CreateObject("Scripting.Dictionary") Set dictMap = CreateObject("Scripting.Dictionary") ' ---------------- 1) Считать Items -> dictItems (как массив) ---------------- lastRow = wsItems.Cells(wsItems.Rows.Count, "A").End(xlUp).Row Dim tempMap As Object Set tempMap = CreateObject("Scripting.Dictionary") ' временно собираем списки в Collections For r = 2 To lastRow Dim itemCode As String, itemGTIN As String itemCode = Trim(CStr(wsItems.Cells(r, 1).Text)) itemGTIN = Trim(CStr(wsItems.Cells(r, 2).Text)) If itemGTIN <> "" And itemCode <> "" Then If Not tempMap.Exists(itemGTIN) Then tempMap.Add itemGTIN, New Collection End If tempMap(itemGTIN).Add itemCode End If Next r ' Преобразуем Collection -> массив и инициализируем pointers Dim coll As Collection For Each key In tempMap.Keys Set coll = tempMap(key) Dim arr() As Variant ReDim arr(1 To coll.Count) Dim ii As Long For ii = 1 To coll.Count arr(ii) = CStr(coll(ii)) Next ii dictItems.Add CStr(key), arr dictPointers.Add CStr(key), 1 ' следующий индекс для выдачи (1-based) Next key Set tempMap = Nothing ' ---------------- 2) Считать Sets -> dictSets ---------------- lastRow = wsSets.Cells(wsSets.Rows.Count, "A").End(xlUp).Row For r = 2 To lastRow Dim setCode As String, setGTIN As String setCode = Trim(CStr(wsSets.Cells(r, 1).Text)) setGTIN = Trim(CStr(wsSets.Cells(r, 2).Text)) If setGTIN <> "" And setCode <> "" Then If Not dictSets.Exists(setGTIN) Then dictSets.Add setGTIN, New Collection End If dictSets(setGTIN).Add setCode End If Next r ' ---------------- 3) Считать Map -> dictMap ---------------- lastRow = wsMap.Cells(wsMap.Rows.Count, "A").End(xlUp).Row For r = 2 To lastRow Dim mapSetGTIN As String, mapItemGTIN As String Dim cntRaw As String, cnt As Long mapSetGTIN = Trim(CStr(wsMap.Cells(r, 1).Text)) mapItemGTIN = Trim(CStr(wsMap.Cells(r, 4).Text)) cntRaw = Trim(CStr(wsMap.Cells(r, 5).Text)) cnt = ParseCountToLong(cntRaw) ' безопасно парсим 3, 3.00, "3" и т.д. If cnt < 1 Then cnt = 1 If mapSetGTIN <> "" And mapItemGTIN <> "" Then If Not dictMap.Exists(mapSetGTIN) Then dictMap.Add mapSetGTIN, New Collection End If dictMap(mapSetGTIN).Add mapItemGTIN & "|" & CStr(cnt) End If Next r ' ---------------- 4) Основное распределение (используя массивы + pointers) ---------------- Dim outRow As Long: outRow = 2 Dim errRow As Long: errRow = 2 For Each key In dictSets.Keys Dim curSetGTIN As String: curSetGTIN = CStr(key) If Not dictMap.Exists(curSetGTIN) Then GoTo NextSetGTIN_Fix Dim setCodesColl As Collection: Set setCodesColl = dictSets(curSetGTIN) Dim mapColl As Collection: Set mapColl = dictMap(curSetGTIN) Dim si As Long For si = 1 To setCodesColl.Count Dim curSetCode As String: curSetCode = CStr(setCodesColl(si)) Dim mi As Long For mi = 1 To mapColl.Count Dim parts As Variant parts = Split(mapColl(mi), "|") Dim neededItemGTIN As String: neededItemGTIN = CStr(parts(0)) Dim neededCnt As Long: neededCnt = CLng(parts(1)) Dim j As Long For j = 1 To neededCnt ' Проверяем, есть ли пул для neededItemGTIN If dictItems.Exists(neededItemGTIN) Then Dim arrCodes As Variant arrCodes = dictItems(neededItemGTIN) Dim ptr As Long ptr = CLng(dictPointers(neededItemGTIN)) If ptr <= UBound(arrCodes) Then ' взять текущий код по указателю Dim takeCode As String takeCode = CStr(arrCodes(ptr)) ' записать в результат (строки как текст) wsOut.Cells(outRow, 1).NumberFormat = "@" wsOut.Cells(outRow, 2).NumberFormat = "@" wsOut.Cells(outRow, 3).NumberFormat = "@" wsOut.Cells(outRow, 4).NumberFormat = "@" wsOut.Cells(outRow, 1).Value = CStr(curSetGTIN) wsOut.Cells(outRow, 2).Value = CStr(curSetCode) wsOut.Cells(outRow, 3).Value = CStr(neededItemGTIN) wsOut.Cells(outRow, 4).Value = CStr(takeCode) outRow = outRow + 1 ' увеличить pointer dictPointers(neededItemGTIN) = CLng(ptr) + 1 Else ' пул кончился — логируем 1 недостающую единицу LogError_Text wsErr, errRow, neededItemGTIN, 1, 0 errRow = errRow + 1 End If Else ' вообще нет пула — логируем нужное количество LogError_Text wsErr, errRow, neededItemGTIN, neededCnt, 0 errRow = errRow + 1 Exit For End If Next j Next mi Next si NextSetGTIN_Fix: Next key Application.Calculation = xlCalculationAutomatic Application.ScreenUpdating = True MsgBox "Готово. Результат в листе 'Assignments'. Ошибки — в листе 'Errors' (если есть).", vbInformation End Sub ' ---------------- Вспомогательные процедуры ---------------- ' Безопасный парсер count: принимает "3", "3.00", "3,00" и т.п. Возвращает Long Private Function ParseCountToLong(s As String) As Long Dim t As String t = Trim(s) If t = "" Then ParseCountToLong = 1 Exit Function End If ' Заменим запятую на точку; оставим только цифры и точку t = Replace(t, ",", ".") Dim re As Object Set re = CreateObject("VBScript.RegExp") re.Pattern = "[^0-9.]" re.Global = True t = re.Replace(t, "") If t = "" Then ParseCountToLong = 1 Exit Function End If ' берем целую часть Dim posDot As Long posDot = InStr(t, ".") If posDot > 0 Then t = Left(t, posDot - 1) If t = "" Then ParseCountToLong = 1 Else ParseCountToLong = CLng(Val(t)) If ParseCountToLong < 1 Then ParseCountToLong = 1 End If End Function ' Логирование ошибок — пишет текстом Sub LogError_Text(ws As Worksheet, ByVal ByRefRow As Long, itemGTIN As String, needed As Long, available As Long) ws.Cells.NumberFormat = "@" ws.Cells(ByRefRow, 1).Value = CStr(itemGTIN) ws.Cells(ByRefRow, 2).Value = CStr(needed) ws.Cells(ByRefRow, 3).Value = CStr(available) ws.Cells(ByRefRow, 4).Value = CStr(needed - available) End Sub