docs: def createExpenseItemsForEvents(request=None, datetimeTuple=None, rule=None, event=None):
      '''
      For each StaffMember-related Repeated Expense Rule, look for EventStaffMember
      instances in the designated time window that do not already have expenses associated
      with them.  For hourly rental expenses, then generate new expenses that are
      associated with this rule.  For non-hourly expenses, generate new expenses
      based on the non-overlapping intervals of days, weeks or months for which
      there is not already an ExpenseItem associated with the rule in question.
      '''
  
      # This is used repeatedly, so it is put at the top
      submissionUser = getattr(request, 'user', None)
  
      # Return the number of new expense items created
      generate_count = 0
  
      # First, construct the set of rules that need to be checked for affiliated events
      rule_filters = Q(disabled=False) & Q(rentalRate__gt=0) & \
          Q(Q(staffmemberwageinfo__isnull=False) | Q(staffdefaultwage__isnull=False))
      if rule:
          rule_filters = rule_filters & Q(id=rule.id)
      rulesToCheck = RepeatedExpenseRule.objects.filter(
          rule_filters).distinct().order_by(
              '-staffmemberwageinfo__category', '-staffdefaultwage__category'
          )
  
      # These are the filters placed on Events that overlap the window in which
      # expenses are being generated.
      event_timefilters = Q()
  
      if datetimeTuple and len(datetimeTuple) == 2:
          timelist = list(datetimeTuple)
          timelist.sort()
          event_timefilters = event_timefilters & (
              Q(event__startTime__gte=timelist[0]) & Q(event__startTime__lte=timelist[1])
          )
  
      if event:
          event_timefilters = event_timefilters & Q(event__id=event.id)
  
      # Now, we loop through the set of rules that need to be applied, then loop
      # through the Events in the window in question that involved the staff
      # member indicated by the rule.
      for rule in rulesToCheck:
          staffMember = getattr(rule, 'staffMember', None)
          staffCategory = getattr(rule, 'category', None)
  
          # No need to continue if expenses are not to be generated
          if (
                  (not staffMember and not staffCategory) or
                  (
                      not staffMember and not
                      getConstant('financial__autoGenerateFromStaffCategoryDefaults')
                  )
          ):
              continue
  
          # For construction of expense descriptions
          replacements = {
              'type': _('Staff'),
              'to': _('payment to'),
              'for': _('for'),
          }
  
          # This is the generic category for all Event staff, but it may be overridden below
          expense_category = getConstant('financial__otherStaffExpenseCat')
  
          if staffCategory:
              if staffMember:
                  # This staff member in this category
                  eventstaff_filter = Q(staffMember=staffMember) & Q(category=staffCategory)
              elif getConstant('financial__autoGenerateFromStaffCategoryDefaults'):
                  # Any staff member who does not already have a rule specified this category
                  eventstaff_filter = (
                      Q(category=staffCategory) &
                      ~Q(staffMember__expenserules__category=staffCategory)
                  )
              replacements['type'] = staffCategory.name
  
              # For standard categories of staff, map the EventStaffCategory to
              # an ExpenseCategory using the stored constants.  Otherwise, the
              # ExpenseCategory is a generic one.
              if staffCategory == getConstant('general__eventStaffCategoryAssistant'):
                  expense_category = getConstant('financial__assistantClassInstructionExpenseCat')
              elif staffCategory in [
                      getConstant('general__eventStaffCategoryInstructor'),
                      getConstant('general__eventStaffCategorySubstitute')
              ]:
                  expense_category = getConstant('financial__classInstructionExpenseCat')
  
          else:
              # We don't want to generate duplicate expenses when there is both a category-limited
              # rule and a non-limited rule for the same person, so we have to construct the list
              # of categories that are to be excluded if no category is specified by this rule.
              coveredCategories = list(staffMember.expenserules.filter(
                  category__isnull=False).values_list('category__id', flat=True))
              eventstaff_filter = Q(staffMember=staffMember) & ~Q(category__id__in=coveredCategories)
  
          if rule.advanceDays is not None:
              if rule.advanceDaysReference == RepeatedExpenseRule.MilestoneChoices.end:
                  event_timefilters = event_timefilters & Q(
                      event__endTime__lte=timezone.now() + timedelta(days=rule.advanceDays)
                  )
              elif rule.advanceDaysReference == RepeatedExpenseRule.MilestoneChoices.start:
                  event_timefilters = event_timefilters & Q(
                      event__startTime__lte=timezone.now() + timedelta(days=rule.advanceDays)
                  )
          if rule.priorDays is not None:
              if rule.priorDaysReference == RepeatedExpenseRule.MilestoneChoices.end:
                  event_timefilters = event_timefilters & Q(
                      event__endTime__gte=timezone.now() - timedelta(days=rule.priorDays)
                  )
              elif rule.priorDaysReference == RepeatedExpenseRule.MilestoneChoices.start:
                  event_timefilters = event_timefilters & Q(
                      event__startTime__gte=timezone.now() - timedelta(days=rule.priorDays)
                  )
          if rule.startDate:
              event_timefilters = event_timefilters & Q(event__startTime__gte=timezone.now().replace(
                  year=rule.startDate.year, month=rule.startDate.month, day=rule.startDate.day,
                  hour=0, minute=0, second=0, microsecond=0,
              ))
          if rule.endDate:
              event_timefilters = event_timefilters & Q(event__startTime__lte=timezone.now().replace(
                  year=rule.endDate.year, month=rule.endDate.month, day=rule.endDate.day,
                  hour=0, minute=0, second=0, microsecond=0,
              ))
  
          # Loop through EventStaffMembers for which there are not already
          # directly allocated expenses under this rule, and create new
          # ExpenseItems for them depending on whether the rule requires hourly
          # expenses or non-hourly ones to be generated.
  
          staffers = EventStaffMember.objects.filter(eventstaff_filter & event_timefilters).exclude(
              Q(event__expenseitem__expenseRule=rule)).distinct()
  
          if rule.applyRateRule == rule.RateRuleChoices.hourly:
              for staffer in staffers:
                  # Hourly expenses are always generated without checking for
                  # overlapping windows, because the periods over which hourly
                  # expenses are defined are disjoint.  However, hourly expenses
                  # are allocated directly to events, so we just need to create
                  # expenses for any events that do not already have an Expense
                  # Item generate under this rule.
                  replacements['event'] = staffer.event.name
                  replacements['name'] = staffer.staffMember.fullName
                  replacements['dates'] = staffer.event.startTime.strftime('%Y-%m-%d')
                  if (
                          staffer.event.startTime.strftime('%Y-%m-%d') !=
                          staffer.event.endTime.strftime('%Y-%m-%d')
                  ):
                      replacements['dates'] += ' %s %s' % (
                          _('to'), staffer.event.endTime.strftime('%Y-%m-%d')
                      )
  
                  # Find or create the TransactionParty associated with the staff member.
                  staffer_party = TransactionParty.objects.get_or_create(
                      staffMember=staffer.staffMember,
                      defaults={
                          'name': staffer.staffMember.fullName,
                          'user': getattr(staffer.staffMember, 'userAccount', None)
                      }
                  )[0]
  
                  params = {
                      'event': staffer.event,
                      'category': expense_category,
                      'expenseRule': rule,
                      'description': '%(type)s %(to)s %(name)s %(for)s: %(event)s, %(dates)s' % \
                          replacements,
                      'submissionUser': submissionUser,
                      'hours': staffer.netHours,
                      'wageRate': rule.rentalRate,
                      'total': staffer.netHours * rule.rentalRate,
                      'accrualDate': staffer.event.startTime,
                      'payTo': staffer_party,
                  }
  
                  ExpenseItem.objects.create(**params)
                  generate_count += 1
          else:
              # Non-hourly expenses are generated by constructing the time
              # intervals in which the occurrence occurs, and removing from that
              # interval any intervals in which an expense has already been
              # generated under this rule (so, for example, monthly rentals will
              # now show up multiple times). So, we just need to construct the set
              # of intervals for which to construct expenses.  We first need to
              # split the set of EventStaffMember objects by StaffMember (in case
              # this rule is not person-specific) and then run this provedure
              # separated by StaffMember.
              members = StaffMember.objects.filter(eventstaffmember__in=staffers)
  
              for member in members:
                  events = [x.event for x in staffers.filter(staffMember=member)]
  
                  # Find or create the TransactionParty associated with the staff member.
                  staffer_party = TransactionParty.objects.get_or_create(
                      staffMember=member,
                      defaults={
                          'name': member.fullName,
                          'user': getattr(member, 'userAccount', None)
                      }
                  )[0]
  
                  intervals = [
                      (x.localStartTime, x.localEndTime) for x in
                      EventOccurrence.objects.filter(event__in=events)
                  ]
                  remaining_intervals = rule.getWindowsAndTotals(intervals)
  
                  for startTime, endTime, total, description in remaining_intervals:
                      replacements['when'] = description
                      replacements['name'] = member.fullName
  
                      params = {
                          'category': expense_category,
                          'expenseRule': rule,
                          'periodStart': startTime,
                          'periodEnd': endTime,
                          'description': '%(type)s %(to)s %(name)s %(for)s %(when)s' % replacements,
                          'submissionUser': submissionUser,
                          'total': total,
                          'accrualDate': startTime,
                          'payTo': staffer_party,
                      }
  
                      ExpenseItem.objects.create(**params)
                      generate_count += 1
      rulesToCheck.update(lastRun=timezone.now())
      return generate_count