From 250498ebe128a66b35181e48fce7d0e174941933 Mon Sep 17 00:00:00 2001 From: Irina Peshinskaya Date: Fri, 7 Aug 2015 02:17:12 +0300 Subject: [PATCH] MT#8457 Billing profiles duplication. Used working but extremely ugly SQL::Abstract variant For review. Change-Id: Id0e2ec6ab5fbbe8cbd791673d1dd8f2efd9e03ba --- lib/NGCP/Panel/Controller/Billing.pm | 113 +++++++++++++++++--------- lib/NGCP/Panel/Utils/Billing.pm | 109 +++++++++++++++++++++++-- share/templates/billing/list.tt | 2 + share/templates/helpers/datatables.tt | 2 + share/templates/helpers/modal.tt | 2 +- 5 files changed, 179 insertions(+), 49 deletions(-) diff --git a/lib/NGCP/Panel/Controller/Billing.pm b/lib/NGCP/Panel/Controller/Billing.pm index fd0f40bb28..7acf95018c 100644 --- a/lib/NGCP/Panel/Controller/Billing.pm +++ b/lib/NGCP/Panel/Controller/Billing.pm @@ -73,10 +73,10 @@ sub root :Chained('profile_list') :PathPart('') :Args(0) { sub ajax :Chained('profile_list') :PathPart('ajax') :Args(0) { my ($self, $c) = @_; - + my $resultset = $c->stash->{profiles_rs}; NGCP::Panel::Utils::Datatables::process($c, $resultset, $c->stash->{profile_dt_columns}); - + $c->detach( $c->view("JSON") ); } @@ -108,7 +108,7 @@ sub base :Chained('profile_list') :PathPart('') :CaptureArgs(1) { { name => 'zone', search => 1, title => $c->loc('Zone') }, { name => 'detail', search => 1, title => $c->loc('Zone Details') }, ]); - + my $res = $c->stash->{profiles_rs}->find($profile_id); unless(defined($res)) { NGCP::Panel::Utils::Message->error( @@ -124,13 +124,20 @@ sub base :Chained('profile_list') :PathPart('') :CaptureArgs(1) { } sub edit :Chained('base') :PathPart('edit') { - my ($self, $c) = @_; + my ($self, $c ) = @_; + $c->forward('process_edit', [0] ); +} +sub process_edit :Private { + my ($self, $c, $duplicate) = @_; my $posted = ($c->request->method eq 'POST'); my $form; my $params = $c->stash->{profile}; $params->{reseller}{id} = delete $params->{reseller_id}; $params = $params->merge($c->session->{created_objects}); + if( $duplicate ) { + NGCP::Panel::Utils::Billing::get_billing_profile_uniq_params( params => $params ); + } if($c->user->is_superuser) { $form = NGCP::Panel::Form::BillingProfile::Admin->new; } else { @@ -152,7 +159,7 @@ sub edit :Chained('base') :PathPart('edit') { if($posted && $form->validated) { try { if($c->user->is_superuser) { - $form->values->{reseller_id} = $form->values->{reseller}{id}; + $form->values->{reseller_id} = $form->values->{reseller}{id}; } else { $form->values->{reseller_id} = $c->user->reseller_id; } @@ -162,14 +169,14 @@ sub edit :Chained('base') :PathPart('edit') { my $schema = $c->model('DB'); $schema->txn_do(sub { - + unless($c->stash->{profile_result}->get_column('contract_cnt') == 0) { die('Cannnot modify billing profile that is still used in profile mappings'); } unless($c->stash->{profile_result}->get_column('package_cnt') == 0) { die('Cannnot modify billing profile that is still used in profile packages'); - } - + } + $c->stash->{profile_result}->update($form->values); # if prepaid flag changed, update all subscribers for customers @@ -214,7 +221,7 @@ sub edit :Chained('base') :PathPart('edit') { } } } - + }); delete $c->session->{created_objects}->{reseller}; @@ -231,20 +238,33 @@ sub edit :Chained('base') :PathPart('edit') { } NGCP::Panel::Utils::Navigation::back_or($c, $c->uri_for('/billing')); } - - $c->stash(edit_flag => 1); - $c->stash(form => $form); + $c->stash( 'duplicate_flag' => 1 ) if $duplicate; + $c->stash( 'edit_flag' => 1 ); + $c->stash( 'form' => $form ); } sub create :Chained('profile_list') :PathPart('create') :Args(0) { my ($self, $c, $no_reseller) = @_; + $c->forward('process_create', [$no_reseller, 0 ]); +} +sub duplicate :Chained('base') :PathPart('duplicate') { + my ($self, $c, $no_reseller) = @_; + my $posted = ($c->request->method eq 'POST'); + if(!$posted){ + $c->forward('process_edit', [1] ); + }else{ + $c->forward('process_create', [ $no_reseller, 1 ] ); + } +} +sub process_create :Private { + my ($self, $c, $no_reseller, $duplicate ) = @_; + my $schema = $c->model('DB'); my $posted = ($c->request->method eq 'POST'); my $form; my $params = {}; $params->{reseller}{id} = delete $params->{reseller_id}; $params = $params->merge($c->session->{created_objects}); - # no_reseller - sounds as "!reseller", and thus !$no_reseller => !(!reseller) => reseller, but old code meant axacly admin view for this case, so, we can suppose that no_reseller could be number of reseller if($c->user->is_superuser && !$no_reseller) { $form = NGCP::Panel::Form::BillingProfile::Admin->new; } else { @@ -268,13 +288,28 @@ sub create :Chained('profile_list') :PathPart('create') :Args(0) { if($c->user->is_superuser && $no_reseller) { $form->values->{reseller_id} = $c->user->reseller_id; } elsif($c->user->is_superuser) { - $form->values->{reseller_id} = $form->values->{reseller}{id}; + $form->values->{reseller_id} = $form->values->{reseller}{id}; } else { $form->values->{reseller_id} = $c->user->reseller_id; } $form->values->{create_timestamp} = $form->values->{modify_timestamp} = NGCP::Panel::Utils::DateTime::current_local; delete $form->values->{reseller}; + delete $form->values->{id} if $duplicate; my $profile = $c->model('DB')->resultset('billing_profiles')->create($form->values); + + if( $duplicate ) { + + NGCP::Panel::Utils::Billing::clone_billing_profile_tackles( + c => $c, + profile_old => $c->stash->{'profile_result'}, + profile_new => $profile, + #profile_new => $c->stash->{profiles_rs}->find( + # $c->session->{created_objects}->{billing_profile}->{id}, + #), + schema => $schema, + ); + } + $c->session->{created_objects}->{billing_profile} = { id => $profile->id }; delete $c->session->{created_objects}->{reseller}; NGCP::Panel::Utils::Message->info( @@ -298,7 +333,7 @@ sub create :Chained('profile_list') :PathPart('create') :Args(0) { sub create_without_reseller :Chained('profile_list') :PathPart('create/noreseller') :Args(0) { my ($self, $c) = @_; - $self->create($c, 1); + $self->create($c, 1); } sub terminate :Chained('base') :PathPart('terminate') :Args(0) { @@ -312,7 +347,7 @@ sub terminate :Chained('base') :PathPart('terminate') :Args(0) { ); NGCP::Panel::Utils::Navigation::back_or($c, $c->uri_for('/billing')); } - + try { #todo: putting the profile fetch into a transaction wouldn't help since the count columns a prone to phantom reads... unless($profile->get_column('contract_cnt') == 0) { @@ -320,7 +355,7 @@ sub terminate :Chained('base') :PathPart('terminate') :Args(0) { } unless($profile->get_column('package_cnt') == 0) { die('Cannnot terminate billing profile that is still used in profile packages'); - } + } $profile->update({ status => 'terminated', terminate_timestamp => NGCP::Panel::Utils::DateTime::current_local, @@ -372,7 +407,7 @@ sub fees_base :Chained('fees_list') :PathPart('') :CaptureArgs(1) { $c->response->redirect($c->uri_for($c->stash->{profile}->{id}, 'fees')); return; } - + my $res = $c->stash->{'profile_result'}->billing_fees ->search(undef, {join => 'billing_zone',}) ->find($fee_id); @@ -437,7 +472,7 @@ sub fees_create :Chained('fees_list') :PathPart('create') :Args(0) { sub fees_upload :Chained('fees_list') :PathPart('upload') :Args(0) { my ($self, $c) = @_; - + my $form = NGCP::Panel::Form::BillingFeeUpload->new; my $upload = $c->req->upload('upload_fees'); my $posted = $c->req->method eq 'POST'; @@ -467,14 +502,14 @@ sub fees_upload :Chained('fees_list') :PathPart('upload') :Args(0) { try { my $schema = $c->model('DB'); $schema->txn_do(sub { - ( $fees, $fails, $text_success ) = NGCP::Panel::Utils::Billing::process_billing_fees( - c => $c, - data => \$data, + ( $fees, $fails, $text_success ) = NGCP::Panel::Utils::Billing::process_billing_fees( + c => $c, + data => \$data, profile => $c->stash->{'profile_result'}, schema => $schema, ); }); - + NGCP::Panel::Utils::Message->info( c => $c, desc => $$text_success, @@ -498,8 +533,8 @@ sub fees_upload :Chained('fees_list') :PathPart('upload') :Args(0) { sub fees_download :Chained('fees_list') :PathPart('download') :Args(0) { my ($self, $c) = @_; my $schema = $c->model('DB'); - my $data = NGCP::Panel::Utils::Billing::combine_billing_fees( - c => $c, + my $data = NGCP::Panel::Utils::Billing::combine_billing_fees( + c => $c, profile => $c->stash->{'profile_result'}, schema => $schema, ); @@ -511,7 +546,7 @@ sub fees_download :Chained('fees_list') :PathPart('download') :Args(0) { sub fees_edit :Chained('fees_base') :PathPart('edit') :Args(0) { my ($self, $c) = @_; - + my $profile_id = $c->stash->{profile}->{id}; my $posted = ($c->request->method eq 'POST'); my $params = $c->stash->{fee}; @@ -543,7 +578,7 @@ sub fees_edit :Chained('fees_base') :PathPart('edit') :Args(0) { $c->response->redirect($c->uri_for($c->stash->{profile}->{id}, 'fees')); return; } - + $c->stash(edit_fee_flag => 1); $c->stash(form => $form); } @@ -571,7 +606,7 @@ sub fees_delete :Chained('fees_base') :PathPart('delete') :Args(0) { sub zones_list :Chained('base') :PathPart('zones') :CaptureArgs(0) { my ($self, $c) = @_; - + $c->stash( zones_root_uri => $c->uri_for_action('/billing/zones', [$c->req->captures->[0]]) ); @@ -588,7 +623,7 @@ sub zones_ajax :Chained('zones_list') :PathPart('ajax') :Args(0) { sub zones_create :Chained('zones_list') :PathPart('create') :Args(0) { my ($self, $c) = @_; - + my $form = NGCP::Panel::Form::BillingZone->new; my $posted = ($c->request->method eq 'POST'); $form->process( @@ -629,7 +664,7 @@ sub zones :Chained('zones_list') :PathPart('') :Args(0) { sub zones_base :Chained('zones_list') :PathPart('') :CaptureArgs(1) { my ($self, $c, $zone_id) = @_; - + unless($zone_id && $zone_id->is_integer) { $zone_id //= ''; NGCP::Panel::Utils::Message->error( @@ -640,7 +675,7 @@ sub zones_base :Chained('zones_list') :PathPart('') :CaptureArgs(1) { ); NGCP::Panel::Utils::Navigation::back_or($c, $c->stash->{zones_root_uri}); } - + my $res = $c->stash->{'profile_result'}->billing_zones ->find($zone_id); unless(defined($res)) { @@ -657,7 +692,7 @@ sub zones_base :Chained('zones_list') :PathPart('') :CaptureArgs(1) { sub zones_delete :Chained('zones_base') :PathPart('delete') :Args(0) { my ($self, $c) = @_; - + my $zone_info = { $c->stash->{zone_result}->get_inflated_columns }; try { $c->stash->{zone_result}->delete; @@ -682,7 +717,7 @@ sub peaktimes_list :Chained('base') :PathPart('peaktimes') :CaptureArgs(0) { $c->stash(peaktimes_root_uri => $c->uri_for_action('/billing/peaktimes', [$c->req->captures->[0]]) ); - + my $rs = $c->stash->{profile_result}->billing_peaktime_weekdays; $rs = $rs->search(undef, {order_by => 'start'}); @@ -720,7 +755,7 @@ sub peaktime_weekdays_base :Chained('peaktimes_list') :PathPart('weekday') :Capt sub peaktime_weekdays_edit :Chained('peaktime_weekdays_base') :PathPart('edit') :Args(0) { my ($self, $c) = @_; - + my $form = NGCP::Panel::Form::BillingPeaktimeWeekdays->new; $form->process( posted => ($c->request->method eq 'POST'), @@ -793,7 +828,7 @@ sub load_weekdays { [$c->req->captures->[0], $_]), }; } - + foreach my $range ($c->stash->{weekdays_result}->all) { push @{ $weekdays[$range->weekday]->{ranges} }, { start => $range->start, @@ -801,7 +836,7 @@ sub load_weekdays { id => $range->id, } } - + $c->stash(weekdays => \@weekdays); } @@ -815,7 +850,7 @@ sub peaktime_specials_ajax :Chained('peaktimes_list') :PathPart('ajax') :Args(0) sub peaktime_specials_base :Chained('peaktimes_list') :PathPart('date') :CaptureArgs(1) { my ($self, $c, $special_id) = @_; - + unless($special_id && $special_id->is_integer) { $special_id //= ''; NGCP::Panel::Utils::Message->error( @@ -825,7 +860,7 @@ sub peaktime_specials_base :Chained('peaktimes_list') :PathPart('date') :Capture $c->response->redirect($c->stash->{peaktimes_root_uri}); return; } - + my $res = $c->stash->{'profile_result'}->billing_peaktime_specials ->find($special_id); unless(defined($res)) { @@ -909,7 +944,7 @@ sub peaktime_specials_delete :Chained('peaktime_specials_base') :PathPart('delet sub peaktime_specials_create :Chained('peaktimes_list') :PathPart('date/create') :Args(0) { my ($self, $c) = @_; $self->load_weekdays($c); - + my $posted = ($c->request->method eq 'POST'); my $form = NGCP::Panel::Form::BillingPeaktimeSpecial->new; my $params = {}; diff --git a/lib/NGCP/Panel/Utils/Billing.pm b/lib/NGCP/Panel/Utils/Billing.pm index ac1a889295..8a909b70aa 100644 --- a/lib/NGCP/Panel/Utils/Billing.pm +++ b/lib/NGCP/Panel/Utils/Billing.pm @@ -4,6 +4,8 @@ use warnings; use Text::CSV_XS; use IO::String; +use NGCP::Schema; + sub process_billing_fees{ my(%params) = @_; @@ -46,25 +48,25 @@ sub process_billing_fees{ delete $row->{zone_detail}; push @fees, $row; } - + $profile->billing_fees_raw->populate(\@fees); - $schema->storage->dbh_do(sub{ + $schema->storage->dbh_do(sub{ my ($storage, $dbh) = @_; $dbh->do("call billing.fill_billing_fees(?)", undef, $profile->id ); }); - + my $text = $c->loc('Billing Fee successfully uploaded'); if(@fails) { $text .= $c->loc(", but skipped the following line numbers: ") . (join ", ", @fails); } - + return ( \@fees, \@fails, \$text ); } sub combine_billing_fees{ my(%params) = @_; my($c,$profile,$schema) = @params{qw/c profile schema/}; - + my $csv = Text::CSV_XS->new({ allow_whitespace => 1, binary => 1, keep_meta_info => 1 }); my @cols = @{ $c->config->{fees_csv}->{element_order} }; $csv->column_names(@cols); @@ -89,8 +91,97 @@ sub combine_billing_fees{ } return $io->string_ref; } +sub get_billing_profile_uniq_params{ + my(%params) = @_; + my($params) = $params{params}; + my $uniq = '_dup_'.time(); + my $uniq_length = length($uniq); + my %uniq_columns = ('handle' => 63, 'name' => 31); + while(my($column,$limit) = each %uniq_columns){ + $params->{$column}= substr( $params->{$column}, 0, $limit - $uniq_length ).$uniq ; + } + return $params; +} +sub clone_billing_profile_tackles{ + my(%params) = @_; + my($c, $profile_old, $profile_new, $schema) = @params{qw/c profile_old profile_new schema/}; + $schema //= $c->model('DB'); + + my %struct_info = ( + 'billing_zones' => 'billing_zones', + 'billing_peaktime_weekdays' => 'billing_peaktime_weekdays', + 'billing_peaktime_special' => 'billing_peaktime_specials' + ); + while (my ($table_name,$rel_name) = each %struct_info ){ + my $source = NGCP::Schema->source($table_name); + my @columns = grep { !/^billing_profile_id$|^id$/i } $source->columns; + my $resultset = $profile_old->$rel_name->search_rs(undef,{ + 'select' => [@columns,{ '' => \[ $profile_new->id], -as => 'billing_profile_id' } ], + 'as' => [@columns,'billing_profile_id'], + }); + $resultset->result_class('DBIx::Class::ResultClass::HashRefInflator'); + my @records = $resultset->all; + $profile_new->$rel_name->populate(\@records) if @records; + + #insert into billing_peaktime_special(billing_profile_id,end,start) select ?,end,start from billing_peaktime_special where billing_profile_id=?, undef, $profile_new->id, $profile_old->id + + #insert into billing_peaktime_weekdays(billing_profile_id,end,start,weekday) select ?,end,start,weekday from billing_peaktime_weekdays where billing_profile_id=?, undef, $profile_new->id, $profile_old->id + + #insert into billing_zones(billing_profile_id,zone,detail) select ?,zone,detail from billing_zones where billing_profile_id=?, undef, $profile_new->id, $profile_old->id + } + + #insert into billing_fees(billing_profile_id,billing_zone_id,source,destination,direction,type,onpeak_init_rate,onpeak_init_interval,onpeak_follow_rate,onpeak_follow_interval,offpeak_init_rate,offpeak_init_interval,offpeak_follow_rate,offpeak_follow_interval,use_free_time) select ?,bz_new.billing_zone_id,source,destination,direction,type,onpeak_init_rate,onpeak_init_interval,onpeak_follow_rate,onpeak_follow_interval,offpeak_init_rate,offpeak_init_interval,offpeak_follow_rate,offpeak_follow_interval,use_free_time + #from billing_fees + #inner join billing_zones bz_old on billing_fees.billing_zone_id=bz_old.billing_zone_id + #inner join billing_zones bz_new on bz_old.zone=bz_new.zone and bz_old.detail=bz_new.detail and bz_new.billing_profile_id=? where billing_fees.billing_profile_id=?, undef, $profile_new->id, $profile_new->id, $profile_old->id + + my $source = NGCP::Schema->source('billing_fees'); + my @columns = grep { !/^billing_profile_id$|^id$|^billing_zone_id$/i } $source->columns; + my $fees_rs = $profile_old->billing_fees->search_rs( + undef, + { + 'select' => [ + @columns, + { '' =>\['bz_new.id'], -as => 'billing_zone_id' }, + { '' => \[ $profile_new->id], -as => 'billing_profile_id' } + ], + 'as' => [ @columns,'billing_zone_id','billing_profile_id' ], + alias => 'me', + from => [ + { 'me' => 'billing.billing_fees' }, + [ + #!Attention: -join-type DOESN'T WORK here!!! But in optimistic case, when all billing_zones created successfully - inner, which is default, is sufficient. + { 'bz_old' => 'billing.billing_zones', '-join-type' => 'inner' }, + [ + { 'me.billing_zone_id' => 'bz_old.id' }, + ], + ], + [ + #!Attention: -join-type DOESN'T WORK here!!! But in optimistic case, when all billing_zones created successfully - inner, which is default, is sufficient. + { 'bz_new' => 'billing.billing_zones', '-join-type' => 'inner' }, + [ + { + '-and' => [ + { + 'bz_new.zone' => { -ident => 'bz_old.zone'} , + 'bz_new.detail' => { -ident => 'bz_old.detail'} , + 'bz_new.billing_profile_id' => $profile_new->id + }, + ], + }, + ], + ], + ], + } + ); + + $fees_rs->result_class('DBIx::Class::ResultClass::HashRefInflator'); + my @records = $fees_rs->all; + $profile_new->billing_fees->populate(\@records) if @records; +} + sub get_contract_count_stmt { return "select count(distinct c.id) from `billing`.`billing_mappings` bm join `billing`.`contracts` c on c.id = bm.contract_id where bm.`billing_profile_id` = `me`.`id` and c.status != 'terminated' and (bm.end_date is null or bm.end_date >= now())"; } @@ -99,14 +190,14 @@ sub get_package_count_stmt { } sub get_datatable_cols { - + my ($c) = @_; return ( { name => "contract_cnt", "search" => 0, "title" => $c->loc("Used (contracts)"), }, - { name => "package_cnt", "search" => 0, "title" => $c->loc("Used (packages)"), }, - + { name => "package_cnt", "search" => 0, "title" => $c->loc("Used (packages)"), }, + ); - + } 1; diff --git a/share/templates/billing/list.tt b/share/templates/billing/list.tt index 6f95e17529..5ec39cd5c8 100644 --- a/share/templates/billing/list.tt +++ b/share/templates/billing/list.tt @@ -9,6 +9,7 @@ helper.close_target = close_target; helper.create_flag = create_flag; helper.edit_flag = edit_flag; + helper.duplicate_flag = duplicate_flag; helper.form_object = form; helper.ajax_uri = c.uri_for( c.controller.action_for('ajax') ); @@ -16,6 +17,7 @@ helper.dt_buttons = [ { name = c.loc('Terminate'), uri = "/billing/'+full[\"id\"]+'/terminate", class = 'btn-small btn-secondary', icon = 'icon-remove', condition => 'full.contract_cnt == "0" && full.package_cnt == "0"' }, { name = c.loc('Edit'), uri = "/billing/'+full[\"id\"]+'/edit", class = 'btn-small btn-primary', icon = 'icon-edit' }, + { name = c.loc('Duplicate'), uri = "/billing/'+full[\"id\"]+'/duplicate", class = 'btn-small btn-primary', icon = 'icon-edit' }, { name = c.loc('Fees'), uri = "/billing/'+full[\"id\"]+'/fees", class = 'btn-small btn-tertiary', icon = 'icon-shopping-cart' }, { name = c.loc('Off-Peaktimes'), uri = "/billing/'+full[\"id\"]+'/peaktimes", class = 'btn-small btn-tertiary', icon = 'icon-time' }, ]; diff --git a/share/templates/helpers/datatables.tt b/share/templates/helpers/datatables.tt index 45f9ea1e29..26e73e92c8 100644 --- a/share/templates/helpers/datatables.tt +++ b/share/templates/helpers/datatables.tt @@ -273,6 +273,8 @@ $(document).ready(function() { END; PROCESS "helpers/modal.tt"; modal_header(m.create_flag=helper.create_flag, + m.duplicate_flag=helper.duplicate_flag, + m.edit_flag=helper.edit_flag, m.name = helper.name); helper.form_object = translate_form( helper.form_object ); helper.form_object.render; diff --git a/share/templates/helpers/modal.tt b/share/templates/helpers/modal.tt index b3efa376e3..6d3c591ac3 100644 --- a/share/templates/helpers/modal.tt +++ b/share/templates/helpers/modal.tt @@ -3,7 +3,7 @@