С определенным периодом даты генерируйте новые строки на основе прошедших месяцев - PullRequest
0 голосов
/ 07 января 2019

У нас есть производитель продукта, и мы «нанимаем» (неофициально) всех, кто хочет помочь, и мы платим им в зависимости от того, сколько дней они работают (в конце каждого семестра), проблема в том, что мы не может должным образом контролировать денежные потоки. Главным образом потому, что у нас много людей, работающих от одной недели до двух месяцев. Теперь у нас есть простой лист Excel с решением, которое я нашел здесь и здесь .

The Current System

Хотя это очень полезно и легко для нашего бухгалтера, его обработка занимает много времени (этот рабочий лист хранит почти все в компании, + 50 тыс. Записей и ~ 10 МБ данных), и нам по-прежнему приходится обрабатывать платежи за каждый месяц. Каждый рабочий от руки. Я хочу создать сценарий VBA, который может хранить простую функцию Excel, способную разделять периоды по месяцам (отображаемые в виде новых строк) или просто копировать / вставлять строки и заменять их новыми периодами, что-то вроде этого:

Desired System

Мне удалось обнаружить, что период x занимает более одного месяца, и скопировать / вставить его на другой лист, но я не могу представить метод / логику на VBA, способный это сделать. Любые советы приветствуются.

Sub mainFunc()
Hoja1 = Worksheets("Sheet1").Cells(Rows.Count, 1).End(xlUp).Row

Dim dateS As Date 'Start Date
Dim dateE As Date 'End Date

For i = 2 To Hoja1
    If IsDate(Range("$B2")) And IsDate(Range("$C2")) Then
        dateS = Range("$B2")
        dateE = Range("$C2")
        If Month(dateE) > Month(dateS) Then
            'If end month is lesser or equal to start date then
            'The month spliter should go here and it should copypaste it in another sheet
            Worksheets("Sheet1").Rows(i).Copy
            Worksheets("Sheet2").Activate
            Hoja2 = Worksheets("Sheet2").Cells(Rows.Count, 1).End(xlUp).Row
            Worksheets("Sheet2").Cells(Hoja2 + 1, 1).Select
            ActiveSheet.Paste
            Worksheets("Sheet1").Activate

            Else
            'Else (same month) should just copypaste the same row, no changes
            Worksheets("Sheet1").Rows(i).Copy
            Worksheets("Sheet2").Activate
            Hoja2 = Worksheets("Sheet2").Cells(Rows.Count, 1).End(xlUp).Row
            Worksheets("Sheet2").Cells(Hoja2 + 1, 1).Select
            ActiveSheet.Paste
            Worksheets("Sheet1").Activate
            End If
    End If

Next
Application.CutCopyMode = False
ThisWorkbook.Worksheets("Sheet1").Cells(1, 1).Select
End Sub

1 Ответ

0 голосов
/ 07 января 2019

Предполагая, что:

  • Исходные данные находятся на рабочем листе с именем "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 (а не в формулу).

...