Предполагая, что:
- Исходные данные находятся на рабочем листе с именем
"Sheet1"
с "member"
в столбце A, "day start"
в столбце B, "day exit"
в столбце C
- И вы выводите данные на лист с именем
"Sheet2"
Приведенный ниже код должен дать вам представление о том, чего вы хотите (это дает мне то, что вы показываете на своем втором снимке экрана, за исключением числа отработанных дней, поскольку я использую функцию NETWORKDAYS
).
Option Explicit
Sub SplitPayDataIntoMonths()
Dim destinationSheet As Worksheet
Set destinationSheet = ThisWorkbook.Worksheets("Sheet2")
Dim sourceSheet As Worksheet
Set sourceSheet = ThisWorkbook.Worksheets("Sheet1")
Dim lastSourceRow As Long
lastSourceRow = sourceSheet.Cells(sourceSheet.Rows.Count, "A").End(xlUp).Row
Dim sourceRowIndex As Long
Dim destinationRowIndex As Long
destinationRowIndex = 1 ' Skip first row/headers
Dim startDate As Variant
Dim endDate As Variant
Dim dateIndex As Date
For sourceRowIndex = 2 To lastSourceRow ' Skip first row/headers.
startDate = sourceSheet.Cells(sourceRowIndex, "B").Value
endDate = sourceSheet.Cells(sourceRowIndex, "C").Value
' Validate dates before looping through them to prevent unwanted behaviour later
If Not IsDate(startDate) Or Not IsDate(endDate) Then
MsgBox ("Invalid date encountered in row '" & sourceRowIndex & "' of sheet '" & sourceSheet.Name & "'." & vbNewLine & vbNewLine & "Code will stop running now.")
Application.Goto sourceSheet.Cells(sourceRowIndex, "B")
Exit Sub
ElseIf startDate > endDate Then
MsgBox ("'Start date' exceed 'end date' on row '" & sourceRowIndex & "' of sheet '" & sourceSheet.Name & "'." & vbNewLine & vbNewLine & "Code will stop running now.")
Application.Goto sourceSheet.Cells(sourceRowIndex, "B")
Exit Sub
End If
For dateIndex = startDate To endDate
destinationRowIndex = destinationRowIndex + 1
destinationSheet.Cells(destinationRowIndex, "A").Value = sourceSheet.Cells(sourceRowIndex, "A").Value
destinationSheet.Cells(destinationRowIndex, "B").Value = dateIndex
dateIndex = Application.Min(Application.EoMonth(dateIndex, 0), endDate)
destinationSheet.Cells(destinationRowIndex, "C").Value = dateIndex
Next dateIndex
Next sourceRowIndex
destinationSheet.Range("D2:D" & destinationRowIndex).Formula = "=NETWORKDAYS(B2,C2)" 'Days worked
destinationSheet.Range("E2:E" & destinationRowIndex).Formula = "=D2*15" ' Pay, same daily rate assumed for everyone (based on screenshot in question), but change as necessary
End Sub
Очевидно, что вы можете настроить формулы, которые записываются в destinationSheet
, так как я сделал некоторые предположения, которые, вероятно, не верны для каждого "члена".
Edit:
NETWORKDAYS
функция имеет третий параметр с именем Holidays
, который можно использовать для указания дат, которые вы хотите исключить. Зависит от того, как вы хотите это реализовать.
Подход 1
Если вы храните все даты, которые являются выходными, в диапазоне "Z1:Z5"
на "Sheet1"
(например), вы можете изменить эту строку в коде:
destinationSheet.Range("D2:D" & destinationRowIndex).Formula = "=NETWORKDAYS(B2,C2)" 'Days worked
до:
destinationSheet.Range("D2:D" & destinationRowIndex).Formula = "=NETWORKDAYS(B2,C2,'Sheet1'!$Z$1:$Z$5)" 'Days worked
И он должен автоматически делать то, что вам нужно.
Подход 2
Если вы вместо этого хотите сохранить их в некоторой переменной VBA (а не на листе), попробуйте заменить:
destinationSheet.Range("D2:D" & destinationRowIndex).Formula = "=NETWORKDAYS(B2,C2)" 'Days worked
с:
Dim Holidays As Variant
Holidays = Array(#1/10/2019#, #1/28/2019#) ' Add as many dates as you need to. You can also add dates with DateSerial() function instead of literals, if needed.
destinationSheet.Range("D2:D" & destinationRowIndex).Formula = "=NETWORKDAYS(B1,C1,{" & holidaysToString(Holidays) & "})"
и поместите эту функцию в тот же модуль, что и ваш код:
Private Function holidaysToString(ByVal Holidays As Variant) As String
' https://support.office.com/en-us/article/networkdays-function-48e717bf-a7a3-495f-969e-5005e3eb18e7 says to avoid putting dates as "text"
'"Important: Dates should be entered by using the DATE function, or as results of other formulas or functions. For example, use DATE(2012,5,23) for the 23rd day of May, 2012. Problems can occur if dates are entered as text."
Dim index As Long
For index = LBound(Holidays) To UBound(Holidays)
Holidays(index) = CStr(CDbl(Holidays(index)))
Next index
holidaysToString = VBA.Strings.Join(Holidays, ",")
End Function
Это приведет к жестко закодированному массиву чисел (представляющих выходные дни) в формуле.
Уверен, есть и другие способы его реализации, например, вычисление его непосредственно в цикле For
и запись только возвращаемого значения в столбец D (а не в формулу).