From b1d445f94514a29e5d4753839798b0291d89aee3 Mon Sep 17 00:00:00 2001 From: ivan Date: Mon, 9 Aug 2010 01:03:49 +0000 Subject: [PATCH] package web import from CSV/XLS, RT#9529 --- FS/FS/Mason.pm | 3 + FS/FS/Record.pm | 98 ++++--- FS/FS/Schema.pm | 4 +- FS/FS/cust_pkg.pm | 9 + FS/FS/cust_pkg/Import.pm | 373 +++++++++++++++++++++++++++ FS/bin/freeside-queued | 2 +- Makefile | 2 +- httemplate/elements/menu.html | 3 +- httemplate/misc/cust_main-import.cgi | 2 +- httemplate/misc/cust_pkg-import.html | 132 ++++++++++ httemplate/misc/process/cust_pkg-import.html | 10 + httemplate/search/cust_pkg.cgi | 5 +- 12 files changed, 607 insertions(+), 36 deletions(-) create mode 100644 FS/FS/cust_pkg/Import.pm create mode 100644 httemplate/misc/cust_pkg-import.html create mode 100644 httemplate/misc/process/cust_pkg-import.html diff --git a/FS/FS/Mason.pm b/FS/FS/Mason.pm index 2a4b42ffe..f5d7c8566 100644 --- a/FS/FS/Mason.pm +++ b/FS/FS/Mason.pm @@ -3,6 +3,7 @@ package FS::Mason; use strict; use vars qw( @ISA @EXPORT_OK $addl_handler_use ); use Exporter; +use Carp; use File::Slurp qw( slurp ); use HTML::Mason 1.27; #http://www.masonhq.com/?ApacheModPerl2Redirect use HTML::Mason::Interp; @@ -146,6 +147,7 @@ if ( -e $addl_handler_use_file ) { use FS::cust_location; use FS::cust_pay; use FS::cust_pkg; + use FS::cust_pkg::Import; use FS::part_pkg_taxclass; use FS::cust_pkg_reason; use FS::cust_refund; @@ -361,6 +363,7 @@ if ( -e $addl_handler_use_file ) { sub include { use vars qw($m); + #carp #should just switch to <& &> syntax $m->scomp(@_); } diff --git a/FS/FS/Record.pm b/FS/FS/Record.pm index bc075dde9..758e0f96c 100644 --- a/FS/FS/Record.pm +++ b/FS/FS/Record.pm @@ -1611,6 +1611,8 @@ Class method for batch imports. Available params: =item table +=item format - usual way to specify import, with this format string selecting data from the formats and format_* info hashes + =item formats =item format_types @@ -1623,6 +1625,10 @@ Class method for batch imports. Available params: =item format_row_callbacks +=item fields - Alternate way to specify import, specifying import fields directly as a listref + +=item postinsert_callback + =item params =item job @@ -1635,8 +1641,6 @@ FS::queue object, will be updated with progress csv, xls or fixedlength -=item format - =item empty_ok =back @@ -1647,21 +1651,64 @@ sub batch_import { my $param = shift; warn "$me batch_import call with params: \n". Dumper($param) - ;# if $DEBUG; + if $DEBUG; my $table = $param->{table}; - my $formats = $param->{formats}; my $job = $param->{job}; my $file = $param->{file}; - my $format = $param->{'format'}; my $params = $param->{params} || {}; - die "unknown format $format" unless exists $formats->{ $format }; + my( $type, $header, $sep_char, $fixedlength_format, $row_callback, @fields ); + my $postinsert_callback = ''; + if ( $param->{'format'} ) { + + my $format = $param->{'format'}; + my $formats = $param->{formats}; + die "unknown format $format" unless exists $formats->{ $format }; + + $type = $param->{'format_types'} + ? $param->{'format_types'}{ $format } + : $param->{type} || 'csv'; + + + $header = $param->{'format_headers'} + ? $param->{'format_headers'}{ $param->{'format'} } + : 0; + + $sep_char = $param->{'format_sep_chars'} + ? $param->{'format_sep_chars'}{ $param->{'format'} } + : ','; + + $fixedlength_format = + $param->{'format_fixedlength_formats'} + ? $param->{'format_fixedlength_formats'}{ $param->{'format'} } + : ''; + + $row_callback = + $param->{'format_row_callbacks'} + ? $param->{'format_row_callbacks'}{ $param->{'format'} } + : ''; + + @fields = @{ $formats->{ $format } }; + + } elsif ( $param->{'fields'} ) { + + $type = ''; #infer from filename + $header = 0; + $sep_char = ','; + $fixedlength_format = ''; + $row_callback = ''; + @fields = @{ $param->{'fields'} }; - my $type = $param->{'format_types'} - ? $param->{'format_types'}{ $format } - : $param->{type} || 'csv'; + $postinsert_callback = $param->{'postinsert_callback'} + if $param->{'postinsert_callback'} + + } else { + die "neither format nor fields specified"; + } + + #my $file = $param->{file}; unless ( $type ) { if ( $file =~ /\.(\w+)$/i ) { @@ -1675,25 +1722,6 @@ sub batch_import { if $param->{'default_csv'} && $type ne 'xls'; } - my $header = $param->{'format_headers'} - ? $param->{'format_headers'}{ $param->{'format'} } - : 0; - - my $sep_char = $param->{'format_sep_chars'} - ? $param->{'format_sep_chars'}{ $param->{'format'} } - : ','; - - my $fixedlength_format = - $param->{'format_fixedlength_formats'} - ? $param->{'format_fixedlength_formats'}{ $param->{'format'} } - : ''; - - my $row_callback = - $param->{'format_row_callbacks'} - ? $param->{'format_row_callbacks'}{ $param->{'format'} } - : ''; - - my @fields = @{ $formats->{ $format } }; my $row = 0; my $count; @@ -1757,6 +1785,7 @@ sub batch_import { local $FS::UID::AutoCommit = 0; my $dbh = dbh; + #my $params = $param->{params} || {}; if ( $param->{'batch_namecol'} && $param->{'batch_namevalue'} ) { my $batch_col = $param->{'batch_keycol'}; @@ -1774,7 +1803,8 @@ sub batch_import { $params->{ $batch_col } = $batch_value; } - + + #my $job = $param->{job}; my $line; my $imported = 0; my( $last, $min_sec ) = ( time, 5 ); #progressbar foo @@ -1832,6 +1862,7 @@ sub batch_import { } + #my $table = $param->{table}; my $class = "FS::$table"; my $record = $class->new( \%hash ); @@ -1855,6 +1886,15 @@ sub batch_import { $row++; $imported++; + if ( $postinsert_callback ) { + my $error = &{$postinsert_callback}($record, $param); + if ( $error ) { + $dbh->rollback if $oldAutoCommit; + return "postinsert_callback error". ( $line ? " for $line" : '' ). + ": $error"; + } + } + if ( $job && time - $min_sec > $last ) { #progress bar $job->update_statustext( int(100 * $imported / $count) ); $last = time; diff --git a/FS/FS/Schema.pm b/FS/FS/Schema.pm index d7d5a0413..dc8f2f3aa 100644 --- a/FS/FS/Schema.pm +++ b/FS/FS/Schema.pm @@ -1289,6 +1289,7 @@ sub tables_hashref { 'pkgnum', 'serial', '', '', '', '', 'custnum', 'int', '', '', '', '', 'pkgpart', 'int', '', '', '', '', + 'pkgbatch', 'varchar', 'NULL', $char_d, '', '', 'locationnum', 'int', 'NULL', '', '', '', 'otaker', 'varchar', 'NULL', 32, '', '', 'usernum', 'int', 'NULL', '', '', '', @@ -1310,7 +1311,8 @@ sub tables_hashref { ], 'primary_key' => 'pkgnum', 'unique' => [], - 'index' => [ ['custnum'], ['pkgpart'], [ 'locationnum' ], [ 'usernum' ], + 'index' => [ ['custnum'], ['pkgpart'], [ 'pkgbatch' ], [ 'locationnum' ], + [ 'usernum' ], [ 'start_date' ], ['setup'], ['last_bill'], ['bill'], ['susp'], ['adjourn'], ['expire'], ['cancel'], ['change_date'], diff --git a/FS/FS/cust_pkg.pm b/FS/FS/cust_pkg.pm index 0f9a611eb..c3ee4e40a 100644 --- a/FS/FS/cust_pkg.pm +++ b/FS/FS/cust_pkg.pm @@ -2624,6 +2624,15 @@ sub search { } ## + # custbatch + ## + + if ( $params->{'pkgbatch'} =~ /^([\w\/\-\:\.]+)$/ and $1 ) { + push @where, + "cust_pkg.pkgbatch = '$1'"; + } + + ## # parse status ## diff --git a/FS/FS/cust_pkg/Import.pm b/FS/FS/cust_pkg/Import.pm new file mode 100644 index 000000000..7a4b9d50c --- /dev/null +++ b/FS/FS/cust_pkg/Import.pm @@ -0,0 +1,373 @@ +package FS::cust_pkg::Import; + +use strict; +use vars qw( $DEBUG ); #$conf ); +use Storable qw(thaw); +use Data::Dumper; +use MIME::Base64; +use FS::Misc::DateTime qw( parse_datetime ); +use FS::Record qw( qsearchs ); +use FS::cust_pkg; +use FS::cust_main; +use FS::svc_acct; +use FS::svc_external; +use FS::svc_phone; + +$DEBUG = 0; + +#install_callback FS::UID sub { +# $conf = new FS::Conf; +#}; + +=head1 NAME + +FS::cust_pkg::Import - Batch customer importing + +=head1 SYNOPSIS + + use FS::cust_pkg::Import; + + #import + FS::cust_pkg::Import::batch_import( { + file => $file, #filename + type => $type, #csv or xls + format => $format, #extended, extended-plus_company, svc_external, + # or svc_external_svc_phone + agentnum => $agentnum, + job => $job, #optional job queue job, for progressbar updates + pkgbatch => $pkgbatch, #optional batch unique identifier + } ); + die $error if $error; + + #ajax helper + use FS::UI::Web::JSRPC; + my $server = + new FS::UI::Web::JSRPC 'FS::cust_pkg::Import::process_batch_import', $cgi; + print $server->process; + +=head1 DESCRIPTION + +Batch package importing. + +=head1 SUBROUTINES + +=item process_batch_import + +Load a batch import as a queued JSRPC job + +=cut + +sub process_batch_import { + my $job = shift; + + my $param = thaw(decode_base64(shift)); + warn Dumper($param) if $DEBUG; + + my $files = $param->{'uploaded_files'} + or die "No files provided.\n"; + + my (%files) = map { /^(\w+):([\.\w]+)$/ ? ($1,$2):() } split /,/, $files; + + my $dir = '%%%FREESIDE_CACHE%%%/cache.'. $FS::UID::datasrc. '/'; + my $file = $dir. $files{'file'}; + + my $type; + if ( $file =~ /\.(\w+)$/i ) { + $type = lc($1); + } else { + #or error out??? + warn "can't parse file type from filename $file; defaulting to CSV"; + $type = 'csv'; + } + + my $error = + FS::cust_pkg::Import::batch_import( { + job => $job, + file => $file, + type => $type, + 'params' => { pkgbatch => $param->{pkgbatch} }, + agentnum => $param->{'agentnum'}, + 'format' => $param->{'format'}, + } ); + + unlink $file; + + die "$error\n" if $error; + +} + +=item batch_import + +=cut + +my %formatfields = ( + 'default' => [], + 'svc_acct' => [qw( username _password )], + 'svc_phone' => [qw( countrycode phonenum sip_password pin )], + 'svc_external' => [qw( id title )], +); + +sub _formatfields { + \%formatfields; +} + +my %import_options = ( + 'table' => 'cust_pkg', + + 'postinsert_callback' => sub { + my( $record, $param ) = @_; + + my $formatfields = _formatfields; + foreach my $svc_x ( grep { $_ ne 'default' } keys %$formatfields ) { + + my $ff = $formatfields->{$svc_x}; + + if ( grep $param->{"$svc_x.$_"}, @$ff ) { + my $svc_x = "FS::$svc_x"->new( { + 'pkgnum' => $record->pkgnum, + 'svcpart' => $record->part_pkg->svcpart($svc_x), + map { $_ => $param->{"$svc_x.$_"} } @$ff + } ); + my $error = $svc_x->insert; + return $error if $error; + } + + } + + return ''; #no error + + }, +); + +sub _import_options { + \%import_options; +} + +sub batch_import { + my $opt = shift; + + my $iopt = _import_options; + $opt->{$_} = $iopt->{$_} foreach keys %$iopt; + + my $agentnum = delete $opt->{agentnum}; # i like closures (delete though?) + + my $format = delete $opt->{'format'}; + my @fields = (); + + if ( $format =~ /^(.*)-agent_custid$/ ) { + $format = $1; + @fields = ( + sub { + my( $self, $value ) = @_; # $conf, $param + my $cust_main = qsearchs('cust_main', { + 'agentnum' => $agentnum, + 'agent_custid' => $value, + }); + $self->custnum($cust_main->custnum) if $cust_main; + }, + ); + } else { + @fields = ( 'custnum' ); + } + + push @fields, ( 'pkgpart', 'discountnum' ); + + foreach my $field ( + qw( start_date setup bill last_bill susp adjourn cancel expire ) + ) { + push @fields, sub { + my( $self, $value ) = @_; # $conf, $param + #->$field has undesirable effects + $self->set($field, parse_datetime($value) ); #$field closure + }; + } + + my $formatfields = _formatfields(); + + die "unknown format $format" unless $formatfields->{$format}; + + foreach my $field ( @{ $formatfields->{$format} } ) { + + push @fields, sub { + my( $self, $value, $conf, $param ) = @_; + $param->{"$format.$field"} = $value; + }; + + } + + $opt->{'fields'} = \@fields; + + FS::Record::batch_import( $opt ); + +} + +=for comment + + my $billtime = time; + my %cust_pkg = ( pkgpart => $pkgpart ); + my %svc_x = (); + foreach my $field ( @fields ) { + + if ( $field =~ /^cust_pkg\.(pkgpart|setup|bill|susp|adjourn|expire|cancel)$/ ) { + + #$cust_pkg{$1} = parse_datetime( shift @$columns ); + if ( $1 eq 'pkgpart' ) { + $cust_pkg{$1} = shift @columns; + } elsif ( $1 eq 'setup' ) { + $billtime = parse_datetime(shift @columns); + } else { + $cust_pkg{$1} = parse_datetime( shift @columns ); + } + + } elsif ( $field =~ /^svc_acct\.(username|_password)$/ ) { + + $svc_x{$1} = shift @columns; + + } elsif ( $field =~ /^svc_external\.(id|title)$/ ) { + + $svc_x{$1} = shift @columns; + + } elsif ( $field =~ /^svc_phone\.(countrycode|phonenum|sip_password|pin)$/ ) { + $svc_x{$1} = shift @columns; + + } else { + + #refnum interception + if ( $field eq 'refnum' && $columns[0] !~ /^\s*(\d+)\s*$/ ) { + + my $referral = $columns[0]; + my %hash = ( 'referral' => $referral, + 'agentnum' => $agentnum, + 'disabled' => '', + ); + + my $part_referral = qsearchs('part_referral', \%hash ) + || new FS::part_referral \%hash; + + unless ( $part_referral->refnum ) { + my $error = $part_referral->insert; + if ( $error ) { + $dbh->rollback if $oldAutoCommit; + return "can't auto-insert advertising source: $referral: $error"; + } + } + + $columns[0] = $part_referral->refnum; + } + + my $value = shift @columns; + $cust_main{$field} = $value if length($value); + } + } + + $cust_main{'payby'} = 'CARD' + if defined $cust_main{'payinfo'} + && length $cust_main{'payinfo'}; + + my $invoicing_list = $cust_main{'invoicing_list'} + ? [ delete $cust_main{'invoicing_list'} ] + : []; + + my $cust_main = new FS::cust_main ( \%cust_main ); + + use Tie::RefHash; + tie my %hash, 'Tie::RefHash'; #this part is important + + if ( $cust_pkg{'pkgpart'} ) { + my $cust_pkg = new FS::cust_pkg ( \%cust_pkg ); + + my @svc_x = (); + my $svcdb = ''; + if ( $svc_x{'username'} ) { + $svcdb = 'svc_acct'; + } elsif ( $svc_x{'id'} || $svc_x{'title'} ) { + $svcdb = 'svc_external'; + } + + my $svc_phone = ''; + if ( $svc_x{'countrycode'} || $svc_x{'phonenum'} ) { + $svc_phone = FS::svc_phone->new( { + map { $_ => delete($svc_x{$_}) } + qw( countrycode phonenum sip_password pin) + } ); + } + + if ( $svcdb || $svc_phone ) { + my $part_pkg = $cust_pkg->part_pkg; + unless ( $part_pkg ) { + $dbh->rollback if $oldAutoCommit; + return "unknown pkgpart: ". $cust_pkg{'pkgpart'}; + } + if ( $svcdb ) { + $svc_x{svcpart} = $part_pkg->svcpart_unique_svcdb( $svcdb ); + my $class = "FS::$svcdb"; + push @svc_x, $class->new( \%svc_x ); + } + if ( $svc_phone ) { + $svc_phone->svcpart( $part_pkg->svcpart_unique_svcdb('svc_phone') ); + push @svc_x, $svc_phone; + } + } + + $hash{$cust_pkg} = \@svc_x; + } + + my $error = $cust_main->insert( \%hash, $invoicing_list ); + + if ( $error ) { + $dbh->rollback if $oldAutoCommit; + return "can't insert customer". ( $line ? " for $line" : '' ). ": $error"; + } + + if ( $format eq 'simple' ) { + + #false laziness w/bill.cgi + $error = $cust_main->bill( 'time' => $billtime ); + if ( $error ) { + $dbh->rollback if $oldAutoCommit; + return "can't bill customer for $line: $error"; + } + + $error = $cust_main->apply_payments_and_credits; + if ( $error ) { + $dbh->rollback if $oldAutoCommit; + return "can't bill customer for $line: $error"; + } + + $error = $cust_main->collect(); + if ( $error ) { + $dbh->rollback if $oldAutoCommit; + return "can't collect customer for $line: $error"; + } + + } + + $row++; + + if ( $job && time - $min_sec > $last ) { #progress bar + $job->update_statustext( int(100 * $row / $count) ); + $last = time; + } + + } + + $dbh->commit or die $dbh->errstr if $oldAutoCommit;; + + return "Empty file!" unless $row; + + ''; #no error + +} + +=head1 BUGS + +Not enough documentation. + +=head1 SEE ALSO + +L, L, +L, L, L + +=cut + +1; diff --git a/FS/bin/freeside-queued b/FS/bin/freeside-queued index c9b0edb10..756b699d4 100644 --- a/FS/bin/freeside-queued +++ b/FS/bin/freeside-queued @@ -186,7 +186,7 @@ while (1) { dbh->{'private_profile'} = {} if UNIVERSAL::can(dbh, 'sprintProfile'); #auto-use classes... - if ( $ljob->job =~ /(FS::(part_export|cust_main)::\w+)::/ + if ( $ljob->job =~ /(FS::(part_export|cust_main|cust_pkg)::\w+)::/ || $ljob->job =~ /(FS::\w+)::/ ) { diff --git a/Makefile b/Makefile index bf3f3d7a1..110cfa0c2 100644 --- a/Makefile +++ b/Makefile @@ -210,7 +210,7 @@ perl-modules: " blib/lib/FS/part_export/*.pm;\ perl -p -i -e "\ s|%%%FREESIDE_CACHE%%%|${FREESIDE_CACHE}|g;\ - " blib/lib/FS/cust_main/*.pm;\ + " blib/lib/FS/cust_main/*.pm blib/lib/FS/cust_pkg/*.pm;\ perl -p -i -e "\ s|%%%FREESIDE_CONF%%%|${FREESIDE_CONF}|g;\ s|%%%FREESIDE_LOG%%%|${FREESIDE_LOG}|g;\ diff --git a/httemplate/elements/menu.html b/httemplate/elements/menu.html index 2404ef291..5c48e1c3f 100644 --- a/httemplate/elements/menu.html +++ b/httemplate/elements/menu.html @@ -318,6 +318,7 @@ $report_menu{'SQL Query'} = [ $fsurl.'search/report_sql.html', 'SQL Query' ] tie my %tools_importing, 'Tie::IxHash', 'Customers' => [ $fsurl.'misc/cust_main-import.cgi', '' ], + 'Customer packages' => [ $fsurl.'misc/cust_pkg-import.html', '' ], 'Customer comments from CSV file' => [ $fsurl.'misc/cust_main_note-import.html', '' ], 'One-time charges from CSV file' => [ $fsurl.'misc/cust_main-import_charges.cgi', '' ], 'Payments from CSV file' => [ $fsurl.'misc/cust_pay-import.cgi', '' ], @@ -399,7 +400,7 @@ if ( $curuser->access_right('Configuration') ) { #package grouping sub-menu? $config_pkg{'Package classes'} = [ $fsurl.'browse/pkg_class.html', 'Package classes define groups of packages, for taxation, ordering convenience and reporting.' ]; - $config_pkg{'Package categories'} = [ $fsurl.'browse/pkg_category.html', 'Package categories define groups of package classes.' ]; + $config_pkg{'Package categories'} = [ $fsurl.'browse/pkg_category.html', 'Package categories define groups of package classes, for invoice sections.' ]; $config_pkg{'Package report classes'} = [ $fsurl.'browse/part_pkg_report_option.html', 'Package classes define optional groups of packages for reporting only.' ]; #eo package grouping sub-menu diff --git a/httemplate/misc/cust_main-import.cgi b/httemplate/misc/cust_main-import.cgi index 9c1f98479..2ccf997c8 100644 --- a/httemplate/misc/cust_main-import.cgi +++ b/httemplate/misc/cust_main-import.cgi @@ -119,7 +119,7 @@ advertising source table.
  • invoicing_list: Email address for invoices, or POST for postal invoices. -
  • pkgpart: Package definition. Configuration -> Provisioning, services and packages -> View/Edit package definitions +
  • pkgpart: Package definition. Configuration -> Packages -> Package definitions
  • username and _password are required if pkgpart is specified. (Extended and Extended plus company formats) diff --git a/httemplate/misc/cust_pkg-import.html b/httemplate/misc/cust_pkg-import.html new file mode 100644 index 000000000..ad582b088 --- /dev/null +++ b/httemplate/misc/cust_pkg-import.html @@ -0,0 +1,132 @@ +<% include("/elements/header.html",'Batch Package Import') %> + +Import a file containing package records. +

    + +<% include( '/elements/form-file_upload.html', + 'name' => 'PackageImportForm', + 'action' => 'process/cust_pkg-import.html', + 'num_files' => 1, + 'fields' => [ 'agentnum', 'pkgbatch', 'format' ], + 'message' => 'Package import successful', + 'url' => $p."search/cust_pkg.cgi?pkgbatch=$pkgbatch", + ) +%> + +<% &ntable("#cccccc", 2) %> + + <% include( '/elements/tr-select-agent.html', + #'curr_value' => '', #$agentnum, + 'label' => "Agent", + 'empty_label' => 'Select agent', + ) + %> + + + + + Format + + + + + + <% include( '/elements/file-upload.html', + 'field' => 'file', + 'label' => 'Filename', + ) + %> + + + + + + + + + + + +
    +Uploaded files can be CSV (comma-separated value) files or Excel spreadsheets. The file should have a .CSV or .XLS extension. +

    + +Default format has the following field order: custnum<%$req%>, pkgpart<%$req%>, discountnum, start_date, setup, bill, last_bill, susp, adjourn, cancel, expire +

    + +Default with agent_custid format has the following field order: agent_custid<%$req%>, pkgpart<%$req%>, discountnum, start_date, setup, bill, last_bill, susp, adjourn, cancel, expire +

    + +Account service format has the following field order: custnum<%$req%>, pkgpart<%$req%>, discountnum, start_date, setup, bill, last_bill, susp, adjourn, cancel, expire, username, _password +

    + +Account service with agent_custid format has the following field order: agent_custid<%$req%>, pkgpart<%$req%>, discountnum, start_date, setup, bill, last_bill, susp, adjourn, cancel, expire, username, _password +

    + +Phone sevice format has the following field order: custnum<%$req%>, pkgpart<%$req%>, discountnum, start_date, setup, bill, last_bill, susp, adjourn, cancel, expire, countrycode, phonenum, sip_password, pin +

    + +Phone service with agent_custid format has the following field order: agent_custid<%$req%>, pkgpart<%$req%>, discountnum, start_date, setup, bill, last_bill, susp, adjourn, cancel, expire, countrycode, phonenum, sip_password, pin +

    + +External sevice format has the following field order: custnum<%$req%>, pkgpart<%$req%>, discountnum, start_date, setup, bill, last_bill, susp, adjourn, cancel, expire, id, title +

    + +External service with agent_custid format has the following field order: agent_custid<%$req%>, pkgpart<%$req%>, discountnum, start_date, setup, bill, last_bill, susp, adjourn, cancel, expire, id, title +

    + +<%$req%> Required fields +

    + +Field information: + +
      + +
    • custnum: This specifies an existing customer by custnum. + +
    • agent_custid: This specifies an existing customer record by agent_custid. + +
    • pkgpart: Package definition. Configuration -> Packages -> Package definitions + +
    • pkgpart: Optional discount. Configuration -> Packages -> Discounts + + + +
    • id: External service id, integer + +
    • title: External service identifier, text + +
    + +
    + +<% include('/elements/footer.html') %> + +<%once> + +my $req = qq!*!; + + +<%init> + +die "access denied" + unless $FS::CurrentUser::CurrentUser->access_right('Import'); + +my $pkgbatch = time2str('webimport-%Y/%m/%d-%T'. "-$$-". rand() * 2**32, time); + + diff --git a/httemplate/misc/process/cust_pkg-import.html b/httemplate/misc/process/cust_pkg-import.html new file mode 100644 index 000000000..1021817e4 --- /dev/null +++ b/httemplate/misc/process/cust_pkg-import.html @@ -0,0 +1,10 @@ +<% $server->process %> +<%init> + +die "access denied" + unless $FS::CurrentUser::CurrentUser->access_right('Import'); + +my $server = + new FS::UI::Web::JSRPC 'FS::cust_pkg::Import::process_batch_import', $cgi; + + diff --git a/httemplate/search/cust_pkg.cgi b/httemplate/search/cust_pkg.cgi index 74a3a6d1e..bd8ea9aa6 100755 --- a/httemplate/search/cust_pkg.cgi +++ b/httemplate/search/cust_pkg.cgi @@ -175,8 +175,9 @@ my %search_hash = (); #some false laziness w/misc/bulk_change_pkg.cgi $search_hash{'query'} = $cgi->keywords; - -for (qw( agentnum custnum magic status classnum custom cust_fields )) { + +#scalars +for (qw( agentnum custnum magic status classnum custom cust_fields pkgbatch )) { $search_hash{$_} = $cgi->param($_) if $cgi->param($_); } -- 2.11.0