From: ivan Date: Wed, 1 Jul 2009 10:28:49 +0000 (+0000) Subject: start of settlement CDR processing, RT#5495 X-Git-Tag: root_of_svc_elec_features~1071 X-Git-Url: http://git.freeside.biz/gitweb/?p=freeside.git;a=commitdiff_plain;h=a67fd3bbfeec137ebf494e36eaa920145b8509a1 start of settlement CDR processing, RT#5495 --- diff --git a/FS/FS/Schema.pm b/FS/FS/Schema.pm index 048fa0a4e..1ae37197f 100644 --- a/FS/FS/Schema.pm +++ b/FS/FS/Schema.pm @@ -694,6 +694,7 @@ sub tables_hashref { 'comments', 'text', 'NULL', '', '', '', 'spool_cdr','char', 'NULL', 1, '', '', 'squelch_cdr','char', 'NULL', 1, '', '', + 'cdr_termination_percentage', 'decimal', 'NULL', '', '', '', 'invoice_terms', 'varchar', 'NULL', $char_d, '', '', 'archived', 'char', 'NULL', 1, '', '', ], @@ -2144,6 +2145,32 @@ sub tables_hashref { 'index' => [ [ 'calldate' ], [ 'src' ], [ 'dst' ], [ 'charged_party' ], [ 'accountcode' ], [ 'freesidestatus' ], [ 'freesiderewritestatus' ], [ 'cdrbatch' ], ], }, + 'cdr_termination' => { + 'columns' => [ + 'cdrtermnum', 'bigserial', '', '', '', '', + 'acctid', 'bigint', '', '', '', '', + 'termpart', 'int', '', '', '', '',#future expansion, see below + 'rated_price', @money_typen, '', '', + 'status', 'varchar', 'NULL', 32, '', '', + ], + 'primary_key' => 'cdrtermnum', + 'unique' => [ [ 'acctid', 'termpart' ] ], + 'index' => [ [ 'acctid' ], [ 'status' ], ], + }, + + #to handle multiple termination/settlement passes... + # 'part_termination' => { + # 'columns' => [ + # 'termpart', 'int', '', '', '', '', + # 'termname', 'varchar', '', $char_d, '', '', + # 'cdr_column', 'varchar', '', $char_d, '', '', #maybe set it here instead of in the price plan? + # ], + # 'primary_key' => 'termpart', + # 'unique' => [], + # 'index' => [], + # }, + + #the remaining cdr_ tables are not really used 'cdr_calltype' => { 'columns' => [ 'calltypenum', 'serial', '', '', '', '', diff --git a/FS/FS/cdr_termination.pm b/FS/FS/cdr_termination.pm new file mode 100644 index 000000000..5fe8db6bf --- /dev/null +++ b/FS/FS/cdr_termination.pm @@ -0,0 +1,152 @@ +package FS::cdr_termination; + +use strict; +use base qw( FS::Record ); +use FS::Record qw( qsearch qsearchs ); + +=head1 NAME + +FS::cdr_termination - Object methods for cdr_termination records + +=head1 SYNOPSIS + + use FS::cdr_termination; + + $record = new FS::cdr_termination \%hash; + $record = new FS::cdr_termination { 'column' => 'value' }; + + $error = $record->insert; + + $error = $new_record->replace($old_record); + + $error = $record->delete; + + $error = $record->check; + +=head1 DESCRIPTION + +An FS::cdr_termination object represents an CDR termination status. +FS::cdr_termination inherits from FS::Record. The following fields are +currently supported: + +=over 4 + +=item cdrtermnum + +primary key + +=item acctid + +acctid + +=item termpart + +termpart + +=item rated_price + +rated_price + +=item status + +status + + +=back + +=head1 METHODS + +=over 4 + +=item new HASHREF + +Creates a new record. To add the record to the database, see L<"insert">. + +Note that this stores the hash reference, not a distinct copy of the hash it +points to. You can ask the object for a copy with the I method. + +=cut + +# the new method can be inherited from FS::Record, if a table method is defined + +sub table { 'cdr_termination'; } + +=item insert + +Adds this record to the database. If there is an error, returns the error, +otherwise returns false. + +=cut + +# the insert method can be inherited from FS::Record + +=item delete + +Delete this record from the database. + +=cut + +# the delete method can be inherited from FS::Record + +=item replace OLD_RECORD + +Replaces the OLD_RECORD with this one in the database. If there is an error, +returns the error, otherwise returns false. + +=cut + +# the replace method can be inherited from FS::Record + +=item check + +Checks all fields to make sure this is a valid record. If there is +an error, returns the error, otherwise returns false. Called by the insert +and replace methods. + +=cut + +# the check method should currently be supplied - FS::Record contains some +# data checking routines + +sub check { + my $self = shift; + + my $error = + $self->ut_numbern('cdrtermnum') + || $self->ut_foreign_key('acctid', 'cdr', 'acctid') + #|| $self->ut_foreign_key('termpart', 'part_termination', 'termpart') + || $self->ut_number('termpart') + || $self->ut_float('rated_price') + || $self->ut_enum('status', '', 'done' ) # , 'skipped' ) + ; + return $error if $error; + + $self->SUPER::check; +} + +=item set_status_and_rated_price STATUS [ RATED_PRICE ] + +Sets the status to the provided string. If there is an error, returns the +error, otherwise returns false. + +=cut + +sub set_status_and_rated_price { + my($self, $status, $rated_price) = @_; + $self->status($status); + $self->rated_price($rated_price); + $self->replace(); +} + +=back + +=head1 BUGS + +=head1 SEE ALSO + +L, L, schema.html from the base documentation. + +=cut + +1; + diff --git a/FS/FS/cust_main.pm b/FS/FS/cust_main.pm index 5768bebf9..1e4351931 100644 --- a/FS/FS/cust_main.pm +++ b/FS/FS/cust_main.pm @@ -1553,6 +1553,7 @@ sub check { || $self->ut_textn('stateid_state') || $self->ut_textn('invoice_terms') || $self->ut_alphan('geocode') + || $self->ut_floatn('cdr_termination_percentage') ; #barf. need message catalogs. i18n. etc. @@ -6739,11 +6740,38 @@ sub credit { } -=item charge AMOUNT [ PKG [ COMMENT [ TAXCLASS ] ] ] +=item charge HASHREF || AMOUNT [ PKG [ COMMENT [ TAXCLASS ] ] ] Creates a one-time charge for this customer. If there is an error, returns the error, otherwise returns false. +New-style, with a hashref of options: + + my $error = $cust_main->charge( + { + 'amount' => 54.32, + 'quantity' => 1, + 'start_date' => str2time('7/4/2009'), + 'pkg' => 'Description', + 'comment' => 'Comment', + 'additional' => [], #extra invoice detail + 'classnum' => 1, #pkg_class + + 'setuptax' => '', # or 'Y' for tax exempt + + #internal taxation + 'taxclass' => 'Tax class', + + #vendor taxation + 'taxproduct' => 2, #part_pkg_taxproduct + 'override' => {}, #XXX describe + } + ); + +Old-style: + + my $error = $cust_main->charge( 54.32, 'Description', 'Comment', 'Tax class' ); + =cut sub charge { diff --git a/FS/FS/part_pkg/cdr_termination.pm b/FS/FS/part_pkg/cdr_termination.pm new file mode 100644 index 000000000..c0d99b72a --- /dev/null +++ b/FS/FS/part_pkg/cdr_termination.pm @@ -0,0 +1,96 @@ +package FS::part_pkg::cdr_termination; + +use strict; +use base qw( FS::part_pkg::recur_Common ); +use vars qw( $DEBUG %info ); +use Tie::IxHash; + +tie my %temporalities, 'Tie::IxHash', + 'upcoming' => "Upcoming (future)", + 'preceding' => "Preceding (past)", +; + +%info = ( + 'name' => 'VoIP rating of CDR records for termination partners.', + 'shortname' => 'VoIP/telco CDR termination', + 'fields' => { + + 'setup_fee' => { 'name' => 'Setup fee for this package', + 'default' => 0, + }, + 'recur_fee' => { 'name' => 'Base recurring fee for this package', + 'default' => 0, + }, + + #false laziness w/flat.pm + 'recur_temporality' => { 'name' => 'Charge recurring fee for period', + 'type' => 'select', + 'select_options' => \%temporalities, + }, + + 'unused_credit' => { 'name' => 'Credit the customer for the unused portion'. + ' of service at cancellation', + 'type' => 'checkbox', + }, + + 'cutoff_day' => { 'name' => 'Billing Day (1 - 28) for prorating or '. + 'subscription', + 'default' => '1', + }, + + 'recur_method' => { 'name' => 'Recurring fee method', + #'type' => 'radio', + #'options' => \%recur_method, + 'type' => 'select', + 'select_options' => \%FS::part_pkg::recur_Common::recur_method, + }, + }, + + 'fieldorder' => [qw( + setup_fee recur_fee recur_temporality unused_credit + recur_method cutoff_day + ) + ], + + 'weight' => 48, + +); + +sub calc_setup { + my($self, $cust_pkg ) = @_; + $self->option('setup_fee'); +} + +sub calc_recur { + my $self = shift; + my($cust_pkg, $sdate, $details, $param ) = @_; + + #my $last_bill = $cust_pkg->last_bill; + my $last_bill = $cust_pkg->get('last_bill'); #->last_bill falls back to setup + + return 0 + if $self->option('recur_temporality', 1) eq 'preceding' + && ( $last_bill eq '' || $last_bill == 0 ); + + my $charges = 0; + + # termination calculations + + # find CDRs with cdr_termination.status NULL + # and matching our customer via svc_external.id/title? (and via what field?) + + #for each cdr, set status and rated price and add the charges, and add a line + #to the invoice + + # eotermiation calculation + + $charges += $self->calc_recur_Common(@_); + + $charges; +} + +sub is_free { + 0; +} + +1; diff --git a/FS/FS/part_pkg/recur_Common.pm b/FS/FS/part_pkg/recur_Common.pm new file mode 100644 index 000000000..71f3af718 --- /dev/null +++ b/FS/FS/part_pkg/recur_Common.pm @@ -0,0 +1,57 @@ +package FS::part_pkg::recur_Common; + +use strict; +use vars qw( @ISA %recur_method ); +use Tie::IxHash; +use Time::Local; +use FS::part_pkg::prorate; + +@ISA = qw(FS::part_pkg::prorate); + +tie %recur_method, 'Tie::IxHash', + 'anniversary' => 'Charge the recurring fee at the frequency specified above', + 'prorate' => 'Charge a prorated fee the first time (selectable billing date)', + 'subscription' => 'Charge the full fee for the first partial period (selectable billing date)', +; + +sub calc_recur_Common { + my $self = shift; + my($cust_pkg, $sdate, $details, $param ) = @_; #only need $sdate & $param + + my $charges = 0; + + if ( $param->{'increment_next_bill'} ) { + + my $recur_method = $self->option('recur_method', 1) || 'anniversary'; + + if ( $recur_method eq 'prorate' ) { + + $charges = $self->SUPER::calc_recur(@_); + + } else { + + $charges = $self->option('recur_fee'); + + if ( $recur_method eq 'subscription' ) { + + my $cutoff_day = $self->option('cutoff_day', 1) || 1; + my ($day, $mon, $year) = ( localtime($$sdate) )[ 3..5 ]; + + if ( $day < $cutoff_day ) { + if ( $mon == 0 ) { $mon=11; $year--; } + else { $mon--; } + } + + $$sdate = timelocal(0, 0, 0, $cutoff_day, $mon, $year); + + }#$recur_method eq 'subscription' + + }#$recur_method eq 'prorate' + + }#increment_next_bill + + $charges; + +} + +1; diff --git a/FS/MANIFEST b/FS/MANIFEST index 732e3de53..57cbc492c 100644 --- a/FS/MANIFEST +++ b/FS/MANIFEST @@ -449,3 +449,5 @@ FS/phone_device.pm t/phone_device.t FS/part_device.pm t/part_device.t +FS/cdr_termination.pm +t/cdr_termination.t diff --git a/FS/t/cdr_termination.t b/FS/t/cdr_termination.t new file mode 100644 index 000000000..7167bf288 --- /dev/null +++ b/FS/t/cdr_termination.t @@ -0,0 +1,5 @@ +BEGIN { $| = 1; print "1..1\n" } +END {print "not ok 1\n" unless $loaded;} +use FS::cdr_termination; +$loaded=1; +print "ok 1\n"; diff --git a/httemplate/edit/cust_main/billing.html b/httemplate/edit/cust_main/billing.html index f0d9b853a..4c4be23c0 100644 --- a/httemplate/edit/cust_main/billing.html +++ b/httemplate/edit/cust_main/billing.html @@ -427,18 +427,31 @@ spool_cdr eq "Y" ? 'CHECKED' : '' %>> Spool CDRs % } else { - -% } +% } % if ( $conf->exists('voip-cust_cdr_squelch') ) { squelch_cdr eq "Y" ? 'CHECKED' : '' %>> Omit CDRs from invoices % } else { - -% } +% } + + +% if ( $show_term || $cust_main->cdr_termination_percentage ) { + + CDR termination settlement + % + +% } else { + +% } @@ -464,4 +477,10 @@ my @payby = grep /\w/, $conf->config('payby'); @payby = (qw( CARD DCRD CHEK DCHK LECB BILL CASH COMP )) unless @payby; +#false laziness w/view/cust_main/billing.html +my $term_sql = "SELECT COUNT(*) FROM cust_pkg LEFT JOIN part_pkg USING ( pkgpart ) WHERE custnum = ? AND plan = 'cdr_termination' LIMIT 1"; +my $term_sth = dbh->prepare($term_sql) or die dbh->errstr; +$term_sth->execute($cust_main->custnum) or die $term_sth->errstr; +my $show_term = $term_sth->fetchrow_arrayref->[0]; + diff --git a/httemplate/view/cust_main/billing.html b/httemplate/view/cust_main/billing.html index e02c04868..049461dbd 100644 --- a/httemplate/view/cust_main/billing.html +++ b/httemplate/view/cust_main/billing.html @@ -216,6 +216,13 @@ Billing information % } +% if ( $show_term || $cust_main->cdr_termination_percentage ) { + + CDR termination settlement + <% $cust_main->cdr_termination_percentage %><% $cust_main->cdr_termination_percentage =~ /\d/ ? '%' : '' %> + +% } + <%once> @@ -230,4 +237,10 @@ my @invoicing_list = $cust_main->invoicing_list; my $conf = new FS::Conf; my $money_char = $conf->config('money_char') || '$'; +#false laziness w/edit/cust_main/billing.html +my $term_sql = "SELECT COUNT(*) FROM cust_pkg LEFT JOIN part_pkg USING ( pkgpart ) WHERE custnum = ? AND plan = 'cdr_termination' LIMIT 1"; +my $term_sth = dbh->prepare($term_sql) or die dbh->errstr; +$term_sth->execute($cust_main->custnum) or die $term_sth->errstr; +my $show_term = $term_sth->fetchrow_arrayref->[0]; +