Раньше для рабочих задач мне приходилось вручную добавлять к таблице со списком книг отдельную таблицу для заказов по филиалам — с формулами для подсчёта количества и суммы. Со временем это стало слишком утомительно, поэтому я решил автоматизировать процесс: теперь таблица строится сама, учитывая все филиалы из массива.
Option Explicit
Sub BuildBranchColumnsSafe()
Dim ws As Worksheet
Set ws = ActiveSheet
' === Настройки ===
Dim branches As Variant
branches = Array("256", "258", "259", "260", "261", "262", "263", "264", "265", "Юбилейный") ' <-- Массив с филиалами
Const BASE_COL As Long = 8 ' H — Колонка для формулы "Сумма"
Const SPACER_COL As Long = 9 ' I — Это колонка отступ
Const START_COL As Long = 10 ' J — С этой колонки будем рисовать
Const SPACER_COL_WIDTH = 1 ' Ширина колонки "отступа"
Const HEADER_ROW_1 As Long = 1
Const HEADER_ROW_2 As Long = 2
Const FIRST_DATA_ROW As Long = 3
Dim lastDataRow As Long
lastDataRow = SafeLastRow(ws, BASE_COL, FIRST_DATA_ROW)
If lastDataRow < FIRST_DATA_ROW Then lastDataRow = FIRST_DATA_ROW
' === ОТКЛЮЧАЕМ ТЯЖЕЛЫЕ ВЕЩИ НА ВРЕМЯ ПОСТРОЕНИЯ ===
Dim oldCalc As XlCalculation, oldScrUp As Boolean, oldEvt As Boolean
oldCalc = Application.Calculation
oldScrUp = Application.ScreenUpdating
oldEvt = Application.EnableEvents
Application.ScreenUpdating = False
Application.Calculation = xlCalculationManual
Application.EnableEvents = False
On Error GoTo CleanFail
' (Необязательно) Очистка колонки-отступа от содержимого
ws.Columns(SPACER_COL).ClearContents
ws.Columns(SPACER_COL).ColumnWidth = SPACER_COL_WIDTH
' Перед построением снимаем старые объединения ячеек в нашей зоне построения
Call UnmergeHead(ws, START_COL, START_COL + UBound(branches) * 2 + 1, HEADER_ROW_1, HEADER_ROW_2)
Dim i As Long, qtyCol As Long, sumCol As Long
For i = LBound(branches) To UBound(branches)
qtyCol = START_COL + i * 2
sumCol = qtyCol + 1
' Шапка: объединение на строке 1
With ws.Range(ws.Cells(HEADER_ROW_1, qtyCol), ws.Cells(HEADER_ROW_1, sumCol))
If .MergeCells Then .UnMerge
.Merge
.Value = branches(i)
.HorizontalAlignment = xlCenter
.VerticalAlignment = xlCenter
.WrapText = True
.Font.Bold = True
.Borders.LineStyle = xlContinuous
End With
' === Формат шапки с названием филиала ===
With ws.Range(ws.Cells(HEADER_ROW_1, qtyCol), ws.Cells(HEADER_ROW_1, sumCol))
.Font.Name = "Times New Roman"
.Font.Size = 10
.Font.Bold = True
.HorizontalAlignment = xlCenter
.VerticalAlignment = xlCenter
.Interior.Color = 13431551
End With
' Строка 2: подзаголовки
ws.Cells(HEADER_ROW_2, qtyCol).Value = "кол-во"
ws.Cells(HEADER_ROW_2, sumCol).Value = "сумма"
With ws.Range(ws.Cells(HEADER_ROW_2, qtyCol), ws.Cells(HEADER_ROW_2, sumCol))
.HorizontalAlignment = xlCenter
.VerticalAlignment = xlCenter
.Font.Bold = True
.Borders.LineStyle = xlContinuous
End With
' === Формат подзаголовков и ширина колонок ===
With ws.Range(ws.Cells(HEADER_ROW_2, qtyCol), ws.Cells(HEADER_ROW_2, sumCol))
.Font.Name = "Times New Roman"
.Font.Size = 8
.HorizontalAlignment = xlCenter
.VerticalAlignment = xlCenter
End With
ws.Columns(qtyCol).ColumnWidth = 5
ws.Columns(sumCol).ColumnWidth = 7.5
' Диапазон строк с данными
Dim dataRngQty As Range, dataRngSum As Range
Set dataRngQty = ws.Range(ws.Cells(FIRST_DATA_ROW, qtyCol), ws.Cells(lastDataRow, qtyCol))
Set dataRngSum = ws.Range(ws.Cells(FIRST_DATA_ROW, sumCol), ws.Cells(lastDataRow, sumCol))
' === Формат данных ===
If Not dataRngQty Is Nothing Then
With dataRngQty
.Font.Name = "Times New Roman"
.Font.Size = 12
.HorizontalAlignment = xlCenter
.VerticalAlignment = xlCenter
End With
End If
If Not dataRngSum Is Nothing Then
With dataRngSum
.Font.Name = "Times New Roman"
.Font.Size = 8
.HorizontalAlignment = xlRight
.VerticalAlignment = xlCenter
End With
End If
' Валидация 0..5 для "кол-во"
On Error Resume Next
dataRngQty.Validation.Delete
On Error GoTo 0
If dataRngQty.Rows.Count > 0 Then
dataRngQty.Validation.Add Type:=xlValidateWholeNumber, AlertStyle:=xlValidAlertStop, _
Operator:=xlBetween, Formula1:="0", Formula2:="5"
With dataRngQty.Validation
.IgnoreBlank = True
.InCellDropdown = True
.InputTitle = "Введите от 0 до 5"
.InputMessage = "Только целые числа 0…5."
.ErrorTitle = "Недопустимое значение"
.ErrorMessage = "Введите цело число от 0 до 5."
End With
End If
' Формула для "сумма" (значение из колонки H * кол-во)
If dataRngSum.Rows.Count > 0 Then
dataRngSum.FormulaR1C1 = "=RC8*RC[-1]"
dataRngSum.NumberFormat = "#,##0.00" & ChrW(32) & ChrW(&H20BD) ' Устанавливаем формат с валютой рубль
End If
' Итоговая сумма для кол-во
ws.Cells(lastDataRow + 1, qtyCol).Formula = "=SUM(" & ws.Range(ws.Cells(FIRST_DATA_ROW, qtyCol), ws.Cells(lastDataRow, qtyCol)).Address(False, False) & ")"
' Итоговая сумма для сумма
ws.Cells(lastDataRow + 1, sumCol).Formula = "=SUM(" & ws.Range(ws.Cells(FIRST_DATA_ROW, sumCol), ws.Cells(lastDataRow, sumCol)).Address(False, False) & ")"
ws.Cells(lastDataRow + 1, sumCol).NumberFormat = "#,##0.00" & ChrW(32) & ChrW(&H20BD) ' Устанавливаем формат с валютой рубль
' === Границы для таблицы ===
Dim headBlock As Range, totalBlock As Range, dataBlock As Range
Set dataBlock = ws.Range(ws.Cells(FIRST_DATA_ROW, qtyCol), ws.Cells(lastDataRow + 1, sumCol))
Set headBlock = ws.Range(ws.Cells(HEADER_ROW_1, qtyCol), ws.Cells(HEADER_ROW_2, sumCol))
Set totalBlock = ws.Range(ws.Cells(lastDataRow + 1, qtyCol), ws.Cells(lastDataRow + 1, sumCol))
' Тонкие границы для всех данных + итоговой строки
With dataBlock.Borders
.LineStyle = xlContinuous
.Weight = xlThin
End With
' Толстая рамка вокруг шапки
With headBlock.Borders(xlInsideVertical)
.LineStyle = xlContinuous: .Weight = xlThin
End With
With headBlock.Borders(xlInsideHorizontal)
.LineStyle = xlContinuous: .Weight = xlThin
End With
With headBlock.Borders(xlEdgeLeft)
.LineStyle = xlContinuous: .Weight = xlMedium
End With
With headBlock.Borders(xlEdgeTop)
.LineStyle = xlContinuous: .Weight = xlMedium
End With
With headBlock.Borders(xlEdgeRight)
.LineStyle = xlContinuous: .Weight = xlMedium
End With
With headBlock.Borders(xlEdgeBottom)
.LineStyle = xlContinuous: .Weight = xlMedium
End With
' Толстая рамка для итоговой строки
With totalBlock.Borders(xlEdgeLeft)
.LineStyle = xlContinuous: .Weight = xlMedium
End With
With totalBlock.Borders(xlEdgeTop)
.LineStyle = xlContinuous: .Weight = xlMedium
End With
With totalBlock.Borders(xlEdgeRight)
.LineStyle = xlContinuous: .Weight = xlMedium
End With
With totalBlock.Borders(xlEdgeBottom)
.LineStyle = xlContinuous: .Weight = xlMedium
End With
Next i
' Суммируем все колонки "кол-во" по строке и записываем сумму в колонку 1
Dim sumFormula As String
Dim firstQtyCol As Long, lastQtyCol As Long
firstQtyCol = START_COL
lastQtyCol = START_COL + (UBound(branches)) * 2 ' т.к. "кол-во" — это первая колонка каждого блока
Dim qtyCols As String, c As Long
qtyCols = ""
For c = firstQtyCol To lastQtyCol Step 2
qtyCols = qtyCols & ws.Cells(FIRST_DATA_ROW, c).Address(False, False) & ","
Next c
qtyCols = Left(qtyCols, Len(qtyCols) - 1) ' убираем последнюю запятую
' Записываем формулу для всех строк с данными
With ws.Range(ws.Cells(FIRST_DATA_ROW, 1), ws.Cells(lastDataRow, 1))
.Formula = "=SUM(" & qtyCols & ")"
End With
' Итоги в колонках A (общее кол-во) и B (общая сумма)
Dim grandRow As Long
Dim qtyStartCol As Long, qtyEndCol As Long
Dim colIndex As Long
Dim qtyTotalsList As String, sumTotalsList As String
grandRow = lastDataRow + 1
qtyStartCol = START_COL
qtyEndCol = START_COL + (UBound(branches) * 2) ' последняя колонка "кол-во"
' Собираем адреса итогов под колонками "кол-во"
qtyTotalsList = ""
For colIndex = qtyStartCol To qtyEndCol Step 2
qtyTotalsList = qtyTotalsList & ws.Cells(grandRow, colIndex).Address(False, False) & ","
Next colIndex
If Len(qtyTotalsList) > 0 Then qtyTotalsList = Left(qtyTotalsList, Len(qtyTotalsList) - 1)
' Собираем адреса итогов под колонками "сумма"
sumTotalsList = ""
For colIndex = qtyStartCol + 1 To qtyEndCol + 1 Step 2
sumTotalsList = sumTotalsList & ws.Cells(grandRow, colIndex).Address(False, False) & ","
Next colIndex
If Len(sumTotalsList) > 0 Then sumTotalsList = Left(sumTotalsList, Len(sumTotalsList) - 1)
' Записываем формулы в A(grandRow) и B(grandRow)
If qtyTotalsList <> "" Then ws.Cells(grandRow, 1).Formula = "=SUM(" & qtyTotalsList & ")"
If sumTotalsList <> "" Then
ws.Cells(grandRow, 2).Formula = "=SUM(" & sumTotalsList & ")"
ws.Cells(grandRow, 2).NumberFormat = "#,##0.00" & ChrW(32) & ChrW(&H20BD) ' денежный формат для общего итога
End If
' === Форматирование итоговых ячеек A(grandRow) и B(grandRow) ===
Dim cellA As Range, cellB As Range, grandTotalBlock As Range
Set cellA = ws.Cells(grandRow, 1)
Set cellB = ws.Cells(grandRow, 2)
Set grandTotalBlock = ws.Range(cellA, cellB)
' A: общее кол-во — TNR 7, центр
With cellA
.Font.Name = "Times New Roman"
.Font.Size = 7
.HorizontalAlignment = xlCenter
.VerticalAlignment = xlCenter
End With
' B: общая сумма — TNR 11, по правому краю, по центру по вертикали + денежный формат (на всякий случай)
With cellB
.Font.Name = "Times New Roman"
.Font.Size = 11
.HorizontalAlignment = xlRight
.VerticalAlignment = xlCenter
If .NumberFormat = "General" Or .NumberFormat = "" Then
.NumberFormat = "#,##0.00" & ChrW(32) & ChrW(&H20BD)
End If
End With
' Границы: тонкая внутренняя между A и B, толстая общая рамка по краям блока
With grandTotalBlock.Borders
.LineStyle = xlContinuous
.Weight = xlThin ' базово — тонкие везде
End With
With grandTotalBlock.Borders(xlInsideVertical)
.LineStyle = xlContinuous
.Weight = xlThin ' внутренняя между ячейками — обычная (тонкая)
End With
With grandTotalBlock.Borders(xlEdgeLeft)
.LineStyle = xlContinuous
.Weight = xlMedium ' общая рамка — пожирнее
End With
With grandTotalBlock.Borders(xlEdgeTop)
.LineStyle = xlContinuous
.Weight = xlMedium
End With
With grandTotalBlock.Borders(xlEdgeRight)
.LineStyle = xlContinuous
.Weight = xlMedium
End With
With grandTotalBlock.Borders(xlEdgeBottom)
.LineStyle = xlContinuous
.Weight = xlMedium
End With
CleanOk:
Application.Calculation = oldCalc
Application.ScreenUpdating = oldScrUp
Application.EnableEvents = oldEvt
'MsgBox "Готово.", vbInformation
Exit Sub
CleanFail:
' В случае ошибки — вернуть настройки и показать номер строки/описание
Application.Calculation = oldCalc
Application.ScreenUpdating = oldScrUp
Application.EnableEvents = oldEvt
MsgBox "Ошибка: " & Err.Number & " — " & Err.Description, vbExclamation
End Sub
' Возвращает безопасную «последнюю строку» по колонке baseCol:
' - ищет последнюю непустую ячейку снизу;
' - ограничивает диапазон, чтоб не получить миллион строк по случайному вводу;
' - если данных нет — возвращает FIRST_DATA_ROW.
Private Function SafeLastRow(ws As Worksheet, baseCol As Long, FIRST_DATA_ROW As Long) As Long
Dim r As Long
r = ws.Cells(ws.Rows.Count, baseCol).End(xlUp).Row
If r < FIRST_DATA_ROW Then
SafeLastRow = FIRST_DATA_ROW
Else
' Доп. страховка: если «случайно» очень далеко — ограничим, например, 10 000 строками
' (при желании увеличте лимит)
Dim MAX_ROWS As Long: MAX_ROWS = 10000
If r - FIRST_DATA_ROW + 1 > MAX_ROWS Then
SafeLastRow = FIRST_DATA_ROW + MAX_ROWS - 1
Else
SafeLastRow = r
End If
End If
End Function
' Снять объединение в шапке (если есть) в заданном диапазоне колонок.
Private Sub UnmergeHead(ws As Worksheet, firstCol As Long, lastCol As Long, hr1 As Long, hr2 As Long)
Dim rng As Range
Set rng = ws.Range(ws.Cells(hr1, firstCol), ws.Cells(hr1, lastCol))
If rng.MergeCells Then rng.UnMerge
' На всякий случай - подчистим и строку 2 там же
Set rng = ws.Range(ws.Cells(hr2, firstCol), ws.Cells(hr2, lastCol))
If rng.MergeCells Then rng.UnMerge
End Sub
Файл шаблон (Excel):