Загрузка данных
Option Explicit
Public Sub RefreshMonthData()
Dim folderPath As String
Dim fileName As String
Dim fullPath As String
Dim wb As Workbook
Dim wsSummary As Worksheet, wsRepairs As Worksheet, wsReasons As Worksheet
Dim wsFiles As Worksheet, wsDash As Worksheet
Dim dictSummary As Object, dictReasons As Object
Dim repairsRow As Long, filesRow As Long
Dim reportName As String
Dim dt As Variant
Dim openedHere As Boolean
On Error GoTo EH
Application.ScreenUpdating = False
Application.DisplayAlerts = False
Application.EnableEvents = False
Application.AskToUpdateLinks = False
Application.Calculation = xlCalculationManual
Application.StatusBar = "Обновление отчета..."
folderPath = ThisWorkbook.Path
If Len(folderPath) = 0 Then
MsgBox "Сначала сохраните файл отчета в папку с исходными файлами.", vbExclamation
GoTo SafeExit
End If
reportName = ThisWorkbook.Name
Set dictSummary = CreateObject("Scripting.Dictionary")
Set dictReasons = CreateObject("Scripting.Dictionary")
PrepareSheets wsDash, wsSummary, wsRepairs, wsReasons, wsFiles
repairsRow = 2
filesRow = 2
fileName = Dir(folderPath & "\*.xls*")
Do While fileName <> ""
If LCase(fileName) <> LCase(reportName) Then
If Left$(fileName, 2) <> "~$" Then
fullPath = folderPath & "\" & fileName
dt = ExtractDateFromFilename(fileName)
If IsDate(dt) Then
openedHere = False
On Error Resume Next
Set wb = Workbooks.Open(Filename:=fullPath, ReadOnly:=True, UpdateLinks:=False, IgnoreReadOnlyRecommended:=True)
If Err.Number = 0 Then
openedHere = True
End If
On Error GoTo EH
If Not wb Is Nothing Then
ProcessWorkbook wb, CDate(dt), fileName, dictSummary, dictReasons, wsRepairs, repairsRow, wsFiles, filesRow
If openedHere Then
wb.Close SaveChanges:=False
End If
Set wb = Nothing
Else
LogFile wsFiles, filesRow, fileName, CDate(dt), "Ошибка", "Не удалось открыть файл"
filesRow = filesRow + 1
End If
Else
LogFile wsFiles, filesRow, fileName, Empty, "Пропущен", "Не найдена дата в имени файла"
filesRow = filesRow + 1
End If
End If
End If
fileName = Dir
Loop
WriteSummary wsSummary, dictSummary
WriteReasons wsReasons, dictReasons
BuildDashboard wsDash, wsSummary, wsReasons, wsFiles
FormatSheets wsSummary, wsRepairs, wsReasons, wsFiles, wsDash
Application.StatusBar = False
MsgBox "Обновление завершено.", vbInformation
SafeExit:
Application.ScreenUpdating = True
Application.DisplayAlerts = True
Application.EnableEvents = True
Application.AskToUpdateLinks = True
Application.Calculation = xlCalculationAutomatic
Application.StatusBar = False
Exit Sub
EH:
MsgBox "Ошибка при обновлении: " & Err.Description, vbCritical
Resume SafeExit
End Sub
Private Sub PrepareSheets(ByRef wsDash As Worksheet, ByRef wsSummary As Worksheet, ByRef wsRepairs As Worksheet, ByRef wsReasons As Worksheet, ByRef wsFiles As Worksheet)
Set wsDash = GetOrCreateSheet("Дашборд")
Set wsSummary = GetOrCreateSheet("Сводка по дням")
Set wsRepairs = GetOrCreateSheet("Ремонты")
Set wsReasons = GetOrCreateSheet("Причины")
Set wsFiles = GetOrCreateSheet("Файлы")
wsDash.Cells.Clear
wsSummary.Cells.Clear
wsRepairs.Cells.Clear
wsReasons.Cells.Clear
wsFiles.Cells.Clear
wsSummary.Range("A1:H1").Value = Array("Дата", "Файлов", "Ремонтов", "Выработка ДЭС", "Расход топлива котельные", "ДГУ в работе", "ДГУ в резерве", "ДГУ в ремонте")
wsRepairs.Range("A1:F1").Value = Array("Дата", "Источник", "Объект/оборудование", "Причина", "Примечание", "Исходный файл")
wsReasons.Range("A1:B1").Value = Array("Причина", "Количество")
wsFiles.Range("A1:D1").Value = Array("Файл", "Дата", "Статус", "Комментарий")
End Sub
Private Sub ProcessWorkbook(ByVal wb As Workbook, ByVal dt As Date, ByVal fileName As String, ByVal dictSummary As Object, ByVal dictReasons As Object, ByVal wsRepairs As Worksheet, ByRef repairsRow As Long, ByVal wsFiles As Worksheet, ByRef filesRow As Long)
Dim sumGeneration As Double, sumFuel As Double
Dim cntRepairs As Long, cntWork As Long, cntReserve As Long, cntRepairState As Long
Dim okAny As Boolean
okAny = False
sumGeneration = ReadDES(wb, cntWork, cntReserve, cntRepairState)
If sumGeneration <> 0 Or cntWork <> 0 Or cntReserve <> 0 Or cntRepairState <> 0 Then okAny = True
sumFuel = ReadBoilers(wb)
If sumFuel <> 0 Then okAny = True
cntRepairs = ReadRepairs(wb, dt, fileName, dictReasons, wsRepairs, repairsRow)
If cntRepairs <> 0 Then okAny = True
AddSummary dictSummary, dt, 1, cntRepairs, sumGeneration, sumFuel, cntWork, cntReserve, cntRepairState
If okAny Then
LogFile wsFiles, filesRow, fileName, dt, "ОК", "Обработан"
Else
LogFile wsFiles, filesRow, fileName, dt, "ОК", "Файл открыт, но данные не распознаны"
End If
filesRow = filesRow + 1
End Sub
Private Function ReadDES(ByVal wb As Workbook, ByRef cntWork As Long, ByRef cntReserve As Long, ByRef cntRepairState As Long) As Double
Dim ws As Worksheet
Dim headerRow As Long
Dim genCol As Long, statusCol As Long
Dim lastRow As Long, r As Long
Dim v As Variant, txt As String
Dim totalGen As Double
On Error Resume Next
Set ws = wb.Worksheets("ДЭС")
On Error GoTo 0
If ws Is Nothing Then Exit Function
headerRow = FindHeaderRow(ws, Array("выработ", "состоя", "режим", "статус"))
If headerRow = 0 Then headerRow = 1
genCol = FindColumnByKeywords(ws, headerRow, Array("выработ"))
statusCol = FindColumnByKeywords(ws, headerRow, Array("состоя", "режим", "статус"))
lastRow = LastUsedRow(ws)
totalGen = 0
If genCol > 0 Then
For r = headerRow + 1 To lastRow
v = ws.Cells(r, genCol).Value
If IsNumeric(v) Then totalGen = totalGen + CDbl(v)
Next r
End If
If statusCol > 0 Then
For r = headerRow + 1 To lastRow
txt = LCase(Trim(CStr(ws.Cells(r, statusCol).Value)))
If txt <> "" Then
If InStr(txt, "работ") > 0 Then
cntWork = cntWork + 1
ElseIf InStr(txt, "резерв") > 0 Then
cntReserve = cntReserve + 1
ElseIf InStr(txt, "ремонт") > 0 Then
cntRepairState = cntRepairState + 1
End If
End If
Next r
End If
ReadDES = totalGen
End Function
Private Function ReadBoilers(ByVal wb As Workbook) As Double
Dim ws As Worksheet
Dim headerRow As Long
Dim fuelCol As Long
Dim lastRow As Long, r As Long
Dim v As Variant
Dim totalFuel As Double
On Error Resume Next
Set ws = wb.Worksheets("Котельные")
On Error GoTo 0
If ws Is Nothing Then Exit Function
headerRow = FindHeaderRow(ws, Array("расход", "топлив"))
If headerRow = 0 Then headerRow = 1
fuelCol = FindColumnByKeywords(ws, headerRow, Array("расход", "топлив"))
If fuelCol = 0 Then fuelCol = FindColumnByKeywords(ws, headerRow, Array("топлив"))
lastRow = LastUsedRow(ws)
totalFuel = 0
If fuelCol > 0 Then
For r = headerRow + 1 To lastRow
v = ws.Cells(r, fuelCol).Value
If IsNumeric(v) Then totalFuel = totalFuel + CDbl(v)
Next r
End If
ReadBoilers = totalFuel
End Function
Private Function ReadRepairs(ByVal wb As Workbook, ByVal dt As Date, ByVal fileName As String, ByVal dictReasons As Object, ByVal wsRepairs As Worksheet, ByRef repairsRow As Long) As Long
Dim ws As Worksheet
Dim headerRow As Long
Dim objCol As Long, reasonCol As Long, noteCol As Long
Dim lastRow As Long, r As Long
Dim objVal As String, reasonVal As String, noteVal As String
Dim cnt As Long
On Error Resume Next
Set ws = wb.Worksheets("Ремонт оборудования")
On Error GoTo 0
If ws Is Nothing Then Exit Function
headerRow = FindHeaderRow(ws, Array("прич", "оборуд", "объект", "примеч"))
If headerRow = 0 Then headerRow = 1
objCol = FindColumnByKeywords(ws, headerRow, Array("оборуд", "объект", "наимен"))
reasonCol = FindColumnByKeywords(ws, headerRow, Array("прич"))
noteCol = FindColumnByKeywords(ws, headerRow, Array("примеч", "описан", "коммент"))
lastRow = LastUsedRow(ws)
cnt = 0
For r = headerRow + 1 To lastRow
objVal = ""
reasonVal = ""
noteVal = ""
If objCol > 0 Then objVal = Trim(CStr(ws.Cells(r, objCol).Value))
If reasonCol > 0 Then reasonVal = Trim(CStr(ws.Cells(r, reasonCol).Value))
If noteCol > 0 Then noteVal = Trim(CStr(ws.Cells(r, noteCol).Value))
If objVal <> "" Or reasonVal <> "" Or noteVal <> "" Then
wsRepairs.Cells(repairsRow, 1).Value = dt
wsRepairs.Cells(repairsRow, 2).Value = "Ремонт оборудования"
wsRepairs.Cells(repairsRow, 3).Value = objVal
wsRepairs.Cells(repairsRow, 4).Value = reasonVal
wsRepairs.Cells(repairsRow, 5).Value = noteVal
wsRepairs.Cells(repairsRow, 6).Value = fileName
repairsRow = repairsRow + 1
cnt = cnt + 1
If reasonVal <> "" Then
If dictReasons.Exists(reasonVal) Then
dictReasons(reasonVal) = CLng(dictReasons(reasonVal)) + 1
Else
dictReasons.Add reasonVal, 1
End If
End If
End If
Next r
ReadRepairs = cnt
End Function
Private Sub AddSummary(ByVal dictSummary As Object, ByVal dt As Date, ByVal fileCount As Long, ByVal repairCount As Long, ByVal generation As Double, ByVal fuel As Double, ByVal workCnt As Long, ByVal reserveCnt As Long, ByVal repairStateCnt As Long)
Dim key As String
Dim arr As Variant
key = Format(dt, "yyyy-mm-dd")
If dictSummary.Exists(key) Then
arr = dictSummary(key)
Else
ReDim arr(1 To 7)
arr(1) = 0
arr(2) = 0
arr(3) = 0#
arr(4) = 0#
arr(5) = 0
arr(6) = 0
arr(7) = 0
End If
arr(1) = arr(1) + fileCount
arr(2) = arr(2) + repairCount
arr(3) = arr(3) + generation
arr(4) = arr(4) + fuel
arr(5) = arr(5) + workCnt
arr(6) = arr(6) + reserveCnt
arr(7) = arr(7) + repairStateCnt
dictSummary(key) = arr
End Sub
Private Sub WriteSummary(ByVal ws As Worksheet, ByVal dictSummary As Object)
Dim keys As Variant, i As Long, r As Long, arr As Variant
Dim temp As String
If dictSummary.Count = 0 Then Exit Sub
keys = dictSummary.Keys
SortStringDates keys
r = 2
For i = LBound(keys) To UBound(keys)
arr = dictSummary(keys(i))
ws.Cells(r, 1).Value = CDate(keys(i))
ws.Cells(r, 2).Value = arr(1)
ws.Cells(r, 3).Value = arr(2)
ws.Cells(r, 4).Value = arr(3)
ws.Cells(r, 5).Value = arr(4)
ws.Cells(r, 6).Value = arr(5)
ws.Cells(r, 7).Value = arr(6)
ws.Cells(r, 8).Value = arr(7)
r = r + 1
Next i
End Sub
Private Sub WriteReasons(ByVal ws As Worksheet, ByVal dictReasons As Object)
Dim keys As Variant, i As Long, r As Long
If dictReasons.Count = 0 Then Exit Sub
keys = dictReasons.Keys
r = 2
For i = LBound(keys) To UBound(keys)
ws.Cells(r, 1).Value = keys(i)
ws.Cells(r, 2).Value = dictReasons(keys(i))
r = r + 1
Next i
Dim lastRow As Long
lastRow = LastUsedRow(ws)
If lastRow >= 2 Then
ws.Range("A1:B" & lastRow).Sort Key1:=ws.Range("B2"), Order1:=xlDescending, Header:=xlYes
End If
End Sub
Private Sub BuildDashboard(ByVal wsDash As Worksheet, ByVal wsSummary As Worksheet, ByVal wsReasons As Worksheet, ByVal wsFiles As Worksheet)
Dim lastSummaryRow As Long, lastReasonsRow As Long, lastFilesRow As Long
Dim ch As ChartObject
wsDash.Cells.Clear
wsDash.Range("A1").Value = "Месячный дашборд"
wsDash.Range("A1").Font.Size = 18
wsDash.Range("A1").Font.Bold = True
wsDash.Range("A3").Value = "Файлов обработано"
wsDash.Range("B3").Formula = "=MAX(0,COUNTA('Файлы'!A:A)-1)"
wsDash.Range("D3").Value = "Дней с данными"
wsDash.Range("E3").Formula = "=MAX(0,COUNTA('Сводка по дням'!A:A)-1)"
wsDash.Range("A4").Value = "Всего ремонтов"
wsDash.Range("B4").Formula = "=SUM('Сводка по дням'!C:C)"
wsDash.Range("D4").Value = "Сумма выработки"
wsDash.Range("E4").Formula = "=SUM('Сводка по дням'!D:D)"
wsDash.Range("A5").Value = "Расход топлива"
wsDash.Range("B5").Formula = "=SUM('Сводка по дням'!E:E)"
wsDash.Range("D5").Value = "Последняя дата"
wsDash.Range("E5").Formula = "=MAX('Сводка по дням'!A:A)"
wsDash.Range("E5").NumberFormat = "dd.mm.yyyy"
lastSummaryRow = LastUsedRow(wsSummary)
lastReasonsRow = LastUsedRow(wsReasons)
lastFilesRow = LastUsedRow(wsFiles)
DeleteAllCharts wsDash
If lastSummaryRow >= 2 Then
Set ch = wsDash.ChartObjects.Add(Left:=20, Top:=110, Width:=420, Height:=220)
With ch.Chart
.ChartType = xlColumnClustered
.SetSourceData Source:=wsSummary.Range("A1:C" & lastSummaryRow)
.HasTitle = True
.ChartTitle.Text = "Ремонты по дням"
.Axes(xlCategory).TickLabels.NumberFormat = "dd.mm"
End With
Set ch = wsDash.ChartObjects.Add(Left:=460, Top:=110, Width:=420, Height:=220)
With ch.Chart
.ChartType = xlLine
.SetSourceData Source:=wsSummary.Range("A1:D" & lastSummaryRow)
.HasTitle = True
.ChartTitle.Text = "Выработка по дням"
.Axes(xlCategory).TickLabels.NumberFormat = "dd.mm"
End With
Set ch = wsDash.ChartObjects.Add(Left:=20, Top:=350, Width:=420, Height:=220)
With ch.Chart
.ChartType = xlLine
.SetSourceData Source:=wsSummary.Range("A1:E" & lastSummaryRow)
.HasTitle = True
.ChartTitle.Text = "Расход топлива по дням"
.Axes(xlCategory).TickLabels.NumberFormat = "dd.mm"
End With
Set ch = wsDash.ChartObjects.Add(Left:=460, Top:=350, Width:=420, Height:=220)
With ch.Chart
.ChartType = xlColumnClustered
.SetSourceData Source:=wsSummary.Range("F1:H" & lastSummaryRow)
.HasTitle = True
.ChartTitle.Text = "Состояние ДГУ"
End With
End If
If lastReasonsRow >= 2 Then
Set ch = wsDash.ChartObjects.Add(Left:=20, Top:=590, Width:=860, Height:=250)
With ch.Chart
.ChartType = xlBarClustered
.SetSourceData Source:=wsReasons.Range("A1:B" & WorksheetFunction.Min(lastReasonsRow, 11))
.HasTitle = True
.ChartTitle.Text = "Топ причин ремонтов"
End With
End If
End Sub
Private Sub DeleteAllCharts(ByVal ws As Worksheet)
Dim i As Long
For i = ws.ChartObjects.Count To 1 Step -1
ws.ChartObjects(i).Delete
Next i
End Sub
Private Sub FormatSheets(ByVal wsSummary As Worksheet, ByVal wsRepairs As Worksheet, ByVal wsReasons As Worksheet, ByVal wsFiles As Worksheet, ByVal wsDash As Worksheet)
Dim ws As Worksheet
For Each ws In Array(wsSummary, wsRepairs, wsReasons, wsFiles)
With ws.Rows(1)
.Font.Bold = True
.Interior.Color = RGB(217, 225, 242)
End With
ws.Cells.EntireColumn.AutoFit
ws.Rows(1).AutoFilter
Next ws
wsSummary.Columns("A").NumberFormat = "dd.mm.yyyy"
wsRepairs.Columns("A").NumberFormat = "dd.mm.yyyy"
wsFiles.Columns("B").NumberFormat = "dd.mm.yyyy"
wsDash.Columns("A:F").ColumnWidth = 16
wsDash.Range("A3:E5").Font.Bold = True
End Sub
Private Sub LogFile(ByVal ws As Worksheet, ByVal rowNum As Long, ByVal fileName As String, ByVal dt As Variant, ByVal statusText As String, ByVal commentText As String)
ws.Cells(rowNum, 1).Value = fileName
If IsDate(dt) Then ws.Cells(rowNum, 2).Value = CDate(dt)
ws.Cells(rowNum, 3).Value = statusText
ws.Cells(rowNum, 4).Value = commentText
End Sub
Private Function GetOrCreateSheet(ByVal sheetName As String) As Worksheet
On Error Resume Next
Set GetOrCreateSheet = ThisWorkbook.Worksheets(sheetName)
On Error GoTo 0
If GetOrCreateSheet Is Nothing Then
Set GetOrCreateSheet = ThisWorkbook.Worksheets.Add(After:=ThisWorkbook.Worksheets(ThisWorkbook.Worksheets.Count))
GetOrCreateSheet.Name = sheetName
End If
End Function
Private Function ExtractDateFromFilename(ByVal fileName As String) As Variant
Dim re As Object, m As Object
Dim s As String, d As Integer, mn As Integer, y As Integer
Set re = CreateObject("VBScript.RegExp")
re.Global = False
re.IgnoreCase = True
re.Pattern = "(\d{1,2})[.\-_](\d{1,2})[.\-_](\d{4})"
If re.Test(fileName) Then
Set m = re.Execute(fileName)(0)
d = CInt(m.SubMatches(0))
mn = CInt(m.SubMatches(1))
y = CInt(m.SubMatches(2))
On Error Resume Next
ExtractDateFromFilename = DateSerial(y, mn, d)
If Err.Number <> 0 Then ExtractDateFromFilename = Empty
On Error GoTo 0
Else
ExtractDateFromFilename = Empty
End If
End Function
Private Function FindHeaderRow(ByVal ws As Worksheet, ByVal keywords As Variant) As Long
Dim r As Long, c As Long, lastCol As Long, txt As String, k As Variant
Dim score As Long, bestScore As Long, bestRow As Long
bestScore = 0
bestRow = 0
For r = 1 To WorksheetFunction.Min(10, ws.UsedRange.Rows.Count)
lastCol = LastUsedColInRow(ws, r)
score = 0
For c = 1 To lastCol
txt = LCase(Trim(CStr(ws.Cells(r, c).Value)))
If txt <> "" Then
For Each k In keywords
If InStr(txt, LCase(CStr(k))) > 0 Then
score = score + 1
Exit For
End If
Next k
End If
Next c
If score > bestScore Then
bestScore = score
bestRow = r
End If
Next r
FindHeaderRow = bestRow
End Function
Private Function FindColumnByKeywords(ByVal ws As Worksheet, ByVal headerRow As Long, ByVal keywords As Variant) As Long
Dim c As Long, lastCol As Long, txt As String, k As Variant
lastCol = LastUsedColInRow(ws, headerRow)
For c = 1 To lastCol
txt = LCase(Trim(CStr(ws.Cells(headerRow, c).Value)))
If txt <> "" Then
For Each k In keywords
If InStr(txt, LCase(CStr(k))) > 0 Then
FindColumnByKeywords = c
Exit Function
End If
Next k
End If
Next c
End Function
Private Function LastUsedRow(ByVal ws As Worksheet) As Long
On Error Resume Next
LastUsedRow = ws.Cells.Find(What:="*", After:=ws.Range("A1"), LookAt:=xlPart, LookIn:=xlFormulas, SearchOrder:=xlByRows, SearchDirection:=xlPrevious).Row
If LastUsedRow = 0 Then LastUsedRow = 1
On Error GoTo 0
End Function
Private Function LastUsedColInRow(ByVal ws As Worksheet, ByVal rowNum As Long) As Long
On Error Resume Next
LastUsedColInRow = ws.Cells(rowNum, ws.Columns.Count).End(xlToLeft).Column
If LastUsedColInRow = 0 Then LastUsedColInRow = 1
On Error GoTo 0
End Function
Private Sub SortStringDates(ByRef arr As Variant)
Dim i As Long, j As Long
Dim tmp As Variant
For i = LBound(arr) To UBound(arr) - 1
For j = i + 1 To UBound(arr)
If CDate(arr(j)) < CDate(arr(i)) Then
tmp = arr(i)
arr(i) = arr(j)
arr(j) = tmp
End If
Next j
Next i
End Sub
Public Sub CreateRefreshButton()
Dim ws As Worksheet
Dim shp As Shape
Set ws = GetOrCreateSheet("Дашборд")
On Error Resume Next
ws.Shapes("btnRefresh").Delete
On Error GoTo 0
Set shp = ws.Shapes.AddShape(msoShapeRoundedRectangle, 700, 20, 180, 35)
shp.Name = "btnRefresh"
shp.TextFrame.Characters.Text = "Обновить данные"
shp.OnAction = "RefreshMonthData"
End Sub