diff options
Diffstat (limited to 'eg')
| -rwxr-xr-x | eg/TEMPLATE_cust_main.import | 196 | ||||
| -rw-r--r-- | eg/cdr_template.pm | 101 | ||||
| -rw-r--r-- | eg/export_template.pm | 113 | ||||
| -rw-r--r-- | eg/part_event-Action-template.pm | 55 | ||||
| -rw-r--r-- | eg/part_event-Condition-template.pm | 57 | ||||
| -rw-r--r-- | eg/table_template-svc.pm | 212 | ||||
| -rw-r--r-- | eg/table_template.pm | 116 | ||||
| -rwxr-xr-x | eg/xmlrpc-example.pl | 23 | 
8 files changed, 873 insertions, 0 deletions
| diff --git a/eg/TEMPLATE_cust_main.import b/eg/TEMPLATE_cust_main.import new file mode 100755 index 000000000..f6d88c701 --- /dev/null +++ b/eg/TEMPLATE_cust_main.import @@ -0,0 +1,196 @@ +#!/usr/bin/perl -w +# +# Template for importing legacy customer data + +use strict; +use Date::Parse; +use FS::UID qw(adminsuidsetup datasrc); +use FS::Record qw(fields qsearch qsearchs); +use FS::cust_main; +use FS::cust_pkg; +use FS::cust_svc; +use FS::svc_acct; +use FS::pkg_svc; + +my $user = shift or die &usage; +adminsuidsetup $user; + +# use these for the imported cust_main records (unless you have these in legacy +# data) +my($agentnum)=4; +my($refnum)=5; + +# map from legacy billing data to pkgpart, maps imported field +# LegacyBillingData to pkgpart.  your names and pkgparts will be different +my(%pkgpart)=( +  'Employee'          => 10, +  'Business'          => 11, +  'Individual'        => 12, +  'Basic PPP'         => 13, +  'Slave'             => 14, +  'Co-Located Server' => 15, +  'Virtual Web'       => 16, +  'Perk Mail'         => 17, +  'Credit Hold'       => 18, +); + +my($file)="legacy_file"; + +open(CLIENT,$file)  +  or die "Can't open $file: $!";  + +# put a tab-separated header atop the file, or define @fields +#   (use these names or change them below) +# +# for cust_main +#   custnum - unique +#   last - (name) +#   first - (name) +#   company +#   address1 +#   address2 +#   city +#   state +#   zip +#   country +#   daytime - (phone) +#   night - (phone) +#   fax +#   payby - CARD, BILL or COMP +#   payinfo - Credit card #, P.O. # or COMP authorization +#   paydate - Expiration +#   tax - 'Y' for tax exempt +# for cust_pkg +#   LegacyBillingData - maps via %pkgpart above to a pkgpart +# for svc_acct +#   username + +my($header); +$header=<CLIENT>; +chop $header; +my(@fields)=map { /^\s*(.*[^\s]+)\s*$/; $1 } split(/\t/,$header); +#print join("\n",@fields); + +my($error); +my($link,$line)=(0,0); +while (<CLIENT>) { +  chop;  +  next if /^[\s\t]*$/; #skip any blank lines + +  #define %svc hash for this record +  my(@record)=split(/\t/); +  my(%svc); +  foreach (@fields) { +    $svc{$_}=shift @record;    +  } + +  # might need to massage some data like this +  $svc{'payby'} =~ s/^Credit Card$/CARD/io; +  $svc{'payby'} =~ s/^Check$/BILL/io; +  $svc{'payby'} =~ s/^Cash$/BILL/io; +  $svc{'payby'} =~ s/^$/BILL/o; +  $svc{'First'} =~ s/&/and/go;  +  $svc{'Zip'} =~ s/\s+$//go; + +  my($cust_main) = new FS::cust_main ( { +    'custnum'  => $svc{'custnum'}, +    'agentnum' => $agentnum, +    'last'     => $svc{'last'}, +    'first'    => $svc{'first'}, +    'company'  => $svc{'company'}, +    'address1' => $svc{'address1'}, +    'address2' => $svc{'address2'}, +    'city'     => $svc{'city'}, +    'state'    => $svc{'state'}, +    'zip'      => $svc{'zip'}, +    'country'  => $svc{'country'}, +    'daytime'  => $svc{'daytime'}, +    'night'    => $svc{'night'}, +    'fax'      => $svc{'fax'}, +    'payby'    => $svc{'payby'}, +    'payinfo'  => $svc{'payinfo'}, +    'paydate'  => $svc{'paydate'}, +    'payname'  => $svc{'payname'}, +    'tax'      => $svc{'tax'}, +    'refnum'   => $refnum, +  } ); + +  $error=$cust_main->insert; + +  if ( $error ) { +    warn $cust_main->_dump; +    warn map "$_: ". $svc{$_}. "|\n", keys %svc; +    die $error; +  } + +  my($cust_pkg)=new FS::cust_pkg ( { +    'custnum' => $svc{'custnum'}, +    'pkgpart' => $pkgpart{$svc{'LegacyBillingData'}}, +    'setup'   => '',  +    'bill'    => '', +    'susp'    => '', +    'expire'  => '', +    'cancel'  => '', +  } ); + +  $error=$cust_pkg->insert; +  if ( $error ) { +    warn $svc{'LegacyBillingData'}; +    die $error; +  } + +  unless ( $svc{'username'} ) { +    warn "Empty login"; +  } else { +    #find svc_acct record (imported with bin/svc_acct.import) for this username +    my($svc_acct)=qsearchs('svc_acct',{'username'=>$svc{'username'}}); +    unless ( $svc_acct ) { +      warn "username ", $svc{'username'}, " not found\n"; +    } else { +      #link to the cust_pkg record we created above + +      #find cust_svc record for this svc_acct record +      my($o_cust_svc)=qsearchs('cust_svc',{ +        'svcnum' => $svc_acct->svcnum, +        'pkgnum' => '', +      } ); +      unless ( $o_cust_svc ) { +        warn "No unlinked cust_svc for svcnum ", $svc_acct->svcnum; +      } else { + +        #make sure this svcpart is in pkgpart +        my($pkg_svc)=qsearchs('pkg_svc',{ +          'pkgpart'  => $pkgpart{$svc{'LegacyBillingData'}}, +          'svcpart'  => $o_cust_svc->svcpart, +          'quantity' => 1, +        }); +        unless ( $pkg_svc ) { +          warn "login ", $svc{'username'}, ": No svcpart ", $o_cust_svc->svcpart, +               " for pkgpart ", $pkgpart{$svc{'Acct. Type'}}, "\n" ; +        } else { + +          #create new cust_svc record linked to cust_pkg record  +          my($n_cust_svc) = new FS::cust_svc ({ +            'svcnum'  => $o_cust_svc->svcnum, +            'pkgnum'  => $cust_pkg->pkgnum, +            'svcpart' => $pkg_svc->svcpart, +          }); +          my($error) = $n_cust_svc->replace($o_cust_svc); +          die $error if $error; +          $link++; +        } +      } +    } +  } + +  $line++; + +} + +warn "\n$link of $line lines linked\n"; + +# --- + +sub usage { +  die "Usage:\n\n  cust_main.import user\n"; +} diff --git a/eg/cdr_template.pm b/eg/cdr_template.pm new file mode 100644 index 000000000..4e55c65de --- /dev/null +++ b/eg/cdr_template.pm @@ -0,0 +1,101 @@ +package FS::cdr::cdr_template; + +use strict; +use base qw( FS::cdr ); +use vars qw( %info ); +use FS::cdr qw( _cdr_date_parser_maker _cdr_min_parser_maker ); + +%info = ( +  'name'          => 'Example CDR format', +  'weight'        => 500, +  'header'        => 0,     #0 default, set to 1 to ignore the first line, or +                            # to higher numbers to ignore that number of lines +  'type'          => 'csv', #csv (default), fixedlength or xls +  'sep_char'      => ',',   #for csv, defaults to , +  'disabled'      => 0,     #0 default, set to 1 to disable + +  #listref of what to do with each field from the CDR, in order +  'import_fields' => [ +     +    #place data directly in the specified field +    'freeside_cdr_fieldname', + +    #subroutine reference +    sub { my($cdr, $field_data) = @_;  +          #do something to $field_data +          $cdr->fieldname($field_data); +        }, + +    #premade subref factory for date+time parsing, understands dates like: +    #  10/31/2007 08:57:24 +    #  2007-10-31 08:57:24.113000000 +    #  Mon Dec 15 11:38:34 2003 +    _cdr_date_parser_maker('startddate'), #for example +     +    #premade subref factory for decimal minute parsing +    _cdr_min_parser_maker, #defaults to billsec and duration +    _cdr_min_parser_maker('fieldname'), #one field +    _cdr_min_parser_maker(['billsec', 'duration']), #listref for multiple fields + +  ], + +  #Parse::FixedLength field descriptions & lengths, for type=>'fixedlength' only +  'fixedlength_format' => [qw( +    Type:2:1:2 +    Sequence:4:3:6 +  )], + +); + +1; + +__END__ + +list of freeside CDR fields, useful ones marked with * + +       acctid - primary key +*[1]   calldate - Call timestamp (SQL timestamp) +       clid - Caller*ID with text +*      src - Caller*ID number / Source number +*      dst - Destination extension +       dcontext - Destination context +       channel - Channel used +       dstchannel - Destination channel if appropriate +       lastapp - Last application if appropriate +       lastdata - Last application data +*      startdate - Start of call (UNIX-style integer timestamp) +       answerdate - Answer time of call (UNIX-style integer timestamp) +*      enddate - End time of call (UNIX-style integer timestamp) +*      duration - Total time in system, in seconds +*      billsec - Total time call is up, in seconds +*[2]   disposition - What happened to the call: ANSWERED, NO ANSWER, BUSY +       amaflags - What flags to use: BILL, IGNORE etc, specified on a per +       channel basis like accountcode. +*[3]   accountcode - CDR account number to use: account +       uniqueid - Unique channel identifier +       userfield - CDR user-defined field +       cdr_type - CDR type - see FS::cdr_type (Usage = 1, S&E = 7, OC&C = 8) +*[4]   charged_party - Service number to be billed +       upstream_currency - Wholesale currency from upstream +*[5]   upstream_price - Wholesale price from upstream +       upstream_rateplanid - Upstream rate plan ID +       rated_price - Rated (or re-rated) price +       distance - km (need units field?) +       islocal - Local - 1, Non Local = 0 +*[6]   calltypenum - Type of call - see FS::cdr_calltype +       description - Description (cdr_type 7&8 only) (used for +       cust_bill_pkg.itemdesc) +       quantity - Number of items (cdr_type 7&8 only) +*[7]   carrierid - Upstream Carrier ID (see FS::cdr_carrier) +       upstream_rateid - Upstream Rate ID +       svcnum - Link to customer service (see FS::cust_svc) +       freesidestatus - NULL, done (or something) + +[1] Auto-populated from startdate if not present +[2] Package options available to ignore calls without a specific disposition +[3] When using 'cdr-charged_party-accountcode' config +[4] Auto-populated from src (normal calls) or dst (toll free calls) if not present +[5] When using 'upstream_simple' rating method. +[6] Set to usage class classnum when using pre-rated CDRs and usage class-based +    taxation (local/intrastate/interstate/international) +[7] If doing settlement charging diff --git a/eg/export_template.pm b/eg/export_template.pm new file mode 100644 index 000000000..22eb36a42 --- /dev/null +++ b/eg/export_template.pm @@ -0,0 +1,113 @@ +package FS::part_export::myexport; + +use vars qw(@ISA %info); +use Tie::IxHash; +use FS::part_export; + +@ISA = qw(FS::part_export); + +tie my %options, 'Tie::IxHash', +  'regular_option'  => { label => 'Option description', default => 'value' }, +  'select_option'   => { label   => 'Select option description', +                         type    => 'select', options=>[qw(chocolate vanilla)], +                         default => 'vanilla', +                       }, +  'textarea_option' => { label   => 'Textarea option description', +                         type    => 'textarea', +                         default => 'Default text.', +                      }, +  'checkbox_option' => { label => 'Checkbox label', type => 'checkbox' }, +; + +%info = ( +  'svc'      => 'svc_acct', +  #'svc'      => [qw( svc_acct svc_forward )], +  'desc'     => +    'Export short description', +  'options'  => \%options, +  'nodomain' => 'Y', +  'notes'    => <<'END' +HTML notes about this export. +END + +sub rebless { shift; } + +sub _export_insert { +  my($self, $svc_something) = (shift, shift); +  $err_or_queue = $self->myexport_queue( $svc_something->svcnum, 'insert', +    $svc_something->username, $svc_something->_password ); +  ref($err_or_queue) ? '' : $err_or_queue; +} + +sub _export_replace { +  my( $self, $new, $old ) = (shift, shift, shift); +  #return "can't change username with myexport" +  #  if $old->username ne $new->username; +  #return '' unless $old->_password ne $new->_password; +  $err_or_queue = $self->myexport_queue( $new->svcnum, +    'replace', $new->username, $new->_password ); +  ref($err_or_queue) ? '' : $err_or_queue; +} + +sub _export_delete { +  my( $self, $svc_something ) = (shift, shift); +  $err_or_queue = $self->myexport_queue( $svc_something->svcnum, +    'delete', $svc_something->username ); +  ref($err_or_queue) ? '' : $err_or_queue; +} + +#these three are optional +# fallback for svc_acct will change and restore password +sub _export_suspend { +  my( $self, $svc_something ) = (shift, shift); +  $err_or_queue = $self->myexport_queue( $svc_something->svcnum, +    'suspend', $svc_something->username ); +  ref($err_or_queue) ? '' : $err_or_queue; +} + +sub _export_unsuspend { +  my( $self, $svc_something ) = (shift, shift); +  $err_or_queue = $self->myexport_queue( $svc_something->svcnum, +    'unsuspend', $svc_something->username ); +  ref($err_or_queue) ? '' : $err_or_queue; +} + +sub export_links { +  my($self, $svc_something, $arrayref) = (shift, shift, shift); +  #push @$arrayref, qq!<A HREF="http://example.com/~!. $svc_something->username. +  #                 qq!">!. $svc_something->username. qq!</A>!; +  ''; +} + +### + +#a good idea to queue anything that could fail or take any time +sub myexport_queue { +  my( $self, $svcnum, $method ) = (shift, shift, shift); +  my $queue = new FS::queue { +    'svcnum' => $svcnum, +    'job'    => "FS::part_export::myexport::myexport_$method", +  }; +  $queue->insert( @_ ) or $queue; +} + +sub myexport_insert { #subroutine, not method +  my( $username, $password ) = @_; +  #do things with $username and $password +} + +sub myexport_replace { #subroutine, not method +} + +sub myexport_delete { #subroutine, not method +  my( $username ) = @_; +  #do things with $username +} + +sub myexport_suspend { #subroutine, not method +} + +sub myexport_unsuspend { #subroutine, not method +} + + diff --git a/eg/part_event-Action-template.pm b/eg/part_event-Action-template.pm new file mode 100644 index 000000000..c2f5ba58f --- /dev/null +++ b/eg/part_event-Action-template.pm @@ -0,0 +1,55 @@ +package FS::part_event::Action::myaction; + +use strict; + +use base qw( FS::part_event::Action ); + +# see the FS::part_event::Action manpage for full documentation on each +# of the required and optional methods. + +sub description { +  'New action (the author forgot to change this description)'; +} + +#sub eventtable_hashref { +#    { 'cust_main' => 1, +#      'cust_bill' => 1, +#      'cust_pkg'  => 1, +#    }; +#} + +#sub option_fields { +#  ( +#    'field'         => 'description', +# +#    'another_field' => { 'label'=>'Amount', 'type'=>'money', }, +# +#    'third_field'   => { 'label'         => 'Types', +#                         'type'          => 'select', +#                         'options'       => [ 'h', 's' ], +#                         'option_labels' => { 'h' => 'Happy', +#                                              's' => 'Sad', +#                                            }, +#  ); +#} + +#sub default_weight { +#  100; +#} + + +sub do_action { +  my( $self, $object ) = @_; + +  my $cust_main = $self->cust_main($object); + +  my $value_of_field = $self->option('field'); + +  #do your action +   +  #die "Error: $error"; +  return 'Null example action completed sucessfully.'; + +} + +1; diff --git a/eg/part_event-Condition-template.pm b/eg/part_event-Condition-template.pm new file mode 100644 index 000000000..cc05843b4 --- /dev/null +++ b/eg/part_event-Condition-template.pm @@ -0,0 +1,57 @@ +package FS::part_event::Condition::mycondition; + +use strict; + +use base qw( FS::part_event::Condition ); + +# see the FS::part_event::Condition manpage for full documentation on each +# of the required and optional methods. + +sub description { +  'New condition (the author forgot to change this description)'; +} + +#sub eventtable_hashref { +#    { 'cust_main'      => 1, +#      'cust_bill'      => 1, +#      'cust_pkg'       => 1, +#      'cust_pay_batch' => 1, +#    }; +#} + +#sub option_fields { +#  ( +#    'field'         => 'description', +# +#    'another_field' => { 'label'=>'Amount', 'type'=>'money', }, +# +#    'third_field'   => { 'label'         => 'Types', +#                         'type'          => 'checkbox-multiple', +#                         'options'       => [ 'h', 's' ], +#                         'option_labels' => { 'h' => 'Happy', +#                                              's' => 'Sad', +#                                            }, +#  ); +#} + +sub condition { +  my($self, $object, %opt) = @_; + +  my $cust_main = $self->cust_main($object); + +  my $value_of_field = $self->option('field'); + +  my $time = $opt{'time'}; #use this instead of time or $^T + +  #test your condition +  1; + +} + +#sub condition_sql { +#  my( $class, $table ) = @_; +#  #... +#  'true'; +#} + +1; diff --git a/eg/table_template-svc.pm b/eg/table_template-svc.pm new file mode 100644 index 000000000..7e10275cd --- /dev/null +++ b/eg/table_template-svc.pm @@ -0,0 +1,212 @@ +package FS::svc_table; + +use strict; +use base qw( FS::svc_Common ); +#use FS::Record qw( qsearch qsearchs ); +use FS::cust_svc; + +=head1 NAME + +FS::table_name - Object methods for table_name records + +=head1 SYNOPSIS + +  use FS::table_name; + +  $record = new FS::table_name \%hash; +  $record = new FS::table_name { 'column' => 'value' }; + +  $error = $record->insert; + +  $error = $new_record->replace($old_record); + +  $error = $record->delete; + +  $error = $record->check; + +  $error = $record->suspend; + +  $error = $record->unsuspend; + +  $error = $record->cancel; + +=head1 DESCRIPTION + +An FS::table_name object represents an example.  FS::table_name inherits from +FS::svc_Common.  The following fields are currently supported: + +=over 4 + +=item field - description + +=back + +=head1 METHODS + +=over 4 + +=item new HASHREF + +Creates a new example.  To add the example 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<hash> method. + +=cut + +sub table { 'table_name'; } + +sub table_info { +  { +    'name' => 'Example', +    'name_plural' => 'Example services', #optional, +    'longname_plural' => 'Example services', #optional +    'sorts' => 'svcnum', # optional sort field (or arrayref of sort fields, main first) +    'display_weight' => 100, +    'cancel_weight'  => 100, +    'fields' => { +      'field'         => 'Description', +      'another_field' => {  +                           'label'     => 'Description', +			   'def_label' => 'Description for service definitions', +			   'type'      => 'text', +			   'disable_default'   => 1, #disable switches +			   'disable_fixed'     => 1, # +			   'disable_inventory' => 1, # +			 }, +      'foreign_key'   => {  +                           'label'        => 'Description', +			   'def_label'    => 'Description for service defs', +			   'type'         => 'select', +			   'select_table' => 'foreign_table', +			   'select_key'   => 'key_field_in_table', +			   'select_label' => 'label_field_in_table', +			 }, + +    }, +  }; +} + +=item search_sql STRING + +Class method which returns an SQL fragment to search for the given string. + +=cut + +#or something more complicated if necessary +sub search_sql { +  my($class, $string) = @_; +  $class->search_sql_field('search_field', $string); +} + +=item label + +Returns a meaningful identifier for this example + +=cut + +sub label { +  my $self = shift; +  $self->label_field; #or something more complicated if necessary +} + +=item insert + +Adds this record to the database.  If there is an error, returns the error, +otherwise returns false. + +The additional fields pkgnum and svcpart (see L<FS::cust_svc>) should be  +defined.  An FS::cust_svc record will be created and inserted. + +=cut + +sub insert { +  my $self = shift; +  my $error; + +  $error = $self->SUPER::insert; +  return $error if $error; + +  ''; +} + +=item delete + +Delete this record from the database. + +=cut + +sub delete { +  my $self = shift; +  my $error; + +  $error = $self->SUPER::delete; +  return $error if $error; + +  ''; +} + + +=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 + +sub replace { +  my ( $new, $old ) = ( shift, shift ); +  my $error; + +  $error = $new->SUPER::replace($old); +  return $error if $error; + +  ''; +} + +=item suspend + +Called by the suspend method of FS::cust_pkg (see L<FS::cust_pkg>). + +=item unsuspend + +Called by the unsuspend method of FS::cust_pkg (see L<FS::cust_pkg>). + +=item cancel + +Called by the cancel method of FS::cust_pkg (see L<FS::cust_pkg>). + +=item check + +Checks all fields to make sure this is a valid example.  If there is +an error, returns the error, otherwise returns false.  Called by the insert +and repalce methods. + +=cut + +sub check { +  my $self = shift; + +  my $x = $self->setfixed; +  return $x unless ref($x); +  my $part_svc = $x; + + +  $self->SUPER::check; +} + +=back + +=head1 BUGS + +The author forgot to customize this manpage. + +=head1 SEE ALSO + +L<FS::svc_Common>, L<FS::Record>, L<FS::cust_svc>, L<FS::part_svc>, +L<FS::cust_pkg>, schema.html from the base documentation. + +=cut + +1; + diff --git a/eg/table_template.pm b/eg/table_template.pm new file mode 100644 index 000000000..9c71b3adc --- /dev/null +++ b/eg/table_template.pm @@ -0,0 +1,116 @@ +package FS::table_name; + +use strict; +use base qw( FS::Record ); +use FS::Record qw( qsearch qsearchs ); + +=head1 NAME + +FS::table_name - Object methods for table_name records + +=head1 SYNOPSIS + +  use FS::table_name; + +  $record = new FS::table_name \%hash; +  $record = new FS::table_name { 'column' => 'value' }; + +  $error = $record->insert; + +  $error = $new_record->replace($old_record); + +  $error = $record->delete; + +  $error = $record->check; + +=head1 DESCRIPTION + +An FS::table_name object represents an example.  FS::table_name inherits from +FS::Record.  The following fields are currently supported: + +=over 4 + +=item field - description + +=back + +=head1 METHODS + +=over 4 + +=item new HASHREF + +Creates a new example.  To add the example 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<hash> method. + +=cut + +# the new method can be inherited from FS::Record, if a table method is defined + +sub table { 'table_name'; } + +=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 example.  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('primary_key') +    || $self->ut_number('validate_other_fields') +  ; +  return $error if $error; + +  $self->SUPER::check; +} + +=back + +=head1 BUGS + +The author forgot to customize this manpage. + +=head1 SEE ALSO + +L<FS::Record>, schema.html from the base documentation. + +=cut + +1; + diff --git a/eg/xmlrpc-example.pl b/eg/xmlrpc-example.pl new file mode 100755 index 000000000..7a2a0a6f0 --- /dev/null +++ b/eg/xmlrpc-example.pl @@ -0,0 +1,23 @@ +#!/usr/bin/perl + +use strict; +use Frontier::Client; +use Data::Dumper; + +my $server = new Frontier::Client ( +	url => 'http://user:pass@freesidehost/misc/xmlrpc.cgi', +); + +#my $method = 'cust_main.smart_search'; +#my @args = (search => '1'); + +my $method = 'Record.qsearch'; +my @args = (cust_main => { }); + +my $result = $server->call($method, @args); + +if (ref($result) eq 'ARRAY') { +  print "Result:\n"; +  print Dumper(@$result); +} + | 
