X-Git-Url: http://git.freeside.biz/gitweb/?a=blobdiff_plain;f=FS%2FFS%2Fcdr%2Fgsm_tap3_12.pm;h=275e7b35cf0d3b94cf245d3b680aee25a48251b3;hb=4b05b20576ddb14577d59c87c8257c6804449410;hp=4d0917cd6f60c5f1988c2ea66520d914af247a57;hpb=73954e6f0f1f31cb33f231bc381ab93ddabd8c02;p=freeside.git diff --git a/FS/FS/cdr/gsm_tap3_12.pm b/FS/FS/cdr/gsm_tap3_12.pm index 4d0917cd6..275e7b35c 100644 --- a/FS/FS/cdr/gsm_tap3_12.pm +++ b/FS/FS/cdr/gsm_tap3_12.pm @@ -2,9 +2,39 @@ package FS::cdr::gsm_tap3_12; use base qw( FS::cdr ); use strict; -use vars qw( %info ); +use vars qw( %info %TZ ); use Time::Local; -use Data::Dumper; +#use Data::Dumper; + +#false laziness w/huawei_softx3000.pm +%TZ = ( + '+0000' => 'XXX-0', + '+0100' => 'XXX-1', + '+0200' => 'XXX-2', + '+0300' => 'XXX-3', + '+0400' => 'XXX-4', + '+0500' => 'XXX-5', + '+0600' => 'XXX-6', + '+0700' => 'XXX-7', + '+0800' => 'XXX-8', + '+0900' => 'XXX-9', + '+1000' => 'XXX-10', + '+1100' => 'XXX-11', + '+1200' => 'XXX-12', + '-0000' => 'XXX+0', + '-0100' => 'XXX+1', + '-0200' => 'XXX+2', + '-0300' => 'XXX+3', + '-0400' => 'XXX+4', + '-0500' => 'XXX+5', + '-0600' => 'XXX+6', + '-0700' => 'XXX+7', + '-0800' => 'XXX+8', + '-0900' => 'XXX+9', + '-1000' => 'XXX+10', + '-1100' => 'XXX+11', + '-1200' => 'XXX+12', +); %info = ( 'name' => 'GSM TAP3 release 12', @@ -14,29 +44,60 @@ use Data::Dumper; 'asn_format' => { 'spec' => _asn_spec(), 'macro' => 'TransferBatch', #XXX & skip the Notification ones? + 'header_buffer' => sub { + my $TransferBatch = shift; + + my $networkInfo = $TransferBatch->{networkInfo}; + + my $recEntityInfo = $networkInfo->{recEntityInfo}; + my %recEntity = map { $_->{recEntityCode} => $_->{recEntityId} } @$recEntityInfo; + + my $utcTimeOffsetInfo = $networkInfo->{utcTimeOffsetInfo}; + my %utcTimeOffset = map { $_->{utcTimeOffsetCode} => $_->{utcTimeOffset} } @$utcTimeOffsetInfo; + + { recEntity => \%recEntity, + utcTimeOffset => \%utcTimeOffset, + tapDecimalPlaces => $TransferBatch->{accountingInfo}{tapDecimalPlaces}, + }; + }, 'arrayref' => sub { shift->{'callEventDetails'}; }, 'map' => { - 'startdate' => sub { my $callinfo = shift->{mobileOriginatedCall}{basicCallInformation}; - my $timestamp = $callinfo->{callEventStartTimeStamp}; - my $localTimeStamp = $timestamp->{localTimeStamp}; - my $utcTimeOffsetCode = $timestamp->{utcTimeOffsetCode}; #XXX not handled, utcTimeOffsetInfo in header - $localTimeStamp =~ /^(\d{4})(\d{2})(\d{2})(\d{2})(\d{2})(\d{2})$/ or die "unparsable timestamp: $localTimeStamp\n"; #. Dumper($callinfo); - my($year, $mon, $day, $hour, $min, $sec) = ($1, $2, $3, $4, $5, $6); - timelocal($sec, $min, $hour, $day, $mon-1, $year); - }, - 'duration' => sub { shift->{mobileOriginatedCall}{basicCallInformation}{totalCallEventDuration} }, - 'billsec' => sub { shift->{mobileOriginatedCall}{basicCallInformation}{totalCallEventDuration} }, #same.. - '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} }, - '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 - 'calltypenum' => sub { shift->{mobileOriginatedCall}{basicServiceUsedList}[0]{chargeInformationList}[0]{callTypeGroup}{callTypelevel1} }, - 'quantity' => sub { shift->{mobileOriginatedCall}{basicServiceUsedList}[0]{chargeInformationList}[0]{chargedUnits} }, - 'quantity_able' => sub { shift->{mobileOriginatedCall}{basicServiceUsedList}[0]{chargeInformationList}[0]{chargeableUnits} }, - }, + 'startdate' => sub { my($row, $buffer) = @_; + my $callinfo = $row->{mobileOriginatedCall}{basicCallInformation}; + my $timestamp = $callinfo->{callEventStartTimeStamp}; + + my $localTimeStamp = $timestamp->{localTimeStamp}; + $localTimeStamp =~ /^(\d{4})(\d{2})(\d{2})(\d{2})(\d{2})(\d{2})$/ + or die "unparsable timestamp: $localTimeStamp\n"; #. Dumper($callinfo); + my($year, $mon, $day, $hour, $min, $sec) = ($1, $2, $3, $4, $5, $6); + + my $utcTimeOffsetCode = $timestamp->{utcTimeOffsetCode}; + my $utcTimeOffset = $buffer->{utcTimeOffset}{ $utcTimeOffsetCode }; + local($ENV{TZ}) = $TZ{ $utcTimeOffset }; + + timelocal($sec, $min, $hour, $day, $mon-1, $year); + }, + 'duration' => sub { shift->{mobileOriginatedCall}{basicCallInformation}{totalCallEventDuration} }, + 'billsec' => sub { shift->{mobileOriginatedCall}{basicCallInformation}{totalCallEventDuration} }, #same.. + '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 { my( $row, $buffer ) = @_; + my $recEntityCode = $row->{mobileOriginatedCall}{locationInformation}{networkLocation}{recEntityCode}; + $buffer->{recEntity}{ $recEntityCode }; + }, + 'userfield' => sub { shift->{mobileOriginatedCall}{operatorSpecInformation}[0] }, + 'servicecode' => sub { shift->{mobileOriginatedCall}{basicServiceUsedList}[0]{basicService}{serviceCode}{teleServiceCode} }, + 'upstream_price' => sub { my($row, $buffer) = @_; + sprintf('%.'.$buffer->{tapDecimalPlaces}.'f', + $row->{mobileOriginatedCall}{basicServiceUsedList}[0]{chargeInformationList}[0]{chargeDetailList}[0]{charge} + / ( 10 ** $buffer->{tapDecimalPlaces} ) + ) + }, + 'calltypenum' => sub { shift->{mobileOriginatedCall}{basicServiceUsedList}[0]{chargeInformationList}[0]{callTypeGroup}{callTypelevel1} }, + 'quantity' => sub { shift->{mobileOriginatedCall}{basicServiceUsedList}[0]{chargeInformationList}[0]{chargedUnits} }, + 'quantity_able' => sub { shift->{mobileOriginatedCall}{basicServiceUsedList}[0]{chargeInformationList}[0]{chargeableUnits} }, + }, }, ); @@ -44,6 +105,7 @@ use Data::Dumper; #old-style qsearch('cdr', { field=>'value' }) use Date::Format; +use FS::Conf; sub tap3_12_export { my %qsearch = (); if ( ref($_[0]) eq 'HASH' ) { @@ -55,6 +117,8 @@ sub tap3_12_export { #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' }); + my $conf = new FS::Conf; + eval "use Convert::ASN1"; die $@ if $@; @@ -65,34 +129,54 @@ sub tap3_12_export { my %hash = _TransferBatch(); #static information etc. - my $utcTimeOffset = '+0300'; #XXX local timezone at least - my $now = time; + my $utcTimeOffset = time2str('%z', $now); ### # accountingInfo ### + #mandatory + $hash{localCurrency} = $conf->config('currency') || 'USD'; + ### # 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), + + #The timestamp used to select calls for transfer. All call records available prior to the timestamp are transferred. + # This gives an indication to the HPMN as to how ‘up-to-date’ the information is. + $hash{batchControlInfo}->{transferCutOffTimeStamp} = { 'localTimeStamp' => time2str('%Y%m%d%H%M%S', $cdrs[-1]->calldate_unix ), 'utcTimeOffset' => $utcTimeOffset, }; + #The date and time at which the file was made available to the Recipient PMN. + # Physically this will normally be the timestamp when the file transfer + # commenced to the Recipient PMN, i.e. start of push, however on some systems + # this will be the timestamp when the file was made available to be pulled. $hash{batchControlInfo}->{fileAvailableTimeStamp} = { 'localTimeStamp' => time2str('%Y%m%d%H%M%S', $now), 'utcTimeOffset' => $utcTimeOffset, }; - #XXX - $hash{batchControlInfo}->{sender} = 'MDGTM'; + # A unique identifier used to determine the network which is the Sender of the data. + # The full list of codes in use is given in TADIG PRD TD.13: PMN Naming Conventions. + $hash{batchControlInfo}->{sender} = $conf->config('cdr-gsm_tap3-sender') || 'ZZZZZ'; #reserved: Y*, ZO-ZZ + + #XXX customer or agent field of some sort + # A unique identifier used to determine which network the data is being sent to, + # i.e. the Recipient. + # Derivation: GSM Association PRD TD.13: PMN Naming Conventions. $hash{batchControlInfo}->{recipient} = 'GNQHT'; - $hash{batchControlInfo}->{fileSequenceNumber} = '00178'; #XXX global? per recipient? + + #XXX + #A unique reference which identifies each TAP Data Interchange sent by one PMN to another, specific, PMN. + # The sequence commences at 1 and is incremented by one for each subsequent TAP Data Interchange sent by the Sender PMN to a particular Recipient PMN. + # Separate sequence numbering must be used for Test Data and Chargeable Data. Having reached the maximum value (99999) the number must recycle to 1. + $hash{batchControlInfo}->{fileSequenceNumber} = '00178'; ### # networkInfo @@ -100,7 +184,7 @@ sub tap3_12_export { $hash{networkInfo}->{utcTimeOffsetInfo}[0]{utcTimeOffset} = $utcTimeOffset; - #XXX receiver entity IDs? why two? + #XXX recording entity IDs, referenced by recEntityCode #$hash->{networkInfo}->{recEntityInfo}[0]{recEntityId} = '340010100'; #$hash->{networkInfo}->{recEntityInfo}[1]{recEntityId} = '240556000000'; @@ -108,7 +192,10 @@ sub tap3_12_export { # 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, }; @@ -116,6 +203,7 @@ sub tap3_12_export { 'utcTimeOffset' => $utcTimeOffset, }; + #mandatory my $totalCharge = 0; $totalCharge += $_->rated_price foreach @cdrs; $hash{totalCharge} = sprintf('%.5f', $totalCharge); @@ -124,147 +212,348 @@ sub tap3_12_export { # callEventDetails ### - $hash{callEventDetails} = [ - map { - { - 'mobileOriginatedCall' => { - 'locationInformation' => { - 'networkLocation' => { - 'recEntityCode' => $_->carrierid, #XXX 1 - } - }, - 'operatorSpecInformation' => [ - $_->userfield, ##'|Seq: 178 Loc: 1|' - ], - 'basicServiceUsedList' => [ - { - 'basicService' => { - 'serviceCode' => { - 'teleServiceCode' => $_->servicecode, #'11' - } - }, - 'chargeInformationList' => [ - { - 'callTypeGroup' => { - 'callTypeLevel1' => $_->calltypenum, - 'callTypeLevel3' => 0, - 'callTypeLevel2' => 0 - }, - 'chargeDetailList' => [ - { - 'charge' => $_->rated_price * 100000, #XXX numberOfDecimalPlaces - 'chargeType' => '00', - 'chargeableUnits' => $_->quantity_able, - 'chargedUnits' => $_->quantity, - } - ], - 'chargedItem' => 'D', #XXX or E? what's this do? - 'exchangeRateCode' => 1 - } - ] - } - ], - 'basicCallInformation' => { - 'totalCallEventDuration' => $_->duration, - 'destination' => { - 'calledNumber' => $_->dst, - #XXX 'dialledDigits' => '322221350' - }, - 'callEventStartTimeStamp' => { - 'localTimeStamp' => time2str('%Y%m%d%H%M%S', $_->startdate), - 'utcTimeOffsetCode' => 1 - }, - 'chargeableSubscriber' => { - 'simChargeableSubscriber' => { - 'msisdn' => $_->charged_pary, #src - 'imsi' => $_->charged_party_imsi, - } - } - } - } - }; - } - @cdrs - ]; - + $hash{callEventDetails} = [ map tap3_12_export_cdr($_), @cdrs ]; ### - - my $pdu = $TransferBatch->encode( \%hash ); - - return $pdu; + $TransferBatch->encode( \%hash ); } sub _TransferBatch { - 'accountingInfo' => { - 'currencyConversionInfo' => [ - { - 'numberOfDecimalPlaces' => 5, - 'exchangeRate' => 152549, #??? - 'exchangeRateCode' => 1 - } - ], - 'tapDecimalPlaces' => 5, - 'localCurrency' => 'USD' - }, - 'batchControlInfo' => { - 'specificationVersionNumber' => 3, - 'releaseVersionNumber' => 12, #11? - #'transferCutOffTimeStamp' => { - # 'localTimeStamp' => '20121230050222', - # 'utcTimeOffset' => '+0300' - # }, - 'operatorSpecInformation' => [ - '', # XXX '|File proc MTH LUXMA: 1285348027|' - ], - #'recipient' => 'GNQHT', - #'fileCreationTimeStamp' => { - # 'localTimeStamp' => '20121230050222', - # 'utcTimeOffset' => '+0300' - # }, - #'sender' => 'MDGTM', - #'fileSequenceNumber' => '00178', - #'fileAvailableTimeStamp' => { - # 'localTimeStamp' => '20121230035052', - # 'utcTimeOffset' => '+0100' - # } - }, - 'networkInfo' => { - 'recEntityInfo' => [ - { - 'recEntityType' => 1, - #'recEntityId' => '340010100', - 'recEntityCode' => 1 - }, - { - 'recEntityType' => 2, - #'recEntityId' => '240556000000', - 'recEntityCode' => 2 - } - ], - 'utcTimeOffsetInfo' => [ - { - 'utcTimeOffset' => '+0300', - 'utcTimeOffsetCode' => 1 + + #accounting related information + 'accountingInfo' => { + #mandatory + #'localCurrency' => 'USD', + 'tapDecimalPlaces' => 5, + 'currencyConversionInfo' => [ + { + 'numberOfDecimalPlaces' => 5, + 'exchangeRate' => 152549, #XXX ??? "exchange rate +VAT" ? + 'exchangeRateCode' => 1 + } + ], + #optional: may conditionally include taxation and discounting tables, and, optionally, TAP currency + }, + + 'batchControlInfo' => { + #mandatory + 'specificationVersionNumber' => 3, + 'releaseVersionNumber' => 12, + + #'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' => [ + # '', # '|File proc MTH LUXMA: 1285348027|' Operator Specific Information + # # probably just leave out + # ], + + + }, + + #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' => [ + { + 'recEntityCode' => 1, + 'recEntityType' => 1, #MSC + #'recEntityId' => '340010100', + }, + { + 'recEntityCode' => 2, + 'recEntityType' => 2, #SMSC + #'recEntityId' => '240556000000', + }, + ], + #mandatory + 'utcTimeOffsetInfo' => [ + { + 'utcTimeOffsetCode' => 1, + #'utcTimeOffset' => '+0300', + } + ] + }, + + #identifies the end of the Transfer Batch + 'auditControlInfo' => { + #mandatory + #'callEventDetailsCount' => 4, + 'totalTaxValue' => 0, + 'totalDiscountValue' => 0, + #'totalCharge' => 50474, + + #these two are optional + #'earliestCallTimeStamp' => { + # 'localTimeStamp' => '20121229102501', + # 'utcTimeOffset' => '+0300' + # }, + #'latestCallTimeStamp' => { + # 'localTimeStamp' => '20121229102807', + # 'utcTimeOffset' => '+0300' + # } + #optional: beyond the scope of TAP and has been bilaterally agreed + #'operatorSpecInformation' => [ + # '', + # ], + }, +} + +sub tap3_12_export_cdr { + my $self = shift; + + #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 + + { #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' => $self->carrierid, #XXX Recording Entity (per 2.5, from "Reference Tables") } - ] - }, - 'auditControlInfo' => { - #'callEventDetailsCount' => 4, - 'totalTaxValue' => 0, - #'earliestCallTimeStamp' => { - # 'localTimeStamp' => '20121229102501', - # 'utcTimeOffset' => '+0300' - # }, - 'totalDiscountValue' => 0, - #'totalCharge' => 50474, - #'latestCallTimeStamp' => { - # 'localTimeStamp' => '20121229102807', - # 'utcTimeOffset' => '+0300' - # } - }, + }, + + #Operator Specific Information: beyond the scope of TAP and has been bilaterally agreed + 'operatorSpecInformation' => [ + $self->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' => $self->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' => $self->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' => $self->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' => $self->quantity_able, + + #optional + # the rounded number of units which are actually charged for + 'chargedUnits' => $self->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' => $self->charged_party, #src + 'imsi' => $self->charged_party_imsi, + } + }, + # the start of the call event + 'callEventStartTimeStamp' => { + 'localTimeStamp' => time2str('%Y%m%d%H%M%S', $self->startdate), + 'utcTimeOffsetCode' => 1 + }, + + # the actual total duration of a call event as a number of seconds + 'totalCallEventDuration' => $self->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' => $self->dst, + + #the actual digits as dialled by the subscriber, i.e. unmodified, in establishing a call + # This will contain ‘+’ and ‘#’ where appropriate. + #'dialledDigits' => '322221350' + }, + } + } + }; + } sub _asn_spec {