summaryrefslogtreecommitdiff
path: root/eg
diff options
context:
space:
mode:
Diffstat (limited to 'eg')
-rwxr-xr-xeg/TEMPLATE_cust_main.import196
-rw-r--r--eg/cdr_template.pm101
-rw-r--r--eg/export_template.pm113
-rw-r--r--eg/part_event-Action-template.pm55
-rw-r--r--eg/part_event-Condition-template.pm57
-rw-r--r--eg/table_template-svc.pm212
-rw-r--r--eg/table_template.pm116
-rwxr-xr-xeg/xmlrpc-example.pl23
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);
+}
+