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.html16
-rw-r--r--httemplate/edit/process/access_user.html21
-rwxr-xr-xhttemplate/edit/process/addr_block/add.cgi21
-rwxr-xr-xhttemplate/edit/process/addr_block/allocate.cgi26
-rwxr-xr-xhttemplate/edit/process/addr_block/deallocate.cgi25
-rwxr-xr-xhttemplate/edit/process/addr_block/split.cgi20
-rwxr-xr-xhttemplate/edit/process/agent.cgi30
-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_svc.cgi9
-rwxr-xr-xhttemplate/edit/process/cust_bill_pay.cgi49
-rwxr-xr-xhttemplate/edit/process/cust_credit.cgi63
-rwxr-xr-xhttemplate/edit/process/cust_credit_bill.cgi50
-rwxr-xr-xhttemplate/edit/process/cust_main.cgi203
-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.cgi60
-rwxr-xr-xhttemplate/edit/process/cust_pay.cgi55
-rwxr-xr-xhttemplate/edit/process/cust_pkg.cgi68
-rwxr-xr-xhttemplate/edit/process/cust_refund.cgi43
-rw-r--r--httemplate/edit/process/cust_svc.cgi30
-rwxr-xr-xhttemplate/edit/process/domain_record.cgi30
-rw-r--r--httemplate/edit/process/elements/process.html225
-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.cgi92
-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.cgi113
-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_class.html11
-rw-r--r--httemplate/edit/process/prepay_credit.cgi62
-rw-r--r--httemplate/edit/process/quick-charge.cgi50
-rw-r--r--httemplate/edit/process/quick-cust_pkg.cgi33
-rwxr-xr-xhttemplate/edit/process/rate.cgi9
-rw-r--r--httemplate/edit/process/rate_detail.html13
-rwxr-xr-xhttemplate/edit/process/rate_region.cgi53
-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.cgi70
-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.cgi30
-rw-r--r--httemplate/edit/process/svc_broadband.cgi38
-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
59 files changed, 2546 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..581b50f9e
--- /dev/null
+++ b/httemplate/edit/process/access_group.html
@@ -0,0 +1,16 @@
+<% 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',
+ },
+ )
+%>
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..85780c678
--- /dev/null
+++ b/httemplate/edit/process/addr_block/add.cgi
@@ -0,0 +1,21 @@
+%
+%
+%my $error = '';
+%my $ip_gateway = $cgi->param('ip_gateway');
+%my $ip_netmask = $cgi->param('ip_netmask');
+%
+%my $new = new FS::addr_block {
+% ip_gateway => $ip_gateway,
+% ip_netmask => $ip_netmask,
+% routernum => 0 };
+%
+%$error = $new->insert;
+%
+%if ( $error ) {
+% $cgi->param('error', $error);
+% print $cgi->redirect(popurl(4). "browse/addr_block.cgi?". $cgi->query_string );
+%} else {
+% print $cgi->redirect(popurl(4). "browse/addr_block.cgi");
+%}
+%
+
diff --git a/httemplate/edit/process/addr_block/allocate.cgi b/httemplate/edit/process/addr_block/allocate.cgi
new file mode 100755
index 000000000..a94c0320f
--- /dev/null
+++ b/httemplate/edit/process/addr_block/allocate.cgi
@@ -0,0 +1,26 @@
+%
+%my $error = '';
+%my $blocknum = $cgi->param('blocknum');
+%my $routernum = $cgi->param('routernum');
+%
+%my $addr_block = qsearchs('addr_block', { blocknum => $blocknum });
+%my $router = qsearchs('router', { routernum => $routernum });
+%
+%if($addr_block) {
+% if ($router) {
+% $error = $addr_block->allocate($router);
+% } else {
+% $error = "Cannot find router with routernum $routernum";
+% }
+%} else {
+% $error = "Cannot find block with blocknum $blocknum";
+%}
+%
+%if ( $error ) {
+% $cgi->param('error', $error);
+% print $cgi->redirect(popurl(4). "browse/addr_block.cgi?" . $cgi->query_string);
+%} else {
+% print $cgi->redirect(popurl(4). "browse/addr_block.cgi");
+%}
+%
+
diff --git a/httemplate/edit/process/addr_block/deallocate.cgi b/httemplate/edit/process/addr_block/deallocate.cgi
new file mode 100755
index 000000000..494c19f75
--- /dev/null
+++ b/httemplate/edit/process/addr_block/deallocate.cgi
@@ -0,0 +1,25 @@
+%
+%my $error = '';
+%my $blocknum = $cgi->param('blocknum');
+%
+%my $addr_block = qsearchs('addr_block', { blocknum => $blocknum });
+%
+%if($addr_block) {
+% my $router = $addr_block->router;
+% if ($router) {
+% $error = $addr_block->deallocate($router);
+% } else {
+% $error = "Block is not allocated to a router";
+% }
+%} else {
+% $error = "Cannot find block with blocknum $blocknum";
+%}
+%
+%if ( $error ) {
+% $cgi->param('error', $error);
+% print $cgi->redirect(popurl(4). "browse/addr_block.cgi?" . $cgi->query_string);
+%} else {
+% print $cgi->redirect(popurl(4). "browse/addr_block.cgi");
+%}
+%
+
diff --git a/httemplate/edit/process/addr_block/split.cgi b/httemplate/edit/process/addr_block/split.cgi
new file mode 100755
index 000000000..617c3f8ce
--- /dev/null
+++ b/httemplate/edit/process/addr_block/split.cgi
@@ -0,0 +1,20 @@
+%
+%my $error = '';
+%my $blocknum = $cgi->param('blocknum');
+%my $addr_block = qsearchs('addr_block', { blocknum => $blocknum });
+%
+%if ( $addr_block) {
+% $error = $addr_block->split_block;
+%} else {
+% $error = "Unknown blocknum: $blocknum";
+%}
+%
+%
+%if ( $error ) {
+% $cgi->param('error', $error);
+% print $cgi->redirect(popurl(4). "browse/addr_block.cgi?". $cgi->query_string );
+%} else {
+% print $cgi->redirect(popurl(4). "browse/addr_block.cgi");
+%}
+%
+
diff --git a/httemplate/edit/process/agent.cgi b/httemplate/edit/process/agent.cgi
new file mode 100755
index 000000000..ad550cc37
--- /dev/null
+++ b/httemplate/edit/process/agent.cgi
@@ -0,0 +1,30 @@
+%if ( $error ) {
+% $cgi->param('error', $error);
+<% $cgi->redirect(popurl(2). "agent.cgi?". $cgi->query_string ) %>
+%} else {
+<% $cgi->redirect(popurl(3). "browse/agent.cgi") %>
+%}
+<%init>
+
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('Configuration');
+
+my $agentnum = $cgi->param('agentnum');
+
+my $old = qsearchs('agent',{'agentnum'=>$agentnum}) if $agentnum;
+
+my $new = new FS::agent ( {
+ map {
+ $_, scalar($cgi->param($_));
+ } fields('agent')
+} );
+
+my $error;
+if ( $agentnum ) {
+ $error=$new->replace($old);
+} else {
+ $error=$new->insert;
+ $agentnum=$new->getfield('agentnum');
+}
+
+</%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_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/cust_bill_pay.cgi b/httemplate/edit/process/cust_bill_pay.cgi
new file mode 100755
index 000000000..e2f89f197
--- /dev/null
+++ b/httemplate/edit/process/cust_bill_pay.cgi
@@ -0,0 +1,49 @@
+%if ( $error ) {
+% $cgi->param('error', $error);
+<% $cgi->redirect(popurl(2). "cust_bill_pay.cgi?". $cgi->query_string ) %>
+%} else {
+<% header('Payment application sucessful') %>
+ <SCRIPT TYPE="text/javascript">
+ window.top.location.reload();
+ </SCRIPT>
+ </BODY>
+ </HTML>
+% }
+<%init>
+
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('Apply payment');
+
+$cgi->param('paynum') =~ /^(\d*)$/ or die "Illegal paynum!";
+my $paynum = $1;
+
+my $cust_pay = qsearchs('cust_pay', { 'paynum' => $paynum } )
+ or die "No such paynum";
+
+my $cust_main = qsearchs('cust_main', { 'custnum' => $cust_pay->custnum } )
+ or die "Bogus credit: not attached to customer";
+
+my $custnum = $cust_main->custnum;
+
+my $new;
+if ($cgi->param('invnum') =~ /^Refund$/) {
+ $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 {
+ $new = new FS::cust_bill_pay ( {
+ map {
+ $_, scalar($cgi->param($_));
+ #} qw(custnum _date amount invnum)
+ } fields('cust_bill_pay')
+ } );
+}
+
+my $error = $new->insert;
+
+</%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..17f9fcb35
--- /dev/null
+++ b/httemplate/edit/process/cust_credit_bill.cgi
@@ -0,0 +1,50 @@
+%if ( $error ) {
+% $cgi->param('error', $error);
+<% $cgi->redirect(popurl(2). "cust_credit_bill.cgi?". $cgi->query_string ) %>
+%} else {
+<% header('Credit application sucessful') %>
+ <SCRIPT TYPE="text/javascript">
+ window.top.location.reload();
+ </SCRIPT>
+ </BODY>
+ </HTML>
+% }
+<%init>
+
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('Apply credit');
+
+$cgi->param('crednum') =~ /^(\d*)$/ or die "Illegal crednum!";
+my $crednum = $1;
+
+my $cust_credit = qsearchs('cust_credit', { 'crednum' => $crednum } )
+ or die "No such crednum";
+
+my $cust_main = qsearchs('cust_main', { 'custnum' => $cust_credit->custnum } )
+ or die "Bogus credit: not attached to customer";
+
+my $custnum = $cust_main->custnum;
+
+my $new;
+if ($cgi->param('invnum') =~ /^Refund$/) {
+ $new = new FS::cust_refund ( {
+ 'reason' => ( $cust_credit->reason || 'refund from credit' ),
+ 'refund' => $cgi->param('amount'),
+ 'payby' => 'BILL',
+ #'_date' => $cgi->param('_date'),
+ #'payinfo' => 'Cash',
+ 'payinfo' => 'Refund',
+ 'crednum' => $crednum,
+ } );
+} else {
+ $new = new FS::cust_credit_bill ( {
+ map {
+ $_, scalar($cgi->param($_));
+ #} qw(custnum _date amount invnum)
+ } fields('cust_credit_bill')
+ } );
+}
+
+my $error = $new->insert;
+
+</%init>
diff --git a/httemplate/edit/process/cust_main.cgi b/httemplate/edit/process/cust_main.cgi
new file mode 100755
index 000000000..b0c9e3e57
--- /dev/null
+++ b/httemplate/edit/process/cust_main.cgi
@@ -0,0 +1,203 @@
+% 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')
+} );
+
+ delete( $new->hashref->{'agent_custid'} )
+ unless $new->hashref->{'agent_custid'};
+
+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|CHEK|DCHK)$/ && $new->payinfo =~ /xx/) {
+ $new->payinfo($old->payinfo);
+ }
+
+ 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..758345e8c
--- /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..9689ca6d6
--- /dev/null
+++ b/httemplate/edit/process/cust_main_note.cgi
@@ -0,0 +1,60 @@
+%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">
+ parent.cust_main_notes.location.reload();
+ try{parent.cust_main_notes.cClick()}
+ catch(err){}
+ try{parent.cClick()}
+ catch(err){}
+ </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_pkg.cgi b/httemplate/edit/process/cust_pkg.cgi
new file mode 100755
index 000000000..bdade321f
--- /dev/null
+++ b/httemplate/edit/process/cust_pkg.cgi
@@ -0,0 +1,68 @@
+% if ($error) {
+% $cgi->param('error', $error);
+% $cgi->redirect(popurl(3). $error_redirect. '?'. $cgi->query_string );
+% } elsif ( $action eq 'change' ) {
+
+ <% header("Package changed") %>
+ <SCRIPT TYPE="text/javascript">
+ window.top.location.reload();
+ </SCRIPT>
+ </BODY>
+ </HTML>
+
+% } elsif ( $action eq 'bulk' ) {
+<% $cgi->redirect(popurl(3). "view/cust_main.cgi?$custnum") %>
+% } else {
+% die "guru exception #5: action is neither change nor bulk!";
+% }
+<%init>
+
+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 $curuser = $FS::CurrentUser::CurrentUser;
+
+my( $action, $error_redirect );
+my @pkgparts = ();
+if ( $cgi->param('new_pkgpart') =~ /^(\d+)$/ ) { #came from misc/change_pkg.cgi
+
+ $action = 'change';
+ $error_redirect = "misc/change_pkg.cgi";
+ @pkgparts = ($1);
+
+ die "access denied"
+ unless $curuser->access_right('Change customer package');
+
+} else { #came from edit/cust_pkg.cgi
+
+ $action = 'bulk';
+ $error_redirect = "edit/cust_pkg.cgi";
+
+ die "access denied"
+ unless $curuser->access_right('Bulk change customer packages');
+
+ 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_refund.cgi b/httemplate/edit/process/cust_refund.cgi
new file mode 100755
index 000000000..1a7a394b3
--- /dev/null
+++ b/httemplate/edit/process/cust_refund.cgi
@@ -0,0 +1,43 @@
+%if ( $error ) {
+% $cgi->param('error', $error);
+<% $cgi->redirect(popurl(2). "cust_refund.cgi?". $cgi->query_string ) %>
+%} else {
+<% $cgi->redirect(popurl(3). "view/cust_main.cgi?$custnum") %>
+%}
+<%init>
+
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('Refund payment');
+
+$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 $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 {
+ die 'unimplemented';
+ #my $new = new FS::cust_refund ( {
+ # map {
+ # $_, scalar($cgi->param($_));
+ # } ( fields('cust_refund'), '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/process.html b/httemplate/edit/process/elements/process.html
new file mode 100644
index 000000000..a671ca118
--- /dev/null
+++ b/httemplate/edit/process/elements/process.html
@@ -0,0 +1,225 @@
+<%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'
+ OR
+ 'redirect' => 'view/table.cgi?', # value of primary key is appended
+ 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', ... ],
+
+ 'process_m2m' => { 'link_table' => 'link_table_name',
+ 'target_table' => 'target_table_name',
+ },
+ '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',
+
+
+ },
+
+ #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>
+
+%} elsif ( $opt{'redirect'} ) {
+%
+<% $cgi->redirect( $opt{'redirect'}. $pkeyvalue ) %>
+%
+%} else {
+%
+<% $cgi->redirect( popurl(3). ($opt{viewall_dir}||'search'). "/$table.html" ) %>
+%}
+<%once>
+
+ my $me = 'process.html:';
+
+</%once>
+<%init>
+
+my(%opt) = @_;
+
+my $curuser = $FS::CurrentUser::CurrentUser;
+
+#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 { $_ => scalar($cgi->param($_)) } @$fields;
+
+my $new = $class->new( \%hash );
+
+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'});
+}
+
+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));
+ }
+}
+
+my $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'} ) {
+
+ if ( $opt{'debug'} ) {
+ warn "$me processing m2m:\n". Dumper( %{ $opt{'process_m2m'} },
+ 'params' => scalar($cgi->Vars),
+ );
+ }
+
+ $error = $new->process_m2m( %{ $opt{'process_m2m'} },
+ 'params' => scalar($cgi->Vars),
+ );
+}
+
+if ( !$error && $opt{'process_m2name'} ) {
+
+ if ( $opt{'debug'} ) {
+ warn "$me processing m2name:\n". Dumper( %{ $opt{'process_m2name'} },
+ 'params' => scalar($cgi->Vars),
+ );
+ }
+
+ $error = $new->process_m2name( %{ $opt{'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..757fa94b7
--- /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("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..3534519fd
--- /dev/null
+++ b/httemplate/edit/process/part_bill_event.cgi
@@ -0,0 +1,92 @@
+%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){
+ my $new = new FS::part_bill_event ( {
+ map {
+ $_, scalar($cgi->param($_));
+ } fields('part_bill_event'),
+ } );
+ $new->setfield('reason', $rnum);
+
+ if ( $eventpart ) {
+ $error = $new->replace($old);
+ } else {
+ $error = $new->insert;
+ $eventpart = $new->getfield('eventpart');
+ }
+ }
+}
+
+</%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..36debfce0
--- /dev/null
+++ b/httemplate/edit/process/part_pkg.cgi
@@ -0,0 +1,113 @@
+%if ( $error ) {
+% $dbh->rollback if $oldAutoCommit;
+% $cgi->param('error', $error );
+<% $cgi->redirect(popurl(2). "part_pkg.cgi?". $cgi->query_string ) %>
+%} elsif ( $custnum ) {
+% $dbh->commit or die $dbh->errstr if $oldAutoCommit;
+<% $cgi->redirect(popurl(3). "view/cust_main.cgi?$custnum") %>
+%} else {
+% $dbh->commit or die $dbh->errstr if $oldAutoCommit;
+<% $cgi->redirect(popurl(3). "browse/part_pkg.cgi") %>
+%}
+<%init>
+
+my $dbh = dbh;
+my $conf = new FS::Conf;
+
+my $pkgpart = $cgi->param('pkgpart');
+
+my $old = qsearchs('part_pkg',{'pkgpart'=>$pkgpart}) if $pkgpart;
+
+tie my %plans, 'Tie::IxHash', %{ FS::part_pkg::plan_info() };
+my $href = $plans{$cgi->param('plan')}->{'fields'};
+
+#fixup plandata
+my $error;
+my $plandata = $cgi->param('plandata');
+my @plandata = split(',', $plandata);
+$cgi->param('plandata',
+ join('', map { my $parser = sub { shift };
+ $parser = $href->{$_}{parse} if exists($href->{$_}{parse});
+ my $value = join(', ', &$parser($cgi->param($_)));
+ my $check = $href->{$_}{check};
+ if ( $check && ! &$check($value) ) {
+ $value = join(', ', $cgi->param($_));
+ $error ||= "Illegal ". ($href->{$_}{name}||$_). ": $value";
+ }
+ "$_=$value\n";
+ } @plandata )
+);
+
+foreach (qw( setuptax recurtax disabled )) {
+ $cgi->param($_, '') unless defined $cgi->param($_);
+}
+
+my @agents;
+foreach ($cgi->param('agent_type')) {
+ /^(\d+)$/;
+ push @agents, $1 if $1;
+}
+$error = "At least one agent type must be specified."
+ unless( scalar(@agents) ||
+ $cgi->param('clone') && $cgi->param('clone') =~ /^\d+$/ ||
+ !$pkgpart && $conf->exists('agent-defaultpkg')
+ );
+
+my $new = new FS::part_pkg ( {
+ map {
+ $_ => scalar($cgi->param($_));
+ } fields('part_pkg')
+} );
+
+my $oldAutoCommit = $FS::UID::AutoCommit;
+local $FS::UID::AutoCommit = 0;
+
+my %pkg_svc = map { $_ => scalar($cgi->param("pkg_svc$_")) }
+ map { $_->svcpart }
+ qsearch('part_svc', {} );
+
+my $curuser = $FS::CurrentUser::CurrentUser;
+
+my $custnum = '';
+if ( $error ) {
+
+ # fall through
+
+} elsif ( $cgi->param('taxclass') eq '(select)' ) {
+
+ $error = 'Must select a tax class';
+
+} elsif ( $pkgpart ) {
+
+ die "access denied"
+ unless $curuser->access_right('Edit package definitions')
+ || $curuser->access_right('Edit global package definitions');
+
+ $error = $new->replace( $old,
+ pkg_svc => \%pkg_svc,
+ primary_svc => scalar($cgi->param('pkg_svc_primary')),
+ );
+} else {
+
+ die "access denied"
+ unless $curuser->access_right('Edit package definitions')
+ || $curuser->access_right('Edit global package definitions')
+ || ( $cgi->param('pkgnum') && $curuser->access_right('Customize customer package') );
+
+ $error = $new->insert( pkg_svc => \%pkg_svc,
+ primary_svc => scalar($cgi->param('pkg_svc_primary')),
+ cust_pkg => $cgi->param('pkgnum'),
+ custnum_ref => \$custnum,
+ );
+ $pkgpart = $new->pkgpart;
+}
+
+unless ( $error || $conf->exists('agent_defaultpkg') ) {
+ my $error = $new->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_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..22f96852f
--- /dev/null
+++ b/httemplate/edit/process/quick-charge.cgi
@@ -0,0 +1,50 @@
+% 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 $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;
+
+if ( $param->{'taxclass'} eq '(select)' ) {
+ $error .= "Must select a tax class. ";
+}
+
+unless ( $error ) {
+ my $cust_main = qsearchs('cust_main', { 'custnum' => $custnum } )
+ or $error .= "Unknown customer number $custnum. ";
+
+ $error ||= $cust_main->charge( {
+ 'amount' => $amount,
+ 'pkg' => scalar($cgi->param('pkg')),
+ 'taxclass' => scalar($cgi->param('taxclass')),
+ '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..6b65653c2
--- /dev/null
+++ b/httemplate/edit/process/quick-cust_pkg.cgi
@@ -0,0 +1,33 @@
+%if ($error) {
+% $cgi->param('error', $error);
+<% $cgi->redirect(popurl(2). 'misc/order_pkg.html?'. $cgi->query_string ) %>
+%} else {
+% my $frag = "cust_pkg". $cust_pkg[0]->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
+$cgi->param('custnum') =~ /^(\d+)$/
+ or die 'illegal custnum '. $cgi->param('custnum');
+my $custnum = $1;
+$cgi->param('pkgpart') =~ /^(\d+)$/
+ or die 'illegal pkgpart '. $cgi->param('pkgpart');
+my $pkgpart = $1;
+
+my @cust_pkg = ();
+my $error = FS::cust_pkg::order($custnum, [ $pkgpart ], [], \@cust_pkg, [ $cgi->param('refnum') ] );
+
+</%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..3933ff3c5
--- /dev/null
+++ b/httemplate/edit/process/rate_region.cgi
@@ -0,0 +1,53 @@
+%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 {
+ new FS::rate_prefix {
+ 'countrycode' => $countrycode,
+ '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 )
+ };
+} 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..7e0baf782
--- /dev/null
+++ b/httemplate/edit/process/router.cgi
@@ -0,0 +1,70 @@
+%local $FS::UID::AutoCommit=0;
+%
+%sub check {
+% my $error = shift;
+% if($error) {
+% $cgi->param('error', $error);
+% print $cgi->redirect(popurl(3) . "edit/router.cgi?". $cgi->query_string);
+% dbh->rollback;
+% exit;
+% }
+%}
+%
+%my $error = '';
+%my $routernum = $cgi->param('routernum');
+%my $routername = $cgi->param('routername');
+%my $old = qsearchs('router', { routernum => $routernum });
+%my @old_psr;
+%
+%my $new = new FS::router {
+% map {
+% ($_, scalar($cgi->param($_)));
+% } fields('router')
+%};
+%
+%if($old) {
+% $error = $new->replace($old);
+%} else {
+% $error = $new->insert;
+% $routernum = $new->routernum;
+%}
+%
+%check($error);
+%
+%if ($old) {
+% @old_psr = $old->part_svc_router;
+% foreach my $psr (@old_psr) {
+% if($cgi->param('svcpart_'.$psr->svcpart) eq 'ON') {
+% # do nothing
+% } else {
+% $error = $psr->delete;
+% }
+% }
+% check($error);
+%}
+%
+%foreach($cgi->param) {
+% if($cgi->param($_) eq 'ON' and /^svcpart_(\d+)$/) {
+% my $svcpart = $1;
+% if(grep {$_->svcpart == $svcpart} @old_psr) {
+% # do nothing
+% } else {
+% my $new_psr = new FS::part_svc_router { svcpart => $svcpart,
+% routernum => $routernum };
+% $error = $new_psr->insert;
+% }
+% check($error);
+% }
+%}
+%
+%
+%# Yay, everything worked!
+%dbh->commit or die dbh->errstr;
+%print $cgi->redirect(popurl(3). "browse/router.cgi");
+%
+<%init>
+
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('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..75b89c88f
--- /dev/null
+++ b/httemplate/edit/process/svc_acct_pop.cgi
@@ -0,0 +1,30 @@
+%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>
+
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('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..8600da349
--- /dev/null
+++ b/httemplate/edit/process/svc_broadband.cgi
@@ -0,0 +1,38 @@
+%if ( $error ) {
+% $cgi->param('error', $error);
+% $cgi->param('ip_addr', $new->ip_addr);
+<% $cgi->redirect(popurl(2). "svc_broadband.cgi?". $cgi->query_string ) %>
+%} else {
+<% $cgi->redirect(popurl(3). "view/svc_broadband.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_broadband', { 'svcnum' => $svcnum } )
+ or die "fatal: can't find broadband service (svcnum $svcnum)!";
+} else {
+ $old = '';
+}
+
+my $new = new FS::svc_broadband ( {
+ map {
+ ($_, scalar($cgi->param($_)));
+ } ( fields('svc_broadband'), 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/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>