summaryrefslogtreecommitdiff
path: root/httemplate/edit/process
diff options
context:
space:
mode:
Diffstat (limited to 'httemplate/edit/process')
-rwxr-xr-xhttemplate/edit/process/REAL_cust_pkg.cgi36
-rw-r--r--httemplate/edit/process/access_group.html28
-rw-r--r--httemplate/edit/process/access_user.html21
-rwxr-xr-xhttemplate/edit/process/addr_block/add.cgi20
-rwxr-xr-xhttemplate/edit/process/addr_block/allocate.cgi16
-rwxr-xr-xhttemplate/edit/process/addr_block/deallocate.cgi20
-rwxr-xr-xhttemplate/edit/process/addr_block/manual_flag.cgi30
-rwxr-xr-xhttemplate/edit/process/addr_block/split.cgi27
-rwxr-xr-xhttemplate/edit/process/agent.cgi16
-rw-r--r--httemplate/edit/process/agent_payment_gateway.html29
-rwxr-xr-xhttemplate/edit/process/agent_type.cgi35
-rw-r--r--httemplate/edit/process/bulk-cust_main_county.html63
-rw-r--r--httemplate/edit/process/bulk-cust_svc.cgi9
-rw-r--r--httemplate/edit/process/change-cust_pkg.html46
-rwxr-xr-xhttemplate/edit/process/cust_bill_pay.cgi13
-rwxr-xr-xhttemplate/edit/process/cust_credit.cgi63
-rwxr-xr-xhttemplate/edit/process/cust_credit_bill.cgi13
-rwxr-xr-xhttemplate/edit/process/cust_credit_refund.cgi13
-rwxr-xr-xhttemplate/edit/process/cust_main.cgi210
-rwxr-xr-xhttemplate/edit/process/cust_main_county-collapse.cgi44
-rwxr-xr-xhttemplate/edit/process/cust_main_county-expand.cgi78
-rw-r--r--httemplate/edit/process/cust_main_county.html13
-rwxr-xr-xhttemplate/edit/process/cust_main_note.cgi54
-rwxr-xr-xhttemplate/edit/process/cust_pay.cgi55
-rw-r--r--httemplate/edit/process/cust_pay_pending.html68
-rwxr-xr-xhttemplate/edit/process/cust_pay_refund.cgi13
-rwxr-xr-xhttemplate/edit/process/cust_pkg.cgi42
-rw-r--r--httemplate/edit/process/cust_pkg_detail.html59
-rwxr-xr-xhttemplate/edit/process/cust_refund.cgi56
-rw-r--r--httemplate/edit/process/cust_svc.cgi30
-rwxr-xr-xhttemplate/edit/process/domain_record.cgi30
-rw-r--r--httemplate/edit/process/elements/ApplicationCommon.html77
-rw-r--r--httemplate/edit/process/elements/process.html268
-rw-r--r--httemplate/edit/process/elements/svc_Common.html15
-rw-r--r--httemplate/edit/process/generic.cgi77
-rw-r--r--httemplate/edit/process/inventory_class.html11
-rw-r--r--httemplate/edit/process/invoice_logo.html25
-rw-r--r--httemplate/edit/process/invoice_template.html15
-rw-r--r--httemplate/edit/process/msgcat.cgi22
-rwxr-xr-xhttemplate/edit/process/part_bill_event.cgi106
-rw-r--r--httemplate/edit/process/part_event.html86
-rw-r--r--httemplate/edit/process/part_export.cgi41
-rwxr-xr-xhttemplate/edit/process/part_pkg.cgi198
-rw-r--r--httemplate/edit/process/part_pkg_taxclass.html53
-rwxr-xr-xhttemplate/edit/process/part_referral.html12
-rwxr-xr-xhttemplate/edit/process/part_svc.cgi9
-rw-r--r--httemplate/edit/process/payment_gateway.html35
-rw-r--r--httemplate/edit/process/pkg_category.html11
-rw-r--r--httemplate/edit/process/pkg_class.html11
-rw-r--r--httemplate/edit/process/prepay_credit.cgi62
-rw-r--r--httemplate/edit/process/quick-charge.cgi68
-rw-r--r--httemplate/edit/process/quick-cust_pkg.cgi63
-rwxr-xr-xhttemplate/edit/process/rate.cgi9
-rw-r--r--httemplate/edit/process/rate_detail.html13
-rwxr-xr-xhttemplate/edit/process/rate_region.cgi57
-rw-r--r--httemplate/edit/process/reason.html12
-rw-r--r--httemplate/edit/process/reason_type.html12
-rw-r--r--httemplate/edit/process/reg_code.cgi45
-rw-r--r--httemplate/edit/process/router.cgi20
-rw-r--r--httemplate/edit/process/svc_Common.html16
-rwxr-xr-xhttemplate/edit/process/svc_acct.cgi64
-rwxr-xr-xhttemplate/edit/process/svc_acct_pop.cgi33
-rw-r--r--httemplate/edit/process/svc_broadband.cgi8
-rwxr-xr-xhttemplate/edit/process/svc_domain.cgi33
-rwxr-xr-xhttemplate/edit/process/svc_external.cgi31
-rwxr-xr-xhttemplate/edit/process/svc_forward.cgi31
-rw-r--r--httemplate/edit/process/svc_phone.html10
-rw-r--r--httemplate/edit/process/svc_www.cgi38
-rw-r--r--httemplate/edit/process/tax_class.html49
-rw-r--r--httemplate/edit/process/tax_rate.html22
-rw-r--r--httemplate/edit/process/usage_class.html11
71 files changed, 3029 insertions, 0 deletions
diff --git a/httemplate/edit/process/REAL_cust_pkg.cgi b/httemplate/edit/process/REAL_cust_pkg.cgi
new file mode 100755
index 000000000..ebcb7e4ba
--- /dev/null
+++ b/httemplate/edit/process/REAL_cust_pkg.cgi
@@ -0,0 +1,36 @@
+%if ( $error ) {
+% $cgi->param('error', $error);
+<% $cgi->redirect(popurl(2). "REAL_cust_pkg.cgi?". $cgi->query_string ) %>
+%} else {
+% my $custnum = $new->custnum;
+<% $cgi->redirect(popurl(3). "view/cust_main.cgi?$custnum#cust_pkg$pkgnum" ) %>
+%}
+<%init>
+
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('Edit customer package dates');
+
+my $pkgnum = $cgi->param('pkgnum') or die;
+my $old = qsearchs('cust_pkg',{'pkgnum'=>$pkgnum});
+my %hash = $old->hash;
+$hash{'setup'} = $cgi->param('setup') ? str2time($cgi->param('setup')) : '';
+$hash{'bill'} = $cgi->param('bill') ? str2time($cgi->param('bill')) : '';
+$hash{'last_bill'} =
+ $cgi->param('last_bill') ? str2time($cgi->param('last_bill')) : '';
+$hash{'adjourn'} = $cgi->param('adjourn') ? str2time($cgi->param('adjourn')) : '';
+$hash{'expire'} = $cgi->param('expire') ? str2time($cgi->param('expire')) : '';
+
+my $new;
+my $error;
+if ( $hash{'bill'} != $old->bill # if the next bill date was changed
+ && $hash{'bill'} < time # to a date in the past
+ && ! $cgi->param('bill_areyousure') # and it wasn't confirmed
+ )
+{
+ $error = '_bill_areyousure';
+} else {
+ $new = new FS::cust_pkg \%hash;
+ $error = $new->replace($old);
+}
+
+</%init>
diff --git a/httemplate/edit/process/access_group.html b/httemplate/edit/process/access_group.html
new file mode 100644
index 000000000..ab25cb3a2
--- /dev/null
+++ b/httemplate/edit/process/access_group.html
@@ -0,0 +1,28 @@
+% if ( $conf->exists('disable_acl_changes') ) {
+ ACL changes disabled in public demo.
+% } else {
+<% include( 'elements/process.html',
+ 'table' => 'access_group',
+ 'viewall_dir' => 'browse',
+ 'process_m2m' => { 'link_table' => 'access_groupagent',
+ 'target_table' => 'agent',
+ },
+ 'process_m2name' => {
+ 'link_table' => 'access_right',
+ 'link_static' => { 'righttype' => 'FS::access_group', },
+ 'num_col' => 'rightobjnum',
+ 'name_col' => 'rightname',
+ 'names_list' => [ FS::AccessRight->rights() ],
+ 'param_style' => 'link_table.value checkboxes',
+ },
+ )
+%>
+% }
+<%init>
+
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('Configuration');
+
+my $conf = new FS::Conf;
+
+</%init>
diff --git a/httemplate/edit/process/access_user.html b/httemplate/edit/process/access_user.html
new file mode 100644
index 000000000..ca6bb603f
--- /dev/null
+++ b/httemplate/edit/process/access_user.html
@@ -0,0 +1,21 @@
+% if ( $cgi->param('_password') ne $cgi->param('_password2') ) {
+% $cgi->param('error', "The passwords do not match");
+% print $cgi->redirect(popurl(2) . "access_user.html?" . $cgi->query_string);
+% } else {
+<% include( 'elements/process.html',
+ 'table' => 'access_user',
+ 'viewall_dir' => 'browse',
+ 'copy_on_empty' => [ '_password' ],
+ 'clear_on_error' => [ '_password', '_password2' ],
+ 'process_m2m' => { 'link_table' => 'access_usergroup',
+ 'target_table' => 'access_group',
+ },
+ )
+%>
+% }
+<%init>
+
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('Configuration');
+
+</%init>
diff --git a/httemplate/edit/process/addr_block/add.cgi b/httemplate/edit/process/addr_block/add.cgi
new file mode 100755
index 000000000..39d6348ce
--- /dev/null
+++ b/httemplate/edit/process/addr_block/add.cgi
@@ -0,0 +1,20 @@
+<% include( '../elements/process.html',
+ 'table' => 'addr_block',
+ 'redirect' => popurl(4). 'browse/addr_block.cgi?dummy=',
+ 'error_redirect' => popurl(4). 'browse/addr_block.cgi?',
+ 'agent_virt' => 1,
+ 'agent_null_right' => 'Broadband global configuration',
+
+ )
+%>
+<%init>
+
+my $curuser = $FS::CurrentUser::CurrentUser;
+die "access denied"
+ unless $curuser->access_right('Broadband configuration')
+ || $curuser->access_right('Broadband global configuration');
+
+$cgi->param('routernum', 0) # in FS::addr_block::check instead?
+ unless $cgi->param('routernum');
+
+</%init>
diff --git a/httemplate/edit/process/addr_block/allocate.cgi b/httemplate/edit/process/addr_block/allocate.cgi
new file mode 100755
index 000000000..40d04b369
--- /dev/null
+++ b/httemplate/edit/process/addr_block/allocate.cgi
@@ -0,0 +1,16 @@
+<% include( '../elements/process.html',
+ 'table' => 'addr_block',
+ 'copy_on_empty' => [ fields 'addr_block' ],
+ 'error_redirect' => popurl(3). 'allocate.html?',
+ 'popup_reload' => 'Block allocated',
+ )
+%>
+<%init>
+
+my $conf = new FS::Conf;
+my $curuser = $FS::CurrentUser::CurrentUser;
+die "access denied"
+ unless $curuser->access_right('Broadband configuration')
+ || $curuser->access_right('Broadband global configuration');
+
+</%init>
diff --git a/httemplate/edit/process/addr_block/deallocate.cgi b/httemplate/edit/process/addr_block/deallocate.cgi
new file mode 100755
index 000000000..128824ec7
--- /dev/null
+++ b/httemplate/edit/process/addr_block/deallocate.cgi
@@ -0,0 +1,20 @@
+<% include( '../elements/process.html',
+ 'table' => 'addr_block',
+ 'copy_on_empty' => [ grep { $_ ne 'routernum' }
+ fields 'addr_block' ],
+ 'redirect' => popurl(4). 'browse/addr_block.cgi?',
+ 'error_redirect' => popurl(4). 'browse/addr_block.cgi?',
+ 'agent_virt' => 1,
+ 'agent_null_right' => 'Broadband global configuration',
+ )
+%>
+<%init>
+
+my $conf = new FS::Conf;
+my $curuser = $FS::CurrentUser::CurrentUser;
+die "access denied"
+ unless $curuser->access_right('Broadband configuration')
+ || $curuser->access_right('Broadband global configuration');
+
+$cgi->param('routernum', 0); # just to be explicit about what we are doing
+</%init>
diff --git a/httemplate/edit/process/addr_block/manual_flag.cgi b/httemplate/edit/process/addr_block/manual_flag.cgi
new file mode 100755
index 000000000..dc0cbbbf5
--- /dev/null
+++ b/httemplate/edit/process/addr_block/manual_flag.cgi
@@ -0,0 +1,30 @@
+<% $cgi->redirect(popurl(4). "browse/addr_block.cgi?". $cgi->query_string ) %>
+<%init>
+
+my $curuser = $FS::CurrentUser::CurrentUser;
+
+die "access denied"
+ unless $curuser->access_right('Broadband configuration')
+ || $curuser->access_right('Broadband global configuration');
+
+my $error = '';
+$cgi->param('blocknum') =~ /^(\d+)$/ or die "invalid blocknum";
+my $blocknum = $1;
+
+my $addr_block = qsearchs({ 'table' => 'addr_block',
+ 'hashref' => { blocknum => $blocknum },
+ 'extra_sql' => ' AND '. $curuser->agentnums_sql(
+ 'null_right' => 'Broadband global configuration'
+ ),
+ })
+ or $error = "Unknown blocknum: $blocknum";
+
+$addr_block->manual_flag($cgi->param('manual_flag'))
+ unless $error;
+
+$error ||= $addr_block->replace;
+
+$cgi->param('error', $error)
+ if $error;
+
+</%init>
diff --git a/httemplate/edit/process/addr_block/split.cgi b/httemplate/edit/process/addr_block/split.cgi
new file mode 100755
index 000000000..045fd30de
--- /dev/null
+++ b/httemplate/edit/process/addr_block/split.cgi
@@ -0,0 +1,27 @@
+<% $cgi->redirect(popurl(4). "browse/addr_block.cgi?". $cgi->query_string ) %>
+<%init>
+
+my $curuser = $FS::CurrentUser::CurrentUser;
+
+die "access denied"
+ unless $curuser->access_right('Broadband configuration')
+ || $curuser->access_right('Broadband global configuration');
+
+my $error = '';
+$cgi->param('blocknum') =~ /^(\d+)$/ or die "invalid blocknum";
+my $blocknum = $1;
+
+my $addr_block = qsearchs({ 'table' => 'addr_block',
+ 'hashref' => { blocknum => $blocknum },
+ 'extra_sql' => ' AND '. $curuser->agentnums_sql(
+ 'null_right' => 'Broadband global configuration'
+ ),
+ })
+ or $error = "Unknown blocknum: $blocknum";
+
+$error ||= $addr_block->split_block;
+
+$cgi->param('error', $error)
+ if $error;
+
+</%init>
diff --git a/httemplate/edit/process/agent.cgi b/httemplate/edit/process/agent.cgi
new file mode 100755
index 000000000..3cdf40c9b
--- /dev/null
+++ b/httemplate/edit/process/agent.cgi
@@ -0,0 +1,16 @@
+<% include( 'elements/process.html',
+ 'table' => 'agent',
+ 'viewall_dir' => 'browse',
+ 'viewall_ext' => 'cgi',
+ 'process_m2m' => { 'link_table' => 'access_groupagent',
+ 'target_table' => 'access_group',
+ },
+ 'edit_ext' => 'cgi',
+ )
+%>
+<%init>
+
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('Configuration');
+
+</%init>
diff --git a/httemplate/edit/process/agent_payment_gateway.html b/httemplate/edit/process/agent_payment_gateway.html
new file mode 100644
index 000000000..5b5fd948a
--- /dev/null
+++ b/httemplate/edit/process/agent_payment_gateway.html
@@ -0,0 +1,29 @@
+<% $cgi->redirect(popurl(3). "browse/agent.cgi") %>
+<%init>
+
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('Configuration');
+
+$cgi->param('agentnum') =~ /(\d+)$/ or die "illegal agentnum";
+my $agent = qsearchs('agent', { 'agentnum' => $1 } );
+die "agentnum $1 not found" unless $agent;
+
+#my $old
+
+my @new = map {
+ my $cardtype = $_;
+ new FS::agent_payment_gateway {
+ ( map { $_ => scalar($cgi->param($_)) }
+ fields('agent_payment_gateway')
+ ),
+ 'cardtype' => $cardtype,
+ };
+ }
+ $cgi->param('cardtype');
+
+foreach my $new (@new) {
+ my $error = $new->insert;
+ die $error if $error;
+}
+
+</%init>
diff --git a/httemplate/edit/process/agent_type.cgi b/httemplate/edit/process/agent_type.cgi
new file mode 100755
index 000000000..ad5963b31
--- /dev/null
+++ b/httemplate/edit/process/agent_type.cgi
@@ -0,0 +1,35 @@
+%if ( $error ) {
+% $cgi->param('error', $error);
+<% $cgi->redirect(popurl(2). "agent_type.cgi?". $cgi->query_string ) %>
+%} else {
+<% $cgi->redirect(popurl(3). "browse/agent_type.cgi") %>
+%}
+<%init>
+
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('Configuration');
+
+my $typenum = $cgi->param('typenum');
+my $old = qsearchs('agent_type',{'typenum'=>$typenum}) if $typenum;
+
+my $new = new FS::agent_type ( {
+ map {
+ $_, scalar($cgi->param($_));
+ } fields('agent_type')
+} );
+
+my $error;
+if ( $typenum ) {
+ $error = $new->replace($old);
+} else {
+ $error = $new->insert;
+ $typenum = $new->getfield('typenum');
+}
+
+ $error ||= $new->process_m2m(
+ 'link_table' => 'type_pkgs',
+ 'target_table' => 'part_pkg',
+ 'params' => scalar($cgi->Vars)
+ );
+
+</%init>
diff --git a/httemplate/edit/process/bulk-cust_main_county.html b/httemplate/edit/process/bulk-cust_main_county.html
new file mode 100644
index 000000000..e05192e96
--- /dev/null
+++ b/httemplate/edit/process/bulk-cust_main_county.html
@@ -0,0 +1,63 @@
+% if ( $error ) { #better to redirect back to
+%# <% $cgi->redirect("$url?". $cgi->query_string ) %>
+ <% include('/elements/header-popup.html', 'Error adding taxes' ) %>
+
+ <FONT SIZE="+1" COLOR="#ff0000">Error: <% $error |h %></FONT>
+ <BR><BR>
+
+ </BODY>
+ </HTML>
+
+% } else {
+ <% include('/elements/header-popup.html', 'Taxes added') %>
+
+ <SCRIPT TYPE="text/javascript">
+ window.top.location.reload();
+ </SCRIPT>
+
+ </BODY>
+ </HTML>
+% }
+<%init>
+
+$cgi->param('taxnum') =~ /^([\d,]+)$/
+ or die 'Guru Meditation #69'; #??? should have been passed in
+my @taxnum = split(',', $1);
+
+my $error = '';
+foreach my $taxnum ( @taxnum ) {
+
+ my $cust_main_county = qsearchs('cust_main_county', { 'taxnum' => $taxnum } )
+ or die "unknown taxnum: $taxnum";
+
+ if ( $cust_main_county->tax == 0 ) { #let's replace
+
+ foreach (qw( taxname tax exempt_amount setuptax recurtax )) {
+ $cust_main_county->set( $_ => scalar($cgi->param($_)) )
+ }
+
+ $error = $cust_main_county->replace and last;
+
+ } else { #let's insert a new record
+
+ my $new =
+ new FS::cust_main_county {
+ ( map { $_ => scalar($cgi->param($_)) }
+ qw( taxname tax exempt_amount setuptax recurtax )
+ ),
+ ( map { $_ => $cust_main_county->get($_) }
+ qw( country state county taxclass )
+ )
+ };
+
+ $error = $new->insert and last;
+
+ }
+
+}
+
+if ( $error ) {
+ $cgi->param('error', $error);
+}
+
+</%init>
diff --git a/httemplate/edit/process/bulk-cust_svc.cgi b/httemplate/edit/process/bulk-cust_svc.cgi
new file mode 100644
index 000000000..313b061ff
--- /dev/null
+++ b/httemplate/edit/process/bulk-cust_svc.cgi
@@ -0,0 +1,9 @@
+<% $server->process %>
+<%init>
+
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('Configuration');
+
+my $server = new FS::UI::Web::JSRPC 'FS::part_svc::process_bulk_cust_svc', $cgi;
+
+</%init>
diff --git a/httemplate/edit/process/change-cust_pkg.html b/httemplate/edit/process/change-cust_pkg.html
new file mode 100644
index 000000000..7356e6122
--- /dev/null
+++ b/httemplate/edit/process/change-cust_pkg.html
@@ -0,0 +1,46 @@
+% if ($error) {
+% $cgi->param('error', $error);
+% $cgi->redirect(popurl(3). 'misc/change_pkg.cgi?'. $cgi->query_string );
+% } else {
+
+ <% header("Package changed") %>
+ <SCRIPT TYPE="text/javascript">
+ window.top.location.reload();
+ </SCRIPT>
+ </BODY>
+ </HTML>
+
+% }
+<%init>
+
+my $curuser = $FS::CurrentUser::CurrentUser;
+
+die "access denied"
+ unless $curuser->access_right('Change customer package');
+
+my $cust_pkg = qsearchs({
+ #'select' => 'cust_pkg.*',
+ 'table' => 'cust_pkg',
+ 'addl_from' => 'LEFT JOIN cust_main USING ( custnum )',
+ 'hashref' => { 'pkgnum' => scalar($cgi->param('pkgnum')), },
+ 'extra_sql' => ' AND '. $curuser->agentnums_sql,
+});
+die 'unknown pkgnum' unless $cust_pkg;
+
+my %change = map { $_ => scalar($cgi->param($_)) }
+ qw( locationnum pkgpart );
+
+if ( $cgi->param('locationnum') == -1 ) {
+ my $cust_location = new FS::cust_location {
+ 'custnum' => $cust_pkg->custnum,
+ map { $_ => scalar($cgi->param($_)) }
+ qw( address1 address2 city county state zip country )
+ };
+ $change{'cust_location'} = $cust_location;
+}
+
+my $pkg_or_error = $cust_pkg->change( \%change );
+
+my $error = ref($pkg_or_error) ? '' : $pkg_or_error;
+
+</%init>
diff --git a/httemplate/edit/process/cust_bill_pay.cgi b/httemplate/edit/process/cust_bill_pay.cgi
new file mode 100755
index 000000000..2845d3233
--- /dev/null
+++ b/httemplate/edit/process/cust_bill_pay.cgi
@@ -0,0 +1,13 @@
+<% include('elements/ApplicationCommon.html',
+ 'error_redirect' => 'cust_bill_pay.cgi',
+ 'src_table' => 'cust_pay',
+ 'src_thing' => 'payment',
+ 'link_table' => 'cust_bill_pay',
+ )
+%>
+<%init>
+
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('Apply payment');
+
+</%init>
diff --git a/httemplate/edit/process/cust_credit.cgi b/httemplate/edit/process/cust_credit.cgi
new file mode 100755
index 000000000..8715ad61e
--- /dev/null
+++ b/httemplate/edit/process/cust_credit.cgi
@@ -0,0 +1,63 @@
+%if ( $error ) {
+% $cgi->param('reasonnum', $reasonnum);
+% $cgi->param('error', $error);
+% $dbh->rollback if $oldAutoCommit;
+%
+<% $cgi->redirect(popurl(2). "cust_credit.cgi?". $cgi->query_string ) %>
+%
+%} else {
+%
+% if ( $cgi->param('apply') eq 'yes' ) {
+% my $cust_main = qsearchs('cust_main', { 'custnum' => $custnum })
+% or die "unknown custnum $custnum";
+% $cust_main->apply_credits;
+% }
+% #print $cgi->redirect(popurl(3). "view/cust_main.cgi?$custnum");
+%
+% $dbh->commit or die $dbh->errstr if $oldAutoCommit;
+%
+<% header('Credit sucessful') %>
+ <SCRIPT TYPE="text/javascript">
+ window.top.location.reload();
+ </SCRIPT>
+
+ </BODY></HTML>
+% }
+<%init>
+
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('Post credit');
+
+$cgi->param('custnum') =~ /^(\d*)$/ or die "Illegal custnum!";
+my $custnum = $1;
+
+$cgi->param('reasonnum') =~ /^(-?\d+)$/ or die "Illegal reasonnum";
+my $reasonnum = $1;
+
+my $oldAutoCommit = $FS::UID::AutoCommit;
+local $FS::UID::AutoCommit = 0;
+my $dbh = dbh;
+
+my $error = '';
+if ($reasonnum == -1) {
+
+ $error = 'Enter a new reason (or select an existing one)'
+ unless $cgi->param('newreasonnum') !~ /^\s*$/;
+ my $reason = new FS::reason({ 'reason_type' => $cgi->param('newreasonnumT'),
+ 'reason' => $cgi->param('newreasonnum'),
+ });
+ $error ||= $reason->insert;
+ $cgi->param('reasonnum', $reason->reasonnum)
+ unless $error;
+}
+
+unless ($error) {
+ my $new = new FS::cust_credit ( {
+ map {
+ $_, scalar($cgi->param($_));
+ } fields('cust_credit')
+ } );
+ $error = $new->insert;
+}
+
+</%init>
diff --git a/httemplate/edit/process/cust_credit_bill.cgi b/httemplate/edit/process/cust_credit_bill.cgi
new file mode 100755
index 000000000..c0f34ae5b
--- /dev/null
+++ b/httemplate/edit/process/cust_credit_bill.cgi
@@ -0,0 +1,13 @@
+<% include('elements/ApplicationCommon.html',
+ 'error_redirect' => 'cust_credit_bill.cgi',
+ 'src_table' => 'cust_credit',
+ 'src_thing' => 'credit',
+ 'link_table' => 'cust_credit_bill',
+ )
+%>
+<%init>
+
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('Apply credit');
+
+</%init>
diff --git a/httemplate/edit/process/cust_credit_refund.cgi b/httemplate/edit/process/cust_credit_refund.cgi
new file mode 100755
index 000000000..88420f8ab
--- /dev/null
+++ b/httemplate/edit/process/cust_credit_refund.cgi
@@ -0,0 +1,13 @@
+<% include('elements/ApplicationCommon.html',
+ 'error_redirect' => 'cust_credit_refund.cgi',
+ 'src_table' => 'cust_credit',
+ 'src_thing' => 'credit',
+ 'link_table' => 'cust_credit_refund',
+ )
+%>
+<%init>
+
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('Apply credit');
+
+</%init>
diff --git a/httemplate/edit/process/cust_main.cgi b/httemplate/edit/process/cust_main.cgi
new file mode 100755
index 000000000..097d38204
--- /dev/null
+++ b/httemplate/edit/process/cust_main.cgi
@@ -0,0 +1,210 @@
+% if ( $error ) {
+% $cgi->param('error', $error);
+%
+<% $cgi->redirect(popurl(2). "cust_main.cgi?". $cgi->query_string ) %>
+%
+% } else {
+%
+<% $cgi->redirect(popurl(3). "view/cust_main.cgi?". $new->custnum) %>
+%
+% }
+<%once>
+
+my $me = '[edit/process/cust_main.cgi]';
+my $DEBUG = 0;
+
+</%once>
+<%init>
+
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('Edit customer');
+
+my $error = '';
+
+#unmunge stuff
+
+$cgi->param('tax','') unless defined $cgi->param('tax');
+
+$cgi->param('refnum', (split(/:/, ($cgi->param('refnum'))[0] ))[0] );
+
+#my $payby = $cgi->param('payby');
+my $payby = $cgi->param('select'); # XXX key
+
+my %noauto = (
+ 'CARD' => 'DCRD',
+ 'CHEK' => 'DCHK',
+);
+$payby = $noauto{$payby}
+ if ! $cgi->param('payauto') && exists $noauto{$payby};
+
+$cgi->param('payby', $payby);
+
+if ( $payby ) {
+ if ( $payby eq 'CHEK' || $payby eq 'DCHK' ) {
+ $cgi->param('payinfo',
+ $cgi->param('payinfo1'). '@'. $cgi->param('payinfo2') );
+ }
+ $cgi->param('paydate',
+ $cgi->param( 'exp_month' ). '-'. $cgi->param( 'exp_year' ) );
+}
+
+my @invoicing_list = split( /\s*\,\s*/, $cgi->param('invoicing_list') );
+push @invoicing_list, 'POST' if $cgi->param('invoicing_list_POST');
+push @invoicing_list, 'FAX' if $cgi->param('invoicing_list_FAX');
+$cgi->param('invoicing_list', join(',', @invoicing_list) );
+
+
+#create new record object
+
+my $new = new FS::cust_main ( {
+ map {
+ $_, scalar($cgi->param($_))
+# } qw(custnum agentnum last first ss company address1 address2 city county
+# state zip daytime night fax payby payinfo paydate payname tax
+# otaker refnum)
+ } fields('cust_main')
+} );
+
+if ( defined($cgi->param('same')) && $cgi->param('same') eq "Y" ) {
+ $new->setfield("ship_$_", '') foreach qw(
+ last first company address1 address2 city county state zip
+ country daytime night fax
+ );
+}
+
+if ( $cgi->param('birthdate') && $cgi->param('birthdate') =~ /^([ 0-9\-\/]{0,10})$/) {
+ my $conf = new FS::Conf;
+ my $format = $conf->config('date_format') || "%m/%d/%Y";
+ my $parser = DateTime::Format::Strptime->new(pattern => $format,
+ time_zone => 'floating',
+ );
+ my $dt = $parser->parse_datetime($1);
+ if ($dt) {
+ $new->setfield('birthdate', $dt->epoch);
+ $cgi->param('birthdate', $dt->epoch);
+ } else {
+# $error ||= $cgi->param('birthdate') . " is an invalid birthdate:" . $parser->errmsg;
+ $error ||= "Invalid birthdate: " . $cgi->param('birthdate') . ".";
+ $cgi->param('birthdate', '');
+ }
+}
+
+$new->setfield('paid', $cgi->param('paid') )
+ if $cgi->param('paid');
+
+#perhaps this stuff should go to cust_main.pm
+my $cust_pkg = '';
+my $svc_acct = '';
+if ( $new->custnum eq '' ) {
+
+ if ( $cgi->param('pkgpart_svcpart') ) {
+ my $x = $cgi->param('pkgpart_svcpart');
+ $x =~ /^(\d+)_(\d+)$/ or die "illegal pkgpart_svcpart $x\n";
+ my($pkgpart, $svcpart) = ($1, $2);
+ #false laziness: copied from FS::cust_pkg::order (which should become a
+ #FS::cust_main method)
+ my(%part_pkg);
+ # generate %part_pkg
+ # $part_pkg{$pkgpart} is true iff $custnum may purchase $pkgpart
+ my $agent = qsearchs('agent',{'agentnum'=> $new->agentnum });
+ #my($type_pkgs);
+ #foreach $type_pkgs ( qsearch('type_pkgs',{'typenum'=> $agent->typenum }) ) {
+ # my($pkgpart)=$type_pkgs->pkgpart;
+ # $part_pkg{$pkgpart}++;
+ #}
+ # $pkgpart_href->{PKGPART} is true iff $custnum may purchase $pkgpart
+ my $pkgpart_href = $agent->pkgpart_hashref;
+ #eslaf
+
+ # this should wind up in FS::cust_pkg!
+ $error ||= "Agent ". $new->agentnum. " (type ". $agent->typenum. ") can't ".
+ "purchase pkgpart ". $pkgpart
+ #unless $part_pkg{ $pkgpart };
+ unless $pkgpart_href->{ $pkgpart };
+
+ $cust_pkg = new FS::cust_pkg ( {
+ #later 'custnum' => $custnum,
+ 'pkgpart' => $pkgpart,
+ } );
+ #$error ||= $cust_pkg->check;
+
+ #$cust_svc = new FS::cust_svc ( { 'svcpart' => $svcpart } );
+
+ #$error ||= $cust_svc->check;
+
+ my %svc_acct = (
+ 'svcpart' => $svcpart,
+ 'username' => $cgi->param('username'),
+ '_password' => $cgi->param('_password'),
+ 'popnum' => $cgi->param('popnum'),
+ );
+ $svc_acct{'domsvc'} = $cgi->param('domsvc')
+ if $cgi->param('domsvc');
+
+ $svc_acct = new FS::svc_acct \%svc_acct;
+
+ #and just in case you were silly
+ $svc_acct->svcpart($svcpart);
+ $svc_acct->username($cgi->param('username'));
+ $svc_acct->_password($cgi->param('_password'));
+ $svc_acct->popnum($cgi->param('popnum'));
+
+ #$error ||= $svc_acct->check;
+
+ } elsif ( $cgi->param('username') ) { #good thing to catch
+ $error = "Can't assign username without a package!";
+ }
+
+ use Tie::RefHash;
+ tie my %hash, 'Tie::RefHash';
+ %hash = ( $cust_pkg => [ $svc_acct ] ) if $cust_pkg;
+ $error ||= $new->insert( \%hash, \@invoicing_list );
+
+ my $conf = new FS::Conf;
+ if ( $conf->exists('backend-realtime') && ! $error ) {
+
+ my $berror = $new->bill
+ || $new->apply_payments_and_credits
+ || $new->collect( 'realtime' => 1 );
+ warn "Warning, error billing during backend-realtime: $berror" if $berror;
+
+ }
+
+} else { #create old record object
+
+ my $old = qsearchs( 'cust_main', { 'custnum' => $new->custnum } );
+ $error ||= "Old record not found!" unless $old;
+ if ( length($old->paycvv) && $new->paycvv =~ /^\s*\*+\s*$/ ) {
+ $new->paycvv($old->paycvv);
+ }
+ if ($new->ss =~ /xx/) {
+ $new->ss($old->ss);
+ }
+ if ($new->stateid =~ /^xxx/) {
+ $new->stateid($old->stateid);
+ }
+ if ($new->payby =~ /^(CARD|DCRD)$/ && $new->payinfo =~ /xx/) {
+ $new->payinfo($old->payinfo);
+ } elsif ($new->payby =~ /^(CHEK|DCHK)$/ && $new->payinfo =~ /xx/) {
+ #fix for #3085 "edit of customer's routing code only surprisingly causes
+ #nothing to happen...
+ # this probably won't do the right thing when we don't have the
+ # public key (can't actually get the real $old->payinfo)
+ my($new_account, $new_aba) = split('@', $new->payinfo);
+ my($old_account, $old_aba) = split('@', $old->payinfo);
+ $new_account = $old_account if $new_account =~ /xx/;
+ $new_aba = $old_aba if $new_aba =~ /xx/;
+ $new->payinfo($new_account.'@'.$new_aba);
+ }
+
+ warn "$me calling $new -> replace( $old, \ @invoicing_list )" if $DEBUG;
+ local($FS::cust_main::DEBUG) = $DEBUG if $DEBUG;
+ local($FS::Record::DEBUG) = $DEBUG if $DEBUG;
+
+ $error ||= $new->replace($old, \@invoicing_list);
+
+ warn "$me returned from replace" if $DEBUG;
+
+}
+
+</%init>
diff --git a/httemplate/edit/process/cust_main_county-collapse.cgi b/httemplate/edit/process/cust_main_county-collapse.cgi
new file mode 100755
index 000000000..a917825ce
--- /dev/null
+++ b/httemplate/edit/process/cust_main_county-collapse.cgi
@@ -0,0 +1,44 @@
+%
+%
+%my($query) = $cgi->keywords;
+%$query =~ /^(\d+)$/ or die "Illegal taxnum!";
+%my $taxnum = $1;
+%my $cust_main_county = qsearchs('cust_main_county', { 'taxnum' => $taxnum } )
+% or die "Unknown taxnum $taxnum";
+%
+%#really should do this in a .pm & start transaction
+%
+%foreach my $delete ( qsearch('cust_main_county', {
+% 'country' => $cust_main_county->country,
+% 'state' => $cust_main_county->state
+% } ) ) {
+%# unless ( qsearch('cust_main',{
+%# 'state' => $cust_main_county->getfield('state'),
+%# 'county' => $cust_main_county->getfield('county'),
+%# 'country' => $cust_main_county->getfield('country'),
+%# } ) ) {
+% my $error = $delete->delete;
+% die $error if $error;
+%# } else {
+% #should really fix the $cust_main record
+%# }
+%
+%}
+%
+%$cust_main_county->taxnum('');
+%$cust_main_county->county('');
+%my $error = $cust_main_county->insert;
+%die $error if $error;
+%
+%print $cgi->redirect(popurl(3). "browse/cust_main_county.cgi");
+%
+%
+<%init>
+
+#this isn't actually linked from anywhere just now, but it will be again soon
+
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('Configuration');
+
+
+</%init>
diff --git a/httemplate/edit/process/cust_main_county-expand.cgi b/httemplate/edit/process/cust_main_county-expand.cgi
new file mode 100755
index 000000000..04533a539
--- /dev/null
+++ b/httemplate/edit/process/cust_main_county-expand.cgi
@@ -0,0 +1,78 @@
+<% include('/elements/header-popup.html', 'Addition successful' ) %>
+
+<SCRIPT TYPE="text/javascript">
+ window.top.location.reload();
+</SCRIPT>
+
+</BODY>
+</HTML>
+<%init>
+
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('Configuration');
+
+$cgi->param('taxnum') =~ /^(\d+)$/ or die "Illegal taxnum!";
+my $taxnum = $1;
+my $cust_main_county = qsearchs('cust_main_county',{'taxnum'=>$taxnum})
+ or die ("Unknown taxnum!");
+
+my @expansion;
+if ( $cgi->param('taxclass') ) {
+ my $sth = dbh->prepare('SELECT taxclass FROM part_pkg_taxclass')
+ or die dbh->errstr;
+ $sth->execute or die $sth->errstr;
+ @expansion = map $_->[0], @{$sth->fetchall_arrayref};
+ die "no taxclasses - add one first" unless @expansion;#XXX better err handling
+} else {
+ @expansion = split /[\n\r]{1,2}/, $cgi->param('expansion');
+
+ #warn scalar(@expansion);
+ #warn "$_: $expansion[$_]\n" foreach (0..$#expansion);
+
+ @expansion=map {
+ unless ( /^\s*([\w \!\@\#\$\%\&\(\)\-\+\;\:\'\"\,\.\?\/\=\[\]]+)\s*$/ ) {
+ $cgi->param('error', "Illegal item in expansion: $_");
+ print $cgi->redirect(popurl(2). "cust_main_county-expand.cgi?". $cgi->query_string );
+ myexit();
+ }
+ $1;
+ } @expansion;
+
+}
+
+foreach ( @expansion) {
+ my(%hash)=$cust_main_county->hash;
+ my($new)=new FS::cust_main_county \%hash;
+ $new->setfield('taxnum','');
+ if ( $cgi->param('taxclass') ) {
+ $new->setfield('taxclass', $_);
+ } elsif ( ! $cust_main_county->state ) {
+ $new->setfield('state',$_);
+ } else {
+ $new->setfield('county',$_);
+ }
+ my $error = $new->insert;
+ die $error if $error;
+}
+
+unless ( qsearch( 'cust_main', {
+ 'state' => $cust_main_county->state,
+ 'county' => $cust_main_county->county,
+ 'country' => $cust_main_county->country,
+ } )
+ || ! @expansion
+) {
+ my $error = $cust_main_county->delete;
+ die $error if $error;
+}
+
+if ( $cgi->param('taxclass') ) {
+ print $cgi->redirect(popurl(3). "browse/cust_main_county.cgi?".
+ 'state='. uri_escape($cust_main_county->state ).';'.
+ 'county='. uri_escape($cust_main_county->county ).';'.
+ 'country='. uri_escape($cust_main_county->country)
+ );
+ myexit;
+}
+
+</%init>
diff --git a/httemplate/edit/process/cust_main_county.html b/httemplate/edit/process/cust_main_county.html
new file mode 100644
index 000000000..cb56166c8
--- /dev/null
+++ b/httemplate/edit/process/cust_main_county.html
@@ -0,0 +1,13 @@
+<% include( 'elements/process.html',
+ 'table' => 'cust_main_county',
+ 'popup_reload' => 'Tax changed', #a popup "parent reload" for now
+ #someday change the individual element and go away instead
+ )
+%>
+<%init>
+
+my $conf = new FS::Conf;
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('Configuration');
+
+</%init>
diff --git a/httemplate/edit/process/cust_main_note.cgi b/httemplate/edit/process/cust_main_note.cgi
new file mode 100755
index 000000000..5127c72d1
--- /dev/null
+++ b/httemplate/edit/process/cust_main_note.cgi
@@ -0,0 +1,54 @@
+%if ($error) {
+% $cgi->param('error', $error);
+<% $cgi->redirect(popurl(2). 'cust_main_note.cgi?'. $cgi->query_string ) %>
+%} else {
+<% header('Note ' . ($notenum ? 'updated' : 'added') ) %>
+ <SCRIPT TYPE="text/javascript">
+ window.top.location.reload();
+ </SCRIPT>
+ </BODY></HTML>
+% }
+<%init>
+
+$cgi->param('custnum') =~ /^(\d+)$/
+ or die "Illegal custnum: ". $cgi->param('custnum');
+my $custnum = $1;
+
+$cgi->param('notenum') =~ /^(\d*)$/
+ or die "Illegal notenum: ". $cgi->param('notenum');
+my $notenum = $1;
+
+my $otaker = $FS::CurrentUser::CurrentUser->name;
+$otaker = $FS::CurrentUser::CurrentUser->username
+ if ($otaker eq "User, Legacy");
+
+my $new = new FS::cust_main_note ( {
+ notenum => $notenum,
+ custnum => $custnum,
+ _date => time,
+ otaker => $otaker,
+ comments => $cgi->param('comment'),
+} );
+
+my $error;
+if ($notenum) {
+
+ die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('Edit customer note');
+
+ my $old = qsearchs('cust_main_note', { 'notenum' => $notenum });
+ $error = "No such note: $notenum" unless $old;
+ unless ($error) {
+ map { $new->$_($old->$_) } ('_date', 'otaker');
+ $error = $new->replace($old);
+ }
+
+} else {
+
+ die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('Add customer note');
+
+ $error = $new->insert;
+}
+
+</%init>
diff --git a/httemplate/edit/process/cust_pay.cgi b/httemplate/edit/process/cust_pay.cgi
new file mode 100755
index 000000000..647f6fc6c
--- /dev/null
+++ b/httemplate/edit/process/cust_pay.cgi
@@ -0,0 +1,55 @@
+%if ($error) {
+% $cgi->param('error', $error);
+<% $cgi->redirect(popurl(2). 'cust_pay.cgi?'. $cgi->query_string ) %>
+%} elsif ( $field eq 'invnum' ) {
+<% $cgi->redirect(popurl(3). "view/cust_bill.cgi?$linknum") %>
+%} elsif ( $field eq 'custnum' ) {
+% if ( $cgi->param('apply') eq 'yes' ) {
+% my $cust_main = qsearchs('cust_main', { 'custnum' => $linknum })
+% or die "unknown custnum $linknum";
+% $cust_main->apply_payments;
+% }
+% if ( $link eq 'popup' ) {
+%
+<% header('Payment entered') %>
+ <SCRIPT TYPE="text/javascript">
+ window.top.location.reload();
+ </SCRIPT>
+
+ </BODY></HTML>
+%
+% } elsif ( $link eq 'custnum' ) {
+<% $cgi->redirect(popurl(3). "view/cust_main.cgi?$linknum") %>
+% } else {
+% die "unknown link $link";
+% }
+%
+%}
+<%init>
+
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('Post payment');
+
+$cgi->param('linknum') =~ /^(\d+)$/
+ or die "Illegal linknum: ". $cgi->param('linknum');
+my $linknum = $1;
+
+$cgi->param('link') =~ /^(custnum|invnum|popup)$/
+ or die "Illegal link: ". $cgi->param('link');
+my $field = my $link = $1;
+$field = 'custnum' if $field eq 'popup';
+
+my $_date = str2time($cgi->param('_date'));
+
+my $new = new FS::cust_pay ( {
+ $field => $linknum,
+ _date => $_date,
+ map {
+ $_, scalar($cgi->param($_));
+ } qw(paid payby payinfo paybatch)
+ #} fields('cust_pay')
+} );
+
+my $error = $new->insert( 'manual' => 1 );
+
+</%init>
diff --git a/httemplate/edit/process/cust_pay_pending.html b/httemplate/edit/process/cust_pay_pending.html
new file mode 100644
index 000000000..1bad6cffe
--- /dev/null
+++ b/httemplate/edit/process/cust_pay_pending.html
@@ -0,0 +1,68 @@
+<% include('/elements/header-popup.html', $title ) %>
+% if ( $error ) {
+ <FONT SIZE="+1" COLOR="#ff0000">Error: <% $error |h %></FONT>
+% } else {
+ <SCRIPT TYPE="text/javascript">
+ window.top.location.reload();
+ </SCRIPT>
+% }
+</BODY>
+</HTML>
+<%init>
+
+my $curuser = $FS::CurrentUser::CurrentUser;
+
+die "access denied"
+ unless $curuser->access_right('Edit customer pending payments');
+
+$cgi->param('action') =~ /^(\w+)$/ or die 'illegal action';
+my $action = $1;
+
+$cgi->param('paypendingnum') =~ /^(\d+)$/ or die 'illegal paypendingnum';
+my $paypendingnum = $1;
+my $cust_pay_pending =
+ qsearchs({
+ 'select' => 'cust_pay_pending.*',
+ 'table' => 'cust_pay_pending',
+ 'addl_from' => 'LEFT JOIN cust_main USING ( custnum )',
+ 'hashref' => { 'paypendingnum' => $paypendingnum },
+ 'extra_sql' => ' AND '. $curuser->agentnums_sql,
+ })
+ or die 'unknown paypendingnum';
+
+my $error;
+my $title;
+if ( $action eq 'delete' ) {
+
+ $error = $cust_pay_pending->delete;
+ if ( $error ) {
+ $title = 'Error deleting pending payment';
+ } else {
+ $title = 'Pending payment deletion sucessful';
+ }
+
+} elsif ( $action eq 'insert_cust_pay' ) {
+
+ $error = $cust_pay_pending->insert_cust_pay;
+ if ( $error ) {
+ $title = 'Error completing pending payment';
+ } else {
+ $title = 'Pending payment completed';
+ }
+
+} elsif ( $action eq 'decline' ) {
+
+ $error = $cust_pay_pending->decline;
+ if ( $error ) {
+ $title = 'Error declining pending payment';
+ } else {
+ $title = 'Pending payment completed (decline)';
+ }
+
+} else {
+
+ die "unknown action $action";
+
+}
+
+</%init>
diff --git a/httemplate/edit/process/cust_pay_refund.cgi b/httemplate/edit/process/cust_pay_refund.cgi
new file mode 100755
index 000000000..2616cad8c
--- /dev/null
+++ b/httemplate/edit/process/cust_pay_refund.cgi
@@ -0,0 +1,13 @@
+<% include('elements/ApplicationCommon.html',
+ 'error_redirect' => 'cust_pay_refund.cgi',
+ 'src_table' => 'cust_pay',
+ 'src_thing' => 'payment',
+ 'link_table' => 'cust_pay_refund',
+ )
+%>
+<%init>
+
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('Apply payment');
+
+</%init>
diff --git a/httemplate/edit/process/cust_pkg.cgi b/httemplate/edit/process/cust_pkg.cgi
new file mode 100755
index 000000000..c564c417e
--- /dev/null
+++ b/httemplate/edit/process/cust_pkg.cgi
@@ -0,0 +1,42 @@
+% if ($error) {
+% $cgi->param('error', $error);
+% $cgi->redirect(popurl(3). 'edit/cust_pkg.cgi?'. $cgi->query_string );
+% } else {
+<% $cgi->redirect(popurl(3). "view/cust_main.cgi?$custnum") %>
+% }
+<%init>
+
+my $curuser = $FS::CurrentUser::CurrentUser;
+
+die "access denied"
+ unless $curuser->access_right('Bulk change customer packages');
+
+my $error = '';
+
+#untaint custnum
+$cgi->param('custnum') =~ /^(\d+)$/;
+my $custnum = $1;
+
+my @remove_pkgnums = map {
+ /^(\d+)$/ or die "Illegal remove_pkg value!";
+ $1;
+} $cgi->param('remove_pkg');
+
+my( $action, $error_redirect ) = ( '', '' );
+my @pkgparts = ();
+
+foreach my $pkgpart ( map /^pkg(\d+)$/ ? $1 : (), $cgi->param ) {
+ if ( $cgi->param("pkg$pkgpart") =~ /^(\d+)$/ ) {
+ my $num_pkgs = $1;
+ while ( $num_pkgs-- ) {
+ push @pkgparts,$pkgpart;
+ }
+ } else {
+ $error = "Illegal quantity";
+ last;
+ }
+}
+
+$error ||= FS::cust_pkg::order($custnum,\@pkgparts,\@remove_pkgnums);
+
+</%init>
diff --git a/httemplate/edit/process/cust_pkg_detail.html b/httemplate/edit/process/cust_pkg_detail.html
new file mode 100644
index 000000000..132ff63c5
--- /dev/null
+++ b/httemplate/edit/process/cust_pkg_detail.html
@@ -0,0 +1,59 @@
+% if ( $error ) {
+<% header('Error') %>
+<FONT COLOR="#ff0000"><B><% $error |h %></B></FONT><BR><BR>
+<CENTER><INPUT TYPE="BUTTON" VALUE="OK" onClick="parent.cClick()"></CENTER>
+</BODY></HTML>
+% } else {
+<% header($action) %>
+ <SCRIPT TYPE="text/javascript">
+ window.top.location.reload();
+ </SCRIPT>
+ </BODY></HTML>
+% }
+<%init>
+
+my %access_right = (
+ 'I' => 'Edit customer package invoice details',
+ 'C' => 'Edit customer package comments',
+);
+
+my %name = (
+ 'I' => 'invoice details',
+ 'C' => 'package comments',
+);
+
+my $curuser = $FS::CurrentUser::CurrentUser;
+
+$cgi->param('detailtype') =~ /^(\w)$/ or die 'illegal detailtype';
+my $detailtype = $1;
+
+my $right = $access_right{$detailtype};
+die "access denied"
+ unless $curuser->access_right($right);
+
+$cgi->param('pkgnum') =~ /^(\d+)$/ or die 'illegal pkgnum';
+my $pkgnum = $1;
+
+my $cust_pkg = qsearchs({
+ 'table' => 'cust_pkg',
+ 'addl_from' => 'LEFT JOIN cust_main USING ( custnum )',
+ 'hashref' => { 'pkgnum' => $pkgnum },
+ 'extra_sql' => ' AND '. $curuser->agentnums_sql,
+});
+
+
+my @orig_details = $cust_pkg->cust_pkg_detail($detailtype);
+
+my $action = ucfirst($name{$detailtype}).
+ ( scalar(@orig_details) ? ' changed ' : ' added ' );
+
+my $param = $cgi->Vars;
+my @details = ();
+for ( my $row = 0; exists($param->{"detail$row"}); $row++ ) {
+ push @details, $param->{"detail$row"}
+ if $param->{"detail$row"} =~ /\S/;
+}
+
+my $error = $cust_pkg->set_cust_pkg_detail($detailtype, @details);
+
+</%init>
diff --git a/httemplate/edit/process/cust_refund.cgi b/httemplate/edit/process/cust_refund.cgi
new file mode 100755
index 000000000..5749e5346
--- /dev/null
+++ b/httemplate/edit/process/cust_refund.cgi
@@ -0,0 +1,56 @@
+%if ( $error ) {
+% $cgi->param('error', $error);
+<% $cgi->redirect(popurl(2). "cust_refund.cgi?". $cgi->query_string ) %>
+%} else {
+%
+% if ( $link eq 'popup' ) {
+%
+<% header('Refund entered') %>
+ <SCRIPT TYPE="text/javascript">
+ window.top.location.reload();
+ </SCRIPT>
+
+ </BODY></HTML>
+% } else {
+<% $cgi->redirect(popurl(3). "view/cust_main.cgi?$custnum") %>
+% }
+%}
+<%init>
+
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('Refund payment')
+ || $FS::CurrentUser::CurrentUser->access_right('Post refund');
+
+$cgi->param('custnum') =~ /^(\d*)$/ or die "Illegal custnum!";
+my $custnum = $1;
+my $cust_main = qsearchs('cust_main', { 'custnum' => $custnum } )
+ or die "unknown custnum $custnum";
+
+my $link = $cgi->param('popup') ? 'popup' : '';
+
+my $error = '';
+if ( $cgi->param('payby') =~ /^(CARD|CHEK)$/ ) {
+ my %options = ();
+ my $bop = $FS::payby::payby2bop{$1};
+ $cgi->param('refund') =~ /^(\d*)(\.\d{2})?$/
+ or die "illegal refund amount ". $cgi->param('refund');
+ my $refund = "$1$2";
+ $cgi->param('paynum') =~ /^(\d*)$/ or die "Illegal paynum!";
+ my $paynum = $1;
+ my $reason = $cgi->param('reason');
+ my $paydate = $cgi->param('exp_year'). '-'. $cgi->param('exp_month'). '-01';
+ $options{'paydate'} = $paydate if $paydate =~ /^\d{2,4}-\d{1,2}-01$/;
+ $error = $cust_main->realtime_refund_bop( $bop, 'amount' => $refund,
+ 'paynum' => $paynum,
+ 'reason' => $reason,
+ %options );
+} else {
+ my $new = new FS::cust_refund ( {
+ map {
+ $_, scalar($cgi->param($_));
+ } fields('cust_refund') #huh? , 'paynum' )
+ } );
+ $error = $new->insert;
+}
+
+</%init>
diff --git a/httemplate/edit/process/cust_svc.cgi b/httemplate/edit/process/cust_svc.cgi
new file mode 100644
index 000000000..e22cbb201
--- /dev/null
+++ b/httemplate/edit/process/cust_svc.cgi
@@ -0,0 +1,30 @@
+%if ( $error ) {
+% errorpage($error);
+%} else {
+% my $svcdb = $new->part_svc->svcdb;
+<% $cgi->redirect(popurl(3). "view/$svcdb.cgi?$svcnum") %>
+%}
+<%init>
+
+die 'access deined'
+ unless $FS::CurrentUser::CurrentUser->access_right('Change customer service');
+
+my $svcnum = $cgi->param('svcnum');
+
+my $old = qsearchs('cust_svc',{'svcnum'=>$svcnum}) if $svcnum;
+
+my $new = new FS::cust_svc ( {
+ map {
+ $_, scalar($cgi->param($_));
+ } fields('cust_svc')
+} );
+
+my $error;
+if ( $svcnum ) {
+ $error=$new->replace($old);
+} else {
+ $error=$new->insert;
+ $svcnum=$new->getfield('svcnum');
+}
+
+</%init>
diff --git a/httemplate/edit/process/domain_record.cgi b/httemplate/edit/process/domain_record.cgi
new file mode 100755
index 000000000..2e427e4fb
--- /dev/null
+++ b/httemplate/edit/process/domain_record.cgi
@@ -0,0 +1,30 @@
+%if ( $error ) {
+% errorpage($error);
+%} else {
+% my $svcnum = $new->svcnum;
+<% $cgi->redirect(popurl(3). "view/svc_domain.cgi?$svcnum") %>
+%}
+<%init>
+
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('Edit domain nameservice');
+
+my $recnum = $cgi->param('recnum');
+
+my $old = qsearchs('agent',{'recnum'=>$recnum}) if $recnum;
+
+my $new = new FS::domain_record ( {
+ map {
+ $_, scalar($cgi->param($_));
+ } fields('domain_record')
+} );
+
+my $error;
+if ( $recnum ) {
+ $error=$new->replace($old);
+} else {
+ $error=$new->insert;
+ $recnum=$new->getfield('recnum');
+}
+
+</%init>
diff --git a/httemplate/edit/process/elements/ApplicationCommon.html b/httemplate/edit/process/elements/ApplicationCommon.html
new file mode 100644
index 000000000..2782dc231
--- /dev/null
+++ b/httemplate/edit/process/elements/ApplicationCommon.html
@@ -0,0 +1,77 @@
+<%doc>
+
+Examples:
+
+ #cust_bill_pay
+ include('elements/ApplicationCommon.html',
+ 'error_redirect' => 'cust_bill_pay.cgi',
+ 'src_table' => 'cust_pay',
+ 'src_thing' => 'payment',
+ 'link_table' => 'cust_bill_pay',
+ )
+
+ #cust_credit_bill
+ include('elements/ApplicationCommon.html',
+ 'error_redirect' => 'cust_credit_bill.cgi',
+ 'src_table' => 'cust_credit',
+ 'src_thing' => 'credit',
+ 'link_table' => 'cust_credit_bill',
+ )
+
+</%doc>
+%if ( $error ) {
+% $cgi->param('error', $error);
+<% $cgi->redirect(popurl(2). $opt{error_redirect}. '?'. $cgi->query_string ) %>
+%} else {
+<% header("$src_thing application$to sucessful") %>
+ <SCRIPT TYPE="text/javascript">
+ window.top.location.reload();
+ </SCRIPT>
+ </BODY>
+ </HTML>
+% }
+<%init>
+
+my %opt = @_;
+
+my $src_thing = ucfirst($opt{'src_thing'});
+my $src_table = $opt{'src_table'};
+my $src_pkey = dbdef->table($src_table)->primary_key;
+
+my $to = $opt{'link_table'} =~ /refund/ ? ' to Refund' : '';
+
+$cgi->param($src_pkey) =~ /^(\d+)$/ or die "Illegal $src_pkey!";
+my $src_pkeyvalue = $1;
+
+my $src = qsearchs($src_table, { $src_pkey => $src_pkeyvalue } )
+ or die "No such $src_pkey: $src_pkeyvalue";
+
+my $cust_main = qsearchs('cust_main', { 'custnum' => $src->custnum } )
+ or die "Bogus $src_thing: not attached to customer";
+
+my $custnum = $cust_main->custnum;
+
+my $new;
+# $new = new FS::cust_refund ( {
+# 'reason' => 'Refunding payment', #enter reason in UI
+# 'refund' => $cgi->param('amount'),
+# 'payby' => 'BILL',
+# #'_date' => $cgi->param('_date'),
+# 'payinfo' => 'Cash', #enter payinfo in UI
+# 'paynum' => $paynum,
+# } );
+#} else {
+
+ my $class = 'FS::'. $opt{link_table};
+
+ $new = $class->new( {
+ map {
+ $_ => scalar($cgi->param($_));
+ } fields($opt{link_table})
+ } );
+
+#}
+
+my $error = $new->insert;
+
+</%init>
diff --git a/httemplate/edit/process/elements/process.html b/httemplate/edit/process/elements/process.html
new file mode 100644
index 000000000..5befdd337
--- /dev/null
+++ b/httemplate/edit/process/elements/process.html
@@ -0,0 +1,268 @@
+<%doc>
+
+Example:
+
+ include( 'elements/process.html',
+
+ ###
+ # required
+ ###
+
+ 'table' => 'tablename',
+
+ #? 'primary_key' => #required when the dbdef doesn't know...???
+ #? 'fields' => [] #""
+
+ ###
+ # optional
+ ###
+
+ 'viewall_dir' => '', #'search' or 'browse', defaults to 'search'
+ 'viewall_ext' => 'html', #'cgi' or 'html', defaults to 'html'
+ OR
+ 'redirect' => 'view/table.cgi?', # value of primary key is appended
+ # (string or coderef returning a string)
+ OR
+ 'popup_reload' => 'Momentary success message', #will reload parent window
+
+ 'error_redirect' => popurl(2).'edit/table.cgi?', #query string appended
+
+ 'edit_ext' => 'html', #defaults to 'html', you might want 'cgi' while the
+ #naming is still inconsistent
+
+ 'copy_on_empty' => [ 'old_field_name', 'another_old_field', ... ],
+
+ 'clear_on_error' => [ 'form_field1', 'form_field2', ... ],
+
+ #pass an arrayref of hashrefs for multiple m2ms or m2names
+ #be certain you incorporate m2m_Common if you see error: param
+
+ 'process_m2m' => { 'link_table' => 'link_table_name',
+ 'target_table' => 'target_table_name',
+ #optional (see m2m_Common::process_m2m), if not specified
+ # all CGI params will be passed)
+ 'params' =>
+ },
+ 'process_m2name' => { 'link_table' => 'link_table_name',
+ 'link_static' => { 'column' => 'value' },
+ 'num_col' => 'column', #if column name is different in
+ #link_table than source_table
+ 'name_col' => 'name_column',
+ 'names_list' => [ 'list', 'names' ],
+
+ 'param_style' => 'link_table.value checkboxes',
+ #or#
+ 'param_style' => 'name_colN values',
+
+
+ },
+
+ #checks CGI params and whatever else before much else runs
+ #return an error string or empty for no error
+ 'precheck_callback' => sub { my( $cgi ) = @_; },
+
+ #supplies arguments to insert() and replace()
+ # for use with tables that are FS::option_Common
+ 'args_callback' => sub { my( $cgi, $object ) = @_; },
+
+ 'debug' => 1, #turns on debugging output
+
+ #agent virtualization
+ 'agent_virt' => 1,
+ 'agent_null_right' => 'Access Right Name',
+
+ )
+
+</%doc>
+%if ( $error ) {
+%
+% my $edit_ext = $opt{'edit_ext'} || 'html';
+% my $url = $opt{'error_redirect'} || popurl(2)."$table.$edit_ext";
+% if ( length($cgi->query_string) > 1920 ) { #stupid IE 2083 URL limit
+%
+% my $session = int(rand(4294967296)); #XXX
+% my $pref = new FS::access_user_pref({
+% 'usernum' => $FS::CurrentUser::CurrentUser->usernum,
+% 'prefname' => "redirect$session",
+% 'prefvalue' => $cgi->query_string,
+% 'expiration' => time + 3600, #1h? 1m?
+% });
+% my $pref_error = $pref->insert;
+% if ( $pref_error ) {
+% die "FATAL: couldn't even set redirect cookie: $pref_error".
+% " attempting to set redirect$session to ". $cgi->query_string."\n";
+% }
+%
+<% $cgi->redirect("$url?redirect=$session") %>
+%
+% } else {
+%
+<% $cgi->redirect("$url?". $cgi->query_string ) %>
+%
+% }
+%
+% #different ways of handling success
+%
+%} elsif ( $opt{'popup_reload'} ) {
+
+ <% include('/elements/header-popup.html', $opt{'popup_reload'} ) %>
+
+ <SCRIPT TYPE="text/javascript">
+ window.top.location.reload();
+ </SCRIPT>
+
+ </BODY>
+ </HTML>
+
+%} else {
+%
+% $opt{'redirect'} = &{$opt{'redirect'}}($cgi, $new)
+% if ref($opt{'redirect'}) eq 'CODE';
+%
+% if ( $opt{'redirect'} ) {
+%
+<% $cgi->redirect( $opt{'redirect'}. $pkeyvalue ) %>
+%
+% } else {
+%
+% my $ext = $opt{'viewall_ext'} || 'html';
+%
+<% $cgi->redirect( popurl(3). ($opt{viewall_dir}||'search'). "/$table.$ext" ) %>
+%
+% }
+%
+%}
+%
+<%init>
+
+my $me = 'process.html:';
+
+my(%opt) = @_;
+
+my $curuser = $FS::CurrentUser::CurrentUser;
+
+my $error = '';
+if ( $opt{'precheck_callback'} ) {
+ $error = &{ $opt{'precheck_callback'} }( $cgi );
+}
+
+#false laziness w/edit.html
+my $table = $opt{'table'};
+my $class = "FS::$table";
+my $pkey = dbdef->table($table)->primary_key; #? $opt{'primary_key'} ||
+my $fields = $opt{'fields'}
+ #|| [ grep { $_ ne $pkey } dbdef->table($table)->columns ];
+ || [ fields($table) ];
+
+my $pkeyvalue = $cgi->param($pkey);
+
+my $old = '';
+if ( $pkeyvalue ) {
+ $old = qsearchs({
+ 'table' => $table,
+ 'hashref' => { $pkey => $pkeyvalue },
+ 'extra_sql' => ( $opt{'agent_virt'}
+ ? ' AND '. $curuser->agentnums_sql(
+ 'null_right' => $opt{'agent_null_right'}
+ )
+ : ''
+ ),
+ });
+}
+
+my %hash =
+ map { my @entry = ( $_ => scalar($cgi->param($_)) );
+ $opt{'value_callback'} ? ( $_ => &{ $opt{'value_callback'} }( @entry ))
+ : ( @entry )
+ } @$fields;
+
+my $new = $class->new( \%hash );
+
+if ($old && exists($opt{'copy_on_empty'})) {
+ foreach my $field (@{$opt{'copy_on_empty'}}) {
+ $new->set($field, $old->get($field))
+ unless scalar($cgi->param($field));
+ }
+}
+
+if ( $opt{'agent_virt'} ) {
+ die "illegal agentnum"
+ unless $curuser->agentnums_href->{$new->agentnum}
+ or $opt{'agent_null_right'}
+ && ! $new->agentnum
+ && $curuser->access_right($opt{'agent_null_right'});
+}
+
+$error ||= $new->check;
+
+my @args = ();
+if ( !$error && $opt{'args_callback'} ) {
+ @args = &{ $opt{'args_callback'} }( $cgi, $new );
+}
+
+if ( !$error && $opt{'debug'} ) {
+ warn "$me updating record in $table table using $class class\n";
+ warn Dumper(\%hash);
+ warn "with args: \n". Dumper(\@args) if @args;
+}
+
+if ( !$error ) {
+ if ( $pkeyvalue ) {
+ $error = $new->replace($old, @args);
+ } else {
+ $error = $new->insert(@args);
+ $pkeyvalue = $new->getfield($pkey);
+ }
+}
+
+if ( !$error && $opt{'process_m2m'} ) {
+
+ my @process_m2m = ref($opt{'process_m2m'}) eq 'ARRAY'
+ ? @{ $opt{'process_m2m'} }
+ : ( $opt{'process_m2m'} );
+
+ foreach my $process_m2m (@process_m2m) {
+
+ $process_m2m->{'params'} ||= scalar($cgi->Vars);
+
+ warn "$me processing m2m:\n". Dumper( %$process_m2m )
+ if $opt{'debug'};
+
+ $error = $new->process_m2m( %$process_m2m );
+ }
+
+}
+
+if ( !$error && $opt{'process_m2name'} ) {
+
+ my @process_m2name = ref($opt{'process_m2name'}) eq 'ARRAY'
+ ? @{ $opt{'process_m2name'} }
+ : ( $opt{'process_m2name'} );
+
+
+ foreach my $process_m2name (@process_m2name) {
+
+ if ( $opt{'debug'} ) {
+ warn "$me processing m2name:\n". Dumper( %{ $process_m2name },
+ 'params' => scalar($cgi->Vars),
+ );
+ }
+
+ $error = $new->process_m2name( %{ $process_m2name },
+ 'params' => scalar($cgi->Vars),
+ );
+ }
+
+}
+
+
+if ( $error ) {
+ $cgi->param('error', $error);
+ if ( $opt{'clear_on_error'} && scalar(@{$opt{'clear_on_error'}}) ) {
+ foreach my $field (@{$opt{'clear_on_error'}}) {
+ $cgi->param($field, '')
+ }
+ }
+}
+
+</%init>
diff --git a/httemplate/edit/process/elements/svc_Common.html b/httemplate/edit/process/elements/svc_Common.html
new file mode 100644
index 000000000..8e8c99a42
--- /dev/null
+++ b/httemplate/edit/process/elements/svc_Common.html
@@ -0,0 +1,15 @@
+%
+%
+% my %opt = @_;
+% my $table = $opt{'table'};
+% $opt{'fields'} ||= [ fields($table) ];
+% push @{ $opt{'fields'} }, qw( pkgnum svcpart );
+%
+%
+<% include( 'process.html',
+ 'edit_ext' => 'cgi',
+ 'redirect' => popurl(3)."view/$table.cgi?",
+ %opt,
+ )
+%>
+
diff --git a/httemplate/edit/process/generic.cgi b/httemplate/edit/process/generic.cgi
new file mode 100644
index 000000000..642876386
--- /dev/null
+++ b/httemplate/edit/process/generic.cgi
@@ -0,0 +1,77 @@
+%if($error) {
+% $cgi->param('error', $error);
+<% $cgi->redirect($redirect_error . '?' . $cgi->query_string) %>
+%} else {
+<% $cgi->redirect($redirect_ok) %>
+%}
+<%doc>
+
+See elements/process.html, newer and somewhat along the same lines,
+though it still makes you setup a process file for the table.
+Perhaps safer, perhaps more of a pain in the ass.
+
+In any case, this is probably pretty deprecated; it is only used by
+part_virtual_field.cgi, and so its ACL is hardcoded to 'Configuration'.
+
+Welcome to generic.cgi.
+
+This script provides a generic edit/process/ backend for simple table
+editing. All it knows how to do is take the values entered into
+the script and insert them into the table specified by $cgi->param('table').
+If there's an existing record with the same primary key, it will be
+replaced. (Deletion will be added in the future.)
+
+Special cgi params for this script:
+table: the name of the table to be edited. The script will die horribly
+ if it can't find the table.
+redirect_ok: URL to be displayed after a successful edit. The value of
+ the record's primary key will be passed as a keyword.
+ Defaults to (freeside root)/view/$table.cgi.
+redirect_error: URL to be displayed if there's an error. The original
+ query string, plus the error message, will be passed.
+ Defaults to $cgi->referer() (i.e. go back where you
+ came from).
+
+</%doc>
+<%init>
+
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('Configuration');
+
+my $error;
+my $p2 = popurl(2);
+my $p3 = popurl(3);
+my $table = $cgi->param('table');
+my $dbdef = dbdef or die "Cannot fetch dbdef!";
+
+my $dbdef_table = $dbdef->table($table) or die "Cannot fetch schema for $table";
+
+my $pkey = $dbdef_table->primary_key or die "Cannot fetch pkey for $table";
+my $pkey_val = $cgi->param($pkey);
+
+
+#warn "new FS::Record ( $table, (hashref) )";
+my $new = FS::Record::new ( "FS::$table", {
+ map { $_, scalar($cgi->param($_)) } fields($table)
+} );
+
+#warn 'created $new of class '.ref($new);
+
+if($pkey_val and (my $old = qsearchs($table, { $pkey, $pkey_val} ))) {
+ # edit
+ $error = $new->replace($old);
+} else {
+ #add
+ $error = $new->insert;
+ $pkey_val = $new->getfield($pkey);
+ # New records usually don't have their primary keys set until after
+ # they've been checked/inserted, so grab the new $pkey_val so we can
+ # redirect to it.
+}
+
+my $redirect_ok = (($cgi->param('redirect_ok')) ?
+ $cgi->param('redirect_ok') : $p3."browse/generic.cgi?$table");
+my $redirect_error = (($cgi->param('redirect_error')) ?
+ $cgi->param('redirect_error') : $cgi->referer());
+
+</%init>
diff --git a/httemplate/edit/process/inventory_class.html b/httemplate/edit/process/inventory_class.html
new file mode 100644
index 000000000..dbf978e72
--- /dev/null
+++ b/httemplate/edit/process/inventory_class.html
@@ -0,0 +1,11 @@
+<% include( 'elements/process.html',
+ 'table' => 'inventory_class',
+ 'viewall_dir' => 'browse',
+ )
+%>
+<%init>
+
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('Configuration');
+
+</%init>
diff --git a/httemplate/edit/process/invoice_logo.html b/httemplate/edit/process/invoice_logo.html
new file mode 100644
index 000000000..524d32542
--- /dev/null
+++ b/httemplate/edit/process/invoice_logo.html
@@ -0,0 +1,25 @@
+<%init>
+
+my $curuser = $FS::CurrentUser::CurrentUser;
+
+die "access denied"
+ unless $curuser->access_right('Configuration');
+
+my $conf = new FS::Conf;
+
+$cgi->param('type') =~ /^(png|eps)$/ or die "illegal type";
+my $type = $1;
+
+$cgi->param('name') =~ /^([^\.\/]*)$/ or die "illegal name";
+my $tname = my $name = $1;
+$tname = "_$tname" if length($tname);
+
+$cgi->param('preview_session') =~ /^(\w*)$/ or die "illegal preview_session";
+my $session = $1;
+my $data = decode_base64( $curuser->option("logo_preview$session") );
+
+$conf->set_binary("logo$name.$type", $data);
+
+$cgi->redirect(popurl(3). "edit/invoice_logo.html?type=$type;name=$name;msg=Logo%20changed");
+
+</%init>
diff --git a/httemplate/edit/process/invoice_template.html b/httemplate/edit/process/invoice_template.html
new file mode 100644
index 000000000..6c9371ad1
--- /dev/null
+++ b/httemplate/edit/process/invoice_template.html
@@ -0,0 +1,15 @@
+<%init>
+
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('Configuration');
+
+my $conf = new FS::Conf;
+
+my $confname = $cgi->param('confname');
+my $value = $cgi->param('value');
+
+$conf->set($confname, $value);
+
+$cgi->redirect(popurl(3). 'browse/invoice_template.html');
+
+</%init>
diff --git a/httemplate/edit/process/msgcat.cgi b/httemplate/edit/process/msgcat.cgi
new file mode 100644
index 000000000..7175fa2b3
--- /dev/null
+++ b/httemplate/edit/process/msgcat.cgi
@@ -0,0 +1,22 @@
+%if ( $error ) {
+% $cgi->param('error',$error);
+<% $cgi->redirect($p. "msgcat.cgi?". $cgi->query_string ) %>
+%} else {
+<% $cgi->redirect(popurl(3). "browse/msgcat.cgi") %>
+%}
+<%init>
+
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('Configuration');
+
+my $error;
+foreach my $param ( grep { /^\d+$/ } $cgi->param ) {
+ my $old = qsearchs('msgcat', { msgnum=>$param } );
+ next if $old->msg eq $cgi->param($param); #no need to update identical records
+ my $new = new FS::msgcat { $old->hash };
+ $new->msg($cgi->param($param));
+ $error = $new->replace($old);
+ last if $error;
+}
+
+</%init>
diff --git a/httemplate/edit/process/part_bill_event.cgi b/httemplate/edit/process/part_bill_event.cgi
new file mode 100755
index 000000000..eb0529bb8
--- /dev/null
+++ b/httemplate/edit/process/part_bill_event.cgi
@@ -0,0 +1,106 @@
+%if ( $error ) {
+% $cgi->param('error', $error);
+<% $cgi->redirect(popurl(2). "part_bill_event.cgi?". $cgi->query_string ) %>
+%} else {
+<% $cgi->redirect(popurl(3)."browse/part_bill_event.cgi") %>
+%}
+<%init>
+
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('Configuration');
+
+my $eventpart = $cgi->param('eventpart');
+
+my $old = qsearchs('part_bill_event',{'eventpart'=>$eventpart}) if $eventpart;
+
+#s/days/seconds/
+$cgi->param('seconds', int( $cgi->param('days') * 86400 ) );
+
+my $error;
+if ( ! $cgi->param('plan_weight_eventcode') ) {
+ $error = "Must select an action";
+} else {
+
+ $cgi->param('plan_weight_eventcode') =~ /^([\w\-]+):(\d+):(.*)$/s
+ or die "illegal plan_weight_eventcode:".
+ $cgi->param('plan_weight_eventcode');
+ $cgi->param('plan', $1);
+ $cgi->param('weight', $2);
+ my $eventcode = $3;
+ my $plandata = '';
+
+ my $rnum;
+ my $rtype;
+ my $reasonm;
+ my $class = '';
+ $class='c' if ($eventcode =~ /cancel/);
+ $class='s' if ($eventcode =~ /suspend/);
+ if ($class) {
+ $cgi->param("${class}reason") =~ /^(-?\d+)$/
+ or $error = "Invalid ${class}reason";
+ $rnum = $1;
+ if ($rnum == -1) {
+ $cgi->param("new${class}reasonT") =~ /^(\d+)$/
+ or $error = "Invalid new${class}reasonT";
+ $rtype = $1;
+ $cgi->param("new${class}reason") =~ /^([\s\w]+)$/
+ or $error = "Invalid new${class}reason";
+ $reasonm = $1;
+ }
+ }
+
+ if ($rnum == -1 && !$error) {
+ my $reason = new FS::reason ({ 'reason' => $reasonm,
+ 'reason_type' => $rtype,
+ });
+ $error = $reason->insert;
+ unless ($error) {
+ $rnum = $reason->reasonnum;
+ $cgi->param("${class}reason", $rnum);
+ $cgi->param("new${class}reason", '');
+ $cgi->param("new${class}reasonT", '');
+ }
+ }
+
+ while ( $eventcode =~ /%%%(\w+)%%%/ ) {
+ my $field = $1;
+ my $value = join(', ', $cgi->param($field) );
+ $cgi->param($field, $value); #in case it errors out
+ $eventcode =~ s/%%%$field%%%/$value/;
+ $plandata .= "$field $value\n";
+ }
+ $cgi->param('eventcode', $eventcode);
+ $cgi->param('plandata', $plandata);
+
+ unless($error) {
+
+ if ( $eventpart ) {
+
+ my $new = new FS::part_bill_event ( {
+ map { $_ => scalar($cgi->param($_)) }
+ fields('part_bill_event'),
+ } );
+ $new->setfield('reason' => $rnum);
+ $error = $new->replace($old);
+
+ } else {
+
+ foreach my $payby ( $cgi->param('payby') ) {
+ my $new = new FS::part_bill_event ( {
+ map { $_ => scalar($cgi->param($_)) }
+ grep { $_ ne 'payby' }
+ fields('part_bill_event')
+ } );
+ $new->setfield('payby' => $payby);
+ $new->setfield('reason' => $rnum );
+ $error = $new->insert;
+ last if $error;
+ }
+
+ }
+
+ }
+
+}
+
+</%init>
diff --git a/httemplate/edit/process/part_event.html b/httemplate/edit/process/part_event.html
new file mode 100644
index 000000000..428025fd1
--- /dev/null
+++ b/httemplate/edit/process/part_event.html
@@ -0,0 +1,86 @@
+<% include( 'elements/process.html',
+ #'debug' => 1,
+ 'table' => 'part_event',
+ 'viewall_dir' => 'browse',
+ 'process_m2name' =>
+ {
+ 'link_table' => 'part_event_condition',
+ 'num_col' => 'eventpart',
+ 'name_col' => 'conditionname',
+ 'names_list' => [ FS::part_event_condition->all_conditionnames() ],
+ 'param_style' => 'name_colN values',
+ 'args_callback' => sub { # FS/FS/m2name_Common.pm
+ my( $object, $prefix, $params, $listref ) = @_;
+ #warn "$object $prefix $params $listref\n";
+
+ my $cond = $object->conditionname;
+
+ my %option_fields = $object->option_fields;
+
+ push @$listref, map {
+ my $field = $_;
+
+ my $cgi_field = "$prefix$cond.$field";
+
+ my $value = $params->{$cgi_field};
+
+ my $info = $option_fields{$_};
+ $info = { label=>$info, type=>'text' }
+ unless ref($info);
+
+ if ( $info->{'type'} =~
+ /^(select|checkbox)-?multiple$/
+ or $info->{'type'} =~ /^select/
+ && $info->{'multiple'}
+ )
+ {
+ #special processing for compound fields
+ $value = { map { $_ => 1 }
+ split(/\0/, $value)
+ };
+ } elsif ( $info->{'type'} eq 'freq' ) {
+ $value .= $params->{$cgi_field.'_units'};
+ }
+
+ #warn "value of $cgi_field is $value\n";
+
+ ( $field => $value );
+ }
+ keys %option_fields;
+ },
+ },
+
+ 'args_callback' => sub {
+
+ my( $cgi, $object ) = @_;
+
+ my $prefix = $object->action.'.';
+
+ map { my $option = $_;
+ #my $value = scalar( $cgi->param( "$prefix$option" ) );
+ my $value = join(',', $cgi->param( "$prefix$option" ) );
+
+ if ( $option eq 'reasonnum' && $value == -1 ) {
+ $value = {
+ 'typenum' => scalar( $cgi->param( "new$prefix${option}T" ) ),
+ 'reason' => scalar( $cgi->param( "new$prefix${option}" ) ),
+ };
+ }
+
+ ( $option => $value );
+ }
+ @{ $object->option_fields_listref };
+
+ },
+
+ 'agent_virt' => 1,
+ 'agent_null_right' => 'Edit global billing events',
+)
+%>
+<%init>
+
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('Edit billing events')
+ || $FS::CurrentUser::CurrentUser->access_right('Edit global billing events');
+
+</%init>
diff --git a/httemplate/edit/process/part_export.cgi b/httemplate/edit/process/part_export.cgi
new file mode 100644
index 000000000..b5f82e892
--- /dev/null
+++ b/httemplate/edit/process/part_export.cgi
@@ -0,0 +1,41 @@
+%if ( $error ) {
+% $cgi->param('error', $error );
+<% $cgi->redirect(popurl(2). "part_export.cgi?". $cgi->query_string ) %>
+%} else {
+<% $cgi->redirect(popurl(3). "browse/part_export.cgi") %>
+%}
+<%init>
+
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('Configuration');
+
+my $exportnum = $cgi->param('exportnum');
+
+my $old = qsearchs('part_export', { 'exportnum'=>$exportnum } ) if $exportnum;
+
+#fixup options
+#warn join('-', split(',',$cgi->param('options')));
+my %options = map {
+ my $value = $cgi->param($_);
+ $value =~ s/\r\n/\n/g; #browsers? (textarea)
+ $_ => $value;
+} split(',', $cgi->param('options'));
+
+my $new = new FS::part_export ( {
+ map {
+ $_, scalar($cgi->param($_));
+ } fields('part_export')
+} );
+
+my $error;
+if ( $exportnum ) {
+ #warn $old;
+ #warn $exportnum;
+ #warn $new->machine;
+ $error = $new->replace($old,\%options);
+} else {
+ $error = $new->insert(\%options);
+# $exportnum = $new->exportnum;
+}
+
+</%init>
diff --git a/httemplate/edit/process/part_pkg.cgi b/httemplate/edit/process/part_pkg.cgi
new file mode 100755
index 000000000..96c5b36b7
--- /dev/null
+++ b/httemplate/edit/process/part_pkg.cgi
@@ -0,0 +1,198 @@
+<% include( 'elements/process.html',
+ #'debug' => 1,
+ 'table' => 'part_pkg',
+ 'agent_virt' => 1,
+ 'agent_null_right' => \@agent_null_right,
+ 'redirect' => $redirect_callback,
+ 'viewall_dir' => 'browse',
+ 'viewall_ext' => 'cgi',
+ 'edit_ext' => 'cgi',
+ 'precheck_callback' => $precheck_callback,
+ 'args_callback' => $args_callback,
+ 'process_m2m' => \@process_m2m,
+ )
+%>
+<%init>
+
+my $customizing = ( ! $cgi->param('pkgpart') && $cgi->param('pkgnum') );
+
+my $curuser = $FS::CurrentUser::CurrentUser;
+
+my $edit_global = 'Edit global package definitions';
+my $customize = 'Customize customer package';
+
+die "access denied"
+ unless $curuser->access_right('Edit package definitions')
+ || $curuser->access_right($edit_global)
+ || ( $customizing && $curuser->access_right($customize) );
+
+my @agent_null_right = ( $edit_global );
+push @agent_null_right, $customize if $customizing;
+
+
+my $precheck_callback = sub {
+ my( $cgi ) = @_;
+
+ my $conf = new FS::Conf;
+
+ foreach (qw( setuptax recurtax disabled )) {
+ $cgi->param($_, '') unless defined $cgi->param($_);
+ }
+
+ return 'Must select a tax class'
+ if $cgi->param('taxclass') eq '(select)';
+
+ my @agents = ();
+ foreach ($cgi->param('agent_type')) {
+ /^(\d+)$/;
+ push @agents, $1 if $1;
+ }
+ return "At least one agent type must be specified."
+ unless scalar(@agents)
+ || ( $cgi->param('clone') && $cgi->param('clone') =~ /^\d+$/ )
+ || ( !$cgi->param('pkgpart') && $conf->exists('agent-defaultpkg') )
+ || $cgi->param('disabled')
+ || $cgi->param('agentnum');
+
+ return '';
+
+};
+
+my $custnum = '';
+
+my $args_callback = sub {
+ my( $cgi, $new ) = @_;
+
+ my @args = ( 'primary_svc' => scalar($cgi->param('pkg_svc_primary')) );
+
+ ##
+ #options
+ ##
+
+ $cgi->param('plan') =~ /^(\w+)$/ or die 'unparsable plan';
+ my $plan = $1;
+
+ tie my %plans, 'Tie::IxHash', %{ FS::part_pkg::plan_info() };
+ my $href = $plans{$plan}->{'fields'};
+
+ my $error = '';
+ my $options = $cgi->param($plan."__OPTIONS");
+ my @options = split(',', $options);
+ my %options =
+ map { my $optionname = $_;
+ my $param = $plan."__$optionname";
+ my $parser = exists($href->{$optionname}{parse})
+ ? $href->{$optionname}{parse}
+ : sub { shift };
+ my $value = join(', ', &$parser($cgi->param($param)));
+ my $check = $href->{$optionname}{check};
+ if ( $check && ! &$check($value) ) {
+ $value = join(', ', $cgi->param($param));
+ $error ||= "Illegal ".
+ ($href->{$optionname}{name}||$optionname). ": $value";
+ }
+ ( $optionname => $value );
+ }
+ @options;
+
+ foreach ( split(',', $cgi->param('taxproductnums') ) ) {
+ my $value = $cgi->param("taxproductnum_$_");
+ $error ||= "Illegal taxproductnum_$_: $value"
+ unless ( $value =~ /^\d*$/ );
+ $options{"usage_taxproductnum_$_"} = $value;
+ }
+
+ $options{$_} = scalar( $cgi->param($_) )
+ for (qw( setup_fee recur_fee ));
+
+ push @args, 'options' => \%options;
+
+ ###
+ #pkg_svc
+ ###
+
+ my %pkg_svc = map { $_ => scalar($cgi->param("pkg_svc$_")) }
+ map { $_->svcpart }
+ qsearch('part_svc', {} );
+
+ push @args, 'pkg_svc' => \%pkg_svc;
+
+ ###
+ # cust_pkg and custnum_ref (inserts only)
+ ###
+ unless ( $cgi->param('pkgpart') ) {
+ push @args, 'cust_pkg' => scalar($cgi->param('pkgnum')),
+ 'custnum_ref' => \$custnum;
+ }
+
+ warn "args: ".join('/', @args). "\n";
+
+ @args;
+
+};
+
+my $redirect_callback = sub {
+ #my( $cgi, $new ) = @_;
+ return '' unless $custnum;
+ popurl(3). "view/cust_main.cgi?keywords=$custnum;dummy=";
+};
+
+#these should probably move to @args above and be processed by part_pkg.pm...
+
+$cgi->param('tax_override') =~ /^([\d,]+)$/;
+my (@tax_overrides) = (grep "$_", split (",", $1));
+
+my @process_m2m = (
+ {
+ 'link_table' => 'part_pkg_taxoverride',
+ 'target_table' => 'tax_class',
+ 'params' => \@tax_overrides,
+ },
+ { 'link_table' => 'part_pkg_link',
+ 'target_table' => 'part_pkg',
+ 'base_field' => 'src_pkgpart',
+ 'target_field' => 'dst_pkgpart',
+ 'hashref' => { 'link_type' => 'bill' },
+ 'params' => [ map $cgi->param($_), grep /^bill_dst_pkgpart/, $cgi->param ],
+ },
+ { 'link_table' => 'part_pkg_link',
+ 'target_table' => 'part_pkg',
+ 'base_field' => 'src_pkgpart',
+ 'target_field' => 'dst_pkgpart',
+ 'hashref' => { 'link_type' => 'svc' },
+ 'params' => [ map $cgi->param($_), grep /^svc_dst_pkgpart/, $cgi->param ],
+ },
+);
+
+foreach my $override_class ($cgi->param) {
+ next unless $override_class =~ /^tax_override_(\w+)$/;
+ my $class = $1;
+
+ my (@tax_overrides) = (grep "$_", split (",", $1))
+ if $cgi->param($override_class) =~ /^([\d,]+)$/;
+
+ push @process_m2m, {
+ 'link_table' => 'part_pkg_taxoverride',
+ 'target_table' => 'tax_class',
+ 'hashref' => { 'usage_class' => $class },
+ 'params' => [ @tax_overrides ],
+ };
+
+}
+
+my $conf = new FS::Conf;
+
+if ( $cgi->param('pkgpart') || ! $conf->exists('agent_defaultpkg') ) {
+ my @agents = ();
+ foreach ($cgi->param('agent_type')) {
+ /^(\d+)$/;
+ push @agents, $1 if $1;
+ }
+ push @process_m2m, {
+ 'link_table' => 'type_pkgs',
+ 'target_table' => 'agent_type',
+ 'params' => \@agents,
+ };
+}
+
+</%init>
diff --git a/httemplate/edit/process/part_pkg_taxclass.html b/httemplate/edit/process/part_pkg_taxclass.html
new file mode 100644
index 000000000..8f149bb94
--- /dev/null
+++ b/httemplate/edit/process/part_pkg_taxclass.html
@@ -0,0 +1,53 @@
+% if ( $error ) {
+% $cgi->param('error', $error);
+<% $cgi->redirect(popurl(2). "part_pkg_taxclass.html?". $cgi->query_string ) %>
+%} else {
+<% $cgi->redirect(popurl(3). "browse/cust_main_county.cgi?taxclass=". uri_escape($part_pkg_taxclass->taxclass) ) %>
+%}
+<%init>
+
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('Configuration');
+
+my $part_pkg_taxclass = new FS::part_pkg_taxclass {
+ 'taxclass' => $cgi->param('taxclass'),
+};
+
+#maybe this whole thing should be in a transaction. at some point, no biggie
+#none of the follow-up stuff will fail unless there's a more serious problem
+#than a hanging record in part_pkg_taxclass...
+
+my $error = $part_pkg_taxclass->insert;
+
+unless ( $error ) {
+ #auto-add the new taxclass to any regions that have taxclasses already
+
+ my $sth = dbh->prepare("
+ SELECT country, state, county FROM cust_main_county
+ WHERE taxclass IS NOT NULL AND taxclass != ''
+ GROUP BY country, state, county
+ ") or die dbh->errstr;
+ $sth->execute or die $sth->errstr;
+
+ while ( my $row = $sth->fetchrow_hashref ) {
+ warn "inserting for $row";
+ my $cust_main_county = new FS::cust_main_county {
+ 'country' => $row->{country},
+ 'state' => $row->{state},
+ 'county' => $row->{county},
+ 'tax' => 0,
+ 'taxclass' => $part_pkg_taxclass->taxclass,
+ #exempt_amount
+ #taxname
+ #setuptax
+ #recurtax
+ };
+ $error = $cust_main_county->insert;
+ #last if $error;
+ die $error if $error;
+ }
+
+
+}
+
+</%init>
diff --git a/httemplate/edit/process/part_referral.html b/httemplate/edit/process/part_referral.html
new file mode 100755
index 000000000..40cbc97bf
--- /dev/null
+++ b/httemplate/edit/process/part_referral.html
@@ -0,0 +1,12 @@
+<% include( 'elements/process.html',
+ 'table' => 'part_referral',
+ 'viewall_dir' => 'browse',
+ )
+%>
+<%init>
+
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('Edit advertising sources')
+ || $FS::CurrentUser::CurrentUser->access_right('Edit global advertising sources');
+
+</%init>
diff --git a/httemplate/edit/process/part_svc.cgi b/httemplate/edit/process/part_svc.cgi
new file mode 100755
index 000000000..65de3fc6c
--- /dev/null
+++ b/httemplate/edit/process/part_svc.cgi
@@ -0,0 +1,9 @@
+<% $server->process %>
+<%init>
+
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('Configuration');
+
+my $server = new FS::UI::Web::JSRPC 'FS::part_svc::process', $cgi;
+
+</%init>
diff --git a/httemplate/edit/process/payment_gateway.html b/httemplate/edit/process/payment_gateway.html
new file mode 100644
index 000000000..b16bc3d27
--- /dev/null
+++ b/httemplate/edit/process/payment_gateway.html
@@ -0,0 +1,35 @@
+%if ( $error ) {
+% $cgi->param('error', $error);
+<% $cgi->redirect(popurl(2). "payment_gateway.html?". $cgi->query_string ) %>
+%} else {
+<% $cgi->redirect(popurl(3). "browse/payment_gateway.html") %>
+%}
+<%init>
+
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('Configuration');
+
+my $gatewaynum = $cgi->param('gatewaynum');
+
+my $old = qsearchs('payment_gateway',{'gatewaynum'=>$gatewaynum}) if $gatewaynum;
+
+my $new = new FS::payment_gateway ( {
+ map {
+ $_, scalar($cgi->param($_));
+ } fields('payment_gateway')
+} );
+
+my @options = split(/\r?\n/, $cgi->param('gateway_options') );
+pop @options
+ if scalar(@options) % 2 && $options[-1] =~ /^\s*$/;
+my %options = @options;
+
+my $error;
+if ( $gatewaynum ) {
+ $error=$new->replace($old, \%options);
+} else {
+ $error=$new->insert(\%options);
+ $gatewaynum=$new->getfield('gatewaynum');
+}
+
+</%init>
diff --git a/httemplate/edit/process/pkg_category.html b/httemplate/edit/process/pkg_category.html
new file mode 100644
index 000000000..50cd5cb29
--- /dev/null
+++ b/httemplate/edit/process/pkg_category.html
@@ -0,0 +1,11 @@
+<% include( 'elements/process.html',
+ 'table' => 'pkg_category',
+ 'viewall_dir' => 'browse',
+ )
+%>
+<%init>
+
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('Configuration');
+
+</%init>
diff --git a/httemplate/edit/process/pkg_class.html b/httemplate/edit/process/pkg_class.html
new file mode 100644
index 000000000..b196df3f7
--- /dev/null
+++ b/httemplate/edit/process/pkg_class.html
@@ -0,0 +1,11 @@
+<% include( 'elements/process.html',
+ 'table' => 'pkg_class',
+ 'viewall_dir' => 'browse',
+ )
+%>
+<%init>
+
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('Configuration');
+
+</%init>
diff --git a/httemplate/edit/process/prepay_credit.cgi b/httemplate/edit/process/prepay_credit.cgi
new file mode 100644
index 000000000..8f2eb2b25
--- /dev/null
+++ b/httemplate/edit/process/prepay_credit.cgi
@@ -0,0 +1,62 @@
+%unless ( ref($error) ) {
+% $cgi->param('error', $error );
+<% $cgi->redirect(popurl(3). "edit/prepay_credit.cgi?". $cgi->query_string ) %>
+% } else {
+
+<% include('/elements/header.html', "$num prepaid cards generated".
+ ( $agent ? ' for '.$agent->agent : '' )
+ )
+%>
+
+<FONT SIZE="+1">
+% foreach my $card ( @$error ) {
+
+ <code><% $card %></code>
+ -
+ <% $hashref->{amount} ? sprintf('$%.2f', $hashref->{amount} ) : '' %>
+ <% $hashref->{amount} && $hashref->{seconds} ? 'and' : '' %>
+ <% $hashref->{seconds} ? duration_exact($hashref->{seconds}) : '' %>
+ <% $hashref->{upbytes} ? FS::UI::bytecount::bytecount_unexact($hashref->{upbytes}) : '' %>
+ <% $hashref->{downbytes} ? FS::UI::bytecount::bytecount_unexact($hashref->{downbytes}) : '' %>
+ <% $hashref->{totalbytes} ? FS::UI::bytecount::bytecount_unexact($hashref->{totalbytes}) : '' %>
+ <br>
+% }
+
+</FONT>
+
+<% include('/elements/footer.html') %>
+
+% }
+<%init>
+
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('Configuration');
+
+my $hashref = {};
+
+my $agent = '';
+if ( $cgi->param('agentnum') =~ /^(\d+)$/ ) {
+ $agent = qsearchs('agent', { 'agentnum' => $hashref->{agentnum}=$1 } );
+}
+
+my $error = '';
+
+my $num = 0;
+if ( $cgi->param('num') =~ /^\s*(\d+)\s*$/ ) {
+ $num = $1;
+} else {
+ $error = 'Illegal number of prepaid cards: '. $cgi->param('num');
+}
+
+$hashref->{amount} = $cgi->param('amount');
+$hashref->{seconds} = $cgi->param('seconds') * $cgi->param('multiplier');
+$hashref->{upbytes} = $cgi->param('upbytes') * $cgi->param('upmultiplier');
+$hashref->{downbytes} = $cgi->param('downbytes') * $cgi->param('downmultiplier');
+$hashref->{totalbytes} = $cgi->param('totalbytes') * $cgi->param('totalmultiplier');
+
+$error ||= FS::prepay_credit::generate( $num,
+ scalar($cgi->param('type')),
+ $hashref
+ );
+
+</%init>
diff --git a/httemplate/edit/process/quick-charge.cgi b/httemplate/edit/process/quick-charge.cgi
new file mode 100644
index 000000000..8fa57ddea
--- /dev/null
+++ b/httemplate/edit/process/quick-charge.cgi
@@ -0,0 +1,68 @@
+% if ( $error ) {
+% $cgi->param('error', $error );
+<% $cgi->redirect($p.'quick-charge.html?'. $cgi->query_string) %>
+% } else {
+<% header("One-time charge added") %>
+ <SCRIPT TYPE="text/javascript">
+ window.top.location.reload();
+ </SCRIPT>
+ </BODY></HTML>
+% }
+<%init>
+
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('One-time charge');
+
+my $error = '';
+my $conf = new FS::conf;
+my $param = $cgi->Vars;
+
+my @description = ();
+for ( my $row = 0; exists($param->{"description$row"}); $row++ ) {
+ push @description, $param->{"description$row"}
+ if ($param->{"description$row"} =~ /\S/);
+}
+
+$param->{"custnum"} =~ /^(\d+)$/
+ or $error .= "Illegal customer number " . $param->{"custnum"} . " ";
+my $custnum = $1;
+
+$param->{"amount"} =~ /^\s*(\d+(\.\d{1,2})?)\s*$/
+ or $error .= "Illegal amount " . $param->{"amount"} . " ";
+my $amount = $1;
+
+my $quantity = 1;
+if ( $cgi->param('quantity') =~ /^\s*(\d+)\s*$/ ) {
+ $quantity = $1;
+}
+
+$param->{'tax_override'} =~ /^\s*([,\d]*)\s*$/
+ or $error .= "Illegal tax override " . $param->{"tax_override"} . " ";
+my $override = $1;
+
+if ( $param->{'taxclass'} eq '(select)' ) {
+ $error .= "Must select a tax class. "
+ unless ($conf->exists('enable_taxproducts') &&
+ ( $override || $param->{taxproductnum} )
+ );
+ $cgi->param('taxclass', '');
+}
+
+unless ( $error ) {
+ my $cust_main = qsearchs('cust_main', { 'custnum' => $custnum } )
+ or $error .= "Unknown customer number $custnum. ";
+
+ $error ||= $cust_main->charge( {
+ 'amount' => $amount,
+ 'quantity' => $quantity,
+ 'pkg' => scalar($cgi->param('pkg')),
+ 'setuptax' => scalar($cgi->param('setuptax')),
+ 'taxclass' => scalar($cgi->param('taxclass')),
+ 'taxproductnum' => scalar($cgi->param('taxproductnum')),
+ 'tax_override' => $override,
+ 'classnum' => scalar($cgi->param('classnum')),
+ 'additional' => \@description,
+ } );
+}
+
+</%init>
diff --git a/httemplate/edit/process/quick-cust_pkg.cgi b/httemplate/edit/process/quick-cust_pkg.cgi
new file mode 100644
index 000000000..9c2474330
--- /dev/null
+++ b/httemplate/edit/process/quick-cust_pkg.cgi
@@ -0,0 +1,63 @@
+%if ($error) {
+% $cgi->param('error', $error);
+<% $cgi->redirect(popurl(3). 'misc/order_pkg.html?'. $cgi->query_string ) %>
+%} else {
+% my $frag = "cust_pkg". $cust_pkg->pkgnum;
+<% header('Package ordered') %>
+ <SCRIPT TYPE="text/javascript">
+ // XXX fancy ajax rebuild table at some point, but a page reload will do for now
+
+ // XXX chop off trailing #target and replace... ?
+ window.top.location = '<% popurl(3). "view/cust_main.cgi?keywords=$custnum;fragment=$frag#$frag" %>';
+
+ </SCRIPT>
+
+ </BODY></HTML>
+%}
+<%init>
+
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('Order customer package');
+
+#untaint custnum (probably not necessary, searching for it is escape enough)
+$cgi->param('custnum') =~ /^(\d+)$/
+ or die 'illegal custnum '. $cgi->param('custnum');
+my $custnum = $1;
+my $cust_main = qsearchs({
+ 'table' => 'cust_main',
+ 'hashref' => { 'custnum' => $custnum },
+ 'extra_sql' => ' AND '. $FS::CurrentUser::CurrentUser->agentnums_sql,
+});
+die 'unknown custnum' unless $cust_main;
+
+#probably not necessary, taken care of by cust_pkg::check
+$cgi->param('pkgpart') =~ /^(\d+)$/
+ or die 'illegal pkgpart '. $cgi->param('pkgpart');
+my $pkgpart = $1;
+$cgi->param('refnum') =~ /^(\d*)$/
+ or die 'illegal refnum '. $cgi->param('refnum');
+my $refnum = $1;
+$cgi->param('locationnum') =~ /^(\-?\d*)$/
+ or die 'illegal locationnum '. $cgi->param('locationnum');
+my $locationnum = $1;
+
+my $cust_pkg = new FS::cust_pkg {
+ 'custnum' => $custnum,
+ 'pkgpart' => $pkgpart,
+ 'refnum' => $refnum,
+ 'locationnum' => $locationnum,
+};
+
+my %opt = ( 'cust_pkg' => $cust_pkg );
+
+if ( $locationnum == -1 ) {
+ my $cust_location = new FS::cust_location {
+ map { $_ => scalar($cgi->param($_)) }
+ qw( custnum address1 address2 city county state zip country )
+ };
+ $opt{'cust_location'} = $cust_location;
+}
+
+my $error = $cust_main->order_pkg( %opt );
+
+</%init>
diff --git a/httemplate/edit/process/rate.cgi b/httemplate/edit/process/rate.cgi
new file mode 100755
index 000000000..48d9322ca
--- /dev/null
+++ b/httemplate/edit/process/rate.cgi
@@ -0,0 +1,9 @@
+<% $server->process %>
+<%init>
+
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('Configuration');
+
+my $server = new FS::UI::Web::JSRPC 'FS::rate::process', $cgi;
+
+</%init>
diff --git a/httemplate/edit/process/rate_detail.html b/httemplate/edit/process/rate_detail.html
new file mode 100644
index 000000000..6200d615f
--- /dev/null
+++ b/httemplate/edit/process/rate_detail.html
@@ -0,0 +1,13 @@
+<% include( 'elements/process.html',
+ 'table' => 'rate_detail',
+ 'popup_reload' => 'Rate changed', #a popup "parent reload" for now
+ #someday change the individual element and go away instead
+ )
+%>
+<%init>
+
+my $conf = new FS::Conf;
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('Configuration');
+
+</%init>
diff --git a/httemplate/edit/process/rate_region.cgi b/httemplate/edit/process/rate_region.cgi
new file mode 100755
index 000000000..882991e9d
--- /dev/null
+++ b/httemplate/edit/process/rate_region.cgi
@@ -0,0 +1,57 @@
+%if ( $error ) {
+% $cgi->param('error', $error);
+<% $cgi->redirect(popurl(2). "rate_region.cgi?". $cgi->query_string ) %>
+%} else {
+<% $cgi->redirect(popurl(3). "browse/rate_region.html") %>
+%}
+<%init>
+
+my $conf = new FS::Conf;
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('Configuration');
+
+my $regionnum = $cgi->param('regionnum');
+
+my $old = qsearchs('rate_region', { 'regionnum' => $regionnum } ) if $regionnum;
+
+my $new = new FS::rate_region ( {
+ map {
+ $_, scalar($cgi->param($_));
+ } ( fields('rate_region') )
+} );
+
+my $countrycode = $cgi->param('countrycode');
+my @npa = split(/\s*,\s*/, $cgi->param('npa'));
+$npa[0] = '' unless @npa;
+my @rate_prefix = map {
+ #my($npa,$nxx) = split('-', $_);
+ s/\D//g;
+ new FS::rate_prefix {
+ 'countrycode' => $countrycode,
+ #'npa' => $npa,
+ #'nxx' => $nxx,
+ 'npa' => $_,
+ }
+ } @npa;
+
+my @dest_detail = map {
+ my $ratenum = $_->ratenum;
+ new FS::rate_detail {
+ 'ratenum' => $ratenum,
+ map { $_ => $cgi->param("$_$ratenum") }
+ qw( min_included min_charge sec_granularity classnum )
+ };
+} qsearch('rate', {} );
+
+
+my $error;
+if ( $regionnum ) {
+ $error = $new->replace($old, 'rate_prefix' => \@rate_prefix,
+ 'dest_detail' => \@dest_detail, );
+} else {
+ $error = $new->insert( 'rate_prefix' => \@rate_prefix,
+ 'dest_detail' => \@dest_detail, );
+ $regionnum = $new->getfield('regionnum');
+}
+
+</%init>
diff --git a/httemplate/edit/process/reason.html b/httemplate/edit/process/reason.html
new file mode 100644
index 000000000..cb79ed254
--- /dev/null
+++ b/httemplate/edit/process/reason.html
@@ -0,0 +1,12 @@
+<% include( 'elements/process.html',
+ 'table' => 'reason',
+ 'redirect' => popurl(3) . 'browse/reason.html?class=' .
+ $cgi->param('class') . '&',
+ )
+%>
+<%init>
+
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('Configuration');
+
+</%init>
diff --git a/httemplate/edit/process/reason_type.html b/httemplate/edit/process/reason_type.html
new file mode 100644
index 000000000..3172b27c4
--- /dev/null
+++ b/httemplate/edit/process/reason_type.html
@@ -0,0 +1,12 @@
+<% include( 'elements/process.html',
+ 'table' => 'reason_type',
+ 'redirect' => popurl(3) . 'browse/reason_type.html?class=' .
+ $cgi->param('class') . '&',
+ )
+%>
+<%init>
+
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('Configuration');
+
+</%init>
diff --git a/httemplate/edit/process/reg_code.cgi b/httemplate/edit/process/reg_code.cgi
new file mode 100644
index 000000000..035e10b90
--- /dev/null
+++ b/httemplate/edit/process/reg_code.cgi
@@ -0,0 +1,45 @@
+%unless ( ref($error) ) {
+% $cgi->param('error'. $error );
+<% $cgi->redirect(popurl(3). "edit/reg_code.cgi?". $cgi->query_string ) %>
+% } else {
+
+<% include("/elements/header.html","$num registration codes generated for ". $agent->agent, menubar(
+ 'View all agents' => popurl(3). 'browse/agent.cgi',
+) ) %>
+
+<PRE><FONT SIZE="+1">
+% foreach my $code ( @$error ) {
+ <% $code %>
+% }
+</FONT></PRE>
+
+<% include('/elements/footer.html') %>
+% }
+<%init>
+
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('Configuration');
+
+$cgi->param('agentnum') =~ /^(\d+)$/
+ or errorpage('illegal agentnum '. $cgi->param('agentnum'));
+my $agentnum = $1;
+my $agent = qsearchs('agent', { 'agentnum' => $agentnum } );
+
+my $error = '';
+
+my $num = 0;
+if ( $cgi->param('num') =~ /^\s*(\d+)\s*$/ ) {
+ $num = $1;
+} else {
+ $error = 'Illegal number of codes: '. $cgi->param('num');
+}
+
+my @pkgparts =
+ map { /^pkgpart(.*)$/; $1 }
+ grep { $cgi->param($_) }
+ grep { /^pkgpart/ }
+ $cgi->param;
+
+$error ||= $agent->generate_reg_codes($num, \@pkgparts);
+
+</%init>
diff --git a/httemplate/edit/process/router.cgi b/httemplate/edit/process/router.cgi
new file mode 100644
index 000000000..3cbb8c58e
--- /dev/null
+++ b/httemplate/edit/process/router.cgi
@@ -0,0 +1,20 @@
+<% include('elements/process.html',
+ 'table' => 'router',
+ 'viewall_dir' => 'browse',
+ 'viewall_ext' => 'cgi',
+ 'edit_ext' => 'cgi',
+ 'process_m2m' => { 'link_table' => 'part_svc_router',
+ 'target_table' => 'part_svc',
+ },
+ 'agent_virt' => 1,
+ 'agent_null_right' => 'Broadband global configuration',
+ )
+%>
+<%init>
+my $curuser = $FS::CurrentUser::CurrentUser;
+
+die "access denied"
+ unless $curuser->access_right('Broadband configuration')
+ || $curuser->access_right('Broadband global configuration');
+
+</%init>
diff --git a/httemplate/edit/process/svc_Common.html b/httemplate/edit/process/svc_Common.html
new file mode 100644
index 000000000..cf5f01f71
--- /dev/null
+++ b/httemplate/edit/process/svc_Common.html
@@ -0,0 +1,16 @@
+<% include( 'elements/svc_Common.html',
+ 'table' => $table,
+ 'redirect' => popurl(3)."view/svc_Common.html?svcdb=$table;svcnum=",
+ 'error_redirect' => popurl(3)."edit/svc_Common.html?svcdb=$table;",
+ )
+%>
+<%init>
+
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('Provision customer service'); #something else more specific?
+
+$cgi->param('svcdb') =~ /^(svc_\w+)$/ or die "unparsable svcdb";
+my $table = $1;
+require "FS/$table.pm";
+
+</%init>
diff --git a/httemplate/edit/process/svc_acct.cgi b/httemplate/edit/process/svc_acct.cgi
new file mode 100755
index 000000000..0a89e253c
--- /dev/null
+++ b/httemplate/edit/process/svc_acct.cgi
@@ -0,0 +1,64 @@
+%if ( $error ) {
+% $cgi->param('error', $error);
+<% $cgi->redirect(popurl(2). "svc_acct.cgi?". $cgi->query_string ) %>
+%} else {
+<% $cgi->redirect(popurl(3). "view/svc_acct.cgi?" . $svcnum ) %>
+%}
+<%init>
+
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('Provision customer service'); #something else more specific?
+
+$cgi->param('svcnum') =~ /^(\d*)$/ or die "Illegal svcnum!";
+my $svcnum = $1;
+
+my $old;
+if ( $svcnum ) {
+ $old = qsearchs('svc_acct', { 'svcnum' => $svcnum } )
+ or die "fatal: can't find account (svcnum $svcnum)!";
+} else {
+ $old = '';
+}
+
+#unmunge popnum
+$cgi->param('popnum', (split(/:/, $cgi->param('popnum') ))[0] );
+
+#unmunge passwd
+if ( $cgi->param('_password') eq '*HIDDEN*' ) {
+ die "fatal: no previous account to recall hidden password from!" unless $old;
+ $cgi->param('_password',$old->getfield('_password'));
+}
+
+#unmunge usergroup
+$cgi->param('usergroup', [ $cgi->param('radius_usergroup') ] );
+
+#unmunge bytecounts
+foreach (map { $_,$_."_threshold" } qw( upbytes downbytes totalbytes )) {
+ $cgi->param($_, FS::UI::bytecount::parse_bytecount($cgi->param($_)) );
+}
+
+my %hash = $svcnum ? $old->hash : ();
+map {
+ $hash{$_} = scalar($cgi->param($_));
+ #} qw(svcnum pkgnum svcpart username _password popnum uid gid finger dir
+ # shell quota slipip)
+ } (fields('svc_acct'), qw ( pkgnum svcpart usergroup ));
+my $new = new FS::svc_acct ( \%hash );
+
+my $error;
+if ( $svcnum ) {
+ foreach (grep { $old->$_ != $new->$_ } qw( seconds upbytes downbytes totalbytes )) {
+ my %hash = map { $_ => $new->$_ }
+ grep { $new->$_ }
+ qw( seconds upbytes downbytes totalbytes );
+
+ $error = $new->set_usage(\%hash); #unoverlimit and trigger radius changes
+ last; #once is enough
+ }
+ $error ||= $new->replace($old);
+} else {
+ $error = $new->insert;
+ $svcnum = $new->svcnum;
+}
+
+</%init>
diff --git a/httemplate/edit/process/svc_acct_pop.cgi b/httemplate/edit/process/svc_acct_pop.cgi
new file mode 100755
index 000000000..6e823a824
--- /dev/null
+++ b/httemplate/edit/process/svc_acct_pop.cgi
@@ -0,0 +1,33 @@
+%if ( $error ) {
+% $cgi->param('error', $error);
+<% $cgi->redirect(popurl(2). "svc_acct_pop.cgi?". $cgi->query_string ) %>
+%} else {
+<% $cgi->redirect(popurl(3). "browse/svc_acct_pop.cgi") %>
+%}
+<%init>
+
+my $curuser = $FS::CurrentUser::CurrentUser;
+
+die "access denied"
+ unless $curuser->access_right('Dialup configuration')
+ || $curuser->access_right('Dialup global configuration');
+
+my $popnum = $cgi->param('popnum');
+
+my $old = qsearchs('svc_acct_pop',{'popnum'=>$popnum}) if $popnum;
+
+my $new = new FS::svc_acct_pop ( {
+ map {
+ $_, scalar($cgi->param($_));
+ } fields('svc_acct_pop')
+} );
+
+my $error = '';
+if ( $popnum ) {
+ $error = $new->replace($old);
+} else {
+ $error = $new->insert;
+ $popnum=$new->getfield('popnum');
+}
+
+</%init>
diff --git a/httemplate/edit/process/svc_broadband.cgi b/httemplate/edit/process/svc_broadband.cgi
new file mode 100644
index 000000000..d5c9820bb
--- /dev/null
+++ b/httemplate/edit/process/svc_broadband.cgi
@@ -0,0 +1,8 @@
+<% include('elements/svc_Common.html', 'table' => 'svc_broadband') %>
+<%init>
+my $curuser = $FS::CurrentUser::CurrentUser;
+
+die "access denied"
+ unless $curuser->access_right('Provision customer service'); #something else more specific?
+
+</%init>
diff --git a/httemplate/edit/process/svc_domain.cgi b/httemplate/edit/process/svc_domain.cgi
new file mode 100755
index 000000000..9993a879e
--- /dev/null
+++ b/httemplate/edit/process/svc_domain.cgi
@@ -0,0 +1,33 @@
+%if ($error) {
+% $cgi->param('error', $error);
+<% $cgi->redirect(popurl(2). "svc_domain.cgi?". $cgi->query_string ) %>
+%} else {
+<% $cgi->redirect(popurl(3). "view/svc_domain.cgi?$svcnum") %>
+%}
+<%init>
+
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('Provision customer service'); #something else more specific?
+
+#remove this to actually test the domains!
+$FS::svc_domain::whois_hack = 1;
+
+$cgi->param('svcnum') =~ /^(\d*)$/ or die "Illegal svcnum!";
+my $svcnum = $1;
+
+my $new = new FS::svc_domain ( {
+ map {
+ $_, scalar($cgi->param($_));
+ #} qw(svcnum pkgnum svcpart domain action purpose)
+ } ( fields('svc_domain'), qw( pkgnum svcpart action purpose ) )
+} );
+
+my $error = '';
+if ($cgi->param('svcnum')) {
+ $error="Can't modify a domain!";
+} else {
+ $error=$new->insert;
+ $svcnum=$new->svcnum;
+}
+
+</%init>
diff --git a/httemplate/edit/process/svc_external.cgi b/httemplate/edit/process/svc_external.cgi
new file mode 100755
index 000000000..673e5a5a0
--- /dev/null
+++ b/httemplate/edit/process/svc_external.cgi
@@ -0,0 +1,31 @@
+%if ($error) {
+% $cgi->param('error', $error);
+<% $cgi->redirect(popurl(2). "svc_external.cgi?". $cgi->query_string ) %>
+%} else {
+<% $cgi->redirect(popurl(3). "view/svc_external.cgi?$svcnum") %>
+%}
+<%init>
+
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('Provision customer service'); #something else more specific?
+
+$cgi->param('svcnum') =~ /^(\d*)$/ or die "Illegal svcnum!";
+my $svcnum =$1;
+
+my $old = qsearchs('svc_external',{'svcnum'=>$svcnum}) if $svcnum;
+
+my $new = new FS::svc_external ( {
+ map {
+ ($_, scalar($cgi->param($_)));
+ } ( fields('svc_external'), qw( pkgnum svcpart ) )
+} );
+
+my $error = '';
+if ( $svcnum ) {
+ $error = $new->replace($old);
+} else {
+ $error = $new->insert;
+ $svcnum = $new->getfield('svcnum');
+}
+
+</%init>
diff --git a/httemplate/edit/process/svc_forward.cgi b/httemplate/edit/process/svc_forward.cgi
new file mode 100755
index 000000000..fffad84d6
--- /dev/null
+++ b/httemplate/edit/process/svc_forward.cgi
@@ -0,0 +1,31 @@
+%if ($error) {
+% $cgi->param('error', $error);
+<% $cgi->redirect(popurl(2). "svc_forward.cgi?". $cgi->query_string ) %>
+%} else {
+<% $cgi->redirect(popurl(3). "view/svc_forward.cgi?$svcnum") %>
+%}
+<%init>
+
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('Provision customer service'); #something else more specific?
+
+$cgi->param('svcnum') =~ /^(\d*)$/ or die "Illegal svcnum!";
+my $svcnum =$1;
+
+my $old = qsearchs('svc_forward',{'svcnum'=>$svcnum}) if $svcnum;
+
+my $new = new FS::svc_forward ( {
+ map {
+ ($_, scalar($cgi->param($_)));
+ } ( fields('svc_forward'), qw( pkgnum svcpart ) )
+} );
+
+my $error = '';
+if ( $svcnum ) {
+ $error = $new->replace($old);
+} else {
+ $error = $new->insert;
+ $svcnum = $new->getfield('svcnum');
+}
+
+</%init>
diff --git a/httemplate/edit/process/svc_phone.html b/httemplate/edit/process/svc_phone.html
new file mode 100644
index 000000000..27a703cdf
--- /dev/null
+++ b/httemplate/edit/process/svc_phone.html
@@ -0,0 +1,10 @@
+<% include( 'elements/svc_Common.html',
+ 'table' => 'svc_phone',
+ )
+%>
+<%init>
+
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('Provision customer service'); #something else more specific?
+
+</%init>
diff --git a/httemplate/edit/process/svc_www.cgi b/httemplate/edit/process/svc_www.cgi
new file mode 100644
index 000000000..f02d25305
--- /dev/null
+++ b/httemplate/edit/process/svc_www.cgi
@@ -0,0 +1,38 @@
+%if ( $error ) {
+% $cgi->param('error', $error);
+<% $cgi->redirect(popurl(2). "svc_www.cgi?". $cgi->query_string ) %>
+%} else {
+<% $cgi->redirect(popurl(3). "view/svc_www.cgi?" . $svcnum ) %>
+%}
+<%init>
+
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('Provision customer service'); #something else more specific?
+
+$cgi->param('svcnum') =~ /^(\d*)$/ or die "Illegal svcnum!";
+my $svcnum = $1;
+
+my $old;
+if ( $svcnum ) {
+ $old = qsearchs('svc_www', { 'svcnum' => $svcnum } )
+ or die "fatal: can't find website (svcnum $svcnum)!";
+} else {
+ $old = '';
+}
+
+my $new = new FS::svc_www ( {
+ map {
+ ($_, scalar($cgi->param($_)));
+ #} qw(svcnum pkgnum svcpart recnum usersvc)
+ } ( fields('svc_www'), qw( pkgnum svcpart ) )
+} );
+
+my $error;
+if ( $svcnum ) {
+ $error = $new->replace($old);
+} else {
+ $error = $new->insert;
+ $svcnum = $new->svcnum;
+}
+
+</%init>
diff --git a/httemplate/edit/process/tax_class.html b/httemplate/edit/process/tax_class.html
new file mode 100644
index 000000000..339c9083e
--- /dev/null
+++ b/httemplate/edit/process/tax_class.html
@@ -0,0 +1,49 @@
+% if ( $error ) {
+% $cgi->param('error', $error);
+<% $cgi->redirect(popurl(2). "tax_class.html?". $cgi->query_string ) %>
+%} else {
+<% $cgi->redirect(popurl(3). "browse/tax_rate.cgi?taxclassnum=". uri_escape($tax_class->taxclassnum) ) %>
+%}
+<%init>
+
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('Configuration');
+
+my $tax_class = new FS::tax_class {
+ 'taxclass' => $cgi->param('taxclass'),
+ 'description' => $cgi->param('description'),
+};
+
+#maybe this whole thing should be in a transaction. at some point, no biggie
+#none of the follow-up stuff will fail unless there's a more serious problem
+#than a hanging record in tax_class...
+
+my $error = $tax_class->insert;
+
+# all of this is highly dubious at the moment
+
+#unless ( $error ) {
+# #auto-add the new taxclass to any regions that have taxclasses already
+#
+# my $sth = dbh->prepare("
+# SELECT geocode FROM tax_rate
+# WHERE taxclass IS NOT NULL AND taxclass != ''
+# GROUP BY geocode
+# ") or die dbh->errstr;
+# $sth->execute or die $sth->errstr;
+#
+# while ( my $row = $sth->fetchrow_hashref ) {
+# warn "inserting for $row";
+# my $cust_main_county = new FS::tax_rate {
+# 'geocode' => $row->{geocode},
+# 'tax' => 0,
+# 'taxclassnum' => $tax_class->taxclassnum,
+# };
+# $error = $cust_main_county->insert;
+# #last if $error;
+# die $error if $error;
+# }
+#
+#}
+
+</%init>
diff --git a/httemplate/edit/process/tax_rate.html b/httemplate/edit/process/tax_rate.html
new file mode 100644
index 000000000..431e54264
--- /dev/null
+++ b/httemplate/edit/process/tax_rate.html
@@ -0,0 +1,22 @@
+<% include( 'elements/process.html',
+ 'table' => 'tax_rate',
+ 'value_callback' => $value_callback,
+ 'popup_reload' => 'Tax changed', #a popup "parent reload" for now
+ #someday change the individual element and go away instead
+ )
+%>
+<%once>
+
+my $value_callback = sub { my ($field, $value) = @_;
+ ($field =~ /^(tax|excessrate|usetax|useexcessrate)$/)
+ ? $value/100
+ : $value
+ };
+</%once>
+<%init>
+
+my $conf = new FS::Conf;
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('Configuration');
+
+</%init>
diff --git a/httemplate/edit/process/usage_class.html b/httemplate/edit/process/usage_class.html
new file mode 100644
index 000000000..cf50cb762
--- /dev/null
+++ b/httemplate/edit/process/usage_class.html
@@ -0,0 +1,11 @@
+<% include( 'elements/process.html',
+ 'table' => 'usage_class',
+ 'viewall_dir' => 'browse',
+ )
+%>
+<%init>
+
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('Configuration');
+
+</%init>