summaryrefslogtreecommitdiff
path: root/httemplate
diff options
context:
space:
mode:
Diffstat (limited to 'httemplate')
-rw-r--r--httemplate/autohandler21
-rw-r--r--httemplate/browse/addr_block.cgi76
-rwxr-xr-xhttemplate/browse/agent.cgi108
-rwxr-xr-xhttemplate/browse/agent_type.cgi8
-rwxr-xr-xhttemplate/browse/cust_main_county.cgi27
-rwxr-xr-xhttemplate/browse/cust_pay_batch.cgi90
-rw-r--r--httemplate/browse/generic.cgi46
-rwxr-xr-xhttemplate/browse/part_export.cgi2
-rwxr-xr-xhttemplate/browse/part_pkg.cgi93
-rwxr-xr-xhttemplate/browse/part_referral.cgi54
-rwxr-xr-xhttemplate/browse/part_svc.cgi42
-rw-r--r--httemplate/browse/part_virtual_field.cgi39
-rw-r--r--httemplate/browse/router.cgi38
-rwxr-xr-xhttemplate/browse/svc_acct_pop.cgi58
-rw-r--r--httemplate/config/config-view.cgi4
-rw-r--r--httemplate/config/config.cgi4
-rw-r--r--httemplate/docs/billing.html2
-rw-r--r--httemplate/docs/cvv2.html25
-rw-r--r--httemplate/docs/ieak.html75
-rw-r--r--httemplate/docs/index.html6
-rw-r--r--httemplate/docs/install.html70
-rwxr-xr-xhttemplate/docs/legacy.html5
-rw-r--r--httemplate/docs/man/FS/part_export/.cvs_is_on_crack0
-rw-r--r--httemplate/docs/schema.diabin14414 -> 14438 bytes
-rw-r--r--httemplate/docs/schema.html37
-rw-r--r--httemplate/docs/session.html7
-rw-r--r--httemplate/docs/signup.html6
-rwxr-xr-xhttemplate/docs/ssh.html5
-rw-r--r--httemplate/docs/upgrade-1.4.2.html20
-rw-r--r--httemplate/docs/upgrade10.html204
-rw-r--r--httemplate/docs/upgrade4.html27
-rw-r--r--httemplate/docs/upgrade5.html34
-rw-r--r--httemplate/docs/upgrade6.html66
-rw-r--r--httemplate/docs/upgrade8.html2
-rw-r--r--httemplate/docs/upgrade9.html28
-rwxr-xr-xhttemplate/edit/REAL_cust_pkg.cgi64
-rwxr-xr-xhttemplate/edit/agent.cgi65
-rwxr-xr-xhttemplate/edit/cust_main.cgi213
-rwxr-xr-xhttemplate/edit/cust_main_county.cgi44
-rwxr-xr-xhttemplate/edit/part_bill_event.cgi40
-rw-r--r--httemplate/edit/part_export.cgi19
-rwxr-xr-xhttemplate/edit/part_pkg.cgi199
-rwxr-xr-xhttemplate/edit/part_svc.cgi114
-rw-r--r--httemplate/edit/part_virtual_field.cgi92
-rwxr-xr-xhttemplate/edit/process/REAL_cust_pkg.cgi2
-rwxr-xr-xhttemplate/edit/process/addr_block/add.cgi20
-rwxr-xr-xhttemplate/edit/process/addr_block/allocate.cgi25
-rwxr-xr-xhttemplate/edit/process/addr_block/deallocate.cgi24
-rwxr-xr-xhttemplate/edit/process/addr_block/split.cgi19
-rwxr-xr-xhttemplate/edit/process/cust_main.cgi22
-rwxr-xr-xhttemplate/edit/process/cust_main_county-collapse.cgi4
-rwxr-xr-xhttemplate/edit/process/cust_main_county.cgi21
-rwxr-xr-xhttemplate/edit/process/cust_pkg.cgi25
-rw-r--r--httemplate/edit/process/generic.cgi70
-rwxr-xr-xhttemplate/edit/process/part_bill_event.cgi2
-rwxr-xr-xhttemplate/edit/process/part_pkg.cgi16
-rwxr-xr-xhttemplate/edit/process/part_svc.cgi2
-rw-r--r--httemplate/edit/process/router.cgi67
-rwxr-xr-xhttemplate/edit/process/svc_acct_sm.cgi34
-rw-r--r--httemplate/edit/process/svc_broadband.cgi45
-rwxr-xr-xhttemplate/edit/process/svc_external.cgi29
-rwxr-xr-xhttemplate/edit/router.cgi77
-rwxr-xr-xhttemplate/edit/svc_acct.cgi31
-rwxr-xr-xhttemplate/edit/svc_acct_sm.cgi178
-rw-r--r--httemplate/edit/svc_broadband.cgi172
-rwxr-xr-xhttemplate/edit/svc_domain.cgi2
-rw-r--r--httemplate/edit/svc_external.cgi105
-rwxr-xr-xhttemplate/edit/svc_forward.cgi105
-rw-r--r--httemplate/edit/svc_www.cgi12
-rw-r--r--httemplate/elements/calendar-en.js114
-rw-r--r--httemplate/elements/calendar-setup.js163
-rw-r--r--httemplate/elements/calendar-win2k-2.css263
-rw-r--r--httemplate/elements/calendar.js1641
-rw-r--r--httemplate/elements/calendar_stripped.js12
-rw-r--r--httemplate/elements/header.html19
-rw-r--r--httemplate/elements/menubar.html8
-rw-r--r--httemplate/elements/pager.html42
-rw-r--r--httemplate/elements/table.html8
-rwxr-xr-xhttemplate/graph/money_time-graph.cgi108
-rw-r--r--httemplate/graph/money_time.cgi59
-rw-r--r--httemplate/images/calendar.pngbin0 -> 426 bytes
-rw-r--r--httemplate/images/cvv2.pngbin0 -> 3854 bytes
-rw-r--r--httemplate/images/cvv2_amex.pngbin0 -> 4573 bytes
-rw-r--r--httemplate/index.html65
-rwxr-xr-xhttemplate/misc/bill.cgi3
-rwxr-xr-xhttemplate/misc/catchall.cgi2
-rwxr-xr-xhttemplate/misc/change_pkg.cgi66
-rwxr-xr-xhttemplate/misc/cust_main-cancel.cgi16
-rw-r--r--httemplate/misc/cust_main-import.cgi51
-rw-r--r--httemplate/misc/cust_main-import_charges.cgi14
-rwxr-xr-xhttemplate/misc/delete-cust_credit.cgi16
-rw-r--r--httemplate/misc/download-batch.cgi16
-rw-r--r--httemplate/misc/dump.cgi19
-rwxr-xr-xhttemplate/misc/email-invoice.cgi23
-rwxr-xr-xhttemplate/misc/link.cgi1
-rw-r--r--httemplate/misc/meta-import.cgi64
-rwxr-xr-xhttemplate/misc/print-invoice.cgi10
-rw-r--r--httemplate/misc/process/cust_main-import.cgi30
-rw-r--r--httemplate/misc/process/cust_main-import_charges.cgi26
-rwxr-xr-xhttemplate/misc/process/link.cgi13
-rw-r--r--httemplate/misc/process/meta-import.cgi178
-rwxr-xr-xhttemplate/misc/unapply-cust_pay.cgi18
-rwxr-xr-xhttemplate/misc/unprovision.cgi33
-rw-r--r--httemplate/misc/upload-batch.cgi29
-rwxr-xr-xhttemplate/search/cust_bill.cgi2
-rw-r--r--httemplate/search/cust_bill_event.cgi4
-rwxr-xr-xhttemplate/search/cust_main-quickpay.html11
-rwxr-xr-xhttemplate/search/cust_main.cgi140
-rwxr-xr-xhttemplate/search/cust_pay.cgi108
-rwxr-xr-xhttemplate/search/cust_pkg.cgi94
-rwxr-xr-xhttemplate/search/cust_pkg.html24
-rwxr-xr-xhttemplate/search/cust_pkg_report.cgi63
-rw-r--r--httemplate/search/elements/search.html59
-rwxr-xr-xhttemplate/search/report_cc.html44
-rwxr-xr-xhttemplate/search/report_credit.html44
-rw-r--r--httemplate/search/report_cust_pay.html54
-rw-r--r--httemplate/search/report_prepaid_income.cgi75
-rw-r--r--httemplate/search/report_prepaid_income.html17
-rwxr-xr-xhttemplate/search/report_receivables.cgi159
-rwxr-xr-xhttemplate/search/report_tax.html46
-rwxr-xr-xhttemplate/search/sql.cgi76
-rw-r--r--httemplate/search/sql.html12
-rwxr-xr-xhttemplate/search/svc_acct.cgi48
-rwxr-xr-xhttemplate/search/svc_acct_sm.cgi84
-rwxr-xr-xhttemplate/search/svc_acct_sm.html23
-rwxr-xr-xhttemplate/search/svc_domain.cgi132
-rwxr-xr-xhttemplate/search/svc_forward.cgi79
-rwxr-xr-xhttemplate/view/cust_bill-pdf.cgi13
-rwxr-xr-xhttemplate/view/cust_bill-ps.cgi13
-rwxr-xr-xhttemplate/view/cust_bill.cgi15
-rwxr-xr-xhttemplate/view/cust_main.cgi986
-rwxr-xr-xhttemplate/view/cust_pkg.cgi33
-rwxr-xr-xhttemplate/view/svc_acct.cgi87
-rwxr-xr-xhttemplate/view/svc_acct_sm.cgi58
-rw-r--r--httemplate/view/svc_broadband.cgi143
-rwxr-xr-xhttemplate/view/svc_domain.cgi4
-rw-r--r--httemplate/view/svc_external.cgi52
-rwxr-xr-xhttemplate/view/svc_forward.cgi28
-rw-r--r--httemplate/view/svc_www.cgi17
139 files changed, 7561 insertions, 1704 deletions
diff --git a/httemplate/autohandler b/httemplate/autohandler
new file mode 100644
index 0000000..2bd3adf
--- /dev/null
+++ b/httemplate/autohandler
@@ -0,0 +1,21 @@
+% $m->call_next;
+<%init>
+ dbh->{'private_profile'} = {} if UNIVERSAL::can(dbh, 'sprintProfile');
+</%init>
+<%filter>
+
+my $profile = '';
+if ( UNIVERSAL::can(dbh, 'sprintProfile') ) {
+
+ if ( lc($r->content_type) eq 'text/html' ) {
+
+ $profile = '<PRE>'. ("\n"x4096). encode_entities(dbh->sprintProfile()).
+ #"\n\n". &sprintAutoProfile(). '</PRE>';
+ "\n\n". '</PRE>';
+ }
+
+ dbh->{'private_profile'} = {};
+}
+
+s/(<\/BODY>[\s\n]*<\/HTML>[\s\n]*)$/$profile$1/i;
+</%filter>
diff --git a/httemplate/browse/addr_block.cgi b/httemplate/browse/addr_block.cgi
new file mode 100644
index 0000000..06ac556
--- /dev/null
+++ b/httemplate/browse/addr_block.cgi
@@ -0,0 +1,76 @@
+<%= header('Address Blocks', menubar('Main Menu' => $p)) %>
+<%
+
+use NetAddr::IP;
+
+my @addr_block = qsearch('addr_block', {});
+my @router = qsearch('router', {});
+my $block;
+my $p2 = popurl(2);
+my $path = $p2 . "edit/process/addr_block";
+
+%>
+
+<% if ($cgi->param('error')) { %>
+ <FONT SIZE="+1" COLOR="#ff0000">Error: <%=$cgi->param('error')%></FONT>
+ <BR><BR>
+<% } %>
+
+<%=table()%>
+
+<% foreach $block (sort {$a->NetAddr cmp $b->NetAddr} @addr_block) { %>
+ <TR>
+ <TD><%=$block->NetAddr%></TD>
+ <% if (my $router = $block->router) { %>
+ <% if (scalar($block->svc_broadband) == 0) { %>
+ <TD>
+ <%=$router->routername%>
+ </TD>
+ <TD>
+ <FORM ACTION="<%=$path%>/deallocate.cgi" METHOD="POST">
+ <INPUT TYPE="hidden" NAME="blocknum" VALUE="<%=$block->blocknum%>">
+ <INPUT TYPE="submit" NAME="submit" VALUE="Deallocate">
+ </FORM>
+ </TD>
+ <% } else { %>
+ <TD COLSPAN="2">
+ <%=$router->routername%>
+ </TD>
+ <% } %>
+ <% } else { %>
+ <TD>
+ <FORM ACTION="<%=$path%>/allocate.cgi" METHOD="POST">
+ <INPUT TYPE="hidden" NAME="blocknum" VALUE="<%=$block->blocknum%>">
+ <SELECT NAME="routernum" SIZE="1">
+ <% foreach (@router) { %>
+ <OPTION VALUE="<%=$_->routernum %>"><%=$_->routername%></OPTION>
+ <% } %>
+ </SELECT>
+ <INPUT TYPE="submit" NAME="submit" VALUE="Allocate">
+ </FORM>
+ </TD>
+ <TD>
+ <FORM ACTION="<%=$path%>/split.cgi" METHOD="POST">
+ <INPUT TYPE="hidden" NAME="blocknum" VALUE="<%=$block->blocknum%>">
+ <INPUT TYPE="submit" NAME="submit" VALUE="Split">
+ </FORM>
+ </TD>
+ </TR>
+<% }
+ } %>
+ <TR><TD COLSPAN="3"><BR></TD></TR>
+ <TR>
+ <FORM ACTION="<%=$path%>/add.cgi" METHOD="POST">
+ <TD>Gateway/Netmask</TD>
+ <TD>
+ <INPUT TYPE="text" NAME="ip_gateway" SIZE="15">/<INPUT TYPE="text" NAME="ip_netmask" SIZE="2">
+ </TD>
+ <TD>
+ <INPUT TYPE="submit" NAME="submit" VALUE="Add">
+ </TD>
+ </FORM>
+ </TR>
+</TABLE>
+</BODY>
+</HTML>
+
diff --git a/httemplate/browse/agent.cgi b/httemplate/browse/agent.cgi
index cff111c..2eef5bb 100755
--- a/httemplate/browse/agent.cgi
+++ b/httemplate/browse/agent.cgi
@@ -1,15 +1,38 @@
<!-- mason kludge -->
+
<%
-#Begin silliness
-#
-#use FS::UI::CGI;
-#use FS::UI::agent;
-#
-#$ui = new FS::UI::agent;
-#$ui->browse;
-#exit;
-#__END__
-#End silliness
+
+ my %search;
+ if ( $cgi->param('showdisabled')
+ || !dbdef->table('agent')->column('disabled') ) {
+ %search = ();
+ } else {
+ %search = ( 'disabled' => '' );
+ }
+
+ #bad false laziness with search/cust_main.cgi (also needs fixing up for
+ #old mysql)
+ my $ncancelled = "
+ 0 < ( SELECT COUNT(*) FROM cust_pkg
+ WHERE cust_pkg.custnum = cust_main.custnum
+ AND ( cust_pkg.cancel IS NULL
+ OR cust_pkg.cancel = 0
+ )
+ )
+ OR 0 = ( SELECT COUNT(*) FROM cust_pkg
+ WHERE cust_pkg.custnum = cust_main.custnum
+ )
+ ";
+
+ my $ncancelled_sth = dbh->prepare("SELECT COUNT(*) FROM cust_main
+ WHERE agentnum = ?
+ AND ( $ncancelled ) ")
+ or die dbh->errstr;
+
+ my $total_sth = dbh->prepare("SELECT COUNT(*) FROM cust_main
+ WHERE agentnum = ? ")
+ or die dbh->errstr;
+
%>
<%= header('Agent Listing', menubar(
@@ -21,10 +44,20 @@ Agents are resellers of your service. Agents may be limited to a subset of your
full offerings (via their type).<BR><BR>
<A HREF="<%= $p %>edit/agent.cgi"><I>Add a new agent</I></A><BR><BR>
+<% if ( dbdef->table('agent')->column('disabled') ) { %>
+ <%= $cgi->param('showdisabled')
+ ? do { $cgi->param('showdisabled', 0);
+ '( <a href="'. $cgi->self_url. '">hide disabled agents</a> )'; }
+ : do { $cgi->param('showdisabled', 1);
+ '( <a href="'. $cgi->self_url. '">show disabled agents</a> )'; }
+ %>
+<% } %>
+
<%= table() %>
<TR>
- <TH COLSPAN=2>Agent</TH>
+ <TH COLSPAN=<%= ( $cgi->param('showdisabled') || !dbdef->table('agent')->column('disabled') ) ? 2 : 3 %>>Agent</TH>
<TH>Type</TH>
+ <TH>Customers</TH>
<TH><FONT SIZE=-1>Freq.</FONT></TH>
<TH><FONT SIZE=-1>Prog.</FONT></TH>
</TR>
@@ -35,29 +68,44 @@ full offerings (via their type).<BR><BR>
foreach my $agent ( sort {
#$a->getfield('agentnum') <=> $b->getfield('agentnum')
$a->getfield('agent') cmp $b->getfield('agent')
-} qsearch('agent',{}) ) {
- my($hashref)=$agent->hashref;
- my($typenum)=$hashref->{typenum};
- my($agent_type)=qsearchs('agent_type',{'typenum'=>$typenum});
- my($atype)=$agent_type->getfield('atype');
- print <<END;
+} qsearch('agent', \%search ) ) {
+
+ $ncancelled_sth->execute($agent->agentnum) or die $ncancelled_sth->errstr;
+ my $num_ncancelled = $ncancelled_sth->fetchrow_arrayref->[0];
+
+ $total_sth->execute($agent->agentnum) or die $total_sth->errstr;
+ my $num_total = $total_sth->fetchrow_arrayref->[0];
+
+ my $num_cancelled = $num_total - $num_ncancelled;
+
+ my $cust_main_link = $p. 'search/cust_main.cgi?agentnum_on=1&'.
+ 'agentnum='. $agent->agentnum;
+
+%>
+
<TR>
- <TD><A HREF="${p}edit/agent.cgi?$hashref->{agentnum}">
- $hashref->{agentnum}</A></TD>
- <TD><A HREF="${p}edit/agent.cgi?$hashref->{agentnum}">
- $hashref->{agent}</A></TD>
- <TD><A HREF="${p}edit/agent_type.cgi?$typenum">$atype</A></TD>
- <TD>$hashref->{freq}</TD>
- <TD>$hashref->{prog}</TD>
+ <TD><A HREF="<%=$p%>edit/agent.cgi?<%= $agent->agentnum %>">
+ <%= $agent->agentnum %></A></TD>
+<% if ( dbdef->table('agent')->column('disabled')
+ && !$cgi->param('showdisabled') ) { %>
+ <TD><%= $agent->disabled ? 'DISABLED' : '' %></TD>
+<% } %>
+
+ <TD><A HREF="<%=$p%>edit/agent.cgi?<%= $agent->agentnum %>">
+ <%= $agent->agent %></A></TD>
+ <TD><A HREF="<%=$p%>edit/agent_type.cgi?<%= $agent->typenum %>"><%= $agent->agent_type->atype %></A></TD>
+ <TD>
+ <FONT COLOR="#00CC00"><B><%= $num_ncancelled %></B></FONT>
+ <A HREF="<%= $cust_main_link %>&showcancelledcustomers=0">active</A>
+ <BR><FONT COLOR="#FF0000"><B><%= $num_cancelled %></B></FONT>
+ <A HREF="<%= $cust_main_link %>&showcancelledcustomers=1&cancelled=1">cancelled</A>
+ </TD>
+ <TD><%= $agent->freq %></TD>
+ <TD><%= $agent->prog %></TD>
</TR>
-END
-}
+<% } %>
-print <<END;
</TABLE>
</BODY>
</HTML>
-END
-
-%>
diff --git a/httemplate/browse/agent_type.cgi b/httemplate/browse/agent_type.cgi
index 5a84385..5473804 100755
--- a/httemplate/browse/agent_type.cgi
+++ b/httemplate/browse/agent_type.cgi
@@ -17,9 +17,11 @@ agents.<BR><BR>
foreach my $agent_type ( sort {
$a->getfield('typenum') <=> $b->getfield('typenum')
} qsearch('agent_type',{}) ) {
- my($hashref)=$agent_type->hashref;
- my(@type_pkgs)=qsearch('type_pkgs',{'typenum'=> $hashref->{typenum} });
- my($rowspan)=scalar(@type_pkgs);
+ my $hashref = $agent_type->hashref;
+ #more efficient to do this with SQL...
+ my @type_pkgs = grep { $_->part_pkg and ! $_->part_pkg->disabled }
+ qsearch('type_pkgs',{'typenum'=> $hashref->{typenum} });
+ my $rowspan = scalar(@type_pkgs);
$rowspan = int($rowspan/2+0.5) ;
print <<END;
<TR>
diff --git a/httemplate/browse/cust_main_county.cgi b/httemplate/browse/cust_main_county.cgi
index 9916060..1e0e088 100755
--- a/httemplate/browse/cust_main_county.cgi
+++ b/httemplate/browse/cust_main_county.cgi
@@ -21,9 +21,10 @@ print '<BR><BR>'. &table(). <<END;
<TH><FONT SIZE=-1>Country</FONT></TH>
<TH><FONT SIZE=-1>State</FONT></TH>
<TH>County</TH>
- <TH>Taxclass</TH>
+ <TH>Taxclass<BR><FONT SIZE=-1>(per-package classification)</FONT></TH>
+ <TH>Tax name<BR><FONT SIZE=-1>(printed on invoices)</FONT></TH>
<TH><FONT SIZE=-1>Tax</FONT></TH>
- <TH><FONT SIZE=-1>Exempt<BR>per<BR>month</TH>
+ <TH><FONT SIZE=-1>Exemption</TH>
</TR>
END
@@ -53,7 +54,9 @@ END
last if $hashref->{country} ne $regions[$i+$j]->country
|| $hashref->{state} ne $regions[$i+$j]->state
|| $hashref->{tax} != $regions[$i+$j]->tax
- || $hashref->{exempt_amount} != $regions[$i+$j]->exempt_amount;
+ || $hashref->{exempt_amount} != $regions[$i+$j]->exempt_amount
+ || $hashref->{setuptax} ne $regions[$i+$j]->setuptax
+ || $hashref->{recurtax} ne $regions[$i+$j]->recurtax;
}
my $newsup=0;
@@ -111,10 +114,22 @@ END
}
print "</TD>";
+ print "<TD";
+ if ( $hashref->{taxname} ) {
+ print ' BGCOLOR="#ffffff">'. $hashref->{taxname};
+ } else {
+ print ' BGCOLOR="#cccccc">Tax';
+ }
+ print "</TD>";
+
print "<TD BGCOLOR=\"#ffffff\">$hashref->{tax}%</TD>".
- '<TD BGCOLOR="#ffffff">$'.
- sprintf("%.2f", $hashref->{exempt_amount} || 0). '</TD>'.
- '</TR>';
+ '<TD BGCOLOR="#ffffff">';
+ print '$'. sprintf("%.2f", $hashref->{exempt_amount} ).
+ '&nbsp;per&nbsp;month<BR>'
+ if $hashref->{exempt_amount} > 0;
+ print 'Setup&nbsp;fee<BR>' if $hashref->{setuptax} =~ /^Y$/i;
+ print 'Recurring&nbsp;fee<BR>' if $hashref->{recurtax} =~ /^Y$/i;
+ print '</TD></TR>';
}
diff --git a/httemplate/browse/cust_pay_batch.cgi b/httemplate/browse/cust_pay_batch.cgi
index 608a58d..3420e97 100755
--- a/httemplate/browse/cust_pay_batch.cgi
+++ b/httemplate/browse/cust_pay_batch.cgi
@@ -1,10 +1,38 @@
<!-- mason kludge -->
+<%= header("Pending credit card batch", menubar( 'Main Menu' => $p,)) %>
+
+<FORM ACTION="<%=$p%>misc/download-batch.cgi" METHOD="POST">
+Download batch in format <SELECT NAME="format">
+<OPTION VALUE="csv-td_canada_trust-merchant_pc_batch">CSV file for TD Canada Trust Merchant PC Batch</OPTION>
+</SELECT><INPUT TYPE="submit" VALUE="Download"></FORM>
+<BR><BR>
+
+<FORM ACTION="<%=$p%>misc/upload-batch.cgi" METHOD="POST" ENCTYPE="multipart/form-data">
+Upload results<BR>
+Filename <INPUT TYPE="file" NAME="batch_results"><BR>
+Format <SELECT NAME="format">
+<OPTION VALUE="csv-td_canada_trust-merchant_pc_batch">CSV results from TD Canada Trust Merchant PC Batch</OPTION>
+</SELECT><BR>
+<INPUT TYPE="submit" VALUE="Upload"></FORM>
+<BR>
+
<%
+ my $statement = "SELECT SUM(amount) from cust_pay_batch";
+ my $sth = dbh->prepare($statement) or die dbh->errstr. "doing $statement";
+ $sth->execute or die "Error executing \"$statement\": ". $sth->errstr;
+ my $total = $sth->fetchrow_arrayref->[0];
-print header("Pending credit card batch", menubar(
- 'Main Menu' => $p,
-# 'Add new referral' => "../edit/part_referral.cgi",
-)), &table(), <<END;
+ my $c_statement = "SELECT COUNT(*) from cust_pay_batch";
+ my $c_sth = dbh->prepare($c_statement)
+ or die dbh->errstr. "doing $c_statement";
+ $c_sth->execute or die "Error executing \"$c_statement\": ". $c_sth->errstr;
+ my $cards = $c_sth->fetchrow_arrayref->[0];
+%>
+<%= $cards %> credit card payments batched<BR>
+$<%= sprintf("%.2f", $total) %> total in pending batch<BR>
+
+<BR>
+<%= &table() %>
<TR>
<TH>#</TH>
<TH><font size=-1>inv#</font></TH>
@@ -14,39 +42,35 @@ print header("Pending credit card batch", menubar(
<TH>Exp</TH>
<TH>Amount</TH>
</TR>
-END
-
-foreach my $cust_pay_batch ( sort {
- $a->getfield('paybatchnum') <=> $b->getfield('paybatchnum')
-} qsearch('cust_pay_batch',{}) ) {
-# my $date = time2str( "%a %b %e %T %Y", $queue->_date );
-# my $status = $hashref->{status};
-# if ( $status eq 'failed' || $status eq 'locked' ) {
-# $status .=
-# qq! ( <A HREF="$p/edit/cust_pay_batch.cgi?jobnum=$jobnum&action=new">retry</A> |!.
-# qq! <A HREF="$p/edit/cust_pay_batch.cgi?jobnum$jobnum&action=del">remove </A> )!;
-# }
- my $cardnum = $cust_pay_batch->{cardnum};
- $cardnum =~ s/.{4}$/xxxx/;
- print <<END;
+
+<%
+foreach my $cust_pay_batch ( sort { $a->paybatchnum <=> $b->paybatchnum }
+ qsearch('cust_pay_batch', {} )
+) {
+ my $cardnum = $cust_pay_batch->cardnum;
+ #$cardnum =~ s/.{4}$/xxxx/;
+ $cardnum = 'x'x(length($cardnum)-4). substr($cardnum,(length($cardnum)-4));
+
+ $cust_pay_batch->exp =~ /^\d{2}(\d{2})[\/\-](\d+)[\/\-]\d+$/;
+ my( $mon, $year ) = ( $2, $1 );
+ $mon = "0$mon" if $mon < 10;
+ my $exp = "$mon/$year";
+
+%>
+
<TR>
- <TD>$cust_pay_batch->{paybatchnum}</TD>
- <TD><A HREF="../view/cust_bill.cgi?$cust_pay_batch->{invnum}">$cust_pay_batch->{invnum}</TD>
- <TD><A HREF="../view/cust_main.cgi?$cust_pay_batch->{custnum}">$cust_pay_batch->{custnum}</TD>
- <TD>$cust_pay_batch->{last}, $cust_pay_batch->{last}</TD>
- <TD>$cust_pay_batch->{payname}</TD>
- <TD>$cardnum</TD>
- <TD>$cust_pay_batch->{exp}</TD>
- <TD align="right">\$$cust_pay_batch->{amount}</TD>
+ <TD><%= $cust_pay_batch->paybatchnum %></TD>
+ <TD><A HREF="../view/cust_bill.cgi?<%= $cust_pay_batch->invnum %>"><%= $cust_pay_batch->invnum %></TD>
+ <TD><A HREF="../view/cust_main.cgi?<%= $cust_pay_batch->custnum %>"><%= $cust_pay_batch->custnum %></TD>
+ <TD><%= $cust_pay_batch->get('last'). ', '. $cust_pay_batch->first %></TD>
+ <TD><%= $cust_pay_batch->payname %></TD>
+ <TD><%= $cardnum %></TD>
+ <TD><%= $exp %></TD>
+ <TD align="right">$<%= $cust_pay_batch->amount %></TD>
</TR>
-END
-}
+<% } %>
-print <<END;
</TABLE>
</BODY>
</HTML>
-END
-
-%>
diff --git a/httemplate/browse/generic.cgi b/httemplate/browse/generic.cgi
new file mode 100644
index 0000000..9ac0f23
--- /dev/null
+++ b/httemplate/browse/generic.cgi
@@ -0,0 +1,46 @@
+<%
+
+use FS::Record qw(qsearch dbdef);
+use DBIx::DBSchema;
+use DBIx::DBSchema::Table;
+
+my $error;
+my $p2 = popurl(2);
+my ($table) = $cgi->keywords;
+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";
+print header("Browse $table", menubar('Main Menu' => $p));
+
+my @rec = qsearch($table, {});
+my @col = $dbdef_table->columns;
+
+if ($cgi->param('error')) { %>
+ <FONT SIZE="+1" COLOR="#ff0000">Error: <%=$cgi->param('error')%></FONT>
+ <BR><BR>
+<% }
+%>
+<A HREF="<%=$p2%>edit/<%=$table%>.cgi"><I>Add a new <%=$table%></I></A><BR><BR>
+
+<%=table()%>
+<TH>
+<% foreach (grep { $_ ne $pkey } @col) {
+ %><TD><%=$_%></TD>
+ <% } %>
+</TH>
+<% foreach $rec (sort {$a->getfield($pkey) cmp $b->getfield($pkey) } @rec) {
+ %>
+ <TR>
+ <TD>
+ <A HREF="<%=$p2%>edit/<%=$table%>.cgi?<%=$rec->getfield($pkey)%>">
+ <%=$rec->getfield($pkey)%></A> </TD> <%
+ foreach $col (grep { $_ ne $pkey } @col) { %>
+ <TD><%=$rec->getfield($col)%></TD> <% } %>
+ </A>
+ </TR>
+<% } %>
+</TABLE>
+</BODY>
+</HTML>
+
diff --git a/httemplate/browse/part_export.cgi b/httemplate/browse/part_export.cgi
index 76662e0..79c57ae 100755
--- a/httemplate/browse/part_export.cgi
+++ b/httemplate/browse/part_export.cgi
@@ -26,7 +26,7 @@ function part_export_areyousure(href) {
<%= itable() %>
<% my %opt = $part_export->options;
foreach my $opt ( keys %opt ) { %>
- <TR><TD><%= $opt %></TD><TD><%= $opt{$opt} %></TD></TR>
+ <TR><TD><%= $opt %></TD><TD><%= encode_entities($opt{$opt}) %></TD></TR>
<% } %>
</TABLE>
</TD>
diff --git a/httemplate/browse/part_pkg.cgi b/httemplate/browse/part_pkg.cgi
index 58422c6..180f182 100755
--- a/httemplate/browse/part_pkg.cgi
+++ b/httemplate/browse/part_pkg.cgi
@@ -11,15 +11,50 @@ if ( $cgi->param('showdisabled') ) {
my @part_pkg = qsearch('part_pkg', \%search );
my $total = scalar(@part_pkg);
+my $sortby;
+my %num_active_cust_pkg = ();
+my( $suspended_sth, $canceled_sth ) = ( '', '' );
+if ( $cgi->param('active') ) {
+ my $active_sth = dbh->prepare(
+ 'SELECT COUNT(*) FROM cust_pkg WHERE pkgpart = ?'.
+ ' AND ( cancel IS NULL OR cancel = 0 )'.
+ ' AND ( susp IS NULL OR susp = 0 )'
+ ) or die dbh->errstr;
+ foreach my $part_pkg ( @part_pkg ) {
+ $active_sth->execute($part_pkg->pkgpart) or die $active_sth->errstr;
+ $num_active_cust_pkg{$part_pkg->pkgpart} =
+ $active_sth->fetchrow_arrayref->[0];
+ }
+ $sortby = sub {
+ $num_active_cust_pkg{$b->pkgpart} <=> $num_active_cust_pkg{$a->pkgpart};
+ };
+
+ $suspended_sth = dbh->prepare(
+ 'SELECT COUNT(*) FROM cust_pkg WHERE pkgpart = ?'.
+ ' AND ( cancel IS NULL OR cancel = 0 )'.
+ ' AND susp IS NOT NULL AND susp != 0'
+ ) or die dbh->errstr;
+
+ $canceled_sth = dbh->prepare(
+ 'SELECT COUNT(*) FROM cust_pkg WHERE pkgpart = ?'.
+ ' AND cancel IS NOT NULL AND cancel != 0'
+ ) or die dbh->errstr;
+
+} else {
+ $sortby = \*pkgpart_sort;
+}
+
%>
<%= header("Package Definition Listing",menubar( 'Main Menu' => $p )) %>
-One or more services are grouped together into a package and given pricing
-information. Customers purchase packages rather than purchase services
-directly.<BR><BR>
-<A HREF="<%= $p %>edit/part_pkg.cgi"><I>Add a new package definition</I></A>
-<BR><BR>
+<% unless ( $cgi->param('active') ) { %>
+ One or more service definitions are grouped together into a package
+ definition and given pricing information. Customers purchase packages
+ rather than purchase services directly.<BR><BR>
+ <A HREF="<%= $p %>edit/part_pkg.cgi"><I>Add a new package definition</I></A>
+ <BR><BR>
+<% } %>
-<%= $total %> packages
+<%= $total %> package definitions
<%
if ( $cgi->param('showdisabled') ) {
$cgi->param('showdisabled', 0);
@@ -34,17 +69,21 @@ print &table(), <<END;
<TR>
<TH COLSPAN=$colspan>Package</TH>
<TH>Comment</TH>
+END
+print ' <TH><FONT SIZE=-1>Customer<BR>packages</FONT></TH>'
+ if $cgi->param('active');
+print <<END;
<TH><FONT SIZE=-1>Freq.</FONT></TH>
<TH><FONT SIZE=-1>Plan</FONT></TH>
<TH><FONT SIZE=-1>Data</FONT></TH>
<TH>Service</TH>
<TH><FONT SIZE=-1>Quan.</FONT></TH>
- </TR>
END
+print '<TH><FONT SIZE=-1>Primary</FONT></TH>'
+ if dbdef->table('pkg_svc')->column('primary_svc');
+print '</TR>';
-foreach my $part_pkg ( sort {
- $a->getfield('pkgpart') <=> $b->getfield('pkgpart')
-} @part_pkg ) {
+foreach my $part_pkg ( sort $sortby @part_pkg ) {
my($hashref)=$part_pkg->hashref;
my(@pkg_svc)=grep $_->getfield('quantity'),
qsearch('pkg_svc',{'pkgpart'=> $hashref->{pkgpart} });
@@ -73,6 +112,27 @@ END
print <<END;
<TD ROWSPAN=$rowspan><A HREF="${p}edit/part_pkg.cgi?$hashref->{pkgpart}">$hashref->{pkg}</A></TD>
<TD ROWSPAN=$rowspan>$hashref->{comment}</TD>
+END
+ if ( $cgi->param('active') ) {
+ print " <TD ROWSPAN=$rowspan>";
+ print '<FONT COLOR="#00CC00"><B>'.
+ $num_active_cust_pkg{$hashref->{'pkgpart'}}.
+ qq!</B></FONT>&nbsp;<A HREF="${p}search/cust_pkg.cgi?magic=active;pkgpart=$hashref->{pkgpart}">active</A><BR>!;
+
+ $suspended_sth->execute( $part_pkg->pkgpart ) or die $suspended_sth->errstr;
+ my $num_suspended = $suspended_sth->fetchrow_arrayref->[0];
+ print '<FONT COLOR="#FF9900"><B>'. $num_suspended.
+ qq!</B></FONT>&nbsp;<A HREF="${p}search/cust_pkg.cgi?magic=suspended;pkgpart=$hashref->{pkgpart}">suspended</A><BR>!;
+
+ $canceled_sth->execute( $part_pkg->pkgpart ) or die $canceled_sth->errstr;
+ my $num_canceled = $canceled_sth->fetchrow_arrayref->[0];
+ print '<FONT COLOR="#FF0000"><B>'. $num_canceled.
+ qq!</B></FONT>&nbsp;<A HREF="${p}search/cust_pkg.cgi?magic=canceled;pkgpart=$hashref->{pkgpart}">canceled</A>!;
+
+
+ print '</TD>';
+ }
+ print <<END;
<TD ROWSPAN=$rowspan>$hashref->{freq}</TD>
<TD ROWSPAN=$rowspan>$hashref->{plan}</TD>
<TD ROWSPAN=$rowspan>$plandata</TD>
@@ -85,7 +145,13 @@ END
my($part_svc) = qsearchs('part_svc',{'svcpart'=> $svcpart });
print $n,qq!<TD><A HREF="${p}edit/part_svc.cgi?$svcpart">!,
$part_svc->getfield('svc'),"</A></TD><TD>",
- $pkg_svc->getfield('quantity'),"</TD></TR>\n";
+ $pkg_svc->getfield('quantity'),"</TD>";
+ if ( dbdef->table('pkg_svc')->column('primary_svc') ) {
+ print '<TD>';
+ print 'PRIMARY' if $pkg_svc->primary_svc =~ /^Y/i;
+ print '</TD>';
+ }
+ print "</TR>\n";
$n="<TR>";
}
@@ -99,4 +165,9 @@ print <<END;
</BODY>
</HTML>
END
+
+sub pkgpart_sort {
+ $a->pkgpart <=> $b->pkgpart;
+}
+
%>
diff --git a/httemplate/browse/part_referral.cgi b/httemplate/browse/part_referral.cgi
index 084c21b..3f59abc 100755
--- a/httemplate/browse/part_referral.cgi
+++ b/httemplate/browse/part_referral.cgi
@@ -8,31 +8,57 @@ Where a customer heard about your service. Tracked for informational purposes.
<A HREF="<%= $p %>edit/part_referral.cgi"><I>Add a new advertising source</I></A>
<BR><BR>
+<%
+ my $today = timelocal(0, 0, 0, (localtime(time))[3..5] );
+ my %past;
+ tie %past, 'Tie::IxHash',
+ 'Today' => 0,
+ 'Past week' => 518400, # 60sec * 60min * 24hrs * 6days
+ 'Past 30 days' => 2505600, # 60sec * 60min * 24hrs * 29days
+ 'Past 60 days' => 5097600, # 60sec * 60min * 24hrs * 29days
+ 'Past 90 days' => 7689600, # 60sec * 60min * 24hrs * 29days
+ 'Past 6 months' => 15724800, # 60sec * 60min * 24hrs * 182days
+ 'Past year' => 31486000, # 60sec * 60min * 24hrs * 364days
+ 'Total' => $today,
+ ;
+
+ my $sth = dbh->prepare("SELECT COUNT(*) FROM h_cust_main
+ WHERE history_action = 'insert'
+ AND refnum = ?
+ AND history_date > ? ")
+ or die dbh->errstr;
+%>
+
<%= table() %>
<TR>
- <TH COLSPAN=2>Advertising source</TH>
+ <TH COLSPAN=2 ROWSPAN=2>Advertising source</TH>
+ <TH COLSPAN=<%= scalar(keys %past) %>>Customers</TH>
+</TR>
+<% for my $period ( keys %past ) { %>
+ <TH><FONT SIZE=-1><%= $period %></FONT></TH>
+<% } %>
</TR>
<%
foreach my $part_referral ( sort {
$a->getfield('refnum') <=> $b->getfield('refnum')
} qsearch('part_referral',{}) ) {
- my($hashref)=$part_referral->hashref;
- print <<END;
+%>
<TR>
- <TD><A HREF="${p}edit/part_referral.cgi?$hashref->{refnum}">
- $hashref->{refnum}</A></TD>
- <TD><A HREF="${p}edit/part_referral.cgi?$hashref->{refnum}">
- $hashref->{referral}</A></TD>
+ <TD><A HREF="<%= $p %>edit/part_referral.cgi?<%= $part_referral->refnum %>">
+ <%= $part_referral->refnum %></A></TD>
+ <TD><A HREF="<%= $p %>edit/part_referral.cgi?<%= $part_referral->refnum %>">
+ <%= $part_referral->referral %></A></TD>
+ <% for my $period ( values %past ) {
+ $sth->execute($part_referral->refnum, $today-$period)
+ or die $sth->errstr;
+ my $number = $sth->fetchrow_arrayref->[0];
+ %>
+ <TD ALIGN="right"><%= $number %></TD>
+ <% } %>
</TR>
-END
-
-}
+<% } %>
-print <<END;
</TABLE>
</BODY>
</HTML>
-END
-
-%>
diff --git a/httemplate/browse/part_svc.cgi b/httemplate/browse/part_svc.cgi
index 9fb359d..ef0de13 100755
--- a/httemplate/browse/part_svc.cgi
+++ b/httemplate/browse/part_svc.cgi
@@ -13,6 +13,20 @@ my @part_svc =
qsearch('part_svc', \%search );
my $total = scalar(@part_svc);
+my %num_active_cust_svc = ();
+if ( $cgi->param('active') ) {
+ my $active_sth = dbh->prepare(
+ 'SELECT COUNT(*) FROM cust_svc WHERE svcpart = ?'
+ ) or die dbh->errstr;
+ foreach my $part_svc ( @part_svc ) {
+ $active_sth->execute($part_svc->svcpart) or die $active_sth->errstr;
+ $num_active_cust_svc{$part_svc->svcpart} =
+ $active_sth->fetchrow_arrayref->[0];
+ }
+ @part_svc = sort { $num_active_cust_svc{$b->svcpart} <=>
+ $num_active_cust_svc{$a->svcpart} } @part_svc;
+}
+
%>
<%= header('Service Definition Listing', menubar( 'Main Menu' => $p) ) %>
@@ -23,16 +37,18 @@ function part_export_areyousure(href) {
}
</SCRIPT>
- Services are items you offer to your customers.<BR><BR>
+ Service definitions are the templates for items you offer to your customers.<BR><BR>
-<FORM METHOD="POST" ACTION="<%= $p %>edit/part_svc.cgi"><A HREF="<%= $p %>edit/part_svc.cgi"><I>Add a new service definition</I></A>&nbsp;or&nbsp;<SELECT NAME="clone"><OPTION></OPTION>
+<FORM METHOD="POST" ACTION="<%= $p %>edit/part_svc.cgi">
+<A HREF="<%= $p %>edit/part_svc.cgi"><I>Add a new service definition</I></A><% if ( @part_svc ) { %>&nbsp;or&nbsp;<SELECT NAME="clone"><OPTION></OPTION>
<% foreach my $part_svc ( @part_svc ) { %>
<OPTION VALUE="<%= $part_svc->svcpart %>"><%= $part_svc->svc %></OPTION>
<% } %>
</SELECT><INPUT TYPE="submit" VALUE="Clone existing service">
+<% } %>
</FORM><BR>
-<%= $total %> services
+<%= $total %> service definitions
<%= $cgi->param('showdisabled')
? do { $cgi->param('showdisabled', 0);
'( <a href="'. $cgi->self_url. '">hide disabled services</a> )'; }
@@ -43,6 +59,9 @@ function part_export_areyousure(href) {
<TR>
<TH COLSPAN=<%= $cgi->param('showdisabled') ? 2 : 3 %>>Service</TH>
<TH>Table</TH>
+<% if ( $cgi->param('active') ) { %>
+ <TH><FONT SIZE=-1>Customer<BR>Services</FONT></TH>
+<% } %>
<TH>Export</TH>
<TH>Field</TH>
<TH COLSPAN=2>Modifier</TH>
@@ -51,12 +70,13 @@ function part_export_areyousure(href) {
<% foreach my $part_svc ( @part_svc ) {
my $hashref = $part_svc->hashref;
my $svcdb = $hashref->{svcdb};
- my @dfields = fields($svcdb);
+ my $svc_x = "FS::$svcdb"->new( { svcpart => $part_svc->svcpart } );
+ my @dfields = $svc_x->fields;
push @dfields, 'usergroup' if $svcdb eq 'svc_acct'; #kludge
my @fields =
- grep { $_ ne 'svcnum' && $part_svc->part_svc_column($_)->columnflag }
- @dfields;
-
+ grep { $svc_x->pvf($_)
+ or $_ ne 'svcnum' && $part_svc->part_svc_column($_)->columnflag }
+ @dfields ;
my $rowspan = scalar(@fields) || 1;
my $url = "${p}edit/part_svc.cgi?$hashref->{svcpart}";
%>
@@ -72,6 +92,11 @@ function part_export_areyousure(href) {
<%= $hashref->{svc} %></A></TD>
<TD ROWSPAN=<%= $rowspan %>>
<%= $hashref->{svcdb} %></TD>
+<% if ( $cgi->param('active') ) { %>
+ <TD ROWSPAN=<%= $rowspan %>>
+ <FONT COLOR="#00CC00"><B><%= $num_active_cust_svc{$hashref->{svcpart}} %></B></FONT>&nbsp;<A HREF="<%=$p%>search/<%= $hashref->{svcdb} %>.cgi?svcpart=<%= $hashref->{svcpart} %>">active</A>
+ </TD>
+<% } %>
<TD ROWSPAN=<%= $rowspan %>><%= itable() %>
<%
# my @part_export =
@@ -82,7 +107,7 @@ map { qsearchs('part_export', { exportnum => $_->exportnum } ) } qsearch('export
) {
%>
<TR>
- <TD><A HREF="<%= $p %>edit/part_export.cgi?<%= $part_export->exportnum %>"><%= $part_export->exporttype %>&nbsp;to&nbsp;<%= $part_export->machine %></A></TD></TR>
+ <TD><A HREF="<%= $p %>edit/part_export.cgi?<%= $part_export->exportnum %>"><%= $part_export->exportnum %>:&nbsp;<%= $part_export->exporttype %>&nbsp;to&nbsp;<%= $part_export->machine %></A></TD></TR>
<% } %>
</TABLE></TD>
@@ -94,6 +119,7 @@ map { qsearchs('part_export', { exportnum => $_->exportnum } ) } qsearch('export
<% if ( $flag eq "D" ) { print "Default"; }
elsif ( $flag eq "F" ) { print "Fixed"; }
+ elsif ( not $flag ) { }
else { print "(Unknown!)"; }
%>
</TD><TD><%= $part_svc->part_svc_column($field)->columnvalue%></TD>
diff --git a/httemplate/browse/part_virtual_field.cgi b/httemplate/browse/part_virtual_field.cgi
new file mode 100644
index 0000000..9bb5c13
--- /dev/null
+++ b/httemplate/browse/part_virtual_field.cgi
@@ -0,0 +1,39 @@
+<%= header('Virtual field definitions', menubar('Main Menu' => $p)) %>
+<%
+
+my %pvfs;
+my $block;
+my $p2 = popurl(2);
+my $dbtable;
+
+foreach (qsearch('part_virtual_field', {})) {
+ push @{ $pvfs{$_->dbtable} }, $_;
+}
+%>
+
+<% if ($cgi->param('error')) { %>
+ <FONT SIZE="+1" COLOR="#ff0000">Error: <%=$cgi->param('error')%></FONT>
+ <BR><BR>
+<% } %>
+
+<A HREF="<%=$p2%>edit/part_virtual_field.cgi"><I>Add a new field</I></A><BR><BR>
+
+<% foreach $dbtable (sort { $a cmp $b } keys (%pvfs)) { %>
+<H3><%=$dbtable%></H3>
+
+<%=table()%>
+<TH><TD>Field name</TD><TD>Description</TD></TH>
+<% foreach $pvf (sort {$a->name cmp $b->name} @{ $pvfs{$dbtable} }) { %>
+ <TR>
+ <TD></TD>
+ <TD>
+ <A HREF="<%=$p2%>edit/part_virtual_field.cgi?<%=$pvf->vfieldpart%>">
+ <%=$pvf->name%></A></TD>
+ <TD><%=$pvf->label%></TD>
+ </TR>
+<% } %>
+</TABLE>
+<% } %>
+</BODY>
+</HTML>
+
diff --git a/httemplate/browse/router.cgi b/httemplate/browse/router.cgi
new file mode 100644
index 0000000..feee4ec
--- /dev/null
+++ b/httemplate/browse/router.cgi
@@ -0,0 +1,38 @@
+<%= header('Routers', menubar('Main Menu' => $p)) %>
+<%
+
+my @router = qsearch('router', {});
+my $p2 = popurl(2);
+
+%>
+
+<% if ($cgi->param('error')) { %>
+ <FONT SIZE="+1" COLOR="#ff0000">Error: <%=$cgi->param('error')%></FONT>
+ <BR><BR>
+<% } %>
+
+<A HREF="<%=$p2%>edit/router.cgi"><I>Add a new router</I></A><BR><BR>
+
+<%=table()%>
+ <TR>
+ <TD><B>Router name</B></TD>
+ <TD><B>Address block(s)</B></TD>
+ </TR>
+<% foreach my $router (sort {$a->routernum <=> $b->routernum} @router) {
+ my @addr_block = $router->addr_block;
+%>
+ <TR>
+ <TD ROWSPAN="<%=scalar(@addr_block)%>">
+ <A HREF="<%=$p2%>edit/router.cgi?<%=$router->routernum%>"><%=$router->routername%></A>
+ </TD>
+ <TD>
+ <% foreach my $block ( @addr_block ) { %>
+ <%=$block->NetAddr%></BR>
+ <% } %>
+ </TD>
+ </TR>
+<% } %>
+</TABLE>
+</BODY>
+</HTML>
+
diff --git a/httemplate/browse/svc_acct_pop.cgi b/httemplate/browse/svc_acct_pop.cgi
index e890f07..44cda81 100755
--- a/httemplate/browse/svc_acct_pop.cgi
+++ b/httemplate/browse/svc_acct_pop.cgi
@@ -1,9 +1,10 @@
<!-- mason kludge -->
<%
-
-print header('Access Number Listing', menubar(
- 'Main Menu' => $p,
-)) %>
+ my $accounts_sth = dbh->prepare("SELECT COUNT(*) FROM svc_acct
+ WHERE popnum = ? ")
+ or die dbh->errstr;
+%>
+<%= header('Access Number Listing', menubar( 'Main Menu' => $p )) %>
Points of Presence<BR><BR>
<A HREF="<%= $p %>edit/svc_acct_pop.cgi"><I>Add new Access Number</I></A><BR><BR>
<%= table() %>
@@ -14,6 +15,7 @@ Points of Presence<BR><BR>
<TH>Area code</TH>
<TH>Exchange</TH>
<TH>Local</TH>
+ <TH>Accounts</TH>
</TR>
<%
@@ -22,32 +24,40 @@ foreach my $svc_acct_pop ( sort {
$a->state cmp $b->state || $a->city cmp $b->city
|| $a->ac <=> $b->ac || $a->exch <=> $b->exch || $a->loc <=> $b->loc
} qsearch('svc_acct_pop',{}) ) {
- my($hashref)=$svc_acct_pop->hashref;
- print <<END;
+
+ my $svc_acct_pop_link = $p . 'edit/svc_acct_pop.cgi?'. $svc_acct_pop->popnum;
+
+ $accounts_sth->execute($svc_acct_pop->popnum) or die $accounts_sth->errstr;
+ my $num_accounts = $accounts_sth->fetchrow_arrayref->[0];
+
+ my $svc_acct_link = $p. 'search/svc_acct.cgi?popnum='. $svc_acct_pop->popnum;
+
+%>
<TR>
- <TD><A HREF="${p}edit/svc_acct_pop.cgi?$hashref->{popnum}">
- $hashref->{popnum}</A></TD>
- <TD><A HREF="${p}edit/svc_acct_pop.cgi?$hashref->{popnum}">
- $hashref->{city}</A></TD>
- <TD><A HREF="${p}edit/svc_acct_pop.cgi?$hashref->{popnum}">
- $hashref->{state}</A></TD>
- <TD><A HREF="${p}edit/svc_acct_pop.cgi?$hashref->{popnum}">
- $hashref->{ac}</A></TD>
- <TD><A HREF="${p}edit/svc_acct_pop.cgi?$hashref->{popnum}">
- $hashref->{exch}</A></TD>
- <TD><A HREF="${p}edit/svc_acct_pop.cgi?$hashref->{popnum}">
- $hashref->{loc}</A></TD>
+ <TD><A HREF="<%= $svc_acct_pop_link %>">
+ <%= $svc_acct_pop->popnum %></A></TD>
+ <TD><A HREF="<%= $svc_acct_pop_link %>">
+ <%= $svc_acct_pop->city %></A></TD>
+ <TD><A HREF="<%= $svc_acct_pop_link %>">
+ <%= $svc_acct_pop->state %></A></TD>
+ <TD><A HREF="<%= $svc_acct_pop_link %>">
+ <%= $svc_acct_pop->ac %></A></TD>
+ <TD><A HREF="<%= $svc_acct_pop_link %>">
+ <%= $svc_acct_pop->exch %></A></TD>
+ <TD><A HREF="<%= $svc_acct_pop_link %>">
+ <%= $svc_acct_pop->loc %></A></TD>
+ <TD>
+ <FONT COLOR="#00CC00"><B><%= $num_accounts %></B></FONT>
+ <% if ( $num_accounts ) { %><A HREF="<%= $svc_acct_link %>"><% } %>
+ active
+ <% if ( $num_accounts ) { %></A><% } %>
+ </TD>
</TR>
-END
+<% } %>
-}
-
-print <<END;
<TR>
</TR>
</TABLE>
</BODY>
</HTML>
-END
-%>
diff --git a/httemplate/config/config-view.cgi b/httemplate/config/config-view.cgi
index bafe5a8..9a00067 100644
--- a/httemplate/config/config-view.cgi
+++ b/httemplate/config/config-view.cgi
@@ -5,13 +5,13 @@
<% my $conf = new FS::Conf; my @config_items = $conf->config_items; %>
<% foreach my $section ( qw(required billing username password UI session
- shell mail apache BIND
+ shell BIND
),
'', 'deprecated') { %>
<A NAME="<%= $section || 'unclassified' %>"></A>
<FONT SIZE="-2">
<% foreach my $nav_section ( qw(required billing username password UI session
- shell mail apache BIND
+ shell BIND
),
'', 'deprecated') { %>
<% if ( $section eq $nav_section ) { %>
diff --git a/httemplate/config/config.cgi b/httemplate/config/config.cgi
index fd9a829..409869e 100644
--- a/httemplate/config/config.cgi
+++ b/httemplate/config/config.cgi
@@ -25,13 +25,13 @@ function SafeOnsubmit() {
<form name="OneTrueForm" action="config-process.cgi" METHOD="POST" onSubmit="SafeOnsubmit()">
<% foreach my $section ( qw(required billing username password UI session
- shell mail apache BIND
+ shell BIND
),
'', 'deprecated') { %>
<A NAME="<%= $section || 'unclassified' %>"></A>
<FONT SIZE="-2">
<% foreach my $nav_section ( qw(required billing username password UI session
- shell mail apache BIND
+ shell BIND
),
'', 'deprecated') { %>
<% if ( $section eq $nav_section ) { %>
diff --git a/httemplate/docs/billing.html b/httemplate/docs/billing.html
index c78a87f..1d6f8c4 100644
--- a/httemplate/docs/billing.html
+++ b/httemplate/docs/billing.html
@@ -11,7 +11,7 @@
<li>Credit card decline alerts: Customize the <a href="../config/config.cgi#declinetemplate">declinetemplate</a> configuration option and set the <a href="../config/config.cgi#emaildecline">emaildecline</a> configuration option.
<li>Optional: Invoice template customization
<ul>
- <li>See the <a href="http://search.cpan.org/doc/MJD/Text-Template-1.42/Template.pm">Text::Template</a> documentation for details on the substitution language.
+ <li>See the <a href="http://search.cpan.org/~mjd/Text-Template/lib/Text/Template.pm">Text::Template</a> documentation for details on the substitution language.
<li>You <b>must</b> call the invoice_lines() function at least once - pass it a number of lines, and it returns a list of array references, each of two elements: a service description column, and a price column. Alternatively, call invoice_lines() with no arguments, and pagination will be disabled - all invoice line items will print on one page, with no padding (recommended for email invoices).
<li>In addition, the following variables are available:
<ul>
diff --git a/httemplate/docs/cvv2.html b/httemplate/docs/cvv2.html
new file mode 100644
index 0000000..fe8a17f
--- /dev/null
+++ b/httemplate/docs/cvv2.html
@@ -0,0 +1,25 @@
+<HTML>
+ <HEAD>
+ <TITLE>
+ CVV2 information
+ </TITLE>
+ </HEAD>
+ <BODY BGCOLOR="#e8e8e8">
+ The CVV2 number (also called CVC2 or CID) is a three- or four-digit
+ security code used to reduce credit card fraud.<BR><BR>
+ <TABLE BORDER=0 CELLSPACING=4>
+ <TR>
+ <TH>Visa / MasterCard / Discover</TH>
+ <TH>American Express</TH>
+ </TR>
+ <TR>
+ <TD>
+ <IMG BORDER=0 ALT="Visa/MasterCard/Discover" SRC="../images/cvv2.png">
+ </TD>
+ <TD>
+ <IMG BORDER=0 ALT="American Express" SRC="../images/cvv2_amex.png">
+ </TD>
+ </TABLE>
+ <CENTER><A HREF="javascript:close()">(close window)</A></CENTER>
+ </BODY>
+</HTML>
diff --git a/httemplate/docs/ieak.html b/httemplate/docs/ieak.html
new file mode 100644
index 0000000..00c5342
--- /dev/null
+++ b/httemplate/docs/ieak.html
@@ -0,0 +1,75 @@
+<pre>
+this is incomplete
+mostly it should be merged into signup.html and fs_signup/ieak.template
+
+- download and install the IEAK from
+ http://www.microsoft.com/windows/ieak/default.asp
+
+- Good examples may be found in
+ C:\Program Files\IEAK\toolkit\isp\server\ICW\signup\perl\signup08.pl
+ C:\Program Files\IEAK\toolkit\isp\server\ICW\reconfig\perl\reconfig04.pl
+ C:\Program Files\IEAK6\toolkit\isp\servless\basic\sample.ins
+ C:\Program Files\IEAK6\toolkit\isp\servless\advanced\4567.ins
+ C:\Program Files\IEAK6\toolkit\isp\servless\advanced\4568.ins
+ C:\Program Files\IEAK6\toolkit\isp\servless\advanced\7890.ins
+ C:\Program Files\IEAK6\toolkit\isp\servless\advanced\7891.ins
+
+- Full documentation on all the settings available in .INS files is
+ avaialble under Program Files | Microsoft IEAK 6 | IEAK Help
+ | Reference | Internet Settings (.ins) Files
+
+- Freeside will make the following substitutions before sending the file
+ to the user:
+
+ { $ac } - area code of selected POP
+ { $exch } - exchange of selected POP
+ { $loc } - local part of selected POP
+ { $username }
+ { $password }
+ { $email_name } - first and last name
+ { $pkg } - package name
+
+- Simple example follows:
+
+[Entry]
+Entry Name = IEAK Sample
+[Phone]
+Dial_As_Is = No
+Phone_Number = { $exch }{ $loc }
+Area_Code = { $ac }
+Country_Code = 1
+Country_Id = 1
+[Server]
+Type = PPP
+SW_Compress = Yes
+PW_Encrypt = Yes
+Negotiate_TCP/IP = Yes
+Disable_LCP = No
+[TCP/IP]
+Specity_IP_Address = No
+Specity_Server_Address = No
+IP_Header_Compress = Yes
+Gateway_On_Remote = Yes
+[User]
+Name = { $username }
+Passowrd = { $password }
+Display_Password = Yes
+[Internet_Mail]
+Email_Name = { $email_name }
+Email_Address = { $username }@example.com
+POP_Server = mail.example.com
+POP_Server_Port_Number = 110
+POP_Logon_Password = { $password }
+SMTP_Server = mail.example.com
+SMTP_Server_Port_Number = 25
+Install_Mail = 1
+[URL]
+Help_Page = http://www.ieaksample.net/helpdesk
+Home_Page = http://www.ieaksample.net
+Search_Page = http://www.ieaksample,net/search
+[Favorites]
+IEAK Sample \\ IEAK Sample Home Page.url = http://acme.ieaksample.net/
+[Branding]
+Window_Title = Internet Explorer from Acme Internet Services
+
+</pre>
diff --git a/httemplate/docs/index.html b/httemplate/docs/index.html
index 00c863b..648cb98 100644
--- a/httemplate/docs/index.html
+++ b/httemplate/docs/index.html
@@ -6,11 +6,11 @@
<img src="overview.png">
<ul>
<li><a href="install.html">New Installation</a>
- <li><a href="upgrade4.html">Upgrading from 1.2.x to 1.2.2</a>
- <li><a href="upgrade5.html">Upgrading from 1.2.2 to 1.2.3</a>
- <li><a href="upgrade6.html">Upgrading from 1.2.3 to 1.3.0</a>
<li><a href="upgrade7.html">Upgrading from 1.3.0 to 1.3.1</a>
<li><a href="upgrade8.html">Upgrading from 1.3.1 to 1.4.0</a>
+ <li><a href="upgrade9.html">Upgrading from 1.4.0 to 1.4.1</a>
+ <li><a href="upgrade-1.4.2.html">Upgrading from 1.4.1 to 1.4.2</a>
+ <li><a href="upgrade10.html">Upgrading from 1.4.1 (or 1.4.2?) to 1.5.0</a>
<!--
<li><a href="config.html">Configuration files</a>
!-->
diff --git a/httemplate/docs/install.html b/httemplate/docs/install.html
index 75f039d..ed306f2 100644
--- a/httemplate/docs/install.html
+++ b/httemplate/docs/install.html
@@ -3,21 +3,22 @@
</head>
<body>
<h1>Installation</h1>
+<i>Note: Install Freeside on a firewalled, private server, not a public (web, RADIUS, etc.) server.</i><br><br>
Before installing, you need:
<ul>
- <li><a href="http://www.perl.com/">Perl</a> Don't enable experimental features like threads or the PerlIO abstraction layer.
+ <li><a href="http://www.perl.com/">Perl</a>
<li><a href="http://www.apache.org">Apache</a> (<a href="http://www.modssl.org/">mod_ssl</a> or <a href="http://www.apache-ssl.org">Apache-SSL</a> highly recommended)
<li><a href="http://perl.apache.org/">mod_perl</a> (if compiling your own mod_perl, make sure you set the <a href="http://perl.apache.org/guide/install.html#EVERYTHING">EVERYTHING</a>=1 compile-time option)
<li><a href="http://www.openssh.com/">SSH</a> (<a href="http://www.openssh.com//">OpenSSH</a> is recommended. SSH Communications Security <a href="http://www.ssh.com/products/ssh/download.cfm">commercial SSH version 3</a> has been reported incompatible with Freeside.)
<li><a href="http://rsync.samba.org/">rsync</a>
<li>A <b>transactional</b> database engine <a href="http://search.cpan.org/search?mode=module&query=DBD%3A%3A">supported</a> by Perl's <a href="http://dbi.perl.org">DBI</a>.
<ul>
- <li><a href="http://www.postgresql.org/">PostgreSQL</a> (v7 or higher) is recommended.
- <li>MySQL versions before 4.1 do not support standard SQL subqueries and are <b>NOT SUPPORTED</b>. If you are a developer who wishes to contribute MySQL 3.x/4.0 support, see <a href="http://pouncequick.420.am/rt/Ticket/Display.html?id=438">ticket #438</a> in the bug-tracking system and ask on the -devel mailing list.
-<!-- <li>MySQL has been reported to work.
- <b>MySQL's default <a href="http://www.mysql.com/doc/M/y/MyISAM.html">MyISAM</a> and <a href="http://www.mysql.com/doc/I/S/ISAM.html">ISAM</a> table types are not supported</b>. If you want to use MySQL, you <b>must</b> use one of the new <a href="http://www.mysql.com/doc/T/a/Table_types.html">transaction-safe table types</a> such as <a href="http://www.mysql.com/doc/B/D/BDB.html">BDB</a> or <a href="http://www.mysql.com/doc/I/n/InnoDB.html">InnoDB</a>, and set it as the default table type using the <code>--default-table-type=BDB</code> or <code>--default-table-type=InnoDB</code> <a href="http://www.mysql.com/documentation/mysql/bychapter/manual_MySQL_Database_Administration.html#Command-line_options">mysqld command-line option</a> or by setting <code>default-table-type=BDB</code> or <code>default-table-type=InnoDB</code> in the <a href="http://www.mysql.com/documentation/mysql/bychapter/manual_MySQL_Database_Administration.html#Option_files">my.cnf option file</a>. -->
+ <li><a href="http://www.postgresql.org/">PostgreSQL</a> is recommended (v7or later).
+ <li><a href="http://www.mysql.com/">MySQL</a> <b>MINIMUM VERSION 4.1</b> is untested but may work. Versions before 4.1 do not support standard SQL subqueries and are <b>NOT SUPPORTED</b>. If you are a developer who wishes to contribute MySQL 3.x/4.0 support, see <a href="http://pouncequick.420.am/rt/Ticket/Display.html?id=438">ticket #438</a> in the bug-tracking system and ask on the -devel mailing list.
+<!-- <li>MySQL has been reported to work. -->
+ <b>MySQL's default <a href="http://www.mysql.com/doc/M/y/MyISAM.html">MyISAM</a> and <a href="http://www.mysql.com/doc/I/S/ISAM.html">ISAM</a> table types are not supported</b>. If you want to use MySQL, you <b>must</b> use one of the new <a href="http://www.mysql.com/doc/T/a/Table_types.html">transaction-safe table types</a> such as <a href="http://www.mysql.com/doc/B/D/BDB.html">BDB</a> or <a href="http://www.mysql.com/doc/I/n/InnoDB.html">InnoDB</a>, and set it as the default table type using the <code>--default-table-type=BDB</code> or <code>--default-table-type=InnoDB</code> <a href="http://www.mysql.com/documentation/mysql/bychapter/manual_MySQL_Database_Administration.html#Command-line_options">mysqld command-line option</a> or by setting <code>default-table-type=BDB</code> or <code>default-table-type=InnoDB</code> in the <a href="http://www.mysql.com/documentation/mysql/bychapter/manual_MySQL_Database_Administration.html#Option_files">my.cnf option file</a>.
</ul>
- <li>Perl modules (<a href="http://theoryx5.uwinnipeg.ca/CPAN/perl/CPAN.html">CPAN</a> will query, download and build perl modules automatically)
+ <li>Perl modules (<a href="http://search.cpan.org/~andk/CPAN/lib/CPAN.pm">CPAN</a> will query, download and build perl modules automatically)
<ul>
<!-- <li><a href="http://search.cpan.org/search?dist=Array-PrintCols">Array-PrintCols</a>
<li><a href="http://search.cpan.org/search?dist=Term-Query">Term-Query</a> (make test broken; install manually) -->
@@ -41,18 +42,20 @@ Before installing, you need:
<li><a href="http://search.cpan.org/search?dist=String-Approx">String-Approx</a>
<li><a href="http://search.cpan.org/search?dist=Text-Template">Text-Template</a>
<li><a href="http://search.cpan.org/search?dist=DBI">DBI</a>
- <li><a href="http://search.cpan.org/search?mode=module&query=DBD">DBD for your database engine</a> (<a href="http://search.cpan.org/search?dist=DBD-Pg">DBD::Pg</a> for PostgreSQL<!--, <a href="http://search.cpan.org/search?dist=DBD-mysql">DBD::mysql</a> for MySQL-->)
+ <li><a href="http://search.cpan.org/search?mode=module&query=DBD">DBD for your database engine</a> (<a href="http://search.cpan.org/search?dist=DBD-Pg">DBD::Pg</a> for PostgreSQL, <a href="http://search.cpan.org/search?dist=DBD-mysql">DBD::mysql</a> for MySQL)
<li><a href="http://search.cpan.org/search?dist=DBIx-DataSource">DBIx-DataSource</a>
<li><a href="http://search.cpan.org/search?dist=DBIx-DBSchema">DBIx-DBSchema</a>
<li><a href="http://search.cpan.org/search?dist=Net-SSH">Net-SSH</a>
<li><a href="http://search.cpan.org/search?dist=String-ShellQuote">String-ShellQuote</a>
<li><a href="http://search.cpan.org/search?dist=Net-SCP">Net-SCP</a>
- <li><a href="http://www.apache-asp.org/">Apache::ASP</a> or <a href="http://www.masonhq.com/">HTML::Mason</a> (use version 1.0x - Freeside is not yet compatible with version 1.1x)
+ <li><a href="http://www.apache-asp.org/">Apache::ASP</a> or <a href="http://www.masonhq.com/">HTML::Mason</a>
<li><a href="http://search.cpan.org/search?dist=Tie-IxHash">Tie-IxHash</a>
<li><a href="http://search.cpan.org/search?dist=Time-Duration">Time-Duration</a>
<li><a href="http://search.cpan.org/search?dist=HTML-Widgets-SelectLayers">HTML-Widgets-SelectLayers</a>
<li><a href="http://search.cpan.org/search?dist=Storable">Storable</a>
<!-- MyAccounts, maybe only for dev <li><a href="http://search.cpan.org/search?dist=Cache-Cache">Cache::Cache</a> -->
+ <li><a href="http://search.cpan.org/search?dist=NetAddr-IP">NetAddr-IP</a>
+ <li><a href="http://search.cpan.org/search?dist=Chart">Chart</a>
<li><a href="http://search.cpan.org/search?dist=ApacheDBI">Apache::DBI</a> <i>(optional but recommended for better webinterface performance)</i>
</ul>
</ul>
@@ -63,24 +66,23 @@ Install the Freeside distribution:
<ul>
<li> with <a href="http://www.postgresql.org/users-lounge/docs/7.1/postgres/user-manag.html#DATABASE-USERS">PostgreSQL</a>:
<pre>
-$ su postgres
+$ su postgres (pgsql on some distributions)
$ createuser -P freeside
Enter password for user "freeside":
Enter it again:
Shall the new user be allowed to create databases? (y/n) y
Shall the new user be allowed to create more new users? (y/n) n
CREATE USER</pre>
-<!-- <li> with <a href="http://www.mysql.com/documentation/mysql/bychapter/manual_MySQL_Database_Administration.html#User_Account_Management">MySQL</a>:
+ <li> with <a href="http://www.mysql.com/documentation/mysql/bychapter/manual_MySQL_Database_Administration.html#User_Account_Management">MySQL</a>:
<pre>
$ mysqladmin -u root password '<i>set_a_root_database_password</i>'
$ mysql -u root -p
mysql> GRANT SELECT,INSERT,UPDATE,DELETE,INDEX,ALTER,CREATE,DROP on freeside.* TO freeside@localhost IDENTIFIED BY '<i>set_a_freeside_database_password</i>';</pre>
--->
</ul>
<!-- <li>Unpack the tarball: <pre>gunzip -c fs-x.y.z.tar.gz | tar xvf -</pre>-->
<li>Edit the top-level Makefile:
<ul>
- <li>Set <tt>DATASOURCE</tt> to your <a href="http://search.cpan.org/doc/TIMB/DBI-1.28/DBI.pm">DBI data source</a>, for example, <tt>DBI:Pg:host=localhost;dbname=freeside</tt> for PostgresSQL<!-- or <tt>DBI:mysql:freeside</tt> for MySQL-->. See the <a href="http://search.cpan.org/doc/TIMB/DBI-1.28/DBI.pm">DBI manpage</a> and the <a href="http://search.cpan.org/search?mode=module&query=DBD%3A%3A">manpage for your DBD</a> for the exact syntax of your DBI data source.
+ <li>Set <tt>DATASOURCE</tt> to your <a href="http://search.cpan.org/doc/TIMB/DBI-1.28/DBI.pm">DBI data source</a>, for example, <tt>DBI:Pg:dbname=freeside</tt> for PostgresSQL or <tt>DBI:mysql:freeside</tt> for MySQL. See the <a href="http://search.cpan.org/doc/TIMB/DBI-1.28/DBI.pm">DBI manpage</a> and the <a href="http://search.cpan.org/search?mode=module&query=DBD%3A%3A">manpage for your DBD</a> for the exact syntax of your DBI data source.
<li>Set <tt>DB_PASSWORD</tt> to the freeside database user's password.
</ul>
<li>Add the freeside database to your database engine:
@@ -91,10 +93,9 @@ $ su
<pre>
$ su freeside
$ createdb freeside</pre>
-<!-- (with MySQL:)
+ (with MySQL:)
<pre>
$ mysqladmin -u freeside -p create freeside </pre>
--->
<li>Build and install the Perl modules:
<pre>
$ make perl-modules
@@ -113,7 +114,10 @@ $ su
<tr>
<td><ul>
<li>Run <tt>make aspdocs</tt>
- <li>Copy <tt>aspdocs/</tt> to your web server's document space.
+ <li>Copy <tt>aspdocs/</tt> to your web server's document space:
+<font size="-1"><pre>
+cp&nbsp;aspdocs&nbsp;/usr/local/apache/htdocs/freeside-asp
+</pre></font>
<li>Create a <a href="http://www.apache-asp.org/config.html#Global">Global</a> directory, such as <tt>/usr/local/etc/freeside/asp-global/</tt>:
<font size="-1"><pre>
mkdir&nbsp;/usr/local/etc/freeside/asp-global/
@@ -125,34 +129,41 @@ cp&nbsp;htetc/global.asa&nbsp;/usr/local/etc/freeside/asp-global/global.asa
</pre></font>
<li>Configure Apache for the Global directory and to execute .cgi files using Apache::ASP. For example:
<font size="-1"><pre>
+PerlModule Apache::ASP
&lt;Directory&nbsp;/usr/local/apache/htdocs/freeside-asp&gt;
-&lt;Files ~ (\.cgi)&gt;
-AddHandler perl-script .cgi
+&lt;Files ~ (\.cgi|\.html)&gt;
+SetHandler perl-script
PerlHandler Apache::ASP
&lt;/Files&gt;
&lt;Perl&gt;
$MLDBM::RemoveTaint = 1;
&lt;/Perl&gt;
PerlSetVar&nbsp;Global&nbsp;/usr/local/etc/freeside/asp-global/
-PerlSetVar Debug 2
+PerlSetVar&nbsp;Debug&nbsp;2
+PerlSetVar&nbsp;RequestBinaryRead&nbsp;Off
&lt;/Directory&gt;
</pre></font>
</ul></td>
<td><ul>
- <li>(use version 1.0x - Freeside is not yet compatible with version 1.1x)
<li>Run <tt>make masondocs</tt>
- <li>Copy <tt>masondocs/</tt> to your web server's document space.
- <li>Copy <tt>htetc/handler.pl</tt> to your web server's configuration directory.
- <li>Edit <tt>handler.pl</tt> and set an appropriate <tt>data_dir</tt>, such as <tt>/usr/local/etc/freeside/mason-data</tt>
+ <li>Copy <tt>masondocs/</tt> to your web server's document space. (For example: <tt>/usr/local/apache/htdocs/freeside-mason</tt>)
+ <li>Copy <tt>htetc/handler.pl</tt> to <tt>/usr/local/etc/freeside</tt>
+ <li>Edit <tt>handler.pl</tt> and:
+ <ul>
+ <li> set an appropriate <tt>comp_root</tt>, such as <tt>/usr/local/apache/htdocs/freeside-mason</tt>
+ <li> set an appropriate <tt>data_dir</tt>, such as <tt>/usr/local/etc/freeside/masondata</tt>
+ </ul>
+
<li>Configure Apache to use the <tt>handler.pl</tt> file and to execute .cgi files using HTML::Mason. For example:
<font size="-1"><pre>
+PerlModule HTML::Mason
&lt;Directory&nbsp;/usr/local/apache/htdocs/freeside-mason&gt;
-&lt;Files ~ (\.cgi)&gt;
-AddHandler perl-script .cgi
+&lt;Files ~ (\.cgi|.html)&gt;
+SetHandler perl-script
PerlHandler HTML::Mason
&lt;/Files&gt;
&lt;Perl&gt;
-require&nbsp;"/usr/local/apache/conf/handler.pl";
+require&nbsp;"/usr/local/etc/freeside/handler.pl";
&lt;/Perl&gt;
&lt;/Directory&gt;
</pre></font>
@@ -163,7 +174,6 @@ require&nbsp;"/usr/local/apache/conf/handler.pl";
<li>Restrict access to this web interface - see the <a href="http://httpd.apache.org/docs/misc/FAQ.html#user-authentication">Apache documentation on user authentication</a>. For example, to configure user authentication with <a href="http://httpd.apache.org/docs/mod/mod_auth.html">mod_auth</a> (flat files):
<pre>
&lt;Directory /usr/local/apache/htdocs/freeside-asp&gt;
-PerlSetVar Global /usr/local/etc/freeside/asp-global/
AuthName Freeside
AuthType Basic
AuthUserFile /usr/local/etc/freeside/htpasswd
@@ -180,16 +190,16 @@ $ <a href="man/bin/freeside-adduser.html">freeside-adduser</a> -c -h /usr/local/
$ <a href="man/bin/freeside-adduser.html">freeside-adduser</a> -h /usr/local/etc/freeside/htpasswd <i>username</i></pre></font>
</ul>
<i>(using other auth types, add each user to your <a href="http://httpd.apache.org/docs/misc/FAQ.html#user-authentication">Apache authentication</a> and then run: <tt>freeside-adduser <b>username</b></tt></i>
- <li>As the freeside UNIX user, run <tt>bin/fs-setup <b>username</b></tt> (in the untar'ed freeside directory) to create the database tables, passing the username of a Freeside user you created above:
+ <li>As the freeside UNIX user, run <tt>freeside-setup <b>username</b></tt> to create the database tables, passing the username of a Freeside user you created above:
<pre>
$ su freeside
-$ cd <b>/path/to/freeside-1.4.0/</b>
-$ bin/fs-setup <b>username</b>
+$ freeside-setup <b>username</b>
</pre>
+ Alternately, use the -s option to enable shipping addresses: <tt>freeside-setup -s <b>username</b></tt>
<li>As the freeside UNIX user, run <tt>bin/populate-msgcat <b>username</b></tt> (in the untar'ed freeside directory) to populate the message catalog, passing the username of a Freeside user you created above:
<pre>
$ su freeside
-$ cd <b>/path/to/freeside-1.4.0/</b>
+$ cd <b>/path/to/freeside/</b>
$ bin/populate-msgcat <b>username</b>
</pre>
<li><tt>freeside-queued</tt> was installed with the Perl modules. Start it now and ensure that is run upon system startup (Do this manually, or edit the top-level Makefile, replacing INIT_FILE with the appropriate location on your systemand QUEUED_USER with the username of a Freeside user you created above, and run <tt>make install-init</tt>)
diff --git a/httemplate/docs/legacy.html b/httemplate/docs/legacy.html
index 2db9edb..94efe53 100755
--- a/httemplate/docs/legacy.html
+++ b/httemplate/docs/legacy.html
@@ -3,7 +3,7 @@
</head>
<body>
<h1>Importing legacy data</h1>
-<font size="+2">In most cases, legacy data import all cases will require writing custom code to deal with your particular legacy data. The example scripts here will not work "out-of-the-box". Importing your legacy data will most probably involve some hacking on the example scripts noted below. Contributions to the import process are welcome.</font>
+<font size="+2">In almost all cases, legacy data import will require writing custom code to deal with your particular legacy data. The example scripts here will probably <b>not</b> work "out-of-the-box", and are provided <b>as a starting point only</b>.</font>
<br><br><i>Some import scripts may require installation of the <a href="http://search.cpan.org/search?dist=Array-PrintCols">Array-PrintCols</a> and <a href="http://search.cpan.org/search?dist=Term-Query">Term-Query</a> (make test broken; install manually) modules.</i><br>
<ul>
<li><a name="bind">bin/bind.import</a> - Import domain information from BIND named
@@ -17,11 +17,12 @@
<li>POP mail accounts have entries in passwd only, and have a particular shell.
<li>Everything else in passwd is a shell account.
</ul>
- <li><a name="svc_acct_sm">bin/svc_acct_sm.import</a> - Import qmail ( `virtualdomains' and `rcpthosts' ), or sendmail ( `virtusertable' and `sendmail.cw' ) files. Before running bin/svc_acct_sm.import, you need <a href="../browse/part_svc.cgi">services</a> as follows:
+<!-- <li><a name="svc_acct_sm">bin/svc_acct_sm.import</a> - Import qmail ( `virtualdomains' and `rcpthosts' ), or sendmail ( `virtusertable' and `sendmail.cw' ) files. Before running bin/svc_acct_sm.import, you need <a href="../browse/part_svc.cgi">services</a> as follows:
<ul>
<li>Domain (table svc_acct)
<li>Mail alias (table svc_acct_sm)
</ul>
+-->
<li><a name="cust_main">Importing customer data</a>
<ul>
<li>Manually
diff --git a/httemplate/docs/man/FS/part_export/.cvs_is_on_crack b/httemplate/docs/man/FS/part_export/.cvs_is_on_crack
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/httemplate/docs/man/FS/part_export/.cvs_is_on_crack
diff --git a/httemplate/docs/schema.dia b/httemplate/docs/schema.dia
index 092d2f8..7465615 100644
--- a/httemplate/docs/schema.dia
+++ b/httemplate/docs/schema.dia
Binary files differ
diff --git a/httemplate/docs/schema.html b/httemplate/docs/schema.html
index 2b8b3a1..b380317 100644
--- a/httemplate/docs/schema.html
+++ b/httemplate/docs/schema.html
@@ -12,6 +12,9 @@
<li>typenum - <a href="#agent_type">agent type</a>
<li>prog - (unimplemented)
<li>freq - (unimplemented)
+ <li>disabled - Disabled flag, empty or 'Y'
+ <li>username - Username for the Agent interface
+ <li>_password - Password for the Agent interface
</ul>
<li><a name="agent_type" href="man/FS/agent_type.html">agent_type</a> - Agent types define groups of packages that you can then assign to particular agents.
<ul>
@@ -39,7 +42,7 @@
<li><a name="part_bill_event" href="man/FS/part_bill_event.html">part_bill_event</a> - Invoice event definitions
<ul>
<li>eventpart - primary key
- <li>payby - CARD, BILL, or COMP
+ <li>payby - CARD, DCRD, CHEK, DCHK, LECB, BILL, or COMP
<li>event - event name
<li>eventcode - event action
<li>seconds - how long after the invoice date (<a href="#cust_bill">cust_bill</a>._date) events of this type are triggered
@@ -57,6 +60,14 @@
<li>recur - recurring fee
<li>sdate - starting date
<li>edate - ending date
+ <li>itemdesc - Line item description (currently used only when pkgnum is 0)
+ </ul>
+ <li><a name="cust_bill_pkg_detail" href="man/FS/cust_bill_pkg_detail.html">cust_bill_pkg_detail</a> - Invoice line items detail
+ <ul>
+ <li>detailnum - primary key
+ <li>pkgnum -
+ <li>invnum -
+ <li>detail - Detail description
</ul>
<li><a name="cust_credit" href="man/FS/cust_credit.html">cust_credit</a> - Credits. The equivalent of a negative <a href="#cust_bill">cust_bill</a> record.
<ul>
@@ -108,8 +119,9 @@
<li><i>ship_daytime</i>
<li><i>ship_night</i>
<li><i>ship_fax</i>
- <li>payby - CARD, BILL, or COMP
+ <li>payby - CARD, DCHK, CHEK, DCHK, LECB, BILL, or COMP
<li>payinfo - card number, P.O.#, or comp issuer
+ <li>paycvv - Card Verification Value, "CVV2" (also known as CVC2 or CID), the 3 or 4 digit number on the back (or front, for American Express) of the credit card
<li>paydate - expiration date
<li>payname - billing name (name on card)
<li>tax - tax exempt, Y or null
@@ -133,6 +145,9 @@
<li>tax - % rate
<li>taxclass
<li>exempt_amount
+ <li>taxname - if defined, printed on invoices instead of "Tax"
+ <li>setuptax - if 'Y', this tax does not apply to setup fees
+ <li>recurtax - if 'Y', this tax does not apply to recurring fees
</ul>
<li><a name="cust_tax_exempt" href="man/FS/cust_tax_exempt.html">cust_tax_exempt</a> - Tax exemption record
<ul>
@@ -148,7 +163,7 @@
<li>custnum - <a href="#cust_main">customer</a>
<li>paid - amount
<li>_date
- <li>payby - CARD, BILL, or COMP
+ <li>payby - CARD, CHEK, LECB, BILL, or COMP
<li>payinfo - card number, P.O.#, or comp issuer
<li>paybatch - text field for tracking card processor batches
<li>closed - books closed flag, empty or `Y'
@@ -186,6 +201,7 @@
<li>pkgpart - <a href="#part_pkg">Package definition</a>
<li>setup - date
<li>bill - next bill date
+ <li>last_bill - last bill date
<li>susp - (past) suspension date
<li>expire - (future) cancellation date
<li>cancel - (past) cancellation date
@@ -198,7 +214,7 @@
<li>custnum - <a href="#cust_main">customer</a>
<li>refund - amount
<li>_date
- <li>payby - CARD, BILL or COMP
+ <li>payby - CARD, CHEK, LECB, BILL or COMP
<li>payinfo - card number, P.O.#, or comp issuer
<li>otaker - order taker
<li>closed - books closed flag, empty or `Y'
@@ -248,7 +264,7 @@
<ul>
<li>svcpart - primary key
<li>svc - name of this service
- <li>svcdb - table used for this service: svc_acct, svc_acct_sm, svc_forward, svc_domain, svc_charge or svc_wo
+ <li>svcdb - table used for this service: svc_acct, svc_forward, svc_domain, svc_charge or svc_wo
<li>disabled - Disabled flag, empty or `Y'
<!-- <li><i>table</i>__<i>field</i> - Default or fixed value for <i>field</i> in <i>table</i>
<li><i>table</i>__<i>field</i>_flag - null, D or F
@@ -267,6 +283,7 @@
<li>pkgpart - <a href="#part_pkg">Package definition</a>
<li>svcpart - <a href="#part_svc">Service definition</a>
<li>quantity - quantity of this service that this package includes
+ <li>primary_svc - blank or Y: primary service
</ul>
<li><a name="export_svc" href="man/FS/export_svc.html">export_svc</a>
<ul>
@@ -347,13 +364,6 @@
<li>npa - area code
<li>nxx - exchange
</ul>
- <li><a name="svc_acct_sm" href="man/FS/svc_acct_sm.html">svc_acct_sm</a> - <b>DEPRECIATED</b> Domain mail aliases
- <ul>
- <li>svcnum - <a href="#cust_svc">primary key</a>
- <li>domsvc - <a href="#svc_domain">Domain</a> (by svcnum)
- <li>domuid - <a href="#svc_acct">Account</a> (by uid)
- <li>domuser - domuser @ <a href="#svc_domain">Domain</a> forwards to <a href="#svc_acct">Account</a>
- </ul>
<li><a name="svc_domain" href="man/FS/svc_domain.html">svc_domain</a> - Domains
<ul>
<li>svcnum - <a href="#cust_svc">primary key</a>
@@ -363,8 +373,9 @@
<ul>
<li>svcnum - <a href="#cust_svc">primary key</a>
<li>srcsvc - <a href="#svc_acct">svcnum of the source of this forward</a>
+ <li>src - literal source (username or full email address)
<li>dstsvc - <a href="#svc_acct">svcnum of the destination of this forward</a>
- <li>dst - foreign destination (email address) - forward not local to freeside
+ <li>dst - literal destination (username or full email address)
</ul>
<li><a name="domain_record" href="man/FS/domain_record.html">domain_record</a> - Domain zone detail
<ul>
diff --git a/httemplate/docs/session.html b/httemplate/docs/session.html
index 7dac5fd..72e1642 100644
--- a/httemplate/docs/session.html
+++ b/httemplate/docs/session.html
@@ -38,9 +38,14 @@ freeside-logout username ( portnum | ip | nasnum nasport )</pre>
<li><i>username</i> is a customer username from the svc_acct table
<li><i>portnum</i>, <i>ip</i> or <i>nasport</i> and <i>nasnum</i> uniquely identify a port in the <a href="schema.html#port">port</a> database table.
</ul>
- <li>RADIUS
+ <li>RADIUS - One of:
<ul>
+ <li>Run the <b>freeside-sqlradius-radacctd</b> daemon to import radacct
+ records from all configured sqlradius exports:
+ <tt>freeside-sqlradius-radacctd username</tt>
<li>Configure your RADIUS server's login and logout callbacks to use the command-line <tt>freeside-login</tt> and <tt>freeside-logout</tt> utilites.
+ <li> <i>(incomplete)</i>Use the <b>fs_radlog/fs_radlogd</b> tool to
+ import records from a text radacct file.
</ul>
</ul>
<h2>Callbacks</h2>
diff --git a/httemplate/docs/signup.html b/httemplate/docs/signup.html
index 5168f47..97d7aa7 100644
--- a/httemplate/docs/signup.html
+++ b/httemplate/docs/signup.html
@@ -12,7 +12,7 @@ webserver. On this machine, install:
<li><a href="http://search.cpan.org/search?dist=Text-Template">Text::Template</a>
<li><a href="http://search.cpan.org/search?dist=Storable">Storable</a>
<li><a href="http://search.cpan.org/search?dist=Business-CreditCard">Business-CreditCard</a>
- <li><a href="http://www.sisd.com/useragent">HTTP::Headers::UserAgent</a> (version 2.0 or higher; not yet indexed correctly on CPAN)
+ <li><a href="http://search.cpan.org/search?dist=HTTP-BrowserDetect">HTTP::BrowserDetect</a>
<li><a href="man/FS/SignupClient.html">FS::SignupClient</a> (copy the fs_signup/FS-SignupClient directory to the external machine, then: perl Makefile.PL; make; make install)
</ul>
@@ -36,9 +36,7 @@ Then:
Optional:
<ul>
<li>If you create a <b>/usr/local/freeside/ieak.template</b> file on the external machine, it will be sent to IE users with MIME type <i>application/x-Internet-signup</i>. This file will be processed with <a href="http://search.cpan.org/doc/MJD/Text-Template-1.23/Template.pm">Text::Template</a> with the variables listed below available.
- (an example file is included as <b>fs_signup/ieak.template</b>) See the <a href="http://www.microsoft.com/windows/ieak/techinfo/deploy/60/en/toc.asp">IEAK documentation</a> for more information.
- <li>If you create a <b>/usr/local/freeside/cck.template</b> file on the external machine, the variables defined will be sent to Netscape users with MIME type <i>application/x-netscape-autoconfigure-dialer-v2</i>. This file will be processed with <a href="http://search.cpan.org/doc/MJD/Text-Template-1.23/Template.pm">Text::Template</a> with the variables listed below available.
- (an example file is included as <b>fs_signup/cck.template</b>). See the <a href="http://help.netscape.com/products/client/mc/acctproc4.html">Netscape documentation</a> for more information.
+ (an example file is included as <b>fs_signup/ieak.template</b>) See the section on <a href="http://www.microsoft.com/windows/ieak/techinfo/deploy/60/en/INS.HTM">internet settings files</a> in the <a href="http://www.microsoft.com/windows/ieak/techinfo/deploy/60/en/toc.asp">IEAK documentation</a> for more information.
<li>If you create a <b>/usr/local/freeside/success.html</b> file on the external machine, it will be used as the success HTML page. Although template substiutions are available, a regular HTML file will work fine here, unlike signup.html. An example file is included as <b>fs_signup/FS-SignupClient/cgi/success.html</b>
<li>Variable substitutions available in <b>ieak.template</b>, <b>cck.template</b> and <b>success.html</b>:
<ul>
diff --git a/httemplate/docs/ssh.html b/httemplate/docs/ssh.html
index 5503a24..d2c501e 100755
--- a/httemplate/docs/ssh.html
+++ b/httemplate/docs/ssh.html
@@ -6,9 +6,10 @@
<br><a name=ssh>Unattended remote login</a> - Freeside can login to remote machines unattended using SSH. This can pose a security risk if not configured correctly, and will allow an intruder who breaks into your freeside machine full access to your remote machines. <b>Do not use this feature unless you understand what you are doing!</b>
<ul>
<li>As the freeside user (on your freeside machine), generate an authentication key using <a href="http://www.tac.eu.org/cgi-bin/man-cgi?ssh-keygen+1">ssh-keygen</a>. Since this is for unattended operation, use a blank passphrase.
- <li>Append the newly-created <code>identity.pub</code> file to <code>~root/.ssh/authorized_keys</code> on the remote machine(s).
- <li>Some new SSH v2 implementation accept v2 style keys only. Use the <code>-t</code> option to <a href="http://www.tac.eu.org/cgi-bin/man-cgi?ssh-keygen+1">ssh-keygen</a>, and append the created <code>id_dsa.pub</code> or <code>id_rsa.pub</code> to <code>~root/.ssh/authorized_keys2</code> on the remote machine(s).
+ <li>Append the newly-created <code>identity.pub</code> file to <code>~root/.ssh/authorized_keys</code> (or the appopriate <code>~username/.ssh/authorized_keys</code>) on the remote machine(s).
+ <li>Some new SSH v2 implementation accept v2 style keys only. Use the <code>-t</code> option to <a href="http://www.tac.eu.org/cgi-bin/man-cgi?ssh-keygen+1">ssh-keygen</a>, and append the created <code>id_dsa.pub</code> or <code>id_rsa.pub</code> to <code>~root/.ssh/authorized_keys2</code> (or the appopriate <code>~username/.ssh/authorized_keys</code>) on the remote machine(s).
<li>You may need to set <code>PermitRootLogin without-password</code> (meaning with keys only) in your <code>sshd_config</code> file on the remote machine(s).
+ <li>You may want to set <code>ForwardX11 = no</code> in <code>~root/.ssh/config</code> to prevent spurious errors if your distribution turns on X11 forwarding by default.
</ul>
</body>
diff --git a/httemplate/docs/upgrade-1.4.2.html b/httemplate/docs/upgrade-1.4.2.html
new file mode 100644
index 0000000..1feaa80
--- /dev/null
+++ b/httemplate/docs/upgrade-1.4.2.html
@@ -0,0 +1,20 @@
+<head>
+ <title>Upgrading to 1.4.2</title>
+</head>
+<body>
+<h1>Upgrading to 1.4.2 from 1.4.1</h1>
+<ul>
+ <li>If migrating from less than 1.4.1, see these <a href="upgrade9.html">instructions</a> first.
+ <li>Back up your data and current Freeside installation.
+ <li>Install <a href="http://search.cpan.org/search?dist=Locale-SubCountry">Locale::SubCountry</a>
+ <li>Install <a href="http://search.cpan.org/search?dist=IPC-ShareLite">IPC::ShareLite</a>
+ <li>Install <a href="http://search.cpan.org/search?dist=HTML-Widgets-SelectLayers">HTML::Widgets::SelectLayers</a> 0.03.
+ <li>CGI.pm minimum version 2.47 is required. You will probably need to install a current CGI.pm from CPAN if you are using Perl 5.005 or earlier.
+ <li>If using Apache::ASP, add <code>PerlSetVar RequestBinaryRead Off</code> to your Apache configuration and make sure you are using Apache::ASP minimum version 2.55.
+ <li>Run <code>make aspdocs</code> or <code>make masondocs</code>.
+ <li>Copy <code>aspdocs/</code> or <code>masondocs/</code> to your web server's document space.
+ <li>Run <code>make install-perl-modules</code>.
+ <li>The signup server and password server are deprecated in 1.4.2. Their functionality has been incorperated into the self-service server. Edit or reinstall your init script, and set the "signup_server-default_agentnum" and "signup_server-default_refnum" configuration options. The FS::SignupClient interface is still available as a compatibility wrapper, so you should be able to continue to use your current signup.cgi.
+ <li>Optional: To use typeset invoices, install tetex and ghostscript, and copy conf/invoice_latex, conf/invoice_latexnotes, and conf/invoice_latexfooter to /usr/local/etc/freeside/conf.<datasrc>/
+ <li>Restart Apache and freeside-queued.
+</body>
diff --git a/httemplate/docs/upgrade10.html b/httemplate/docs/upgrade10.html
new file mode 100644
index 0000000..774babb
--- /dev/null
+++ b/httemplate/docs/upgrade10.html
@@ -0,0 +1,204 @@
+<pre>
+this is incomplete
+
+install DBIx::DBSchema 0.22
+
+install NetAddr::IP, Chart::Base, IPC::ShareLite and Locale::SubCountry
+
+CREATE TABLE cust_bill_pkg_detail (
+ detailnum serial,
+ pkgnum int NOT NULL,
+ invnum int NOT NULL,
+ detail varchar(80),
+ PRIMARY KEY (detailnum)
+);
+CREATE INDEX cust_bill_pkg_detail1 ON cust_bill_pkg_detail ( pkgnum, invnum );
+
+CREATE TABLE part_virtual_field (
+ vfieldpart int NOT NULL,
+ dbtable varchar(32) NOT NULL,
+ name varchar(32) NOT NULL,
+ check_block text,
+ list_source text,
+ length integer,
+ label varchar(80),
+ PRIMARY KEY (vfieldpart)
+);
+
+CREATE TABLE virtual_field (
+ recnum integer NOT NULL,
+ vfieldpart integer NOT NULL,
+ value varchar(128) NOT NULL,
+ PRIMARY KEY (vfieldpart, recnum)
+);
+
+CREATE TABLE router (
+ routernum serial,
+ routername varchar(80),
+ svcnum int,
+ PRIMARY KEY (routernum)
+);
+
+CREATE TABLE part_svc_router (
+ svcpart int NOT NULL,
+ routernum int NOT NULL
+);
+
+CREATE TABLE addr_block (
+ blocknum serial,
+ routernum int NOT NULL,
+ ip_gateway varchar(15) NOT NULL,
+ ip_netmask int NOT NULL,
+ PRIMARY KEY (blocknum)
+);
+CREATE UNIQUE INDEX addr_block1 ON addr_block ( blocknum, routernum );
+
+CREATE TABLE svc_broadband (
+ svcnum int NOT NULL,
+ blocknum int NOT NULL,
+ speed_up int NOT NULL,
+ speed_down int NOT NULL,
+ ip_addr varchar(15),
+ PRIMARY KEY (svcnum)
+);
+
+CREATE TABLE acct_snarf (
+ snarfnum serial,
+ svcnum int NOT NULL,
+ machine varchar(255) NULL,
+ protocol varchar(80) NULL,
+ username varchar(80) NULL,
+ _password varchar(80) NULL,
+ PRIMARY KEY (snarfnum)
+);
+CREATE INDEX acct_snarf1 ON acct_snarf ( svcnum );
+
+CREATE TABLE svc_external (
+ svcnum int NOT NULL,
+ id int NOT NULL,
+ title varchar(80),
+ PRIMARY KEY (svcnum)
+);
+
+CREATE TABLE part_pkg_temp (
+ pkgpart serial NOT NULL,
+ pkg varchar(80) NOT NULL,
+ "comment" varchar(80) NOT NULL,
+ setup text NULL,
+ freq varchar(80) NOT NULL,
+ recur text NULL,
+ setuptax char(1) NULL,
+ recurtax char(1) NULL,
+ plan varchar(80) NULL,
+ plandata text NULL,
+ disabled char(1) NULL,
+ taxclass varchar(80) NULL,
+ PRIMARY KEY (pkgpart),
+);
+INSERT INTO part_pkg_temp SELECT * from part_pkg;
+DROP TABLE part_pkg;
+ALTER TABLE part_pkg_temp RENAME TO part_pkg;
+ALTER TABLE part_pkg DROP CONSTRAINT part_pkg_temp_pkey;
+ALTER TABLE part_pkg ADD PRIMARY KEY (pkgpart);
+CREATE INDEX part_pkg1 ON part_pkg(disabled);
+select setval('public.part_pkg_temp_pkgpart_seq', ( select max(pkgpart) from part_pkg) ); #?
+
+CREATE TABLE h_part_pkg_temp (
+ historynum serial NOT NULL,
+ history_date int,
+ history_user varchar(80) NOT NULL,
+ history_action varchar(80) NOT NULL,
+ pkgpart int NOT NULL,
+ pkg varchar(80) NOT NULL,
+ "comment" varchar(80) NOT NULL,
+ setup text NULL,
+ freq varchar(80) NOT NULL,
+ recur text NULL,
+ setuptax char(1) NULL,
+ recurtax char(1) NULL,
+ plan varchar(80) NULL,
+ plandata text NULL,
+ disabled char(1) NULL,
+ taxclass varchar(80) NULL,
+ PRIMARY KEY (historynum)
+);
+INSERT INTO h_part_pkg_temp SELECT * from h_part_pkg;
+DROP TABLE h_part_pkg;
+ALTER TABLE h_part_pkg_temp RENAME TO h_part_pkg;
+ALTER TABLE h_part_pkg DROP CONSTRAINT h_part_pkg_temp_pkey;
+ALTER TABLE h_part_pkg ADD PRIMARY KEY (historynum);
+CREATE INDEX h_part_pkg1 ON h_part_pkg(disabled);
+select setval('public.h_part_pkg_temp_historynum_seq', ( select max(historynum) from h_part_pkg) );
+
+
+DROP INDEX cust_bill_pkg1;
+
+ALTER TABLE cust_bill_pkg ADD itemdesc varchar(80) NULL;
+ALTER TABLE h_cust_bill_pkg ADD itemdesc varchar(80) NULL;
+ALTER TABLE cust_main_county ADD taxname varchar(80) NULL;
+ALTER TABLE h_cust_main_county ADD taxname varchar(80) NULL;
+ALTER TABLE cust_main_county ADD setuptax char(1) NULL;
+ALTER TABLE h_cust_main_county ADD setuptax char(1) NULL;
+ALTER TABLE cust_main_county ADD recurtax char(1) NULL;
+ALTER TABLE h_cust_main_county ADD recurtax char(1) NULL;
+ALTER TABLE cust_pkg ADD last_bill int NULL;
+ALTER TABLE h_cust_pkg ADD last_bill int NULL;
+ALTER TABLE agent ADD disabled char(1) NULL;
+ALTER TABLE h_agent ADD disabled char(1) NULL;
+ALTER TABLE agent ADD username varchar(80) NULL;
+ALTER TABLE h_agent ADD username varchar(80) NULL;
+ALTER TABLE agent ADD _password varchar(80) NULL;
+ALTER TABLE h_agent ADD _password varchar(80) NULL;
+ALTER TABLE cust_main ADD paycvv varchar(4) NULL;
+ALTER TABLE h_cust_main ADD paycvv varchar(4) NULL;
+ALTER TABLE part_referral ADD disabled char(1) NULL;
+ALTER TABLE h_part_referral ADD disabled char(1) NULL;
+CREATE INDEX part_referral1 ON part_referral ( disabled );
+ALTER TABLE pkg_svc ADD primary_svc char(1) NULL;
+ALTER TABLE h_pkg_svc ADD primary_svc char(1) NULL;
+ALTER TABLE svc_forward ADD src varchar(255) NULL;
+ALTER TABLE h_svc_forward ADD src varchar(255) NULL;
+
+On recent Pg versions:
+
+ALTER TABLE svc_forward ALTER COLUMN srcsvc DROP NOT NULL;
+ALTER TABLE h_svc_forward ALTER COLUMN srcsvc DROP NOT NULL;
+ALTER TABLE svc_forward ALTER COLUMN dstsvc DROP NOT NULL;
+ALTER TABLE h_svc_forward ALTER COLUMN dstsvc DROP NOT NULL;
+
+Or on Pg versions that don't support DROP NOT NULL (tested only on 7.2 so far):
+UPDATE pg_attribute SET attnotnull = FALSE WHERE ( attname = 'srcsvc' OR attname = 'dstsvc' ) AND ( attrelid = ( SELECT oid FROM pg_class WHERE relname = 'svc_forward' ) OR attrelid = ( SELECT oid FROM pg_class WHERE relname = 'h_svc_forward' ) );
+
+dump database, edit:
+- cust_main: increase otaker from 8 to 32
+- cust_main: change ss from char(11) to varchar(11)
+- cust_credit: increase otaker from 8 to 32
+- cust_pkg: increase otaker from 8 to 32
+- cust_refund: increase otaker from 8 to 32
+- domain_record: increase reczone from 80 to 255
+- domain_record: change rectype from char to varchar
+- domain_record: increase recdata from 80 to 255
+then reload
+
+optionally:
+
+ CREATE INDEX cust_main6 ON cust_main ( daytime );
+ CREATE INDEX cust_main7 ON cust_main ( night );
+ CREATE INDEX cust_main8 ON cust_main ( fax );
+ CREATE INDEX cust_main9 ON cust_main ( ship_daytime );
+ CREATE INDEX cust_main10 ON cust_main ( ship_night );
+ CREATE INDEX cust_main11 ON cust_main ( ship_fax );
+ CREATE INDEX agent2 ON agent ( disabled );
+ CREATE INDEX part_bill_event2 ON part_bill_event ( disabled );
+
+ serial columns
+
+mandatory again:
+
+dbdef-create username
+create-history-tables username cust_bill_pkg_detail router part_svc_router addr_block svc_broadband acct_snarf svc_external
+dbdef-create username
+
+apache - fix <Files> sections to include .html also
+
+</pre>
diff --git a/httemplate/docs/upgrade4.html b/httemplate/docs/upgrade4.html
deleted file mode 100644
index 1d70f8b..0000000
--- a/httemplate/docs/upgrade4.html
+++ /dev/null
@@ -1,27 +0,0 @@
-<head>
- <title>Upgrading to 1.2.2</title>
-</head>
-<body>
-<h1>Upgrading to 1.2.2 from 1.2.x</h1>
-<ul>
- <li>If migrating from 1.0.0, see these <a href="upgrade.html">instructions</a> first.
- <li>If migrating from less than 1.1.4, see these <a href="upgrade2.html">instructions</a> first.
- <li>If migrating from less than 1.2.0, see these <a href="upgrade3.html">instructions</a> first.
- <li>Back up your data and current Freeside installation.
- <li>Install the Perl modules <a href="http://www.perl.com/CPAN/modules/by-module/Locale/">Locale-Codes</a> and <a href="http://www.perl.com/CPAN/modules/by-module/Net/">Net-Whois</a>.
- <li>Apply the following changes to your database:
-<pre>
-ALTER TABLE cust_pay_batch CHANGE exp exp VARCHAR(11);
-</pre>
- <li>Copy or symlink htdocs to the new copy.
- <li>Remove the symlink or directory <i>(your_site_perl_directory)</i>/FS.
- <li>Change to the FS directory in the new tarball, and build and install the
- Perl modules:
- <pre>
-$ cd FS/
-$ perl Makefile.PL
-$ make
-$ su
-# make install</pre>
- <li>Run bin/dbdef-create. This file uses MySQL-specific syntax. If you are running a different database engine you will need to modify it slightly.
-</body>
diff --git a/httemplate/docs/upgrade5.html b/httemplate/docs/upgrade5.html
deleted file mode 100644
index 3f34316..0000000
--- a/httemplate/docs/upgrade5.html
+++ /dev/null
@@ -1,34 +0,0 @@
-<head>
- <title>Upgrading to 1.3.0</title>
-</head>
-<body>
-<h1>Upgrading to 1.2.3 from 1.2.2</h1>
-<ul>
- <li>If migrating from 1.0.0, see these <a href="upgrade.html">instructions</a> first.
- <li>If migrating from less than 1.1.4, see these <a href="upgrade2.html">instructions</a> first.
- <li>If migrating from less than 1.2.0, see these <a href="upgrade3.html">instructions</a> first.
- <li>If migrating from less than 1.2.2, see these <a href="upgrade4.html">instructions</a> first.
- <li>Back up your data and current Freeside installation.
- <li>Apply the following changes to your database:
-<pre>
-ALTER TABLE svc_acct_pop ADD loc CHAR(4);
-CREATE TABLE prepay_credit (
- prepaynum int NOT NULL,
- identifier varchar(80) NOT NULL,
- amount decimal(10,2) NOT NULL,
- PRIMARY KEY (prepaynum),
- INDEX (identifier)
-);
-</pre>
- <li>Copy or symlink htdocs to the new copy.
- <li>Remove the symlink or directory <i>(your_site_perl_directory)</i>/FS.
- <li>Change to the FS directory in the new tarball, and build and install the
- Perl modules:
- <pre>
-$ cd FS/
-$ perl Makefile.PL
-$ make
-$ su
-# make install</pre>
- <li>Run bin/dbdef-create. This file uses MySQL-specific syntax. If you are running a different database engine you will need to modify it slightly.
-</body>
diff --git a/httemplate/docs/upgrade6.html b/httemplate/docs/upgrade6.html
deleted file mode 100644
index dc82975..0000000
--- a/httemplate/docs/upgrade6.html
+++ /dev/null
@@ -1,66 +0,0 @@
-<head>
- <title>Upgrading to 1.3.0</title>
-</head>
-<body>
-<h1>Upgrading to 1.3.0 from 1.2.3</h1>
-<ul>
- <li>If migrating from 1.0.0, see these <a href="upgrade.html">instructions</a> first.
- <li>If migrating from less than 1.1.4, see these <a href="upgrade2.html">instructions</a> first.
- <li>If migrating from less than 1.2.0, see these <a href="upgrade3.html">instructions</a> first.
- <li>If migrating from less than 1.2.2, see these <a href="upgrade4.html">instructions</a> first.
- <li>If migrating from less than 1.2.3, see these <a href="upgrade5.html">instructions</a> first.
- <li>Back up your data and current Freeside installation.
- <li>As 1.3.0 requires transactions, <b>MySQL's default <a href="http://www.mysql.com/doc/M/y/MyISAM.html">MyISAM</a> and <a href="http://www.mysql.com/doc/I/S/ISAM.html">ISAM</a> table types are no longer supported</b>. Converting to <a href="http://www.postgresql.org/">PostgreSQL</a> is recommended. If you really want to use MySQL, convert your tables to one of the <a href="http://www.mysql.com/doc/T/a/Table_types.html">transaction-safe table types</a> such as <a href="http://www.mysql.com/doc/B/D/BDB.html">BDB</a>.
- <li>Copy the <i>invoice_template</i> file from the <i>conf/</i> directory in the distribution to your <a href="config.html">configuration directory</a>.
- <li>Install the <a href="http://search.cpan.org/search?dist=Text-Template">Text-Template</a>, <a href="http://search.cpan.org/search?dist=DBIx-DBSchema">DBIx-DBSchema</a>, <a href="http://search.cpan.org/search?dist=Net-SSH">Net-SSH</a>, <a href="http://search.cpan.org/search?dist=String-ShellQuote">String-ShellQuote</a> and <a href="http://search.cpan.org/search?dist=Net-SCP">Net-SCP</a> Perl modules.
- <li>Apply the following changes to your database:
-<pre>
-CREATE TABLE domain_record (
- recnum int NOT NULL,
- svcnum int NOT NULL,
- reczone varchar(80) NOT NULL,
- recaf char(2) NOT NULL,
- rectype char(5) NOT NULL,
- recdata varchar(80) NOT NULL,
- PRIMARY KEY (recnum)
-);
-CREATE TABLE svc_www (
- svcnum int NOT NULL,
- recnum int NOT NULL,
- usersvc int NOT NULL,
- PRIMARY KEY (svcnum)
-);
-ALTER TABLE part_svc ADD svc_www__recnum varchar(80) NULL;
-ALTER TABLE part_svc ADD svc_www__recnum_flag char(1) NULL;
-ALTER TABLE part_svc ADD svc_www__usersvc varchar(80) NULL;
-ALTER TABLE part_svc ADD svc_www__uesrsvc_flag char(1) NULL;
-ALTER TABLE svc_acct CHANGE _password _password varchar(50) NULL;
-ALTER TABLE svc_acct ADD seconds integer NULL;
-ALTER TABLE part_svc ADD svc_acct__seconds integer NULL;
-ALTER TABLE part_svc ADD svc_acct__seconds_flag char(1) NULL;
-ALTER TABLE prepay_credit ADD seconds integer NULL;
-
-</pre>
- <li>If your database supports dropping columns:
-<pre>
-ALTER TABLE cust_bill DROP owed;
-ALTER TABLE cust_credit DROP credited;
-</pre>
- Or, if your database does not support dropping columns, you can do this:
-<pre>
-ALTER TABLE cust_bill CHANGE owed depriciated decimal(10,2);
-ALTER TABLE cust_credit CHANGE credited depriciated2 decimal(10,2);
-</pre>
-
- <li>Copy or symlink htdocs to the new copy.
- <li>Remove the symlink or directory <i>(your_site_perl_directory)</i>/FS.
- <li>Change to the FS directory in the new tarball, and build and install the
- Perl modules:
- <pre>
-$ cd FS/
-$ perl Makefile.PL
-$ make
-$ su
-# make install</pre>
- <li>Run bin/dbdef-create.
-</body>
diff --git a/httemplate/docs/upgrade8.html b/httemplate/docs/upgrade8.html
index 0210430..cf60a85 100644
--- a/httemplate/docs/upgrade8.html
+++ b/httemplate/docs/upgrade8.html
@@ -369,7 +369,7 @@ ALTER TABLE cust_refund DROP COLUMN crednum;
<li><b>IMPORTANT: After running bin/create-history-tables</b>, run <tt>bin/dbdef-create <i>username</i></tt> again.
<li>As the freeside UNIX user, run <tt>bin/populate-msgcat <i>username</i></tt
> to populate the message catalog
- <li>set the <a href="../config/config.cgi#username_policy">user_policy configuration value</a> as appropriate for your site.
+<!-- <li>set the <a href="../config/config.cgi#username_policy">user_policy configuration value</a> as appropriate for your site. -->
<li>set the <a href="../config/config.cgi#locale">locale configuration value</a> to en_US.
<li>the mxmachines, nsmachines, arecords and cnamerecords configuration values have been deprecated. Set the <a href="../config/config.cgi#defaultrecords">defaultrecords configuration value</a> instead.
<li>Create the `/usr/local/etc/freeside/cache.<i>datasrc</i>' directory
diff --git a/httemplate/docs/upgrade9.html b/httemplate/docs/upgrade9.html
new file mode 100644
index 0000000..6a8fd96
--- /dev/null
+++ b/httemplate/docs/upgrade9.html
@@ -0,0 +1,28 @@
+<head>
+ <title>Upgrading to 1.4.1</title>
+</head>
+<body>
+<h1>Upgrading to 1.4.1 from 1.4.0</h1>
+<ul>
+ <li>If migrating from less than 1.4.0, see these <a href="upgrade8.html">instructions</a> first.
+ <li>Back up your data and current Freeside installation.
+ <li>Run <code>make aspdocs</code> or <code>make masondocs</code>.
+ <li>Copy <code>aspdocs/</code> or <code>masondocs/</code> to your web server's document space.
+ <li>Run <code>make install-perl-modules</code>.
+ <li>Install <a href="http://search.cpan.org/search?dist=Net-SSH">Net::SSH</a> minimum version 0.07
+ <li>Apply the following changes to your database:
+<pre>
+INSERT INTO msgcat ( msgnum, msgcode, locale, msg ) VALUES ( 18, 'daytime', 'en_US', 'Day Phone' );
+INSERT INTO msgcat ( msgnum, msgcode, locale, msg ) VALUES ( 19, 'night', 'en_US', 'Night Phone' );
+</pre>
+ <li>Optionally, apply the following changes to your database (performance improvements):
+<pre>
+CREATE INDEX part_pkg1 ON part_pkg ( disabled );
+CREATE INDEX part_svc1 ON part_svc ( disabled );
+CREATE INDEX cust_bill2 ON cust_bill ( _date );
+</pre>
+ <li>If you want to use ACH (electronic checks), you will need to make changes to your database. The easiest way to make these changes is to dump your database (with pg_dump), change the payinfo field in the cust_pay, cust_refund, h_cust_pay and h_cust_refund tables from varchar(16) to varchar(80), reload the database from the dump.
+ <li>If you will be doing bind exports you should make additional changes to your database. Follow the directions above to dump the database and change the reczone and recdata fields in the domain_record and h_domain_record tables from varchar(80) to varchar(255).
+ <li>If you made changes to your db schema from a dump as listed above run dbdef-create.
+ <li>Restart Apache and freeside-queued.
+</body>
diff --git a/httemplate/edit/REAL_cust_pkg.cgi b/httemplate/edit/REAL_cust_pkg.cgi
index 0d2f1c2..d9b7579 100755
--- a/httemplate/edit/REAL_cust_pkg.cgi
+++ b/httemplate/edit/REAL_cust_pkg.cgi
@@ -1,6 +1,6 @@
<!-- mason kludge -->
<%
-# <!-- $Id: REAL_cust_pkg.cgi,v 1.4 2002-07-08 13:07:40 ivan Exp $ -->
+# <!-- $Id: REAL_cust_pkg.cgi,v 1.7 2003-11-19 12:21:09 ivan Exp $ -->
my $error ='';
my $pkgnum = '';
@@ -30,6 +30,15 @@ print header('Package Edit'); #, menubar(
# 'Main Menu' => popurl(2)
#));
+%>
+
+ <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>
+
+<%
+
#print info
my($susp,$cancel,$expire)=(
$cust_pkg->getfield('susp'),
@@ -45,6 +54,9 @@ print '<FORM NAME="formname" ACTION="process/REAL_cust_pkg.cgi" METHOD="POST">',
print qq!<FONT SIZE="+1" COLOR="#ff0000">Error: $error</FONT>!
if $error;
+#my $format = "%c %z (%Z)";
+my $format = "%m/%d/%Y %T %z (%Z)";
+
print ntable("#cccccc",2),
'<TR><TD ALIGN="right">Package number</TD><TD BGCOLOR="#ffffff">',
$pkgnum, '</TD></TR>',
@@ -55,15 +67,29 @@ print ntable("#cccccc",2),
'<TR><TD ALIGN="right">Order taker</TD><TD BGCOLOR="#ffffff">',
$otaker, '</TD></TR>',
'<TR><TD ALIGN="right">Setup date</TD><TD>'.
- '<INPUT TYPE="text" NAME="setup" SIZE=32 VALUE="',
- ( $setup ? time2str("%c %z (%Z)",$setup) : "" ), '"></TD></TR>',
- '<TR><TD ALIGN="right">Next bill date</TD><TD>',
- '<INPUT TYPE="text" NAME="bill" SIZE=32 VALUE="',
- ( $bill ? time2str("%c %z (%Z)",$bill) : "" ), '"></TD></TR>',
-;
+ '<INPUT TYPE="text" NAME="setup" SIZE=32 ID="setup_text" VALUE="',
+ ( $setup ? time2str($format, $setup) : "" ), '">'.
+ ' <IMG SRC="../images/calendar.png" ID="setup_button" STYLE="cursor: pointer" TITLE="Select date">'.
+ '</TD></TR>';
+
+print '<TR><TD ALIGN="right">Last bill date</TD><TD>',
+ '<INPUT TYPE="text" NAME="last_bill" SIZE=32 ID="last_bill_text" VALUE="',
+ ( $cust_pkg->last_bill
+ ? time2str($format, $cust_pkg->last_bill)
+ : "" ),
+ '">'.
+ ' <IMG SRC="../images/calendar.png" ID="last_bill_button" STYLE="cursor: pointer" TITLE="Select date">'.
+ '</TD></TR>'
+ if $cust_pkg->dbdef_table->column('last_bill');
+
+print '<TR><TD ALIGN="right">Next bill date</TD><TD>',
+ '<INPUT TYPE="text" NAME="bill" SIZE=32 ID="bill_text" VALUE="',
+ ( $bill ? time2str($format, $bill) : "" ), '">'.
+ ' <IMG SRC="../images/calendar.png" ID="bill_button" STYLE="cursor: pointer" TITLE="Select date">'.
+ '</TD></TR>';
print '<TR><TD ALIGN="right">Suspension date</TD><TD BGCOLOR="#ffffff">',
- time2str("%D",$susp), '</TD></TR>'
+ time2str($format, $susp), '</TD></TR>'
if $susp;
#print '<TR><TD ALIGN="right">Expiration date</TD><TD BGCOLOR="#ffffff">',
@@ -71,18 +97,34 @@ print '<TR><TD ALIGN="right">Suspension date</TD><TD BGCOLOR="#ffffff">',
# if $expire;
print '<TR><TD ALIGN="right">Expiration date'.
'</TD><TD>',
- '<INPUT TYPE="text" NAME="expire" SIZE=32 VALUE="',
- ( $expire ? time2str("%c %z (%Z)",$expire) : "" ), '">'.
+ '<INPUT TYPE="text" NAME="expire" SIZE=32 ID="expire_text" VALUE="',
+ ( $expire ? time2str($format, $expire) : "" ), '">'.
+ ' <IMG SRC="../images/calendar.png" ID="expire_button" STYLE="cursor: pointer" TITLE="Select date">'.
'<BR><FONT SIZE=-1>(will <b>cancel</b> this package'.
' when the date is reached)</FONT>'.
'</TD></TR>';
print '<TR><TD ALIGN="right">Cancellation date</TD><TD BGCOLOR="#ffffff">',
- time2str("%D",$cancel), '</TD></TR>'
+ time2str($format, $cancel), '</TD></TR>'
if $cancel;
%>
</TABLE>
+<SCRIPT TYPE="text/javascript">
+<%
+ my @cal = qw( setup bill expire );
+ push @cal, 'last_bill'
+ if $cust_pkg->dbdef_table->column('last_bill');
+ foreach my $cal (@cal) {
+%>
+ Calendar.setup({
+ inputField: "<%= $cal %>_text",
+ ifFormat: "%m/%d/%Y",
+ button: "<%= $cal %>_button",
+ align: "BR"
+ });
+<% } %>
+</SCRIPT>
<BR><INPUT TYPE="submit" VALUE="Apply Changes">
</FORM>
</BODY>
diff --git a/httemplate/edit/agent.cgi b/httemplate/edit/agent.cgi
index 449456c..8a1cb2a 100755
--- a/httemplate/edit/agent.cgi
+++ b/httemplate/edit/agent.cgi
@@ -16,59 +16,64 @@ if ( $cgi->param('error') ) {
my $action = $agent->agentnum ? 'Edit' : 'Add';
my $hashref = $agent->hashref;
-print header("$action Agent", menubar(
+%>
+
+<%= header("$action Agent", menubar(
'Main Menu' => $p,
'View all agents' => $p. 'browse/agent.cgi',
-));
+)) %>
-print qq!<FONT SIZE="+1" COLOR="#ff0000">Error: !, $cgi->param('error'),
- "</FONT>"
- if $cgi->param('error');
+<% if ( $cgi->param('error') ) { %>
+<FONT SIZE="+1" COLOR="#ff0000">Error: <%= $cgi->param('error') %></FONT>
+<% } %>
-print '<FORM ACTION="', popurl(1), 'process/agent.cgi" METHOD=POST>',
- qq!<INPUT TYPE="hidden" NAME="agentnum" VALUE="$hashref->{agentnum}">!,
- "Agent #", $hashref->{agentnum} ? $hashref->{agentnum} : "(NEW)";
+<FORM ACTION="<%=popurl(1)%>process/agent.cgi" METHOD=POST>
+<INPUT TYPE="hidden" NAME="agentnum" VALUE="<%= $hashref->{agentnum} %>">
+Agent #<%= $hashref->{agentnum} ? $hashref->{agentnum} : "(NEW)" %>
-print &ntable("#cccccc", 2, ''), <<END;
+<%= &ntable("#cccccc", 2, '') %>
<TR>
<TH ALIGN="right">Agent</TH>
- <TD><INPUT TYPE="text" NAME="agent" SIZE=32 VALUE="$hashref->{agent}"></TD>
+ <TD><INPUT TYPE="text" NAME="agent" SIZE=32 VALUE="<%= $hashref->{agent} %>"></TD>
</TR>
<TR>
<TH ALIGN="right">Agent type</TH>
<TD><SELECT NAME="typenum" SIZE=1>
-END
-foreach my $agent_type (qsearch('agent_type',{})) {
- print "<OPTION VALUE=". $agent_type->typenum;
- print " SELECTED"
- if $hashref->{typenum} && ( $hashref->{typenum} == $agent_type->typenum );
- print ">", $agent_type->getfield('typenum'), ": ",
- $agent_type->getfield('atype'),"\n";
-}
+<% foreach my $agent_type (qsearch('agent_type',{})) { %>
+ <OPTION VALUE="<%= $agent_type->typenum %>"<%= ( $hashref->{typenum} && ( $hashref->{typenum} == $agent_type->typenum ) ) ? ' SELECTED' : '' %>>
+ <%= $agent_type->getfield('typenum') %>: <%= $agent_type->getfield('atype') %>
+<% } %>
-print <<END;
</SELECT></TD>
</TR>
+<% if ( dbdef->table('agent')->column('disabled') ) { %>
+ <TR>
+ <TD ALIGN="right">Disable</TD>
+ <TD><INPUT TYPE="checkbox" NAME="disabled" VALUE="Y"<%= $hashref->{disabled} eq 'Y' ? ' CHECKED' : '' %>></TD>
+ </TR>
+<% } %>
<TR>
<TD ALIGN="right"><!--Frequency--></TD>
- <TD><INPUT TYPE="hidden" NAME="freq" VALUE="$hashref->{freq}"></TD>
+ <TD><INPUT TYPE="hidden" NAME="freq" VALUE="<%= $hashref->{freq} %>"></TD>
</TR>
<TR>
<TD ALIGN="right"><!--Program--></TD>
- <TD><INPUT TYPE="hidden" NAME="prog" VALUE="$hashref->{prog}"></TD>
+ <TD><INPUT TYPE="hidden" NAME="prog" VALUE="<%= $hashref->{prog} %>"></TD>
</TR>
+<% if ( dbdef->table('agent')->column('username') ) { %>
+ <TR>
+ <TD ALIGN="right">Agent interface username</TD>
+ <TD><INPUT TYPE="text" NAME="username" VALUE="<%= $hashref->{username} %>"></TD>
+ </TR>
+ <TR>
+ <TD ALIGN="right">Agent interface password</TD>
+ <TD><INPUT TYPE="text" NAME="_password" VALUE="<%= $hashref->{_password} %>"></TD>
+ </TR>
+<% } %>
</TABLE>
-END
-print qq!<BR><INPUT TYPE="submit" VALUE="!,
- $hashref->{agentnum} ? "Apply changes" : "Add agent",
- qq!">!;
-
-print <<END;
+<BR><INPUT TYPE="submit" VALUE="<%= $hashref->{agentnum} ? "Apply changes" : "Add agent" %>">
</FORM>
</BODY>
</HTML>
-END
-
-%>
diff --git a/httemplate/edit/cust_main.cgi b/httemplate/edit/cust_main.cgi
index cf8de2f..857e769 100755
--- a/httemplate/edit/cust_main.cgi
+++ b/httemplate/edit/cust_main.cgi
@@ -17,6 +17,7 @@ my $conf = new FS::Conf;
my $error = '';
my($custnum, $username, $password, $popnum, $cust_main, $saved_pkgpart);
+my(@invoicing_list);
if ( $cgi->param('error') ) {
$error = $cgi->param('error');
$cust_main = new FS::cust_main ( {
@@ -32,15 +33,23 @@ if ( $cgi->param('error') ) {
$username = $cgi->param('username');
$password = $cgi->param('_password');
$popnum = $cgi->param('popnum');
+ @invoicing_list = split( /\s*,\s*/, $cgi->param('invoicing_list') );
} 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;
$username = '';
$password = '';
$popnum = 0;
+ @invoicing_list = $cust_main->invoicing_list;
} else {
$custnum='';
$cust_main = new FS::cust_main ( {} );
@@ -50,6 +59,7 @@ if ( $cgi->param('error') ) {
$username = '';
$password = '';
$popnum = 0;
+ @invoicing_list = ();
}
$cgi->delete_all();
my $action = $custnum ? 'Edit' : 'Add';
@@ -57,7 +67,7 @@ my $action = $custnum ? 'Edit' : 'Add';
# top
my $p1 = popurl(1);
-print header("Customer $action", '');
+print header("Customer $action", '', ' onUnload="myclose()"');
print qq!<FONT SIZE="+1" COLOR="#ff0000">Error: !, $error, "</FONT>"
if $error;
@@ -71,9 +81,11 @@ print qq!<FORM ACTION="${p1}process/cust_main.cgi" METHOD=POST NAME="form1">!,
my $r = qq!<font color="#ff0000">*</font>&nbsp;!;
-my @agents = qsearch( 'agent', {} );
+my %agent_search = dbdef->table('agent')->column('disabled')
+ ? ( 'disabled' => '' ) : ();
+my @agents = qsearch( 'agent', \%agent_search );
#die "No agents created!" unless @agents;
-die "You have not created any agents. You must create at least one agent before adding a customer. Go to ". popurl(2). "browse/agent.cgi and create one or more agents." unless @agents;
+eidiot "You have not created any agents (or all agents are disabled). You must create at least one agent before adding a customer. Go to ". popurl(2). "browse/agent.cgi and create one or more agents." unless @agents;
my $agentnum = $cust_main->agentnum || $agents[0]->agentnum; #default to first
if ( scalar(@agents) == 1 ) {
print qq!<INPUT TYPE="hidden" NAME="agentnum" VALUE="$agentnum">!;
@@ -99,7 +111,7 @@ if ( $custnum && ! $conf->exists('editreferrals') ) {
} else {
my(@referrals) = qsearch('part_referral',{});
if ( scalar(@referrals) == 0 ) {
- die "You have not created any advertising sources. You must create at least one advertising source before adding a customer. Go to ". popurl(2). "browse/part_referral.cgi and create one or more advertising sources.";
+ eidiot "You have not created any advertising sources. You must create at least one advertising source before adding a customer. Go to ". popurl(2). "browse/part_referral.cgi and create one or more advertising sources.";
} elsif ( scalar(@referrals) == 1 ) {
$refnum ||= $referrals[0]->refnum;
print qq!<INPUT TYPE="hidden" NAME="refnum" VALUE="$refnum">!;
@@ -120,9 +132,11 @@ if ( $custnum && ! $conf->exists('editreferrals') ) {
#referring customer
#print qq!<BR><BR>Referring Customer: !;
-if ( $cust_main->referral_custnum ) {
- my $referring_cust_main =
- qsearchs('cust_main', { custnum => $cust_main->referral_custnum } );
+my $referring_cust_main = '';
+if ( $cust_main->referral_custnum
+ and $referring_cust_main =
+ qsearchs('cust_main', { custnum => $cust_main->referral_custnum } )
+) {
print '<BR><BR>Referring Customer: <A HREF="'. popurl(1). '/cust_main.cgi?'.
$cust_main->referral_custnum. '">'.
$cust_main->referral_custnum. ': '.
@@ -177,8 +191,10 @@ END
my $countrydefault = $conf->config('countrydefault') || 'US';
$cust_main->country( $countrydefault ) unless $cust_main->country;
-$cust_main->state( $conf->config('statedefault') || 'CA' )
- unless $cust_main->state || $cust_main->country ne 'US';
+my $statedefault = $conf->config('statedefault')
+ || ($countrydefault eq 'US' ? 'CA' : '');
+$cust_main->state( $statedefault )
+ unless $cust_main->state || $cust_main->country ne $countrydefault;
my($county_html, $state_html, $country_html) =
FS::cust_main_county::regionselector( $cust_main->county,
@@ -195,10 +211,13 @@ my($daytime,$night,$fax)=(
$cust_main->fax,
);
+my $daytime_label = FS::Msgcat::_gettext('daytime') || 'Day Phone';
+my $night_label = FS::Msgcat::_gettext('night') || 'Night Phone';
+
print <<END;
<TR><TH ALIGN="right">${r}Country</TH><TD>$country_html</TD></TR>
-<TR><TD ALIGN="right">Day Phone</TD><TD COLSPAN=5><INPUT TYPE="text" NAME="daytime" VALUE="$daytime" SIZE=18></TD></TR>
-<TR><TD ALIGN="right">Night Phone</TD><TD COLSPAN=5><INPUT TYPE="text" NAME="night" VALUE="$night" SIZE=18></TD></TR>
+<TR><TD ALIGN="right">$daytime_label</TD><TD COLSPAN=5><INPUT TYPE="text" NAME="daytime" VALUE="$daytime" SIZE=18></TD></TR>
+<TR><TD ALIGN="right">$night_label</TD><TD COLSPAN=5><INPUT TYPE="text" NAME="night" VALUE="$night" SIZE=18></TD></TR>
<TR><TD ALIGN="right">Fax</TD><TD COLSPAN=5><INPUT TYPE="text" NAME="fax" VALUE="$fax" SIZE=12></TD></TR>
END
@@ -231,7 +250,7 @@ END
print '<BR>Service address ',
'(<INPUT TYPE="checkbox" NAME="same" VALUE="Y" onClick="samechanged(this)"';
- unless ( $cust_main->ship_last ) {
+ unless ( $cust_main->ship_last && $cgi->param('same') ne 'Y' ) {
print ' CHECKED';
foreach (
qw( last first company address1 address2 city county state zip country
@@ -272,8 +291,9 @@ END
#false laziness with regular state
$cust_main->ship_country( $countrydefault ) unless $cust_main->ship_country;
- $cust_main->ship_state( $conf->config('statedefault') || 'CA' )
- unless $cust_main->ship_state || $cust_main->ship_country ne 'US';
+ $cust_main->ship_state( $statedefault )
+ unless $cust_main->ship_state
+ || $cust_main->ship_country ne $countrydefault;
my($ship_county_html, $ship_state_html, $ship_country_html) =
FS::cust_main_county::regionselector( $cust_main->ship_county,
@@ -294,8 +314,8 @@ END
print <<END;
<TR><TH ALIGN="right">${r}Country</TH><TD>$ship_country_html</TD></TR>
- <TR><TD ALIGN="right">Day Phone</TD><TD COLSPAN=5><INPUT TYPE="text" NAME="ship_daytime" VALUE="$ship_daytime" SIZE=18 onChange="changed(this)"></TD></TR>
- <TR><TD ALIGN="right">Night Phone</TD><TD COLSPAN=5><INPUT TYPE="text" NAME="ship_night" VALUE="$ship_night" SIZE=18 onChange="changed(this)"></TD></TR>
+ <TR><TD ALIGN="right">$daytime_label</TD><TD COLSPAN=5><INPUT TYPE="text" NAME="ship_daytime" VALUE="$ship_daytime" SIZE=18 onChange="changed(this)"></TD></TR>
+ <TR><TD ALIGN="right">$night_label</TD><TD COLSPAN=5><INPUT TYPE="text" NAME="ship_night" VALUE="$ship_night" SIZE=18 onChange="changed(this)"></TD></TR>
<TR><TD ALIGN="right">Fax</TD><TD COLSPAN=5><INPUT TYPE="text" NAME="ship_fax" VALUE="$ship_fax" SIZE=12 onChange="changed(this)"></TD></TR>
END
@@ -326,7 +346,9 @@ sub expselect {
$return .= ">$_";
}
$return .= qq!</SELECT>/<SELECT NAME="$prefix!. qq!_year" SIZE="1">!;
- for ( 2001 .. 2037 ) {
+ my @t = localtime;
+ my $thisYear = $t[5] + 1900;
+ for ( ($thisYear > $y && $y > 0 ? $y : $thisYear) .. 2037 ) {
$return .= "<OPTION";
$return .= " SELECTED" if $_ == $y;
$return .= ">$_";
@@ -336,48 +358,129 @@ sub expselect {
$return;
}
-print "<BR>Billing information", &itable("#cccccc"),
- qq!<TR><TD><INPUT TYPE="checkbox" NAME="tax" VALUE="Y"!;
-print qq! CHECKED! if $cust_main->tax eq "Y";
-print qq!>Tax Exempt</TD></TR>!;
-print qq!<TR><TD><INPUT TYPE="checkbox" NAME="invoicing_list_POST" VALUE="POST"!;
-my @invoicing_list = $cust_main->invoicing_list;
-print qq! CHECKED!
- if ( ! @invoicing_list && ! $conf->exists('disablepostalinvoicedefault') )
- || grep { $_ eq 'POST' } @invoicing_list;
-print qq!>Postal mail invoice</TD></TR>!;
-my $invoicing_list = join(', ', grep { $_ ne 'POST' } @invoicing_list );
-print qq!<TR><TD>Email invoice <INPUT TYPE="text" NAME="invoicing_list" VALUE="$invoicing_list"></TD></TR>!;
-
-print "<TR><TD>Billing type</TD></TR>",
- "</TABLE>",
- &table("#cccccc"), "<TR>";
-
-my($payinfo, $payname)=(
- $cust_main->payinfo,
- $cust_main->payname,
-);
+my $payby_default = $conf->config('payby-default');
+
+if ( $payby_default eq 'HIDE' ) {
+
+ $cust_main->payby('BILL') unless $cust_main->payby;
+
+ foreach my $field (qw( tax payby )) {
+ print qq!<INPUT TYPE="hidden" NAME="$field" VALUE="!.
+ $cust_main->getfield($field). '">';
+ }
+
+ print qq!<INPUT TYPE="hidden" NAME="invoicing_list" VALUE="!.
+ join(', ', $cust_main->invoicing_list). '">';
+
+ foreach my $payby (qw( CARD DCRD CHEK DCHK LECB BILL COMP )) {
+ foreach my $field (qw( payinfo payname )) {
+ print qq!<INPUT TYPE="hidden" NAME="${payby}_$field" VALUE="!.
+ $cust_main->getfield($field). '">';
+ }
+
+ #false laziness w/expselect
+ my( $m, $y );
+ my $date = $cust_main->paydate || '12-2037';
+ if ( $date =~ /^(\d{4})-(\d{1,2})-\d{1,2}$/ ) { #PostgreSQL date format
+ ( $m, $y ) = ( $2, $1 );
+ } elsif ( $date =~ /^(\d{1,2})-(\d{1,2}-)?(\d{4}$)/ ) {
+ ( $m, $y ) = ( $1, $3 );
+ } else {
+ die "unrecognized expiration date format: $date";
+ }
+
+ print qq!<INPUT TYPE="hidden" NAME="${payby}_month" VALUE="$m">!.
+ qq!<INPUT TYPE="hidden" NAME="${payby}_year" VALUE="$y">!;
+
+ }
-my %payby = (
- 'CARD' => qq!Credit card<BR>${r}<INPUT TYPE="text" NAME="CARD_payinfo" VALUE="" MAXLENGTH=19><BR>${r}Exp !. expselect("CARD"). qq!<BR>${r}Name on card<BR><INPUT TYPE="text" NAME="CARD_payname" VALUE="">!,
- 'BILL' => qq!Billing<BR>P.O. <INPUT TYPE="text" NAME="BILL_payinfo" VALUE=""><BR>${r}Exp !. expselect("BILL", "12-2037"). qq!<BR>Attention<BR><INPUT TYPE="text" NAME="BILL_payname" VALUE="">!,
- 'COMP' => qq!Complimentary<BR>${r}Approved by<INPUT TYPE="text" NAME="COMP_payinfo" VALUE=""><BR>${r}Exp !. expselect("COMP"),
+} else {
+
+ print "<BR>Billing information", &itable("#cccccc"),
+ qq!<TR><TD><INPUT TYPE="checkbox" NAME="tax" VALUE="Y"!;
+ print qq! CHECKED! if $cust_main->tax eq "Y";
+ print qq!>Tax Exempt</TD></TR><TR><TD>!.
+ qq!<INPUT TYPE="checkbox" NAME="invoicing_list_POST" VALUE="POST"!;
+
+ #my @invoicing_list = $cust_main->invoicing_list;
+ print qq! CHECKED!
+ if ( ! @invoicing_list && ! $conf->exists('disablepostalinvoicedefault') )
+ || grep { $_ eq 'POST' } @invoicing_list;
+ print qq!>Postal mail invoice</TD></TR>!;
+ my $invoicing_list = join(', ', grep { $_ ne 'POST' } @invoicing_list );
+ print qq!<TR><TD>Email invoice <INPUT TYPE="text" NAME="invoicing_list" VALUE="$invoicing_list"></TD></TR>!;
+
+ print "<TR><TD>Billing type</TD></TR>",
+ "</TABLE>", '<script language="JavaScript"><!--
+ var mywindow = -1;
+ function myopen(filename,windowname,properties) {
+ myclose();
+ mywindow = window.open(filename,windowname,properties);
+ }
+ function myclose() {
+ if ( mywindow != -1 )
+ mywindow.close();
+ mywindow = -1;
+ }
+
+ //--></script>',
+ &table("#cccccc"), "<TR>";
+
+ my($payinfo, $payname)=(
+ $cust_main->payinfo,
+ $cust_main->payname,
+ );
+
+ my %payby = (
+ 'CARD' => qq!Credit card (automatic)<BR>${r}<INPUT TYPE="text" NAME="CARD_payinfo" VALUE="" MAXLENGTH=19><BR>${r}Exp !. expselect("CARD"). qq!<BR>${r}Name on card<BR><INPUT TYPE="text" NAME="CARD_payname" VALUE="">!,
+ 'DCRD' => qq!Credit card (on-demand)<BR>${r}<INPUT TYPE="text" NAME="DCRD_payinfo" VALUE="" MAXLENGTH=19><BR>${r}Exp !. expselect("DCRD"). qq!<BR>${r}Name on card<BR><INPUT TYPE="text" NAME="DCRD_payname" VALUE="">!,
+ 'CHEK' => qq!Electronic check (automatic)<BR>${r}Account number <INPUT TYPE="text" NAME="CHEK_payinfo1" VALUE=""><BR>${r}ABA/Routing code <INPUT TYPE="text" NAME="CHEK_payinfo2" VALUE="" SIZE=10 MAXLENGTH=9><INPUT TYPE="hidden" NAME="CHEK_month" VALUE="12"><INPUT TYPE="hidden" NAME="CHEK_year" VALUE="2037"><BR>${r}Bank name <INPUT TYPE="text" NAME="CHEK_payname" VALUE="">!,
+ 'DCHK' => qq!Electronic check (on-demand)<BR>${r}Account number <INPUT TYPE="text" NAME="DCHK_payinfo1" VALUE=""><BR>${r}ABA/Routing code <INPUT TYPE="text" NAME="DCHK_payinfo2" VALUE="" SIZE=10 MAXLENGTH=9><INPUT TYPE="hidden" NAME="DCHK_month" VALUE="12"><INPUT TYPE="hidden" NAME="DCHK_year" VALUE="2037"><BR>${r}Bank name <INPUT TYPE="text" NAME="DCHK_payname" VALUE="">!,
+ 'LECB' => qq!Phone bill billing<BR>${r}Phone number <INPUT TYPE="text" BANE="LECB_payinfo" VALUE="" MAXLENGTH=15 SIZE=16><INPUT TYPE="hidden" NAME="LECB_month" VALUE="12"><INPUT TYPE="hidden" NAME="LECB_year" VALUE="2037"><INPUT TYPE="hidden" NAME="LECB_payname" VALUE="">!,
+ 'BILL' => qq!Billing<BR>P.O. <INPUT TYPE="text" NAME="BILL_payinfo" VALUE=""><BR><INPUT TYPE="hidden" NAME="BILL_month" VALUE="12"><INPUT TYPE="hidden" NAME="BILL_year" VALUE="2037">Attention<BR><INPUT TYPE="text" NAME="BILL_payname" VALUE="">!,
+ 'COMP' => qq!Complimentary<BR>${r}Approved by<INPUT TYPE="text" NAME="COMP_payinfo" VALUE=""><BR>${r}Exp !. expselect("COMP"),
);
-my %paybychecked = (
- 'CARD' => qq!Credit card<BR>${r}<INPUT TYPE="text" NAME="CARD_payinfo" VALUE="$payinfo" MAXLENGTH=19><BR>${r}Exp !. expselect("CARD", $cust_main->paydate). qq!<BR>${r}Name on card<BR><INPUT TYPE="text" NAME="CARD_payname" VALUE="$payname">!,
- 'BILL' => qq!Billing<BR>P.O. <INPUT TYPE="text" NAME="BILL_payinfo" VALUE="$payinfo"><BR>${r}Exp !. expselect("BILL", $cust_main->paydate). qq!<BR>Attention<BR><INPUT TYPE="text" NAME="BILL_payname" VALUE="$payname">!,
- 'COMP' => qq!Complimentary<BR>${r}Approved by<INPUT TYPE="text" NAME="COMP_payinfo" VALUE="$payinfo"><BR>${r}Exp !. expselect("COMP", $cust_main->paydate),
+
+ if ( $cust_main->dbdef_table->column('paycvv') ) {
+ foreach my $payby ( grep { exists $payby{$_} } qw(CARD DCRD) ) { #1.4/1.5 bs
+ $payby{$payby} .= qq!<BR>CVV2&nbsp;(<A HREF="javascript:myopen('../docs/cvv2.html','cvv2','toolbar=no,location=no,directories=no,status=no,menubar=no,scrollbars=no,resizable=yes,copyhistory=no,width=480,height=288')">help</A>)&nbsp;<INPUT TYPE="text" NAME=${payby}_paycvv VALUE="" SIZE=4 MAXLENGTH=4>!;
+ }
+ }
+
+ my( $account, $aba ) = split('@', $payinfo);
+
+ my %paybychecked = (
+ 'CARD' => qq!Credit card (automatic)<BR>${r}<INPUT TYPE="text" NAME="CARD_payinfo" VALUE="$payinfo" MAXLENGTH=19><BR>${r}Exp !. expselect("CARD", $cust_main->paydate). qq!<BR>${r}Name on card<BR><INPUT TYPE="text" NAME="CARD_payname" VALUE="$payname">!,
+ 'DCRD' => qq!Credit card (on-demand)<BR>${r}<INPUT TYPE="text" NAME="DCRD_payinfo" VALUE="$payinfo" MAXLENGTH=19><BR>${r}Exp !. expselect("DCRD", $cust_main->paydate). qq!<BR>${r}Name on card<BR><INPUT TYPE="text" NAME="DCRD_payname" VALUE="$payname">!,
+ 'CHEK' => qq!Electronic check (automatic)<BR>${r}Account number <INPUT TYPE="text" NAME="CHEK_payinfo1" VALUE="$account"><BR>${r}ABA/Routing code <INPUT TYPE="text" NAME="CHEK_payinfo2" VALUE="$aba" SIZE=10 MAXLENGTH=9><INPUT TYPE="hidden" NAME="CHEK_month" VALUE="12"><INPUT TYPE="hidden" NAME="CHEK_year" VALUE="2037"><BR>${r}Bank name <INPUT TYPE="text" NAME="CHEK_payname" VALUE="$payname">!,
+ 'DCHK' => qq!Electronic check (on-demand)<BR>${r}Account number <INPUT TYPE="text" NAME="DCHK_payinfo1" VALUE="$account"><BR>${r}ABA/Routing code <INPUT TYPE="text" NAME="DCHK_payinfo2" VALUE="$aba" SIZE=10 MAXLENGTH=9><INPUT TYPE="hidden" NAME="DCHK_month" VALUE="12"><INPUT TYPE="hidden" NAME="DCHK_year" VALUE="2037"><BR>${r}Bank name <INPUT TYPE="text" NAME="DCHK_payname" VALUE="$payname">!,
+ 'LECB' => qq!Phone bill billing<BR>${r}Phone number <INPUT TYPE="text" BANE="LECB_payinfo" VALUE="$payinfo" MAXLENGTH=15 SIZE=16><INPUT TYPE="hidden" NAME="LECB_month" VALUE="12"><INPUT TYPE="hidden" NAME="LECB_year" VALUE="2037"><INPUT TYPE="hidden" NAME="LECB_payname" VALUE="">!,
+ 'BILL' => qq!Billing<BR>P.O. <INPUT TYPE="text" NAME="BILL_payinfo" VALUE="$payinfo"><BR><INPUT TYPE="hidden" NAME="BILL_month" VALUE="12"><INPUT TYPE="hidden" NAME="BILL_year" VALUE="2037">Attention<BR><INPUT TYPE="text" NAME="BILL_payname" VALUE="$payname">!,
+ 'COMP' => qq!Complimentary<BR>${r}Approved by<INPUT TYPE="text" NAME="COMP_payinfo" VALUE="$payinfo"><BR>${r}Exp !. expselect("COMP", $cust_main->paydate),
);
-for (qw(CARD BILL COMP)) {
- print qq!<TD VALIGN=TOP><INPUT TYPE="radio" NAME="payby" VALUE="$_"!;
- if ($cust_main->payby eq "$_") {
- print qq! CHECKED> $paybychecked{$_}</TD>!;
- } else {
- print qq!> $payby{$_}</TD>!;
+
+ if ( $cust_main->dbdef_table->column('paycvv') ) {
+ my $paycvv = $cust_main->paycvv;
+
+ foreach my $payby ( grep { exists $payby{$_} } qw(CARD DCRD) ) { #1.4/1.5 bs
+ $paybychecked{$payby} .= qq!<BR>CVV2&nbsp;(<A HREF="javascript:myopen('../docs/cvv2.html','cvv2','toolbar=no,location=no,directories=no,status=no,menubar=no,scrollbars=no,resizable=yes,copyhistory=no,width=480,height=288')">help</A>)&nbsp;<INPUT TYPE="text" NAME=${payby}_paycvv VALUE="$paycvv" SIZE=4 MAXLENGTH=4>!;
+ }
+ }
+
+
+ $cust_main->payby($payby_default) unless $cust_main->payby;
+ for (qw(CARD DCRD CHEK DCHK LECB BILL COMP)) {
+ print qq!<TD VALIGN=TOP><INPUT TYPE="radio" NAME="payby" VALUE="$_"!;
+ if ($cust_main->payby eq "$_") {
+ print qq! CHECKED> $paybychecked{$_}</TD>!;
+ } else {
+ print qq!> $payby{$_}</TD>!;
+ }
}
-}
-print "</TR></TABLE>$r required fields for each billing type";
+ print "</TR></TABLE>$r required fields for each billing type";
+
+}
if ( defined $cust_main->dbdef_table->column('comments') ) {
print "<BR><BR>Comments", &itable("#cccccc"),
@@ -422,7 +525,7 @@ unless ( $custnum ) {
foreach my $part_pkg ( @part_pkg ) {
print qq!<OPTION VALUE="!,
# $part_pkg->pkgpart. "_". $pkgpart{ $part_pkg->pkgpart }, '"';
- $part_pkg->pkgpart. "_". $part_pkg->svcpart, '"';
+ $part_pkg->pkgpart. "_". $part_pkg->svcpart('svc_acct'), '"';
print " SELECTED" if $saved_pkgpart && ( $part_pkg->pkgpart == $saved_pkgpart );
print ">", $part_pkg->pkg, " - ", $part_pkg->comment;
}
diff --git a/httemplate/edit/cust_main_county.cgi b/httemplate/edit/cust_main_county.cgi
index 7ef37a4..4bcfcbe 100755
--- a/httemplate/edit/cust_main_county.cgi
+++ b/httemplate/edit/cust_main_county.cgi
@@ -15,12 +15,27 @@ print qq!<FORM ACTION="!, popurl(1),
<TH><FONT SIZE=-1>Country</FONT></TH>
<TH><FONT SIZE=-1>State</FONT></TH>
<TH><FONT SIZE=-1>County</FONT></TH>
- <TH><FONT SIZE=-1>Taxclass</FONT></TH>
+ <TH><FONT SIZE=-1>Taxclass</FONT><BR><FONT SIZE=-2>(per-package classification)</FONT></TH>
+END
+
+if ( dbdef->table('cust_main_county')->column('taxname') ) {
+ print '<TH><FONT SIZE=-1>Tax name</FONT><BR><FONT SIZE=-2>(printed on invoices)</FONT></TH>';
+}
+
+print <<END;
<TH><FONT SIZE=-1>Tax</FONT></TH>
<TH><FONT SIZE=-1>Exempt<BR>per<BR>month</TH>
- </TR>
END
+if ( dbdef->table('cust_main_county')->column('setuptax') ) {
+ print '<TH><FONT SIZE=-1>Setup<BR>fee<BR>exempt</TH>';
+}
+if ( dbdef->table('cust_main_county')->column('recurtax') ) {
+ print '<TH><FONT SIZE=-1>Recurring<BR>fee<BR>exempt</TH>';
+}
+
+print '</TR>';
+
foreach my $cust_main_county ( sort { $a->country cmp $b->country
or $a->state cmp $b->state
or $a->county cmp $b->county
@@ -46,10 +61,27 @@ END
: ' BGCOLOR="#cccccc">(ALL)'
, "</TD>";
- print qq!<TD><INPUT TYPE="text" NAME="tax!, $hashref->{taxnum},
- qq!" VALUE="!, $hashref->{tax}, qq!" SIZE=6 MAXLENGTH=6>%</TD>!;
- print qq!<TD>\$<INPUT TYPE="text" NAME="exempt_amount!, $hashref->{taxnum},
- qq!" VALUE="!, $hashref->{exempt_amount}||0, qq!" SIZE=6></TD>!;
+ print qq!<TD><INPUT TYPE="text" NAME="taxname!, $hashref->{taxnum},
+ qq!" VALUE="!, $hashref->{taxname}, qq!"></TD>!
+ if dbdef->table('cust_main_county')->column('taxname');
+
+ print qq!<TD><TABLE><TR><TD><INPUT TYPE="text" NAME="tax!, $hashref->{taxnum},
+ qq!" VALUE="!, $hashref->{tax}, qq!" SIZE=6 MAXLENGTH=6></TD><TD>%</TD></TR></TABLE></TD>!;
+ print qq!<TD><TABLE><TR><TD>\$</TD><TD><INPUT TYPE="text" NAME="exempt_amount!, $hashref->{taxnum},
+ qq!" VALUE="!, $hashref->{exempt_amount}||0, qq!" SIZE=6></TD></TR></TABLE></TD>!;
+
+ print qq!<TD><INPUT TYPE="checkbox" NAME="setuptax!. $hashref->{taxnum}.
+ '" VALUE="Y"'.
+ ( $hashref->{setuptax} =~ /^Y$/i ? ' CHECKED' : '' ).
+ '></TD>'
+ if dbdef->table('cust_main_county')->column('setuptax');
+
+ print qq!<TD><INPUT TYPE="checkbox" NAME="recurtax!. $hashref->{taxnum}.
+ '" VALUE="Y"'.
+ ( $hashref->{recurtax} =~ /^Y$/i ? ' CHECKED' : '' ).
+ '></TD>'
+ if dbdef->table('cust_main_county')->column('recurtax');
+
print '</TR>';
}
diff --git a/httemplate/edit/part_bill_event.cgi b/httemplate/edit/part_bill_event.cgi
index 324daeb..48ed791 100755
--- a/httemplate/edit/part_bill_event.cgi
+++ b/httemplate/edit/part_bill_event.cgi
@@ -20,7 +20,7 @@ if ( $query && $query =~ /^(\d+)$/ ) {
} else {
$part_bill_event ||= new FS::part_bill_event {};
}
-$action ||= $part_bill_event->pkgpart ? 'Edit' : 'Add';
+$action ||= $part_bill_event->eventpart ? 'Edit' : 'Add';
my $hashref = $part_bill_event->hashref;
print header("$action Invoice Event Definition", menubar(
@@ -41,7 +41,7 @@ print ntable("#cccccc",2), <<END;
<TR><TD ALIGN="right">Payby</TD><TD><SELECT NAME="payby">
END
-for (qw(CARD BILL COMP)) {
+for (qw(CARD DCRD CHEK DCHK LECB BILL COMP)) {
print qq!<OPTION VALUE="$_"!;
if ($part_bill_event->payby eq $_) {
print " SELECTED>$_</OPTION>";
@@ -108,9 +108,15 @@ tie my %events, 'Tie::IxHash',
'weight' => 30,
},
- 'realtime-card-cybercash' => {
- 'name' => '(<b>deprecated</b>) Run card with <a href="http://www.cybercash.com/cashregister">CyberCash CashRegister</a> realtime gateway',
- 'code' => '$cust_bill->realtime_card_cybercash();',
+ '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,
},
@@ -134,6 +140,30 @@ tie my %events, 'Tie::IxHash',
'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%%%\' );',
+ 'html' =>
+ '<TABLE BORDER=0><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,
+ },
+
'bill' => {
'name' => 'Generate invoices (normally only used with a <i>Late Fee</i> event)',
'code' => '$cust_main->bill();',
diff --git a/httemplate/edit/part_export.cgi b/httemplate/edit/part_export.cgi
index 486bd43..4d0c739 100644
--- a/httemplate/edit/part_export.cgi
+++ b/httemplate/edit/part_export.cgi
@@ -49,24 +49,33 @@ my $widget = new HTML::Widgets::SelectLayers(
my $label = $optinfo->{label};
my $type = defined($optinfo->{type}) ? $optinfo->{type} : 'text';
my $value = $cgi->param($option)
- || $part_export->option($option)
- || (exists $optinfo->{default} ? $optinfo->{default} : '');
+ || ( $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 {
- $selected = $select_option eq $value ? ' SELECTED' : '';
+ 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">$value</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="$value" SIZE=64>!;
+ $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";
}
diff --git a/httemplate/edit/part_pkg.cgi b/httemplate/edit/part_pkg.cgi
index b6ecff2..8416b35 100755
--- a/httemplate/edit/part_pkg.cgi
+++ b/httemplate/edit/part_pkg.cgi
@@ -66,12 +66,52 @@ print '<FORM NAME="dummy">';
#print qq!<INPUT TYPE="hidden" NAME="pkgpart" VALUE="$hashref->{pkgpart}">!,
print "Package Part #", $hashref->{pkgpart} ? $hashref->{pkgpart} : "(NEW)";
-print ntable("#cccccc",2), <<END;
-<TR><TD ALIGN="right">Package (customer-visable)</TD><TD><INPUT TYPE="text" NAME="pkg" SIZE=32 VALUE="$hashref->{pkg}"></TD></TR>
-<TR><TD ALIGN="right">Comment (customer-hidden)</TD><TD><INPUT TYPE="text" NAME="comment" SIZE=32 VALUE="$hashref->{comment}"></TD></TR>
-<TR><TD ALIGN="right">Frequency (months) of recurring fee</TD><TD><INPUT TYPE="text" NAME="freq" VALUE="$hashref->{freq}" SIZE=3>&nbsp;&nbsp;<I>0=no recurring fee, 1=monthly, 3=quarterly, 12=yearly</TD></TR>
-<TR><TD ALIGN="right">Setup fee tax exempt</TD><TD>
-END
+#false laziness w/view/cust_main.cgi
+my %freq;
+tie %freq, 'Tie::IxHash',
+ '0' => '(no recurring fee)',
+ '1d' => 'daily',
+ '1w' => 'weekly',
+ '2w' => 'biweekly (every 2 weeks)',
+ '1' => 'monthly',
+ '2' => 'bimonthly (every 2 months)',
+ '3' => 'quarterly (every 3 months)',
+ '6' => 'semiannually (every 6 months)',
+ '12' => 'annually',
+ '24' => 'biannually (every 2 years)',
+;
+if ( $part_pkg->dbdef_table->column('freq')->type =~ /(int)/i ) {
+ delete $freq{$_} foreach grep { ! /^\d+$/ } keys %freq;
+}
+
+%>
+<%= ntable("#cccccc",2) %>
+ <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>
+ <TR>
+ <TD ALIGN="right">Recurring fee frequency </TD>
+ <TD>
+ <SELECT NAME="freq">
+ <% foreach my $freq ( keys %freq ) { %>
+ <OPTION VALUE="<%= $freq %>"<%= $freq eq $part_pkg->freq ? ' SELECTED' : '' %>><%= $freq{$freq} %>
+ <% } %>
+ </SELECT>
+ </TD>
+ </TR>
+ <TR>
+ <TD ALIGN="right">Setup fee tax exempt</TD>
+ <TD>
+<%
print '<INPUT TYPE="checkbox" NAME="setuptax" VALUE="Y"';
print ' CHECKED' if $hashref->{setuptax} eq "Y";
@@ -112,9 +152,11 @@ print ' CHECKED' if $hashref->{disabled} eq "Y";
print '>';
print '</TD></TR></TABLE>';
-my $thead = "\n\n". ntable('#cccccc', 2). <<END;
-<TR><TH BGCOLOR="#dcdcdc"><FONT SIZE=-1>Quan.</FONT></TH><TH BGCOLOR="#dcdcdc">Service</TH></TR>
-END
+my $thead = "\n\n". ntable('#cccccc', 2).
+ '<TR><TH BGCOLOR="#dcdcdc"><FONT SIZE=-1>Quan.</FONT></TH>';
+$thead .= '<TH BGCOLOR="#dcdcdc"><FONT SIZE=-1>Primary</FONT></TH>'
+ if dbdef->table('pkg_svc')->column('primary_svc');
+$thead .= '<TH BGCOLOR="#dcdcdc">Service</TH></TR>';
#unless ( $cgi->param('clone') ) {
#dunno why...
@@ -131,13 +173,15 @@ my $columns = 3;
my @part_svc = qsearch( 'part_svc', { 'disabled' => '' } );
foreach my $part_svc ( @part_svc ) {
my $svcpart = $part_svc->svcpart;
- my $pkg_svc = qsearchs( 'pkg_svc', {
- 'pkgpart' => $cgi->param('clone') || $part_pkg->pkgpart,
+ my $pkgpart = $cgi->param('clone') || $part_pkg->pkgpart;
+ my $pkg_svc = $pkgpart && qsearchs( 'pkg_svc', {
+ 'pkgpart' => $pkgpart,
'svcpart' => $svcpart,
} ) || new FS::pkg_svc ( {
- 'pkgpart' => $cgi->param('clone') || $part_pkg->pkgpart,
- 'svcpart' => $svcpart,
- 'quantity' => 0,
+ 'pkgpart' => $pkgpart,
+ 'svcpart' => $svcpart,
+ 'quantity' => 0,
+ 'primary_svc' => '',
});
#? #next unless $pkg_svc;
@@ -149,7 +193,13 @@ foreach my $part_svc ( @part_svc ) {
print '<TR>'; # if $count == 0 ;
print qq!<TD><INPUT TYPE="text" NAME="pkg_svc$svcpart" SIZE=4 MAXLENGTH=3 VALUE="!,
$cgi->param("pkg_svc$svcpart") || $pkg_svc->quantity || 0,
- qq!"></TD><TD><A HREF="part_svc.cgi?!,$part_svc->svcpart,
+ qq!"></TD>!;
+ if ( dbdef->table('pkg_svc')->column('primary_svc') ) {
+ print qq!<TD><INPUT TYPE="radio" NAME="pkg_svc_primary" VALUE="$svcpart"!;
+ print ' CHECKED' if $pkg_svc->primary_svc =~ /^Y/i;
+ print '></TD>';
+ }
+ print qq!<TD><A HREF="part_svc.cgi?!,$part_svc->svcpart,
qq!">!, $part_svc->getfield('svc'), "</A></TD></TR>";
# print "</TABLE></TD><TD>$thead" if ++$count == int(scalar(@part_svc) / 2);
$count+=1;
@@ -355,6 +405,114 @@ tie my %plans, 'Tie::IxHash',
},
+ 'sqlradacct_hour' => {
+ 'name' => 'Base charge plus charge per-hour (and for data) from an external sqlradius radacct table',
+ 'fields' => {
+ 'setup_fee' => { 'name' => 'Setup fee for this package',
+ 'default' => 0,
+ },
+ 'recur_flat' => { 'name' => 'Base monthly charge for this package',
+ 'default' => 0,
+ },
+ 'recur_included_hours' => { 'name' => 'Hours included',
+ 'default' => 0,
+ },
+ 'recur_hourly_charge' => { 'name' => 'Additional charge per hour',
+ 'default' => 0,
+ },
+ 'recur_included_input' => { 'name' => 'Input megabytes included',
+ 'default' => 0,
+ },
+ 'recur_input_charge' => { 'name' =>
+ 'Additional charge per input megabyte',
+ 'default' => 0,
+ },
+ 'recur_included_output' => { 'name' => 'Output megabytes included',
+ 'default' => 0,
+ },
+ 'recur_output_charge' => { 'name' =>
+ 'Additional charge per output megabyte',
+ 'default' => 0,
+ },
+ 'recur_included_total' => { 'name' =>
+ 'Total input+output megabytes included',
+ 'default' => 0,
+ },
+ 'recur_total_charge' => { 'name' =>
+ 'Additional charge per input+output megabyte',
+ 'default' => 0,
+ },
+ },
+ 'fieldorder' => [qw( setup_fee recur_flat recur_included_hours recur_hourly_charge recur_included_input recur_input_charge recur_included_output recur_output_charge recur_included_total recur_total_charge )],
+ 'setup' => 'what.setup_fee.value',
+ 'recur' => '\'my $last_bill = $cust_pkg->last_bill; my $hours = $cust_pkg->seconds_since_sqlradacct($last_bill, $sdate ) / 3600 - \' + what.recur_included_hours.value + \'; $hours = 0 if $hours < 0; my $input = $cust_pkg->attribute_since_sqlradacct($last_bill, $sdate, \"AcctInputOctets\" ) / 1048576; my $output = $cust_pkg->attribute_since_sqlradacct($last_bill, $sdate, \"AcctOutputOctets\" ) / 1048576; my $total = $input + $output - \' + what.recur_included_total.value + \'; $total = 0 if $total < 0; my $input = $input - \' + what.recur_included_input.value + \'; $input = 0 if $input < 0; my $output = $output - \' + what.recur_included_output.value + \'; $output = 0 if $output < 0; my $totalcharge = sprintf(\"%.2f\", \' + what.recur_total_charge.value + \' * $total); my $hourscharge = sprintf(\"%.2f\", \' + what.recur_hourly_charge.value + \' * $hours); push @details, \"Last month\\\'s excess data \". sprintf(\"%.1f\", $total). \" megs: \\\$$totalcharge\", \"Last month\\\'s excess time \". sprintf(\"%.1f\", $hours). \" hours: \\\$$hourscharge\"; \' + what.recur_flat.value + \' + $hourscharge + \' + what.recur_input_charge.value + \' * $input + \' + what.recur_output_charge.value + \' * $output + $totalcharge ;\'',
+ },
+
+ 'sql_generic' => {
+ 'name' => 'Base charge plus a metered rate from a configurable SQL query',
+ 'fields' => {
+ 'setup_fee' => { 'name' => 'Setup fee for this package',
+ 'default' => 0,
+ },
+ 'recur_flat' => { 'name' => 'Base monthly charge for this package',
+ 'default' => 0,
+ },
+ 'recur_included' => { 'name' => 'Units included',
+ 'default' => 0,
+ },
+ 'recur_unit_charge' => { 'name' => 'Additional charge per unit',
+ 'default' => 0,
+ },
+ 'datasrc' => { 'name' => 'DBI data source',
+ 'default' => '',
+ },
+ 'db_username' => { 'name' => 'Database username',
+ 'default' => '',
+ },
+ 'db_password' => { 'name' => 'Database username',
+ 'default' => '',
+ },
+ 'query' => { 'name' => 'SQL query',
+ 'default' => '',
+ },
+ },
+ 'fieldorder' => [qw( setup_fee recur_flat recur_included recur_unit_charge datasrc db_username db_password query )],
+ 'setup' => 'what.setup_fee.value',
+ # 'recur' => '\'my $dbh = DBI->connect(\"\' + what.datasrc.value + \'\", \"\' + what.db_username.value + \'\") or die $DBI::errstr; \'',
+ 'recur' => '\'my $dbh = DBI->connect(\"\' + what.datasrc.value + \'\", \"\' + what.db_username.value + \'\", \"\' + what.db_password.value + \'\" ) or die $DBI::errstr; my $sth = $dbh->prepare(\"\' + what.query.value + \'\") or die $dbh->errstr; my $units = 0; foreach my $cust_svc ( grep { $_->part_svc->svcdb eq \"svc_domain\" } $cust_pkg->cust_svc ) { my $domain = $cust_svc->svc_x->domain; $sth->execute($domain) or die $sth->errstr; $units += $sth->fetchrow_arrayref->[0]; } $units -= \' + what.recur_included.value + \'; $units = 0 if $units < 0; \' + what.recur_flat.value + \' + $units * \' + what.recur_unit_charge.value + \';\'',
+ #'recur' => '\'my $dbh = DBI->connect("\' + what.datasrc.value + \'", "\' + what.db_username.value + \'", "\' what.db_password.value + \'" ) or die $DBI::errstr; my $sth = $dbh->prepare("\' + what.query.value + \'") or die $dbh->errstr; my $units = 0; foreach my $cust_svc ( grep { $_->part_svc->svcdb eq "svc_domain" } $cust_pkg->cust_svc ) { my $domain = $cust_svc->svc_x->domain; $sth->execute($domain) or die $sth->errstr; $units += $sth->fetchrow_arrayref->[0]; } $units -= \' + what.recur_included.value + \'; $units = 0 if $units < 0; \' + what.recur_flat.value + \' + $units * \' + what.recur_unit_charge + \';\'',
+ },
+
+
+
+ 'sql_external' => {
+ 'name' => 'Base charge plus additional fees for external services from a configurable SQL query',
+ 'fields' => {
+ 'setup_fee' => { 'name' => 'Setup fee for this package',
+ 'default' => 0,
+ },
+ 'recur_flat' => { 'name' => 'Base monthly charge for this package',
+ 'default' => 0,
+ },
+ 'datasrc' => { 'name' => 'DBI data source',
+ 'default' => '',
+ },
+ 'db_username' => { 'name' => 'Database username',
+ 'default' => '',
+ },
+ 'db_password' => { 'name' => 'Database password',
+ 'default' => '',
+ },
+ 'query' => { 'name' => 'SQL query',
+ 'default' => '',
+ },
+ },
+ 'fieldorder' => [qw( setup_fee recur_flat datasrc db_username db_password query )],
+ 'setup' => 'what.setup_fee.value',
+ 'recur' => q!'my $dbh = DBI->connect("' + what.datasrc.value + '", "' + what.db_username.value + '", "' + what.db_password.value + '" ) or die $DBI::errstr; my $sth = $dbh->prepare("' + what.query.value + '") or die $dbh->errstr; my $price = ' + what.recur_flat.value + '; foreach my $cust_svc ( grep { $_->part_svc->svcdb eq "svc_external" } $cust_pkg->cust_svc ){ my $id = $cust_svc->svc_x->id; $sth->execute($id) or die $sth->errstr; $price += $sth->fetchrow_arrayref->[0]; } $price;'!,
+
+ },
+
;
my %plandata = map { /^(\w+)=(.*)$/; ( $1 => $2 ); }
@@ -369,6 +527,10 @@ if ( $conf->exists('enable_taxclasses') ) {
push @fixups, 'taxclass'; #hidden
}
+my @form_radio = ();
+if ( dbdef->table('pkg_svc')->column('primary_svc') ) {
+ push @form_radio, 'pkg_svc_primary';
+}
my $widget = new HTML::Widgets::SelectLayers(
'selected_layer' => $part_pkg->plan,
@@ -377,7 +539,8 @@ my $widget = new HTML::Widgets::SelectLayers(
'form_action' => 'process/part_pkg.cgi',
'form_text' => [ qw(pkg comment freq clone pkgnum pkgpart), @fixups ],
'form_checkbox' => [ qw(setuptax recurtax disabled) ],
- 'form_select' => [ @form_select ],
+ 'form_radio' => \@form_radio,
+ 'form_select' => \@form_select,
'fixup_callback' => sub {
#my $ = @_;
my $html = '';
@@ -442,11 +605,11 @@ my $widget = new HTML::Widgets::SelectLayers(
'<TR><TD>'.
'<FONT SIZE="1">Setup expression<BR>'.
'<INPUT TYPE="text" NAME="setup" SIZE="160" VALUE="'.
- $hashref->{setup}. '" onLoad="fchanged(this)">'.
+ encode_entities($hashref->{setup}). '" onLoad="fchanged(this)">'.
'</FONT><BR>'.
'<FONT SIZE="1">Recurring espression<BR>'.
'<INPUT TYPE="text" NAME="recur" SIZE="160" VALUE="'.
- $hashref->{recur}. '" onLoad="fchanged(this)">'.
+ encode_entities($hashref->{recur}). '" onLoad="fchanged(this)">'.
'</FONT>'.
'</TR></TD>'.
'</TABLE>';
diff --git a/httemplate/edit/part_svc.cgi b/httemplate/edit/part_svc.cgi
index 4ccb770..6868ffd 100755
--- a/httemplate/edit/part_svc.cgi
+++ b/httemplate/edit/part_svc.cgi
@@ -50,9 +50,10 @@ Disable new orders <INPUT TYPE="checkbox" NAME="disabled" VALUE="Y"<%= $hashref-
Services are items you offer to your customers.
<UL><LI>svc_acct - Shell accounts, POP mailboxes, SLIP/PPP and ISDN accounts
<LI>svc_domain - Domains
- <LI>svc_acct_sm - <B>deprecated</B> (use svc_forward for new installations) Virtual domain mail aliasing.
<LI>svc_forward - mail forwarding
<LI>svc_www - Virtual domain website
+ <LI>svc_broadband - Broadband/High-speed Internet service
+ <LI>svc_external - Externally-tracked service
<!-- <LI>svc_charge - One-time charges (Partially unimplemented)
<LI>svc_wo - Work orders (Partially unimplemented)
-->
@@ -65,6 +66,9 @@ blank <B>slipip</B> as well as a fixed shell something like <B>/bin/true</B> or
<BR><BR>
<%
+
+my %vfields;
+
#these might belong somewhere else for other user interfaces
#pry need to eventually create stuff that's shared amount UIs
my %defs = (
@@ -80,7 +84,10 @@ my %defs = (
select_key => 'popnum',
select_label => 'city',
},
- 'username' => 'Username',
+ 'username' => {
+ desc => 'Username',
+ type => 'disabled',
+ },
'quota' => '',
'_password' => 'Password',
'gid' => 'GID (when blank, defaults to UID)',
@@ -101,34 +108,57 @@ my %defs = (
'svc_domain' => {
'domain' => 'Domain',
},
- 'svc_acct_sm' => {
- 'domuser' => 'domuser@virtualdomain.com',
- 'domuid' => 'UID where domuser@virtualdomain.com mail is forwarded',
- 'domsvc' => 'svcnum from svc_domain for virtualdomain.com',
- },
'svc_forward' => {
'srcsvc' => 'service from which mail is to be forwarded',
'dstsvc' => 'service to which mail is to be forwarded',
'dst' => 'someone@another.domain.com to use when dstsvc is 0',
},
- 'svc_charge' => {
- 'amount' => 'amount',
- },
- 'svc_wo' => {
- 'worker' => 'Worker',
- '_date' => 'Date',
- },
+# 'svc_charge' => {
+# 'amount' => 'amount',
+# },
+# 'svc_wo' => {
+# 'worker' => 'Worker',
+# '_date' => 'Date',
+# },
'svc_www' => {
#'recnum' => '',
#'usersvc' => '',
},
+ 'svc_broadband' => {
+ 'speed_down' => 'Maximum download speed for this service in Kbps. 0 denotes unlimited.',
+ 'speed_up' => 'Maximum upload speed for this service in Kbps. 0 denotes unlimited.',
+ 'ip_addr' => 'IP address. Leave blank for automatic assignment.',
+ 'blocknum' => 'Address block.',
+ },
+ 'svc_external' => {
+ #'id' => '',
+ #'title' => '',
+ },
);
+ foreach my $svcdb (grep dbdef->table($_), keys %defs ) {
+ 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);
+ my @list = $pvf->list;
+ if (scalar @list) {
+ $defs{$svcdb}->{$field} = { desc => $pvf->label,
+ type => 'select',
+ select_list => \@list };
+ } else {
+ $defs{$svcdb}->{$field} = $pvf->label;
+ } #endif
+ $vfields{$svcdb}->{$field} = $pvf;
+ warn "\$vfields{$svcdb}->{$field} = $pvf";
+ } #next $field
+ } #next $svcdb
+
my @dbs = $hashref->{svcdb}
? ( $hashref->{svcdb} )
- : qw( svc_acct svc_domain svc_acct_sm svc_forward svc_www );
+ : qw( svc_acct svc_domain svc_forward svc_www svc_broadband svc_external );
- tie my %svcdb, 'Tie::IxHash', map { $_=>$_ } @dbs;
+ 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',
@@ -146,17 +176,18 @@ my %defs = (
my @part_export =
map { qsearch( 'part_export', {exporttype => $_ } ) }
keys %{FS::part_export::export_info($layer)};
- $html .= '<BR><BR>'. table().
+ $html .= '<BR><BR>'. table().
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 qsearchs( 'export_svc', {
+ 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->exporttype. ' to '. $part_export->machine.
- '</TD>';
+ $html .= '>'. $part_export->exportnum. ': '. $part_export->exporttype.
+ ' to '. $part_export->machine. '</TD>';
$count++;
$html .= '</TR><TR>' unless $count % $columns;
}
@@ -183,28 +214,44 @@ my %defs = (
$html .= "<TR><TD>$field";
$html .= "- <FONT SIZE=-1>$desc</FONT>" if $desc;
$html .= "</TD>";
+ $flag = '' if ref($def) && $def->{type} eq 'disabled';
$html .=
qq!<TD><INPUT TYPE="radio" NAME="${layer}__${field}_flag" VALUE=""!.
' CHECKED'x($flag eq ''). ">Off</TD>".
- qq!<TD><INPUT TYPE="radio" NAME="${layer}__${field}_flag" VALUE="D"!.
- ' CHECKED'x($flag eq 'D'). ">Default ".
- qq!<INPUT TYPE="radio" NAME="${layer}__${field}_flag" VALUE="F"!.
- ' CHECKED'x($flag eq 'F'). ">Fixed ".
- '<BR>';
+ '<TD>';
+ unless ( ref($def) && $def->{type} eq 'disabled' ) {
+ $html .=
+ qq!<INPUT TYPE="radio" NAME="${layer}__${field}_flag" VALUE="D"!.
+ ' CHECKED'x($flag eq 'D'). ">Default ".
+ qq!<INPUT TYPE="radio" NAME="${layer}__${field}_flag" VALUE="F"!.
+ ' CHECKED'x($flag eq 'F'). ">Fixed ";
+ $html .= '<BR>';
+ }
if ( ref($def) ) {
if ( $def->{type} eq 'select' ) {
$html .= qq!<SELECT NAME="${layer}__${field}">!;
$html .= '<OPTION> </OPTION>' unless $value;
- foreach my $record ( qsearch( $def->{select_table}, {} ) ) {
- my $rvalue = $record->getfield($def->{select_key});
- $html .= qq!<OPTION VALUE="$rvalue"!.
- ( $rvalue==$value ? ' SELECTED>' : '>' ).
- $record->getfield($def->{select_label}). '</OPTION>';
- }
+ if ( $def->{select_table} ) {
+ foreach my $record ( qsearch( $def->{select_table}, {} ) ) {
+ my $rvalue = $record->getfield($def->{select_key});
+ $html .= qq!<OPTION VALUE="$rvalue"!.
+ ( $rvalue==$value ? ' SELECTED>' : '>' ).
+ $record->getfield($def->{select_label}). '</OPTION>';
+ } #next $record
+ } else { # select_list
+ foreach my $item ( @{$def->{select_list}} ) {
+ $html .= qq!<OPTION VALUE="$item"!.
+ ( $item eq $value ? ' SELECTED>' : '>' ).
+ $item. '</OPTION>';
+ } #next $item
+ } #endif
$html .= '</SELECT>';
} elsif ( $def->{type} eq 'radius_usergroup_selector' ) {
$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};
}
@@ -212,6 +259,11 @@ my %defs = (
$html .=
qq!<INPUT TYPE="text" NAME="${layer}__${field}" VALUE="$value">!;
}
+
+ if($vfields{$layer}->{$field}) {
+ $html .= qq!<BR><INPUT TYPE="radio" NAME="${layer}__${field}_flag" VALUE="X"!.
+ ' CHECKED'x($flag eq 'X'). ">Excluded ";
+ }
$html .= "</TD></TR>\n";
}
$part_svc->svcpart('') if $clone; #undone
diff --git a/httemplate/edit/part_virtual_field.cgi b/httemplate/edit/part_virtual_field.cgi
new file mode 100644
index 0000000..8bc8438
--- /dev/null
+++ b/httemplate/edit/part_virtual_field.cgi
@@ -0,0 +1,92 @@
+<!-- mason kludge -->
+<%
+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);
+print header("$action Virtual Field Definition", '');
+
+print qq!<FONT SIZE="+1" COLOR="#ff0000">Error: !, $cgi->param('error'),
+ "</FONT>"
+ if $cgi->param('error');
+%>
+<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=15 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 ($dbdef->tables) {
+ 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="20" 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><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>
+
+
+</BODY>
+</HTML>
diff --git a/httemplate/edit/process/REAL_cust_pkg.cgi b/httemplate/edit/process/REAL_cust_pkg.cgi
index 2e0352c..7f5c5e4 100755
--- a/httemplate/edit/process/REAL_cust_pkg.cgi
+++ b/httemplate/edit/process/REAL_cust_pkg.cgi
@@ -5,6 +5,8 @@ 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{'expire'} = $cgi->param('expire') ? str2time($cgi->param('expire')) : '';
my $new = new FS::cust_pkg \%hash;
diff --git a/httemplate/edit/process/addr_block/add.cgi b/httemplate/edit/process/addr_block/add.cgi
new file mode 100755
index 0000000..34d799c
--- /dev/null
+++ b/httemplate/edit/process/addr_block/add.cgi
@@ -0,0 +1,20 @@
+<%
+
+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 0000000..85b0d7a
--- /dev/null
+++ b/httemplate/edit/process/addr_block/allocate.cgi
@@ -0,0 +1,25 @@
+<%
+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 0000000..cfb7ed0
--- /dev/null
+++ b/httemplate/edit/process/addr_block/deallocate.cgi
@@ -0,0 +1,24 @@
+<%
+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 0000000..bb6d4ba
--- /dev/null
+++ b/httemplate/edit/process/addr_block/split.cgi
@@ -0,0 +1,19 @@
+<%
+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/cust_main.cgi b/httemplate/edit/process/cust_main.cgi
index 6ce60d1..25c346e 100755
--- a/httemplate/edit/process/cust_main.cgi
+++ b/httemplate/edit/process/cust_main.cgi
@@ -10,16 +10,25 @@ $cgi->param('refnum', (split(/:/, ($cgi->param('refnum'))[0] ))[0] );
my $payby = $cgi->param('payby');
if ( $payby ) {
- $cgi->param('payinfo', $cgi->param( $payby. '_payinfo' ) );
+ if ( $payby eq 'CHEK' || $payby eq 'DCHK' ) {
+ $cgi->param('payinfo',
+ $cgi->param($payby. '_payinfo1'). '@'. $cgi->param($payby. '_payinfo2') );
+ } else {
+ $cgi->param('payinfo', $cgi->param( $payby. '_payinfo' ) );
+ }
$cgi->param('paydate',
- $cgi->param( $payby. '_month' ). '-'. $cgi->param( $payby. '_year' ) );
+ $cgi->param( $payby. '_month' ). '-'. $cgi->param( $payby. '_year' ) );
$cgi->param('payname', $cgi->param( $payby. '_payname' ) );
+ $cgi->param('paycvv', $cgi->param( $payby. '_paycvv' ) )
+ if defined $cgi->param( $payby. '_paycvv' );
}
$cgi->param('otaker', &getotaker );
my @invoicing_list = split( /\s*\,\s*/, $cgi->param('invoicing_list') );
push @invoicing_list, 'POST' if $cgi->param('invoicing_list_POST');
+$cgi->param('invoicing_list', join(',', @invoicing_list) );
+
#create new record object
@@ -46,7 +55,7 @@ if ( $new->custnum eq '' ) {
if ( $cgi->param('pkgpart_svcpart') ) {
my $x = $cgi->param('pkgpart_svcpart');
- $x =~ /^(\d+)_(\d+)$/;
+ $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)
@@ -64,7 +73,7 @@ if ( $new->custnum eq '' ) {
#eslaf
# this should wind up in FS::cust_pkg!
- $error ||= "Agent ". $new->agentnum. " (type ". $agent->typenum. ") can't".
+ $error ||= "Agent ". $new->agentnum. " (type ". $agent->typenum. ") can't ".
"purchase pkgpart ". $pkgpart
#unless $part_pkg{ $pkgpart };
unless $pkgpart_href->{ $pkgpart };
@@ -107,6 +116,11 @@ if ( $new->custnum eq '' ) {
} else { #create old record object
my $old = qsearchs( 'cust_main', { 'custnum' => $new->custnum } );
$error ||= "Old record not found!" unless $old;
+ if ( defined dbdef->table('cust_main')->column('paycvv')
+ && length($old->paycvv)
+ && $new->paycvv =~ /^\s*\*+\s*$/ ) {
+ $new->paycvv($old->paycvv);
+ }
$error ||= $new->replace($old, \@invoicing_list);
}
diff --git a/httemplate/edit/process/cust_main_county-collapse.cgi b/httemplate/edit/process/cust_main_county-collapse.cgi
index 8e67140..5da9dea 100755
--- a/httemplate/edit/process/cust_main_county-collapse.cgi
+++ b/httemplate/edit/process/cust_main_county-collapse.cgi
@@ -3,8 +3,8 @@
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!");
+my $cust_main_county = qsearchs('cust_main_county', { 'taxnum' => $taxnum } )
+ or die "Unknown taxnum $taxnum";
#really should do this in a .pm & start transaction
diff --git a/httemplate/edit/process/cust_main_county.cgi b/httemplate/edit/process/cust_main_county.cgi
index 990a239..9287ed1 100755
--- a/httemplate/edit/process/cust_main_county.cgi
+++ b/httemplate/edit/process/cust_main_county.cgi
@@ -2,17 +2,22 @@
foreach ( grep { /^tax\d+$/ } $cgi->param ) {
/^tax(\d+)$/ or die "Illegal form $_!";
- my($taxnum)=$1;
- my($old)=qsearchs('cust_main_county',{'taxnum'=>$taxnum})
+ my $taxnum = $1;
+ my $old = qsearchs('cust_main_county', { 'taxnum' => $taxnum })
or die "Couldn't find taxnum $taxnum!";
- my $exempt_amount = $cgi->param("exempt_amount$taxnum");
- next unless $old->tax ne $cgi->param("tax$taxnum")
- || $old->exempt_amount ne $exempt_amount;
+ next unless $old->tax != $cgi->param("tax$taxnum")
+ || $old->exempt_amount != $cgi->param("exempt_amount$taxnum")
+ || $old->taxname ne $cgi->param("taxname$taxnum")
+ || $old->setuptax ne $cgi->param("setuptax$taxnum")
+ || $old->recurtax ne $cgi->param("recurtax$taxnum");
my %hash = $old->hash;
$hash{tax} = $cgi->param("tax$taxnum");
- $hash{exempt_amount} = $exempt_amount;
- my($new)=new FS::cust_main_county \%hash;
- my($error)=$new->replace($old);
+ $hash{exempt_amount} = $cgi->param("exempt_amount$taxnum");
+ $hash{taxname} = $cgi->param("taxname$taxnum");
+ $hash{setuptax} = $cgi->param("setuptax$taxnum");
+ $hash{recurtax} = $cgi->param("recurtax$taxnum");
+ my $new = new FS::cust_main_county \%hash;
+ my $error = $new->replace($old);
if ( $error ) {
$cgi->param('error', $error);
print $cgi->redirect(popurl(2). "cust_main_county.cgi?". $cgi->query_string );
diff --git a/httemplate/edit/process/cust_pkg.cgi b/httemplate/edit/process/cust_pkg.cgi
index f8c9f51..df8471c 100755
--- a/httemplate/edit/process/cust_pkg.cgi
+++ b/httemplate/edit/process/cust_pkg.cgi
@@ -11,16 +11,23 @@ my @remove_pkgnums = map {
$1;
} $cgi->param('remove_pkg');
+my $error_redirect;
my @pkgparts;
-foreach my $pkgpart ( map /^pkg(\d+)$/ ? $1 : (), $cgi->param ) {
- if ( $cgi->param("pkg$pkgpart") =~ /^(\d+)$/ ) {
- my $num_pkgs = $1;
- while ( $num_pkgs-- ) {
- push @pkgparts,$pkgpart;
+if ( $cgi->param('new_pkgpart') =~ /^(\d+)$/ ) { #came from misc/change_pkg.cgi
+ $error_redirect = "misc/change_pkg.cgi";
+ @pkgparts = ($1);
+} else { #came from edit/cust_pkg.cgi
+ $error_redirect = "edit/cust_pkg.cgi";
+ 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;
}
- } else {
- $error = "Illegal quantity";
- last;
}
}
@@ -28,7 +35,7 @@ $error ||= FS::cust_pkg::order($custnum,\@pkgparts,\@remove_pkgnums);
if ($error) {
$cgi->param('error', $error);
- print $cgi->redirect(popurl(2). "cust_pkg.cgi?". $cgi->query_string );
+ print $cgi->redirect(popurl(3). $error_redirect. '?'. $cgi->query_string );
} else {
print $cgi->redirect(popurl(3). "view/cust_main.cgi?$custnum");
}
diff --git a/httemplate/edit/process/generic.cgi b/httemplate/edit/process/generic.cgi
new file mode 100644
index 0000000..9c54feb
--- /dev/null
+++ b/httemplate/edit/process/generic.cgi
@@ -0,0 +1,70 @@
+<%
+
+# 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).
+
+
+use FS::Record qw(qsearchs dbdef);
+use DBIx::DBSchema;
+use DBIx::DBSchema::Table;
+
+
+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());
+
+if($error) {
+ $cgi->param('error', $error);
+ print $cgi->redirect($redirect_error . '?' . $cgi->query_string);
+} else {
+ print $cgi->redirect($redirect_ok);
+}
+%>
diff --git a/httemplate/edit/process/part_bill_event.cgi b/httemplate/edit/process/part_bill_event.cgi
index 4049ade..e224bf6 100755
--- a/httemplate/edit/process/part_bill_event.cgi
+++ b/httemplate/edit/process/part_bill_event.cgi
@@ -12,7 +12,7 @@ if ( ! $cgi->param('plan_weight_eventcode') ) {
$error = "Must select an action";
} else {
- $cgi->param('plan_weight_eventcode') =~ /^([\w\-]+):(\d+):(.*)$/
+ $cgi->param('plan_weight_eventcode') =~ /^([\w\-]+):(\d+):(.*)$/s
or die "illegal plan_weight_eventcode:".
$cgi->param('plan_weight_eventcode');
$cgi->param('plan', $1);
diff --git a/httemplate/edit/process/part_pkg.cgi b/httemplate/edit/process/part_pkg.cgi
index d489426..7eada7b 100755
--- a/httemplate/edit/process/part_pkg.cgi
+++ b/httemplate/edit/process/part_pkg.cgi
@@ -62,16 +62,24 @@ if ( $error ) {
foreach my $part_svc (qsearch('part_svc',{})) {
my $quantity = $cgi->param('pkg_svc'. $part_svc->svcpart) || 0;
+ my $primary_svc =
+ $cgi->param('pkg_svc_primary') == $part_svc->svcpart ? 'Y' : '';
my $old_pkg_svc = qsearchs('pkg_svc', {
'pkgpart' => $pkgpart,
'svcpart' => $part_svc->svcpart,
} );
my $old_quantity = $old_pkg_svc ? $old_pkg_svc->quantity : 0;
- next unless $old_quantity != $quantity; #!here
+ my $old_primary_svc =
+ ( $old_pkg_svc && $old_pkg_svc->dbdef_table->column('primary_svc') )
+ ? $old_pkg_svc->primary_svc
+ : '';
+ next unless $old_quantity != $quantity || $old_primary_svc ne $primary_svc;
+
my $new_pkg_svc = new FS::pkg_svc( {
- 'pkgpart' => $pkgpart,
- 'svcpart' => $part_svc->svcpart,
- 'quantity' => $quantity,
+ 'pkgpart' => $pkgpart,
+ 'svcpart' => $part_svc->svcpart,
+ 'quantity' => $quantity,
+ 'primary_svc' => $primary_svc,
} );
if ( $old_pkg_svc ) {
my $myerror = $new_pkg_svc->replace($old_pkg_svc);
diff --git a/httemplate/edit/process/part_svc.cgi b/httemplate/edit/process/part_svc.cgi
index 859670b..9633fab 100755
--- a/httemplate/edit/process/part_svc.cgi
+++ b/httemplate/edit/process/part_svc.cgi
@@ -17,7 +17,7 @@ my $new = new FS::part_svc ( {
push @fields, 'usergroup' if $svcdb eq 'svc_acct'; #kludge
map { ( $svcdb.'__'.$_, $svcdb.'__'.$_.'_flag' ) } @fields;
} grep defined( $FS::Record::dbdef->table($_) ),
- qw( svc_acct svc_domain svc_acct_sm svc_forward svc_www )
+ qw( svc_acct svc_domain svc_forward svc_www svc_broadband )
)
} );
diff --git a/httemplate/edit/process/router.cgi b/httemplate/edit/process/router.cgi
new file mode 100644
index 0000000..a2fa46d
--- /dev/null
+++ b/httemplate/edit/process/router.cgi
@@ -0,0 +1,67 @@
+<%
+
+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");
+
+%>
diff --git a/httemplate/edit/process/svc_acct_sm.cgi b/httemplate/edit/process/svc_acct_sm.cgi
deleted file mode 100755
index 41d03fb..0000000
--- a/httemplate/edit/process/svc_acct_sm.cgi
+++ /dev/null
@@ -1,34 +0,0 @@
-<%
-
-$cgi->param('svcnum') =~ /^(\d*)$/ or die "Illegal svcnum!";
-my $svcnum =$1;
-
-my $old = qsearchs('svc_acct_sm',{'svcnum'=>$svcnum}) if $svcnum;
-
-#unmunge domsvc and domuid
-#$cgi->param('domsvc',(split(/:/, $cgi->param('domsvc') ))[0] );
-#$cgi->param('domuid',(split(/:/, $cgi->param('domuid') ))[0] );
-
-my $new = new FS::svc_acct_sm ( {
- map {
- ($_, scalar($cgi->param($_)));
- #} qw(svcnum pkgnum svcpart domuser domuid domsvc)
- } ( fields('svc_acct_sm'), qw( pkgnum svcpart ) )
-} );
-
-my $error = '';
-if ( $svcnum ) {
- $error = $new->replace($old);
-} else {
- $error = $new->insert;
- $svcnum = $new->getfield('svcnum');
-}
-
-if ($error) {
- $cgi->param('error', $error);
- print $cgi->redirect(popurl(2). "svc_acct_sm.cgi?". $cgi->query_string );
-} else {
- print $cgi->redirect(popurl(3). "view/svc_acct_sm.cgi?$svcnum");
-}
-
-%>
diff --git a/httemplate/edit/process/svc_broadband.cgi b/httemplate/edit/process/svc_broadband.cgi
new file mode 100644
index 0000000..4912a3a
--- /dev/null
+++ b/httemplate/edit/process/svc_broadband.cgi
@@ -0,0 +1,45 @@
+<%
+
+# If it's stupid but it works, it's not stupid.
+# -- U.S. Army
+
+local $FS::UID::AutoCommit = 0;
+my $dbh = FS::UID::dbh;
+
+$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;
+}
+
+
+if ( $error ) {
+ $cgi->param('error', $error);
+ $cgi->param('ip_addr', $new->ip_addr);
+ $dbh->rollback;
+ print $cgi->redirect(popurl(2). "svc_broadband.cgi?". $cgi->query_string );
+} else {
+ $dbh->commit or die $dbh->errstr;
+ print $cgi->redirect(popurl(3). "view/svc_broadband.cgi?" . $svcnum );
+}
+
+%>
diff --git a/httemplate/edit/process/svc_external.cgi b/httemplate/edit/process/svc_external.cgi
new file mode 100755
index 0000000..728cd21
--- /dev/null
+++ b/httemplate/edit/process/svc_external.cgi
@@ -0,0 +1,29 @@
+<%
+
+$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');
+}
+
+if ($error) {
+ $cgi->param('error', $error);
+ print $cgi->redirect(popurl(2). "svc_external.cgi?". $cgi->query_string );
+} else {
+ print $cgi->redirect(popurl(3). "view/svc_external.cgi?$svcnum");
+}
+
+%>
diff --git a/httemplate/edit/router.cgi b/httemplate/edit/router.cgi
new file mode 100755
index 0000000..a573c65
--- /dev/null
+++ b/httemplate/edit/router.cgi
@@ -0,0 +1,77 @@
+<HTML><BODY>
+
+<%
+
+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';
+
+print header("$action Router", menubar(
+ 'Main Menu' => "$p",
+ 'View all routers' => "${p}browse/router.cgi",
+));
+
+my $p3 = popurl(3);
+
+if($cgi->param('error')) {
+%> <FONT SIZE="+1" COLOR="#ff0000">Error: <%=$cgi->param('error')%></FONT>
+<% } %>
+
+<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>
+</BODY></HTML>
+
diff --git a/httemplate/edit/svc_acct.cgi b/httemplate/edit/svc_acct.cgi
index 90b2632..f1b8b80 100755
--- a/httemplate/edit/svc_acct.cgi
+++ b/httemplate/edit/svc_acct.cgi
@@ -35,8 +35,6 @@ if ( $cgi->param('error') ) {
} else { #adding
- $svc_acct = new FS::svc_acct({});
-
foreach $_ (split(/-/,$query)) {
$pkgnum=$1 if /^pkgnum(\d+)$/;
$svcpart=$1 if /^svcpart(\d+)$/;
@@ -44,6 +42,8 @@ if ( $cgi->param('error') ) {
$part_svc = qsearchs( 'part_svc', { 'svcpart' => $svcpart } );
die "No part_svc entry for svcpart $svcpart!" unless $part_svc;
+ $svc_acct = new FS::svc_acct({svcpart => $svcpart});
+
$svcnum='';
#set gecos
@@ -72,6 +72,12 @@ if ( $cgi->param('error') ) {
}
}
+
+#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');
@@ -163,7 +169,7 @@ if ( $part_svc->part_svc_column('domsvc')->columnflag eq 'F' ) {
}
my $cust_pkg = qsearchs('cust_pkg', { 'pkgnum' => $pkgnum } );
- if ($cust_pkg) {
+ if ($cust_pkg && !$conf->exists('svc_acct-alldomains') ) {
my @cust_svc =
map { qsearch('cust_svc', { 'pkgnum' => $_->pkgnum } ) }
qsearch('cust_pkg', { 'custnum' => $cust_pkg->custnum } );
@@ -238,7 +244,16 @@ my($quota,$slipip)=(
$svc_acct->slipip,
);
-print qq!<INPUT TYPE="hidden" NAME="quota" VALUE="$quota">!;
+if ( $part_svc->part_svc_column('quota')->columnflag eq "F" )
+{
+ print qq!<INPUT TYPE="hidden" NAME="quota" VALUE="$quota">!;
+} else {
+ print <<END;
+ <TR><TD ALIGN="right">Quota:</TD>
+ <TD> <INPUT TYPE="text" NAME="quota" VALUE="$quota" ></TD>
+ </TR>
+END
+}
if ( $part_svc->part_svc_column('slipip')->columnflag eq "F" ) {
print qq!<INPUT TYPE="hidden" NAME="slipip" VALUE="$slipip">!;
@@ -266,6 +281,14 @@ if ( $part_svc->part_svc_column('usergroup')->columnflag eq "F" ) {
}
print '</TD></TR>';
+foreach my $field ($svc_acct->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.
+ print $svc_acct->pvf($field)->widget('HTML', 'edit',
+ $svc_acct->getfield($field));
+ }
+}
+
#submit
print qq!</TABLE><BR><INPUT TYPE="submit" VALUE="Submit">!;
diff --git a/httemplate/edit/svc_acct_sm.cgi b/httemplate/edit/svc_acct_sm.cgi
deleted file mode 100755
index 0fd5f76..0000000
--- a/httemplate/edit/svc_acct_sm.cgi
+++ /dev/null
@@ -1,178 +0,0 @@
-<!-- mason kludge -->
-<%
-
-my $conf = new FS::Conf;
-my $mydomain = $conf->config('domain');
-
-my($svcnum, $pkgnum, $svcpart, $part_svc, $svc_acct_sm );
-if ( $cgi->param('error') ) {
- $svc_acct_sm = new FS::svc_acct_sm ( {
- map { $_, scalar($cgi->param($_)) } fields('svc_acct_sm')
- } );
- $svcnum = $svc_acct_sm->svcnum;
- $pkgnum = $cgi->param('pkgnum');
- $svcpart = $cgi->param('svcpart');
- #$part_svc=qsearchs('part_svc',{'svcpart'=>$svcpart});
- #die "No part_svc entry!" unless $part_svc;
-} else {
- my($query) = $cgi->keywords;
- if ( $query =~ /^(\d+)$/ ) { #editing
- $svcnum=$1;
- $svc_acct_sm=qsearchs('svc_acct_sm',{'svcnum'=>$svcnum})
- or die "Unknown (svc_acct_sm) 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;
-
- } else { #adding
-
- $svc_acct_sm = new FS::svc_acct_sm({});
-
- foreach $_ (split(/-/,$query)) { #get & untaint pkgnum & svcpart
- $pkgnum=$1 if /^pkgnum(\d+)$/;
- $svcpart=$1 if /^svcpart(\d+)$/;
- }
- my $part_svc=qsearchs('part_svc',{'svcpart'=>$svcpart});
- die "No part_svc entry!" unless $part_svc;
-
- $svcnum='';
-
- #set fixed and default fields from part_svc
- foreach my $part_svc_column (
- grep { $_->columnflag } $part_svc->all_part_svc_column
- ) {
- $svc_acct_sm->setfield( $part_svc_column->columnname,
- $part_svc_column->columnvalue,
- );
- }
-
- }
-}
-my $action = $svc_acct_sm->svcnum ? 'Edit' : 'Add';
-
-my %username = ();
-my %domain = ();
-if ($pkgnum) {
-
- #find all possible uids (and usernames)
-
- my @u_acct_svcparts = ();
- foreach my $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');
- foreach my $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')});
- $username{$svc_acct->getfield('uid')}=$svc_acct->getfield('username');
- }
- }
- }
-
- #find all possible domains (and domsvc's)
-
- my @d_acct_svcparts = ();
- foreach my $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->getfield('pkgnum');
- my($acct_svcpart);
- foreach $acct_svcpart (@d_acct_svcparts) {
- my($i_cust_svc);
- foreach $i_cust_svc ( qsearch('cust_svc',{'pkgnum'=>$cust_pkgnum,'svcpart'=>$acct_svcpart}) ) {
- my($svc_domain)=qsearch('svc_domain',{'svcnum'=>$i_cust_svc->getfield('svcnum')});
- $domain{$svc_domain->getfield('svcnum')}=$svc_domain->getfield('domain');
- }
- }
- }
-
-} elsif ( $action eq 'Edit' ) {
-
- my($svc_acct)=qsearchs('svc_acct',{'uid'=>$svc_acct_sm->domuid});
- $username{$svc_acct_sm->uid} = $svc_acct->username;
-
- my($svc_domain)=qsearchs('svc_domain',{'svcnum'=>$svc_acct_sm->domsvc});
- $domain{$svc_acct_sm->domsvc} = $svc_domain->domain;
-
-} else {
- die "\$action eq Add, but \$pkgnum is null!\n";
-}
-
-my $p1 = popurl(1);
-print header("Mail Alias $action", '');
-
-print qq!<FONT SIZE="+1" COLOR="#ff0000">Error: !, $cgi->param('error'),
- "</FONT>"
- if $cgi->param('error');
-
-print qq!<FORM ACTION="${p1}process/svc_acct_sm.cgi" METHOD=POST>!;
-
-#display
-
- #formatting
- print "<PRE>";
-
-#svcnum
-print qq!<INPUT TYPE="hidden" NAME="svcnum" VALUE="$svcnum">!;
-print qq!Service #<FONT SIZE=+1><B>!, $svcnum ? $svcnum : " (NEW)", "</B></FONT>";
-
-#pkgnum
-print qq!<INPUT TYPE="hidden" NAME="pkgnum" VALUE="$pkgnum">!;
-
-#svcpart
-print qq!<INPUT TYPE="hidden" NAME="svcpart" VALUE="$svcpart">!;
-
-my($domuser,$domsvc,$domuid)=(
- $svc_acct_sm->domuser,
- $svc_acct_sm->domsvc,
- $svc_acct_sm->domuid,
-);
-
-#domuser
-print qq!\n\nMail to <INPUT TYPE="text" NAME="domuser" VALUE="$domuser"> <I>( * for anything )</I>!;
-
-#domsvc
-print qq! \@ <SELECT NAME="domsvc" SIZE=1>!;
-foreach $_ (keys %domain) {
- print "<OPTION", $_ eq $domsvc ? " SELECTED" : "",
- qq! VALUE="$_">$domain{$_}!;
-}
-print "</SELECT>";
-
-#uid
-print qq!\nforwards to <SELECT NAME="domuid" SIZE=1>!;
-foreach $_ (keys %username) {
- print "<OPTION", ($_ eq $domuid) ? " SELECTED" : "",
- qq! VALUE="$_">$username{$_}!;
-}
-print "</SELECT>\@$mydomain mailbox.";
-
- #formatting
- print "</PRE>\n";
-
-print qq!<CENTER><INPUT TYPE="submit" VALUE="Submit"></CENTER>!;
-
-print <<END;
-
- </FORM>
- </BODY>
-</HTML>
-END
-
-%>
diff --git a/httemplate/edit/svc_broadband.cgi b/httemplate/edit/svc_broadband.cgi
new file mode 100644
index 0000000..db27b32
--- /dev/null
+++ b/httemplate/edit/svc_broadband.cgi
@@ -0,0 +1,172 @@
+<!-- mason kludge -->
+<%
+
+# 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')
+ } );
+ $svcnum = $svc_broadband->svcnum;
+ $pkgnum = $cgi->param('pkgnum');
+ $svcpart = $cgi->param('svcpart');
+ $part_svc=qsearchs('part_svc',{'svcpart'=>$svcpart});
+ die "No part_svc entry!" unless $part_svc;
+} else {
+ my($query) = $cgi->keywords;
+ if ( $query =~ /^(\d+)$/ ) { #editing
+ $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;
+
+ } else { #adding
+
+ foreach $_ (split(/-/,$query)) { #get & untaint pkgnum & svcpart
+ $pkgnum=$1 if /^pkgnum(\d+)$/;
+ $svcpart=$1 if /^svcpart(\d+)$/;
+ }
+ $part_svc=qsearchs('part_svc',{'svcpart'=>$svcpart});
+ die "No part_svc entry!" unless $part_svc;
+
+ $svc_broadband = new FS::svc_broadband({ svcpart => $svcpart });
+
+ $svcnum='';
+
+ #set fixed and default fields from part_svc
+ foreach my $part_svc_column (
+ grep { $_->columnflag } $part_svc->all_part_svc_column
+ ) {
+ $svc_broadband->setfield( $part_svc_column->columnname,
+ $part_svc_column->columnvalue,
+ );
+ }
+
+ }
+}
+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) =
+ ($svc_broadband->ip_addr,
+ $svc_broadband->speed_up,
+ $svc_broadband->speed_down,
+ $svc_broadband->blocknum);
+
+%>
+
+<%=header("Broadband Service $action", '')%>
+
+<% if ($cgi->param('error')) { %>
+<FONT SIZE="+1" COLOR="#ff0000">Error: <%=$cgi->param('error')%></FONT><BR>
+<% } %>
+
+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">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">
+<%
+ foreach my $router ($svc_broadband->allowed_routers) {
+ 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>
+
+<% } %>
+
+<%
+foreach my $field ($svc_broadband->virtual_fields) {
+ if ( $part_svc->part_svc_column($field)->columnflag ne 'F' ) {
+ print $svc_broadband->pvf($field)->widget('HTML', 'edit',
+ $svc_broadband->getfield($field));
+ }
+} %>
+ </TABLE>
+ <BR>
+ <INPUT TYPE="submit" NAME="submit" VALUE="Submit">
+</FORM>
+</BODY>
+</HTML>
+
diff --git a/httemplate/edit/svc_domain.cgi b/httemplate/edit/svc_domain.cgi
index d20e1f3..ca0e339 100755
--- a/httemplate/edit/svc_domain.cgi
+++ b/httemplate/edit/svc_domain.cgi
@@ -87,7 +87,7 @@ print ' CHECKED' if $kludge_action eq 'M';
print qq!>Transfer!;
print <<END;
-<P>Domain <INPUT TYPE="text" NAME="domain" VALUE="$domain" SIZE=28 MAXLENGTH=26>
+<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>
diff --git a/httemplate/edit/svc_external.cgi b/httemplate/edit/svc_external.cgi
new file mode 100644
index 0000000..bcfc85e
--- /dev/null
+++ b/httemplate/edit/svc_external.cgi
@@ -0,0 +1,105 @@
+<!-- mason kludge -->
+<%
+
+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;
+} else {
+ my($query) = $cgi->keywords;
+ if ( $query =~ /^(\d+)$/ ) { #editing
+ $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;
+
+ } else { #adding
+
+ foreach $_ (split(/-/,$query)) { #get & untaint pkgnum & svcpart
+ $pkgnum=$1 if /^pkgnum(\d+)$/;
+ $svcpart=$1 if /^svcpart(\d+)$/;
+ }
+ $svc_external = new FS::svc_external { svcpart => $svcpart };
+
+ $part_svc=qsearchs('part_svc',{'svcpart'=>$svcpart});
+ die "No part_svc entry!" unless $part_svc;
+
+ $svcnum='';
+
+ #set fixed and default fields from part_svc
+ foreach my $part_svc_column (
+ grep { $_->columnflag } $part_svc->all_part_svc_column
+ ) {
+ $svc_external->setfield( $part_svc_column->columnname,
+ $part_svc_column->columnvalue,
+ );
+ }
+
+ }
+}
+my $action = $svc_external->svcnum ? 'Edit' : 'Add';
+
+my $p1 = popurl(1);
+print header("External service $action", '');
+
+print qq!<FONT SIZE="+1" COLOR="#ff0000">Error: !, $cgi->param('error'),
+ "</FONT>"
+ if $cgi->param('error');
+
+print qq!<FORM ACTION="${p1}process/svc_external.cgi" METHOD=POST>!;
+
+#display
+
+
+#svcnum
+print qq!<INPUT TYPE="hidden" NAME="svcnum" VALUE="$svcnum">!;
+print qq!Service #<B>!, $svcnum ? $svcnum : "(NEW)", "</B><BR><BR>";
+
+#pkgnum
+print qq!<INPUT TYPE="hidden" NAME="pkgnum" VALUE="$pkgnum">!;
+
+#svcpart
+print qq!<INPUT TYPE="hidden" NAME="svcpart" VALUE="$svcpart">!;
+
+my($id,$title)=(
+ $svc_external->id,
+ $svc_external->title,
+);
+
+print &ntable("#cccccc",2),
+ '<TR><TD ALIGN="right">External ID</TD><TD>'.
+ qq!<INPUT TYPE="text" NAME="id" VALUE="$id">!.
+ '</TD></TR>'.
+ '<TR><TD ALIGN="right">Title</TD><TD>'.
+ qq!<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.
+ print $svc_external->pvf($field)->widget('HTML', 'edit',
+ $svc_external->getfield($field));
+ }
+}
+
+%>
+
+</TABLE><BR><INPUT TYPE="submit" VALUE="Submit">
+ </FORM>
+ </BODY>
+</HTML>
+
diff --git a/httemplate/edit/svc_forward.cgi b/httemplate/edit/svc_forward.cgi
index bc19fe1..2e6c5f1 100755
--- a/httemplate/edit/svc_forward.cgi
+++ b/httemplate/edit/svc_forward.cgi
@@ -2,7 +2,6 @@
<%
my $conf = new FS::Conf;
-my $mydomain = $conf->config('domain');
my($svcnum, $pkgnum, $svcpart, $part_svc, $svc_forward);
if ( $cgi->param('error') ) {
@@ -59,20 +58,17 @@ if ( $cgi->param('error') ) {
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)
- #starting with those currently attached
- if ( $svc_forward->srcsvc ) {
- my $svc_acct = qsearchs( 'svc_acct', { 'svcnum' => $svc_forward->srcsvc } );
- $email{$svc_forward->srcsvc} = $svc_acct->email;
- }
- if ( $svc_forward->dstsvc ) {
- my $svc_acct = qsearchs( 'svc_acct', { 'svcnum' => $svc_forward->dstsvc } );
- $email{$svc_forward->dstsvc} = $svc_acct->email;
- }
-
#and including the rest for this customer
my($u_part_svc,@u_acct_svcparts);
foreach $u_part_svc ( qsearch('part_svc',{'svcdb'=>'svc_acct'}) ) {
@@ -99,15 +95,7 @@ if ($pkgnum) {
}
}
-} elsif ( $action eq 'Edit' ) {
-
- my($svc_acct)=qsearchs('svc_acct',{'svcnum'=>$svc_forward->srcsvc});
- $email{$svc_forward->srcsvc} = $svc_acct->email;
-
- $svc_acct=qsearchs('svc_acct',{'svcnum'=>$svc_forward->dstsvc});
- $email{$svc_forward->dstsvc} = $svc_acct->email;
-
-} else {
+} elsif ( $action eq 'Add' ) {
die "\$action eq Add, but \$pkgnum is null!\n";
}
@@ -116,6 +104,7 @@ my($srcsvc,$dstsvc,$dst)=(
$svc_forward->dstsvc,
$svc_forward->dst,
);
+my $src = $svc_forward->dbdef_table->column('src') ? $svc_forward->src : '';
#display
@@ -131,46 +120,54 @@ my($srcsvc,$dstsvc,$dst)=(
Service #<%= $svcnum ? "<B>$svcnum</B>" : " (NEW)" %><BR>
Service: <B><%= $part_svc->svc %></B><BR><BR>
-<FORM NAME="dummy">
+<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;
+ } else {
+ what.form.src.disabled = true;
+ }
+}
+function dstchanged(what) {
+ if ( what.options[what.selectedIndex].value == 0 ) {
+ what.form.dst.disabled = false;
+ } else {
+ what.form.dst.disabled = true;
+ }
+}
+</SCRIPT>
<%= ntable("#cccccc",2) %>
-<TR><TD ALIGN="right">Email to</TD><TD><SELECT NAME="srcsvc" SIZE=1>
+<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>
<% } %>
-</SELECT></TD></TR>
-
-<%
- tie my %tied_email, 'Tie::IxHash',
- '' => 'SELECT DESTINATION',
- %email,
- '0' => '(other email address)';
- my $widget = new HTML::Widgets::SelectLayers(
- 'selected_layer' => $dstsvc,
- 'options' => \%tied_email,
- 'form_name' => 'dummy',
- 'form_action' => 'process/svc_forward.cgi',
- 'form_select' => ['srcsvc'],
- 'html_between' => '</TD></TR></TABLE>',
- 'layer_callback' => sub {
- my $layer = shift;
- my $html = qq!<INPUT TYPE="hidden" NAME="svcnum" VALUE="$svcnum">!.
- qq!<INPUT TYPE="hidden" NAME="pkgnum" VALUE="$pkgnum">!.
- qq!<INPUT TYPE="hidden" NAME="svcpart" VALUE="$svcpart">!.
- qq!<INPUT TYPE="hidden" NAME="dstsvc" VALUE="$layer">!;
- if ( $layer eq '0' ) {
- $html .= ntable("#cccccc",2).
- '<TR><TD ALIGN="right">Destination email</TD>'.
- qq!<TD><INPUT TYPE="text" NAME="dst" VALUE="$dst"></TD>!.
- '</TR></TABLE>';
- }
- $html .= '<BR><INPUT TYPE="submit" VALUE="Submit">';
- $html;
- },
- );
-%>
+<% 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' %>>
+<% } %>
+</TD></TR>
<TR><TD ALIGN="right">Forwards to</TD>
-<TD><%= $widget->html %>
+<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' %>>
+</TD></TR>
+ </TABLE>
+<BR><INPUT TYPE="submit" VALUE="Submit">
+</FORM>
</BODY>
</HTML>
diff --git a/httemplate/edit/svc_www.cgi b/httemplate/edit/svc_www.cgi
index d2c9ade..ec5169e 100644
--- a/httemplate/edit/svc_www.cgi
+++ b/httemplate/edit/svc_www.cgi
@@ -29,12 +29,12 @@ if ( $cgi->param('error') ) {
} else { #adding
- $svc_www = new FS::svc_www({});
-
foreach $_ (split(/-/,$query)) { #get & untaint pkgnum & svcpart
$pkgnum=$1 if /^pkgnum(\d+)$/;
$svcpart=$1 if /^svcpart(\d+)$/;
}
+ $svc_www = new FS::svc_www { svcpart => $svcpart };
+
$part_svc=qsearchs('part_svc',{'svcpart'=>$svcpart});
die "No part_svc entry!" unless $part_svc;
@@ -167,6 +167,14 @@ foreach $_ (keys %username) {
}
print "</SELECT></TD></TR>";
+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.
+ print $svc_www->pvf($field)->widget('HTML', 'edit',
+ $svc_www->getfield($field));
+ }
+}
+
print '</TABLE><BR><INPUT TYPE="submit" VALUE="Submit">';
print <<END;
diff --git a/httemplate/elements/calendar-en.js b/httemplate/elements/calendar-en.js
new file mode 100644
index 0000000..e9291e1
--- /dev/null
+++ b/httemplate/elements/calendar-en.js
@@ -0,0 +1,114 @@
+// ** I18N
+
+// Calendar EN language
+// Author: Mihai Bazon, <mishoo@infoiasi.ro>
+// Encoding: any
+// Distributed under the same terms as the calendar itself.
+
+// For translators: please use UTF-8 if possible. We strongly believe that
+// Unicode is the answer to a real internationalized world. Also please
+// include your contact information in the header, as can be seen above.
+
+// full day names
+Calendar._DN = new Array
+("Sunday",
+ "Monday",
+ "Tuesday",
+ "Wednesday",
+ "Thursday",
+ "Friday",
+ "Saturday",
+ "Sunday");
+
+// Please note that the following array of short day names (and the same goes
+// for short month names, _SMN) isn't absolutely necessary. We give it here
+// for exemplification on how one can customize the short day names, but if
+// they are simply the first N letters of the full name you can simply say:
+//
+// Calendar._SDN_len = N; // short day name length
+// Calendar._SMN_len = N; // short month name length
+//
+// If N = 3 then this is not needed either since we assume a value of 3 if not
+// present, to be compatible with translation files that were written before
+// this feature.
+
+// short day names
+Calendar._SDN = new Array
+("Sun",
+ "Mon",
+ "Tue",
+ "Wed",
+ "Thu",
+ "Fri",
+ "Sat",
+ "Sun");
+
+// full month names
+Calendar._MN = new Array
+("January",
+ "February",
+ "March",
+ "April",
+ "May",
+ "June",
+ "July",
+ "August",
+ "September",
+ "October",
+ "November",
+ "December");
+
+// short month names
+Calendar._SMN = new Array
+("Jan",
+ "Feb",
+ "Mar",
+ "Apr",
+ "May",
+ "Jun",
+ "Jul",
+ "Aug",
+ "Sep",
+ "Oct",
+ "Nov",
+ "Dec");
+
+// tooltips
+Calendar._TT = {};
+Calendar._TT["INFO"] = "About the calendar";
+
+Calendar._TT["ABOUT"] =
+"DHTML Date/Time Selector\n" +
+"(c) dynarch.com 2002-2003\n" + // don't translate this this ;-)
+"For latest version visit: http://dynarch.com/mishoo/calendar.epl\n" +
+"Distributed under GNU LGPL. See http://gnu.org/licenses/lgpl.html for details." +
+"\n\n" +
+"Date selection:\n" +
+"- Use the \xab, \xbb buttons to select year\n" +
+"- Use the " + String.fromCharCode(0x2039) + ", " + String.fromCharCode(0x203a) + " buttons to select month\n" +
+"- Hold mouse button on any of the above buttons for faster selection.";
+Calendar._TT["ABOUT_TIME"] = "\n\n" +
+"Time selection:\n" +
+"- Click on any of the time parts to increase it\n" +
+"- or Shift-click to decrease it\n" +
+"- or click and drag for faster selection.";
+
+Calendar._TT["PREV_YEAR"] = "Prev. year (hold for menu)";
+Calendar._TT["PREV_MONTH"] = "Prev. month (hold for menu)";
+Calendar._TT["GO_TODAY"] = "Go Today";
+Calendar._TT["NEXT_MONTH"] = "Next month (hold for menu)";
+Calendar._TT["NEXT_YEAR"] = "Next year (hold for menu)";
+Calendar._TT["SEL_DATE"] = "Select date";
+Calendar._TT["DRAG_TO_MOVE"] = "Drag to move";
+Calendar._TT["PART_TODAY"] = " (today)";
+Calendar._TT["MON_FIRST"] = "Display Monday first";
+Calendar._TT["SUN_FIRST"] = "Display Sunday first";
+Calendar._TT["CLOSE"] = "Close";
+Calendar._TT["TODAY"] = "Today";
+Calendar._TT["TIME_PART"] = "(Shift-)Click or drag to change value";
+
+// date formats
+Calendar._TT["DEF_DATE_FORMAT"] = "%Y-%m-%d";
+Calendar._TT["TT_DATE_FORMAT"] = "%a, %b %e";
+
+Calendar._TT["WK"] = "wk";
diff --git a/httemplate/elements/calendar-setup.js b/httemplate/elements/calendar-setup.js
new file mode 100644
index 0000000..0dc3caa
--- /dev/null
+++ b/httemplate/elements/calendar-setup.js
@@ -0,0 +1,163 @@
+/* Copyright Mihai Bazon, 2002, 2003 | http://dynarch.com/mishoo/
+ * ---------------------------------------------------------------------------
+ *
+ * The DHTML Calendar
+ *
+ * Details and latest version at:
+ * http://dynarch.com/mishoo/calendar.epl
+ *
+ * This script is distributed under the GNU Lesser General Public License.
+ * Read the entire license text here: http://www.gnu.org/licenses/lgpl.html
+ *
+ * This file defines helper functions for setting up the calendar. They are
+ * intended to help non-programmers get a working calendar on their site
+ * quickly. This script should not be seen as part of the calendar. It just
+ * shows you what one can do with the calendar, while in the same time
+ * providing a quick and simple method for setting it up. If you need
+ * exhaustive customization of the calendar creation process feel free to
+ * modify this code to suit your needs (this is recommended and much better
+ * than modifying calendar.js itself).
+ */
+
+// $Id: calendar-setup.js,v 1.3 2003-11-07 10:53:35 ivan Exp $
+
+/**
+ * This function "patches" an input field (or other element) to use a calendar
+ * widget for date selection.
+ *
+ * The "params" is a single object that can have the following properties:
+ *
+ * prop. name | description
+ * -------------------------------------------------------------------------------------------------
+ * inputField | the ID of an input field to store the date
+ * displayArea | the ID of a DIV or other element to show the date
+ * button | ID of a button or other element that will trigger the calendar
+ * eventName | event that will trigger the calendar, without the "on" prefix (default: "click")
+ * ifFormat | date format that will be stored in the input field
+ * daFormat | the date format that will be used to display the date in displayArea
+ * singleClick | (true/false) wether the calendar is in single click mode or not (default: true)
+ * mondayFirst | (true/false) if true Monday is the first day of week, Sunday otherwise (default: true)
+ * align | alignment (default: "Bl"); if you don't know what's this see the calendar documentation
+ * range | array with 2 elements. Default: [1900, 2999] -- the range of years available
+ * weekNumbers | (true/false) if it's true (default) the calendar will display week numbers
+ * flat | null or element ID; if not null the calendar will be a flat calendar having the parent with the given ID
+ * flatCallback | function that receives a JS Date object and returns an URL to point the browser to (for flat calendar)
+ * disableFunc | function that receives a JS Date object and should return true if that date has to be disabled in the calendar
+ * onSelect | function that gets called when a date is selected. You don't _have_ to supply this (the default is generally okay)
+ * onClose | function that gets called when the calendar is closed. [default]
+ * onUpdate | function that gets called after the date is updated in the input field. Receives a reference to the calendar.
+ * date | the date that the calendar will be initially displayed to
+ * showsTime | default: false; if true the calendar will include a time selector
+ * timeFormat | the time format; can be "12" or "24", default is "12"
+ *
+ * None of them is required, they all have default values. However, if you
+ * pass none of "inputField", "displayArea" or "button" you'll get a warning
+ * saying "nothing to setup".
+ */
+Calendar.setup = function (params) {
+ function param_default(pname, def) { if (typeof params[pname] == "undefined") { params[pname] = def; } };
+
+ param_default("inputField", null);
+ param_default("displayArea", null);
+ param_default("button", null);
+ param_default("eventName", "click");
+ param_default("ifFormat", "%Y/%m/%d");
+ param_default("daFormat", "%Y/%m/%d");
+ param_default("singleClick", true);
+ param_default("disableFunc", null);
+ param_default("dateStatusFunc", params["disableFunc"]); // takes precedence if both are defined
+ param_default("mondayFirst", true);
+ param_default("align", "Bl");
+ param_default("range", [1900, 2999]);
+ param_default("weekNumbers", true);
+ param_default("flat", null);
+ param_default("flatCallback", null);
+ param_default("onSelect", null);
+ param_default("onClose", null);
+ param_default("onUpdate", null);
+ param_default("date", null);
+ param_default("showsTime", false);
+ param_default("timeFormat", "24");
+
+ var tmp = ["inputField", "displayArea", "button"];
+ for (var i in tmp) {
+ if (typeof params[tmp[i]] == "string") {
+ params[tmp[i]] = document.getElementById(params[tmp[i]]);
+ }
+ }
+ if (!(params.flat || params.inputField || params.displayArea || params.button)) {
+ alert("Calendar.setup:\n Nothing to setup (no fields found). Please check your code");
+ return false;
+ }
+
+ function onSelect(cal) {
+ if (cal.params.flat) {
+ if (typeof cal.params.flatCallback == "function") {
+ cal.params.flatCallback(cal);
+ } else {
+ alert("No flatCallback given -- doing nothing.");
+ }
+ return false;
+ }
+ if (cal.params.inputField) {
+ cal.params.inputField.value = cal.date.print(cal.params.ifFormat);
+ }
+ if (cal.params.displayArea) {
+ cal.params.displayArea.innerHTML = cal.date.print(cal.params.daFormat);
+ }
+ if (cal.params.singleClick && cal.dateClicked) {
+ cal.callCloseHandler();
+ }
+ if (typeof cal.params.onUpdate == "function") {
+ cal.params.onUpdate(cal);
+ }
+ };
+
+ if (params.flat != null) {
+ params.flat = document.getElementById(params.flat);
+ if (!params.flat) {
+ alert("Calendar.setup:\n Flat specified but can't find parent.");
+ return false;
+ }
+ var cal = new Calendar(params.mondayFirst, params.date, params.onSelect || onSelect);
+ cal.showsTime = params.showsTime;
+ cal.time24 = (params.timeFormat == "24");
+ cal.params = params;
+ cal.weekNumbers = params.weekNumbers;
+ cal.setRange(params.range[0], params.range[1]);
+ cal.setDateStatusHandler(params.dateStatusFunc);
+ cal.create(params.flat);
+ cal.show();
+ return false;
+ }
+
+ var triggerEl = params.button || params.displayArea || params.inputField;
+ triggerEl["on" + params.eventName] = function() {
+ var dateEl = params.inputField || params.displayArea;
+ var dateFmt = params.inputField ? params.ifFormat : params.daFormat;
+ var mustCreate = false;
+ var cal = window.calendar;
+ if (!window.calendar) {
+ window.calendar = cal = new Calendar(params.mondayFirst,
+ params.date,
+ params.onSelect || onSelect,
+ params.onClose || function(cal) { cal.hide(); });
+ cal.showsTime = params.showsTime;
+ cal.time24 = (params.timeFormat == "24");
+ cal.weekNumbers = params.weekNumbers;
+ mustCreate = true;
+ } else {
+ cal.hide();
+ }
+ cal.setRange(params.range[0], params.range[1]);
+ cal.params = params;
+ cal.setDateStatusHandler(params.dateStatusFunc);
+ cal.setDateFormat(dateFmt);
+ if (mustCreate)
+ cal.create();
+ cal.parseDate(dateEl.value || dateEl.innerHTML);
+ cal.refresh();
+ cal.showAtElement(params.displayArea || params.inputField, params.align);
+ return false;
+ };
+};
diff --git a/httemplate/elements/calendar-win2k-2.css b/httemplate/elements/calendar-win2k-2.css
new file mode 100644
index 0000000..9727d1b
--- /dev/null
+++ b/httemplate/elements/calendar-win2k-2.css
@@ -0,0 +1,263 @@
+/* The main calendar widget. DIV containing a table. */
+
+.calendar {
+ position: relative;
+ display: none;
+ border-top: 2px solid #fff;
+ border-right: 2px solid #000;
+ border-bottom: 2px solid #000;
+ border-left: 2px solid #fff;
+ font-size: 11px;
+ color: #000;
+ cursor: default;
+ background: #d4c8d0;
+ font-family: tahoma,verdana,sans-serif;
+}
+
+.calendar table {
+ border-top: 1px solid #000;
+ border-right: 1px solid #fff;
+ border-bottom: 1px solid #fff;
+ border-left: 1px solid #000;
+ font-size: 11px;
+ color: #000;
+ cursor: default;
+ background: #d4c8d0;
+ font-family: tahoma,verdana,sans-serif;
+}
+
+/* Header part -- contains navigation buttons and day names. */
+
+.calendar .button { /* "<<", "<", ">", ">>" buttons have this class */
+ text-align: center;
+ padding: 1px;
+ border-top: 1px solid #fff;
+ border-right: 1px solid #000;
+ border-bottom: 1px solid #000;
+ border-left: 1px solid #fff;
+}
+
+.calendar .nav {
+ background: transparent url(menuarrow.gif) no-repeat 100% 100%;
+}
+
+.calendar thead .title { /* This holds the current "month, year" */
+ font-weight: bold;
+ padding: 1px;
+ border: 1px solid #000;
+ background: #847880;
+ color: #fff;
+ text-align: center;
+}
+
+.calendar thead .headrow { /* Row <TR> containing navigation buttons */
+}
+
+.calendar thead .daynames { /* Row <TR> containing the day names */
+}
+
+.calendar thead .name { /* Cells <TD> containing the day names */
+ border-bottom: 1px solid #000;
+ padding: 2px;
+ text-align: center;
+ background: #f4e8f0;
+}
+
+.calendar thead .weekend { /* How a weekend day name shows in header */
+ color: #f00;
+}
+
+.calendar thead .hilite { /* How do the buttons in header appear when hover */
+ border-top: 2px solid #fff;
+ border-right: 2px solid #000;
+ border-bottom: 2px solid #000;
+ border-left: 2px solid #fff;
+ padding: 0px;
+ background-color: #e4d8e0;
+}
+
+.calendar thead .active { /* Active (pressed) buttons in header */
+ padding: 2px 0px 0px 2px;
+ border-top: 1px solid #000;
+ border-right: 1px solid #fff;
+ border-bottom: 1px solid #fff;
+ border-left: 1px solid #000;
+ background-color: #c4b8c0;
+}
+
+/* The body part -- contains all the days in month. */
+
+.calendar tbody .day { /* Cells <TD> containing month days dates */
+ width: 2em;
+ text-align: right;
+ padding: 2px 4px 2px 2px;
+}
+
+.calendar table .wn {
+ padding: 2px 3px 2px 2px;
+ border-right: 1px solid #000;
+ background: #f4e8f0;
+}
+
+.calendar tbody .rowhilite td {
+ background: #e4d8e0;
+}
+
+.calendar tbody .rowhilite td.wn {
+ background: #d4c8d0;
+}
+
+.calendar tbody td.hilite { /* Hovered cells <TD> */
+ padding: 1px 3px 1px 1px;
+ border-top: 1px solid #fff;
+ border-right: 1px solid #000;
+ border-bottom: 1px solid #000;
+ border-left: 1px solid #fff;
+}
+
+.calendar tbody td.active { /* Active (pressed) cells <TD> */
+ padding: 2px 2px 0px 2px;
+ border-top: 1px solid #000;
+ border-right: 1px solid #fff;
+ border-bottom: 1px solid #fff;
+ border-left: 1px solid #000;
+}
+
+.calendar tbody td.selected { /* Cell showing selected date */
+ font-weight: bold;
+ border-top: 1px solid #000;
+ border-right: 1px solid #fff;
+ border-bottom: 1px solid #fff;
+ border-left: 1px solid #000;
+ padding: 2px 2px 0px 2px;
+ background: #e4d8e0;
+}
+
+.calendar tbody td.weekend { /* Cells showing weekend days */
+ color: #f00;
+}
+
+.calendar tbody td.today { /* Cell showing today date */
+ font-weight: bold;
+ color: #00f;
+}
+
+.calendar tbody .disabled { color: #999; }
+
+.calendar tbody .emptycell { /* Empty cells (the best is to hide them) */
+ visibility: hidden;
+}
+
+.calendar tbody .emptyrow { /* Empty row (some months need less than 6 rows) */
+ display: none;
+}
+
+/* The footer part -- status bar and "Close" button */
+
+.calendar tfoot .footrow { /* The <TR> in footer (only one right now) */
+}
+
+.calendar tfoot .ttip { /* Tooltip (status bar) cell <TD> */
+ background: #f4e8f0;
+ padding: 1px;
+ border: 1px solid #000;
+ background: #847880;
+ color: #fff;
+ text-align: center;
+}
+
+.calendar tfoot .hilite { /* Hover style for buttons in footer */
+ border-top: 1px solid #fff;
+ border-right: 1px solid #000;
+ border-bottom: 1px solid #000;
+ border-left: 1px solid #fff;
+ padding: 1px;
+ background: #e4d8e0;
+}
+
+.calendar tfoot .active { /* Active (pressed) style for buttons in footer */
+ padding: 2px 0px 0px 2px;
+ border-top: 1px solid #000;
+ border-right: 1px solid #fff;
+ border-bottom: 1px solid #fff;
+ border-left: 1px solid #000;
+}
+
+/* Combo boxes (menus that display months/years for direct selection) */
+
+.combo {
+ position: absolute;
+ display: none;
+ width: 4em;
+ top: 0px;
+ left: 0px;
+ cursor: default;
+ border-top: 1px solid #fff;
+ border-right: 1px solid #000;
+ border-bottom: 1px solid #000;
+ border-left: 1px solid #fff;
+ background: #e4d8e0;
+ font-size: smaller;
+ padding: 1px;
+}
+
+.combo .label,
+.combo .label-IEfix {
+ text-align: center;
+ padding: 1px;
+}
+
+.combo .label-IEfix {
+ width: 4em;
+}
+
+.combo .active {
+ background: #d4c8d0;
+ padding: 0px;
+ border-top: 1px solid #000;
+ border-right: 1px solid #fff;
+ border-bottom: 1px solid #fff;
+ border-left: 1px solid #000;
+}
+
+.combo .hilite {
+ background: #408;
+ color: #fea;
+}
+
+.calendar td.time {
+ border-top: 1px solid #000;
+ padding: 1px 0px;
+ text-align: center;
+ background-color: #f4f0e8;
+}
+
+.calendar td.time .hour,
+.calendar td.time .minute,
+.calendar td.time .ampm {
+ padding: 0px 3px 0px 4px;
+ border: 1px solid #889;
+ font-weight: bold;
+ background-color: #fff;
+}
+
+.calendar td.time .ampm {
+ text-align: center;
+}
+
+.calendar td.time .colon {
+ padding: 0px 2px 0px 3px;
+ font-weight: bold;
+}
+
+.calendar td.time span.hilite {
+ border-color: #000;
+ background-color: #766;
+ color: #fff;
+}
+
+.calendar td.time span.active {
+ border-color: #f00;
+ background-color: #000;
+ color: #0f0;
+}
diff --git a/httemplate/elements/calendar.js b/httemplate/elements/calendar.js
new file mode 100644
index 0000000..3c028cc
--- /dev/null
+++ b/httemplate/elements/calendar.js
@@ -0,0 +1,1641 @@
+/* Copyright Mihai Bazon, 2002, 2003 | http://dynarch.com/mishoo/
+ * ------------------------------------------------------------------
+ *
+ * The DHTML Calendar, version 0.9.5 "Your favorite time, bis"
+ *
+ * Details and latest version at:
+ * http://dynarch.com/mishoo/calendar.epl
+ *
+ * This script is distributed under the GNU Lesser General Public License.
+ * Read the entire license text here: http://www.gnu.org/licenses/lgpl.html
+ */
+
+// $Id: calendar.js,v 1.3 2003-11-07 10:53:35 ivan Exp $
+
+/** The Calendar object constructor. */
+Calendar = function (mondayFirst, dateStr, onSelected, onClose) {
+ // member variables
+ this.activeDiv = null;
+ this.currentDateEl = null;
+ this.getDateStatus = null;
+ this.timeout = null;
+ this.onSelected = onSelected || null;
+ this.onClose = onClose || null;
+ this.dragging = false;
+ this.hidden = false;
+ this.minYear = 1970;
+ this.maxYear = 2050;
+ this.dateFormat = Calendar._TT["DEF_DATE_FORMAT"];
+ this.ttDateFormat = Calendar._TT["TT_DATE_FORMAT"];
+ this.isPopup = true;
+ this.weekNumbers = true;
+ this.mondayFirst = mondayFirst;
+ this.dateStr = dateStr;
+ this.ar_days = null;
+ this.showsTime = false;
+ this.time24 = true;
+ // HTML elements
+ this.table = null;
+ this.element = null;
+ this.tbody = null;
+ this.firstdayname = null;
+ // Combo boxes
+ this.monthsCombo = null;
+ this.yearsCombo = null;
+ this.hilitedMonth = null;
+ this.activeMonth = null;
+ this.hilitedYear = null;
+ this.activeYear = null;
+ // Information
+ this.dateClicked = false;
+
+ // one-time initializations
+ if (typeof Calendar._SDN == "undefined") {
+ // table of short day names
+ if (typeof Calendar._SDN_len == "undefined")
+ Calendar._SDN_len = 3;
+ var ar = new Array();
+ for (var i = 8; i > 0;) {
+ ar[--i] = Calendar._DN[i].substr(0, Calendar._SDN_len);
+ }
+ Calendar._SDN = ar;
+ // table of short month names
+ if (typeof Calendar._SMN_len == "undefined")
+ Calendar._SMN_len = 3;
+ ar = new Array();
+ for (var i = 12; i > 0;) {
+ ar[--i] = Calendar._MN[i].substr(0, Calendar._SMN_len);
+ }
+ Calendar._SMN = ar;
+ }
+};
+
+// ** constants
+
+/// "static", needed for event handlers.
+Calendar._C = null;
+
+/// detect a special case of "web browser"
+Calendar.is_ie = ( /msie/i.test(navigator.userAgent) &&
+ !/opera/i.test(navigator.userAgent) );
+
+/// detect Opera browser
+Calendar.is_opera = /opera/i.test(navigator.userAgent);
+
+/// detect KHTML-based browsers
+Calendar.is_khtml = /Konqueror|Safari|KHTML/i.test(navigator.userAgent);
+
+// BEGIN: UTILITY FUNCTIONS; beware that these might be moved into a separate
+// library, at some point.
+
+Calendar.getAbsolutePos = function(el) {
+ var SL = 0, ST = 0;
+ var is_div = /^div$/i.test(el.tagName);
+ if (is_div && el.scrollLeft)
+ SL = el.scrollLeft;
+ if (is_div && el.scrollTop)
+ ST = el.scrollTop;
+ var r = { x: el.offsetLeft - SL, y: el.offsetTop - ST };
+ if (el.offsetParent) {
+ var tmp = Calendar.getAbsolutePos(el.offsetParent);
+ r.x += tmp.x;
+ r.y += tmp.y;
+ }
+ return r;
+};
+
+Calendar.isRelated = function (el, evt) {
+ var related = evt.relatedTarget;
+ if (!related) {
+ var type = evt.type;
+ if (type == "mouseover") {
+ related = evt.fromElement;
+ } else if (type == "mouseout") {
+ related = evt.toElement;
+ }
+ }
+ while (related) {
+ if (related == el) {
+ return true;
+ }
+ related = related.parentNode;
+ }
+ return false;
+};
+
+Calendar.removeClass = function(el, className) {
+ if (!(el && el.className)) {
+ return;
+ }
+ var cls = el.className.split(" ");
+ var ar = new Array();
+ for (var i = cls.length; i > 0;) {
+ if (cls[--i] != className) {
+ ar[ar.length] = cls[i];
+ }
+ }
+ el.className = ar.join(" ");
+};
+
+Calendar.addClass = function(el, className) {
+ Calendar.removeClass(el, className);
+ el.className += " " + className;
+};
+
+Calendar.getElement = function(ev) {
+ if (Calendar.is_ie) {
+ return window.event.srcElement;
+ } else {
+ return ev.currentTarget;
+ }
+};
+
+Calendar.getTargetElement = function(ev) {
+ if (Calendar.is_ie) {
+ return window.event.srcElement;
+ } else {
+ return ev.target;
+ }
+};
+
+Calendar.stopEvent = function(ev) {
+ ev || (ev = window.event);
+ if (Calendar.is_ie) {
+ ev.cancelBubble = true;
+ ev.returnValue = false;
+ } else {
+ ev.preventDefault();
+ ev.stopPropagation();
+ }
+ return false;
+};
+
+Calendar.addEvent = function(el, evname, func) {
+ if (el.attachEvent) { // IE
+ el.attachEvent("on" + evname, func);
+ } else if (el.addEventListener) { // Gecko / W3C
+ el.addEventListener(evname, func, true);
+ } else {
+ el["on" + evname] = func;
+ }
+};
+
+Calendar.removeEvent = function(el, evname, func) {
+ if (el.detachEvent) { // IE
+ el.detachEvent("on" + evname, func);
+ } else if (el.removeEventListener) { // Gecko / W3C
+ el.removeEventListener(evname, func, true);
+ } else {
+ el["on" + evname] = null;
+ }
+};
+
+Calendar.createElement = function(type, parent) {
+ var el = null;
+ if (document.createElementNS) {
+ // use the XHTML namespace; IE won't normally get here unless
+ // _they_ "fix" the DOM2 implementation.
+ el = document.createElementNS("http://www.w3.org/1999/xhtml", type);
+ } else {
+ el = document.createElement(type);
+ }
+ if (typeof parent != "undefined") {
+ parent.appendChild(el);
+ }
+ return el;
+};
+
+// END: UTILITY FUNCTIONS
+
+// BEGIN: CALENDAR STATIC FUNCTIONS
+
+/** Internal -- adds a set of events to make some element behave like a button. */
+Calendar._add_evs = function(el) {
+ with (Calendar) {
+ addEvent(el, "mouseover", dayMouseOver);
+ addEvent(el, "mousedown", dayMouseDown);
+ addEvent(el, "mouseout", dayMouseOut);
+ if (is_ie) {
+ addEvent(el, "dblclick", dayMouseDblClick);
+ el.setAttribute("unselectable", true);
+ }
+ }
+};
+
+Calendar.findMonth = function(el) {
+ if (typeof el.month != "undefined") {
+ return el;
+ } else if (typeof el.parentNode.month != "undefined") {
+ return el.parentNode;
+ }
+ return null;
+};
+
+Calendar.findYear = function(el) {
+ if (typeof el.year != "undefined") {
+ return el;
+ } else if (typeof el.parentNode.year != "undefined") {
+ return el.parentNode;
+ }
+ return null;
+};
+
+Calendar.showMonthsCombo = function () {
+ var cal = Calendar._C;
+ if (!cal) {
+ return false;
+ }
+ var cal = cal;
+ var cd = cal.activeDiv;
+ var mc = cal.monthsCombo;
+ if (cal.hilitedMonth) {
+ Calendar.removeClass(cal.hilitedMonth, "hilite");
+ }
+ if (cal.activeMonth) {
+ Calendar.removeClass(cal.activeMonth, "active");
+ }
+ var mon = cal.monthsCombo.getElementsByTagName("div")[cal.date.getMonth()];
+ Calendar.addClass(mon, "active");
+ cal.activeMonth = mon;
+ var s = mc.style;
+ s.display = "block";
+ if (cd.navtype < 0)
+ s.left = cd.offsetLeft + "px";
+ else
+ s.left = (cd.offsetLeft + cd.offsetWidth - mc.offsetWidth) + "px";
+ s.top = (cd.offsetTop + cd.offsetHeight) + "px";
+};
+
+Calendar.showYearsCombo = function (fwd) {
+ var cal = Calendar._C;
+ if (!cal) {
+ return false;
+ }
+ var cal = cal;
+ var cd = cal.activeDiv;
+ var yc = cal.yearsCombo;
+ if (cal.hilitedYear) {
+ Calendar.removeClass(cal.hilitedYear, "hilite");
+ }
+ if (cal.activeYear) {
+ Calendar.removeClass(cal.activeYear, "active");
+ }
+ cal.activeYear = null;
+ var Y = cal.date.getFullYear() + (fwd ? 1 : -1);
+ var yr = yc.firstChild;
+ var show = false;
+ for (var i = 12; i > 0; --i) {
+ if (Y >= cal.minYear && Y <= cal.maxYear) {
+ yr.firstChild.data = Y;
+ yr.year = Y;
+ yr.style.display = "block";
+ show = true;
+ } else {
+ yr.style.display = "none";
+ }
+ yr = yr.nextSibling;
+ Y += fwd ? 2 : -2;
+ }
+ if (show) {
+ var s = yc.style;
+ s.display = "block";
+ if (cd.navtype < 0)
+ s.left = cd.offsetLeft + "px";
+ else
+ s.left = (cd.offsetLeft + cd.offsetWidth - yc.offsetWidth) + "px";
+ s.top = (cd.offsetTop + cd.offsetHeight) + "px";
+ }
+};
+
+// event handlers
+
+Calendar.tableMouseUp = function(ev) {
+ var cal = Calendar._C;
+ if (!cal) {
+ return false;
+ }
+ if (cal.timeout) {
+ clearTimeout(cal.timeout);
+ }
+ var el = cal.activeDiv;
+ if (!el) {
+ return false;
+ }
+ var target = Calendar.getTargetElement(ev);
+ ev || (ev = window.event);
+ Calendar.removeClass(el, "active");
+ if (target == el || target.parentNode == el) {
+ Calendar.cellClick(el, ev);
+ }
+ var mon = Calendar.findMonth(target);
+ var date = null;
+ if (mon) {
+ date = new Date(cal.date);
+ if (mon.month != date.getMonth()) {
+ date.setMonth(mon.month);
+ cal.setDate(date);
+ cal.dateClicked = false;
+ cal.callHandler();
+ }
+ } else {
+ var year = Calendar.findYear(target);
+ if (year) {
+ date = new Date(cal.date);
+ if (year.year != date.getFullYear()) {
+ date.setFullYear(year.year);
+ cal.setDate(date);
+ cal.dateClicked = false;
+ cal.callHandler();
+ }
+ }
+ }
+ with (Calendar) {
+ removeEvent(document, "mouseup", tableMouseUp);
+ removeEvent(document, "mouseover", tableMouseOver);
+ removeEvent(document, "mousemove", tableMouseOver);
+ cal._hideCombos();
+ _C = null;
+ return stopEvent(ev);
+ }
+};
+
+Calendar.tableMouseOver = function (ev) {
+ var cal = Calendar._C;
+ if (!cal) {
+ return;
+ }
+ var el = cal.activeDiv;
+ var target = Calendar.getTargetElement(ev);
+ if (target == el || target.parentNode == el) {
+ Calendar.addClass(el, "hilite active");
+ Calendar.addClass(el.parentNode, "rowhilite");
+ } else {
+ if (typeof el.navtype == "undefined" || (el.navtype != 50 && (el.navtype == 0 || Math.abs(el.navtype) > 2)))
+ Calendar.removeClass(el, "active");
+ Calendar.removeClass(el, "hilite");
+ Calendar.removeClass(el.parentNode, "rowhilite");
+ }
+ ev || (ev = window.event);
+ if (el.navtype == 50 && target != el) {
+ var pos = Calendar.getAbsolutePos(el);
+ var w = el.offsetWidth;
+ var x = ev.clientX;
+ var dx;
+ var decrease = true;
+ if (x > pos.x + w) {
+ dx = x - pos.x - w;
+ decrease = false;
+ } else
+ dx = pos.x - x;
+
+ if (dx < 0) dx = 0;
+ var range = el._range;
+ var current = el._current;
+ var count = Math.floor(dx / 10) % range.length;
+ for (var i = range.length; --i >= 0;)
+ if (range[i] == current)
+ break;
+ while (count-- > 0)
+ if (decrease) {
+ if (!(--i in range))
+ i = range.length - 1;
+ } else if (!(++i in range))
+ i = 0;
+ var newval = range[i];
+ el.firstChild.data = newval;
+
+ cal.onUpdateTime();
+ }
+ var mon = Calendar.findMonth(target);
+ if (mon) {
+ if (mon.month != cal.date.getMonth()) {
+ if (cal.hilitedMonth) {
+ Calendar.removeClass(cal.hilitedMonth, "hilite");
+ }
+ Calendar.addClass(mon, "hilite");
+ cal.hilitedMonth = mon;
+ } else if (cal.hilitedMonth) {
+ Calendar.removeClass(cal.hilitedMonth, "hilite");
+ }
+ } else {
+ if (cal.hilitedMonth) {
+ Calendar.removeClass(cal.hilitedMonth, "hilite");
+ }
+ var year = Calendar.findYear(target);
+ if (year) {
+ if (year.year != cal.date.getFullYear()) {
+ if (cal.hilitedYear) {
+ Calendar.removeClass(cal.hilitedYear, "hilite");
+ }
+ Calendar.addClass(year, "hilite");
+ cal.hilitedYear = year;
+ } else if (cal.hilitedYear) {
+ Calendar.removeClass(cal.hilitedYear, "hilite");
+ }
+ } else if (cal.hilitedYear) {
+ Calendar.removeClass(cal.hilitedYear, "hilite");
+ }
+ }
+ return Calendar.stopEvent(ev);
+};
+
+Calendar.tableMouseDown = function (ev) {
+ if (Calendar.getTargetElement(ev) == Calendar.getElement(ev)) {
+ return Calendar.stopEvent(ev);
+ }
+};
+
+Calendar.calDragIt = function (ev) {
+ var cal = Calendar._C;
+ if (!(cal && cal.dragging)) {
+ return false;
+ }
+ var posX;
+ var posY;
+ if (Calendar.is_ie) {
+ posY = window.event.clientY + document.body.scrollTop;
+ posX = window.event.clientX + document.body.scrollLeft;
+ } else {
+ posX = ev.pageX;
+ posY = ev.pageY;
+ }
+ cal.hideShowCovered();
+ var st = cal.element.style;
+ st.left = (posX - cal.xOffs) + "px";
+ st.top = (posY - cal.yOffs) + "px";
+ return Calendar.stopEvent(ev);
+};
+
+Calendar.calDragEnd = function (ev) {
+ var cal = Calendar._C;
+ if (!cal) {
+ return false;
+ }
+ cal.dragging = false;
+ with (Calendar) {
+ removeEvent(document, "mousemove", calDragIt);
+ removeEvent(document, "mouseover", stopEvent);
+ removeEvent(document, "mouseup", calDragEnd);
+ tableMouseUp(ev);
+ }
+ cal.hideShowCovered();
+};
+
+Calendar.dayMouseDown = function(ev) {
+ var el = Calendar.getElement(ev);
+ if (el.disabled) {
+ return false;
+ }
+ var cal = el.calendar;
+ cal.activeDiv = el;
+ Calendar._C = cal;
+ if (el.navtype != 300) with (Calendar) {
+ if (el.navtype == 50)
+ el._current = el.firstChild.data;
+ addClass(el, "hilite active");
+ addEvent(document, "mouseover", tableMouseOver);
+ addEvent(document, "mousemove", tableMouseOver);
+ addEvent(document, "mouseup", tableMouseUp);
+ } else if (cal.isPopup) {
+ cal._dragStart(ev);
+ }
+ if (el.navtype == -1 || el.navtype == 1) {
+ if (cal.timeout) clearTimeout(cal.timeout);
+ cal.timeout = setTimeout("Calendar.showMonthsCombo()", 250);
+ } else if (el.navtype == -2 || el.navtype == 2) {
+ if (cal.timeout) clearTimeout(cal.timeout);
+ cal.timeout = setTimeout((el.navtype > 0) ? "Calendar.showYearsCombo(true)" : "Calendar.showYearsCombo(false)", 250);
+ } else {
+ cal.timeout = null;
+ }
+ return Calendar.stopEvent(ev);
+};
+
+Calendar.dayMouseDblClick = function(ev) {
+ Calendar.cellClick(Calendar.getElement(ev), ev || window.event);
+ if (Calendar.is_ie) {
+ document.selection.empty();
+ }
+};
+
+Calendar.dayMouseOver = function(ev) {
+ var el = Calendar.getElement(ev);
+ if (Calendar.isRelated(el, ev) || Calendar._C || el.disabled) {
+ return false;
+ }
+ if (el.ttip) {
+ if (el.ttip.substr(0, 1) == "_") {
+ var date = null;
+ with (el.calendar.date) {
+ date = new Date(getFullYear(), getMonth(), el.caldate);
+ }
+ el.ttip = date.print(el.calendar.ttDateFormat) + el.ttip.substr(1);
+ }
+ el.calendar.tooltips.firstChild.data = el.ttip;
+ }
+ if (el.navtype != 300) {
+ Calendar.addClass(el, "hilite");
+ if (el.caldate) {
+ Calendar.addClass(el.parentNode, "rowhilite");
+ }
+ }
+ return Calendar.stopEvent(ev);
+};
+
+Calendar.dayMouseOut = function(ev) {
+ with (Calendar) {
+ var el = getElement(ev);
+ if (isRelated(el, ev) || _C || el.disabled) {
+ return false;
+ }
+ removeClass(el, "hilite");
+ if (el.caldate) {
+ removeClass(el.parentNode, "rowhilite");
+ }
+ el.calendar.tooltips.firstChild.data = _TT["SEL_DATE"];
+ return stopEvent(ev);
+ }
+};
+
+/**
+ * A generic "click" handler :) handles all types of buttons defined in this
+ * calendar.
+ */
+Calendar.cellClick = function(el, ev) {
+ var cal = el.calendar;
+ var closing = false;
+ var newdate = false;
+ var date = null;
+ if (typeof el.navtype == "undefined") {
+ Calendar.removeClass(cal.currentDateEl, "selected");
+ Calendar.addClass(el, "selected");
+ closing = (cal.currentDateEl == el);
+ if (!closing) {
+ cal.currentDateEl = el;
+ }
+ cal.date.setDate(el.caldate);
+ date = cal.date;
+ newdate = true;
+ // a date was clicked
+ cal.dateClicked = true;
+ } else {
+ if (el.navtype == 200) {
+ Calendar.removeClass(el, "hilite");
+ cal.callCloseHandler();
+ return;
+ }
+ date = (el.navtype == 0) ? new Date() : new Date(cal.date);
+ // unless "today" was clicked, we assume no date was clicked so
+ // the selected handler will know not to close the calenar when
+ // in single-click mode.
+ // cal.dateClicked = (el.navtype == 0);
+ cal.dateClicked = false;
+ var year = date.getFullYear();
+ var mon = date.getMonth();
+ function setMonth(m) {
+ var day = date.getDate();
+ var max = date.getMonthDays(m);
+ if (day > max) {
+ date.setDate(max);
+ }
+ date.setMonth(m);
+ };
+ switch (el.navtype) {
+ case 400:
+ Calendar.removeClass(el, "hilite");
+ var text = Calendar._TT["ABOUT"];
+ if (typeof text != "undefined") {
+ text += cal.showsTime ? Calendar._TT["ABOUT_TIME"] : "";
+ } else {
+ // FIXME: this should be removed as soon as lang files get updated!
+ text = "Help and about box text is not translated into this language.\n" +
+ "If you know this language and you feel generous please update\n" +
+ "the corresponding file in \"lang\" subdir to match calendar-en.js\n" +
+ "and send it back to <mishoo@infoiasi.ro> to get it into the distribution ;-)\n\n" +
+ "Thank you!\n" +
+ "http://dynarch.com/mishoo/calendar.epl\n";
+ }
+ alert(text);
+ return;
+ case -2:
+ if (year > cal.minYear) {
+ date.setFullYear(year - 1);
+ }
+ break;
+ case -1:
+ if (mon > 0) {
+ setMonth(mon - 1);
+ } else if (year-- > cal.minYear) {
+ date.setFullYear(year);
+ setMonth(11);
+ }
+ break;
+ case 1:
+ if (mon < 11) {
+ setMonth(mon + 1);
+ } else if (year < cal.maxYear) {
+ date.setFullYear(year + 1);
+ setMonth(0);
+ }
+ break;
+ case 2:
+ if (year < cal.maxYear) {
+ date.setFullYear(year + 1);
+ }
+ break;
+ case 100:
+ cal.setMondayFirst(!cal.mondayFirst);
+ return;
+ case 50:
+ var range = el._range;
+ var current = el.firstChild.data;
+ for (var i = range.length; --i >= 0;)
+ if (range[i] == current)
+ break;
+ if (ev && ev.shiftKey) {
+ if (!(--i in range))
+ i = range.length - 1;
+ } else if (!(++i in range))
+ i = 0;
+ var newval = range[i];
+ el.firstChild.data = newval;
+ cal.onUpdateTime();
+ return;
+ case 0:
+ // TODAY will bring us here
+ if ((typeof cal.getDateStatus == "function") && cal.getDateStatus(date, date.getFullYear(), date.getMonth(), date.getDate())) {
+ // remember, "date" was previously set to new
+ // Date() if TODAY was clicked; thus, it
+ // contains today date.
+ return false;
+ }
+ break;
+ }
+ if (!date.equalsTo(cal.date)) {
+ cal.setDate(date);
+ newdate = true;
+ }
+ }
+ if (newdate) {
+ cal.callHandler();
+ }
+ if (closing) {
+ Calendar.removeClass(el, "hilite");
+ cal.callCloseHandler();
+ }
+};
+
+// END: CALENDAR STATIC FUNCTIONS
+
+// BEGIN: CALENDAR OBJECT FUNCTIONS
+
+/**
+ * This function creates the calendar inside the given parent. If _par is
+ * null than it creates a popup calendar inside the BODY element. If _par is
+ * an element, be it BODY, then it creates a non-popup calendar (still
+ * hidden). Some properties need to be set before calling this function.
+ */
+Calendar.prototype.create = function (_par) {
+ var parent = null;
+ if (! _par) {
+ // default parent is the document body, in which case we create
+ // a popup calendar.
+ parent = document.getElementsByTagName("body")[0];
+ this.isPopup = true;
+ } else {
+ parent = _par;
+ this.isPopup = false;
+ }
+ this.date = this.dateStr ? new Date(this.dateStr) : new Date();
+
+ var table = Calendar.createElement("table");
+ this.table = table;
+ table.cellSpacing = 0;
+ table.cellPadding = 0;
+ table.calendar = this;
+ Calendar.addEvent(table, "mousedown", Calendar.tableMouseDown);
+
+ var div = Calendar.createElement("div");
+ this.element = div;
+ div.className = "calendar";
+ if (this.isPopup) {
+ div.style.position = "absolute";
+ div.style.display = "none";
+ }
+ div.appendChild(table);
+
+ var thead = Calendar.createElement("thead", table);
+ var cell = null;
+ var row = null;
+
+ var cal = this;
+ var hh = function (text, cs, navtype) {
+ cell = Calendar.createElement("td", row);
+ cell.colSpan = cs;
+ cell.className = "button";
+ if (navtype != 0 && Math.abs(navtype) <= 2)
+ cell.className += " nav";
+ Calendar._add_evs(cell);
+ cell.calendar = cal;
+ cell.navtype = navtype;
+ if (text.substr(0, 1) != "&") {
+ cell.appendChild(document.createTextNode(text));
+ }
+ else {
+ // FIXME: dirty hack for entities
+ cell.innerHTML = text;
+ }
+ return cell;
+ };
+
+ row = Calendar.createElement("tr", thead);
+ var title_length = 6;
+ (this.isPopup) && --title_length;
+ (this.weekNumbers) && ++title_length;
+
+ hh("?", 1, 400).ttip = Calendar._TT["INFO"];
+ this.title = hh("", title_length, 300);
+ this.title.className = "title";
+ if (this.isPopup) {
+ this.title.ttip = Calendar._TT["DRAG_TO_MOVE"];
+ this.title.style.cursor = "move";
+ hh("&#x00d7;", 1, 200).ttip = Calendar._TT["CLOSE"];
+ }
+
+ row = Calendar.createElement("tr", thead);
+ row.className = "headrow";
+
+ this._nav_py = hh("&#x00ab;", 1, -2);
+ this._nav_py.ttip = Calendar._TT["PREV_YEAR"];
+
+ this._nav_pm = hh("&#x2039;", 1, -1);
+ this._nav_pm.ttip = Calendar._TT["PREV_MONTH"];
+
+ this._nav_now = hh(Calendar._TT["TODAY"], this.weekNumbers ? 4 : 3, 0);
+ this._nav_now.ttip = Calendar._TT["GO_TODAY"];
+
+ this._nav_nm = hh("&#x203a;", 1, 1);
+ this._nav_nm.ttip = Calendar._TT["NEXT_MONTH"];
+
+ this._nav_ny = hh("&#x00bb;", 1, 2);
+ this._nav_ny.ttip = Calendar._TT["NEXT_YEAR"];
+
+ // day names
+ row = Calendar.createElement("tr", thead);
+ row.className = "daynames";
+ if (this.weekNumbers) {
+ cell = Calendar.createElement("td", row);
+ cell.className = "name wn";
+ cell.appendChild(document.createTextNode(Calendar._TT["WK"]));
+ }
+ for (var i = 7; i > 0; --i) {
+ cell = Calendar.createElement("td", row);
+ cell.appendChild(document.createTextNode(""));
+ if (!i) {
+ cell.navtype = 100;
+ cell.calendar = this;
+ Calendar._add_evs(cell);
+ }
+ }
+ this.firstdayname = (this.weekNumbers) ? row.firstChild.nextSibling : row.firstChild;
+ this._displayWeekdays();
+
+ var tbody = Calendar.createElement("tbody", table);
+ this.tbody = tbody;
+
+ for (i = 6; i > 0; --i) {
+ row = Calendar.createElement("tr", tbody);
+ if (this.weekNumbers) {
+ cell = Calendar.createElement("td", row);
+ cell.appendChild(document.createTextNode(""));
+ }
+ for (var j = 7; j > 0; --j) {
+ cell = Calendar.createElement("td", row);
+ cell.appendChild(document.createTextNode(""));
+ cell.calendar = this;
+ Calendar._add_evs(cell);
+ }
+ }
+
+ if (this.showsTime) {
+ row = Calendar.createElement("tr", tbody);
+ row.className = "time";
+
+ cell = Calendar.createElement("td", row);
+ cell.className = "time";
+ cell.colSpan = 2;
+ cell.innerHTML = "&nbsp;";
+
+ cell = Calendar.createElement("td", row);
+ cell.className = "time";
+ cell.colSpan = this.weekNumbers ? 4 : 3;
+
+ (function(){
+ function makeTimePart(className, init, range_start, range_end) {
+ var part = Calendar.createElement("span", cell);
+ part.className = className;
+ part.appendChild(document.createTextNode(init));
+ part.calendar = cal;
+ part.ttip = Calendar._TT["TIME_PART"];
+ part.navtype = 50;
+ part._range = [];
+ if (typeof range_start != "number")
+ part._range = range_start;
+ else {
+ for (var i = range_start; i <= range_end; ++i) {
+ var txt;
+ if (i < 10 && range_end >= 10) txt = '0' + i;
+ else txt = '' + i;
+ part._range[part._range.length] = txt;
+ }
+ }
+ Calendar._add_evs(part);
+ return part;
+ };
+ var hrs = cal.date.getHours();
+ var mins = cal.date.getMinutes();
+ var t12 = !cal.time24;
+ var pm = (hrs > 12);
+ if (t12 && pm) hrs -= 12;
+ var H = makeTimePart("hour", hrs, t12 ? 1 : 0, t12 ? 12 : 23);
+ var span = Calendar.createElement("span", cell);
+ span.appendChild(document.createTextNode(":"));
+ span.className = "colon";
+ var M = makeTimePart("minute", mins, 0, 59);
+ var AP = null;
+ cell = Calendar.createElement("td", row);
+ cell.className = "time";
+ cell.colSpan = 2;
+ if (t12)
+ AP = makeTimePart("ampm", pm ? "pm" : "am", ["am", "pm"]);
+ else
+ cell.innerHTML = "&nbsp;";
+
+ cal.onSetTime = function() {
+ var hrs = this.date.getHours();
+ var mins = this.date.getMinutes();
+ var pm = (hrs > 12);
+ if (pm && t12) hrs -= 12;
+ H.firstChild.data = (hrs < 10) ? ("0" + hrs) : hrs;
+ M.firstChild.data = (mins < 10) ? ("0" + mins) : mins;
+ if (t12)
+ AP.firstChild.data = pm ? "pm" : "am";
+ };
+
+ cal.onUpdateTime = function() {
+ var date = this.date;
+ var h = parseInt(H.firstChild.data, 10);
+ if (t12) {
+ if (/pm/i.test(AP.firstChild.data) && h < 12)
+ h += 12;
+ else if (/am/i.test(AP.firstChild.data) && h == 12)
+ h = 0;
+ }
+ var d = date.getDate();
+ var m = date.getMonth();
+ var y = date.getFullYear();
+ date.setHours(h);
+ date.setMinutes(parseInt(M.firstChild.data, 10));
+ date.setFullYear(y);
+ date.setMonth(m);
+ date.setDate(d);
+ this.dateClicked = false;
+ this.callHandler();
+ };
+ })();
+ } else {
+ this.onSetTime = this.onUpdateTime = function() {};
+ }
+
+ var tfoot = Calendar.createElement("tfoot", table);
+
+ row = Calendar.createElement("tr", tfoot);
+ row.className = "footrow";
+
+ cell = hh(Calendar._TT["SEL_DATE"], this.weekNumbers ? 8 : 7, 300);
+ cell.className = "ttip";
+ if (this.isPopup) {
+ cell.ttip = Calendar._TT["DRAG_TO_MOVE"];
+ cell.style.cursor = "move";
+ }
+ this.tooltips = cell;
+
+ div = Calendar.createElement("div", this.element);
+ this.monthsCombo = div;
+ div.className = "combo";
+ for (i = 0; i < Calendar._MN.length; ++i) {
+ var mn = Calendar.createElement("div");
+ mn.className = Calendar.is_ie ? "label-IEfix" : "label";
+ mn.month = i;
+ mn.appendChild(document.createTextNode(Calendar._SMN[i]));
+ div.appendChild(mn);
+ }
+
+ div = Calendar.createElement("div", this.element);
+ this.yearsCombo = div;
+ div.className = "combo";
+ for (i = 12; i > 0; --i) {
+ var yr = Calendar.createElement("div");
+ yr.className = Calendar.is_ie ? "label-IEfix" : "label";
+ yr.appendChild(document.createTextNode(""));
+ div.appendChild(yr);
+ }
+
+ this._init(this.mondayFirst, this.date);
+ parent.appendChild(this.element);
+};
+
+/** keyboard navigation, only for popup calendars */
+Calendar._keyEvent = function(ev) {
+ if (!window.calendar) {
+ return false;
+ }
+ (Calendar.is_ie) && (ev = window.event);
+ var cal = window.calendar;
+ var act = (Calendar.is_ie || ev.type == "keypress");
+ if (ev.ctrlKey) {
+ switch (ev.keyCode) {
+ case 37: // KEY left
+ act && Calendar.cellClick(cal._nav_pm);
+ break;
+ case 38: // KEY up
+ act && Calendar.cellClick(cal._nav_py);
+ break;
+ case 39: // KEY right
+ act && Calendar.cellClick(cal._nav_nm);
+ break;
+ case 40: // KEY down
+ act && Calendar.cellClick(cal._nav_ny);
+ break;
+ default:
+ return false;
+ }
+ } else switch (ev.keyCode) {
+ case 32: // KEY space (now)
+ Calendar.cellClick(cal._nav_now);
+ break;
+ case 27: // KEY esc
+ act && cal.hide();
+ break;
+ case 37: // KEY left
+ case 38: // KEY up
+ case 39: // KEY right
+ case 40: // KEY down
+ if (act) {
+ var date = cal.date.getDate() - 1;
+ var el = cal.currentDateEl;
+ var ne = null;
+ var prev = (ev.keyCode == 37) || (ev.keyCode == 38);
+ switch (ev.keyCode) {
+ case 37: // KEY left
+ (--date >= 0) && (ne = cal.ar_days[date]);
+ break;
+ case 38: // KEY up
+ date -= 7;
+ (date >= 0) && (ne = cal.ar_days[date]);
+ break;
+ case 39: // KEY right
+ (++date < cal.ar_days.length) && (ne = cal.ar_days[date]);
+ break;
+ case 40: // KEY down
+ date += 7;
+ (date < cal.ar_days.length) && (ne = cal.ar_days[date]);
+ break;
+ }
+ if (!ne) {
+ if (prev) {
+ Calendar.cellClick(cal._nav_pm);
+ } else {
+ Calendar.cellClick(cal._nav_nm);
+ }
+ date = (prev) ? cal.date.getMonthDays() : 1;
+ el = cal.currentDateEl;
+ ne = cal.ar_days[date - 1];
+ }
+ Calendar.removeClass(el, "selected");
+ Calendar.addClass(ne, "selected");
+ cal.date.setDate(ne.caldate);
+ cal.callHandler();
+ cal.currentDateEl = ne;
+ }
+ break;
+ case 13: // KEY enter
+ if (act) {
+ cal.callHandler();
+ cal.hide();
+ }
+ break;
+ default:
+ return false;
+ }
+ return Calendar.stopEvent(ev);
+};
+
+/**
+ * (RE)Initializes the calendar to the given date and style (if mondayFirst is
+ * true it makes Monday the first day of week, otherwise the weeks start on
+ * Sunday.
+ */
+Calendar.prototype._init = function (mondayFirst, date) {
+ var today = new Date();
+ var year = date.getFullYear();
+ if (year < this.minYear) {
+ year = this.minYear;
+ date.setFullYear(year);
+ } else if (year > this.maxYear) {
+ year = this.maxYear;
+ date.setFullYear(year);
+ }
+ this.mondayFirst = mondayFirst;
+ this.date = new Date(date);
+ var month = date.getMonth();
+ var mday = date.getDate();
+ var no_days = date.getMonthDays();
+ date.setDate(1);
+ var wday = date.getDay();
+ var MON = mondayFirst ? 1 : 0;
+ var SAT = mondayFirst ? 5 : 6;
+ var SUN = mondayFirst ? 6 : 0;
+ if (mondayFirst) {
+ wday = (wday > 0) ? (wday - 1) : 6;
+ }
+ var iday = 1;
+ var row = this.tbody.firstChild;
+ var MN = Calendar._SMN[month];
+ var hasToday = ((today.getFullYear() == year) && (today.getMonth() == month));
+ var todayDate = today.getDate();
+ var week_number = date.getWeekNumber();
+ var ar_days = new Array();
+ for (var i = 0; i < 6; ++i) {
+ if (iday > no_days) {
+ row.className = "emptyrow";
+ row = row.nextSibling;
+ continue;
+ }
+ var cell = row.firstChild;
+ if (this.weekNumbers) {
+ cell.className = "day wn";
+ cell.firstChild.data = week_number;
+ cell = cell.nextSibling;
+ }
+ ++week_number;
+ row.className = "daysrow";
+ for (var j = 0; j < 7; ++j) {
+ cell.className = "day";
+ if ((!i && j < wday) || iday > no_days) {
+ // cell.className = "emptycell";
+ cell.innerHTML = "&nbsp;";
+ cell.disabled = true;
+ cell = cell.nextSibling;
+ continue;
+ }
+ cell.disabled = false;
+ cell.firstChild.data = iday;
+ if (typeof this.getDateStatus == "function") {
+ date.setDate(iday);
+ var status = this.getDateStatus(date, year, month, iday);
+ if (status === true) {
+ cell.className += " disabled";
+ cell.disabled = true;
+ } else {
+ if (/disabled/i.test(status))
+ cell.disabled = true;
+ cell.className += " " + status;
+ }
+ }
+ if (!cell.disabled) {
+ ar_days[ar_days.length] = cell;
+ cell.caldate = iday;
+ cell.ttip = "_";
+ if (iday == mday) {
+ cell.className += " selected";
+ this.currentDateEl = cell;
+ }
+ if (hasToday && (iday == todayDate)) {
+ cell.className += " today";
+ cell.ttip += Calendar._TT["PART_TODAY"];
+ }
+ if (wday == SAT || wday == SUN) {
+ cell.className += " weekend";
+ }
+ }
+ ++iday;
+ ((++wday) ^ 7) || (wday = 0);
+ cell = cell.nextSibling;
+ }
+ row = row.nextSibling;
+ }
+ this.ar_days = ar_days;
+ this.title.firstChild.data = Calendar._MN[month] + ", " + year;
+ this.onSetTime();
+ // PROFILE
+ // this.tooltips.firstChild.data = "Generated in " + ((new Date()) - today) + " ms";
+};
+
+/**
+ * Calls _init function above for going to a certain date (but only if the
+ * date is different than the currently selected one).
+ */
+Calendar.prototype.setDate = function (date) {
+ if (!date.equalsTo(this.date)) {
+ this._init(this.mondayFirst, date);
+ }
+};
+
+/**
+ * Refreshes the calendar. Useful if the "disabledHandler" function is
+ * dynamic, meaning that the list of disabled date can change at runtime.
+ * Just * call this function if you think that the list of disabled dates
+ * should * change.
+ */
+Calendar.prototype.refresh = function () {
+ this._init(this.mondayFirst, this.date);
+};
+
+/** Modifies the "mondayFirst" parameter (EU/US style). */
+Calendar.prototype.setMondayFirst = function (mondayFirst) {
+ this._init(mondayFirst, this.date);
+ this._displayWeekdays();
+};
+
+/**
+ * Allows customization of what dates are enabled. The "unaryFunction"
+ * parameter must be a function object that receives the date (as a JS Date
+ * object) and returns a boolean value. If the returned value is true then
+ * the passed date will be marked as disabled.
+ */
+Calendar.prototype.setDateStatusHandler = Calendar.prototype.setDisabledHandler = function (unaryFunction) {
+ this.getDateStatus = unaryFunction;
+};
+
+/** Customization of allowed year range for the calendar. */
+Calendar.prototype.setRange = function (a, z) {
+ this.minYear = a;
+ this.maxYear = z;
+};
+
+/** Calls the first user handler (selectedHandler). */
+Calendar.prototype.callHandler = function () {
+ if (this.onSelected) {
+ this.onSelected(this, this.date.print(this.dateFormat));
+ }
+};
+
+/** Calls the second user handler (closeHandler). */
+Calendar.prototype.callCloseHandler = function () {
+ if (this.onClose) {
+ this.onClose(this);
+ }
+ this.hideShowCovered();
+};
+
+/** Removes the calendar object from the DOM tree and destroys it. */
+Calendar.prototype.destroy = function () {
+ var el = this.element.parentNode;
+ el.removeChild(this.element);
+ Calendar._C = null;
+ window.calendar = null;
+};
+
+/**
+ * Moves the calendar element to a different section in the DOM tree (changes
+ * its parent).
+ */
+Calendar.prototype.reparent = function (new_parent) {
+ var el = this.element;
+ el.parentNode.removeChild(el);
+ new_parent.appendChild(el);
+};
+
+// This gets called when the user presses a mouse button anywhere in the
+// document, if the calendar is shown. If the click was outside the open
+// calendar this function closes it.
+Calendar._checkCalendar = function(ev) {
+ if (!window.calendar) {
+ return false;
+ }
+ var el = Calendar.is_ie ? Calendar.getElement(ev) : Calendar.getTargetElement(ev);
+ for (; el != null && el != calendar.element; el = el.parentNode);
+ if (el == null) {
+ // calls closeHandler which should hide the calendar.
+ window.calendar.callCloseHandler();
+ return Calendar.stopEvent(ev);
+ }
+};
+
+/** Shows the calendar. */
+Calendar.prototype.show = function () {
+ var rows = this.table.getElementsByTagName("tr");
+ for (var i = rows.length; i > 0;) {
+ var row = rows[--i];
+ Calendar.removeClass(row, "rowhilite");
+ var cells = row.getElementsByTagName("td");
+ for (var j = cells.length; j > 0;) {
+ var cell = cells[--j];
+ Calendar.removeClass(cell, "hilite");
+ Calendar.removeClass(cell, "active");
+ }
+ }
+ this.element.style.display = "block";
+ this.hidden = false;
+ if (this.isPopup) {
+ window.calendar = this;
+ Calendar.addEvent(document, "keydown", Calendar._keyEvent);
+ Calendar.addEvent(document, "keypress", Calendar._keyEvent);
+ Calendar.addEvent(document, "mousedown", Calendar._checkCalendar);
+ }
+ this.hideShowCovered();
+};
+
+/**
+ * Hides the calendar. Also removes any "hilite" from the class of any TD
+ * element.
+ */
+Calendar.prototype.hide = function () {
+ if (this.isPopup) {
+ Calendar.removeEvent(document, "keydown", Calendar._keyEvent);
+ Calendar.removeEvent(document, "keypress", Calendar._keyEvent);
+ Calendar.removeEvent(document, "mousedown", Calendar._checkCalendar);
+ }
+ this.element.style.display = "none";
+ this.hidden = true;
+ this.hideShowCovered();
+};
+
+/**
+ * Shows the calendar at a given absolute position (beware that, depending on
+ * the calendar element style -- position property -- this might be relative
+ * to the parent's containing rectangle).
+ */
+Calendar.prototype.showAt = function (x, y) {
+ var s = this.element.style;
+ s.left = x + "px";
+ s.top = y + "px";
+ this.show();
+};
+
+/** Shows the calendar near a given element. */
+Calendar.prototype.showAtElement = function (el, opts) {
+ var self = this;
+ var p = Calendar.getAbsolutePos(el);
+ if (!opts || typeof opts != "string") {
+ this.showAt(p.x, p.y + el.offsetHeight);
+ return true;
+ }
+ this.element.style.display = "block";
+ Calendar.continuation_for_the_fucking_khtml_browser = function() {
+ var w = self.element.offsetWidth;
+ var h = self.element.offsetHeight;
+ self.element.style.display = "none";
+ var valign = opts.substr(0, 1);
+ var halign = "l";
+ if (opts.length > 1) {
+ halign = opts.substr(1, 1);
+ }
+ // vertical alignment
+ switch (valign) {
+ case "T": p.y -= h; break;
+ case "B": p.y += el.offsetHeight; break;
+ case "C": p.y += (el.offsetHeight - h) / 2; break;
+ case "t": p.y += el.offsetHeight - h; break;
+ case "b": break; // already there
+ }
+ // horizontal alignment
+ switch (halign) {
+ case "L": p.x -= w; break;
+ case "R": p.x += el.offsetWidth; break;
+ case "C": p.x += (el.offsetWidth - w) / 2; break;
+ case "r": p.x += el.offsetWidth - w; break;
+ case "l": break; // already there
+ }
+ self.showAt(p.x, p.y);
+ };
+ if (Calendar.is_khtml)
+ setTimeout("Calendar.continuation_for_the_fucking_khtml_browser()", 10);
+ else
+ Calendar.continuation_for_the_fucking_khtml_browser();
+};
+
+/** Customizes the date format. */
+Calendar.prototype.setDateFormat = function (str) {
+ this.dateFormat = str;
+};
+
+/** Customizes the tooltip date format. */
+Calendar.prototype.setTtDateFormat = function (str) {
+ this.ttDateFormat = str;
+};
+
+/**
+ * Tries to identify the date represented in a string. If successful it also
+ * calls this.setDate which moves the calendar to the given date.
+ */
+Calendar.prototype.parseDate = function (str, fmt) {
+ var y = 0;
+ var m = -1;
+ var d = 0;
+ var a = str.split(/\W+/);
+ if (!fmt) {
+ fmt = this.dateFormat;
+ }
+ var b = [];
+ fmt.replace(/(%.)/g, function(str, par) {
+ return b[b.length] = par;
+ });
+ var i = 0, j = 0;
+ var hr = 0;
+ var min = 0;
+ for (i = 0; i < a.length; ++i) {
+ if (b[i] == "%a" || b[i] == "%A") {
+ continue;
+ }
+ if (b[i] == "%d" || b[i] == "%e") {
+ d = parseInt(a[i], 10);
+ }
+ if (b[i] == "%m") {
+ m = parseInt(a[i], 10) - 1;
+ }
+ if (b[i] == "%Y" || b[i] == "%y") {
+ y = parseInt(a[i], 10);
+ (y < 100) && (y += (y > 29) ? 1900 : 2000);
+ }
+ if (b[i] == "%b" || b[i] == "%B") {
+ for (j = 0; j < 12; ++j) {
+ if (Calendar._MN[j].substr(0, a[i].length).toLowerCase() == a[i].toLowerCase()) { m = j; break; }
+ }
+ } else if (/%[HIkl]/.test(b[i])) {
+ hr = parseInt(a[i], 10);
+ } else if (/%[pP]/.test(b[i])) {
+ if (/pm/i.test(a[i]) && hr < 12)
+ hr += 12;
+ } else if (b[i] == "%M") {
+ min = parseInt(a[i], 10);
+ }
+ }
+ if (y != 0 && m != -1 && d != 0) {
+ this.setDate(new Date(y, m, d, hr, min, 0));
+ return;
+ }
+ y = 0; m = -1; d = 0;
+ for (i = 0; i < a.length; ++i) {
+ if (a[i].search(/[a-zA-Z]+/) != -1) {
+ var t = -1;
+ for (j = 0; j < 12; ++j) {
+ if (Calendar._MN[j].substr(0, a[i].length).toLowerCase() == a[i].toLowerCase()) { t = j; break; }
+ }
+ if (t != -1) {
+ if (m != -1) {
+ d = m+1;
+ }
+ m = t;
+ }
+ } else if (parseInt(a[i], 10) <= 12 && m == -1) {
+ m = a[i]-1;
+ } else if (parseInt(a[i], 10) > 31 && y == 0) {
+ y = parseInt(a[i], 10);
+ (y < 100) && (y += (y > 29) ? 1900 : 2000);
+ } else if (d == 0) {
+ d = a[i];
+ }
+ }
+ if (y == 0) {
+ var today = new Date();
+ y = today.getFullYear();
+ }
+ if (m != -1 && d != 0) {
+ this.setDate(new Date(y, m, d, hr, min, 0));
+ }
+};
+
+Calendar.prototype.hideShowCovered = function () {
+ var self = this;
+ Calendar.continuation_for_the_fucking_khtml_browser = function() {
+ function getVisib(obj){
+ var value = obj.style.visibility;
+ if (!value) {
+ if (document.defaultView && typeof (document.defaultView.getComputedStyle) == "function") { // Gecko, W3C
+ if (!Calendar.is_khtml)
+ value = document.defaultView.
+ getComputedStyle(obj, "").getPropertyValue("visibility");
+ else
+ value = '';
+ } else if (obj.currentStyle) { // IE
+ value = obj.currentStyle.visibility;
+ } else
+ value = '';
+ }
+ return value;
+ };
+
+ var tags = new Array("applet", "iframe", "select");
+ var el = self.element;
+
+ var p = Calendar.getAbsolutePos(el);
+ var EX1 = p.x;
+ var EX2 = el.offsetWidth + EX1;
+ var EY1 = p.y;
+ var EY2 = el.offsetHeight + EY1;
+
+ for (var k = tags.length; k > 0; ) {
+ var ar = document.getElementsByTagName(tags[--k]);
+ var cc = null;
+
+ for (var i = ar.length; i > 0;) {
+ cc = ar[--i];
+
+ p = Calendar.getAbsolutePos(cc);
+ var CX1 = p.x;
+ var CX2 = cc.offsetWidth + CX1;
+ var CY1 = p.y;
+ var CY2 = cc.offsetHeight + CY1;
+
+ if (self.hidden || (CX1 > EX2) || (CX2 < EX1) || (CY1 > EY2) || (CY2 < EY1)) {
+ if (!cc.__msh_save_visibility) {
+ cc.__msh_save_visibility = getVisib(cc);
+ }
+ cc.style.visibility = cc.__msh_save_visibility;
+ } else {
+ if (!cc.__msh_save_visibility) {
+ cc.__msh_save_visibility = getVisib(cc);
+ }
+ cc.style.visibility = "hidden";
+ }
+ }
+ }
+ };
+ if (Calendar.is_khtml)
+ setTimeout("Calendar.continuation_for_the_fucking_khtml_browser()", 10);
+ else
+ Calendar.continuation_for_the_fucking_khtml_browser();
+};
+
+/** Internal function; it displays the bar with the names of the weekday. */
+Calendar.prototype._displayWeekdays = function () {
+ var MON = this.mondayFirst ? 0 : 1;
+ var SUN = this.mondayFirst ? 6 : 0;
+ var SAT = this.mondayFirst ? 5 : 6;
+ var cell = this.firstdayname;
+ for (var i = 0; i < 7; ++i) {
+ cell.className = "day name";
+ if (!i) {
+ cell.ttip = this.mondayFirst ? Calendar._TT["SUN_FIRST"] : Calendar._TT["MON_FIRST"];
+ cell.navtype = 100;
+ cell.calendar = this;
+ Calendar._add_evs(cell);
+ }
+ if (i == SUN || i == SAT) {
+ Calendar.addClass(cell, "weekend");
+ }
+ cell.firstChild.data = Calendar._SDN[i + 1 - MON];
+ cell = cell.nextSibling;
+ }
+};
+
+/** Internal function. Hides all combo boxes that might be displayed. */
+Calendar.prototype._hideCombos = function () {
+ this.monthsCombo.style.display = "none";
+ this.yearsCombo.style.display = "none";
+};
+
+/** Internal function. Starts dragging the element. */
+Calendar.prototype._dragStart = function (ev) {
+ if (this.dragging) {
+ return;
+ }
+ this.dragging = true;
+ var posX;
+ var posY;
+ if (Calendar.is_ie) {
+ posY = window.event.clientY + document.body.scrollTop;
+ posX = window.event.clientX + document.body.scrollLeft;
+ } else {
+ posY = ev.clientY + window.scrollY;
+ posX = ev.clientX + window.scrollX;
+ }
+ var st = this.element.style;
+ this.xOffs = posX - parseInt(st.left);
+ this.yOffs = posY - parseInt(st.top);
+ with (Calendar) {
+ addEvent(document, "mousemove", calDragIt);
+ addEvent(document, "mouseover", stopEvent);
+ addEvent(document, "mouseup", calDragEnd);
+ }
+};
+
+// BEGIN: DATE OBJECT PATCHES
+
+/** Adds the number of days array to the Date object. */
+Date._MD = new Array(31,28,31,30,31,30,31,31,30,31,30,31);
+
+/** Constants used for time computations */
+Date.SECOND = 1000 /* milliseconds */;
+Date.MINUTE = 60 * Date.SECOND;
+Date.HOUR = 60 * Date.MINUTE;
+Date.DAY = 24 * Date.HOUR;
+Date.WEEK = 7 * Date.DAY;
+
+/** Returns the number of days in the current month */
+Date.prototype.getMonthDays = function(month) {
+ var year = this.getFullYear();
+ if (typeof month == "undefined") {
+ month = this.getMonth();
+ }
+ if (((0 == (year%4)) && ( (0 != (year%100)) || (0 == (year%400)))) && month == 1) {
+ return 29;
+ } else {
+ return Date._MD[month];
+ }
+};
+
+/** Returns the number of day in the year. */
+Date.prototype.getDayOfYear = function() {
+ var now = new Date(this.getFullYear(), this.getMonth(), this.getDate(), 0, 0, 0);
+ var then = new Date(this.getFullYear(), 0, 1, 0, 0, 0);
+ var time = now - then;
+ return Math.floor(time / Date.DAY);
+};
+
+/** Returns the number of the week in year, as defined in ISO 8601. */
+Date.prototype.getWeekNumber = function() {
+ var now = new Date(this.getFullYear(), this.getMonth(), this.getDate(), 0, 0, 0);
+ var then = new Date(this.getFullYear(), 0, 1, 0, 0, 0);
+ var time = now - then;
+ var day = then.getDay(); // 0 means Sunday
+ if (day == 0) day = 7;
+ (day > 4) && (day -= 4) || (day += 3);
+ return Math.round(((time / Date.DAY) + day) / 7);
+};
+
+/** Checks dates equality (ignores time) */
+Date.prototype.equalsTo = function(date) {
+ return ((this.getFullYear() == date.getFullYear()) &&
+ (this.getMonth() == date.getMonth()) &&
+ (this.getDate() == date.getDate()) &&
+ (this.getHours() == date.getHours()) &&
+ (this.getMinutes() == date.getMinutes()));
+};
+
+/** Prints the date in a string according to the given format. */
+Date.prototype.print = function (str) {
+ var m = this.getMonth();
+ var d = this.getDate();
+ var y = this.getFullYear();
+ var wn = this.getWeekNumber();
+ var w = this.getDay();
+ var s = {};
+ var hr = this.getHours();
+ var pm = (hr >= 12);
+ var ir = (pm) ? (hr - 12) : hr;
+ var dy = this.getDayOfYear();
+ if (ir == 0)
+ ir = 12;
+ var min = this.getMinutes();
+ var sec = this.getSeconds();
+ s["%a"] = Calendar._SDN[w]; // abbreviated weekday name [FIXME: I18N]
+ s["%A"] = Calendar._DN[w]; // full weekday name
+ s["%b"] = Calendar._SMN[m]; // abbreviated month name [FIXME: I18N]
+ s["%B"] = Calendar._MN[m]; // full month name
+ // FIXME: %c : preferred date and time representation for the current locale
+ s["%C"] = 1 + Math.floor(y / 100); // the century number
+ s["%d"] = (d < 10) ? ("0" + d) : d; // the day of the month (range 01 to 31)
+ s["%e"] = d; // the day of the month (range 1 to 31)
+ // FIXME: %D : american date style: %m/%d/%y
+ // FIXME: %E, %F, %G, %g, %h (man strftime)
+ s["%H"] = (hr < 10) ? ("0" + hr) : hr; // hour, range 00 to 23 (24h format)
+ s["%I"] = (ir < 10) ? ("0" + ir) : ir; // hour, range 01 to 12 (12h format)
+ s["%j"] = (dy < 100) ? ((dy < 10) ? ("00" + dy) : ("0" + dy)) : dy; // day of the year (range 001 to 366)
+ s["%k"] = hr; // hour, range 0 to 23 (24h format)
+ s["%l"] = ir; // hour, range 1 to 12 (12h format)
+ s["%m"] = (m < 9) ? ("0" + (1+m)) : (1+m); // month, range 01 to 12
+ s["%M"] = (min < 10) ? ("0" + min) : min; // minute, range 00 to 59
+ s["%n"] = "\n"; // a newline character
+ s["%p"] = pm ? "PM" : "AM";
+ s["%P"] = pm ? "pm" : "am";
+ // FIXME: %r : the time in am/pm notation %I:%M:%S %p
+ // FIXME: %R : the time in 24-hour notation %H:%M
+ s["%s"] = Math.floor(this.getTime() / 1000);
+ s["%S"] = (sec < 10) ? ("0" + sec) : sec; // seconds, range 00 to 59
+ s["%t"] = "\t"; // a tab character
+ // FIXME: %T : the time in 24-hour notation (%H:%M:%S)
+ s["%U"] = s["%W"] = s["%V"] = (wn < 10) ? ("0" + wn) : wn;
+ s["%u"] = w + 1; // the day of the week (range 1 to 7, 1 = MON)
+ s["%w"] = w; // the day of the week (range 0 to 6, 0 = SUN)
+ // FIXME: %x : preferred date representation for the current locale without the time
+ // FIXME: %X : preferred time representation for the current locale without the date
+ s["%y"] = ('' + y).substr(2, 2); // year without the century (range 00 to 99)
+ s["%Y"] = y; // year with the century
+ s["%%"] = "%"; // a literal '%' character
+ var re = Date._msh_formatRegexp;
+ if (typeof re == "undefined") {
+ var tmp = "";
+ for (var i in s)
+ tmp += tmp ? ("|" + i) : i;
+ Date._msh_formatRegexp = re = new RegExp("(" + tmp + ")", 'g');
+ }
+ return str.replace(re, function(match, par) { return s[par]; });
+};
+
+// END: DATE OBJECT PATCHES
+
+// global object that remembers the calendar
+window.calendar = null;
diff --git a/httemplate/elements/calendar_stripped.js b/httemplate/elements/calendar_stripped.js
new file mode 100644
index 0000000..029496a
--- /dev/null
+++ b/httemplate/elements/calendar_stripped.js
@@ -0,0 +1,12 @@
+/* Copyright Mihai Bazon, 2002, 2003 | http://dynarch.com/mishoo/
+ * ------------------------------------------------------------------
+ *
+ * The DHTML Calendar, version 0.9.5 "Your favorite time, bis"
+ *
+ * Details and latest version at:
+ * http://dynarch.com/mishoo/calendar.epl
+ *
+ * This script is distributed under the GNU Lesser General Public License.
+ * Read the entire license text here: http://www.gnu.org/licenses/lgpl.html
+ */
+ Calendar=function(mondayFirst,dateStr,onSelected,onClose){this.activeDiv=null;this.currentDateEl=null;this.getDateStatus=null;this.timeout=null;this.onSelected=onSelected||null;this.onClose=onClose||null;this.dragging=false;this.hidden=false;this.minYear=1970;this.maxYear=2050;this.dateFormat=Calendar._TT["DEF_DATE_FORMAT"];this.ttDateFormat=Calendar._TT["TT_DATE_FORMAT"];this.isPopup=true;this.weekNumbers=true;this.mondayFirst=mondayFirst;this.dateStr=dateStr;this.ar_days=null;this.showsTime=false;this.time24=true;this.table=null;this.element=null;this.tbody=null;this.firstdayname=null;this.monthsCombo=null;this.yearsCombo=null;this.hilitedMonth=null;this.activeMonth=null;this.hilitedYear=null;this.activeYear=null;this.dateClicked=false;if(typeof Calendar._SDN=="undefined"){if(typeof Calendar._SDN_len=="undefined")Calendar._SDN_len=3;var ar=new Array();for(var i=8;i>0;){ar[--i]=Calendar._DN[i].substr(0,Calendar._SDN_len);}Calendar._SDN=ar;if(typeof Calendar._SMN_len=="undefined")Calendar._SMN_len=3;ar=new Array();for(var i=12;i>0;){ar[--i]=Calendar._MN[i].substr(0,Calendar._SMN_len);}Calendar._SMN=ar;}};Calendar._C=null;Calendar.is_ie=(/msie/i.test(navigator.userAgent)&&!/opera/i.test(navigator.userAgent));Calendar.is_opera=/opera/i.test(navigator.userAgent);Calendar.is_khtml=/Konqueror|Safari|KHTML/i.test(navigator.userAgent);Calendar.getAbsolutePos=function(el){var SL=0,ST=0;var is_div=/^div$/i.test(el.tagName);if(is_div&&el.scrollLeft)SL=el.scrollLeft;if(is_div&&el.scrollTop)ST=el.scrollTop;var r={x:el.offsetLeft-SL,y:el.offsetTop-ST};if(el.offsetParent){var tmp=Calendar.getAbsolutePos(el.offsetParent);r.x+=tmp.x;r.y+=tmp.y;}return r;};Calendar.isRelated=function(el,evt){var related=evt.relatedTarget;if(!related){var type=evt.type;if(type=="mouseover"){related=evt.fromElement;}else if(type=="mouseout"){related=evt.toElement;}}while(related){if(related==el){return true;}related=related.parentNode;}return false;};Calendar.removeClass=function(el,className){if(!(el&&el.className)){return;}var cls=el.className.split(" ");var ar=new Array();for(var i=cls.length;i>0;){if(cls[--i]!=className){ar[ar.length]=cls[i];}}el.className=ar.join(" ");};Calendar.addClass=function(el,className){Calendar.removeClass(el,className);el.className+=" "+className;};Calendar.getElement=function(ev){if(Calendar.is_ie){return window.event.srcElement;}else{return ev.currentTarget;}};Calendar.getTargetElement=function(ev){if(Calendar.is_ie){return window.event.srcElement;}else{return ev.target;}};Calendar.stopEvent=function(ev){ev||(ev=window.event);if(Calendar.is_ie){ev.cancelBubble=true;ev.returnValue=false;}else{ev.preventDefault();ev.stopPropagation();}return false;};Calendar.addEvent=function(el,evname,func){if(el.attachEvent){el.attachEvent("on"+evname,func);}else if(el.addEventListener){el.addEventListener(evname,func,true);}else{el["on"+evname]=func;}};Calendar.removeEvent=function(el,evname,func){if(el.detachEvent){el.detachEvent("on"+evname,func);}else if(el.removeEventListener){el.removeEventListener(evname,func,true);}else{el["on"+evname]=null;}};Calendar.createElement=function(type,parent){var el=null;if(document.createElementNS){el=document.createElementNS("http://www.w3.org/1999/xhtml",type);}else{el=document.createElement(type);}if(typeof parent!="undefined"){parent.appendChild(el);}return el;};Calendar._add_evs=function(el){with(Calendar){addEvent(el,"mouseover",dayMouseOver);addEvent(el,"mousedown",dayMouseDown);addEvent(el,"mouseout",dayMouseOut);if(is_ie){addEvent(el,"dblclick",dayMouseDblClick);el.setAttribute("unselectable",true);}}};Calendar.findMonth=function(el){if(typeof el.month!="undefined"){return el;}else if(typeof el.parentNode.month!="undefined"){return el.parentNode;}return null;};Calendar.findYear=function(el){if(typeof el.year!="undefined"){return el;}else if(typeof el.parentNode.year!="undefined"){return el.parentNode;}return null;};Calendar.showMonthsCombo=function(){var cal=Calendar._C;if(!cal){return false;}var cal=cal;var cd=cal.activeDiv;var mc=cal.monthsCombo;if(cal.hilitedMonth){Calendar.removeClass(cal.hilitedMonth,"hilite");}if(cal.activeMonth){Calendar.removeClass(cal.activeMonth,"active");}var mon=cal.monthsCombo.getElementsByTagName("div")[cal.date.getMonth()];Calendar.addClass(mon,"active");cal.activeMonth=mon;var s=mc.style;s.display="block";if(cd.navtype<0)s.left=cd.offsetLeft+"px";else s.left=(cd.offsetLeft+cd.offsetWidth-mc.offsetWidth)+"px";s.top=(cd.offsetTop+cd.offsetHeight)+"px";};Calendar.showYearsCombo=function(fwd){var cal=Calendar._C;if(!cal){return false;}var cal=cal;var cd=cal.activeDiv;var yc=cal.yearsCombo;if(cal.hilitedYear){Calendar.removeClass(cal.hilitedYear,"hilite");}if(cal.activeYear){Calendar.removeClass(cal.activeYear,"active");}cal.activeYear=null;var Y=cal.date.getFullYear()+(fwd?1:-1);var yr=yc.firstChild;var show=false;for(var i=12;i>0;--i){if(Y>=cal.minYear&&Y<=cal.maxYear){yr.firstChild.data=Y;yr.year=Y;yr.style.display="block";show=true;}else{yr.style.display="none";}yr=yr.nextSibling;Y+=fwd?2:-2;}if(show){var s=yc.style;s.display="block";if(cd.navtype<0)s.left=cd.offsetLeft+"px";else s.left=(cd.offsetLeft+cd.offsetWidth-yc.offsetWidth)+"px";s.top=(cd.offsetTop+cd.offsetHeight)+"px";}};Calendar.tableMouseUp=function(ev){var cal=Calendar._C;if(!cal){return false;}if(cal.timeout){clearTimeout(cal.timeout);}var el=cal.activeDiv;if(!el){return false;}var target=Calendar.getTargetElement(ev);ev||(ev=window.event);Calendar.removeClass(el,"active");if(target==el||target.parentNode==el){Calendar.cellClick(el,ev);}var mon=Calendar.findMonth(target);var date=null;if(mon){date=new Date(cal.date);if(mon.month!=date.getMonth()){date.setMonth(mon.month);cal.setDate(date);cal.dateClicked=false;cal.callHandler();}}else{var year=Calendar.findYear(target);if(year){date=new Date(cal.date);if(year.year!=date.getFullYear()){date.setFullYear(year.year);cal.setDate(date);cal.dateClicked=false;cal.callHandler();}}}with(Calendar){removeEvent(document,"mouseup",tableMouseUp);removeEvent(document,"mouseover",tableMouseOver);removeEvent(document,"mousemove",tableMouseOver);cal._hideCombos();_C=null;return stopEvent(ev);}};Calendar.tableMouseOver=function(ev){var cal=Calendar._C;if(!cal){return;}var el=cal.activeDiv;var target=Calendar.getTargetElement(ev);if(target==el||target.parentNode==el){Calendar.addClass(el,"hilite active");Calendar.addClass(el.parentNode,"rowhilite");}else{if(typeof el.navtype=="undefined"||(el.navtype!=50&&(el.navtype==0||Math.abs(el.navtype)>2)))Calendar.removeClass(el,"active");Calendar.removeClass(el,"hilite");Calendar.removeClass(el.parentNode,"rowhilite");}ev||(ev=window.event);if(el.navtype==50&&target!=el){var pos=Calendar.getAbsolutePos(el);var w=el.offsetWidth;var x=ev.clientX;var dx;var decrease=true;if(x>pos.x+w){dx=x-pos.x-w;decrease=false;}else dx=pos.x-x;if(dx<0)dx=0;var range=el._range;var current=el._current;var count=Math.floor(dx/10)%range.length;for(var i=range.length;--i>=0;)if(range[i]==current)break;while(count-->0)if(decrease){if(!(--i in range))i=range.length-1;}else if(!(++i in range))i=0;var newval=range[i];el.firstChild.data=newval;cal.onUpdateTime();}var mon=Calendar.findMonth(target);if(mon){if(mon.month!=cal.date.getMonth()){if(cal.hilitedMonth){Calendar.removeClass(cal.hilitedMonth,"hilite");}Calendar.addClass(mon,"hilite");cal.hilitedMonth=mon;}else if(cal.hilitedMonth){Calendar.removeClass(cal.hilitedMonth,"hilite");}}else{if(cal.hilitedMonth){Calendar.removeClass(cal.hilitedMonth,"hilite");}var year=Calendar.findYear(target);if(year){if(year.year!=cal.date.getFullYear()){if(cal.hilitedYear){Calendar.removeClass(cal.hilitedYear,"hilite");}Calendar.addClass(year,"hilite");cal.hilitedYear=year;}else if(cal.hilitedYear){Calendar.removeClass(cal.hilitedYear,"hilite");}}else if(cal.hilitedYear){Calendar.removeClass(cal.hilitedYear,"hilite");}}return Calendar.stopEvent(ev);};Calendar.tableMouseDown=function(ev){if(Calendar.getTargetElement(ev)==Calendar.getElement(ev)){return Calendar.stopEvent(ev);}};Calendar.calDragIt=function(ev){var cal=Calendar._C;if(!(cal&&cal.dragging)){return false;}var posX;var posY;if(Calendar.is_ie){posY=window.event.clientY+document.body.scrollTop;posX=window.event.clientX+document.body.scrollLeft;}else{posX=ev.pageX;posY=ev.pageY;}cal.hideShowCovered();var st=cal.element.style;st.left=(posX-cal.xOffs)+"px";st.top=(posY-cal.yOffs)+"px";return Calendar.stopEvent(ev);};Calendar.calDragEnd=function(ev){var cal=Calendar._C;if(!cal){return false;}cal.dragging=false;with(Calendar){removeEvent(document,"mousemove",calDragIt);removeEvent(document,"mouseover",stopEvent);removeEvent(document,"mouseup",calDragEnd);tableMouseUp(ev);}cal.hideShowCovered();};Calendar.dayMouseDown=function(ev){var el=Calendar.getElement(ev);if(el.disabled){return false;}var cal=el.calendar;cal.activeDiv=el;Calendar._C=cal;if(el.navtype!=300)with(Calendar){if(el.navtype==50)el._current=el.firstChild.data;addClass(el,"hilite active");addEvent(document,"mouseover",tableMouseOver);addEvent(document,"mousemove",tableMouseOver);addEvent(document,"mouseup",tableMouseUp);}else if(cal.isPopup){cal._dragStart(ev);}if(el.navtype==-1||el.navtype==1){if(cal.timeout)clearTimeout(cal.timeout);cal.timeout=setTimeout("Calendar.showMonthsCombo()",250);}else if(el.navtype==-2||el.navtype==2){if(cal.timeout)clearTimeout(cal.timeout);cal.timeout=setTimeout((el.navtype>0)?"Calendar.showYearsCombo(true)":"Calendar.showYearsCombo(false)",250);}else{cal.timeout=null;}return Calendar.stopEvent(ev);};Calendar.dayMouseDblClick=function(ev){Calendar.cellClick(Calendar.getElement(ev),ev||window.event);if(Calendar.is_ie){document.selection.empty();}};Calendar.dayMouseOver=function(ev){var el=Calendar.getElement(ev);if(Calendar.isRelated(el,ev)||Calendar._C||el.disabled){return false;}if(el.ttip){if(el.ttip.substr(0,1)=="_"){var date=null;with(el.calendar.date){date=new Date(getFullYear(),getMonth(),el.caldate);}el.ttip=date.print(el.calendar.ttDateFormat)+el.ttip.substr(1);}el.calendar.tooltips.firstChild.data=el.ttip;}if(el.navtype!=300){Calendar.addClass(el,"hilite");if(el.caldate){Calendar.addClass(el.parentNode,"rowhilite");}}return Calendar.stopEvent(ev);};Calendar.dayMouseOut=function(ev){with(Calendar){var el=getElement(ev);if(isRelated(el,ev)||_C||el.disabled){return false;}removeClass(el,"hilite");if(el.caldate){removeClass(el.parentNode,"rowhilite");}el.calendar.tooltips.firstChild.data=_TT["SEL_DATE"];return stopEvent(ev);}};Calendar.cellClick=function(el,ev){var cal=el.calendar;var closing=false;var newdate=false;var date=null;if(typeof el.navtype=="undefined"){Calendar.removeClass(cal.currentDateEl,"selected");Calendar.addClass(el,"selected");closing=(cal.currentDateEl==el);if(!closing){cal.currentDateEl=el;}cal.date.setDate(el.caldate);date=cal.date;newdate=true;cal.dateClicked=true;}else{if(el.navtype==200){Calendar.removeClass(el,"hilite");cal.callCloseHandler();return;}date=(el.navtype==0)?new Date():new Date(cal.date);cal.dateClicked=false;var year=date.getFullYear();var mon=date.getMonth();function setMonth(m){var day=date.getDate();var max=date.getMonthDays(m);if(day>max){date.setDate(max);}date.setMonth(m);};switch(el.navtype){case 400:Calendar.removeClass(el,"hilite");var text=Calendar._TT["ABOUT"];if(typeof text!="undefined"){text+=cal.showsTime?Calendar._TT["ABOUT_TIME"]:"";}else{text="Help and about box text is not translated into this language.\n"+"If you know this language and you feel generous please update\n"+"the corresponding file in \"lang\" subdir to match calendar-en.js\n"+"and send it back to <mishoo@infoiasi.ro> to get it into the distribution ;-)\n\n"+"Thank you!\n"+"http://dynarch.com/mishoo/calendar.epl\n";}alert(text);return;case-2:if(year>cal.minYear){date.setFullYear(year-1);}break;case-1:if(mon>0){setMonth(mon-1);}else if(year-->cal.minYear){date.setFullYear(year);setMonth(11);}break;case 1:if(mon<11){setMonth(mon+1);}else if(year<cal.maxYear){date.setFullYear(year+1);setMonth(0);}break;case 2:if(year<cal.maxYear){date.setFullYear(year+1);}break;case 100:cal.setMondayFirst(!cal.mondayFirst);return;case 50:var range=el._range;var current=el.firstChild.data;for(var i=range.length;--i>=0;)if(range[i]==current)break;if(ev&&ev.shiftKey){if(!(--i in range))i=range.length-1;}else if(!(++i in range))i=0;var newval=range[i];el.firstChild.data=newval;cal.onUpdateTime();return;case 0:if((typeof cal.getDateStatus=="function")&&cal.getDateStatus(date,date.getFullYear(),date.getMonth(),date.getDate())){return false;}break;}if(!date.equalsTo(cal.date)){cal.setDate(date);newdate=true;}}if(newdate){cal.callHandler();}if(closing){Calendar.removeClass(el,"hilite");cal.callCloseHandler();}};Calendar.prototype.create=function(_par){var parent=null;if(!_par){parent=document.getElementsByTagName("body")[0];this.isPopup=true;}else{parent=_par;this.isPopup=false;}this.date=this.dateStr?new Date(this.dateStr):new Date();var table=Calendar.createElement("table");this.table=table;table.cellSpacing=0;table.cellPadding=0;table.calendar=this;Calendar.addEvent(table,"mousedown",Calendar.tableMouseDown);var div=Calendar.createElement("div");this.element=div;div.className="calendar";if(this.isPopup){div.style.position="absolute";div.style.display="none";}div.appendChild(table);var thead=Calendar.createElement("thead",table);var cell=null;var row=null;var cal=this;var hh=function(text,cs,navtype){cell=Calendar.createElement("td",row);cell.colSpan=cs;cell.className="button";if(navtype!=0&&Math.abs(navtype)<=2)cell.className+=" nav";Calendar._add_evs(cell);cell.calendar=cal;cell.navtype=navtype;if(text.substr(0,1)!="&"){cell.appendChild(document.createTextNode(text));}else{cell.innerHTML=text;}return cell;};row=Calendar.createElement("tr",thead);var title_length=6;(this.isPopup)&&--title_length;(this.weekNumbers)&&++title_length;hh("?",1,400).ttip=Calendar._TT["INFO"];this.title=hh("",title_length,300);this.title.className="title";if(this.isPopup){this.title.ttip=Calendar._TT["DRAG_TO_MOVE"];this.title.style.cursor="move";hh("&#x00d7;",1,200).ttip=Calendar._TT["CLOSE"];}row=Calendar.createElement("tr",thead);row.className="headrow";this._nav_py=hh("&#x00ab;",1,-2);this._nav_py.ttip=Calendar._TT["PREV_YEAR"];this._nav_pm=hh("&#x2039;",1,-1);this._nav_pm.ttip=Calendar._TT["PREV_MONTH"];this._nav_now=hh(Calendar._TT["TODAY"],this.weekNumbers?4:3,0);this._nav_now.ttip=Calendar._TT["GO_TODAY"];this._nav_nm=hh("&#x203a;",1,1);this._nav_nm.ttip=Calendar._TT["NEXT_MONTH"];this._nav_ny=hh("&#x00bb;",1,2);this._nav_ny.ttip=Calendar._TT["NEXT_YEAR"];row=Calendar.createElement("tr",thead);row.className="daynames";if(this.weekNumbers){cell=Calendar.createElement("td",row);cell.className="name wn";cell.appendChild(document.createTextNode(Calendar._TT["WK"]));}for(var i=7;i>0;--i){cell=Calendar.createElement("td",row);cell.appendChild(document.createTextNode(""));if(!i){cell.navtype=100;cell.calendar=this;Calendar._add_evs(cell);}}this.firstdayname=(this.weekNumbers)?row.firstChild.nextSibling:row.firstChild;this._displayWeekdays();var tbody=Calendar.createElement("tbody",table);this.tbody=tbody;for(i=6;i>0;--i){row=Calendar.createElement("tr",tbody);if(this.weekNumbers){cell=Calendar.createElement("td",row);cell.appendChild(document.createTextNode(""));}for(var j=7;j>0;--j){cell=Calendar.createElement("td",row);cell.appendChild(document.createTextNode(""));cell.calendar=this;Calendar._add_evs(cell);}}if(this.showsTime){row=Calendar.createElement("tr",tbody);row.className="time";cell=Calendar.createElement("td",row);cell.className="time";cell.colSpan=2;cell.innerHTML="&nbsp;";cell=Calendar.createElement("td",row);cell.className="time";cell.colSpan=this.weekNumbers?4:3;(function(){function makeTimePart(className,init,range_start,range_end){var part=Calendar.createElement("span",cell);part.className=className;part.appendChild(document.createTextNode(init));part.calendar=cal;part.ttip=Calendar._TT["TIME_PART"];part.navtype=50;part._range=[];if(typeof range_start!="number")part._range=range_start;else{for(var i=range_start;i<=range_end;++i){var txt;if(i<10&&range_end>=10)txt='0'+i;else txt=''+i;part._range[part._range.length]=txt;}}Calendar._add_evs(part);return part;};var hrs=cal.date.getHours();var mins=cal.date.getMinutes();var t12=!cal.time24;var pm=(hrs>12);if(t12&&pm)hrs-=12;var H=makeTimePart("hour",hrs,t12?1:0,t12?12:23);var span=Calendar.createElement("span",cell);span.appendChild(document.createTextNode(":"));span.className="colon";var M=makeTimePart("minute",mins,0,59);var AP=null;cell=Calendar.createElement("td",row);cell.className="time";cell.colSpan=2;if(t12)AP=makeTimePart("ampm",pm?"pm":"am",["am","pm"]);else cell.innerHTML="&nbsp;";cal.onSetTime=function(){var hrs=this.date.getHours();var mins=this.date.getMinutes();var pm=(hrs>12);if(pm&&t12)hrs-=12;H.firstChild.data=(hrs<10)?("0"+hrs):hrs;M.firstChild.data=(mins<10)?("0"+mins):mins;if(t12)AP.firstChild.data=pm?"pm":"am";};cal.onUpdateTime=function(){var date=this.date;var h=parseInt(H.firstChild.data,10);if(t12){if(/pm/i.test(AP.firstChild.data)&&h<12)h+=12;else if(/am/i.test(AP.firstChild.data)&&h==12)h=0;}var d=date.getDate();var m=date.getMonth();var y=date.getFullYear();date.setHours(h);date.setMinutes(parseInt(M.firstChild.data,10));date.setFullYear(y);date.setMonth(m);date.setDate(d);this.dateClicked=false;this.callHandler();};})();}else{this.onSetTime=this.onUpdateTime=function(){};}var tfoot=Calendar.createElement("tfoot",table);row=Calendar.createElement("tr",tfoot);row.className="footrow";cell=hh(Calendar._TT["SEL_DATE"],this.weekNumbers?8:7,300);cell.className="ttip";if(this.isPopup){cell.ttip=Calendar._TT["DRAG_TO_MOVE"];cell.style.cursor="move";}this.tooltips=cell;div=Calendar.createElement("div",this.element);this.monthsCombo=div;div.className="combo";for(i=0;i<Calendar._MN.length;++i){var mn=Calendar.createElement("div");mn.className=Calendar.is_ie?"label-IEfix":"label";mn.month=i;mn.appendChild(document.createTextNode(Calendar._SMN[i]));div.appendChild(mn);}div=Calendar.createElement("div",this.element);this.yearsCombo=div;div.className="combo";for(i=12;i>0;--i){var yr=Calendar.createElement("div");yr.className=Calendar.is_ie?"label-IEfix":"label";yr.appendChild(document.createTextNode(""));div.appendChild(yr);}this._init(this.mondayFirst,this.date);parent.appendChild(this.element);};Calendar._keyEvent=function(ev){if(!window.calendar){return false;}(Calendar.is_ie)&&(ev=window.event);var cal=window.calendar;var act=(Calendar.is_ie||ev.type=="keypress");if(ev.ctrlKey){switch(ev.keyCode){case 37:act&&Calendar.cellClick(cal._nav_pm);break;case 38:act&&Calendar.cellClick(cal._nav_py);break;case 39:act&&Calendar.cellClick(cal._nav_nm);break;case 40:act&&Calendar.cellClick(cal._nav_ny);break;default:return false;}}else switch(ev.keyCode){case 32:Calendar.cellClick(cal._nav_now);break;case 27:act&&cal.hide();break;case 37:case 38:case 39:case 40:if(act){var date=cal.date.getDate()-1;var el=cal.currentDateEl;var ne=null;var prev=(ev.keyCode==37)||(ev.keyCode==38);switch(ev.keyCode){case 37:(--date>=0)&&(ne=cal.ar_days[date]);break;case 38:date-=7;(date>=0)&&(ne=cal.ar_days[date]);break;case 39:(++date<cal.ar_days.length)&&(ne=cal.ar_days[date]);break;case 40:date+=7;(date<cal.ar_days.length)&&(ne=cal.ar_days[date]);break;}if(!ne){if(prev){Calendar.cellClick(cal._nav_pm);}else{Calendar.cellClick(cal._nav_nm);}date=(prev)?cal.date.getMonthDays():1;el=cal.currentDateEl;ne=cal.ar_days[date-1];}Calendar.removeClass(el,"selected");Calendar.addClass(ne,"selected");cal.date.setDate(ne.caldate);cal.callHandler();cal.currentDateEl=ne;}break;case 13:if(act){cal.callHandler();cal.hide();}break;default:return false;}return Calendar.stopEvent(ev);};Calendar.prototype._init=function(mondayFirst,date){var today=new Date();var year=date.getFullYear();if(year<this.minYear){year=this.minYear;date.setFullYear(year);}else if(year>this.maxYear){year=this.maxYear;date.setFullYear(year);}this.mondayFirst=mondayFirst;this.date=new Date(date);var month=date.getMonth();var mday=date.getDate();var no_days=date.getMonthDays();date.setDate(1);var wday=date.getDay();var MON=mondayFirst?1:0;var SAT=mondayFirst?5:6;var SUN=mondayFirst?6:0;if(mondayFirst){wday=(wday>0)?(wday-1):6;}var iday=1;var row=this.tbody.firstChild;var MN=Calendar._SMN[month];var hasToday=((today.getFullYear()==year)&&(today.getMonth()==month));var todayDate=today.getDate();var week_number=date.getWeekNumber();var ar_days=new Array();for(var i=0;i<6;++i){if(iday>no_days){row.className="emptyrow";row=row.nextSibling;continue;}var cell=row.firstChild;if(this.weekNumbers){cell.className="day wn";cell.firstChild.data=week_number;cell=cell.nextSibling;}++week_number;row.className="daysrow";for(var j=0;j<7;++j){cell.className="day";if((!i&&j<wday)||iday>no_days){cell.innerHTML="&nbsp;";cell.disabled=true;cell=cell.nextSibling;continue;}cell.disabled=false;cell.firstChild.data=iday;if(typeof this.getDateStatus=="function"){date.setDate(iday);var status=this.getDateStatus(date,year,month,iday);if(status===true){cell.className+=" disabled";cell.disabled=true;}else{if(/disabled/i.test(status))cell.disabled=true;cell.className+=" "+status;}}if(!cell.disabled){ar_days[ar_days.length]=cell;cell.caldate=iday;cell.ttip="_";if(iday==mday){cell.className+=" selected";this.currentDateEl=cell;}if(hasToday&&(iday==todayDate)){cell.className+=" today";cell.ttip+=Calendar._TT["PART_TODAY"];}if(wday==SAT||wday==SUN){cell.className+=" weekend";}}++iday;((++wday)^ 7)||(wday=0);cell=cell.nextSibling;}row=row.nextSibling;}this.ar_days=ar_days;this.title.firstChild.data=Calendar._MN[month]+", "+year;this.onSetTime();};Calendar.prototype.setDate=function(date){if(!date.equalsTo(this.date)){this._init(this.mondayFirst,date);}};Calendar.prototype.refresh=function(){this._init(this.mondayFirst,this.date);};Calendar.prototype.setMondayFirst=function(mondayFirst){this._init(mondayFirst,this.date);this._displayWeekdays();};Calendar.prototype.setDateStatusHandler=Calendar.prototype.setDisabledHandler=function(unaryFunction){this.getDateStatus=unaryFunction;};Calendar.prototype.setRange=function(a,z){this.minYear=a;this.maxYear=z;};Calendar.prototype.callHandler=function(){if(this.onSelected){this.onSelected(this,this.date.print(this.dateFormat));}};Calendar.prototype.callCloseHandler=function(){if(this.onClose){this.onClose(this);}this.hideShowCovered();};Calendar.prototype.destroy=function(){var el=this.element.parentNode;el.removeChild(this.element);Calendar._C=null;window.calendar=null;};Calendar.prototype.reparent=function(new_parent){var el=this.element;el.parentNode.removeChild(el);new_parent.appendChild(el);};Calendar._checkCalendar=function(ev){if(!window.calendar){return false;}var el=Calendar.is_ie?Calendar.getElement(ev):Calendar.getTargetElement(ev);for(;el!=null&&el!=calendar.element;el=el.parentNode);if(el==null){window.calendar.callCloseHandler();return Calendar.stopEvent(ev);}};Calendar.prototype.show=function(){var rows=this.table.getElementsByTagName("tr");for(var i=rows.length;i>0;){var row=rows[--i];Calendar.removeClass(row,"rowhilite");var cells=row.getElementsByTagName("td");for(var j=cells.length;j>0;){var cell=cells[--j];Calendar.removeClass(cell,"hilite");Calendar.removeClass(cell,"active");}}this.element.style.display="block";this.hidden=false;if(this.isPopup){window.calendar=this;Calendar.addEvent(document,"keydown",Calendar._keyEvent);Calendar.addEvent(document,"keypress",Calendar._keyEvent);Calendar.addEvent(document,"mousedown",Calendar._checkCalendar);}this.hideShowCovered();};Calendar.prototype.hide=function(){if(this.isPopup){Calendar.removeEvent(document,"keydown",Calendar._keyEvent);Calendar.removeEvent(document,"keypress",Calendar._keyEvent);Calendar.removeEvent(document,"mousedown",Calendar._checkCalendar);}this.element.style.display="none";this.hidden=true;this.hideShowCovered();};Calendar.prototype.showAt=function(x,y){var s=this.element.style;s.left=x+"px";s.top=y+"px";this.show();};Calendar.prototype.showAtElement=function(el,opts){var self=this;var p=Calendar.getAbsolutePos(el);if(!opts||typeof opts!="string"){this.showAt(p.x,p.y+el.offsetHeight);return true;}this.element.style.display="block";Calendar.continuation_for_the_fucking_khtml_browser=function(){var w=self.element.offsetWidth;var h=self.element.offsetHeight;self.element.style.display="none";var valign=opts.substr(0,1);var halign="l";if(opts.length>1){halign=opts.substr(1,1);}switch(valign){case "T":p.y-=h;break;case "B":p.y+=el.offsetHeight;break;case "C":p.y+=(el.offsetHeight-h)/2;break;case "t":p.y+=el.offsetHeight-h;break;case "b":break;}switch(halign){case "L":p.x-=w;break;case "R":p.x+=el.offsetWidth;break;case "C":p.x+=(el.offsetWidth-w)/2;break;case "r":p.x+=el.offsetWidth-w;break;case "l":break;}self.showAt(p.x,p.y);};if(Calendar.is_khtml)setTimeout("Calendar.continuation_for_the_fucking_khtml_browser()",10);else Calendar.continuation_for_the_fucking_khtml_browser();};Calendar.prototype.setDateFormat=function(str){this.dateFormat=str;};Calendar.prototype.setTtDateFormat=function(str){this.ttDateFormat=str;};Calendar.prototype.parseDate=function(str,fmt){var y=0;var m=-1;var d=0;var a=str.split(/\W+/);if(!fmt){fmt=this.dateFormat;}var b=[];fmt.replace(/(%.)/g,function(str,par){return b[b.length]=par;});var i=0,j=0;var hr=0;var min=0;for(i=0;i<a.length;++i){if(b[i]=="%a"||b[i]=="%A"){continue;}if(b[i]=="%d"||b[i]=="%e"){d=parseInt(a[i],10);}if(b[i]=="%m"){m=parseInt(a[i],10)-1;}if(b[i]=="%Y"||b[i]=="%y"){y=parseInt(a[i],10);(y<100)&&(y+=(y>29)?1900:2000);}if(b[i]=="%b"||b[i]=="%B"){for(j=0;j<12;++j){if(Calendar._MN[j].substr(0,a[i].length).toLowerCase()==a[i].toLowerCase()){m=j;break;}}}else if(/%[HIkl]/.test(b[i])){hr=parseInt(a[i],10);}else if(/%[pP]/.test(b[i])){if(/pm/i.test(a[i])&&hr<12)hr+=12;}else if(b[i]=="%M"){min=parseInt(a[i],10);}}if(y!=0&&m!=-1&&d!=0){this.setDate(new Date(y,m,d,hr,min,0));return;}y=0;m=-1;d=0;for(i=0;i<a.length;++i){if(a[i].search(/[a-zA-Z]+/)!=-1){var t=-1;for(j=0;j<12;++j){if(Calendar._MN[j].substr(0,a[i].length).toLowerCase()==a[i].toLowerCase()){t=j;break;}}if(t!=-1){if(m!=-1){d=m+1;}m=t;}}else if(parseInt(a[i],10)<=12&&m==-1){m=a[i]-1;}else if(parseInt(a[i],10)>31&&y==0){y=parseInt(a[i],10);(y<100)&&(y+=(y>29)?1900:2000);}else if(d==0){d=a[i];}}if(y==0){var today=new Date();y=today.getFullYear();}if(m!=-1&&d!=0){this.setDate(new Date(y,m,d,hr,min,0));}};Calendar.prototype.hideShowCovered=function(){var self=this;Calendar.continuation_for_the_fucking_khtml_browser=function(){function getVisib(obj){var value=obj.style.visibility;if(!value){if(document.defaultView&&typeof(document.defaultView.getComputedStyle)=="function"){if(!Calendar.is_khtml)value=document.defaultView. getComputedStyle(obj,"").getPropertyValue("visibility");else value='';}else if(obj.currentStyle){value=obj.currentStyle.visibility;}else value='';}return value;};var tags=new Array("applet","iframe","select");var el=self.element;var p=Calendar.getAbsolutePos(el);var EX1=p.x;var EX2=el.offsetWidth+EX1;var EY1=p.y;var EY2=el.offsetHeight+EY1;for(var k=tags.length;k>0;){var ar=document.getElementsByTagName(tags[--k]);var cc=null;for(var i=ar.length;i>0;){cc=ar[--i];p=Calendar.getAbsolutePos(cc);var CX1=p.x;var CX2=cc.offsetWidth+CX1;var CY1=p.y;var CY2=cc.offsetHeight+CY1;if(self.hidden||(CX1>EX2)||(CX2<EX1)||(CY1>EY2)||(CY2<EY1)){if(!cc.__msh_save_visibility){cc.__msh_save_visibility=getVisib(cc);}cc.style.visibility=cc.__msh_save_visibility;}else{if(!cc.__msh_save_visibility){cc.__msh_save_visibility=getVisib(cc);}cc.style.visibility="hidden";}}}};if(Calendar.is_khtml)setTimeout("Calendar.continuation_for_the_fucking_khtml_browser()",10);else Calendar.continuation_for_the_fucking_khtml_browser();};Calendar.prototype._displayWeekdays=function(){var MON=this.mondayFirst?0:1;var SUN=this.mondayFirst?6:0;var SAT=this.mondayFirst?5:6;var cell=this.firstdayname;for(var i=0;i<7;++i){cell.className="day name";if(!i){cell.ttip=this.mondayFirst?Calendar._TT["SUN_FIRST"]:Calendar._TT["MON_FIRST"];cell.navtype=100;cell.calendar=this;Calendar._add_evs(cell);}if(i==SUN||i==SAT){Calendar.addClass(cell,"weekend");}cell.firstChild.data=Calendar._SDN[i+1-MON];cell=cell.nextSibling;}};Calendar.prototype._hideCombos=function(){this.monthsCombo.style.display="none";this.yearsCombo.style.display="none";};Calendar.prototype._dragStart=function(ev){if(this.dragging){return;}this.dragging=true;var posX;var posY;if(Calendar.is_ie){posY=window.event.clientY+document.body.scrollTop;posX=window.event.clientX+document.body.scrollLeft;}else{posY=ev.clientY+window.scrollY;posX=ev.clientX+window.scrollX;}var st=this.element.style;this.xOffs=posX-parseInt(st.left);this.yOffs=posY-parseInt(st.top);with(Calendar){addEvent(document,"mousemove",calDragIt);addEvent(document,"mouseover",stopEvent);addEvent(document,"mouseup",calDragEnd);}};Date._MD=new Array(31,28,31,30,31,30,31,31,30,31,30,31);Date.SECOND=1000;Date.MINUTE=60*Date.SECOND;Date.HOUR=60*Date.MINUTE;Date.DAY=24*Date.HOUR;Date.WEEK=7*Date.DAY;Date.prototype.getMonthDays=function(month){var year=this.getFullYear();if(typeof month=="undefined"){month=this.getMonth();}if(((0==(year%4))&&((0!=(year%100))||(0==(year%400))))&&month==1){return 29;}else{return Date._MD[month];}};Date.prototype.getDayOfYear=function(){var now=new Date(this.getFullYear(),this.getMonth(),this.getDate(),0,0,0);var then=new Date(this.getFullYear(),0,1,0,0,0);var time=now-then;return Math.floor(time/Date.DAY);};Date.prototype.getWeekNumber=function(){var now=new Date(this.getFullYear(),this.getMonth(),this.getDate(),0,0,0);var then=new Date(this.getFullYear(),0,1,0,0,0);var time=now-then;var day=then.getDay();if(day==0)day=7;(day>4)&&(day-=4)||(day+=3);return Math.round(((time/Date.DAY)+day)/7);};Date.prototype.equalsTo=function(date){return((this.getFullYear()==date.getFullYear())&&(this.getMonth()==date.getMonth())&&(this.getDate()==date.getDate())&&(this.getHours()==date.getHours())&&(this.getMinutes()==date.getMinutes()));};Date.prototype.print=function(str){var m=this.getMonth();var d=this.getDate();var y=this.getFullYear();var wn=this.getWeekNumber();var w=this.getDay();var s={};var hr=this.getHours();var pm=(hr>=12);var ir=(pm)?(hr-12):hr;var dy=this.getDayOfYear();if(ir==0)ir=12;var min=this.getMinutes();var sec=this.getSeconds();s["%a"]=Calendar._SDN[w];s["%A"]=Calendar._DN[w];s["%b"]=Calendar._SMN[m];s["%B"]=Calendar._MN[m];s["%C"]=1+Math.floor(y/100);s["%d"]=(d<10)?("0"+d):d;s["%e"]=d;s["%H"]=(hr<10)?("0"+hr):hr;s["%I"]=(ir<10)?("0"+ir):ir;s["%j"]=(dy<100)?((dy<10)?("00"+dy):("0"+dy)):dy;s["%k"]=hr;s["%l"]=ir;s["%m"]=(m<9)?("0"+(1+m)):(1+m);s["%M"]=(min<10)?("0"+min):min;s["%n"]="\n";s["%p"]=pm?"PM":"AM";s["%P"]=pm?"pm":"am";s["%s"]=Math.floor(this.getTime()/1000);s["%S"]=(sec<10)?("0"+sec):sec;s["%t"]="\t";s["%U"]=s["%W"]=s["%V"]=(wn<10)?("0"+wn):wn;s["%u"]=w+1;s["%w"]=w;s["%y"]=(''+y).substr(2,2);s["%Y"]=y;s["%%"]="%";var re=Date._msh_formatRegexp;if(typeof re=="undefined"){var tmp="";for(var i in s)tmp+=tmp?("|"+i):i;Date._msh_formatRegexp=re=new RegExp("("+tmp+")",'g');}return str.replace(re,function(match,par){return s[par];});};window.calendar=null; \ No newline at end of file
diff --git a/httemplate/elements/header.html b/httemplate/elements/header.html
new file mode 100644
index 0000000..581bbab
--- /dev/null
+++ b/httemplate/elements/header.html
@@ -0,0 +1,19 @@
+<%
+ my($title, $menubar) = @_;
+ my $etc = @_ ? shift : ''; #$etc is for things like onLoad= etc.
+%>
+ <HTML>
+ <HEAD>
+ <TITLE>
+ <%= $title %>
+ </TITLE>
+ <META HTTP-Equiv="Cache-Control" Content="no-cache">
+ <META HTTP-Equiv="Pragma" Content="no-cache">
+ <META HTTP-Equiv="Expires" Content="0">
+ </HEAD>
+ <BODY BGCOLOR="#e8e8e8"<%= $etc %>>
+ <FONT SIZE=7>
+ <%= $title %>
+ </FONT>
+ <BR><BR>
+ <%= $menubar ? "$menubar<BR><BR>" : '' %>
diff --git a/httemplate/elements/menubar.html b/httemplate/elements/menubar.html
new file mode 100644
index 0000000..87a5031
--- /dev/null
+++ b/httemplate/elements/menubar.html
@@ -0,0 +1,8 @@
+<%
+ my($item, $url, @html);
+ while (@_) {
+ ($item, $url) = splice(@_,0,2);
+ push @html, qq!<A HREF="$url">$item</A>!;
+ }
+%>
+<%= join(' | ', @html) %>
diff --git a/httemplate/elements/pager.html b/httemplate/elements/pager.html
new file mode 100644
index 0000000..db9ff83
--- /dev/null
+++ b/httemplate/elements/pager.html
@@ -0,0 +1,42 @@
+<%
+
+ my %opt = @_;
+
+ my $pager = '';
+ if ( $opt{'total'} != $opt{'num_rows'} && $opt{'maxrecords'} ) {
+ unless ( $opt{'offset'} == 0 ) {
+ $cgi->param('offset', $opt{'offset'} - $opt{'maxrecords'});
+%>
+
+ <A HREF="<%= $cgi->self_url %>"><B><FONT SIZE="+1">Previous</FONT></B></A>
+
+<%
+ }
+ my $page = 0;
+ for ( my $poff = 0; $poff < $opt{'total'}; $poff += $opt{'maxrecords'} ) {
+ $page++;
+ if ( $opt{'offset'} == $poff ) {
+%>
+
+ <FONT SIZE="+2"><%= $page %></FONT>
+
+<%
+ } else {
+ $cgi->param('offset', $poff);
+%>
+
+ <A HREF="<%= $cgi->self_url %>">$page</A>
+
+<%
+ }
+ }
+ unless ( $opt{'offset'} + $opt{'maxrecords'} > $opt{'total'} ) {
+ $cgi->param('offset', $opt{'offset'} + $opt{'maxrecords'});
+%>
+
+ <A HREF="<%= $cgi->self_url %>"><B><FONT SIZE="+1">Next</FONT></B></A>
+
+<%
+ }
+ }
+%>
diff --git a/httemplate/elements/table.html b/httemplate/elements/table.html
new file mode 100644
index 0000000..3b61087
--- /dev/null
+++ b/httemplate/elements/table.html
@@ -0,0 +1,8 @@
+<%
+ my $color = shift;
+ if ( $color ) {
+%>
+ <TABLE BGCOLOR="<%= $color %>" BORDER=1 WIDTH="100%" CELLSPACING=0 CELLPADDING=2 BORDERCOLOR="#999999">
+<% } else { %>
+ <TABLE BORDER=1 CELLSPACING=0 CELLPADDING=2 BORDERCOLOR="#999999">
+<% } %>
diff --git a/httemplate/graph/money_time-graph.cgi b/httemplate/graph/money_time-graph.cgi
new file mode 100755
index 0000000..76f1bd7
--- /dev/null
+++ b/httemplate/graph/money_time-graph.cgi
@@ -0,0 +1,108 @@
+<%
+
+#my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime(time);
+my ($curmon,$curyear) = (localtime(time))[4,5];
+
+#find first month
+my $syear = $cgi->param('syear') || 1899+$curyear;
+my $smonth = $cgi->param('smonth') || $curmon+1;
+
+#find last month
+my $eyear = $cgi->param('eyear') || 1900+$curyear;
+my $emonth = $cgi->param('emonth') || $curmon+1;
+if ( $emonth++>12 ) { $emonth-=12; $eyear++; }
+
+my @labels;
+my %data;
+
+while ( $syear < $eyear || ( $syear == $eyear && $smonth < $emonth ) ) {
+ push @labels, "$smonth/$syear";
+
+ my $speriod = timelocal(0,0,0,1,$smonth-1,$syear);
+ if ( ++$smonth == 13 ) { $syear++; $smonth=1; }
+ my $eperiod = timelocal(0,0,0,1,$smonth-1,$syear);
+
+ my $where = "WHERE _date >= $speriod AND _date < $eperiod";
+
+ # Invoiced
+ my $charged_sql = "SELECT SUM(charged) FROM cust_bill $where";
+ my $charged_sth = dbh->prepare($charged_sql) or die dbh->errstr;
+ $charged_sth->execute or die $charged_sth->errstr;
+ my $charged = $charged_sth->fetchrow_arrayref->[0] || 0;
+
+ push @{$data{charged}}, $charged;
+
+ #accounts receivable
+# my $ar_sql2 = "SELECT SUM(amount) FROM cust_credit $where";
+ my $credited_sql = "SELECT SUM(cust_credit_bill.amount) FROM cust_credit_bill, cust_bill WHERE cust_bill.invnum = cust_credit_bill.invnum AND cust_bill._date >= $speriod AND cust_bill._date < $eperiod";
+ my $credited_sth = dbh->prepare($credited_sql) or die dbh->errstr;
+ $credited_sth->execute or die $credited_sth->errstr;
+ my $credited = $credited_sth->fetchrow_arrayref->[0] || 0;
+
+ #horrible local kludge
+ my $expenses_sql = "SELECT SUM(cust_bill_pkg.setup) FROM cust_bill_pkg, cust_bill, cust_pkg, part_pkg WHERE cust_bill.invnum = cust_bill_pkg.invnum AND cust_bill._date >= $speriod AND cust_bill._date < $eperiod AND cust_pkg.pkgnum = cust_bill_pkg.pkgnum AND cust_pkg.pkgpart = part_pkg.pkgpart AND LOWER(part_pkg.pkg) LIKE 'expense _%'";
+ my $expenses_sth = dbh->prepare($expenses_sql) or die dbh->errstr;
+ $expenses_sth->execute or die $expenses_sth->errstr;
+ my $expenses = $expenses_sth->fetchrow_arrayref->[0] || 0;
+
+ push @{$data{ar}}, $charged-$credited-$expenses;
+
+ #deferred revenue
+# push @{$data{defer}}, '0';
+
+ #cashflow
+ my $paid_sql = "SELECT SUM(paid) FROM cust_pay $where";
+ my $paid_sth = dbh->prepare($paid_sql) or die dbh->errstr;
+ $paid_sth->execute or die $paid_sth->errstr;
+ my $paid = $paid_sth->fetchrow_arrayref->[0] || 0;
+
+ my $refunded_sql = "SELECT SUM(refund) FROM cust_refund $where";
+ my $refunded_sth = dbh->prepare($refunded_sql) or die dbh->errstr;
+ $refunded_sth->execute or die $refunded_sth->errstr;
+ my $refunded = $refunded_sth->fetchrow_arrayref->[0] || 0;
+
+ #horrible local kludge that doesn't even really work right
+ my $expenses_sql2 = "SELECT SUM(cust_bill_pay.amount) FROM cust_bill_pay, cust_bill WHERE cust_bill_pay.invnum = cust_bill.invnum AND cust_bill_pay._date >= $speriod AND cust_bill_pay._date < $eperiod AND 0 < ( select count(*) from cust_bill_pkg, cust_pkg, part_pkg WHERE cust_bill.invnum = cust_bill_pkg.invnum AND cust_pkg.pkgnum = cust_bill_pkg.pkgnum AND cust_pkg.pkgpart = part_pkg.pkgpart AND LOWER(part_pkg.pkg) LIKE 'expense _%' )";
+
+# my $expenses_sql2 = "SELECT SUM(cust_bill_pay.amount) FROM cust_bill_pay, cust_bill_pkg, cust_bill, cust_pkg, part_pkg WHERE cust_bill_pay.invnum = cust_bill.invnum AND cust_bill.invnum = cust_bill_pkg.invnum AND cust_bill_pay._date >= $speriod AND cust_bill_pay._date < $eperiod AND cust_pkg.pkgnum = cust_bill_pkg.pkgnum AND cust_pkg.pkgpart = part_pkg.pkgpart AND LOWER(part_pkg.pkg) LIKE 'expense _%'";
+ my $expenses_sth2 = dbh->prepare($expenses_sql2) or die dbh->errstr;
+ $expenses_sth2->execute or die $expenses_sth2->errstr;
+ my $expenses2 = $expenses_sth2->fetchrow_arrayref->[0] || 0;
+
+ push @{$data{cash}}, $paid-$refunded-$expenses2;
+
+}
+
+#my $chart = Chart::LinesPoints->new(1024,480);
+my $chart = Chart::LinesPoints->new(768,480);
+
+$chart->set(
+ #'min_val' => 0,
+ 'legend' => 'bottom',
+ 'legend_labels' => [ #'Invoiced (cust_bill)',
+ 'Accounts receivable (invoices - applied credits)',
+ #'Deferred revenue',
+ 'Actual cashflow (payments - refunds)' ],
+);
+
+my @data = ( \@labels,
+ #map $data{$_}, qw( ar defer cash )
+ #map $data{$_}, qw( charged ar cash )
+ map $data{$_}, qw( ar cash )
+ );
+
+#my $gd = $chart->plot(\@data);
+#open (IMG, ">i_r_c.png");
+#print IMG $gd->png;
+#close IMG;
+
+#$chart->png("i_r_c.png", \@data);
+
+#$chart->cgi_png(\@data);
+
+http_header('Content-Type' => 'image/png' );
+#$Response->{ContentType} = 'image/png';
+
+$chart->_set_colors();
+
+%><%= $chart->scalar_png(\@data) %>
diff --git a/httemplate/graph/money_time.cgi b/httemplate/graph/money_time.cgi
new file mode 100644
index 0000000..de8f6ee
--- /dev/null
+++ b/httemplate/graph/money_time.cgi
@@ -0,0 +1,59 @@
+<!-- mason kludge -->
+<%
+
+#my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime(time);
+my ($curmon,$curyear) = (localtime(time))[4,5];
+
+#find first month
+my $syear = $cgi->param('syear') || 1899+$curyear;
+my $smonth = $cgi->param('smonth') || $curmon+1;
+
+#find last month
+my $eyear = $cgi->param('eyear') || 1900+$curyear;
+my $emonth = $cgi->param('emonth') || $curmon+1;
+
+%>
+
+<HTML>
+ <HEAD>
+ <TITLE>Graphing monetary values over time</TITLE>
+ </HEAD>
+<BODY BGCOLOR="#e8e8e8">
+<IMG SRC="money_time-graph.cgi?<%= $cgi->query_string %>" WIDTH="768" HEIGHT="480">
+<BR>
+<FORM METHOD="POST">
+<INPUT TYPE="checkbox" NAME="ar">
+ Accounts receivable (invoices - applied credits)<BR>
+<INPUT TYPE="checkbox" NAME="charged">
+ Just Invoices<BR>
+<INPUT TYPE="checkbox" NAME="defer">
+ Accounts receivable, with deferred revenue (invoices - applied credits, with charges for annual/semi-annual/quarterly/etc. services deferred over applicable time period) (there has got to be a shorter description for this)<BR>
+<INPUT TYPE="checkbox" NAME="cash">
+ Cashflow (payments - refunds)<BR>
+<BR>
+From <SELECT NAME="smonth">
+<% my @mon = qw(Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec); %>
+<% foreach my $mon ( 1..12 ) { %>
+<OPTION VALUE="<%= $mon %>"<%= $mon == $smonth ? ' SELECTED' : '' %>><%= $mon[$mon-1] %>
+<% } %>
+</SELECT>
+<SELECT NAME="syear">
+<% foreach my $y ( 1999 .. 2010 ) { %>
+<OPTION VALUE="<%= $y %>"<%= $y == $syear ? ' SELECTED' : '' %>><%= $y %>
+<% } %>
+</SELECT>
+ to <SELECT NAME="emonth">
+<% foreach my $mon ( 1..12 ) { %>
+<OPTION VALUE="<%= $mon %>"<%= $mon == $emonth ? ' SELECTED' : '' %>><%= $mon[$mon-1] %>
+<% } %>
+</SELECT>
+<SELECT NAME="eyear">
+<% foreach my $y ( 1999 .. 2010 ) { %>
+<OPTION VALUE="<%= $y %>"<%= $y == $eyear ? ' SELECTED' : '' %>><%= $y %>
+<% } %>
+</SELECT>
+
+<INPUT TYPE="submit" VALUE="Graph">
+</FORM>
+</BODY>
+</HTML>
diff --git a/httemplate/images/calendar.png b/httemplate/images/calendar.png
new file mode 100644
index 0000000..1632661
--- /dev/null
+++ b/httemplate/images/calendar.png
Binary files differ
diff --git a/httemplate/images/cvv2.png b/httemplate/images/cvv2.png
new file mode 100644
index 0000000..4610dcb
--- /dev/null
+++ b/httemplate/images/cvv2.png
Binary files differ
diff --git a/httemplate/images/cvv2_amex.png b/httemplate/images/cvv2_amex.png
new file mode 100644
index 0000000..21c36a0
--- /dev/null
+++ b/httemplate/images/cvv2_amex.png
Binary files differ
diff --git a/httemplate/index.html b/httemplate/index.html
index 29dd3b4..d39f8b0 100644
--- a/httemplate/index.html
+++ b/httemplate/index.html
@@ -11,8 +11,8 @@
</td><td>
<font color="#ff0000" size=7>freeside main menu</font>
</td><td align=right valign=bottom>
- version 1.4.0
- <BR><A HREF="http://www.sisd.com/freeside">Freeside home page</A>
+ version %%%VERSION%%%
+ <BR><A HREF="http://www.sisd.com/freeside">Freeside&nbsp;home&nbsp;page</A>
<BR><A HREF="docs/">Documentation</A>
</td></tr>
</table>
@@ -22,7 +22,7 @@
[ <A HREF="#bookkeeping">Bookkeeping / Collections</A> ]
[ <A HREF="#reports">Reports</A> ]
[ <A HREF="#sysadmin">Sysadmin</A> ]
- <TABLE CELLSPACING=2 CELLPADDING=0 BORDERCOLOR="#000000" WIDTH="100%" BGCOLOR="#eeeeee">
+ <TABLE CELLSPACING=2 CELLPADDING=0 BORDER=0" WIDTH="100%" BGCOLOR="#eeeeee">
<TR><TH BGCOLOR="#cccccc">Sales / Customer service</TH></TR>
<TR><TD>
<BR><FONT SIZE="+1"><A HREF="edit/cust_main.cgi">New Customer</A></FONT>
@@ -34,8 +34,8 @@
<FORM ACTION="search/cust_main.cgi" METHOD="POST"><INPUT TYPE="hidden" NAME="phone_on" VALUE="1">Phone # <INPUT TYPE="text" NAME="phone_text"><INPUT TYPE="submit" VALUE="Search"></FORM>
<BR><FORM ACTION="search/svc_acct.cgi" METHOD="POST">Username <INPUT TYPE="text" NAME="username"><SELECT NAME="username_type"><OPTION VALUE="All">(all)</OPTION><OPTION>Fuzzy</OPTION><OPTION>Substring</OPTION><OPTION SELECTED>Exact</OPTION></SELECT><INPUT TYPE="submit" VALUE="Search"> or <A HREF="search/svc_acct.cgi?username">all accounts by username</A> or <A HREF="search/svc_acct.cgi?uid">uid</A></FORM>
<BR><FORM ACTION="search/svc_domain.cgi" METHOD="POST">Domain <INPUT TYPE="text" NAME="domain"><INPUT TYPE="submit" VALUE="Search"> or <A HREF="search/svc_domain.cgi?domain">all domains</A></FORM>
-<!-- <LI><A HREF="search/svc_acct_sm.html">mail aliases (by domain, and optionally username)</A>-->
-<!-- <LI><A HREF="search/svc_forward.html">mail forwards (by ?)</A>-->
+ <BR><A HREF="search/svc_forward.cgi?svcnum">all mail forwards by svcnum</A><BR>
+
<BR>
</TD></TR>
</TABLE>
@@ -49,7 +49,7 @@
[<A NAME="bookkeeping" style="background-color: #cccccc"> Bookkeeping / Collections </A>]
[ <A HREF="#reports">Reports</A> ]
[ <A HREF="#sysadmin">Sysadmin</A> ]
- <TABLE CELLSPACING=2 CELLPADDING=0 BORDERCOLOR="#000000" WIDTH="100%" BGCOLOR="#eeeeee">
+ <TABLE CELLSPACING=2 CELLPADDING=0 BORDER=0 WIDTH="100%" BGCOLOR="#eeeeee">
<TR><TH BGCOLOR="#cccccc">Bookkeeping / Collections</TH></TR>
<TR><TD>
<BR><A HREF="search/cust_main-quickpay.html">Quick payment entry</A>
@@ -57,7 +57,7 @@
<BR><FORM ACTION="search/cust_main.cgi" METHOD="POST">Credit card # <INPUT TYPE="hidden" NAME="card_on" VALUE="1"><INPUT TYPE="text" NAME="card"><INPUT TYPE="submit" VALUE="Search"></FORM>
<FORM ACTION="search/cust_bill.cgi" METHOD="POST">Invoice # <INPUT TYPE="text" NAME="invnum" SIZE="8"><INPUT TYPE="submit" VALUE="Search"></FORM>
<FORM ACTION="search/cust_pay.cgi" METHOD="POST">Check # <INPUT TYPE="text" NAME="payinfo" SIZE="8"><INPUT TYPE="hidden" NAME="payby" VALUE="BILL"><INPUT TYPE="submit" VALUE="Search"></FORM>
- <BR><A HREF="browse/cust_pay_batch.cgi">View pending credit card batch</A> <BR><BR><A HREF="search/cust_pkg.html">Packages (by next bill date range)</A>
+ <BR><A HREF="browse/cust_pay_batch.cgi">View pending credit card batch</A> <BR><BR><A HREF="search/cust_pkg_report.cgi">Packages (by next bill date range)</A>
<BR><BR>Invoice reports
<UL>
<LI><a href="search/cust_bill_event.html">Invoice event errors (failed credit cards)</a>
@@ -68,9 +68,11 @@
<LI>120 day open invoices (<A HREF="search/cust_bill.cgi?OPEN120_invnum">by invoice number</A>) (<A HREF="search/cust_bill.cgi?OPEN120_date">by date</A>) (<A HREF="search/cust_bill.cgi?OPEN120_custnum">by customer number</A>)
<LI>all invoices (<A HREF="search/cust_bill.cgi?invnum">by invoice number</A>) (<A HREF="search/cust_bill.cgi?date">by date</A>) (<A HREF="search/cust_bill.cgi?custnum">by customer number</A>)
</UL>
- Financial reports
+ <A HREF="search/report_cust_pay.html">Payment report (by type and/or date range)</A>
+ <BR><BR><A HREF="search/report_receivables.cgi">Accounts Receivable Aging Summary</A>
+ <BR><BR><A HREF="search/report_prepaid_income.html">Prepaid Income (Unearned Revenue) Report</A>
+ <BR><BR>(old) Financial reports (being rewritten)
<UL>
- <LI> <A HREF="search/report_receivables.cgi">current receivables</A>
<LI> <A HREF="search/report_tax.html">tax reports</A>
<LI> <A HREF="search/report_cc.html">credit card receipts</A>
<LI> <A HREF="search/report_credit.html">credit memos</A>
@@ -108,44 +110,30 @@
[ <A HREF="#bookkeeping">Bookkeeping / Collections</A> ]
[<A NAME="reports" style="background-color: #cccccc"> Reports </A>]
[ <A HREF="#sysadmin">Sysadmin</A> ]
- <TABLE CELLSPACING=2 CELLPADDING=0 BORDERCOLOR="#000000" WIDTH="100%" BGCOLOR="#eeeeee">
+ <TABLE CELLSPACING=2 CELLPADDING=0 BORDER=0 WIDTH="100%" BGCOLOR="#eeeeee">
<TR><TH BGCOLOR="#cccccc">Reports</TH></TR>
<TR><TD>
<BR>
Auditing pre-Freeside services with no customer record
<UL>
<LI>unlinked accounts (<A HREF="search/svc_acct.cgi?UN_svcnum">by service number</A>) (<A HREF="search/svc_acct.cgi?UN_username">by username</A>) (<A HREF="search/svc_acct.cgi?UN_uid">by uid</A>)
- <LI>unlinked mail forwards (<A HREF="search/svc_forward.cgi?UN_svcnum">by service number</A>) (by ?))
+<!-- <LI>unlinked mail forwards (<A HREF="search/svc_forward.cgi?UN_svcnum">by service number</A>) (by ?)) -->
<LI>unlinked domains (<A HREF="search/svc_domain.cgi?UN_svcnum">by service number</A>) (<A HREF="search/svc_domain.cgi?UN_domain">by domain</A>)
</UL>
Packages
<UL>
- <LI><A HREF="search/cust_pkg.cgi?pkgnum">packages (by package number)</A>
+ <LI><A HREF="search/cust_pkg.cgi?pkgnum">all packages (by package number)</A>
+ <LI><A HREF="search/cust_pkg.cgi?magic=suspended">suspended packages (by package number)</A>
<LI><A HREF="search/cust_pkg.cgi?APKG_pkgnum">packages with unconfigured services (by package number)</A>
- <LI><A HREF="search/cust_pkg.html">packages (by next bill date range)</A>
- </UL>
- Invoices
- <UL>
- <LI><a href="search/cust_bill_event.html">Invoice event errors (failed credit cards)</a>
- <LI>open invoices (<A HREF="search/cust_bill.cgi?OPEN_invnum">by invoice number</A>) (<A HREF="search/cust_bill.cgi?OPEN_date">by date</A>) (<A HREF="search/cust_bill.cgi?OPEN_custnum">by customer number</A>)
- <LI>30 day open invoices (<A HREF="search/cust_bill.cgi?OPEN30_invnum">by invoice number</A>) (<A HREF="search/cust_bill.cgi?OPEN30_date">by date</A>) (<A HREF="search/cust_bill.cgi?OPEN30_custnum">by customer number</A>)
- <LI>60 day open invoices (<A HREF="search/cust_bill.cgi?OPEN60_invnum">by invoice number</A>) (<A HREF="search/cust_bill.cgi?OPEN60_date">by date</A>) (<A HREF="search/cust_bill.cgi?OPEN60_custnum">by customer number</A>)
- <LI>90 day open invoices (<A HREF="search/cust_bill.cgi?OPEN90_invnum">by invoice number</A>) (<A HREF="search/cust_bill.cgi?OPEN90_date">by date</A>) (<A HREF="search/cust_bill.cgi?OPEN90_custnum">by customer number</A>)
- <LI>120 day open invoices (<A HREF="search/cust_bill.cgi?OPEN120_invnum">by invoice number</A>) (<A HREF="search/cust_bill.cgi?OPEN120_date">by date</A>) (<A HREF="search/cust_bill.cgi?OPEN120_custnum">by customer number</A>)
- <LI>all invoices (<A HREF="search/cust_bill.cgi?invnum">by invoice number</A>) (<A HREF="search/cust_bill.cgi?date">by date</A>) (<A HREF="search/cust_bill.cgi?custnum">by customer number</A>)
+ <LI><A HREF="search/cust_pkg_report.cgi">packages (by next bill date range)</A>
</UL>
- Financial reports
- <UL>
- <LI> <A HREF="search/report_receivables.cgi">current receivables</A>
- <LI> <A HREF="search/report_tax.html">tax reports</A>
- <LI> <A HREF="search/report_cc.html">credit card receipts</A>
- <LI> <A HREF="search/report_credit.html">credit memos</A>
- </UL>
+ <A HREF="browse/part_pkg.cgi?active=1">Package definitions (by number of active packages)</A><BR><BR>
+ <A HREF="browse/part_svc.cgi?active=1">Service definitions (by number of active services)</A><BR><BR>
Customers
<UL>
<LI><A HREF="search/cust_main-otaker.cgi">Search customers by order-taker</A>
</UL>
- <FORM ACTION="search/sql.cgi" METHOD="POST">SQL query: <TT>SELECT </TT><INPUT TYPE="text" NAME="sql" SIZE=32><INPUT TYPE="submit" VALUE="Query"></FORM>
+ <FORM ACTION="search/sql.html" METHOD="POST">SQL query: <TT>SELECT </TT><INPUT TYPE="text" NAME="sql" SIZE=32><INPUT TYPE="submit" VALUE="Query"></FORM>
<BR>
</TD></TR>
@@ -160,12 +148,15 @@
[ <A HREF="#bookkeeping">Bookkeeping / Collections</A> ]
[ <A HREF="#reports">Reports</A> ]
[<A NAME="sysadmin" style="background-color: #cccccc"> Sysadmin </A>]
- <TABLE CELLSPACING=2 CELLPADDING=0 BORDERCOLOR="#000000" WIDTH="100%" BGCOLOR="#eeeeee">
+ <TABLE CELLSPACING=2 CELLPADDING=0 BORDER=0 WIDTH="100%" BGCOLOR="#eeeeee">
<TR><TH BGCOLOR="#cccccc">Sysadmin</TH></TR>
<TR><TD>
<BR>
<A HREF="browse/nas.cgi">View active NAS ports</A>
<BR><A HREF="browse/queue.cgi">View pending job queue</A>
+ <BR><A HREF="misc/cust_main-import.cgi">Batch import customers from CSV file</A>
+ <BR><A HREF="misc/cust_main-import_charges.cgi">Batch import charges from CSV file</A>
+ <BR><A HREF="misc/dump.cgi">Download database dump</A>
<BR><BR><CENTER><HR WIDTH="94%" NOSHADE></CENTER><BR>
<A NAME="config" HREF="config/config-view.cgi">Configuration</a><!-- - <font size="+2" color="#ff0000">start here</font> -->
<BR><BR><A NAME="admin">Administration</a>
@@ -190,10 +181,16 @@
<LI><A HREF="browse/cust_main_county.cgi">View/Edit locales and tax rates</A>
- Change tax rates, or break down a country into states, or a state
into counties and assign different tax rates to each.
- <LI><A HREF="browse/svc_acct_pop.cgi">View/Edit Access Numbers</A>
+ <LI><A HREF="browse/svc_acct_pop.cgi">View/Edit access numbers</A>
- Points of Presence
<LI><A HREF="browse/part_bill_event.cgi">View/Edit invoice events</A> - Actions for overdue invoices
- <LI><A HREF="browse/msgcat.cgi">View/Edit message catalog</A> - Change error messages and other customizable labels.
+ <LI><A HREF="browse/msgcat.cgi">View/Edit message catalog</A> - Change error messages and other customizable labels.
+ <LI><A HREF="browse/part_virtual_field.cgi">View/Edit virtual fields</A>
+ - Locally defined fields
+ <LI><A HREF="browse/router.cgi">View/Edit routers</A>
+ - Broadband access routers
+ <LI><A HREF="browse/addr_block.cgi">View/Edit address blocks</A>
+ - Manage address blocks and block assignments to broadband routers.
</ul>
<BR>
</TD></TR>
diff --git a/httemplate/misc/bill.cgi b/httemplate/misc/bill.cgi
index f048e55..44d85b8 100755
--- a/httemplate/misc/bill.cgi
+++ b/httemplate/misc/bill.cgi
@@ -21,7 +21,8 @@ unless ( $error ) {
#'batch_card'=> 'yes',
#'batch_card'=> 'no',
#'report_badcard'=> 'yes',
- 'retry_card' => 'yes',
+ #'retry_card' => 'yes',
+ 'retry' => 'yes',
);
}
#&eidiot($error) if $error;
diff --git a/httemplate/misc/catchall.cgi b/httemplate/misc/catchall.cgi
index 9aa84be..3402b61 100755
--- a/httemplate/misc/catchall.cgi
+++ b/httemplate/misc/catchall.cgi
@@ -77,7 +77,7 @@ if ($pkgnum) {
}
# add an absence of a catchall
-$email{0} = "(none)";
+$email{''} = "(none)";
my $p1 = popurl(1);
print header("Domain Catchall Edit", '');
diff --git a/httemplate/misc/change_pkg.cgi b/httemplate/misc/change_pkg.cgi
new file mode 100755
index 0000000..5346fd9
--- /dev/null
+++ b/httemplate/misc/change_pkg.cgi
@@ -0,0 +1,66 @@
+<!-- mason kludge -->
+<%
+
+my $pkgnum;
+if ( $cgi->param('error') ) {
+ #$custnum = $cgi->param('custnum');
+ #%remove_pkg = map { $_ => 1 } $cgi->param('remove_pkg');
+ $pkgnum = ($cgi->param('remove_pkg'))[0];
+} else {
+ my($query) = $cgi->keywords;
+ $query =~ /^(\d+)$/;
+ #$custnum = $1;
+ $pkgnum = $1;
+ #%remove_pkg = ();
+}
+
+my $cust_pkg = qsearchs( 'cust_pkg', { 'pkgnum' => $pkgnum } )
+ or die "unknown pkgnum $pkgnum";
+my $custnum = $cust_pkg->custnum;
+
+my $conf = new FS::Conf;
+
+my $p1 = popurl(1);
+
+my $cust_main = $cust_pkg->cust_main
+ or die "can't get cust_main record for custnum ". $cust_pkg->custnum.
+ " ( pkgnum ". cust_pkg->pkgnum. ")";
+my $agent = $cust_main->agent;
+
+print header("Change Package", menubar(
+ "View this customer (#$custnum)" => "${p}view/cust_main.cgi?$custnum",
+ 'Main Menu' => $p,
+));
+
+print qq!<FONT SIZE="+1" COLOR="#ff0000">Error: !, $cgi->param('error'),
+ "</FONT><BR><BR>"
+ if $cgi->param('error');
+
+my $part_pkg = $cust_pkg->part_pkg;
+
+print small_custview( $cust_main, $conf->config('countrydefault') ).
+ qq!<FORM ACTION="${p}edit/process/cust_pkg.cgi" METHOD=POST>!.
+ qq!<INPUT TYPE="hidden" NAME="custnum" VALUE="$custnum">!.
+ qq!<INPUT TYPE="hidden" NAME="remove_pkg" VALUE="$pkgnum">!.
+ '<BR>Current package: '. $part_pkg->pkg. ' - '. $part_pkg->comment.
+ qq!<BR>New package: <SELECT NAME="new_pkgpart"><OPTION VALUE=0></OPTION>!;
+
+foreach my $part_pkg (
+ grep { ! $_->disabled && $_->pkgpart != $cust_pkg->pkgpart }
+ map { $_->part_pkg } $agent->agent_type->type_pkgs
+) {
+ my $pkgpart = $part_pkg->pkgpart;
+ print qq!<OPTION VALUE="$pkgpart"!;
+ print ' SELECTED' if $cgi->param('error')
+ && $cgi->param('new_pkgpart') == $pkgpart;
+ print qq!>$pkgpart: !. $part_pkg->pkg. ' - '. $part_pkg->comment. '</OPTION>';
+}
+
+print <<END;
+</SELECT>
+<BR><BR><INPUT TYPE="submit" VALUE="Change package">
+ </FORM>
+ </BODY>
+</HTML>
+END
+%>
diff --git a/httemplate/misc/cust_main-cancel.cgi b/httemplate/misc/cust_main-cancel.cgi
new file mode 100755
index 0000000..257c338
--- /dev/null
+++ b/httemplate/misc/cust_main-cancel.cgi
@@ -0,0 +1,16 @@
+<%
+
+#untaint custnum
+my($query) = $cgi->keywords;
+$query =~ /^(\d+)$/ || die "Illegal custnum";
+my $custnum = $1;
+
+my $cust_main = qsearchs('cust_main', { 'custnum' => $custnum } );
+
+my @errors = $cust_main->cancel;
+eidiot(join(' / ', @errors)) if scalar(@errors);
+
+#print $cgi->redirect($p. "view/cust_main.cgi?". $cust_main->custnum);
+print $cgi->redirect($p);
+
+%>
diff --git a/httemplate/misc/cust_main-import.cgi b/httemplate/misc/cust_main-import.cgi
new file mode 100644
index 0000000..6b36f47
--- /dev/null
+++ b/httemplate/misc/cust_main-import.cgi
@@ -0,0 +1,51 @@
+<!-- mason kludge -->
+<%= header('Batch Customer Import') %>
+<FORM ACTION="process/cust_main-import.cgi" METHOD="post" ENCTYPE="multipart/form-data">
+Import a CSV file containing customer records.<BR><BR>
+Default file format is CSV, with the following field order: <i>cust_pkg.setup, dayphone, first, last, address1, address2, city, state, zip, comments</i><BR><BR>
+
+<%
+ #false laziness with edit/cust_main.cgi
+ my @agents = qsearch( 'agent', {} );
+ die "No agents created!" unless @agents;
+ my $agentnum = $agents[0]->agentnum; #default to first
+
+ if ( scalar(@agents) == 1 ) {
+%>
+ <INPUT TYPE="hidden" NAME="agentnum" VALUE="<%= $agentnum %>">
+<% } else { %>
+ <BR><BR>Agent <SELECT NAME="agentnum" SIZE="1">
+ <% foreach my $agent (sort { $a->agent cmp $b->agent } @agents) { %>
+ <OPTION VALUE="<%= $agent->agentnum %>" <%= " SELECTED"x($agent->agentnum==$agentnum) %>><%= $agent->agent %></OPTION>
+ <% } %>
+ </SELECT><BR><BR>
+<% } %>
+
+<%
+ my @referrals = qsearch('part_referral',{});
+ die "No advertising sources created!" unless @referrals;
+ my $refnum = $referrals[0]->refnum; #default to first
+
+ if ( scalar(@referrals) == 1 ) {
+%>
+ <INPUT TYPE="hidden" NAME="refnum" VALUE="<%= $refnum %>">
+<% } else { %>
+ <BR><BR>Advertising source <SELECT NAME="refnum" SIZE="1">
+ <% foreach my $referral ( sort { $a->referral <=> $b->referral } @referrals) { %>
+ <OPTION VALUE="<%= $referral->refnum %>" <%= " SELECTED"x($referral->refnum==$refnum) %>><%= $referral->refnum %>: <%= $referral->referral %></OPTION>
+ <% } %>
+ </SELECT><BR><BR>
+<% } %>
+
+ First package: <SELECT NAME="pkgpart"><OPTION VALUE="">(none)</OPTION>
+<% foreach my $part_pkg ( qsearch('part_pkg',{'disabled'=>'' }) ) { %>
+ <OPTION VALUE="<%= $part_pkg->pkgpart %>"><%= $part_pkg->pkg. ' - '. $part_pkg->comment %></OPTION>
+<% } %>
+</SELECT><BR><BR>
+
+ CSV Filename: <INPUT TYPE="file" NAME="csvfile"><BR><BR>
+ <INPUT TYPE="submit" VALUE="Import">
+ </FORM>
+ </BODY>
+<HTML>
+
diff --git a/httemplate/misc/cust_main-import_charges.cgi b/httemplate/misc/cust_main-import_charges.cgi
new file mode 100644
index 0000000..0822b9e
--- /dev/null
+++ b/httemplate/misc/cust_main-import_charges.cgi
@@ -0,0 +1,14 @@
+<!-- mason kludge -->
+<%= header('Batch Customer Charge') %>
+<FORM ACTION="process/cust_main-import_charges.cgi" METHOD="post" ENCTYPE="multipart/form-data">
+Import a CSV file containing customer charges.<BR><BR>
+Default file format is CSV, with the following field order: <i>custnum, amount, description</i><BR><BR>
+If <i>amount</i> is negative, a credit will be applied instead.<BR><BR>
+<BR><BR>
+
+ CSV Filename: <INPUT TYPE="file" NAME="csvfile"><BR><BR>
+ <INPUT TYPE="submit" VALUE="Import">
+ </FORM>
+ </BODY>
+<HTML>
+
diff --git a/httemplate/misc/delete-cust_credit.cgi b/httemplate/misc/delete-cust_credit.cgi
new file mode 100755
index 0000000..30de04d
--- /dev/null
+++ b/httemplate/misc/delete-cust_credit.cgi
@@ -0,0 +1,16 @@
+<%
+
+#untaint crednum
+my($query) = $cgi->keywords;
+$query =~ /^(\d+)$/ || die "Illegal crednum";
+my $crednum = $1;
+
+my $cust_credit = qsearchs('cust_credit',{'crednum'=>$crednum});
+my $custnum = $cust_credit->custnum;
+
+my $error = $cust_credit->delete;
+eidiot($error) if $error;
+
+print $cgi->redirect($p. "view/cust_main.cgi?". $custnum);
+
+%>
diff --git a/httemplate/misc/download-batch.cgi b/httemplate/misc/download-batch.cgi
new file mode 100644
index 0000000..306ef5d
--- /dev/null
+++ b/httemplate/misc/download-batch.cgi
@@ -0,0 +1,16 @@
+<%
+
+#http_header('Content-Type' => 'text/comma-separated-values' ); #IE chokes
+http_header('Content-Type' => 'text/plain' );
+
+for my $cust_pay_batch ( sort { $a->paybatchnum <=> $b->paybatchnum }
+ qsearch('cust_pay_batch', {} )
+) {
+
+$cust_pay_batch->exp =~ /^\d{2}(\d{2})[\/\-](\d+)[\/\-]\d+$/;
+my( $mon, $y ) = ( $2, $1 );
+$mon = "0$mon" if $mon < 10;
+my $exp = "$mon$y";
+
+%>,,,,<%= $cust_pay_batch->cardnum %>,<%= $exp %>,<%= $cust_pay_batch->amount %>,<%= $cust_pay_batch->paybatchnum %>
+<% } %>
diff --git a/httemplate/misc/dump.cgi b/httemplate/misc/dump.cgi
new file mode 100644
index 0000000..dc1323b
--- /dev/null
+++ b/httemplate/misc/dump.cgi
@@ -0,0 +1,19 @@
+<%
+ if ( driver_name =~ /^Pg$/ ) {
+ my $dbname = (split(':', datasrc))[2];
+ if ( $dbname =~ /[;=]/ ) {
+ my %elements = map { /^(\w+)=(.*)$/; $1=>$2 } split(';', $dbname);
+ $dbname = $elements{'dbname'};
+ }
+ open(DUMP,"pg_dump $dbname |");
+ } else {
+ eidiot "don't (yet) know how to dump ". driver_name. " databases\n";
+ }
+
+ http_header('Content-Type' => 'text/plain' );
+
+ while (<DUMP>) {
+ print $_;
+ }
+ close DUMP;
+%>
diff --git a/httemplate/misc/email-invoice.cgi b/httemplate/misc/email-invoice.cgi
new file mode 100755
index 0000000..7ab1613
--- /dev/null
+++ b/httemplate/misc/email-invoice.cgi
@@ -0,0 +1,23 @@
+<%
+
+my $conf = new FS::Conf;
+
+#untaint invnum
+my($query) = $cgi->keywords;
+$query =~ /^(\d*)$/;
+my $invnum = $1;
+my $cust_bill = qsearchs('cust_bill',{'invnum'=>$invnum});
+die "Can't find invoice!\n" unless $cust_bill;
+
+my $error = send_email(
+ 'from' => $conf->config('invoice_from'),
+ 'to' => [ grep { $_ ne 'POST' } $cust_bill->cust_main->invoicing_list ],
+ 'subject' => 'Invoice',
+ 'body' => [ $cust_bill->print_text ],
+);
+eidiot($error) if $error;
+
+my $custnum = $cust_bill->getfield('custnum');
+print $cgi->redirect("${p}view/cust_main.cgi?$custnum");
+
+%>
diff --git a/httemplate/misc/link.cgi b/httemplate/misc/link.cgi
index efc762c..79adce8 100755
--- a/httemplate/misc/link.cgi
+++ b/httemplate/misc/link.cgi
@@ -4,7 +4,6 @@
my %link_field = (
'svc_acct' => 'username',
'svc_domain' => 'domain',
- 'svc_acct_sm' => '',
'svc_charge' => '',
'svc_wo' => '',
);
diff --git a/httemplate/misc/meta-import.cgi b/httemplate/misc/meta-import.cgi
new file mode 100644
index 0000000..2f3b738
--- /dev/null
+++ b/httemplate/misc/meta-import.cgi
@@ -0,0 +1,64 @@
+<!-- mason kludge -->
+<%= header('Import') %>
+<FORM ACTION="process/meta-import.cgi" METHOD="post" ENCTYPE="multipart/form-data">
+Import data from a DBI data source<BR><BR>
+
+<%
+ #false laziness with edit/cust_main.cgi
+ my @agents = qsearch( 'agent', {} );
+ die "No agents created!" unless @agents;
+ my $agentnum = $agents[0]->agentnum; #default to first
+
+ if ( scalar(@agents) == 1 ) {
+%>
+ <INPUT TYPE="hidden" NAME="agentnum" VALUE="<%= $agentnum %>">
+<% } else { %>
+ <BR><BR>Agent <SELECT NAME="agentnum" SIZE="1">
+ <% foreach my $agent (sort { $a->agent cmp $b->agent } @agents) { %>
+ <OPTION VALUE="<%= $agent->agentnum %>" <%= " SELECTED"x($agent->agentnum==$agentnum) %>><%= $agent->agent %></OPTION>
+ <% } %>
+ </SELECT><BR><BR>
+<% } %>
+
+<%
+ my @referrals = qsearch('part_referral',{});
+ die "No advertising sources created!" unless @referrals;
+ my $refnum = $referrals[0]->refnum; #default to first
+
+ if ( scalar(@referrals) == 1 ) {
+%>
+ <INPUT TYPE="hidden" NAME="refnum" VALUE="<%= $refnum %>">
+<% } else { %>
+ <BR><BR>Advertising source <SELECT NAME="refnum" SIZE="1">
+ <% foreach my $referral ( sort { $a->referral <=> $b->referral } @referrals) { %>
+ <OPTION VALUE="<%= $referral->refnum %>" <%= " SELECTED"x($referral->refnum==$refnum) %>><%= $referral->refnum %>: <%= $referral->referral %></OPTION>
+ <% } %>
+ </SELECT><BR><BR>
+<% } %>
+
+ First package: <SELECT NAME="pkgpart"><OPTION VALUE="">(none)</OPTION>
+<% foreach my $part_pkg ( qsearch('part_pkg',{'disabled'=>'' }) ) { %>
+ <OPTION VALUE="<%= $part_pkg->pkgpart %>"><%= $part_pkg->pkg. ' - '. $part_pkg->comment %></OPTION>
+<% } %>
+</SELECT><BR><BR>
+
+ <table>
+ <tr>
+ <td align="right">DBI data source: </td>
+ <td><INPUT TYPE="text" NAME="data_source"></td>
+ </tr>
+ <tr>
+ <td align="right">DBI username: </td>
+ <td><INPUT TYPE="text" NAME="username"></td>
+ </tr>
+ <tr>
+ <td align="right">DBI password: </td>
+ <td><INPUT TYPE="text" NAME="password"></td>
+ </tr>
+ </table>
+ <INPUT TYPE="submit" VALUE="Import">
+
+ </FORM>
+ </BODY>
+<HTML>
+
diff --git a/httemplate/misc/print-invoice.cgi b/httemplate/misc/print-invoice.cgi
index a5500bf..144f615 100755
--- a/httemplate/misc/print-invoice.cgi
+++ b/httemplate/misc/print-invoice.cgi
@@ -11,13 +11,19 @@ my $cust_bill = qsearchs('cust_bill',{'invnum'=>$invnum});
die "Can't find invoice!\n" unless $cust_bill;
open(LPR,"|$lpr") or die "Can't open $lpr: $!";
- print LPR $cust_bill->print_text; #( date )
+
+ if ( $conf->exists('invoice_latex') ) {
+ print LPR $cust_bill->print_ps; #( date )
+ } else {
+ print LPR $cust_bill->print_text; #( date )
+ }
+
close LPR
or die $! ? "Error closing $lpr: $!"
: "Exit status $? from $lpr";
my $custnum = $cust_bill->getfield('custnum');
-print $cgi->redirect(popurl(2). "view/cust_main.cgi?$custnum#history");
+print $cgi->redirect("${p}view/cust_main.cgi?$custnum");
%>
diff --git a/httemplate/misc/process/cust_main-import.cgi b/httemplate/misc/process/cust_main-import.cgi
new file mode 100644
index 0000000..9e1adce
--- /dev/null
+++ b/httemplate/misc/process/cust_main-import.cgi
@@ -0,0 +1,30 @@
+<%
+
+ my $fh = $cgi->upload('csvfile');
+ #warn $cgi;
+ #warn $fh;
+
+ my $error = defined($fh)
+ ? FS::cust_main::batch_import( {
+ filehandle => $fh,
+ agentnum => scalar($cgi->param('agentnum')),
+ refnum => scalar($cgi->param('refnum')),
+ pkgpart => scalar($cgi->param('pkgpart')),
+ 'fields' => [qw( cust_pkg.setup dayphone first last address1 address2
+ city state zip comments )],
+ } )
+ : 'No file';
+
+ if ( $error ) {
+ %>
+ <!-- mason kludge -->
+ <%
+ eidiot($error);
+# $cgi->param('error', $error);
+# print $cgi->redirect( "${p}cust_main-import.cgi
+ } else {
+ %>
+ <!-- mason kludge -->
+ <%= header('Import sucessful') %> <%
+ }
+%>
diff --git a/httemplate/misc/process/cust_main-import_charges.cgi b/httemplate/misc/process/cust_main-import_charges.cgi
new file mode 100644
index 0000000..14df1bd
--- /dev/null
+++ b/httemplate/misc/process/cust_main-import_charges.cgi
@@ -0,0 +1,26 @@
+<%
+
+ my $fh = $cgi->upload('csvfile');
+ #warn $cgi;
+ #warn $fh;
+
+ my $error = defined($fh)
+ ? FS::cust_main::batch_charge( {
+ filehandle => $fh,
+ 'fields' => [qw( custnum amount pkg )],
+ } )
+ : 'No file';
+
+ if ( $error ) {
+ %>
+ <!-- mason kludge -->
+ <%
+ eidiot($error);
+# $cgi->param('error', $error);
+# print $cgi->redirect( "${p}cust_main-import_charges.cgi
+ } else {
+ %>
+ <!-- mason kludge -->
+ <%= header('Import sucessful') %> <%
+ }
+%>
diff --git a/httemplate/misc/process/link.cgi b/httemplate/misc/process/link.cgi
index 4b220a8..5d80ade 100755
--- a/httemplate/misc/process/link.cgi
+++ b/httemplate/misc/process/link.cgi
@@ -8,12 +8,15 @@ $cgi->param('svcnum') =~ /^(\d*)$/;
my $svcnum = $1;
unless ( $svcnum ) {
- my($part_svc) = qsearchs('part_svc',{'svcpart'=>$svcpart});
- my($svcdb) = $part_svc->getfield('svcdb');
- $cgi->param('link_field') =~ /^(\w+)$/; my($link_field)=$1;
- my($svc_x)=qsearchs($svcdb,{$link_field => $cgi->param('link_value') });
+ my $part_svc = qsearchs('part_svc',{'svcpart'=>$svcpart});
+ my $svcdb = $part_svc->getfield('svcdb');
+ $cgi->param('link_field') =~ /^(\w+)$/;
+ my $link_field = $1;
+ my $svc_x = ( grep { $_->cust_svc->svcpart == $svcpart }
+ qsearch( $svcdb, { $link_field => $cgi->param('link_value') })
+ )[0];
eidiot("$link_field not found!") unless $svc_x;
- $svcnum=$svc_x->svcnum;
+ $svcnum = $svc_x->svcnum;
}
my $old = qsearchs('cust_svc',{'svcnum'=>$svcnum});
diff --git a/httemplate/misc/process/meta-import.cgi b/httemplate/misc/process/meta-import.cgi
new file mode 100644
index 0000000..59d236f
--- /dev/null
+++ b/httemplate/misc/process/meta-import.cgi
@@ -0,0 +1,178 @@
+<!-- mason kludge -->
+<%= header('Map tables') %>
+
+<SCRIPT>
+var gSafeOnload = new Array();
+var gSafeOnsubmit = new Array();
+window.onload = SafeOnload;
+function SafeAddOnLoad(f) {
+ gSafeOnload[gSafeOnload.length] = f;
+}
+function SafeOnload() {
+ for (var i=0;i<gSafeOnload.length;i++)
+ gSafeOnload[i]();
+}
+function SafeAddOnSubmit(f) {
+ gSafeOnsubmit[gSafeOnsubmit.length] = f;
+}
+function SafeOnsubmit() {
+ for (var i=0;i<gSafeOnsubmit.length;i++)
+ gSafeOnsubmit[i]();
+}
+</SCRIPT>
+
+<FORM NAME="OneTrueForm" METHOD="POST" ACTION="meta-import.cgi">
+
+<%
+ #use DBIx::DBSchema;
+ my $schema = new_native DBIx::DBSchema
+ map { $cgi->param($_) } qw( data_source username password );
+ foreach my $field (qw( data_source username password )) { %>
+ <INPUT TYPE="hidden" NAME=<%= $field %> VALUE="<%= $cgi->param($field) %>">
+ <% }
+
+ my %schema;
+ use Tie::DxHash;
+ tie %schema, 'Tie::DxHash';
+ if ( $cgi->param('schema') ) {
+ my $schema_string = $cgi->param('schema');
+ %> <INPUT TYPE="hidden" NAME="schema" VALUE="<%=$schema_string%>"> <%
+ %schema = map { /^\s*(\w+)\s*=>\s*(\w+)\s*$/
+ or die "guru meditation #420: $_";
+ ( $1 => $2 );
+ }
+ split( /\n/, $schema_string );
+ }
+
+ #first page
+ unless ( $cgi->param('magic') ) { %>
+
+ <INPUT TYPE="hidden" NAME="magic" VALUE="process">
+ <%= hashmaker('schema', [ $schema->tables ],
+ [ grep !/^h_/, dbdef->tables ], ) %>
+ <br><INPUT TYPE="submit" VALUE="done">
+ <%
+
+ #second page
+ } elsif ( $cgi->param('magic') eq 'process' ) { %>
+
+ <INPUT TYPE="hidden" NAME="magic" VALUE="process2">
+ <%
+
+ my %unique;
+ foreach my $table ( keys %schema ) {
+
+ my @from_columns = $schema->table($table)->columns;
+ my @fs_columns = dbdef->table($schema{$table})->columns;
+
+ %>
+ <%= hashmaker( $table.'__'.$unique{$table}++,
+ \@from_columns => \@fs_columns,
+ $table => $schema{$table}, ) %>
+ <br><hr><br>
+ <%
+
+ }
+
+ %>
+ <br><INPUT TYPE="submit" VALUE="done">
+ <%
+
+ #third (results)
+ } elsif ( $cgi->param('magic') eq 'process2' ) {
+
+ print "<pre>\n";
+
+ my %unique;
+ foreach my $table ( keys %schema ) {
+ ( my $spaces = $table ) =~ s/./ /g;
+ print "'$table' => { 'table' => '$schema{$table}',\n".
+ #(length($table) x ' '). " 'map' => {\n";
+ "$spaces 'map' => {\n";
+ my %map = map { /^\s*(\w+)\s*=>\s*(\w+)\s*$/
+ or die "guru meditation #420: $_";
+ ( $1 => $2 );
+ }
+ split( /\n/, $cgi->param($table.'__'.$unique{$table}++) );
+ foreach ( keys %map ) {
+ print "$spaces '$_' => '$map{$_}',\n";
+ }
+ print "$spaces },\n";
+ print "$spaces },\n";
+
+ }
+ print "\n</pre>";
+
+ } else {
+ warn "unrecognized magic: ". $cgi->param('magic');
+ }
+
+ %>
+</FORM>
+</BODY>
+</HTML>
+
+ <%
+ #hashmaker widget
+ sub hashmaker {
+ my($name, $from, $to, $labelfrom, $labelto) = @_;
+ my $fromsize = scalar(@$from);
+ my $tosize = scalar(@$to);
+ "<TABLE><TR><TH>$labelfrom</TH><TH>$labelto</TH></TR><TR><TD>".
+ qq!<SELECT NAME="${name}_from" SIZE=$fromsize>\n!.
+ join("\n", map { qq!<OPTION VALUE="$_">$_</OPTION>! } sort { $a cmp $b } @$from ).
+ "</SELECT>\n<BR>".
+ qq!<INPUT TYPE="button" VALUE="refill" onClick="repack_${name}_from()">!.
+ '</TD><TD>'.
+ qq!<SELECT NAME="${name}_to" SIZE=$tosize>\n!.
+ join("\n", map { qq!<OPTION VALUE="$_">$_</OPTION>! } sort { $a cmp $b } @$to ).
+ "</SELECT>\n<BR>".
+ qq!<INPUT TYPE="button" VALUE="refill" onClick="repack_${name}_to()">!.
+ '</TD></TR>'.
+ '<TR><TD COLSPAN=2>'.
+ qq!<INPUT TYPE="button" VALUE="map" onClick="toke_$name(this.form)">!.
+ '</TD></TR><TR><TD COLSPAN=2>'.
+ qq!<TEXTAREA NAME="$name" COLS=80 ROWS=8></TEXTAREA>!.
+ '</TD></TR></TABLE>'.
+ "<script>
+ function toke_$name() {
+ fromObject = document.OneTrueForm.${name}_from;
+ for (var i=fromObject.options.length-1;i>-1;i--) {
+ if (fromObject.options[i].selected)
+ fromname = deleteOption_$name(fromObject,i);
+ }
+ toObject = document.OneTrueForm.${name}_to;
+ for (var i=toObject.options.length-1;i>-1;i--) {
+ if (toObject.options[i].selected)
+ toname = deleteOption_$name(toObject,i);
+ }
+ document.OneTrueForm.$name.value = document.OneTrueForm.$name.value + fromname + ' => ' + toname + '\\n';
+ }
+ function deleteOption_$name(object,index) {
+ value = object.options[index].value;
+ object.options[index] = null;
+ return value;
+ }
+ function repack_${name}_from() {
+ var object = document.OneTrueForm.${name}_from;
+ object.options.length = 0;
+ ". join("\n",
+ map { "addOption_$name(object, '$_');\n" }
+ ( sort { $a cmp $b } @$from ) ). "
+ }
+ function repack_${name}_to() {
+ var object = document.OneTrueForm.${name}_to;
+ object.options.length = 0;
+ ". join("\n",
+ map { "addOption_$name(object, '$_');\n" }
+ ( sort { $a cmp $b } @$to ) ). "
+ }
+ function addOption_$name(object,value) {
+ var length = object.length;
+ object.options[length] = new Option(value, value, false, false);
+ }
+ </script>".
+ '';
+ }
+
+%>
diff --git a/httemplate/misc/unapply-cust_pay.cgi b/httemplate/misc/unapply-cust_pay.cgi
new file mode 100755
index 0000000..28643ef
--- /dev/null
+++ b/httemplate/misc/unapply-cust_pay.cgi
@@ -0,0 +1,18 @@
+<%
+
+#untaint paynum
+my($query) = $cgi->keywords;
+$query =~ /^(\d+)$/ || die "Illegal paynum";
+my $paynum = $1;
+
+my $cust_pay = qsearchs('cust_pay', { 'paynum' => $paynum } );
+my $custnum = $cust_pay->custnum;
+
+foreach my $cust_bill_pay ( $cust_pay->cust_bill_pay ) {
+ my $error = $cust_bill_pay->delete;
+ eidiot($error) if $error;
+}
+
+print $cgi->redirect($p. "view/cust_main.cgi?". $custnum);
+
+%>
diff --git a/httemplate/misc/unprovision.cgi b/httemplate/misc/unprovision.cgi
new file mode 100755
index 0000000..8f2a7d1
--- /dev/null
+++ b/httemplate/misc/unprovision.cgi
@@ -0,0 +1,33 @@
+<%
+
+my $dbh = dbh;
+
+#untaint svcnum
+my($query) = $cgi->keywords;
+$query =~ /^(\d+)$/;
+my $svcnum = $1;
+
+#my $svc_acct = qsearchs('svc_acct',{'svcnum'=>$svcnum});
+#die "Unknown svcnum!" unless $svc_acct;
+
+my $cust_svc = qsearchs('cust_svc',{'svcnum'=>$svcnum});
+die "Unknown svcnum!" unless $cust_svc;
+#&eidiot(qq!This account has already been audited. Cancel the
+# <A HREF="!. popurl(2). qq!view/cust_pkg.cgi?! . $cust_svc->getfield('pkgnum') .
+# qq!pkgnum"> package</A> instead.!)
+# if $cust_svc->pkgnum ne '' && $cust_svc->pkgnum ne '0';
+
+my $custnum = $cust_svc->cust_pkg->custnum;
+
+my $error = $cust_svc->cancel;
+
+if ( $error ) {
+ %>
+<!-- mason kludge -->
+<%
+ &eidiot($error);
+} else {
+ print $cgi->redirect(popurl(2)."view/cust_main.cgi?$custnum");
+}
+
+%>
diff --git a/httemplate/misc/upload-batch.cgi b/httemplate/misc/upload-batch.cgi
new file mode 100644
index 0000000..cc53466
--- /dev/null
+++ b/httemplate/misc/upload-batch.cgi
@@ -0,0 +1,29 @@
+<%
+
+ my $fh = $cgi->upload('batch_results');
+ my $filename = $cgi->param('batch_results');
+ $filename =~ /^.*[\/\\]([^\/\\]+)$/ or die;
+ my $paybatch = $1;
+
+ my $error = defined($fh)
+ ? FS::cust_pay_batch::import_results( {
+ 'filehandle' => $fh,
+ 'format' => $cgi->param('format'),
+ 'paybatch' => $paybatch,
+ } )
+ : 'No file';
+
+ if ( $error ) {
+ %>
+ <!-- mason kludge -->
+ <%
+ eidiot($error);
+# $cgi->param('error', $error);
+# print $cgi->redirect( "${p}cust_main-import.cgi
+ } else {
+ %>
+ <!-- mason kludge -->
+ <%= header('Batch results upload sucessful') %> <%
+ }
+%>
+
diff --git a/httemplate/search/cust_bill.cgi b/httemplate/search/cust_bill.cgi
index 586399a..985e3db 100755
--- a/httemplate/search/cust_bill.cgi
+++ b/httemplate/search/cust_bill.cgi
@@ -11,7 +11,7 @@ $limit .= "LIMIT $maxrecords" if $maxrecords;
my $offset = $cgi->param('offset') || 0;
$limit .= " OFFSET $offset" if $offset;
-my $total;
+my($total, $tot_amount, $tot_balance);
my(@cust_bill);
if ( $cgi->keywords ) {
diff --git a/httemplate/search/cust_bill_event.cgi b/httemplate/search/cust_bill_event.cgi
index 9cb36d2..ec952ea 100644
--- a/httemplate/search/cust_bill_event.cgi
+++ b/httemplate/search/cust_bill_event.cgi
@@ -4,10 +4,10 @@
#false laziness with view/cust_bill.cgi
$cgi->param('beginning') =~ /^([ 0-9\-\/]{0,10})$/;
-my $beginning = str2time($1);
+my $beginning = str2time($1) || 0;
$cgi->param('ending') =~ /^([ 0-9\-\/]{0,10})$/;
-my $ending = str2time($1) + 86400;
+my $ending = str2time($1) + 86399;
my @cust_bill_event =
sort { $a->_date <=> $b->_date }
diff --git a/httemplate/search/cust_main-quickpay.html b/httemplate/search/cust_main-quickpay.html
index 9f39db9..d48f1d0 100755
--- a/httemplate/search/cust_main-quickpay.html
+++ b/httemplate/search/cust_main-quickpay.html
@@ -7,27 +7,28 @@
Quick payment entry
</FONT>
<BR><BR>
+ <A HREF="../">Main Menu</A><BR><BR>
<FORM ACTION="cust_main.cgi" METHOD="post">
<INPUT TYPE="hidden" NAME="quickpay" VALUE="yes">
<INPUT TYPE="checkbox" NAME="last_on" CHECKED> Search for <B>last name</B>:
<INPUT TYPE="text" NAME="last_text">
using search method: <SELECT NAME="last_type">
- <OPTION SELECTED>All
+ <OPTION>All
<OPTION>Fuzzy
<OPTION>Substring
- <OPTION>Exact
+ <OPTION SELECTED>Exact
</SELECT>
<P><INPUT TYPE="checkbox" NAME="company_on" CHECKED> Search for <B>company</B>:
<INPUT TYPE="text" NAME="company_text">
using search methods: <SELECT NAME="company_type">
- <OPTION SELECTED>All
+ <OPTION>All
<OPTION>Fuzzy
<OPTION>Substring
- <OPTION>Exact
+ <OPTION SELECTED>Exact
</SELECT>
- <P><INPUT TYPE="submit" VALUE="Search"> Note: Fuzzy searching can take a while. Please be patient.
+ <P><INPUT TYPE="submit" VALUE="Search">
</FORM>
diff --git a/httemplate/search/cust_main.cgi b/httemplate/search/cust_main.cgi
index 586f8d9..50d367e 100755
--- a/httemplate/search/cust_main.cgi
+++ b/httemplate/search/cust_main.cgi
@@ -50,6 +50,7 @@ my $total = 0;
my(@cust_main, $sortby, $orderby);
if ( $cgi->param('browse')
|| $cgi->param('otaker_on')
+ || $cgi->param('agentnum_on')
) {
my %search = ();
@@ -73,6 +74,9 @@ if ( $cgi->param('browse')
if ( $cgi->param('otaker_on') ) {
$cgi->param('otaker') =~ /^(\w{1,32})$/ or eidiot "Illegal otaker\n";
$search{otaker} = $1;
+ } elsif ( $cgi->param('agentnum_on') ) {
+ $cgi->param('agentnum') =~ /^(\d+)$/ or eidiot "Illegal agentnum\n";
+ $search{agentnum} = $1;
} else {
die "unknown query...";
}
@@ -82,22 +86,22 @@ if ( $cgi->param('browse')
if ( driver_name eq 'mysql' ) {
- my $query = "CREATE TEMPORARY TABLE temp1_$$ TYPE=MYISAM
- SELECT cust_pkg.custnum,COUNT(*) as count
- FROM cust_pkg,cust_main
- WHERE cust_pkg.custnum = cust_main.custnum
- AND ( cust_pkg.cancel IS NULL
- OR cust_pkg.cancel = 0 )
- GROUP BY cust_pkg.custnum";
- my $sth = dbh->prepare($query) or die dbh->errstr. " preparing $query";
- $sth->execute or die "Error executing \"$query\": ". $sth->errstr;
- $query = "CREATE TEMPORARY TABLE temp2_$$ TYPE=MYISAM
- SELECT cust_pkg.custnum,COUNT(*) as count
- FROM cust_pkg,cust_main
- WHERE cust_pkg.custnum = cust_main.custnum
- GROUP BY cust_pkg.custnum";
- my $sth = dbh->prepare($query) or die dbh->errstr. " preparing $query";
- $sth->execute or die "Error executing \"$query\": ". $sth->errstr;
+ my $sql = "CREATE TEMPORARY TABLE temp1_$$ TYPE=MYISAM
+ SELECT cust_pkg.custnum,COUNT(*) as count
+ FROM cust_pkg,cust_main
+ WHERE cust_pkg.custnum = cust_main.custnum
+ AND ( cust_pkg.cancel IS NULL
+ OR cust_pkg.cancel = 0 )
+ GROUP BY cust_pkg.custnum";
+ my $sth = dbh->prepare($sql) or die dbh->errstr. " preparing $sql";
+ $sth->execute or die "Error executing \"$sql\": ". $sth->errstr;
+ $sql = "CREATE TEMPORARY TABLE temp2_$$ TYPE=MYISAM
+ SELECT cust_pkg.custnum,COUNT(*) as count
+ FROM cust_pkg,cust_main
+ WHERE cust_pkg.custnum = cust_main.custnum
+ GROUP BY cust_pkg.custnum";
+ $sth = dbh->prepare($sql) or die dbh->errstr. " preparing $sql";
+ $sth->execute or die "Error executing \"$sql\": ". $sth->errstr;
}
if ( $cgi->param('showcancelledcustomers') eq '0' #see if it was set by me
@@ -112,6 +116,7 @@ if ( $cgi->param('browse')
AND (temp1_$$.count > 0
OR temp2_$$.count = 0 )
";
+
} else {
$ncancelled = "
0 < ( SELECT COUNT(*) FROM cust_pkg
@@ -124,15 +129,32 @@ if ( $cgi->param('browse')
WHERE cust_pkg.custnum = cust_main.custnum
)
";
- }
-
+ }
+ }
+
+ my $cancelled = '';
+ if ( $cgi->param('cancelled') ) {
+ $cancelled = "
+ 0 = ( SELECT COUNT(*) FROM cust_pkg
+ WHERE cust_pkg.custnum = cust_main.custnum
+ AND ( cust_pkg.cancel IS NULL
+ OR cust_pkg.cancel = 0
+ )
+ )
+ AND 0 < ( SELECT COUNT(*) FROM cust_pkg
+ WHERE cust_pkg.custnum = cust_main.custnum
+ )
+ ";
}
#EWWWWWW
my $qual = join(' AND ',
map { "$_ = ". dbh->quote($search{$_}) } keys %search );
- if ( $ncancelled ) {
+ if ( $cancelled ) {
+ $qual .= ' AND ' if $qual;
+ $qual .= $cancelled;
+ } elsif ( $ncancelled ) {
$qual .= ' AND ' if $qual;
$qual .= $ncancelled;
}
@@ -150,26 +172,27 @@ if ( $cgi->param('browse')
$total = $sth->fetchrow_arrayref->[0];
- if ( $ncancelled ) {
+ my $rqual = $cancelled || $ncancelled;
+ if ( $rqual ) {
if ( %search ) {
- $ncancelled = " AND $ncancelled";
+ $rqual = " AND $rqual";
} else {
- $ncancelled = " WHERE $ncancelled";
+ $rqual = " WHERE $rqual";
}
}
my @just_cust_main;
if ( driver_name eq 'mysql' ) {
@just_cust_main = qsearch('cust_main', \%search, 'cust_main.*',
- ",temp1_$$,temp2_$$ $ncancelled $orderby $limit");
+ ",temp1_$$,temp2_$$ $rqual $orderby $limit");
} else {
@just_cust_main = qsearch('cust_main', \%search, '',
- "$ncancelled $orderby $limit" );
+ "$rqual $orderby $limit" );
}
if ( driver_name eq 'mysql' ) {
- $query = "DROP TABLE temp1_$$,temp2_$$;";
- my $sth = dbh->prepare($query) or die dbh->errstr. " preparing $query";
- $sth->execute or die "Error executing \"$query\": ". $sth->errstr;
+ my $sql = "DROP TABLE temp1_$$,temp2_$$;";
+ my $sth = dbh->prepare($sql) or die dbh->errstr. " preparing $sql";
+ $sth->execute or die "Error executing \"$sql\": ". $sth->errstr;
}
@cust_main = @just_cust_main;
@@ -213,9 +236,12 @@ if ( $cgi->param('browse')
}
@cust_main = grep { $_->ncancelled_pkgs || ! $_->all_pkgs } @cust_main
- if $cgi->param('showcancelledcustomers') eq '0' #see if it was set by me
- || ( $conf->exists('hidecancelledcustomers')
- && ! $cgi->param('showcancelledcustomers') );
+ if ! $cgi->param('cancelled')
+ && (
+ $cgi->param('showcancelledcustomers') eq '0' #see if it was set by me
+ || ( $conf->exists('hidecancelledcustomers')
+ && ! $cgi->param('showcancelledcustomers') )
+ );
my %saw = ();
@cust_main = grep { !$saw{$_->custnum}++ } @cust_main;
@@ -277,19 +303,22 @@ if ( scalar(@cust_main) == 1 && ! $cgi->param('referral_custnum') ) {
}
}
#end pager
-
- if ( $cgi->param('showcancelledcustomers') eq '0' #see if it was set by me
- || ( $conf->exists('hidecancelledcustomers')
- && ! $cgi->param('showcancelledcustomers')
- )
- ) {
- $cgi->param('showcancelledcustomers', 1);
- $cgi->param('offset', 0);
- print qq!( <a href="!. $cgi->self_url. qq!">show cancelled customers</a> )!;
- } else {
- $cgi->param('showcancelledcustomers', 0);
- $cgi->param('offset', 0);
- print qq!( <a href="!. $cgi->self_url. qq!">hide cancelled customers</a> )!;
+
+ unless ( $cgi->param('cancelled') ) {
+ if ( $cgi->param('showcancelledcustomers') eq '0' #see if it was set by me
+ || ( $conf->exists('hidecancelledcustomers')
+ && ! $cgi->param('showcancelledcustomers')
+ )
+ ) {
+ $cgi->param('showcancelledcustomers', 1);
+ $cgi->param('offset', 0);
+ print qq!( <a href="!. $cgi->self_url. qq!">show!;
+ } else {
+ $cgi->param('showcancelledcustomers', 0);
+ $cgi->param('offset', 0);
+ print qq!( <a href="!. $cgi->self_url. qq!">hide!;
+ }
+ print ' cancelled customers</a> )';
}
if ( $cgi->param('referral_custnum') ) {
$cgi->param('referral_custnum') =~ /^(\d+)$/
@@ -457,7 +486,7 @@ sub custnumsearch {
my $custnum = $cgi->param('custnum_text');
$custnum =~ s/\D//g;
$custnum =~ /^(\d{1,23})$/ or eidiot "Illegal customer number\n";
- my $custnum = $1;
+ $custnum = $1;
[ qsearchs('cust_main', { 'custnum' => $custnum } ) ];
}
@@ -469,7 +498,9 @@ sub cardsearch {
$card =~ /^(\d{13,16})$/ or eidiot "Illegal card number\n";
my($payinfo)=$1;
- [ qsearch('cust_main',{'payinfo'=>$payinfo, 'payby'=>'CARD'}) ];
+ [ qsearch('cust_main',{'payinfo'=>$payinfo, 'payby'=>'CARD'}),
+ qsearch('cust_main',{'payinfo'=>$payinfo, 'payby'=>'DCRD'})
+ ];
}
sub referralsearch {
@@ -636,12 +667,19 @@ sub phonesearch {
my $phone = $cgi->param('phone_text');
- #false laziness with Record::ut_phonen, only works with US/CA numbers...
+ #(no longer really) false laziness with Record::ut_phonen
+ #only works with US/CA numbers...
$phone =~ s/\D//g;
- $phone =~ /^(\d{3})(\d{3})(\d{4})(\d*)$/
- or eidiot gettext('illegal_phone'). ": $phone";
- $phone = "$1-$2-$3";
- $phone .= " x$4" if $4;
+ if ( $phone =~ /^(\d{3})(\d{3})(\d{4})(\d*)$/ ) {
+ $phone = "$1-$2-$3";
+ $phone .= " x$4" if $4;
+ } elsif ( $phone =~ /^(\d{3})(\d{4})$/ ) {
+ $phone = "$1-$2";
+ } elsif ( $phone =~ /^(\d{3,4})$/ ) {
+ $phone = $1;
+ } else {
+ eidiot gettext('illegal_phone'). ": $phone";
+ }
my @fields = qw(daytime night fax);
push @fields, qw(ship_daytime ship_night ship_fax)
@@ -650,7 +688,7 @@ sub phonesearch {
for my $field ( @fields ) {
push @cust_main, qsearch ( 'cust_main',
{ $field => { 'op' => 'LIKE',
- 'value' => "$phone%" } } );
+ 'value' => "%$phone%" } } );
}
\@cust_main;
diff --git a/httemplate/search/cust_pay.cgi b/httemplate/search/cust_pay.cgi
index b5bdf82..51dd3b3 100755
--- a/httemplate/search/cust_pay.cgi
+++ b/httemplate/search/cust_pay.cgi
@@ -1,12 +1,73 @@
<%
-$cgi->param('payinfo') =~ /^\s*(\d+)\s*$/ or die "illegal payinfo";
-my $payinfo = $1;
-$cgi->param('payby') =~ /^(\w+)$/ or die "illegal payby";
-my $payby = $1;
-my @cust_pay = qsearch('cust_pay', { 'payinfo' => $payinfo,
+my $sortby;
+my @cust_pay;
+if ( $cgi->param('magic') && $cgi->param('magic') eq '_date' ) {
+
+ my %search;
+ my @search;
+
+ if ( $cgi->param('payby') ) {
+ $cgi->param('payby') =~ /^(CARD|CHEK|BILL)(-(VisaMC|Amex|Discover))?$/
+ or die "illegal payby ". $cgi->param('payby');
+ $search{'payby'} = $1;
+ if ( $3 ) {
+ if ( $3 eq 'VisaMC' ) {
+ #avoid posix regexes for portability
+ push @search, " ( substring(payinfo from 1 for 1) = '4' ".
+ " OR substring(payinfo from 1 for 2) = '51' ".
+ " OR substring(payinfo from 1 for 2) = '52' ".
+ " OR substring(payinfo from 1 for 2) = '53' ".
+ " OR substring(payinfo from 1 for 2) = '54' ".
+ " OR substring(payinfo from 1 for 2) = '54' ".
+ " OR substring(payinfo from 1 for 2) = '55' ".
+ " ) ";
+ } elsif ( $3 eq 'Amex' ) {
+ push @search, " ( substring(payinfo from 1 for 2 ) = '34' ".
+ " OR substring(payinfo from 1 for 2 ) = '37' ".
+ " ) ";
+ } elsif ( $3 eq 'Discover' ) {
+ push @search, " substring(payinfo from 1 for 4 ) = '6011' ";
+ } else {
+ die "unknown card type $3";
+ }
+ }
+ }
+
+ #false laziness with cust_pkg.cgi
+ if ( $cgi->param('beginning')
+ && $cgi->param('beginning') =~ /^([ 0-9\-\/]{0,10})$/ ) {
+ my $beginning = str2time($1);
+ push @search, "_date >= $beginning ";
+ }
+ if ( $cgi->param('ending')
+ && $cgi->param('ending') =~ /^([ 0-9\-\/]{0,10})$/ ) {
+ my $ending = str2time($1) + 86399;
+ push @search, " _date <= $ending ";
+ }
+ my $search;
+ if ( @search ) {
+ $search = ( scalar(keys %search) ? ' AND ' : ' WHERE ' ).
+ join(' AND ', @search);
+ }
+
+ @cust_pay = qsearch('cust_pay', \%search, '', $search );
+
+ $sortby = \*date_sort;
+
+} else {
+
+ $cgi->param('payinfo') =~ /^\s*(\d+)\s*$/ or die "illegal payinfo";
+ my $payinfo = $1;
+
+ $cgi->param('payby') =~ /^(\w+)$/ or die "illegal payby";
+ my $payby = $1;
+
+ @cust_pay = qsearch('cust_pay', { 'payinfo' => $payinfo,
'payby' => $payby } );
-my $sortby = \*date_sort;
+ $sortby = \*date_sort;
+
+}
if (0) {
#if ( scalar(@cust_pay) == 1 ) {
@@ -16,7 +77,7 @@ if (0) {
%>
<!-- mason kludge -->
<%
- idiot("Check # not found.");
+ idiot("Payment not found.");
#exit;
} else {
my $total = scalar(@cust_pay);
@@ -24,9 +85,9 @@ if (0) {
%>
<!-- mason kludge -->
<%
- print header("Check # Search Results", menubar(
+ print header("Payment Search Results", menubar(
'Main Menu', popurl(2)
- )), "$total matching check$s found<BR>", &table(), <<END;
+ )), "$total matching payment$s found<BR>", &table(), <<END;
<TR>
<TH></TH>
<TH>Amount</TH>
@@ -37,26 +98,41 @@ if (0) {
END
my(%saw, $cust_pay);
+ my $tot_amount = 0;
foreach my $cust_pay (
sort $sortby grep(!$saw{$_->paynum}++, @cust_pay)
) {
- my($paynum, $custnum, $payinfo, $amount, $date ) = (
+ my($paynum, $custnum, $payby, $payinfo, $amount, $date ) = (
$cust_pay->paynum,
$cust_pay->custnum,
+ $cust_pay->payby,
$cust_pay->payinfo,
sprintf("%.2f", $cust_pay->paid),
$cust_pay->_date,
);
- my $pdate = time2str("%b %d %Y", $date);
+ $tot_amount += $amount;
+ my $pdate = time2str("%b&nbsp;%d&nbsp;%Y", $date);
my $rowspan = 1;
my $view = popurl(2). "view/cust_main.cgi?". $custnum.
"#". $payby. $payinfo;
+ my $payment_info;
+ if ( $payby eq 'CARD' ) {
+ $payment_info = 'Card&nbsp;#'. 'x'x(length($payinfo)-4).
+ substr($payinfo,(length($payinfo)-4));
+ } elsif ( $payby eq 'CHEK' ) {
+ $payment_info = "E-check&nbsp;acct#$payinfo";
+ } elsif ( $payby eq 'BILL' ) {
+ $payment_info = "Check&nbsp;#$payinfo";
+ } else {
+ $payment_info = "$payby&nbsp;$payinfo";
+ }
+
print <<END;
<TR>
- <TD ROWSPAN=$rowspan><A HREF="$view"><FONT SIZE=-1>$payinfo</FONT></A></TD>
+ <TD ROWSPAN=$rowspan><A HREF="$view"><FONT SIZE=-1>$payment_info</FONT></A></TD>
<TD ROWSPAN=$rowspan ALIGN="right"><A HREF="$view"><FONT SIZE=-1>\$$amount</FONT></A></TD>
<TD ROWSPAN=$rowspan><A HREF="$view"><FONT SIZE=-1>$pdate</FONT></A></TD>
END
@@ -79,7 +155,13 @@ END
print "</TR>";
}
- print <<END;
+
+ $tot_amount = sprintf("%.2f", $tot_amount);
+ print '</TABLE><BR>'. table(). <<END;
+ <TR>
+ <TH>Total&nbsp;Amount</TH>
+ <TD ALIGN="right">\$$tot_amount</TD>
+ </TR>
</TABLE>
</BODY>
</HTML>
diff --git a/httemplate/search/cust_pkg.cgi b/httemplate/search/cust_pkg.cgi
index abf6eee..45420f4 100755
--- a/httemplate/search/cust_pkg.cgi
+++ b/httemplate/search/cust_pkg.cgi
@@ -19,6 +19,8 @@ my @cust_pkg;
if ( $cgi->param('magic') && $cgi->param('magic') eq 'bill' ) {
$sortby=\*bill_sort;
+
+ #false laziness with cust_pay.cgi
my $range = '';
if ( $cgi->param('beginning')
&& $cgi->param('beginning') =~ /^([ 0-9\-\/]{0,10})$/ ) {
@@ -27,10 +29,18 @@ if ( $cgi->param('magic') && $cgi->param('magic') eq 'bill' ) {
}
if ( $cgi->param('ending')
&& $cgi->param('ending') =~ /^([ 0-9\-\/]{0,10})$/ ) {
- my $ending = str2time($1) + 86400;
+ my $ending = str2time($1) + 86399;
$range .= ( $range ? ' AND ' : ' WHERE ' ). " bill <= $ending ";
}
+ $range .= ( $range ? 'AND ' : ' WHERE ' ). '( cancel IS NULL OR cancel = 0 )';
+
+ if ( $cgi->param('agentnum') =~ /^(\d+)$/ and $1 ) {
+ $range .= ( $range ? 'AND ' : ' WHERE ' ).
+ "$1 = ( SELECT agentnum FROM cust_main".
+ " WHERE cust_main.custnum = cust_pkg.custnum )";
+ }
+
#false laziness with below
my $statement = "SELECT COUNT(*) FROM cust_pkg $range";
warn $statement;
@@ -43,8 +53,31 @@ if ( $cgi->param('magic') && $cgi->param('magic') eq 'bill' ) {
} else {
- my $unconf = '';
- if ( $query eq 'pkgnum' ) {
+ my $qual = '';
+ if ( $cgi->param('magic') &&
+ $cgi->param('magic') =~ /^(active|suspended|canceled)$/
+ ) {
+
+ if ( $cgi->param('magic') eq 'active' ) {
+ $qual = 'WHERE ( susp IS NULL OR susp = 0 )'.
+ ' AND ( cancel IS NULL OR cancel = 0)';
+ } elsif ( $cgi->param('magic') eq 'suspended' ) {
+ $qual = 'WHERE susp IS NOT NULL AND susp != 0'.
+ ' AND ( cancel IS NULL OR cancel = 0)';
+ } elsif ( $cgi->param('magic') eq 'canceled' ) {
+ $qual = 'WHERE cancel IS NOT NULL AND cancel != 0';
+ } else {
+ die "guru meditation #420";
+ }
+
+ $sortby = \*pkgnum_sort;
+
+ if ( $cgi->param('pkgpart') =~ /^(\d+)$/ ) {
+ $qual .= " AND pkgpart = $1";
+ }
+
+ } elsif ( $query eq 'pkgnum' ) {
+
$sortby=\*pkgnum_sort;
} elsif ( $query eq 'APKG_pkgnum' ) {
@@ -87,7 +120,7 @@ if ( $cgi->param('magic') && $cgi->param('magic') eq 'bill' ) {
AND cust_svc.svcpart = pkg_svc.svcpart
AND cust_pkg.pkgpart = pkg_svc.pkgpart
GROUP BY cust_svc.pkgnum,cust_svc.svcpart";
- $sth = dbh->prepare($query) or die dbh->errstr. " preparing $query";
+ my $sth = dbh->prepare($query) or die dbh->errstr. " preparing $query";
$sth->execute or die "Error executing \"$query\": ". $sth->errstr;
@@ -101,12 +134,12 @@ if ( $cgi->param('magic') && $cgi->param('magic') eq 'bill' ) {
AND pkg_svc.quantity != 0;";
$sth = dbh->prepare($query) or die dbh->errstr. " preparing $query";
$sth->execute or die "Error executing \"$query\": ". $sth->errstr;
- $unconf = " LEFT JOIN temp2_$$ ON cust_pkg.pkgnum = temp2_$$.pkgnum
- WHERE temp2_$$.pkgnum IS NOT NULL";
+ $qual = " LEFT JOIN temp2_$$ ON cust_pkg.pkgnum = temp2_$$.pkgnum
+ WHERE temp2_$$.pkgnum IS NOT NULL";
} else {
- $unconf = "
+ $qual = "
WHERE 0 <
( SELECT count(*) FROM pkg_svc
WHERE pkg_svc.pkgpart = cust_pkg.pkgpart
@@ -120,10 +153,10 @@ if ( $cgi->param('magic') && $cgi->param('magic') eq 'bill' ) {
}
} else {
- die "Empty QUERY_STRING!";
+ die "Empty or unknown QUERY_STRING!";
}
- my $statement = "SELECT COUNT(*) FROM cust_pkg $unconf";
+ my $statement = "SELECT COUNT(*) FROM cust_pkg $qual";
my $sth = dbh->prepare($statement) or die dbh->errstr." preparing $statement";
$sth->execute or die "Error executing \"$statement\": ". $sth->errstr;
@@ -131,7 +164,7 @@ if ( $cgi->param('magic') && $cgi->param('magic') eq 'bill' ) {
my $tblname = driver_name eq 'mysql' ? 'cust_pkg.' : '';
@cust_pkg =
- qsearch('cust_pkg',{}, '', "$unconf ORDER BY ${tblname}pkgnum $limit" );
+ qsearch('cust_pkg',{}, '', "$qual ORDER BY ${tblname}pkgnum $limit" );
if ( driver_name eq 'mysql' ) {
$query = "DROP TABLE temp1_$$,temp2_$$;";
@@ -188,6 +221,12 @@ if ( scalar(@cust_pkg) == 1 ) {
<TR>
<TH>Package</TH>
<TH><FONT SIZE=-1>Setup</FONT></TH>
+END
+
+ print '<TH><FONT SIZE=-1>Last<BR>bill</FONT></TH>'
+ if defined dbdef->table('cust_pkg')->column('last_bill');
+
+ print <<END;
<TH><FONT SIZE=-1>Next<BR>bill</FONT></TH>
<TH><FONT SIZE=-1>Susp.</FONT></TH>
<TH><FONT SIZE=-1>Expire</FONT></TH>
@@ -197,17 +236,10 @@ if ( scalar(@cust_pkg) == 1 ) {
<TH>company</TH>
END
-if ( defined dbdef->table('cust_main')->column('ship_last') ) {
- print <<END;
- <TH>(service) name</TH>
- <TH>company</TH>
-END
-}
+ print '<TH>(service) name</TH><TH>company</TH>'
+ if defined dbdef->table('cust_main')->column('ship_last');
-print <<END;
- <TH COLSPAN=2>Services</TH>
- </TR>
-END
+ print '<TH COLSPAN=2>Services</TH></TR>';
my $n1 = '<TR>';
my(%saw,$cust_pkg);
@@ -238,6 +270,12 @@ END
$cust_main ? $cust_main->first : '',
$cust_main ? $cust_main->company : '',
);
+
+ my $last_bill = $cust_pkg->getfield('last_bill')
+ ? time2str("%D", $cust_pkg->getfield('last_bill') )
+ : ''
+ if defined dbdef->table('cust_pkg')->column('last_bill');
+
my($ship_last, $ship_first, $ship_company);
if ( defined dbdef->table('cust_main')->column('ship_last') ) {
($ship_last, $ship_first, $ship_company) = (
@@ -263,11 +301,17 @@ END
my $p = popurl(2);
print $n1, <<END;
<TD ROWSPAN=$rowspan><A HREF="${p}view/cust_pkg.cgi?$pkgnum"><FONT SIZE=-1>$pkgnum - $pkg</FONT></A></TD>
- <TD>$setup</TD>
- <TD>$bill</TD>
- <TD>$susp</TD>
- <TD>$expire</TD>
- <TD>$cancel</TD>
+ <TD ROWSPAN=$rowspan>$setup</TD>
+END
+
+ print "<TD ROWSPAN=$rowspan>$last_bill</TD>"
+ if defined dbdef->table('cust_pkg')->column('last_bill');
+
+ print <<END;
+ <TD ROWSPAN=$rowspan>$bill</TD>
+ <TD ROWSPAN=$rowspan>$susp</TD>
+ <TD ROWSPAN=$rowspan>$expire</TD>
+ <TD ROWSPAN=$rowspan>$cancel</TD>
END
if ( $cust_main ) {
print <<END;
diff --git a/httemplate/search/cust_pkg.html b/httemplate/search/cust_pkg.html
deleted file mode 100755
index bb0a540..0000000
--- a/httemplate/search/cust_pkg.html
+++ /dev/null
@@ -1,24 +0,0 @@
-<HTML>
- <HEAD>
- <TITLE>Packages</TITLE>
- </HEAD>
- <BODY>
- <CENTER>
- <H1>Packages</H1>
- </CENTER>
- <HR>
- <FORM ACTION="cust_pkg.cgi" METHOD="post">
- <INPUT TYPE="hidden" NAME="magic" VALUE="bill">
- Return <B>packages</B> with next bill date:
- from <INPUT TYPE="text" NAME="beginning"> <i>m/d/y</i>
- to <INPUT TYPE="text" NAME="ending"> <i>m/d/y</i>
-
- <P><INPUT TYPE="submit" VALUE="Get Report">
-
- </FORM>
-
- <HR>
-
- </BODY>
-</HTML>
-
diff --git a/httemplate/search/cust_pkg_report.cgi b/httemplate/search/cust_pkg_report.cgi
new file mode 100755
index 0000000..b316745
--- /dev/null
+++ b/httemplate/search/cust_pkg_report.cgi
@@ -0,0 +1,63 @@
+<HTML>
+ <HEAD>
+ <TITLE>Packages</TITLE>
+ <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>
+ </HEAD>
+ <BODY BGCOLOR="#e8e8e8">
+ <H1>Packages</H1>
+ <FORM ACTION="cust_pkg.cgi" METHOD="post">
+ <INPUT TYPE="hidden" NAME="magic" VALUE="bill">
+ Return packages with next bill date:<BR><BR>
+ <TABLE>
+ <TR>
+ <TD ALIGN="right">From: </TD>
+ <TD><INPUT TYPE="text" NAME="beginning" ID="beginning_text" VALUE="" SIZE=11 MAXLENGTH=10> <IMG SRC="../images/calendar.png" ID="beginning_button" STYLE="cursor: pointer" TITLE="Select date"><BR><I>m/d/y</I></TD>
+<SCRIPT TYPE="text/javascript">
+ Calendar.setup({
+ inputField: "beginning_text",
+ ifFormat: "%m/%d/%Y",
+ button: "beginning_button",
+ align: "BR"
+ });
+</SCRIPT>
+ </TR>
+ <TR>
+ <TD ALIGN="right">To: </TD>
+ <TD><INPUT TYPE="text" NAME="ending" ID="ending_text" VALUE="" SIZE=11 MAXLENGTH=10> <IMG SRC="../images/calendar.png" ID="ending_button" STYLE="cursor: pointer" TITLE="Select date"><BR><I>m/d/y</I></TD>
+<SCRIPT TYPE="text/javascript">
+ Calendar.setup({
+ inputField: "ending_text",
+ ifFormat: "%m/%d/%Y",
+ button: "ending_button",
+ align: "BR"
+ });
+</SCRIPT>
+ </TR>
+<% my %agent_search = dbdef->table('agent')->column('disabled')
+ ? ( 'disabled' => '' ) : ();
+ my @agents = qsearch( 'agent', \%agent_search );
+ if ( scalar(@agents) == 1 ) {
+%>
+ <INPUT TYPE="hidden" NAME="agentnum" VALUE="<%= $agents[0]->agentnum %>">
+<% } else { %>
+
+ <TR>
+ <TD ALIGN="right">Agent: </TD>
+ <TD><SELECT NAME="agentnum"><OPTION VALUE="">(all)
+ <% foreach my $agent ( sort { $a->agent cmp $b->agent; } @agents) { %>
+ <OPTION VALUE="<%= $agent->agentnum %>"><%= $agent->agent %>
+ <% } %>
+ </TD>
+ </TR>
+<% } %>
+ </TABLE>
+ <BR><INPUT TYPE="submit" VALUE="Get Report">
+
+ </FORM>
+
+ </BODY>
+</HTML>
+
diff --git a/httemplate/search/elements/search.html b/httemplate/search/elements/search.html
new file mode 100644
index 0000000..fbedcaa
--- /dev/null
+++ b/httemplate/search/elements/search.html
@@ -0,0 +1,59 @@
+<%
+
+ my %opt = @_;
+ unless (exists($opt{'count_query'}) && length($opt{'count_query'})) {
+ ( $opt{'count_query'} = $opt{'query'} ) =~
+ s/^\s*SELECT\s*(.*)\s+FROM\s/SELECT COUNT(*) FROM /i;
+ }
+
+ my $conf = new FS::Conf;
+ my $maxrecords = $conf->config('maxsearchrecordsperpage');
+
+ my $limit = $maxrecords ? "LIMIT $maxrecords" : '';
+
+ my $offset = $cgi->param('offset') || 0;
+ $limit .= " OFFSET $offset" if $offset;
+
+ my $count_sth = dbh->prepare($opt{'count_query'})
+ or die "Error preparing $opt{'count_query'}: ". dbh->errstr;
+ $count_sth->execute
+ or die "Error executing $opt{'count_query'}: ". $count_sth->errstr;
+ my $total = $count_sth->fetchrow_arrayref->[0];
+
+ my $sth = dbh->prepare("$opt{'query'} $limit")
+ or die "Error preparing $opt{'query'}: ". dbh->errstr;
+ $sth->execute
+ or die "Error executing $opt{'query'}: ". $sth->errstr;
+
+ #can get # of rows without fetching them all?
+ my $rows = $sth->fetchall_arrayref;
+
+%>
+<!-- mason kludge -->
+<% my $pager = include ( '/elements/pager.html',
+ 'offset' => $offset,
+ 'num_rows' => scalar(@$rows),
+ 'total' => $total,
+ 'maxrecords' => $maxrecords,
+ );
+%>
+
+<%= $total %> total <%= $opt{'name'} %><BR><BR><%= $pager %>
+<%= include( '/elements/table.html' ) %>
+ <TR>
+ <% foreach ( @{$sth->{NAME}} ) { %>
+ <TH><%= $_ %></TH>
+ <% } %>
+ </TR>
+ <% foreach my $row ( @$rows ) { %>
+ <TR>
+ <% foreach ( @$row ) { %>
+ <TD><%= $_ %></TD>
+ <% } %>
+ </TR>
+ <% } %>
+
+</TABLE>
+<%= $pager %>
+</BODY>
+</HTML>
diff --git a/httemplate/search/report_cc.html b/httemplate/search/report_cc.html
index 8653dcc..595a7b1 100755
--- a/httemplate/search/report_cc.html
+++ b/httemplate/search/report_cc.html
@@ -1,23 +1,43 @@
<HTML>
<HEAD>
<TITLE>Credit Card Receipt Report Criteria</TITLE>
- </HEAD>
- <BODY>
- <CENTER>
- <H1>Credit Card Receipt Report Criteria</H1>
- </CENTER>
- <HR>
+ <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> </HEAD>
+ <BODY BGCOLOR="#e8e8e8">
+ <H1>Credit Card Receipt Report Criteria</H1>
<FORM ACTION="report_cc.cgi" METHOD="post">
Return <B>credit card receipt report</B> for period:
- from <INPUT TYPE="text" NAME="beginning"> <i>m/d/y</i>
- to <INPUT TYPE="text" NAME="ending"> <i>m/d/y</i>
+ <TABLE>
+ <TR>
+ <TD ALIGN="right">From: </TD>
+ <TD><INPUT TYPE="text" NAME="beginning" ID="beginning_text" VALUE="" SIZE=11 MAXLENGTH=10> <IMG SRC="../images/calendar.png" ID="beginning_button" STYLE="cursor: pointer" TITLE="Select date"><BR><i>m/d/y</i></TD>
+<SCRIPT TYPE="text/javascript">
+ Calendar.setup({
+ inputField: "beginning_text",
+ ifFormat: "%m/%d/%Y",
+ button: "beginning_button",
+ align: "BR"
+ });
+</SCRIPT>
+ </TR>
+ <TD ALIGN="right">To: </TD>
+ <TD><INPUT TYPE="text" NAME="ending" ID="ending_text" VALUE="" SIZE=11 MAXLENGTH=10> <IMG SRC="../images/calendar.png" ID="ending_button" STYLE="cursor: pointer" TITLE="Select date"><BR><i>m/d/y</i></TD>
+<SCRIPT TYPE="text/javascript">
+ Calendar.setup({
+ inputField: "ending_text",
+ ifFormat: "%m/%d/%Y",
+ button: "ending_button",
+ align: "BR"
+ });
+</SCRIPT>
+ </TR>
+ </TABLE>
- <P><INPUT TYPE="submit" VALUE="Get Report">
+ <BR><INPUT TYPE="submit" VALUE="Get Report">
</FORM>
-
- <HR>
-
</BODY>
</HTML>
diff --git a/httemplate/search/report_credit.html b/httemplate/search/report_credit.html
index df9b958..11cb32e 100755
--- a/httemplate/search/report_credit.html
+++ b/httemplate/search/report_credit.html
@@ -1,23 +1,43 @@
<HTML>
<HEAD>
<TITLE>In House Credit Report Criteria</TITLE>
- </HEAD>
- <BODY>
- <CENTER>
- <H1>In House Credit Report Criteria</H1>
- </CENTER>
- <HR>
+ <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> </HEAD>
+ <BODY BGCOLOR="#e8e8e8">
+ <H1>In House Credit Report Criteria</H1>
<FORM ACTION="report_credit.cgi" METHOD="post">
Return <B>in house credit report</B> for period:
- from <INPUT TYPE="text" NAME="beginning"> <i>m/d/y</i>
- to <INPUT TYPE="text" NAME="ending"> <i>m/d/y</i>
+ <TABLE>
+ <TR>
+ <TD ALIGN="right">From: </TD>
+ <TD><INPUT TYPE="text" NAME="beginning" ID="beginning_text" VALUE="" SIZE=11 MAXLENGTH=10> <IMG SRC="../images/calendar.png" ID="beginning_button" STYLE="cursor: pointer" TITLE="Select date"><BR><i>m/d/y</i></TD>
+<SCRIPT TYPE="text/javascript">
+ Calendar.setup({
+ inputField: "beginning_text",
+ ifFormat: "%m/%d/%Y",
+ button: "beginning_button",
+ align: "BR"
+ });
+</SCRIPT>
+ </TR>
+ <TD ALIGN="right">To: </TD>
+ <TD><INPUT TYPE="text" NAME="ending" ID="ending_text" VALUE="" SIZE=11 MAXLENGTH=10> <IMG SRC="../images/calendar.png" ID="ending_button" STYLE="cursor: pointer" TITLE="Select date"><BR><i>m/d/y</i></TD>
+<SCRIPT TYPE="text/javascript">
+ Calendar.setup({
+ inputField: "ending_text",
+ ifFormat: "%m/%d/%Y",
+ button: "ending_button",
+ align: "BR"
+ });
+</SCRIPT>
+ </TR>
+ </TABLE>
- <P><INPUT TYPE="submit" VALUE="Get Report">
+ <BR><INPUT TYPE="submit" VALUE="Get Report">
</FORM>
-
- <HR>
-
</BODY>
</HTML>
diff --git a/httemplate/search/report_cust_pay.html b/httemplate/search/report_cust_pay.html
new file mode 100644
index 0000000..1b30685
--- /dev/null
+++ b/httemplate/search/report_cust_pay.html
@@ -0,0 +1,54 @@
+<HTML>
+ <HEAD>
+ <TITLE>Payment report criteria</TITLE>
+ <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>
+ </HEAD>
+ <BODY BGCOLOR="#e8e8e8">
+ <H1>Payment report criteria</H1>
+ <FORM ACTION="cust_pay.cgi" METHOD="post">
+ <INPUT TYPE="hidden" NAME="magic" VALUE="_date">
+ <TABLE>
+ <TR>
+ <TD ALIGN="right">Payments of type: </TD>
+ <TD><SELECT NAME="payby">
+ <OPTION VALUE="">all</OPTION>
+ <OPTION VALUE="CARD">credit card (all)</OPTION>
+ <OPTION VALUE="CARD-VisaMC">credit card (Visa/MasterCard)</OPTION>
+ <OPTION VALUE="CARD-Amex">credit card (American Express)</OPTION>
+ <OPTION VALUE="CARD-Discover">credit card (Discover)</OPTION>
+ <OPTION VALUE="CHEK">electronic check / ACH</OPTION>
+ <OPTION VALUE="BILL">check / cash</OPTION>
+ </SELECT>
+ </TD>
+ </TR>
+ <TR>
+ <TD ALIGN="right">From: </TD>
+ <TD><INPUT TYPE="text" NAME="beginning" ID="beginning_text" VALUE="" SIZE=11 MAXLENGTH=10> <IMG SRC="../images/calendar.png" ID="beginning_button" STYLE="cursor: pointer" TITLE="Select date"><BR><i>m/d/y</i></TD>
+<SCRIPT TYPE="text/javascript">
+ Calendar.setup({
+ inputField: "beginning_text",
+ ifFormat: "%m/%d/%Y",
+ button: "beginning_button",
+ align: "BR"
+ });
+</SCRIPT>
+ </TR>
+ <TD ALIGN="right">To: </TD>
+ <TD><INPUT TYPE="text" NAME="ending" ID="ending_text" VALUE="" SIZE=11 MAXLENGTH=10> <IMG SRC="../images/calendar.png" ID="ending_button" STYLE="cursor: pointer" TITLE="Select date"><BR><i>m/d/y</i></TD>
+<SCRIPT TYPE="text/javascript">
+ Calendar.setup({
+ inputField: "ending_text",
+ ifFormat: "%m/%d/%Y",
+ button: "ending_button",
+ align: "BR"
+ });
+</SCRIPT>
+ </TR>
+ </TABLE>
+ <BR><INPUT TYPE="submit" VALUE="Get Report">
+ </FORM>
+ </BODY>
+</HTML>
diff --git a/httemplate/search/report_prepaid_income.cgi b/httemplate/search/report_prepaid_income.cgi
new file mode 100644
index 0000000..eb8bbb5
--- /dev/null
+++ b/httemplate/search/report_prepaid_income.cgi
@@ -0,0 +1,75 @@
+<!-- mason kludge -->
+<%
+
+ #doesn't yet deal with daily/weekly packages
+
+ #needs to be re-written in sql for efficiency
+
+ my $now = $cgi->param('date') && str2time($cgi->param('date')) || time;
+ $now =~ /^(\d+)$/ or die "unparsable date?";
+ $now = $1;
+
+ my %prepaid;
+
+ my @cust_bill_pkg =
+ grep { $_->cust_pkg && $_->cust_pkg->part_pkg->freq !~ /^([01]|\d+[dw])$/ }
+ qsearch( 'cust_bill_pkg', {
+ 'recur' => { op=>'!=', value=>0 },
+ 'edate' => { op=>'>', value=>$now },
+ }, );
+
+ foreach my $cust_bill_pkg ( @cust_bill_pkg ) {
+
+ #conceptual false laziness w/texas tax exempt_amount stuff in
+ #FS::cust_main::bill
+
+ my $freq = $cust_bill_pkg->cust_pkg->part_pkg->freq;
+ my $per_month = sprintf("%.2f", $cust_bill_pkg->recur / $freq);
+
+ my($mon, $year) = (localtime($cust_bill_pkg->sdate) )[4,5];
+ $mon+=2; $year+=1900;
+
+ foreach my $which_month ( 2 .. $freq ) {
+ until ( $mon < 13 ) { $mon -= 12; $year++; }
+ $prepaid{"$year-$mon"} += $per_month;
+ $mon++;
+ }
+
+ }
+
+ my @mon = qw(Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec);
+
+%>
+
+<%= header( 'Prepaid Income (Unearned Revenue) Report',
+ menubar( 'Main Menu'=>$p, ) ) %>
+<%= table() %>
+<%
+
+ my $total = 0;
+
+ my ($now_mon, $now_year) = (localtime($now))[4,5];
+ $now_mon+=2; $now_year+=1900;
+ until ( $now_mon < 13 ) { $now_mon -= 12; $now_year++; }
+
+ my $subseq = 0;
+ for my $year ( $now_year .. 2037 ) {
+ for my $mon ( ( $subseq++ ? 1 : $now_mon ) .. 12 ) {
+ if ( $prepaid{"$year-$mon"} ) {
+ $total += $prepaid{"$year-$mon"};
+ %> <TR><TD ALIGN="right"><%= $mon[$mon-1]. ' '. $year %></TD>
+ <TD ALIGN="right">
+ <%= sprintf("%.2f", $prepaid{"$year-$mon"} ) %>
+ </TD>
+ </TR>
+ <%
+ }
+ }
+
+ }
+
+%>
+<TR><TH>Total</TH><TD ALIGN="right"><%= sprintf("%.2f", $total) %></TD></TR>
+</TABLE>
+</BODY>
+</HTML>
diff --git a/httemplate/search/report_prepaid_income.html b/httemplate/search/report_prepaid_income.html
new file mode 100644
index 0000000..b85a481
--- /dev/null
+++ b/httemplate/search/report_prepaid_income.html
@@ -0,0 +1,17 @@
+<HTML>
+ <HEAD>
+ <TITLE>Prepaid Income (Unearned Revenue) Report</TITLE>
+ <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>
+ </HEAD>
+ <BODY BGCOLOR="#e8e8e8">
+ <H1>Prepaid Income (Unearned Revenue) Report</H1>
+ <FORM ACTION="report_prepaid_income.cgi" METHOD="post">
+ Prepaid income (unearned revenue) as of <INPUT TYPE="text" NAME="date" VALUE="now">
+ <INPUT TYPE="submit" VALUE="Generate report">
+ </BODY>
+</HTML>
+ <TABLE>
+
diff --git a/httemplate/search/report_receivables.cgi b/httemplate/search/report_receivables.cgi
index fdd3779..ad353a1 100755
--- a/httemplate/search/report_receivables.cgi
+++ b/httemplate/search/report_receivables.cgi
@@ -1,19 +1,158 @@
<!-- mason kludge -->
<%
-my $user = getotaker;
+ my $charged = <<END;
+ sum( charged
+ - coalesce(
+ ( select sum(amount) from cust_bill_pay
+ where cust_bill.invnum = cust_bill_pay.invnum )
+ ,0
+ )
+ - coalesce(
+ ( select sum(amount) from cust_credit_bill
+ where cust_bill.invnum = cust_credit_bill.invnum )
+ ,0
+ )
-print header('Current Receivables Report Results');
+ )
+END
-open (REPORT, "freeside-receivables-report -v $user |");
+ my $owed_cols = <<END;
+ coalesce(
+ ( select $charged from cust_bill
+ where cust_bill._date > extract(epoch from now())-2592000
+ and cust_main.custnum = cust_bill.custnum
+ )
+ ,0
+ ) as owed_0_30,
-print '<PRE>';
-while(<REPORT>) {
- print $_;
-}
-print '</PRE>';
+ coalesce(
+ ( select $charged from cust_bill
+ where cust_bill._date > extract(epoch from now())-5184000
+ and cust_bill._date <= extract(epoch from now())-2592000
+ and cust_main.custnum = cust_bill.custnum
+ )
+ ,0
+ ) as owed_30_60,
-print '</BODY></HTML>';
+ coalesce(
+ ( select $charged from cust_bill
+ where cust_bill._date > extract(epoch from now())-7776000
+ and cust_bill._date <= extract(epoch from now())-5184000
+ and cust_main.custnum = cust_bill.custnum
+ )
+ ,0
+ ) as owed_60_90,
-%>
+ coalesce(
+ ( select $charged from cust_bill
+ where cust_bill._date <= extract(epoch from now())-7776000
+ and cust_main.custnum = cust_bill.custnum
+ )
+ ,0
+ ) as owed_90_plus,
+
+ coalesce(
+ ( select $charged from cust_bill
+ where cust_main.custnum = cust_bill.custnum
+ )
+ ,0
+ ) as owed_total
+END
+
+ my $recurring = <<END;
+ 0 < ( select freq from part_pkg
+ where cust_pkg.pkgpart = part_pkg.pkgpart )
+END
+
+ my $packages_cols = <<END;
+
+ ( select count(*) from cust_pkg
+ where cust_main.custnum = cust_pkg.custnum
+ and $recurring
+ and ( cancel = 0 or cancel is null )
+ ) as uncancelled_pkgs,
+
+ ( select count(*) from cust_pkg
+ where cust_main.custnum = cust_pkg.custnum
+ and $recurring
+ and ( cancel = 0 or cancel is null )
+ and ( susp = 0 or susp is null )
+ ) as active_pkgs
+
+END
+
+ my $sql = <<END;
+select *, $owed_cols, $packages_cols from cust_main
+where 0 <
+ coalesce(
+ ( select $charged from cust_bill
+ where cust_main.custnum = cust_bill.custnum
+ )
+ ,0
+ )
+
+order by lower(company), lower(last)
+
+END
+
+ my $total_sql = "select $owed_cols";
+
+ my $sth = dbh->prepare($sql) or die dbh->errstr;
+ $sth->execute or die $sth->errstr;
+
+ my $total_sth = dbh->prepare($total_sql) or die dbh->errstr;
+ $total_sth->execute or die $total_sth->errstr;
+
+%>
+<%= header('Accounts Receivable Aging Summary', menubar( 'Main Menu'=>$p, ) ) %>
+<%= table() %>
+ <TR>
+ <TH>Customer</TH>
+ <TH>Status</TH>
+ <TH>0-30</TH>
+ <TH>30-60</TH>
+ <TH>60-90</TH>
+ <TH>90+</TH>
+ <TH>Total</TH>
+ </TR>
+<% while ( my $row = $sth->fetchrow_hashref() ) {
+ my $status = 'Cancelled';
+ my $statuscol = 'FF0000';
+ if ( $row->{uncancelled_pkgs} ) {
+ $status = 'Suspended';
+ $statuscol = 'FF9900';
+ if ( $row->{active_pkgs} ) {
+ $status = 'Active';
+ $statuscol = '00CC00';
+ }
+ }
+%>
+ <TR>
+ <TD><A HREF="<%= $p %>view/cust_main.cgi?<%= $row->{'custnum'} %>"><%= $row->{'custnum'} %>:
+ <%= $row->{'company'} ? $row->{'company'}. ' (' : '' %><%= $row->{'last'}. ', '. $row->{'first'} %><%= $row->{'company'} ? ')' : '' %></A>
+ </TD>
+ <TD><B><FONT SIZE=-1 COLOR="#<%= $statuscol %>"><%= $status %></FONT></B></TD>
+ <TD ALIGN="right">$<%= sprintf("%.2f", $row->{'owed_0_30'} ) %></TD>
+ <TD ALIGN="right">$<%= sprintf("%.2f", $row->{'owed_30_60'} ) %></TD>
+ <TD ALIGN="right">$<%= sprintf("%.2f", $row->{'owed_60_90'} ) %></TD>
+ <TD ALIGN="right">$<%= sprintf("%.2f", $row->{'owed_90_plus'} ) %></TD>
+ <TD ALIGN="right"><B>$<%= sprintf("%.2f", $row->{'owed_total'} ) %></B></TD>
+ </TR>
+<% } %>
+<% my $row = $total_sth->fetchrow_hashref(); %>
+ <TR>
+ <TD COLSPAN=6>&nbsp;</TD>
+ </TR>
+ <TR>
+ <TD COLSPAN=2><I>Total</I></TD>
+ <TD ALIGN="right"><I>$<%= sprintf("%.2f", $row->{'owed_0_30'} ) %></TD>
+ <TD ALIGN="right"><I>$<%= sprintf("%.2f", $row->{'owed_30_60'} ) %></TD>
+ <TD ALIGN="right"><I>$<%= sprintf("%.2f", $row->{'owed_60_90'} ) %></TD>
+ <TD ALIGN="right"><I>$<%= sprintf("%.2f", $row->{'owed_90_plus'} ) %></TD>
+ <TD ALIGN="right"><I><B>$<%= sprintf("%.2f", $row->{'owed_total'} ) %></B></I></TD>
+ </TR>
+</TABLE>
+</BODY>
+</HTML>
diff --git a/httemplate/search/report_tax.html b/httemplate/search/report_tax.html
index 7bf681b..7bc35e3 100755
--- a/httemplate/search/report_tax.html
+++ b/httemplate/search/report_tax.html
@@ -1,23 +1,43 @@
<HTML>
<HEAD>
<TITLE>Tax Report Criteria</TITLE>
- </HEAD>
- <BODY>
- <CENTER>
- <H1>Tax Report Criteria</H1>
- </CENTER>
- <HR>
+ <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> </HEAD>
+ <BODY BGCOLOR="#e8e8e8">
+ <H1>Tax Report Criteria</H1>
<FORM ACTION="report_tax.cgi" METHOD="post">
- Return <B>tax report</B> for period:
- from <INPUT TYPE="text" NAME="beginning"> <i>m/d/y</i>
- to <INPUT TYPE="text" NAME="ending"> <i>m/d/y</i>
+ Return <B>tax report</B> for period:
+ <TABLE>
+ <TR>
+ <TD ALIGN="right">From: </TD>
+ <TD><INPUT TYPE="text" NAME="beginning" ID="beginning_text" VALUE="" SIZE=11 MAXLENGTH=10> <IMG SRC="../images/calendar.png" ID="beginning_button" STYLE="cursor: pointer" TITLE="Select date"><BR><i>m/d/y</i></TD>
+<SCRIPT TYPE="text/javascript">
+ Calendar.setup({
+ inputField: "beginning_text",
+ ifFormat: "%m/%d/%Y",
+ button: "beginning_button",
+ align: "BR"
+ });
+</SCRIPT>
+ </TR>
+ <TD ALIGN="right">To: </TD>
+ <TD><INPUT TYPE="text" NAME="ending" ID="ending_text" VALUE="" SIZE=11 MAXLENGTH=10> <IMG SRC="../images/calendar.png" ID="ending_button" STYLE="cursor: pointer" TITLE="Select date"><BR><i>m/d/y</i></TD>
+<SCRIPT TYPE="text/javascript">
+ Calendar.setup({
+ inputField: "ending_text",
+ ifFormat: "%m/%d/%Y",
+ button: "ending_button",
+ align: "BR"
+ });
+</SCRIPT>
+ </TR>
+ </TABLE>
- <P><INPUT TYPE="submit" VALUE="Get Report">
+ <BR><INPUT TYPE="submit" VALUE="Get Report">
</FORM>
-
- <HR>
-
</BODY>
</HTML>
diff --git a/httemplate/search/sql.cgi b/httemplate/search/sql.cgi
deleted file mode 100755
index b83ef03..0000000
--- a/httemplate/search/sql.cgi
+++ /dev/null
@@ -1,76 +0,0 @@
-<%
-
-my $conf = new FS::Conf;
-my $maxrecords = $conf->config('maxsearchrecordsperpage');
-
-my $limit = '';
-$limit .= "LIMIT $maxrecords" if $maxrecords;
-
-my $offset = $cgi->param('offset') || 0;
-$limit .= " OFFSET $offset" if $offset;
-
-my $total;
-
-my $sql = $cgi->param('sql');
-$sql =~ s/^\s*SELECT//i;
-
-my $count_sql = $sql;
-$count_sql =~ s/^(.*)\s+FROM\s/COUNT(*) FROM /i;
-
-my $sth = dbh->prepare("SELECT $count_sql")
- or eidiot dbh->errstr. " doing $count_sql\n";
-$sth->execute or eidiot "Error executing \"$count_sql\": ". $sth->errstr;
-
-$total = $sth->fetchrow_arrayref->[0];
-
-my $sth = dbh->prepare("SELECT $sql $limit")
- or eidiot dbh->errstr. " doing $sql\n";
-$sth->execute or eidiot "Error executing \"$sql\": ". $sth->errstr;
-my $rows = $sth->fetchall_arrayref;
-
-%>
-<!-- mason kludge -->
-<%
-
- #begin pager
- my $pager = '';
- if ( $total != scalar(@$rows) && $maxrecords ) {
- unless ( $offset == 0 ) {
- $cgi->param('offset', $offset - $maxrecords);
- $pager .= '<A HREF="'. $cgi->self_url.
- '"><B><FONT SIZE="+1">Previous</FONT></B></A> ';
- }
- my $poff;
- my $page;
- for ( $poff = 0; $poff < $total; $poff += $maxrecords ) {
- $page++;
- if ( $offset == $poff ) {
- $pager .= qq!<FONT SIZE="+2">$page</FONT> !;
- } else {
- $cgi->param('offset', $poff);
- $pager .= qq!<A HREF="!. $cgi->self_url. qq!">$page</A> !;
- }
- }
- unless ( $offset + $maxrecords > $total ) {
- $cgi->param('offset', $offset + $maxrecords);
- $pager .= '<A HREF="'. $cgi->self_url.
- '"><B><FONT SIZE="+1">Next</FONT></B></A> ';
- }
- }
- #end pager
-
- print header('Query Results', menubar('Main Menu'=>$p) ).
- "$total total rows<BR><BR>$pager". table().
- "<TR>";
- print "<TH>$_</TH>" foreach @{$sth->{NAME}};
- print "</TR>";
-
- foreach $row ( @$rows ) {
- print "<TR>";
- print "<TD>$_</TD>" foreach @$row;
- print "</TR>";
- }
-
- print "</TABLE>$pager</BODY></HTML>";
-
-%>
diff --git a/httemplate/search/sql.html b/httemplate/search/sql.html
new file mode 100644
index 0000000..7d7fc08
--- /dev/null
+++ b/httemplate/search/sql.html
@@ -0,0 +1,12 @@
+<%= include( '/elements/header.html', 'Query Results',
+ include( '/elements/menubar.html', 'Main Menu' => $p )
+ )
+%>
+
+<%= include( 'elements/search.html',
+ 'name' => 'rows',
+ 'query' => 'SELECT '. ( $cgi->param('sql')
+ || eidiot('Empty query') ),
+ )
+%>
+
diff --git a/httemplate/search/svc_acct.cgi b/httemplate/search/svc_acct.cgi
index 549231d..1e4a03d 100755
--- a/httemplate/search/svc_acct.cgi
+++ b/httemplate/search/svc_acct.cgi
@@ -1,7 +1,5 @@
<%
-my $mydomain = '';
-
my $conf = new FS::Conf;
my $maxrecords = $conf->config('maxsearchrecordsperpage');
@@ -32,7 +30,7 @@ if ( $query =~ /^UN_(.*)$/ ) {
WHERE 0 <
( SELECT count(*) FROM cust_svc
WHERE cust_svc.svcnum = svc_acct.svcnum
- AND ( pkgnum IS NULL OR pkgnum = 0 OR pkgnum = $empty )
+ AND ( pkgnum IS NULL OR pkgnum = 0 )
)
";
}
@@ -48,14 +46,31 @@ if ( $query eq 'svcnum' ) {
$orderby = "ORDER BY ${tblname}username";
} elsif ( $query eq 'uid' ) {
$sortby=\*uid_sort;
- $orderby = ( $unlinked ? 'AND' : 'WHERE' ).
+ $orderby = ( $unlinked ? ' AND' : ' WHERE' ).
" ${tblname}uid IS NOT NULL ORDER BY ${tblname}uid";
+} elsif ( $cgi->param('popnum') =~ /^(\d+)$/ ) {
+ $unlinked .= ( $unlinked ? 'AND' : 'WHERE' ).
+ " popnum = $1";
+ $sortby=\*username_sort;
+ $orderby = "ORDER BY ${tblname}username";
+} elsif ( $cgi->param('svcpart') =~ /^(\d+)$/ ) {
+ $unlinked .= ( $unlinked ? ' AND' : ' WHERE' ).
+ " $1 = ( SELECT svcpart FROM cust_svc ".
+ " WHERE cust_svc.svcnum = svc_acct.svcnum ) ";
+ $sortby=\*uid_sort;
+ #$sortby=\*svcnum_sort;
} else {
$sortby=\*uid_sort;
@svc_acct = @{&usernamesearch};
}
-if ( $query eq 'svcnum' || $query eq 'username' || $query eq 'uid' ) {
+
+if ( $query eq 'svcnum'
+ || $query eq 'username'
+ || $query eq 'uid'
+ || $cgi->param('popnum') =~ /^(\d+)$/
+ || $cgi->param('svcpart') =~ /^(\d+)$/
+ ) {
my $statement = "SELECT COUNT(*) FROM svc_acct $unlinked";
my $sth = dbh->prepare($statement)
@@ -147,14 +162,8 @@ END
$domain = "<A HREF=\"${p}view/svc_domain.cgi?". $svc_domain->svcnum.
"\">". $svc_domain->domain. "</A>";
} else {
- unless ( $mydomain ) {
- my $conf = new FS::Conf;
- unless ( $mydomain = $conf->config('domain') ) {
- die "No legacy domain config file and no svc_domain.svcnum record ".
- "for svc_acct.domsvc: ". $svc_acct->domsvc;
- }
- }
- $domain = "<i>$mydomain</i><FONT COLOR=\"#FF0000\">*</FONT>";
+ die "No svc_domain.svcnum record for svc_acct.domsvc: ".
+ $svc_acct->domsvc;
}
my($cust_pkg,$cust_main);
if ( $cust_svc->pkgnum ) {
@@ -216,17 +225,8 @@ END
}
- print "</TABLE>$pager<BR>";
-
- if ( $mydomain ) {
- print "<BR><FONT COLOR=\"#FF0000\">*</FONT> The <I>$mydomain</I> domain ".
- "is contained in your legacy <CODE>domain</CODE> ".
- "<A HREF=\"${p}docs/config.html#domain\">configuration file</A>. ".
- "You should run the <CODE>bin/fs-migrate-svc_acct_sm</CODE> script ".
- "to create a proper svc_domain record for this domain.";
- }
-
- print '</BODY></HTML>';
+ print "</TABLE>$pager<BR>".
+ '</BODY></HTML>';
}
diff --git a/httemplate/search/svc_acct_sm.cgi b/httemplate/search/svc_acct_sm.cgi
deleted file mode 100755
index 4ee3006..0000000
--- a/httemplate/search/svc_acct_sm.cgi
+++ /dev/null
@@ -1,84 +0,0 @@
-<%
-
-my $conf = new FS::Conf;
-my $mydomain = $conf->config('domain');
-
-$cgi->param('domuser') =~ /^([a-z0-9_\-]{0,32})$/;
-my $domuser = $1;
-
-$cgi->param('domain') =~ /^([\w\-\.]+)$/ or die "Illegal domain";
-my $svc_domain = qsearchs('svc_domain',{'domain'=>$1})
- or die "Unknown domain";
-my $domsvc = $svc_domain->svcnum;
-
-my @svc_acct_sm;
-if ($domuser) {
- @svc_acct_sm=qsearch('svc_acct_sm',{
- 'domuser' => $domuser,
- 'domsvc' => $domsvc,
- });
-} else {
- @svc_acct_sm=qsearch('svc_acct_sm',{'domsvc' => $domsvc});
-}
-
-if ( scalar(@svc_acct_sm) == 1 ) {
- my($svcnum)=$svc_acct_sm[0]->svcnum;
- print $cgi->redirect(popurl(2). "view/svc_acct_sm.cgi?$svcnum");
-} elsif ( scalar(@svc_acct_sm) > 1 ) {
-%>
-<!-- mason kludge -->
-<%
- print header('Mail Alias Search Results'), &table(), <<END;
- <TR>
- <TH>Mail to<BR><FONT SIZE=-1>(click to view mail alias)</FONT></TH>
- <TH>Forwards to<BR><FONT SIZE=-1>(click to view account)</FONT></TH>
- </TR>
-END
-
- my($svc_acct_sm);
- foreach $svc_acct_sm (@svc_acct_sm) {
- my($svcnum,$domuser,$domuid,$domsvc)=(
- $svc_acct_sm->svcnum,
- $svc_acct_sm->domuser,
- $svc_acct_sm->domuid,
- $svc_acct_sm->domsvc,
- );
-
- my $svc_domain = qsearchs( 'svc_domain', { 'svcnum' => $domsvc } );
- if ( $svc_domain ) {
- my $domain = $svc_domain->domain;
-
- print qq!<TR><TD><A HREF="!. popurl(2). qq!view/svc_acct_sm.cgi?$svcnum">!,
- #print '', ( ($domuser eq '*') ? "<I>(anything)</I>" : $domuser );
- ( ($domuser eq '*') ? "<I>(anything)</I>" : $domuser ),
- qq!\@$domain</A> </TD>!,
- ;
- } else {
- my $warning = "couldn't find svc_domain.svcnum $svcnum ( svc_acct_sm.svcnum $svcnum";
- warn $warning;
- print "<TR><TD>WARNING: $warning</TD>";
- }
-
- my $svc_acct = qsearchs( 'svc_acct', { 'uid' => $domuid } );
- if ( $svc_acct ) {
- my $username = $svc_acct->username;
- my $svc_acct_svcnum =$svc_acct->svcnum;
- print qq!<TD><A HREF="!, popurl(2),
- qq!view/svc_acct.cgi?$svc_acct_svcnum">$username\@$mydomain</A>!,
- qq!</TD></TR>!
- ;
- } else {
- my $warning = "couldn't find svc_acct.uid $domuid (svc_acct_sm.svcnum $svcnum)!";
- warn $warning;
- print "<TD>WARNING: $warning</TD></TR>";
- }
-
- }
-
- print '</TABLE></BODY></HTML>';
-
-} else { #error
- idiot("Mail Alias not found");
-}
-
-%>
diff --git a/httemplate/search/svc_acct_sm.html b/httemplate/search/svc_acct_sm.html
deleted file mode 100755
index 0719856..0000000
--- a/httemplate/search/svc_acct_sm.html
+++ /dev/null
@@ -1,23 +0,0 @@
-<HTML>
- <HEAD>
- <TITLE>Mail Alias Search</TITLE>
- </HEAD>
- <BODY>
- <CENTER>
- <H1>Mail Alias Search</H1>
- </CENTER>
- <HR>
- <FORM ACTION="svc_acct_sm.cgi" METHOD="post">
- Search for <B>mail alias</B>:
- <INPUT TYPE="text" NAME="domuser"><FONT SIZE=-1>(opt.)</FONT> @
- <INPUT TYPE="text" NAME="domain"><FONT SIZE=-1>(req.)</FONT>
-
- <P><INPUT TYPE="submit" VALUE="Search">
-
- </FORM>
-
- <HR>
-
- </BODY>
-</HTML>
-
diff --git a/httemplate/search/svc_domain.cgi b/httemplate/search/svc_domain.cgi
index fb372db..948b1d9 100755
--- a/httemplate/search/svc_domain.cgi
+++ b/httemplate/search/svc_domain.cgi
@@ -1,7 +1,6 @@
<%
my $conf = new FS::Conf;
-my $mydomain = $conf->config('domain');
my($query)=$cgi->keywords;
$query ||= ''; #to avoid use of unitialized value errors
@@ -24,6 +23,13 @@ if ( $query eq 'svcnum' ) {
'svcnum' => $_->svcnum,
'pkgnum' => '',
}), qsearch('svc_domain',{});
+} elsif ( $cgi->param('svcpart') =~ /^(\d+)$/ ) {
+ @svc_domain =
+ qsearch( 'svc_domain', {}, '',
+ " WHERE $1 = ( SELECT svcpart FROM cust_svc ".
+ " WHERE cust_svc.svcnum = svc_domain.svcnum ) "
+ );
+ $sortby=\*svcnum_sort;
} else {
$cgi->param('domain') =~ /^([\w\-\.]+)$/;
my($domain)=$1;
@@ -51,8 +57,9 @@ if ( scalar(@svc_domain) == 1 ) {
<TR>
<TH>Service #</TH>
<TH>Domain</TH>
- <TH>Mail to<BR><FONT SIZE=-1>(click to view account)</FONT></TH>
+<!-- <TH>Mail to<BR><FONT SIZE=-1>(click to view account)</FONT></TH>
<TH>Forwards to<BR><FONT SIZE=-1>(click to view account)</FONT></TH>
+-->
</TR>
END
@@ -67,78 +74,69 @@ END
$svc_domain->svcnum,
$svc_domain->domain,
);
- #my($malias);
- #if ( qsearch('svc_acct_sm',{'domsvc'=>$svcnum}) ) {
- # $malias=(
- # qq|<FORM ACTION="svc_acct_sm.cgi" METHOD="post">|.
- # qq|<INPUT TYPE="hidden" NAME="domuser" VALUE="">|.
- # qq|<INPUT TYPE="hidden" NAME="domain" VALUE="$domain">|.
- # qq|<INPUT TYPE="submit" VALUE="(mail aliases)">|.
- # qq|</FORM>|
+
+ #don't display all accounts here
+ my $rowspan = 1;
+
+ #my @svc_acct=qsearch('svc_acct',{'domsvc' => $svcnum});
+ #my $rowspan = 0;
+ #
+ #my $n1 = '';
+ #my($svc_acct, @rows);
+ #foreach $svc_acct (
+ # sort {$b->getfield('username') cmp $a->getfield('username')} (@svc_acct)
+ #) {
+ #
+ # my (@forwards) = ();
+ #
+ # my($svcnum,$username)=(
+ # $svc_acct->svcnum,
+ # $svc_acct->username,
# );
- #} else {
- # $malias='';
+ #
+ # my @svc_forward = qsearch( 'svc_forward', { 'srcsvc' => $svcnum } );
+ # my $svc_forward;
+ # foreach $svc_forward (@svc_forward) {
+ # my($dstsvc,$dst) = (
+ # $svc_forward->dstsvc,
+ # $svc_forward->dst,
+ # );
+ # if ($dstsvc) {
+ # my $dst_svc_acct=qsearchs( 'svc_acct', { 'svcnum' => $dstsvc } );
+ # my $destination=$dst_svc_acct->email;
+ # push @forwards, qq!<TD><A HREF="!, popurl(2),
+ # qq!view/svc_acct.cgi?$dstsvc">$destination</A>!,
+ # qq!</TD></TR>!
+ # ;
+ # }else{
+ # push @forwards, qq!<TD>$dst</TD></TR>!
+ # ;
+ # }
+ # }
+ #
+ # push @rows, qq!$n1<TD ROWSPAN=!, (scalar(@svc_forward) || 1),
+ # qq!><A HREF="!. popurl(2). qq!view/svc_acct.cgi?$svcnum">!,
+ # #print '', ( ($domuser eq '*') ? "<I>(anything)</I>" : $domuser );
+ # ( ($username eq '*') ? "<I>(anything)</I>" : $username ),
+ # qq!\@$domain</A> </TD>!,
+ # ;
+ #
+ # push @rows, @forwards;
+ #
+ # $rowspan += (scalar(@svc_forward) || 1);
+ # $n1 = "</TR><TR>";
#}
-
- my @svc_acct=qsearch('svc_acct',{'domsvc' => $svcnum});
- my $rowspan = 0;
-
- my $n1 = '';
- my($svc_acct, @rows);
- foreach $svc_acct (
- sort {$b->getfield('username') cmp $a->getfield('username')} (@svc_acct)
- ) {
-
- my (@forwards) = ();
-
- my($svcnum,$username)=(
- $svc_acct->svcnum,
- $svc_acct->username,
- );
-
- my @svc_forward = qsearch( 'svc_forward', { 'srcsvc' => $svcnum } );
- my $svc_forward;
- foreach $svc_forward (@svc_forward) {
- my($dstsvc,$dst) = (
- $svc_forward->dstsvc,
- $svc_forward->dst,
- );
- if ($dstsvc) {
- my $dst_svc_acct=qsearchs( 'svc_acct', { 'svcnum' => $dstsvc } );
- my $destination=$dst_svc_acct->email;
- push @forwards, qq!<TD><A HREF="!, popurl(2),
- qq!view/svc_acct.cgi?$dstsvc">$destination</A>!,
- qq!</TD></TR>!
- ;
- }else{
- push @forwards, qq!<TD>$dst</TD></TR>!
- ;
- }
- }
-
- push @rows, qq!$n1<TD ROWSPAN=!, (scalar(@svc_forward) || 1),
- qq!><A HREF="!. popurl(2). qq!view/svc_acct.cgi?$svcnum">!,
- #print '', ( ($domuser eq '*') ? "<I>(anything)</I>" : $domuser );
- ( ($username eq '*') ? "<I>(anything)</I>" : $username ),
- qq!\@$domain</A> </TD>!,
- ;
-
- push @rows, @forwards;
-
- $rowspan += (scalar(@svc_forward) || 1);
- $n1 = "</TR><TR>";
- }
- #end of false laziness
-
-
+ ##end of false laziness
+ #
+ #
print <<END;
<TR>
- <TD ROWSPAN=$rowspan><A HREF="${p}view/svc_domain.cgi?$svcnum"><FONT SIZE=-1>$svcnum</FONT></A></TD>
- <TD ROWSPAN=$rowspan>$domain</TD>
+ <TD ROWSPAN=$rowspan><A HREF="${p}view/svc_domain.cgi?$svcnum">$svcnum</A></TD>
+ <TD ROWSPAN=$rowspan><A HREF="${p}view/svc_domain.cgi?$svcnum">$domain</A></TD>
END
- print @rows;
+ #print @rows;
print "</TR>";
}
diff --git a/httemplate/search/svc_forward.cgi b/httemplate/search/svc_forward.cgi
new file mode 100755
index 0000000..10094bc
--- /dev/null
+++ b/httemplate/search/svc_forward.cgi
@@ -0,0 +1,79 @@
+<%
+
+my $conf = new FS::Conf;
+
+my($query)=$cgi->keywords;
+$query ||= ''; #to avoid use of unitialized value errors
+my(@svc_forward,$sortby);
+if ( $query eq 'svcnum' ) {
+ $sortby=\*svcnum_sort;
+ @svc_forward=qsearch('svc_forward',{});
+} else {
+ eidiot('unimplemented');
+}
+
+if ( scalar(@svc_forward) == 1 ) {
+ print $cgi->redirect(popurl(2). "view/svc_forward.cgi?". $svc_forward[0]->svcnum);
+ #exit;
+} elsif ( scalar(@svc_forward) == 0 ) {
+%>
+<!-- mason kludge -->
+<%
+ eidiot "No matching forwards found!\n";
+} else {
+%>
+<!-- mason kludge -->
+<%
+ my $total = scalar(@svc_forward);
+ print header("Mail forward Search Results",''), <<END;
+
+ $total matching mail forwards found
+ <TABLE BORDER=4 CELLSPACING=0 CELLPADDING=0>
+ <TR>
+ <TH>Service #<BR><FONT SIZE=-1>(click to view forward)</FONT></TH>
+ <TH>Mail to<BR><FONT SIZE=-1>(click to view account)</FONT></TH>
+ <TH>Forwards to<BR><FONT SIZE=-1>(click to view account)</FONT></TH>
+ </TR>
+END
+
+ foreach my $svc_forward (
+ sort $sortby (@svc_forward)
+ ) {
+ my $svcnum = $svc_forward->svcnum;
+
+ my $src = $svc_forward->src;
+ $src = "<I>(anything)</I>$src" if $src =~ /^@/;
+ if ( $svc_forward->srcsvc_acct ) {
+ $src = qq!<A HREF="${p}view/svc_acct.cgi?!. $svc_forward->srcsvc. '">'.
+ $svc_forward->srcsvc_acct->email. '</A>';
+ }
+
+ my $dst = $svc_forward->dst;
+ if ( $svc_forward->dstsvc_acct ) {
+ $dst = qq!<A HREF="${p}view/svc_acct.cgi?!. $svc_forward->dstsvc. '">'.
+ $svc_forward->dstsvc_acct->email. '</A>';
+ }
+
+ print <<END;
+ <TR>
+ <TD><A HREF="${p}view/svc_forward.cgi?$svcnum">$svcnum</A></TD>
+ <TD>$src</TD>
+ <TD>$dst</TD>
+ </TR>
+END
+
+ }
+
+ print <<END;
+ </TABLE>
+ </BODY>
+</HTML>
+END
+
+}
+
+sub svcnum_sort {
+ $a->getfield('svcnum') <=> $b->getfield('svcnum');
+}
+
+%>
diff --git a/httemplate/view/cust_bill-pdf.cgi b/httemplate/view/cust_bill-pdf.cgi
new file mode 100755
index 0000000..7aa6910
--- /dev/null
+++ b/httemplate/view/cust_bill-pdf.cgi
@@ -0,0 +1,13 @@
+<%
+
+#untaint invnum
+my($query) = $cgi->keywords;
+$query =~ /^(\d+)$/;
+my $invnum = $1;
+
+my $cust_bill = qsearchs('cust_bill',{'invnum'=>$invnum});
+die "Invoice #$invnum not found!" unless $cust_bill;
+
+http_header('Content-Type' => 'application/pdf' );
+%>
+<%= $cust_bill->print_pdf %>
diff --git a/httemplate/view/cust_bill-ps.cgi b/httemplate/view/cust_bill-ps.cgi
new file mode 100755
index 0000000..03340a1
--- /dev/null
+++ b/httemplate/view/cust_bill-ps.cgi
@@ -0,0 +1,13 @@
+<%
+
+#untaint invnum
+my($query) = $cgi->keywords;
+$query =~ /^(\d+)$/;
+my $invnum = $1;
+
+my $cust_bill = qsearchs('cust_bill',{'invnum'=>$invnum});
+die "Invoice #$invnum not found!" unless $cust_bill;
+
+http_header('Content-Type' => 'application/postscript' );
+%>
+<%= $cust_bill->print_ps %>
diff --git a/httemplate/view/cust_bill.cgi b/httemplate/view/cust_bill.cgi
index 53d7bc0..50ee8b3 100755
--- a/httemplate/view/cust_bill.cgi
+++ b/httemplate/view/cust_bill.cgi
@@ -20,7 +20,20 @@ print header('Invoice View', menubar(
print qq!<A HREF="${p}edit/cust_pay.cgi?$invnum">Enter payments (check/cash) against this invoice</A> | !
if $cust_bill->owed > 0;
-print qq!<A HREF="${p}misc/print-invoice.cgi?$invnum">Reprint this invoice</A>!. '<BR><BR>';
+print qq!<A HREF="${p}misc/print-invoice.cgi?$invnum">Reprint this invoice</A>!;
+if ( grep { $_ ne 'POST' } $cust_bill->cust_main->invoicing_list ) {
+ print qq! | <A HREF="${p}misc/email-invoice.cgi?$invnum">!.
+ qq!Re-email this invoice</A>!;
+}
+
+print '<BR><BR>';
+
+my $conf = new FS::Conf;
+if ( $conf->exists('invoice_latex') ) {
+ print menubar(
+ 'View typeset invoice' => "${p}view/cust_bill-pdf.cgi?$invnum",
+ ), '<BR><BR>';
+}
#false laziness with search/cust_bill_event.cgi
diff --git a/httemplate/view/cust_main.cgi b/httemplate/view/cust_main.cgi
index 421bd98..ee5f869 100755
--- a/httemplate/view/cust_main.cgi
+++ b/httemplate/view/cust_main.cgi
@@ -15,6 +15,15 @@ print header("Customer View", menubar(
'Main Menu' => popurl(2)
));
+print <<END;
+<STYLE TYPE="text/css">
+.package TH { font-size: medium }
+.package TR { font-size: smaller }
+.package .pkgnum { font-size: medium }
+.package .provision { font-weight: bold }
+</STYLE>
+END
+
die "No customer specified (bad URL)!" unless $cgi->keywords;
my($query) = $cgi->keywords; # needs parens with my, ->keywords returns array
$query =~ /^(\d+)$/;
@@ -22,10 +31,23 @@ my $custnum = $1;
my $cust_main = qsearchs('cust_main',{'custnum'=>$custnum});
die "Customer not found!" unless $cust_main;
-print qq!<A HREF="!, popurl(2),
- qq!edit/cust_main.cgi?$custnum">Edit this customer</A>!;
-print qq! | <A HREF="!, popurl(2),
- qq!misc/delete-customer.cgi?$custnum"> Delete this customer</A>!
+print qq!<A HREF="${p}edit/cust_main.cgi?$custnum">Edit this customer</A>!;
+
+print <<END;
+<SCRIPT>
+function cancel_areyousure(href) {
+ if (confirm("Perminantly delete all services and cancel this customer?") == true)
+ window.location.href = href;
+}
+</SCRIPT>
+END
+
+print qq! | <A HREF="javascript:cancel_areyousure('${p}misc/cust_main-cancel.cgi?$custnum')">!.
+ 'Cancel this customer</A>'
+ if $cust_main->ncancelled_pkgs;
+
+print qq! | <A HREF="${p}misc/delete-customer.cgi?$custnum">!.
+ 'Delete this customer</A>'
if $conf->exists('deletecustomers');
unless ( $conf->exists('disable_customer_referrals') ) {
@@ -54,7 +76,7 @@ print '<TD VALIGN="top">';
print "Billing address", &ntable("#cccccc"), "<TR><TD>",
&ntable("#cccccc",2),
- '<TR><TD ALIGN="right">Contact name</TD>',
+ '<TR><TD ALIGN="right">Contact&nbsp;name</TD>',
'<TD COLSPAN=3 BGCOLOR="#ffffff">',
$cust_main->last, ', ', $cust_main->first,
'</TD>';
@@ -83,9 +105,13 @@ print '</TR>',
$cust_main->country,
'</TD></TR>',
;
- print '<TR><TD ALIGN="right">Day Phone</TD><TD COLSPAN=5 BGCOLOR="#ffffff">',
+ my $daytime_label = FS::Msgcat::_gettext('daytime') || 'Day&nbsp;Phone';
+ my $night_label = FS::Msgcat::_gettext('night') || 'Night&nbsp;Phone';
+ print '<TR><TD ALIGN="right">'. $daytime_label.
+ '</TD><TD COLSPAN=5 BGCOLOR="#ffffff">',
$cust_main->daytime || '&nbsp', '</TD></TR>',
- '<TR><TD ALIGN="right">Night Phone</TD><TD COLSPAN=5 BGCOLOR="#ffffff">',
+ '<TR><TD ALIGN="right">'. $night_label.
+ '</TD><TD COLSPAN=5 BGCOLOR="#ffffff">',
$cust_main->night || '&nbsp', '</TD></TR>',
'<TR><TD ALIGN="right">Fax</TD><TD COLSPAN=5 BGCOLOR="#ffffff">',
$cust_main->fax || '&nbsp', '</TD></TR>',
@@ -122,10 +148,10 @@ print '</TR>',
$cust_main->get("${pre}country"),
'</TD></TR>',
;
- print '<TR><TD ALIGN="right">Day Phone</TD>',
+ print '<TR><TD ALIGN="right">'. $daytime_label. '</TD>',
'<TD COLSPAN=5 BGCOLOR="#ffffff">',
$cust_main->get("${pre}daytime") || '&nbsp', '</TD></TR>',
- '<TR><TD ALIGN="right">Night Phone</TD>'.
+ '<TR><TD ALIGN="right">'. $night_label. '</TD>'.
'<TD COLSPAN=5 BGCOLOR="#ffffff">',
$cust_main->get("${pre}night") || '&nbsp', '</TD></TR>',
'<TR><TD ALIGN="right">Fax</TD><TD COLSPAN=5 BGCOLOR="#ffffff">',
@@ -140,7 +166,7 @@ print '</TD>';
print '<TD VALIGN="top">';
print &ntable("#cccccc"), "<TR><TD>", &ntable("#cccccc",2),
- '<TR><TD ALIGN="right">Customer number</TD><TD BGCOLOR="#ffffff">',
+ '<TR><TD ALIGN="right">Customer&nbsp;number</TD><TD BGCOLOR="#ffffff">',
$custnum, '</TD></TR>',
;
@@ -158,13 +184,13 @@ print '<TD VALIGN="top">';
my $referral = qsearchs('part_referral', {
'refnum' => $cust_main->refnum
} );
- print '<TR><TD ALIGN="right">Advertising source</TD><TD BGCOLOR="#ffffff">',
+ print '<TR><TD ALIGN="right">Advertising&nbsp;source</TD><TD BGCOLOR="#ffffff">',
$referral->refnum, ": ", $referral->referral, '</TD></TR>';
}
print '<TR><TD ALIGN="right">Order taker</TD><TD BGCOLOR="#ffffff">',
$cust_main->otaker, '</TD></TR>';
- print '<TR><TD ALIGN="right">Referring Customer</TD><TD BGCOLOR="#ffffff">';
+ print '<TR><TD ALIGN="right">Referring&nbsp;Customer</TD><TD BGCOLOR="#ffffff">';
my $referring_cust_main = '';
if ( $cust_main->referral_custnum
&& ( $referring_cust_main =
@@ -188,26 +214,30 @@ print '<TD VALIGN="top">';
print '<BR>';
+if ( $conf->config('payby-default') ne 'HIDE' ) {
+
my @invoicing_list = $cust_main->invoicing_list;
print "Billing information (",
qq!<A HREF="!, popurl(2), qq!misc/bill.cgi?$custnum">!, "Bill now</A>)",
&ntable("#cccccc"), "<TR><TD>", &ntable("#cccccc",2),
- '<TR><TD ALIGN="right">Tax exempt</TD><TD BGCOLOR="#ffffff">',
+ '<TR><TD ALIGN="right">Tax&nbsp;exempt</TD><TD BGCOLOR="#ffffff">',
$cust_main->tax ? 'yes' : 'no',
'</TD></TR>',
- '<TR><TD ALIGN="right">Postal invoices</TD><TD BGCOLOR="#ffffff">',
+ '<TR><TD ALIGN="right">Postal&nbsp;invoices</TD><TD BGCOLOR="#ffffff">',
( grep { $_ eq 'POST' } @invoicing_list ) ? 'yes' : 'no',
'</TD></TR>',
- '<TR><TD ALIGN="right">Email invoices</TD><TD BGCOLOR="#ffffff">',
+ '<TR><TD ALIGN="right">Email&nbsp;invoices</TD><TD BGCOLOR="#ffffff">',
join(', ', grep { $_ ne 'POST' } @invoicing_list ) || 'no',
'</TD></TR>',
- '<TR><TD ALIGN="right">Billing type</TD><TD BGCOLOR="#ffffff">',
+ '<TR><TD ALIGN="right">Billing&nbsp;type</TD><TD BGCOLOR="#ffffff">',
;
- if ( $cust_main->payby eq 'CARD' ) {
+ if ( $cust_main->payby eq 'CARD' || $cust_main->payby eq 'DCRD' ) {
my $payinfo = $cust_main->payinfo;
$payinfo = 'x'x(length($payinfo)-4). substr($payinfo,(length($payinfo)-4));
- print 'Credit card</TD></TR>',
+ print 'Credit&nbsp;card&nbsp;',
+ ( $cust_main->payby eq 'CARD' ? '(automatic)' : '(on-demand)' ),
+ '</TD></TR>',
'<TR><TD ALIGN="right">Card number</TD><TD BGCOLOR="#ffffff">',
$payinfo, '</TD></TR>',
'<TR><TD ALIGN="right">Expiration</TD><TD BGCOLOR="#ffffff">',
@@ -215,6 +245,25 @@ print '<BR>';
'<TR><TD ALIGN="right">Name on card</TD><TD BGCOLOR="#ffffff">',
$cust_main->payname, '</TD></TR>'
;
+ } elsif ( $cust_main->payby eq 'CHEK' || $cust_main->payby eq 'DCHK') {
+ my( $account, $aba ) = split('@', $cust_main->payinfo );
+ print 'Electronic&nbsp;check&nbsp;',
+ ( $cust_main->payby eq 'CHEK' ? '(automatic)' : '(on-demand)' ),
+ '</TD></TR>',
+ '<TR><TD ALIGN="right">Account number</TD><TD BGCOLOR="#ffffff">',
+ $account, '</TD></TR>',
+ '<TR><TD ALIGN="right">ABA/Routing code</TD><TD BGCOLOR="#ffffff">',
+ $aba, '</TD></TR>',
+ '<TR><TD ALIGN="right">Bank name</TD><TD BGCOLOR="#ffffff">',
+ $cust_main->payname, '</TD></TR>'
+ ;
+ } elsif ( $cust_main->payby eq 'LECB' ) {
+ $cust_main->payinfo =~ /^(\d{3})(\d{3})(\d{4})$/;
+ my $payinfo = "$1-$2-$3";
+ print 'Phone&nbsp;bill&nbsp;billing</TD></TR>',
+ '<TR><TD ALIGN="right">Phone number</TD><TD BGCOLOR="#ffffff">',
+ $payinfo, '</TD></TR>',
+ ;
} elsif ( $cust_main->payby eq 'BILL' ) {
print 'Billing</TD></TR>';
print '<TR><TD ALIGN="right">P.O. </TD><TD BGCOLOR="#ffffff">',
@@ -227,7 +276,7 @@ print '<BR>';
;
} elsif ( $cust_main->payby eq 'COMP' ) {
print 'Complimentary</TD></TR>',
- '<TR><TD ALIGN="right">Authorized by</TD><TD BGCOLOR="#ffffff">',
+ '<TR><TD ALIGN="right">Authorized&nbsp;by</TD><TD BGCOLOR="#ffffff">',
$cust_main->payinfo, '</TD></TR>',
'<TR><TD ALIGN="right">Expiration</TD><TD BGCOLOR="#ffffff">',
$cust_main->paydate, '</TD></TR>',
@@ -236,14 +285,17 @@ print '<BR>';
print "</TABLE></TD></TR></TABLE>";
+}
+
print '</TD></TR></TABLE>';
if ( defined $cust_main->dbdef_table->column('comments')
- && $cust_main->comments )
+ && $cust_main->comments =~ /[^\s\n\r]/ )
{
- print "<BR>Comments", &ntable("#cccccc"), "<TR><TD>",
- &ntable("#cccccc",2),
- '<TR><TD BGCOLOR="#ffffff"><PRE>', $cust_main->comments,
+ print "<BR>Comments". &ntable("#cccccc"). "<TR><TD>".
+ &ntable("#cccccc",2).
+ '<TR><TD BGCOLOR="#ffffff"><PRE>'.
+ encode_entities($cust_main->comments).
'</PRE></TD></TR></TABLE></TABLE>';
}
@@ -254,43 +306,47 @@ print '<BR>'.
qq!<INPUT TYPE="hidden" NAME="custnum" VALUE="$custnum">!.
'<SELECT NAME="pkgpart"><OPTION> ';
-foreach my $type_pkgs ( qsearch('type_pkgs',{'typenum'=> $agent->typenum }) ) {
- my $pkgpart = $type_pkgs->pkgpart;
-# my $part_pkg = qsearchs('part_pkg', { 'pkgpart' => $pkgpart } )
-# or do { warn "unknown type_pkgs.pkgpart $pkgpart"; next; };
- my $part_pkg =
- qsearchs('part_pkg', { 'pkgpart' => $pkgpart, 'disabled' => '' } )
- or next;
- print qq!<OPTION VALUE="$pkgpart">!. $part_pkg->pkg. ' - '.
+foreach my $part_pkg (
+ qsearch( 'part_pkg', { 'disabled' => '' }, '',
+ ' AND 0 < ( SELECT COUNT(*) FROM type_pkgs '.
+ ' WHERE typenum = '. $agent->typenum.
+ ' AND type_pkgs.pkgpart = part_pkg.pkgpart )'
+ )
+) {
+ print '<OPTION VALUE="'. $part_pkg->pkgpart. '">'. $part_pkg->pkg. ' - '.
$part_pkg->comment;
}
print '</SELECT><INPUT TYPE="submit" VALUE="Order Package"></FORM><BR>';
-print '<BR>'.
- qq!<FORM ACTION="${p}edit/process/quick-charge.cgi" METHOD="POST">!.
- qq!<INPUT TYPE="hidden" NAME="custnum" VALUE="$custnum">!.
- qq!Description:<INPUT TYPE="text" NAME="pkg">!.
- qq!&nbsp;Amount:<INPUT TYPE="text" NAME="amount" SIZE=6>!.
- qq!&nbsp;!;
-
-#false laziness w/ edit/part_pkg.cgi
-if ( $conf->exists('enable_taxclasses') ) {
- print '<SELECT NAME="taxclass">';
- my $sth = dbh->prepare('SELECT DISTINCT taxclass FROM cust_main_county')
- or die dbh->errstr;
- $sth->execute or die $sth->errstr;
- foreach my $taxclass ( map $_->[0], @{$sth->fetchall_arrayref} ) {
- print qq!<OPTION VALUE="$taxclass"!;
- #print ' SELECTED' if $taxclass eq $hashref->{taxclass};
- print qq!>$taxclass</OPTION>!;
+if ( $conf->config('payby-default') ne 'HIDE' ) {
+
+ print '<BR>'.
+ qq!<FORM ACTION="${p}edit/process/quick-charge.cgi" METHOD="POST">!.
+ qq!<INPUT TYPE="hidden" NAME="custnum" VALUE="$custnum">!.
+ qq!Description:<INPUT TYPE="text" NAME="pkg">!.
+ qq!&nbsp;Amount:<INPUT TYPE="text" NAME="amount" SIZE=6>!.
+ qq!&nbsp;!;
+
+ #false laziness w/ edit/part_pkg.cgi
+ if ( $conf->exists('enable_taxclasses') ) {
+ print '<SELECT NAME="taxclass">';
+ my $sth = dbh->prepare('SELECT DISTINCT taxclass FROM cust_main_county')
+ or die dbh->errstr;
+ $sth->execute or die $sth->errstr;
+ foreach my $taxclass ( map $_->[0], @{$sth->fetchall_arrayref} ) {
+ print qq!<OPTION VALUE="$taxclass"!;
+ #print ' SELECTED' if $taxclass eq $hashref->{taxclass};
+ print qq!>$taxclass</OPTION>!;
+ }
+ print '</SELECT>';
+ } else {
+ print '<INPUT TYPE="hidden" NAME="taxclass" VALUE="">';
}
- print '</SELECT>';
-} else {
- print '<INPUT TYPE="hidden" NAME="taxclass" VALUE="">';
-}
+
+ print qq!<INPUT TYPE="submit" VALUE="One-time charge"></FORM><BR>!;
-print qq!<INPUT TYPE="submit" VALUE="One-time charge"></FORM><BR>!;
+}
print <<END;
<SCRIPT>
@@ -298,6 +354,10 @@ function cust_pkg_areyousure(href) {
if (confirm("Permanently delete included services and cancel this package?") == true)
window.location.href = href;
}
+function svc_areyousure(href) {
+ if (confirm("Permanently unprovision and delete this service?") == true)
+ window.location.href = href;
+}
</SCRIPT>
END
@@ -306,131 +366,181 @@ print qq!<BR><A NAME="cust_pkg">Packages</A> !,
qq!( <A HREF="!, popurl(2), qq!edit/cust_pkg.cgi?$custnum">Order and cancel packages</A> (preserves services) )!,
;
-#display packages
-
-#formatting
-print qq!!, &table(), "\n",
- qq!<TR><TH COLSPAN=2 ROWSPAN=2>Package</TH><TH COLSPAN=5>!,
- qq!Dates</TH><TH COLSPAN=2 ROWSPAN=2>Services</TH></TR>\n!,
- qq!<TR><TH><FONT SIZE=-1>Setup</FONT></TH><TH>!,
- qq!<FONT SIZE=-1>Next bill</FONT>!,
- qq!</TH><TH><FONT SIZE=-1>Susp.</FONT></TH><TH><FONT SIZE=-1>Expire!,
- qq!</FONT></TH>!,
- qq!<TH><FONT SIZE=-1>Cancel</FONT></TH>!,
- qq!</TR>\n!;
+#begin display packages
#get package info
-my @packages;
-if ( $conf->exists('hidecancelledpackages') ) {
- @packages = sort { $a->pkgnum <=> $b->pkgnum } ($cust_main->ncancelled_pkgs);
-} else {
- @packages = sort { $a->pkgnum <=> $b->pkgnum } ($cust_main->all_pkgs);
-}
-my $n1 = '<TR>';
-foreach my $package (@packages) {
- my $pkgnum = $package->pkgnum;
- my $pkg = $package->part_pkg->pkg;
- my $comment = $package->part_pkg->comment;
- my $pkgview = popurl(2). "view/cust_pkg.cgi?$pkgnum";
+my $packages = get_packages($cust_main, $conf);
- #my @cust_svc = qsearch( 'cust_svc', { 'pkgnum' => $pkgnum } );
- #my $rowspan = scalar(@cust_svc) || 1;
- my @cust_svc = ();
+if ( @$packages ) {
+%>
+<TABLE CLASS="package" BORDER=1 CELLSPACING=0 CELLPADDING=2 BORDERCOLOR="#999999">
+<TR>
+ <TH COLSPAN=2>Package</TH>
+ <TH>Status</TH>
+ <TH COLSPAN=2>Services</TH>
+</TR>
+<%
+foreach my $pkg (sort pkgsort_pkgnum_cancel @$packages) {
my $rowspan = 0;
- my %pkg_svc = ();
- unless ( $package->getfield('cancel') ) {
- foreach my $pkg_svc (
- grep { $_->quantity }
- qsearch('pkg_svc',{'pkgpart'=> $package->pkgpart })
- ) {
- $rowspan += ( $pkg_svc{$pkg_svc->svcpart} = $pkg_svc->quantity );
- }
+
+ if ($pkg->{cancel}) {
+ $rowspan = 0;
} else {
- #@cust_svc = qsearch( 'cust_svc', { 'pkgnum' => $pkgnum } );
- @cust_svc = ();
- $rowspan = scalar(@cust_svc) || 1;
- }
- $rowspan ||= 1;
-
- my $button_cgi = new CGI;
- $button_cgi->param('clone', $package->part_pkg->pkgpart);
- $button_cgi->param('pkgnum', $package->pkgnum);
- my $button_url = popurl(2). "edit/part_pkg.cgi?". $button_cgi->query_string;
-
- #print $n1, qq!<TD ROWSPAN=$rowspan><A HREF="$pkgview">$pkgnum</A></TD>!,
- print $n1, qq!<TD ROWSPAN=$rowspan>$pkgnum</TD>!,
- qq!<TD ROWSPAN=$rowspan><FONT SIZE=-1>!,
- #qq!<A HREF="$pkgview">$pkg - $comment</A>!,
- qq!$pkg - $comment (&nbsp;<a href="$pkgview">Details</a>&nbsp;)!;
- # | !;
-
- #false laziness with view/cust_pkg.cgi, but i'm trying to make that go away so
- unless ( $package->getfield('cancel') ) {
- print ' (&nbsp;';
- if ( $package->getfield('susp') ) {
- print qq!<A HREF="${p}misc/unsusp_pkg.cgi?$pkgnum">Unsuspend</A>!;
- } else {
- print qq!<A HREF="${p}misc/susp_pkg.cgi?$pkgnum">Suspend</A>!;
+ foreach my $svcpart (@{$pkg->{svcparts}}) {
+ $rowspan += $svcpart->{count};
+ $rowspan++ if ($svcpart->{count} < $svcpart->{quantity});
}
- print '&nbsp;|&nbsp;<A HREF="javascript:cust_pkg_areyousure(\''. popurl(2).
- 'misc/cancel_pkg.cgi?'. $pkgnum. '\')">Cancel</A>';
-
- print '&nbsp;) ';
+ }
- print ' (&nbsp;<A HREF="'. popurl(2). 'edit/REAL_cust_pkg.cgi?'. $pkgnum.
- '">Edit&nbsp;dates</A>&nbsp;|&nbsp;';
-
- print qq!<A HREF="$button_url">Customize</A>&nbsp;)!;
+%>
+<!--pkgnum: <%=$pkg->{pkgnum}%>-->
+<TR>
+ <TD ROWSPAN=<%=$rowspan%> CLASS="pkgnum"><%=$pkg->{pkgnum}%></TD>
+ <TD ROWSPAN=<%=$rowspan%>>
+ <%=$pkg->{pkg}%> - <%=$pkg->{comment}%> (&nbsp;<%=pkg_details_link($pkg)%>&nbsp;)<BR>
+<% unless ($pkg->{cancel}) { %>
+ (&nbsp;<%=pkg_change_link($pkg)%>&nbsp;)
+ (&nbsp;<%=pkg_dates_link($pkg)%>&nbsp;|&nbsp;<%=pkg_customize_link($pkg,$custnum)%>&nbsp;)
+<% } %>
+ </TD>
+<%
+ #foreach (qw(setup last_bill next_bill susp expire cancel)) {
+ # print qq! <TD ROWSPAN=$rowspan>! . pkg_datestr($pkg,$_,$conf) . qq!</TD>\n!;
+ #}
+ print "<TD ROWSPAN=$rowspan>". &itable('');
+
+ sub freq {
+
+ #false laziness w/edit/part_pkg.cgi
+ my %freq = ( #move this
+ '1d' => 'daily',
+ '1w' => 'weekly',
+ '2w' => 'biweekly (every 2 weeks)',
+ '1' => 'monthly',
+ '2' => 'bimonthly (every 2 months)',
+ '3' => 'quarterly (every 3 months)',
+ '6' => 'semiannually (every 6 months)',
+ '12' => 'annually',
+ '24' => 'biannually (every 2 years)',
+ );
+ my $freq = shift;
+ exists $freq{$freq} ? $freq{$freq} : "every&nbsp;$freq&nbsp;months";
}
- print '</FONT></TD>';
-
- for ( qw( setup bill susp expire cancel ) ) {
- print "<TD ROWSPAN=$rowspan><FONT SIZE=-1>", ( $package->getfield($_)
- ? time2str("%D</FONT><BR><FONT SIZE=-3>%l:%M:%S%P&nbsp;%z</FONT>",
- $package->getfield($_) )
- : '&nbsp'
- ), '</FONT></TD>',
- ;
- }
- my $n2 = '';
- #false laziness with view/cust_pkg.cgi, but i'm trying to make that go away so
- #foreach my $cust_svc ( @cust_svc ) {
- foreach my $svcpart ( sort { $a<=>$b } keys %pkg_svc ) {
- my $svc = qsearchs('part_svc',{'svcpart'=>$svcpart})->getfield('svc');
- my(@cust_svc)=qsearch('cust_svc',{'pkgnum'=>$pkgnum,
- 'svcpart'=>$svcpart,
- });
- for my $enum ( 1 .. $pkg_svc{$svcpart} ) {
- my $cust_svc;
- if ( $cust_svc = shift @cust_svc ) {
- my($label, $value, $svcdb) = $cust_svc->label;
- my($svcnum) = $cust_svc->svcnum;
- my($sview) = popurl(2). "view";
- print $n2,qq!<TD><A HREF="$sview/$svcdb.cgi?$svcnum"><FONT SIZE=-1>$label</FONT></A></TD>!,
- qq!<TD><A HREF="$sview/$svcdb.cgi?$svcnum"><FONT SIZE=-1>$value</FONT></A></TD>!;
+ #eomove
+
+ if ( $pkg->{cancel} ) { #status: cancelled
+
+ print '<TR><TD><FONT COLOR="#ff0000"><B>Cancelled&nbsp;</B></FONT></TD>'.
+ '<TD>'. pkg_datestr($pkg,'cancel',$conf). '</TD></TR>';
+ unless ( $pkg->{setup} ) {
+ print '<TR><TD COLSPAN=2>Never billed</TD></TR>';
+ } else {
+ print "<TR><TD>Setup&nbsp;</TD><TD>".
+ pkg_datestr($pkg, 'setup',$conf). '</TD></TR>';
+ print "<TR><TD>Last&nbsp;bill&nbsp;</TD><TD>".
+ pkg_datestr($pkg, 'last_bill',$conf). '</TD></TR>'
+ if $pkg->{'last_bill'};
+ print "<TR><TD>Suspended&nbsp;</TD><TD>".
+ pkg_datestr($pkg, 'susp',$conf). '</TD></TR>'
+ if $pkg->{'susp'};
+ }
+
+ } else {
+
+ if ( $pkg->{susp} ) { #status: suspended
+ print '<TR><TD><FONT COLOR="#FF9900"><B>Suspended</B>&nbsp;</FONT></TD>'.
+ '<TD>'. pkg_datestr($pkg,'susp',$conf). '</TD></TR>';
+ unless ( $pkg->{setup} ) {
+ print '<TR><TD COLSPAN=2>Never billed</TD></TR>';
} else {
- print $n2, qq!<TD COLSPAN=2><A HREF="$uiadd{$svcpart}?pkgnum$pkgnum-svcpart$svcpart"><b><font size="+1" color="#ff0000">!.
- qq!Provision $svc</A></b></font>!;
+ print "<TR><TD>Setup&nbsp;</TD><TD>".
+ pkg_datestr($pkg, 'setup',$conf). '</TD></TR>';
+ }
+ print "<TR><TD>Last&nbsp;bill&nbsp;</TD><TD>".
+ pkg_datestr($pkg, 'last_bill',$conf). '</TD></TR>'
+ if $pkg->{'last_bill'};
+ # next bill ??
+ print "<TR><TD>Expires&nbsp;</TD><TD>".
+ pkg_datestr($pkg, 'expire',$conf). '</TD></TR>'
+ if $pkg->{'expire'};
+ print '<TR><TD COLSPAN=2>(&nbsp;'. pkg_unsuspend_link($pkg).
+ '&nbsp;|&nbsp;'. pkg_cancel_link($pkg). '&nbsp;)</TD></TR>';
+
+ } else { #status: active
+
+ unless ( $pkg->{setup} ) { #not setup
+
+ print '<TR><TD COLSPAN=2>Not&nbsp;yet&nbsp;billed&nbsp;(';
+ unless ( $pkg->{freq} ) {
+ print 'one-time&nbsp;charge)</TD></TR>';
+ print '<TR><TD COLSPAN=2>(&nbsp;'. pkg_cancel_link($pkg).
+ '&nbsp;)</TD</TR>';
+ } else {
+ print 'billed&nbsp;'. freq($pkg->{freq}). ')</TD></TR>';
+ }
+
+ } else { #setup
+
+ unless ( $pkg->{freq} ) {
+ print "<TR><TD COLSPAN=2>One-time&nbsp;charge</TD></TR>".
+ '<TR><TD>Billed&nbsp;</TD><TD>'.
+ pkg_datestr($pkg,'setup',$conf). '</TD></TR>';
+ } else {
+ print '<TR><TD COLSPAN=2><FONT COLOR="#00CC00"><B>Active</B></FONT>'.
+ ',&nbsp;billed&nbsp;'. freq($pkg->{freq}). '</TD></TR>'.
+ '<TR><TD>Setup&nbsp;</TD><TD>'.
+ pkg_datestr($pkg, 'setup',$conf). '</TD></TR>';
+ }
- print qq!<BR><A HREF="../misc/link.cgi?pkgnum$pkgnum-svcpart$svcpart">!.
- qq!<b><font size="+1" color="#ff0000">Link to legacy $svc</A></b></font>!
- if $conf->exists('legacy_link');
+ }
- print '</TD>';
+ print "<TR><TD>Last&nbsp;bill&nbsp;</TD><TD>".
+ pkg_datestr($pkg, 'last_bill',$conf). '</TD></TR>'
+ if $pkg->{'last_bill'};
+ print "<TR><TD>Next&nbsp;bill&nbsp;</TD><TD>".
+ pkg_datestr($pkg, 'next_bill',$conf). '</TD></TR>'
+ if $pkg->{'next_bill'};
+ print "<TR><TD>Expires&nbsp;</TD><TD>".
+ pkg_datestr($pkg, 'expire',$conf). '</TD></TR>'
+ if $pkg->{'expire'};
+ if ( $pkg->{freq} ) {
+ print '<TR><TD COLSPAN=2>(&nbsp;'. pkg_suspend_link($pkg).
+ '&nbsp;|&nbsp;'. pkg_cancel_link($pkg). '&nbsp;)</TD></TR>';
}
- $n2="</TR><TR>";
+
+ }
+
+ }
+
+ print "</TABLE></TD>\n";
+
+ if ($rowspan == 0) { print qq!</TR>\n!; next; }
+
+ my $cnt = 0;
+ foreach my $svcpart (sort {$a->{svcpart} <=> $b->{svcpart}} @{$pkg->{svcparts}}) {
+ foreach my $service (@{$svcpart->{services}}) {
+ print '<TR>' if ($cnt > 0);
+%>
+ <TD><%=svc_link($svcpart,$service)%></TD>
+ <TD><%=svc_label_link($svcpart,$service)%><BR>(&nbsp;<%=svc_unprovision_link($service)%>&nbsp;)</TD>
+</TR>
+<%
+ $cnt++;
+ }
+ if ($svcpart->{count} < $svcpart->{quantity}) {
+ print qq!<TR>\n! if ($cnt > 0);
+ print qq! <TD COLSPAN=2>!.svc_provision_link($pkg,$svcpart).qq!</TD>\n</TR>\n!;
}
}
+}
+print '</TABLE>'
+}
- $n1="</TR><TR>";
-}
-print "</TR>";
+#end display packages
-#formatting
-print "</TABLE>";
print <<END;
<SCRIPT>
@@ -439,51 +549,157 @@ function cust_pay_areyousure(href) {
== true)
window.location.href = href;
}
+function cust_pay_unapply_areyousure(href) {
+ if (confirm("Are you sure you want to unapply this payment?")
+ == true)
+ window.location.href = href;
+}
+function cust_credit_areyousure(href) {
+ if (confirm("Are you sure you want to delete this credit?")
+ == true)
+ window.location.href = href;
+}
</SCRIPT>
END
-#formatting
-print qq!<BR><BR><A NAME="history">Payment History!.
- qq!</A> ( !.
- qq!<A HREF="!. popurl(2). qq!edit/cust_pay.cgi?custnum=$custnum">!.
- qq!Post payment</A> | !.
- qq!<A HREF="!. popurl(2). qq!edit/cust_credit.cgi?$custnum">!.
- qq!Post credit</A> )!;
-
-#get payment history
-#
-# major problem: this whole thing is way too sloppy.
-# minor problem: the description lines need better formatting.
-
-my @history = (); #needed for mod_perl :)
-
-my %target = ();
-
-my @bills = qsearch('cust_bill',{'custnum'=>$custnum});
-foreach my $bill (@bills) {
- my($bref)=$bill->hashref;
- my $bpre = ( $bill->owed > 0 )
- ? '<b><font size="+1" color="#ff0000"> Open '
- : '';
- my $bpost = ( $bill->owed > 0 ) ? '</font></b>' : '';
- push @history,
- $bref->{_date} . qq!\t<A HREF="!. popurl(2). qq!view/cust_bill.cgi?! .
- $bref->{invnum} . qq!">${bpre}Invoice #! . $bref->{invnum} .
- qq! (Balance \$! . $bill->owed . qq!)$bpost</A>\t! .
- $bref->{charged} . qq!\t\t\t!;
-
- my(@cust_bill_pay)=qsearch('cust_bill_pay',{'invnum'=> $bref->{invnum} } );
-# my(@payments)=qsearch('cust_pay',{'invnum'=> $bref->{invnum} } );
-# my($payment);
-# foreach $payment (@payments) {
- foreach my $cust_bill_pay (@cust_bill_pay) {
- my $payment = $cust_bill_pay->cust_pay;
- my($date,$invnum,$payby,$payinfo,$paid)=($payment->_date,
- $cust_bill_pay->invnum,
- $payment->payby,
- $payment->payinfo,
- $cust_bill_pay->amount,
- );
+if ( $conf->config('payby-default') ne 'HIDE' ) {
+
+ #formatting
+ print qq!<BR><BR><A NAME="history">Payment History!.
+ qq!</A> ( !.
+ qq!<A HREF="!. popurl(2). qq!edit/cust_pay.cgi?custnum=$custnum">!.
+ qq!Post payment</A> | !.
+ qq!<A HREF="!. popurl(2). qq!edit/cust_credit.cgi?$custnum">!.
+ qq!Post credit</A> )!;
+
+ #get payment history
+ #
+ # major problem: this whole thing is way too sloppy.
+ # minor problem: the description lines need better formatting.
+
+ my @history = (); #needed for mod_perl :)
+
+ my %target = ();
+
+ my @bills = qsearch('cust_bill',{'custnum'=>$custnum});
+ foreach my $bill (@bills) {
+ my($bref)=$bill->hashref;
+ my $bpre = ( $bill->owed > 0 )
+ ? '<b><font size="+1" color="#ff0000"> Open '
+ : '';
+ my $bpost = ( $bill->owed > 0 ) ? '</font></b>' : '';
+ push @history,
+ $bref->{_date} . qq!\t<A HREF="!. popurl(2). qq!view/cust_bill.cgi?! .
+ $bref->{invnum} . qq!">${bpre}Invoice #! . $bref->{invnum} .
+ qq! (Balance \$! . $bill->owed . qq!)$bpost</A>\t! .
+ $bref->{charged} . qq!\t\t\t!;
+
+ my(@cust_bill_pay)=qsearch('cust_bill_pay',{'invnum'=> $bref->{invnum} } );
+ # my(@payments)=qsearch('cust_pay',{'invnum'=> $bref->{invnum} } );
+ # my($payment);
+ foreach my $cust_bill_pay (@cust_bill_pay) {
+ my $payment = $cust_bill_pay->cust_pay;
+ my($date,$invnum,$payby,$payinfo,$paid)=($payment->_date,
+ $cust_bill_pay->invnum,
+ $payment->payby,
+ $payment->payinfo,
+ $cust_bill_pay->amount,
+ );
+ $payinfo = 'x'x(length($payinfo)-4). substr($payinfo,(length($payinfo)-4))
+ if $payby eq 'CARD';
+ my $target = "$payby$payinfo";
+ $payby =~ s/^BILL$/Check #/ if $payinfo;
+ $payby =~ s/^(CARD|COMP)$/$1 /;
+ my $delete = $payment->closed !~ /^Y/i && $conf->exists('deletepayments')
+ ? qq! (<A HREF="javascript:cust_pay_areyousure('${p}misc/delete-cust_pay.cgi?!. $payment->paynum. qq!')">delete</A>)!
+ : '';
+ my $unapply =
+ $payment->closed !~ /^Y/i && $conf->exists('unapplypayments')
+ ? qq! (<A HREF="javascript:cust_pay_unapply_areyousure('${p}misc/unapply-cust_pay.cgi?!. $payment->paynum. qq!')">unapply</A>)!
+ : '';
+ push @history,
+ "$date\tPayment, Invoice #$invnum ($payby$payinfo)$delete$unapply\t\t$paid\t\t\t$target";
+ }
+
+ my(@cust_credit_bill)=
+ qsearch('cust_credit_bill', { 'invnum'=> $bref->{invnum} } );
+ foreach my $cust_credit_bill (@cust_credit_bill) {
+ my $cust_credit = $cust_credit_bill->cust_credit;
+ my($date, $invnum, $crednum, $amount, $reason, $app_date ) = (
+ $cust_credit->_date,
+ $cust_credit_bill->invnum,
+ $cust_credit_bill->crednum,
+ $cust_credit_bill->amount,
+ $cust_credit->reason,
+ time2str("%D", $cust_credit_bill->_date),
+ );
+ my $delete =
+ $cust_credit->closed !~ /^Y/i && $conf->exists('deletecredits')
+ ? qq! (<A HREF="javascript:cust_credit_areyousure('${p}misc/delete-cust_credit.cgi?!. $cust_credit->crednum. qq!')">delete</A>)!
+ : '';
+ push @history,
+ "$date\tCredit #$crednum: $reason<BR>".
+ "(applied to invoice #$invnum on $app_date)$delete\t\t\t$amount\t";
+ }
+ }
+
+ my @credits = grep { scalar(my @array = $_->cust_credit_refund) }
+ qsearch('cust_credit',{'custnum'=>$custnum});
+ foreach my $credit (@credits) {
+ my($cref)=$credit->hashref;
+ my(@cust_credit_refund)=
+ qsearch('cust_credit_refund', { 'crednum'=> $cref->{crednum} } );
+ foreach my $cust_credit_refund (@cust_credit_refund) {
+ my $cust_refund = $cust_credit_refund->cust_credit;
+ my($date, $crednum, $amount, $reason, $app_date ) = (
+ $credit->_date,
+ $credit->crednum,
+ $cust_credit_refund->amount,
+ $credit->reason,
+ time2str("%D", $cust_credit_refund->_date),
+ );
+ push @history,
+ "$date\tCredit #$crednum: $reason<BR>".
+ "(applied to refund on $app_date)\t\t\t$amount\t";
+ }
+ }
+
+ @credits = grep { $_->credited > 0 }
+ qsearch('cust_credit',{'custnum'=>$custnum});
+ foreach my $credit (@credits) {
+ my($cref)=$credit->hashref;
+ my $delete =
+ $credit->closed !~ /^Y/i && $conf->exists('deletecredits')
+ ? qq! (<A HREF="javascript:cust_credit_areyousure('${p}misc/delete-cust_credit.cgi?!. $credit->crednum. qq!')">delete</A>)!
+ : '';
+ push @history,
+ $cref->{_date} . "\t" .
+ qq!<A HREF="! . popurl(2). qq!edit/cust_credit_bill.cgi?!. $cref->{crednum} . qq!">!.
+ '<b><font size="+1" color="#ff0000">Unapplied credit #' .
+ $cref->{crednum} . "</font></b></A>: ".
+ $cref->{reason} . "$delete\t\t\t" . $credit->credited . "\t";
+ }
+
+ my(@refunds)=qsearch('cust_refund',{'custnum'=> $custnum } );
+ foreach my $refund (@refunds) {
+ my($rref)=$refund->hashref;
+ my($refundnum) = (
+ $refund->refundnum,
+ );
+
+ push @history,
+ $rref->{_date} . "\tRefund #$refundnum, (" .
+ $rref->{payby} . " " . $rref->{payinfo} . ") by " .
+ $rref->{otaker} . " - ". $rref->{reason} . "\t\t\t\t" .
+ $rref->{refund};
+ }
+
+ my @unapplied_payments =
+ grep { $_->unapplied > 0 } qsearch('cust_pay', { 'custnum' => $custnum } );
+ foreach my $payment (@unapplied_payments) {
+ my $payby = $payment->payby;
+ my $payinfo = $payment->payinfo;
+ #false laziness w/above
$payinfo = 'x'x(length($payinfo)-4). substr($payinfo,(length($payinfo)-4))
if $payby eq 'CARD';
my $target = "$payby$payinfo";
@@ -493,161 +709,257 @@ foreach my $bill (@bills) {
? qq! (<A HREF="javascript:cust_pay_areyousure('${p}misc/delete-cust_pay.cgi?!. $payment->paynum. qq!')">delete</A>)!
: '';
push @history,
- "$date\tPayment, Invoice #$invnum ($payby$payinfo)$delete\t\t$paid\t\t\t$target";
+ $payment->_date. "\t".
+ '<b><font size="+1" color="#ff0000">Unapplied payment #' .
+ $payment->paynum . " ($payby$payinfo)</font></b> ".
+ '(<A HREF="'. popurl(2). 'edit/cust_bill_pay.cgi?'. $payment->paynum. '">'.
+ "apply</A>)$delete".
+ "\t\t" . $payment->unapplied . "\t\t\t$target";
}
-
- my(@cust_credit_bill)=
- qsearch('cust_credit_bill', { 'invnum'=> $bref->{invnum} } );
- foreach my $cust_credit_bill (@cust_credit_bill) {
- my $cust_credit = $cust_credit_bill->cust_credit;
- my($date, $invnum, $crednum, $amount, $reason, $app_date ) = (
- $cust_credit->_date,
- $cust_credit_bill->invnum,
- $cust_credit_bill->crednum,
- $cust_credit_bill->amount,
- $cust_credit->reason,
- time2str("%D", $cust_credit_bill->_date),
- );
- push @history,
- "$date\tCredit #$crednum: $reason<BR>".
- "(applied to invoice #$invnum on $app_date)\t\t\t$amount\t";
+
+ #formatting
+ print &table(), <<END;
+ <TR>
+ <TH>Date</TH>
+ <TH>Description</TH>
+ <TH><FONT SIZE=-1>Charge</FONT></TH>
+ <TH><FONT SIZE=-1>Payment</FONT></TH>
+ <TH><FONT SIZE=-1>In-house<BR>Credit</FONT></TH>
+ <TH><FONT SIZE=-1>Refund</FONT></TH>
+ <TH><FONT SIZE=-1>Balance</FONT></TH>
+ </TR>
+END
+
+ #display payment history
+
+ my $balance = 0;
+ foreach my $item (sort keyfield_numerically @history) {
+ my($date,$desc,$charge,$payment,$credit,$refund,$target)=split(/\t/,$item);
+ $charge ||= 0;
+ $payment ||= 0;
+ $credit ||= 0;
+ $refund ||= 0;
+ $balance += $charge - $payment;
+ $balance -= $credit - $refund;
+ $balance = sprintf("%.2f", $balance);
+ $balance =~ s/^\-0\.00$/0.00/; #yay ieee fp
+ $target = '' unless defined $target;
+
+ print "<TR><TD><FONT SIZE=-1>";
+ print qq!<A NAME="$target">! unless $target && $target{$target}++;
+ print time2str("%D",$date);
+ print '</A>' if $target && $target{$target} == 1;
+ print "</FONT></TD>",
+ "<TD><FONT SIZE=-1>$desc</FONT></TD>",
+ "<TD><FONT SIZE=-1>",
+ ( $charge ? "\$".sprintf("%.2f",$charge) : '' ),
+ "</FONT></TD>",
+ "<TD><FONT SIZE=-1>",
+ ( $payment ? "-&nbsp;\$".sprintf("%.2f",$payment) : '' ),
+ "</FONT></TD>",
+ "<TD><FONT SIZE=-1>",
+ ( $credit ? "-&nbsp;\$".sprintf("%.2f",$credit) : '' ),
+ "</FONT></TD>",
+ "<TD><FONT SIZE=-1>",
+ ( $refund ? "\$".sprintf("%.2f",$refund) : '' ),
+ "</FONT></TD>",
+ "<TD><FONT SIZE=-1>\$" . $balance,
+ "</FONT></TD>",
+ "\n";
}
+
+ print "</TABLE>";
+
}
-my @credits = grep { scalar(my @array = $_->cust_credit_refund) }
- qsearch('cust_credit',{'custnum'=>$custnum});
-foreach my $credit (@credits) {
- my($cref)=$credit->hashref;
- my(@cust_credit_refund)=
- qsearch('cust_credit_refund', { 'crednum'=> $cref->{crednum} } );
- foreach my $cust_credit_refund (@cust_credit_refund) {
- my $cust_refund = $cust_credit_refund->cust_credit;
- my($date, $crednum, $amount, $reason, $app_date ) = (
- $credit->_date,
- $credit->crednum,
- $cust_credit_refund->amount,
- $credit->reason,
- time2str("%D", $cust_credit_refund->_date),
- );
- push @history,
- "$date\tCredit #$crednum: $reason<BR>".
- "(applied to refund on $app_date)\t\t\t$amount\t";
+print '</BODY></HTML>';
+
+#subroutiens
+sub keyfield_numerically { (split(/\t/,$a))[0] <=> (split(/\t/,$b))[0]; }
+
+%>
+
+<%
+
+
+sub get_packages {
+ my $cust_main = shift or return undef;
+ my $conf = shift;
+
+ my @packages = ();
+
+ foreach my $cust_pkg (
+ $conf->exists('hidecancelledpackages')
+ ? $cust_main->ncancelled_pkgs
+ : $cust_main->all_pkgs
+ ) {
+
+ my $part_pkg = $cust_pkg->part_pkg;
+
+ my %pkg = ();
+ $pkg{pkgnum} = $cust_pkg->pkgnum;
+ $pkg{pkg} = $part_pkg->pkg;
+ $pkg{pkgpart} = $part_pkg->pkgpart;
+ $pkg{comment} = $part_pkg->getfield('comment');
+ $pkg{freq} = $part_pkg->freq;
+ $pkg{setup} = $cust_pkg->getfield('setup');
+ $pkg{last_bill} = $cust_pkg->getfield('last_bill');
+ $pkg{next_bill} = $cust_pkg->getfield('bill');
+ $pkg{susp} = $cust_pkg->getfield('susp');
+ $pkg{expire} = $cust_pkg->getfield('expire');
+ $pkg{cancel} = $cust_pkg->getfield('cancel');
+
+ my %svcparts = ();
+
+ foreach my $pkg_svc (
+ qsearch('pkg_svc', { 'pkgpart' => $part_pkg->pkgpart })
+ ) {
+
+ next if ($pkg_svc->quantity == 0);
+
+ my $part_svc = qsearchs('part_svc', { 'svcpart' => $pkg_svc->svcpart });
+
+ my $svcpart = {};
+ $svcpart->{svcpart} = $part_svc->svcpart;
+ $svcpart->{svc} = $part_svc->svc;
+ $svcpart->{svcdb} = $part_svc->svcdb;
+ $svcpart->{quantity} = $pkg_svc->quantity;
+ $svcpart->{count} = 0;
+
+ $svcpart->{services} = [];
+
+ $svcparts{$svcpart->{svcpart}} = $svcpart;
+
+ }
+
+ foreach my $cust_svc (
+ qsearch( 'cust_svc', {
+ 'pkgnum' => $cust_pkg->pkgnum,
+ #'svcpart' => $part_svc->svcpart,
+ }
+ )
+ ) {
+
+ warn "svcnum ". $cust_svc->svcnum. " / svcpart ". $cust_svc->svcpart. "\n";
+ my $svc = {
+ 'svcnum' => $cust_svc->svcnum,
+ 'label' => ($cust_svc->label)[1],
+ };
+
+ #false laziness with above, to catch extraneous services. whole
+ #damn thing should be OO...
+ my $svcpart = ( $svcparts{$cust_svc->svcpart} ||= {
+ 'svcpart' => $cust_svc->svcpart,
+ 'svc' => $cust_svc->part_svc->svc,
+ 'svcdb' => $cust_svc->part_svc->svcdb,
+ 'quantity' => 0,
+ 'count' => 0,
+ 'services' => [],
+ } );
+
+ push @{$svcpart->{services}}, $svc;
+
+ $svcpart->{count}++;
+
+ }
+
+ $pkg{svcparts} = [ values %svcparts ];
+
+ push @packages, \%pkg;
+
}
+
+ return \@packages;
+
+}
+
+sub svc_link {
+
+ my ($svcpart, $svc) = (shift,shift) or return '';
+ return qq!<A HREF="${p}view/$svcpart->{svcdb}.cgi?$svc->{svcnum}">$svcpart->{svc}</A>!;
+
}
-@credits = grep { $_->credited > 0 }
- qsearch('cust_credit',{'custnum'=>$custnum});
-foreach my $credit (@credits) {
- my($cref)=$credit->hashref;
- push @history,
- $cref->{_date} . "\t" .
- qq!<A HREF="! . popurl(2). qq!edit/cust_credit_bill.cgi?!. $cref->{crednum} . qq!">!.
- '<b><font size="+1" color="#ff0000">Unapplied credit #' .
- $cref->{crednum} . "</font></b></A>: ".
- $cref->{reason} . "\t\t\t" . $credit->credited . "\t";
+sub svc_label_link {
+
+ my ($svcpart, $svc) = (shift,shift) or return '';
+ return qq!<A HREF="${p}view/$svcpart->{svcdb}.cgi?$svc->{svcnum}">$svc->{label}</A>!;
+
}
-my(@refunds)=qsearch('cust_refund',{'custnum'=> $custnum } );
-foreach my $refund (@refunds) {
- my($rref)=$refund->hashref;
- my($refundnum) = (
- $refund->refundnum,
- );
-
- push @history,
- $rref->{_date} . "\tRefund #$refundnum, (" .
- $rref->{payby} . " " . $rref->{payinfo} . ") by " .
- $rref->{otaker} . " - ". $rref->{reason} . "\t\t\t\t" .
- $rref->{refund};
+sub svc_provision_link {
+ my ($pkg, $svcpart) = (shift,shift) or return '';
+ ( my $svc_nbsp = $svcpart->{svc} ) =~ s/\s+/&nbsp;/g;
+ return qq!<A CLASS="provision" HREF="${p}edit/$svcpart->{svcdb}.cgi?! .
+ qq!pkgnum$pkg->{pkgnum}-svcpart$svcpart->{svcpart}">! .
+ "Provision&nbsp;$svc_nbsp&nbsp;(".
+ ($svcpart->{quantity} - $svcpart->{count}).
+ ')</A>';
}
-my @unapplied_payments =
- grep { $_->unapplied > 0 } qsearch('cust_pay', { 'custnum' => $custnum } );
-foreach my $payment (@unapplied_payments) {
- my $payby = $payment->payby;
- my $payinfo = $payment->payinfo;
- #false laziness w/above
- $payinfo = 'x'x(length($payinfo)-4). substr($payinfo,(length($payinfo)-4))
- if $payby eq 'CARD';
- my $target = "$payby$payinfo";
- $payby =~ s/^BILL$/Check #/ if $payinfo;
- $payby =~ s/^(CARD|COMP)$/$1 /;
- my $delete = $payment->closed !~ /^Y/i && $conf->exists('deletepayments')
- ? qq! (<A HREF="javascript:cust_pay_areyousure('${p}misc/delete-cust_pay.cgi?!. $payment->paynum. qq!')">delete</A>)!
- : '';
- push @history,
- $payment->_date. "\t".
- '<b><font size="+1" color="#ff0000">Unapplied payment #' .
- $payment->paynum . " ($payby$payinfo)</font></b> ".
- '(<A HREF="'. popurl(2). 'edit/cust_bill_pay.cgi?'. $payment->paynum. '">'.
- "apply</A>)$delete".
- "\t\t" . $payment->unapplied . "\t\t\t$target";
+sub svc_unprovision_link {
+ my $svc = shift or return '';
+ return qq!<A HREF="javascript:svc_areyousure('${p}misc/unprovision.cgi?$svc->{svcnum}')">Unprovision</A>!;
}
- #formatting
- print &table(), <<END;
-<TR>
- <TH>Date</TH>
- <TH>Description</TH>
- <TH><FONT SIZE=-1>Charge</FONT></TH>
- <TH><FONT SIZE=-1>Payment</FONT></TH>
- <TH><FONT SIZE=-1>In-house<BR>Credit</FONT></TH>
- <TH><FONT SIZE=-1>Refund</FONT></TH>
- <TH><FONT SIZE=-1>Balance</FONT></TH>
-</TR>
-END
+# This should be generalized to use config options to determine order.
+sub pkgsort_pkgnum_cancel {
+ if ($a->{cancel} and $b->{cancel}) {
+ return ($a->{pkgnum} <=> $b->{pkgnum});
+ } elsif ($a->{cancel} or $b->{cancel}) {
+ return (-1) if ($b->{cancel});
+ return (1) if ($a->{cancel});
+ return (0);
+ } else {
+ return($a->{pkgnum} <=> $b->{pkgnum});
+ }
+}
+
+sub pkg_datestr {
+ my($pkg, $field, $conf) = @_ or return '';
+ return '&nbsp;' unless $pkg->{$field};
+ my $format = $conf->exists('pkg_showtimes')
+ ? '<B>%D</B>&nbsp;<FONT SIZE=-3>%l:%M:%S%P&nbsp;%z</FONT>'
+ : '<B>%b&nbsp;%o,&nbsp;%Y</B>';
+ ( my $strip = time2str($format, $pkg->{$field}) ) =~ s/ (\d)/$1/g;
+ $strip;
+}
-#display payment history
-
-my $balance = 0;
-foreach my $item (sort keyfield_numerically @history) {
- my($date,$desc,$charge,$payment,$credit,$refund,$target)=split(/\t/,$item);
- $charge ||= 0;
- $payment ||= 0;
- $credit ||= 0;
- $refund ||= 0;
- $balance += $charge - $payment;
- $balance -= $credit - $refund;
- $balance = sprintf("%.2f", $balance);
- $balance =~ s/^\-0\.00$/0.00/; #yay ieee fp
- $target = '' unless defined $target;
-
- print "<TR><TD><FONT SIZE=-1>";
- print qq!<A NAME="$target">! unless $target && $target{$target}++;
- print time2str("%D",$date);
- print '</A>' if $target && $target{$target} == 1;
- print "</FONT></TD>",
- "<TD><FONT SIZE=-1>$desc</FONT></TD>",
- "<TD><FONT SIZE=-1>",
- ( $charge ? "\$".sprintf("%.2f",$charge) : '' ),
- "</FONT></TD>",
- "<TD><FONT SIZE=-1>",
- ( $payment ? "-&nbsp;\$".sprintf("%.2f",$payment) : '' ),
- "</FONT></TD>",
- "<TD><FONT SIZE=-1>",
- ( $credit ? "-&nbsp;\$".sprintf("%.2f",$credit) : '' ),
- "</FONT></TD>",
- "<TD><FONT SIZE=-1>",
- ( $refund ? "\$".sprintf("%.2f",$refund) : '' ),
- "</FONT></TD>",
- "<TD><FONT SIZE=-1>\$" . $balance,
- "</FONT></TD>",
- "\n";
+sub pkg_details_link {
+ my $pkg = shift or return '';
+ return qq!<a href="${p}view/cust_pkg.cgi?$pkg->{pkgnum}">Details</a>!;
}
-#formatting
-print "</TABLE>";
+sub pkg_change_link {
+ my $pkg = shift or return '';
+ return qq!<a href="${p}misc/change_pkg.cgi?$pkg->{pkgnum}">Change&nbsp;package</a>!;
+}
-#end
+sub pkg_suspend_link {
+ my $pkg = shift or return '';
+ return qq!<a href="${p}misc/susp_pkg.cgi?$pkg->{pkgnum}">Suspend</a>!;
+}
-#formatting
-print <<END;
+sub pkg_unsuspend_link {
+ my $pkg = shift or return '';
+ return qq!<a href="${p}misc/unsusp_pkg.cgi?$pkg->{pkgnum}">Unsuspend</a>!;
+}
- </BODY>
-</HTML>
-END
+sub pkg_cancel_link {
+ my $pkg = shift or return '';
+ return qq!<A HREF="javascript:cust_pkg_areyousure('${p}misc/cancel_pkg.cgi?$pkg->{pkgnum}')">Cancel</A>!;
+}
-#subroutiens
-sub keyfield_numerically { (split(/\t/,$a))[0] <=> (split(/\t/,$b))[0] ; }
+sub pkg_dates_link {
+ my $pkg = shift or return '';
+ return qq!<A HREF="${p}edit/REAL_cust_pkg.cgi?$pkg->{pkgnum}">Edit&nbsp;dates</A>!;
+}
+
+sub pkg_customize_link {
+ my $pkg = shift or return '';
+ my $custnum = shift;
+ return qq!<A HREF="${p}edit/part_pkg.cgi?keywords=$custnum;clone=$pkg->{pkgpart};pkgnum=$pkg->{pkgnum}">Customize</A>!;
+}
%>
+
diff --git a/httemplate/view/cust_pkg.cgi b/httemplate/view/cust_pkg.cgi
index 09a2a7a..5f0e6bf 100755
--- a/httemplate/view/cust_pkg.cgi
+++ b/httemplate/view/cust_pkg.cgi
@@ -68,10 +68,16 @@ print &ntable("#cccccc"), '<TR><TD>', &ntable("#cccccc",2),
'<TR><TD ALIGN="right">Comment</TD><TD BGCOLOR="#ffffff">',
$comment, '</TD></TR>',
'<TR><TD ALIGN="right">Setup date</TD><TD BGCOLOR="#ffffff">',
- ( $setup ? time2str("%D",$setup) : "(Not setup)" ), '</TD></TR>',
- '<TR><TD ALIGN="right">Next bill date</TD><TD BGCOLOR="#ffffff">',
- ( $bill ? time2str("%D",$bill) : "&nbsp;" ), '</TD></TR>',
-;
+ ( $setup ? time2str("%D",$setup) : "(Not setup)" ), '</TD></TR>';
+
+print '<TR><TD ALIGN="right">Last bill date</TD><TD BGCOLOR="#ffffff">',
+ ( $cust_pkg->get('last_bill') ? time2str("%D",$cust_pkg->get('last_bill')) : "&nbsp;" ),
+ '</TD></TR>'
+ if $cust_pkg->dbdef_table->column('last_bill');
+
+print '<TR><TD ALIGN="right">Next bill date</TD><TD BGCOLOR="#ffffff">',
+ ( $bill ? time2str("%D",$bill) : "&nbsp;" ), '</TD></TR>';
+
print '<TR><TD ALIGN="right">Suspension date</TD><TD BGCOLOR="#ffffff">',
time2str("%D",$susp), '</TD></TR>' if $susp;
print '<TR><TD ALIGN="right">Expiration date</TD><TD BGCOLOR="#ffffff">',
@@ -80,15 +86,16 @@ print '<TR><TD ALIGN="right">Cancellation date</TD><TD BGCOLOR="#ffffff">',
time2str("%D",$cancel), '</TD></TR>' if $cancel;
print '<TR><TD ALIGN="right">Order taker</TD><TD BGCOLOR="#ffffff">',
$otaker, '</TD></TR>',
- '</TABLE></TD></TR></TABLE>'
-;
-
-# print <<END;
-#<FORM ACTION="../misc/expire_pkg.cgi" METHOD="post">
-#<INPUT TYPE="hidden" NAME="pkgnum" VALUE="$pkgnum">
-#Expire (date): <INPUT TYPE="text" NAME="date" VALUE="" >
-#<INPUT TYPE="submit" VALUE="Cancel later">
-#END
+ '</TABLE></TD></TR></TABLE>';
+
+unless ($expire) {
+ print <<END;
+<FORM ACTION="../misc/expire_pkg.cgi" METHOD="post">
+<INPUT TYPE="hidden" NAME="pkgnum" VALUE="$pkgnum">
+Expire (date): <INPUT TYPE="text" NAME="date" VALUE="" >
+<INPUT TYPE="submit" VALUE="Cancel later">
+END
+}
unless ($cancel) {
diff --git a/httemplate/view/svc_acct.cgi b/httemplate/view/svc_acct.cgi
index f6c1b02..58591fc 100755
--- a/httemplate/view/svc_acct.cgi
+++ b/httemplate/view/svc_acct.cgi
@@ -2,7 +2,6 @@
<%
my $conf = new FS::Conf;
-my $mydomain = $conf->config('domain');
my($query) = $cgi->keywords;
$query =~ /^(\d+)$/;
@@ -32,11 +31,7 @@ if ( $svc_acct->domsvc ) {
die "Unknown domain" unless $svc_domain;
$domain = $svc_domain->domain;
} else {
- unless ( $mydomain ) {
- die "No legacy domain config file and no svc_domain.svcnum record ".
- "for svc_acct.domsvc: ". $cust_svc->domsvc;
- }
- $domain = $mydomain;
+ die "No svc_domain.svcnum record for svc_acct.domsvc: ". $cust_svc->domsvc;
}
%>
@@ -61,6 +56,57 @@ function areyousure(href) {
<%
+#if ( $cust_pkg && $cust_pkg->part_pkg->plan eq 'sqlradacct_hour' ) {
+if ( $part_svc->part_export('sqlradius')
+ || $part_svc->part_export('sqlradius_withdomain')
+) {
+
+ my $last_bill;
+ my %plandata;
+ if ( $cust_pkg ) {
+ #false laziness w/httemplate/edit/part_pkg... this stuff doesn't really
+ #belong in plan data
+ %plandata = map { /^(\w+)=(.*)$/; ( $1 => $2 ); }
+ split("\n", $cust_pkg->part_pkg->plandata );
+
+ $last_bill = $cust_pkg->last_bill;
+ } else {
+ $last_bill = 0;
+ %plandata = ();
+ }
+
+ my $seconds = $svc_acct->seconds_since_sqlradacct( $last_bill, time );
+ my $hour = int($seconds/3600);
+ my $min = int( ($seconds%3600) / 60 );
+ my $sec = $seconds%60;
+
+ my $input = $svc_acct->attribute_since_sqlradacct(
+ $last_bill, time, 'AcctInputOctets'
+ ) / 1048576;
+ my $output = $svc_acct->attribute_since_sqlradacct(
+ $last_bill, time, 'AcctOutputOctets'
+ ) / 1048576;
+
+ if ( $seconds ) {
+ print "Online <B>$hour</B>h <B>$min</B>m <B>$sec</B>s";
+ } else {
+ print 'Has not logged on';
+ }
+
+ if ( $cust_pkg ) {
+ print ' since last bill ('. time2str("%C", $last_bill). ') - '.
+ $plandata{recur_included_hours}. ' total hours in plan<BR>';
+ } else {
+ print ' (no billing cycle available for unaudited account)<BR>';
+ }
+
+ print 'Input: <B>'. sprintf("%.3f", $input). '</B> megabytes<BR>';
+ print 'Output: <B>'. sprintf("%.3f", $output). '</B> megabytes<BR>';
+
+ print '<BR>';
+
+}
+
#print qq!<BR><A HREF="../misc/sendconfig.cgi?$svcnum">Send account information</A>!;
print qq!<A HREF="${p}edit/svc_acct.cgi?$svcnum">Edit this information</A><BR>!.
@@ -83,7 +129,7 @@ if ( $password =~ /^\*\w+\* (.*)$/ ) {
print "<I>(login disabled)</I> ";
}
if ( $conf->exists('showpasswords') ) {
- print "$password";
+ print '<PRE>'. encode_entities($password). '</PRE>';
} else {
print "<I>(hidden)</I>";
}
@@ -96,7 +142,9 @@ if ( $conf->exists('security_phrase') ) {
$svc_acct->sec_phrase. '</TD></TR>';
}
-my $svc_acct_pop = qsearchs('svc_acct_pop',{'popnum'=>$svc_acct->popnum});
+my $svc_acct_pop = $svc_acct->popnum
+ ? qsearchs('svc_acct_pop',{'popnum'=>$svc_acct->popnum})
+ : '';
print "<TR><TD ALIGN=\"right\">Access number</TD>".
"<TD BGCOLOR=\"#ffffff\">". $svc_acct_pop->text. '</TD></TR>'
if $svc_acct_pop;
@@ -126,7 +174,7 @@ if ($svc_acct->slipip) {
: $svc_acct->slipip
). "</TD>";
my($attribute);
- foreach $attribute ( grep /^radius_/, fields('svc_acct') ) {
+ foreach $attribute ( grep /^radius_/, $svc_acct->fields ) {
#warn $attribute;
$attribute =~ /^radius_(.*)$/;
my $pattribute = $FS::raddb::attrib{$1};
@@ -134,7 +182,7 @@ if ($svc_acct->slipip) {
"<TD BGCOLOR=\"#ffffff\">". $svc_acct->getfield($attribute).
"</TD></TR>";
}
- foreach $attribute ( grep /^rc_/, fields('svc_acct') ) {
+ foreach $attribute ( grep /^rc_/, $svc_acct->fields ) {
#warn $attribute;
$attribute =~ /^rc_(.*)$/;
my $pattribute = $FS::raddb::attrib{$1};
@@ -149,8 +197,21 @@ if ($svc_acct->slipip) {
print '<TR><TD ALIGN="right">RADIUS groups</TD><TD BGCOLOR="#ffffff">'.
join('<BR>', $svc_acct->radius_groups). '</TD></TR>';
-print "</TABLE></TD></TR></TABLE>".
- '<BR>'. joblisting({'svcnum'=>$svcnum}, 1).
- "</BODY></HTML>";
+# Can this be abstracted further? Maybe a library function like
+# widget('HTML', 'view', $svc_acct) ? It would definitely make UI
+# style management easier.
+
+foreach (sort { $a cmp $b } $svc_acct->virtual_fields) {
+ print $svc_acct->pvf($_)->widget('HTML', 'view', $svc_acct->getfield($_)),
+ "\n";
+}
+%>
+</TABLE></TD></TR></TABLE>
+<%
+
+print '<BR><BR>';
+
+print join("\n", $conf->config('svc_acct-notes') ). '<BR><BR>'.
+ joblisting({'svcnum'=>$svcnum}, 1). '</BODY></HTML>';
%>
diff --git a/httemplate/view/svc_acct_sm.cgi b/httemplate/view/svc_acct_sm.cgi
deleted file mode 100755
index 4e5acc4..0000000
--- a/httemplate/view/svc_acct_sm.cgi
+++ /dev/null
@@ -1,58 +0,0 @@
-<!-- mason kludge -->
-<%
-
-my $conf = new FS::Conf;
-my $mydomain = $conf->config('domain');
-
-my($query) = $cgi->keywords;
-$query =~ /^(\d+)$/;
-my $svcnum = $1;
-my $svc_acct_sm = qsearchs('svc_acct_sm',{'svcnum'=>$svcnum});
-die "Unknown svcnum" unless $svc_acct_sm;
-
-my $cust_svc = qsearchs('cust_svc',{'svcnum'=>$svcnum});
-my $pkgnum = $cust_svc->getfield('pkgnum');
-my($cust_pkg, $custnum);
-if ($pkgnum) {
- $cust_pkg=qsearchs('cust_pkg',{'pkgnum'=>$pkgnum});
- $custnum=$cust_pkg->getfield('custnum');
-} else {
- $cust_pkg = '';
- $custnum = '';
-}
-
-my $part_svc = qsearchs('part_svc',{'svcpart'=> $cust_svc->svcpart } )
- or die "Unkonwn svcpart";
-
-print header('Mail Alias View', menubar(
- ( ( $pkgnum || $custnum )
- ? ( "View this package (#$pkgnum)" => "${p}view/cust_pkg.cgi?$pkgnum",
- "View this customer (#$custnum)" => "${p}view/cust_main.cgi?$custnum",
- )
- : ( "Cancel this (unaudited) account" =>
- "${p}misc/cancel-unaudited.cgi?$svcnum" )
- ),
- "Main menu" => $p,
-));
-
-my($domsvc,$domuid,$domuser) = (
- $svc_acct_sm->domsvc,
- $svc_acct_sm->domuid,
- $svc_acct_sm->domuser,
-);
-my $svc = $part_svc->svc;
-my $svc_domain = qsearchs('svc_domain',{'svcnum'=>$domsvc})
- or die "Corrupted database: no svc_domain.svcnum matching domsvc $domsvc";
-my $domain = $svc_domain->domain;
-my $svc_acct = qsearchs('svc_acct',{'uid'=>$domuid})
- or die "Corrupted database: no svc_acct.uid matching domuid $domuid";
-my $username = $svc_acct->username;
-
-print qq!<A HREF="${p}edit/svc_acct_sm.cgi?$svcnum">Edit this information</A>!,
- "<BR>Service #$svcnum",
- "<BR>Service: <B>$svc</B>",
- qq!<BR>Mail to <B>!, ( ($domuser eq '*') ? "<I>(anything)</I>" : $domuser ) , qq!</B>\@<B>$domain</B> forwards to <B>$username</B>\@$mydomain mailbox.!,
- '</BODY></HTML>'
-;
-
-%>
diff --git a/httemplate/view/svc_broadband.cgi b/httemplate/view/svc_broadband.cgi
new file mode 100644
index 0000000..a4ec756
--- /dev/null
+++ b/httemplate/view/svc_broadband.cgi
@@ -0,0 +1,143 @@
+<!-- mason kludge -->
+<%
+
+my($query) = $cgi->keywords;
+$query =~ /^(\d+)$/;
+my $svcnum = $1;
+my $svc_broadband = qsearchs( 'svc_broadband', { 'svcnum' => $svcnum } )
+ or die "svc_broadband: Unknown svcnum $svcnum";
+
+#false laziness w/all svc_*.cgi
+my $cust_svc = qsearchs( 'cust_svc', { 'svcnum' => $svcnum } );
+my $pkgnum = $cust_svc->getfield('pkgnum');
+my($cust_pkg, $custnum);
+if ($pkgnum) {
+ $cust_pkg = qsearchs( 'cust_pkg', { 'pkgnum' => $pkgnum } );
+ $custnum = $cust_pkg->custnum;
+} else {
+ $cust_pkg = '';
+ $custnum = '';
+}
+#eofalse
+
+my $router = $svc_broadband->addr_block->router;
+
+if (not $router) { die "Could not lookup router for svc_broadband (svcnum $svcnum)" };
+
+my (
+ $routername,
+ $routernum,
+ $speed_down,
+ $speed_up,
+ $ip_addr
+ ) = (
+ $router->getfield('routername'),
+ $router->getfield('routernum'),
+ $svc_broadband->getfield('speed_down'),
+ $svc_broadband->getfield('speed_up'),
+ $svc_broadband->getfield('ip_addr')
+ );
+%>
+
+<%=header('Broadband Service View', menubar(
+ ( ( $custnum )
+ ? ( "View this package (#$pkgnum)" => "${p}view/cust_pkg.cgi?$pkgnum",
+ "View this customer (#$custnum)" => "${p}view/cust_main.cgi?$custnum",
+ )
+ : ( "Cancel this (unaudited) website" =>
+ "${p}misc/cancel-unaudited.cgi?$svcnum" )
+ ),
+ "Main menu" => $p,
+))
+%>
+
+<A HREF="<%=${p}%>edit/svc_broadband.cgi?<%=$svcnum%>">Edit this information</A>
+<BR>
+<%=ntable("#cccccc")%>
+ <TR>
+ <TD>
+ <%=ntable("#cccccc",2)%>
+ <TR>
+ <TD ALIGN="right">Service number</TD>
+ <TD BGCOLOR="#ffffff"><%=$svcnum%></TD>
+ </TR>
+ <TR>
+ <TD ALIGN="right">Router</TD>
+ <TD BGCOLOR="#ffffff"><%=$routernum%>: <%=$routername%></TD>
+ </TR>
+ <TR>
+ <TD ALIGN="right">Download Speed</TD>
+ <TD BGCOLOR="#ffffff"><%=$speed_down%></TD>
+ </TR>
+ <TR>
+ <TD ALIGN="right">Upload Speed</TD>
+ <TD BGCOLOR="#ffffff"><%=$speed_up%></TD>
+ </TR>
+ <TR>
+ <TD ALIGN="right">IP Address</TD>
+ <TD BGCOLOR="#ffffff"><%=$ip_addr%></TD>
+ </TR>
+ <TR COLSPAN="2"><TD></TD></TR>
+
+<%
+foreach (sort { $a cmp $b } $svc_broadband->virtual_fields) {
+ print $svc_broadband->pvf($_)->widget('HTML', 'view',
+ $svc_broadband->getfield($_)), "\n";
+}
+
+%>
+ </TABLE>
+ </TD>
+ </TR>
+</TABLE>
+
+<BR>
+<%=ntable("#cccccc", 2)%>
+<%
+ my $sb_router = qsearchs('router', { svcnum => $svcnum });
+ if ($sb_router) {
+ %>
+ <B>Router associated: <%=$sb_router->routername%> </B>
+ <A HREF="<%=popurl(2)%>edit/router.cgi?<%=$sb_router->routernum%>">
+ (details)
+ </A>
+ <BR>
+ <% my @addr_block;
+ if (@addr_block = $sb_router->addr_block) {
+ %>
+ <B>Address space </B>
+ <A HREF="<%=popurl(2)%>browse/addr_block.cgi">
+ (edit)
+ </A>
+ <BR>
+ <% print ntable("#cccccc", 1);
+ foreach (@addr_block) { %>
+ <TR>
+ <TD><%=$_->ip_gateway%>/<%=$_->ip_netmask%></TD>
+ </TR>
+ <% } %>
+ </TABLE>
+ <% } else { %>
+ <B>No address space allocated.</B>
+ <% } %>
+ <BR>
+ <%
+ } else {
+%>
+
+<FORM METHOD="GET" ACTION="<%=popurl(2)%>edit/router.cgi">
+ <INPUT TYPE="hidden" NAME="svcnum" VALUE="<%=$svcnum%>">
+Add router named
+ <INPUT TYPE="text" NAME="routername" SIZE="32" VALUE="Broadband router (<%=$svcnum%>)">
+ <INPUT TYPE="submit" VALUE="Add router">
+</FORM>
+
+<%
+}
+%>
+
+<BR>
+<%=joblisting({'svcnum'=>$svcnum}, 1)%>
+ </BODY>
+</HTML>
+
diff --git a/httemplate/view/svc_domain.cgi b/httemplate/view/svc_domain.cgi
index b70ac8f..fd017de 100755
--- a/httemplate/view/svc_domain.cgi
+++ b/httemplate/view/svc_domain.cgi
@@ -46,7 +46,7 @@ my $domain = $svc_domain->domain;
Service #<%= $svcnum %>
<BR>Service: <B><%= $part_svc->svc %></B>
<BR>Domain name: <B><%= $domain %></B>
-<BR>Catch all email <A HREF="${p}misc/catchall.cgi?<%= $svcnum %>">(change)</A>:
+<BR>Catch all email <A HREF="<%= ${p} %>misc/catchall.cgi?<%= $svcnum %>">(change)</A>:
<%= $email ? "<B>$email</B>" : "<I>(none)<I>" %>
<BR><BR><A HREF="http://www.geektools.com/cgi-bin/proxy.cgi?query=<%=$domain%>;targetnic=auto">View whois information.</A>
<BR><BR>
@@ -85,7 +85,7 @@ Service #<%= $svcnum %>
<INPUT TYPE="text" NAME="reczone">
<INPUT TYPE="hidden" NAME="recaf" VALUE="IN"> IN
<SELECT NAME="rectype">
-<% foreach (qw( A NS CNAME MX) ) { %>
+<% foreach (qw( A NS CNAME MX PTR) ) { %>
<OPTION VALUE="<%=$_%>"><%=$_%></OPTION>
<% } %>
</SELECT>
diff --git a/httemplate/view/svc_external.cgi b/httemplate/view/svc_external.cgi
new file mode 100644
index 0000000..e5c977f
--- /dev/null
+++ b/httemplate/view/svc_external.cgi
@@ -0,0 +1,52 @@
+<!-- mason kludge -->
+<%
+
+my($query) = $cgi->keywords;
+$query =~ /^(\d+)$/;
+my $svcnum = $1;
+my $svc_external = qsearchs( 'svc_external', { 'svcnum' => $svcnum } )
+ or die "svc_external: Unknown svcnum $svcnum";
+
+#false laziness w/all svc_*.cgi
+my $cust_svc = qsearchs( 'cust_svc', { 'svcnum' => $svcnum } );
+my $pkgnum = $cust_svc->getfield('pkgnum');
+my($cust_pkg, $custnum);
+if ($pkgnum) {
+ $cust_pkg = qsearchs( 'cust_pkg', { 'pkgnum' => $pkgnum } );
+ $custnum = $cust_pkg->custnum;
+} else {
+ $cust_pkg = '';
+ $custnum = '';
+}
+#eofalse
+
+%>
+
+<%= header('External Service View', menubar(
+ ( ( $custnum )
+ ? ( "View this package (#$pkgnum)" => "${p}view/cust_pkg.cgi?$pkgnum",
+ "View this customer (#$custnum)" => "${p}view/cust_main.cgi?$custnum",
+ )
+ : ( "Cancel this (unaudited) external service" =>
+ "${p}misc/cancel-unaudited.cgi?$svcnum" )
+ ),
+ "Main menu" => $p,
+)) %>
+
+<A HREF="<%=$p%>edit/svc_external.cgi?<%=$svcnum%>">Edit this information</A><BR>
+<%= ntable("#cccccc") %><TR><TD><%= ntable("#cccccc",2) %>
+
+<TR><TD ALIGN="right">Service number</TD>
+ <TD BGCOLOR="#ffffff"><%= $svcnum %></TD></TR>
+<TR><TD ALIGN="right">External ID</TD>
+ <TD BGCOLOR="#ffffff"><%= $svc_external->id %></TD></TR>
+<TR><TD ALIGN="right">Title</TD>
+ <TD BGCOLOR="#ffffff"><%= $svc_external->title %></TD></TR>
+
+<% foreach (sort { $a cmp $b } $svc_external->virtual_fields) { %>
+ <%= $svc_external->pvf($_)->widget('HTML', 'view', $svc_external->getfield($_)) %>
+<% } %>
+
+</TABLE></TD></TR></TABLE>
+<BR><%= joblisting({'svcnum'=>$svcnum}, 1) %>
+</BODY></HTML>
diff --git a/httemplate/view/svc_forward.cgi b/httemplate/view/svc_forward.cgi
index c8d1d62..6509724 100755
--- a/httemplate/view/svc_forward.cgi
+++ b/httemplate/view/svc_forward.cgi
@@ -28,7 +28,7 @@ print header('Mail Forward View', menubar(
? ( "View this package (#$pkgnum)" => "${p}view/cust_pkg.cgi?$pkgnum",
"View this customer (#$custnum)" => "${p}view/cust_main.cgi?$custnum",
)
- : ( "Cancel this (unaudited) account" =>
+ : ( "Cancel this (unaudited) mail forward" =>
"${p}misc/cancel-unaudited.cgi?$svcnum" )
),
"Main menu" => $p,
@@ -39,16 +39,25 @@ my($srcsvc,$dstsvc,$dst) = (
$svc_forward->dstsvc,
$svc_forward->dst,
);
+my $src = $svc_forward->dbdef_table->column('src') ? $svc_forward->src : '';
+
my $svc = $part_svc->svc;
-my $svc_acct = qsearchs('svc_acct',{'svcnum'=>$srcsvc})
- or die "Corrupted database: no svc_acct.svcnum matching srcsvc $srcsvc";
-my $source = $svc_acct->email;
+
+my $source;
+if ($srcsvc) {
+ my $svc_acct = qsearchs('svc_acct',{'svcnum'=>$srcsvc})
+ or die "Corrupted database: no svc_acct.svcnum matching srcsvc $srcsvc";
+ $source = $svc_acct->email;
+} else {
+ $source = $src;
+}
+
my $destination;
if ($dstsvc) {
my $svc_acct = qsearchs('svc_acct',{'svcnum'=>$dstsvc})
or die "Corrupted database: no svc_acct.svcnum matching dstsvc $dstsvc";
$destination = $svc_acct->email;
-}else{
+} else {
$destination = $dst;
}
@@ -61,7 +70,14 @@ print qq!<A HREF="${p}edit/svc_forward.cgi?$svcnum">Edit this information</A>!.
qq!<TR><TD ALIGN="right">Email to</TD>!.
qq!<TD BGCOLOR="#ffffff">$source</TD></TR>!.
qq!<TR><TD ALIGN="right">Forwards to </TD>!.
- qq!<TD BGCOLOR="#ffffff">$destination</TD></TR></TABLE>!.
+ qq!<TD BGCOLOR="#ffffff">$destination</TD></TR>!;
+
+foreach (sort { $a cmp $b } $svc_forward->virtual_fields) {
+ print $svc_forward->pvf($_)->widget('HTML', 'view', $svc_forward->getfield($_)),
+ "\n";
+}
+
+print qq! </TABLE>!.
'<BR>'. joblisting({'svcnum'=>$svcnum}, 1).
'</BODY></HTML>'
;
diff --git a/httemplate/view/svc_www.cgi b/httemplate/view/svc_www.cgi
index 9fa9661..7a716b4 100644
--- a/httemplate/view/svc_www.cgi
+++ b/httemplate/view/svc_www.cgi
@@ -28,11 +28,7 @@ my $email = $svc_acct->email;
my $domain_record = qsearchs('domain_record', { 'recnum' => $svc_www->recnum } )
or die "svc_www: Unknown recnum ". $svc_www->recnum;
-my $www = $domain_record->reczone;
-unless ( $www =~ /\.$/ ) {
- my $svc_domain = qsearchs('svc_domain', { svcnum=>$domain_record->svcnum } );
- $www .= '.'. $svc_domain->domain;
-}
+my $www = $domain_record->zone;
print header('Website View', menubar(
( ( $custnum )
@@ -51,8 +47,15 @@ print header('Website View', menubar(
qq!<TR><TD ALIGN="right">Website name</TD>!.
qq!<TD BGCOLOR="#ffffff"><A HREF="http://$www">$www<A></TD></TR>!.
qq!<TR><TD ALIGN="right">Account</TD>!.
- qq!<TD BGCOLOR="#ffffff"><A HREF="${p}view/svc_acct.cgi?$usersvc">$email</A></TD></TR>!.
- '</TABLE></TD></TR></TABLE>'.
+ qq!<TD BGCOLOR="#ffffff"><A HREF="${p}view/svc_acct.cgi?$usersvc">$email</A></TD></TR>!;
+
+foreach (sort { $a cmp $b } $svc_www->virtual_fields) {
+ print $svc_www->pvf($_)->widget('HTML', 'view', $svc_www->getfield($_)),
+ "\n";
+}
+
+
+print '</TABLE></TD></TR></TABLE>'.
'<BR>'. joblisting({'svcnum'=>$svcnum}, 1).
'</BODY></HTML>'
;