docs: def createExpenseItemsForVenueRental(request=None, datetimeTuple=None, rule=None, event=None):
      '''
      For each Location or Room-related Repeated Expense Rule, look for Events
      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.
      '''
  
      # These are used repeatedly, so they are put at the top
      submissionUser = getattr(request, 'user', None)
      rental_category = getConstant('financial__venueRentalExpenseCat')
  
      # 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(locationrentalinfo__isnull=False) | Q(roomrentalinfo__isnull=False))
      if rule:
          rule_filters = rule_filters & Q(id=rule.id)
      rulesToCheck = RepeatedExpenseRule.objects.filter(rule_filters).distinct()
  
      # These are the filters place 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(startTime__gte=timelist[0]) & Q(startTime__lte=timelist[1])
          )
      if event:
          event_timefilters = event_timefilters & Q(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 occurred at the location indicated by the rule.
      for rule in rulesToCheck:
  
          venue = (
              getattr(rule, 'location', None) if
              isinstance(rule, RoomRentalInfo) else
              getattr(rule, 'location', None)
          )
          loc = getattr(venue, 'location') if isinstance(venue, Room) else venue
          event_locfilter = Q(room=venue) if isinstance(venue, Room) else Q(location=venue)
  
          # Find or create the TransactionParty associated with the location.
          loc_party = TransactionParty.objects.get_or_create(
              location=loc, defaults={'name': loc.name}
          )[0]
  
          if rule.advanceDays:
              if rule.advanceDaysReference == RepeatedExpenseRule.MilestoneChoices.end:
                  event_timefilters = event_timefilters & \
                      Q(endTime__lte=timezone.now() + timedelta(days=rule.advanceDays))
              elif rule.advanceDaysReference == RepeatedExpenseRule.MilestoneChoices.start:
                  event_timefilters = event_timefilters & \
                      Q(startTime__lte=timezone.now() + timedelta(days=rule.advanceDays))
          if rule.priorDays:
              if rule.priorDaysReference == RepeatedExpenseRule.MilestoneChoices.end:
                  event_timefilters = event_timefilters & \
                      Q(endTime__gte=timezone.now() - timedelta(days=rule.priorDays))
              elif rule.priorDaysReference == RepeatedExpenseRule.MilestoneChoices.start:
                  event_timefilters = event_timefilters & \
                      Q(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,
                  )
              )
  
          # For construction of expense descriptions
          replacements = {
              'type': _('Event/Series venue rental'),
              'of': _('of'),
              'location': venue.name,
              'for': _('for'),
          }
  
          # Loop through Events 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.
          events = Event.objects.filter(event_locfilter & event_timefilters).exclude(
              Q(expenseitem__expenseRule=rule)).distinct()
  
          if rule.applyRateRule == rule.RateRuleChoices.hourly:
              for this_event in events:
                  # 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['name'] = this_event.name
                  replacements['dates'] = this_event.localStartTime.strftime('%Y-%m-%d')
                  if (
                          event.localStartTime.strftime('%Y-%m-%d') != \
                          this_event.localEndTime.strftime('%Y-%m-%d')
                  ):
                      replacements['dates'] += ' %s %s' % (
                          _('to'), this_event.localEndTime.strftime('%Y-%m-%d')
                      )
  
                  ExpenseItem.objects.create(
                      event=this_event,
                      category=rental_category,
                      payTo=loc_party,
                      expenseRule=rule,
                      description='%(type)s %(of)s %(location)s %(for)s: %(name)s, %(dates)s' % \
                          replacements,
                      submissionUser=submissionUser,
                      total=this_event.duration * rule.rentalRate,
                      accrualDate=this_event.startTime,
                  )
                  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
              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
  
                  ExpenseItem.objects.create(
                      category=rental_category,
                      payTo=loc_party,
                      expenseRule=rule,
                      periodStart=startTime,
                      periodEnd=endTime,
                      description='%(type)s %(of)s %(location)s %(for)s %(when)s' % replacements,
                      submissionUser=submissionUser,
                      total=total,
                      accrualDate=startTime,
                  )
                  generate_count += 1
      rulesToCheck.update(lastRun=timezone.now())
      return generate_count