diff --git a/lib/NGCP/Panel/Controller/API/CustomerBalances.pm b/lib/NGCP/Panel/Controller/API/CustomerBalances.pm index 4bddeff582..4d28dee5ca 100644 --- a/lib/NGCP/Panel/Controller/API/CustomerBalances.pm +++ b/lib/NGCP/Panel/Controller/API/CustomerBalances.pm @@ -21,7 +21,7 @@ sub allowed_methods{ } sub api_description { - return 'Defines customer balances to access cash and free time balance.'; + return 'Defines customer balances to access cash and free-time balance.'; }; sub query_params { diff --git a/lib/NGCP/Panel/Controller/API/CustomerBalancesItem.pm b/lib/NGCP/Panel/Controller/API/CustomerBalancesItem.pm index f7ff77f178..4adb4f5a17 100644 --- a/lib/NGCP/Panel/Controller/API/CustomerBalancesItem.pm +++ b/lib/NGCP/Panel/Controller/API/CustomerBalancesItem.pm @@ -62,7 +62,7 @@ sub auto :Private { $self->set_body($c); $self->log_request($c); - #$self->apply_fake_time($c); + #$self->apply_fake_time($c); } sub GET :Allow { @@ -119,7 +119,7 @@ sub PATCH :Allow { last unless $preference; my $json = $self->get_valid_patch_data( - c => $c, + c => $c, id => $id, media_type => 'application/json-patch+json', ); @@ -129,16 +129,18 @@ sub PATCH :Allow { my $item = $self->item_by_id($c, $id, $now); last unless $self->resource_exists($c, customerbalance => $item); my $old_resource = { $item->get_inflated_columns }; + $old_resource->{cash_balance} /= 100.0 if defined $old_resource->{cash_balance}; my $resource = $self->apply_patch($c, $old_resource, $json); last unless $resource; my $form = $self->get_form($c); $item = $self->update_item($c, $item, $old_resource, $resource, $form, $now); last unless $item; - + my $hal = $self->hal_from_item($c, $item, $form); - last unless $self->add_update_journal_item_hal($c,$hal); - + + last unless $self->add_update_journal_item_hal($c, $hal); + $guard->commit; if ('minimal' eq $preference) { @@ -176,6 +178,7 @@ sub PUT :Allow { ); last unless $resource; my $old_resource = { $item->get_inflated_columns }; + $old_resource->{cash_balance} /= 100.0 if defined $old_resource->{cash_balance}; my $form = $self->get_form($c); $item = $self->update_item($c, $item, $old_resource, $resource, $form, $now); @@ -183,8 +186,8 @@ sub PUT :Allow { my $hal = $self->hal_from_item($c, $item, $form); last unless $self->add_update_journal_item_hal($c,$hal); - - $guard->commit; + + $guard->commit; if ('minimal' eq $preference) { $c->response->status(HTTP_NO_CONTENT); diff --git a/lib/NGCP/Panel/Controller/Customer.pm b/lib/NGCP/Panel/Controller/Customer.pm index dc9aaef83e..3dcb37eb77 100644 --- a/lib/NGCP/Panel/Controller/Customer.pm +++ b/lib/NGCP/Panel/Controller/Customer.pm @@ -1020,6 +1020,8 @@ sub edit_balance :Chained('base_restricted') :PathPart('balance/edit') :Args(0) back_uri => $c->req->uri, ); if($posted && $form->validated) { + my $entities = { contract => $contract, }; + my $log_vals = {}; try { my $schema = $c->model('DB'); $schema->set_transaction_isolation('READ COMMITTED'); @@ -1027,11 +1029,21 @@ sub edit_balance :Chained('base_restricted') :PathPart('balance/edit') :Args(0) $balance = NGCP::Panel::Utils::ProfilePackages::get_contract_balance(c => $c, contract => $contract, now => $now); - $balance = NGCP::Panel::Utils::ProfilePackages::underrun_update_balance(c => $c, - balance =>$balance, + $balance = NGCP::Panel::Utils::ProfilePackages::set_contract_balance( + c => $c, + balance => $balance, + cash_balance => $form->values->{cash_balance}, + free_time_balance => $form->values->{free_time_balance}, now => $now, - new_cash_balance => $form->values->{cash_balance} ); - $balance->update($form->values); + log_vals => $log_vals); + + my $topup_log = NGCP::Panel::Utils::ProfilePackages::create_topup_log_record( + c => $c, + now => $now, + entities => $entities, + log_vals => $log_vals, + request_token => NGCP::Panel::Utils::ProfilePackages::PANEL_TOPUP_REQUEST_TOKEN, + ); }); NGCP::Panel::Utils::Message::info( c => $c, diff --git a/lib/NGCP/Panel/Form/Balance/BalanceIntervalAPI.pm b/lib/NGCP/Panel/Form/Balance/BalanceIntervalAPI.pm index 8a8987d278..c5eaec9727 100644 --- a/lib/NGCP/Panel/Form/Balance/BalanceIntervalAPI.pm +++ b/lib/NGCP/Panel/Form/Balance/BalanceIntervalAPI.pm @@ -91,6 +91,18 @@ has_field 'cash_balance' => ( }, ); +has_field 'initial_cash_balance' => ( + type => 'Money', + #label => 'Cash Balance', + #required => 1, + #inflate_method => sub { return $_[1] * 100 }, + #deflate_method => sub { return $_[1] / 100 }, + element_attr => { + rel => ['tooltip'], + title => ['The interval\'s initial cash balance of the contract in EUR/USD/etc.'] + }, +); + has_field 'cash_debit' => ( type => 'Money', #label => 'Cash Balance', @@ -113,6 +125,16 @@ has_field 'free_time_balance' => ( }, ); +has_field 'initial_free_time_balance' => ( + type => 'Integer', + #label => 'Free-Time Balance', + #required => 1, + element_attr => { + rel => ['tooltip'], + title => ['The interval\'s initial free-time balance of the contract in seconds.'] + }, +); + has_field 'free_time_spent' => ( type => 'Integer', #label => 'Free-Time Balance', diff --git a/lib/NGCP/Panel/Form/Balance/CustomerBalanceAPI.pm b/lib/NGCP/Panel/Form/Balance/CustomerBalanceAPI.pm index 57ba150890..014ef9937a 100644 --- a/lib/NGCP/Panel/Form/Balance/CustomerBalanceAPI.pm +++ b/lib/NGCP/Panel/Form/Balance/CustomerBalanceAPI.pm @@ -4,6 +4,16 @@ use HTML::FormHandler::Moose; extends 'NGCP::Panel::Form::Balance::CustomerBalance'; use Moose::Util::TypeConstraints; +has_field 'cash_balance' => ( + type => 'Money', + label => 'Cash Balance', + required => 1, + element_attr => { + rel => ['tooltip'], + title => ['The current cash balance of the customer in EUR/USD/etc.'] + }, +); + has_field 'cash_debit' => ( type => 'Money', #label => 'Cash Balance (Interval)', diff --git a/lib/NGCP/Panel/Form/Topup/Log.pm b/lib/NGCP/Panel/Form/Topup/Log.pm index 77d89ad7e4..d546023b02 100644 --- a/lib/NGCP/Panel/Form/Topup/Log.pm +++ b/lib/NGCP/Panel/Form/Topup/Log.pm @@ -25,6 +25,7 @@ has_field 'type' => ( options => [ { value => 'cash', label => 'Cash top-up' }, { value => 'voucher', label => 'Voucher top-up' }, + { value => 'set_balance', label => 'Balance edited' }, ], required => 1, ); diff --git a/lib/NGCP/Panel/Role/API/BalanceIntervals.pm b/lib/NGCP/Panel/Role/API/BalanceIntervals.pm index 75339062d4..3c26da5ecc 100644 --- a/lib/NGCP/Panel/Role/API/BalanceIntervals.pm +++ b/lib/NGCP/Panel/Role/API/BalanceIntervals.pm @@ -15,29 +15,29 @@ use NGCP::Panel::Utils::ProfilePackages qw(); use NGCP::Panel::Utils::DateTime; sub _contract_rs { - + my ($self, $c, $include_terminated,$now) = @_; - + my $item_rs = NGCP::Panel::Utils::Contract::get_contract_rs( schema => $c->model('DB'), include_terminated => (defined $include_terminated && $include_terminated ? 1 : 0), now => $now, - ); + ); if($c->user->roles eq "admin") { } elsif($c->user->roles eq "reseller") { - $item_rs = $item_rs->search({ + $item_rs = $item_rs->search({ 'contact.reseller_id' => $c->user->reseller_id },{ join => 'contact', }); } - return $item_rs; - + return $item_rs; + #my $item_rs = $c->model('DB')->resultset('contract_balances'); #if($c->user->roles eq "admin") { #} elsif($c->user->roles eq "reseller") { - # $item_rs = $item_rs->search({ + # $item_rs = $item_rs->search({ # 'contact.reseller_id' => $c->user->reseller_id # },{ # join => { contract => 'contact' }, @@ -47,10 +47,10 @@ sub _contract_rs { } sub _item_rs { - + my $self = shift; return $self->_contract_rs(@_); - + } sub get_form { @@ -60,7 +60,7 @@ sub get_form { sub hal_from_balance { my ($self, $c, $item, $form, $now, $use_root_collection_link) = @_; - + my $contract = $item->contract; my $is_customer = (defined $contract->contact->reseller_id ? 1 : 0); my $bm_start = NGCP::Panel::Utils::ProfilePackages::get_actual_billing_mapping(c => $c, contract => $contract, now => $item->start); @@ -75,30 +75,31 @@ sub hal_from_balance { $notopup_expiration = NGCP::Panel::Utils::ProfilePackages::get_notopup_expiration( package => $contract->profile_package, contract => $contract, - balance => $item) if $is_actual; + balance => $item) if $is_actual; #my $invoice = $item->invoice; - + my %resource = $item->get_inflated_columns; $resource{cash_balance} /= 100.0; + $resource{initial_cash_balance} /= 100.0; $resource{cash_debit} = (delete $resource{cash_balance_interval}) / 100.0; $resource{free_time_spent} = delete $resource{free_time_balance_interval}; my $datetime_fmt = DateTime::Format::Strptime->new( - pattern => '%F %T', + pattern => '%F %T', ); $resource{start} = delete $resource{start}; $resource{stop} = delete $resource{end}; $resource{start} = $datetime_fmt->format_datetime($resource{start}) if defined $resource{start}; $resource{stop} = $datetime_fmt->format_datetime($resource{stop}) if defined $resource{stop}; - + $resource{billing_profile_id} = $profile_at_start->id; $resource{timely_topup_start} = (defined $timely_start ? $datetime_fmt->format_datetime($timely_start) : undef); $resource{timely_topup_stop} = (defined $timely_end ? $datetime_fmt->format_datetime($timely_end) : undef); - + $resource{notopup_discard_expiry} = (defined $notopup_expiration ? $datetime_fmt->format_datetime($notopup_expiration) : undef); - + $resource{is_actual} = $is_actual; - + my $hal = NGCP::Panel::Utils::DataHal->new( links => [ NGCP::Panel::Utils::DataHalLink->new( @@ -143,14 +144,14 @@ sub contract_by_id { sub balances_rs { my ($self, $c, $contract, $now) = @_; - + $now //= NGCP::Panel::Utils::DateTime::current_local; NGCP::Panel::Utils::ProfilePackages::catchup_contract_balances(c => $c, contract => $contract, now => $now); - + return $self->apply_query_params($c,$self->can('query_params') ? $self->query_params : {},$contract->contract_balances); - + } sub balance_by_id { @@ -160,12 +161,12 @@ sub balance_by_id { my $balance = NGCP::Panel::Utils::ProfilePackages::catchup_contract_balances(c => $c, contract => $contract, now => $now); - + if (defined $id) { $balance = $contract->contract_balances->find($id); } return $balance; - + } 1; diff --git a/lib/NGCP/Panel/Role/API/CustomerBalances.pm b/lib/NGCP/Panel/Role/API/CustomerBalances.pm index 7653093608..392ce0d6ec 100644 --- a/lib/NGCP/Panel/Role/API/CustomerBalances.pm +++ b/lib/NGCP/Panel/Role/API/CustomerBalances.pm @@ -102,6 +102,14 @@ sub item_by_id { sub update_item { my ($self, $c, $item, $old_resource, $resource, $form, $now) = @_; + # remove any readonly field before validation: + my %ro_fields = map { $_ => 1; } keys %$resource; + $ro_fields{cash_balance} = 0; + $ro_fields{free_time_balance} = 0; + foreach my $field (keys %$resource) { + delete $resource->{$field} if $ro_fields{$field}; + } + $form //= $self->get_form($c); return unless $self->validate_form( c => $c, @@ -109,17 +117,24 @@ sub update_item { resource => $resource, ); - $item = NGCP::Panel::Utils::ProfilePackages::underrun_update_balance(c => $c, + my $entities = { contract => $item->contract, }; + my $log_vals = {}; + $item = NGCP::Panel::Utils::ProfilePackages::set_contract_balance( + c => $c, balance => $item, + cash_balance => $resource->{cash_balance} * 100.0, + free_time_balance => $resource->{free_time_balance}, now => $now, - new_cash_balance => $resource->{cash_balance} * 100.0); + log_vals => $log_vals); + + my $topup_log = NGCP::Panel::Utils::ProfilePackages::create_topup_log_record( + c => $c, + now => $now, + entities => $entities, + log_vals => $log_vals, + request_token => NGCP::Panel::Utils::ProfilePackages::API_DEFAULT_TOPUP_REQUEST_TOKEN, + ); - $resource->{cash_balance} *= 100.0; - # ignoring cash_debit and free_time_spent: - $item->update({ - cash_balance => $resource->{cash_balance}, - free_time_balance => $resource->{free_time_balance}, - }); $item->discard_changes; return $item; diff --git a/lib/NGCP/Panel/Utils/Billing.pm b/lib/NGCP/Panel/Utils/Billing.pm index 79a6bd3076..952c583690 100644 --- a/lib/NGCP/Panel/Utils/Billing.pm +++ b/lib/NGCP/Panel/Utils/Billing.pm @@ -251,7 +251,7 @@ sub insert_unique_billing_fees{ my($c,$schema,$profile,$fees,$return_created) = @params{qw/c schema profile fees return_created/}; $return_created //= 0; - #while we use lower id we don't need insert records from billing_fees, they are already contain in billing_fees with lower id + #while we use lower id we don't need insert records from billing_fees, they are already contain in billing_fees with lower id $profile->billing_fees_raw->delete(); $schema->storage->dbh_do(sub{ diff --git a/lib/NGCP/Panel/Utils/ProfilePackages.pm b/lib/NGCP/Panel/Utils/ProfilePackages.pm index a58168432c..45659b5a27 100644 --- a/lib/NGCP/Panel/Utils/ProfilePackages.pm +++ b/lib/NGCP/Panel/Utils/ProfilePackages.pm @@ -44,6 +44,7 @@ use constant _ENABLE_UNDERRUN_PROFILES => 1; use constant _ENABLE_UNDERRUN_LOCK => 1; use constant PANEL_TOPUP_REQUEST_TOKEN => 'panel'; +use constant API_DEFAULT_TOPUP_REQUEST_TOKEN => 'api'; sub get_contract_balance { my %params = @_; @@ -382,11 +383,56 @@ PREPARE_BALANCE_CATCHUP: } +sub set_contract_balance { + my %params = @_; + my($c,$balance,$cash_balance,$free_time_balance,$now,$schema,$log_vals) = @params{qw/c balance cash_balance free_time_balance now schema log_vals/}; + + $schema //= $c->model('DB'); + my $contract; + $contract = $balance->contract if $log_vals; + $now //= NGCP::Panel::Utils::DateTime::current_local; + + $cash_balance //= $balance->cash_balance; + $free_time_balance //= $balance->free_time_balance; + + $c->log->debug('set contract ' . $contract->id . ' cash_balance from ' . $balance->cash_balance . ' to ' . $cash_balance . ', free_time_balance from ' . $balance->free_time_balance . ' to ' . $free_time_balance); + + if ($log_vals) { + my $package = $contract->profile_package; + $log_vals->{old_package} = ( $package ? { $package->get_inflated_columns } : undef); + $log_vals->{new_package} = $log_vals->{old_package}; + $log_vals->{old_balance} = { $balance->get_inflated_columns }; + my $bm_actual = get_actual_billing_mapping(schema => $schema, contract => $contract, now => $now); + my $profile = $bm_actual->billing_mappings->first->billing_profile; + $log_vals->{old_profile} = { $profile->get_inflated_columns }; + $log_vals->{amount} = $cash_balance - $balance->cash_balance; + } + + $balance = _underrun_update_balance(c => $c, + balance =>$balance, + now => $now, + new_cash_balance => $cash_balance ); + + $balance->update({ + cash_balance => $cash_balance, + free_time_balance => $free_time_balance, + }); + $contract->discard_changes(); + if ($log_vals) { + $log_vals->{new_balance} = { $balance->get_inflated_columns }; + my $bm_actual = get_actual_billing_mapping(schema => $schema, contract => $contract, now => $now); + my $profile = $bm_actual->billing_mappings->first->billing_profile; + $log_vals->{new_profile} = { $profile->get_inflated_columns }; + } + + return $balance; +} + sub topup_contract_balance { my %params = @_; my($c,$contract,$package,$voucher,$amount,$now,$request_token,$schema,$log_vals,$subscriber) = @params{qw/c contract package voucher amount now request_token schema log_vals subscriber/}; - $schema = $c->model('DB'); + $schema //= $c->model('DB'); $contract = lock_contracts(schema => $schema, contract_id => $contract->id); $now //= NGCP::Panel::Utils::DateTime::current_local; @@ -508,8 +554,8 @@ sub create_topup_log_record { return $c->model('DB')->resultset('topup_logs')->create({ username => $username, timestamp => $now->hires_epoch, - type => ($is_cash ? 'cash' : 'voucher'), - outcome => ($is_success ? 'ok' : 'failed'), + type => (defined $is_cash ? ($is_cash ? 'cash' : 'voucher') : 'set_balance'), + outcome => ((not defined $is_cash or $is_success) ? 'ok' : 'failed'), message => (defined $message ? substr($message,0,255) : undef), subscriber_id => ($entities->{subscriber} ? $entities->{subscriber}->id : $resource->{subscriber_id}), contract_id => ($entities->{contract} ? $entities->{contract}->id : $resource->{contract_id}), @@ -696,8 +742,10 @@ sub _get_balance_values { $free_time_balance_interval = 0; return [cash_balance => sprintf("%.4f",$cash_balance), + initial_cash_balance => sprintf("%.4f",$cash_balance), cash_balance_interval => sprintf("%.4f",$cash_balance_interval), free_time_balance => sprintf("%.0f",$free_time_balance), + initial_free_time_balance => sprintf("%.0f",$free_time_balance), free_time_balance_interval => sprintf("%.0f",$free_time_balance_interval)]; } @@ -964,7 +1012,7 @@ sub underrun_lock_subscriber { } } -sub underrun_update_balance { +sub _underrun_update_balance { my %params = @_; my ($c,$balance,$new_cash_balance,$now,$schema) = @params{qw/c balance new_cash_balance now schema/}; $schema = $c->model('DB'); @@ -1394,12 +1442,19 @@ sub get_balanceinterval_datatable_cols { #convert_code => sub { my $s = shift; return $s if ($parser_date->parse_datetime($s) or $parser_datetime->parse_datetime($s)); } }, { name => "end", search => 0, search_upper_column => 'interval', title => $c->loc('To'), }, #convert_code => sub { my $s = shift; return $s if ($parser_date->parse_datetime($s) or $parser_datetime->parse_datetime($s)); } }, - { name => "balance", search => 0, title => $c->loc('Cash'), literal_sql => "FORMAT(cash_balance / 100,2)" }, + + { name => "initial_balance", search => 0, title => $c->loc('Initial Cash'), literal_sql => "FORMAT(initial_cash_balance / 100,2)" }, + { name => "balance", search => 0, title => $c->loc('Cash Balance'), literal_sql => "FORMAT(cash_balance / 100,2)" }, { name => "debit", search => 0, title => $c->loc('Debit'), literal_sql => "FORMAT(cash_balance_interval / 100,2)" }, - { name => "topup_count", search => 0, title => $c->loc('#Top-ups') }, - { name => "timely_topup_count", search => 0, title => $c->loc('#Timely Top-ups') }, - { name => "underrun_profiles", search => 0, title => $c->loc('Underrun detected (Profiles)') }, - { name => "underrun_lock", search => 0, title => $c->loc('Underrun detected (Lock)') }, + + { name => "initial_free_time_balance", search => 0, title => $c->loc('Initial Free-Time') }, + { name => "free_time_balance", search => 0, title => $c->loc('Free-Time Balance') }, + { name => "free_time_interval", search => 0, title => $c->loc('Free-Time spent') }, + + { name => "topups", search => 0, title => $c->loc('#Top-ups (timely)'), literal_sql => 'CONCAT(topup_count," (",timely_topup_count,")")' }, + + { name => "underrun_profiles", search => 0, title => $c->loc('Last Underrun (Profiles)') }, + { name => "underrun_lock", search => 0, title => $c->loc('Last Underrun (Lock)') }, ); } diff --git a/lib/NGCP/Panel/Utils/Voucher.pm b/lib/NGCP/Panel/Utils/Voucher.pm index cfa162e5f3..15b311e3fe 100644 --- a/lib/NGCP/Panel/Utils/Voucher.pm +++ b/lib/NGCP/Panel/Utils/Voucher.pm @@ -45,10 +45,10 @@ sub decrypt_code { sub check_topup { my %params = @_; my ($c,$plain_code,$voucher_id,$now,$subscriber_id,$contract,$package_id,$schema,$err_code,$entities,$resource) = @params{qw/c plain_code voucher_id now subscriber_id contract package_id schema err_code entities resource/}; - + $schema //= $c->model('DB'); $now //= NGCP::Panel::Utils::DateTime::current_local; - + if (!defined $err_code || ref $err_code ne 'CODE') { $err_code = sub { return 0; }; } @@ -71,7 +71,7 @@ sub check_topup { $entities->{subscriber} = $subscriber if defined $entities; $contract //= $subscriber->contract; } - + $entities->{contract} = $contract if defined $entities; unless($contract->status eq 'active') { @@ -80,17 +80,17 @@ sub check_topup { unless($contract->contact->reseller) { return 0 unless &{$err_code}('Contract is not a customer contract.'); } - + # if reseller, check if subscriber_id belongs to the calling reseller if($reseller_id && $reseller_id != $contract->contact->reseller_id) { return 0 unless &{$err_code}('Subscriber customer contract belongs to another reseller.'); } - + if (defined $plain_code || defined $voucher_id) { my $voucher; my $dtf = $schema->storage->datetime_parser; - + if (defined $plain_code) { $voucher = $schema->resultset('vouchers')->search_rs({ code => encrypt_code($c, $plain_code), @@ -120,21 +120,20 @@ sub check_topup { if (defined $resource) { $resource->{voucher_id} = undef if exists $resource->{voucher_id}; $resource->{voucher}->{id} = undef if (exists $resource->{voucher} && exists $resource->{voucher}->{id}); - } + } return 0 unless &{$err_code}("Invalid voucher ID $voucher_id, already used or expired."); - } + } } - + $entities->{voucher} = $voucher if defined $entities; if($voucher->customer_id && $contract->id != $voucher->customer_id) { return 0 unless &{$err_code}('Voucher is reserved for a different customer.'); - } + } unless($voucher->reseller_id == $contract->contact->reseller_id) { return 0 unless &{$err_code}('Voucher belongs to another reseller.'); } - - $entities->{voucher} = $voucher if defined $entities; + } else { my $package = undef; if (defined $package_id) { @@ -143,7 +142,7 @@ sub check_topup { if (defined $resource) { $resource->{package_id} = undef if exists $resource->{package_id}; $resource->{package}->{id} = undef if (exists $resource->{package} && exists $resource->{package}->{id}); - } + } return 0 unless &{$err_code}("Unknown profile package ID $package_id."); } $entities->{package} = $package if defined $entities; @@ -152,15 +151,15 @@ sub check_topup { } } } - + # TODO: add and check billing.vouchers.active flag for internal/emergency use - + return 1; - + } sub get_datatable_cols { - + my ($c,$hide_package) = @_; return ( { name => "id", "search" => 1, "title" => $c->loc("#") }, diff --git a/share/templates/customer/details.tt b/share/templates/customer/details.tt index 8316197457..1b3164bcc3 100644 --- a/share/templates/customer/details.tt +++ b/share/templates/customer/details.tt @@ -446,7 +446,7 @@ $(function() {
- | + | @@ -454,17 +454,21 @@ $(function() { [% money_format = format('%.2f') %] | |||||||
---|---|---|---|---|---|---|---|---|---|
[% c.loc('Cash balance') %] | -[% money_format(balance.cash_balance / 100) %] | -[% c.loc('Debit') %] | -[% money_format( balance.cash_balance_interval / 100 ) %] | +[% c.loc('Initial Cash Balance:') %] | +[% money_format(balance.initial_cash_balance / 100) %] | +[% c.loc('Cash Balance:') %] | +[% money_format(balance.cash_balance / 100) %] | +[% c.loc('Debit:') %] | +[% money_format( balance.cash_balance_interval / 100 ) %] |
[% c.loc('Free time balance') %] | -[% balance.free_time_balance %] | -[% c.loc('Free time spent') %] | -[% balance.free_time_balance_interval %] | +[% c.loc('Initial Free-Time Balance:') %] | +[% balance.initial_free_time_balance %] | +[% c.loc('Free-Time Balance:') %] | +[% balance.free_time_balance %] | +[% c.loc('Free-Time spent:') %] | +[% balance.free_time_balance_interval %] |