diff options
author | Mark Wells <mark@freeside.biz> | 2013-01-12 12:08:42 -0800 |
---|---|---|
committer | Mark Wells <mark@freeside.biz> | 2013-01-12 12:08:42 -0800 |
commit | ceaa9e6ca4222595c1795c4bbde8d1c9609045e7 (patch) | |
tree | 2e4c2847dbd9f6a7ab20b6c825c1e20d3ccfb0ce /FS | |
parent | b70b0d8c6f571a68ffb60c5ca728a230926abee4 (diff) | |
parent | e722e522c695781f626adfdf36bf0d130698f665 (diff) |
Merge branch 'master' of git.freeside.biz:/home/git/freeside
Diffstat (limited to 'FS')
-rw-r--r-- | FS/FS/Schema.pm | 1 | ||||
-rw-r--r-- | FS/FS/cdr/gsm_tap3_12.pm | 427 | ||||
-rw-r--r-- | FS/FS/svc_phone.pm | 15 |
3 files changed, 439 insertions, 4 deletions
diff --git a/FS/FS/Schema.pm b/FS/FS/Schema.pm index 5ac2b5f0f..bde022026 100644 --- a/FS/FS/Schema.pm +++ b/FS/FS/Schema.pm @@ -3600,6 +3600,7 @@ sub tables_hashref { 'svcnum', 'int', '', '', '', '', 'countrycode', 'varchar', '', 3, '', '', 'phonenum', 'varchar', '', 15, '', '', #12 ? + 'sim_imsi', 'varchar', 'NULL', 15, '', '', 'pin', 'varchar', 'NULL', $char_d, '', '', 'sip_password', 'varchar', 'NULL', $char_d, '', '', 'phone_name', 'varchar', 'NULL', $char_d, '', '', diff --git a/FS/FS/cdr/gsm_tap3_12.pm b/FS/FS/cdr/gsm_tap3_12.pm index d1536c050..8f50690bd 100644 --- a/FS/FS/cdr/gsm_tap3_12.pm +++ b/FS/FS/cdr/gsm_tap3_12.pm @@ -4,7 +4,7 @@ use base qw( FS::cdr ); use strict; use vars qw( %info ); use Time::Local; -#use Data::Dumper; +use Data::Dumper; %info = ( 'name' => 'GSM TAP3 release 12', @@ -29,7 +29,7 @@ use Time::Local; 'src' => sub { shift->{mobileOriginatedCall}{basicCallInformation}{chargeableSubscriber}{simChargeableSubscriber}{msisdn} }, 'charged_party_imsi' => sub { shift->{mobileOriginatedCall}{basicCallInformation}{chargeableSubscriber}{simChargeableSubscriber}{imsi} }, 'dst' => sub { shift->{mobileOriginatedCall}{basicCallInformation}{destination}{calledNumber} }, #dialledDigits? - 'carrierid' => sub { shift->{mobileOriginatedCall}{locationInformation}{networkLocation}{recEntityCode} }, + 'carrierid' => sub { shift->{mobileOriginatedCall}{locationInformation}{networkLocation}{recEntityCode} }, #XXX translate to recEntityId via info in header 'userfield' => sub { shift->{mobileOriginatedCall}{operatorSpecInformation}[0] }, 'servicecode' => sub { shift->{mobileOriginatedCall}{basicServiceUsedList}[0]{basicService}{serviceCode}{teleServiceCode} }, 'upstream_price' => sub { sprintf('%.5f', shift->{mobileOriginatedCall}{basicServiceUsedList}[0]{chargeInformationList}[0]{chargeDetailList}[0]{charge} / 100000 ) }, #XXX numberOfDecimalPlaces in header @@ -40,6 +40,429 @@ use Time::Local; }, ); +#accepts qsearch parameters as a hash or list of name/value pairs, but not +#old-style qsearch('cdr', { field=>'value' }) + +use Date::Format; +sub tap3_12_export { + my %qsearch = (); + if ( ref($_[0]) eq 'HASH' ) { + %qsearch = %{ $_[0] }; + } else { + %qsearch = @_; + } + + #if these get huge we might need to get a count and do a paged search + my @cdrs = qsearch({ 'table'=>'cdr', %qsearch, 'order_by'=>'calldate ASC' }); + + eval "use Convert::ASN1"; + die $@ if $@; + + my $asn = Convert::ASN1->new; + $asn->prepare( _asn_spec() ) or die $asn->error; + + my $TransferBatch = $asn->find('TransferBatch') or die $asn->error; + + my %hash = _TransferBatch(); #static information etc. + + my $utcTimeOffset = '+0300'; #XXX local timezone at least + + my $now = time; + + ### + # accountingInfo + ### + + ### + # batchControlInfo + ### + + #optional + $hash{batchControlInfo}->{fileCreationTimeStamp} = { 'localTimeStamp' => time2str('%Y%m%d%H%M%S', $now), + 'utcTimeOffset' => $utcTimeOffset, + }; + #XXX what do these do? do they need to be different from fileCreationTimeStamp? + $hash{batchControlInfo}->{transferCutOffTimeStamp} = { 'localTimeStamp' => time2str('%Y%m%d%H%M%S', $now), + 'utcTimeOffset' => $utcTimeOffset, + }; + + $hash{batchControlInfo}->{fileAvailableTimeStamp} = { 'localTimeStamp' => time2str('%Y%m%d%H%M%S', $now), + 'utcTimeOffset' => $utcTimeOffset, + }; + + #XXX + $hash{batchControlInfo}->{sender} = 'MDGTM'; + $hash{batchControlInfo}->{recipient} = 'GNQHT'; + $hash{batchControlInfo}->{fileSequenceNumber} = '00178'; #XXX global? per recipient? + + ### + # networkInfo + ### + + $hash{networkInfo}->{utcTimeOffsetInfo}[0]{utcTimeOffset} = $utcTimeOffset; + + #XXX recording entity IDs, referenced by recEntityCode + #$hash->{networkInfo}->{recEntityInfo}[0]{recEntityId} = '340010100'; + #$hash->{networkInfo}->{recEntityInfo}[1]{recEntityId} = '240556000000'; + + ### + # auditControlInfo + ### + + #mandatory + $hash{auditControlInfo}->{callEventDetailsCount} = scalar(@cdrs); + + #these two are optional + $hash{auditControlInfo}->{earliestCallTimeStamp} = { 'localTimeStamp' => time2str('%Y%m%d%H%M%S', $cdrs[0]->calldate_unix), + 'utcTimeOffset' => $utcTimeOffset, + }; + $hash{auditControlInfo}->{latestCallTimeStamp} = { 'localTimeStamp' => time2str('%Y%m%d%H%M%S', $cdrs[-1]->calldate_unix), + 'utcTimeOffset' => $utcTimeOffset, + }; + + #mandatory + my $totalCharge = 0; + $totalCharge += $_->rated_price foreach @cdrs; + $hash{totalCharge} = sprintf('%.5f', $totalCharge); + + ### + # callEventDetails + ### + + #one of Mobile Originated Call, Mobile Terminated Call, Mobile Session, Messaging Event, Supplementary Service Event, Service Centre Usage, GPRS Call, Content Transaction or Location Service + # Each occurrence must have no more than one of these present + + $hash{callEventDetails} = [ + map { + { #either tele or bearer service usage originated by the mobile subscription (others?) + 'mobileOriginatedCall' => { + + #identifies the Network Location, which includes the MSC responsible for handling the call and, where appropriate, the Geographical Location of the mobile + 'locationInformation' => { + 'networkLocation' => { + 'recEntityCode' => $_->carrierid, #XXX Recording Entity (per 2.5, from "Reference Tables") + } + }, + + #Operator Specific Information: beyond the scope of TAP and has been bilaterally agreed + 'operatorSpecInformation' => [ + $_->userfield, ##'|Seq: 178 Loc: 1|' + ], + + #The type of service used together with all related charging information + 'basicServiceUsedList' => [ + { + #identifies the actual Basic Service used + 'basicService' => { + #one of Teleservice Code or Bearer Service Code as determined by the service type used + 'serviceCode' => { + #XXX + #00 All teleservices + #10 All Speech transmission services + #11 Telephony + #12 Emergency calls + #20 All SMS Services + #21 Short Message MT/PP + #22 Short Message MO/PP + #60 All Fax Services + #61 Facsimile Group 3 & alternative speech + #62 Automatic Facsimile Group 3 + #63 Automatic Facsimile Group 4 + #70 All data teleservices (compound) + #80 All teleservices except SMS (compound) + #90 All voice group call services + #91 Voice group call + #92 Voice broadcast call + 'teleServiceCode' => $_->servicecode, #'11' + + #Bearer Service Code + # Must be present within group Service Code where the type of service used + # was a bearer service. Must not be present when the type of service used + # was a tele service and, therefore, Teleservice Code is present. + # Group Bearer Codes, identifiable by the description ‘All’, should only + # be used where details of the specific services affected are not + # available from the network. + #00 All Bearer Services + #20 All Data Circuit Asynchronous Services + #21 Duplex Asynch. 300bps data circuit + #22 Duplex Asynch. 1200bps data circuit + #23 Duplex Asynch. 1200/75bps data circuit + #24 Duplex Asynch. 2400bps data circuit + #25 Duplex Asynch. 4800bps data circuit + #26 Duplex Asynch. 9600bps data circuit + #27 General Data Circuit Asynchronous Service + #30 All Data Circuit Synchronous Services + #32 Duplex Synch. 1200bps data circuit + #34 Duplex Synch. 2400bps data circuit + #35 Duplex Synch. 4800bps data circuit + #36 Duplex Synch. 9600bps data circuit + #37 General Data Circuit Synchronous Service + #40 All Dedicated PAD Access Services + #41 Duplex Asynch. 300bps PAD access + #42 Duplex Asynch. 1200bps PAD access + #43 Duplex Asynch. 1200/75bps PAD access + #44 Duplex Asynch. 2400bps PAD access + #45 Duplex Asynch. 4800bps PAD access + #46 Duplex Asynch. 9600bps PAD access + #47 General PAD Access Service + #50 All Dedicated Packet Access Services + #54 Duplex Synch. 2400bps PAD access + #55 Duplex Synch. 4800bps PAD access + #56 Duplex Synch. 9600bps PAD access + #57 General Packet Access Service + #60 All Alternat Speech/Asynchronous Services + #70 All Alternate Speech/Synchronous Services + #80 All Speech followed by Data Asynchronous Services + #90 All Speech followed by Data Synchronous Services + #A0 All Data Circuit Asynchronous Services (compound) + #B0 All Data Circuit Synchronous Services (compound) + #C0 All Asynchronous Services (compound) + } + #conditionally also contain the following for UMTS: Transparency Indicator, Fixed Network User + # Rate, User Protocol Indicator, Guaranteed Bit Rate and Maximum Bit Rate + }, + + #Charge information is provided for all chargeable elements except within Messaging Event and Mobile Session call events + # must contain Charged Item and at least one occurrence of Charge Detail + 'chargeInformationList' => [ + { + #XXX + #mandatory + # the charging principle applied and the unitisation of Chargeable Units. It + # is not intended to identify the service used. + #A: Call set up attempt + #C: Content + #D: Duration based charge + #E: Event based charge + #F: Fixed (one-off) charge + #L: Calendar (for example daily usage charge) + #V: Volume (outgoing) based charge + #W: Volume (incoming) based charge + #X: Volume (total volume) based charge + #(?? fields to be used as a basis for the calculation of the correct Charge + # A: Chargeable Units (if present) + # D,V,W,X: Chargeable Units + # C: Depends on the content + # E: Not Applicable + # F: Not Applicable + # L: Call Event Start Timestamp) + 'chargedItem' => 'D', + + # the IOT used by the VPMN to price the call + 'callTypeGroup' => { + + #The highest category call type in respect of the destination of the call + #0: Unknown/Not Applicable + #1: National + #2: International + #10: HGGSN/HP-GW + #11: VGGSN/VP-GW + #12: Other GGSN/Other P-GW + #100: WLAN + 'callTypeLevel1' => $_->calltypenum, + + #the sub category of Call Type Level 1 + #0: Unknown/Not Applicable + #1: Mobile + #2: PSTN + #3: Non Geographic + #4: Premium Rate + #5: Satellite destination + #6: Forwarded call + #7: Non forwarded call + #10: Broadband + #11: Narrowband + #12: Conversational + #13: Streaming + #14: Interactive + #15: Background + 'callTypeLevel2' => 0, + + #the sub category of Call Type Level 2 + 'callTypeLevel3' => 0, + }, + + #mandatory, at least one occurence must be present + #A repeating group detailing the Charge and/or charge element + # Note that, where a Charge has been levied, even where that Charge is zero, + # there must be one occurance, and only one, with a Charge Type of '00' + 'chargeDetailList' => [ + { + #mandatory + # after discounts have been deducted but before any tax is added + 'charge' => $_->rated_price * 100000, #XXX numberOfDecimalPlaces + + #mandatory + # the type of charge represented + #00: Total charge for Charge Information (the invoiceable value) + #01: Airtime charge + #02: reserved + #03: Toll charge + #04: Directory assistance + #05–20: reserved + #21: VPMN surcharge + #50: Total charge for Charge Information according to the published IOT + # Note that the use of value 50 is only for use by bilateral agreement, use without + # bilateral agreement can be treated as per reserved values, that is ‘out of range’ + #69–99: reserved + 'chargeType' => '00', + + #conditional + # the number of units which are chargeable within the Charge Detail, this may not + # correspond to the number of rounded units charged. + # The item Charged Item defines what the units represent. + 'chargeableUnits' => $_->quantity_able, + + #optional + # the rounded number of units which are actually charged for + 'chargedUnits' => $_->quantity, + } + ], + 'exchangeRateCode' => 1, #from header + } + ] + } + ], + + #MO Basic Call Information provides the basic detail of who made the call and where to in respect of mobile originated traffic. + 'basicCallInformation' => { + #mandatory + # the identification of the chargeable subscriber. + # The group must contain either the IMSI or the MIN of the Chargeable Subscriber, but not both. + 'chargeableSubscriber' => { + 'simChargeableSubscriber' => { + 'msisdn' => $_->charged_party, #src + 'imsi' => $_->charged_party_imsi, + } + }, + # the start of the call event + 'callEventStartTimeStamp' => { + 'localTimeStamp' => time2str('%Y%m%d%H%M%S', $_->startdate), + 'utcTimeOffsetCode' => 1 + }, + + # the actual total duration of a call event as a number of seconds + 'totalCallEventDuration' => $_->duration, + + #conditional + # the number dialled by the subscriber (Called Number) + # or the SMSC Address in case of SMS usage or in cases involving supplementary services + # such as call forwarding or transfer etc., the number to which the call is routed + 'destination' => { + #the international representation of the destination + 'calledNumber' => $_->dst, + + #the actual digits as dialled by the subscriber, i.e. unmodified, in establishing a call + # This will contain ‘+’ and ‘#’ where appropriate. + #'dialledDigits' => '322221350' + }, + } + } + }; + } + @cdrs + ]; + + + ### + + + my $pdu = $TransferBatch->encode( \%hash ); + + return $pdu; + +} + +sub _TransferBatch { + 'accountingInfo' => { + #mandatory + 'localCurrency' => 'USD', + 'currencyConversionInfo' => [ + { + 'numberOfDecimalPlaces' => 5, + 'exchangeRate' => 152549, #??? + 'exchangeRateCode' => 1 + } + ], + 'tapDecimalPlaces' => 5, + #optional: may conditionally include taxation and discounting tables, and, optionally, TAP currency + }, + 'batchControlInfo' => { + #mandatory + 'specificationVersionNumber' => 3, + 'releaseVersionNumber' => 12, #11? + + #'sender' => 'MDGTM', + #'recipient' => 'GNQHT', + #'fileSequenceNumber' => '00178', + + #'transferCutOffTimeStamp' => { + # 'localTimeStamp' => '20121230050222', + # 'utcTimeOffset' => '+0300' + # }, + #'fileAvailableTimeStamp' => { + # 'localTimeStamp' => '20121230035052', + # 'utcTimeOffset' => '+0100' + # } + + #optional + #'fileCreationTimeStamp' => { + # 'localTimeStamp' => '20121230050222', + # 'utcTimeOffset' => '+0300' + # }, + + #optional: file type indicator which will only be present where the file represents test data + #optional: RAP File Sequence Number (used where the batch has previously been returned with a fatal error and is now being resubmitted) (not fileSequenceNumber?) + + #optional: beyond the scope of TAP and has been bilaterally agreed + 'operatorSpecInformation' => [ + '', # XXX '|File proc MTH LUXMA: 1285348027|' Operator Specific Information + ], + + + }, + + #Network Information is a group of related information which pertains to the Sender PMN + 'networkInfo' => { + #must be present where Recording Entity Codes are present within the TAP file + 'recEntityInfo' => [ + { + 'recEntityType' => 1, #MSC + #'recEntityId' => '340010100', + 'recEntityCode' => 1 + }, + { + 'recEntityType' => 2, #SMSC + #'recEntityId' => '240556000000', + 'recEntityCode' => 2 + }, + ], + #mandatory + 'utcTimeOffsetInfo' => [ + { + 'utcTimeOffset' => '+0300', + 'utcTimeOffsetCode' => 1 + } + ] + }, + 'auditControlInfo' => { + #'callEventDetailsCount' => 4, #mandatory + 'totalTaxValue' => 0, #mandatory + 'totalDiscountValue' => 0, #mandatory + #'totalCharge' => 50474, #mandatory + + #these two are optional + #'earliestCallTimeStamp' => { + # 'localTimeStamp' => '20121229102501', + # 'utcTimeOffset' => '+0300' + # }, + #'latestCallTimeStamp' => { + # 'localTimeStamp' => '20121229102807', + # 'utcTimeOffset' => '+0300' + # } + }, +} + sub _asn_spec { <<'END'; -- diff --git a/FS/FS/svc_phone.pm b/FS/FS/svc_phone.pm index 1296c1e85..bf610c62b 100644 --- a/FS/FS/svc_phone.pm +++ b/FS/FS/svc_phone.pm @@ -23,10 +23,11 @@ $DEBUG = 0; @pw_set = ( 'a'..'k', 'm','n', 'p-z', 'A'..'N', 'P'..'Z' , '2'..'9' ); #ask FS::UID to run this stuff for us later -$FS::UID::callback{'FS::svc_acct'} = sub { +FS::UID->install_callback( sub { $conf = new FS::Conf; $phone_name_max = $conf->config('svc_phone-phone_name-max_length'); -}; +} +); =head1 NAME @@ -68,6 +69,10 @@ primary key =item phonenum +=item sim_imsi + +SIM IMSI (http://en.wikipedia.org/wiki/International_mobile_subscriber_identity) + =item sip_password =item pin @@ -147,6 +152,7 @@ sub table_info { disable_select => 1, }, 'phonenum' => 'Phone number', + 'sim_imsi' => 'IMSI', #http://en.wikipedia.org/wiki/International_mobile_subscriber_identity 'pin' => { label => 'Voicemail PIN', #'Personal Identification Number', type => 'text', disable_inventory => 1, @@ -466,6 +472,7 @@ sub check { $self->ut_numbern('svcnum') || $self->ut_numbern('countrycode') || $self->$phonenum_check_method('phonenum') + || $self->ut_numbern('sim_imsi') || $self->ut_anything('sip_password') || $self->ut_numbern('pin') || $self->ut_textn('phone_name') @@ -486,6 +493,10 @@ sub check { ; return $error if $error; + return 'Illegal IMSI (not 14-15 digits)' #shorter? + if length($self->sim_imsi) + && ( length($self->sim_imsi) < 14 || length($self->sim_imsi) > 15 ); + # LNP data validation return 'Cannot set LNP fields: no LNP in progress' if ( ($self->lnp_desired_due_date || $self->lnp_due_date |