summaryrefslogtreecommitdiff
path: root/httemplate/edit
diff options
context:
space:
mode:
Diffstat (limited to 'httemplate/edit')
-rwxr-xr-xhttemplate/edit/REAL_cust_pkg.cgi181
-rw-r--r--httemplate/edit/access_group.html80
-rw-r--r--httemplate/edit/access_user.html50
-rwxr-xr-xhttemplate/edit/agent.cgi98
-rw-r--r--httemplate/edit/agent_payment_gateway.html68
-rwxr-xr-xhttemplate/edit/agent_type.cgi57
-rw-r--r--httemplate/edit/bulk-cust_svc.html97
-rwxr-xr-xhttemplate/edit/cust_bill_pay.cgi86
-rwxr-xr-xhttemplate/edit/cust_credit.cgi84
-rwxr-xr-xhttemplate/edit/cust_credit_bill.cgi93
-rwxr-xr-xhttemplate/edit/cust_main.cgi543
-rw-r--r--httemplate/edit/cust_main/billing.html484
-rw-r--r--httemplate/edit/cust_main/contact.html183
-rw-r--r--httemplate/edit/cust_main/select-country.html76
-rw-r--r--httemplate/edit/cust_main/select-county.html113
-rw-r--r--httemplate/edit/cust_main/select-domain.html67
-rw-r--r--httemplate/edit/cust_main/select-state.html24
-rwxr-xr-xhttemplate/edit/cust_main_county-expand.cgi50
-rw-r--r--httemplate/edit/cust_main_county.html62
-rwxr-xr-xhttemplate/edit/cust_main_note.cgi45
-rwxr-xr-xhttemplate/edit/cust_pay.cgi147
-rwxr-xr-xhttemplate/edit/cust_pkg.cgi154
-rwxr-xr-xhttemplate/edit/cust_refund.cgi141
-rw-r--r--httemplate/edit/elements/edit.html536
-rw-r--r--httemplate/edit/elements/svc_Common.html99
-rw-r--r--httemplate/edit/inventory_class.html16
-rw-r--r--httemplate/edit/invoice_logo.html136
-rw-r--r--httemplate/edit/invoice_template.html80
-rwxr-xr-xhttemplate/edit/msgcat.cgi54
-rwxr-xr-xhttemplate/edit/part_bill_event.cgi554
-rw-r--r--httemplate/edit/part_event.html681
-rw-r--r--httemplate/edit/part_export.cgi123
-rwxr-xr-xhttemplate/edit/part_pkg.cgi429
-rw-r--r--httemplate/edit/part_pkg_taxclass.html32
-rwxr-xr-xhttemplate/edit/part_referral.html19
-rwxr-xr-xhttemplate/edit/part_svc.cgi360
-rw-r--r--httemplate/edit/part_virtual_field.cgi104
-rw-r--r--httemplate/edit/payment_gateway.html132
-rw-r--r--httemplate/edit/pkg_class.html22
-rw-r--r--httemplate/edit/prepay_credit.cgi110
-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
-rw-r--r--httemplate/edit/quick-charge.html182
-rw-r--r--httemplate/edit/rate.cgi43
-rw-r--r--httemplate/edit/rate_detail.html59
-rw-r--r--httemplate/edit/rate_region.cgi153
-rw-r--r--httemplate/edit/reason.html50
-rw-r--r--httemplate/edit/reason_type.html29
-rw-r--r--httemplate/edit/reg_code.cgi44
-rwxr-xr-xhttemplate/edit/router.cgi78
-rw-r--r--httemplate/edit/svc_Common.html33
-rwxr-xr-xhttemplate/edit/svc_acct.cgi452
-rwxr-xr-xhttemplate/edit/svc_acct_pop.cgi50
-rw-r--r--httemplate/edit/svc_broadband.cgi254
-rwxr-xr-xhttemplate/edit/svc_domain.cgi91
-rw-r--r--httemplate/edit/svc_external.cgi102
-rwxr-xr-xhttemplate/edit/svc_forward.cgi175
-rw-r--r--httemplate/edit/svc_phone.cgi17
-rw-r--r--httemplate/edit/svc_www.cgi240
116 files changed, 11068 insertions, 0 deletions
diff --git a/httemplate/edit/REAL_cust_pkg.cgi b/httemplate/edit/REAL_cust_pkg.cgi
new file mode 100755
index 000000000..fea85456f
--- /dev/null
+++ b/httemplate/edit/REAL_cust_pkg.cgi
@@ -0,0 +1,181 @@
+<% include("/elements/header.html",'Customer package - Edit dates') %>
+
+%#, menubar(
+%# "View this customer (#$custnum)" => popurl(2). "view/cust_main.cgi?$custnum",
+%#));
+
+<LINK REL="stylesheet" TYPE="text/css" HREF="../elements/calendar-win2k-2.css" TITLE="win2k-2">
+<SCRIPT TYPE="text/javascript" SRC="../elements/calendar_stripped.js"></SCRIPT>
+<SCRIPT TYPE="text/javascript" SRC="../elements/calendar-en.js"></SCRIPT>
+<SCRIPT TYPE="text/javascript" SRC="../elements/calendar-setup.js"></SCRIPT>
+
+<FORM NAME="formname" ACTION="process/REAL_cust_pkg.cgi" METHOD="POST">
+<INPUT TYPE="hidden" NAME="pkgnum" VALUE="<% $pkgnum %>">
+
+% # raw error from below
+% if ( $error ) {
+ <FONT SIZE="+1" COLOR="#ff0000">Error: <% $error %></FONT>
+% }
+% #or, regular error handler
+<% include('/elements/error.html') %>
+
+<% ntable("#cccccc",2) %>
+
+ <TR>
+ <TD ALIGN="right">Package number</TD>
+ <TD BGCOLOR="#ffffff"><% $cust_pkg->pkgnum %></TD>
+ </TR>
+
+ <TR>
+ <TD ALIGN="right">Package</TD>
+ <TD BGCOLOR="#ffffff"><% $cust_pkg->pkg %></TD>
+ </TR>
+
+ <TR>
+ <TD ALIGN="right">Comment</TD>
+ <TD BGCOLOR="#ffffff"><% $cust_pkg->comment %></TD>
+ </TR>
+
+ <TR>
+ <TD ALIGN="right">Order taker</TD>
+ <TD BGCOLOR="#ffffff"><% $cust_pkg->otaker %></TD>
+ </TR>
+
+ <& .row_edit, cust_pkg=>$cust_pkg, column=>'setup', label=>'Setup' &>
+ <& .row_edit, cust_pkg=>$cust_pkg, column=>'last_bill', label=>$last_bill_or_renewed &>
+ <& .row_edit, cust_pkg=>$cust_pkg, column=>'bill', label=>$next_bill_or_prepaid_until &>
+ <& .row_edit, cust_pkg=>$cust_pkg, column=>'adjourn', label=>'Adjournment', note=>'(will <b>suspend</b> this package when the date is reached)' &>
+ <& .row_display, cust_pkg=>$cust_pkg, column=>'susp', label=>'Suspension' &>
+
+ <& .row_edit, cust_pkg=>$cust_pkg, column=>'expire', label=>'Expiration', note=>'(will <b>cancel</b> this package when the date is reached)' &>
+ <& .row_display, cust_pkg=>$cust_pkg, column=>'cancel', label=>'Cancellation' &>
+
+<%def .row_edit>
+<%args>
+ $cust_pkg
+ $column
+ $label
+ $note => ''
+</%args>
+% my $value = $cust_pkg->get($column);
+% $value = $value ? time2str($format, $value) : "";
+
+ <TR>
+ <TD ALIGN="right"><% $label %> date</TD>
+ <TD>
+ <INPUT TYPE = "text"
+ NAME = "<% $column %>"
+ SIZE = 32
+ ID = "<% $column %>_text"
+ VALUE = "<% $value %>"
+ >
+ <IMG SRC = "../images/calendar.png"
+ ID = "<% $column %>_button"
+ STYLE = "cursor: pointer"
+ TITLE = "Select date"
+ >
+% if ( $note ) {
+ <BR><FONT SIZE=-1><% $note %></FONT>
+% }
+ </TD>
+ </TR>
+
+ <SCRIPT TYPE="text/javascript">
+ Calendar.setup({
+ inputField: "<% $column %>_text",
+ ifFormat: "%m/%d/%Y",
+ button: "<% $column %>_button",
+ align: "BR"
+ });
+ </SCRIPT>
+
+</%def>
+
+<%def .row_display>
+<%args>
+ $cust_pkg
+ $column
+ $label
+</%args>
+% if ( $cust_pkg->get($column) ) {
+ <TR>
+ <TD ALIGN="right"><% $label %> date</TD>
+ <TD BGCOLOR="#ffffff"><% time2str($format,$cust_pkg->get($column)) %></TD>
+ </TR>
+% }
+</%def>
+
+</TABLE>
+
+<BR>
+<INPUT TYPE="submit" VALUE="Apply Changes">
+</FORM>
+
+<% include('/elements/footer.html') %>
+
+<%once>
+
+#my $format = "%c %z (%Z)";
+my $format = "%m/%d/%Y %T %z (%Z)";
+
+#false laziness w/view/cust_main/packages.html
+#my( $billed_or_prepaid,
+
+</%once>
+<%init>
+
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('Edit customer package dates');
+
+my $error = '';
+my( $pkgnum, $cust_pkg );
+
+if ( $cgi->param('error') ) {
+
+ $pkgnum = $cgi->param('pkgnum');
+ if ( $cgi->param('error') eq '_bill_areyousure' ) {
+ if ( $cgi->param('bill') =~ /^([\s\d\/\:\-\(\w\)]*)$/ ) {
+ my $bill = $1;
+ $cgi->param('error', '');
+ $error = "You are attempting to set the next bill date to $bill, which is
+ in the past. This will charge the customer for the interval
+ from $bill until now. Are you sure you want to do this? ".
+ '<INPUT TYPE="checkbox" NAME="bill_areyousure" VALUE="1">';
+ }
+ }
+
+ #get package record
+ $cust_pkg = qsearchs('cust_pkg',{'pkgnum'=>$pkgnum});
+ die "No package!" unless $cust_pkg;
+
+ foreach my $col (qw( setup last_bill bill adjourn expire )) {
+ my $value = $cgi->param($col);
+ $cust_pkg->set( $col, $value ? str2time($value) : '' );
+ }
+
+} else {
+
+ my($query) = $cgi->keywords;
+ $query =~ /^(\d+)$/ or die "no pkgnum";
+ $pkgnum = $1;
+
+ #get package record
+ $cust_pkg = qsearchs('cust_pkg',{'pkgnum'=>$pkgnum});
+ die "No package!" unless $cust_pkg;
+
+}
+
+my $part_pkg = qsearchs( 'part_pkg', { 'pkgpart' => $cust_pkg->pkgpart } );
+
+my( $last_bill_or_renewed, $next_bill_or_prepaid_until );
+unless ( $part_pkg->is_prepaid ) {
+ #$billed_or_prepaid = 'billed';
+ $last_bill_or_renewed = 'Last bill';
+ $next_bill_or_prepaid_until = 'Next bill';
+} else {
+ #$billed_or_prepaid = 'prepaid';
+ $last_bill_or_renewed = 'Renewed';
+ $next_bill_or_prepaid_until = 'Prepaid until';
+}
+
+</%init>
diff --git a/httemplate/edit/access_group.html b/httemplate/edit/access_group.html
new file mode 100644
index 000000000..4686a627a
--- /dev/null
+++ b/httemplate/edit/access_group.html
@@ -0,0 +1,80 @@
+<% include( 'elements/edit.html',
+ 'name' => 'Internal Access Group',
+ 'table' => 'access_group',
+ 'labels' => {
+ 'groupnum' => 'Group number',
+ 'groupname' => 'Group name',
+ },
+
+ 'viewall_dir' => 'browse',
+
+ 'html_bottom' => $html_bottom_sub,
+ )
+%>
+<%once>
+
+tie my %rights, 'Tie::IxHash', FS::AccessRight->rights_info;
+
+</%once>
+<%init>
+
+my $html_bottom_sub = sub {
+ my $access_group = shift;
+
+ #some false laziness w/browse/access_group.html
+ my $columns = 3;
+ my $count = 0;
+
+ '<BR>'.
+ '<FONT SIZE="+1">Group limited to these agent(s)</FONT><BR>'.
+ 'Employees in this group will only see customers of the selected agents in the system and reports.<BR>'.
+ ntable("#cccccc",2).
+ '<TR><TD>'.
+ include( '/elements/checkboxes-table.html',
+ 'source_obj' => $access_group,
+ 'link_table' => 'access_groupagent',
+ 'target_table' => 'agent',
+ 'name_col' => 'agent',
+ 'target_link' => $p.'edit/agent.cgi?',
+ 'disable-able' => 1,
+ ).
+ '</TD></TR></TABLE>'.
+
+ '<BR><FONT SIZE="+1">Group access rights</FONT><BR>'.
+ include('/elements/table-grid.html', bgcolor=>'#cccccc' ).
+ '<TR>'. join( '', map {
+ '<TD CLASS="inv" VALIGN="top"><TABLE BGCOLOR="#cccccc" WIDTH=100%>'.
+ '<TR><TH BGCOLOR="#dcdcdc">'. $_. '</TH></TR>'.
+ '<TR><TD>'.
+ include( '/elements/checkboxes-table-name.html',
+ 'source_obj' => $access_group,
+ 'link_table' => 'access_right',
+ 'link_static' => { 'righttype' =>
+ 'FS::access_group',
+ },
+ 'num_col' => 'rightobjnum',
+ 'name_col' => 'rightname',
+ 'names_list' => [ map {
+ my $rn =
+ ref($_) ? $_->{'rightname'} : $_;
+ my %hash = ();
+ $hash{'note'} = '&nbsp;*'
+ if ref($_) && $_->{'global'};
+ $hash{'desc'} = $_->{'desc'}
+ if ref($_) && $_->{'desc'};
+ [ $rn => \%hash ];
+ }
+ @{ $rights{$_} }
+ ],
+ ).
+ '<BR>'.
+ '</TD></TR></TABLE></TD>'.
+ ( ++$count % $columns ? '' : '</TR><TR>')
+
+ } keys %rights ). '</TR></TABLE>'.
+
+ '* Global rights. These rights provide access to global data which is shared among all agents. Their use is not recommended for groups which are limited to a subset of agents.<BR>';
+
+};
+
+</%init>
diff --git a/httemplate/edit/access_user.html b/httemplate/edit/access_user.html
new file mode 100644
index 000000000..224d8d722
--- /dev/null
+++ b/httemplate/edit/access_user.html
@@ -0,0 +1,50 @@
+<% include( 'elements/edit.html',
+ 'name' => 'Internal User',
+ 'table' => 'access_user',
+ 'fields' => [
+ 'username',
+ { field=>'_password', type=>'password' },
+ { field=>'_password2', type=>'password' },
+ 'last',
+ 'first',
+ { field=>'disabled', type=>'checkbox', value=>'Y' },
+ ],
+ 'labels' => {
+ 'usernum' => 'User number',
+ 'username' => 'Username',
+ '_password' => 'Password',
+ '_password2'=> 'Re-enter Password',
+ 'last' => 'Last name',
+ 'first' => 'First name',
+ 'disabled' => 'Disable employee',
+ },
+ 'edit_callback' => sub { my( $c, $o ) = @_;
+ $o->set('_password', '');
+ },
+ 'viewall_dir' => 'browse',
+ 'html_bottom' =>
+ sub {
+ my $access_user = shift;
+
+ '<BR>Internal Access Groups<BR>'.
+ ntable("#cccccc",2).
+ '<TR><TD>'.
+ include( '/elements/checkboxes-table.html',
+ 'source_obj' => $access_user,
+ 'link_table' => 'access_usergroup',
+ 'target_table' => 'access_group',
+ 'name_col' => 'groupname',
+ 'target_link' => $p.'edit/access_group.html?',
+ #'disable-able' => 1,
+ ).
+ '</TR></TD></TABLE>'
+ ;
+ },
+ )
+%>
+<%init>
+
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('Configuration');
+
+</%init>
diff --git a/httemplate/edit/agent.cgi b/httemplate/edit/agent.cgi
new file mode 100755
index 000000000..11bfc5932
--- /dev/null
+++ b/httemplate/edit/agent.cgi
@@ -0,0 +1,98 @@
+<% include("/elements/header.html","$action Agent", menubar(
+ 'View all agents' => $p. 'browse/agent.cgi',
+)) %>
+
+<% include('/elements/error.html') %>
+
+<FORM ACTION="<%popurl(1)%>process/agent.cgi" METHOD=POST>
+<INPUT TYPE="hidden" NAME="agentnum" VALUE="<% $agent->agentnum %>">
+Agent #<% $agent->agentnum ? $agent->agentnum : "(NEW)" %>
+
+<% &ntable("#cccccc", 2, '') %>
+
+ <TR>
+ <TH ALIGN="right">Agent</TH>
+ <TD><INPUT TYPE="text" NAME="agent" SIZE=32 VALUE="<% $agent->agent %>"></TD>
+ </TR>
+
+ <TR>
+ <TH ALIGN="right">Agent type</TH>
+ <TD>
+ <SELECT NAME="typenum" SIZE=1>
+% foreach my $agent_type (qsearch('agent_type',{})) {
+
+ <OPTION VALUE="<% $agent_type->typenum %>"<% ( $agent->typenum && ( $agent->typenum == $agent_type->typenum ) ) ? ' SELECTED' : '' %>>
+ <% $agent_type->getfield('typenum') %>: <% $agent_type->getfield('atype') %>
+% }
+
+ </SELECT>
+ </TD>
+ </TR>
+
+ <TR>
+ <TD ALIGN="right">Disable</TD>
+ <TD><INPUT TYPE="checkbox" NAME="disabled" VALUE="Y"<% $agent->disabled eq 'Y' ? ' CHECKED' : '' %>></TD>
+ </TR>
+
+ <% include('/elements/tr-select-invoice_template.html',
+ 'label' => 'Invoice template',
+ 'field' => 'invoice_template',
+ 'curr_value' => $agent->invoice_template,
+ )
+ %>
+
+% if ( $conf->config('ticket_system') ) {
+% my $default_queueid = $conf->config('ticket_system-default_queueid');
+% my $default_queue = FS::TicketSystem->queue($default_queueid);
+% $default_queue = "(default) $default_queueid: $default_queue"
+% if $default_queueid;
+% my %queues = FS::TicketSystem->queues();
+% my @queueids = sort { $a <=> $b } keys %queues;
+%
+
+ <TR>
+ <TD ALIGN="right">Ticketing queue</TD>
+ <TD>
+ <SELECT NAME="ticketing_queueid">
+ <OPTION VALUE=""><% $default_queue %>
+% foreach my $queueid ( @queueids ) {
+
+ <OPTION VALUE="<% $queueid %>" <% $agent->ticketing_queueid == $queueid ? ' SELECTED' : '' %>><% $queueid %>: <% $queues{$queueid} %>
+% }
+
+ </SELECT>
+ </TD>
+ </TR>
+% }
+
+</TABLE>
+
+<BR>
+<INPUT TYPE="submit" VALUE="<% $agent->agentnum ? "Apply changes" : "Add agent" %>">
+
+</FORM>
+
+<% include('/elements/footer.html') %>
+
+<%init>
+
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('Configuration');
+
+my $agent;
+if ( $cgi->param('error') ) {
+ $agent = new FS::agent ( {
+ map { $_, scalar($cgi->param($_)) } fields('agent')
+ } );
+} elsif ( $cgi->keywords ) {
+ my($query) = $cgi->keywords;
+ $query =~ /^(\d+)$/;
+ $agent = qsearchs( 'agent', { 'agentnum' => $1 } );
+} else { #adding
+ $agent = new FS::agent {};
+}
+my $action = $agent->agentnum ? 'Edit' : 'Add';
+
+my $conf = new FS::Conf;
+
+</%init>
diff --git a/httemplate/edit/agent_payment_gateway.html b/httemplate/edit/agent_payment_gateway.html
new file mode 100644
index 000000000..4a7cedf79
--- /dev/null
+++ b/httemplate/edit/agent_payment_gateway.html
@@ -0,0 +1,68 @@
+<% include("/elements/header.html","$action payment gateway override for ". $agent->agent, menubar(
+ #'View all payment gateways' => $p. 'browse/payment_gateway.html',
+ 'View all agents' => $p. 'browse/agent.html',
+)) %>
+
+<% include('/elements/error.html') %>
+
+<FORM ACTION="<%popurl(1)%>process/agent_payment_gateway.html" METHOD=POST>
+<INPUT TYPE="hidden" NAME="agentnum" VALUE="<% $agent->agentnum %>">
+
+Use gateway <SELECT NAME="gatewaynum">
+% foreach my $payment_gateway (
+% qsearch('payment_gateway', { 'disabled' => '' } )
+% ) {
+%
+
+ <OPTION VALUE="<% $payment_gateway->gatewaynum %>"><% $payment_gateway->gateway_module %> (<% $payment_gateway->gateway_username %>)
+% }
+
+</SELECT>
+<BR><BR>
+
+for <SELECT NAME="cardtype" MULTIPLE>
+% foreach my $cardtype (
+% "",
+% "VISA card",
+% "MasterCard",
+% "Discover card",
+% "American Express card",
+% "Diner's Club/Carte Blanche",
+% "enRoute",
+% "JCB",
+% "BankCard",
+% "Switch",
+% "Solo",
+% 'ACH',
+%) {
+
+ <OPTION VALUE="<% $cardtype %>"><% $cardtype || '(Default fallback)' %>
+% }
+
+</SELECT>
+<BR><BR>
+
+(optional) when invoice contains only items of taxclass <INPUT TYPE="text" NAME="taxclass">
+<BR><BR>
+
+<INPUT TYPE="submit" VALUE="Add gateway override">
+</FORM>
+
+<% include('/elements/footer.html') %>
+
+<%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 @agent_payment_gateway;
+if ( $cgi->param('error') ) {
+}
+
+my $action = 'Add';
+
+</%init>
diff --git a/httemplate/edit/agent_type.cgi b/httemplate/edit/agent_type.cgi
new file mode 100755
index 000000000..abf4bf89f
--- /dev/null
+++ b/httemplate/edit/agent_type.cgi
@@ -0,0 +1,57 @@
+<% include("/elements/header.html","$action Agent Type", menubar(
+ 'View all agent types' => "${p}browse/agent_type.cgi",
+))
+%>
+
+<% include('/elements/error.html') %>
+
+<FORM ACTION="<% popurl(1) %>process/agent_type.cgi" METHOD=POST>
+<INPUT TYPE="hidden" NAME="typenum" VALUE="<% $agent_type->typenum %>">
+Agent Type #<% $agent_type->typenum || "(NEW)" %>
+<BR>
+
+Agent Type
+<INPUT TYPE="text" NAME="atype" SIZE=32 VALUE="<% $agent_type->atype %>">
+<BR><BR>
+
+Select which packages agents of this type may sell to customers<BR>
+<% ntable("#cccccc", 2) %><TR><TD>
+<% include('/elements/checkboxes-table.html',
+ 'source_obj' => $agent_type,
+ 'link_table' => 'type_pkgs',
+ 'target_table' => 'part_pkg',
+ 'name_callback' => sub { $_[0]->pkg. ' - '. $_[0]->comment; },
+ 'target_link' => $p.'edit/part_pkg.cgi?',
+ 'disable-able' => 1,
+
+ )
+%>
+</TD></TR></TABLE>
+<BR>
+
+<INPUT TYPE="submit" VALUE="<% $agent_type->typenum ? "Apply changes" : "Add agent type" %>">
+
+ </FORM>
+
+<% include('/elements/footer.html') %>
+
+<%init>
+
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('Configuration');
+
+my($agent_type);
+if ( $cgi->param('error') ) {
+ $agent_type = new FS::agent_type ( {
+ map { $_, scalar($cgi->param($_)) } fields('agent')
+ } );
+} elsif ( $cgi->keywords ) { #editing
+ my( $query ) = $cgi->keywords;
+ $query =~ /^(\d+)$/;
+ $agent_type=qsearchs('agent_type',{'typenum'=>$1});
+} else { #adding
+ $agent_type = new FS::agent_type {};
+}
+my $action = $agent_type->typenum ? 'Edit' : 'Add';
+
+</%init>
diff --git a/httemplate/edit/bulk-cust_svc.html b/httemplate/edit/bulk-cust_svc.html
new file mode 100644
index 000000000..6f6e3f850
--- /dev/null
+++ b/httemplate/edit/bulk-cust_svc.html
@@ -0,0 +1,97 @@
+<% include('/elements/header.html', 'Bulk customer service change') %>
+
+<SCRIPT TYPE="text/javascript" SRC="../elements/overlibmws.js"></SCRIPT>
+<SCRIPT TYPE="text/javascript" SRC="../elements/overlibmws_iframe.js"></SCRIPT>
+<SCRIPT TYPE="text/javascript" SRC="../elements/overlibmws_draggable.js"></SCRIPT>
+
+<% include('/elements/progress-init.html',
+ 'OneTrueForm',
+ [qw( old_svcpart new_svcpart pkgpart )],
+ 'process/bulk-cust_svc.cgi',
+ $p.'browse/part_svc.cgi',
+ )
+%>
+
+<FORM NAME="OneTrueForm">
+%
+% $cgi->param('svcpart') =~ /^(\d+)$/
+% or die "illegal svcpart: ". $cgi->param('svcpart');
+%
+% my $old_svcpart = $1;
+% my $src_part_svc = qsearchs('part_svc', { 'svcpart' => $old_svcpart } )
+% or die "unknown svcpart: $old_svcpart";
+%
+
+
+<INPUT NAME="old_svcpart" TYPE="hidden" VALUE="<% $old_svcpart %>">
+Change <!-- customer
+<B><% $src_part_svc->svcpart %>: <% $src_part_svc->svc %></B> services
+<BR>
+-->
+
+<SELECT NAME="pkgpart">
+% my $num_cust_svc = $src_part_svc->num_cust_svc;
+% if ( $num_cust_svc > 1 ) {
+
+ <OPTION VALUE="">all <% $num_cust_svc %> <% $src_part_svc->svc %> services
+% } else {
+
+ <OPTION VALUE="">the <% $num_cust_svc %> <% $src_part_svc->svc %> service
+% }
+%
+% my $num_unlinked = $src_part_svc->num_cust_svc(0);
+% if ( $num_unlinked ) {
+%
+
+ <OPTION VALUE="0">the <% $num_unlinked %> unlinked <% $src_part_svc->svc %> services
+% }
+% foreach my $schwartz (
+% grep { $_->[1] }
+% map { [ $_, $src_part_svc->num_cust_svc($_->pkgpart) ] }
+% qsearch('part_pkg', {} )
+% ) {
+% my( $part_pkg, $num_cust_svc ) = @$schwartz;
+%
+
+ <OPTION VALUE="<% $part_pkg->pkgpart %>">the <% $num_cust_svc %>
+ <% $src_part_svc->svc %> service<% $num_cust_svc > 1 ? 's in' : ' in a' %>
+ <% $part_pkg->pkg %> package<% $num_cust_svc > 1 ? 's' : '' %>
+% }
+
+</SELECT>
+<BR>
+
+to new service definition
+<SELECT NAME="new_svcpart">
+% foreach my $dest_part_svc (
+% grep { $_->svcpart != $old_svcpart
+% && $_->svcdb eq $src_part_svc->svcdb
+% }
+% qsearch('part_svc', { 'disabled' => '' } )
+% ) {
+%
+
+ <OPTION VALUE="<% $dest_part_svc->svcpart %>"><% $dest_part_svc->svcpart %>: <% $dest_part_svc->svc %>
+% }
+
+</SELECT>
+<BR>
+
+<BR>
+
+<SCRIPT TYPE="text/javascript">
+var confirm_change = '<P ALIGN="center"><B>Bulk customer service change - Are you sure?</B><BR><P ALIGN="CENTER" <INPUT TYPE="button" VALUE="Yes, make changes" onClick="process();">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<INPUT TYPE="BUTTON" VALUE="Cancel" onClick="cClick()">';
+</SCRIPT>
+
+<INPUT TYPE="button" VALUE="Bulk change customer services" onClick="overlib(confirm_change, CAPTION, 'Confirm bulk customer service change', STICKY, AUTOSTATUSCAP, CLOSETEXT, '', MIDX, 0, MIDY, 0, DRAGGABLE, WIDTH, 576, HEIGHT, 128, TEXTSIZE, 3, BGCOLOR, '#ff0000', CGCOLOR, '#ff0000' );">
+
+</FORM>
+
+<% include('/elements/footer.html') %>
+
+<%init>
+
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('Configuration');
+
+</%init>
diff --git a/httemplate/edit/cust_bill_pay.cgi b/httemplate/edit/cust_bill_pay.cgi
new file mode 100755
index 000000000..b6a064768
--- /dev/null
+++ b/httemplate/edit/cust_bill_pay.cgi
@@ -0,0 +1,86 @@
+<% include('/elements/header-popup.html', 'Apply Payment') %>
+
+<% include('/elements/error.html') %>
+
+<FORM ACTION="<% $p1 %>process/cust_bill_pay.cgi" METHOD=POST>
+
+Payment #<B><% $paynum %></B>
+<INPUT TYPE="hidden" NAME="paynum" VALUE="<% $paynum %>">
+
+<BR>Date: <B><% time2str("%D", $cust_pay->_date) %></B>
+
+<BR>Amount: $<B><% $cust_pay->paid %></B>
+
+<BR>Unapplied amount: $<B><% $unapplied %></B>
+
+<SCRIPT TYPE="text/javascript">
+function changed(what) {
+ cust_bill = what.options[what.selectedIndex].value;
+
+% foreach my $cust_bill ( @cust_bill ) {
+
+ if ( cust_bill == <% $cust_bill->invnum %> ) {
+ what.form.amount.value = "<% min($cust_bill->owed, $unapplied) %>";
+ }
+
+% }
+
+ if ( cust_bill == "Refund" ) {
+ what.form.amount.value = "<% $unapplied %>";
+ }
+}
+</SCRIPT>
+
+<BR>Invoice #<SELECT NAME="invnum" SIZE=1 onChange="changed(this)">
+<OPTION VALUE="">
+
+% foreach my $cust_bill ( @cust_bill ) {
+ <OPTION<% $cust_bill->invnum eq $invnum ? ' SELECTED' : '' %> VALUE="<% $cust_bill->invnum %>"><% $cust_bill->invnum %> - <% time2str("%D", $cust_bill->_date) %> - $<% $cust_bill->owed %>
+% }
+
+<OPTION VALUE="Refund">Refund
+</SELECT>
+
+<BR>Amount $<INPUT TYPE="text" NAME="amount" VALUE="<% $amount %>" SIZE=8 MAXLENGTH=8>
+
+<BR>
+<CENTER><INPUT TYPE="submit" VALUE="Apply"></CENTER>
+
+</FORM>
+
+<% include('/elements/footer.html') %>
+
+<%init>
+
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('Apply payment');
+
+my($paynum, $amount, $invnum);
+if ( $cgi->param('error') ) {
+ $paynum = $cgi->param('paynum');
+ $amount = $cgi->param('amount');
+ $invnum = $cgi->param('invnum');
+} else {
+ my($query) = $cgi->keywords;
+ $query =~ /^(\d+)$/;
+ $paynum = $1;
+ $amount = '';
+ $invnum = '';
+}
+
+my $otaker = getotaker;
+
+my $p1 = popurl(1);
+
+my $cust_pay = qsearchs('cust_pay', { 'paynum' => $paynum } );
+die "payment $paynum not found!" unless $cust_pay;
+
+my $unapplied = $cust_pay->unapplied;
+
+my @cust_bill = sort { $a->_date <=> $b->_date
+ or $a->invnum <=> $b->invnum
+ }
+ grep { $_->owed != 0 }
+ qsearch('cust_bill', { 'custnum' => $cust_pay->custnum } );
+
+</%init>
diff --git a/httemplate/edit/cust_credit.cgi b/httemplate/edit/cust_credit.cgi
new file mode 100755
index 000000000..36109cf5d
--- /dev/null
+++ b/httemplate/edit/cust_credit.cgi
@@ -0,0 +1,84 @@
+<% include('/elements/header-popup.html', 'Enter Credit') %>
+
+<% include('/elements/error.html') %>
+
+<FORM NAME="credit_popup" ACTION="<% $p1 %>process/cust_credit.cgi" METHOD=POST>
+<INPUT TYPE="hidden" NAME="crednum" VALUE="">
+<INPUT TYPE="hidden" NAME="custnum" VALUE="<% $custnum %>">
+<INPUT TYPE="hidden" NAME="paybatch" VALUE="">
+<INPUT TYPE="hidden" NAME="_date" VALUE="<% $_date %>">
+<INPUT TYPE="hidden" NAME="credited" VALUE="">
+<INPUT TYPE="hidden" NAME="otaker" VALUE="<% $otaker %>">
+
+Credit
+<% ntable("#cccccc", 2) %>
+
+ <TR>
+ <TD ALIGN="right">Date</TD>
+ <TD BGCOLOR="#ffffff"><% time2str("%D",$_date) %></TD>
+ </TR>
+
+ <TR>
+ <TD ALIGN="right">Amount</TD>
+ <TD BGCOLOR="#ffffff">$<INPUT TYPE="text" NAME="amount" VALUE="<% $amount %>" SIZE=8 MAXLENGTH=8></TD>
+ </TR>
+
+%
+%#print qq! <INPUT TYPE="checkbox" NAME="refund" VALUE="$refund">Also post refund!;
+%
+
+<% include('/elements/tr-select-reason.html',
+ 'field' => 'reasonnum',
+ 'reason_class' => 'R',
+ 'control_button' => 'document.credit_popup.submit',
+ )
+%>
+
+ <TR>
+ <TD ALIGN="right">Auto-apply<BR>to invoices</TD>
+ <TD><SELECT NAME="apply"><OPTION VALUE="yes" SELECTED>yes<OPTION>no</SELECT></TD>
+ </TR>
+
+</TABLE>
+
+<BR>
+
+<CENTER><INPUT TYPE="submit" VALUE="Enter credit"></CENTER>
+
+</FORM>
+</BODY>
+</HTML>
+<%once>
+
+my $conf = new FS::Conf;
+
+</%once>
+<%init>
+
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('Post credit');
+
+my($custnum, $amount, $reason);
+if ( $cgi->param('error') ) {
+ #$cust_credit = new FS::cust_credit ( {
+ # map { $_, scalar($cgi->param($_)) } fields('cust_credit')
+ #} );
+ $custnum = $cgi->param('custnum');
+ $amount = $cgi->param('amount');
+ #$refund = $cgi->param('refund');
+ $reason = $cgi->param('reason');
+} else {
+ my($query) = $cgi->keywords;
+ $query =~ /^(\d+)$/;
+ $custnum = $1;
+ $amount = '';
+ #$refund = 'yes';
+ $reason = '';
+}
+my $_date = time;
+
+my $otaker = getotaker;
+
+my $p1 = popurl(1);
+
+</%init>
diff --git a/httemplate/edit/cust_credit_bill.cgi b/httemplate/edit/cust_credit_bill.cgi
new file mode 100755
index 000000000..59a74b279
--- /dev/null
+++ b/httemplate/edit/cust_credit_bill.cgi
@@ -0,0 +1,93 @@
+<% include('/elements/header-popup.html', 'Apply Credit') %>
+
+<% include('/elements/error.html') %>
+
+<FORM ACTION="<% $p1 %>process/cust_credit_bill.cgi" METHOD=POST>
+
+Credit #<B><% $crednum %></B>
+<INPUT TYPE="hidden" NAME="crednum" VALUE="<% $crednum %>">
+
+<BR>Date: <B><% time2str("%D", $cust_credit->_date) %></B>
+
+<BR>Amount: $<B><% $cust_credit->amount %></B>
+
+<BR>Unapplied amount: $<B><% $credited %></B>
+
+<BR>Reason: <B><% $cust_credit->reason %></B>
+
+<SCRIPT>
+function changed(what) {
+ cust_bill = what.options[what.selectedIndex].value;
+
+% foreach my $cust_bill ( @cust_bill ) {
+
+ if ( cust_bill == <% $cust_bill->invnum %> ) {
+ what.form.amount.value = "<% min($cust_bill->owed, $credited) %>";
+ }
+
+% }
+
+ if ( cust_bill == "Refund" ) {
+ what.form.amount.value = "<% $credited %>";
+ }
+}
+</SCRIPT>
+
+<BR>Invoice #<SELECT NAME="invnum" SIZE=1 onChange="changed(this)">
+<OPTION VALUE="">
+
+% foreach my $cust_bill ( @cust_bill ) {
+ <OPTION<% $cust_bill->invnum eq $invnum ? ' SELECTED' : '' %> VALUE="<% $cust_bill->invnum %>"><% $cust_bill->invnum %> - <% time2str("%D",$cust_bill->_date) %> - $<% $cust_bill->owed %>
+% }
+
+<OPTION VALUE="Refund">Refund
+</SELECT>
+
+<BR>Amount $<INPUT TYPE="text" NAME="amount" VALUE="<% $amount %>" SIZE=8 MAXLENGTH=8>
+
+<BR>
+<CENTER><INPUT TYPE="submit" VALUE="Apply"></CENTER>
+
+</FORM>
+</BODY>
+</HTML>
+
+<%init>
+
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('Apply credit');
+
+my($crednum, $amount, $invnum);
+if ( $cgi->param('error') ) {
+ #$cust_credit_bill = new FS::cust_credit_bill ( {
+ # map { $_, scalar($cgi->param($_)) } fields('cust_credit_bill')
+ #} );
+ $crednum = $cgi->param('crednum');
+ $amount = $cgi->param('amount');
+ #$refund = $cgi->param('refund');
+ $invnum = $cgi->param('invnum');
+} else {
+ my($query) = $cgi->keywords;
+ $query =~ /^(\d+)$/;
+ $crednum = $1;
+ $amount = '';
+ #$refund = 'yes';
+ $invnum = '';
+}
+
+my $otaker = getotaker;
+
+my $p1 = popurl(1);
+
+my $cust_credit = qsearchs('cust_credit', { 'crednum' => $crednum } );
+die "credit $crednum not found!" unless $cust_credit;
+
+my $credited = $cust_credit->credited;
+
+my @cust_bill = sort { $a->_date <=> $b->_date
+ or $a->invnum <=> $b->invnum
+ }
+ grep { $_->owed != 0 }
+ qsearch('cust_bill', { 'custnum' => $cust_credit->custnum } );
+
+</%init>
diff --git a/httemplate/edit/cust_main.cgi b/httemplate/edit/cust_main.cgi
new file mode 100755
index 000000000..aae809370
--- /dev/null
+++ b/httemplate/edit/cust_main.cgi
@@ -0,0 +1,543 @@
+<% include('/elements/header.html',
+ "Customer $action",
+ '',
+ ' onUnload="myclose()"'
+) %>
+
+<% include('/elements/error.html') %>
+
+<FORM NAME="topform" STYLE="margin-bottom: 0">
+<INPUT TYPE="hidden" NAME="custnum" VALUE="<% $custnum %>">
+% if ( $custnum ) {
+
+ Customer #<B><% $custnum %></B> -
+ <B><FONT COLOR="#<% $cust_main->statuscolor %>">
+ <% ucfirst($cust_main->status) %>
+ </FONT></B>
+ <BR><BR>
+% }
+
+
+<% &ntable("#cccccc") %>
+
+<!-- agent -->
+
+<% include('/elements/tr-select-agent.html',
+ 'curr_value' => $cust_main->agentnum,
+ 'label' => "<B>${r}Agent</B>",
+ 'empty_label' => 'Select agent',
+ 'disable_empty' => ( $cust_main->agentnum ? 1 : 0 ),
+ )
+%>
+
+<!-- referral (advertising source) -->
+%
+%my $refnum = $cust_main->refnum || $conf->config('referraldefault') || 0;
+%if ( $custnum && ! $conf->exists('editreferrals') ) {
+%
+
+
+ <INPUT TYPE="hidden" NAME="refnum" VALUE="<% $refnum %>">
+% } else {
+
+
+ <% include('/elements/tr-select-part_referral.html',
+ 'curr_value' => $refnum
+ )
+ %>
+% }
+
+
+<!-- referring customer -->
+%
+%my $referring_cust_main = '';
+%if ( $cust_main->referral_custnum
+% and $referring_cust_main =
+% qsearchs('cust_main', { custnum => $cust_main->referral_custnum } )
+%) {
+%
+
+
+ <TR>
+ <TD ALIGN="right">Referring customer</TD>
+ <TD>
+ <A HREF="<% popurl(1) %>/cust_main.cgi?<% $cust_main->referral_custnum %>"><% $cust_main->referral_custnum %>: <% $referring_cust_main->name %></A>
+ </TD>
+ </TR>
+ <INPUT TYPE="hidden" NAME="referral_custnum" VALUE="<% $cust_main->referral_custnum %>">
+% } elsif ( ! $conf->exists('disable_customer_referrals') ) {
+
+
+ <TR>
+ <TD ALIGN="right">Referring customer</TD>
+ <TD>
+ <!-- <INPUT TYPE="text" NAME="referral_custnum" VALUE=""> -->
+ <% include('/elements/search-cust_main.html',
+ 'field_name' => 'referral_custnum',
+ )
+ %>
+ </TD>
+ </TR>
+% } else {
+
+
+ <INPUT TYPE="hidden" NAME="referral_custnum" VALUE="">
+% }
+
+
+</TABLE>
+
+<!-- birthdate -->
+
+% if ( $conf->exists('cust_main-enable_birthdate') ) {
+
+ <BR>
+ <% ntable("#cccccc", 2) %>
+ <% include ('/elements/tr-input-date-field.html',
+ 'birthdate',
+ $cust_main->birthdate,
+ 'Date of Birth',
+ $conf->config('date_format') || "%m/%d/%Y",
+ 1)
+ %>
+
+ </TABLE>
+
+% }
+
+<!-- contact info -->
+
+% my $same_checked = '';
+% my $ship_disabled = '';
+% unless ( $cust_main->ship_last && $same ne 'Y' ) {
+% $same_checked = 'CHECKED';
+% $ship_disabled = 'DISABLED STYLE="background-color: #dddddd"';
+% foreach (
+% qw( last first company address1 address2 city county state zip country
+% daytime night fax )
+% ) {
+% $cust_main->set("ship_$_", $cust_main->get($_) );
+% }
+% }
+
+<BR><BR>
+Billing address
+<% include('cust_main/contact.html',
+ 'cust_main' => $cust_main,
+ 'pre' => '',
+ 'onchange' => 'bill_changed(this)',
+ 'disabled' => '',
+ 'ss' => $ss,
+ 'stateid' => $stateid,
+ 'same_checked' => $same_checked, #for address2 "Unit #" labeling
+ )
+%>
+
+<SCRIPT>
+function bill_changed(what) {
+ if ( what.form.same.checked ) {
+% for (qw( last first company address1 address2 city zip daytime night fax )) {
+
+ what.form.ship_<%$_%>.value = what.form.<%$_%>.value;
+% }
+
+ what.form.ship_country.selectedIndex = what.form.country.selectedIndex;
+
+ function fix_ship_county() {
+ what.form.ship_county.selectedIndex = what.form.county.selectedIndex;
+ }
+
+ function fix_ship_state() {
+ what.form.ship_state.selectedIndex = what.form.state.selectedIndex;
+ ship_state_changed(what.form.ship_state, fix_ship_county );
+ }
+
+ ship_country_changed(what.form.ship_country, fix_ship_state );
+
+ }
+}
+function samechanged(what) {
+ if ( what.checked ) {
+ bill_changed(what);
+
+% for (qw( last first company address1 address2 city county state zip country daytime night fax )) {
+ what.form.ship_<%$_%>.disabled = true;
+ what.form.ship_<%$_%>.style.backgroundColor = '#dddddd';
+% }
+
+% if ( $conf->exists('cust_main-require_address2') ) {
+ document.getElementById('address2_required').style.visibility = '';
+ document.getElementById('address2_label').style.visibility = '';
+ document.getElementById('ship_address2_required').style.visibility = 'hidden';
+ document.getElementById('ship_address2_label').style.visibility = 'hidden';
+% }
+
+ } else {
+
+% for (qw( last first company address1 address2 city county state zip country daytime night fax )) {
+ what.form.ship_<%$_%>.disabled = false;
+ what.form.ship_<%$_%>.style.backgroundColor = '#ffffff';
+% }
+
+% if ( $conf->exists('cust_main-require_address2') ) {
+ document.getElementById('address2_required').style.visibility = 'hidden';
+ document.getElementById('address2_label').style.visibility = 'hidden';
+ document.getElementById('ship_address2_required').style.visibility = '';
+ document.getElementById('ship_address2_label').style.visibility = '';
+% }
+
+ }
+}
+</SCRIPT>
+
+<BR>
+Service address
+(<INPUT TYPE="checkbox" NAME="same" VALUE="Y" onClick="samechanged(this)" <%$same_checked%>>same as billing address)
+<% include('cust_main/contact.html',
+ 'cust_main' => $cust_main,
+ 'pre' => 'ship_',
+ 'onchange' => '',
+ 'disabled' => $ship_disabled,
+ )
+%>
+
+
+<!-- billing info -->
+
+<% include( 'cust_main/billing.html', $cust_main,
+ 'payinfo' => $payinfo,
+ 'invoicing_list' => \@invoicing_list,
+ )
+%>
+
+<SCRIPT>
+function bottomfixup(what) {
+
+ var topvars = new Array(
+ 'birthdate',
+
+ 'custnum', 'agentnum', 'refnum', 'referral_custnum',
+
+ 'last', 'first', 'ss', 'company',
+ 'address1', 'address2', 'city',
+ 'county', 'state', 'zip', 'country',
+ 'daytime', 'night', 'fax',
+ 'stateid', 'stateid_state',
+
+ 'same',
+
+ 'ship_last', 'ship_first', 'ship_company',
+ 'ship_address1', 'ship_address2', 'ship_city',
+ 'ship_county', 'ship_state', 'ship_zip', 'ship_country',
+ 'ship_daytime','ship_night', 'ship_fax',
+
+ 'select' // XXX key
+ );
+
+ var layervars = new Array(
+ 'payauto',
+ 'payinfo', 'payinfo1', 'payinfo2', 'paytype',
+ 'payname', 'paystate', 'exp_month', 'exp_year', 'paycvv',
+ 'paystart_month', 'paystart_year', 'payissue',
+ 'payip',
+ 'paid'
+ );
+
+ var billing_bottomvars = new Array(
+ 'tax',
+ 'invoicing_list', 'invoicing_list_POST', 'invoicing_list_FAX',
+ 'invoice_terms',
+ 'spool_cdr'
+ );
+
+ for ( f=0; f < topvars.length; f++ ) {
+ var field = topvars[f];
+ copyelement( document.topform.elements[field],
+ document.bottomform.elements[field]
+ );
+ }
+
+ var layerform = document.topform.select.options[document.topform.select.selectedIndex].value;
+ for ( f=0; f < layervars.length; f++ ) {
+ var field = layervars[f];
+ copyelement( document.forms[layerform].elements[field],
+ document.bottomform.elements[field]
+ );
+ }
+
+ for ( f=0; f < billing_bottomvars.length; f++ ) {
+ var field = billing_bottomvars[f];
+ copyelement( document.billing_bottomform.elements[field],
+ document.bottomform.elements[field]
+ );
+ }
+
+}
+
+function copyelement(from, to) {
+ if ( from == undefined ) {
+ to.value = '';
+ } else if ( from.type == 'select-one' ) {
+ to.value = from.options[from.selectedIndex].value;
+ //alert(from + " (" + from.type + "): " + to.name + " => (" + from.selectedIndex + ") " + to.value);
+ } else if ( from.type == 'checkbox' ) {
+ if ( from.checked ) {
+ to.value = from.value;
+ } else {
+ to.value = '';
+ }
+ } else {
+ if ( from.value == undefined ) {
+ to.value = '';
+ } else {
+ to.value = from.value;
+ }
+ }
+ //alert(from + " (" + from.type + "): " + to.name + " => " + to.value);
+}
+
+</SCRIPT>
+
+<FORM ACTION="<% popurl(1) %>process/cust_main.cgi" METHOD=POST NAME="bottomform" onSubmit="document.bottomform.submit.disabled=true; bottomfixup(this.form);" STYLE="margin-top: 0; margin-bottom: 0">
+% foreach my $hidden (
+% 'birthdate',
+%
+% 'custnum', 'agentnum', 'refnum', 'referral_custnum',
+% 'last', 'first', 'ss', 'company',
+% 'address1', 'address2', 'city',
+% 'county', 'state', 'zip', 'country',
+% 'daytime', 'night', 'fax',
+% 'stateid', 'stateid_state',
+%
+% 'same',
+%
+% 'ship_last', 'ship_first', 'ship_company',
+% 'ship_address1', 'ship_address2', 'ship_city',
+% 'ship_county', 'ship_state', 'ship_zip', 'ship_country',
+% 'ship_daytime','ship_night', 'ship_fax',
+%
+% 'select', #XXX key
+%
+% 'payauto',
+% 'payinfo', 'payinfo1', 'payinfo2', 'paytype',
+% 'payname', 'paystate', 'exp_month', 'exp_year', 'paycvv',
+% 'paystart_month', 'paystart_year', 'payissue',
+% 'payip',
+% 'paid',
+%
+% 'tax',
+% 'invoicing_list', 'invoicing_list_POST', 'invoicing_list_FAX',
+% 'invoice_terms',
+% 'spool_cdr'
+% ) {
+%
+
+ <INPUT TYPE="hidden" NAME="<% $hidden %>" VALUE="">
+% }
+%
+% my $ro_comments = $conf->exists('cust_main-use_comments')?'':'readonly';
+% if (!$ro_comments || $cust_main->comments) {
+
+<BR>Comments
+<% &ntable("#cccccc") %>
+ <TR>
+ <TD>
+ <TEXTAREA COLS=80 ROWS=5 WRAP="HARD" NAME="comments" <%$ro_comments%>><% $cust_main->comments %></TEXTAREA>
+ </TD>
+ </TR>
+</TABLE>
+%
+% }
+%
+%unless ( $custnum ) {
+% # pry the wrong place for this logic. also pretty expensive
+% #use FS::part_pkg;
+%
+% #false laziness, copied from FS::cust_pkg::order
+% my $pkgpart;
+% my @agents = $FS::CurrentUser::CurrentUser->agents;
+% if ( scalar(@agents) == 1 ) {
+% # $pkgpart->{PKGPART} is true iff $custnum may purchase PKGPART
+% $pkgpart = $agents[0]->pkgpart_hashref;
+% } else {
+% #can't know (agent not chosen), so, allow all
+% my %typenum;
+% foreach my $agent ( @agents ) {
+% next if $typenum{$agent->typenum}++;
+% #fixed in 5.004_05 #$pkgpart->{$_}++ foreach keys %{ $agent->pkgpart_hashref }
+% foreach ( keys %{ $agent->pkgpart_hashref } ) { $pkgpart->{$_}++; } #5.004_04 workaround
+% }
+% }
+% #eslaf
+%
+% my @part_pkg = grep { $_->svcpart('svc_acct') && $pkgpart->{ $_->pkgpart } }
+% qsearch( 'part_pkg', { 'disabled' => '' }, '', 'ORDER BY pkg' ); # case?
+%
+% if ( @part_pkg ) {
+%
+% # print "<BR><BR>First package", &itable("#cccccc", "0 ALIGN=LEFT"),
+% #apiabuse & undesirable wrapping
+%
+%
+
+ <BR>First package
+ <% ntable("#cccccc") %>
+
+ <TR>
+ <TD COLSPAN=2>
+ <% include('cust_main/select-domain.html',
+ 'pkgparts' => \@part_pkg,
+ 'saved_pkgpart' => $saved_pkgpart,
+ 'saved_domsvc' => $saved_domsvc,
+ )
+ %>
+ </TD>
+ </TR>
+%
+% #false laziness: (mostly) copied from edit/svc_acct.cgi
+% #$ulen = $svc_acct->dbdef_table->column('username')->length;
+% my $ulen = dbdef->table('svc_acct')->column('username')->length;
+% my $ulen2 = $ulen+2;
+% my $passwordmax = $conf->config('passwordmax') || 8;
+% my $pmax2 = $passwordmax + 2;
+%
+
+
+ <TR>
+ <TD ALIGN="right">Username</TD>
+ <TD>
+ <INPUT TYPE="text" NAME="username" VALUE="<% $username %>" SIZE=<% $ulen2 %> MAXLENGTH=<% $ulen %>>
+ </TD>
+ </TR>
+
+ <TR>
+ <TD ALIGN="right">Domain</TD>
+ <TD>
+ <SELECT NAME="domsvc">
+ <OPTION>(none)</OPTION>
+ </SELECT>
+ </TD>
+ </TR>
+
+ <TR>
+ <TD ALIGN="right">Password</TD>
+ <TD>
+ <INPUT TYPE="text" NAME="_password" VALUE="<% $password %>" SIZE=<% $pmax2 %> MAXLENGTH=<% $passwordmax %>>
+ (blank to generate)
+ </TD>
+ </TR>
+
+ <TR>
+ <TD ALIGN="right">Access number</TD>
+ <TD><% FS::svc_acct_pop::popselector($popnum) %></TD>
+ </TR>
+ </TABLE>
+% }
+% }
+
+
+<INPUT TYPE="hidden" NAME="otaker" VALUE="<% $cust_main->otaker %>">
+<BR>
+<INPUT TYPE="submit" NAME="submit" VALUE="<% $custnum ? "Apply Changes" : "Add Customer" %>">
+<BR>
+</FORM>
+
+<% include('/elements/footer.html') %>
+
+<%init>
+
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('Edit customer');
+
+#for misplaced logic below
+#use FS::part_pkg;
+
+#for false laziness below (now more properly lazy)
+#use FS::svc_acct_pop;
+
+#for (other) false laziness below
+#use FS::agent;
+#use FS::type_pkgs;
+
+my $conf = new FS::Conf;
+
+#get record
+
+my($custnum, $username, $password, $popnum, $cust_main, $saved_pkgpart, $saved_domsvc);
+my(@invoicing_list);
+my ($ss,$stateid,$payinfo);
+my $same = '';
+if ( $cgi->param('error') ) {
+ $cust_main = new FS::cust_main ( {
+ map { $_, scalar($cgi->param($_)) } fields('cust_main')
+ } );
+ $custnum = $cust_main->custnum;
+ $saved_domsvc = $cgi->param('domsvc') || '';
+ if ( $saved_domsvc =~ /^(\d+)$/ ) {
+ $saved_domsvc = $1;
+ } else {
+ $saved_domsvc = '';
+ }
+ $saved_pkgpart = $cgi->param('pkgpart_svcpart') || '';
+ if ( $saved_pkgpart =~ /^(\d+)_/ ) {
+ $saved_pkgpart = $1;
+ } else {
+ $saved_pkgpart = '';
+ }
+ $username = $cgi->param('username');
+ $password = $cgi->param('_password');
+ $popnum = $cgi->param('popnum');
+ @invoicing_list = split( /\s*,\s*/, $cgi->param('invoicing_list') );
+ $same = $cgi->param('same');
+ $cust_main->setfield('paid' => $cgi->param('paid')) if $cgi->param('paid');
+ $ss = $cust_main->ss; # don't mask an entered value on errors
+ $stateid = $cust_main->stateid; # don't mask an entered value on errors
+ $payinfo = $cust_main->payinfo; # don't mask an entered value on errors
+} elsif ( $cgi->keywords ) { #editing
+ my( $query ) = $cgi->keywords;
+ $query =~ /^(\d+)$/;
+ $custnum=$1;
+ $cust_main = qsearchs('cust_main', { 'custnum' => $custnum } );
+ if ( $cust_main->dbdef_table->column('paycvv')
+ && length($cust_main->paycvv) ) {
+ my $paycvv = $cust_main->paycvv;
+ $paycvv =~ s/./*/g;
+ $cust_main->paycvv($paycvv);
+ }
+ $saved_pkgpart = 0;
+ $saved_domsvc = 0;
+ $username = '';
+ $password = '';
+ $popnum = 0;
+ @invoicing_list = $cust_main->invoicing_list;
+ $ss = $cust_main->masked('ss');
+ $stateid = $cust_main->masked('stateid');
+ $payinfo = $cust_main->paymask;
+} else {
+ $custnum='';
+ $cust_main = new FS::cust_main ( {} );
+ $cust_main->otaker( &getotaker );
+ $cust_main->referral_custnum( $cgi->param('referral_custnum') );
+ $saved_pkgpart = 0;
+ $saved_domsvc = 0;
+ $username = '';
+ $password = '';
+ $popnum = 0;
+ @invoicing_list = ();
+ push @invoicing_list, 'POST'
+ unless $conf->exists('disablepostalinvoicedefault');
+ $ss = '';
+ $stateid = '';
+ $payinfo = '';
+}
+
+my $error = $cgi->param('error');
+$cgi->delete_all();
+$cgi->param('error', $error);
+
+my $action = $custnum ? 'Edit' : 'Add';
+$action .= ": ". $cust_main->name if $custnum;
+
+my $r = qq!<font color="#ff0000">*</font>&nbsp;!;
+
+</%init>
diff --git a/httemplate/edit/cust_main/billing.html b/httemplate/edit/cust_main/billing.html
new file mode 100644
index 000000000..6ed35c15a
--- /dev/null
+++ b/httemplate/edit/cust_main/billing.html
@@ -0,0 +1,484 @@
+%if ( $payby_default eq 'HIDE' ) {
+%
+% $cust_main->payby('BILL') unless $cust_main->payby;
+
+ <INPUT TYPE="hidden" NAME="select" VALUE="<% $cust_main->payby %>">
+
+ </FORM>
+
+ <FORM NAME="<% $cust_main->payby %>" STYLE="margin-top: 0; margin-bottom: 0">
+
+ <INPUT TYPE="hidden" NAME="payinfo" VALUE="<% $cust_main->paymask %>">
+
+% foreach my $field (qw( payname paycvv paystart_month paystart_year payissue payip paytype paystate )) {
+
+ <INPUT TYPE="hidden" NAME="<% $field %>" VALUE="<% $cust_main->getfield($field) %>">
+
+% }
+
+% #false laziness w/elements/select-month_year.html & view/cust_main/billing.html
+% my( $mon, $year );
+% my $date = $cust_main->paydate || '12-2037';
+% if ( $date =~ /^(\d{4})-(\d{1,2})-\d{1,2}$/ ) { #PostgreSQL date format
+% ( $mon, $year ) = ( $2, $1 );
+% } elsif ( $date =~ /^(\d{1,2})-(\d{1,2}-)?(\d{4}$)/ ) {
+% ( $mon, $year ) = ( $1, $3 );
+% } else {
+% die "unrecognized expiration date format: $date";
+% }
+
+ <INPUT TYPE="hidden" NAME="exp_month" VALUE="<% $mon %>">
+ <INPUT TYPE="hidden" NAME="exp_year" VALUE="<% $year %>">
+
+ </FORM>
+
+ <FORM NAME="billing_bottomform" STYLE="margin-top: 0; margin-bottom: 0">
+
+ <INPUT TYPE="hidden" NAME="tax" VALUE="<% $cust_main->tax %>">
+
+ <INPUT TYPE="hidden" NAME="invoicing_list" VALUE="<% join(', ', @invoicing_list) %>">
+
+ </FORM>
+
+% } else {
+%
+% my $r = qq!<font color="#ff0000">*</font>&nbsp;!;
+
+ <BR>Billing information
+ <% &ntable("#cccccc") %>
+
+ <TR>
+ <TD ALIGN="right" WIDTH="200"><%$r%>Billing type</TD>
+
+ <SCRIPT>
+
+ var mywindow = -1;
+ function myopen(filename,windowname,properties) {
+ myclose();
+ mywindow = window.open(filename,windowname,properties);
+ }
+ function myclose() {
+ if ( mywindow != -1 )
+ mywindow.close();
+ mywindow = -1;
+ }
+
+ var achwindow = -1;
+ function achopen(filename,windowname,properties) {
+ achclose();
+ achwindow = window.open(filename,windowname,properties);
+ }
+ function achclose() {
+ if ( achwindow != -1 )
+ achwindow.close();
+ achwindow = -1;
+ }
+
+ function card_changed(what) {
+ if (
+ what.form.payinfo.value.substring(0, 4) == '4093'
+ || what.form.payinfo.value.substring(0, 4) == '4911'
+ || what.form.payinfo.value.substring(0, 4) == '4936'
+ || what.form.payinfo.value.substring(0, 6) == '564132'
+ || what.form.payinfo.value.substring(0, 2) == '63'
+ || what.form.payinfo.value.substring(0, 2) == '67'
+ )
+ {
+ what.form.paystart_month.disabled = false;
+ what.form.paystart_year.disabled = false;
+ what.form.payissue.disabled = false;
+ what.form.paystart_month.style.backgroundColor = '#ffffff';
+ what.form.paystart_year.style.backgroundColor = '#ffffff';
+ what.form.payissue.style.backgroundColor = '#ffffff';
+ document.getElementById('paystart_label').style.color = '#000000';
+ document.getElementById('payissue_label').style.color = '#000000';
+ } else {
+ what.form.paystart_month.disabled = true;
+ what.form.paystart_year.disabled = true;
+ what.form.payissue.disabled = true;
+ what.form.paystart_month.style.backgroundColor = '#dddddd';
+ what.form.paystart_year.style.backgroundColor = '#dddddd';
+ what.form.payissue.style.backgroundColor = '#dddddd';
+ document.getElementById('paystart_label').style.color = '#999999';
+ document.getElementById('payissue_label').style.color = '#999999';
+ }
+ return true;
+ }
+
+ </SCRIPT>
+
+ <SCRIPT TYPE="text/javascript" SRC="../elements/overlibmws.js"></SCRIPT>
+ <SCRIPT TYPE="text/javascript" SRC="../elements/overlibmws_iframe.js"></SCRIPT>
+ <SCRIPT TYPE="text/javascript" SRC="../elements/overlibmws_draggable.js"></SCRIPT>
+ <SCRIPT TYPE="text/javascript">
+ function OLiframeContent(src, width, height, name) {
+ return ('<iframe src="'+src+'" width="'+width+'" height="'+height+'"'
+ +(name?' name="'+name+'" id="'+name+'"':'')+' scrolling="auto">'
+ +'<div>[iframe not supported]</div></iframe>');
+ }
+ </SCRIPT>
+
+% my $payby = $cust_main->payby;
+% my $paytype = $cust_main->paytype;
+% my( $account, $aba ) = split('@', $payinfo);
+%
+% my $disabled = 'DISABLED style="background-color: #dddddd"';
+% my $text_disabled = 'style="color: #999999"';
+%
+% if ( $payby =~ /^(CARD|DCRD)$/ && cardtype($payinfo) =~ /^(Switch|Solo)$/ ) {
+% $disabled = 'style="background-color: #ffffff"';
+% $text_disabled = 'style="color: #000000";'
+% }
+%
+% my %payby = (
+%
+% 'CARD' =>
+%
+% '<TABLE BGCOLOR="#cccccc" BORDER=0 CELLSPACING=0 HEIGHT=192>'.
+%
+% qq!<TR><TD ALIGN="right" WIDTH="200">${r}Card number </TD>!.
+% qq!<TD WIDTH="408"><INPUT TYPE="text" NAME="payinfo" VALUE="!. ( $payby =~ /^(CARD|DCRD)$/ ? $payinfo : '' ). qq!" MAXLENGTH=19 onChange="card_changed(this)" onKeyUp="card_changed(this)"></TD></TR>!.
+%
+% qq!<TR><TD ALIGN="right" WIDTH="200">${r}Expiration </TD>!.
+% '<TD WIDTH="408">'.
+%
+% include('/elements/select-month_year.html',
+% 'prefix' => 'exp',
+% 'selected_date' =>
+% ( $payby =~ /^(CARD|DCRD)$/ ? $cust_main->paydate : '' ),
+% ).
+%
+% '</TD></TR>'.
+%
+% qq!<TR><TD ALIGN="right" WIDTH="200">CVV2&nbsp;!.
+%
+% qq!(<A HREF="javascript:void(0);" onClick="overlib( OLiframeContent('../docs/cvv2.html', 480, 352, 'cvv2_popup' ), CAPTION, 'CVV2 Help', STICKY, AUTOSTATUSCAP, CLOSECLICK, DRAGGABLE ); return false;">help</A>)!.
+% qq!</TD>!.
+% '<TD WIDTH="408"><INPUT TYPE="text" NAME="paycvv" VALUE="'. ( $payby =~ /^(CARD|DCRD)$/ && !$cust_main->is_encrypted($cust_main->paycvv) ? $cust_main->paycvv : '' ). '" SIZE=4 MAXLENGTH=4>'.
+%
+%
+% qq!<TR><TD ALIGN="right" WIDTH="200"><SPAN ID="paystart_label" $text_disabled>Start date </SPAN></TD>!.
+% '<TD WIDTH="408">'.
+%
+% include('/elements/select-month_year.html',
+% 'prefix' => 'paystart',
+% 'disabled' => $disabled,
+% 'empty_option' => 1,
+% 'start_year' => 2000,
+% 'end_year' => (localtime())[5] + 1900,
+% 'selected_date' => (
+% ( $payby =~ /^(CARD|DCRD)$/
+% && cardtype($payinfo) =~ /^(Switch|Solo)$/ )
+% ? $cust_main->paystart_month. '-'.
+% $cust_main->paystart_year
+% : ''
+% )
+% ).
+%
+% qq!<SPAN ID="payissue_label" $text_disabled> or Issue number </SPAN>!.
+% '<INPUT TYPE="text" NAME="payissue" VALUE="'. ( $payby =~ /^(CARD|DCRD)$/ ? $cust_main->payissue : '' ). qq!" SIZE=3 MAXLENGTH=2 $disabled></TD></TR>!.
+%
+% qq!<TR><TD ALIGN="right" WIDTH="200">${r}Exact name on card </TD>!.
+% qq!<TD WIDTH="408"><INPUT TYPE="text" NAME="payname" VALUE="!. ( $payby =~ /^(CARD|DCRD)$/ ? $cust_main->payname : '' ). qq!"></TD></TR>!.
+%
+% qq!<TR><TD COLSPAN=2 WIDTH="608"><INPUT TYPE="checkbox" NAME="payauto" !. ( $payby eq 'DCRD' ? '' : 'CHECKED' ). '> Charge future payments to this card automatically</TD></TR>'.
+%
+% '</TABLE>',
+%
+% 'CHEK' =>
+%
+% '<TABLE BGCOLOR="#cccccc" BORDER=0 CELLSPACING=0 HEIGHT=192>'.
+%
+% qq!<TR><TD ALIGN="right" WIDTH="200">${r}Account number </TD>!.
+% qq!<TD><INPUT TYPE="text" SIZE=12 NAME="payinfo1" VALUE="!. ( $payby =~ /^(CHEK|DCHK)$/ ? $account : '' ). '"></TD>'.
+% qq!<TD ALIGN="right">Type</TD><TD><SELECT NAME="paytype">!.
+% join('', map { qq!<OPTION VALUE="$_" !.($paytype eq $_ ? 'SELECTED' : '').">$_</OPTION>" } @FS::cust_main::paytypes).
+% qq!</SELECT></TD></TR>!.
+%
+% qq!<TR><TD ALIGN="right" WIDTH="200">${r}ABA/Routing number </TD>!.
+% qq!<TD COLSPAN="3" WIDTH="408"><INPUT TYPE="text" SIZE=10 MAXLENGTH=9 NAME="payinfo2" VALUE="!. ( $payby =~ /^(CHEK|DCHK)$/ ? $aba : '' ). qq!" SIZE=10 MAXLENGTH=9> !.
+% qq!(<A HREF="javascript:void(0);" onClick="overlib( OLiframeContent('../docs/ach.html', 380, 240, 'ach_popup' ), CAPTION, 'ACH Help', STICKY, AUTOSTATUSCAP, CLOSECLICK, DRAGGABLE ); return false;">help</A>)!.
+% qq!</TD></TR>!.
+%
+% qq!<INPUT TYPE="hidden" NAME="exp_month" VALUE="12">!.
+% qq!<INPUT TYPE="hidden" NAME="exp_year" VALUE="2037">!.
+%
+% qq!<TR><TD ALIGN="right" WIDTH="200">${r}Bank name </TD>!.
+% qq!<TD COLSPAN="3" WIDTH="408"><INPUT TYPE="text" NAME="payname" VALUE="!. ( $payby =~ /^(CHEK|DCHK)$/ ? $cust_main->payname : '' ). qq!"></TD></TR>!.
+% ( $conf->exists('show_bankstate') ?
+% qq!<TR><TD ALIGN="right" WIDTH="200">$paystate_label</TD>!.
+% qq!<TD COLSPAN="3" WIDTH="408">!.
+% include('select-state.html',
+% 'empty' => '(choose)',
+% 'state' => $cust_main->paystate,
+% 'country' => $cust_main->country,
+% 'prefix' => 'pay',
+% ). "</TD></TR>"
+% : '<INPUT TYPE="hidden" NAME="paystate" VALUE="'.
+% $cust_main->paystate. '">'
+% ).
+%
+%
+% qq!<TR><TD COLSPAN=4 WIDTH="608"><INPUT TYPE="checkbox" NAME="payauto" !. ( $payby eq 'DCHK' ? '' : 'CHECKED' ). '> Charge future payments to this electronic check automatically</TD></TR>'.
+%
+% '<TR><TD>&nbsp;</TD></TR>'.
+% '<TR><TD>&nbsp;</TD></TR>'.
+% '<TR><TD>&nbsp;</TD></TR>'.
+%
+% '</TABLE>',
+%
+% 'LECB' =>
+%
+% '<TABLE BGCOLOR="#cccccc" BORDER=0 CELLSPACING=0 HEIGHT=192>'.
+%
+% qq!<TR><TD ALIGN="right" WIDTH="200">${r}Phone number </TD>!.
+% qq!<TD WIDTH="408"><INPUT TYPE="text" NAME="payinfo" VALUE="!. ( $payby eq 'LECB' ? $cust_main->payinfo : '' ). qq!" MAXLENGTH=15 SIZE=16></TD></TR>!.
+%
+% qq!<INPUT TYPE="hidden" NAME="exp_month" VALUE="12">!.
+% qq!<INPUT TYPE="hidden" NAME="exp_year" VALUE="2037">!.
+% qq!<INPUT TYPE="hidden" NAME="payname" VALUE="">!.
+%
+% '<TR><TD>&nbsp;</TD></TR>'.
+% '<TR><TD>&nbsp;</TD></TR>'.
+% '<TR><TD>&nbsp;</TD></TR>'.
+% '<TR><TD>&nbsp;</TD></TR>'.
+% '<TR><TD>&nbsp;</TD></TR>'.
+% '<TR><TD>&nbsp;</TD></TR>'.
+%
+% '</TABLE>',
+%
+% 'BILL' =>
+%
+% '<TABLE BGCOLOR="#cccccc" BORDER=0 CELLSPACING=0 HEIGHT=192>'.
+%
+% qq!<TR><TD ALIGN="right" WIDTH="200">P.O. </TD>!.
+% qq!<TD WIDTH="408"><INPUT TYPE="text" NAME="payinfo" VALUE="!. ( $payby eq 'BILL' ? $cust_main->payinfo : '' ). qq!"></TD></TR>!.
+%
+% qq!<INPUT TYPE="hidden" NAME="exp_month" VALUE="12">!.
+% qq!<INPUT TYPE="hidden" NAME="exp_year" VALUE="2037">!.
+%
+% qq!<TR><TD ALIGN="right" WIDTH="200">Attention </TD>!.
+% qq!<TD WIDTH="408"><INPUT TYPE="text" NAME="payname" VALUE="!. ( $payby eq 'BILL' ? $cust_main->payname : '' ). qq!"></TD></TR>!.
+%
+% '<TR><TD>&nbsp;</TD></TR>'.
+% '<TR><TD>&nbsp;</TD></TR>'.
+% '<TR><TD>&nbsp;</TD></TR>'.
+% '<TR><TD>&nbsp;</TD></TR>'.
+% '<TR><TD>&nbsp;</TD></TR>'.
+%
+% '</TABLE>',
+%
+% 'COMP' =>
+%
+% '<TABLE BGCOLOR="#cccccc" BORDER=0 CELLSPACING=0 HEIGHT=192>'.
+%
+% qq!<TR><TD ALIGN="right" WIDTH="200">${r}Approved by </TD>!.
+% qq!<TD WIDTH="408"><INPUT TYPE="text" NAME="payinfo" VALUE=""></TD></TR>!.
+%
+% qq!<TR><TD ALIGN="right" WIDTH="200">${r}Expiration </TD>!.
+% '<TD WIDTH="408">'.
+%
+% include('/elements/select-month_year.html',
+% 'prefix' => 'exp',
+% 'selected_date' =>
+% ( $payby eq 'COMP' ? $cust_main->paydate : '' ),
+% ).
+%
+% '</TD></TR>'.
+%
+% '<TR><TD>&nbsp;</TD></TR>'.
+% '<TR><TD>&nbsp;</TD></TR>'.
+% '<TR><TD>&nbsp;</TD></TR>'.
+% '<TR><TD>&nbsp;</TD></TR>'.
+% '<TR><TD>&nbsp;</TD></TR>'.
+%
+% '</TABLE>',
+%
+% 'CASH' =>
+%
+% '<TABLE BGCOLOR="#cccccc" BORDER=0 CELLSPACING=0 HEIGHT=192>'.
+%
+% qq!<TR><TD ALIGN="right" WIDTH="200">${r}Amount </TD>!.
+% qq!<TD WIDTH="408"><INPUT TYPE="text" NAME="paid" VALUE="!. ( $payby eq 'CASH' ? $cust_main->paid : '' ). qq!"></TD></TR>!.
+%
+% '<TR><TD>&nbsp;</TD></TR>'.
+% '<TR><TD>&nbsp;</TD></TR>'.
+% '<TR><TD>&nbsp;</TD></TR>'.
+% '<TR><TD>&nbsp;</TD></TR>'.
+% '<TR><TD>&nbsp;</TD></TR>'.
+% '<TR><TD>&nbsp;</TD></TR>'.
+%
+% '</TABLE>',
+%
+% 'WEST' =>
+%
+% '<TABLE BGCOLOR="#cccccc" BORDER=0 CELLSPACING=0 HEIGHT=192>'.
+%
+% qq!<TR><TD ALIGN="right" WIDTH="200">${r}Amount </TD>!.
+% qq!<TD WIDTH="408"><INPUT TYPE="text" NAME="paid" VALUE="!. ( $payby eq 'WEST' ? $cust_main->paid : '' ). qq!"></TD></TR>!.
+%
+% '<TR><TD>&nbsp;</TD></TR>'.
+% '<TR><TD>&nbsp;</TD></TR>'.
+% '<TR><TD>&nbsp;</TD></TR>'.
+% '<TR><TD>&nbsp;</TD></TR>'.
+% '<TR><TD>&nbsp;</TD></TR>'.
+% '<TR><TD>&nbsp;</TD></TR>'.
+%
+% '</TABLE>',
+%
+% 'MCRD' =>
+%
+% '<TABLE BGCOLOR="#cccccc" BORDER=0 CELLSPACING=0 HEIGHT=192>'.
+%
+% qq!<TR><TD ALIGN="right" WIDTH="200">${r}Amount </TD>!.
+% qq!<TD WIDTH="408"><INPUT TYPE="text" NAME="paid" VALUE="!. ( $payby eq 'MCRD' ? $cust_main->paid : '' ). qq!"></TD></TR>!.
+%
+% '<TR><TD>&nbsp;</TD></TR>'.
+% '<TR><TD>&nbsp;</TD></TR>'.
+% '<TR><TD>&nbsp;</TD></TR>'.
+% '<TR><TD>&nbsp;</TD></TR>'.
+% '<TR><TD>&nbsp;</TD></TR>'.
+% '<TR><TD>&nbsp;</TD></TR>'.
+%
+% '</TABLE>',
+%
+% );
+%
+% #this should use FS::payby
+% my %allopt = (
+% 'CARD' => 'Credit card',
+% 'CHEK' => 'Electronic check',
+% 'LECB' => 'Phone bill billing',
+% 'BILL' => 'Billing',
+% 'CASH' => 'Cash', # initial payment, then billing',
+% 'WEST' => 'Western Union', # initial payment, then billing',
+% 'MCRD' => 'Manual credit card', # initial payment, then billing',
+% 'COMP' => 'Complimentary',
+% );
+% if ( $cust_main->custnum ) { #don't offer CASH/WEST/MCRD initial payment types
+% # when editing customer
+% delete $allopt{$_} for qw(CASH WEST MCRD);
+% }
+%
+% tie my %options, 'Tie::IxHash',
+% map { $_ => $allopt{$_} }
+% grep { exists $allopt{$_} }
+% @payby;
+%
+% my %payby2option = (
+% ( map { $_ => $_ } keys %options ),
+% 'DCRD' => 'CARD',
+% 'DCHK' => 'CHEK',
+% );
+%
+% my $widget = new HTML::Widgets::SelectLayers(
+% 'options' => \%options,
+% #'form_name' => 'dummy',
+% #'form_action' => 'nothingyet',
+% #chops bottom of page in IE# 'under_position' => 'absolute',
+% 'html_between' => '</TD></TR></TABLE>',
+% 'selected_layer' => $payby2option{$payby || $payby_default || $payby[0] },
+% 'layer_callback' => sub { my $layer = shift; $payby{$layer}; },
+% );
+%
+%
+
+
+ <TD WIDTH="408"><% $widget->html %>
+
+ <FORM NAME="billing_bottomform" STYLE="margin-top: 0; margin-bottom: 0">
+
+ <% &ntable("#cccccc") %>
+
+ <TR><TD>&nbsp;</TD></TR>
+
+ <TR>
+ <TD WIDTH="608" COLSPAN="2"><INPUT TYPE="checkbox" NAME="tax" VALUE="Y" <% $cust_main->tax eq "Y" ? 'CHECKED' : '' %>> Tax Exempt</TD>
+ </TR>
+
+% unless ( $conf->exists('emailinvoiceonly') ) {
+
+ <TR>
+ <TD WIDTH="608" COLSPAN="2"><INPUT TYPE="checkbox" NAME="invoicing_list_POST" VALUE="POST" <%
+
+ ( grep { $_ eq 'POST' } @invoicing_list )
+
+ ? 'CHECKED'
+ : ''
+
+ %>> Postal mail invoice
+
+ </TD>
+ </TR>
+
+ <TR>
+ <TD WIDTH="608" COLSPAN="2"><INPUT TYPE="checkbox" NAME="invoicing_list_FAX" VALUE="FAX" <%
+
+ ( grep { $_ eq 'FAX' } @invoicing_list )
+ ? 'CHECKED'
+ : ''
+
+ %>> Fax invoice
+
+ </TD>
+ </TR>
+
+% }
+
+ <TR>
+ <TD ALIGN="right" WIDTH="200">
+ <% $conf->exists('cust_main-require_invoicing_list_email') ? $r : '' %>Email address(es)
+ </TD>
+ <TD WIDTH="408"><INPUT TYPE="text" NAME="invoicing_list" VALUE="<% join(', ', grep { $_ !~ /^(POST|FAX)$/ } @invoicing_list ) %>"></TD>
+ </TR>
+
+ <TR>
+ <TD ALIGN="right" WIDTH="200">Invoice terms </TD>
+ <TD WIDTH="408">
+ <SELECT NAME="invoice_terms">
+ <OPTION VALUE="">Default (<% $conf->config('invoice_default_terms') || 'Payable upon receipt' %>)
+% foreach my $term ( 'Payable upon receipt',
+% ( map "Net $_", 0, 10, 15, 30, 45, 60 ),
+% ) {
+ <OPTION VALUE="<% $term %>" <% $cust_main->invoice_terms eq $term ? ' SELECTED' : '' %>><% $term %>
+% }
+ </SELECT>
+ </TD>
+ </TR>
+
+% if ( $conf->exists('voip-cust_cdr_spools') ) {
+ <TR>
+ <TD COLSPAN="2"><INPUT TYPE="checkbox" NAME="spool_cdr" VALUE="Y" <% $cust_main->spool_cdr eq "Y" ? 'CHECKED' : '' %>> Spool CDRs</TD>
+ </TR>
+% } else {
+
+ <INPUT TYPE="hidden" NAME="spool_cdr" VALUE="<% $cust_main->spool_cdr %>">
+% }
+
+ </TABLE>
+
+ </FORM>
+
+ <% $r %> required fields
+% }
+
+<%once>
+
+my $paystate_label = FS::Msgcat::_gettext('paystate');
+$paystate_label = 'Bank state' if $paystate_label =~/^paystate$/;
+
+</%once>
+<%init>
+
+my( $cust_main, %options ) = @_;
+my @invoicing_list = @{ $options{'invoicing_list'} };
+my $payinfo = $options{'payinfo'};
+my $conf = new FS::Conf;
+my $payby_default = $conf->config('payby-default');
+
+my @payby = grep /\w/, $conf->config('payby');
+#@payby = (qw( CARD DCRD CHEK DCHK LECB BILL CASH WEST COMP ))
+@payby = (qw( CARD DCRD CHEK DCHK LECB BILL CASH COMP ))
+ unless @payby;
+
+</%init>
diff --git a/httemplate/edit/cust_main/contact.html b/httemplate/edit/cust_main/contact.html
new file mode 100644
index 000000000..21c6b2990
--- /dev/null
+++ b/httemplate/edit/cust_main/contact.html
@@ -0,0 +1,183 @@
+<% &ntable("#cccccc") %>
+
+<TR>
+ <TH ALIGN="right"><%$r%>Contact&nbsp;name<BR>(last,&nbsp;first)</TH>
+ <TD COLSPAN=5>
+ <INPUT TYPE="text" NAME="<%$pre%>last" VALUE="<% $cust_main->get($pre.'last') %>" onChange="<% $onchange %>" <%$disabled%>> ,
+ <INPUT TYPE="text" NAME="<%$pre%>first" VALUE="<% $cust_main->get($pre.'first') %>" onChange="<% $onchange %>" <%$disabled%>>
+ </TD>
+% if ( $conf->exists('show_ss') && !$pre ) {
+
+ <TD ALIGN="right">SS#</TD>
+ <TD><INPUT TYPE="text" NAME="ss" VALUE="<% $opt{ss} %>" SIZE=11></TD>
+% } elsif ( !$pre ) {
+
+ <TD><INPUT TYPE="hidden" NAME="ss" VALUE="<% $opt{ss} %>"></TD>
+% }
+
+
+</TR>
+
+<TR>
+ <TD ALIGN="right">Company</TD>
+ <TD COLSPAN=7>
+ <INPUT TYPE="text" NAME="<%$pre%>company" VALUE="<% $cust_main->get($pre.'company') %>" SIZE=70 onChange="<% $onchange %>" <%$disabled%>>
+ </TD>
+</TR>
+
+<TR>
+ <TH ALIGN="right"><%$r%>Address</TH>
+ <TD COLSPAN=7>
+ <INPUT TYPE="text" NAME="<%$pre%>address1" VALUE="<% $cust_main->get($pre.'address1') %>" SIZE=70 onChange="<% $onchange %>" <%$disabled%>>
+ </TD>
+</TR>
+
+% my $address2_label_style =
+% ( $disabled
+% || ! $conf->exists('cust_main-require_address2')
+% || ( !$pre && !$opt{'same_checked'} )
+% )
+% ? 'visibility:hidden'
+% : '';
+
+<TR>
+ <TD ALIGN="right"><FONT ID="<% $pre %>address2_required" color="#ff0000" STYLE="<% $address2_label_style %>">*</FONT>&nbsp;<FONT ID="<% $pre %>address2_label" STYLE="<% $address2_label_style %>"><B>Unit&nbsp;#</B></FONT></TD>
+ <TD COLSPAN=7>
+ <INPUT TYPE="text" NAME="<%$pre%>address2" VALUE="<% $cust_main->get($pre.'address2') %>" SIZE=70 onChange="<% $onchange %>" <%$disabled%>>
+ </TD>
+</TR>
+
+<TR>
+ <TH ALIGN="right"><%$r%>City</TH>
+ <TD>
+ <INPUT TYPE="text" NAME="<%$pre%>city" VALUE="<% $cust_main->get($pre.'city') %>" onChange="<% $onchange %>" <%$disabled%>>
+ </TD>
+ <TH ALIGN="right" ID="<%$pre%>countylabel" <%$county_style%>><%$r%>County</TH>
+ <TD>
+ <% include('select-county.html', %select_hash ) %>
+ </TD>
+ <TH ALIGN="right"><%$r%>State</TH>
+ <TD>
+ <% include('select-state.html', %select_hash ) %>
+ </TD>
+ <TH><%$r%>Zip</TH>
+ <TD>
+ <INPUT TYPE="text" NAME="<%$pre%>zip" VALUE="<% $cust_main->get($pre.'zip') %>" SIZE=10 onChange="<% $onchange %>" <%$disabled%>>
+ </TD>
+</TR>
+
+<TR>
+ <TH ALIGN="right"><%$r%>Country</TH>
+ <TD COLSPAN=5><% include('select-country.html', %select_hash ) %></TD>
+</TR>
+
+<TR>
+ <TD ALIGN="right"><% $daytime_label %></TD>
+ <TD COLSPAN=5>
+ <INPUT TYPE="text" NAME="<%$pre%>daytime" VALUE="<% $cust_main->get($pre.'daytime') %>" SIZE=18 onChange="<% $onchange %>" <%$disabled%>>
+ </TD>
+</TR>
+
+<TR>
+ <TD ALIGN="right"><% $night_label %></TD>
+ <TD COLSPAN=5>
+ <INPUT TYPE="text" NAME="<%$pre%>night" VALUE="<% $cust_main->get($pre.'night') %>" SIZE=18 onChange="<% $onchange %>" <%$disabled%>>
+ </TD>
+</TR>
+
+<TR>
+ <TD ALIGN="right">Fax</TD>
+ <TD COLSPAN=5>
+ <INPUT TYPE="text" NAME="<%$pre%>fax" VALUE="<% $cust_main->get($pre.'fax') %>" SIZE=12 onChange="<% $onchange %>" <%$disabled%>>
+ </TD>
+</TR>
+
+% if ( $conf->exists('show_stateid') && !$pre ) {
+
+<TR>
+ <TD ALIGN="right"><% $stateid_label %></TD>
+ <TD><INPUT TYPE="text" NAME="stateid" VALUE="<% $opt{stateid} %>" SIZE=12 onChange="<% $onchange %>" <%$disabled%>></TD>
+ <TD ALIGN="right"><% $stateid_state_label %></TD>
+ <TD><% include('select-state.html', 'state' => $cust_main->stateid_state,
+ 'country' => $cust_main->country,
+ 'prefix' => 'stateid_',
+ 'onchange' => $onchange,
+ 'disabled' => $disabled) %></TD>
+</TR>
+% } elsif ( !$pre ) {
+
+ <TD><INPUT TYPE="hidden" NAME="stateid" VALUE="<% $opt{stateid} %>"></TD>
+ <TD><INPUT TYPE="hidden" NAME="stateid_state" VALUE="<% $cust_main->stateid_state %>"></TD>
+% }
+
+</TABLE>
+<%$r%>required fields<BR>
+
+<%init>
+
+#my( $cust_main, $pre, $onchange, $disabled, %opt ) = @_;
+my %opt = @_;
+my $cust_main = $opt{'cust_main'};
+my $pre = $opt{'pre'};
+my $onchange = $opt{'onchange'};
+my $disabled = $opt{'disabled'};
+
+my $conf = new FS::Conf;
+
+foreach (qw(ss stateid)) {
+ $opt{$_} = $cust_main->masked($_) unless exists $opt{$_};
+}
+
+#false laziness with ship state
+my $countrydefault = $conf->config('countrydefault') || 'US';
+$cust_main->set($pre.'country', $countrydefault )
+ unless $cust_main->get($pre.'country');
+
+my $statedefault = $conf->config('statedefault')
+ || ($countrydefault eq 'US' ? 'CA' : '');
+$cust_main->set($pre.'state', $statedefault )
+ unless $cust_main->get($pre.'state')
+ || $cust_main->get($pre.'country') ne $countrydefault;
+
+$cust_main->set('stateid_state', $cust_main->state )
+ unless $pre || $cust_main->get('stateid_state');
+
+#my($county_html, $state_html, $country_html) =
+# FS::cust_main_county::regionselector( $cust_main->get($pre.'county'),
+# $cust_main->get($pre.'state'),
+# $cust_main->get($pre.'country'),
+# $pre,
+# $onchange,
+# $disabled,
+# );
+
+my %select_hash = (
+ 'county' => $cust_main->get($pre.'county'),
+ 'state' => $cust_main->get($pre.'state'),
+ 'country' => $cust_main->get($pre.'country'),
+ 'prefix' => $pre,
+ 'onchange' => $onchange,
+ 'disabled' => $disabled,
+);
+
+my @counties = counties( $cust_main->get($pre.'state'),
+ $cust_main->get($pre.'country'),
+ );
+my $county_style = scalar(@counties) > 1 ? '' : 'STYLE="visibility:hidden"';
+
+my $daytime_label = FS::Msgcat::_gettext('daytime') =~ /^(daytime)?$/
+ ? 'Day Phone'
+ : FS::Msgcat::_gettext('daytime');
+my $night_label = FS::Msgcat::_gettext('night') =~/^(night)?$/
+ ? 'Night Phone'
+ : FS::Msgcat::_gettext('night') || 'Night Phone';
+my $stateid_label = FS::Msgcat::_gettext('stateid') =~ /^(stateid)?$/
+ ? 'Driver&rsquo;s License'
+ : FS::Msgcat::_gettext('stateid') || 'Driver&rsquo;s License';
+my $stateid_state_label = FS::Msgcat::_gettext('stateid_state') =~ /^(stateid_state)?$/
+ ? 'Driver&rsquo;s License State'
+ : FS::Msgcat::_gettext('stateid_state') || 'Driver&rsquo;s License State';
+
+my $r = qq!<font color="#ff0000">*</font>&nbsp;!;
+
+</%init>
diff --git a/httemplate/edit/cust_main/select-country.html b/httemplate/edit/cust_main/select-country.html
new file mode 100644
index 000000000..137f61975
--- /dev/null
+++ b/httemplate/edit/cust_main/select-country.html
@@ -0,0 +1,76 @@
+
+<% include('/elements/xmlhttp.html',
+ 'url' => $p.'misc/states.cgi',
+ 'subs' => [ $opt{'prefix'}. 'get_states' ],
+ )
+%>
+
+<SCRIPT TYPE="text/javascript">
+
+ function opt(what,value,text) {
+ var optionName = new Option(text, value, false, false);
+ var length = what.length;
+ what.options[length] = optionName;
+ }
+
+ function <% $opt{'prefix'} %>country_changed(what, callback) {
+
+ country = what.options[what.selectedIndex].value;
+
+ function <% $opt{'prefix'} %>update_states(states) {
+
+ // blank the current state list
+ for ( var i = what.form.<% $opt{'prefix'} %>state.length; i >= 0; i-- )
+ what.form.<% $opt{'prefix'} %>state.options[i] = null;
+
+ // add the new states
+ var statesArray = eval('(' + states + ')' );
+ for ( var s = 0; s < statesArray.length; s=s+2 ) {
+ var stateLabel = statesArray[s+1];
+ if ( stateLabel == "" )
+ stateLabel = '(n/a)';
+ opt(what.form.<% $opt{'prefix'} %>state, statesArray[s], stateLabel);
+ }
+
+ //run the callback
+ if ( callback != null )
+ callback();
+ }
+
+ // go get the new states
+ <% $opt{'prefix'} %>get_states( country, <% $opt{'prefix'} %>update_states );
+
+ }
+
+</SCRIPT>
+
+<SELECT NAME="<% $opt{'prefix'} %>country" onChange="<% $opt{'prefix'} %>country_changed(this); <% $opt{'onchange'} %>" <% $opt{'disabled'} %>>
+
+% foreach my $country (
+% sort { ($b eq $countrydefault) <=> ($a eq $countrydefault)
+% or code2country($a) cmp code2country($b) }
+% map { $_->country }
+% qsearch({
+% 'select' => 'country',
+% 'table' => 'cust_main_county',
+% 'hashref' => {},
+% 'extra_sql' => 'GROUP BY country',
+% })
+% ) {
+
+ <OPTION VALUE="<% $country %>"<% $country eq $opt{'country'} ? ' SELECTED' : '' %>><% code2country($country). " ($country)" %>
+
+% }
+
+</SELECT>
+
+<%init>
+my %opt = @_;
+foreach my $opt (qw( county state country prefix onchange disabled )) {
+ $opt{$_} = '' unless exists($opt{$_}) && defined($opt{$_});
+}
+
+my $conf = new FS::Conf;
+my $countrydefault = $conf->config('countrydefault') || 'US';
+</%init>
+
diff --git a/httemplate/edit/cust_main/select-county.html b/httemplate/edit/cust_main/select-county.html
new file mode 100644
index 000000000..0dc826896
--- /dev/null
+++ b/httemplate/edit/cust_main/select-county.html
@@ -0,0 +1,113 @@
+% if ( $countyflag ) {
+
+ <% include('/elements/xmlhttp.html',
+ 'url' => $p.'misc/counties.cgi',
+ 'subs' => [ $opt{'prefix'}. 'get_counties' ],
+ )
+ %>
+
+ <SCRIPT TYPE="text/javascript">
+
+ function opt(what,value,text) {
+ var optionName = new Option(text, value, false, false);
+ var length = what.length;
+ what.options[length] = optionName;
+ }
+
+ function <% $opt{'prefix'} %>state_changed(what, callback) {
+
+ state = what.options[what.selectedIndex].value;
+ country = what.form.<% $opt{'prefix'} %>country.options[what.form.<% $opt{'prefix'} %>country.selectedIndex].value;
+
+ function <% $opt{'prefix'} %>update_counties(counties) {
+
+ // blank the current county list
+ for ( var i = what.form.<% $opt{'prefix'} %>county.length; i >= 0; i-- )
+ what.form.<% $opt{'prefix'} %>county.options[i] = null;
+
+ // add the new counties
+ var countiesArray = eval('(' + counties + ')' );
+ for ( var s = 0; s < countiesArray.length; s++ ) {
+ var countyLabel = countiesArray[s];
+ if ( countyLabel == "" )
+ countyLabel = '(n/a)';
+ opt(what.form.<% $opt{'prefix'} %>county, countiesArray[s], countyLabel);
+ }
+
+ var countyFormLabel = document.getElementById('<% $opt{'prefix'} %>countylabel');
+
+ if ( countiesArray.length > 1 ) {
+ what.form.<% $opt{'prefix'} %>county.style.display = '';
+ countyFormLabel.style.visibility = 'visible';
+ } else {
+ what.form.<% $opt{'prefix'} %>county.style.display = 'none';
+ countyFormLabel.style.visibility = 'hidden';
+ }
+
+ //run the callback
+ if ( callback != null )
+ callback();
+ }
+
+ // go get the new counties
+ <% $opt{'prefix'} %>get_counties( state, country, <% $opt{'prefix'} %>update_counties );
+
+ }
+
+ </SCRIPT>
+
+ <SELECT NAME="<% $opt{'prefix'} %>county" onChange="<% $opt{'onchange'} %>" <% $opt{'disabled'} %>>
+
+% foreach my $county ( @counties ) {
+
+ <OPTION VALUE="<% $county %>"<% $county eq $opt{'county'} ? ' SELECTED' : '' %>><% $county %>
+
+% }
+
+ </SELECT>
+
+% } else {
+
+
+ <SCRIPT TYPE="text/javascript">
+ function <% $opt{'prefix'} %>state_changed(what) {
+ }
+ </SCRIPT>
+
+ <INPUT TYPE="hidden" NAME="<% $opt{'prefix'} %>county" VALUE="<% $opt{'county'} %>">
+
+% }
+
+<%init>
+
+my %opt = @_;
+foreach my $opt (qw( county state country prefix onchange disabled )) {
+ $opt{$_} = '' unless exists($opt{$_}) && defined($opt{$_});
+}
+
+my @counties = ();
+if ( $countyflag ) {
+
+ @counties = counties( $opt{'state'}, $opt{'country'} );
+
+ # this is very hacky
+ unless ( scalar(@counties) > 1 ) {
+ if ( $opt{'disabled'} =~ /STYLE=/i ) {
+ $opt{'disabled'} =~ s/STYLE="([^"]+)"/STYLE="$1; display:none"/i;
+ } else {
+ $opt{'disabled'} .= ' STYLE="display:none"';
+ }
+ }
+
+}
+
+</%init>
+<%once>
+
+my $sql = "SELECT COUNT(*) FROM cust_main_county".
+ " WHERE county IS NOT NULL AND county != ''";
+my $sth = dbh->prepare($sql) or die dbh->errstr;
+$sth->execute or die $sth->errstr;
+my $countyflag = $sth->fetchrow_arrayref->[0];
+
+</%once>
diff --git a/httemplate/edit/cust_main/select-domain.html b/httemplate/edit/cust_main/select-domain.html
new file mode 100644
index 000000000..bec1e834c
--- /dev/null
+++ b/httemplate/edit/cust_main/select-domain.html
@@ -0,0 +1,67 @@
+
+<% include('/elements/xmlhttp.html',
+ 'url' => $p.'misc/svc_acct-domains.cgi',
+ 'subs' => [ $opt{'prefix'}. 'get_domains' ],
+ )
+%>
+
+<SCRIPT TYPE="text/javascript">
+
+ function selopt(what,value,text,selected) {
+ var optionName = new Option(text, value, false, selected);
+ var length = what.length;
+ what.options[length] = optionName;
+ }
+
+ function <% $opt{'prefix'} %>pkgpart_svcpart_changed(what,selected) {
+
+ pkgpart_svcpart = what.options[what.selectedIndex].value;
+
+ function <% $opt{'prefix'} %>update_domains(domains) {
+
+ // blank the current domain list
+ for ( var i = what.form.<% $opt{'prefix'} %>domsvc.length; i >= 0; i-- )
+ what.form.<% $opt{'prefix'} %>domsvc.options[i] = null;
+
+ // add the new domains
+ var domainArray = eval('(' + domains + ')' );
+ for ( var s = 0; s < domainArray.length; s=s+2 ) {
+ var domainLabel = domainArray[s+1];
+ if ( domainLabel == "" )
+ domainLabel = '(n/a)';
+ selopt(what.form.<% $opt{'prefix'} %>domsvc, domainArray[s], domainLabel, (domainArray[s] == selected) ? true : false);
+ }
+
+ }
+
+ // go get the new domains
+ <% $opt{'prefix'} %>get_domains( pkgpart_svcpart, <% $opt{'prefix'} %>update_domains );
+
+ }
+
+</SCRIPT>
+
+<SELECT NAME="<% $opt{'prefix'} %>pkgpart_svcpart" onchange="<% $opt{'prefix'} %>pkgpart_svcpart_changed(this,0);" >
+ <OPTION VALUE="">(none)
+
+% foreach my $part_pkg ( @part_pkg ) {
+
+ <OPTION VALUE="<% $part_pkg->pkgpart. "_". $part_pkg->svcpart('svc_acct') %>"<% ( $opt{saved_pkgpart} && $part_pkg->pkgpart == $opt{saved_pkgpart} ) ? ' SELECTED' : '' %>><% $part_pkg->pkg. " - ". $part_pkg->comment %>
+
+% }
+
+</SELECT>
+<SCRIPT>
+ pkgpart_svcpart_changed(document.bottomform.pkgpart_svcpart, <% $opt{saved_domsvc} %>);
+</SCRIPT>
+
+<%init>
+my %opt = @_;
+foreach my $opt (qw( svc_part pkgparts saved_pkgpart saved_domsvc prefix)) {
+ $opt{$_} = '' unless exists($opt{$_}) && defined($opt{$_});
+}
+$opt{saved_domsvc} = 0 unless $opt{saved_domsvc};
+my @part_pkg = @{$opt{'pkgparts'}};
+
+</%init>
+
diff --git a/httemplate/edit/cust_main/select-state.html b/httemplate/edit/cust_main/select-state.html
new file mode 100644
index 000000000..4f1c056b5
--- /dev/null
+++ b/httemplate/edit/cust_main/select-state.html
@@ -0,0 +1,24 @@
+<SELECT NAME="<% $opt{'prefix'} %>state" onChange="<% $opt{'prefix'} %>state_changed(this); <% $opt{'onchange'} %>" <% $opt{'disabled'} %>>
+
+% if ($opt{empty}) {
+ <OPTION VALUE=""<% $opt{state} eq '' ? ' SELECTED' : '' %>><% $opt{empty} %>
+% }
+
+% foreach my $state ( keys %states ) {
+
+ <OPTION VALUE="<% $state %>"<% $state eq $opt{'state'} ? ' SELECTED' : '' %>><% $states{$state} || '(n/a)' %>
+
+% }
+
+
+</SELECT>
+
+<%init>
+my %opt = @_;
+foreach my $opt (qw( county state country prefix onchange disabled empty )) {
+ $opt{$_} = '' unless exists($opt{$_}) && defined($opt{$_});
+}
+
+tie my %states, 'Tie::IxHash', states_hash( $opt{'country'} );
+</%init>
+
diff --git a/httemplate/edit/cust_main_county-expand.cgi b/httemplate/edit/cust_main_county-expand.cgi
new file mode 100755
index 000000000..d5297ab0a
--- /dev/null
+++ b/httemplate/edit/cust_main_county-expand.cgi
@@ -0,0 +1,50 @@
+<% include('/elements/header-popup.html', "Enter $title") %>
+
+<% include('/elements/error.html') %>
+
+<FORM ACTION="<% $p1 %>process/cust_main_county-expand.cgi" METHOD=POST>
+
+<INPUT TYPE="hidden" NAME="taxnum" VALUE="<% $taxnum %>">
+
+<TEXTAREA NAME="expansion" COLS="50" ROWS="16"><% $expansion |h %></TEXTAREA>
+
+<BR>
+<INPUT TYPE="submit" VALUE="Add <% $title %>">
+
+</FORM>
+</BODY>
+</HTML>
+
+<%init>
+
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('Configuration');
+
+my($taxnum, $expansion);
+my($query) = $cgi->keywords;
+if ( $cgi->param('error') ) {
+ $taxnum = $cgi->param('taxnum');
+ $expansion = $cgi->param('expansion');
+} else {
+ $query =~ /^(\d+)$/
+ or die "Illegal taxnum (query $query)";
+ $taxnum = $1;
+ $expansion = '';
+}
+
+my $cust_main_county = qsearchs('cust_main_county',{'taxnum'=>$taxnum})
+ or die "cust_main_county.taxnum $taxnum not found";
+
+my $title;
+
+die "Can't expand entry!" if $cust_main_county->county;
+
+if ( $cust_main_county->state ) {
+ $title = 'Counties';
+} else {
+ $title = 'States/Provinces';
+}
+
+my $p1 = popurl(1);
+
+</%init>
diff --git a/httemplate/edit/cust_main_county.html b/httemplate/edit/cust_main_county.html
new file mode 100644
index 000000000..510839d71
--- /dev/null
+++ b/httemplate/edit/cust_main_county.html
@@ -0,0 +1,62 @@
+<% include('elements/edit.html',
+ 'popup' => 1,
+ 'name' => 'Tax rate', #Edit tax rate
+ 'table' => 'cust_main_county',
+ 'labels' => { 'taxnum' => 'Tax',
+ 'country' => 'Country',
+ 'state' => 'State',
+ 'county' => 'County',
+ 'taxclass' => 'Tax class',
+ 'taxname' => 'Tax name',
+ 'tax' => 'Tax rate',
+ 'setuptax' => 'This tax not applicable to setup fees',
+ 'recurtax' => 'This tax not applicable to recurring fees',
+ 'exempt_amount' => 'Monthly exemption per customer ($25 "Texas tax")',
+ },
+ 'fields' => \@fields,
+ )
+%>
+<%once>
+
+my $conf = new FS::Conf;
+
+</%once>
+
+<%init>
+
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('Configuration');
+
+my $taxnum;
+if ( $cgi->param('error') ) {
+ $cgi->param('taxnum') =~ /^(\d+)$/ or die 'error, but no taxnum';
+ $taxnum = $1;
+} else {
+ my($query) = $cgi->keywords;
+ $query =~ /^(\d+)$/ or die 'no taxnum';
+ $taxnum = $1;
+}
+
+my $cust_main_county = qsearchs('cust_main_county', { 'taxnum' => $taxnum })
+ or die "unknown taxnum $1";
+
+my @fields = (
+ { field=>'country', type=>'fixed-country', },
+ { field=>'state', type=>'fixed-state', },
+ { field=>'county', type=>'fixed', },
+);
+
+push @fields, { field=>'taxclass', type=>'fixed', }
+ if $conf->exists('enable_taxclasses');
+
+push @fields,
+ 'taxname',
+ { field=>'tax', type=>'percentage', },
+
+ { type=>'tablebreak-tr-title', value=>'Exemptions' },
+ { field=>'setuptax', type=>'checkbox', value=>'Y', },
+ { field=>'recurtax', type=>'checkbox', value=>'Y', },
+ { field=>'exempt_amount', type=>'money', },
+;
+
+</%init>
diff --git a/httemplate/edit/cust_main_note.cgi b/httemplate/edit/cust_main_note.cgi
new file mode 100755
index 000000000..6c6a1a9a0
--- /dev/null
+++ b/httemplate/edit/cust_main_note.cgi
@@ -0,0 +1,45 @@
+<% include('/elements/header-popup.html', "$action Customer Note") %>
+
+<% include('/elements/error.html') %>
+
+<FORM ACTION="<% popurl(1) %>process/cust_main_note.cgi" METHOD=POST>
+<INPUT TYPE="hidden" NAME="custnum" VALUE="<% $custnum %>">
+<INPUT TYPE="hidden" NAME="notenum" VALUE="<% $notenum %>">
+
+
+<BR><BR>
+<TEXTAREA NAME="comment" ROWS="12" COLS="60">
+<% $comment %>
+</TEXTAREA>
+
+<BR><BR>
+<INPUT TYPE="submit" VALUE="<% $notenum ? "Apply Changes" : "Add Note" %>">
+
+</FORM>
+</BODY>
+</HTML>
+
+<%init>
+
+my $comment;
+my $notenum = '';
+if ( $cgi->param('error') ) {
+ $comment = $cgi->param('comment');
+} elsif ( $cgi->param('notenum') =~ /^(\d+)$/ ) {
+ $notenum = $1;
+ die "illegal query ". $cgi->keywords unless $notenum;
+ my $note = qsearchs('cust_main_note', { 'notenum' => $notenum });
+ die "no such note: ". $notenum unless $note;
+ $comment = $note->comments;
+}
+
+$cgi->param('custnum') =~ /^(\d+)$/ or die "illeagl custnum";
+my $custnum = $1;
+
+my $action = $notenum ? 'Edit' : 'Add';
+
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right("$action customer note");
+
+</%init>
+
diff --git a/httemplate/edit/cust_pay.cgi b/httemplate/edit/cust_pay.cgi
new file mode 100755
index 000000000..92abb7be8
--- /dev/null
+++ b/httemplate/edit/cust_pay.cgi
@@ -0,0 +1,147 @@
+% if ( $link eq 'popup' ) {
+ <% include('/elements/header-popup.html', $title ) %>
+% } else {
+ <% include("/elements/header.html", $title, '') %>
+% }
+
+<% include('/elements/error.html') %>
+
+<LINK REL="stylesheet" TYPE="text/css" HREF="../elements/calendar-win2k-2.css" TITLE="win2k-2">
+<SCRIPT TYPE="text/javascript" SRC="../elements/calendar_stripped.js"></SCRIPT>
+<SCRIPT TYPE="text/javascript" SRC="../elements/calendar-en.js"></SCRIPT>
+<SCRIPT TYPE="text/javascript" SRC="../elements/calendar-setup.js"></SCRIPT>
+
+<FORM ACTION="<% popurl(1) %>process/cust_pay.cgi" METHOD=POST>
+<INPUT TYPE="hidden" NAME="link" VALUE="<% $link %>">
+<INPUT TYPE="hidden" NAME="linknum" VALUE="<% $linknum %>">
+
+% unless ( $link eq 'popup' ) {
+ <% small_custview($custnum, $conf->config('countrydefault')) %>
+% }
+
+<INPUT TYPE="hidden" NAME="payby" VALUE="<% $payby %>">
+
+<BR><BR>
+Payment
+<% ntable("#cccccc", 2) %>
+
+<TR>
+ <TD ALIGN="right">Date</TD>
+ <TD COLSPAN=2>
+ <INPUT TYPE="text" NAME="_date" ID="_date_text" VALUE="<% time2str("%m/%d/%Y %r",$_date) %>">
+ <IMG SRC="../images/calendar.png" ID="_date_button" STYLE="cursor: pointer" TITLE="Select date">
+ </TD>
+</TR>
+
+<SCRIPT TYPE="text/javascript">
+ Calendar.setup({
+ inputField: "_date_text",
+ ifFormat: "%m/%d/%Y",
+ button: "_date_button",
+ align: "BR"
+ });
+</SCRIPT>
+
+<TR>
+ <TD ALIGN="right">Amount</TD>
+ <TD BGCOLOR="#ffffff" ALIGN="right"><% $money_char %></TD>
+ <TD><INPUT TYPE="text" NAME="paid" VALUE="<% $paid %>" SIZE=8 MAXLENGTH=8> by <B><% $payby{$payby} %></B></TD>
+</TR>
+
+% if ( $payby eq 'BILL' ) {
+ <TR>
+ <TD ALIGN="right">Check #</TD>
+ <TD COLSPAN=2><INPUT TYPE="text" NAME="payinfo" VALUE="<% $payinfo %>" SIZE=10></TD>
+ </TR>
+% }
+
+<TR>
+% if ( $link eq 'custnum' || $link eq 'popup' ) {
+
+ <TD ALIGN="right">Auto-apply<BR>to invoices</TD>
+ <TD COLSPAN=2>
+ <SELECT NAME="apply">
+ <OPTION VALUE="yes" SELECTED>yes
+ <OPTION>no</SELECT>
+ </TD>
+
+% } elsif ( $link eq 'invnum' ) {
+
+ <TD ALIGN="right">Apply to</TD>
+ <TD COLSPAN=2 BGCOLOR="#ffffff">Invoice #<B><% $linknum %></B> only</TD>
+ <INPUT TYPE="hidden" NAME="apply" VALUE="no">
+
+% }
+</TR>
+
+</TABLE>
+
+<INPUT TYPE="hidden" NAME="paybatch" VALUE="<% $paybatch %>">
+
+<BR>
+<INPUT TYPE="submit" VALUE="Post payment">
+
+</FORM>
+</BODY>
+</HTML>
+
+<%once>
+
+my $conf = new FS::Conf;
+
+my %payby = (
+ 'BILL' => 'Check',
+ 'CASH' => 'Cash',
+ 'WEST' => 'Western Union',
+ 'MCRD' => 'Manual credit card',
+);
+
+my $money_char = $conf->config('money_char') || '$';
+
+</%once>
+<%init>
+
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('Post payment');
+
+my($link, $linknum, $paid, $payby, $payinfo, $_date);
+if ( $cgi->param('error') ) {
+ $link = $cgi->param('link');
+ $linknum = $cgi->param('linknum');
+ $paid = $cgi->param('paid');
+ $payby = $cgi->param('payby');
+ $payinfo = $cgi->param('payinfo');
+ $_date = $cgi->param('_date') ? str2time($cgi->param('_date')) : time;
+} elsif ( $cgi->param('custnum') =~ /^(\d+)$/ ) {
+ $link = $cgi->param('popup') ? 'popup' : 'custnum';
+ $linknum = $1;
+ $paid = '';
+ $payby = $cgi->param('payby') || 'BILL';
+ $payinfo = '';
+ $_date = time;
+} elsif ( $cgi->param('invnum') =~ /^(\d+)$/ ) {
+ $link = 'invnum';
+ $linknum = $1;
+ $paid = '';
+ $payby = $cgi->param('payby') || 'BILL';
+ $payinfo = "";
+ $_date = time;
+} else {
+ die "illegal query ". $cgi->keywords;
+}
+
+my $paybatch = "webui-$_date-$$-". rand() * 2**32;
+
+my $title = 'Post '. $payby{$payby}. ' payment';
+$title .= " against Invoice #$linknum" if $link eq 'invnum';
+
+my $custnum;
+if ( $link eq 'invnum' ) {
+ my $cust_bill = qsearchs('cust_bill', { 'invnum' => $linknum } )
+ or die "unknown invnum $linknum";
+ $custnum = $cust_bill->custnum;
+} elsif ( $link eq 'custnum' ) {
+ $custnum = $linknum;
+}
+</%init>
+
diff --git a/httemplate/edit/cust_pkg.cgi b/httemplate/edit/cust_pkg.cgi
new file mode 100755
index 000000000..ecc21195d
--- /dev/null
+++ b/httemplate/edit/cust_pkg.cgi
@@ -0,0 +1,154 @@
+<% include('/elements/header.html', "Add/Edit Packages", '') %>
+
+<% include('/elements/error.html') %>
+
+<FORM ACTION="<% $p1 %>process/cust_pkg.cgi" METHOD=POST>
+
+<INPUT TYPE="hidden" NAME="custnum" VALUE="<% $custnum %>">
+%
+%#current packages
+%my @cust_pkg = qsearch('cust_pkg', { 'custnum' => $custnum, 'cancel' => '' } );
+%
+%if (@cust_pkg) {
+%
+
+
+ Current packages - select to remove (services are moved to a new package below)
+ <TABLE>
+ <TR STYLE="background-color: #cccccc;">
+ <TH COLSPAN="2">Pkg #</TH>
+ <TH>Package description</TH>
+ </TR>
+ <BR><BR>
+%
+%
+% foreach ( sort { $all_pkg{ $a->getfield('pkgpart') }
+% cmp $all_pkg{ $b->getfield('pkgpart') }
+% }
+% @cust_pkg
+% )
+% {
+% my($pkgnum,$pkgpart)=( $_->getfield('pkgnum'), $_->getfield('pkgpart') );
+% my $checked = $remove_pkg{$pkgnum} ? ' CHECKED' : '';
+%
+%
+
+
+ <TR>
+ <TD><INPUT TYPE="checkbox" NAME="remove_pkg" VALUE="<% $pkgnum %>"<% $checked %>></TD>
+ <TD ALIGN="right"><% $pkgnum %>:</TD>
+ <TD><% $all_pkg{$pkgpart} %> - <% $all_comment{$pkgpart} %></TD>
+ </TR>
+% }
+
+
+ </TABLE>
+ <BR><BR>
+% }
+
+
+Order new packages
+<BR><BR>
+%
+%my $cust_main = qsearchs('cust_main',{'custnum'=>$custnum});
+%my $agent = qsearchs('agent',{'agentnum'=> $cust_main->agentnum });
+%
+%my %agent_pkgs = map { ( $_->pkgpart , $all_pkg{$_->pkgpart} ) }
+% qsearch('type_pkgs',{'typenum'=> $agent->typenum });
+%
+%my $count = 0;
+%my $pkgparts = 0;
+%
+
+
+<TABLE>
+ <TR STYLE="background-color: #cccccc;">
+ <TH>Qty.</TH>
+ <TH COLSPAN="2">Package Description</TH>
+ </TR>
+%
+%#foreach my $type_pkgs ( qsearch('type_pkgs',{'typenum'=> $agent->typenum }) ) {
+%foreach my $pkgpart ( sort { $agent_pkgs{$a} cmp $agent_pkgs{$b} }
+% keys(%agent_pkgs) ) {
+% $pkgparts++;
+% next unless exists $pkg{$pkgpart}; #skip disabled ones
+% #print qq!<TR>! if ( $count == 0 );
+% my $value = $cgi->param("pkg$pkgpart") || 0;
+%
+
+
+ <TR>
+ <TD>
+ <INPUT TYPE="text" NAME="<% "pkg$pkgpart" %>" VALUE="<% $value %>" SIZE="2" MAXLENGTH="2">
+ </TD>
+ <TD ALIGN="right"><% $pkgpart %>:</TD>
+ <TD><% $pkg{$pkgpart} %> - <% $comment{$pkgpart}%></TD>
+ </TR>
+%
+% $count ++ ;
+% #if ( $count == 2 ) {
+% # print qq!</TR>\n! ;
+% # $count = 0;
+% #}
+%}
+%
+
+
+</TABLE>
+% unless ( $pkgparts ) {
+% my $p2 = popurl(2);
+% my $typenum = $agent->typenum;
+% my $agent_type = qsearchs( 'agent_type', { 'typenum' => $typenum } );
+% my $atype = $agent_type->atype;
+%
+
+
+ (No <A HREF="<% $p2 %>browse/part_pkg.cgi">package definitions</A>,
+ or agent type
+ <A HREF="<% $p2 %>edit/agent_type.cgi?<% $typenum %>"><% $atype %></a>
+ is not allowed to purchase any packages.)
+% }
+
+
+<P><INPUT TYPE="submit" VALUE="Order">
+
+</FORM>
+
+<% include('/elements/footer.html') %>
+
+<%init>
+
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('Bulk change customer packages');
+
+my %pkg = ();
+my %comment = ();
+my %all_pkg = ();
+my %all_comment = ();
+#foreach (qsearch('part_pkg', { 'disabled' => '' })) {
+# $pkg{ $_ -> getfield('pkgpart') } = $_->getfield('pkg');
+# $comment{ $_ -> getfield('pkgpart') } = $_->getfield('comment');
+#}
+foreach (qsearch('part_pkg', {} )) {
+ $all_pkg{ $_ -> getfield('pkgpart') } = $_->getfield('pkg');
+ $all_comment{ $_ -> getfield('pkgpart') } = $_->getfield('comment');
+ next if $_->disabled;
+ $pkg{ $_ -> getfield('pkgpart') } = $_->getfield('pkg');
+ $comment{ $_ -> getfield('pkgpart') } = $_->getfield('comment');
+}
+
+my($custnum, %remove_pkg);
+if ( $cgi->param('error') ) {
+ $custnum = $cgi->param('custnum');
+ %remove_pkg = map { $_ => 1 } $cgi->param('remove_pkg');
+} else {
+ my($query) = $cgi->keywords;
+ $query =~ /^(\d+)$/;
+ $custnum = $1;
+ %remove_pkg = ();
+}
+
+my $p1 = popurl(1);
+
+</%init>
+
diff --git a/httemplate/edit/cust_refund.cgi b/httemplate/edit/cust_refund.cgi
new file mode 100755
index 000000000..3333f5d8c
--- /dev/null
+++ b/httemplate/edit/cust_refund.cgi
@@ -0,0 +1,141 @@
+<% include('/elements/header.html', 'Refund '. ucfirst(lc($payby)). ' payment', '') %>
+
+<% include('/elements/error.html') %>
+
+<% small_custview($custnum, $conf->config('countrydefault')) %>
+
+<FORM NAME="RefundForm" ACTION="<% $p1 %>process/cust_refund.cgi" METHOD=POST onSubmit="document.RefundForm.submit.disabled=true">
+<INPUT TYPE="hidden" NAME="refundnum" VALUE="">
+<INPUT TYPE="hidden" NAME="custnum" VALUE="<% $custnum %>">
+<INPUT TYPE="hidden" NAME="paynum" VALUE="<% $paynum %>">
+<INPUT TYPE="hidden" NAME="_date" VALUE="<% $_date %>">
+<INPUT TYPE="hidden" NAME="payby" VALUE="<% $payby %>">
+<INPUT TYPE="hidden" NAME="payinfo" VALUE="">
+<INPUT TYPE="hidden" NAME="paybatch" VALUE="">
+<INPUT TYPE="hidden" NAME="credited" VALUE="">
+<BR>
+% if ( $cust_pay ) {
+%
+% #false laziness w/FS/FS/cust_pay.pm
+% my $payby = $cust_pay->payby;
+% my $paymask = $cust_pay->paymask;
+% my $paydate = $cust_pay->paydate;
+% if ( $cgi->param('error') ) {
+% $paydate = $cgi->param('exp_year'). '-'. $cgi->param('exp_month'). '-01';
+% $paydate = '' unless ($paydate =~ /^\d{2,4}-\d{1,2}-01$'/);
+% }
+% $payby =~ s/^BILL$/Check/ if $paymask;
+% $payby =~ s/^CHEK$/Electronic check/;
+%
+%
+
+
+ <BR>Payment
+ <% ntable("#cccccc", 2) %>
+
+ <TR>
+ <TD ALIGN="right">Amount</TD><TD BGCOLOR="#ffffff">$<% $cust_pay->paid %></TD>
+ </TR>
+
+ <TR>
+ <TD ALIGN="right">Date</TD><TD BGCOLOR="#ffffff"><% time2str("%D",$cust_pay->_date) %></TD>
+ </TR>
+
+ <TR>
+ <TD ALIGN="right">Method</TD><TD BGCOLOR="#ffffff"><% ucfirst(lc($payby)) %> # <% $paymask %></TD>
+ </TR>
+
+% unless ( $paydate ) { # possibly other reasons: i.e. card has since expired
+ <TR>
+ <TD ALIGN="right">Expiration</TD><TD BGCOLOR="#ffffff">
+ <% include( '/elements/select-month_year.html',
+ 'prefix' => 'exp',
+ 'selected_date' => $paydate,
+ 'empty_option' => !$paydate,
+ ) %>
+ </TD>
+ </TR>
+% }
+
+%
+% #false laziness w/FS/FS/cust_main::realtime_refund_bop
+% if ( $cust_pay->paybatch =~ /^(\w+):(\w+)(:(\w+))?$/ ) {
+% my ( $processor, $auth, $order_number ) = ( $1, $2, $4 );
+%
+
+
+ <TR>
+ <TD ALIGN="right">Processor</TD><TD BGCOLOR="#ffffff"><% $processor %></TD>
+ </TR>
+% if ( length($auth) ) {
+
+ <TR>
+ <TD ALIGN="right">Authorization</TD><TD BGCOLOR="#ffffff"><% $auth %></TD>
+ </TR>
+% }
+% if ( length($order_number) ) {
+
+ <TR>
+ <TD ALIGN="right">Order number</TD><TD BGCOLOR="#ffffff"><% $order_number %></TD>
+ </TR>
+% }
+% }
+
+ </TABLE>
+% }
+
+
+<BR>Refund
+<% ntable("#cccccc", 2) %>
+
+ <TR>
+ <TD ALIGN="right">Date</TD><TD BGCOLOR="#ffffff"><% time2str("%D",$_date) %></TD>
+ </TR>
+
+ <TR>
+ <TD ALIGN="right">Amount</TD><TD BGCOLOR="#ffffff">$<INPUT TYPE="text" NAME="refund" VALUE="<% $refund %>" SIZE=8 MAXLENGTH=8></TD>
+ </TR>
+
+ <TR>
+ <TD ALIGN="right">Reason</TD><TD BGCOLOR="#ffffff"><INPUT TYPE="text" NAME="reason" VALUE="<% $reason %>"></TD>
+ </TR>
+</TABLE>
+
+<BR>
+<INPUT TYPE="submit" NAME="submit" VALUE="Post refund">
+
+</FORM>
+
+<% include('/elements/footer.html') %>
+
+<%init>
+
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('Refund payment');
+
+my $conf = new FS::Conf;
+my $custnum = $cgi->param('custnum');
+my $refund = $cgi->param('refund');
+my $payby = $cgi->param('payby');
+my $reason = $cgi->param('reason');
+
+my( $paynum, $cust_pay ) = ( '', '' );
+if ( $cgi->param('paynum') =~ /^(\d+)$/ ) {
+ $paynum = $1;
+ $cust_pay = qsearchs('cust_pay', { paynum=>$paynum } )
+ or die "unknown payment # $paynum";
+ $refund ||= $cust_pay->unrefunded;
+ if ( $custnum ) {
+ die "payment # $paynum is not for specified customer # $custnum"
+ unless $custnum == $cust_pay->custnum;
+ } else {
+ $custnum = $cust_pay->custnum;
+ }
+}
+die "no custnum or paynum specified!" unless $custnum;
+
+my $_date = time;
+
+my $p1 = popurl(1);
+
+</%init>
diff --git a/httemplate/edit/elements/edit.html b/httemplate/edit/elements/edit.html
new file mode 100644
index 000000000..ad52f7a4c
--- /dev/null
+++ b/httemplate/edit/elements/edit.html
@@ -0,0 +1,536 @@
+<%doc>
+
+Example:
+
+ include( 'elements/edit.html',
+ 'name' =>
+ 'table' =>
+ #? 'primary_key' => #required when the dbdef doesn't know...???
+ 'labels' => {
+ 'column' => 'Label',
+ }
+
+ #listref - each item is a literal column name (or method) or hashref
+ # or (notyet) coderef
+ #if not specified all columns (except for the primary key) will be editable
+ 'fields' => [
+ 'columname',
+ { 'field' => 'another_columname',
+ 'type' => 'text', #text
+ #password
+ #money
+ #percentage
+ #checkbox
+ #select
+ #selectlayers (can't use after a tablebreak-tr-title yet... grep "OneTrueTable")
+ #title
+ #tablebreak-tr-title
+ #hidden - hidden value from object
+ #fixed - display fixed value from object or here
+ #fixed-country
+ #fixed-state
+ 'value' => 'Y', #for checkbox, title, fixed, fixedhidden
+ 'disabled' => 0,
+ 'onchange' => 'javascript_function',
+ 'm2name_table' => 'table_name', #only tested w/
+ # selectlayers so far
+ # might work w/select
+ # dunno others
+ 'm2name_namecol' => 'name_column', #
+ 'm2name_label' => 'Label', #
+ 'm2name_new_default' => \@table_name_objects, #default
+ #m2name
+ #objects for
+ #new records
+ 'm2name_error_callback' => sub { my($cgi, $object) = @_; },
+ 'm2name_remove_warnings' => \%warnings, #hashref of warning
+ #messages for
+ #m2name removal
+ 'm2name_new_js' => 'function_name', #javascript function
+ #called on spawned rows
+ #(one arg: new_element)
+ 'm2name_remove_js' => 'function_name', #js function called
+ #when a row is
+ #deleted
+ #(three args:
+ # value, text,
+ # 'no_match')
+ #layer_fields & layer_values_callback only for selectlayer
+ 'layer_fields' => [
+ 'fieldname' => 'Label',
+ 'another_field' => {
+ label=>'Label',
+ type =>'text', #text, money
+ },
+ ],
+ 'layer_values_callback' =>
+ sub {
+ my( $cgi, $object ) = @_;
+ { 'layer' => { 'fieldname' => 'current_value',
+ 'fieldname2' => 'field2value',
+ ...
+ },
+ 'layer2' => { 'l2fieldname' => 'l2value',
+ ...
+ },
+ ...
+ };
+ },
+ },
+ ]
+
+ 'menubar' => '', #menubar arrayref
+
+ #agent virtualization
+ 'agent_virt' => 1,
+ 'agent_null_right' => 'Access Right Name',
+
+ #run when re-displaying with an error
+ 'error_callback' => sub { my( $cgi, $object, $fields_listref ) = @_; },
+
+ #run when editing
+ 'edit_callback' => sub { my( $cgi, $object, $fields_listref ) = @_; },
+
+ # returns a hashref for the new object
+ 'new_hashref_callback'
+
+ #run when adding
+ 'new_callback' => sub { my( $cgi, $object, $fields_listref ) = @_; },
+
+ #XXX describe
+ 'field_callback' => sub { },
+
+ #string or coderef of additional HTML to add before </TABLE>
+ 'html_table_bottom' => '',
+
+ 'viewall_dir' => '', #'search' or 'browse', defaults to 'search'
+
+ 'html_bottom' => '', #string
+ 'html_bottom' => sub {
+ my $object = shift;
+ # ...
+ "html_string";
+ },
+
+ # overrides default popurl(1)."process/$table.html"
+ 'post_url' => popurl(1).'process/something',
+
+ #we're in a popup (no title/menu/searchboxes)
+ 'popup' => 1,
+
+ );
+
+</%doc>
+
+<% include('/elements/header'. ( $opt{popup} ? '-popup' : '' ). '.html',
+ $title,
+ include( '/elements/menubar.html', @menubar )
+ )
+%>
+
+<% include('/elements/error.html') %>
+
+% my $url = $opt{'post_url'} || popurl(1)."process/$table.html";
+
+<FORM ACTION="<% $url %>" METHOD=POST NAME="edit_topform">
+
+<INPUT TYPE="hidden" NAME="svcdb" VALUE="<% $table %>">
+<INPUT TYPE="hidden" NAME="<% $pkey %>" VALUE="<% $object->$pkey() %>">
+
+<FONT SIZE="+1"><B>
+<% ( $opt{labels} && exists $opt{labels}->{$pkey} )
+ ? $opt{labels}->{$pkey}
+ : $pkey
+%>
+</B></FONT>
+#<% $object->$pkey() || "(NEW)" %>
+
+%# <% ntable("#cccccc",0) %>
+<TABLE ID="OneTrueTable" BGCOLOR="#cccccc" BORDER=0 CELLSPACING=0>
+
+% my $g_row = 0;
+% foreach my $f ( map { ref($_) ? $_ : {'field'=>$_} }
+% @$fields
+% ) {
+%
+% &{ $opt{'field_callback'} }( $f )
+% if $opt{'field_callback'};
+%
+% my $field = $f->{'field'};
+% my $type = $f->{'type'} ||= 'text';
+%
+% my $label = ( $opt{labels} && exists $opt{labels}->{$field} )
+% ? $opt{labels}->{$field}
+% : $field;
+%
+% my $onchange = $f->{'onchange'};
+%
+% my $layer_values = {};
+% if ( $f->{'layer_values_callback'} && ! $f->{'m2name_table'} ) {
+% $layer_values = &{ $f->{'layer_values_callback'} }( $cgi, $object );
+% }
+% warn "layer values: ". Dumper($layer_values)
+% if $opt{'debug'};
+%
+% my %include_common = (
+%
+% #text and derivitives
+% 'size' => $f->{'size'},
+%
+% #checkbox, title, fixed, fixedhidden
+% #& deprecated weird value hashref used only by reason.html
+% 'value' => $f->{'value'},
+%
+% #select(-*)
+% 'options' => $f->{'options'},
+% 'labels' => $f->{'labels'},
+% 'multiple' => $f->{'multiple'},
+% 'disable_empty' => $f->{'disable_empty'},
+% #select-reason
+% 'reason_class' => $f->{'reason_class'},
+%
+% #selectlayers
+% 'layer_fields' => $f->{'layer_fields'},
+% 'layer_values' => $layer_values,
+% 'html_between' => $f->{'html_between'},
+% );
+%
+% my $layer_prefix_on = '';
+%
+% my $include_sub = sub {
+% my %opt = @_;
+%
+% my $fieldnum = delete $opt{'fieldnum'};
+%
+% my $include = $type;
+% $include = "input-$include" if $include =~ /^(text|money|percentage)$/;
+% $include = "tr-$include" unless $include =~ /^(hidden|tablebreak)/;
+%
+% $include_common{'layer_prefix'} = "$field$fieldnum."
+% if $layer_prefix_on;
+%
+% my @include =
+% ( "/elements/$include.html",
+% 'field' => "$field$fieldnum",
+% 'id' => "$field$fieldnum", #separate?
+% 'label_id' => $field."_label$fieldnum", #don't want field0_label0...
+% %include_common,
+% %opt,
+% );
+% @include;
+% };
+%
+% $g_row++;
+% $g_row++ if $type eq 'title';
+%
+% my $fieldnum = '';
+% my $curr_value = '';
+% if ( $f->{'m2name_table'} ) { #XXX test this for all types of fields
+% my $table = $f->{'m2name_table'};
+% my $col = $f->{'m2name_namecol'};
+% $fieldnum = 0;
+% $layer_prefix_on = 1;
+% #print out the fields for the existing m2names
+% my @existing = ();
+% if ( $mode eq 'error' ) {
+% @existing = &{ $f->{'m2name_error_callback'} }( $cgi, $object );
+% } elsif ( $object->$pkey() ) { # $mode eq 'edit'
+% @existing = $object->$table();
+% } elsif ( $f->{'m2name_new_default'} ) { # && $mode eq 'new'
+% @existing = @{ $f->{'m2name_new_default'} };
+% }
+% foreach my $name_obj ( @existing ) {
+%
+% my $ex_label = '<INPUT TYPE="button" VALUE="X" TITLE="Remove this '.
+% lc($f->{'m2name_label'}).
+% qq(" onClick="remove_$field($fieldnum);").
+% ' STYLE="color:#ff0000;font-weight:bold;'.
+% 'padding-left:2px;padding-right:2px"'.
+% '>&nbsp;'. ($f->{'m2name_label'} || $field ). ' ';
+%
+% if ( $f->{'layer_values_callback'} ) {
+% my %switches = ( 'mode' => $mode );
+% $layer_values =
+% &{ $f->{'layer_values_callback'} }( $cgi, $name_obj, \%switches );
+% }
+% warn "layer values: ". Dumper($layer_values)
+% if $opt{'debug'};
+%
+% my @existing = &{ $include_sub }(
+% 'label' => $ex_label,
+% 'fieldnum' => $fieldnum,
+% 'curr_value' => $name_obj->$col(),
+% 'onchange' => $onchange,
+% 'layer_values' => $layer_values,
+% 'cell_style' => ( $fieldnum ? 'border-top:1px solid black' : '' ),
+% );
+
+ <% include( @existing ) %>
+
+% $fieldnum++;
+% $g_row++;
+% }
+% #$field .= $fieldnum;
+% $onchange .= "\nspawn_$field(what);";
+% } else {
+% $curr_value = $object->$field();
+% }
+%
+% my @include = &{ $include_sub }(
+% 'label' => $label,
+% 'fieldnum' => $fieldnum,
+% 'curr_value' => $curr_value,
+% 'object' => $object,
+% 'onchange' => $onchange,
+% 'cell_style' => ( $fieldnum ? 'border-top:1px solid black' : '' ),
+% );
+
+ <% include( @include ) %>
+
+% if ( $f->{'m2name_table'} ) {
+
+ <SCRIPT TYPE="text/javascript">
+
+ var rownum = <% $g_row %>;
+ var fieldnum = <% $fieldnum %>;
+
+ function spawn_<%$field%>(what) {
+
+ // only spawn if we're the last element... return if not
+
+ var field_regex = /(\d+)$/;
+ var match = field_regex.exec(what.name);
+ if ( !match ) {
+ alert(what.name + " didn't match?!");
+ return;
+ }
+ if ( match[1] != fieldnum ) {
+ return;
+ }
+
+ // change the label on the last entry & add a remove button
+ var prev_label = document.getElementById('<% $field %>_label' + fieldnum );
+ prev_label.innerHTML = '<INPUT TYPE="button" VALUE="X" TITLE="Remove this <% lc($f->{'m2name_label'}) %>" onClick="remove_<% $field %>(' + fieldnum + ');" STYLE="color:#ff0000;font-weight:bold;padding-left:2px;padding-right:2px" >&nbsp;<% $f->{'m2name_label'} || $field %>';
+
+ fieldnum++;
+
+ //get the new widget
+
+% $include[0] =~ s(^/elements/tr-)(/elements/);
+% my @layer_opt = ( @include,
+% 'field' => $field."MAGIC_NUMBER",
+% 'layer_prefix' => $field."MAGIC_NUMBER.",
+% );
+
+ var newrow = <% include(@layer_opt, html_only=>1) |js_string %>;
+
+% if ( $type eq 'selectlayers' ) { #until the rest have html/js_only
+ var newfunc = <% include(@layer_opt, js_only =>1) |js_string %>;
+% } else {
+ var newfunc = '';
+% }
+
+ // substitute in the new field name
+ var magic_regex = /MAGIC_NUMBER/g;
+ newrow = newrow.replace( magic_regex, fieldnum );
+ newfunc = newfunc.replace( magic_regex, fieldnum );
+
+ // evaluate new_func
+ if (window.ActiveXObject) {
+ window.execScript(newfunc);
+ } else { /* (window.XMLHttpRequest) */
+ //window.eval(newfunc);
+ setTimeout(newfunc, 0);
+ }
+
+ // add new row
+
+ //hmm, can't use selectlayers after a tablebreak-title for now
+ var table = document.getElementById('OneTrueTable');
+
+ var row = table.insertRow(rownum++);
+
+ var label_cell = document.createElement('TD');
+
+ label_cell.id = '<% $field %>_label' + fieldnum;
+
+ label_cell.style.textAlign = "right";
+ label_cell.style.verticalAlign = "top";
+ label_cell.style.borderTop = "1px solid black";
+ label_cell.style.paddingTop = "5px";
+
+ label_cell.innerHTML = '<% $label %>';
+
+ row.appendChild(label_cell);
+
+ var widget_cell = document.createElement('TD');
+
+ widget_cell.style.borderTop = "1px solid black";
+ widget_cell.style.paddingTop = "3px";
+
+ widget_cell.innerHTML = newrow;
+
+ row.appendChild(widget_cell);
+
+% if ( $f->{'m2name_new_js'} ) {
+ // take out items selected in previous dropdowns
+ var new_element = document.getElementById("<%$field%>" + fieldnum );
+ <% $f->{'m2name_new_js'} %>(new_element);
+
+ if ( new_element.length < 2 ) {
+ //just the ** Select new **, so don't display the row
+ row.style.display = 'none';
+ }
+% }
+
+ }
+
+ function remove_<%$field%>(remove_fieldnum) {
+ //alert("remove <%$field%> " + remove_fieldnum);
+ var select = document.getElementById('<%$field%>' + remove_fieldnum);
+
+% my $warnings = $f->{'m2name_remove_warnings'};
+% if ( $warnings ) {
+ var sel_value = select.options[select.selectedIndex].value;
+% foreach my $value ( keys %$warnings ) {
+ if ( sel_value == '<% $value %>' ) {
+ if ( ! confirm( <% $warnings->{$value} |js_string %> ) ) {
+ return;
+ }
+ }
+% }
+% }
+
+ select.disabled = 'disabled'; // this seems to prevent it from being submitted on tested browsers so far (IE, moz, konq at least)
+ var label_td = document.getElementById('<%$field%>_label' + remove_fieldnum );
+ label_td.parentNode.style.display = 'none';
+
+% if ( $f->{m2name_remove_js} ) {
+ var opt = select.options[select.selectedIndex];
+ <% $f->{m2name_remove_js} %>( opt.value, opt.text, 'no_match');
+% }
+
+ }
+
+ </SCRIPT>
+
+% }
+
+% }
+
+<% ref( $opt{'html_table_bottom'} )
+ ? &{ $opt{'html_table_bottom'} }( $object )
+ : $opt{'html_table_bottom'}
+%>
+
+</TABLE>
+
+<% ref( $opt{'html_bottom'} )
+ ? &{ $opt{'html_bottom'} }( $object )
+ : $opt{'html_bottom'}
+%>
+
+<BR>
+
+<INPUT TYPE="submit" ID="submit" VALUE="<% $object->$pkey() ? "Apply changes" : "Add $opt{'name'}" %>">
+
+</FORM>
+
+<% include("/elements/footer.html") %>
+<%init>
+
+my(%opt) = @_;
+
+my $curuser = $FS::CurrentUser::CurrentUser;
+
+#false laziness w/process.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 ];
+ || [ grep { $_ ne $pkey } fields($table) ];
+#my @actualfields = map { ref($_) ? $_->{'field'} : $_ } @$fields;
+
+if ( $cgi->param('redirect') ) {
+ my $session = $cgi->param('redirect');
+ my $pref = $curuser->option("redirect$session");
+ die "unknown redirect session $session\n" unless length($pref);
+ $cgi = new CGI($pref);
+}
+
+my $object;
+my $mode;
+if ( $cgi->param('error') ) {
+
+ $mode = 'error';
+
+
+ $object = $class->new( {
+ map { $_ => scalar($cgi->param($_)) } fields($table)
+ });
+
+ &{$opt{'error_callback'}}($cgi, $object, $fields)
+ if $opt{'error_callback'};
+
+} elsif ( $cgi->keywords || $cgi->param($pkey) ) { #editing
+
+ $mode = 'edit';
+
+ my $value;
+ if ( $cgi->param($pkey) ) {
+ $value = $cgi->param($pkey)
+ } else {
+ my( $query ) = $cgi->keywords;
+ $value = $query;
+ }
+ $value =~ /^(\d+)$/ or die "unparsable $pkey";
+ $object = qsearchs({
+ 'table' => $table,
+ 'hashref' => { $pkey => $1 },
+ 'extra_sql' => ( $opt{'agent_virt'}
+ ? ' AND '. $curuser->agentnums_sql(
+ 'null_right' => $opt{'agent_null_right'}
+ )
+ : ''
+ ),
+ });
+ warn "$table $pkey => $1"
+ if $opt{'debug'};
+
+ &{$opt{'edit_callback'}}($cgi, $object, $fields)
+ if $opt{'edit_callback'};
+
+} else { #adding
+
+ $mode = 'new';
+
+ my $hashref = $opt{'new_hashref_callback'}
+ ? &{$opt{'new_hashref_callback'}}
+ : {};
+
+ $object = $class->new( $hashref );
+
+ &{$opt{'new_callback'}}($cgi, $object, $fields)
+ if $opt{'new_callback'};
+
+}
+
+my $action = $object->$pkey() ? 'Edit' : 'Add';
+
+my $title = "$action $opt{'name'}";
+
+my $viewall_url = $p . ( $opt{'viewall_dir'} || 'search' ) . "/$table.html";
+$viewall_url = $opt{'viewall_url'} if $opt{'viewall_url'};
+
+my @menubar = ();
+if ( $opt{'menubar'} ) {
+ @menubar = @{ $opt{'menubar'} };
+} else {
+ @menubar = (
+ #eventually use Lingua::bs to pluralize
+ "View all $opt{'name'}s" => $viewall_url,
+ );
+}
+
+</%init>
diff --git a/httemplate/edit/elements/svc_Common.html b/httemplate/edit/elements/svc_Common.html
new file mode 100644
index 000000000..72abcba1f
--- /dev/null
+++ b/httemplate/edit/elements/svc_Common.html
@@ -0,0 +1,99 @@
+%
+% my %opt = @_;
+%
+% #my( $svcnum, $pkgnum, $svcpart, $part_svc );
+% my( $pkgnum, $svcpart, $part_svc );
+%
+% #get & untaint pkgnum & svcpart
+% if ( ! $cgi->param('error')
+% && $cgi->param('pkgnum') && $cgi->param('svcpart')
+% )
+% {
+% $cgi->param('pkgnum') =~ /^(\d+)$/ or die 'unparsable pkgnum';
+% $pkgnum = $1;
+% $cgi->param('svcpart') =~ /^(\d+)$/ or die 'unparsable svcpart';
+% $svcpart = $1;
+% $cgi->delete_all(); #so edit.html treats this correctly as new??
+% }
+%
+<% include( 'edit.html',
+
+ 'menubar' => [],
+
+ 'error_callback' => sub {
+ my( $cgi, $svc_x ) = @_;
+ #$svcnum = $svc_x->svcnum;
+ $pkgnum = $cgi->param('pkgnum');
+ $svcpart = $cgi->param('svcpart');
+
+ $part_svc = qsearchs( 'part_svc', { svcpart=>$svcpart });
+ die "No part_svc entry!" unless $part_svc;
+ },
+
+ 'edit_callback' => sub {
+ my( $cgi, $svc_x ) = @_;
+ #$svcnum = $svc_x->svcnum;
+ my $cust_svc = $svc_x->cust_svc
+ or die "Unknown (cust_svc) svcnum!";
+
+ $pkgnum = $cust_svc->pkgnum;
+ $svcpart = $cust_svc->svcpart;
+
+ $part_svc = qsearchs ('part_svc', { svcpart=>$svcpart });
+ die "No part_svc entry!" unless $part_svc;
+ },
+
+ 'new_hash_callback' => sub {
+ #my( $cgi, $svc_x ) = @_;
+
+ { svcpart => $svcpart };
+
+ },
+
+ 'new_callback' => sub {
+ my( $cgi, $svc_x ) = @_;;
+
+ $part_svc = qsearchs( 'part_svc', { svcpart=>$svcpart });
+ die "No part_svc entry!" unless $part_svc;
+
+ #$svcnum='';
+
+ $svc_x->set_default_and_fixed;
+
+ },
+
+ 'field_callback' => sub {
+ my $f = shift;
+ my $columndef = $part_svc->part_svc_column($f->{'field'});
+ my $flag = $columndef->columnflag;
+ if ( $flag eq 'F' ) {
+ $f->{'type'} = 'fixed';
+ $f->{'value'} = $columndef->columnvalue;
+ }
+ },
+
+ 'html_table_bottom' => sub {
+ my $svc_x = shift;
+ my $html = '';
+ foreach my $field ($svc_x->virtual_fields) {
+ if ($part_svc->part_svc_column($field)->columnflag ne 'F'){
+ # If the flag is X, it won't even show up
+ # in $svc_acct->virtual_fields.
+ $html .=
+ $svc_x->pvf($field)->widget( 'HTML',
+ 'edit',
+ $svc_x->getfield($field)
+ );
+ }
+ }
+ $html;
+ },
+
+ 'html_bottom' => sub {
+ qq!<INPUT TYPE="hidden" NAME="pkgnum" VALUE="$pkgnum">!.
+ qq!<INPUT TYPE="hidden" NAME="svcpart" VALUE="$svcpart">!;
+ },
+
+ %opt #pass through/override params
+ )
+%>
diff --git a/httemplate/edit/inventory_class.html b/httemplate/edit/inventory_class.html
new file mode 100644
index 000000000..3ab47fe28
--- /dev/null
+++ b/httemplate/edit/inventory_class.html
@@ -0,0 +1,16 @@
+<% include( 'elements/edit.html',
+ 'name' => 'Inventory Class',
+ 'table' => 'inventory_class',
+ 'labels' => {
+ 'classnum' => 'Class number',
+ 'classname' => 'Class name',
+ },
+ 'viewall_dir' => 'browse',
+ )
+%>
+<%init>
+
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('Configuration');
+
+</%init>
diff --git a/httemplate/edit/invoice_logo.html b/httemplate/edit/invoice_logo.html
new file mode 100644
index 000000000..0c45e4b20
--- /dev/null
+++ b/httemplate/edit/invoice_logo.html
@@ -0,0 +1,136 @@
+<% include("/elements/header.html", "Edit $type2desc{$type} invoice logo",
+ menubar(
+ 'View all invoice templates' => $p.'browse/invoice_template.html'
+ )
+ )
+%>
+
+% if ( $error ) {
+ <FONT SIZE="+1" COLOR="#ff0000">Error: <% $error %></FONT>
+ <BR><BR>
+% }
+
+% if ( $cgi->param('msg') ) {
+ <FONT SIZE="+1"><B><% $cgi->param('msg') |h %></B></FONT>
+ <BR><BR>
+% }
+
+% if ( $mode eq 'upload' ) {
+ <FORM ACTION="invoice_logo.html" METHOD="POST" ENCTYPE="multipart/form-data">
+ <INPUT TYPE="hidden" NAME="mode" VALUE="preview">
+% } elsif ( $mode eq 'preview' ) {
+ <FORM ACTION="process/invoice_logo.html" METHOD="POST">
+ <INPUT TYPE="hidden" NAME="preview_session" VALUE="<% $session %>">
+% }
+
+<INPUT TYPE="hidden" NAME="type" VALUE="<% $type %>">
+<INPUT TYPE="hidden" NAME="name" VALUE="<% $name %>">
+
+<% include('/elements/table-grid.html') %>
+
+<TR>
+ <TH CLASS="grid" BGCOLOR="#cccccc">Current logo</TH>
+ <TH CLASS="grid" BGCOLOR="#cccccc">New logo preview</TH>
+</TR>
+
+<TR>
+
+ <TD CLASS="grid" BGCOLOR="#ffffff">
+
+% if ( $type eq 'png' ) {
+
+ <IMG SRC="<% $p %>view/logo.cgi?type=png;name=<% $name %>">
+
+% } elsif ( $type eq 'eps' ) {
+
+ <i>EPS preview not yet supported</i>
+
+% }
+
+ </TD>
+
+ <TD CLASS="grid" BGCOLOR="#ffffff">
+
+% if ( $mode eq 'upload' ) {
+
+ Upload new logo (PNG format): <INPUT TYPE="file" NAME="new_logo">
+ <BR><INPUT TYPE="submit" NAME="submit" VALUE="Upload">
+
+% } elsif ( $mode eq 'preview' ) {
+
+ <IMG SRC="<% $p %>view/logo.cgi?type=png;preview_session=<% $session %>">
+
+% }
+
+ </TD>
+
+
+</TR>
+
+</TABLE>
+
+% if ( $mode eq 'preview' ) {
+ <BR>
+ <INPUT TYPE="submit" NAME="submit" VALUE="Change logo">
+% }
+
+</FORM>
+
+<% include("/elements/footer.html") %>
+
+<%once>
+
+my %type2desc = (
+ 'png' => 'online',
+ 'eps' => 'Print/PDF (typeset)',
+);
+
+</%once>
+<%init>
+
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('Configuration');
+
+my $conf = new FS::Conf;
+
+my $type = $cgi->param('type');
+
+$cgi->param('name') =~ /^([^\.\/]*)$/ or die "illegal name";
+my $name = $1;
+
+$cgi->param('mode') =~ /^(\w*)$/ or die "illegal mode";
+my $mode = $1 || 'upload';
+
+my $error = '';
+my $session = '';
+if ( $mode eq 'preview' ) {
+
+ my $fh = $cgi->upload('new_logo');
+
+ if ( defined $fh ) {
+
+ local $/;
+ my $logo_data = <$fh>;
+
+ $session = int(rand(4294967296)); #XXX
+ my $pref = new FS::access_user_pref({
+ 'usernum' => $FS::CurrentUser::CurrentUser->usernum,
+ 'prefname' => "logo_preview$session",
+ 'prefvalue' => encode_base64($logo_data),
+ 'expiration' => time + 3600, #1h? 1m?
+ });
+ my $pref_error = $pref->insert;
+ if ( $pref_error ) {
+ die "FATAL: couldn't set preview cookie: $pref_error\n";
+ }
+
+ } else {
+
+ $mode = 'upload';
+ $error = 'No file uploaded';
+
+ }
+
+}
+
+</%init>
diff --git a/httemplate/edit/invoice_template.html b/httemplate/edit/invoice_template.html
new file mode 100644
index 000000000..851ab5ecf
--- /dev/null
+++ b/httemplate/edit/invoice_template.html
@@ -0,0 +1,80 @@
+<% include("/elements/header.html", "Edit $type2desc{$type} invoice template",
+ menubar(
+ 'View all invoice templates' => $p.'browse/invoice_template.html'
+ )
+ )
+%>
+
+<FORM ACTION="process/invoice_template.html" METHOD="POST">
+<INPUT TYPE="hidden" NAME="confname" VALUE="<% $confname %>">
+
+% if ( $type eq 'html' ) {
+
+% #init
+ <SCRIPT TYPE="text/javascript" src="<% $p %>elements/fckeditor/fckeditor.js">
+ </SCRIPT>
+
+% #editor
+ <SCRIPT TYPE="text/javascript">
+ var oFCKeditor = new FCKeditor('value');
+ oFCKeditor.Value = <% $value |js_string %>;
+
+ oFCKeditor.BasePath = '<% $p %>elements/fckeditor/';
+ oFCKeditor.Config['SkinPath'] = '<% $p %>elements/fckeditor/editor/skins/silver/';
+ oFCKeditor.Height = '800';
+ oFCKeditor.Config['StartupFocus'] = true;
+
+ oFCKeditor.Create();
+
+ </SCRIPT>
+
+% } else {
+
+ <TEXTAREA NAME="value" ROWS=30 COLS=80 WRAP="off"><%$value |h %></TEXTAREA>
+
+% }
+
+<BR><BR>
+<INPUT TYPE="submit" VALUE="Change template">
+
+</FORM>
+
+<% include("/elements/footer.html") %>
+
+<%once>
+
+my %type2desc = (
+ 'html' => 'HTML',
+ 'latex' => 'Print/PDF (typeset)',
+ 'text' => 'Plaintext',
+);
+
+my %type2base = (
+ 'html' => 'invoice_html',
+ 'latex' => 'invoice_latex',
+ 'text' => 'invoice_template',
+);
+
+</%once>
+<%init>
+
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('Configuration');
+
+my $type = $cgi->param('type');
+my $name = $cgi->param('name');
+my $suffix = $cgi->param('suffix');
+
+#XXX type handling, just testing this out for now
+
+my $conf = new FS::Conf;
+
+my $value = length($name)
+ ? join("\n", $conf->config_orbase($type2base{$type}.$suffix, $name) )
+ : join("\n", $conf->config($type2base{$type}.$suffix) );
+
+my $confname = length($name)
+ ? $type2base{$type}.$suffix. '_'. $name
+ : $type2base{$type}.$suffix;
+
+</%init>
diff --git a/httemplate/edit/msgcat.cgi b/httemplate/edit/msgcat.cgi
new file mode 100755
index 000000000..85b300876
--- /dev/null
+++ b/httemplate/edit/msgcat.cgi
@@ -0,0 +1,54 @@
+<% header("Edit Message catalog" ) %>
+<BR>
+
+<% include('/elements/error.html') %>
+
+<% $widget->html %>
+
+ </TABLE>
+ </BODY>
+</HTML>
+<%init>
+
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('Configuration');
+
+my $widget = new HTML::Widgets::SelectLayers(
+ 'selected_layer' => 'en_US',
+ 'options' => { 'en_US'=>'en_US' },
+ 'form_action' => 'process/msgcat.cgi',
+ 'layer_callback' => sub {
+ my $layer = shift;
+ my $html = qq!<INPUT TYPE="hidden" NAME="locale" VALUE="$layer">!.
+ "<BR>Messages for locale $layer<BR>". table().
+ "<TR><TH COLSPAN=2>Code</TH>".
+ "<TH>Message</TH>";
+ $html .= "<TH>en_US Message</TH>" unless $layer eq 'en_US';
+ $html .= '</TR>';
+
+ #foreach my $msgcat ( sort { $a->msgcode cmp $b->msgcode }
+ # qsearch('msgcat', { 'locale' => $layer } ) ) {
+ foreach my $msgcat ( qsearch('msgcat', { 'locale' => $layer } ) ) {
+ $html .=
+ '<TR><TD>'. $msgcat->msgnum. '</TD><TD>'. $msgcat->msgcode. '</TD>'.
+ '<TD><INPUT TYPE="text" SIZE=32 '.
+ qq! NAME="!. $msgcat->msgnum. '" '.
+ qq!VALUE="!. ($cgi->param($msgcat->msgnum)||$msgcat->msg). qq!"></TD>!;
+ unless ( $layer eq 'en_US' ) {
+ my $en_msgcat = qsearchs('msgcat', {
+ 'locale' => 'en_US',
+ 'msgcode' => $msgcat->msgcode,
+ } );
+ $html .= '<TD>'. $en_msgcat->msg. '</TD>';
+ }
+ $html .= '</TR>';
+ }
+
+ $html .= '</TABLE><BR><INPUT TYPE="submit" VALUE="Apply changes">';
+
+ $html;
+ },
+
+);
+
+</%init>
diff --git a/httemplate/edit/part_bill_event.cgi b/httemplate/edit/part_bill_event.cgi
new file mode 100755
index 000000000..25470679f
--- /dev/null
+++ b/httemplate/edit/part_bill_event.cgi
@@ -0,0 +1,554 @@
+<% include('/elements/header.html',
+ "$action Invoice Event Definition",
+ menubar(
+ 'View all invoice events' => popurl(2). 'browse/part_bill_event.cgi',
+ )
+ )
+%>
+
+<% include('/elements/error.html') %>
+
+<FORM ACTION="<% popurl(1) %>process/part_bill_event.cgi" NAME="editEvent" METHOD=POST>
+<INPUT TYPE="hidden" NAME="eventpart" VALUE="<% $part_bill_event->eventpart %>">
+Invoice Event #<% $hashref->{eventpart} ? $hashref->{eventpart} : "(NEW)" %>
+
+<% ntable("#cccccc",2) %>
+
+ <TR>
+ <TD ALIGN="right">Event name </TD>
+ <TD><INPUT TYPE="text" NAME="event" VALUE="<% $hashref->{event} %>"></TD>
+ </TR>
+
+ <TR>
+ <TD ALIGN="right">For </TD>
+ <TD>
+ <SELECT NAME="payby">
+% tie my %payby, 'Tie::IxHash', FS::payby->cust_payby2longname;
+% foreach my $payby ( keys %payby ) {
+%
+
+
+ <OPTION VALUE="<% $payby %>"<% ($part_bill_event->payby eq $payby) ? ' SELECTED' : '' %>><% $payby{$payby} %></OPTION>
+% }
+
+
+ </SELECT> customers
+ </TD>
+ </TR>
+% my $days = $hashref->{seconds}/86400;
+
+
+ <TR>
+ <TD ALIGN="right">After</TD>
+ <TD><INPUT TYPE="text" NAME="days" VALUE="<% $days %>"> days</TD>
+ </TR>
+
+ <TR>
+ <TD ALIGN="right">Test event</TD>
+ <TD>
+ <SELECT NAME="freq">
+% tie my %freq, 'Tie::IxHash', '1d' => 'daily', '1m' => 'monthly';
+% foreach my $freq ( keys %freq ) {
+%
+
+
+ <OPTION VALUE="<% $freq %>"<% ($part_bill_event->freq eq $freq) ? ' SELECTED' : '' %>><% $freq{$freq} %></OPTION>
+% }
+
+
+ </SELECT>
+ </TD>
+ </TR>
+
+
+ <TR>
+ <TD ALIGN="right">Disabled</TD>
+ <TD>
+ <INPUT TYPE="checkbox" NAME="disabled" VALUE="Y"<% $hashref->{disabled} eq 'Y' ? ' CHECKED' : '' %>>
+ </TD>
+ </TR>
+
+ <TR>
+ <TD VALIGN="top" ALIGN="right">Action</TD>
+ <TD>
+%
+%
+%#print ntable();
+%
+%sub select_pkgpart {
+% my $label = shift;
+% my $plandata = shift;
+% my %selected = map { $_=>1 } split(/,\s*/, $plandata->{$label});
+% qq(<SELECT NAME="$label" MULTIPLE>).
+% join("\n", map {
+% '<OPTION VALUE="'. $_->pkgpart. '"'.
+% ( $selected{$_->pkgpart} ? ' SELECTED' : '' ).
+% '>'. $_->pkg. ' - '. $_->comment
+% } qsearch('part_pkg', { 'disabled' => '' } ) ).
+% '</SELECT>';
+%}
+%
+%sub select_agentnum {
+% my $plandata = shift;
+% #my $agentnum = $plandata->{'agentnum'};
+% my %agentnums = map { $_=>1 } split(/,\s*/, $plandata->{'agentnum'});
+% '<SELECT NAME="agentnum" MULTIPLE>'.
+% join("\n", map {
+% '<OPTION VALUE="'. $_->agentnum. '"'.
+% ( $agentnums{$_->agentnum} ? ' SELECTED' : '' ).
+% '>'. $_->agent
+% } qsearch('agent', { 'disabled' => '' } ) ).
+% '</SELECT>';
+%}
+%
+%my $conf = new FS::Conf;
+%my $money_char = $conf->config('money_char') || '$';
+%
+%my $late_taxclass = '';
+%my $late_percent_taxclass = '';
+%if ( $conf->exists('enable_taxclasses') ) {
+% $late_taxclass =
+% '<BR>Taxclass '.
+% include('/elements/select-taxclass.html', '%%%late_taxclass%%%',
+% 'name' => 'late_taxclass' );
+% $late_percent_taxclass =
+% '<BR>Taxclass '.
+% include('/elements/select-taxclass.html', '%%%late_percent_taxclass%%%',
+% 'name' => 'late_percent_taxclass' );
+%}
+%
+%#this is pretty kludgy right here.
+%tie my %events, 'Tie::IxHash',
+%
+% 'fee' => {
+% 'name' => 'Late fee (flat)',
+% 'code' => '$cust_main->charge( %%%charge%%%, \'%%%reason%%%\', \'$%%%charge%%%\', \'%%%late_taxclass%%%\' );',
+% 'html' =>
+% 'Amount <INPUT TYPE="text" SIZE="7" NAME="charge" VALUE="%%%charge%%%">'.
+% '<BR>Reason <INPUT TYPE="text" NAME="reason" VALUE="%%%reason%%%">'.
+% $late_taxclass,
+% 'weight' => 10,
+% },
+% 'fee_percent' => {
+% 'name' => 'Late fee (percentage)',
+% 'code' => '$cust_main->charge( sprintf(\'%.2f\', $cust_bill->owed * %%%percent%%% / 100 ), \'%%%percent_reason%%%\', \'%%%percent%%% percent\', \'%%%late_percent_taxclass%%%\' );',
+% 'html' =>
+% 'Percent <INPUT TYPE="text" SIZE="2" NAME="percent" VALUE="%%%percent%%%">%'.
+% '<BR>Reason <INPUT TYPE="text" NAME="percent_reason" VALUE="%%%percent_reason%%%">'.
+% $late_percent_taxclass,
+% 'weight' => 10,
+% },
+% 'suspend' => {
+% 'name' => 'Suspend',
+% 'code' => '$cust_main->suspend(reason => %%%sreason%%%);',
+% 'weight' => 10,
+% 'reason' => 'S',
+% },
+% 'suspend-if-balance' => {
+% 'name' => 'Suspend if balance (this invoice and previous) over',
+% 'code' => '$cust_bill->cust_suspend_if_balance_over( %%%balanceover%%%, reason => %%%sreason%%%, );',
+% 'html' => " $money_char ". '<INPUT TYPE="text" SIZE="7" NAME="balanceover" VALUE="%%%balanceover%%%">',
+% 'weight' => 10,
+% 'reason' => 'S',
+% },
+% 'suspend-if-pkgpart' => {
+% 'name' => 'Suspend packages',
+% 'code' => '$cust_main->suspend_if_pkgpart({pkgparts => [%%%if_pkgpart%%%,], reason => %%%sreason%%%,});',
+% 'html' => sub { &select_pkgpart('if_pkgpart', @_) },
+% 'weight' => 10,
+% 'reason' => 'S',
+% },
+% 'suspend-unless-pkgpart' => {
+% 'name' => 'Suspend packages except',
+% 'code' => '$cust_main->suspend_unless_pkgpart({unless_pkgpart => [%%%unless_pkgpart%%%], reason => %%%sreason%%%,});',
+% 'html' => sub { &select_pkgpart('unless_pkgpart', @_) },
+% 'weight' => 10,
+% 'reason' => 'S',
+% },
+% 'cancel' => {
+% 'name' => 'Cancel',
+% 'code' => '$cust_main->cancel(reason => %%%creason%%%);',
+% 'weight' => 10,
+% 'reason' => 'C',
+% },
+%
+% 'addpost' => {
+% 'name' => 'Add postal invoicing',
+% 'code' => '$cust_main->invoicing_list_addpost(); "";',
+% 'weight' => 20,
+% },
+%
+% 'comp' => {
+% 'name' => 'Pay invoice with a complimentary "payment"',
+% 'code' => '$cust_bill->comp();',
+% 'weight' => 30,
+% },
+%
+% 'credit' => {
+% 'name' => "Create and apply a credit for the customer's balance (i.e. write off as bad debt)",
+% 'code' => '$cust_main->credit( $cust_main->balance, \'%%%credit_reason%%%\' );',
+% 'html' => '<INPUT TYPE="text" NAME="credit_reason" VALUE="%%%credit_reason%%%">',
+% 'weight' => 30,
+% },
+%
+% 'realtime-card' => {
+% 'name' => 'Run card with a <a href="http://search.cpan.org/search?mode=module&query=Business%3A%3AOnlinePayment">Business::OnlinePayment</a> realtime gateway',
+% 'code' => '$cust_bill->realtime_card();',
+% 'weight' => 30,
+% },
+%
+% 'realtime-check' => {
+% 'name' => 'Run check with a <a href="http://search.cpan.org/search?mode=module&query=Business%3A%3AOnlinePayment">Business::OnlinePayment</a> realtime gateway',
+% 'code' => '$cust_bill->realtime_ach();',
+% 'weight' => 30,
+% },
+%
+% 'realtime-lec' => {
+% 'name' => 'Run phone bill ("LEC") billing with a <a href="http://search.cpan.org/search?mode=module&query=Business%3A%3AOnlinePayment">Business::OnlinePayment</a> realtime gateway',
+% 'code' => '$cust_bill->realtime_lec();',
+% 'weight' => 30,
+% },
+%
+% 'batch-card' => {
+% 'name' => 'Add card or check to a pending batch',
+% 'code' => '$cust_bill->batch_card(%options);',
+% 'weight' => 40,
+% },
+%
+%
+% #'retriable' => {
+% # 'name' => 'Mark batched card event as retriable',
+% # 'code' => '$cust_pay_batch->retriable();',
+% # 'weight' => 60,
+% #},
+%
+% 'send' => {
+% 'name' => 'Send invoice (email/print/fax)',
+% 'code' => '$cust_bill->send();',
+% 'weight' => 50,
+% },
+%
+% 'send_email' => {
+% 'name' => 'Send invoice (email only)',
+% 'code' => '$cust_bill->email();',
+% 'weight' => 50,
+% },
+%
+% 'send_alternate' => {
+% 'name' => 'Send invoice (email/print/fax) with alternate template',
+% 'code' => '$cust_bill->send(\'%%%templatename%%%\');',
+% 'html' =>
+% '<INPUT TYPE="text" NAME="templatename" VALUE="%%%templatename%%%">',
+% 'weight' => 50,
+% },
+%
+% 'send_if_newest' => {
+% 'name' => 'Send invoice (email/print/fax) with alternate template, if it is still the newest invoice (useful for late notices - set to 31 days or later)',
+% 'code' => '$cust_bill->send_if_newest(\'%%%if_newest_templatename%%%\');',
+% 'html' =>
+% '<INPUT TYPE="text" NAME="if_newest_templatename" VALUE="%%%if_newest_templatename%%%">',
+% 'weight' => 50,
+% },
+%
+% 'send_agent' => {
+% 'name' => 'Send invoice (email/print/fax) ',
+% 'code' => '$cust_bill->send(\'%%%agent_templatename%%%\', [ %%%agentnum%%% ], \'%%%agent_invoice_from%%%\');',
+% 'html' => sub {
+% '<TABLE BORDER=0>
+% <TR>
+% <TD ALIGN="right">only for agent(s) </TD>
+% <TD>'. &select_agentnum(@_). '</TD>
+% </TR>
+% <TR>
+% <TD ALIGN="right">with template </TD>
+% <TD>
+% <INPUT TYPE="text" NAME="agent_templatename" VALUE="%%%agent_templatename%%%">
+% </TD>
+% </TR>
+% <TR>
+% <TD ALIGN="right">email From: </TD>
+% <TD>
+% <INPUT TYPE="text" NAME="agent_invoice_from" VALUE="%%%agent_invoice_from%%%">
+% </TD>
+% </TR>
+% </TABLE>';
+% },
+% 'weight' => 50,
+% },
+%
+% 'send_csv_ftp' => {
+% 'name' => 'Upload CSV invoice data to an FTP server',
+% 'code' => '$cust_bill->send_csv( protocol => \'ftp\',
+% server => \'%%%ftpserver%%%\',
+% username => \'%%%ftpusername%%%\',
+% password => \'%%%ftppassword%%%\',
+% dir => \'%%%ftpdir%%%\',
+% \'format\' => \'%%%ftpformat%%%\',
+% );',
+% 'html' =>
+% '<TABLE BORDER=0>'.
+% '<TR><TD ALIGN="right">Format ("default" or "billco"): </TD>'.
+% '<TD>'.
+% '<!--'.
+% '<SELECT NAME="ftpformat">'.
+% '<OPTION VALUE="default">Default'.
+% '<OPTION VALUE="billco">Billco'.
+% '</SELECT>'.
+% '-->'.
+% '<INPUT TYPE="text" NAME="ftpformat" VALUE="%%%ftpformat%%%">'.
+% '</TD></TR>'.
+% '<TR><TD ALIGN="right">FTP server: </TD>'.
+% '<TD><INPUT TYPE="text" NAME="ftpserver" VALUE="%%%ftpserver%%%">'.
+% '</TD></TR>'.
+% '<TR><TD ALIGN="right">FTP username: </TD><TD>'.
+% '<INPUT TYPE="text" NAME="ftpusername" VALUE="%%%ftpusername%%%">'.
+% '</TD></TR>'.
+% '<TR><TD ALIGN="right">FTP password: </TD><TD>'.
+% '<INPUT TYPE="text" NAME="ftppassword" VALUE="%%%ftppassword%%%">'.
+% '</TD></TR>'.
+% '<TR><TD ALIGN="right">FTP directory: </TD>'.
+% '<TD><INPUT TYPE="text" NAME="ftpdir" VALUE="%%%ftpdir%%%">'.
+% '</TD></TR>'.
+% '</TABLE>',
+% 'weight' => 50,
+% },
+%
+% 'spool_csv' => {
+% 'name' => 'Spool CSV invoice data',
+% 'code' => '$cust_bill->spool_csv(
+% \'format\' => \'%%%spoolformat%%%\',
+% \'dest\' => \'%%%spooldest%%%\',
+% \'balanceover\' => \'%%%spoolbalanceover%%%\',
+% \'agent_spools\' => \'%%%spoolagent_spools%%%\',
+% );',
+% 'html' => sub {
+% my $plandata = shift;
+%
+% my $html =
+% '<TABLE BORDER=0>'.
+% '<TR><TD ALIGN="right">Format: </TD>'.
+% '<TD>'.
+% '<SELECT NAME="spoolformat">';
+%
+% foreach my $option (qw( default billco )) {
+% $html .= qq(<OPTION VALUE="$option");
+% $html .= ' SELECTED' if $option eq $plandata->{'spoolformat'};
+% $html .= ">\u$option";
+% }
+%
+% $html .=
+% '</SELECT>'.
+% '</TD></TR>'.
+% '<TR><TD ALIGN="right">For destination: </TD>'.
+% '<TD>'.
+% '<SELECT NAME="spooldest">';
+%
+% tie my %dest, 'Tie::IxHash',
+% '' => '(all)',
+% 'POST' => 'Postal Mail',
+% 'EMAIL' => 'Email',
+% 'FAX' => 'Fax',
+% ;
+%
+% foreach my $dest (keys %dest) {
+% $html .= qq(<OPTION VALUE="$dest");
+% $html .= ' SELECTED' if $dest eq $plandata->{'spooldest'};
+% $html .= '>'. $dest{$dest};
+% }
+%
+% $html .=
+% '</SELECT>'.
+% '</TD></TR>'.
+%
+% '<TR>'.
+% '<TD ALIGN="right">if balance (this invoice and previous) over </TD>'.
+% '<TD>'.
+% "$money_char ".
+% '<INPUT TYPE="text" SIZE="7" NAME="spoolbalanceover" VALUE="%%%spoolbalanceover%%%">'.
+% '</TD>'.
+% '<TR><TD ALIGN="right">Individual per-agent spools? </TD>'.
+% '<TD><INPUT TYPE="checkbox" NAME="spoolagent_spools" VALUE="1" '.
+% ( $plandata->{'spoolagent_spools'} ? 'CHECKED' : '' ).
+% '>'.
+% '</TD></TR>'.
+% '</TABLE>';
+%
+% $html;
+% },
+% 'weight' => 50,
+% },
+%
+% 'bill' => {
+% 'name' => 'Generate invoices (normally only used with a <i>Late Fee</i> event)',
+% 'code' => '$cust_main->bill();',
+% 'weight' => 60,
+% },
+%
+% 'apply' => {
+% 'name' => 'Apply unapplied payments and credits',
+% 'code' => '$cust_main->apply_payments_and_credits; "";',
+% 'weight' => 70,
+% },
+%
+% 'collect' => {
+% 'name' => 'Collect on invoices (normally only used with a <i>Late Fee</i> and <i>Generate Invoice</i> events)',
+% 'code' => '$cust_main->collect();',
+% 'weight' => 80,
+% },
+%
+%;
+%
+<SCRIPT TYPE="text/javascript">var myreasons = new Array();</SCRIPT>
+%foreach my $event ( keys %events ) {
+% my %plandata = map { /^(\w+) (.*)$/; ($1, $2); }
+% split(/\n/, $part_bill_event->plandata);
+% my $html = $events{$event}{html};
+% if ( ref($html) eq 'CODE' ) {
+% $html = &{$html}(\%plandata);
+% }
+% while ( $html =~ /%%%(\w+)%%%/ ) {
+% my $field = $1;
+% $html =~ s/%%%$field%%%/$plandata{$field}/;
+% }
+%
+<SCRIPT TYPE="text/javascript">myreasons.push('<% $events{$event}{reason} %>');
+</SCRIPT>
+% if ($event eq $part_bill_event->plan){
+% $currentreasonclass=$events{$event}{reason};
+% }
+% print ntable( "#cccccc", 2).
+% qq!<TR><TD><INPUT TYPE="radio" NAME="plan_weight_eventcode" !;
+% print "CHECKED " if $event eq $part_bill_event->plan;
+% print qq!onClick="showhide_table()" !;
+% print qq!VALUE="!. $event. ":". $events{$event}{weight}. ":".
+% encode_entities($events{$event}{code}).
+% qq!">$events{$event}{name}</TD>!;
+% print '<TD>'. $html. '</TD>' if $html;
+% print qq!</TR>!;
+% print '</TABLE>';
+%}
+%
+% if ($currentreasonclass eq 'C'){
+% if ($cgi->param('creason') =~ /^(-?\d+)$/){
+% $creason = $1;
+% }else{
+% $creason = $part_bill_event->reason;
+% }
+% if ($cgi->param('newcreasonT') =~ /^(\d+)$/){
+% $newcreasonT = $1;
+% }
+% if ($cgi->param('newcreason') =~ /^([\w\s]+)$/){
+% $newcreason = $1;
+% }
+% }elsif ($currentreasonclass eq 'S'){
+% if ($cgi->param('sreason') =~ /^(-?\d+)$/){
+% $sreason = $1;
+% }else{
+% $sreason = $part_bill_event->reason;
+% }
+% if ($cgi->param('newsreasonT') =~ /^(\d+)$/){
+% $newsreasonT = $1;
+% }
+% if ($cgi->param('newsreason') =~ /^([\w\s]+)$/){
+% $newsreason = $1;
+% }
+% }
+%
+
+</TD></TR>
+</TABLE>
+
+<SCRIPT TYPE="text/javascript">
+ function showhide_table()
+ {
+ for(i=0;i<document.editEvent.plan_weight_eventcode.length;i++){
+ if (document.editEvent.plan_weight_eventcode[i].checked == true){
+ currentevent=i;
+ }
+ }
+ if(myreasons[currentevent] == 'C'){
+ document.getElementById('Ctable').style.display = 'inline';
+ document.getElementById('Stable').style.display = 'none';
+ }else if(myreasons[currentevent] == 'S'){
+ document.getElementById('Ctable').style.display = 'none';
+ document.getElementById('Stable').style.display = 'inline';
+ }else{
+ document.getElementById('Ctable').style.display = 'none';
+ document.getElementById('Stable').style.display = 'none';
+ }
+ }
+</SCRIPT>
+
+<TABLE BGCOLOR="#cccccc" BORDER=0 WIDTH="100%">
+<TR><TD>
+<TABLE BORDER=0 id="Ctable" style="display:<% $currentreasonclass eq 'C' ? 'inline' : 'none' %>">
+<% include('/elements/tr-select-reason.html',
+ 'field' => 'creason',
+ 'reason_class' => 'C',
+ 'curr_value' => $creason,
+ 'init_type' => $newcreasonT,
+ 'init_newreason' => $newcreason
+ )
+%>
+</TABLE>
+</TR></TD>
+</TABLE>
+
+<TABLE BGCOLOR="#cccccc" BORDER=0 WIDTH="100%">
+<TR><TD>
+<TABLE BORDER=0 id="Stable" style="display:<% $currentreasonclass eq 'S' ? 'inline' : 'none' %>">
+<% include('/elements/tr-select-reason.html',
+ 'field' => 'sreason',
+ 'reason_class' => 'S',
+ 'curr_value' => $sreason,
+ 'init_type' => $newsreasonT,
+ 'init_newreason' => $newsreason
+ )
+%>
+</TABLE>
+</TR></TD>
+</TABLE>
+
+%
+%print qq!<INPUT TYPE="submit" VALUE="!,
+% $hashref->{eventpart} ? "Apply changes" : "Add invoice event",
+% qq!">!;
+%
+
+
+ </FORM>
+
+<% include('/elements/footer.html') %>
+
+<%init>
+
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('Configuration');
+
+if ( $cgi->param('eventpart') && $cgi->param('eventpart') =~ /^(\d+)$/ ) {
+ $cgi->param('eventpart', $1);
+} else {
+ $cgi->param('eventpart', '');
+}
+
+my ($creason, $newcreasonT, $newcreason);
+my ($sreason, $newsreasonT, $newsreason);
+
+my ($query) = $cgi->keywords;
+my $action = '';
+my $part_bill_event = '';
+my $currentreasonclass = '';
+if ( $cgi->param('error') ) {
+ $part_bill_event = new FS::part_bill_event ( {
+ map { $_, scalar($cgi->param($_)) } fields('part_bill_event')
+ } );
+}
+if ( $query && $query =~ /^(\d+)$/ ) {
+ $part_bill_event ||= qsearchs('part_bill_event',{'eventpart'=>$1});
+} else {
+ $part_bill_event ||= new FS::part_bill_event {};
+}
+$action ||= $part_bill_event->eventpart ? 'Edit' : 'Add';
+my $hashref = $part_bill_event->hashref;
+
+</%init>
diff --git a/httemplate/edit/part_event.html b/httemplate/edit/part_event.html
new file mode 100644
index 000000000..5b14edfa7
--- /dev/null
+++ b/httemplate/edit/part_event.html
@@ -0,0 +1,681 @@
+<% include( 'elements/edit.html',
+ 'name' => 'Billing event definition',
+ 'table' => 'part_event',
+ 'fields' => [
+ 'event',
+ { field => 'eventtable',
+ type => 'select',
+ options => [ FS::part_event->eventtables ],
+ labels => $eventtable_labels,
+ onchange => 'eventtable_changed',
+ },
+ { field => 'agentnum',
+ type => 'select-agent',
+ disable_empty => $disable_empty_agent,
+ },
+ { field => 'check_freq',
+ type => 'select',
+ options => [ '1d', '1m' ],
+ labels => $check_freq_labels,
+ },
+ { field => 'disabled',
+ type => 'checkbox',
+ value => 'Y',
+ },
+ { type => 'title',
+ value => 'Event Conditions',
+ },
+ { field => 'conditionname',
+ type => 'selectlayers',
+ options => [ keys %all_conditions ],
+ labels => \%condition_labels,
+ onchange => 'condition_changed(what);',
+ layer_fields => \%condition_fields,
+ layer_values_callback => $condition_layer_values,
+ html_between => n_a('action'),
+ m2name_table => 'part_event_condition',
+ m2name_namecol => 'conditionname',
+ m2name_label => 'Condition',
+ m2name_new_default => \@implicit_condition_objs,
+ m2name_error_callback =>
+ $condition_error_callback,
+ m2name_remove_warnings =>
+ \%condition_remove_warnings,
+ m2name_new_js => 'condition_repop',
+ m2name_remove_js => 'condition_add',
+ },
+ { type => 'title',
+ value => 'Event Action',
+ },
+ { field => 'action',
+ type => 'selectlayers',
+ options => [ keys %all_actions ],
+ labels => \%action_labels,
+ onchange => 'action_changed(what);',
+ layer_fields => \%action_fields,
+ layer_values_callback => $action_layer_values,
+ html_between => n_a('action'),
+ },
+
+ ],
+ 'labels' => {
+ 'eventpart' => 'Event',
+ 'event' => 'Event name',
+ 'eventtable' => 'Type',
+ 'agentnum' => 'Agent',
+ 'check_freq' => 'Check frequency',
+ 'disabled' => 'Disable event',
+
+ 'conditionname' => 'Add&nbsp;new&nbsp;condition',
+ #'weight',
+ 'action' => 'Action',
+ },
+ 'viewall_dir' => 'browse',
+ 'new_callback' => sub { #start empty for new events only
+ my( $cgi, $object, $fields_listref ) = @_;
+ unshift @{ $fields_listref->[1]{'options'} }, '';
+ },
+ 'error_callback' => $error_callback,
+
+ 'agent_virt' => 1,
+ 'agent_null_right' => 'Edit global billing events',
+ )
+%>
+<SCRIPT TYPE="text/javascript">
+
+ window.onload = function () { eventtable_changed(document.getElementById('eventtable')) };
+ var notonload = 0;
+
+ function eventtable_changed(what) {
+
+% if ( $JS_DEBUG ) {
+ alert('eventtable_changed called on ' + what );
+% }
+
+ var eventtable = what.options[what.selectedIndex].value;
+% if ( $JS_DEBUG ) {
+ alert ("eventtable: " + eventtable);
+% }
+ var eventdesc = what.options[what.selectedIndex].text;
+
+ //remove the ** Select type **
+ if ( what.options[0].value == '' && notonload++ > 0 ) {
+ what.options[0] = null;
+ }
+
+ ////
+ // XXX gray out conditions that can't apply (in addition to the warning)?
+ ////
+
+ ////
+ // update condition selects
+ ////
+
+ for ( var cnum=0; document.getElementById('conditionname'+cnum); cnum++ ) {
+ var cond_id = 'conditionname' + cnum;
+ var cond_select = document.getElementById(cond_id);
+
+% if ( $JS_DEBUG ) {
+ alert('updating ' + cond_id);
+% }
+
+ // save off the current value
+ var conditionname = cond_select.options[cond_select.selectedIndex].value;
+ var cond_desc = cond_select.options[cond_select.selectedIndex].text;
+
+ var seen_condition = condition_repop(cond_select);
+
+ var warning = document.getElementById(cond_id + '_warning');
+% if ( $JS_DEBUG ) {
+ alert('turning off warning; setting style.display of '+ cond_id +
+ '_warning (' + warning + ') to none');
+% }
+ warning.style.display = 'none';
+
+ if ( ! seen_condition && conditionname != '' ) {
+ // add the current (not valid) condition back
+ opt(cond_select, conditionname, cond_desc, true );
+ if ( ! condition_is_implicit(conditionname) ) {
+ cond_select.parentNode.parentNode.style.display = '';
+ cond_select.disabled = '';
+ // turn on a warning and gray out the condition row
+% if ( $JS_DEBUG ) {
+ alert('turning on warning; setting style.display of '+ cond_id +
+ '_warning (' + warning + ') to ""');
+% }
+ warning.innerHTML = 'Not applicable to ' + eventdesc + ' events';
+ warning.style.display = '';
+ } else {
+ if ( ! condition_in_eventtable(conditionname) ) {
+% if ( $JS_DEBUG ) {
+ alert(conditionname + " not in " + eventtable + "; disabling");
+% }
+ cond_select.parentNode.parentNode.style.display = 'none';
+ cond_select.disabled = 'disabled';
+ } else {
+% if ( $JS_DEBUG ) {
+ alert(conditionname + " implicit for " + eventtable + "; enabling");
+% }
+ cond_select.parentNode.parentNode.style.display = '';
+ cond_select.disabled = '';
+ }
+ }
+ }
+
+ }
+
+
+ ////
+ // update action select
+ ////
+
+ // save off the current value first!!
+ var action = what.form.action.options[what.form.action.selectedIndex].value;
+ var a_desc = what.form.action.options[what.form.action.selectedIndex].text;
+ var seen_action = false;
+
+ // blank the current action select
+ for ( var i = what.form.action.length; i >= 0; i-- )
+ what.form.action.options[i] = null;
+
+ if ( action == '' ) {
+ opt(what.form.action, action, a_desc, true );
+ }
+
+ // repopulate it
+% foreach my $eventtable ( FS::part_event->eventtables ) {
+% tie my %actions, 'Tie::IxHash', FS::part_event->actions($eventtable);
+% #use Data::Dumper; warn Dumper(%actions);
+
+ if ( eventtable == '<% $eventtable %>' ) {
+
+% foreach my $action ( keys %actions ) {
+% ( my $description = $actions{$action}->{'description'} ) =~ s/'/\\'/g;
+
+ var sel = false;
+ if ( action == '<% $action %>' ) {
+ seen_action = true;
+ sel = true;
+ }
+ opt( what.form.action, '<% $action %>', '<% $description %>', sel );
+% }
+
+ }
+
+% }
+
+ // by default, turn off warnings and enable the submit button
+ var warning = document.getElementById('action_warning');
+ warning.style.display = 'none';
+ var submit_button = document.getElementById('submit');
+ submit_button.disabled = '';
+
+ if ( ! seen_action && action != '' ) {
+ // add the current (not valid) action back
+ opt( what.form.action, action, a_desc, true );
+ // turn on a warning and disable the submit button
+ //warning.innerHTML = a_desc + ' event not available as a ' +
+ warning.innerHTML = 'Not available as a ' + eventdesc + ' action';
+ warning.style.display = '';
+ submit_button.disabled = 'disabled';
+ }
+
+ }
+
+ function opt(what,value,text,selected) {
+ var optionName = new Option(text, value, false, selected);
+ var length = what.length;
+ what.options[length] = optionName;
+ }
+
+ function action_changed(what) {
+ // remove '** Select new **'
+ if ( what.options[0].value == '' ) {
+ what.options[0] = null;
+ }
+ // remove the warning, remove the invalid action, enable the submit button
+ var warning = document.getElementById('action_warning');
+ if ( warning.style.display == '' ) {
+ warning.style.display = 'none';
+ what.options[what.length-1] = null;
+ document.getElementById('submit').disabled = '';
+ }
+ }
+
+ function condition_changed(what) {
+ // remove '** Select new **'
+ if ( what.options[0].value == '' ) {
+ what.options[0] = null;
+ }
+
+ var previousValue = what.getAttribute('previousValue');
+ var previousText = what.getAttribute('previousText');
+ var value = what.options[what.selectedIndex].value;
+ var text = what.options[what.selectedIndex].text;
+
+% foreach my $value ( keys %condition_remove_warnings ) {
+ if ( previousValue == '<% $value %>' ) {
+ if ( !confirm( <% $condition_remove_warnings{$value} |js_string %> ) ) {
+ for ( var i=0; i < what.length; i++ ) {
+ if ( what.options[i].value == previousValue ) {
+ what.selectedIndex = i;
+ }
+ }
+ return false;
+ }
+ }
+% }
+
+ //alert(previous + ' changed to ' + value);
+
+ var field_regex = /(\d+)$/;
+ var match = field_regex.exec(what.name);
+ if ( !match ) {
+ alert(what.name + " didn't match?!");
+ return;
+ }
+
+ //add the previous condition *back* to all the other selects...
+ condition_add(previousValue, previousText, match[1]);
+
+ what.setAttribute('previousValue', value);
+ what.setAttribute('previousText', text);
+
+ // remove the new condition from all other selects
+ condition_remove(value, match[1]);
+
+ }
+
+ function condition_avail(check_cond, curnum) {
+ for ( var cnum=0; document.getElementById('conditionname'+cnum); cnum++ ) {
+ if ( cnum == curnum ) continue;
+
+ var cond_id = 'conditionname' + cnum;
+ var cond_select = document.getElementById(cond_id);
+
+ //alert("checking " + cond_id + " (" + cond_select.disabled + ")");
+
+ if ( cond_select.disabled ) continue;
+
+ // the current value
+ var conditionname = cond_select.options[cond_select.selectedIndex].value;
+
+ if ( check_cond == conditionname ) return false;
+
+ }
+
+ return true;
+
+ }
+
+ function condition_remove(remove_cond, curnum) {
+
+ if ( remove_cond.length == 0 ) return;
+
+ for ( var cnum=0; document.getElementById('conditionname'+cnum); cnum++ ) {
+ if ( cnum == curnum ) continue;
+
+ var cond_id = 'conditionname' + cnum;
+ var cond_select = document.getElementById(cond_id);
+
+ //for ( var i = cond_select.length; i >= 0; i-- ) {
+ for ( var i=0; i < cond_select.length; i++ ) {
+ if ( cond_select.options[i].value == remove_cond ) {
+ cond_select.options[i] = null;
+ }
+ }
+
+ }
+
+ }
+
+ function condition_add(add_condname, add_conddesc, curnum) {
+
+ if ( add_condname.length == 0 ) return;
+
+ var in_eventtable = condition_in_eventtable(add_condname);
+
+ if ( ! in_eventtable ) return;
+
+ for ( var cnum=0; document.getElementById('conditionname'+cnum); cnum++ ) {
+ if ( cnum == curnum ) continue;
+
+ var cond_id = 'conditionname' + cnum;
+ var cond_select = document.getElementById(cond_id);
+
+ if ( cond_select.disabled ) continue;
+
+ //alert("adding " + add_condname + " to " + cond_id);
+
+ opt(cond_select, add_condname, add_conddesc, false );
+
+ cond_select.parentNode.parentNode.style.display = '';
+
+ }
+
+ }
+
+ function condition_in_eventtable(condname) {
+
+ var eventtable_el = document.getElementById('eventtable');
+ var eventtable = eventtable_el.options[eventtable_el.selectedIndex].value;
+
+ var in_eventtable = false;
+
+% foreach my $eventtable ( FS::part_event->eventtables ) {
+% tie my %conditions, 'Tie::IxHash',
+% FS::part_event_condition->conditions($eventtable);
+
+ if ( eventtable == '<% $eventtable %>' ) {
+
+% foreach my $conditionname ( keys %conditions ) {
+
+ if ( condname == '<% $conditionname %>' ) {
+ in_eventtable = true;
+ }
+
+% }
+
+ }
+
+% }
+
+ return in_eventtable;
+
+ }
+
+ function condition_is_implicit(condname) {
+
+ if ( true <% @implicit_conditions
+ ? ( ' && '. join(' && ', map { "condname != '$_'" }
+ @implicit_conditions
+ )
+ )
+ : ''
+ %> ) {
+ return false;
+ } else {
+ return true;
+ }
+ }
+
+ function condition_repop(cond_select) {
+
+ var eventtable_el = document.getElementById('eventtable');
+ var eventtable = eventtable_el.options[eventtable_el.selectedIndex].value;
+
+ // save off the current value
+ var conditionname = cond_select.options[cond_select.selectedIndex].value;
+ var cond_desc = cond_select.options[cond_select.selectedIndex].text;
+ var seen_condition = false;
+
+ //skip deleted conditions
+ if ( cond_select.disabled && conditionname != '' && ! condition_is_implicit(conditionname) ) {
+ return false;
+ }
+
+ var field_regex = /(\d+)$/;
+ var match = field_regex.exec(cond_select.name);
+ if ( !match ) {
+ alert(what.name + " didn't match?!");
+ return;
+ }
+ var cnum = match[1];
+
+ // blank the current condition select
+ for ( var i = cond_select.length; i >= 0; i-- )
+ cond_select.options[i] = null;
+
+ if ( conditionname == '' ) {
+ opt(cond_select, conditionname, cond_desc, true );
+ }
+
+ // repopulate it
+% foreach my $eventtable ( FS::part_event->eventtables ) {
+% tie my %conditions, 'Tie::IxHash',
+% FS::part_event_condition->conditions($eventtable);
+
+ if ( eventtable == '<% $eventtable %>' ) {
+
+% foreach my $conditionname ( keys %conditions ) {
+% my $description = $conditions{$conditionname}->{'description'};
+% $description =~ s/'/\\'/g;
+
+ var sel = false;
+ if ( conditionname == '<% $conditionname %>' ) {
+ seen_condition = true;
+ sel = true;
+ }
+
+ if ( condition_avail("<% $conditionname %>", cnum) ) {
+ opt(cond_select, '<% $conditionname %>', '<% $description %>', sel);
+ }
+
+% }
+
+ }
+
+% }
+
+ if ( cond_select.length > 1 || cond_select.length == 1 && cond_select.options[0].value.length > 0 ) {
+
+ cond_select.parentNode.parentNode.style.display = '';
+ cond_select.disabled = '';
+
+ } else {
+ cond_select.parentNode.parentNode.style.display = 'none';
+ cond_select.disabled = 'disabled';
+ }
+
+ return seen_condition;
+
+ }
+
+</SCRIPT>
+<%once>
+
+#misc (eventtable, check_freq)
+
+my $eventtable_labels = FS::part_event->eventtable_labels;
+$eventtable_labels->{''} = '** Select type **';
+
+my $check_freq_labels = FS::part_event->check_freq_labels;
+
+#conditions
+
+tie my %all_conditions, 'Tie::IxHash',
+ '' => { 'description' => '*** Select new condition ***', },
+ FS::part_event_condition->conditions();
+
+my %condition_labels = map { $_ => $all_conditions{$_}->{'description'} }
+ keys %all_conditions;
+
+#my %condition_fields = map { $_ => $all_conditions{$_}->{option_fields} }
+# keys %all_conditions;
+my %condition_fields = map { my $c = $_;
+ tie my %opts, 'Tie::IxHash',
+ @{ $all_conditions{$c}->{'option_fields'} || []};
+ %opts = ( map { ( "$c.$_" => $opts{$_} ); }
+ keys %opts
+ );
+ ( $c => [ %opts ] );
+ }
+ keys %all_conditions;
+
+my @implicit_conditions = sort { $all_conditions{$a}->{'implicit_flag'} <=>
+ $all_conditions{$b}->{'implicit_flag'}
+ }
+ grep { $all_conditions{$_}->{'implicit_flag'} }
+ keys %all_conditions;
+
+my @implicit_condition_objs = map {
+ new FS::part_event_condition {
+ 'conditionname' => $_,
+ };
+ }
+ @implicit_conditions;
+
+my %condition_remove_warnings =
+ map { ( $_ => $all_conditions{$_}->{'remove_warning'} ); }
+ grep { $all_conditions{$_}->{'remove_warning'} }
+ keys %all_conditions;
+
+#actions
+
+tie my %all_actions, 'Tie::IxHash',
+ '' => { 'description' => '*** Select event action ***', },
+ FS::part_event->actions();
+
+my %action_labels = map { $_ => $all_actions{$_}->{'description'} }
+ keys %all_actions;
+
+#my %action_fields = map { $_ => $all_actions{$_}->{option_fields} }
+# keys %all_actions;
+my %action_fields = map { my $action = $_;
+ tie my %opts, 'Tie::IxHash',
+ @{ $all_actions{$action}->{option_fields} || [] };
+ %opts = ( map { ( "$action.$_" => $opts{$_} ); }
+ keys %opts
+ );
+ ( $action => [ %opts ] );
+ }
+ keys %all_actions;
+
+#subs
+
+sub n_a {
+ my $t = shift;
+
+ return sub {
+ my $field = shift;
+ qq( <FONT ID="${field}_warning" STYLE="display:none" COLOR="#FF0000">).
+ "Party Party Join us Join us".
+ '</FONT>';
+ };
+}
+
+my $action_layer_values = sub {
+ my( $cgi, $part_event ) = @_;
+ my $action = $cgi->param('action') || $part_event->action;
+ return {} unless $action;
+ scalar( #force hashref
+ {
+ #map { $_ => { $part_event->options } }
+ # keys %action_fields
+ map { my $action = $_;
+ my %fields = @{ $action_fields{$action} };
+ my %obj_opts = $part_event->options;
+ %obj_opts = map { ( "$action.$_" => $obj_opts{$_} ); }
+ keys %obj_opts;
+ my %opts =
+ map { #false laziness w/process/part_event.html
+ my $option = $_;
+ my $value = scalar($cgi->param($_)) || $obj_opts{$_};
+
+ if ( $option =~ /^(.*)\.reasonnum$/ && $value == -1 ) {
+ $value = {
+ 'typenum' => scalar( $cgi->param( "new${option}T" ) ),
+ 'reason' => scalar( $cgi->param( "new${option}" ) ),
+ };
+ }
+
+ ( $option => $value );
+
+ }
+ keys %fields;
+ ( $action => \%opts );
+ }
+ keys %action_fields
+ }
+ );
+};
+
+tie my %cgi_conditions, 'Tie::IxHash';
+
+my $error_callback = sub {
+ my( $cgi, $object, $fields_listref ) = @_;
+
+ my @cond_params = grep /^conditionname\d+$/, $cgi->param;
+
+ %cgi_conditions = map {
+ my $param = $_;
+ my $conditionname = $cgi->param($param);
+ $conditionname => {
+ map {
+
+ my $cgi_key = $_;
+ $cgi_key =~ /^$param\.$conditionname\.(.*)$/ or die 'wtf!';
+ my $key = $1;
+ #my $value = $cgi->param($_);
+
+ #my $info = $all_conditions->{$conditionname}
+ my %cond_opts =
+ @{ $all_conditions{$conditionname}->{'option_fields'} || []};
+ my $info = $cond_opts{$key};
+
+ my $value;
+ #false laziness w/process/part_event.html
+ if ( $info->{'type'} =~ /^(select|checkbox)-?multiple$/
+ or $info->{'type'} =~ /^select/ && $info->{'multiple'} ) {
+ $value = { map { $_ => 1 } $cgi->param($cgi_key) };
+ } elsif ( $info->{'type'} eq 'freq' ) {
+ $value = $cgi->param($cgi_key). $cgi->param($cgi_key.'_units');
+ } else {
+ $value = $cgi->param($cgi_key);
+ }
+
+ $key => $value;
+
+ } grep /^$param\.$conditionname\./, $cgi->param
+ };
+ } grep $cgi->param($_), grep /^conditionname\d+$/, $cgi->param;
+
+};
+
+my $condition_error_callback = sub {
+ map {
+ new FS::part_event_condition { 'conditionname' => $_, };
+ } keys %cgi_conditions;
+};
+
+my $condition_layer_values = sub {
+ #m2name_table option causes this to be
+ # part_event_condition instead of part_event
+ my ( $cgi, $part_event_condition, $switches ) = @_;
+ scalar( #force hashref
+ {
+ #map { $_ => { $part_event_condition->options } }
+ # keys %condition_fields
+ map { my $conditionname = $_;
+ my %opts = $switches->{'mode'} eq 'error'
+ ? %{ $cgi_conditions{$conditionname} || {} }
+ : $part_event_condition->options;
+ %opts = (
+ map { ( "$conditionname.$_" => $opts{$_} ); }
+ keys %opts
+ );
+ ( $conditionname => \%opts );
+ }
+ keys %condition_fields
+ }
+ );
+};
+
+
+</%once>
+<%init>
+
+my $curuser = $FS::CurrentUser::CurrentUser;
+
+die "access denied"
+ unless $curuser->access_right('Edit billing events')
+ || $curuser->access_right('Edit global billing events');
+
+my $disable_empty_agent= ! $curuser->access_right('Edit global billing events');
+
+%cgi_conditions = ();
+my $use_cgi_conditions = 0;
+
+my $JS_DEBUG = 0;
+
+</%init>
diff --git a/httemplate/edit/part_export.cgi b/httemplate/edit/part_export.cgi
new file mode 100644
index 000000000..d57979751
--- /dev/null
+++ b/httemplate/edit/part_export.cgi
@@ -0,0 +1,123 @@
+<% include('/elements/header.html', "$action Export", '', ' onLoad="visualize()"') %>
+
+<% include('/elements/error.html') %>
+
+<FORM NAME="dummy">
+<INPUT TYPE="hidden" NAME="exportnum" VALUE="<% $part_export->exportnum %>">
+
+<% ntable("#cccccc",2) %>
+<TR>
+ <TD ALIGN="right">Export host</TD>
+ <TD>
+ <INPUT TYPE="text" NAME="machine" VALUE="<% $part_export->machine %>">
+ </TD>
+</TR>
+<TR>
+ <TD ALIGN="right">Export</TD>
+ <TD><% $widget->html %>
+
+<% include('/elements/footer.html') %>
+<%init>
+
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('Configuration');
+
+#if ( $cgi->param('clone') && $cgi->param('clone') =~ /^(\d+)$/ ) {
+# $cgi->param('clone', $1);
+#} else {
+# $cgi->param('clone', '');
+#}
+
+my($query) = $cgi->keywords;
+my $action = '';
+my $part_export = '';
+if ( $cgi->param('error') ) {
+ $part_export = new FS::part_export ( {
+ map { $_, scalar($cgi->param($_)) } fields('part_export')
+ } );
+} elsif ( $query =~ /^(\d+)$/ ) {
+ $part_export = qsearchs('part_export', { 'exportnum' => $1 } );
+} else {
+ $part_export = new FS::part_export;
+}
+$action ||= $part_export->exportnum ? 'Edit' : 'Add';
+
+#my $exports = FS::part_export::export_info($svcdb);
+my $exports = FS::part_export::export_info();
+
+my %layers = map { $_ => "$_ - ". $exports->{$_}{desc} } keys %$exports;
+$layers{''}='';
+
+my $widget = new HTML::Widgets::SelectLayers(
+ 'selected_layer' => $part_export->exporttype,
+ 'options' => \%layers,
+ 'form_name' => 'dummy',
+ 'form_action' => 'process/part_export.cgi',
+ 'form_text' => [qw( exportnum machine )],
+# 'form_checkbox' => [qw()],
+ 'html_between' => "</TD></TR></TABLE>\n",
+ 'layer_callback' => sub {
+ my $layer = shift;
+ my $html = qq!<INPUT TYPE="hidden" NAME="exporttype" VALUE="$layer">!.
+ ntable("#cccccc",2);
+
+ $html .= '<TR><TD ALIGN="right">Description</TD><TD BGCOLOR=#ffffff>'.
+ $exports->{$layer}{notes}. '</TD></TR>'
+ if $layer;
+
+ foreach my $option ( keys %{$exports->{$layer}{options}} ) {
+ my $optinfo = $exports->{$layer}{options}{$option};
+ die "Retreived non-ref export info option from $layer export: $optinfo"
+ unless ref($optinfo);
+ my $label = $optinfo->{label};
+ my $type = defined($optinfo->{type}) ? $optinfo->{type} : 'text';
+ my $value = $cgi->param($option)
+ || ( $part_export->exportnum && $part_export->option($option) )
+ || ( (exists $optinfo->{default} && !$part_export->exportnum)
+ ? $optinfo->{default}
+ : ''
+ );
+ $html .= qq!<TR><TD ALIGN="right">$label</TD><TD>!;
+ if ( $type eq 'select' ) {
+ $html .= qq!<SELECT NAME="$option">!;
+ foreach my $select_option ( @{$optinfo->{options}} ) {
+ #if ( ref($select_option) ) {
+ #} else {
+ my $selected = $select_option eq $value ? ' SELECTED' : '';
+ $html .= qq!<OPTION VALUE="$select_option"$selected>!.
+ qq!$select_option</OPTION>!;
+ #}
+ }
+ $html .= '</SELECT>';
+ } elsif ( $type eq 'textarea' ) {
+ $html .= qq!<TEXTAREA NAME="$option" COLS=80 ROWS=8 WRAP="virtual">!.
+ encode_entities($value). '</TEXTAREA>';
+ } elsif ( $type eq 'text' ) {
+ $html .= qq!<INPUT TYPE="text" NAME="$option" VALUE="!.
+ encode_entities($value). '" SIZE=64>';
+ } elsif ( $type eq 'checkbox' ) {
+ $html .= qq!<INPUT TYPE="checkbox" NAME="$option" VALUE="1"!;
+ $html .= ' CHECKED' if $value;
+ $html .= '>';
+ } else {
+ $html .= "unknown type $type";
+ }
+ $html .= '</TD></TR>';
+ }
+ $html .= '</TABLE>';
+
+ $html .= '<INPUT TYPE="hidden" NAME="options" VALUE="'.
+ join(',', keys %{$exports->{$layer}{options}} ). '">';
+
+ $html .= '<INPUT TYPE="hidden" NAME="nodomain" VALUE="'.
+ $exports->{$layer}{nodomain}. '">';
+
+ $html .= '<INPUT TYPE="submit" VALUE="'.
+ ( $part_export->exportnum ? "Apply changes" : "Add export" ).
+ '">';
+
+ $html;
+ },
+);
+
+</%init>
diff --git a/httemplate/edit/part_pkg.cgi b/httemplate/edit/part_pkg.cgi
new file mode 100755
index 000000000..ec001cb0c
--- /dev/null
+++ b/httemplate/edit/part_pkg.cgi
@@ -0,0 +1,429 @@
+<% include('/elements/header.html', "$action Package Definition", menubar(
+ 'View all packages' => popurl(2). 'browse/part_pkg.cgi',
+)) %>
+% #), ' onLoad="visualize()"');
+
+<% include('/elements/error.html') %>
+
+<FORM NAME="dummy">
+
+<% itable('',8,1) %><TR><TD VALIGN="top">
+
+Package information
+
+<% ntable("#cccccc",2) %>
+ <TR>
+ <TD ALIGN="right">Package Definition #</TD>
+ <TD BGCOLOR="#ffffff">
+ <% $hashref->{pkgpart} ? $hashref->{pkgpart} : "(NEW)" %>
+ </TD>
+ </TR>
+ <TR>
+ <TD ALIGN="right">Package (customer-visible)</TD>
+ <TD>
+ <INPUT TYPE="text" NAME="pkg" SIZE=32 VALUE="<% $part_pkg->pkg %>">
+ </TD>
+ </TR>
+ <TR>
+ <TD ALIGN="right">Comment (customer-hidden)</TD>
+ <TD>
+ <INPUT TYPE="text" NAME="comment" SIZE=32 VALUE="<%$part_pkg->comment%>">
+ </TD>
+ </TR>
+ <% include( '/elements/tr-select-pkg_class.html',
+ 'curr_value' => $part_pkg->classnum,
+ )
+ %>
+ <TR>
+ <TD ALIGN="right">Promotional code</TD>
+ <TD>
+ <INPUT TYPE="text" NAME="promo_code" SIZE=32 VALUE="<%$part_pkg->promo_code%>">
+ </TD>
+ </TR>
+ <TR>
+ <TD ALIGN="right">Disable new orders</TD>
+ <TD>
+ <INPUT TYPE="checkbox" NAME="disabled" VALUE="Y"<% $hashref->{disabled} eq 'Y' ? ' CHECKED' : '' %>
+ </TD>
+ </TR>
+
+</TABLE>
+
+</TD><TD VALIGN="top">
+
+Tax information
+<% ntable("#cccccc", 2) %>
+ <TR>
+ <TD ALIGN="right">Setup fee tax exempt</TD>
+ <TD>
+ <INPUT TYPE="checkbox" NAME="setuptax" VALUE="Y" <% $hashref->{setuptax} eq 'Y' ? ' CHECKED' : '' %>>
+ </TD>
+ </TR>
+ <TR>
+ <TD ALIGN="right">Recurring fee tax exempt</TD>
+ <TD>
+ <INPUT TYPE="checkbox" NAME="recurtax" VALUE="Y" <% $hashref->{recurtax} eq 'Y' ? ' CHECKED' : '' %>>
+ </TD>
+ </TR>
+
+% if ( $conf->exists('enable_taxclasses') ) {
+
+ <TR>
+ <TD align="right">Tax class</TD>
+ <TD>
+ <% include('/elements/select-taxclass.html', $hashref->{taxclass} ) %>
+ </TD>
+ </TR>
+
+% } else {
+
+ <% include('/elements/select-taxclass.html', $hashref->{taxclass} ) %>
+
+% }
+
+</TABLE>
+<BR>
+
+Line-item revenue recognition
+<% ntable("#cccccc", 2) %>
+% tie my %weight, 'Tie::IxHash',
+% 'pay_weight' => 'Payment',
+% 'credit_weight' => 'Credit'
+% ;
+% foreach my $weight (keys %weight) {
+ <TR>
+ <TD ALIGN="right"><% $weight{$weight} %> weight</TD>
+ <TD>
+ <INPUT TYPE="text" NAME="<% $weight %>" SIZE=6 VALUE=<% $hashref->{$weight} || 0 %>>
+ </TD>
+ </TR>
+% }
+</TABLE>
+
+</TD><TD VALIGN="top">
+
+% if ( $cgi->param('clone') ) {
+
+ <INPUT TYPE="hidden" NAME="agent_type" VALUE="">
+
+% } elsif ( scalar(@all_agent_types) == 1) {
+
+ <INPUT TYPE="hidden" NAME="agent_type" VALUE="<% $all_agent_types[0] %>">
+
+% } else {
+
+ Reseller information
+ <% ntable("#cccccc", 2) %>
+ <TR>
+ <TD ALIGN="right"><% 'Agent Types' %></TD>
+ <TD>
+ <% include( '/elements/select-table.html',
+ 'element_name' => 'agent_type',
+ 'table' => 'agent_type',
+ 'name_col' => 'atype',
+ 'value' => \@agent_type,
+ 'multiple' => '1',
+ 'element_etc' => 'size="10"',
+ )
+ %>
+ </TD>
+ </TR>
+ </TABLE>
+
+% }
+
+</TD></TR></TABLE>
+
+%my $thead = "\n\n". ntable('#cccccc', 2).
+% '<TR><TH BGCOLOR="#dcdcdc"><FONT SIZE=-1>Quan.</FONT></TH>'.
+% '<TH BGCOLOR="#dcdcdc"><FONT SIZE=-1>Primary</FONT></TH>'.
+% '<TH BGCOLOR="#dcdcdc">Service</TH></TR>';
+
+<BR><BR>Services included
+<% itable('', 4, 1) %><TR><TD VALIGN="top">
+<% $thead %>
+%
+%
+%my $where = "WHERE disabled IS NULL OR disabled = ''";
+%if ( $pkgpart ) {
+% $where .= " OR 0 < ( SELECT quantity FROM pkg_svc
+% WHERE pkg_svc.svcpart = part_svc.svcpart
+% AND pkgpart = $pkgpart
+% )";
+%}
+%my @part_svc = qsearch('part_svc', {}, '', $where);
+%my $q_part_pkg = $clone_part_pkg || $part_pkg;
+%my %pkg_svc = map { $_->svcpart => $_ } $q_part_pkg->pkg_svc;
+%
+%my @fixups = ();
+%my $count = 0;
+%my $columns = 3;
+%foreach my $part_svc ( @part_svc ) {
+% my $svcpart = $part_svc->svcpart;
+% my $pkg_svc = $pkg_svc{$svcpart}
+% || new FS::pkg_svc ( {
+% 'pkgpart' => $pkgpart,
+% 'svcpart' => $svcpart,
+% 'quantity' => 0,
+% 'primary_svc' => '',
+% } );
+% if ( $cgi->param('error') ) {
+% my $primary_svc = ( $pkg_svc->primary_svc =~ /^Y/i );
+% my $pkg_svc_primary = scalar($cgi->param('pkg_svc_primary'));
+% $pkg_svc->primary_svc('')
+% if $primary_svc && $pkg_svc_primary != $svcpart;
+% $pkg_svc->primary_svc('Y')
+% if ! $primary_svc && $pkg_svc_primary == $svcpart;
+% }
+%
+% push @fixups, "pkg_svc$svcpart";
+%
+% my $quan = 0;
+% if ( $cgi->param("pkg_svc$svcpart") =~ /^\s*(\d+)\s*$/ ) {
+% $quan = $1;
+% } elsif ( $pkg_svc->quantity ) {
+% $quan = $pkg_svc->quantity;
+% }
+
+
+ <TR>
+ <TD>
+ <INPUT TYPE="text" NAME="pkg_svc<% $svcpart %>" SIZE=4 MAXLENGTH=3 VALUE="<% $quan %>">
+ </TD>
+
+ <TD>
+ <INPUT TYPE="radio" NAME="pkg_svc_primary" VALUE="<% $svcpart %>" <% $pkg_svc->primary_svc =~ /^Y/i ? ' CHECKED' : '' %>>
+ </TD>
+
+ <TD>
+ <A HREF="part_svc.cgi?<% $part_svc->svcpart %>"><% $part_svc->svc %></A> <% $part_svc->disabled =~ /^Y/i ? ' (DISABLED' : '' %>
+ </TD>
+ </TR>
+% foreach ( 1 .. $columns-1 ) {
+% if ( $count == int( $_ * scalar(@part_svc) / $columns ) ) {
+%
+
+ </TABLE></TD><TD VALIGN="top"><% $thead %>
+% }
+% }
+% $count++;
+%
+% }
+
+
+</TR></TABLE></TD></TR></TABLE>
+% foreach my $f ( qw( clone pkgnum ) ) { #safe, these were untained in %init
+ <INPUT TYPE="hidden" NAME="<% $f %>" VALUE="<% $cgi->param($f) %>">
+% }
+
+<INPUT TYPE="hidden" NAME="pkgpart" VALUE="<% $part_pkg->pkgpart %>">
+%
+%
+%# prolly should be in database
+%tie my %plans, 'Tie::IxHash', %{ FS::part_pkg::plan_info() };
+%
+%my %plandata = map { /^(\w+)=(.*)$/; ( $1 => $2 ); }
+% split("\n", ($clone_part_pkg||$part_pkg)->plandata );
+%#warn join("\n", map { "$_: $plandata{$_}" } keys %plandata ). "\n";
+%
+%tie my %options, 'Tie::IxHash', map { $_=>$plans{$_}->{'name'} } keys %plans;
+%
+%#my @form_select = ('classnum');
+%#if ( $conf->exists('enable_taxclasses') ) {
+%# push @form_select, 'taxclass';
+%#} else {
+%# push @fixups, 'taxclass'; #hidden
+%#}
+%my @form_elements = ( 'classnum', 'taxclass', 'agent_type' );
+%
+%my @form_radio = ( 'pkg_svc_primary' );
+%
+%tie my %freq, 'Tie::IxHash', %{FS::part_pkg->freqs_href()};
+%if ( $part_pkg->dbdef_table->column('freq')->type =~ /(int)/i ) {
+% delete $freq{$_} foreach grep { ! /^\d+$/ } keys %freq;
+%}
+%
+%#this should be replaced by /elements/selectlayers.html
+%my $widget = new HTML::Widgets::SelectLayers(
+% 'selected_layer' => $part_pkg->plan,
+% 'options' => \%options,
+% 'form_name' => 'dummy',
+% 'form_action' => 'process/part_pkg.cgi',
+% 'form_elements' => \@form_elements,
+% 'form_text' => [ qw(pkg comment promo_code clone pkgnum pkgpart),
+% qw(pay_weight credit_weight), #keys(%weight),
+% @fixups,
+% ],
+% 'form_checkbox' => [ qw(setuptax recurtax disabled) ],
+% 'form_radio' => \@form_radio,
+% 'layer_callback' => sub {
+% my $layer = shift;
+% my $html = qq!<INPUT TYPE="hidden" NAME="plan" VALUE="$layer">!.
+% ntable("#cccccc",2);
+% $html .= '
+% <TR>
+% <TD ALIGN="right">Recurring fee frequency </TD>
+% <TD><SELECT NAME="freq">
+% ';
+%
+% my @freq = keys %freq;
+% @freq = grep { /^\d+$/ } @freq
+% if exists($plans{$layer}->{'freq'}) && $plans{$layer}->{'freq'} eq 'm';
+% foreach my $freq ( @freq ) {
+% $html .= qq(<OPTION VALUE="$freq");
+% $html .= ' SELECTED' if $freq eq $part_pkg->freq;
+% $html .= ">$freq{$freq}";
+% }
+% $html .= '</SELECT></TD></TR>';
+%
+% my $href = $plans{$layer}->{'fields'};
+% foreach my $field ( exists($plans{$layer}->{'fieldorder'})
+% ? @{$plans{$layer}->{'fieldorder'}}
+% : keys %{ $href }
+% ) {
+%
+% $html .= '<TR><TD ALIGN="right">'. $href->{$field}{'name'}. '</TD><TD>';
+%
+% my $format = sub { shift };
+% $format = $href->{$field}{'format'} if exists($href->{$field}{'format'});
+% if ( ! exists($href->{$field}{'type'}) ) {
+% $html .= qq!<INPUT TYPE="text" NAME="$field" VALUE="!.
+% ( exists($plandata{$field})
+% ? &$format($plandata{$field})
+% : $href->{$field}{'default'} ).
+% qq!" onChange="fchanged(this)">!;
+% } elsif ( $href->{$field}{'type'} eq 'checkbox' ) {
+% $html .= qq!<INPUT TYPE="checkbox" NAME="$field" VALUE=1 !.
+% ( exists($plandata{$field}) && $plandata{$field}
+% ? ' CHECKED'
+% : ''
+% ). '>';
+% } elsif ( $href->{$field}{'type'} =~ /^select/ ) {
+% $html .= '<SELECT';
+% $html .= ' MULTIPLE'
+% if $href->{$field}{'type'} eq 'select_multiple';
+% $html .= qq! NAME="$field" onChange="fchanged(this)">!;
+%
+% if ( $href->{$field}{'select_table'} ) {
+% foreach my $record (
+% qsearch( $href->{$field}{'select_table'},
+% $href->{$field}{'select_hash'} )
+% ) {
+% my $value = $record->getfield($href->{$field}{'select_key'});
+% $html .= qq!<OPTION VALUE="$value"!.
+% ( $plandata{$field} =~ /(^|, *)$value *(,|$)/
+% ? ' SELECTED'
+% : ''
+% ).
+% '>'. $record->getfield($href->{$field}{'select_label'});
+% }
+% } elsif ( $href->{$field}{'select_options'} ) {
+% foreach my $key ( keys %{ $href->{$field}{'select_options'} } ) {
+% my $value = $href->{$field}{'select_options'}{$key};
+% $html .= qq!<OPTION VALUE="$key"!.
+% ( $plandata{$field} =~ /(^|, *)$value *(,|$)/
+% ? ' SELECTED'
+% : ''
+% ).
+% '>'. $value;
+% }
+%
+% } else {
+% $html .= '<font color="#ff0000">warning: '.
+% "don't know how to retreive options for $field select field".
+% '</font>';
+% }
+% $html .= '</SELECT>';
+% }
+%
+% $html .= '</TD></TR>';
+% }
+% $html .= '</TABLE>';
+%
+% $html .= '<INPUT TYPE="hidden" NAME="plandata" VALUE="'.
+% join(',', keys %{ $href } ). '">'.
+% '<BR><BR>';
+%
+% $html .= '<INPUT TYPE="submit" VALUE="'.
+% ( $action eq 'Custom'
+% ? 'Customize package'
+% : ( $hashref->{pkgpart} ? "Apply changes" : "Add package" )
+% ).
+% '" onClick="fchanged(this)">';
+%
+% $html;
+%
+% },
+%);
+%
+%
+
+
+<BR><BR>Price plan <% $widget->html %>
+
+<% include('/elements/footer.html') %>
+<%init>
+
+if ( $cgi->param('clone') && $cgi->param('clone') =~ /^(\d+)$/ ) {
+ $cgi->param('clone', $1);
+} else {
+ $cgi->param('clone', '');
+}
+if ( $cgi->param('pkgnum') && $cgi->param('pkgnum') =~ /^(\d+)$/ ) {
+ $cgi->param('pkgnum', $1);
+} else {
+ $cgi->param('pkgnum', '');
+}
+
+my $curuser = $FS::CurrentUser::CurrentUser;
+
+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') );
+
+my ($query) = $cgi->keywords;
+
+my $conf = new FS::Conf;
+my $part_pkg = '';
+my @agent_type = ();
+my @all_agent_types = map {$_->typenum} qsearch('agent_type',{});
+if ( $cgi->param('error') ) {
+ $part_pkg = new FS::part_pkg ( {
+ map { $_, scalar($cgi->param($_)) } fields('part_pkg')
+ } );
+ (@agent_type) = $cgi->param('agent_type');
+}
+
+my $action = '';
+my $clone_part_pkg = '';
+my $pkgpart = '';
+if ( $cgi->param('clone') ) {
+ $pkgpart = $cgi->param('clone');
+ #$action = 'Custom Pricing';
+ $action = 'Custom';
+ $clone_part_pkg= qsearchs('part_pkg', { 'pkgpart' => $cgi->param('clone') } );
+ $part_pkg ||= $clone_part_pkg->clone;
+ $part_pkg->disabled('Y') unless $cgi->param('error');
+} elsif ( $query && $query =~ /^(\d+)$/ ) {
+ (@agent_type) = map {$_->typenum} qsearch('type_pkgs',{'pkgpart'=>$1})
+ unless $part_pkg;
+ $part_pkg ||= qsearchs('part_pkg',{'pkgpart'=>$1});
+ $pkgpart = $part_pkg->pkgpart;
+} else {
+ unless ( $part_pkg ) {
+ $part_pkg = new FS::part_pkg {};
+ $part_pkg->plan('flat');
+ @agent_type = @all_agent_types if $conf->exists('agent_defaultpkg');
+
+ }
+}
+unless ( $part_pkg->plan ) { #backwards-compat
+ $part_pkg->plan('flat');
+ $part_pkg->plandata("setup_fee=". $part_pkg->setup. "\n".
+ "recur_fee=". $part_pkg->recur. "\n");
+}
+$action ||= $part_pkg->pkgpart ? 'Edit' : 'Add';
+my $hashref = $part_pkg->hashref;
+
+</%init>
diff --git a/httemplate/edit/part_pkg_taxclass.html b/httemplate/edit/part_pkg_taxclass.html
new file mode 100644
index 000000000..e76705722
--- /dev/null
+++ b/httemplate/edit/part_pkg_taxclass.html
@@ -0,0 +1,32 @@
+<% include('/elements/header.html', "$action taxclass") %>
+
+<% include('/elements/error.html') %>
+
+<FORM ACTION="<% $p1 %>process/part_pkg_taxclass.html" METHOD=POST>
+
+<INPUT TYPE="hidden" NAME="taxclassnum" VALUE="">
+
+Tax class <INPUT TYPE="text" NAME="taxclass" VALUE="<% $taxclass |h %>">
+
+<BR><BR>
+<INPUT TYPE="submit" VALUE="<% $action %> taxclass">
+
+</FORM>
+
+<% include('/elements/footer.html') %>
+
+<%init>
+
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('Configuration');
+
+my $taxclass = '';
+if ( $cgi->param('error') ) {
+ $taxclass = $cgi->param('taxclass');
+}
+
+my $action = 'Add';
+
+my $p1 = popurl(1);
+
+</%init>
diff --git a/httemplate/edit/part_referral.html b/httemplate/edit/part_referral.html
new file mode 100755
index 000000000..daf8773f0
--- /dev/null
+++ b/httemplate/edit/part_referral.html
@@ -0,0 +1,19 @@
+<% include( 'elements/edit.html',
+ 'name' => 'Advertising source',
+ 'table' => 'part_referral',
+ 'fields' => [ 'referral',
+ { field=>'agentnum', type=>'select-agent', },
+ ],
+ 'labels' => { 'referral' => 'Advertising source',
+ 'agentnum' => 'Agent',
+ },
+ '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/part_svc.cgi b/httemplate/edit/part_svc.cgi
new file mode 100755
index 000000000..4b8a24080
--- /dev/null
+++ b/httemplate/edit/part_svc.cgi
@@ -0,0 +1,360 @@
+<% include('/elements/header.html', "$action Service Definition",
+ menubar('View all service definitions' => "${p}browse/part_svc.cgi"),
+ #" onLoad=\"visualize()\""
+ )
+%>
+
+<FORM NAME="dummy">
+
+ Service Part #<% $part_svc->svcpart ? $part_svc->svcpart : "(NEW)" %>
+<BR><BR>
+Service <INPUT TYPE="text" NAME="svc" VALUE="<% $hashref->{svc} %>"><BR>
+Disable new orders <INPUT TYPE="checkbox" NAME="disabled" VALUE="Y"<% $hashref->{disabled} eq 'Y' ? ' CHECKED' : '' %>><BR>
+<INPUT TYPE="hidden" NAME="svcpart" VALUE="<% $hashref->{svcpart} %>">
+<BR>
+Service definitions are the templates for items you offer to your customers.
+<UL><LI>svc_acct - Accounts - anything with a username (Mailboxes, PPP accounts, shell accounts, RADIUS entries for broadband, etc.)
+ <LI>svc_domain - Domains
+ <LI>svc_forward - mail forwarding
+ <LI>svc_www - Virtual domain website
+ <LI>svc_broadband - Broadband/High-speed Internet service (always-on)
+ <LI>svc_phone - Customer phone numbers
+ <LI>svc_external - Externally-tracked service
+<!-- <LI>svc_charge - One-time charges (Partially unimplemented)
+ <LI>svc_wo - Work orders (Partially unimplemented)
+-->
+</UL>
+For the selected table, you can give fields default or fixed (unchangable)
+values, or select an inventory class to manually or automatically fill in
+that field.
+<BR><BR>
+
+% #YUCK. false laziness w/part_svc.pm. go away virtual fields, please
+% my %vfields;
+% foreach my $svcdb ( FS::part_svc->svc_tables() ) {
+% eval "use FS::$svcdb;";
+% my $self = "FS::$svcdb"->new;
+% $vfields{$svcdb} = {};
+% foreach my $field ($self->virtual_fields) { # svc_Common::virtual_fields with a null svcpart returns all of them
+% my $pvf = $self->pvf($field);
+% $vfields{$svcdb}->{$field} = $pvf;
+% #warn "\$vfields{$svcdb}->{$field} = $pvf";
+% } #next $field
+% } #next $svcdb
+%
+% #code duplication w/ edit/part_svc.cgi, should move this hash to part_svc.pm
+% # and generalize the subs
+% # condition sub is tested to see whether to disable display of this choice
+% # params: ( $def, $layer, $field ) (see SUB below)
+% my $inv_sub = sub {
+% $_[0]->{disable_inventory}
+% || $_[0]->{'type'} ne 'text'
+% };
+% tie my %flag, 'Tie::IxHash',
+% '' => { 'desc' => 'No default', },
+% 'D' => { 'desc' => 'Default',
+% 'condition' =>
+% sub { $_[0]->{disable_default} },
+% },
+% 'F' => { 'desc' => 'Fixed (unchangeable)',
+% 'condition' =>
+% sub { $_[0]->{disable_fixed} },
+% },
+% 'S' => { 'desc' => 'Selectable Choice',
+% 'condition' =>
+% sub { !ref($_[0]) || $_[0]->{disable_select} },
+% },
+%# need to template-ize httemplate/edit/svc_* first
+%# 'M' => { 'desc' => 'Manual selection from inventory',
+%# 'condition' => $inv_sub,
+%# },
+% 'A' => { 'desc' => 'Automatically fill in from inventory',
+% 'condition' => $inv_sub,
+% },
+% 'X' => { 'desc' => 'Excluded',
+% 'condition' =>
+% sub { ! $vfields{$_[1]}->{$_[2]} },
+%
+% },
+% ;
+%
+% my @dbs = $hashref->{svcdb}
+% ? ( $hashref->{svcdb} )
+% : FS::part_svc->svc_tables();
+%
+% tie my %svcdb, 'Tie::IxHash', map { $_=>$_ } grep dbdef->table($_), @dbs;
+% my $widget = new HTML::Widgets::SelectLayers(
+% #'selected_layer' => $p_svcdb,
+% 'selected_layer' => $hashref->{svcdb} || 'svc_acct',
+% 'options' => \%svcdb,
+% 'form_name' => 'dummy',
+% #'form_action' => 'process/part_svc.cgi',
+% 'form_action' => 'part_svc.cgi', #self
+% 'form_text' => [ qw( svc svcpart ) ],
+% 'form_checkbox' => [ 'disabled' ],
+% 'layer_callback' => sub {
+% my $layer = shift;
+%
+% my $html = qq!<INPUT TYPE="hidden" NAME="svcdb" VALUE="$layer">!;
+%
+% my $columns = 3;
+% my $count = 0;
+% my @part_export =
+% map { qsearch( 'part_export', {exporttype => $_ } ) }
+% keys %{FS::part_export::export_info($layer)};
+% $html .= '<BR><BR>'. table().
+% "<TR><TH COLSPAN=$columns>Exports</TH></TR><TR>";
+% foreach my $part_export ( @part_export ) {
+% $html .= '<TD><INPUT TYPE="checkbox"'.
+% ' NAME="exportnum'. $part_export->exportnum. '" VALUE="1" ';
+% $html .= 'CHECKED'
+% if ( $clone || $part_svc->svcpart ) #null svcpart search causing error
+% && qsearchs( 'export_svc', {
+% exportnum => $part_export->exportnum,
+% svcpart => $clone || $part_svc->svcpart });
+% $html .= '>'. $part_export->exportnum. ': '. $part_export->exporttype.
+% ' to '. $part_export->machine. '</TD>';
+% $count++;
+% $html .= '</TR><TR>' unless $count % $columns;
+% }
+% $html .= '</TR></TABLE><BR><BR>';
+%
+% $html .= include('/elements/table-grid.html', 'cellpadding' => 4 ).
+% '<TR>'.
+% '<TH CLASS="grid" BGCOLOR="#cccccc">Field</TH>'.
+% '<TH CLASS="grid" BGCOLOR="#cccccc" COLSPAN=2>Modifier</TH>'.
+% '</TR>';
+%
+% my $bgcolor1 = '#eeeeee';
+% my $bgcolor2 = '#ffffff';
+% my $bgcolor;
+%
+% #yucky kludge
+% my @fields = defined( dbdef->table($layer) )
+% ? grep { $_ ne 'svcnum' } fields($layer)
+% : ();
+% push @fields, 'usergroup' if $layer eq 'svc_acct'; #kludge
+% $part_svc->svcpart($clone) if $clone; #haha, undone below
+%
+%
+% foreach my $field (@fields) {
+%
+% #my $def = $defs{$layer}{$field};
+% my $def = FS::part_svc->svc_table_fields($layer)->{$field};
+% my $label = $def->{'def_label'} || $def->{'label'};
+% my $formatter = $def->{'format'} || sub { shift };
+% my $part_svc_column = $part_svc->part_svc_column($field);
+% my $value = &$formatter($part_svc_column->columnvalue);
+% my $flag = $part_svc_column->columnflag;
+%
+% if ( $bgcolor eq $bgcolor1 ) {
+% $bgcolor = $bgcolor2;
+% } else {
+% $bgcolor = $bgcolor1;
+% }
+%
+% $html .= qq!<TR><TD CLASS="grid" BGCOLOR="$bgcolor" ALIGN="right">!.
+% ( $label || $field ).
+% "</TD>";
+% $flag = '' if $def->{type} eq 'disabled';
+%
+% $html .= qq!<TD CLASS="grid" BGCOLOR="$bgcolor">!;
+%
+% if ( $def->{type} eq 'disabled' ) {
+%
+% $html .= 'No default';
+%
+% } else {
+%
+% $html .= qq!<SELECT NAME="${layer}__${field}_flag"!.
+% qq! onChange="${layer}__${field}_flag_changed(this)">!;
+%
+% foreach my $f ( keys %flag ) {
+%
+% #here is where the SUB from above is called, to skip some choices
+% next if $flag{$f}->{condition}
+% && &{ $flag{$f}->{condition} }( $def, $layer, $field );
+%
+% $html .= qq!<OPTION VALUE="$f"!.
+% ' SELECTED'x($flag eq $f ).
+% '>'. $flag{$f}->{desc};
+%
+% }
+%
+% $html .= '</SELECT>';
+%
+% $html .= join("\n",
+% '<SCRIPT>',
+% " function ${layer}__${field}_flag_changed(what) {",
+% ' var f = what.options[what.selectedIndex].value;',
+% ' if ( f == "" || f == "X" ) { //disable',
+% " what.form.${layer}__${field}.disabled = true;".
+% " what.form.${layer}__${field}.style.backgroundColor = '#dddddd';".
+% " if ( what.form.${layer}__${field}_classnum ) {".
+% " what.form.${layer}__${field}_classnum.disabled = true;".
+% " what.form.${layer}__${field}_classnum.style.backgroundColor = '#dddddd';".
+% " }".
+% ' } else if ( f == "D" || f == "F" || f =="S" ) { //enable, text box',
+% " what.form.${layer}__${field}.disabled = false;".
+% " what.form.${layer}__${field}.style.backgroundColor = '#ffffff';".
+% " if ( f == 'S' || '${field}' == 'usergroup' ) {". # kludge
+% " what.form.${layer}__${field}.multiple = true;".
+% " } else {".
+% " what.form.${layer}__${field}.multiple = false;".
+% " }".
+% " what.form.${layer}__${field}.style.display = '';".
+% " if ( what.form.${layer}__${field}_classnum ) {".
+% " what.form.${layer}__${field}_classnum.disabled = false;".
+% " what.form.${layer}__${field}_classnum.style.backgroundColor = '#ffffff';".
+% " what.form.${layer}__${field}_classnum.style.display = 'none';".
+% " }".
+% ' } else if ( f == "M" || f == "A" ) { //enable, inventory',
+% " what.form.${layer}__${field}.disabled = false;".
+% " what.form.${layer}__${field}.style.backgroundColor = '#ffffff';".
+% " what.form.${layer}__${field}.style.display = 'none';".
+% " if ( what.form.${layer}__${field}_classnum ) {".
+% " what.form.${layer}__${field}_classnum.disabled = false;".
+% " what.form.${layer}__${field}_classnum.style.backgroundColor = '#ffffff';".
+% " what.form.${layer}__${field}_classnum.style.display = '';".
+% " }".
+% ' }',
+% ' }',
+% '</SCRIPT>',
+% );
+%
+% }
+%
+% $html .= qq!</TD><TD CLASS="grid" BGCOLOR="$bgcolor">!;
+%
+% my $disabled = $flag ? ''
+% : 'DISABLED STYLE="background-color: #dddddd"';
+%
+% if ( !$def->{type} || $def->{type} eq 'text' ) {
+%
+% my $nodisplay = ' STYLE="display:none"';
+% my $is_inv = ( $flag =~ /^[MA]$/ );
+%
+% $html .=
+% qq!<INPUT TYPE="text" NAME="${layer}__${field}" VALUE="$value" !.
+% $disabled.
+% ( $is_inv ? $nodisplay : $disabled ).
+% '>';
+%
+% $html .= include('/elements/select-table.html',
+% 'element_name' => "${layer}__${field}_classnum",
+% 'element_etc' => ( $is_inv
+% ? $disabled
+% : $nodisplay
+% ),
+% 'table' => 'inventory_class',
+% 'name_col' => 'classname',
+% 'value' => $value,
+% 'empty_label' => 'Select inventory class',
+% );
+%
+% } elsif ( $def->{type} eq 'select' ) {
+%
+% $html .= qq!<SELECT NAME="${layer}__${field}" $disabled!;
+% $html .= ' MULTIPLE' if $flag eq 'S';
+% $html .= '>';
+% $html .= '<OPTION> </OPTION>' unless $value;
+% if ( $def->{select_table} ) {
+% foreach my $record ( qsearch( $def->{select_table}, {} ) ) {
+% my $rvalue = $record->getfield($def->{select_key});
+% $html .= qq!<OPTION VALUE="$rvalue"!.
+% (grep(/^$rvalue$/, split(',',$value)) ? ' SELECTED>' : '>' ).
+% $record->getfield($def->{select_label}). '</OPTION>';
+% } #next $record
+% } else { # select_list
+% foreach my $item ( @{$def->{select_list}} ) {
+% $html .= qq!<OPTION VALUE="$item"!.
+% (grep(/^$item$/, split(',',$value)) ? ' SELECTED>' : '>' ).
+% $item. '</OPTION>';
+% } #next $item
+% } #endif
+% $html .= '</SELECT>';
+%
+% } elsif ( $def->{type} eq 'radius_usergroup_selector' ) {
+%
+% #XXX disable the RADIUS usergroup selector? ugh it sure does need
+% #an overhaul, people have dum group problems because of it
+%
+% $html .= FS::svc_acct::radius_usergroup_selector(
+% [ split(',', $value) ], "${layer}__${field}" );
+%
+% } elsif ( $def->{type} eq 'disabled' ) {
+%
+% $html .=
+% qq!<INPUT TYPE="hidden" NAME="${layer}__${field}" VALUE="">!;
+%
+% } else {
+%
+% $html .= '<font color="#ff0000">unknown type'. $def->{type};
+%
+% }
+%
+% $html .= "</TD></TR>\n";
+%
+% } #foreach my $field (@fields) {
+%
+% $part_svc->svcpart('') if $clone; #undone
+% $html .= "</TABLE>";
+%
+% $html .= include('/elements/progress-init.html',
+% $layer, #form name
+% [ qw(svc svcpart disabled exportnum), @fields ],
+% 'process/part_svc.cgi',
+% $p.'browse/part_svc.cgi',
+% $layer,
+% );
+% $html .= '<BR><INPUT NAME="submit" TYPE="button" VALUE="'.
+% ($hashref->{svcpart} ? 'Apply changes' : 'Add service'). '" '.
+% ' onClick="document.'. "$layer.submit.disabled=true; ".
+% "fixup(document.$layer); $layer". 'process();">';
+%
+% #$html .= '<BR><INPUT TYPE="submit" VALUE="'.
+% # ($hashref->{svcpart} ? 'Apply changes' : 'Add service'). '">';
+%
+% $html;
+%
+% },
+% );
+%
+%
+
+Table <% $widget->html %>
+
+<% include('/elements/footer.html') %>
+
+<%init>
+
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('Configuration');
+
+my $part_svc;
+my $clone = '';
+if ( $cgi->param('clone') && $cgi->param('clone') =~ /^(\d+)$/ ) {#clone
+ #$cgi->param('clone') =~ /^(\d+)$/ or die "malformed query: $query";
+ $part_svc = qsearchs('part_svc', { 'svcpart'=>$1 } )
+ or die "unknown svcpart: $1";
+ $clone = $part_svc->svcpart;
+ $part_svc->svcpart('');
+} elsif ( $cgi->keywords ) { #edit
+ my($query) = $cgi->keywords;
+ $query =~ /^(\d+)$/ or die "malformed query: $query";
+ $part_svc=qsearchs('part_svc', { 'svcpart'=>$1 } )
+ or die "unknown svcpart: $1";
+} else { #adding
+ $part_svc = new FS::part_svc {};
+}
+
+my $action = $part_svc->svcpart ? 'Edit' : 'Add';
+my $hashref = $part_svc->hashref;
+# my $p_svcdb = $part_svc->svcdb || 'svc_acct';
+
+
+
+</%init>
+
+
+
diff --git a/httemplate/edit/part_virtual_field.cgi b/httemplate/edit/part_virtual_field.cgi
new file mode 100644
index 000000000..04ba9b0c0
--- /dev/null
+++ b/httemplate/edit/part_virtual_field.cgi
@@ -0,0 +1,104 @@
+<% include('/elements/header.html', "$action Virtual Field Definition") %>
+
+<% include('/elements/error.html') %>
+
+<FORM ACTION="<%$p1%>process/generic.cgi" METHOD="POST">
+
+<INPUT TYPE="hidden" NAME="table" VALUE="part_virtual_field">
+<INPUT TYPE="hidden" NAME="redirect_ok"
+ VALUE="<%popurl(2)%>browse/part_virtual_field.cgi">
+<INPUT TYPE="hidden" NAME="vfieldpart" VALUE="<%
+ $vfieldpart%>">
+Field #<B><%$vfieldpart or "(NEW)"%></B><BR><BR>
+
+<%ntable("#cccccc",2)%>
+ <TR>
+ <TD ALIGN="right">Name</TD>
+ <TD><INPUT TYPE="text" NAME="name" MAXLENGTH=32 VALUE="<%
+ $part_virtual_field->name%>"></TD>
+ </TR>
+ <TR>
+ <TD ALIGN="right">Table</TD>
+ <TD>
+% if ($action eq 'Add') {
+
+ <SELECT SIZE=1 NAME="dbtable">
+%
+% my $dbdef = dbdef; # ick
+% #foreach my $dbtable (sort { $a cmp $b } $dbdef->tables) {
+% foreach my $dbtable (qw( svc_broadband router )) {
+% if ($dbtable !~ /^h_/
+% and $dbdef->table($dbtable)->primary_key) {
+
+ <OPTION VALUE="<%$dbtable%>"><%$dbtable%></OPTION>
+%
+% }
+% }
+%
+</SELECT>
+%
+% } else { # Edit
+%
+<%$part_virtual_field->dbtable%>
+ <INPUT TYPE="hidden" NAME="dbtable" VALUE="<%$part_virtual_field->dbtable%>">
+% }
+
+ </TD>
+ <TR>
+ <TD ALIGN="right">Label</TD>
+ <TD><INPUT TYPE="text" NAME="label" MAXLENGTH="80" VALUE="<%
+ $part_virtual_field->label%>"></TD>
+ </TR>
+ <TR>
+ <TD ALIGN="right">Length</TD>
+ <TD><INPUT TYPE="text" NAME="length" MAXLENGTH=4 VALUE="<%
+ $part_virtual_field->length%>"></TD>
+ </TR>
+ <TR>
+ <TD ALIGN="right">Check</TD>
+ <TD><TEXTAREA COLS="20" ROWS="4" NAME="check_block"><%
+ $part_virtual_field->check_block%></TEXTAREA></TD>
+ </TR>
+ <TR>
+ <TD ALIGN="right">List source</TD>
+ <TD><TEXTAREA COLS="20" ROWS="4" NAME="list_source"><%
+ $part_virtual_field->list_source%></TEXTAREA></TD>
+ </TR>
+</TABLE><BR><INPUT TYPE="submit" VALUE="Submit">
+
+</FORM>
+
+<BR>
+<FONT SIZE=-2>If you don't understand what <I>check_block</I> and
+<I>list_source</I> mean, <B>LEAVE THEM BLANK</B>. We mean it.</FONT>
+
+<% include('/elements/footer.html') %>
+
+<%init>
+
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('Configuration');
+
+my ($vfieldpart, $part_virtual_field);
+
+if ( $cgi->param('error') ) {
+ $part_virtual_field = new FS::part_virtual_field ( {
+ map { $_, scalar($cgi->param($_)) } fields('part_virtual_field')});
+ $vfieldpart = $part_virtual_field->vfieldpart;
+} else {
+ my($query) = $cgi->keywords;
+ if ( $query =~ /^(\d+)$/ ) { #editing
+ $vfieldpart=$1;
+ $part_virtual_field=qsearchs('part_virtual_field',
+ {'vfieldpart' => $vfieldpart})
+ or die "Unknown vfieldpart!";
+
+ } else { #adding
+ $part_virtual_field = new FS::part_virtual_field({});
+ }
+}
+my $action = $part_virtual_field->vfieldpart ? 'Edit' : 'Add';
+
+my $p1 = popurl(1);
+
+</%init>
diff --git a/httemplate/edit/payment_gateway.html b/httemplate/edit/payment_gateway.html
new file mode 100644
index 000000000..e3893cf49
--- /dev/null
+++ b/httemplate/edit/payment_gateway.html
@@ -0,0 +1,132 @@
+<% include("/elements/header.html","$action Payment gateway", menubar(
+ 'View all payment gateways' => $p. 'browse/payment_gateway.html',
+)) %>
+
+<% include('/elements/error.html') %>
+
+<FORM ACTION="<%popurl(1)%>process/payment_gateway.html" METHOD=POST>
+<INPUT TYPE="hidden" NAME="gatewaynum" VALUE="<% $payment_gateway->gatewaynum %>">
+Gateway #<% $payment_gateway->gatewaynum || "(NEW)" %>
+
+<% ntable('#cccccc', 2, '') %>
+
+<TR>
+ <TH ALIGN="right">Gateway: </TH>
+ <TD>
+% if ( $payment_gateway->gatewaynum ) {
+
+
+ <% $payment_gateway->gateway_module %>
+ <INPUT TYPE="hidden" NAME="gateway_module" VALUE="<% $payment_gateway->gateway_module %>">
+% } else {
+
+
+ <SELECT NAME="gateway_module" SIZE=1>
+% foreach my $module ( qw(
+% 2CheckOut
+% AuthorizeNet
+% BankOfAmerica
+% Beanstream
+% Capstone
+% Cardstream
+% CashCow
+% CyberSource
+% eSec
+% eSelectPlus
+% Exact
+% iAuthorizer
+% IPaymentTPG
+% Jettis
+% LinkPoint
+% MerchantCommerce
+% Network1Financial
+% OCV
+% OpenECHO
+% PayConnect
+% PayflowPro
+% PaymentsGateway
+% PXPost
+% SecureHostingUPG
+% Skipjack
+% StGeorge
+% SurePay
+% TCLink
+% TransactionCentral
+% TransFirsteLink
+% VirtualNet
+% ) ) {
+%
+
+ <OPTION VALUE="<% $module %>"><% $module %>
+% }
+
+ </SELECT>
+% }
+
+
+ </TD>
+</TR>
+
+<TR>
+ <TH ALIGN="right">Username: </TH>
+ <TD><INPUT TYPE="text" NAME="gateway_username" VALUE="<% $payment_gateway->gateway_username %>"></TD>
+</TR>
+
+<TR>
+ <TH ALIGN="right">Password: </TH>
+ <TD><INPUT TYPE="text" NAME="gateway_password" VALUE="<% $payment_gateway->gateway_password %>"></TD>
+</TR>
+
+<TR>
+ <TH ALIGN="right">Action: </TH>
+ <TD>
+ <SELECT NAME="gateway_action" SIZE=1>
+% foreach my $action (
+% 'Normal Authorization',
+% 'Authorization Only',
+% 'Authorization Only, Post Authorization',
+% ) {
+%
+
+ <OPTION VALUE="<% $action %>"<% $action eq $payment_gateway->gateway_action ? ' SELECTED' : '' %>><% $action %>
+% }
+
+ </SELECT>
+ </TD>
+</TR>
+
+<TR>
+ <TH ALIGN="right">Options: (Name/Value pairs, one element per line)</TH>
+ <TD>
+ <TEXTAREA ROWS="5" NAME="gateway_options"><% join("\r", $payment_gateway->options ) %></TEXTAREA>
+ </TD>
+</TR>
+
+</TABLE>
+
+<BR><INPUT TYPE="submit" VALUE="<% $payment_gateway->gatewaynum ? "Apply changes" : "Add gateway" %>">
+ </FORM>
+
+<% include('/elements/footer.html') %>
+
+<%init>
+
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('Configuration');
+
+my $payment_gateway;
+if ( $cgi->param('error') ) {
+ $payment_gateway = new FS::payment_gateway ( {
+ map { $_, scalar($cgi->param($_)) } fields('payment_gateway')
+ } );
+} elsif ( $cgi->keywords ) {
+ my($query) = $cgi->keywords;
+ $query =~ /^(\d+)$/;
+ $payment_gateway = qsearchs( 'payment_gateway', { 'gatewaynum' => $1 } );
+} else { #adding
+ $payment_gateway = new FS::payment_gateway {};
+}
+my $action = $payment_gateway->gatewaynum ? 'Edit' : 'Add';
+#my $hashref = $payment_gateway->hashref;
+
+</%init>
diff --git a/httemplate/edit/pkg_class.html b/httemplate/edit/pkg_class.html
new file mode 100644
index 000000000..eddbfc16e
--- /dev/null
+++ b/httemplate/edit/pkg_class.html
@@ -0,0 +1,22 @@
+<% include( 'elements/edit.html',
+ 'name' => 'Package Class',
+ 'table' => 'pkg_class',
+ 'fields' => [
+ 'classname',
+ { field=>'disabled', type=>'checkbox', value=>'Y', },
+ ],
+ 'labels' => {
+ 'classnum' => 'Class number',
+ 'classname' => 'Class name',
+ 'disabled' => 'Disable class',
+ },
+ 'viewall_dir' => 'browse',
+ )
+
+%>
+<%init>
+
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('Configuration');
+
+</%init>
diff --git a/httemplate/edit/prepay_credit.cgi b/httemplate/edit/prepay_credit.cgi
new file mode 100644
index 000000000..9e1c30ba6
--- /dev/null
+++ b/httemplate/edit/prepay_credit.cgi
@@ -0,0 +1,110 @@
+<% include("/elements/header.html",'Generate prepaid cards'. ($agent ? ' for '. $agent->agent : '') ) %>
+
+<% include('/elements/error.html') %>
+
+<FORM ACTION="<%popurl(1)%>process/prepay_credit.cgi" METHOD="POST" NAME="OneTrueForm" onSubmit="document.OneTrueForm.submit.disabled=true">
+
+Generate
+<INPUT TYPE="text" NAME="num" VALUE="<% $cgi->param('num') || '(quantity)' |h %>" SIZE=10 MAXLENGTH=10 onFocus="if ( this.value == '(quantity)' ) { this.value = ''; }">
+
+<SELECT NAME="type">
+% foreach (qw(alpha alphanumeric numeric)) {
+ <OPTION<% $cgi->param('type') eq $_ ? ' SELECTED' : '' %>><% $_ %>
+% }
+</SELECT>
+
+prepaid cards
+
+<BR>for <SELECT NAME="agentnum"><OPTION>(any agent)
+% foreach my $opt_agent ( qsearch('agent', { 'disabled' => '' } ) ) {
+
+ <OPTION VALUE="<% $opt_agent->agentnum %>"<% $opt_agent->agentnum == $agentnum ? ' SELECTED' : '' %>><% $opt_agent->agent %>
+% }
+
+</SELECT>
+
+<TABLE>
+<TR><TD>Value:
+$<INPUT TYPE="text" NAME="amount" SIZE=8 MAXLENGTH=7 VALUE="<% $cgi->param('amount') |h %>">
+</TD>
+<TD>and/or
+<INPUT TYPE="text" NAME="seconds" SIZE=6 MAXLENGTH=5 VALUE="<% $cgi->param('seconds') |h %>">
+<SELECT NAME="multiplier">
+% foreach my $multiplier ( keys %multiplier ) {
+
+ <OPTION VALUE="<% $multiplier %>"<% $cgi->param('multiplier') eq $multiplier ? ' SELECTED' : '' %>><% $multiplier{$multiplier} %>
+% }
+
+</SELECT>
+</TD></TR>
+<TR><TD></TD>
+<TD>and/or
+<INPUT TYPE="text" NAME="upbytes" SIZE=6 MAXLENGTH=5 VALUE="<% $cgi->param('upbytes') |h %>">
+<SELECT NAME="upmultiplier">
+% foreach my $multiplier ( keys %bytemultiplier ) {
+
+ <OPTION VALUE="<% $multiplier %>"<% $cgi->param('upmultiplier') eq $multiplier ? ' SELECTED' : '' %>><% $bytemultiplier{$multiplier} %>
+% }
+
+</SELECT> upload
+</TD></TR>
+<TR><TD></TD>
+<TD>and/or
+<INPUT TYPE="text" NAME="downbytes" SIZE=6 MAXLENGTH=5 VALUE="<% $cgi->param('downbytes') |h %>">
+<SELECT NAME="downmultiplier">
+% foreach my $multiplier ( keys %bytemultiplier ) {
+
+ <OPTION VALUE="<% $multiplier %>"<% $cgi->param('downmultiplier') eq $multiplier ? ' SELECTED' : '' %>><% $bytemultiplier{$multiplier} %>
+% }
+
+</SELECT> download
+</TD></TR>
+<TR><TD></TD>
+<TD>and/or
+<INPUT TYPE="text" NAME="totalbytes" SIZE=6 MAXLENGTH=5 VALUE="<% $cgi->param('totalbytes') |h %>">
+<SELECT NAME="totalmultiplier">
+% foreach my $multiplier ( keys %bytemultiplier ) {
+
+ <OPTION VALUE="<% $multiplier %>"<% $cgi->param('totalmultiplier') eq $multiplier ? ' SELECTED' : '' %>><% $bytemultiplier{$multiplier} %>
+% }
+
+</SELECT> total transfer
+</TD></TR>
+</TABLE>
+<BR><BR>
+<INPUT TYPE="submit" NAME="submit" VALUE="Generate" onSubmit="this.disabled = true">
+
+</FORM>
+
+<% include('/elements/footer.html') %>
+
+<%init>
+
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('Configuration');
+
+my $agent = '';
+my $agentnum = '';
+if ( $cgi->param('agentnum') =~ /^(\d+)$/ ) {
+ $agent = qsearchs('agent', { 'agentnum' => $agentnum=$1 } );
+}
+
+tie my %multiplier, 'Tie::IxHash',
+ 1 => 'seconds',
+ 60 => 'minutes',
+ 3600 => 'hours',
+;
+
+tie my %bytemultiplier, 'Tie::IxHash',
+ 1 => 'bytes',
+ 1000 => 'Kbytes',
+ 1000000 => 'Mbytes',
+ 1000000000 => 'Gbytes',
+;
+
+$cgi->param('multiplier', '60') unless $cgi->param('multiplier');
+$cgi->param('upmultiplier', '1000000') unless $cgi->param('upmultiplier');
+$cgi->param('downmultiplier', '1000000') unless $cgi->param('downmultiplier');
+$cgi->param('totalmultiplier','1000000') unless $cgi->param('totalmultiplier');
+
+</%init>
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>
diff --git a/httemplate/edit/quick-charge.html b/httemplate/edit/quick-charge.html
new file mode 100644
index 000000000..95ec70c54
--- /dev/null
+++ b/httemplate/edit/quick-charge.html
@@ -0,0 +1,182 @@
+<% include("/elements/header-popup.html", 'One-time charge entry', '',
+ ( $cgi->param('error') ? '' : 'onload="addRow()"' ),
+ )
+%>
+
+<% include('/elements/error.html') %>
+
+<SCRIPT TYPE="text/javascript">
+
+function enable_quick_charge () {
+ if ( document.QuickChargeForm.amount.value
+ && document.QuickChargeForm.pkg.value ) {
+ document.QuickChargeForm.submit.disabled = false;
+ } else {
+ document.QuickChargeForm.submit.disabled = true;
+ }
+}
+
+function enable_quick_charge_desc () {
+ if ( document.QuickChargeForm.amount.value && document.QuickChargeForm.pkg.value ) {
+ document.QuickChargeForm.submit.disabled = false;
+ } else {
+ document.QuickChargeForm.submit.disabled = true;
+ }
+}
+
+function enable_quick_charge_amount () {
+ if ( document.QuickChargeForm.amount.value && document.QuickChargeForm.pkg.value ) {
+ document.QuickChargeForm.submit.disabled = false;
+ } else {
+ document.QuickChargeForm.submit.disabled = true;
+ }
+}
+
+function validate_quick_charge () {
+ var pkg = document.QuickChargeForm.pkg.value;
+ var pkg_regex = /^([\w \!\@\#\$\%\&\(\)\-\+\;\:\'\"\,\.\?\/\=\[\]]*)$/ ;
+ var amount = document.QuickChargeForm.amount.value;
+ var amount_regex = /^\s*\$?\s*(\d+(\.\d{1,2})?)\s*$/ ;
+ var rval = true;
+
+ if ( ! amount_regex.test(amount) ) {
+ alert('Illegal amount - enter an amount to charge, for example, "5" or "43" or "21.46".');
+ return false;
+ }
+ if ( String(pkg).length < 1 ) {
+ rval = false;
+ }
+ if ( ! pkg_regex.test(pkg) ) {
+ rval = false;
+ }
+ var i=0;
+ for (i=0; i < rownum; i++) {
+ if (! eval('pkg_regex.test(document.QuickChargeForm.description' + i + '.value)')){
+ rval = false;
+ break;
+ }
+ }
+ if (rval == true) {
+ return true;
+ }
+
+ if ( ! pkg ) {
+ alert('Enter a description for the one-time charge');
+ return false;
+ }
+
+ alert('Illegal description - spaces, letters, numbers, and the following punctuation characters are allowed: . , ! ? @ # $ % & ( ) - + ; : ' + "'" + ' " = [ ]' );
+ return false;
+}
+
+</SCRIPT>
+
+<FORM ACTION="process/quick-charge.cgi" NAME="QuickChargeForm" METHOD="POST" onsubmit="document.QuickChargeForm.submit.disabled=true;return validate_quick_charge();">
+
+<INPUT TYPE="hidden" NAME="custnum" VALUE="<% $custnum %>">
+
+<TABLE ID="QuickChargeTable" BGCOLOR="#cccccc" BORDER=0 CELLSPACING=0 STYLE="background-color: #cccccc">
+
+<TR>
+ <TD ALIGN="right">Amount:</TD>
+ <TD>
+ $<INPUT TYPE="text" NAME="amount" SIZE=6 VALUE="<% $amount %>" onChange="enable_quick_charge()" onKeyPress="enable_quick_charge_amount()">
+ </TD>
+<% include('/elements/tr-select-pkg_class.html', '') %>
+<% include('/elements/tr-select-taxclass.html') %>
+</TR>
+ <TD>Description:</TD>
+ <TD>
+ <INPUT TYPE="text" NAME="pkg" SIZE="60" MAXLENGTH="65" VALUE="<% $pkg %>" onChange="enable_quick_charge()" onKeyPress="enable_quick_charge_desc()">
+ </TD>
+</TR>
+<TR>
+ <TD></TD>
+ <TD><FONT SIZE="-1">Optional additional description: </FONT></TD>
+</TR>
+
+% my $row = 0;
+% if ( $cgi->param('error') ) {
+% my $param = $cgi->Vars;
+%
+% for ( $row = 0; exists($param->{"description$row"}); $row++ ) {
+
+ <TR>
+ <TD></TD>
+ <TD>
+ <INPUT TYPE="text" NAME="description<% $row %>" SIZE="60" MAXLENGTH="65" VALUE="<% $param->{"description$row"} |h %>" rownum="<% $row %>" onkeyup = "possiblyAddRow;" >
+ </TD>
+ </TR>
+% }
+% }
+
+
+</TABLE>
+
+<BR>
+<INPUT TYPE="submit" NAME="submit" VALUE="Add one-time charge" <% $cgi->param('error') ? '' :' DISABLED' %>>
+
+</FORM>
+
+
+<SCRIPT TYPE="text/javascript">
+
+ var rownum = <% $row %>;
+
+ function possiblyAddRow() {
+ if ( ( rownum - this.getAttribute('rownum') ) == 1 ) {
+ addRow();
+ }
+ }
+
+ function addRow() {
+
+ var table = document.getElementById('QuickChargeTable');
+ var tablebody = table.getElementsByTagName('tbody').item(0);
+
+ var row = document.createElement('TR');
+
+ var empty_cell = document.createElement('TD');
+ row.appendChild(empty_cell);
+
+ var description_cell = document.createElement('TD');
+
+ var description_input = document.createElement('INPUT');
+ description_input.setAttribute('name', 'description'+rownum);
+ description_input.setAttribute('id', 'description'+rownum);
+ description_input.setAttribute('size', 60);
+ description_input.setAttribute('maxLength', 65);
+ description_input.setAttribute('rownum', rownum);
+ description_input.onkeyup = possiblyAddRow;
+ description_cell.appendChild(description_input);
+
+ row.appendChild(description_cell);
+
+ tablebody.appendChild(row);
+
+ rownum++;
+
+ }
+
+</SCRIPT>
+
+</BODY>
+</HTML>
+<%init>
+
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('One-time charge');
+
+$cgi->param('custnum') =~ /^(\d+)$/ or die 'illegal custnum';
+my $custnum = $1;
+
+my $amount = '';
+if ( $cgi->param('amount') =~ /^\s*\$?\s*(\d+(\.\d{1,2})?)\s*$/ ) {
+ $amount = $1;
+}
+
+$cgi->param('pkg') =~ /^([\w \!\@\#\$\%\&\(\)\-\+\;\:\'\"\,\.\?\/\=\[\]]*)$/
+ or die 'illegal description';
+my $pkg = $1;
+
+</%init>
diff --git a/httemplate/edit/rate.cgi b/httemplate/edit/rate.cgi
new file mode 100644
index 000000000..4c0abfe01
--- /dev/null
+++ b/httemplate/edit/rate.cgi
@@ -0,0 +1,43 @@
+<% include("/elements/header.html","$action Rate plan", menubar(
+ 'View all rate plans' => "${p}browse/rate.cgi",
+ ))
+%>
+
+<% include('/elements/progress-init.html',
+ 'OneTrueForm',
+ [ 'rate', 'min_', 'sec_' ],
+ 'process/rate.cgi',
+ $p.'browse/rate.cgi',
+ )
+%>
+<FORM NAME="OneTrueForm">
+<INPUT TYPE="hidden" NAME="ratenum" VALUE="<% $rate->ratenum %>">
+
+Rate plan
+<INPUT TYPE="text" NAME="ratename" SIZE=32 VALUE="<% $rate->ratename %>">
+<BR><BR>
+
+<INPUT NAME="submit" TYPE="button" VALUE="<%
+ $rate->ratenum ? "Apply changes" : "Add rate plan"
+%>" onClick="document.OneTrueForm.submit.disabled=true; process();">
+
+</FORM>
+
+<% include('/elements/footer.html') %>
+
+<%init>
+
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('Configuration');
+
+my $rate;
+if ( $cgi->keywords ) {
+ my($query) = $cgi->keywords;
+ $query =~ /^(\d+)$/;
+ $rate = qsearchs( 'rate', { 'ratenum' => $1 } );
+} else { #adding
+ $rate = new FS::rate {};
+}
+my $action = $rate->ratenum ? 'Edit' : 'Add';
+
+</%init>
diff --git a/httemplate/edit/rate_detail.html b/httemplate/edit/rate_detail.html
new file mode 100644
index 000000000..b9eaf657e
--- /dev/null
+++ b/httemplate/edit/rate_detail.html
@@ -0,0 +1,59 @@
+<% include('elements/edit.html',
+ 'popup' => 1,
+ 'name' => $name,
+ 'table' => 'rate_detail',
+ 'labels' => { 'ratedetailnum' => 'Rate', #should hide...
+ 'dest_regionname' => 'Region',
+ 'dest_prefixes_short' => 'Prefix(es)',
+ 'min_included' => 'Included minutes',
+ 'min_charge' => 'Charge per minute',
+ 'sec_granularity' => 'Granularity',
+ },
+ 'fields' => [
+ { field=>'ratenum', type=>'hidden', },
+ { field=>'orig_regionnum', type=>'hidden', },
+ { field=>'dest_regionnum', type=>'hidden', },
+ { field=>'dest_regionname', type=>'fixed', },
+ { field=>'dest_prefixes_short', type=>'fixed', },
+ { field=>'min_included', type=>'text', size=>5 },
+ { field=>'min_charge', type=>'money', size=>4 },
+ { field =>'sec_granularity',
+ type =>'select',
+ options => [qw( 1 6 30 60 )],
+ labels => \%granularity,
+ disable_empty => 1,
+ },
+
+ ],
+ )
+%>
+<%once>
+
+tie my %granularity, 'Tie::IxHash',
+ '1', => '1 second',
+ '6' => '6 second',
+ '30' => '30 second', # '1/2 minute',
+ '60' => 'minute',
+;
+
+</%once>
+
+<%init>
+
+my $conf = new FS::Conf;
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('Configuration');
+
+#slightly inefficient, i suppose an edit+error callback would be better
+my $name = 'rate';
+if ( $cgi->keywords =~ /^(\d+)$/
+ || $cgi->param('ratedetailnum') =~ /^(\d+)$/ ) {
+ my $rate_detail = qsearchs('rate_detail', { 'ratedetailnum' => $1 } )
+ or die "unknown ratedetailnum $1";
+ $name =
+ $rate_detail->rate->ratename. ' rate for '. $rate_detail->dest_regionname;
+}
+
+#sec_granularity should default to 60! for new rates when this gets used for em
+
+</%init>
diff --git a/httemplate/edit/rate_region.cgi b/httemplate/edit/rate_region.cgi
new file mode 100644
index 000000000..04f285f37
--- /dev/null
+++ b/httemplate/edit/rate_region.cgi
@@ -0,0 +1,153 @@
+<% include("/elements/header.html","$action Region", menubar(
+ 'View all regions' => "${p}browse/rate_region.html",
+ ))
+%>
+
+<% include('/elements/error.html') %>
+
+<FORM ACTION="<%$p1%>process/rate_region.cgi" METHOD=POST>
+
+<INPUT TYPE="hidden" NAME="regionnum" VALUE="<% $rate_region->regionnum %>">
+
+%# region info
+
+<% ntable('#cccccc') %>
+
+ <TR>
+ <TH ALIGN="right">Region name</TH>
+ <TD><INPUT TYPE="text" NAME="regionname" SIZE=32 VALUE="<% $rate_region->regionname %>"></TR>
+ </TR>
+
+ <TR>
+ <TH ALIGN="right">Country code</TH>
+ <TD><INPUT TYPE="text" NAME="countrycode" SIZE=4 MAXLENGTH=3 VALUE="<% $countrycode %>"></TR>
+ </TR>
+
+ <TR>
+ <TD ALIGN="right">
+ <B>Prefixes</B>
+ <BR><FONT SIZE="-1">(comma-separated)</FONT>
+ </TD>
+ <TD>
+ <TEXTAREA NAME="npa" WRAP=SOFT><% join(', ', map $_->npa, @rate_prefix ) %></TEXTAREA>
+ </TD>
+ </TR>
+
+</TABLE>
+
+%# rate plan info
+
+<BR>
+
+<% include('/elements/table-grid.html') %>
+% my $bgcolor1 = '#eeeeee';
+% my $bgcolor2 = '#ffffff';
+% my $bgcolor = '';
+
+ <TR>
+ <TH CLASS="grid" BGCOLOR="#cccccc">
+ Rate plan
+ </TH>
+ <TH CLASS="grid" BGCOLOR="#cccccc">
+ <FONT SIZE=-1>Included<BR>minutes/calls</FONT>
+ </TH>
+ <TH CLASS="grid" BGCOLOR="#cccccc">
+ <FONT SIZE=-1>Charge per<BR>minute/call</FONT>
+ </TH>
+ <TH CLASS="grid" BGCOLOR="#cccccc">
+ <FONT SIZE=-1>Granularity</FONT>
+ </TH>
+ </TR>
+
+% foreach my $rate ( qsearch('rate', {}) ) {
+%
+% my $n = $rate->ratenum;
+% my $rate_detail = $rate->dest_detail($rate_region)
+% || new FS::rate_region { 'min_included' => 0,
+% 'min_charge' => 0,
+% 'sec_granularity' => '60'
+% };
+%
+% if ( $bgcolor eq $bgcolor1 ) {
+% $bgcolor = $bgcolor2;
+% } else {
+% $bgcolor = $bgcolor1;
+% }
+
+ <TR>
+
+ <TD CLASS="grid" BGCOLOR="<% $bgcolor %>">
+ <A HREF="<%$p%>edit/rate.cgi?<% $rate->ratenum %>"><% $rate->ratename %></A>
+ </TD>
+
+ <TD CLASS="grid" BGCOLOR="<% $bgcolor %>">
+ <INPUT TYPE="text" SIZE=9 NAME="min_included<%$n%>" VALUE="<% $cgi->param("min_included$n") || $rate_detail->min_included |h %>">
+ </TD>
+
+ <TD CLASS="grid" BGCOLOR="<% $bgcolor %>">
+ $<INPUT TYPE="text" SIZE=6 NAME="min_charge<%$n%>" VALUE="<% sprintf('%.2f', $cgi->param("min_charge$n") || $rate_detail->min_charge ) %>">
+ </TD>
+
+ <TD CLASS="grid" BGCOLOR="<% $bgcolor %>">
+ <SELECT NAME="sec_granularity<%$n%>">
+% foreach my $granularity ( keys %granularity ) {
+ <OPTION VALUE="<%$granularity%>"<% $granularity == ( $cgi->param("sec_granularity$n") || $rate_detail->sec_granularity ) ? ' SELECTED' : '' %>><%$granularity{$granularity}%>
+% }
+ </SELECT>
+ </TD>
+
+ </TR>
+
+% }
+
+</TABLE>
+
+
+<BR><BR>
+<INPUT TYPE="submit" VALUE="<% $rate_region->regionnum ? "Apply changes" : "Add region" %>">
+
+</FORM>
+
+<% include('/elements/footer.html') %>
+
+<%init>
+
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('Configuration');
+
+my $rate_region;
+if ( $cgi->param('error') ) {
+ $rate_region = new FS::rate_region ( {
+ map { $_, scalar($cgi->param($_)) } fields('rate_region')
+ } );
+} elsif ( $cgi->keywords ) {
+ my($query) = $cgi->keywords;
+ $query =~ /^(\d+)$/ or die "unparsable regionnum";
+ $rate_region = qsearchs( 'rate_region', { 'regionnum' => $1 } )
+ or die "unknown regionnum $1\n";
+} else { #adding
+ $rate_region = new FS::rate_region {};
+}
+my $action = $rate_region->regionnum ? 'Edit' : 'Add';
+
+my $p1 = popurl(1);
+
+tie my %granularity, 'Tie::IxHash',
+ '1', => '1 second',
+ '6' => '6 second',
+ '30' => '30 second', # '1/2 minute',
+ '60' => 'minute',
+ '0' => 'call',
+;
+
+my @rate_prefix = $rate_region->rate_prefix;
+my $countrycode = '';
+if ( @rate_prefix ) {
+ $countrycode = $rate_prefix[0]->countrycode;
+ foreach my $rate_prefix ( @rate_prefix ) {
+ errorpage('multiple country codes per region not yet supported by web UI')
+ unless $rate_prefix->countrycode eq $countrycode;
+ }
+}
+
+</%init>
diff --git a/httemplate/edit/reason.html b/httemplate/edit/reason.html
new file mode 100644
index 000000000..620a2ea15
--- /dev/null
+++ b/httemplate/edit/reason.html
@@ -0,0 +1,50 @@
+%
+% $cgi->param('class') =~ /^(\w)$/ or die "illegal class";
+% my $class=$1;
+%
+% my $classname = $FS::reason_type::class_name{$class};
+%
+% my (@types) = qsearch( 'reason_type', { 'class' => $class } );
+%
+% unless (scalar(@types)) {
+% print $cgi->redirect( "reason_type.html?class=$class" );
+% }
+<% include( 'elements/edit.html',
+ 'name' => ucfirst($classname) . ' Reason',
+ 'table' => 'reason',
+ 'labels' => {
+ 'reasonnum' => ucfirst($classname) . ' Reason',
+ 'reason_type' => ucfirst($classname) . ' Reason type',
+ 'reason' => ucfirst($classname) . ' Reason',
+ 'disabled' => 'Disabled',
+ 'class' => '',
+ },
+ 'fields' => [
+ { 'field' => 'reason_type',
+ 'type' => 'select',
+ #XXX use something more sane than a hashref
+ #then fix tr-select.html
+ 'value' => { 'vcolumn' => 'typenum',
+ 'ccolumn' => 'type',
+ 'values' => \@types,
+ },
+ },
+ 'reason',
+ { 'field' => 'class',
+ 'type' => 'hidden',
+ 'value' => $class,
+ },
+ { 'field' => 'disabled',
+ 'type' => 'checkbox',
+ 'value' => 'Y'
+ },
+ ],
+ 'viewall_url' => $p . "browse/reason.html?class=$class",
+ )
+%>
+<%init>
+
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('Configuration');
+
+</%init>
diff --git a/httemplate/edit/reason_type.html b/httemplate/edit/reason_type.html
new file mode 100644
index 000000000..ea5650ec3
--- /dev/null
+++ b/httemplate/edit/reason_type.html
@@ -0,0 +1,29 @@
+<% include( 'elements/edit.html',
+ 'name' => $classname . ' Reason Type',
+ 'table' => 'reason_type',
+ 'labels' => {
+ 'typenum' => $classname . ' reason type',
+ 'type' => $classname . ' reason type name',
+ 'class' => '',
+ },
+ 'fields' => [
+ 'type',
+ { 'field' => 'class',
+ 'type' => 'hidden',
+ },
+ ],
+ 'viewall_url' => $p . "browse/reason_type.html?class=$class",
+ 'new_hashref_callback' => sub {{ 'class' => $class }},
+ )
+%>
+<%init>
+
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('Configuration');
+
+$cgi->param('class') =~ /^(\w)$/;
+my $class = $1;
+
+my $classname = $FS::reason_type::class_name{$class};
+
+</%init>
diff --git a/httemplate/edit/reg_code.cgi b/httemplate/edit/reg_code.cgi
new file mode 100644
index 000000000..e57ac09bf
--- /dev/null
+++ b/httemplate/edit/reg_code.cgi
@@ -0,0 +1,44 @@
+<% include('/elements/header.html', 'Generate registration codes for '. $agent->agent) %>
+
+<% include('/elements/error.html') %>
+
+<FORM ACTION="<%popurl(1)%>process/reg_code.cgi" METHOD="POST" NAME="OneTrueForm" onSubmit="document.OneTrueForm.submit.disabled=true">
+<INPUT TYPE="hidden" NAME="agentnum" VALUE="<% $agent->agentnum %>">
+
+Generate
+% my $num = '';
+% if ( $cgi->param('num') =~ /^\s*(\d+)\s*$/ ) {
+% $num = $1;
+% }
+<INPUT TYPE="text" NAME="num" VALUE="<% $num %>" SIZE=5 MAXLENGTH=4>
+registration codes for <B><% $agent->agent %></B> allowing the following packages:
+<BR><BR>
+
+% foreach my $part_pkg ( qsearch('part_pkg', { 'disabled' => '' } ) ) {
+% my $pkgpart = $part_pkg->pkgpart;
+
+ <INPUT TYPE="checkbox" NAME="pkgpart<% $pkgpart %>" <% $cgi->param("pkgpart$pkgpart") ? 'CHECKED' : '' %>>
+ <% $part_pkg->pkg %> - <% $part_pkg->comment %>
+ <BR>
+
+% }
+
+
+<BR>
+<INPUT TYPE="submit" NAME="submit" VALUE="Generate">
+
+</FORM>
+
+<% include('/elements/footer.html') %>
+
+<%init>
+
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('Configuration');
+
+my $agentnum = $cgi->param('agentnum');
+$agentnum =~ /^(\d+)$/ or errorpage("illegal agentnum $agentnum");
+$agentnum = $1;
+my $agent = qsearchs('agent', { 'agentnum' => $agentnum } );
+
+</%init>
diff --git a/httemplate/edit/router.cgi b/httemplate/edit/router.cgi
new file mode 100755
index 000000000..c08e54449
--- /dev/null
+++ b/httemplate/edit/router.cgi
@@ -0,0 +1,78 @@
+<% include('/elements/header.html', "$action Router", menubar(
+ 'View all routers' => "${p}browse/router.cgi",
+ ))
+%>
+
+<% include('/elements/error.html') %>
+
+<FORM ACTION="<%popurl(1)%>process/router.cgi" METHOD=POST>
+ <INPUT TYPE="hidden" NAME="table" VALUE="router">
+ <INPUT TYPE="hidden" NAME="redirect_ok" VALUE="<%$p3%>/browse/router.cgi">
+ <INPUT TYPE="hidden" NAME="redirect_error" VALUE="<%$p3%>/edit/router.cgi">
+ <INPUT TYPE="hidden" NAME="routernum" VALUE="<%$routernum%>">
+ <INPUT TYPE="hidden" NAME="svcnum" VALUE="<%$router->svcnum%>">
+ Router #<%$routernum or "(NEW)"%>
+
+<BR><BR>Name <INPUT TYPE="text" NAME="routername" SIZE=32 VALUE="<%$router->routername%>">
+
+<BR><BR>
+Custom fields:
+<BR>
+<%table() %>
+%
+%foreach my $field ($router->virtual_fields) {
+% print $router->pvf($field)->widget('HTML', 'edit',
+% $router->getfield($field));
+%}
+%
+
+</TABLE>
+%
+%unless ($router->svcnum) {
+%
+
+<BR><BR>Select the service types available on this router<BR>
+%
+%
+% foreach my $part_svc ( qsearch('part_svc', { svcdb => 'svc_broadband',
+% disabled => '' }) ) {
+%
+
+ <BR>
+ <INPUT TYPE="checkbox" NAME="svcpart_<%$part_svc->svcpart%>"<%
+ qsearchs('part_svc_router', { svcpart => $part_svc->svcpart,
+ routernum => $routernum } ) ? ' CHECKED' : ''%> VALUE="ON">
+ <A HREF="<%${p}%>edit/part_svc.cgi?<%$part_svc->svcpart%>">
+ <%$part_svc->svcpart%>: <%$part_svc->svc%></A>
+% }
+% }
+
+
+ <BR><BR><INPUT TYPE="submit" VALUE="Apply changes">
+ </FORM>
+
+<% include('/elements/footer.html') %>
+
+<%init>
+
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('Configuration');
+
+my $router;
+if ( $cgi->keywords ) {
+ my($query) = $cgi->keywords;
+ $query =~ /^(\d+)$/;
+ $router = qsearchs('router', { routernum => $1 })
+ or print $cgi->redirect(popurl(2)."browse/router.cgi") ;
+} else {
+ $router = new FS::router ( {
+ map { $_, scalar($cgi->param($_)) } fields('router')
+ } );
+}
+
+my $routernum = $router->routernum;
+my $action = $routernum ? 'Edit' : 'Add';
+
+my $p3 = popurl(3);
+
+</%init>
diff --git a/httemplate/edit/svc_Common.html b/httemplate/edit/svc_Common.html
new file mode 100644
index 000000000..6666d9720
--- /dev/null
+++ b/httemplate/edit/svc_Common.html
@@ -0,0 +1,33 @@
+<% include('elements/svc_Common.html',
+ 'table' => $table,
+ 'post_url' => popurl(1). "process/svc_Common.html",
+ %opt,
+ )
+%>
+<%init>
+
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('Provision customer service'); #something else more specific?
+
+# false laziness w/view/svc_Common.html
+
+$cgi->param('svcdb') =~ /^(svc_\w+)$/ or die "unparsable svcdb";
+my $table = $1;
+require "FS/$table.pm";
+
+my %opt;
+if ( UNIVERSAL::can("FS::$table", 'table_info') ) {
+ $opt{'name'} = "FS::$table"->table_info->{'name'};
+
+ my $fields = "FS::$table"->table_info->{'fields'};
+ my %labels = map { $_ => ( ref($fields->{$_})
+ ? $fields->{$_}{'label'}
+ : $fields->{$_}
+ );
+ }
+ keys %$fields;
+ $opt{'labels'} = \%labels;
+
+}
+
+</%init>
diff --git a/httemplate/edit/svc_acct.cgi b/httemplate/edit/svc_acct.cgi
new file mode 100755
index 000000000..58283ef54
--- /dev/null
+++ b/httemplate/edit/svc_acct.cgi
@@ -0,0 +1,452 @@
+<% include('/elements/header.html', "$action $svc account") %>
+
+<% include('/elements/error.html') %>
+
+% if ( $cust_main ) {
+
+ <% include( '/elements/small_custview.html', $cust_main, '', 1,
+ popurl(2) . "view/cust_main.cgi") %>
+ <BR>
+% }
+
+
+<FORM NAME="OneTrueForm" ACTION="<% $p1 %>process/svc_acct.cgi" METHOD=POST>
+<INPUT TYPE="hidden" NAME="svcnum" VALUE="<% $svcnum %>">
+<INPUT TYPE="hidden" NAME="pkgnum" VALUE="<% $pkgnum %>">
+<INPUT TYPE="hidden" NAME="svcpart" VALUE="<% $svcpart %>">
+
+Service # <% $svcnum ? "<B>$svcnum</B>" : " (NEW)" %><BR>
+
+<% ntable("#cccccc",2) %>
+
+<TR>
+ <TD ALIGN="right">Service</TD>
+ <TD BGCOLOR="#eeeeee"><% $part_svc->svc %></TD>
+</TR>
+
+<TR>
+ <TD ALIGN="right">Username</TD>
+ <TD>
+ <INPUT TYPE="text" NAME="username" VALUE="<% $username %>" SIZE=<% $ulen2 %> MAXLENGTH=<% $ulen %>>
+ </TD>
+</TR>
+
+%if ( $part_svc->part_svc_column('_password')->columnflag ne 'F' ) {
+<TR>
+ <TD ALIGN="right">Password</TD>
+ <TD>
+ <INPUT TYPE="text" NAME="_password" VALUE="<% $password %>" SIZE=<% $pmax2 %> MAXLENGTH=<% $pmax %>>
+ (blank to generate)
+ </TD>
+</TR>
+%}else{
+ <INPUT TYPE="hidden" NAME="_password" VALUE="<% $password %>">
+%}
+%
+%my $sec_phrase = $svc_acct->sec_phrase;
+%if ( $conf->exists('security_phrase')
+% && $part_svc->part_svc_column('sec_phrase')->columnflag ne 'F' ) {
+%
+
+
+ <TR>
+ <TD ALIGN="right">Security phrase</TD>
+ <TD>
+ <INPUT TYPE="text" NAME="sec_phrase" VALUE="<% $sec_phrase %>" SIZE=32>
+ (for forgotten passwords)
+ </TD>
+ </TD>
+% } else {
+
+
+ <INPUT TYPE="hidden" NAME="sec_phrase" VALUE="<% $sec_phrase %>">
+% }
+%
+%#domain
+%my $domsvc = $svc_acct->domsvc || 0;
+%if ( $part_svc->part_svc_column('domsvc')->columnflag eq 'F' ) {
+%
+
+
+ <INPUT TYPE="hidden" NAME="domsvc" VALUE="<% $domsvc %>">
+% } else {
+%
+% my %svc_domain = ();
+%
+% if ( $domsvc ) {
+% my $svc_domain = qsearchs('svc_domain', { 'svcnum' => $domsvc, } );
+% if ( $svc_domain ) {
+% $svc_domain{$svc_domain->svcnum} = $svc_domain;
+% } else {
+% warn "unknown svc_domain.svcnum for svc_acct.domsvc: $domsvc";
+% }
+% }
+%
+% %svc_domain = (%svc_domain,
+% domain_select_hash FS::svc_acct('svcpart' => $svcpart,
+% 'pkgnum' => $pkgnum,
+% )
+% );
+%
+
+
+ <TR>
+ <TD ALIGN="right">Domain</TD>
+ <TD>
+ <SELECT NAME="domsvc" SIZE=1>
+% foreach my $svcnum (
+% sort { $svc_domain{$a} cmp $svc_domain{$b} }
+% keys %svc_domain
+% ) {
+% my $svc_domain = $svc_domain{$svcnum};
+%
+
+
+ <OPTION VALUE="<% $svcnum %>" <% $svcnum == $domsvc ? ' SELECTED' : '' %>><% $svc_domain{$svcnum} %>
+% }
+
+ </SELECT>
+ </TD>
+ </TR>
+% }
+%
+%#pop
+%my $popnum = $svc_acct->popnum || 0;
+%if ( $part_svc->part_svc_column('popnum')->columnflag eq 'F' ) {
+%
+
+
+ <INPUT TYPE="hidden" NAME="popnum" VALUE="<% $popnum %>">
+% } else {
+
+
+ <TR>
+ <TD ALIGN="right">Access number</TD>
+ <TD><% FS::svc_acct_pop::popselector($popnum) %></TD>
+ </TR>
+% }
+% #uid/gid
+% foreach my $xid (qw( uid gid )) {
+%
+% if ( $part_svc->part_svc_column($xid)->columnflag =~ /^[FA]$/
+% || ! $conf->exists("svc_acct-edit_$xid")
+% ) {
+%
+% if ( length($svc_acct->$xid()) ) {
+
+
+ <TR>
+ <TD ALIGN="right"><% uc($xid) %></TD>
+ <TD BGCOLOR="#eeeeee"><% $svc_acct->$xid() %></TD>
+ <TD>
+ </TD>
+ </TR>
+% }
+
+
+ <INPUT TYPE="hidden" NAME="<% $xid %>" VALUE="<% $svc_acct->$xid() %>">
+% } else {
+
+
+ <TR>
+ <TD ALIGN="right"><% uc($xid) %></TD>
+ <TD>
+ <INPUT TYPE="text" NAME="<% $xid %>" SIZE=8 MAXLENGTH=6 VALUE="<% $svc_acct->$xid() %>">
+ </TD>
+ </TR>
+% }
+% }
+%
+%#finger
+%if ( $part_svc->part_svc_column('uid')->columnflag eq 'F'
+% && ! $svc_acct->finger ) {
+%
+
+
+ <INPUT TYPE="hidden" NAME="finger" VALUE="">
+% } else {
+
+
+ <TR>
+ <TD ALIGN="right">GECOS</TD>
+ <TD>
+ <INPUT TYPE="text" NAME="finger" VALUE="<% $svc_acct->finger %>">
+ </TD>
+ </TR>
+% }
+%
+%#dir
+%if ( $part_svc->part_svc_column('dir')->columnflag eq 'F'
+% || !$curuser->access_right('Edit home dir')
+% ) {
+
+
+<INPUT TYPE="hidden" NAME="dir" VALUE="<% $svc_acct->dir %>">
+% } else {
+
+
+ <TR>
+ <TD ALIGN="right">Home directory</TD>
+ <TD><INPUT TYPE="text" NAME="dir" VALUE="<% $svc_acct->dir %>"></TD>
+ </TR>
+% }
+%
+%#shell
+%my $shell = $svc_acct->shell;
+%if ( $part_svc->part_svc_column('shell')->columnflag eq 'F'
+% || ( !$shell && $part_svc->part_svc_column('uid')->columnflag eq 'F' )
+% ) {
+%
+
+
+ <INPUT TYPE="hidden" NAME="shell" VALUE="<% $shell %>">
+% } else {
+
+
+ <TR>
+ <TD ALIGN="right">Shell</TD>
+ <TD>
+ <SELECT NAME="shell" SIZE=1>
+%
+% my($etc_shell);
+% foreach $etc_shell (@shells) {
+%
+
+
+ <OPTION<% $etc_shell eq $shell ? ' SELECTED' : '' %>><% $etc_shell %>
+% }
+
+
+ </SELECT>
+ </TD>
+ </TR>
+% }
+% if ( $part_svc->part_svc_column('quota')->columnflag eq 'F' ) {
+
+
+ <INPUT TYPE="hidden" NAME="quota" VALUE="<% $svc_acct->quota %>">
+% } else {
+
+
+ <TR>
+ <TD ALIGN="right">Quota:</TD>
+ <TD><INPUT TYPE="text" NAME="quota" VALUE="<% $svc_acct->quota %>"></TD>
+ </TR>
+% }
+% if ( $part_svc->part_svc_column('slipip')->columnflag =~ /^[FA]$/ ) {
+
+
+ <INPUT TYPE="hidden" NAME="slipip" VALUE="<% $svc_acct->slipip %>">
+% } else {
+
+
+ <TR>
+ <TD ALIGN="right">IP</TD>
+ <TD><INPUT TYPE="text" NAME="slipip" VALUE="<% $svc_acct->slipip %>"></TD>
+ </TR>
+% }
+%
+% my %label = ( seconds => 'Time',
+% upbytes => 'Upload bytes',
+% downbytes => 'Download bytes',
+% totalbytes => 'Total bytes',
+% );
+% foreach my $uf (keys %label) {
+% my $tf = $uf . "_threshold";
+% if ( $curuser->access_right('Edit usage') ) {
+ <TR>
+ <TD ALIGN="right"><% $label{$uf} %> remaining</TD>
+ <TD><INPUT TYPE="text" NAME="<% $uf %>" VALUE="<% $svc_acct->$uf %>">(blank disables)</TD>
+ </TR>
+ <TR>
+ <TD ALIGN="right"><% $label{$uf} %> threshold</TD>
+ <TD><INPUT TYPE="text" NAME="<% $tf %>" VALUE="<% $svc_acct->$tf %>">(blank disables)</TD>
+ </TR>
+% }else{
+ <INPUT TYPE="hidden" NAME="<% $uf %>" VALUE="<% $svc_acct->$uf %>">
+ <INPUT TYPE="hidden" NAME="<% $tf %>" VALUE="<% $svc_acct->$tf %>">
+% }
+% }
+%
+%foreach my $r ( grep { /^r(adius|[cr])_/ } fields('svc_acct') ) {
+% $r =~ /^^r(adius|[cr])_(.+)$/ or next; #?
+% my $a = $2;
+%
+% if ( $part_svc->part_svc_column($r)->columnflag =~ /^[FA]$/ ) {
+
+
+ <INPUT TYPE="hidden" NAME="<% $r %>" VALUE="<% $svc_acct->getfield($r) %>">
+% } else {
+
+
+ <TR>
+ <TD ALIGN="right"><% $FS::raddb::attrib{$a} %></TD>
+ <TD><INPUT TYPE="text" NAME="<% $r %>" VALUE="<% $svc_acct->getfield($r) %>"></TD>
+ </TR>
+% }
+% }
+
+
+
+<TR>
+ <TD ALIGN="right">RADIUS groups</TD>
+% if ( $part_svc->part_svc_column('usergroup')->columnflag eq 'F' ) {
+
+
+ <TD BGCOLOR="#eeeeee"><% join('<BR>', @groups) %></TD>
+% } else {
+
+
+ <TD><% FS::svc_acct::radius_usergroup_selector( \@groups ) %></TD>
+% }
+
+
+</TR>
+% foreach my $field ($svc_acct->virtual_fields) {
+% # If the flag is X, it won't even show up in $svc_acct->virtual_fields.
+% if ( $part_svc->part_svc_column($field)->columnflag ne 'F' ) {
+
+
+ <% $svc_acct->pvf($field)->widget('HTML', 'edit', $svc_acct->getfield($field)) %>
+% }
+% }
+
+
+</TABLE>
+<BR>
+
+<INPUT TYPE="submit" VALUE="Submit">
+
+</FORM>
+
+<% include('/elements/footer.html') %>
+
+<%init>
+
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('Provision customer service'); #something else more specific?
+
+my $conf = new FS::Conf;
+my @shells = $conf->config('shells');
+
+my $curuser = $FS::CurrentUser::CurrentUser;
+
+my($svcnum, $pkgnum, $svcpart, $part_svc, $svc_acct, @groups);
+if ( $cgi->param('error') ) {
+
+ $svc_acct = new FS::svc_acct ( {
+ map { $_, scalar($cgi->param($_)) } fields('svc_acct')
+ } );
+ $svcnum = $svc_acct->svcnum;
+ $pkgnum = $cgi->param('pkgnum');
+ $svcpart = $cgi->param('svcpart');
+ $part_svc = qsearchs( 'part_svc', { 'svcpart' => $svcpart } );
+ die "No part_svc entry for svcpart $svcpart!" unless $part_svc;
+ @groups = $cgi->param('radius_usergroup');
+
+} elsif ( $cgi->param('pkgnum') && $cgi->param('svcpart') ) { #adding
+
+ $cgi->param('pkgnum') =~ /^(\d+)$/ or die 'unparsable pkgnum';
+ $pkgnum = $1;
+ $cgi->param('svcpart') =~ /^(\d+)$/ or die 'unparsable svcpart';
+ $svcpart = $1;
+
+ $part_svc=qsearchs('part_svc',{'svcpart'=>$svcpart});
+ die "No part_svc entry!" unless $part_svc;
+
+ $svc_acct = new FS::svc_acct({svcpart => $svcpart});
+
+ $svcnum='';
+
+} else { #editing
+
+ my($query) = $cgi->keywords;
+ $query =~ /^(\d+)$/ or die "unparsable svcnum";
+ $svcnum=$1;
+ $svc_acct=qsearchs('svc_acct',{'svcnum'=>$svcnum})
+ or die "Unknown (svc_acct) svcnum!";
+
+ my($cust_svc)=qsearchs('cust_svc',{'svcnum'=>$svcnum})
+ or die "Unknown (cust_svc) svcnum!";
+
+ $pkgnum=$cust_svc->pkgnum;
+ $svcpart=$cust_svc->svcpart;
+
+ $part_svc = qsearchs( 'part_svc', { 'svcpart' => $svcpart } );
+ die "No part_svc entry for svcpart $svcpart!" unless $part_svc;
+
+ @groups = $svc_acct->radius_groups;
+
+}
+
+my( $cust_pkg, $cust_main ) = ( '', '' );
+if ( $pkgnum ) {
+ $cust_pkg = qsearchs('cust_pkg', { 'pkgnum' => $pkgnum } );
+ $cust_main = $cust_pkg->cust_main;
+}
+
+unless ( $svcnum || $cgi->param('error') ) { #adding
+
+ #set gecos
+ if ($cust_main) {
+ unless ( $part_svc->part_svc_column('uid')->columnflag eq 'F' ) {
+ $svc_acct->setfield('finger',
+ $cust_main->getfield('first') . " " . $cust_main->getfield('last')
+ );
+ }
+ }
+
+ $svc_acct->set_default_and_fixed( {
+ #false laziness w/svc-acct::_fieldhandlers
+ 'usergroup' => sub {
+ my( $self, $groups ) = @_;
+ if ( ref($groups) eq 'ARRAY' ) {
+ @groups = @$groups;
+ $groups;
+ } elsif ( length($groups) ) {
+ @groups = split(/\s*,\s*/, $groups);
+ [ @groups ];
+ } else {
+ @groups = ();
+ [];
+ }
+ }
+ } );
+
+}
+
+#fixed radius groups always override & display
+if ( $part_svc->part_svc_column('usergroup')->columnflag eq 'F' ) {
+ @groups = split(',', $part_svc->part_svc_column('usergroup')->columnvalue);
+}
+
+my $action = $svcnum ? 'Edit' : 'Add';
+
+my $svc = $part_svc->getfield('svc');
+
+my $otaker = getotaker;
+
+my $username = $svc_acct->username;
+my $password;
+if ( $svc_acct->_password ) {
+ if ( $conf->exists('showpasswords') || ! $svcnum ) {
+ $password = $svc_acct->_password;
+ } else {
+ $password = "*HIDDEN*";
+ }
+} else {
+ $password = '';
+}
+
+my $ulen =
+ $conf->exists('usernamemax')
+ ? $conf->config('usernamemax')
+ : dbdef->table('svc_acct')->column('username')->length;
+my $ulen2 = $ulen+2;
+
+my $pmax = $conf->config('passwordmax') || 8;
+my $pmax2 = $pmax+2;
+
+my $p1 = popurl(1);
+
+</%init>
diff --git a/httemplate/edit/svc_acct_pop.cgi b/httemplate/edit/svc_acct_pop.cgi
new file mode 100755
index 000000000..3c16a1f95
--- /dev/null
+++ b/httemplate/edit/svc_acct_pop.cgi
@@ -0,0 +1,50 @@
+<% include('/elements/header.html', "$action Access Number", menubar(
+ 'View all Access Numbers' => popurl(2). "browse/svc_acct_pop.cgi",
+ ))
+%>
+
+<% include('/elements/error.html') %>
+
+<FORM ACTION="<%$p1%>process/svc_acct_pop.cgi" METHOD=POST>
+
+<INPUT TYPE="hidden" NAME="popnum" VALUE="<% $hashref->{popnum} %>">
+Access Number #<% $hashref->{popnum} ? $hashref->{popnum} : "(NEW)" %>
+
+<PRE>
+City <INPUT TYPE="text" NAME="city" SIZE=32 VALUE="<% $hashref->{city} %>">
+State <INPUT TYPE="text" NAME="state" SIZE=16 MAXLENGTH=16 VALUE="<% $hashref->{state} %>">
+Area Code <INPUT TYPE="text" NAME="ac" SIZE=4 MAXLENGTH=3 VALUE="<% $hashref->{ac} %>">
+Exchange <INPUT TYPE="text" NAME="exch" SIZE=4 MAXLENGTH=3 VALUE="<% $hashref->{exch} %>">
+Local <INPUT TYPE="text" NAME="loc" SIZE=5 MAXLENGTH=4 VALUE="<% $hashref->{loc} %>">
+</PRE>
+
+<BR>
+<INPUT TYPE="submit" VALUE="<% $hashref->{popnum} ? "Apply changes" : "Add Access Number" %>">
+
+</FORM>
+
+<% include('/elements/footer.html') %>
+
+<%init>
+
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('Configuration');
+
+my $svc_acct_pop;
+if ( $cgi->param('error') ) {
+ $svc_acct_pop = new FS::svc_acct_pop ( {
+ map { $_, scalar($cgi->param($_)) } fields('svc_acct_pop')
+ } );
+} elsif ( $cgi->keywords ) { #editing
+ my($query)=$cgi->keywords;
+ $query =~ /^(\d+)$/;
+ $svc_acct_pop=qsearchs('svc_acct_pop',{'popnum'=>$1});
+} else { #adding
+ $svc_acct_pop = new FS::svc_acct_pop {};
+}
+my $action = $svc_acct_pop->popnum ? 'Edit' : 'Add';
+my $hashref = $svc_acct_pop->hashref;
+
+my $p1 = popurl(1);
+
+</%init>
diff --git a/httemplate/edit/svc_broadband.cgi b/httemplate/edit/svc_broadband.cgi
new file mode 100644
index 000000000..c2fb58dda
--- /dev/null
+++ b/httemplate/edit/svc_broadband.cgi
@@ -0,0 +1,254 @@
+<% include('/elements/header.html', "Broadband Service $action") %>
+
+<% include('/elements/error.html') %>
+
+Service #<B><%$svcnum ? $svcnum : "(NEW)"%></B><BR><BR>
+
+<FORM ACTION="<%${p1}%>process/svc_broadband.cgi" METHOD=POST>
+ <INPUT TYPE="hidden" NAME="svcnum" VALUE="<%$svcnum%>">
+ <INPUT TYPE="hidden" NAME="pkgnum" VALUE="<%$pkgnum%>">
+ <INPUT TYPE="hidden" NAME="svcpart" VALUE="<%$svcpart%>">
+
+ <%&ntable("#cccccc",2)%>
+ <TR>
+ <TD ALIGN="right">Description</TD>
+ <TD BGCOLOR="#ffffff">
+% if ( $part_svc->part_svc_column('description')->columnflag eq 'F' ) {
+
+ <INPUT TYPE="hidden" NAME="description" VALUE="<%$description%>"><%$description%>
+% } else {
+
+ <INPUT TYPE="text" NAME="description" VALUE="<%$description%>">
+% }
+
+ </TD>
+ </TR>
+ <TR>
+ <TD ALIGN="right">IP Address</TD>
+ <TD BGCOLOR="#ffffff">
+% if ( $part_svc->part_svc_column('ip_addr')->columnflag eq 'F' ) {
+
+ <INPUT TYPE="hidden" NAME="ip_addr" VALUE="<%$ip_addr%>"><%$ip_addr%>
+% } else {
+
+ <INPUT TYPE="text" NAME="ip_addr" VALUE="<%$ip_addr%>">
+% }
+
+ </TD>
+ </TR>
+ <TR>
+ <TD ALIGN="right">Download speed</TD>
+ <TD BGCOLOR="#ffffff">
+% if ( $part_svc->part_svc_column('speed_down')->columnflag eq 'F' ) {
+
+ <INPUT TYPE="hidden" NAME="speed_down" VALUE="<%$speed_down%>"><%$speed_down%>Kbps
+% } else {
+
+ <INPUT TYPE="text" NAME="speed_down" SIZE=5 VALUE="<%$speed_down%>">Kbps
+% }
+
+ </TD>
+ </TR>
+ <TR>
+ <TD ALIGN="right">Upload speed</TD>
+ <TD BGCOLOR="#ffffff">
+% if ( $part_svc->part_svc_column('speed_up')->columnflag eq 'F' ) {
+
+ <INPUT TYPE="hidden" NAME="speed_up" VALUE="<%$speed_up%>"><%$speed_up%>Kbps
+% } else {
+
+ <INPUT TYPE="text" NAME="speed_up" SIZE=5 VALUE="<%$speed_up%>">Kbps
+% }
+
+ </TD>
+ </TR>
+% if ($action eq 'Add') {
+
+ <TR>
+ <TD ALIGN="right">Router/Block</TD>
+ <TD BGCOLOR="#ffffff">
+ <SELECT NAME="blocknum">
+%
+% warn $svc_broadband->svcpart;
+% foreach my $router ($svc_broadband->allowed_routers) {
+% warn $router->routername;
+% foreach my $addr_block ($router->addr_block) {
+%
+
+ <OPTION VALUE="<%$addr_block->blocknum%>"<%($addr_block->blocknum eq $blocknum) ? ' SELECTED' : ''%>>
+ <%$router->routername%>:<%$addr_block->ip_gateway%>/<%$addr_block->ip_netmask%></OPTION>
+%
+% }
+% }
+%
+
+ </SELECT>
+ </TD>
+ </TR>
+% } else {
+
+
+ <TR>
+ <TD ALIGN="right">Router/Block</TD>
+ <TD BGCOLOR="#ffffff">
+ <%$svc_broadband->addr_block->router->routername%>:<%$svc_broadband->addr_block->NetAddr%>
+ <INPUT TYPE="hidden" NAME="blocknum" VALUE="<%$svc_broadband->blocknum%>">
+ </TD>
+ </TR>
+% }
+ <TR>
+ <TD ALIGN="right">MAC Address</TD>
+ <TD BGCOLOR="#ffffff">
+ <INPUT TYPE="text" NAME="mac_addr" VALUE="<%$mac_addr%>">
+ </TD>
+ </TR>
+ <TR>
+ <TD ALIGN="right">Latitude</TD>
+ <TD BGCOLOR="#ffffff">
+ <INPUT TYPE="text" NAME="latitude" VALUE="<%$latitude%>">
+ </TD>
+ </TR>
+ <TR>
+ <TD ALIGN="right">Longitude</TD>
+ <TD BGCOLOR="#ffffff">
+ <INPUT TYPE="text" NAME="longitude" VALUE="<%$longitude%>">
+ </TD>
+ </TR>
+ <TR>
+ <TD ALIGN="right">Altitude</TD>
+ <TD BGCOLOR="#ffffff">
+ <INPUT TYPE="text" NAME="altitude" VALUE="<%$altitude%>">
+ </TD>
+ </TR>
+ <TR>
+ <TD ALIGN="right">VLAN Profile</TD>
+ <TD BGCOLOR="#ffffff">
+% if ( $part_svc->part_svc_column('vlan_profile')->columnflag eq 'F' ) {
+
+ <INPUT TYPE="hidden" NAME="vlan_profile" VALUE="<%$vlan_profile%>"><%$vlan_profile%>
+% } else {
+
+ <INPUT TYPE="text" NAME="vlan_profile" VALUE="<%$vlan_profile%>">
+% }
+
+ </TD>
+ </TR>
+ <TR>
+ <TD ALIGN="right">Authentication Key</TD>
+ <TD BGCOLOR="#ffffff">
+% if ( $part_svc->part_svc_column('auth_key')->columnflag eq 'F' ) {
+
+ <INPUT TYPE="hidden" NAME="auth_key" VALUE="<%$auth_key%>"><%$auth_key%>
+% } else {
+
+ <INPUT TYPE="text" NAME="auth_key" VALUE="<%$auth_key%>">
+% }
+
+ </TD>
+ </TR>
+%
+%foreach my $field ($svc_broadband->virtual_fields) {
+% if ( $part_svc->part_svc_column($field)->columnflag ne 'F' &&
+% $part_svc->part_svc_column($field)->columnflag ne 'X') {
+% print $svc_broadband->pvf($field)->widget('HTML', 'edit',
+% $svc_broadband->getfield($field));
+% }
+%}
+
+ </TABLE>
+ <BR>
+ <INPUT TYPE="submit" NAME="submit" VALUE="Submit">
+</FORM>
+
+<% include('/elements/footer.html') %>
+
+<%init>
+
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('Provision customer service'); #something else more specific?
+
+# If it's stupid but it works, it's still stupid.
+# -Kristian
+
+use HTML::Widgets::SelectLayers;
+use Tie::IxHash;
+
+my( $svcnum, $pkgnum, $svcpart, $part_svc, $svc_broadband );
+if ( $cgi->param('error') ) {
+
+ $svc_broadband = new FS::svc_broadband ( {
+ map { $_, scalar($cgi->param($_)) } fields('svc_broadband'), qw(svcpart)
+ } );
+ $svcnum = $svc_broadband->svcnum;
+ $pkgnum = $cgi->param('pkgnum');
+ $svcpart = $svc_broadband->svcpart;
+ $part_svc=qsearchs('part_svc',{'svcpart'=>$svcpart});
+ die "No part_svc entry!" unless $part_svc;
+
+} elsif ( $cgi->param('pkgnum') && $cgi->param('svcpart') ) { #adding
+
+ $cgi->param('pkgnum') =~ /^(\d+)$/ or die 'unparsable pkgnum';
+ $pkgnum = $1;
+ $cgi->param('svcpart') =~ /^(\d+)$/ or die 'unparsable svcpart';
+ $svcpart = $1;
+
+ $part_svc=qsearchs('part_svc',{'svcpart'=>$svcpart});
+ die "No part_svc entry!" unless $part_svc;
+
+ $svc_broadband = new FS::svc_broadband({ svcpart => $svcpart });
+
+ $svcnum='';
+
+ $svc_broadband->set_default_and_fixed;
+
+} else { #editing
+
+ my($query) = $cgi->keywords;
+ $query =~ /^(\d+)$/ or die "unparsable svcnum";
+ $svcnum=$1;
+ $svc_broadband=qsearchs('svc_broadband',{'svcnum'=>$svcnum})
+ or die "Unknown (svc_broadband) svcnum!";
+
+ my($cust_svc)=qsearchs('cust_svc',{'svcnum'=>$svcnum})
+ or die "Unknown (cust_svc) svcnum!";
+
+ $pkgnum=$cust_svc->pkgnum;
+ $svcpart=$cust_svc->svcpart;
+
+ $part_svc=qsearchs('part_svc',{'svcpart'=>$svcpart});
+ die "No part_svc entry!" unless $part_svc;
+
+}
+my $action = $svc_broadband->svcnum ? 'Edit' : 'Add';
+
+if ($pkgnum) {
+
+ #Nothing?
+
+} elsif ( $action eq 'Edit' ) {
+
+ #Nothing?
+
+} else {
+ die "\$action eq Add, but \$pkgnum is null!\n";
+}
+
+my $p1 = popurl(1);
+
+my ($ip_addr, $speed_up, $speed_down, $blocknum, $mac_addr,
+ $latitude, $longitude, $altitude, $vlan_profile, $auth_key,
+ $description) =
+ ($svc_broadband->ip_addr,
+ $svc_broadband->speed_up,
+ $svc_broadband->speed_down,
+ $svc_broadband->blocknum,
+ $svc_broadband->mac_addr,
+ $svc_broadband->latitude,
+ $svc_broadband->longitude,
+ $svc_broadband->altitude,
+ $svc_broadband->vlan_profile,
+ $svc_broadband->auth_key,
+ $svc_broadband->description,
+ );
+
+</%init>
diff --git a/httemplate/edit/svc_domain.cgi b/httemplate/edit/svc_domain.cgi
new file mode 100755
index 000000000..56ba604bf
--- /dev/null
+++ b/httemplate/edit/svc_domain.cgi
@@ -0,0 +1,91 @@
+<% include('/elements/header.html', "$action $svc", '') %>
+
+<% include('/elements/error.html') %>
+
+<FORM ACTION="<% $p1 %>process/svc_domain.cgi" METHOD=POST>
+<INPUT TYPE="hidden" NAME="svcnum" VALUE="<% $svcnum %>">
+<INPUT TYPE="hidden" NAME="pkgnum" VALUE="<% $pkgnum %>">
+<INPUT TYPE="hidden" NAME="svcpart" VALUE="<% $svcpart %>">
+
+<INPUT TYPE="radio" NAME="action" VALUE="N"<% $kludge_action eq 'N' ? ' CHECKED' : '' %>>New
+<BR>
+
+<INPUT TYPE="radio" NAME="action" VALUE="M"<% $kludge_action eq 'M' ? ' CHECKED' : '' %>>Transfer
+
+<P>Domain <INPUT TYPE="text" NAME="domain" VALUE="<% $domain %>" SIZE=28 MAXLENGTH=63>
+
+<BR>Purpose/Description: <INPUT TYPE="text" NAME="purpose" VALUE="<% $purpose %>" SIZE=64>
+
+<P><INPUT TYPE="submit" VALUE="Submit">
+
+</FORM>
+
+<% include('/elements/footer.html') %>
+
+<%init>
+
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('Provision customer service'); #something else more specific?
+
+my($svcnum, $pkgnum, $svcpart, $kludge_action, $purpose, $part_svc,
+ $svc_domain);
+if ( $cgi->param('error') ) {
+
+ $svc_domain = new FS::svc_domain ( {
+ map { $_, scalar($cgi->param($_)) } fields('svc_domain')
+ } );
+ $svcnum = $svc_domain->svcnum;
+ $pkgnum = $cgi->param('pkgnum');
+ $svcpart = $cgi->param('svcpart');
+ $kludge_action = $cgi->param('action');
+ $purpose = $cgi->param('purpose');
+ $part_svc = qsearchs('part_svc', { 'svcpart' => $svcpart } );
+ die "No part_svc entry!" unless $part_svc;
+
+} elsif ( $cgi->param('pkgnum') && $cgi->param('svcpart') ) { #adding
+
+ $cgi->param('pkgnum') =~ /^(\d+)$/ or die 'unparsable pkgnum';
+ $pkgnum = $1;
+ $cgi->param('svcpart') =~ /^(\d+)$/ or die 'unparsable svcpart';
+ $svcpart = $1;
+
+ $part_svc=qsearchs('part_svc',{'svcpart'=>$svcpart});
+ die "No part_svc entry!" unless $part_svc;
+
+ $svc_domain = new FS::svc_domain({});
+
+ $svcnum='';
+
+ $svc_domain->set_default_and_fixed;
+
+} else { #editing
+
+ $kludge_action = '';
+ $purpose = '';
+ my($query) = $cgi->keywords;
+ $query =~ /^(\d+)$/ or die "unparsable svcnum";
+ $svcnum=$1;
+ $svc_domain=qsearchs('svc_domain',{'svcnum'=>$svcnum})
+ or die "Unknown (svc_domain) svcnum!";
+
+ my($cust_svc)=qsearchs('cust_svc',{'svcnum'=>$svcnum})
+ or die "Unknown (cust_svc) svcnum!";
+
+ $pkgnum=$cust_svc->pkgnum;
+ $svcpart=$cust_svc->svcpart;
+
+ $part_svc=qsearchs('part_svc',{'svcpart'=>$svcpart});
+ die "No part_svc entry!" unless $part_svc;
+
+}
+my $action = $svcnum ? 'Edit' : 'Add';
+
+my $svc = $part_svc->getfield('svc');
+
+my $otaker = getotaker;
+
+my $domain = $svc_domain->domain;
+
+my $p1 = popurl(1);
+
+</%init>
diff --git a/httemplate/edit/svc_external.cgi b/httemplate/edit/svc_external.cgi
new file mode 100644
index 000000000..0df842b21
--- /dev/null
+++ b/httemplate/edit/svc_external.cgi
@@ -0,0 +1,102 @@
+<% include('/elements/header.html', "External service $action") %>
+
+<% include('/elements/error.html') %>
+
+<FORM ACTION="<%$p1%>process/svc_external.cgi" METHOD=POST>
+
+<INPUT TYPE="hidden" NAME="svcnum" VALUE="<% $svcnum %>">
+Service #<B><% $svcnum ? $svcnum : "(NEW)" %></B>
+<BR><BR>
+
+<INPUT TYPE="hidden" NAME="pkgnum" VALUE="<% $pkgnum %>">
+
+<INPUT TYPE="hidden" NAME="svcpart" VALUE="<% $svcpart %>">
+
+% my $id = $svc_external->id;
+% my $title = $svc_external->title;
+%
+<% &ntable("#cccccc",2) %>
+ <TR>
+ <TD ALIGN="right">External ID</TD>
+ <TD><INPUT TYPE="text" NAME="id" VALUE="<% $id %>"></TD>
+ </TR>
+ <TR>
+ <TD ALIGN="right">Title</TD>
+ <TD><INPUT TYPE="text" NAME="title" VALUE="<% $title %>"></TD>
+ </TR>
+
+% foreach my $field ($svc_external->virtual_fields) {
+% if ( $part_svc->part_svc_column($field)->columnflag ne 'F' ) {
+% # If the flag is X, it won't even show up in $svc_acct->virtual_fields.
+ <% $svc_external->pvf($field)->widget( 'HTML',
+ 'edit',
+ $svc_external->getfield($field)
+ )
+ %>
+% }
+% }
+
+</TABLE>
+<BR>
+
+<INPUT TYPE="submit" VALUE="Submit">
+</FORM>
+
+<% include('/elements/footer.html') %>
+
+<%init>
+
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('Provision customer service'); #something else more specific?
+
+my( $svcnum, $pkgnum, $svcpart, $part_svc, $svc_external );
+if ( $cgi->param('error') ) {
+
+ $svc_external = new FS::svc_external ( {
+ map { $_, scalar($cgi->param($_)) } fields('svc_external')
+ } );
+ $svcnum = $svc_external->svcnum;
+ $pkgnum = $cgi->param('pkgnum');
+ $svcpart = $cgi->param('svcpart');
+ $part_svc=qsearchs('part_svc',{'svcpart'=>$svcpart});
+ die "No part_svc entry!" unless $part_svc;
+
+} elsif ( $cgi->param('pkgnum') && $cgi->param('svcpart') ) { #adding
+
+ $cgi->param('pkgnum') =~ /^(\d+)$/ or die 'unparsable pkgnum';
+ $pkgnum = $1;
+ $cgi->param('svcpart') =~ /^(\d+)$/ or die 'unparsable svcpart';
+ $svcpart = $1;
+
+ $part_svc=qsearchs('part_svc',{'svcpart'=>$svcpart});
+ die "No part_svc entry!" unless $part_svc;
+
+ $svc_external = new FS::svc_external { svcpart => $svcpart };
+
+ $svcnum='';
+
+ $svc_external->set_default_and_fixed;
+
+} else { #adding
+
+ my($query) = $cgi->keywords;
+ $query =~ /^(\d+)$/ or die "unparsable svcnum";
+ $svcnum=$1;
+ $svc_external=qsearchs('svc_external',{'svcnum'=>$svcnum})
+ or die "Unknown (svc_external) svcnum!";
+
+ my($cust_svc)=qsearchs('cust_svc',{'svcnum'=>$svcnum})
+ or die "Unknown (cust_svc) svcnum!";
+
+ $pkgnum=$cust_svc->pkgnum;
+ $svcpart=$cust_svc->svcpart;
+
+ $part_svc=qsearchs('part_svc',{'svcpart'=>$svcpart});
+ die "No part_svc entry!" unless $part_svc;
+
+}
+my $action = $svc_external->svcnum ? 'Edit' : 'Add';
+
+my $p1 = popurl(1);
+
+</%init>
diff --git a/httemplate/edit/svc_forward.cgi b/httemplate/edit/svc_forward.cgi
new file mode 100755
index 000000000..96a00a5aa
--- /dev/null
+++ b/httemplate/edit/svc_forward.cgi
@@ -0,0 +1,175 @@
+<% include('/elements/header.html', "Mail Forward $action") %>
+
+<% include('/elements/error.html') %>
+
+Service #<% $svcnum ? "<B>$svcnum</B>" : " (NEW)" %><BR>
+Service: <B><% $part_svc->svc %></B><BR><BR>
+
+<FORM ACTION="process/svc_forward.cgi" METHOD="POST">
+<INPUT TYPE="hidden" NAME="svcnum" VALUE="<% $svcnum %>">
+<INPUT TYPE="hidden" NAME="pkgnum" VALUE="<% $pkgnum %>">
+<INPUT TYPE="hidden" NAME="svcpart" VALUE="<% $svcpart %>">
+
+<SCRIPT TYPE="text/javascript">
+function srcchanged(what) {
+ if ( what.options[what.selectedIndex].value == 0 ) {
+ what.form.src.disabled = false;
+ what.form.src.style.backgroundColor = "white";
+ } else {
+ what.form.src.disabled = true;
+ what.form.src.style.backgroundColor = "lightgrey";
+ }
+}
+function dstchanged(what) {
+ if ( what.options[what.selectedIndex].value == 0 ) {
+ what.form.dst.disabled = false;
+ what.form.dst.style.backgroundColor = "white";
+ } else {
+ what.form.dst.disabled = true;
+ what.form.dst.style.backgroundColor = "lightgrey";
+ }
+}
+</SCRIPT>
+
+<% ntable("#cccccc",2) %>
+<TR><TD ALIGN="right">Email to</TD>
+<TD><SELECT NAME="srcsvc" SIZE=1 onChange="srcchanged(this)">
+% foreach $_ (keys %email) {
+
+ <OPTION<% $_ eq $srcsvc ? " SELECTED" : "" %> VALUE="<% $_ %>"><% $email{$_} %></OPTION>
+% }
+% if ( $svc_forward->dbdef_table->column('src') ) {
+
+ <OPTION <% $src ? 'SELECTED' : '' %> VALUE="0">(other email address)</OPTION>
+% }
+
+</SELECT>
+% if ( $svc_forward->dbdef_table->column('src') ) {
+
+<INPUT TYPE="text" NAME="src" VALUE="<% $src %>" <% ( $src || !scalar(%email) ) ? '' : 'DISABLED STYLE="background-color: lightgrey"' %>>
+% }
+
+</TD></TR>
+
+<TR><TD ALIGN="right">Forwards to</TD>
+<TD><SELECT NAME="dstsvc" SIZE=1 onChange="dstchanged(this)">
+% foreach $_ (keys %email) {
+
+ <OPTION<% $_ eq $dstsvc ? " SELECTED" : "" %> VALUE="<% $_ %>"><% $email{$_} %></OPTION>
+% }
+
+<OPTION <% $dst ? 'SELECTED' : '' %> VALUE="0">(other email address)</OPTION>
+</SELECT>
+<INPUT TYPE="text" NAME="dst" VALUE="<% $dst %>" <% ( $dst || !scalar(%email) ) ? '' : 'DISABLED STYLE="background-color: lightgrey"' %>>
+</TD></TR>
+ </TABLE>
+<BR><INPUT TYPE="submit" VALUE="Submit">
+</FORM>
+
+<% include('/elements/footer.html') %>
+
+<%init>
+
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('Provision customer service'); #something else more specific?
+
+my $conf = new FS::Conf;
+
+my($svcnum, $pkgnum, $svcpart, $part_svc, $svc_forward);
+if ( $cgi->param('error') ) {
+ $svc_forward = new FS::svc_forward ( {
+ map { $_, scalar($cgi->param($_)) } fields('svc_forward')
+ } );
+ $svcnum = $svc_forward->svcnum;
+ $pkgnum = $cgi->param('pkgnum');
+ $svcpart = $cgi->param('svcpart');
+ $part_svc=qsearchs('part_svc',{'svcpart'=>$svcpart});
+ die "No part_svc entry!" unless $part_svc;
+
+} elsif ( $cgi->param('pkgnum') && $cgi->param('svcpart') ) { #adding
+
+ $cgi->param('pkgnum') =~ /^(\d+)$/ or die 'unparsable pkgnum';
+ $pkgnum = $1;
+ $cgi->param('svcpart') =~ /^(\d+)$/ or die 'unparsable svcpart';
+ $svcpart = $1;
+
+ $part_svc=qsearchs('part_svc',{'svcpart'=>$svcpart});
+ die "No part_svc entry!" unless $part_svc;
+
+ $svc_forward = new FS::svc_forward({});
+
+ $svcnum='';
+
+ $svc_forward->set_default_and_fixed;
+
+} else { #editing
+
+ my($query) = $cgi->keywords;
+
+ $query =~ /^(\d+)$/ or die "unparsable svcnum";
+ $svcnum=$1;
+ $svc_forward=qsearchs('svc_forward',{'svcnum'=>$svcnum})
+ or die "Unknown (svc_forward) svcnum!";
+
+ my($cust_svc)=qsearchs('cust_svc',{'svcnum'=>$svcnum})
+ or die "Unknown (cust_svc) svcnum!";
+
+ $pkgnum=$cust_svc->pkgnum;
+ $svcpart=$cust_svc->svcpart;
+
+ $part_svc=qsearchs('part_svc',{'svcpart'=>$svcpart});
+ die "No part_svc entry!" unless $part_svc;
+
+}
+my $action = $svc_forward->svcnum ? 'Edit' : 'Add';
+
+my %email;
+
+#starting with those currently attached
+foreach my $method (qw( srcsvc_acct dstsvc_acct )) {
+ my $svc_acct = $svc_forward->$method();
+ $email{$svc_acct->svcnum} = $svc_acct->email if $svc_acct;
+}
+
+if ($pkgnum) {
+
+ #find all possible user svcnums (and emails)
+
+ #and including the rest for this customer
+ my($u_part_svc,@u_acct_svcparts);
+ foreach $u_part_svc ( qsearch('part_svc',{'svcdb'=>'svc_acct'}) ) {
+ push @u_acct_svcparts,$u_part_svc->getfield('svcpart');
+ }
+
+ my($cust_pkg)=qsearchs('cust_pkg',{'pkgnum'=>$pkgnum});
+ my($custnum)=$cust_pkg->getfield('custnum');
+ my($i_cust_pkg);
+ foreach $i_cust_pkg ( qsearch('cust_pkg',{'custnum'=>$custnum}) ) {
+ my($cust_pkgnum)=$i_cust_pkg->getfield('pkgnum');
+ my($acct_svcpart);
+ foreach $acct_svcpart (@u_acct_svcparts) { #now find the corresponding
+ #record(s) in cust_svc ( for this
+ #pkgnum ! )
+ foreach my $i_cust_svc (
+ qsearch( 'cust_svc', { 'pkgnum' => $cust_pkgnum,
+ 'svcpart' => $acct_svcpart } )
+ ) {
+ my $svc_acct =
+ qsearchs( 'svc_acct', { 'svcnum' => $i_cust_svc->svcnum } );
+ $email{$svc_acct->svcnum} = $svc_acct->email;
+ }
+ }
+ }
+
+} elsif ( $action eq 'Add' ) {
+ die "\$action eq Add, but \$pkgnum is null!\n";
+}
+
+my($srcsvc,$dstsvc,$dst)=(
+ $svc_forward->srcsvc,
+ $svc_forward->dstsvc,
+ $svc_forward->dst,
+);
+my $src = $svc_forward->dbdef_table->column('src') ? $svc_forward->src : '';
+
+</%init>
diff --git a/httemplate/edit/svc_phone.cgi b/httemplate/edit/svc_phone.cgi
new file mode 100644
index 000000000..78b849c8d
--- /dev/null
+++ b/httemplate/edit/svc_phone.cgi
@@ -0,0 +1,17 @@
+<% include( 'elements/svc_Common.html',
+ 'name' => 'Phone number',
+ 'table' => 'svc_phone',
+ 'fields' => [qw( countrycode phonenum )], #pin
+ 'labels' => {
+ 'countrycode' => 'Country code',
+ 'phonenum' => 'Phone number',
+ 'pin' => 'PIN',
+ },
+ )
+%>
+<%init>
+
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('Provision customer service'); #something else more specific?
+
+</%init>
diff --git a/httemplate/edit/svc_www.cgi b/httemplate/edit/svc_www.cgi
new file mode 100644
index 000000000..d3416a414
--- /dev/null
+++ b/httemplate/edit/svc_www.cgi
@@ -0,0 +1,240 @@
+<% include('/elements/header.html', "Web Hosting $action") %>
+
+<% include('/elements/error.html') %>
+
+<FORM ACTION="<%$p1%>process/svc_www.cgi" METHOD=POST>
+
+<INPUT TYPE="hidden" NAME="svcnum" VALUE="<% $svcnum %>">
+Service #<B><% $svcnum ? $svcnum : "(NEW)" %></B>
+<BR><BR>
+
+<INPUT TYPE="hidden" NAME="pkgnum" VALUE="<% $pkgnum %>">
+
+<INPUT TYPE="hidden" NAME="svcpart" VALUE="<% $svcpart %>">
+
+% my $recnum = $svc_www->recnum;
+% my $usersvc = $svc_www->usersvc;
+
+<% &ntable("#cccccc",2) %>
+
+ <TR>
+ <TD ALIGN="right">Zone</TD>
+ <TD>
+ <SELECT NAME="recnum" SIZE=1>
+% foreach $_ (keys %arec) {
+ <OPTION<% $_ eq $recnum ? " SELECTED" : "" %> VALUE="<%$_%>"><%$arec{$_}%>
+% }
+ </SELECT>
+ </TD>
+ </TR>
+
+% if ( $part_svc->part_svc_column('usersvc')->columnflag ne 'F'
+% || $part_svc->part_svc_column('usersvc')->columnvalue !~ /^\s*$/) {
+ <TR>
+ <TD ALIGN="right">Username</TD>
+ <TD>
+ <SELECT NAME="usersvc" SIZE=1>
+ <OPTION VALUE="">(none)
+% foreach $_ (keys %svc_acct) {
+ <OPTION<% ($_ eq $usersvc) ? " SELECTED" : "" %> VALUE="<%$_%>"><% $svc_acct{$_} %>
+% }
+ <SELECT>
+ </TD>
+ </TR>
+% }
+
+% if ( $part_svc->part_svc_column('config')->columnflag ne 'F' &&
+% $FS::CurrentUser::CurrentUser->access_right('Edit www config') ) {
+ <TR>
+ <TD ALIGN="right">Config lines</TD>
+ <TD>
+ <TEXTAREA NAME="config" rows="15" cols="80"><% $config |h %></TEXTAREA>
+ </TD>
+ </TR>
+% } else {
+ <INPUT TYPE="hidden" NAME="config" VALUE="<% $config |h %>">
+%}
+
+% foreach my $field ($svc_www->virtual_fields) {
+% if ( $part_svc->part_svc_column($field)->columnflag ne 'F' ) {
+% # If the flag is X, it won't even show up in $svc_acct->virtual_fields.
+ <% $svc_www->pvf($field)->widget( 'HTML', 'edit',
+ $svc_www->getfield($field)
+ )
+ %>
+% }
+% }
+
+</TABLE>
+<BR>
+
+<INPUT TYPE="submit" VALUE="Submit">
+
+</FORM>
+
+<% include('/elements/footer.html') %>
+
+<%init>
+
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('Provision customer service'); #something else more specific?
+
+my $conf = new FS::Conf;
+
+my( $svcnum, $pkgnum, $svcpart, $part_svc, $svc_www, $config );
+
+if ( $cgi->param('error') ) {
+
+ $svc_www = new FS::svc_www ( {
+ map { $_, scalar($cgi->param($_)) } fields('svc_www')
+ } );
+ $svcnum = $svc_www->svcnum;
+ $pkgnum = $cgi->param('pkgnum');
+ $svcpart = $cgi->param('svcpart');
+ $config = $cgi->param('config');
+ $part_svc=qsearchs('part_svc',{'svcpart'=>$svcpart});
+ die "No part_svc entry!" unless $part_svc;
+
+} elsif ( $cgi->param('pkgnum') && $cgi->param('svcpart') ) { #adding
+
+ $cgi->param('pkgnum') =~ /^(\d+)$/ or die 'unparsable pkgnum';
+ $pkgnum = $1;
+ $cgi->param('svcpart') =~ /^(\d+)$/ or die 'unparsable svcpart';
+ $svcpart = $1;
+
+ $part_svc=qsearchs('part_svc',{'svcpart'=>$svcpart});
+ die "No part_svc entry!" unless $part_svc;
+
+ $svc_www = new FS::svc_www { svcpart => $svcpart };
+
+ $svcnum='';
+
+ $svc_www->set_default_and_fixed;
+
+} else { #editing
+
+ my($query) = $cgi->keywords;
+ $query =~ /^(\d+)$/ or die "unparsable svcnum";
+ $svcnum=$1;
+ $svc_www=qsearchs('svc_www',{'svcnum'=>$svcnum})
+ or die "Unknown (svc_www) svcnum!";
+
+ my($cust_svc)=qsearchs('cust_svc',{'svcnum'=>$svcnum})
+ or die "Unknown (cust_svc) svcnum!";
+
+ $pkgnum=$cust_svc->pkgnum;
+ $svcpart=$cust_svc->svcpart;
+ #$config=$cgi->escapeHTML($svc_www->config);
+
+ $part_svc=qsearchs('part_svc',{'svcpart'=>$svcpart});
+ die "No part_svc entry!" unless $part_svc;
+
+}
+my $action = $svc_www->svcnum ? 'Edit' : 'Add';
+
+my( %svc_acct, %arec );
+if ($pkgnum) {
+
+ my @u_acct_svcparts;
+ foreach my $svcpart (
+ map { $_->svcpart } qsearch( 'part_svc', { 'svcdb' => 'svc_acct' } )
+ ) {
+ next if $conf->exists('svc_www-usersvc_svcpart')
+ && ! grep { $svcpart == $_ }
+ $conf->config('svc_www-usersvc_svcpart');
+ push @u_acct_svcparts, $svcpart;
+ }
+
+ my($cust_pkg)=qsearchs('cust_pkg',{'pkgnum'=>$pkgnum});
+ my($custnum)=$cust_pkg->getfield('custnum');
+ my($i_cust_pkg);
+ foreach $i_cust_pkg ( qsearch('cust_pkg',{'custnum'=>$custnum}) ) {
+ my($cust_pkgnum)=$i_cust_pkg->getfield('pkgnum');
+ my($acct_svcpart);
+ foreach $acct_svcpart (@u_acct_svcparts) { #now find the corresponding
+ #record(s) in cust_svc ( for this
+ #pkgnum ! )
+ my($i_cust_svc);
+ foreach $i_cust_svc ( qsearch('cust_svc',{'pkgnum'=>$cust_pkgnum,'svcpart'=>$acct_svcpart}) ) {
+ my($svc_acct)=qsearchs('svc_acct',{'svcnum'=>$i_cust_svc->getfield('svcnum')});
+ $svc_acct{$svc_acct->getfield('svcnum')}=
+ $svc_acct->cust_svc->part_svc->svc. ': '. $svc_acct->email;
+ }
+ }
+ }
+
+
+ my($d_part_svc,@d_acct_svcparts);
+ foreach $d_part_svc ( qsearch('part_svc',{'svcdb'=>'svc_domain'}) ) {
+ push @d_acct_svcparts,$d_part_svc->getfield('svcpart');
+ }
+
+ foreach $i_cust_pkg ( qsearch( 'cust_pkg', { 'custnum' => $custnum } ) ) {
+ my $cust_pkgnum = $i_cust_pkg->pkgnum;
+
+ foreach my $acct_svcpart (@d_acct_svcparts) {
+
+ foreach my $i_cust_svc (
+ qsearch( 'cust_svc', { 'pkgnum' => $cust_pkgnum,
+ 'svcpart' => $acct_svcpart } )
+ ) {
+ my $svc_domain =
+ qsearchs( 'svc_domain', { 'svcnum' => $i_cust_svc->svcnum } );
+
+ my $extra_sql = "AND ( rectype = 'A' OR rectype = 'CNAME' )";
+ unless ( $conf->exists('svc_www-enable_subdomains') ) {
+ $extra_sql .= " AND ( reczone = '\@' OR reczone = '".
+ $svc_domain->domain. ".' )";
+ }
+
+ foreach my $domain_rec (
+ qsearch( 'domain_record',
+ {
+ 'svcnum' => $svc_domain->svcnum,
+ },
+ '',
+ $extra_sql,
+ )
+ ) {
+ $arec{$domain_rec->recnum} = $domain_rec->zone;
+ }
+
+ if ( $conf->exists('svc_www-enable_subdomains') ) {
+ $arec{'www.'. $svc_domain->domain} = 'www.'. $svc_domain->domain
+ unless qsearchs( 'domain_record', {
+ svcnum => $svc_domain->svcnum,
+ reczone => 'www',
+ } )
+ || qsearchs( 'domain_record', {
+ svcnum => $svc_domain->svcnum,
+ reczone => 'www.'.$svc_domain->domain.'.',
+ } );
+ }
+
+ $arec{'@.'. $svc_domain->domain} = $svc_domain->domain
+ unless qsearchs('domain_record', {
+ svcnum => $svc_domain->svcnum,
+ reczone => '@',
+ } )
+ || qsearchs('domain_record', {
+ svcnum => $svc_domain->svcnum,
+ reczone => $svc_domain->domain.'.',
+ } );
+
+ }
+
+ }
+ }
+
+} elsif ( $action eq 'Edit' ) {
+
+ my($domain_rec) = qsearchs('domain_record', { 'recnum'=>$svc_www->recnum });
+ $arec{$svc_www->recnum} = join '.', $domain_rec->recdata, $domain_rec->reczone;
+
+} else {
+ die "\$action eq Add, but \$pkgnum is null!\n";
+}
+
+my $p1 = popurl(1);
+
+</%init>