Files
ubicloud/model/billing_record.rb
Jeremy Evans 30247a3800 Include model annotations at the bottom of all model files
This makes it easier for developers new to the codebase to
easily get important information on the model's table in the
same file as the model code.

To ensure the model annotations stay accurate, run them on
test_up/test_down.  In CI, regenerate the annotations, and
check for no changes, similar to how the linters work.
2024-11-13 09:13:30 -08:00

61 lines
2.5 KiB
Ruby

# frozen_string_literal: true
require_relative "../model"
class BillingRecord < Sequel::Model
many_to_one :project
dataset_module do
def active
where { {Sequel.function(:upper, :span) => nil} }
end
end
include ResourceMethods
def duration(begin_time, end_time)
# Billing logic differs based on the resource type: some are billed by duration, others
# by amount. For 'VmCores' billing records, the core counts are stored in the amount
# column and charges are based on duration. For example, 10 minutes of standard-4 vm
# usage would be calculated as `10 (duration) x 2 (amount) x rate_for_standard_core`.
# 'GitHubRunnerMinutes' records, on the other hand, store the used minutes in the
# 'amount' column, and billing is based on that amount.
# For records billed by amount, the duration is always set to 1.
return 1 if billing_rate["billed_by"] == "amount"
# begin_time and end_time refers to begin and end of the billing window. Duration of
# BillingRecord is subjective to the billing window we are querying for. For example
# if span of the BillingRecord is ['2023-06-15', '2023-08-20'] and billing window is
# ['2023-06-01', '2023-07-01'], then we are only interested in duration of billing
# record that was active in June. This is effectively calculating the intersection of
# two time ranges.
# One complication comes from the fact billing window might have nil end point if it
# is still active. We check for that case with span.unbounded_end?.
duration_begin = [span.begin, begin_time].max
duration_end = span.unbounded_end? ? end_time : [span.end, end_time].min
(duration_end - duration_begin) / 60
end
def finalize
self.class.where(id: id).update(span: Sequel.lit("tstzrange(lower(span), now())"))
end
def billing_rate
@billing_rate ||= BillingRate.from_id(billing_rate_id)
end
end
# Table: billing_record
# Columns:
# id | uuid | PRIMARY KEY
# project_id | uuid | NOT NULL
# resource_id | uuid | NOT NULL
# resource_name | text | NOT NULL
# span | tstzrange | NOT NULL DEFAULT tstzrange(now(), NULL::timestamp with time zone, '[)'::text)
# amount | numeric | NOT NULL
# billing_rate_id | uuid | NOT NULL
# Indexes:
# billing_record_pkey | PRIMARY KEY btree (id)
# billing_record_project_id_index | btree (project_id)
# billing_record_resource_id_index | btree (resource_id)
# billing_record_span_index | gist (span)