diff options
Diffstat (limited to 'httemplate')
117 files changed, 9761 insertions, 0 deletions
diff --git a/httemplate/.htaccess b/httemplate/.htaccess new file mode 100755 index 000000000..f8c6b9c0c --- /dev/null +++ b/httemplate/.htaccess @@ -0,0 +1,3 @@ +AuthName        Freeside +AuthType        Basic +require valid-user diff --git a/httemplate/browse/agent.cgi b/httemplate/browse/agent.cgi new file mode 100755 index 000000000..ebec068fa --- /dev/null +++ b/httemplate/browse/agent.cgi @@ -0,0 +1,66 @@ +<!-- mason kludge --> +<% + +#Begin silliness +# +#use FS::UI::CGI; +#use FS::UI::agent; +# +#$ui = new FS::UI::agent; +#$ui->browse; +#exit; +#__END__ +#End silliness + +print header('Agent Listing', menubar( +  'Main Menu'   => $p, +  'Agent Types' => $p. 'browse/agent_type.cgi', +#  'Add new agent' => '../edit/agent.cgi' +)), <<END; +Agents are resellers of your service. Agents may be limited to a subset of your +full offerings (via their type).<BR><BR> +END +print &table(), <<END; +      <TR> +        <TH COLSPAN=2>Agent</TH> +        <TH>Type</TH> +        <TH><FONT SIZE=-1>Freq. (unimp.)</FONT></TH> +        <TH><FONT SIZE=-1>Prog. (unimp.)</FONT></TH> +      </TR> +END +#        <TH><FONT SIZE=-1>Agent #</FONT></TH> +#        <TH>Agent</TH> + +foreach my $agent ( sort {  +  $a->getfield('agentnum') <=> $b->getfield('agentnum') +} 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; +      <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> +      </TR> +END + +} + +print <<END; +      <TR> +        <TD COLSPAN=2><A HREF="${p}edit/agent.cgi"><I>Add a new agent</I></A></TD> +        <TD><A HREF="${p}edit/agent_type.cgi"><I>Add a new agent type</I></A></TD> +      </TR> +    </TABLE> + +  </BODY> +</HTML> +END + +%> diff --git a/httemplate/browse/agent_type.cgi b/httemplate/browse/agent_type.cgi new file mode 100755 index 000000000..eb20c6404 --- /dev/null +++ b/httemplate/browse/agent_type.cgi @@ -0,0 +1,56 @@ +<!-- mason kludge --> +<% + +print header("Agent Type Listing", menubar( +  'Main Menu' => $p, +)), "Agent types define groups of packages that you can then assign to". +    " particular agents.<BR><BR>", &table(), <<END; +      <TR> +        <TH COLSPAN=2>Agent Type</TH> +        <TH COLSPAN=2>Packages</TH> +      </TR> +END + +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); +  $rowspan = int($rowspan/2+0.5) ; +  print <<END; +      <TR> +        <TD ROWSPAN=$rowspan><A HREF="${p}edit/agent_type.cgi?$hashref->{typenum}"> +          $hashref->{typenum} +        </A></TD> +        <TD ROWSPAN=$rowspan><A HREF="${p}edit/agent_type.cgi?$hashref->{typenum}">$hashref->{atype}</A></TD> +END + +  my($type_pkgs); +  my($tdcount) = -1 ; +  foreach $type_pkgs ( @type_pkgs ) { +    my($pkgpart)=$type_pkgs->getfield('pkgpart'); +    my($part_pkg) = qsearchs('part_pkg',{'pkgpart'=> $pkgpart }); +    print qq!<TR>! if ($tdcount == 0) ; +    $tdcount = 0 if ($tdcount == -1) ; +    print qq!<TD><A HREF="${p}edit/part_pkg.cgi?$pkgpart">!, +          $part_pkg->getfield('pkg'),"</A></TD>"; +    $tdcount ++ ; +    if ($tdcount == 2) +    { +	print qq!</TR>\n! ; +	$tdcount = 0 ; +    } +  } + +  print "</TR>"; +} + +print <<END; +  <TR><TD COLSPAN=4><I><A HREF="${p}edit/agent_type.cgi">Add a new agent type</A></I></TD></TR> +    </TABLE> +  </BODY> +</HTML> +END + +%> diff --git a/httemplate/browse/cust_main_county.cgi b/httemplate/browse/cust_main_county.cgi new file mode 100755 index 000000000..8fbd7faad --- /dev/null +++ b/httemplate/browse/cust_main_county.cgi @@ -0,0 +1,103 @@ +<!-- mason kludge --> +<% + +print header("Tax Rate Listing", menubar( +  'Main Menu' => $p, +  'Edit tax rates' => $p. "edit/cust_main_county.cgi", +)),<<END; +    Click on <u>expand country</u> to specify a country's tax rates by state. +    <BR>Click on <u>expand state</u> to specify a state's tax rates by county. +    <BR><BR> +END +print &table(), <<END; +      <TR> +        <TH><FONT SIZE=-1>Country</FONT></TH> +        <TH><FONT SIZE=-1>State</FONT></TH> +        <TH>County</TH> +        <TH><FONT SIZE=-1>Tax</FONT></TH> +      </TR> +END + +my @regions = sort {    $a->country cmp $b->country +                     or $a->state   cmp $b->state +                     or $a->county  cmp $b->county +                   } qsearch('cust_main_county',{}); + +my $sup=0; +#foreach $cust_main_county ( @regions ) { +for ( my $i=0; $i<@regions; $i++ ) {  +  my $cust_main_county = $regions[$i]; +  my $hashref = $cust_main_county->hashref; +  print <<END; +      <TR> +        <TD>$hashref->{country}</TD> +END + +  my $j; +  if ( $sup ) { +    $sup--; +  } else { + +    #lookahead +    for ( $j=1; $i+$j<@regions; $j++ ) { +      last if $hashref->{country} ne $regions[$i+$j]->country +           || $hashref->{state} ne $regions[$i+$j]->state +           || $hashref->{tax} != $regions[$i+$j]->tax; +    } + +    my $newsup=0; +    if ( $j>1 && $i+$j+1 < @regions +         && ( $hashref->{state} ne $regions[$i+$j+1]->state  +              || $hashref->{country} ne $regions[$i+$j+1]->country +              ) +         && ( ! $i +              || $hashref->{state} ne $regions[$i-1]->state  +              || $hashref->{country} ne $regions[$i-1]->country +              ) +       ) { +       $sup = $j-1; +    } else { +      $j = 1; +    } + +    print "<TD ROWSPAN=$j>", $hashref->{state} +        ? $hashref->{state} +        : qq!(ALL) <FONT SIZE=-1>!. +          qq!<A HREF="${p}edit/cust_main_county-expand.cgi?!. $hashref->{taxnum}. +          qq!">expand country</A></FONT>!; + +    print qq! <FONT SIZE=-1><A HREF="${p}edit/process/cust_main_county-collapse.cgi?!. $hashref->{taxnum}. qq!">collapse state</A></FONT>! if $j>1; + +    print "</TD>"; +  } + +#  $sup=$newsup; + +  print "<TD>"; +  if ( $hashref->{county} ) { +    print $hashref->{county}; +  } else { +    print "(ALL)"; +    if ( $hashref->{state} ) { +      print qq!<FONT SIZE=-1>!. +          qq!<A HREF="${p}edit/cust_main_county-expand.cgi?!. $hashref->{taxnum}. +          qq!">expand state</A></FONT>!; +    } +  } +  print "</TD>"; + +  print <<END; +        <TD>$hashref->{tax}%</TD> +      </TR> +END + +} + +print <<END; +    </TABLE> +    </CENTER> +  </BODY> +</HTML> +END + +%> diff --git a/httemplate/browse/cust_pay_batch.cgi b/httemplate/browse/cust_pay_batch.cgi new file mode 100755 index 000000000..608a58d0d --- /dev/null +++ b/httemplate/browse/cust_pay_batch.cgi @@ -0,0 +1,52 @@ +<!-- mason kludge --> +<% + +print header("Pending credit card batch", menubar( +  'Main Menu' => $p, +#  'Add new referral' => "../edit/part_referral.cgi", +)), &table(), <<END; +      <TR> +        <TH>#</TH> +        <TH><font size=-1>inv#</font></TH> +        <TH COLSPAN=2>Customer</TH> +        <TH>Card name</TH> +        <TH>Card</TH> +        <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; +      <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> +      </TR> +END + +} + +print <<END; +    </TABLE> +  </BODY> +</HTML> +END + +%> diff --git a/httemplate/browse/nas.cgi b/httemplate/browse/nas.cgi new file mode 100755 index 000000000..9ccbfe632 --- /dev/null +++ b/httemplate/browse/nas.cgi @@ -0,0 +1,80 @@ +<!-- mason kludge --> +<% + +print header('NAS ports', menubar( +  'Main Menu' => $p, +)); + +my $now = time; + +foreach my $nas ( sort { $a->nasnum <=> $b->nasnum } qsearch( 'nas', {} ) ) { +  print $nas->nasnum. ": ". $nas->nas. " ". +        $nas->nasfqdn. " (". $nas->nasip. ") ". +        "as of ". time2str("%c",$nas->last). +        " (". &pretty_interval($now - $nas->last). " ago)<br>". +        &table(). "<TR><TH>Nas<BR>Port #</TH><TH>Global<BR>Port #</BR></TH>". +        "<TH>IP address</TH><TH>User</TH><TH>Since</TH><TH>Duration</TH><TR>", +  ; +  foreach my $port ( sort { +    $a->nasport <=> $b->nasport || $a->portnum <=> $b->portnum +  } qsearch( 'port', { 'nasnum' => $nas->nasnum } ) ) { +    my $session = $port->session; +    my($user, $since, $pretty_since, $duration); +    if ( ! $session ) { +      $user = "(empty)"; +      $since = 0; +      $pretty_since = "(never)"; +      $duration = ''; +    } elsif ( $session->logout ) { +      $user = "(empty)"; +      $since = $session->logout; +    } else { +      my $svc_acct = $session->svc_acct; +      $user = "<A HREF=\"$p/view/svc_acct.cgi?". $svc_acct->svcnum. "\">". +              $svc_acct->username. "</A>"; +      $since = $session->login; +    } +    $pretty_since = time2str("%c", $since) if $since; +    $duration = pretty_interval( $now - $since ). " ago" +      unless defined($duration); +    print "<TR><TD>". $port->nasport. "</TD><TD>". $port->portnum. "</TD><TD>". +          $port->ip. "</TD><TD>$user</TD><TD>$pretty_since". +          "</TD><TD>$duration</TD></TR>" +    ; +  } +  print "</TABLE><BR>"; +} + +#Time::Duration?? +sub pretty_interval { +  my $interval = shift; +  my %howlong = ( +    '604800' => 'week', +    '86400'  => 'day', +    '3600'   => 'hour', +    '60'     => 'minute', +    '1'      => 'second', +  ); + +  my $pretty = ""; +  foreach my $key ( sort { $b <=> $a } keys %howlong ) { +    my $value = int( $interval / $key ); +    if ( $value  ) { +      if ( $value == 1 ) { +        $pretty .= +          ( $howlong{$key} eq 'hour' ? 'an ' : 'a ' ). $howlong{$key}. " " +      } else { +        $pretty .= $value. ' '. $howlong{$key}. 's '; +      } +    } +    $interval -= $value * $key; +  } +  $pretty =~ /^\s*(\S.*\S)\s*$/; +  $1; +}  + +#print &table(), <<END; +#<TR> +#  <TH>#</TH> +#  <TH>NAS</ +%> diff --git a/httemplate/browse/part_bill_event.cgi b/httemplate/browse/part_bill_event.cgi new file mode 100755 index 000000000..1d674f749 --- /dev/null +++ b/httemplate/browse/part_bill_event.cgi @@ -0,0 +1,73 @@ +<!-- mason kludge --> +<%  + +my %search; +if ( $cgi->param('showdisabled') ) { +  %search = (); +} else { +  %search = ( 'disabled' => '' ); +} + +my @part_bill_event = qsearch('part_bill_event', \%search ); +my $total = scalar(@part_bill_event); + +%> +<%= header('Invoice Event Listing', menubar( 'Main Menu' => $p) ) %> + +    Invoice events are actions taken on overdue invoices.<BR><BR> +<%= $total %> events +<%= $cgi->param('showdisabled') +      ? do { $cgi->param('showdisabled', 0); +             '( <a href="'. $cgi->self_url. '">hide disabled events</a> )'; } +      : do { $cgi->param('showdisabled', 1); +             '( <a href="'. $cgi->self_url. '">show disabled events</a> )'; } +%> +<%= table() %> +  <TR> +    <TH COLSPAN=<%= $cgi->param('showdisabled') ? 2 : 3 %>>Event</TH> +    <TH>Payby</TH> +    <TH>After</TH> +    <TH>Action</TH> +    <TH>Options</TH> +    <TH>Code</TH> +  </TR> + +<% foreach my $part_bill_event ( sort {    $a->payby     cmp $b->payby +                                        || $a->seconds   <=> $b->seconds +                                        || $a->weight    <=> $b->weight +                                        || $a->eventpart <=> $b->eventpart +                                      } @part_bill_event ) { +     my $url = "${p}edit/part_bill_event.cgi?". $part_bill_event->eventpart; +     use Time::Duration; +     my $delay = duration_exact($part_bill_event->seconds); +     my $plandata = $part_bill_event->plandata; +     $plandata =~ s/\n/<BR>/go; +%> +  <TR> +    <TD><A HREF="<%= $url %>"> +      <%= $part_bill_event->eventpart %></A></TD> +<% unless ( $cgi->param('showdisabled') ) { %> +    <TD> +      <%= $part_bill_event->disabled ? 'DISABLED' : '' %></TD> +<% } %> +    <TD><A HREF="<%= $url %>"> +      <%= $part_bill_event->event %></A></TD> +    <TD> +      <%= $part_bill_event->payby %></TD> +    <TD> +      <%= $delay %></TD> +    <TD> +      <%= $part_bill_event->plan %></TD> +    <TD> +      <%= $plandata %></TD> +    <TD><FONT SIZE="-1"> +      <%= $part_bill_event->eventcode %></FONT></TD> +  </TR> +<% } %> + +  <TR> +    <TD COLSPAN=8><A HREF="<%= $p %>edit/part_bill_event.cgi"><I>Add a new invoice event</I></A></TD> +  </TR> +</TABLE> +</BODY> +</HTML> diff --git a/httemplate/browse/part_pkg.cgi b/httemplate/browse/part_pkg.cgi new file mode 100755 index 000000000..0af64e737 --- /dev/null +++ b/httemplate/browse/part_pkg.cgi @@ -0,0 +1,98 @@ +<!-- mason kludge --> +<% + +my %search; +if ( $cgi->param('showdisabled') ) { +  %search = (); +} else { +  %search = ( 'disabled' => '' ); +} + +my @part_pkg = qsearch('part_pkg', \%search ); +my $total = scalar(@part_pkg); + +print 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>". +  "$total packages "; + +if ( $cgi->param('showdisabled') ) { +  $cgi->param('showdisabled', 0); +  print qq!( <a href="!. $cgi->self_url. qq!">hide disabled packages</a> )!; +} else { +  $cgi->param('showdisabled', 1); +  print qq!( <a href="!. $cgi->self_url. qq!">show disabled packages</a> )!; +} + +my $colspan = $cgi->param('showdisabled') ? 2 : 3; +print &table(), <<END; +      <TR> +        <TH COLSPAN=2>Package</TH> +        <TH>Comment</TH> +        <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 + +foreach my $part_pkg ( sort {  +  $a->getfield('pkgpart') <=> $b->getfield('pkgpart') +} @part_pkg ) { +  my($hashref)=$part_pkg->hashref; +  my(@pkg_svc)=grep $_->getfield('quantity'), +    qsearch('pkg_svc',{'pkgpart'=> $hashref->{pkgpart} }); +  my($rowspan)=scalar(@pkg_svc); +  my $plandata; +  if ( $hashref->{plan} ) { +    $plandata = $hashref->{plandata}; +    $plandata =~ s/^(\w+)=/$1 /mg; +    $plandata =~ s/\n/<BR>/g; +  } else { +    $hashref->{plan} = "(legacy)"; +    $plandata = "Setup ". $hashref->{setup}. +                "<BR>Recur ". $hashref->{recur}; +  } +  print <<END; +      <TR> +        <TD ROWSPAN=$rowspan><A HREF="${p}edit/part_pkg.cgi?$hashref->{pkgpart}">$hashref->{pkgpart}</A></TD> +END + +  unless ( $cgi->param('showdisabled') ) { +    print "<TD ROWSPAN=$rowspan>"; +    print "DISABLED" if $hashref->{disabled}; +    print '</TD>'; +  } + +  print <<END; +        <TD ROWSPAN=$rowspan><A HREF="${p}edit/part_pkg.cgi?$hashref->{pkgpart}">$hashref->{pkg}</A></TD> +        <TD ROWSPAN=$rowspan>$hashref->{comment}</TD> +        <TD ROWSPAN=$rowspan>$hashref->{freq}</TD> +        <TD ROWSPAN=$rowspan>$hashref->{plan}</TD> +        <TD ROWSPAN=$rowspan>$plandata</TD> +END + +  my($pkg_svc); +  my($n)=""; +  foreach $pkg_svc ( @pkg_svc ) { +    my($svcpart)=$pkg_svc->getfield('svcpart'); +    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"; +    $n="<TR>"; +  } + +  print "</TR>"; +} + +print <<END; +   <TR><TD COLSPAN=8><I><A HREF="${p}edit/part_pkg.cgi">Add a new package definition</A></I></TD></TR> +    </TABLE> +  </BODY> +</HTML> +END +%> diff --git a/httemplate/browse/part_referral.cgi b/httemplate/browse/part_referral.cgi new file mode 100755 index 000000000..76cc22659 --- /dev/null +++ b/httemplate/browse/part_referral.cgi @@ -0,0 +1,38 @@ +<!-- mason kludge --> +<% + +print header("Referral Listing", menubar( +  'Main Menu' => $p, +#  'Add new referral' => "../edit/part_referral.cgi", +)), "Where a customer heard about your service. Tracked for informational purposes.<BR><BR>", &table(), <<END; +      <TR> +        <TH COLSPAN=2>Referral</TH> +      </TR> +END + +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> +      </TR> +END + +} + +print <<END; +      <TR> +        <TD COLSPAN=2><A HREF="${p}edit/part_referral.cgi"><I>Add a new referral</I></A></TD> +      </TR> +    </TABLE> +    </CENTER> +  </BODY> +</HTML> +END + +%> diff --git a/httemplate/browse/part_svc.cgi b/httemplate/browse/part_svc.cgi new file mode 100755 index 000000000..4590dc8b5 --- /dev/null +++ b/httemplate/browse/part_svc.cgi @@ -0,0 +1,80 @@ +<!-- mason kludge --> +<%  + +my %search; +if ( $cgi->param('showdisabled') ) { +  %search = (); +} else { +  %search = ( 'disabled' => '' ); +} + +my @part_svc = qsearch('part_svc', \%search ); +my $total = scalar(@part_svc); + +%> +<%= header('Service Definition Listing', menubar( 'Main Menu' => $p) ) %> + +    Services are items you offer to your customers.<BR><BR> +<%= $total %> services +<%= $cgi->param('showdisabled') +      ? do { $cgi->param('showdisabled', 0); +             '( <a href="'. $cgi->self_url. '">hide disabled services</a> )'; } +      : do { $cgi->param('showdisabled', 1); +             '( <a href="'. $cgi->self_url. '">show disabled services</a> )'; } +%> +<%= table() %> +  <TR> +    <TH COLSPAN=<%= $cgi->param('showdisabled') ? 2 : 3 %>>Service</TH> +    <TH>Table</TH> +    <TH>Field</TH> +    <TH COLSPAN=2>Modifier</TH> +  </TR> + +<% foreach my $part_svc ( sort { +     $a->getfield('svcpart') <=> $b->getfield('svcpart') +   } @part_svc ) { +     my($hashref)=$part_svc->hashref; +     my($svcdb)=$hashref->{svcdb}; +     my @fields = +       grep { $_ ne 'svcnum' && $part_svc->part_svc_column($_)->columnflag } +            fields($svcdb); + +     my($rowspan)=scalar(@fields) || 1; +     my $url = "${p}edit/part_svc.cgi?$hashref->{svcpart}"; +%> + +  <TR> +    <TD ROWSPAN=<%= $rowspan %>><A HREF="<%= $url %>"> +      <%= $hashref->{svcpart} %></A></TD> +<% unless ( $cgi->param('showdisabled') ) { %> +    <TD ROWSPAN=<%= $rowspan %>> +      <%= $hashref->{disabled} ? 'DISABLED' : '' %></TD> +<% } %> +    <TD ROWSPAN=<%= $rowspan %>><A HREF="<%= $url %>"> +      <%= $hashref->{svc} %></A></TD> +    <TD ROWSPAN=<%= $rowspan %>> +      <%= $hashref->{svcdb} %></TD> + +<%   my($n1)=''; +     foreach my $field ( @fields ) { +       my $flag = $part_svc->part_svc_column($field)->columnflag; +%> +     <%= $n1 %><TD><%= $field %></TD><TD> + +<%     if ( $flag eq "D" ) { print "Default"; } +         elsif ( $flag eq "F" ) { print "Fixed"; } +         else { print "(Unknown!)"; } +%> +       </TD><TD><%= $part_svc->part_svc_column($field)->columnvalue%></TD> +<%     $n1="</TR><TR>"; +     } +%> +  </TR> +<% } %> + +  <TR> +    <TD COLSPAN=6><A HREF="<%= $p %>edit/part_svc.cgi"><I>Add a new service definition</I></A></TD> +  </TR> +</TABLE> +</BODY> +</HTML> diff --git a/httemplate/browse/queue.cgi b/httemplate/browse/queue.cgi new file mode 100755 index 000000000..7fce1f5df --- /dev/null +++ b/httemplate/browse/queue.cgi @@ -0,0 +1,47 @@ +<!-- mason kludge --> +<% + +print header("Job Queue", menubar( +  'Main Menu' => $p, +#  'Add new referral' => "../edit/part_referral.cgi", +)), &table(), <<END; +      <TR> +        <TH COLSPAN=2>Job</TH> +        <TH>Args</TH> +        <TH>Date</TH> +        <TH>Status</TH> +      </TR> +END + +foreach my $queue ( sort {  +  $a->getfield('jobnum') <=> $b->getfield('jobnum') +} qsearch('queue',{}) ) { +  my($hashref)=$queue->hashref; +  my $jobnum = $hashref->{jobnum}; +  my $args = join(' ', $queue->args); +  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/queue.cgi?jobnum=$jobnum&action=new">retry</A> |!. +      qq! <A HREF="$p/edit/queue.cgi?jobnum$jobnum&action=del">remove </A> )!; +  } +  print <<END; +      <TR> +        <TD>$jobnum</TD> +        <TD>$hashref->{job}</TD> +        <TD>$args</TD> +        <TD>$date</TD> +        <TD>$status</TD> +      </TR> +END + +} + +print <<END; +    </TABLE> +  </BODY> +</HTML> +END + +%> diff --git a/httemplate/browse/svc_acct_pop.cgi b/httemplate/browse/svc_acct_pop.cgi new file mode 100755 index 000000000..fb42aa7e6 --- /dev/null +++ b/httemplate/browse/svc_acct_pop.cgi @@ -0,0 +1,52 @@ +<!-- mason kludge --> +<% + +print header('Access Number Listing', menubar( +  'Main Menu' => $p, +)), "Points of Presence<BR><BR>", &table(), <<END; +      <TR> +        <TH></TH> +        <TH>City</TH> +        <TH>State</TH> +        <TH>Area code</TH> +        <TH>Exchange</TH> +        <TH>Local</TH> +      </TR> +END + +foreach my $svc_acct_pop ( sort {  +  #$a->getfield('popnum') <=> $b->getfield('popnum') +  $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; +      <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> +      </TR> +END + +} + +print <<END; +      <TR> +        <TD COLSPAN=5><A HREF="${p}edit/svc_acct_pop.cgi"><I>Add new Access Number</I></A></TD> +      </TR> +    </TABLE> +    </CENTER> +  </BODY> +</HTML> +END + +%> diff --git a/httemplate/config/config-process.cgi b/httemplate/config/config-process.cgi new file mode 100644 index 000000000..50f0d34ff --- /dev/null +++ b/httemplate/config/config-process.cgi @@ -0,0 +1,45 @@ +<% +  my $conf = new FS::Conf; +  $FS::Conf::DEBUG = 1; +  my @config_items = $conf->config_items; + +  foreach my $i ( @config_items ) { +    my @touch = (); +    my @delete = (); +    my $n = 0; +    foreach my $type ( ref($i->type) ? @{$i->type} : $i->type ) { +      if ( $type eq '' ) { +      } elsif ( $type eq 'textarea' ) { +        if ( $cgi->param($i->key. $n) ne '' ) { +          my $value = $cgi->param($i->key. $n); +          $value =~ s/\r\n/\n/g; #browsers? +          $conf->set($i->key, $value); +        } else { +          $conf->delete($i->key); +        } +      } elsif ( $type eq 'checkbox' ) { +#        if ( defined($cgi->param($i->key. $n)) && $cgi->param($i->key. $n) ) { +        if ( defined $cgi->param($i->key. $n) ) { +          #$conf->touch($i->key); +          push @touch, $i->key; +        } else { +          #$conf->delete($i->key); +          push @delete, $i->key; +        } +      } elsif ( $type eq 'text' )  { +        if ( $cgi->param($i->key. $n) ne '' ) { +          $conf->set($i->key, $cgi->param($i->key. $n)); +        } else { +          $conf->delete($i->key); +        } +      } else { +      } +      $n++; +    } +   # warn @touch; +    $conf->touch($_) foreach @touch; +    $conf->delete($_) foreach @delete; +  } + +%> +<%= $cgi->redirect("config-view.cgi") %> diff --git a/httemplate/config/config-view.cgi b/httemplate/config/config-view.cgi new file mode 100644 index 000000000..b041adaed --- /dev/null +++ b/httemplate/config/config-view.cgi @@ -0,0 +1,49 @@ +<!-- mason kludge --> +<%= header('View Configuration', menubar( 'Main Menu' => $p, +                                     'Edit Configuration' => 'config.cgi' ) ) %> + +<% my $conf = new FS::Conf; my @config_items = $conf->config_items; %> + +<% foreach my $section ( qw(required billing username password UI session +                            shell mail radius apache BIND +                           ), +                         '', 'depreciated') { %> +  <%= table("#cccccc", 2) %> +  <tr> +    <th colspan="2" bgcolor="#dcdcdc"> +      <%= ucfirst($section || 'unclassified') %> configuration options +    </th> +  </tr> +  <% foreach my $i (grep $_->section eq $section, @config_items) { %> +    <tr> +      <td><a name="<%= $i->key %>"> +        <b><%= $i->key %></b> - <%= $i->description %> +      </a></td> +      <td><table border=0> +        <% foreach my $type ( ref($i->type) ? @{$i->type} : $i->type ) { +             my $n = 0; %> +          <% if ( $type eq '' ) { %> +            <tr><td><font color="#ff0000">no type</font></td></tr> +          <% } elsif ( $type eq 'textarea' ) { %> +            <tr><td bgcolor="#ffffff"> +<pre> +<%= encode_entities(join("\n", $conf->config($i->key) ) ) %> +</pre> +            </td></tr> +          <% } elsif ( $type eq 'checkbox' ) { %> +            <tr><td bgcolor="#<%= $conf->exists($i->key) ? '00ff00">YES' : 'ff0000">NO' %></td></tr> +          <% } elsif ( $type eq 'text' )  { %> +            <tr><td bgcolor="#ffffff"><%=  $conf->exists($i->key) ? $conf->config($i->key) : '' %></td></tr> +          <% } else { %> +            <tr><td> +              <font color="#ff0000">unknown type <%= $type %></font> +            </td></tr> +          <% } %> +        <% $n++; } %> +      </table></td> +    </tr> +  <% } %> +  </table><br><br> +<% } %> + +</body></html> diff --git a/httemplate/config/config.cgi b/httemplate/config/config.cgi new file mode 100644 index 000000000..f640d0b5e --- /dev/null +++ b/httemplate/config/config.cgi @@ -0,0 +1,62 @@ +<!-- mason kludge --> +<%= header('Edit Configuration', menubar( 'Main Menu' => $p ) ) %> + +<% my $conf = new FS::Conf; my @config_items = $conf->config_items; %> + +<form action="config-process.cgi" METHOD="POST"> + +<% foreach my $section ( qw(required billing username password UI session +                            shell mail radius apache BIND +                           ), +                         '', 'depreciated') { %> +  <%= table("#cccccc", 2) %> +  <tr> +    <th colspan="2" bgcolor="#dcdcdc"> +      <%= ucfirst($section || 'unclassified') %> configuration options +    </th> +  </tr> +  <% foreach my $i (grep $_->section eq $section, @config_items) { %> +    <tr> +      <td> +        <% my $n = 0; +           foreach my $type ( ref($i->type) ? @{$i->type} : $i->type ) { +             #warn $i->key unless defined($type); +        %> +          <% if ( $type eq '' ) { %> +            <font color="#ff0000">no type</font> +          <% } elsif ( $type eq 'textarea' ) { %> +            <textarea name="<%= $i->key. $n %>" rows=5><%= join("\n", $conf->config($i->key) ) %></textarea> +          <% } elsif ( $type eq 'checkbox' ) { %> +            <input name="<%= $i->key. $n %>" type="checkbox" value="1"<%= $conf->exists($i->key) ? ' CHECKED' : '' %>> +          <% } elsif ( $type eq 'text' )  { %> +            <input name="<%= $i->key. $n %>" type="<%= $type %>" value="<%= $conf->exists($i->key) ? $conf->config($i->key) : '' %>"> +          <% } elsif ( $type eq 'select' )  { %> +            <select name="<%= $i->key. $n %>"> +              <% my %saw; +                 foreach my $value ( "", @{$i->select_enum} ) { +                    local($^W)=0; next if $saw{$value}++; %> +                <option value="<%= $value %>"<%= $value eq $conf->config($i->key) ? ' SELECTED' : '' %>><%= $value %> +              <% } %> +              <% if ( $conf->exists($i->key) && $conf->config($i->key) && ! grep { $conf->config($i->key) eq $_ } @{$i->select_enum}) { %> +                <option value=<%= $conf->config($i->key) %> SELECTED><%= conf->config($i->key) %> +              <% } %> +          <% } else { %> +            <font color="#ff0000">unknown type <%= $type %></font> +          <% } %> +        <% $n++; } %> +      </td> +      <td><a name="<%= $i->key %>"> +        <b><%= $i->key %></b> - <%= $i->description %> +      </a></td> +    </tr> +  <% } %> +  </table><br><br> +<% } %> + +You may need to restart Apache and/or freeside-queued for configuration +changes to take effect.<BR> + +<input type="submit" value="Apply changes"> +</form> + +</body></html> diff --git a/httemplate/docs/admin.html b/httemplate/docs/admin.html new file mode 100755 index 000000000..16d492095 --- /dev/null +++ b/httemplate/docs/admin.html @@ -0,0 +1,75 @@ +<head> +  <title>Administration</title> +</head> +<body> +  <h1>Administration</h1> +</body> +<ul> +  <li>Open up the root of the Freeside document tree in your web +  browser.  For example, if you created the Freeside document tree in    +  /home/httpd/html/freeside, and your web browser's DocumentRoot is +  /home/httpd/html, open https://your_host/freeside/. Replace +  "your_host" with the name or network address of your web server. +  <li>Select <u>Configuration</u> from the main menu and update your configuration values. +  <li>Next you must create a service definition.  An example of a service +  definition would be a dial-up account or a domain.  For starters, it is +  necessary to create a domain definition.  Click on <u>View/Edit service +  definitions</u> and <u>Add a new service definition</u> with <i>Table</i> +  <b>svc_domain</b> (and no modifiers). + +  <li>Now that you have created your first service, you must create a package +  including this service which you can sell to customers.  Zero, one, or many +  services are bundled into a package.  Click on <u>View/Edit package +  definitions</u> and <u>Add a new package definition</u> which includes +  quantity <b>1</b> of the svc_domain service you created above. + +  <li>After you create your first package, then you must define who is +  able to sell that package by creating an agent type.  An example of +  an agent type would be an internal sales representitive which sells +  regular and promotional packages, as opposed to an external sales +  representitive which would only sell regular packages of services.  Click on +  <u>View/Edit agent types</u> and <u>Add a new agent type</u>.  Allow this +  agent type to sell the package you created above. + +  <li>After creating a new agent type, you must create an agent.  Click on +  <u>View/Edit agents</u> and <u>Add a new agent</u>. + +  <li>Set up at least one referral.  Referrals will help you keep track of how +  effective your advertising is, by helping you keep track of where customers +  heard of your service offerings.  You must create at least one referral.  If +  you do not wish to use the referral functionality, simply create a single +  referral only.  Click on <u>View/Edit referrals</u> and <u>Add a new +  referral</u>. + +  <li>Click on <u>New Customer</u> and create a new customer for your system +  accounts with billing type <b>Complimentary</b>.   + +  <li>From the Customer View screen of the newly created customer, order the +  package you defined above. + +  <li>From the Package View screen of the newly created package, choose +  <u>(Add)</u> to add the customer's service for this new package. + +  <li>Add your own domain. + +  <li>Go back to <u>View/Edit service definitions</u> on the main menu, and +  <u>Add a new service definition</u> with <i>Table</i> <b>svc_acct</b>. +  Select your domain in the <b>domsvc</b> Modifier.  Set <b>Fixed</b> to define +  a service locked-in to this domain, or <b>Default</b> to define a service +  which may select from among this domain and the customer's domains. + +  <li><table><tr> +    <td> Create at least POP (Point of Presence) by selecting +        <u>View/Edit POPs</u> from the main menu.</td> +    <th align="left"> OR </th> +    <td>If you are not doing dialup, set slipip to fixed and blank for all your +        Service Definitions which have Table <b>svc_acct</b>.</td> +  </tr></table> + +  <li>If you are using Freeside to keep track of sales taxes, define tax +  information for your locales by clicking on the <u>View/Edit locales and tax +  rates</b> on the main menu. + +</ul> +</body> +</html> diff --git a/httemplate/docs/billing.html b/httemplate/docs/billing.html new file mode 100644 index 000000000..de59fc60a --- /dev/null +++ b/httemplate/docs/billing.html @@ -0,0 +1,52 @@ +<head> +  <title>Billing</title> +</head> +<body> +  <h1>Billing</h1> +  <ul> +    <li>You can bill individual customers by clicking on the <i>Bill now</i> link on the main customer view. +    <li>The <a href="man/bin/freeside-daily.html"><b>freeside-daily</b></a> script should be run daily to bill customers and run invoice collection events. +    <li>Real-time credit card processing: Install the <a href="http://search.cpan.org/search?mode=module&query=Business%3A%3AOnlinePayment">Business::OnlinePayment</a> module for your processor.  Configure the <a href="../config/config-view.cgi#business-onlinepayment">business-onlinepayment</a> configuration option.  Disable the default <b>Batch card</b> <a href="../browse/part_bill_event.cgi">invoice event</a> and add one for Business::OnlinePayment. +    <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>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. +        <li>In addition, the following variables are available: +          <ul> +            <li>$invnum - invoice number +            <li>$date - as a UNIX timestamp (see <a href="http://search.cpan.org/doc/GBARR/TimeDate-1.09/lib/Date/Format.pm">Date::Format</a> for conversion functions). +            <li>$page - current page +            <li>$total_pages - total pages +            <li>@address - A six-element array containing the customer name, company, and address. +            <li>$overdue - true if this invoice is overdue +          </ul> +      </ul> +    <li>Batch credit card processing +      <ul> +        <li>After <a href="man/bin/freeside-daily.html"><b>freeside-daily</b></a> is run, a credit card batch will be in the <a href="schema.html#cust_pay_batch">cust_pay_batch</a> table.  Export this table to your credit card batching. +        <li>When your batch completes, erase the cust_pay_batch records in that batch and add any necessary paymants to the <a href="schema.html#cust_pay">cust_pay</a> table.  Example code to add payments is: +        <pre>use FS::cust_pay; + +# loop over all records in batch + +my $payment=create FS::cust_pay ( +  'invnum' => $invnum, +  'paid' => $paid, +  '_date' => $_date, +  'payby' => $payby, +  'payinfo' => $payinfo, +  'paybatch' => $paybatch, +); + +my $error=$payment->insert; +if ( $error ) { +  #process error +} + +# end loop +</pre> +All fields except paybatch are contained in the cust_pay_batch table.  You can use paybatch field to track particular batches and/or particular transactions within a batch. +    </ul> +      <li>The <a href="man/bin/freeside-print-batch.html"><b>freeside-print-batch</b></a> script can print or email pending credit card batches for manual entry. +  </ul> +</body> diff --git a/httemplate/docs/config.html b/httemplate/docs/config.html new file mode 100644 index 000000000..9caf3bb3a --- /dev/null +++ b/httemplate/docs/config.html @@ -0,0 +1,36 @@ +<head> +  <title>Configuration files</title> +</head> +<body> +  <h1>Configuration files</h1> +<font size="+1" color="#ff0000">Configuration is now done by the top-level Makefile and web interface.  The instructions below are no longer necessary.</font> +<ul> +  <li>Create the <b>/usr/local/etc/freeside</b> directory to hold your configuration. +  <li>Setting up <a href="http://www.apache.org/docs/misc/FAQ.html#user-authentication">Apache user authetication</a> is mandatory. +  <li>Create the <b>/usr/local/etc/freeside/mapsecrets</b> file, which maps Apache users to a secrets file which contains a DBI data source, username and password.  Every +line in <b>/usr/local/etc/freeside/mapsecrets</b> should contain a username and +filename, separated by whitespace.  Note that these are not local usernames - +they are passed from Apache.  <a href="http://www.apache.org/docs/misc/FAQ.html#user-authentication"> +Apache user authetication</a> is mandatory.  For example, if you had the Apache users admin, +john, and sam,   +you mapsecrets file might look like: +<pre> +admin secretfile +john secretfile +sam secretfile +</pre> +  <li>Next, the filename(s) referenced in <b>/usr/local/etc/freeside/mapsecrets</b> file should be created in the <b>/usr/local/etc/freeside/</b> directory.  Each file contains three lines: <a href="http://search.cpan.org/doc/TIMB/DBI-1.20/DBI.pm">DBI data source</a> (for example, +  <tt>DBI:mysql:freeside</tt> or <tt>DBI:Pg:host=localhost;dbname=freeside</tt>), database username, and database password. +  These files should not be world readable.  See the <a href="http://search.cpan.org/doc/TIMB/DBI-1.20/DBI.pm">DBI manpage</a> and the <a href="http://search.cpan.org/search?mode=module&query=DBD">manpage for your DBD</a> for the exact syntax of a DBI data source.  In a normal installation such as the example above, a single file <b>/usr/local/etc/freeside/secretfile</b> would be created - for example: +<pre> +DBI:Pg:host=localhost;dbname=freeside +dbusername +dbpassword +</pre> +<li>Create the <b>/usr/local/etc/freeside/conf.<i>datasource</i></b> directory, for example, <b>/usr/local/etc/freeside/conf.DBI:Pg:host=localhost;dbname=freeside</b> (remember to backslash-escape the ; character when creating directories in the shell: +<pre>mkdir /usr/local/etc/freeside/conf.DBI:Pg:host=localhost\;dbname=freeside +</pre> +<li>The rest of the configuration can be done with the web interface.  Select <u>Configuration</u> from the main menu and update your configuration values. +</ul> +</body> +</html> diff --git a/httemplate/docs/export.html b/httemplate/docs/export.html new file mode 100755 index 000000000..c7f1b4c9e --- /dev/null +++ b/httemplate/docs/export.html @@ -0,0 +1,54 @@ +<head> +  <title>File exporting</title> +</head> +<body> +  <h1>File exporting</h1> +  <ul> +    <li>bin/svc_acct.export will create UNIX <b>passwd</b>, <b>shadow</b> and <b>master.passwd</b> files, ERPCD <b>acp_passwd</b> and <b>acp_dialup</b> files and a RADIUS <b>users</b> file in the <b>/usr/local/etc/freeside/export.<i>datasrc</i></b> directory.  Some RADIUS servers (such as <a href="http://www.open.com.au/radiator/">Radiator</a>, <a href="ftp://ftp.cheapnet.net/pub/icradius/">ICRADIUS</a> and <a href="http://www.freeradius.org/">FreeRADIUS</a>) will authenticate directly out of an SQL database.  In these cases, +it is reccommended that you replicate (<a href="http://www.mysql.com/documentation/mysql/bychapter/manual_MySQL_Database_Administration.html#Replication">Replication in MySQL</a>) the data to an external RADIUS machine or point icradius_secrets to the external machine rather than running the RADIUS server on your Freeside machine.  Using the appropriate <a href="../config/config-view.cgi">configuration settings</a>, you can export these files to your remote machines unattended: +      <ul> +        <li>shellmachines - <b>passwd</b> and <b>shadow</b> are copied to the remote machine as <b>/etc/passwd.new</b> and <b>/etc/shadow.new</b> and then moved to <b>/etc/passwd</b> and <b>/etc/shadow</b> if no errors occur. +        <li>bsdshellmachines - <b>passwd</b> and <b>master.passwd</b> are copied to the remote machine as <b>/etc/passwd.new</b> and <b>/etc/master.passwd.new</b> and moved to <b>/etc/passwd</b> and <b>/etc/master.passwd</b> if no errors occur. +        <li>nismachines - <b>passwd</b> and <b>shadow</b> are copied to the <b>/etc/global</b> directory on the remote machine.  If no errors occur, the command <b>( cd /var/yp; make; )</b> is executed on the remote machine. +        <li>erpcdmachines - <b>acp_passwd</b> and <b>acp_dialup</b> are copied to the <b>/usr/annex</b> directory on the remote machine.  If no errors occur, the command <b>( kill -USR1 `cat /usr/annex/erpcd.pid` )</b> is executed on the remote machine.  +        <li>radiusmachines - <b>users</b> is copied to the <b>/etc/raddb</b> directory on the remote machine.  If no errors occur, the command <b>( builddbm )</b> is executed on the remote machine. +        <li>icradiusmachines - Turn this option on to enable radcheck table population - by default in the Freeside database, or in the database specified by the <a href="http://rootwood.haze.st/aspside/config/config-view.cgi#icradius_secrets">icradius_secrets</a> config option (the radcheck table needs to be created manually).  You do not need to use MySQL for your Freeside database to export to an ICRADIUS/FreeRADIUS MySQL database with this option.  <blockquote><b>ADDITIONAL DEPRECATED FUNCTIONALITY</b> (instead use <a href="http://www.mysql.com/documentation/mysql/bychapter/manual_MySQL_Database_Administration.html#Replication">MySQL replication</a> or point icradius_secrets to the external database) - your <a href="ftp://ftp.cheapnet.net/pub/icradius">ICRADIUS</a> machines or <a href="http://www.freeradius.org/">FreeRADIUS</a> (with MySQL authentication) machines, one per line.  Machines listed in this file will have the radcheck table exported to them.  Each line should contain four items, separted by whitespace: machine name, MySQL database name, MySQL username, and MySQL password.  For example: <CODE>"radius.isp.tld radius_db radius_user passw0rd"</CODE></blockquote> +      </ul> +    <li>svc_acct.pm - If a shellmachine is defined, users can be created, modified and deleted remotely; see below. +      <ul> +        <li>Account creation - If the <b>username</b>, <b>uid</b> and <b>dir</b> fields are defined for a new user, the command(s) specified in the <a href="../config/config-view.cgi#shellmachine-useradd">shellmachine-useradd</a> configuration file are executed on shellmachine via ssh.  If this file does not exist, <code>useradd -d $dir -m -s $shell -u $uid $username</code> is the default.  If the file exists but is empty, <code>cp -pr /etc/skel $dir; chown -R $uid.$gid $dir</code> is the default instead.  Otherwise the contents of the file are treated as a double-quoted perl string, with the following variables available: <code>$username</code>, <code>$uid</code>, <code>$gid</code>, <code>$dir</code>, and <code>$shell</code>. +        <li>Account deletion - The command(s) specified in the <a href="../config/config-view.cgi#shellmachine-userdel">shellmachine-userdel</a> configuration file are executed on shellmachine via ssh.  If this file does not exist, <code>userdel $username</code> is the default.  If the file exists but is empty, <code>rm -rf $dir</code> is the default instead.  Otherwise the contents of the file are treated as a double-quoted perl string, with the following variables available: <code>$username</code> and <code>$dir</code>. +        <li>Account modification - If a user's home directory changes, the command(s) specified in the <a href="../config/config-view.cgi#shellmachine-usermod">shellmachine-usermod</a> configuration file are execute on shellmachine via ssh.  If this file does not exist or is empty, <code>[ -d $old_dir ] && mv $old_dir $new_dir || ( chmod u+t $old_dir; mkdir $new_dir; cd $old_dir; find . -depth -print | cpio -pdm $new_dir; chmod u-t $new_dir; chown -R $uid.$gid $new_dir; rm -rf $old_dir )</code> is the default.  Otherwise the contents of the file are treated as a double-quoted perl string, with the following variables available: <code>$old_dir</code>, <code>$new_dir</code>, <code>$uid</code> and <code>$gid</code>. +      </ul> +    <li>svc_acct.pm - <a href="http://asg.web.cmu.edu/cyrus/imapd/">Cyrus IMAP Server</a> integration, enabled by the <a href="../config/config-view.cgi#cyrus">cyrus configuration file</a> +      <ul> +        <li>Account creation - (Cyrus::IMAP::Admin should be installed locally) +        <li>Account deletion - (Cyrus::IMAP::Admin should be installed locally) +        <li>Account modification - (not yet implemented) +      </ul> +    <li>bin/svc_acct_sm.export will create <a href="http://www.qmail.org">Qmail</a> <b>rcpthosts</b>, <b>recipientmap</b> and <b>virtualdomains</b> files and <a href="http://www.sendmail.org">Sendmail</a> <b>virtusertable</b> and <b>sendmail.cw</b> files in the <b>/usr/local/etc/freeside/export.<i>datasrc</i></b> directory.  Using the appropriate <a href="../config/config-view.cgi">configuration files</a>, you can export these files to your remote machines unattemded: +      <ul> +        <li>qmailmachines - <b>recipientmap</b>, <b>virtualdomains</b> and <b>rcpthosts</b> are copied to the <b>/var/qmail/control</b> directory on the remote machine.  Note: If you <a href="legacy.html#svc_acct_sm">imported</a> qmail configuration files, run the generated <b>/usr/local/etc/freeside/export.<i>datasrc</i>/virtualdomains.FIX</b> on a machine with your user home directories before exporting qmail configuration files. +        <li>shellmachine - The command <b>[ -e <i>homedir</i>/.qmail-default ] || { touch <i>homedir</i>/.qmail-default; chown <i>uid</i>.<i>gid</i> <i>homedir</i>/.qmail-default; }</b> will be run on this machine for users in the virtualdomains file. +        <li>sendmailmachines - <b>sendmail.cw</b> and <b>virtusertable</b> are copied to the remote machine as <b>/etc/sendmail.cw.new</b> and <b>/etc/virtusertable.new</b>.  If no errors occur, they are moved to <b>/etc/sendmail.cw</b> and <b>/etc/virtusertable</b> and the command specified in the <a href="../config/config-view.cgi#sendmailrestart">sendmailrestart</a> configuration file is executed.  (The path can be changed from the default <b>/etc</b> with the <a href="../config/config-view.cgi#sendmailconfigpath">sendmailconfigpath</a> configuration file.) +      </ul> +    <li>svc_domain.pm - If the qmailmachines configuration file exists and a shellmachine is defined, user <b>.qmail-</b> files can be updated for catchall mailboxes. +      <ul> +        <li>The command <pre>[ -e <i>homedir</i>/.qmail-<i>domain</i>-default ] || { +    touch <i>homedir</i>/.qmail-<i>domain</i>-default; +    chown <i>uid</i>.<i>gid</i> <i>homedir</i>/.qmail-<i>domain</i>-default; +}</pre> is run. +      </ul> +    <li>svc_forward.pm - Not yet documented; see manpage. +    <li>svc_www.pm - Not yet documented; see manpage. +  </ul> +  <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>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). +    </ul> + +</body> + diff --git a/httemplate/docs/index.html b/httemplate/docs/index.html new file mode 100644 index 000000000..9e61d4f08 --- /dev/null +++ b/httemplate/docs/index.html @@ -0,0 +1,31 @@ +<head> +  <title>Documentation</title> +</head> +<body bgcolor="#ffffff"> +  <h1>Documentation</h1> +<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="config.html">Configuration files</a> +!--> +  <li><a href="admin.html">Administration</a> +<!-- +  <li><a href="../index.html#admin">Administration</a> +!--> +  <li><a href="legacy.html">Importing legacy data</a> +  <li><a href="export.html">File exporting and remote setup</a> +  <li><a href="passwd.html">fs_passwd</a> +  <li><a href="signup.html">Signup server</a> +  <li><a href="session.html">Session monitor</a> +  <li><a href="billing.html">Billing</a> +  <li><a href="trouble.html">Troubleshooting</a> +  <li><a href="schema.html">Schema reference</a> +  <li><a href="man/FS.html">Perl API</a> +</ul> +</body> diff --git a/httemplate/docs/install.html b/httemplate/docs/install.html new file mode 100644 index 000000000..b739af02c --- /dev/null +++ b/httemplate/docs/install.html @@ -0,0 +1,180 @@ +<head> +  <title>Installation</title> +</head> +<body> +<h1>Installation</h1> +Before installing, you need: +<ul> +  <li>A web server, such as <a href="http://www.apache-ssl.org">Apache-SSL</a> or <a href="http://www.apache.org">Apache</a> +  <li><a href="http://perl.apache.org/">mod_perl</a> +  <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://www.perl.com/">Perl</a>  Don't enable experimental features like threads or the PerlIO abstraction layer. +  <li>A <b>transactional</b> database engine supported by Perl's <a href="http://www.hermetica.com/technologia/DBI/">DBI</a>. +    <ul> +      <li><a href="http://www.postgresql.org/">PostgreSQL</a> (v7 or higher) is recommended. +      <li><b>MySQL does not work at this time.</b> +        <i>The following information is provided for developers who wish to contribute MySQL support:</i> See tickets <a href="http://pouncequick.420.am/rt/Ticket/Display.html?id=300">#300</a> and <a href="http://pouncequick.420.am/rt/Ticket/Display.html?id=334">#334</a> in the bug-tracking system. +      <!-- <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 really want to use MySQL, you need to 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>, and set it as the default table type using the <code>--default-table-type=BDB</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> 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) +    <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) +      <li><a href="http://search.cpan.org/search?dist=MIME-Base64">MIME-Base64</a> +      <li><a href="http://search.cpan.org/search?dist=Digest-MD5">Digest-MD5</a> +      <li><a href="http://search.cpan.org/search?dist=URI">URI</a> +      <li><a href="http://search.cpan.org/search?dist=HTML-Tagset">HTML-Tagset</a> +      <li><a href="http://search.cpan.org/search?dist=HTML-Parser">HTML-Parser</a> +      <li><a href="http://search.cpan.org/search?dist=libnet">libnet</a> +      <li><a href="http://search.cpan.org/search?dist=Locale-Codes">Locale-Codes</a> +      <li><a href="http://search.cpan.org/search?dist=Net-Whois">Net-Whois</a> +      <li><a href="http://search.cpan.org/search?dist=libwww-perl">libwww-perl</a> +      <li><a href="http://search.cpan.org/search?dist=Business-CreditCard">Business-CreditCard</a> +      <li><a href="http://search.cpan.org/search?dist=Data-ShowTable">Data-ShowTable</a> +      <li><a href="http://search.cpan.org/search?dist=MailTools">MailTools</a> +      <li><a href="http://search.cpan.org/search?dist=TimeDate">TimeDate</a> +      <li><a href="http://search.cpan.org/search?dist=DateManip">DateManip</a> +      <li><a href="http://search.cpan.org/search?dist=File-CounterFile">File-CounterFile</a> +      <li><a href="http://search.cpan.org/search?dist=FreezeThaw">FreezeThaw</a> +      <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=Archive-Tar">Archive-Tar</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?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> +      <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> +    </ul> +</ul> +Install the Freeside distribution: +<ul> +  <li>Add the user `freeside' to your system. +  <li>Allow the freeside user full access to the freeside database. +    <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 +$ 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>: +        <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.20/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.20/DBI.pm">DBI manpage</a> and the <a href="http://search.cpan.org/search?mode=module&query=DBD">manpage for your DBD</a> for the exact syntax of a 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: +    <pre> +$ su +# make create-database</pre> +    (or manually, with Postgres:) +    <pre> +$ su freeside +$ createdb freeside</pre> +    (with MySQL:) +    <pre> +$ mysqladmin -u freeside -p create freeside </pre> +  <li>Build and install the Perl modules: +    <pre> +$ make perl-modules +$ su +# make install-perl-modules</pre> +    <li>Create the necessary configuration files:<pre> +$ su +# make create-config +</pre> +    <li>You should run a separate iteration of Apache[-SSL] with mod_perl enabled as the freeside user. +</ul> +<table> +  <tr> +    <th>Apache::ASP</th><th>Mason</th> +  </tr> +  <tr> +    <td><ul> +      <li>Run <tt>make aspdocs</tt> +      <li>Copy <tt>aspdocs/</tt> to your web server's document space. +      <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> +      <li>Copy <tt>htetc/global.asa</tt> to the Global directory: +<font size="-1"><pre> +cp htetc/global.asa /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> +<Directory /usr/local/apache/htdocs/freeside-asp> +<Files ~ (\.cgi)> +AddHandler perl-script .cgi +PerlHandler Apache::ASP +</Files> +<Perl> +$MLDBM::RemoveTaint = 1; +</Perl> +PerlSetVar Global /usr/local/etc/freeside/asp-global/ +PerlSetVar Debug 2 +</Directory> +</pre></font> +    </ul></td> +    <td><ul> +      <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>Configure Apache to use the <tt>handler.pl</tt> file and to execute .cgi files using HTML::Mason.  For example: +<font size="-1"><pre> +<Directory /usr/local/apache/htdocs/freeside-mason> +<Files ~ (\.cgi)> +AddHandler perl-script .cgi +PerlHandler HTML::Mason +</Files> +<Perl> +require "/usr/local/apache/conf/handler.pl"; +</Perl> +</Directory> +</pre></font> +    </ul></td> +  </tr> +</table> +<ul> +<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> +<Directory /usr/local/apache/htdocs/freeside-asp> +PerlSetVar Global /usr/local/etc/freeside/asp-global/ +AuthName Freeside +AuthType Basic +AuthUserFile /usr/local/etc/freeside/htpasswd +require valid-user +</Directory> +</pre> +  <li>Create one or more Freeside users (your internal sales/tech folks, not customer accounts).  These users are setup using using Apache authentication, not UNIX user accounts.  For example, using <a href="http://httpd.apache.org/docs/mod/mod_auth.html">mod_auth</a> (flat files): +    <ul> +      <li>First user:<font size="-1"> +<pre>$ su +$ <a href="man/bin/freeside-adduser.html">freeside-adduser</a> -c -h /usr/local/etc/freeside/htpasswd <i>username</i></pre></font> +      <li>Additional users:<font size="-1"> +<pre>$ su +$ <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> to create the database tables, passing the username of a Freeside user you created above: +<pre> +$ su freeside +$ bin/fs-setup <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. +  <li>Now proceed to the initial <a href="admin.html">administration</a> of your installation. +</ul> +</body> diff --git a/httemplate/docs/legacy.html b/httemplate/docs/legacy.html new file mode 100755 index 000000000..3ab21dab2 --- /dev/null +++ b/httemplate/docs/legacy.html @@ -0,0 +1,34 @@ +<head> +  <title>Importing legacy data</title> +</head> +<body> +  <h1>Importing legacy data</h1> +<ul> +  <li><a name="svc_acct">bin/svc_acct.import</a> - Import `passwd', ( `shadow' or `master.passwd' ) and RADIUS `users'.  Before running bin/svc_acct.import, you need <a href="../browse/part_svc.cgi">services</a> (with table svc_acct) as follows: +    <ul> +      <li>Most accounts probably have entries in passwd and users (with Port-Limit nonexistant or 1) +      <li>Some accounts have entries in passwd and users, but with Port-Limit 2 (or more) +      <li>Some accounts might have entries in users only (Port-Limit 1) +      <li>Some accounts might have entries in users only (Port-Limit >= 2) +      <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: +    <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 +        <ul> +          <li>Add a <a href="../edit/cust_main.cgi">new customer</a> +          <li>Add one or more packages for this customer +          <li>Enter a package by clicking on the package number +          <li>Pick the `Link to existing' option +        </ul> +      <li>Batch - You will need to write a script to import your particular legacy data.  You can use eg/TEMPLATE_cust_main.import as a starting point. +    </ul> +</ul> +</body> + diff --git a/httemplate/docs/overview.dia b/httemplate/docs/overview.dia Binary files differnew file mode 100644 index 000000000..a0e34c30e --- /dev/null +++ b/httemplate/docs/overview.dia diff --git a/httemplate/docs/overview.png b/httemplate/docs/overview.png Binary files differnew file mode 100644 index 000000000..bf2dbc26c --- /dev/null +++ b/httemplate/docs/overview.png diff --git a/httemplate/docs/passwd.html b/httemplate/docs/passwd.html new file mode 100755 index 000000000..c4d91480c --- /dev/null +++ b/httemplate/docs/passwd.html @@ -0,0 +1,23 @@ +<head> +  <title>fs_passwd</title> +</head> +<body> +  <h1>fs_passwd</h1> +You may use fs_passwd/fs_passwd as a "passwd", "chfn" and "chsh" replacement on your shell machine(s) to cause password, gecos and shell changes to update your freeside machine.  You can also use the fs_passwd/fs_passwd.html and fs_passwd/fs_passwd.cgi to run a public password change CGI on a public web server.  This can pose a security risk if not configured correctly.  <b>Do not use this feature unless you understand what you are doing!</b> +<br><br>Currently it is assumed that the the crypt(3) function in the C library is the same on the Freeside machine as on the target machine. +<ul> +  <li>Create a freeside account on the shell or web machine(s). +  <li>Setup SSH keys: +    <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 shell or web 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). +    </ul> +  <li>Copy fs_passwd/fs_passwdd to /usr/local/sbin on the shell or web machine(s).  (chown freeside, chmod 500) +  <li>Create /usr/local/freeside on the shell or web machine(s). (chown freeside, chmod 700) +  <li>Run an iteration of "fs_passwd/fs_passwd_server <i>user</i> shell.machine" as the freeside user for each shell or web machine (this is a daemon process).  <i>user</i> refers to the freeside user from the <a href="config.html">mapsecrets configuration file</a>. +  <li>Copy fs_passwd/fs_passwd to /usr/local/bin on the shell machine(s).  (chown freeside, chmod 4755).  You may link it to passwd, chfn and chsh as well. +  <li>Copy fs_passwd/fs_passwd.cgi to the cgi-bin directory on your web machine(s).  Use <a href="http://www.apache.org/docs/suexec.html">suEXEC</a> or <a href="http://www.perldoc.com/perl5.6.1/pod/perlsec.html">suidperl</a> to run fs_passwd.cgi as the freeside user. +</ul> +</body> diff --git a/httemplate/docs/schema.html b/httemplate/docs/schema.html new file mode 100644 index 000000000..bdf3a500e --- /dev/null +++ b/httemplate/docs/schema.html @@ -0,0 +1,383 @@ +<head> +  <title>Schema reference</title> +</head> +<body> +  <h1>Schema reference</h1> +  <ul> +    <li><a name="agent" href="man/FS/agent.html">agent</a> - Agents are resellers of your service.  Agents may be limited to a subset of your full offerings (via their agent type). +      <ul> +        <li>agentnum - primary key +        <li>agent - name of this agent +        <li>typenum - <a href="#agent_type">agent type</a> +        <li>prog - (unimplemented) +        <li>freq - (unimplemented) +      </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> +        <li>typenum - primary key +        <li>atype - name of this agent type +      </ul> +    <li><a name="cust_bill" href="man/FS/cust_bill.html">cust_bill</a> - Invoices.  Declarations that a customer owes you money.  The specific charges are itemized in <a href="#cust_bill_pkg">cust_bill_pkg</a>. +      <ul> +        <li>invnum - primary key +        <li>custnum - <a href="#cust_main">customer</a> +        <li>_date +        <li>charged - amount of this invoice +        <li>printed - how many times this invoice has been printed automatically +        <li>closed - books closed flag, empty or `Y' +      </ul> +    <li><a name="cust_bill_event" href="man/FS/cust_bill_event.html">cust_bill_event</a> - Invoice event history +      <ul> +        <li>eventnum - primary key +        <li>invnum - <a href="#cust_bill">invoice</a> +        <li>eventpart - <a href="#part_bill_event">event definition</a> +        <li>_date +      </ul> +    <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>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 +        <li>weight - ordering for events with identical seconds +        <li>plan - eventcode plan +        <li>plandata - additional plan data +        <li>disabled - Disabled flag, empty or `Y' +      </ul> +    <li><a name="cust_bill_pkg" href="man/FS/cust_bill_pkg.html">cust_bill_pkg</a> - Invoice line items +      <ul> +        <li>invnum - (multiple) key +        <li>pkgnum - <a href="#cust_pkg">package</a> or 0 for the special virtual sales tax package +        <li>setup - setup fee  +        <li>recur - recurring fee +        <li>sdate - starting date +        <li>edate - ending date +      </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> +        <li>crednum - primary key +        <li>custnum - <a href="#cust_main">customer</a> +        <li>amount - amount credited +        <li>_date +        <li>otaker - order taker +        <li>reason +        <li>closed - books closed flag, empty or `Y' +      </ul> +    <li><a name="cust_credit_bill" href="man/FS/cust_credit_bill.html">cust_credit_bill</a> - Credit invoice application.  Links a credit to an invoice. +      <ul> +        <li>creditbillnum - primary key +        <li>crednum - <a href="#cust_credit">credit</a> being applied +        <li>invnum - <a href="#cust_bill">invoice</a> to which credit is applied +        <li>amount - amount applied +        <li>_date +      </ul> +    <li><a name="cust_main" href="man/FS/cust_main.html">cust_main</a> - Customers +      <ul> +        <li>custnum - primary key +        <li>agentnum - <a href="#agent">agent</a> +        <li>refnum - <a href="#part_referral">referral</a> +        <li>first - name +        <li>last - name +        <li>ss - social security number +        <li>company +        <li>address1 +        <li>address2 +        <li>city +        <li>county +        <li>state +        <li>zip +        <li>country +        <li>daytime - phone +        <li>night - phone +        <li>fax - phone +        <li><i>ship_first</i> +        <li><i>ship_last</i> +        <li><i>ship_company</i> +        <li><i>ship_address1</i> +        <li><i>ship_address2</i> +        <li><i>ship_city</i> +        <li><i>ship_county</i> +        <li><i>ship_state</i> +        <li><i>ship_zip</i> +        <li><i>ship_country</i> +        <li><i>ship_daytime</i> +        <li><i>ship_night</i> +        <li><i>ship_fax</i> +        <li>payby - CARD, BILL, or COMP +        <li>payinfo - card number, P.O.#, or comp issuer +        <li>paydate - expiration date +        <li>payname - billing name (name on card) +        <li>tax - tax exempt, Y or null +        <li>otaker - order taker +        <li>referral_custnum +        <li>comments +      </ul> +      (columns in <i>italics</i> are optional) +    <li><a name="cust_main_invoice" href="man/FS/cust_main_invoice.html">cust_main_invoice</a> - Invoice destinations for email invoices.  Note that a customer can have many email destinations for their invoice (either literal or via svcnum), but only one postal destination. +      <ul> +        <li>destnum - primary key +        <li>custnum - <a href="#cust_main">customer</a> +        <li>dest - Invoice destination.  Freeside supports three types of invoice delivery: send directly to a service defined in Freeside, send to an arbitrary email address, or print the invoice to a printer and have someone send it out via snail mail.  Freeside determines which method to use based on the contents of the dest field.  If the contents are numeric, a <a href="#svc_acct">svcnum</a> pointing to a valid service is expected in the field.  If the contents are a string, a literal email address is expected to be in the field.  If the special keyword `POST' is present, the snail mail method is used (which is the default if no cust_main_invoice records exist).  Snail mail invoices get their address information from <A name="#cust_main">cust_main</A> and are printed with the printer defined in the configuration files. +      </ul> +    <li><a name="cust_main_county" href="man/FS/cust_main_county.html">cust_main_county</a> - Tax rates +      <ul> +        <li>taxnum - primary key +        <li>state +        <li>county +        <li>country +        <li>tax - % rate +      </ul> +    <li><a name="cust_pay" href="man/FS/cust_pay.html">cust_pay</a> - Payments.  Money being transferred from a customer. +      <ul> +        <li>paynum - primary key +        <li>custnum - <a href="#cust_main">customer</a> +        <li>paid - amount +        <li>_date +        <li>payby - CARD, 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' +      </ul> +    <li><a name="cust_bill_pay" href="man/FS/cust_bill_pay.html">cust_bill_pay</a> - Applicaton of a payment to a specific invoice. +      <ul> +        <li>billpaynum +        <li>invnum - <a href="#cust_bill">invoice</a> +        <li>paynum - <a href="#cust_pay">payment</a> +        <li>amount +        <li>_date +      </ul> +    <li><a name="cust_pay_batch" href="man/FS/cust_pay_batch.html">cust_pay_batch</a> - Pending batch +      <ul> +        <li>paybatchnum +        <li>cardnum +        <li>exp - card expiration +        <li>amount +        <li>invnum - <a href="#cust_bill">invoice</a> +        <li>custnum - <a href="#cust_main">customer</a> +        <li>payname - name on card +        <li>first - name +        <li>last - name +        <li>address1 +        <li>address2 +        <li>city +        <li>state +        <li>zip +        <li>country +      </ul> +    <li><a name="cust_pkg" href="man/FS/cust_pkg.html">cust_pkg</a> - Customer billing items +      <ul> +        <li>pkgnum - primary key +        <li>custnum - <a href="#cust_main">customer</a> +        <li>pkgpart - <a href="#part_pkg">Package definition</a> +        <li>setup - date +        <li>bill - next bill date +        <li>susp - (past) suspension date +        <li>expire - (future) cancellation date +        <li>cancel - (past) cancellation date +        <li>otaker - order taker +        <li>manual_flag - If this field is set to 1, disables the automatic unsuspensiond of this package when using the <a href="config.html#unsuspendauto">unsuspendauto</a> config file. +      </ul> +    <li><a name="cust_refund" href="man/FS/cust_refund.html">cust_refund</a> - Refunds.  The transfer of money to a customer; equivalent to a negative <a href="#cust_pay">cust_pay</a> record. +      <ul> +        <li>refundnum - primary key +        <li>custnum - <a href="#cust_main">customer</a> +        <li>refund - amount +        <li>_date +        <li>payby - CARD, BILL or COMP +        <li>payinfo - card number, P.O.#, or comp issuer +        <li>otaker - order taker +        <li>closed - books closed flag, empty or `Y' +      </ul> +    <li><a name="cust_credit_refund" href="man/FS/cust_credit_refund.html">cust_credit_refund</a> - Applicaton of a refund to a specific credit. +      <ul> +        <li>creditrefundnum - primary key +        <li>crednum - <a href="#cust_credit">credit</a> +        <li>refundnum - <a href="#cust_refund">refund</a> +        <li>amount +        <li>_date +      </ul> +    <li><a name="cust_svc" href="man/FS/cust_svc.html">cust_svc</a> - Customer services +      <ul> +        <li>svcnum - primary key +        <li>pkgnum - <a href="#cust_pkg">package</a> +        <li>svcpart - <a href="#part_svc">Service definition</a> +      </ul> +    <li><a name="nas" href="man/FS/nas.html">nas</a> - Network Access Server (terminal server) +      <ul> +        <li>nasnum - primary key +        <li>nas - NAS name +        <li>nasip - NAS ip address +        <li>nasfqdn - NAS fully-qualified domain name +        <li>last - timestamp indicating the last instant the NAS was in a known state (used by the session monitoring). +      </ul> +    <li><a name="part_pkg" href="man/FS/part_pkg.html">part_pkg</a> - Package definitions +      <ul> +        <li>pkgpart - primary key +        <li>pkg - package name +        <li>comment - non-customer visable package comment +        <li>setup - setup fee expression +        <li>freq - recurring frequency (months) +        <li>recur - recurring fee expression +        <li>setuptax - Setup fee tax exempt flag, empty or `Y' +        <li>recurtax - Recurring fee tax exempt flag, empty or `Y' +        <li>plan - price plan +        <li>plandata - additional price plan data +        <li>disabled - Disabled flag, empty or `Y' +      </ul> +    <li><a name="part_referral" href="man/FS/part_referral.html">part_referral</a> - Referral listing +      <ul> +        <li>refnum - primary key +        <li>referral - referral +      </ul> +    <li><a name="part_svc" href="man/FS/part_svc.html">part_svc</a> - Service definitions +      <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>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 +--> +      </ul> +    <li><a name="part_svc_column" href="man/FS/part_svc_column.html">part_svc_column</a> +      <ul> +        <li>columnnum - primary key +        <li>svcpart - <a href="#part_svc">Service definition</a> +        <li>columnname - column name in part_svc.svcdb table +        <li>columnvalue - default or fixed value for the column +        <li>columnflag - null, D or F +      </ul> +    <li><a name="pkg_svc" href="man/FS/pkg_svc.html">pkg_svc</a> +      <ul> +        <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 +      </ul> +    <li><a name="part_export" href="man/FS/part_export.html">part_export</a> - Export to external provisioning +      <ul> +        <li>exportnum - primary key +        <li>svcpart - <a href="#part_svc">Service definition</a> +        <li>machine - Machine name  +        <li>exporttype - Export type +        <li>nodomain - blank or Y: usernames are exported to this service with no domain +      </ul> +    <li><a name="part_export_option" href="man/FS/part_export_option.html">part_export_option</a> - provisioning options +      <ul> +        <li>optionnum - primary key +        <li>exportnum - <a href="#part_export">Export</a> +        <li>option - option name +        <li>optionvalue - option value +      </ul> +    <li><a name="port" href="man/FS/port.html">port</a> - individual port on a <a href="#nas">nas</a> +      <ul> +        <li>portnum - primary key +        <li>ip - IP address of this port +        <li>nasport - port number on the NAS +        <li>nasnum - <a href="#nas">NAS</a> +      </ul> +    <li><a name="prepay_credit" href="man/FS/prepay_credit.html">prepay_credit</a> +      <ul> +        <li>prepaynum - primary key +        <li>identifier - text or numeric string used to receive this credit +        <li>amount - amount of credit +      </ul> +    <li><a name="session" href="man/FS/session.html">session</a> +      <ul> +        <li>sessionnum - primary key +        <li>portnum - <a href="#port">Port</a> +        <li>svcnum - <a href="#svc_acct">Account</a> +        <li>login - timestamp indicating the beginning of this user session. +        <li>logout - timestamp indicating the end of this user session.  May be null, which indicates a currently open session. +      </ul> + +    <li><a name="svc_acct" href="man/FS/svc_acct.html">svc_acct</a> - Accounts +      <ul> +        <li>svcnum - <a href="#cust_svc">primary key</a> +        <li>username +        <li>_password +        <li>popnum - <a href="#svc_acct_pop">Point of Presence</a> +        <li>uid +        <li>gid +        <li>finger - GECOS +        <li>dir +        <li>shell +        <li>quota - (unimplementd) +        <li>slipip - IP address +        <li>seconds +        <li>domsvc +        <li>radius_<i>Radius_Reply_Attribute</i> - Radius-Reply-Attribute +        <li>rc_<i>Radius_Check_Attribute</i> - Radius-Check-Attribute +      </ul> +    <li><a name="svc_acct_pop" href="man/FS/svc_acct_pop.html">svc_acct_pop</a> - Points of Presence +      <ul> +        <li>popnum - primary key +        <li>city +        <li>state +        <li>ac - area code +        <li>exch - exchange +        <li>loc - rest of number +      </ul> +    <li><a name="part_pop_local" href="man/FS/part_pop_local.html">part_pop_local</a> - Local calling areas +      <ul> +        <li>localnum - primary key +        <li>popnum - primary key +        <li>city +        <li>state +        <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> +        <li>domain +      </ul> +    <li><a name="svc_forward" href="man/FS/svc_forward.html">svc_forward</a> - Mail forwarding aliases +      <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>dstsvc - <a href="#svc_acct">svcnum of the destination of this forward</a> +        <li>dst - foreign destination (email address) - forward not local to freeside +      </ul> +    <li><a name="domain_record" href="man/FS/domain_record.html">domain_record</a> - Domain zone detail +      <ul> +        <li>recnum - primary key +        <li>svcnum - <a href="#svc_domain">Domain</a> (by svcnum) +        <li>reczone - zone for this line +        <li>recaf - address family, usually <b>IN</b> +        <li>rectype - type for this record (<b>A</b>, <b>MX</b>, etc.) +        <li>recdata - data for this record +      </ul> +    <li><a name="svc_www" href="man/FS/svc_www.html">svc_www</a> +      <ul> +       <li>svcnum - <a href="#cust-svc">primary key</a> +       <li>recnum - <a href="#domain_record">host</a> +       <li>usersvc - <a href="#svc_acct">account</a> +      </ul> +    <li><a name="type_pkgs" href="man/FS/type_pkgs.html">type_pkgs</a> +      <ul> +        <li>typenum - <a href="#agent_type">agent type</a> +        <li>pkgpart - <a href="#part_pkg">Package definition</a> +      </ul> +    <li><a name="queue" href="man/FS/queue.html">queue</a> - job queue +      <ul> +        <li>jobnum - primary key +        <li>job +        <li>_date +        <li>status +      </ul> +    <li><a name="queue_arg" href="man/FS/queue_arg.html">queue_arg</a> - job arguments +      <ul> +        <li>argnum - primary key +        <li>jobnum - <a href="#queue">job</a> +        <li>arg - argument +      </ul> +  </ul> +</body> diff --git a/httemplate/docs/session.html b/httemplate/docs/session.html new file mode 100644 index 000000000..7dac5fdf7 --- /dev/null +++ b/httemplate/docs/session.html @@ -0,0 +1,54 @@ +<head> +  <title>Session monitor</title> +</head> +<body> +<h1>Session monitor</h1> +<h2>Installation</h2> +For security reasons, the client portion of the session montior may run on one +or more external public machine(s).  On these machines, install: +<ul> +  <li><a href="http://www.perl.com/CPAN/doc/relinfo/INSTALL.html">Perl</a> (at l +east 5.004_05 for the 5.004 series or 5.005_03 for the 5.005 series.  Don't enable experimental features like threads or the PerlIO abstraction layer.) +  <li><a href="man/FS/SessionClient.html">FS::SessionClient</a> (copy the fs_session/FS-SessionClient directory to the external machine, then: perl Makefile.PL; make; make install) +</ul> +Then: +<ul> +  <li>Add the user `freeside' to the the external machine. +  <li>Create the /usr/local/freeside directory on the external machine (owned by the freeside user). +  <li>touch /usr/local/freeside/fs_sessiond_socket; chown freeside /usr/local/freeside/fs_sessiond_socket; chmod 600 /usr/local/freeside/fs_sessiond_socket +    <li>Append the identity.pub from the freeside user on your freeside machine to the authorized_keys file of the newly created freeside user on the external machine(s). +  <li>Run <pre>fs_session_server <i>user</i> <i>machine</i></pre> on the Freeside machine. +  <ul> +    <li><i>user</i> is a user from the mapsecrets file. +    <li><i>machine</i> is the name of the external machine. +  </ul> +</ul> +<h2>Usage</h2> +<ul> +  <li>Web +    <ul> +      <li>Copy FS-SessionClient/cgi/login.cgi and logout.cgi to your web +          server's document space.   +      <li>Use <a href="http://www.apache.org/docs/suexec.html">suEXEC</a> or <a href="http://www.perl.com/CPAN-local/doc/manual/html/pod/perlsec.html#Security_Bugs">setuid</a> (see <a href="install.html">install.html</a> for details) to run login.cgi and logout.cgi as the freeside user. +    </ul> +  <li>Command-line +    <br><pre>freeside-login username ( portnum | ip | nasnum nasport ) +freeside-logout username ( portnum | ip | nasnum nasport )</pre> +    <ul> +      <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 +    <ul> +      <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. +    </ul> +</ul> +<h2>Callbacks</h2> +<ul> +  <li>Sesstion start - The command(s) specified in the <a href="config.html#session-start">session-start</a> configuration file are executed on the Freeside machine.  The contents of the file are treated as a double-quoted perl string, with the following variables available: <code>$ip</code>, <code>$nasip</code> and <code>$nasfqdn</code>, which are the IP address of the starting session, and the IP address and fully-qualified domain name of the NAS this session is on. +  <li>Session end - The command(s) specified in the <a href="config.html#session-stop">session-stop</a> configuration file are executed on the Freeside machine.  The contents of the file are treated as a double-quoted perl string, with the following variables available: <code>$ip</code>, <code>$nasip</code> and <code>$nasfqdn</code>, which are the IP address of the starting session, and the IP address and fully-qualified domain name of the NAS this session is on. +</ul> +<h2>Dropping expired users</h2> +Run <pre>bin/freeside-session-kill username</pre> periodically from cron. +</body> +</html> diff --git a/httemplate/docs/signup.html b/httemplate/docs/signup.html new file mode 100644 index 000000000..262b697bb --- /dev/null +++ b/httemplate/docs/signup.html @@ -0,0 +1,60 @@ +<head> +  <title>Signup server</title> +</head> +<body> +  <h1>Signup server</h1> +For security reasons, the signup server should run on an external public +webserver.  On this machine, install: +<ul> +  <li>A web server, such as <a href="http://www.apache-ssl.org">Apache-SSL</a> or <a href="http://www.apache.org">Apache</a> +  <li><a href="ftp://ftp.cs.hut.fi/pub/ssh/">SSH</a> +  <li><a href="http://www.perl.com/CPAN/doc/relinfo/INSTALL.html">Perl</a> (at least 5.004_05 for the 5.004 series or 5.005_03 for the 5.005 series.  Don't enable experimental features like threads or the PerlIO abstraction layer.) +  <li><a href="http://www.perl.com/CPAN/modules/by-module/Text/">Text::Template</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="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> +Then: +<ul> +  <li>Add the user `freeside' to the the external machine. +  <li>Copy or symlink fs_signup/FS-SignupClient/cgi/signup.cgi into the web server's document space. +  <li>When linking to signup.cgi, you can include a referring custnum in the URL as follows: <code>http://public.web.server/path/signup.cgi?ref=1542</code> +  <li>Enable CGI execution for files with the `.cgi' extension.  (with <a href="http://www.apache.org/docs/mod/mod_mime.html#addhandler">Apache</a>) +  <li>Create the /usr/local/freeside directory on the external machine (owned by the freeside user). +  <li>touch /usr/local/freeside/fs_signupd_socket; chown freeside /usr/local/freeside/fs_signupd_socket; chmod 600 /usr/local/freeside/fs_signupd_socket +  <li>Use <a href="http://www.apache.org/docs/suexec.html">suEXEC</a> or <a href="http://www.perl.com/CPAN-local/doc/manual/html/pod/perlsec.html#Security_Bugs">setuid</a> (see <a href="install.html">install.html</a> for details) to run signup.cgi as the freeside user. +  <li>Append the identity.pub from the freeside user on your freeside machine to the authorized_keys file of the newly created freeside user on the external machine(s). +  <li>Run <pre>fs_signup_server <i>user</i> <i>machine</i> <i>agentnum</i> <i>refnum</i></pre> on the Freeside machine. +  <ul> +    <li><i>user</i> is a user from the mapsecrets file. +    <li><i>machine</i> is the name of the external machine. +    <li><i>agentnum</i> and <i>refnum</i> are the <a href="schema.html#agent">agent</a> and <a href="schema.html#part_referral">referral</a>, respectively, to use for customers who sign up via this signup server. +  </ul> +</ul> +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 following variables available: +  <ul> +    <li>$ac - area code of selected POP +    <li>$exch - exchange of selected POP +    <li>$loc - local part of selected POP +    <li>$username +    <li>$password +    <li>$email_name - first and last name +  </ul> +  (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 following variables available: +  <ul> +    <li>$ac - area code of selected POP +    <li>$exch - exchange of selected POP +    <li>$loc - local part of selected POP +    <li>$username +    <li>$password +    <li>$email_name - first and last name +  </ul> +  (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. +  <li>If you create a <b>/usr/local/freeside/signup.html</b> file on the external machine, it will be used as a template for the form HTML.  This requires the template to be constructed appropriately; probably best to start with the example file included as <b>fs_signup/FS-SignupClient/cgi/signup.html</b>. +  <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>If there are any entries in the <i>prepay_credit</i> table, a user can enter a string matching the <b>identifier</i> column to receive the credit specified in the <b>amount</b> column, and/or the time specified in the <b>seconds</b> column (for use with the <a href="session.html">session monitor</a>), after which that <b>identifier</b> is no longer valid.  This can be used to implement pre-paid "calling card" type signups.  The <i>bin/generate-prepay</i> script can be used to populate the <i>prepay_credit</i> table. +</ul> +</body> diff --git a/httemplate/docs/trouble.html b/httemplate/docs/trouble.html new file mode 100755 index 000000000..fce743928 --- /dev/null +++ b/httemplate/docs/trouble.html @@ -0,0 +1,26 @@ +<head> +  <title>Troubleshooting</title> +</head> +<body> +  <h1>Troubleshooting</h1> +  <ul> +    <li>When troubleshooting the web interface, helpful information is often in your web server's error log. +    <li>If bin/svc_acct.import fails with an "Out of memory!" error using MySQL, upgrede MySQL and recompile the Perl DBD.  There was a memory leak in some older versions of MySQL. +    <li>If you get tons of errors in your web server's error log like this: +<pre> +Ambiguous use of value => resolved to "value" => +at /usr/lib/perl5/site_perl/File/CounterFile.pm line 132. +</pre> +        This clutters up your log files but is otherwise harmless.  Upgrade to the latest File::CounterFile.  +    <li>If you get errors like this: +<pre> +UID.pm: Can't open /var/spool/freeside/conf/secrets: Permission denied  +at <i>/your/path</i>/site_perl/FS/UID.pm line 26. +BEGIN failed--compilation aborted at +<i>/your/path</i>/edit/process/part_svc.cgi line 15. +</pre> +        Then the scripts are not running as the freeside freeside user.  See +the <a href="install.html">New Installation</a> section of the documentation. +  <li>If you receive `can not connect to server' errors using MySQL on a system that doesn't support native threading, you may need to specify the full hostname in your DBI datasource.  See the <a href="http://www.mysql.com/Manual_chapter/manual_Problems.html#Can_not_connect_to_server">MySQL documentation</a>, DBI manpage and the DBD::mysql manpage for details. +  </ul> +</body> diff --git a/httemplate/docs/upgrade4.html b/httemplate/docs/upgrade4.html new file mode 100644 index 000000000..1d70f8b73 --- /dev/null +++ b/httemplate/docs/upgrade4.html @@ -0,0 +1,27 @@ +<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 new file mode 100644 index 000000000..3f3431653 --- /dev/null +++ b/httemplate/docs/upgrade5.html @@ -0,0 +1,34 @@ +<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 new file mode 100644 index 000000000..dc82975f3 --- /dev/null +++ b/httemplate/docs/upgrade6.html @@ -0,0 +1,66 @@ +<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/upgrade7.html b/httemplate/docs/upgrade7.html new file mode 100644 index 000000000..d9dcfe2ae --- /dev/null +++ b/httemplate/docs/upgrade7.html @@ -0,0 +1,24 @@ +<head> +  <title>Upgrading to 1.3.1</title> +</head> +<body> +<h1>Upgrading to 1.3.1 from 1.3.0</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>If migrating from less than 1.3.0, see these <a href="upgrade6.html">instructions</a> first. +  <li>Back up your data and current Freeside installation. +  <li>Copy or symlink htdocs to the new copy. +  <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 UNINST=1</pre> +  <li>Run bin/dbdef-create. +</body> diff --git a/httemplate/docs/upgrade8.html b/httemplate/docs/upgrade8.html new file mode 100644 index 000000000..61d956ce8 --- /dev/null +++ b/httemplate/docs/upgrade8.html @@ -0,0 +1,308 @@ +<head> +  <title>Upgrading to 1.4.0</title> +</head> +<body> +<h1>Upgrading to 1.4.0 from 1.3.1</h1> +<ul> +  <li>If migrating from less than 1.3.1, see these <a href="upgrade7.html">instructions</a> first. +  <li><font size="+2" color="#ff0000">Backup your database and current Freeside installation.</font> (with <a href="http://www.ca.postgresql.org/devel-corner/docs/postgres/backup.html">PostgreSQL</a>) (with <a href="http://www.mysql.com/documentation/mysql/bychapter/manual_MySQL_Database_Administration.html#Backup">MySQL</a>) +  <li><a href="http://perl.apache.org/">mod_perl</a> is now required. +  <li>Install <a href="http://search.cpan.org/search?dist=Archive-Tar">Archive-Tar</a>, <a href="http://search.cpan.org/search?dist=Time-Duration">Time-Duration</a>, and <a href="http://search.cpan.org/search?dist=Tie-IxHash">Tie-IxHash</a> +  <li>Install <a href="http://www.apache-asp.org/">Apache::ASP</a> or <a href="http://www.masonhq.com/">HTML::Mason</a>. +</ul> +<table> +  <tr> +    <th>Apache::ASP</th><th>Mason</th> +  </tr> +  <tr> +    <td><ul> +      <li>Run <tt>make aspdocs</tt> +      <li>Copy <tt>aspdocs/</tt> to your web server's document space. +      <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> +      <li>Copy <tt>htetc/global.asa</tt> to the Global directory. +      <li>Configure Apache for the Global directory and to execute .cgi files using Apache::ASP.  For example: +<font size="-1"><pre> +<Directory /usr/local/apache/htdocs/freeside-asp> +<Files ~ (\.cgi)> +AddHandler perl-script .cgi +PerlHandler Apache::ASP +</Files> +<Perl> +$MLDBM::RemoveTaint = 1; +</Perl> +PerlSetVar Global /usr/local/etc/freeside/asp-global/ +</Directory> +</pre></font> +    </ul></td> +    <td><ul> +      <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>Configure Apache to use the <tt>handler.pl</tt> file and to execute .cgi files using HTML::Mason.  For example: +<font size="-1"><pre> +<Directory /usr/local/apache/htdocs/freeside-mason> +<Files ~ (\.cgi)> +AddHandler perl-script .cgi +PerlHandler HTML::Mason +</Files> +<Perl> +require "/usr/local/apache/conf/handler.pl"; +</Perl> +</Directory> +</pre></font> +    </ul></td> +  </tr> +</table> +<ul> +  <li>Build and install the Perl modules: +    <pre> +$ su +# make install-perl-modules</pre> +   <li>Apply the following changes to your database: +<pre> +CREATE TABLE svc_forward ( +  svcnum int NOT NULL, +  srcsvc int NOT NULL, +  dstsvc int NOT NULL, +  dst varchar(80), +  PRIMARY KEY (svcnum) +); + +CREATE TABLE cust_credit_bill ( +  creditbillnum int primary key, +  crednum int not null, +  invnum int not null, +  _date int not null, +  amount decimal(10,2) not null +); + +CREATE TABLE cust_bill_pay ( +  billpaynum int primary key, +  invnum int not null, +  paynum int not null, +  _date int not null, +  amount decimal(10,2) not null +); + +CREATE TABLE cust_credit_refund ( +  creditrefundnum int primary key, +  crednum int not null, +  refundnum int not null, +  _date int not null, +  amount decimal(10,2) not null +); + +CREATE TABLE part_svc_column ( +  columnnum int primary key, +  svcpart int not null, +  columnname varchar(64) not null, +  columnvalue varchar(80) null, +  columnflag char(1) null +); + +CREATE TABLE queue ( +  jobnum int primary key, +  job text not null, +  _date int not null, +  status varchar(80) not null +); + +CREATE TABLE queue_arg ( +  argnum int primary key, +  jobnum int not null, +  arg text null +); +CREATE INDEX queue_arg1 ON queue_arg ( jobnum ); + +CREATE TABLE part_pop_local ( +  localnum int primary key, +  popnum int not null, +  city varchar(80) null, +  state char(2) null, +  npa char(3) not null, +  nxx char(3) not null +); +CREATE UNIQUE INDEX part_pop_local1 ON part_pop_local ( npa, nxx ); + +CREATE TABLE cust_bill_event ( +  eventnum int primary key, +  invnum int not null, +  eventpart int not null, +  _date int not null +); +CREATE UNIQUE INDEX cust_bill_event1 ON cust_bill_event ( eventpart, invnum ); +CREATE INDEX cust_bill_event2 ON cust_bill_event ( invnum ); + +CREATE TABLE part_bill_event ( +  eventpart int primary key, +  payby char(4) not null, +  event varchar(80) not null, +  eventcode text null, +  seconds int null, +  weight int not null, +  plan varchar(80) null, +  plandata text null, +  disabled char(1) null +); +CREATE INDEX part_bill_event1 ON part_bill_event ( payby ); + +CREATE TABLE part_export ( +  exportnum int primary key, +  svcpart int not null, +  machine varchar(80) not null, +  exporttype varchar(80) not null, +  nodomain char(1) NULL +); +CREATE INDEX part_export1 ON part_export ( machine ); +CREATE INDEX part_export2 ON part_export ( exporttype ); + +CREATE INDEX part_export_option ( +  optionnum int primary key, +  exportnum int not null, +  option varchar(80) not null, +  optionvalue text NULL +); +CREATE INDEX part_export_option1 ON part_export_option ( exportnum ); +CREATE INDEX part_export_option2 ON part_export_option ( option ); + +ALTER TABLE svc_acct ADD domsvc integer NOT NULL; +ALTER TABLE svc_domain ADD catchall integer NULL; +ALTER TABLE cust_main ADD referral_custnum integer NULL; +ALTER TABLE cust_pay ADD custnum integer; +ALTER TABLE cust_pay_batch ADD paybatchnum integer; +ALTER TABLE cust_refund ADD custnum integer; +ALTER TABLE cust_pkg ADD manual_flag char(1) NULL; +ALTER TABLE part_pkg ADD plan varchar(80) NULL; +ALTER TABLE part_pkg ADD plandata text NULL; +ALTER TABLE part_pkg ADD setuptax char(1) NULL; +ALTER TABLE part_pkg ADD recurtax char(1) NULL; +ALTER TABLE part_pkg ADD disabled char(1) NULL; +ALTER TABLE part_svc ADD disabled char(1) NULL; +ALTER TABLE cust_bill ADD closed char(1) NULL; +ALTER TABLE cust_pay ADD closed char(1) NULL; +ALTER TABLE cust_credit ADD closed char(1) NULL; +ALTER TABLE cust_refund ADD closed char(1) NULL; +CREATE INDEX cust_main3 ON cust_main ( referral_custnum ); +CREATE INDEX cust_credit_bill1 ON cust_credit_bill ( crednum ); +CREATE INDEX cust_credit_bill2 ON cust_credit_bill ( invnum ); +CREATE INDEX cust_bill_pay1 ON cust_bill_pay ( invnum ); +CREATE INDEX cust_bill_pay2 ON cust_bill_pay ( paynum ); +CREATE INDEX cust_credit_refund1 ON cust_credit_refund ( crednum ); +CREATE INDEX cust_credit_refund2 ON cust_credit_refund ( refundnum ); +CREATE UNIQUE INDEX cust_pay_batch_pkey ON cust_pay_batch ( paybatchnum ); +CREATE UNIQUE INDEX part_svc_column1 ON part_svc_column ( svcpart, columnname ); +CREATE INDEX cust_pay2 ON cust_pay ( paynum ); +CREATE INDEX cust_pay3 ON cust_pay ( custnum ); +CREATE INDEX cust_pay4 ON cust_pay ( paybatch ); +</pre> + +  <li>If you are using PostgreSQL, apply the following changes to your database: +<pre> +CREATE UNIQUE INDEX agent_pkey ON agent ( agentnum ); +CREATE UNIQUE INDEX agent_type_pkey ON agent_type ( typenum ); +CREATE UNIQUE INDEX cust_bill_pkey ON cust_bill ( invnum ); +CREATE UNIQUE INDEX cust_credit_pkey ON cust_credit ( crednum ); +CREATE UNIQUE INDEX cust_main_pkey ON cust_main ( custnum ); +CREATE UNIQUE INDEX cust_main_county_pkey ON cust_main_county ( taxnum ); +CREATE UNIQUE INDEX cust_main_invoice_pkey ON cust_main_invoice ( destnum ); +CREATE UNIQUE INDEX cust_pay_pkey ON cust_pay ( paynum ); +CREATE UNIQUE INDEX cust_pkg_pkey ON cust_pkg ( pkgnum ); +CREATE UNIQUE INDEX cust_refund_pkey ON cust_refund ( refundnum ); +CREATE UNIQUE INDEX cust_svc_pkey ON cust_svc ( svcnum ); +CREATE UNIQUE INDEX domain_record_pkey ON domain_record ( recnum ); +CREATE UNIQUE INDEX nas_pkey ON nas ( nasnum ); +CREATE UNIQUE INDEX part_pkg_pkey ON part_pkg ( pkgpart ); +CREATE UNIQUE INDEX part_referral_pkey ON part_referral ( refnum ); +CREATE UNIQUE INDEX part_svc_pkey ON part_svc ( svcpart ); +CREATE UNIQUE INDEX port_pkey ON port ( portnum ); +CREATE UNIQUE INDEX prepay_credit_pkey ON prepay_credit ( prepaynum ); +CREATE UNIQUE INDEX session_pkey ON session ( sessionnum ); +CREATE UNIQUE INDEX svc_acct_pkey ON svc_acct ( svcnum ); +CREATE UNIQUE INDEX svc_acct_pop_pkey ON svc_acct_pop ( popnum ); +CREATE UNIQUE INDEX svc_acct_sm_pkey ON svc_acct_sm ( svcnum ); +CREATE UNIQUE INDEX svc_domain_pkey ON svc_domain ( svcnum ); +CREATE UNIQUE INDEX svc_www_pkey ON svc_www ( svcnum ); +CREATE UNIQUE INDEX type_pkgs_pkey ON type_pkgs ( typenum ); +</pre> +  <li>If you wish to enable service/shipping addresses, apply the following +      changes to your database: +<pre> +ALTER TABLE cust_main ADD COLUMN ship_last varchar(80) NULL; +ALTER TABLE cust_main ADD COLUMN ship_first varchar(80) NULL; +ALTER TABLE cust_main ADD COLUMN ship_company varchar(80) NULL; +ALTER TABLE cust_main ADD COLUMN ship_address1 varchar(80) NULL; +ALTER TABLE cust_main ADD COLUMN ship_address2 varchar(80) NULL; +ALTER TABLE cust_main ADD COLUMN ship_city varchar(80) NULL; +ALTER TABLE cust_main ADD COLUMN ship_county varchar(80) NULL; +ALTER TABLE cust_main ADD COLUMN ship_state varchar(80) NULL; +ALTER TABLE cust_main ADD COLUMN ship_zip varchar(10) NULL; +ALTER TABLE cust_main ADD COLUMN ship_country char(2) NULL; +ALTER TABLE cust_main ADD COLUMN ship_daytime varchar(20) NULL; +ALTER TABLE cust_main ADD COLUMN ship_night varchar(20) NULL; +ALTER TABLE cust_main ADD COLUMN ship_fax varchar(12) NULL; +CREATE INDEX cust_main1 ON cust_main ( ship_last ); +CREATE INDEX cust_main2 ON cust_main ( ship_company ); +</pre> +  <li>If you wish to enable customer comments, apply the following change to +      your database: +<pre> +ALTER TABLE cust_main ADD COLUMN comments text NULL; +</pre> +  <li>If you are using the signup server, reinstall it according to the <a href="signup.html">instructions</a>.  The 1.3.x signup server is not compatible with 1.4.x. +  <li>Run bin/dbdef-create. +  <li>If you have svc_acct_sm records or service definitions: +    <ul> +      <li>Create a service definition with table svc_forward +      <li>Run bin/fs-migrate-svc_acct_sm +    </ul> +  <li>Run bin/fs-migrate-payref +  <li>Run bin/fs-migrate-part_svc +  <li><b>After running bin/fs-migrate-payref</b>, apply the following changes to your database: +  <table border><tr><th>PostgreSQL</th><th>MySQL, others</th></tr> +<tr><td> +<font size=-1><pre> +CREATE TABLE cust_pay_temp ( +  paynum int primary key, +  custnum int not null, +  paid decimal(10,2) not null, +  _date int null, +  payby char(4) not null, +  payinfo varchar(16) null, +  paybatch varchar(80) null +); +INSERT INTO cust_pay_temp SELECT * from cust_pay; +DROP TABLE cust_pay; +ALTER TABLE cust_pay_temp RENAME TO cust_pay; +CREATE UNIQUE INDEX cust_pay1 ON cust_pay (paynum); +CREATE TABLE cust_refund_temp ( +  refundnum int primary key, +  custnum int not null, +  _date int null, +  refund decimal(10,2) not null, +  otaker varchar(8) not null, +  reason varchar(80) not null, +  payby char(4) not null, +  payinfo varchar(16) null, +  paybatch varchar(80) null +); +INSERT INTO cust_refund_temp SELECT * from cust_refund; +DROP TABLE cust_refund; +ALTER TABLE cust_refund_temp RENAME TO cust_refund; +CREATE UNIQUE INDEX cust_refund1 ON cust_refund (refundnum); +</pre></font> +</td><td> +<font size=-1><pre> +ALTER TABLE cust_pay DROP COLUMN invnum; +ALTER TABLE cust_refund DROP COLUMN crednum; +</pre></font> +</td></tr></table> +  <li><b>IMPORTANT: After applying the second set of database changes</b>, run bin/dbdef-create again. +  <li>set the <a href="../config/config.cgi#username_policy">user_policy configuration value</a> as appropriate for your site. +  <li>Create the `/usr/local/etc/freeside/cache.<i>datasrc</i>' directory +      (ownded by the freeside user). +  <li>freeside-queued was installed with the Perl modules.  Start it now and ensure that is run upon system startup. +  <li>Set appropriate <a href="../browse/part_bill_event.cgi">invoice events</a> for your site.  At the very least, you'll want to set some invoice events "<i>After 0 days</i>": a <i>BILL</i> invoice event to print invoices, a <i>CARD</i> invoice event to batch or run cards real-time, and a <i>COMP</i> invoice event to "pay" complimentary customers.  If you were using the <i>-i</i> option to <a href="man/bin/freeside-bill.html">freeside-bill</a> it should be removed. +  <li>Use <a href="man/bin/freeside-daily.html">freeside-daily</a> instead of <a href="man/bin/freeside-bill.html">freeside-bill</a>. +</ul> +</body> diff --git a/httemplate/edit/REAL_cust_pkg.cgi b/httemplate/edit/REAL_cust_pkg.cgi new file mode 100755 index 000000000..abfaac3fc --- /dev/null +++ b/httemplate/edit/REAL_cust_pkg.cgi @@ -0,0 +1,82 @@ +<!-- mason kludge --> +<% +# <!-- $Id: REAL_cust_pkg.cgi,v 1.2 2002-02-10 16:05:22 ivan Exp $ --> + +my $error =''; +my $pkgnum = ''; +if ( $cgi->param('error') ) { +  $error = $cgi->param('error'); +  $pkgnum = $cgi->param('pkgnum'); +} else { +  my($query) = $cgi->keywords; +  $query =~ /^(\d+)$/ or die "no pkgnum"; +  $pkgnum = $1; +} + +#get package record +my $cust_pkg = qsearchs('cust_pkg',{'pkgnum'=>$pkgnum}); +die "No package!" unless $cust_pkg; +my $part_pkg = qsearchs('part_pkg',{'pkgpart'=>$cust_pkg->getfield('pkgpart')}); + +if ( $error ) { +  #$cust_pkg->$_(str2time($cgi->param($_)) foreach qw(setup bill); +  $cust_pkg->setup(str2time($cgi->param('setup'))); +  $cust_pkg->bill(str2time($cgi->param('bill'))); +} + +#my $custnum = $cust_pkg->getfield('custnum'); +print header('Package Edit'); #, menubar( +#  "View this customer (#$custnum)" => popurl(2). "view/cust_main.cgi?$custnum", +#  'Main Menu' => popurl(2) +#)); + +#print info +my($susp,$cancel,$expire)=( +  $cust_pkg->getfield('susp'), +  $cust_pkg->getfield('cancel'), +  $cust_pkg->getfield('expire'), +); +my($pkg,$comment)=($part_pkg->getfield('pkg'),$part_pkg->getfield('comment')); +my($setup,$bill)=($cust_pkg->getfield('setup'),$cust_pkg->getfield('bill')); +my $otaker = $cust_pkg->getfield('otaker'); + +print '<FORM NAME="formname" ACTION="process/REAL_cust_pkg.cgi" METHOD="POST">',      qq!<INPUT TYPE="hidden" NAME="pkgnum" VALUE="$pkgnum">!; + +print qq!<FONT SIZE="+1" COLOR="#ff0000">Error: $error</FONT>! +  if $error; + +print &ntable("#cccccc"), '<TR><TD>', &ntable("#cccccc",2), +      '<TR><TD ALIGN="right">Package number</TD><TD BGCOLOR="#ffffff">', +      $pkgnum, '</TD></TR>', +      '<TR><TD ALIGN="right">Package</TD><TD BGCOLOR="#ffffff">', +      $pkg,  '</TD></TR>', +      '<TR><TD ALIGN="right">Comment</TD><TD BGCOLOR="#ffffff">', +      $comment,  '</TD></TR>', +      '<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>', +; + +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">', +       time2str("%D",$expire), '</TD></TR>' +  if $expire; + +print '<TR><TD ALIGN="right">Cancellation date</TD><TD BGCOLOR="#ffffff">', +       time2str("%D",$cancel), '</TD></TR>' +  if $cancel; + +%> +</TABLE></TD></TR></TABLE>'. +<BR><INPUT TYPE="submit" VALUE="Apply Changes"> +</FORM> +</BODY> +</HTML> diff --git a/httemplate/edit/agent.cgi b/httemplate/edit/agent.cgi new file mode 100755 index 000000000..3fca34326 --- /dev/null +++ b/httemplate/edit/agent.cgi @@ -0,0 +1,74 @@ +<!-- mason kludge --> +<% + +my $agent; +if ( $cgi->param('error') ) { +  $agent = new FS::agent ( { +    map { $_, scalar($cgi->param($_)) } fields('agent') +  } ); +} elsif ( $cgi->keywords ) { +  my($query) = $cgi->keywords; +  $query =~ /^(\d+)$/; +  $agent = qsearchs( 'agent', { 'agentnum' => $1 } ); +} else { #adding +  $agent = new FS::agent {}; +} +my $action = $agent->agentnum ? 'Edit' : 'Add'; +my $hashref = $agent->hashref; + +print 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'); + +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)"; + +print &ntable("#cccccc", 2, ''), <<END; +<TR> +  <TH ALIGN="right">Agent</TH> +  <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"; +} + +print <<END; +</SELECT></TD> +</TR> +<TR> +  <TD ALIGN="right">Frequency (unimplemented)</TD> +  <TD><INPUT TYPE="text" NAME="freq" VALUE="$hashref->{freq}"></TD> +</TR> +<TR> +  <TD ALIGN="right">Program (unimplemented)</TD> +  <TD><INPUT TYPE="text" NAME="prog" VALUE="$hashref->{prog}"></TD> +</TR> +</TABLE> +END + +print qq!<BR><INPUT TYPE="submit" VALUE="!, +      $hashref->{agentnum} ? "Apply changes" : "Add agent", +      qq!">!; + +print <<END; +    </FORM> +  </BODY> +</HTML> +END + +%> diff --git a/httemplate/edit/agent_type.cgi b/httemplate/edit/agent_type.cgi new file mode 100755 index 000000000..4a4cd9a47 --- /dev/null +++ b/httemplate/edit/agent_type.cgi @@ -0,0 +1,63 @@ +<!-- mason kludge --> +<% + +my($agent_type); +if ( $cgi->param('error') ) { +  $agent_type = new FS::agent_type ( { +    map { $_, scalar($cgi->param($_)) } fields('agent') +  } ); +} elsif ( $cgi->keywords ) { #editing +  my( $query ) = $cgi->keywords; +  $query =~ /^(\d+)$/; +  $agent_type=qsearchs('agent_type',{'typenum'=>$1}); +} else { #adding +  $agent_type = new FS::agent_type {}; +} +my $action = $agent_type->typenum ? 'Edit' : 'Add'; +my $hashref = $agent_type->hashref; + +print header("$action Agent Type", menubar( +  'Main Menu' => "$p", +  'View all agent types' => "${p}browse/agent_type.cgi", +)); + +print qq!<FONT SIZE="+1" COLOR="#ff0000">Error: !, $cgi->param('error'), +      "</FONT>" +  if $cgi->param('error'); + +print '<FORM ACTION="', popurl(1), 'process/agent_type.cgi" METHOD=POST>', +      qq!<INPUT TYPE="hidden" NAME="typenum" VALUE="$hashref->{typenum}">!, +      "Agent Type #", $hashref->{typenum} ? $hashref->{typenum} : "(NEW)"; + +print <<END; +<BR><BR>Agent Type <INPUT TYPE="text" NAME="atype" SIZE=32 VALUE="$hashref->{atype}"> +<BR><BR>Select which packages agents of this type may sell to customers<BR> +END + +foreach my $part_pkg ( qsearch('part_pkg',{ 'disabled' => '' }) ) { +  print qq!<BR><INPUT TYPE="checkbox" NAME="pkgpart!, +        $part_pkg->getfield('pkgpart'), qq!" !, +       # ( 'CHECKED 'x scalar( +        qsearchs('type_pkgs',{ +          'typenum' => $agent_type->getfield('typenum'), +          'pkgpart'  => $part_pkg->getfield('pkgpart'), +        }) +          ? 'CHECKED ' +          : '', +        qq!VALUE="ON"> !, +    qq!<A HREF="${p}edit/part_pkg.cgi?!, $part_pkg->pkgpart,  +    '">', $part_pkg->getfield('pkg'), '</A>', +  ; +} + +print qq!<BR><BR><INPUT TYPE="submit" VALUE="!, +      $hashref->{typenum} ? "Apply changes" : "Add agent type", +      qq!">!; + +print <<END; +    </FORM> +  </BODY> +</HTML> +END + +%> diff --git a/httemplate/edit/cust_bill_pay.cgi b/httemplate/edit/cust_bill_pay.cgi new file mode 100755 index 000000000..d90659724 --- /dev/null +++ b/httemplate/edit/cust_bill_pay.cgi @@ -0,0 +1,96 @@ +<!-- mason kludge --> +<% + +my($paynum, $amount, $invnum); +if ( $cgi->param('error') ) { +  $paynum = $cgi->param('paynum'); +  $amount = $cgi->param('amount'); +  $invnum = $cgi->param('invnum'); +} else { +  my($query) = $cgi->keywords; +  $query =~ /^(\d+)$/; +  $paynum = $1; +  $amount = ''; +  $invnum = ''; +} + +my $otaker = getotaker; + +my $p1 = popurl(1); + +print header("Apply Payment", ''); +print qq!<FONT SIZE="+1" COLOR="#ff0000">Error: !, $cgi->param('error'), +      "</FONT><BR><BR>" +  if $cgi->param('error'); +print <<END; +    <FORM ACTION="${p1}process/cust_bill_pay.cgi" METHOD=POST> +END + +my $cust_pay = qsearchs('cust_pay', { 'paynum' => $paynum } ); +die "payment $paynum not found!" unless $cust_pay; + +my $unapplied = $cust_pay->unapplied; + +print "Payment # <B>$paynum</B>". +      qq!<INPUT TYPE="hidden" NAME="paynum" VALUE="$paynum">!. +      '<BR>Date: <B>'. time2str("%D", $cust_pay->_date). '</B>'. +      '<BR>Amount: $<B>'. $cust_pay->paid. '</B>'. +      "<BR>Unapplied amount: \$<B>$unapplied</B>" +      ; + +my @cust_bill = grep $_->owed != 0, +                qsearch('cust_bill', { 'custnum' => $cust_pay->custnum } ); + +print <<END; +<SCRIPT> +function changed(what) { +  cust_bill = what.options[what.selectedIndex].value; +END + +foreach my $cust_bill ( @cust_bill ) { +  my $invnum = $cust_bill->invnum; +  my $changeto = $cust_bill->owed < $unapplied +                   ? $cust_bill->owed  +                   : $unapplied; +  print <<END; +  if ( cust_bill == $invnum ) { +    what.form.amount.value = "$changeto"; +  } +END +} + +#print <<END; +#  if ( cust_bill == "Refund" ) { +#    what.form.amount.value = "$credited"; +#  } +#} +#</SCRIPT> +#END +print "</SCRIPT>\n"; + +print qq!<BR>Invoice #<SELECT NAME="invnum" SIZE=1 onChange="changed(this)">!, +      '<OPTION VALUE="">'; +foreach my $cust_bill ( @cust_bill ) { +  print '<OPTION'. ( $cust_bill->invnum eq $invnum ? ' SELECTED' : '' ). +        ' VALUE="'. $cust_bill->invnum. '">'. $cust_bill->invnum. +        ' -  '. time2str("%D",$cust_bill->_date). +        ' - $'. $cust_bill->owed; +} +#print qq!<OPTION VALUE="Refund">Refund!; +print "</SELECT>"; + +print qq!<BR>Amount \$<INPUT TYPE="text" NAME="amount" VALUE="$amount" SIZE=8 MAXLENGTH=8>!; + +print <<END; +<BR> +<INPUT TYPE="submit" VALUE="Apply"> +END + +print <<END; + +    </FORM> +  </BODY> +</HTML> +END + +%> diff --git a/httemplate/edit/cust_credit.cgi b/httemplate/edit/cust_credit.cgi new file mode 100755 index 000000000..aae0df2fc --- /dev/null +++ b/httemplate/edit/cust_credit.cgi @@ -0,0 +1,63 @@ +<!-- mason kludge --> +<% + +my $conf = new FS::Conf; +my($custnum, $amount, $reason); +if ( $cgi->param('error') ) { +  #$cust_credit = new FS::cust_credit ( { +  #  map { $_, scalar($cgi->param($_)) } fields('cust_credit') +  #} ); +  $custnum = $cgi->param('custnum'); +  $amount = $cgi->param('amount'); +  #$refund = $cgi->param('refund'); +  $reason = $cgi->param('reason'); +} else { +  my($query) = $cgi->keywords; +  $query =~ /^(\d+)$/; +  $custnum = $1; +  $amount = ''; +  #$refund = 'yes'; +  $reason = ''; +} +my $_date = time; + +my $otaker = getotaker; + +my $p1 = popurl(1); + +print header("Post Credit", ''); +print qq!<FONT SIZE="+1" COLOR="#ff0000">Error: !, $cgi->param('error'), +      "</FONT>" +  if $cgi->param('error'); +print <<END, small_custview($custnum, $conf->config('countrydefault')); +    <FORM ACTION="${p1}process/cust_credit.cgi" METHOD=POST> +    <INPUT TYPE="hidden" NAME="crednum" VALUE=""> +    <INPUT TYPE="hidden" NAME="custnum" VALUE="$custnum"> +    <INPUT TYPE="hidden" NAME="paybatch" VALUE=""> +    <INPUT TYPE="hidden" NAME="_date" VALUE="$_date"> +    <INPUT TYPE="hidden" NAME="credited" VALUE=""> +    <INPUT TYPE="hidden" NAME="otaker" VALUE="$otaker"> +END + +print '<BR><BR>Credit'. ntable("#cccccc", 2). +      '<TR><TD ALIGN="right">Date</TD><TD BGCOLOR="#ffffff">'. +      time2str("%D",$_date).  '</TD></TR>'; + +print qq!<TR><TD ALIGN="right">Amount</TD><TD BGCOLOR="#ffffff">\$<INPUT TYPE="text" NAME="amount" VALUE="$amount" SIZE=8 MAXLENGTH=8></TD></TR>!; + +#print qq! <INPUT TYPE="checkbox" NAME="refund" VALUE="$refund">Also post refund!; + +print qq!<TR><TD ALIGN="right">Reason</TD><TD BGCOLOR="#ffffff"><INPUT TYPE="text" NAME="reason" VALUE="$reason"></TD></TR>!; + +print qq!<TR><TD ALIGN="right">Auto-apply<BR>to invoices</TD><TD><SELECT NAME="apply"><OPTION VALUE="yes" SELECTED>yes<OPTION>no</SELECT></TD>!; + +print <<END; +</TABLE> +<BR> +<INPUT TYPE="submit" VALUE="Post credit"> +    </FORM> +  </BODY> +</HTML> +END + +%> diff --git a/httemplate/edit/cust_credit_bill.cgi b/httemplate/edit/cust_credit_bill.cgi new file mode 100755 index 000000000..1a97e1312 --- /dev/null +++ b/httemplate/edit/cust_credit_bill.cgi @@ -0,0 +1,101 @@ +<!-- mason kludge --> +<% + +my($crednum, $amount, $invnum); +if ( $cgi->param('error') ) { +  #$cust_credit_bill = new FS::cust_credit_bill ( { +  #  map { $_, scalar($cgi->param($_)) } fields('cust_credit_bill') +  #} ); +  $crednum = $cgi->param('crednum'); +  $amount = $cgi->param('amount'); +  #$refund = $cgi->param('refund'); +  $invnum = $cgi->param('invnum'); +} else { +  my($query) = $cgi->keywords; +  $query =~ /^(\d+)$/; +  $crednum = $1; +  $amount = ''; +  #$refund = 'yes'; +  $invnum = ''; +} + +my $otaker = getotaker; + +my $p1 = popurl(1); + +print header("Apply Credit", ''); +print qq!<FONT SIZE="+1" COLOR="#ff0000">Error: !, $cgi->param('error'), +      "</FONT><BR><BR>" +  if $cgi->param('error'); +print <<END; +    <FORM ACTION="${p1}process/cust_credit_bill.cgi" METHOD=POST> +END + +my $cust_credit = qsearchs('cust_credit', { 'crednum' => $crednum } ); +die "credit $crednum not found!" unless $cust_credit; + +my $credited = $cust_credit->credited; + +print "Credit # <B>$crednum</B>". +      qq!<INPUT TYPE="hidden" NAME="crednum" VALUE="$crednum">!. +      '<BR>Date: <B>'. time2str("%D", $cust_credit->_date). '</B>'. +      '<BR>Amount: $<B>'. $cust_credit->amount. '</B>'. +      "<BR>Unapplied amount: \$<B>$credited</B>". +      '<BR>Reason: <B>'. $cust_credit->reason. '</B>' +      ; + +my @cust_bill = grep $_->owed != 0, +                qsearch('cust_bill', { 'custnum' => $cust_credit->custnum } ); + +print <<END; +<SCRIPT> +function changed(what) { +  cust_bill = what.options[what.selectedIndex].value; +END + +foreach my $cust_bill ( @cust_bill ) { +  my $invnum = $cust_bill->invnum; +  my $changeto = $cust_bill->owed < $cust_credit->credited +                   ? $cust_bill->owed  +                   : $cust_credit->credited; +  print <<END; +  if ( cust_bill == $invnum ) { +    what.form.amount.value = "$changeto"; +  } +END +} + +print <<END; +  if ( cust_bill == "Refund" ) { +    what.form.amount.value = "$credited"; +  } +} +</SCRIPT> +END + +print qq!<BR>Invoice #<SELECT NAME="invnum" SIZE=1 onChange="changed(this)">!, +      '<OPTION VALUE="">'; +foreach my $cust_bill ( @cust_bill ) { +  print '<OPTION'. ( $cust_bill->invnum eq $invnum ? ' SELECTED' : '' ). +        ' VALUE="'. $cust_bill->invnum. '">'. $cust_bill->invnum. +        ' -  '. time2str("%D",$cust_bill->_date). +        ' - $'. $cust_bill->owed; +} +print qq!<OPTION VALUE="Refund">Refund!; +print "</SELECT>"; + +print qq!<BR>Amount \$<INPUT TYPE="text" NAME="amount" VALUE="$amount" SIZE=8 MAXLENGTH=8>!; + +print <<END; +<BR> +<INPUT TYPE="submit" VALUE="Apply"> +END + +print <<END; + +    </FORM> +  </BODY> +</HTML> +END + +%> diff --git a/httemplate/edit/cust_main.cgi b/httemplate/edit/cust_main.cgi new file mode 100755 index 000000000..41643a37f --- /dev/null +++ b/httemplate/edit/cust_main.cgi @@ -0,0 +1,454 @@ +<!-- mason kludge --> +<% + +  #for misplaced logic below +  #use FS::part_pkg; + +  #for false laziness below (now more properly lazy) +  #use FS::svc_acct_pop; + +  #for (other) false laziness below +  #use FS::agent; +  #use FS::type_pkgs; + +my $conf = new FS::Conf; + +#get record + +my $error = ''; +my($custnum, $username, $password, $popnum, $cust_main, $saved_pkgpart); +if ( $cgi->param('error') ) { +  $error = $cgi->param('error'); +  $cust_main = new FS::cust_main ( { +    map { $_, scalar($cgi->param($_)) } fields('cust_main') +  } ); +  $custnum = $cust_main->custnum; +  $saved_pkgpart = $cgi->param('pkgpart_svcpart') || ''; +  if ( $saved_pkgpart =~ /^(\d+)_/ ) { +    $saved_pkgpart = $1; +  } else { +    $saved_pkgpart = ''; +  } +  $username = $cgi->param('username'); +  $password = $cgi->param('_password'); +  $popnum = $cgi->param('popnum'); +} elsif ( $cgi->keywords ) { #editing +  my( $query ) = $cgi->keywords; +  $query =~ /^(\d+)$/; +  $custnum=$1; +  $cust_main = qsearchs('cust_main', { 'custnum' => $custnum } ); +  $saved_pkgpart = 0; +  $username = ''; +  $password = ''; +  $popnum = 0; +} else { +  $custnum=''; +  $cust_main = new FS::cust_main ( {} ); +  $cust_main->otaker( &getotaker ); +  $cust_main->referral_custnum( $cgi->param('referral_custnum') ); +  $saved_pkgpart = 0; +  $username = ''; +  $password = ''; +  $popnum = 0; +} +$cgi->delete_all(); +my $action = $custnum ? 'Edit' : 'Add'; + +# top + +my $p1 = popurl(1); +print header("Customer $action", ''); +print qq!<FONT SIZE="+1" COLOR="#ff0000">Error: !, $error, "</FONT>" +  if $error; + +print qq!<FORM ACTION="${p1}process/cust_main.cgi" METHOD=POST NAME="form1">!, +      qq!<INPUT TYPE="hidden" NAME="custnum" VALUE="$custnum">!, +      qq!Customer # !, ( $custnum ? "<B>$custnum</B>" : " (NEW)" ), +       +; + +# agent + +my $r = qq!<font color="#ff0000">*</font>!; + +my @agents = qsearch( 'agent', {} ); +#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; +my $agentnum = $cust_main->agentnum || $agents[0]->agentnum; #default to first +if ( scalar(@agents) == 1 ) { +  print qq!<INPUT TYPE="hidden" NAME="agentnum" VALUE="$agentnum">!; +} else { +  print qq!<BR><BR>${r}Agent <SELECT NAME="agentnum" SIZE="1">!; +  my $agent; +  foreach $agent (sort { +    $a->agent cmp $b->agent; +  } @agents) { +      print '<OPTION VALUE="', $agent->agentnum, '"', +      " SELECTED"x($agent->agentnum==$agentnum), +      ">", $agent->agentnum,": ", $agent->agent; +  } +  print "</SELECT>"; +} + +#referral + +my $refnum = $cust_main->refnum || $conf->config('referraldefault') || 0; +if ( $custnum && ! $conf->exists('editreferrals') ) { +  print qq!<INPUT TYPE="hidden" NAME="refnum" VALUE="$refnum">!; +} else { +  my(@referrals) = qsearch('part_referral',{}); +  if ( scalar(@referrals) == 1 ) { +    $refnum ||= $referrals[0]->refnum; +    print qq!<INPUT TYPE="hidden" NAME="refnum" VALUE="$refnum">!; +  } else { +    print qq!<BR><BR>${r}Referral <SELECT NAME="refnum" SIZE="1">!; +    print "<OPTION> " unless $refnum; +    my($referral); +    foreach $referral (sort { +      $a->refnum <=> $b->refnum; +    } @referrals) { +      print "<OPTION" . " SELECTED"x($referral->refnum==$refnum), +      ">", $referral->refnum, ": ", $referral->referral; +    } +    print "</SELECT>"; +  } +} + +#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 } ); +  print '<BR><BR>Referring Customer: <A HREF="'. popurl(1). '/cust_main.cgi?'. +        $cust_main->referral_custnum. '">'. +        $cust_main->referral_custnum. ': '. +        ( $referring_cust_main->company +          || $referring_cust_main->last. ', '. $referring_cust_main->first ). +        '</A><INPUT TYPE="hidden" NAME="referral_custnum" VALUE="'. +        $cust_main->referral_custnum. '">'; +} elsif ( ! $conf->exists('disable_customer_referrals') ) { +  print '<BR><BR>Referring customer number: <INPUT TYPE="text" NAME="referral_custnum" VALUE="">'; +} else { +  print '<INPUT TYPE="hidden" NAME="referral_custnum" VALUE="">'; +} + +# contact info + +my($last,$first,$ss,$company,$address1,$address2,$city,$zip)=( +  $cust_main->last, +  $cust_main->first, +  $cust_main->ss, +  $cust_main->company, +  $cust_main->address1, +  $cust_main->address2, +  $cust_main->city, +  $cust_main->zip, +); + +print "<BR><BR>Billing address", &itable("#cccccc"), <<END; +<TR><TH ALIGN="right">${r}Contact name<BR>(last, first)</TH><TD COLSPAN=3> +END + +print <<END; +<INPUT TYPE="text" NAME="last" VALUE="$last"> ,  +<INPUT TYPE="text" NAME="first" VALUE="$first"> +</TD> +END + +if ( $conf->exists('show_ss') ) { +  print qq!<TD ALIGN="right">SS#</TD><TD><INPUT TYPE="text" NAME="ss" VALUE="$ss" SIZE=11></TD>!; +} else { +  print qq!<TD><INPUT TYPE="hidden" NAME="ss" VALUE="$ss"></TD>!; +} + +print <<END; +</TR> +<TR><TD ALIGN="right">Company</TD><TD COLSPAN=5><INPUT TYPE="text" NAME="company" VALUE="$company" SIZE=70></TD></TR> +<TR><TH ALIGN="right">${r}Address</TH><TD COLSPAN=5><INPUT TYPE="text" NAME="address1" VALUE="$address1" SIZE=70></TD></TR> +<TR><TD ALIGN="right"> </TD><TD COLSPAN=5><INPUT TYPE="text" NAME="address2" VALUE="$address2" SIZE=70></TD></TR> +<TR><TH ALIGN="right">${r}City</TH><TD><INPUT TYPE="text" NAME="city" VALUE="$city"></TD><TH ALIGN="right">${r}State/Country</TH><TD><SELECT NAME="state" SIZE="1"> +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'; +foreach ( sort { +     ( $b->country eq $countrydefault ) <=> ( $a->country eq $countrydefault ) +  or $a->country                        cmp $b->country +  or $a->state                          cmp $b->state +  or $a->county                         cmp $b->county +} qsearch('cust_main_county',{}) ) { +  print "<OPTION"; +  print " SELECTED" if ( $cust_main->state eq $_->state +                         && $cust_main->county eq $_->county  +                         && $cust_main->country eq $_->country +                       ); +  print ">",$_->state; +  print " (",$_->county,")" if $_->county; +  print " / ", $_->country; +} +print qq!</SELECT></TD><TH>${r}Zip</TH><TD><INPUT TYPE="text" NAME="zip" VALUE="$zip" SIZE=10></TD></TR>!; + +my($daytime,$night,$fax)=( +  $cust_main->daytime, +  $cust_main->night, +  $cust_main->fax, +); + +print <<END; +<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">Fax</TD><TD COLSPAN=5><INPUT TYPE="text" NAME="fax" VALUE="$fax" SIZE=12></TD></TR> +END + +print "</TABLE>$r required fields<BR>"; + +# service address + +if ( defined $cust_main->dbdef_table->column('ship_last') ) { + +  print "\n", <<END; +  <SCRIPT> +  function changed(what) { +    what.form.same.checked = false; +  } +  function samechanged(what) { +    if ( what.checked ) { +END +print "      what.form.ship_$_.value = what.form.$_.value;\n" +  for (qw( last first company address1 address2 city zip daytime night fax )); +print <<END; +      what.form.ship_state.selectedIndex = what.form.state.selectedIndex; +    } +  } +  </SCRIPT> +END + +  print '<BR>Service address ', +        '(<INPUT TYPE="checkbox" NAME="same" VALUE="Y" onClick="samechanged(this)"'; +  unless ( $cust_main->ship_last ) { +    print ' CHECKED'; +    foreach ( +      qw( last first company address1 address2 city state zip daytime night fax) +    ) { +      $cust_main->set("ship_$_", $cust_main->get($_) ); +    } +  } +  print '>same as billing address)<BR>'; + +  my($ship_last,$ship_first,$ship_company,$ship_address1,$ship_address2,$ship_city,$ship_zip)=( +    $cust_main->ship_last, +    $cust_main->ship_first, +    $cust_main->ship_company, +    $cust_main->ship_address1, +    $cust_main->ship_address2, +    $cust_main->ship_city, +    $cust_main->ship_zip, +  ); + +  print &itable("#cccccc"), <<END; +  <TR><TH ALIGN="right">${r}Contact name<BR>(last, first)</TH><TD COLSPAN=5> +END + +  print <<END; +  <INPUT TYPE="text" NAME="ship_last" VALUE="$ship_last" onChange="changed(this)"> ,  +  <INPUT TYPE="text" NAME="ship_first" VALUE="$ship_first" onChange="changed(this)"> +END + +  print <<END; +  </TD></TR> +  <TR><TD ALIGN="right">Company</TD><TD COLSPAN=5><INPUT TYPE="text" NAME="ship_company" VALUE="$ship_company" SIZE=70 onChange="changed(this)"></TD></TR> +  <TR><TH ALIGN="right">${r}Address</TH><TD COLSPAN=5><INPUT TYPE="text" NAME="ship_address1" VALUE="$ship_address1" SIZE=70 onChange="changed(this)"></TD></TR> +  <TR><TD ALIGN="right"> </TD><TD COLSPAN=5><INPUT TYPE="text" NAME="ship_address2" VALUE="$ship_address2" SIZE=70 onChange="changed(this)"></TD></TR> +  <TR><TH ALIGN="right">${r}City</TH><TD><INPUT TYPE="text" NAME="ship_city" VALUE="$ship_city" onChange="changed(this)"></TD><TH ALIGN="right">${r}State/Country</TH><TD><SELECT NAME="ship_state" SIZE="1" onChange="changed(this)"> +END + +  $cust_main->ship_country( $conf->config('countrydefault') || 'US' ) +    unless $cust_main->ship_country; +  $cust_main->ship_state( $conf->config('statedefault') || 'CA' ) +    unless $cust_main->ship_state || $cust_main->ship_country ne 'US'; +  foreach ( qsearch('cust_main_county',{}) ) { +    print "<OPTION"; +    print " SELECTED" if ( $cust_main->ship_state eq $_->state +                           && $cust_main->ship_county eq $_->county  +                           && $cust_main->ship_country eq $_->country +                         ); +    print ">",$_->state; +    print " (",$_->county,")" if $_->county; +    print " / ", $_->country; +  } +  print qq!</SELECT></TD><TH>${r}Zip</TH><TD><INPUT TYPE="text" NAME="ship_zip" VALUE="$ship_zip" SIZE=10 onChange="changed(this)"></TD></TR>!; + +  my($ship_daytime,$ship_night,$ship_fax)=( +    $cust_main->ship_daytime, +    $cust_main->ship_night, +    $cust_main->ship_fax, +  ); + +  print <<END; +  <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">Fax</TD><TD COLSPAN=5><INPUT TYPE="text" NAME="ship_fax" VALUE="$ship_fax" SIZE=12 onChange="changed(this)"></TD></TR> +END + +  print "</TABLE>$r required fields<BR>"; + +} + +# billing info + +sub expselect { +  my $prefix = shift; +  my( $m, $y ) = (0, 0); +  if ( scalar(@_) ) { +    my $date = shift || '01-2000'; +    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"; +    } +  } + +  my $return = qq!<SELECT NAME="$prefix!. qq!_month" SIZE="1">!; +  for ( 1 .. 12 ) { +    $return .= "<OPTION"; +    $return .= " SELECTED" if $_ == $m; +    $return .= ">$_"; +  } +  $return .= qq!</SELECT>/<SELECT NAME="$prefix!. qq!_year" SIZE="1">!; +  for ( 2001 .. 2037 ) { +    $return .= "<OPTION"; +    $return .= " SELECTED" if $_ == $y; +    $return .= ">$_"; +  } +  $return .= "</SELECT>"; + +  $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 = ( +  '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"), +); +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), +); +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>!; +  } +} + +print "</TR></TABLE>$r required fields for each billing type"; + +if ( defined $cust_main->dbdef_table->column('comments') ) { +    print "<BR><BR>Comments", &itable("#cccccc"), +          qq!<TR><TD><TEXTAREA COLS=80 ROWS=5 WRAP="HARD" NAME="comments">!, +          $cust_main->comments, "</TEXTAREA>", +          "</TD></TR></TABLE>"; +} + +unless ( $custnum ) { +  # pry the wrong place for this logic.  also pretty expensive +  #use FS::part_pkg; + +  #false laziness, copied from FS::cust_pkg::order +  my $pkgpart; +  if ( scalar(@agents) == 1 ) { +    # $pkgpart->{PKGPART} is true iff $custnum may purchase PKGPART +    my($agent)=qsearchs('agent',{'agentnum'=> $agentnum }); +    $pkgpart = $agent->pkgpart_hashref; +  } else { +    #can't know (agent not chosen), so, allow all +    my %typenum; +    foreach my $agent ( @agents ) { +      next if $typenum{$agent->typenum}++; +      #fixed in 5.004_05 #$pkgpart->{$_}++ foreach keys %{ $agent->pkgpart_hashref } +      foreach ( keys %{ $agent->pkgpart_hashref } ) { $pkgpart->{$_}++; } #5.004_04 workaround +    } +  } +  #eslaf + +  my @part_pkg = grep { $_->svcpart('svc_acct') && $pkgpart->{ $_->pkgpart } } +    qsearch( 'part_pkg', { 'disabled' => '' } ); + +  if ( @part_pkg ) { + +#    print "<BR><BR>First package", &itable("#cccccc", "0 ALIGN=LEFT"), +#apiabuse & undesirable wrapping +    print "<BR><BR>First package", &itable("#cccccc"), +          qq!<TR><TD COLSPAN=2><SELECT NAME="pkgpart_svcpart">!; + +    print qq!<OPTION VALUE="">(none)!; + +    foreach my $part_pkg ( @part_pkg ) { +      print qq!<OPTION VALUE="!, +#              $part_pkg->pkgpart. "_". $pkgpart{ $part_pkg->pkgpart }, '"'; +              $part_pkg->pkgpart. "_". $part_pkg->svcpart, '"'; +      print " SELECTED" if $saved_pkgpart && ( $part_pkg->pkgpart == $saved_pkgpart ); +      print ">", $part_pkg->pkg, " - ", $part_pkg->comment; +    } +    print "</SELECT></TD></TR>"; + +    #false laziness: (mostly) copied from edit/svc_acct.cgi +    #$ulen = $svc_acct->dbdef_table->column('username')->length; +    my $ulen = dbdef->table('svc_acct')->column('username')->length; +    my $ulen2 = $ulen+2; +    my $passwordmax = $conf->config('passwordmax') || 8; +    my $pmax2 = $passwordmax + 2; +    print <<END; +<TR><TD ALIGN="right">Username</TD> +<TD><INPUT TYPE="text" NAME="username" VALUE="$username" SIZE=$ulen2 MAXLENGTH=$ulen></TD></TR> +<TR><TD ALIGN="right">Password</TD> +<TD><INPUT TYPE="text" NAME="_password" VALUE="$password" SIZE=$pmax2 MAXLENGTH=$passwordmax> +(blank to generate)</TD></TR> +END + +    print '<TR><TD ALIGN="right">Access number</TD><TD WIDTH="100%">' +          . +          &FS::svc_acct_pop::popselector($popnum). +          '</TD></TR></TABLE>' +          ; +  } +} + +my $otaker = $cust_main->otaker; +print qq!<INPUT TYPE="hidden" NAME="otaker" VALUE="$otaker">!, +      qq!<BR><INPUT TYPE="submit" VALUE="!, +      $custnum ?  "Apply Changes" : "Add Customer", qq!">!, +      "</FORM></BODY></HTML>", +; + +%> diff --git a/httemplate/edit/cust_main_county-expand.cgi b/httemplate/edit/cust_main_county-expand.cgi new file mode 100755 index 000000000..66e8aaf9e --- /dev/null +++ b/httemplate/edit/cust_main_county-expand.cgi @@ -0,0 +1,51 @@ +<!-- mason kludge --> +<% + +my($taxnum, $delim, $expansion ); +if ( $cgi->param('error') ) { +  $taxnum = $cgi->param('taxnum'); +  $delim = $cgi->param('delim'); +  $expansion = $cgi->param('expansion'); +} else { +  my ($query) = $cgi->keywords; +  $query =~ /^(\d+)$/ +    or die "Illegal taxnum!"; +  $taxnum = $1; +  $delim = 'n'; +  $expansion = ''; +} + +my $cust_main_county = qsearchs('cust_main_county',{'taxnum'=>$taxnum}) +  or die "cust_main_county.taxnum $taxnum not found"; +die "Can't expand entry!" if $cust_main_county->getfield('county'); + +my $p1 = popurl(1); +print header("Tax Rate (expand)", menubar( +  'Main Menu' => popurl(2), +)); + +print qq!<FONT SIZE="+1" COLOR="#ff0000">Error: !, $cgi->param('error'), +      "</FONT>" +  if $cgi->param('error'); + +print <<END; +    <FORM ACTION="${p1}process/cust_main_county-expand.cgi" METHOD=POST> +      <INPUT TYPE="hidden" NAME="taxnum" VALUE="$taxnum"> +      Separate by +END +print '<INPUT TYPE="radio" NAME="delim" VALUE="n"'; +print ' CHECKED' if $delim eq 'n'; +print '>line (rumor has it broken on some browsers) or', +      '<INPUT TYPE="radio" NAME="delim" VALUE="s"'; +print ' CHECKED' if $delim eq 's'; +print '>whitespace.'; +print <<END; +      <BR><INPUT TYPE="submit" VALUE="Submit"> +      <BR><TEXTAREA NAME="expansion" ROWS=100>$expansion</TEXTAREA> +    </FORM> +    </CENTER> +  </BODY> +</HTML> +END + +%> diff --git a/httemplate/edit/cust_main_county.cgi b/httemplate/edit/cust_main_county.cgi new file mode 100755 index 000000000..a11711770 --- /dev/null +++ b/httemplate/edit/cust_main_county.cgi @@ -0,0 +1,57 @@ +<!-- mason kludge --> +<% + +print header("Edit tax rates", menubar( +  'Main Menu' => popurl(2), +)); + +print qq!<FONT SIZE="+1" COLOR="#ff0000">Error: !, $cgi->param('error'), +      "</FONT>" +  if $cgi->param('error'); + +print qq!<FORM ACTION="!, popurl(1), +    qq!process/cust_main_county.cgi" METHOD=POST>!, &table(), <<END; +      <TR> +        <TH><FONT SIZE=-1>Country</FONT></TH> +        <TH><FONT SIZE=-1>State</FONT></TH> +        <TH>County</TH> +        <TH><FONT SIZE=-1>Tax</FONT></TH> +      </TR> +END + +foreach my $cust_main_county ( sort {    $a->country cmp $b->country +                                      or $a->state   cmp $b->state +                                      or $a->county  cmp $b->county +                                    } qsearch('cust_main_county',{}) ) { +  my($hashref)=$cust_main_county->hashref; +  print <<END; +      <TR> +        <TD>$hashref->{country}</TD> +END + +  print "<TD>", $hashref->{state} +      ? $hashref->{state} +      : '(ALL)' +    , "</TD>"; + +  print "<TD>", $hashref->{county} +      ? $hashref->{county} +      : '(ALL)' +    , "</TD>"; + +  print qq!<TD><INPUT TYPE="text" NAME="tax!, $hashref->{taxnum}, +        qq!" VALUE="!, $hashref->{tax}, qq!" SIZE=6 MAXLENGTH=6>%</TD></TR>!; +END + +} + +print <<END; +    </TABLE> +    <INPUT TYPE="submit" VALUE="Apply changes"> +    </FORM> +    </CENTER> +  </BODY> +</HTML> +END + +%> diff --git a/httemplate/edit/cust_pay.cgi b/httemplate/edit/cust_pay.cgi new file mode 100755 index 000000000..f6ae7b299 --- /dev/null +++ b/httemplate/edit/cust_pay.cgi @@ -0,0 +1,129 @@ +<!-- mason kludge --> +<% + +my $conf = new FS::Conf; + +my($link, $linknum, $paid, $payby, $payinfo, $quickpay);  +if ( $cgi->param('error') ) { +  $link = $cgi->param('link'); +  $linknum = $cgi->param('linknum'); +  $paid = $cgi->param('paid'); +  $payby = $cgi->param('payby'); +  $payinfo = $cgi->param('payinfo'); +  $quickpay = $cgi->param('quickpay'); +} elsif ($cgi->keywords) { +  my($query) = $cgi->keywords; +  $query =~ /^(\d+)$/; +  $link = 'invnum'; +  $linknum = $1; +  $paid = ''; +  $payby = 'BILL'; +  $payinfo = ""; +  $quickpay = ''; +} elsif ( $cgi->param('custnum')  =~ /^(\d+)$/ ) { +  $link = 'custnum'; +  $linknum = $1; +  $paid = ''; +  $payby = 'BILL'; +  $payinfo = ''; +  $quickpay = $cgi->param('quickpay'); +} else { +  die "illegal query ". $cgi->keywords; +} +my $_date = time; + +my $paybatch = "webui-$_date-$$-". rand() * 2**32; + +my $p1 = popurl(1); +print header("Post payment", ''); + +print qq!<FONT SIZE="+1" COLOR="#ff0000">Error: !, $cgi->param('error'), +      "</FONT><BR><BR>" +  if $cgi->param('error'); + +print <<END, ntable("#cccccc",2); +    <FORM ACTION="${p1}process/cust_pay.cgi" METHOD=POST> +    <INPUT TYPE="hidden" NAME="link" VALUE="$link"> +    <INPUT TYPE="hidden" NAME="linknum" VALUE="$linknum"> +    <INPUT TYPE="hidden" NAME="quickpay" VALUE="$quickpay"> +END + +my $custnum; +if ( $link eq 'invnum' ) { + +  my $cust_bill = qsearchs('cust_bill', { 'invnum' => $linknum } ) +    or die "unknown invnum $linknum"; +  print "Invoice #<B>$linknum</B>". ntable("#cccccc",2). +        '<TR><TD ALIGN="right">Date</TD><TD BGCOLOR="#ffffff">'. +        time2str("%D", $cust_bill->_date). '</TD></TR>'. +        '<TR><TD ALIGN="right" VALIGN="top">Items</TD><TD BGCOLOR="#ffffff">'; +  foreach ( $cust_bill->cust_bill_pkg ) { #false laziness with FS::cust_bill +    if ( $_->pkgnum ) { + +      my($cust_pkg)=qsearchs('cust_pkg', { 'pkgnum', $_->pkgnum } ); +      my($part_pkg)=qsearchs('part_pkg',{'pkgpart'=>$cust_pkg->pkgpart}); +      my($pkg)=$part_pkg->pkg; + +      if ( $_->setup != 0 ) { +        print "$pkg Setup<BR>"; # $money_char. sprintf("%10.2f",$_->setup); +        print join('<BR>', +          map { "  ". $_->[0]. ": ". $_->[1] } $cust_pkg->labels +        ). '<BR>'; +      } + +      if ( $_->recur != 0 ) { +        print +          "$pkg (" . time2str("%x",$_->sdate) . " - " . +                                time2str("%x",$_->edate) . ")<BR>"; +          #$money_char. sprintf("%10.2f",$_->recur) +        print join('<BR>', +          map { '--->'. $_->[0]. ": ". $_->[1] } $cust_pkg->labels +        ). '<BR>'; +      } + +    } else { #pkgnum Tax +      print "Tax<BR>" # $money_char. sprintf("%10.2f",$_->setup) +        if $_->setup != 0; +    } + +  } +  print '</TD></TR></TABLE><BR><BR>'; + +  $custnum = $cust_bill->custnum; + +} elsif ( $link eq 'custnum' ) { +  $custnum = $linknum; +} + +print small_custview($custnum, $conf->config('countrydefault')); + +print qq!<INPUT TYPE="hidden" NAME="_date" VALUE="$_date">!; +print qq!<INPUT TYPE="hidden" NAME="payby" VALUE="$payby">!; + +print '<BR><BR>Payment'. ntable("#cccccc", 2). +      '<TR><TD ALIGN="right">Date</TD><TD BGCOLOR="#ffffff">'. +      time2str("%D",$_date).  '</TD></TR>'; + +print qq!<TR><TD ALIGN="right">Amount</TD><TD BGCOLOR="#ffffff">\$<INPUT TYPE="text" NAME="paid" VALUE="$paid" SIZE=8 MAXLENGTH=8></TD></TR>!; + +print qq!<TR><TD ALIGN="right">Payby</TD><TD BGCOLOR="#ffffff">$payby</TD></TR>!; + +#payinfo (check # now as payby="BILL" hardcoded.. what to do later?) +print qq!<TR><TD ALIGN="right">Check #</TD><TD BGCOLOR="#ffffff"><INPUT TYPE="text" NAME="payinfo" VALUE="$payinfo"></TD></TR>!; + +print qq!<TR><TD ALIGN="right">Auto-apply<BR>to invoices</TD><TD><SELECT NAME="apply"><OPTION VALUE="yes" SELECTED>yes<OPTION>no</SELECT></TD>!; + +print "</TABLE>"; + +#paybatch +print qq!<INPUT TYPE="hidden" NAME="paybatch" VALUE="$paybatch">!; + +print <<END; +<BR> +<INPUT TYPE="submit" VALUE="Post payment"> +    </FORM> +  </BODY> +</HTML> +END + +%> diff --git a/httemplate/edit/cust_pkg.cgi b/httemplate/edit/cust_pkg.cgi new file mode 100755 index 000000000..d546f7409 --- /dev/null +++ b/httemplate/edit/cust_pkg.cgi @@ -0,0 +1,108 @@ +<!-- mason kludge --> +<% + +my %pkg = (); +my %comment = (); +foreach (qsearch('part_pkg', { 'disabled' => '' })) { +  $pkg{ $_ -> getfield('pkgpart') } = $_->getfield('pkg'); +  $comment{ $_ -> getfield('pkgpart') } = $_->getfield('comment'); +} + +my($custnum, %remove_pkg); +if ( $cgi->param('error') ) { +  $custnum = $cgi->param('custnum'); +  %remove_pkg = map { $_ => 1 } $cgi->param('remove_pkg'); +} else { +  my($query) = $cgi->keywords; +  $query =~ /^(\d+)$/; +  $custnum = $1; +  %remove_pkg = (); +} + +my $p1 = popurl(1); +print header("Add/Edit Packages", ''); + +print qq!<FONT SIZE="+1" COLOR="#ff0000">Error: !, $cgi->param('error'), +      "</FONT>" +  if $cgi->param('error'); + +print qq!<FORM ACTION="${p1}process/cust_pkg.cgi" METHOD=POST>!; + +print qq!<INPUT TYPE="hidden" NAME="custnum" VALUE="$custnum">!; + +#current packages +my @cust_pkg = qsearch('cust_pkg',{ 'custnum' => $custnum, 'cancel' => '' } ); + +if (@cust_pkg) { +  print <<END; +Current packages - select to remove (services are moved to a new package below) +<BR><BR> +END + +  my $count = 0 ; +  print qq!<TABLE>! ; +  foreach (@cust_pkg) { +    print '<TR>' if $count == 0; +    my($pkgnum,$pkgpart)=( $_->getfield('pkgnum'), $_->getfield('pkgpart') ); +    print qq!<TD><INPUT TYPE="checkbox" NAME="remove_pkg" VALUE="$pkgnum"!; +    print " CHECKED" if $remove_pkg{$pkgnum}; +    print qq!>$pkgnum: $pkg{$pkgpart} - $comment{$pkgpart}</TD>\n!; +    $count ++ ; +    if ($count == 2) +    { +      $count = 0 ; +      print qq!</TR>\n! ; +    } +  } +  print qq!</TABLE><BR><BR>!; +} + +print <<END; +Order new packages<BR><BR> +END + +my $cust_main = qsearchs('cust_main',{'custnum'=>$custnum}); +my $agent = qsearchs('agent',{'agentnum'=> $cust_main->agentnum }); + +my $count = 0; +my $pkgparts = 0; +print qq!<TABLE>!; +foreach my $type_pkgs ( qsearch('type_pkgs',{'typenum'=> $agent->typenum }) ) { +  $pkgparts++; +  my($pkgpart)=$type_pkgs->pkgpart; +  next unless exists $pkg{$pkgpart}; #skip disabled ones +  print qq!<TR>! if ( $count == 0 ); +  my $value = $cgi->param("pkg$pkgpart") || 0; +  print <<END; +  <TD> +  <INPUT TYPE="text" NAME="pkg$pkgpart" VALUE="$value" SIZE="2" MAXLENGTH="2"> +  $pkgpart: $pkg{$pkgpart} - $comment{$pkgpart}</TD>\n +END +  $count ++ ; +  if ( $count == 2 ) { +    print qq!</TR>\n! ; +    $count = 0; +  } +} +print qq!</TABLE>!; + +unless ( $pkgparts ) { +  my $p2 = popurl(2); +  my $typenum = $agent->typenum; +  my $agent_type = qsearchs( 'agent_type', { 'typenum' => $typenum } ); +  my $atype = $agent_type->atype; +  print <<END; +(No <a href="${p2}browse/part_pkg.cgi">package definitions</a>, or agent type +<a href="${p2}edit/agent_type.cgi?$typenum">$atype</a> not allowed to purchase +any packages.) +END +} + +#submit +print <<END; +<P><INPUT TYPE="submit" VALUE="Order"> +    </FORM> +  </BODY> +</HTML> +END +%> diff --git a/httemplate/edit/part_bill_event.cgi b/httemplate/edit/part_bill_event.cgi new file mode 100755 index 000000000..018fc94a4 --- /dev/null +++ b/httemplate/edit/part_bill_event.cgi @@ -0,0 +1,184 @@ +<!-- mason kludge --> +<% + +if ( $cgi->param('eventpart') && $cgi->param('eventpart') =~ /^(\d+)$/ ) { +  $cgi->param('eventpart', $1); +} else { +  $cgi->param('eventpart', ''); +} + +my ($query) = $cgi->keywords; +my $action = ''; +my $part_bill_event = ''; +if ( $cgi->param('error') ) { +  $part_bill_event = new FS::part_bill_event ( { +    map { $_, scalar($cgi->param($_)) } fields('part_bill_event') +  } ); +} +if ( $query && $query =~ /^(\d+)$/ ) { +  $part_bill_event ||= qsearchs('part_bill_event',{'eventpart'=>$1}); +} else { +  $part_bill_event ||= new FS::part_bill_event {}; +} +$action ||= $part_bill_event->pkgpart ? 'Edit' : 'Add'; +my $hashref = $part_bill_event->hashref; + +print header("$action Invoice Event Definition", menubar( +  'Main Menu' => popurl(2), +  'View all invoice events' => popurl(2). 'browse/part_bill_event.cgi', +)); + +print qq!<FONT SIZE="+1" COLOR="#ff0000">Error: !, $cgi->param('error'), +      "</FONT>" +  if $cgi->param('error'); + +print '<FORM ACTION="', popurl(1), 'process/part_bill_event.cgi" METHOD=POST>'. +      '<INPUT TYPE="hidden" NAME="eventpart" VALUE="'. +      $part_bill_event->eventpart  .'">'; +print "Invoice Event #", $hashref->{eventpart} ? $hashref->{eventpart} : "(NEW)"; + +print ntable("#cccccc",2), <<END; +<TR><TD ALIGN="right">Payby</TD><TD><SELECT NAME="payby"> +END + +for (qw(CARD BILL COMP)) { +  print qq!<OPTION VALUE="$_"!; +  if ($part_bill_event->payby eq $_) { +    print " SELECTED>$_</OPTION>"; +  } else { +    print ">$_</OPTION>"; +  } +} + +my $days = $hashref->{seconds}/86400; + +print <<END; +</SELECT></TD></TR> +<TR><TD ALIGN="right">Event</TD><TD><INPUT TYPE="text" NAME="event" VALUE="$hashref->{event}"></TD></TR> +<TR><TD ALIGN="right">After</TD><TD><INPUT TYPE="text" NAME="days" VALUE="$days"> days</TD></TR> +END + +print '<TR><TD ALIGN="right">Disabled</TD><TD>'; +print '<INPUT TYPE="checkbox" NAME="disabled" VALUE="Y"'; +print ' CHECKED' if $hashref->{disabled} eq "Y"; +print '>'; +print '</TD></TR>'; + +print '<TR><TD ALIGN="right">Action</TD><TD>'; + +#print ntable(); + +#this is pretty kludgy right here. +tie my %events, 'Tie::IxHash', + +  'fee' => { +    'name'   => 'Late fee', +    'code'   => '$cust_main->charge( %%%charge%%%, \'%%%reason%%%\' );', +    'html'   =>  +      'Amount <INPUT TYPE="text" SIZE="7" NAME="charge" VALUE="%%%charge%%%">'. +      '<BR>Reason <INPUT TYPE="text" NAME="reason" VALUE="%%%reason%%%">', +    'weight' => 10, +  }, +  'suspend' => { +    'name'   => 'Suspend', +    'code'   => '$cust_main->suspend();', +    'weight' => 10, +  }, +  'cancel' => { +    'name'   => 'Cancel', +    'code'   => '$cust_main->cancel();', +    'weight' => 10, +  }, + +  'addpost' => { +    'name' => 'Add postal invoicing', +    'code' => '$cust_main->invoicing_list_addpost(); "";', +    'weight'  => 20, +  }, + +  'comp' => { +    'name' => 'Pay invoice with a complimentary "payment"', +    'code' => '$cust_bill->comp();', +    'weight' => 30, +  }, + +  'realtime-card' => { +    'name' => 'Run card with a <a href="http://search.cpan.org/search?mode=module&query=Business%3A%3AOnlinePayment">Business::OnlinePayment</a> realtime gateway', +    'code' => '$cust_bill->realtime_card();', +    'weight' => 30, +  }, + +  'realtime-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();', +    'weight' => 30, +  }, + +  'batch-card' => { +    'name' => 'Add card to the pending credit card batch', +    'code' => '$cust_bill->batch_card();', +    'weight' => 40, +  }, + +  'send' => { +    'name' => 'Send invoice (email/print)', +    'code' => '$cust_bill->send();', +    'weight' => 50, +  }, + +  'bill' => { +    'name' => 'Generate invoices (normally only used with a <i>Late Fee</i> event)', +    'code' => '$cust_main->bill();', +    'weight'  => 60, +  }, + +  'apply' => { +    'name' => 'Apply unapplied payments and credits', +    'code' => '$cust_main->apply_payments; $cust_main->apply_credits; "";', +    'weight'  => 70, +  }, + +  'collect' => { +    'name' => 'Collect on invoices (normally only used with a <i>Late Fee</i> and <i>Generate Invoice</i> events)', +    'code' => '$cust_main->collect();', +    'weight'  => 80, +  }, + +; + +foreach my $event ( keys %events ) { +  my %plandata = map { /^(\w+) (.*)$/; ($1, $2); } +                   split(/\n/, $part_bill_event->plandata); +  my $html = $events{$event}{html}; +  while ( $html =~ /%%%(\w+)%%%/ ) { +    my $field = $1; +    $html =~ s/%%%$field%%%/$plandata{$field}/; +  } + +  print ntable( "#cccccc", 2). +        qq!<TR><TD><INPUT TYPE="radio" NAME="plan_weight_eventcode" !; +  print "CHECKED " if $event eq $part_bill_event->plan; +  print qq!VALUE="!.  $event. ":". $events{$event}{weight}. ":". +        encode_entities($events{$event}{code}). +        qq!">$events{$event}{name}</TD>!; +  print '<TD>'. $html. '</TD>' if $html; +  print qq!</TR>!; +  print '</TABLE>'; +} + +#print '</TABLE>'; + +print <<END; +</TD></TR> +</TABLE> +END + +print qq!<INPUT TYPE="submit" VALUE="!, +      $hashref->{eventpart} ? "Apply changes" : "Add invoice event", +      qq!">!; +%> + +    </FORM> +  </BODY> +</HTML> + diff --git a/httemplate/edit/part_pkg.cgi b/httemplate/edit/part_pkg.cgi new file mode 100755 index 000000000..f4ebd6770 --- /dev/null +++ b/httemplate/edit/part_pkg.cgi @@ -0,0 +1,464 @@ +<!-- mason kludge --> +<% + +if ( $cgi->param('clone') && $cgi->param('clone') =~ /^(\d+)$/ ) { +  $cgi->param('clone', $1); +} else { +  $cgi->param('clone', ''); +} +if ( $cgi->param('pkgnum') && $cgi->param('pkgnum') =~ /^(\d+)$/ ) { +  $cgi->param('pkgnum', $1); +} else { +  $cgi->param('pkgnum', ''); +} + +my ($query) = $cgi->keywords; +my $action = ''; +my $part_pkg = ''; +if ( $cgi->param('error') ) { +  $part_pkg = new FS::part_pkg ( { +    map { $_, scalar($cgi->param($_)) } fields('part_pkg') +  } ); +} +if ( $cgi->param('clone') ) { +  $action='Custom Pricing'; +  my $old_part_pkg = +    qsearchs('part_pkg', { 'pkgpart' => $cgi->param('clone') } ); +  $part_pkg ||= $old_part_pkg->clone; +  $part_pkg->disabled('Y'); +} elsif ( $query && $query =~ /^(\d+)$/ ) { +  $part_pkg ||= qsearchs('part_pkg',{'pkgpart'=>$1}); +} else { +  $part_pkg ||= new FS::part_pkg {}; +  $part_pkg->plan('flat'); +} +unless ( $part_pkg->plan ) { #backwards-compat +  $part_pkg->plan('flat'); +  $part_pkg->plandata("setup_fee=". $part_pkg->setup. "\n". +                      "recur_fee=". $part_pkg->recur. "\n"); +} +$action ||= $part_pkg->pkgpart ? 'Edit' : 'Add'; +my $hashref = $part_pkg->hashref; + +%> + +<SCRIPT> +function visualize(what) { +  if (document.getElementById) { +    document.getElementById('d<%= $part_pkg->plan %>').style.visibility = "visible"; +  } else { +    document.l<%= $part_pkg->plan %>.visibility = "visible"; +  } +} +</SCRIPT> + +<%  + +print header("$action Package Definition", menubar( +  'Main Menu' => popurl(2), +  'View all packages' => popurl(2). 'browse/part_pkg.cgi', +), ' onLoad="visualize()"'); + +print qq!<FONT SIZE="+1" COLOR="#ff0000">Error: !, $cgi->param('error'), +      "</FONT>" +  if $cgi->param('error'); + +#print '<FORM ACTION="', popurl(1), 'process/part_pkg.cgi" METHOD=POST>'; +print '<FORM NAME="dummy">'; + +#if ( $cgi->param('clone') ) { +#  print qq!<INPUT TYPE="hidden" NAME="clone" VALUE="!, $cgi->param('clone'), qq!">!; +#} +#if ( $cgi->param('pkgnum') ) { +#  print qq!<INPUT TYPE="hidden" NAME="pkgnum" VALUE="!, $cgi->param('pkgnum'), qq!">!; +#} +# +#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></TD></TR> +<TR><TD ALIGN="right">Setup fee tax exempt</TD><TD> +END + +print '<INPUT TYPE="checkbox" NAME="setuptax" VALUE="Y"'; +print ' CHECKED' if $hashref->{setuptax} eq "Y"; +print '>'; + +print <<END; +</TD></TR> +<TR><TD ALIGN="right">Recurring fee tax exempt</TD><TD> +END + +print '<INPUT TYPE="checkbox" NAME="recurtax" VALUE="Y"'; +print ' CHECKED' if $hashref->{recurtax} eq "Y"; +print '>'; + +print '</TD></TR>'; + +print '<TR><TD ALIGN="right">Disable new orders</TD><TD>'; +print '<INPUT TYPE="checkbox" NAME="disabled" VALUE="Y"'; +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 + +unless ( $cgi->param('clone') ) { +  #print <<END, $thead; +  print <<END, itable(), '<TR><TD VALIGN="top">', $thead; +<BR><BR>Enter the quantity of each service this package includes.<BR><BR> +END +} + +my @fixups = (); +my $count = 0; +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, +    'svcpart'  => $svcpart, +  } ) || new FS::pkg_svc ( { +    'pkgpart'  => $cgi->param('clone') || $part_pkg->pkgpart, +    'svcpart'  => $svcpart, +    'quantity' => 0, +  }); +  #? #next unless $pkg_svc; + +  push @fixups, "pkg_svc$svcpart"; + +  unless ( defined ($cgi->param('clone')) && $cgi->param('clone') ) { +    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!">!, $part_svc->getfield('svc'), "</A></TD></TR>"; +#    print "</TABLE></TD><TD>$thead" if ++$count == int(scalar(@part_svc) / 2); +    $count+=1; +    foreach ( 1 .. $columns-1 ) { +      print "</TABLE></TD><TD VALIGN=\"top\">$thead" +        if $count == int( $_ * scalar(@part_svc) / $columns ); +    } +  } else { +    print qq!<INPUT TYPE="hidden" NAME="pkg_svc$svcpart" VALUE="!, +          $cgi->param("pkg_svc$svcpart") || $pkg_svc->quantity || 0, qq!">\n!; +  } +} + +unless ( $cgi->param('clone') ) { +  print "</TR></TABLE></TD></TR></TABLE>"; +  #print "</TR></TABLE>"; +} + +# prolly should be in database +use Tie::IxHash; +tie my %plans, 'Tie::IxHash', +  'flat' => { +    'name' => 'Flat rate', +    'fields' => { +      'setup_fee' => { 'name' => 'Setup fee for this package', +                       'default' => 0, +                     }, +      'recur_fee' => { 'name' => 'Recurring fee for this package', +                       'default' => 0, +                      }, +    }, +    'fieldorder' => [ 'setup_fee', 'recur_fee' ], +    'setup' => 'what.setup_fee.value', +    'recur' => 'what.recur_fee.value', +  }, + +  'prorate' => { +    'name' => 'First month pro-rated, then flat-rate', +    'fields' =>  { +      'setup_fee' => { 'name' => 'Setup fee for this package', +                       'default' => 0, +                     }, +      'recur_fee' => { 'name' => 'Recurring fee for this package', +                       'default' => 0, +                      }, +    }, +    'fieldorder' => [ 'setup_fee', 'recur_fee' ], +    'setup' => 'what.setup_fee.value', +    'recur' => '\'my $mnow = $sdate; my ($sec,$min,$hour,$mday,$mon,$year) = (localtime($sdate) )[0,1,2,3,4,5]; my $mstart = timelocal(0,0,0,1,$mon,$year); my $mend = timelocal(0,0,0,1, $mon == 11 ? 0 : $mon+1, $year+($mon==11)); $sdate = $mstart; ( $part_pkg->freq - 1 ) * \' + what.recur_fee.value + \' / $part_pkg->freq + \' + what.recur_fee.value + \' / $part_pkg->freq * ($mend-$mnow) / ($mend-$mstart) ; \'', +  }, + +  'flat_comission_cust' => { +    'name' => 'Flat rate with recurring comission per active customer', +    'fields' => { +      'setup_fee' => { 'name' => 'Setup fee for this package', +                       'default' => 0, +                     }, +      'recur_fee' => { 'name' => 'Recurring fee for this package', +                       'default' => 0, +                     }, +      'comission_amount' => { 'name' => 'Comission amount per month (per active customer)', +                              'default' => 0, +                            }, +      'comission_depth'  => { 'name' => 'Number of layers', +                              'default' => 1, +                            }, +    }, +    'fieldorder' => [ 'setup_fee', 'recur_fee', 'comission_depth', 'comission_amount' ], +    'setup' => 'what.setup_fee.value', +    'recur' => '\'my $error = $cust_pkg->cust_main->credit( \' + what.comission_amount.value + \' * scalar($cust_pkg->cust_main->referral_cust_main_ncancelled(\' + what.comission_depth.value+ \')), "commission" ); die $error if $error; \' + what.recur_fee.value + \';\'', +  }, + +  'flat_comission' => { +    'name' => 'Flat rate with recurring comission per (any) active package', +    'fields' => { +      'setup_fee' => { 'name' => 'Setup fee for this package', +                       'default' => 0, +                     }, +      'recur_fee' => { 'name' => 'Recurring fee for this package', +                       'default' => 0, +                     }, +      'comission_amount' => { 'name' => 'Comission amount per month (per active package)', +                              'default' => 0, +                            }, +      'comission_depth'  => { 'name' => 'Number of layers', +                              'default' => 1, +                            }, +    }, +    'fieldorder' => [ 'setup_fee', 'recur_fee', 'comission_depth', 'comission_amount' ], +    'setup' => 'what.setup_fee.value', +    'recur' => '\'my $error = $cust_pkg->cust_main->credit( \' + what.comission_amount.value + \' * scalar($cust_pkg->cust_main->referral_cust_pkg(\' + what.comission_depth.value+ \')), "commission" ); die $error if $error; \' + what.recur_fee.value + \';\'', +  }, + +  'flat_comission_pkg' => { +    'name' => 'Flat rate with recurring comission per (selected) active package', +    'fields' => { +      'setup_fee' => { 'name' => 'Setup fee for this package', +                       'default' => 0, +                     }, +      'recur_fee' => { 'name' => 'Recurring fee for this package', +                       'default' => 0, +                     }, +      'comission_amount' => { 'name' => 'Comission amount per month (per uncancelled package)', +                              'default' => 0, +                            }, +      'comission_depth'  => { 'name' => 'Number of layers', +                              'default' => 1, +                            }, +      'comission_pkgpart' => { 'name' => 'Applicable packages<BR><FONT SIZE="-1">(hold <b>ctrl</b> to select multiple packages)</FONT>', +                               'type' => 'select_multiple', +                               'select_table' => 'part_pkg', +                               'select_hash'  => { 'disabled' => '' } , +                               'select_key'   => 'pkgpart', +                               'select_label' => 'pkg', +                             }, +    }, +    'fieldorder' => [ 'setup_fee', 'recur_fee', 'comission_depth', 'comission_amount', 'comission_pkgpart' ], +    'setup' => 'what.setup_fee.value', +    'recur' => '""; var pkgparts = ""; for ( var c=0; c < document.flat_comission_pkg.comission_pkgpart.options.length; c++ ) { if (document.flat_comission_pkg.comission_pkgpart.options[c].selected) { pkgparts = pkgparts + document.flat_comission_pkg.comission_pkgpart.options[c].value + \', \'; } } what.recur.value = \'my $error = $cust_pkg->cust_main->credit( \' + what.comission_amount.value + \' * scalar( grep { my $pkgpart = $_->pkgpart; grep { $_ == $pkgpart } ( \' + pkgparts + \'  ) } $cust_pkg->cust_main->referral_cust_pkg(\' + what.comission_depth.value+ \')), "commission" ); die $error if $error; \' + what.recur_fee.value + \';\'', +  }, + + + +  'sesmon_hour' => { +    'name' => 'Base charge plus charge per-hour from the session monitor', +    '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, +                               }, +    }, +    'fieldorder' => [ 'setup_fee', 'recur_flat', 'recur_included_hours', 'recur_hourly_charge' ], +    'setup' => 'what.setup_fee.value', +    'recur' => '\'my $hours = $cust_pkg->seconds_since($cust_bkg->bill || 0) / 3600 - \' + what.recur_included_hours.value + \'; $hours = 0 if $hours < 0; \' + what.recur_flat.value + \' + \' + what.recur_hourly_charge.value + \' * $hours;\'', +  }, + +  'sesmon_minute' => { +    'name' => 'Base charge plus charge per-minute from the session monitor', +    'fields' => { +      'setup_fee' => { 'name' => 'Setup fee for this package', +                       'default' => 0, +                     }, +      'recur_flat' => { 'name' => 'Base monthly charge for this package', +                        'default' => 0, +                      }, +      'recur_included_min' => { 'name' => 'Minutes included', +                                'default' => 0, +                                }, +      'recur_minly_charge' => { 'name' => 'Additional charge per minute', +                                'default' => 0, +                              }, +    }, +    'fieldorder' => [ 'setup_fee', 'recur_flat', 'recur_included_min', 'recur_minly_charge' ], +    'setup' => 'what.setup_fee.value', +    'recur' => '\'my $min = $cust_pkg->seconds_since($cust_bkg->bill || 0) / 60 - \' + what.recur_included_min.value + \'; $min = 0 if $min < 0; \' + what.recur_flat.value + \' + \' + what.recur_minly_charge.value + \' * $min;\'', + +  }, + +; + +%> + +<SCRIPT> +var layer = null; + +function changed(what) { +  layer = what.options[what.selectedIndex].value; +<% foreach my $layer ( keys %plans ) { %> +  if (layer == "<%= $layer %>" ) { +    <% foreach my $not ( grep { $_ ne $layer } keys %plans ) { %> +      if (document.getElementById) { +        document.getElementById('d<%= $not %>').style.visibility = "hidden"; +      } else { +        document.l<%= $not %>.visibility = "hidden"; +      } +    <% } %> +    if (document.getElementById) { +      document.getElementById('d<%= $layer %>').style.visibility = "visible"; +    } else { +      document.l<%= $layer %>.visibility = "visible"; +    } +  } +<% } %> +} + +</SCRIPT> +<BR> +Price plan <SELECT NAME="plan" SIZE=1 onChange="changed(this);"> +<OPTION> +<% foreach my $layer (keys %plans ) { %> +<OPTION VALUE="<%= $layer %>"<%= ' SELECTED'x($layer eq $part_pkg->plan) %>><%= $plans{$layer}->{'name'} %> +<% } %> +</SELECT></FORM> + +<SCRIPT> +function fchanged(what) { +  fixup(what.form); +} + +function fixup(what) { +<% foreach my $f ( qw( pkg comment freq ), @fixups ) { %> +  what.<%= $f %>.value = document.dummy.<%= $f %>.value; +<% } %> +<% foreach my $f ( qw( setuptax recurtax disabled ) ) { %> +  if (document.dummy.<%= $f %>.checked) +    what.<%= $f %>.value = 'Y'; +  else +    what.<%= $f %>.value = ''; +<% } %> +  what.plan.value = document.dummy.plan.options[document.dummy.plan.selectedIndex].value; +<% foreach my $p ( keys %plans ) { %> +  if ( what.plan.value == "<%= $p %>" ) { +    what.setup.value = <%= $plans{$p}->{setup} %>; +    what.recur.value = <%= $plans{$p}->{recur} %>; +  } +<% } %> +} +</SCRIPT> + +<% my %plandata = map { /^(\w+)=(.*)$/; ( $1 => $2 ); } +                    split("\n", $part_pkg->plandata ); +   #foreach my $layer ( 'konq_kludge', keys %plans ) {  +   foreach my $layer ( 'konq_kludge', keys %plans ) { +     my $visibility = "hidden"; +%> +<SCRIPT> +if (document.getElementById) { +    document.write("<DIV ID=\"d<%= $layer %>\" STYLE=\"visibility: <%= $visibility %>; position: absolute\">"); +} else { +<% $visibility="show" if $visibility eq "visible"; %> +    document.write("<LAYER ID=\"l<%= $layer %>\" VISIBILITY=\"<%= $visibility %>\">"); +} +</SCRIPT> + +<FORM NAME="<%= $layer %>" ACTION="process/part_pkg.cgi" METHOD=POST onSubmit="fixup(this)"> +<INPUT TYPE="hidden" NAME="plan" VALUE="<%= $part_pkg->plan %>"> +<INPUT TYPE="hidden" NAME="pkg" VALUE="<%= $hashref->{pkg} %>"> +<INPUT TYPE="hidden" NAME="comment" VALUE="$<%= $hashref->{comment} %>"> +<INPUT TYPE="hidden" NAME="freq" VALUE="<%= $hashref->{freq} %>"> +<INPUT TYPE="hidden" NAME="setuptax" VALUE="<%= $hashref->{setuptax} %>"> +<INPUT TYPE="hidden" NAME="recurtax" VALUE="<%= $hashref->{recurtax} %>"> +<INPUT TYPE="hidden" NAME="disabled" VALUE="<%= $hashref->{disabled} %>"> +<% foreach my $f ( @fixups ) { %> +<INPUT TYPE="hidden" NAME="<%= $f %>" VALUE=""> +<% } %> + +<% +if ( $cgi->param('clone') ) { +  print qq!<INPUT TYPE="hidden" NAME="clone" VALUE="!, $cgi->param('clone'), qq!">!; +} +if ( $cgi->param('pkgnum') ) { +  print qq!<INPUT TYPE="hidden" NAME="pkgnum" VALUE="!, $cgi->param('pkgnum'), qq!">!; +} +%> + +<INPUT TYPE="hidden" NAME="pkgpart" VALUE="<%= $hashref->{pkgpart} %>"> +<%= ntable("#cccccc",2) %> + +<% my $href = $plans{$layer}->{'fields'}; +   foreach my $field ( exists($plans{$layer}->{'fieldorder'}) +                         ? @{$plans{$layer}->{'fieldorder'}} +                         : keys %{ $href } +                     ) { +%> +  <TR><TD ALIGN="right"><%= $href->{$field}{'name'} %></TD> +  <TD> +  <% if ( ! exists($href->{$field}{'type'}) ) { %> +       <INPUT TYPE="text" NAME="<%= $field %>" VALUE="<%= exists($plandata{$field}) ? $plandata{$field} : $href->{$field}{'default'} %>" onChange="fchanged(this)"> +  <% } elsif ( $href->{$field}{'type'} eq 'select_multiple' ) { %> +       <SELECT MULTIPLE NAME="<%= $field %>" onChange="fchanged(this)"> +       <% foreach my $record ( qsearch( $href->{$field}{'select_table'}, $href->{$field}{'select_hash'} ) ) { +          my $value = $record->getfield($href->{$field}{'select_key'}); %> +         <OPTION VALUE="<%= $value %>"<%= $plandata{$field} =~ /(^|, *)$value *(,|$)/ ? ' SELECTED' : '' %>><%= $record->getfield($href->{$field}{'select_label'}) %> +       <% } %> +       </SELECT> +  <% } %> +  </TD></TR> +<% } %> + +</TABLE> +<INPUT TYPE="hidden" NAME="plandata" VALUE="<%= join(',', keys %{ $href } ) %>"> +<BR><BR> + +<% +print qq!<INPUT TYPE="submit" VALUE="!, +      $hashref->{pkgpart} ? "Apply changes" : "Add package", +      qq!" onClick="fchanged(this)">!; +%> + +<BR><BR>don't edit this unless you know what you're doing <INPUT TYPE="button" VALUE="refresh expressions" onClick="fchanged(this)"><%= ntable("#cccccc",2) %><TR><TD> +<FONT SIZE="1">Setup expression<BR><INPUT TYPE="text" NAME="setup" SIZE="160" VALUE="<%= $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)"></FONT> +</TR></TD> +</TABLE> + +</FORM> + +<SCRIPT> +if (document.getElementById) { +  document.write("</DIV>"); +} else { +  document.write("</LAYER>"); +} +</SCRIPT> + +<% } %> + +<TAG onLoad=" +    if (document.getElementById) { +      document.getElementById('d<%= $part_pkg->plan %>').style.visibility = 'visible'; +    } else { +      document.l<%= $part_pkg->plan %>.visibility = 'visible'; +    } +"> +  </BODY> +</HTML> diff --git a/httemplate/edit/part_referral.cgi b/httemplate/edit/part_referral.cgi new file mode 100755 index 000000000..73be9e337 --- /dev/null +++ b/httemplate/edit/part_referral.cgi @@ -0,0 +1,50 @@ +<!-- mason kludge --> +<% + +my $part_referral; +if ( $cgi->param('error') ) { +  $part_referral = new FS::part_referral ( { +    map { $_, scalar($cgi->param($_)) } fields('part_referral') +  } ); +} elsif ( $cgi->keywords ) { +  my($query) = $cgi->keywords; +  $query =~ /^(\d+)$/; +  $part_referral = qsearchs( 'part_referral', { 'refnum' => $1 } ); +} else { #adding +  $part_referral = new FS::part_referral {}; +} +my $action = $part_referral->refnum ? 'Edit' : 'Add'; +my $hashref = $part_referral->hashref; + +my $p1 = popurl(1); +print header("$action Referral", menubar( +  'Main Menu' => popurl(2), +  'View all referrals' => popurl(2). "browse/part_referral.cgi", +)); + +print qq!<FONT SIZE="+1" COLOR="#ff0000">Error: !, $cgi->param('error'), +      "</FONT>" +  if $cgi->param('error'); + +print qq!<FORM ACTION="${p1}process/part_referral.cgi" METHOD=POST>!; + +print qq!<INPUT TYPE="hidden" NAME="refnum" VALUE="$hashref->{refnum}">!, +      "Referral #", $hashref->{refnum} ? $hashref->{refnum} : "(NEW)"; + +print <<END; +<PRE> +Referral   <INPUT TYPE="text" NAME="referral" SIZE=32 VALUE="$hashref->{referral}"> +</PRE> +END + +print qq!<BR><INPUT TYPE="submit" VALUE="!, +      $hashref->{refnum} ? "Apply changes" : "Add referral", +      qq!">!; + +print <<END; +    </FORM> +  </BODY> +</HTML> +END + +%> diff --git a/httemplate/edit/part_svc.cgi b/httemplate/edit/part_svc.cgi new file mode 100755 index 000000000..c3a4942ea --- /dev/null +++ b/httemplate/edit/part_svc.cgi @@ -0,0 +1,264 @@ +<!-- mason kludge --> +<%  +   my $part_svc; +   if ( $cgi->param('error') ) { #error +     $part_svc = new FS::part_svc ( { +       map { $_, scalar($cgi->param($_)) } fields('part_svc') +     } ); +   } elsif ( $cgi->keywords ) { #edit +     my($query) = $cgi->keywords; +     $query =~ /^(\d+)$/ or die "malformed query: $query"; +     $part_svc=qsearchs('part_svc', { 'svcpart'=>$1 } ) +       or die "unknown svcpart: $1"; +   } else { #adding +     $part_svc = new FS::part_svc {}; +   } +   my $action = $part_svc->svcpart ? 'Edit' : 'Add'; +   my $hashref = $part_svc->hashref; +   my $p_svcdb = $part_svc->svcdb || 'svc_acct'; + +%> + +<SCRIPT> +function visualize(what) { +  if (document.getElementById) { +    document.getElementById('d<%= $p_svcdb %>').style.visibility = "visible"; +  } else { +    document.l<%= $p_svcdb %>.visibility = "visible"; +  } +} +</SCRIPT> + +<%= header("$action Service Definition", +           menubar( 'Main Menu'         => $p, +                    'View all service definitions' => "${p}browse/part_svc.cgi" +                  ), +           " onLoad=\"visualize()\"" +           ) +%> + +<% if ( $cgi->param('error') ) { %> +<FONT SIZE="+1" COLOR="#ff0000">Error: <%= $cgi->param('error') %></FONT> +<% } %> + +<FORM NAME="dummy"> + +      Service Part #<%= $part_svc->svcpart ? $part_svc->svcpart : "(NEW)" %> +<BR><BR> +Service  <INPUT TYPE="text" NAME="svc" VALUE="<%= $hashref->{svc} %>"><BR> +Disable new orders <INPUT TYPE="checkbox" NAME="disabled" VALUE="Y"<%= $hashref->{disabled} eq 'Y' ? ' CHECKED' : '' %>><BR> +<BR> +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_charge - One-time charges (Partially unimplemented) +       <LI>svc_wo - Work orders (Partially unimplemented) +--> +</UL> +For the selected table, you can give fields default or fixed (unchangable) +values.  For example, a SLIP/PPP account may have a default (or perhaps fixed) +<B>slipip</B> of <B>0.0.0.0</B>, while a POP mailbox will probably have a fixed +blank <B>slipip</B> as well as a fixed shell something like <B>/bin/true</B> or +<B>/usr/bin/passwd</B>. +<BR><BR> +<SCRIPT> +var svcdb = null; +function changed(what) { +  svcdb = what.options[what.selectedIndex].value; +<% foreach my $svcdb ( qw( svc_acct svc_domain svc_acct_sm svc_forward svc_www ) ) { %> +  if (svcdb == "<%= $svcdb %>" ) { +    <% foreach my $not ( grep { $_ ne $svcdb } ( +                           qw(svc_acct svc_domain svc_acct_sm svc_forward svc_www) ) ) { %> +      if (document.getElementById) { +        document.getElementById('d<%= $not %>').style.visibility = "hidden"; +      } else { +        document.l<%= $not %>.visibility = "hidden"; +      } +    <% } %> +    if (document.getElementById) { +      document.getElementById('d<%= $svcdb %>').style.visibility = "visible"; +    } else { +      document.l<%= $svcdb %>.visibility = "visible"; +    } +  } +<% } %> +} +</SCRIPT> +<% my @dbs = $hashref->{svcdb} +             ? ( $hashref->{svcdb} ) +             : qw( svc_acct svc_domain svc_acct_sm svc_forward svc_www ); %> +Table<SELECT NAME="svcdb" SIZE=1 onChange="changed(this)"> +<% foreach my $svcdb (@dbs) { %> +<OPTION VALUE="<%= $svcdb %>" <%= ' SELECTED'x($svcdb eq $hashref->{svcdb}) %>><%= $svcdb %> +<% } %> +</SELECT></FORM> + +<% +#these might belong somewhere else for other user interfaces  +#pry need to eventually create stuff that's shared amount UIs +my %defs = ( +  'svc_acct' => { +    'dir'       => 'Home directory', +    'uid'       => 'UID (set to fixed and blank for dial-only)', +    'slipip'    => 'IP address (Set to fixed and blank to disable dialin, or, set a value to be exported to RADIUS Framed-IP-Address.  Use the special value <code>0e0</code> [zero e zero] to enable export to RADIUS without a Framed-IP-Address.)', +    'popnum'    => qq!<A HREF="$p/browse/svc_acct_pop.cgi/">POP number</A>!, +    'username'  => 'Username', +    'quota'     => '', +    '_password' => 'Password', +    'gid'       => 'GID (when blank, defaults to UID)', +    'shell'     => 'Shell (all service definitions should have a default or fixed shell that is present in the <b>shells</b> configuration file)', +    'finger'    => 'GECOS', +    'domsvc'    => { +                     desc =>'svcnum from svc_domain', +                     type =>'select', +                     select_table => 'svc_domain', +                     select_key   => 'svcnum', +                     select_label => 'domain', +                   }, +  }, +  '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_www' => { +    #'recnum' => '', +    #'usersvc' => '', +  }, +); + +#  svc_acct svc_domain svc_acct_sm svc_charge svc_wo +foreach my $svcdb ( qw( +  konq_kludge svc_acct svc_domain svc_acct_sm svc_forward svc_www +) ) { + +#  my(@fields) = $svcdb eq 'konq_kludge' +#                  ? () +#                  : grep { $_ ne 'svcnum' } fields($svcdb); +  #yucky kludge +  my(@fields) = defined( $FS::Record::dbdef->table($svcdb) ) +                  ? grep { $_ ne 'svcnum' } fields($svcdb) +                  : (); +  #my($rowspan)=scalar(@rows); + +  #my($ptmp)="<TD ROWSPAN=$rowspan>$svcdb</TD>"; +#  $visibility = $svcdb eq $part_svc->svcdb ? "SHOW" : "HIDDEN"; +#  $visibility = $svcdb eq $p_svcdb ? "visible" : "hidden"; +  my $visibility = "hidden"; +%> +<SCRIPT> +if (document.getElementById) { +    document.write("<DIV ID=\"d<%= $svcdb %>\" STYLE=\"visibility: <%= $visibility %>; position: absolute\">"); +} else { +<% $visibility="show" if $visibility eq "visible"; %> +    document.write("<LAYER ID=\"l<%= $svcdb %>\" VISIBILITY=\"<%= $visibility %>\">"); +} + +function fixup(what) { +  what.svc.value = document.dummy.svc.value; +  what.svcdb.value = document.dummy.svcdb.options[document.dummy.svcdb.selectedIndex].value; +  if (document.dummy.disabled.checked) +    what.disabled.value = 'Y'; +  else +    what.disabled.value = ''; +} +</SCRIPT> +<FORM NAME="<%= $svcdb %>" ACTION="process/part_svc.cgi" METHOD=POST onSubmit="fixup(this)"> +<INPUT TYPE="hidden" NAME="svcpart" VALUE="<%= $hashref->{svcpart} %>"> +<INPUT TYPE="hidden" NAME="svc" VALUE="<%= $hashref->{svc} %>"> +<INPUT TYPE="hidden" NAME="disabled" VALUE="<%= $hashref->{disabled} %>"> +<INPUT TYPE="hidden" NAME="svcdb" VALUE="<%= $svcdb %>"> +<% +  #print "$svcdb<BR>" unless $svcdb eq 'konq_kludge'; +  print table(). "<TH>Field</TH><TH COLSPAN=2>Modifier</TH>" unless $svcdb eq 'konq_kludge'; + +  foreach my $field (@fields) { +    my $part_svc_column = $part_svc->part_svc_column($field); +    my $value = $cgi->param('error') +                  ? $cgi->param("${svcdb}__${field}") +                  : $part_svc_column->columnvalue; +    my $flag = $cgi->param('error') +                 ? $cgi->param("${svcdb}__${field}_flag") +                 : $part_svc_column->columnflag; +    #print "<TR>$ptmp<TD>$field"; +    print "<TR><TD>$field"; +    my $def = $defs{$svcdb}{$field}; +    my $desc = ref($def) ? $def->{desc} : $def; +     +    print "- <FONT SIZE=-1>$desc</FONT>" if $desc; +    print "</TD>"; +    print qq!<TD><INPUT TYPE="radio" NAME="${svcdb}__${field}_flag" VALUE=""!. +      ' CHECKED'x($flag eq ''). ">Off</TD>"; +    print qq!<TD><INPUT TYPE="radio" NAME="${svcdb}__${field}_flag" VALUE="D"!. +      ' CHECKED'x($flag eq 'D'). ">Default "; +    print qq!<INPUT TYPE="radio" NAME="${svcdb}__${field}_flag" VALUE="F"!. +      ' CHECKED'x($flag eq 'F'). ">Fixed "; +    print '<BR>'; +    if ( ref($def) ) { +      if ( $def->{type} eq 'select' ) { +        print qq!<SELECT NAME="${svcdb}__${field}">!; +        print '<OPTION>' unless $value; +        foreach my $record ( qsearch( $def->{select_table}, {} ) ) { +          my $rvalue = $record->getfield($def->{select_key}); +          print qq!<OPTION VALUE="$rvalue"!. +                ( $rvalue==$value ? ' SELECTED>' : '>' ). +                $record->getfield($def->{select_label}); +        } +      } else { +        print '<font color="#ff0000">unknown type'. $def->{type}; +      } +    } else { +      print qq!<INPUT TYPE="text" NAME="${svcdb}__${field}" VALUE="$value">!; +    } +    print "</TD></TR>\n"; +    #$ptmp=''; +  } +  print "</TABLE>" unless $svcdb eq 'konq_kludge'; + +print qq!\n<BR><INPUT TYPE="submit" VALUE="!, +      $hashref->{svcpart} ? "Apply changes" : "Add service", +      qq!">! unless $svcdb eq 'konq_kludge'; + +  print "</FORM>"; +  print <<END; +    <SCRIPT> +    if (document.getElementById) { +      document.write("</DIV>"); +    } else { +      document.write("</LAYER>"); +    } +    </SCRIPT> +END +} +#print "</TABLE>"; +%> + +<TAG onLoad=" +    if (document.getElementById) { +      document.getElementById('d<%= $p_svcdb %>').style.visibility = 'visible'; +    } else { +      document.l<%= $p_svcdb %>.visibility = 'visible'; +    } +"> + +  </BODY> +</HTML> + diff --git a/httemplate/edit/process/REAL_cust_pkg.cgi b/httemplate/edit/process/REAL_cust_pkg.cgi new file mode 100755 index 000000000..6bed85c19 --- /dev/null +++ b/httemplate/edit/process/REAL_cust_pkg.cgi @@ -0,0 +1,19 @@ +<% + +my $pkgnum = $cgi->param('pkgnum') or die; +my $old = qsearchs('cust_pkg',{'pkgnum'=>$pkgnum}); +my %hash = $old->hash; +$hash{'setup'} = $cgi->param('setup') ? str2time($cgi->param('setup')) : ''; +$hash{'bill'} = $cgi->param('bill') ? str2time($cgi->param('bill')) : ''; +my $new = new FS::cust_pkg \%hash; + +my $error = $new->replace($old); + +if ( $error ) { +  $cgi->param('error', $error); +  print $cgi->redirect(popurl(2). "REAL_cust_pkg.cgi?". $cgi->query_string ); +} else {  +  print $cgi->redirect(popurl(3). "view/cust_pkg.cgi?". $pkgnum); +} + +%> diff --git a/httemplate/edit/process/agent.cgi b/httemplate/edit/process/agent.cgi new file mode 100755 index 000000000..182eeab41 --- /dev/null +++ b/httemplate/edit/process/agent.cgi @@ -0,0 +1,28 @@ +<% + +my $agentnum = $cgi->param('agentnum'); + +my $old = qsearchs('agent',{'agentnum'=>$agentnum}) if $agentnum; + +my $new = new FS::agent ( { +  map { +    $_, scalar($cgi->param($_)); +  } fields('agent') +} ); + +my $error; +if ( $agentnum ) { +  $error=$new->replace($old); +} else { +  $error=$new->insert; +  $agentnum=$new->getfield('agentnum'); +} + +if ( $error ) { +  $cgi->param('error', $error); +  print $cgi->redirect(popurl(2). "agent.cgi?". $cgi->query_string ); +} else {  +  print $cgi->redirect(popurl(3). "browse/agent.cgi"); +} + +%> diff --git a/httemplate/edit/process/agent_type.cgi b/httemplate/edit/process/agent_type.cgi new file mode 100755 index 000000000..67aacfdd5 --- /dev/null +++ b/httemplate/edit/process/agent_type.cgi @@ -0,0 +1,54 @@ +<% + +my $typenum = $cgi->param('typenum'); +my $old = qsearchs('agent_type',{'typenum'=>$typenum}) if $typenum; + +my $new = new FS::agent_type ( { +  map { +    $_, scalar($cgi->param($_)); +  } fields('agent_type') +} ); + +my $error; +if ( $typenum ) { +  $error=$new->replace($old); +} else { +  $error=$new->insert; +  $typenum=$new->getfield('typenum'); +} + +if ( $error ) { +  $cgi->param('error', $error); +  print $cgi->redirect(popurl(2). "agent_type.cgi?". $cgi->query_string ); +} else { + +  foreach my $part_pkg (qsearch('part_pkg',{})) { +    my($pkgpart)=$part_pkg->getfield('pkgpart'); + +    my($type_pkgs)=qsearchs('type_pkgs',{ +        'typenum' => $typenum, +        'pkgpart' => $pkgpart, +    }); +    if ( $type_pkgs && ! $cgi->param("pkgpart$pkgpart") ) { +      my($d_type_pkgs)=$type_pkgs; #need to save $type_pkgs for below. +      $error=$d_type_pkgs->delete; +      die $error if $error; + +    } elsif ( $cgi->param("pkgpart$pkgpart") +              && ! $type_pkgs +    ) { +      #ok to clobber it now (but bad form nonetheless?) +      $type_pkgs=new FS::type_pkgs ({ +        'typenum' => $typenum, +        'pkgpart' => $pkgpart, +      }); +      $error= $type_pkgs->insert; +      die $error if $error; +    } + +  } + +  print $cgi->redirect(popurl(3). "browse/agent_type.cgi"); +} + +%> diff --git a/httemplate/edit/process/cust_bill_pay.cgi b/httemplate/edit/process/cust_bill_pay.cgi new file mode 100755 index 000000000..0c33506a8 --- /dev/null +++ b/httemplate/edit/process/cust_bill_pay.cgi @@ -0,0 +1,31 @@ +<% + +$cgi->param('paynum') =~ /^(\d*)$/ or die "Illegal paynum!"; +my $paynum = $1; + +my $cust_pay = qsearchs('cust_pay', { 'paynum' => $paynum } ) +  or die "No such paynum"; + +my $cust_main = qsearchs('cust_main', { 'custnum' => $cust_pay->custnum } ) +  or die "Bogus credit:  not attached to customer"; + +my $custnum = $cust_main->custnum; + +my $new = new FS::cust_bill_pay ( { +  map { +    $_, scalar($cgi->param($_)); +  #} qw(custnum _date amount invnum) +  } fields('cust_bill_pay') +} ); + +my $error = $new->insert; + +if ( $error ) { +  $cgi->param('error', $error); +  print $cgi->redirect(popurl(2). "cust_bill_pay.cgi?". $cgi->query_string ); +} else { +  print $cgi->redirect(popurl(3). "view/cust_main.cgi?$custnum"); +} + + +%> diff --git a/httemplate/edit/process/cust_credit.cgi b/httemplate/edit/process/cust_credit.cgi new file mode 100755 index 000000000..ac92631f8 --- /dev/null +++ b/httemplate/edit/process/cust_credit.cgi @@ -0,0 +1,30 @@ +<% + +$cgi->param('custnum') =~ /^(\d*)$/ or die "Illegal custnum!"; +my $custnum = $1; + +$cgi->param('otaker',getotaker); + +my $new = new FS::cust_credit ( { +  map { +    $_, scalar($cgi->param($_)); +  #} qw(custnum _date amount otaker reason) +  } fields('cust_credit') +} ); + +my $error = $new->insert; + +if ( $error ) { +  $cgi->param('error', $error); +  print $cgi->redirect(popurl(2). "cust_credit.cgi?". $cgi->query_string ); +} else { +  if ( $cgi->param('apply') eq 'yes' ) { +    my $cust_main = qsearchs('cust_main', { 'custnum' => $custnum }) +      or die "unknown custnum $custnum"; +    $cust_main->apply_credits; +  } +  print $cgi->redirect(popurl(3). "view/cust_main.cgi?$custnum"); +} + + +%> diff --git a/httemplate/edit/process/cust_credit_bill.cgi b/httemplate/edit/process/cust_credit_bill.cgi new file mode 100755 index 000000000..4879b0eab --- /dev/null +++ b/httemplate/edit/process/cust_credit_bill.cgi @@ -0,0 +1,43 @@ +<% + +$cgi->param('crednum') =~ /^(\d*)$/ or die "Illegal crednum!"; +my $crednum = $1; + +my $cust_credit = qsearchs('cust_credit', { 'crednum' => $crednum } ) +  or die "No such crednum"; + +my $cust_main = qsearchs('cust_main', { 'custnum' => $cust_credit->custnum } ) +  or die "Bogus credit:  not attached to customer"; + +my $custnum = $cust_main->custnum; + +my $new; +if ($cgi->param('invnum') =~ /^Refund$/) { +  $new = new FS::cust_refund ( { +    'reason'  => $cust_credit->reason, +    'refund'  => $cgi->param('amount'), +    'payby'   => 'BILL', +    '_date'   => $cgi->param('_date'), +    'payinfo' => 'Cash', +    'crednum' => $crednum, +  } ); +} else { +  $new = new FS::cust_credit_bill ( { +    map { +      $_, scalar($cgi->param($_)); +    #} qw(custnum _date amount invnum) +    } fields('cust_credit_bill') +  } ); +} + +my $error = $new->insert; + +if ( $error ) { +  $cgi->param('error', $error); +  print $cgi->redirect(popurl(2). "cust_credit_bill.cgi?". $cgi->query_string ); +} else { +  print $cgi->redirect(popurl(3). "view/cust_main.cgi?$custnum"); +} + + +%> diff --git a/httemplate/edit/process/cust_main.cgi b/httemplate/edit/process/cust_main.cgi new file mode 100755 index 000000000..c8038ecf6 --- /dev/null +++ b/httemplate/edit/process/cust_main.cgi @@ -0,0 +1,132 @@ +<% + +my $error = ''; + +#unmunge stuff + +$cgi->param('tax','') unless defined $cgi->param('tax'); + +$cgi->param('refnum', (split(/:/, ($cgi->param('refnum'))[0] ))[0] ); + +$cgi->param('state') =~ /^(\w*)( \(([\w ]+)\))? ?\/ ?(\w+)$/ +  or die "Oops, illegal \"state\" param: ". $cgi->param('state'); +$cgi->param('state', $1); +$cgi->param('county', $3 || ''); +$cgi->param('country', $4); + +$cgi->param('ship_state') =~ /^(\w*)( \(([\w ]+)\))? ?\/ ?(\w+)$/ +  or $cgi->param('ship_state') =~ /^(((())))$/ +  or die "Oops, illegal \"ship_state\" param: ". $cgi->param('ship_state'); +$cgi->param('ship_state', $1); +$cgi->param('ship_county', $3 || ''); +$cgi->param('ship_country', $4); + +my $payby = $cgi->param('payby'); +if ( $payby ) { +  $cgi->param('payinfo', $cgi->param( $payby. '_payinfo' ) ); +  $cgi->param('paydate', +  $cgi->param( $payby. '_month' ). '-'. $cgi->param( $payby. '_year' ) ); +  $cgi->param('payname', $cgi->param( $payby. '_payname' ) ); +} + +$cgi->param('otaker', &getotaker ); + +my @invoicing_list = split( /\s*\,\s*/, $cgi->param('invoicing_list') ); +push @invoicing_list, 'POST' if $cgi->param('invoicing_list_POST'); + +#create new record object + +my $new = new FS::cust_main ( { +  map { +    $_, scalar($cgi->param($_)) +#  } qw(custnum agentnum last first ss company address1 address2 city county +#       state zip daytime night fax payby payinfo paydate payname tax +#       otaker refnum) +  } fields('cust_main') +} ); + +if ( defined($cgi->param('same')) && $cgi->param('same') eq "Y" ) { +  $new->setfield("ship_$_", '') foreach qw( +    last first company address1 address2 city county state zip +    country daytime night fax +  ); +} + +#perhaps this stuff should go to cust_main.pm +my $cust_pkg = ''; +my $svc_acct = ''; +if ( $new->custnum eq '' ) { + +  if ( $cgi->param('pkgpart_svcpart') ) { +    my $x = $cgi->param('pkgpart_svcpart'); +    $x =~ /^(\d+)_(\d+)$/; +    my($pkgpart, $svcpart) = ($1, $2); +    #false laziness: copied from FS::cust_pkg::order (which should become a +    #FS::cust_main method) +    my(%part_pkg); +    # generate %part_pkg +    # $part_pkg{$pkgpart} is true iff $custnum may purchase $pkgpart +    my $agent = qsearchs('agent',{'agentnum'=> $new->agentnum }); +    	#my($type_pkgs); +    	#foreach $type_pkgs ( qsearch('type_pkgs',{'typenum'=> $agent->typenum }) ) { +    	#  my($pkgpart)=$type_pkgs->pkgpart; +    	#  $part_pkg{$pkgpart}++; +    	#} +    # $pkgpart_href->{PKGPART} is true iff $custnum may purchase $pkgpart +    my $pkgpart_href = $agent->pkgpart_hashref; +    #eslaf + +    # this should wind up in FS::cust_pkg! +    $error ||= "Agent ". $new->agentnum. " (type ". $agent->typenum. ") can't". +               "purchase pkgpart ". $pkgpart +      #unless $part_pkg{ $pkgpart }; +      unless $pkgpart_href->{ $pkgpart }; + +    $cust_pkg = new FS::cust_pkg ( { +      #later         'custnum' => $custnum, +      'pkgpart' => $pkgpart, +    } ); +    $error ||= $cust_pkg->check; + +    #$cust_svc = new FS::cust_svc ( { 'svcpart' => $svcpart } ); + +    #$error ||= $cust_svc->check; + +    $svc_acct = new FS::svc_acct ( { +                                     'svcpart'   => $svcpart, +                                     'username'  => $cgi->param('username'), +                                     '_password' => $cgi->param('_password'), +                                     'popnum'    => $cgi->param('popnum'), +                                   } ); + +    my $y = $svc_acct->setdefault; # arguably should be in new method +    $error ||= $y unless ref($y); +    #and just in case you were silly +    $svc_acct->svcpart($svcpart); +    $svc_acct->username($cgi->param('username')); +    $svc_acct->_password($cgi->param('_password')); +    $svc_acct->popnum($cgi->param('popnum')); + +    $error ||= $svc_acct->check; + +  } elsif ( $cgi->param('username') ) { #good thing to catch +    $error = "Can't assign username without a package!"; +  } + +  use Tie::RefHash; +  tie my %hash, 'Tie::RefHash'; +  %hash = ( $cust_pkg => [ $svc_acct ] ) if $cust_pkg; +  $error ||= $new->insert( \%hash, \@invoicing_list ); +} else { #create old record object +  my $old = qsearchs( 'cust_main', { 'custnum' => $new->custnum } );  +  $error ||= "Old record not found!" unless $old; +  $error ||= $new->replace($old, \@invoicing_list); +} + +if ( $error ) { +  $cgi->param('error', $error); +  print $cgi->redirect(popurl(2). "cust_main.cgi?". $cgi->query_string ); +} else {  +  print $cgi->redirect(popurl(3). "view/cust_main.cgi?". $new->custnum); +}  +%> diff --git a/httemplate/edit/process/cust_main_county-collapse.cgi b/httemplate/edit/process/cust_main_county-collapse.cgi new file mode 100755 index 000000000..8e67140a8 --- /dev/null +++ b/httemplate/edit/process/cust_main_county-collapse.cgi @@ -0,0 +1,35 @@ +<% + +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!"); + +#really should do this in a .pm & start transaction + +foreach my $delete ( qsearch('cust_main_county', { +                    'country' => $cust_main_county->country, +                    'state' => $cust_main_county->state   +                 } ) ) { +#  unless ( qsearch('cust_main',{ +#    'state'  => $cust_main_county->getfield('state'), +#    'county' => $cust_main_county->getfield('county'), +#    'country' =>  $cust_main_county->getfield('country'), +#  } ) ) { +    my $error = $delete->delete; +    die $error if $error; +#  } else { +    #should really fix the $cust_main record +#  } + +} + +$cust_main_county->taxnum(''); +$cust_main_county->county(''); +my $error = $cust_main_county->insert; +die $error if $error; + +print $cgi->redirect(popurl(3). "browse/cust_main_county.cgi"); + +%> diff --git a/httemplate/edit/process/cust_main_county-expand.cgi b/httemplate/edit/process/cust_main_county-expand.cgi new file mode 100755 index 000000000..64061deed --- /dev/null +++ b/httemplate/edit/process/cust_main_county-expand.cgi @@ -0,0 +1,56 @@ +<% + +$cgi->param('taxnum') =~ /^(\d+)$/ or die "Illegal taxnum!"; +my $taxnum = $1; +my $cust_main_county = qsearchs('cust_main_county',{'taxnum'=>$taxnum}) +  or die ("Unknown taxnum!"); + +my @expansion; +if ( $cgi->param('delim') eq 'n' ) { +  @expansion=split(/\n/,$cgi->param('expansion')); +} elsif ( $cgi->param('delim') eq 's' ) { +  @expansion=split(' ',$cgi->param('expansion')); +} else { +  die "Illegal delim!"; +} + +@expansion=map { +  unless ( /^\s*([\w\- ]+)\s*$/ ) { +    $cgi->param('error', "Illegal item in expansion"); +    print $cgi->redirect(popurl(2). "cust_main_county-expand.cgi?". $cgi->query_string ); +    myexit(); +  } +  $1; +} @expansion; + +foreach ( @expansion) { +  my(%hash)=$cust_main_county->hash; +  my($new)=new FS::cust_main_county \%hash; +  $new->setfield('taxnum',''); +  if ( ! $cust_main_county->state ) { +    $new->setfield('state',$_); +  } else { +    $new->setfield('county',$_); +  } +  #if (datasrc =~ m/Pg/) +  #{ +  #    $new->setfield('tax',0.0); +  #} +  my($error)=$new->insert; +  die $error if $error; +} + +unless ( qsearch( 'cust_main', { +                                 'state'  => $cust_main_county->state, +                                 'county' => $cust_main_county->county, +                                 'country' =>  $cust_main_county->country, +                               } ) +         || ! @expansion +) { +  my($error)=($cust_main_county->delete); +  die $error if $error; +} + +print $cgi->redirect(popurl(3). "browse/cust_main_county.cgi"); + +%> diff --git a/httemplate/edit/process/cust_main_county.cgi b/httemplate/edit/process/cust_main_county.cgi new file mode 100755 index 000000000..0800789b5 --- /dev/null +++ b/httemplate/edit/process/cust_main_county.cgi @@ -0,0 +1,22 @@ +<% + +foreach ( $cgi->param ) { +  /^tax(\d+)$/ or die "Illegal form $_!"; +  my($taxnum)=$1; +  my($old)=qsearchs('cust_main_county',{'taxnum'=>$taxnum}) +    or die "Couldn't find taxnum $taxnum!"; +  next unless $old->getfield('tax') ne $cgi->param("tax$taxnum"); +  my(%hash)=$old->hash; +  $hash{tax}=$cgi->param("tax$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 ); +    myexit(); +  } +} + +print $cgi->redirect(popurl(3). "browse/cust_main_county.cgi"); + +%> diff --git a/httemplate/edit/process/cust_pay.cgi b/httemplate/edit/process/cust_pay.cgi new file mode 100755 index 000000000..82442ae00 --- /dev/null +++ b/httemplate/edit/process/cust_pay.cgi @@ -0,0 +1,39 @@ +<% + +$cgi->param('linknum') =~ /^(\d+)$/ +  or die "Illegal linknum: ". $cgi->param('linknum'); +my $linknum = $1; + +$cgi->param('link') =~ /^(custnum|invnum)$/ +  or die "Illegal link: ". $cgi->param('link'); +my $link = $1; + +my $new = new FS::cust_pay ( { +  $link => $linknum, +  map { +    $_, scalar($cgi->param($_)); +  } qw(paid _date payby payinfo paybatch) +  #} fields('cust_pay') +} ); + +my $error = $new->insert; + +if ($error) { +  $cgi->param('error', $error); +  print $cgi->redirect(popurl(2). 'cust_pay.cgi?'. $cgi->query_string ); +} elsif ( $link eq 'invnum' ) { +  print $cgi->redirect(popurl(3). "view/cust_bill.cgi?$linknum"); +} elsif ( $link eq 'custnum' ) { +  if ( $cgi->param('apply') eq 'yes' ) { +    my $cust_main = qsearchs('cust_main', { 'custnum' => $linknum }) +      or die "unknown custnum $linknum"; +    $cust_main->apply_payments; +  } +  if ( $cgi->param('quickpay') eq 'yes' ) { +    print $cgi->redirect(popurl(3). "search/cust_main-quickpay.html"); +  } else { +    print $cgi->redirect(popurl(3). "view/cust_main.cgi?$linknum"); +  } +} + +%> diff --git a/httemplate/edit/process/cust_pkg.cgi b/httemplate/edit/process/cust_pkg.cgi new file mode 100755 index 000000000..f8c9f5151 --- /dev/null +++ b/httemplate/edit/process/cust_pkg.cgi @@ -0,0 +1,36 @@ +<% + +my $error = ''; + +#untaint custnum +$cgi->param('custnum') =~ /^(\d+)$/; +my $custnum = $1; + +my @remove_pkgnums = map { +  /^(\d+)$/ or die "Illegal remove_pkg value!"; +  $1; +} $cgi->param('remove_pkg'); + +my @pkgparts; +foreach my $pkgpart ( map /^pkg(\d+)$/ ? $1 : (), $cgi->param ) { +  if ( $cgi->param("pkg$pkgpart") =~ /^(\d+)$/ ) { +    my $num_pkgs = $1; +    while ( $num_pkgs-- ) { +      push @pkgparts,$pkgpart; +    } +  } else { +    $error = "Illegal quantity"; +    last; +  } +} + +$error ||= FS::cust_pkg::order($custnum,\@pkgparts,\@remove_pkgnums); + +if ($error) { +  $cgi->param('error', $error); +  print $cgi->redirect(popurl(2). "cust_pkg.cgi?". $cgi->query_string ); +} else { +  print $cgi->redirect(popurl(3). "view/cust_main.cgi?$custnum"); +} + +%> diff --git a/httemplate/edit/process/part_bill_event.cgi b/httemplate/edit/process/part_bill_event.cgi new file mode 100755 index 000000000..4049ade80 --- /dev/null +++ b/httemplate/edit/process/part_bill_event.cgi @@ -0,0 +1,53 @@ +<% + +my $eventpart = $cgi->param('eventpart'); + +my $old = qsearchs('part_bill_event',{'eventpart'=>$eventpart}) if $eventpart; + +#s/days/seconds/ +$cgi->param('seconds', $cgi->param('days') * 86400 ); + +my $error; +if ( ! $cgi->param('plan_weight_eventcode') ) { +  $error = "Must select an action"; +} else { + +  $cgi->param('plan_weight_eventcode') =~ /^([\w\-]+):(\d+):(.*)$/ +    or die "illegal plan_weight_eventcode:". +           $cgi->param('plan_weight_eventcode'); +  $cgi->param('plan', $1); +  $cgi->param('weight', $2); +  my $eventcode = $3; +  my $plandata = ''; +  while ( $eventcode =~ /%%%(\w+)%%%/ ) { +    my $field = $1; +    my $value = $cgi->param($field); +    $eventcode =~ s/%%%$field%%%/$value/; +    $plandata .= "$field $value\n"; +  } +  $cgi->param('eventcode', $eventcode); +  $cgi->param('plandata', $plandata); + +  my $new = new FS::part_bill_event ( { +    map { +      $_, scalar($cgi->param($_)); +    } fields('part_bill_event'), +  } ); + +  if ( $eventpart ) { +    $error = $new->replace($old); +  } else { +    $error = $new->insert; +    $eventpart = $new->getfield('eventpart'); +  } +}  + +if ( $error ) { +  $cgi->param('error', $error); +  print $cgi->redirect(popurl(2). "part_bill_event.cgi?". $cgi->query_string ); +} else { +  print $cgi->redirect(popurl(3)."browse/part_bill_event.cgi"); +} + +%> + diff --git a/httemplate/edit/process/part_pkg.cgi b/httemplate/edit/process/part_pkg.cgi new file mode 100755 index 000000000..d489426f9 --- /dev/null +++ b/httemplate/edit/process/part_pkg.cgi @@ -0,0 +1,109 @@ +<% + +my $dbh = dbh; + +my $pkgpart = $cgi->param('pkgpart'); + +my $old = qsearchs('part_pkg',{'pkgpart'=>$pkgpart}) if $pkgpart; + +#fixup plandata +my $plandata = $cgi->param('plandata'); +my @plandata = split(',', $plandata); +$cgi->param('plandata',  +  join('', map { "$_=". join(', ', $cgi->param($_)). "\n" } @plandata ) +); + +foreach (qw( setuptax recurtax disabled )) { +  $cgi->param($_, '') unless defined $cgi->param($_); +} + +my $new = new FS::part_pkg ( { +  map { +    $_, scalar($cgi->param($_)); +  } fields('part_pkg') +} ); + +#warn "setuptax: ". $new->setuptax; +#warn "recurtax: ". $new->recurtax; + +#most of the stuff below should move to part_pkg.pm + +foreach my $part_svc ( qsearch('part_svc', {} ) ) { +  my $quantity = $cgi->param('pkg_svc'. $part_svc->svcpart) || 0; +  unless ( $quantity =~ /^(\d+)$/ ) { +    $cgi->param('error', "Illegal quantity" ); +    print $cgi->redirect(popurl(2). "part_pkg.cgi?". $cgi->query_string ); +    myexit(); +  } +} + +local $SIG{HUP} = 'IGNORE'; +local $SIG{INT} = 'IGNORE'; +local $SIG{QUIT} = 'IGNORE'; +local $SIG{TERM} = 'IGNORE'; +local $SIG{TSTP} = 'IGNORE'; +local $SIG{PIPE} = 'IGNORE'; + +local $FS::UID::AutoCommit = 0; + +my $error; +if ( $pkgpart ) { +  $error = $new->replace($old); +} else { +  $error = $new->insert; +  $pkgpart=$new->pkgpart; +} +if ( $error ) { +  $dbh->rollback; +  $cgi->param('error', $error ); +  print $cgi->redirect(popurl(2). "part_pkg.cgi?". $cgi->query_string ); +  myexit(); +} + +foreach my $part_svc (qsearch('part_svc',{})) { +  my $quantity = $cgi->param('pkg_svc'. $part_svc->svcpart) || 0; +  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 $new_pkg_svc = new FS::pkg_svc( { +    'pkgpart'  => $pkgpart, +    'svcpart'  => $part_svc->svcpart, +    'quantity' => $quantity,  +  } ); +  if ( $old_pkg_svc ) { +    my $myerror = $new_pkg_svc->replace($old_pkg_svc); +    if ( $myerror ) { +      $dbh->rollback; +      die $myerror; +    } +  } else { +    my $myerror = $new_pkg_svc->insert; +    if ( $myerror ) { +      $dbh->rollback; +      die $myerror; +    } +  } +} + +unless ( $cgi->param('pkgnum') && $cgi->param('pkgnum') =~ /^(\d+)$/ ) { +  $dbh->commit or die $dbh->errstr; +  print $cgi->redirect(popurl(3). "browse/part_pkg.cgi"); +} else { +  my($old_cust_pkg) = qsearchs( 'cust_pkg', { 'pkgnum' => $1 } ); +  my %hash = $old_cust_pkg->hash; +  $hash{'pkgpart'} = $pkgpart; +  my($new_cust_pkg) = new FS::cust_pkg \%hash; +  my $myerror = $new_cust_pkg->replace($old_cust_pkg); +  if ( $myerror ) { +    $dbh->rollback; +    die "Error modifying cust_pkg record: $myerror\n"; +  } + +  $dbh->commit or die $dbh->errstr; +  print $cgi->redirect(popurl(3). "view/cust_main.cgi?". $new_cust_pkg->custnum); +} + +%> diff --git a/httemplate/edit/process/part_referral.cgi b/httemplate/edit/process/part_referral.cgi new file mode 100755 index 000000000..fd2c01506 --- /dev/null +++ b/httemplate/edit/process/part_referral.cgi @@ -0,0 +1,28 @@ +<% + +my $refnum = $cgi->param('refnum'); + +my $new = new FS::part_referral ( { +  map { +    $_, scalar($cgi->param($_)); +  } fields('part_referral') +} ); + +my $error; +if ( $refnum ) { +  my $old = qsearchs( 'part_referral', { 'refnum' =>$ refnum } ); +  die "(Old) Record not found!" unless $old; +  $error = $new->replace($old); +} else { +  $error = $new->insert; +} +$refnum=$new->refnum; + +if ( $error ) { +  $cgi->param('error', $error); +  print $cgi->redirect(popurl(2). "part_referral.cgi?". $cgi->query_string ); +} else { +  print $cgi->redirect(popurl(3). "browse/part_referral.cgi"); +} + +%> diff --git a/httemplate/edit/process/part_svc.cgi b/httemplate/edit/process/part_svc.cgi new file mode 100755 index 000000000..423db93b5 --- /dev/null +++ b/httemplate/edit/process/part_svc.cgi @@ -0,0 +1,35 @@ +<% + +my $svcpart = $cgi->param('svcpart'); + +my $old = qsearchs('part_svc',{'svcpart'=>$svcpart}) if $svcpart; + +my $new = new FS::part_svc ( { +  map { +    $_, scalar($cgi->param($_)); +#  } qw(svcpart svc svcdb) +  } ( fields('part_svc'), +      map { my $svcdb = $_; +            map { ( $svcdb.'__'.$_, $svcdb.'__'.$_.'_flag' )  } +              fields($svcdb) +          } grep defined( $FS::Record::dbdef->table($_) ), +                 qw( svc_acct svc_domain svc_acct_sm svc_forward svc_www ) +    ) +} ); + +my $error; +if ( $svcpart ) { +  $error = $new->replace($old, '1.3-COMPAT'); +} else { +  $error = $new->insert; +  $svcpart=$new->getfield('svcpart'); +} + +if ( $error ) { +  $cgi->param('error', $error); +  print $cgi->redirect(popurl(2). "part_svc.cgi?". $cgi->query_string ); +} else { +  print $cgi->redirect(popurl(3)."browse/part_svc.cgi"); +} + +%> diff --git a/httemplate/edit/process/quick-cust_pkg.cgi b/httemplate/edit/process/quick-cust_pkg.cgi new file mode 100644 index 000000000..c663dce32 --- /dev/null +++ b/httemplate/edit/process/quick-cust_pkg.cgi @@ -0,0 +1,24 @@ +<% + +#untaint custnum +$cgi->param('custnum') =~ /^(\d+)$/ +  or eidiot 'illegal custnum '. $cgi->param('custnum'); +my $custnum = $1; +$cgi->param('pkgpart') =~ /^(\d+)$/ +  or eidiot 'illegal pkgpart '. $cgi->param('pkgpart'); +my $pkgpart = $1; + +my @cust_pkg = (); +my $error = FS::cust_pkg::order($custnum, [ $pkgpart ], [], \@cust_pkg, ); + +if ($error) { +%> +<!-- mason kludge --> +<% +  eidiot($error); +} else { +  print $cgi->redirect(popurl(3). "view/cust_pkg.cgi?". $cust_pkg[0]->pkgnum ); +} + +%> + diff --git a/httemplate/edit/process/svc_acct.cgi b/httemplate/edit/process/svc_acct.cgi new file mode 100755 index 000000000..5e8a16f4a --- /dev/null +++ b/httemplate/edit/process/svc_acct.cgi @@ -0,0 +1,46 @@ +<% + +$cgi->param('svcnum') =~ /^(\d*)$/ or die "Illegal svcnum!"; +my $svcnum = $1; + +my $old; +if ( $svcnum ) { +  $old = qsearchs('svc_acct', { 'svcnum' => $svcnum } ) +    or die "fatal: can't find account (svcnum $svcnum)!"; +} else { +  $old = ''; +} + +#unmunge popnum +$cgi->param('popnum', (split(/:/, $cgi->param('popnum') ))[0] ); + +#unmunge passwd +if ( $cgi->param('_password') eq '*HIDDEN*' ) { +  die "fatal: no previous account to recall hidden password from!" unless $old; +  $cgi->param('_password',$old->getfield('_password')); +} + +my $new = new FS::svc_acct ( { +  map { +    $_, scalar($cgi->param($_)); +  #} qw(svcnum pkgnum svcpart username _password popnum uid gid finger dir +  #  shell quota slipip) +  } ( fields('svc_acct'), qw( pkgnum svcpart ) ) +} ); + +my $error; +if ( $svcnum ) { +  $error = $new->replace($old); +} else { +  $error = $new->insert; +  $svcnum = $new->svcnum; +} + +if ( $error ) { +  $cgi->param('error', $error); +  print $cgi->redirect(popurl(2). "svc_acct.cgi?". $cgi->query_string ); +} else { +  print $cgi->redirect(popurl(3). "view/svc_acct.cgi?" . $svcnum ); +} + +%> diff --git a/httemplate/edit/process/svc_acct_pop.cgi b/httemplate/edit/process/svc_acct_pop.cgi new file mode 100755 index 000000000..46ad74d62 --- /dev/null +++ b/httemplate/edit/process/svc_acct_pop.cgi @@ -0,0 +1,28 @@ +<% + +my $popnum = $cgi->param('popnum'); + +my $old = qsearchs('svc_acct_pop',{'popnum'=>$popnum}) if $popnum; + +my $new = new FS::svc_acct_pop ( { +  map { +    $_, scalar($cgi->param($_)); +  } fields('svc_acct_pop') +} ); + +my $error = ''; +if ( $popnum ) { +  $error = $new->replace($old); +} else { +  $error = $new->insert; +  $popnum=$new->getfield('popnum'); +} + +if ( $error ) { +  $cgi->param('error', $error); +  print $cgi->redirect(popurl(2). "svc_acct_pop.cgi?". $cgi->query_string ); +} else { +  print $cgi->redirect(popurl(3). "browse/svc_acct_pop.cgi"); +} + +%> diff --git a/httemplate/edit/process/svc_acct_sm.cgi b/httemplate/edit/process/svc_acct_sm.cgi new file mode 100755 index 000000000..41d03fb92 --- /dev/null +++ b/httemplate/edit/process/svc_acct_sm.cgi @@ -0,0 +1,34 @@ +<% + +$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_domain.cgi b/httemplate/edit/process/svc_domain.cgi new file mode 100755 index 000000000..19f8eb4f8 --- /dev/null +++ b/httemplate/edit/process/svc_domain.cgi @@ -0,0 +1,31 @@ +<% + +#remove this to actually test the domains! +$FS::svc_domain::whois_hack = 1; + +$cgi->param('svcnum') =~ /^(\d*)$/ or die "Illegal svcnum!"; +my $svcnum = $1; + +my $new = new FS::svc_domain ( { +  map { +    $_, scalar($cgi->param($_)); +  #} qw(svcnum pkgnum svcpart domain action purpose) +  } ( fields('svc_domain'), qw( pkgnum svcpart action purpose ) ) +} ); + +my $error = ''; +if ($cgi->param('svcnum')) { +  $error="Can't modify a domain!"; +} else { +  $error=$new->insert; +  $svcnum=$new->svcnum; +} + +if ($error) { +  $cgi->param('error', $error); +  print $cgi->redirect(popurl(2). "svc_domain.cgi?". $cgi->query_string ); +} else { +  print $cgi->redirect(popurl(3). "view/svc_domain.cgi?$svcnum"); +} + +%> diff --git a/httemplate/edit/process/svc_forward.cgi b/httemplate/edit/process/svc_forward.cgi new file mode 100755 index 000000000..bb066d8a6 --- /dev/null +++ b/httemplate/edit/process/svc_forward.cgi @@ -0,0 +1,29 @@ +<% + +$cgi->param('svcnum') =~ /^(\d*)$/ or die "Illegal svcnum!"; +my $svcnum =$1; + +my $old = qsearchs('svc_forward',{'svcnum'=>$svcnum}) if $svcnum; + +my $new = new FS::svc_forward ( { +  map { +    ($_, scalar($cgi->param($_))); +  } ( fields('svc_forward'), qw( pkgnum svcpart ) ) +} ); + +my $error = ''; +if ( $svcnum ) { +  $error = $new->replace($old); +} else { +  $error = $new->insert; +  $svcnum = $new->getfield('svcnum'); +}  + +if ($error) { +  $cgi->param('error', $error); +  print $cgi->redirect(popurl(2). "svc_forward.cgi?". $cgi->query_string ); +} else { +  print $cgi->redirect(popurl(3). "view/svc_forward.cgi?$svcnum"); +} + +%> diff --git a/httemplate/edit/process/svc_www.cgi b/httemplate/edit/process/svc_www.cgi new file mode 100644 index 000000000..38d5e1c79 --- /dev/null +++ b/httemplate/edit/process/svc_www.cgi @@ -0,0 +1,36 @@ +<% + +$cgi->param('svcnum') =~ /^(\d*)$/ or die "Illegal svcnum!"; +my $svcnum = $1; + +my $old; +if ( $svcnum ) { +  $old = qsearchs('svc_acct', { 'svcnum' => $svcnum } ) +    or die "fatal: can't find account (svcnum $svcnum)!"; +} else { +  $old = ''; +} + +my $new = new FS::svc_www ( { +  map { +    ($_, scalar($cgi->param($_))); +  #} qw(svcnum pkgnum svcpart recnum usersvc) +  } ( fields('svc_www'), qw( pkgnum svcpart ) ) +} ); + +my $error; +if ( $svcnum ) { +  $error = $new->replace($old); +} else { +  $error = $new->insert; +  $svcnum = $new->svcnum; +} + +if ( $error ) { +  $cgi->param('error', $error); +  print $cgi->redirect(popurl(2). "svc_www.cgi?". $cgi->query_string ); +} else { +  print $cgi->redirect(popurl(3). "view/svc_www.cgi?" . $svcnum ); +} + +%> diff --git a/httemplate/edit/svc_acct.cgi b/httemplate/edit/svc_acct.cgi new file mode 100755 index 000000000..d147a1683 --- /dev/null +++ b/httemplate/edit/svc_acct.cgi @@ -0,0 +1,251 @@ +<!-- mason kludge --> +<% + +my $conf = new FS::Conf; +my @shells = $conf->config('shells'); + +my($svcnum, $pkgnum, $svcpart, $part_svc, $svc_acct); +if ( $cgi->param('error') ) { +  $svc_acct = new FS::svc_acct ( { +    map { $_, scalar($cgi->param($_)) } fields('svc_acct') +  } ); +  $svcnum = $svc_acct->svcnum; +  $pkgnum = $cgi->param('pkgnum'); +  $svcpart = $cgi->param('svcpart'); +  $part_svc=qsearchs('part_svc',{'svcpart'=>$svcpart}); +  die "No part_svc entry!" unless $part_svc; +} else { +  my($query) = $cgi->keywords; +  if ( $query =~ /^(\d+)$/ ) { #editing +    $svcnum=$1; +    $svc_acct=qsearchs('svc_acct',{'svcnum'=>$svcnum}) +      or die "Unknown (svc_acct) svcnum!"; + +    my($cust_svc)=qsearchs('cust_svc',{'svcnum'=>$svcnum}) +      or die "Unknown (cust_svc) svcnum!"; + +    $pkgnum=$cust_svc->pkgnum; +    $svcpart=$cust_svc->svcpart; + +    $part_svc=qsearchs('part_svc',{'svcpart'=>$svcpart}); +    die "No part_svc entry!" unless $part_svc; + +  } else { #adding + +    $svc_acct = new FS::svc_acct({});  + +    foreach $_ (split(/-/,$query)) { +      $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; + +    $svcnum=''; + +    #set gecos +    my($cust_pkg)=qsearchs('cust_pkg',{'pkgnum'=>$pkgnum}); +    if ($cust_pkg) { +      my($cust_main)=qsearchs('cust_main',{'custnum'=> $cust_pkg->custnum } ); +      unless ( $part_svc->part_svc_column('uid')->columnflag eq 'F' ) { +        $svc_acct->setfield('finger', +          $cust_main->getfield('first') . " " . $cust_main->getfield('last') +        ); +      } +    } + +    #set fixed and default fields from part_svc +    foreach my $part_svc_column ( +      grep { $_->columnflag } $part_svc->all_part_svc_column +    ) { +      $svc_acct->setfield( $part_svc_column->columnname, +                           $part_svc_column->columnvalue, +                         ); +    } + +  } +} +my $action = $svcnum ? 'Edit' : 'Add'; + +my $svc = $part_svc->getfield('svc'); + +my $otaker = getotaker; + +my $username = $svc_acct->username; +my $password; +if ( $svc_acct->_password ) { +  if ( $conf->exists('showpasswords') || ! $svcnum ) { +    $password = $svc_acct->_password; +  } else { +    $password = "*HIDDEN*"; +  } +} else { +  $password = ''; +} + +my $ulen = $conf->config('usernamemax') +           || $svc_acct->dbdef_table->column('username')->length; +my $ulen2 = $ulen+2; + +my $pmax = $conf->config('passwordmax') || 8; +my $pmax2 = $pmax+2; + +my $p1 = popurl(1); +print header("$action $svc account"); + +print qq!<FONT SIZE="+1" COLOR="#ff0000">Error: !, $cgi->param('error'), +      "</FONT><BR><BR>" +  if $cgi->param('error'); + +print 'Service # '. ( $svcnum ? "<B>$svcnum</B>" : " (NEW)" ). '<BR>'. +      'Service: <B>'. $part_svc->svc. '</B><BR><BR>'. +      <<END; +    <FORM ACTION="${p1}process/svc_acct.cgi" METHOD=POST> +      <INPUT TYPE="hidden" NAME="svcnum" VALUE="$svcnum"> +      <INPUT TYPE="hidden" NAME="pkgnum" VALUE="$pkgnum"> +      <INPUT TYPE="hidden" NAME="svcpart" VALUE="$svcpart"> +END + +print &ntable("#cccccc",2), <<END; +<TR><TD ALIGN="right">Username</TD> +<TD><INPUT TYPE="text" NAME="username" VALUE="$username" SIZE=$ulen2 MAXLENGTH=$ulen></TD></TR> +<TR><TD ALIGN="right">Password</TD> +<TD><INPUT TYPE="text" NAME="_password" VALUE="$password" SIZE=$pmax2 MAXLENGTH=$pmax> +(blank to generate)</TD> +</TR> +END + +#domain +my $domsvc = $svc_acct->domsvc || 0; +if ( $part_svc->part_svc_column('domsvc')->columnflag eq 'F' ) { +  print qq!<INPUT TYPE="hidden" NAME="domsvc" VALUE="$domsvc">!; +} else {  +  my %svc_domain = (); + +  if ( $domsvc ) { +    my $svc_domain = qsearchs('svc_domain', { 'svcnum' => $domsvc, } ); +    if ( $svc_domain ) { +      $svc_domain{$svc_domain->svcnum} = $svc_domain; +    } else { +      warn "unknown svc_domain.svcnum for svc_acct.domsvc: $domsvc"; +    } +  } + +  if ( $part_svc->part_svc_column('domsvc')->columnflag eq 'D' ) { +    my $svc_domain = qsearchs('svc_domain', { +      'svcnum' => $part_svc->part_svc_column('domsvc')->columnvalue, +    } ); +    if ( $svc_domain ) { +      $svc_domain{$svc_domain->svcnum} = $svc_domain; +    } else { +      warn "unknown svc_domain.svcnum for part_svc_column domsvc: ". +           $part_svc->part_svc_column('domsvc')->columnvalue; +    } +  } + +  my $cust_pkg = qsearchs('cust_pkg', { 'pkgnum' => $pkgnum } ); +  if ($cust_pkg) { +    my @cust_svc = +      map { qsearch('cust_svc', { 'pkgnum' => $_->pkgnum } ) } +          qsearch('cust_pkg', { 'custnum' => $cust_pkg->custnum } ); +    foreach my $cust_svc ( @cust_svc ) { +      my $svc_domain = +        qsearchs('svc_domain', { 'svcnum' => $cust_svc->svcnum } ); +     $svc_domain{$svc_domain->svcnum} = $svc_domain if $svc_domain; +    } +  } else { +    %svc_domain = map { $_->svcnum => $_ } qsearch('svc_domain', {} ); +  } +  print qq!<TR><TD ALIGN="right">Domain</TD>!. +        qq!<TD><SELECT NAME="domsvc" SIZE=1>\n!; +  foreach my $svcnum ( +    sort { $svc_domain{$a}->domain cmp $svc_domain{$b}->domain } +      keys %svc_domain +  ) { +    my $svc_domain = $svc_domain{$svcnum}; +    print qq!<OPTION VALUE="!. $svc_domain->svcnum. qq!"!. +          ( $svc_domain->svcnum == $domsvc ? ' SELECTED' : '' ). +          '>'. $svc_domain->domain. "\n" ; +  } +  print "</SELECT></TD></TR>"; +} + +#pop +my $popnum = $svc_acct->popnum || 0; +if ( $part_svc->part_svc_column('popnum')->columnflag eq "F" ) { +  print qq!<INPUT TYPE="hidden" NAME="popnum" VALUE="$popnum">!; +} else {  +  print qq!<TR><TD ALIGN="right">Access number</TD>!. +        qq!<TD>!. FS::svc_acct_pop::popselector($popnum). '</TD></TR>'; +} + +my($uid,$gid,$finger,$dir)=( +  $svc_acct->uid, +  $svc_acct->gid, +  $svc_acct->finger, +  $svc_acct->dir, +); + +print <<END; +<INPUT TYPE="hidden" NAME="uid" VALUE="$uid"> +<INPUT TYPE="hidden" NAME="gid" VALUE="$gid"> +END + +if ( !$finger && $part_svc->part_svc_column('uid')->columnflag eq 'F' ) { +  print '<INPUT TYPE="hidden" NAME="finger" VALUE="">'; +} else { +  print '<TR><TD ALIGN="right">GECOS</TD>'. +        qq!<TD><INPUT TYPE="text" NAME="finger" VALUE="$finger"></TD></TR>!; +} +print qq!<INPUT TYPE="hidden" NAME="dir" VALUE="$dir">!; + +my $shell = $svc_acct->shell; +if ( $part_svc->part_svc_column('shell')->columnflag eq "F" +     || ( !$shell && $part_svc->part_svc_column('uid')->columnflag eq 'F' ) +   ) { +  print qq!<INPUT TYPE="hidden" NAME="shell" VALUE="$shell">!; +} else { +  print qq!<TR><TD ALIGN="right">Shell</TD><TD><SELECT NAME="shell" SIZE=1>!; +  my($etc_shell); +  foreach $etc_shell (@shells) { +    print "<OPTION", $etc_shell eq $shell ? ' SELECTED' : '', ">", +          $etc_shell, "\n"; +  } +  print "</SELECT></TD></TR>"; +} + +my($quota,$slipip)=( +  $svc_acct->quota, +  $svc_acct->slipip, +); + +print qq!<INPUT TYPE="hidden" NAME="quota" VALUE="$quota">!; + +if ( $part_svc->part_svc_column('slipip')->columnflag eq "F" ) { +  print qq!<INPUT TYPE="hidden" NAME="slipip" VALUE="$slipip">!; +} else { +  print qq!<TR><TD ALIGN="right">IP</TD><TD><INPUT TYPE="text" NAME="slipip" VALUE="$slipip"></TD></TR>!; +} + +foreach my $r ( grep { /^r(adius|[cr])_/ } fields('svc_acct') ) { +  $r =~ /^^r(adius|[cr])_(.+)$/ or next; #? +  my $a = $2; +  if ( $part_svc->part_svc_column($r)->columnflag eq 'F' ) { +    print qq!<INPUT TYPE="hidden" NAME="$r" VALUE="!. +          $svc_acct->getfield($r). '">'; +  } else { +    print qq!<TR><TD ALIGN="right">$FS::raddb::attrib{$a}</TD><TD><INPUT TYPE="text" NAME="$r" VALUE="!. +          $svc_acct->getfield($r). '"></TD></TR>'; +  } +} + +#submit +print qq!</TABLE><BR><INPUT TYPE="submit" VALUE="Submit">!;  + +print <<END; +    </FORM> +  </BODY> +</HTML> +END + +%> diff --git a/httemplate/edit/svc_acct_pop.cgi b/httemplate/edit/svc_acct_pop.cgi new file mode 100755 index 000000000..399502a70 --- /dev/null +++ b/httemplate/edit/svc_acct_pop.cgi @@ -0,0 +1,56 @@ +<!-- mason kludge --> +<% + +my $svc_acct_pop; +if ( $cgi->param('error') ) { +  $svc_acct_pop = new FS::svc_acct_pop ( { +    map { $_, scalar($cgi->param($_)) } fields('svc_acct_pop') +  } ); +} elsif ( $cgi->keywords ) { #editing +  my($query)=$cgi->keywords; +  $query =~ /^(\d+)$/; +  $svc_acct_pop=qsearchs('svc_acct_pop',{'popnum'=>$1}); +} else { #adding +  $svc_acct_pop = new FS::svc_acct_pop {}; +} +my $action = $svc_acct_pop->popnum ? 'Edit' : 'Add'; +my $hashref = $svc_acct_pop->hashref; + +my $p1 = popurl(1); +print header("$action Access Number", menubar( +  'Main Menu' => popurl(2), +  'View all Access Numbers' => popurl(2). "browse/svc_acct_pop.cgi", +)); + +print qq!<FONT SIZE="+1" COLOR="#ff0000">Error: !, $cgi->param('error'), +      "</FONT>" +  if $cgi->param('error'); + +print qq!<FORM ACTION="${p1}process/svc_acct_pop.cgi" METHOD=POST>!; + +#display + +print qq!<INPUT TYPE="hidden" NAME="popnum" VALUE="$hashref->{popnum}">!, +      "POP #", $hashref->{popnum} ? $hashref->{popnum} : "(NEW)"; + +print <<END; +<PRE> +City      <INPUT TYPE="text" NAME="city" SIZE=32 VALUE="$hashref->{city}"> +State     <INPUT TYPE="text" NAME="state" SIZE=16 MAXLENGTH=16 VALUE="$hashref->{state}"> +Area Code <INPUT TYPE="text" NAME="ac" SIZE=4 MAXLENGTH=3 VALUE="$hashref->{ac}"> +Exchange  <INPUT TYPE="text" NAME="exch" SIZE=4 MAXLENGTH=3 VALUE="$hashref->{exch}"> +Local     <INPUT TYPE="text" NAME="loc" SIZE=5 MAXLENGTH=4 VALUE="$hashref->{loc}"> +</PRE> +END + +print qq!<BR><INPUT TYPE="submit" VALUE="!, +      $hashref->{popnum} ? "Apply changes" : "Add Access Number", +      qq!">!; + +print <<END; +    </FORM> +  </BODY> +</HTML> +END + +%> diff --git a/httemplate/edit/svc_acct_sm.cgi b/httemplate/edit/svc_acct_sm.cgi new file mode 100755 index 000000000..0fd5f7622 --- /dev/null +++ b/httemplate/edit/svc_acct_sm.cgi @@ -0,0 +1,178 @@ +<!-- 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_domain.cgi b/httemplate/edit/svc_domain.cgi new file mode 100755 index 000000000..d20e1f336 --- /dev/null +++ b/httemplate/edit/svc_domain.cgi @@ -0,0 +1,98 @@ +<!-- mason kludge --> +<% + +my($svcnum, $pkgnum, $svcpart, $kludge_action, $purpose, $part_svc, +   $svc_domain); +if ( $cgi->param('error') ) { +  $svc_domain = new FS::svc_domain ( { +    map { $_, scalar($cgi->param($_)) } fields('svc_domain') +  } ); +  $svcnum = $svc_domain->svcnum; +  $pkgnum = $cgi->param('pkgnum'); +  $svcpart = $cgi->param('svcpart'); +  $kludge_action = $cgi->param('action'); +  $purpose = $cgi->param('purpose'); +  $part_svc = qsearchs('part_svc', { 'svcpart' => $svcpart } ); +  die "No part_svc entry!" unless $part_svc; +} else { +  $kludge_action = ''; +  $purpose = ''; +  my($query) = $cgi->keywords; +  if ( $query =~ /^(\d+)$/ ) { #editing +    $svcnum=$1; +    $svc_domain=qsearchs('svc_domain',{'svcnum'=>$svcnum}) +      or die "Unknown (svc_domain) svcnum!"; + +    my($cust_svc)=qsearchs('cust_svc',{'svcnum'=>$svcnum}) +      or die "Unknown (cust_svc) svcnum!"; + +    $pkgnum=$cust_svc->pkgnum; +    $svcpart=$cust_svc->svcpart; + +    $part_svc=qsearchs('part_svc',{'svcpart'=>$svcpart}); +    die "No part_svc entry!" unless $part_svc; + +  } else { #adding + +    $svc_domain = new FS::svc_domain({}); +   +    foreach $_ (split(/-/,$query)) { +      $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; + +    $svcnum=''; + +    #set fixed and default fields from part_svc +    foreach my $part_svc_column ( +      grep { $_->columnflag } $part_svc->all_part_svc_column +    ) { +      $svc_domain->setfield( $part_svc_column->columnname, +                             $part_svc_column->columnvalue, +                           ); +    } + +  } + +} +my $action = $svcnum ? 'Edit' : 'Add'; + +my $svc = $part_svc->getfield('svc'); + +my $otaker = getotaker; + +my $domain = $svc_domain->domain; + +my $p1 = popurl(1); +print header("$action $svc", ''); + +print qq!<FONT SIZE="+1" COLOR="#ff0000">Error: !, $cgi->param('error'), +      "</FONT>" +  if $cgi->param('error'); + +print <<END; +    <FORM ACTION="${p1}process/svc_domain.cgi" METHOD=POST> +      <INPUT TYPE="hidden" NAME="svcnum" VALUE="$svcnum"> +      <INPUT TYPE="hidden" NAME="pkgnum" VALUE="$pkgnum"> +      <INPUT TYPE="hidden" NAME="svcpart" VALUE="$svcpart"> +END + +print qq!<INPUT TYPE="radio" NAME="action" VALUE="N"!; +print ' CHECKED' if $kludge_action eq 'N'; +print qq!>New!; +print qq!<BR><INPUT TYPE="radio" NAME="action" VALUE="M"!; +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> +<BR>Purpose/Description: <INPUT TYPE="text" NAME="purpose" VALUE="$purpose" SIZE=64> +<P><INPUT TYPE="submit" VALUE="Submit"> +    </FORM> +  </BODY> +</HTML> +END + +%> diff --git a/httemplate/edit/svc_forward.cgi b/httemplate/edit/svc_forward.cgi new file mode 100755 index 000000000..5f1466bbb --- /dev/null +++ b/httemplate/edit/svc_forward.cgi @@ -0,0 +1,223 @@ +<!-- mason kludge --> +<% + +my $conf = new FS::Conf; +my $mydomain = $conf->config('domain'); + +my($svcnum, $pkgnum, $svcpart, $part_svc, $svc_forward); +if ( $cgi->param('error') ) { +  $svc_forward = new FS::svc_forward ( { +    map { $_, scalar($cgi->param($_)) } fields('svc_forward') +  } ); +  $svcnum = $svc_forward->svcnum; +  $pkgnum = $cgi->param('pkgnum'); +  $svcpart = $cgi->param('svcpart'); +  $part_svc=qsearchs('part_svc',{'svcpart'=>$svcpart}); +  die "No part_svc entry!" unless $part_svc; +} else { + +  my($query) = $cgi->keywords; + +  if ( $query =~ /^(\d+)$/ ) { #editing +    $svcnum=$1; +    $svc_forward=qsearchs('svc_forward',{'svcnum'=>$svcnum}) +      or die "Unknown (svc_forward) svcnum!"; + +    my($cust_svc)=qsearchs('cust_svc',{'svcnum'=>$svcnum}) +      or die "Unknown (cust_svc) svcnum!"; + +    $pkgnum=$cust_svc->pkgnum; +    $svcpart=$cust_svc->svcpart; +   +    $part_svc=qsearchs('part_svc',{'svcpart'=>$svcpart}); +    die "No part_svc entry!" unless $part_svc; + +  } else { #adding + +    $svc_forward = new FS::svc_forward({}); + +    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; + +    $svcnum=''; + +    #set fixed and default fields from part_svc +    foreach my $part_svc_column ( +      grep { $_->columnflag } $part_svc->all_part_svc_column +    ) { +      $svc_forward->setfield( $part_svc_column->columnname, +                              $part_svc_column->columnvalue, +                            ); +    } +  } + +} +my $action = $svc_forward->svcnum ? 'Edit' : 'Add'; + +my %email; +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'}) ) { +    push @u_acct_svcparts,$u_part_svc->getfield('svcpart'); +  } + +  my($cust_pkg)=qsearchs('cust_pkg',{'pkgnum'=>$pkgnum}); +  my($custnum)=$cust_pkg->getfield('custnum'); +  my($i_cust_pkg); +  foreach $i_cust_pkg ( qsearch('cust_pkg',{'custnum'=>$custnum}) ) { +    my($cust_pkgnum)=$i_cust_pkg->getfield('pkgnum'); +    my($acct_svcpart); +    foreach $acct_svcpart (@u_acct_svcparts) {   #now find the corresponding  +                                              #record(s) in cust_svc ( for this +                                              #pkgnum ! ) +      foreach my $i_cust_svc ( +        qsearch( 'cust_svc', { 'pkgnum'  => $cust_pkgnum, +                               'svcpart' => $acct_svcpart } ) +      ) { +        my $svc_acct = +          qsearchs( 'svc_acct', { 'svcnum' => $i_cust_svc->svcnum } ); +        $email{$svc_acct->svcnum} = $svc_acct->email; +      }   +    } +  } + +} elsif ( $action eq '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 { +  die "\$action eq Add, but \$pkgnum is null!\n"; +} + +my($srcsvc,$dstsvc,$dst)=( +  $svc_forward->srcsvc, +  $svc_forward->dstsvc, +  $svc_forward->dst, +); + +#display + +my $p1 = popurl(1); +print header("Mail Forward $action", '', +      " onLoad=\"visualize()\""); + +%> + +<SCRIPT> +function visualize(what){ +    if (document.getElementById) { +      document.getElementById('dother').style.visibility = '<%= $dstsvc ? 'hidden' : 'visible' %>'; +    } +} +function fixup(what){ +    if (document.getElementById) { +      if (document.getElementById('dother').style.visibility == 'hidden') { +        what.dst.value=''; +      } +    } +} +</SCRIPT> + +<% + +print qq!<FONT SIZE="+1" COLOR="#ff0000">Error: !, $cgi->param('error'), +      "</FONT>" +  if $cgi->param('error'); + +print qq!<FORM ACTION="${p1}process/svc_forward.cgi" onSubmit="fixup(this)" METHOD=POST>!; + +#svcnum +print qq!<INPUT TYPE="hidden" NAME="svcnum" VALUE="$svcnum">!; +print qq!Service #<FONT SIZE=+1><B>!, $svcnum ? $svcnum : " (NEW)", "</B></FONT>"; +print qq!<BR>!; + +#pkgnum +print qq!<INPUT TYPE="hidden" NAME="pkgnum" VALUE="$pkgnum">!; +  +#svcpart +print qq!<INPUT TYPE="hidden" NAME="svcpart" VALUE="$svcpart">!; + +#srcsvc +print qq!\n\nMail to <SELECT NAME="srcsvc" SIZE=1>!; +foreach $_ (keys %email) { +  print "<OPTION", $_ eq $srcsvc ? " SELECTED" : "", +        qq! VALUE="$_">$email{$_}!; +} +print "</SELECT>"; + +#dstsvc +print qq! forwards to <SELECT NAME="dstsvc" SIZE=1 onChange="changed(this)">!; +foreach $_ (keys %email) { +  print "<OPTION", $_ eq $dstsvc ? " SELECTED" : "", +        qq! VALUE="$_">$email{$_}!; +} +print "<OPTION", 0 eq $dstsvc ? " SELECTED" : "", +      qq! VALUE="0">(other)!; +print "</SELECT> mailbox."; + +%> + +<SCRIPT> +var selectchoice = null; +function changed(what) { +  selectchoice = what.options[what.selectedIndex].value; +  if (selectchoice == "0") { +    if (document.getElementById) { +      document.getElementById('dother').style.visibility = "visible"; +    } +  }else{ +    if (document.getElementById) { +      document.getElementById('dother').style.visibility = "hidden"; +    } +  } +} +if (document.getElementById) { +    document.write("<DIV ID=\"dother\" STYLE=\"visibility: hidden\">"); +} +</SCRIPT> + +<% +print qq! Other destination: <INPUT TYPE="text" NAME="dst" VALUE="$dst">!; +%> + +<SCRIPT> +if (document.getElementById) { +    document.write("</DIV>"); +} +</SCRIPT> + +<CENTER><INPUT TYPE="submit" VALUE="Submit"></CENTER> +</FORM> + +<TAG onLoad=" +    if (document.getElementById) { +      document.getElementById('dother').style.visibility = '<%= $dstsvc ? 'hidden' : 'visible' %>'; +      document.getElementById('dlabel').style.visibility = '<%= $dstsvc ? 'hidden' : 'visible' %>'; +    } +"> + + +  </BODY> +</HTML> diff --git a/httemplate/images/mid-logo.png b/httemplate/images/mid-logo.png Binary files differnew file mode 100644 index 000000000..d993419cc --- /dev/null +++ b/httemplate/images/mid-logo.png diff --git a/httemplate/images/small-logo.png b/httemplate/images/small-logo.png Binary files differnew file mode 100644 index 000000000..406a36980 --- /dev/null +++ b/httemplate/images/small-logo.png diff --git a/httemplate/index.html b/httemplate/index.html new file mode 100644 index 000000000..99bfeecec --- /dev/null +++ b/httemplate/index.html @@ -0,0 +1,100 @@ +<HTML> +  <HEAD> +    <TITLE> +      Freeside Main Menu +    </TITLE> +  </HEAD> +  <BODY BGCOLOR="#FFFFFF"> +  <table width="100%"> +    <tr><td> +        <IMG BORDER=0 ALT="Silicon Interactive Software Design" SRC="images/small-logo.png"> +    </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> +      <BR><A HREF="docs/">Documentation</A> +    </td></tr> +  </table> +    <hr noshade> +    <ul> +      <li><A HREF="edit/cust_main.cgi">New Customer</A> +      <li><A NAME="search">Search</A> +        <ul> +        <LI><A HREF="search/cust_main.html">customers (by last name and/or company)</A> +        <LI><A HREF="search/cust_main-payinfo.html">customers (by credit card number)</A> +        <LI><A HREF="search/svc_acct.html">accounts (by username)</A> +        <LI><A HREF="search/svc_domain.html">domains (by domain)</A> +<!--        <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>--> +        <LI><A HREF="search/cust_bill.html">invoices (by invoice number)</A> +        <LI><A HREF="search/cust_pay.html">checks (by check number)</A> +        </ul> +      <li><A NAME="browse">Browse</A> +        <ul> +          <LI>customers (<A HREF="search/cust_main.cgi?browse=custnum">by customer number</A>) (<A HREF="search/cust_main.cgi?browse=last">by last name</A>) (<A HREF="search/cust_main.cgi?browse=company">by company</A>) +          <LI>invoices +            <UL> +              <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>) +            </UL> +          <LI>packages +            <UL> +              <LI><A HREF="search/cust_pkg.cgi?pkgnum">packages (by package number)</A> +              <LI><A HREF="search/cust_pkg.cgi?APKG_pkgnum">packages with unconfigured services (by package number)</A> +            </UL> +          <LI>services +            <UL> +              <LI>accounts (<A HREF="search/svc_acct.cgi?svcnum">by service number</A>) (<A HREF="search/svc_acct.cgi?username">by username</A>) (<A HREF="search/svc_acct.cgi?uid">by uid</A>) +              <LI>mail forwards (<A HREF="search/svc_forward.cgi?svcnum">by service number</A>) (by ?)) +              <LI>domains (<A HREF="search/svc_domain.cgi?svcnum">by service number</A>) (<A HREF="search/svc_domain.cgi?domain">by domain</A>) +            </UL> +          <LI>unlinked services +            <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 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> +          <LI><A HREF="browse/nas.cgi">NAS ports</A> +          <LI><A HREF="browse/queue.cgi">Job queue</A> +          <LI><A HREF="browse/cust_pay_batch.cgi">Pending credit card batch</A> +        </ul> +      <li>Miscellaneous +        <ul> +          <li><A HREF="search/cust_main-quickpay.html">Quick payment entry</A> +        </ul> +    </ul> +    <hr noshade> +    <ul> +      <li><A NAME="config" HREF="config/config-view.cgi">Configuration</a><!-- - <font size="+2" color="#ff0000">start here</font> --> +      <li><A NAME="admin">Administration</a> +        <ul> +          <LI><A HREF="browse/part_svc.cgi">View/Edit service definitions</A> +            - Services are items you offer to your customers. +          <LI><A HREF="browse/part_pkg.cgi">View/Edit package definitions</A> +            - One or more services are grouped together into a package and +              given pricing information.  Customers purchase packages, not +              services. +          <LI><A HREF="browse/agent_type.cgi">View/Edit agent types</A> +            - Agent types define groups of package definitions that you can +              then assign to particular agents. +          <LI><A HREF="browse/agent.cgi">View/Edit agents</A> +            - Agents are resellers of your service.  Agents may be limited +              to a subset of your full offerings (via their type). +          <LI><A HREF="browse/part_referral.cgi">View/Edit referrals</A> +            - Where a customer heard about your service.  Tracked for +              informational purposes. +          <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> +            - Points of Presence  +          <LI><A HREF="browse/part_bill_event.cgi">View/Edit invoice events</A> - Actions for overdue invoices +        </ul> +      </ul> +  </BODY> +</HTML> diff --git a/httemplate/misc/bill.cgi b/httemplate/misc/bill.cgi new file mode 100755 index 000000000..6f523a52c --- /dev/null +++ b/httemplate/misc/bill.cgi @@ -0,0 +1,36 @@ +<% + +#untaint custnum +my($query) = $cgi->keywords; +$query =~ /^(\d*)$/; +my $custnum = $1; +my $cust_main = qsearchs('cust_main',{'custnum'=>$custnum}); +die "Can't find customer!\n" unless $cust_main; + +my $error = $cust_main->bill( +#                          'time'=>$time +                         ); +#&eidiot($error) if $error; + +unless ( $error ) { +  $cust_main->apply_payments; +  $cust_main->apply_credits; + +  $error = $cust_main->collect( +  #                             'invoice-time'=>$time, +  #                             'batch_card'=> 'yes', +                               'batch_card'=> 'no', +                               'report_badcard'=> 'yes', +                              ); +} +#&eidiot($error) if $error; + +if ( $error ) { +%> +<!-- mason kludge --> +<% +  &idiot($error); +} else { +  print $cgi->redirect(popurl(2). "view/cust_main.cgi?$custnum"); +} +%> diff --git a/httemplate/misc/cancel-unaudited.cgi b/httemplate/misc/cancel-unaudited.cgi new file mode 100755 index 000000000..5d3c87316 --- /dev/null +++ b/httemplate/misc/cancel-unaudited.cgi @@ -0,0 +1,47 @@ +<% + +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}); +&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'; + +local $SIG{HUP} = 'IGNORE'; +local $SIG{INT} = 'IGNORE'; +local $SIG{QUIT} = 'IGNORE'; +local $SIG{TERM} = 'IGNORE'; +local $SIG{TSTP} = 'IGNORE'; + +local $FS::UID::AutoCommit = 0; + +my $error = $svc_acct->cancel; +&myeidiot($error) if $error; +$error = $svc_acct->delete; +&myeidiot($error) if $error; + +$error = $cust_svc->delete; + +if ( $error ) { +  $dbh->rollback; +  %> +<!-- mason kludge --> +<% +  &eidiot(@_); +} else { + +  $dbh->commit or die $dbh->errstr; + +  print $cgi->redirect(popurl(2)); +} + +%> diff --git a/httemplate/misc/cancel_pkg.cgi b/httemplate/misc/cancel_pkg.cgi new file mode 100755 index 000000000..0487677df --- /dev/null +++ b/httemplate/misc/cancel_pkg.cgi @@ -0,0 +1,15 @@ +<% + +#untaint pkgnum +my($query) = $cgi->keywords; +$query =~ /^(\d+)$/ || die "Illegal pkgnum"; +my $pkgnum = $1; + +my $cust_pkg = qsearchs('cust_pkg',{'pkgnum'=>$pkgnum}); + +my $error = $cust_pkg->cancel; +eidiot($error) if $error; + +print $cgi->redirect($p. "view/cust_main.cgi?".$cust_pkg->getfield('custnum')); + +%> diff --git a/httemplate/misc/catchall.cgi b/httemplate/misc/catchall.cgi new file mode 100755 index 000000000..9aa84be18 --- /dev/null +++ b/httemplate/misc/catchall.cgi @@ -0,0 +1,133 @@ +<!-- mason kludge --> +<% + +my $conf = new FS::Conf; + +my($svc_domain, $svcnum, $pkgnum, $svcpart, $part_svc); +if ( $cgi->param('error') ) { +  $svc_domain = new FS::svc_domain ( { +    map { $_, scalar($cgi->param($_)) } fields('svc_domain') +  } ); +  $svcnum = $svc_domain->svcnum; +  $pkgnum = $cgi->param('pkgnum'); +  $svcpart = $cgi->param('svcpart'); +  $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_domain=qsearchs('svc_domain',{'svcnum'=>$svcnum}) +      or die "Unknown (svc_domain) svcnum!"; + +    my($cust_svc)=qsearchs('cust_svc',{'svcnum'=>$svcnum}) +      or die "Unknown (cust_svc) svcnum!"; + +    $pkgnum=$cust_svc->pkgnum; +    $svcpart=$cust_svc->svcpart; +   +    $part_svc=qsearchs('part_svc',{'svcpart'=>$svcpart}); +    die "No part_svc entry!" unless $part_svc; + +  } else {  + +    die "Invalid (svc_domain) svcnum!"; + +  } +} + +my %email; +if ($pkgnum) { + +  #find all possible user svcnums (and emails) + +  #starting with that currently attached +  if ($svc_domain->catchall) { +    my($svc_acct)=qsearchs('svc_acct',{'svcnum'=>$svc_domain->catchall}); +    $email{$svc_domain->catchall} = $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'}) ) { +    push @u_acct_svcparts,$u_part_svc->getfield('svcpart'); +  } + +  my($cust_pkg)=qsearchs('cust_pkg',{'pkgnum'=>$pkgnum}); +  my($custnum)=$cust_pkg->getfield('custnum'); +  my($i_cust_pkg); +  foreach $i_cust_pkg ( qsearch('cust_pkg',{'custnum'=>$custnum}) ) { +    my($cust_pkgnum)=$i_cust_pkg->getfield('pkgnum'); +    my($acct_svcpart); +    foreach $acct_svcpart (@u_acct_svcparts) {   #now find the corresponding  +                                              #record(s) in cust_svc ( for this +                                              #pkgnum ! ) +      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')}); +        $email{$svc_acct->getfield('svcnum')}=$svc_acct->email; +      }   +    } +  } + +} else { + +  my($svc_acct)=qsearchs('svc_acct',{'svcnum'=>$svc_domain->catchall}); +  $email{$svc_domain->catchall} = $svc_acct->email; +} + +# add an absence of a catchall +$email{0} = "(none)"; + +my $p1 = popurl(1); +print header("Domain Catchall Edit", ''); + +print qq!<FONT SIZE="+1" COLOR="#ff0000">Error: !, $cgi->param('error'), +      "</FONT>" +  if $cgi->param('error'); + +print qq!<FORM ACTION="${p1}process/catchall.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($domain,$catchall)=( +  $svc_domain->domain, +  $svc_domain->catchall, +); + +print qq!<INPUT TYPE="hidden" NAME="domain" VALUE="$domain">!; + +#catchall +print qq!\n\nMail to <I>(anything)</I>@<B>$domain</B> forwards to <SELECT NAME="catchall" SIZE=1>!; +foreach $_ (keys %email) { +  print "<OPTION", $_ eq $catchall ? " SELECTED" : "", +        qq! VALUE="$_">$email{$_}!; +} +print "</SELECT>"; + +	#formatting +	print "</PRE>\n"; + +print qq!<CENTER><INPUT TYPE="submit" VALUE="Submit"></CENTER>!; + +print <<END; + +    </FORM> +  </BODY> +</HTML> +END + +%> diff --git a/httemplate/misc/delete-cust_pay.cgi b/httemplate/misc/delete-cust_pay.cgi new file mode 100755 index 000000000..3efd918ab --- /dev/null +++ b/httemplate/misc/delete-cust_pay.cgi @@ -0,0 +1,16 @@ +<% + +#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; + +my $error = $cust_pay->delete; +eidiot($error) if $error; + +print $cgi->redirect($p. "view/cust_main.cgi?". $custnum); + +%> diff --git a/httemplate/misc/delete-customer.cgi b/httemplate/misc/delete-customer.cgi new file mode 100755 index 000000000..5088fef7b --- /dev/null +++ b/httemplate/misc/delete-customer.cgi @@ -0,0 +1,46 @@ +<!-- mason kludge --> +<% + +my $conf = new FS::Conf; +die "Customer deletions not enabled" unless $conf->exists('deletecustomers'); + +my($custnum, $new_custnum); +if ( $cgi->param('error') ) { +  $custnum = $cgi->param('custnum'); +  $new_custnum = $cgi->param('new_custnum'); +} else { +  my($query) = $cgi->keywords; +  $query =~ /^(\d+)$/ or die "Illegal query: $query"; +  $custnum = $1; +  $new_custnum = ''; +} +my $cust_main = qsearchs( 'cust_main', { 'custnum' => $custnum } ) +  or die "Customer not found: $custnum"; + +print header('Delete customer'); + +print qq!<FONT SIZE="+1" COLOR="#ff0000">Error: !, $cgi->param('error'), +      "</FONT>" +  if $cgi->param('error'); + +print  +  qq!<form action="!, popurl(1), qq!process/delete-customer.cgi" method=post>!, +  qq!<input type="hidden" name="custnum" value="$custnum">!; + +if ( qsearch('cust_pkg', { 'custnum' => $custnum, 'cancel' => '' } ) ) { +  print "Move uncancelled packages to customer number ", +        qq!<input type="text" name="new_custnum" value="$new_custnum"><br><br>!; +} + +print <<END; +This will <b>completely remove</b> all traces of this customer record.  This +is <B>not</B> what you want if this is a real customer who has simply +canceled service with you.  For that, cancel all of the customer's packages. +(you can optionally hide cancelled customers with the <a href="../docs/config.html#hidecancelledcustomers">hidecancelledcustomers</a> configuration file) +<br> +<br>Are you <b>absolutely sure</b> you want to delete this customer? +<br><input type="submit" value="Yes"> +</form></body></html> +END + +%> diff --git a/httemplate/misc/expire_pkg.cgi b/httemplate/misc/expire_pkg.cgi new file mode 100755 index 000000000..9e4ce8b62 --- /dev/null +++ b/httemplate/misc/expire_pkg.cgi @@ -0,0 +1,25 @@ +<% + +#untaint date & pkgnum + +my $date; +if ( $cgi->param('date') ) { +  str2time($cgi->param('date')) =~ /^(\d+)$/ or die "Illegal date"; +  $date=$1; +} else { +  $date=''; +} + +$cgi->param('pkgnum') =~ /^(\d+)$/ or die "Illegal pkgnum"; +my $pkgnum = $1; + +my $cust_pkg = qsearchs('cust_pkg',{'pkgnum'=>$pkgnum}); +my %hash = $cust_pkg->hash; +$hash{expire}=$date; +my $new = new FS::cust_pkg ( \%hash ); +my $error = $new->replace($cust_pkg); +&eidiot($error) if $error; + +print $cgi->redirect(popurl(2). "view/cust_main.cgi?".$cust_pkg->getfield('custnum')); + +%> diff --git a/httemplate/misc/link.cgi b/httemplate/misc/link.cgi new file mode 100755 index 000000000..efc762cc5 --- /dev/null +++ b/httemplate/misc/link.cgi @@ -0,0 +1,46 @@ +<!-- mason kludge --> +<% + +my %link_field = ( +  'svc_acct'    => 'username', +  'svc_domain'  => 'domain', +  'svc_acct_sm' => '', +  'svc_charge'  => '', +  'svc_wo'      => '', +); + +my($query) = $cgi->keywords; +my($pkgnum, $svcpart) = ('', ''); +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}); +my $svc = $part_svc->getfield('svc'); +my $svcdb = $part_svc->getfield('svcdb'); +my $link_field = $link_field{$svcdb}; + +print header("Link to existing $svc"), +      qq!<FORM ACTION="!, popurl(1), qq!process/link.cgi" METHOD=POST>!; + +if ( $link_field ) {  +  print <<END; +  <INPUT TYPE="hidden" NAME="svcnum" VALUE=""> +  <INPUT TYPE="hidden" NAME="link_field" VALUE="$link_field"> +  $link_field of existing service: <INPUT TYPE="text" NAME="link_value"> +END +} else { +  print qq!Service # of existing service: <INPUT TYPE="text" NAME="svcnum" VALUE="">!; +} + +print <<END; +<INPUT TYPE="hidden" NAME="pkgnum" VALUE="$pkgnum"> +<INPUT TYPE="hidden" NAME="svcpart" VALUE="$svcpart"> +<P><CENTER><INPUT TYPE="submit" VALUE="Link"></CENTER> +    </FORM> +  </BODY> +</HTML> +END + +%> diff --git a/httemplate/misc/print-invoice.cgi b/httemplate/misc/print-invoice.cgi new file mode 100755 index 000000000..a5500bff2 --- /dev/null +++ b/httemplate/misc/print-invoice.cgi @@ -0,0 +1,23 @@ +<% + +my $conf = new FS::Conf; +my $lpr = $conf->config('lpr'); + +#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; + +        open(LPR,"|$lpr") or die "Can't open $lpr: $!"; +        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"); + +%> diff --git a/httemplate/misc/process/catchall.cgi b/httemplate/misc/process/catchall.cgi new file mode 100755 index 000000000..44a63f9f8 --- /dev/null +++ b/httemplate/misc/process/catchall.cgi @@ -0,0 +1,33 @@ +<% + +$FS::svc_domain::whois_hack=1; + +$cgi->param('svcnum') =~ /^(\d*)$/ or die "Illegal svcnum!"; +my $svcnum =$1; + +my $old = qsearchs('svc_domain',{'svcnum'=>$svcnum}) if $svcnum; + +my $new = new FS::svc_domain ( { +  map { +    ($_, scalar($cgi->param($_))); +  } ( fields('svc_domain'), qw( pkgnum svcpart ) ) +} ); + +$new->setfield('action' => 'M'); + +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). "catchall.cgi?". $cgi->query_string ); +} else { +  print $cgi->redirect(popurl(3). "view/svc_domain.cgi?$svcnum"); +} + +%> diff --git a/httemplate/misc/process/delete-customer.cgi b/httemplate/misc/process/delete-customer.cgi new file mode 100755 index 000000000..16bdbaea8 --- /dev/null +++ b/httemplate/misc/process/delete-customer.cgi @@ -0,0 +1,29 @@ +<% + +my $conf = new FS::Conf; +die "Customer deletions not enabled" unless $conf->exists('deletecustomers'); + +$cgi->param('custnum') =~ /^(\d+)$/; +my $custnum = $1; +my $new_custnum; +if ( $cgi->param('new_custnum') ) { +  $cgi->param('new_custnum') =~ /^(\d+)$/ +    or die "Illegal new customer number: ". $cgi->param('new_custnum'); +  $new_custnum = $1; +} else { +  $new_custnum = ''; +} +my $cust_main = qsearchs( 'cust_main', { 'custnum' => $custnum } ) +  or die "Customer not found: $custnum"; + +my $error = $cust_main->delete($new_custnum); + +if ( $error ) { +  $cgi->param('error', $error); +  print $cgi->redirect(popurl(2). "delete-customer.cgi?". $cgi->query_string ); +} elsif ( $new_custnum ) { +  print $cgi->redirect(popurl(3). "view/cust_main.cgi?$new_custnum"); +} else { +  print $cgi->redirect(popurl(3)); +} +%> diff --git a/httemplate/misc/process/link.cgi b/httemplate/misc/process/link.cgi new file mode 100755 index 000000000..4b220a867 --- /dev/null +++ b/httemplate/misc/process/link.cgi @@ -0,0 +1,40 @@ +<% + +$cgi->param('pkgnum') =~ /^(\d+)$/; +my $pkgnum = $1; +$cgi->param('svcpart') =~ /^(\d+)$/; +my $svcpart = $1; +$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') }); +  eidiot("$link_field not found!") unless $svc_x; +  $svcnum=$svc_x->svcnum; +} + +my $old = qsearchs('cust_svc',{'svcnum'=>$svcnum}); +die "svcnum not found!" unless $old; +#die "svcnum $svcnum already linked to package ". $old->pkgnum if $old->pkgnum; +my $new = new FS::cust_svc ({ +  'svcnum' => $svcnum, +  'pkgnum' => $pkgnum, +  'svcpart' => $svcpart, +}); + +my $error = $new->replace($old); + +unless ($error) { +  #no errors, so let's view this customer. +  print $cgi->redirect(popurl(3). "view/cust_pkg.cgi?$pkgnum"); +} else { +%> +<!-- mason kludge --> +<% +  idiot($error); +} + +%> diff --git a/httemplate/misc/susp_pkg.cgi b/httemplate/misc/susp_pkg.cgi new file mode 100755 index 000000000..4a19fa830 --- /dev/null +++ b/httemplate/misc/susp_pkg.cgi @@ -0,0 +1,15 @@ +<% + +#untaint pkgnum +my ($query) = $cgi->keywords; +$query =~ /^(\d+)$/ || die "Illegal pkgnum"; +my $pkgnum = $1; + +my $cust_pkg = qsearchs('cust_pkg',{'pkgnum'=>$pkgnum}); + +my $error = $cust_pkg->suspend; +&eidiot($error) if $error; + +print $cgi->redirect(popurl(2). "view/cust_main.cgi?".$cust_pkg->getfield('custnum')); + +%> diff --git a/httemplate/misc/unsusp_pkg.cgi b/httemplate/misc/unsusp_pkg.cgi new file mode 100755 index 000000000..500872983 --- /dev/null +++ b/httemplate/misc/unsusp_pkg.cgi @@ -0,0 +1,15 @@ +<% + +#untaint pkgnum +my ($query) = $cgi->keywords; +$query =~ /^(\d+)$/ || die "Illegal pkgnum"; +my $pkgnum = $1; + +my $cust_pkg = qsearchs('cust_pkg',{'pkgnum'=>$pkgnum}); + +my $error = $cust_pkg->unsuspend; +&eidiot($error) if $error; + +print $cgi->redirect(popurl(2). "view/cust_main.cgi?".$cust_pkg->getfield('custnum')); + +%> diff --git a/httemplate/search/cust_bill.cgi b/httemplate/search/cust_bill.cgi new file mode 100755 index 000000000..d83851804 --- /dev/null +++ b/httemplate/search/cust_bill.cgi @@ -0,0 +1,146 @@ +<% + +my(@cust_bill, $sortby); +if ( $cgi->keywords ) { +  my($query) = $cgi->keywords; +  if ( $query eq 'invnum' ) { +    $sortby = \*invnum_sort; +    @cust_bill = qsearch('cust_bill', {} ); +  } elsif ( $query eq 'date' ) { +    $sortby = \*date_sort; +    @cust_bill = qsearch('cust_bill', {} ); +  } elsif ( $query eq 'custnum' ) { +    $sortby = \*custnum_sort; +    @cust_bill = qsearch('cust_bill', {} ); +  } elsif ( $query eq 'OPEN_invnum' ) { +    $sortby = \*invnum_sort; +    @cust_bill = grep $_->owed != 0, qsearch('cust_bill', {} ); +  } elsif ( $query eq 'OPEN_date' ) { +    $sortby = \*date_sort; +    @cust_bill = grep $_->owed != 0, qsearch('cust_bill', {} ); +  } elsif ( $query eq 'OPEN_custnum' ) { +    $sortby = \*custnum_sort; +    @cust_bill = grep $_->owed != 0, qsearch('cust_bill', {} ); +  } elsif ( $query =~ /^OPEN(\d+)_invnum$/ ) { +    my $open = $1 * 86400; +    $sortby = \*invnum_sort; +    @cust_bill = +      grep $_->owed != 0 && $_->_date < time - $open, qsearch('cust_bill', {} ); +  } elsif ( $query =~ /^OPEN(\d+)_date$/ ) { +    my $open = $1 * 86400; +    $sortby = \*date_sort; +    @cust_bill = +      grep $_->owed != 0 && $_->_date < time - $open, qsearch('cust_bill', {} ); +  } elsif ( $query =~ /^OPEN(\d+)_custnum$/ ) { +    my $open = $1 * 86400; +    $sortby = \*custnum_sort; +    @cust_bill = +      grep $_->owed != 0 && $_->_date < time - $open, qsearch('cust_bill', {} ); +  } else { +    die "unknown query string $query"; +  } +} else { +  $cgi->param('invnum') =~ /^\s*(FS-)?(\d+)\s*$/; +  my $invnum = $2; +  @cust_bill = qsearchs('cust_bill', { 'invnum' => $invnum } ); +  $sortby = \*invnum_sort; +} + +if ( scalar(@cust_bill) == 1 ) { +  my $invnum = $cust_bill[0]->invnum; +  print $cgi->redirect(popurl(2). "view/cust_bill.cgi?$invnum");  #redirect +} elsif ( scalar(@cust_bill) == 0 ) { +%> +<!-- mason kludge --> +<% +  eidiot("Invoice not found."); +} else { +%> +<!-- mason kludge --> +<% +  my $total = scalar(@cust_bill); +  print header("Invoice Search Results", menubar( +          'Main Menu', popurl(2) +        )), "$total matching invoices found<BR>", &table(), <<END; +      <TR> +        <TH></TH> +        <TH>Balance</TH> +        <TH>Amount</TH> +        <TH>Date</TH> +        <TH>Contact name</TH> +        <TH>Company</TH> +      </TR> +END + +  my(%saw, $cust_bill); +  my($tot_balance, $tot_amount) = (0, 0); +  foreach $cust_bill ( +    sort $sortby grep(!$saw{$_->invnum}++, @cust_bill) +  ) { +    my($invnum, $owed, $charged, $date ) = ( +      $cust_bill->invnum, +      sprintf("%.2f", $cust_bill->owed), +      sprintf("%.2f", $cust_bill->charged), +      $cust_bill->_date, +    ); +    my $pdate = time2str("%b %d %Y", $date); + +    $tot_balance += $owed; +    $tot_amount += $charged; + +    my $rowspan = 1; + +    my $view = popurl(2). "view/cust_bill.cgi?$invnum"; +    print <<END; +      <TR> +        <TD ROWSPAN=$rowspan><A HREF="$view"><FONT SIZE=-1>$invnum</FONT></A></TD> +        <TD ROWSPAN=$rowspan ALIGN="right"><A HREF="$view"><FONT SIZE=-1>\$$owed</FONT></A></TD> +        <TD ROWSPAN=$rowspan ALIGN="right"><A HREF="$view"><FONT SIZE=-1>\$$charged</FONT></A></TD> +        <TD ROWSPAN=$rowspan><A HREF="$view"><FONT SIZE=-1>$pdate</FONT></A></TD> +END +    my $custnum = $cust_bill->custnum; +    my $cust_main = qsearchs('cust_main', { 'custnum' => $custnum } ); +    if ( $cust_main ) { +      my $cview = popurl(2). "view/cust_main.cgi?". $cust_main->custnum; +      my ( $name, $company ) = ( +        $cust_main->last. ', '. $cust_main->first, +        $cust_main->company, +      ); +      print <<END; +        <TD ROWSPAN=$rowspan><A HREF="$cview"><FONT SIZE=-1>$name</FONT></A></TD> +        <TD ROWSPAN=$rowspan><A HREF="$cview"><FONT SIZE=-1>$company</FONT></A></TD> +END +    } else { +      print <<END +        <TD ROWSPAN=$rowspan COLSPAN=2>WARNING: couldn't find cust_main.custnum $custnum (cust_bill.invnum $invnum)</TD> +END +    } + +    print "</TR>"; +  } +  $tot_balance = sprintf("%.2f", $tot_balance); +  $tot_amount = sprintf("%.2f", $tot_amount); +  print <<END; +      <TR><TD></TD><TH><FONT SIZE=-1>Total</FONT></TH><TH><FONT SIZE=-1>Total</FONT></TH></TR> +      <TR><TD></TD><TD ALIGN="right"><FONT SIZE=-1>\$$tot_balance</FONT></TD><TD ALIGN="right"><FONT SIZE=-1>\$$tot_amount</FONT></TD></TD></TR> +    </TABLE> +  </BODY> +</HTML> +END + +} + +# + +sub invnum_sort { +  $a->invnum <=> $b->invnum; +} + +sub custnum_sort { +  $a->custnum <=> $b->custnum || $a->invnum <=> $b->invnum; +} + +sub date_sort { +  $a->_date <=> $b->_date || $a->invnum <=> $b->invnum; +} +%> diff --git a/httemplate/search/cust_bill.html b/httemplate/search/cust_bill.html new file mode 100755 index 000000000..36e8bc91b --- /dev/null +++ b/httemplate/search/cust_bill.html @@ -0,0 +1,19 @@ +<HTML> +  <HEAD> +    <TITLE>Invoice Search</TITLE> +  </HEAD> +  <BODY BGCOLOR="#e8e8e8"> +    <FONT SIZE=7> +      Invoice Search +    </FONT> +    <BR><BR> +    <FORM ACTION="cust_bill.cgi" METHOD="post"> +      Search for <B>invoice #</B>:  +      <INPUT TYPE="text" NAME="invnum"> + +      <P><INPUT TYPE="submit" VALUE="Search"> + +    </FORM> +  </BODY> +</HTML> + diff --git a/httemplate/search/cust_main-payinfo.html b/httemplate/search/cust_main-payinfo.html new file mode 100755 index 000000000..671b5ef08 --- /dev/null +++ b/httemplate/search/cust_main-payinfo.html @@ -0,0 +1,20 @@ +<HTML> +  <HEAD> +    <TITLE>Customer Search</TITLE> +  </HEAD> +  <BODY BGCOLOR="#e8e8e8"> +    <FONT SIZE=7> +      Customer Search +    </FONT> +    <BR> +    <FORM ACTION="cust_main.cgi" METHOD="post"> +      Search for <B>Credit card #</B>:  +      <INPUT TYPE="hidden" NAME="card_on" VALUE="TRUE"> +      <INPUT TYPE="text" NAME="card"> + +      <P><INPUT TYPE="submit" VALUE="Search"> + +    </FORM> +  </BODY> +</HTML> + diff --git a/httemplate/search/cust_main-quickpay.html b/httemplate/search/cust_main-quickpay.html new file mode 100755 index 000000000..3f0ba0412 --- /dev/null +++ b/httemplate/search/cust_main-quickpay.html @@ -0,0 +1,37 @@ +<HTML> +  <HEAD> +    <TITLE>Quick payment entry</TITLE> +  </HEAD> +  <BODY BGCOLOR="#e8e8e8"> +    <FONT SIZE=7> +      Quick payment entry +    </FONT> +    <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>Fuzzy +        <OPTION>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>Fuzzy +        <OPTION>Exact +      </SELECT> + +      <P><INPUT TYPE="submit" VALUE="Search"> Note: Fuzzy searching can take a while.  Please be patient. + +    </FORM> + +  <HR>Explanation of search methods: +  <UL> +    <LI><B>Fuzzy</B> - Searches for matches that are close to your text. +    <LI><B>Exact</B> - Finds exact matches only, but much faster than the other search methods. +  </UL> +  </BODY> +</HTML> + diff --git a/httemplate/search/cust_main.cgi b/httemplate/search/cust_main.cgi new file mode 100755 index 000000000..1e28ad57d --- /dev/null +++ b/httemplate/search/cust_main.cgi @@ -0,0 +1,485 @@ +<% + +my $conf = new FS::Conf; +my $maxrecords = $conf->config('maxsearchrecordsperpage'); + +#my $cache; + +#my $monsterjoin = <<END; +#cust_main left outer join ( +#  ( cust_pkg left outer join part_pkg using(pkgpart) +#  ) left outer join ( +#    ( +#      ( +#        ( cust_svc left outer join part_svc using (svcpart) +#        ) left outer join svc_acct using (svcnum) +#      ) left outer join svc_domain using(svcnum) +#    ) left outer join svc_forward using(svcnum) +#  ) using (pkgnum) +#) using (custnum) +#END + +#my $monsterjoin = <<END; +#cust_main left outer join ( +#  ( cust_pkg left outer join part_pkg using(pkgpart) +#  ) left outer join ( +#    ( +#      ( +#        ( cust_svc left outer join part_svc using (svcpart) +#        ) left outer join ( +#          svc_acct left outer join ( +#            select svcnum, domain, catchall from svc_domain +#            ) as svc_acct_domsvc ( +#              svc_acct_svcnum, svc_acct_domain, svc_acct_catchall +#          ) on svc_acct.domsvc = svc_acct_domsvc.svc_acct_svcnum +#        ) using (svcnum) +#      ) left outer join svc_domain using(svcnum) +#    ) left outer join svc_forward using(svcnum) +#  ) using (pkgnum) +#) using (custnum) +#END + +my $limit = ''; +$limit .= "LIMIT $maxrecords" if $maxrecords; + +my $offset = $cgi->param('offset') || 0; +$limit .= " OFFSET $offset" if $offset; + +my $total = 0; + +my(@cust_main, $sortby, $orderby); +if ( $cgi->param('browse') ) { +  my $query = $cgi->param('browse'); +  if ( $query eq 'custnum' ) { +    $sortby=\*custnum_sort; +    $orderby = 'ORDER BY custnum'; +  } elsif ( $query eq 'last' ) { +    $sortby=\*last_sort; +    $orderby = 'ORDER BY last'; +  } elsif ( $query eq 'company' ) { +    $sortby=\*company_sort; +    $orderby = 'ORDER BY company'; +  } else { +    die "unknown browse field $query"; +  } + +  my $ncancelled = ''; + +  if (  $cgi->param('showcancelledcustomers') eq '0' #see if it was set by me +       || ( $conf->exists('hidecancelledcustomers') +             && ! $cgi->param('showcancelledcustomers') ) +     ) { +    #grep { $_->ncancelled_pkgs || ! $_->all_pkgs } +    #needed for MySQL???    OR cust_pkg.cancel = \"\" +    $ncancelled = " +      WHERE 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 $statement = "SELECT COUNT(*) FROM cust_main $ncancelled"; +  my $sth = dbh->prepare($statement) +    or die dbh->errstr. " doing $statement"; +  $sth->execute or die "Error executing \"$statement\": ". $sth->errstr; + +  $total = $sth->fetchrow_arrayref->[0]; + +  my @just_cust_main = qsearch('cust_main',{}, '', +    "$ncancelled $orderby $limit" +  );     + +  @cust_main = @just_cust_main; + +#  foreach my $cust_main ( @just_cust_main ) { +# +#    my @one_cust_main; +#    $FS::Record::DEBUG=1; +#    ( $cache, @one_cust_main ) = jsearch( +#      "$monsterjoin", +#      { 'custnum' => $cust_main->custnum }, +#      '', +#      '', +#      'cust_main', +#      'custnum', +#    ); +#    push @cust_main, @one_cust_main; +#  } + +} else { +  @cust_main=(); +  $sortby = \*last_sort; + +  push @cust_main, @{&cardsearch} +    if $cgi->param('card_on') && $cgi->param('card'); +  push @cust_main, @{&lastsearch} +    if $cgi->param('last_on') && $cgi->param('last_text'); +  push @cust_main, @{&companysearch} +    if $cgi->param('company_on') && $cgi->param('company_text'); +  push @cust_main, @{&referralsearch} +    if $cgi->param('referral_custnum'); + +  if ( $cgi->param('company_on') && $cgi->param('company_text') ) { +    $sortby = \*company_sort; +    push @cust_main, @{&companysearch}; +  } + +  @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') ); + +  my %saw = (); +  @cust_main = grep { !$saw{$_->custnum}++ } @cust_main; +} + +my %all_pkgs; +if ( $conf->exists('hidecancelledpackages' ) ) { +  %all_pkgs = map { $_->custnum => [ $_->ncancelled_pkgs ] } @cust_main; +} else { +  %all_pkgs = map { $_->custnum => [ $_->all_pkgs ] } @cust_main; +} +#%all_pkgs = (); + +if ( scalar(@cust_main) == 1 && ! $cgi->param('referral_custnum') ) { +  if ( $cgi->param('quickpay') eq 'yes' ) { +    print $cgi->redirect(popurl(2). "edit/cust_pay.cgi?quickpay=yes;custnum=". $cust_main[0]->custnum); +  } else { +    print $cgi->redirect(popurl(2). "view/cust_main.cgi?". $cust_main[0]->custnum); +  } +  #exit; +} elsif ( scalar(@cust_main) == 0 ) { +%> +<!-- mason kludge --> +<% +  eidiot "No matching customers found!\n"; +} else {  +%> +<!-- mason kludge --> +<% + +  $total ||= scalar(@cust_main); +  print header("Customer Search Results",menubar( +    'Main Menu', popurl(2) +  )), "$total matching customers found "; + +  #begin pager +  my $pager = ''; +  if ( $total != scalar(@cust_main) && $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 +   +  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> )!; +  } +  if ( $cgi->param('referral_custnum') ) { +    $cgi->param('referral_custnum') =~ /^(\d+)$/ +      or eidiot "Illegal referral_custnum\n"; +    my $referral_custnum = $1; +    my $cust_main = qsearchs('cust_main', { custnum => $referral_custnum } ); +    print '<FORM METHOD=POST>'. +          qq!<INPUT TYPE="hidden" NAME="referral_custnum" VALUE="$referral_custnum">!. +          'referrals of <A HREF="'. popurl(2). +          "view/cust_main.cgi?$referral_custnum\">$referral_custnum: ". +          ( $cust_main->company +            || $cust_main->last. ', '. $cust_main->first ). +          '</A>'; +    print "\n",<<END; +      <SCRIPT> +      function changed(what) { +        what.form.submit(); +      } +      </SCRIPT> +END +    print ' <SELECT NAME="referral_depth" SIZE="1" onChange="changed(this)">'; +    my $max = 8; #config file +    $cgi->param('referral_depth') =~ /^(\d*)$/  +      or eidiot "Illegal referral_depth"; +    my $referral_depth = $1; + +    foreach my $depth ( 1 .. $max ) { +      print '<OPTION', +            ' SELECTED'x($depth == $referral_depth), +            ">$depth"; +    } +    print "</SELECT> levels deep". +          '<NOSCRIPT> <INPUT TYPE="submit" VALUE="change"></NOSCRIPT>'. +          '</FORM>'; +  } + +  print "<BR><BR>". $pager. &table(). <<END; +      <TR> +        <TH></TH> +        <TH>(bill) name</TH> +        <TH>company</TH> +END + +if ( defined dbdef->table('cust_main')->column('ship_last') ) { +  print <<END; +      <TH>(service) name</TH> +      <TH>company</TH> +END +} + +print <<END; +        <TH>Packages</TH> +        <TH COLSPAN=2>Services</TH> +      </TR> +END + +  my(%saw,$cust_main); +  my $p = popurl(2); +  foreach $cust_main ( +    sort $sortby grep(!$saw{$_->custnum}++, @cust_main) +  ) { +    my($custnum,$last,$first,$company)=( +      $cust_main->custnum, +      $cust_main->getfield('last'), +      $cust_main->getfield('first'), +      $cust_main->company, +    ); + +    my(@lol_cust_svc); +    my($rowspan)=0;#scalar( @{$all_pkgs{$custnum}} ); +    foreach ( @{$all_pkgs{$custnum}} ) { +      #my(@cust_svc) = qsearch( 'cust_svc', { 'pkgnum' => $_->pkgnum } ); +      my @cust_svc = $_->cust_svc; +      push @lol_cust_svc, \@cust_svc; +      $rowspan += scalar(@cust_svc) || 1; +    } + +    #my($rowspan) = scalar(@{$all_pkgs{$custnum}}); +    my $view; +    if ( defined $cgi->param('quickpay') && $cgi->param('quickpay') eq 'yes' ) { +      $view = $p. 'edit/cust_pay.cgi?quickpay=yes;custnum='. $custnum; +    } else { +      $view = $p. 'view/cust_main.cgi?'. $custnum; +    } +    print <<END; +    <TR> +      <TD ROWSPAN=$rowspan><A HREF="$view"><FONT SIZE=-1>$custnum</FONT></A></TD> +      <TD ROWSPAN=$rowspan><A HREF="$view"><FONT SIZE=-1>$last, $first</FONT></A></TD> +      <TD ROWSPAN=$rowspan><A HREF="$view"><FONT SIZE=-1>$company</FONT></A></TD> +END +    if ( defined dbdef->table('cust_main')->column('ship_last') ) { +      my($ship_last,$ship_first,$ship_company)=( +        $cust_main->ship_last || $cust_main->getfield('last'), +        $cust_main->ship_last ? $cust_main->ship_first : $cust_main->first, +        $cust_main->ship_last ? $cust_main->ship_company : $cust_main->company, +      ); +print <<END; +      <TD ROWSPAN=$rowspan><A HREF="$view"><FONT SIZE=-1>$ship_last, $ship_first</FONT></A></TD> +      <TD ROWSPAN=$rowspan><A HREF="$view"><FONT SIZE=-1>$ship_company</FONT></A></TD> +END +    } + +    my($n1)=''; +    foreach ( @{$all_pkgs{$custnum}} ) { +      my $pkgnum = $_->pkgnum; +#      my $part_pkg = qsearchs( 'part_pkg', { pkgpart => $_->pkgpart } ); +      my $part_pkg = $_->part_pkg; + +      my $pkg = $part_pkg->pkg; +      my $comment = $part_pkg->comment; +      my $pkgview = $p. 'view/cust_pkg.cgi?'. $pkgnum; +      my @cust_svc = @{shift @lol_cust_svc}; +      #my(@cust_svc) = qsearch( 'cust_svc', { 'pkgnum' => $_->pkgnum } ); +      my $rowspan = scalar(@cust_svc) || 1; + +      print $n1, qq!<TD ROWSPAN=$rowspan><A HREF="$pkgview"><FONT SIZE=-1>$pkg - $comment</FONT></A></TD>!; +      my($n2)=''; +      foreach my $cust_svc ( @cust_svc ) { +         my($label, $value, $svcdb) = $cust_svc->label; +         my($svcnum) = $cust_svc->svcnum; +         my($sview) = $p.'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>!; +         $n2="</TR><TR>"; +      } +      #print qq!</TR><TR>\n!; +      $n1="</TR><TR>"; +    } +    print "</TR>"; +  } +  +  print "</TABLE>$pager</BODY></HTML>"; + +} + +#undef $cache; #does this help? + +# + +sub last_sort { +  $a->getfield('last') cmp $b->getfield('last'); +} + +sub company_sort { +  return -1 if $a->company && ! $b->company; +  return 1 if ! $a->company && $b->company; +  $a->getfield('company') cmp $b->getfield('company'); +} + +sub custnum_sort { +  $a->getfield('custnum') <=> $b->getfield('custnum'); +} + +sub cardsearch { + +  my($card)=$cgi->param('card'); +  $card =~ s/\D//g; +  $card =~ /^(\d{13,16})$/ or eidiot "Illegal card number\n"; +  my($payinfo)=$1; + +  [ qsearch('cust_main',{'payinfo'=>$payinfo, 'payby'=>'CARD'}) ]; +} + +sub referralsearch { +  $cgi->param('referral_custnum') =~ /^(\d+)$/ +    or eidiot "Illegal referral_custnum"; +  my $cust_main = qsearchs('cust_main', { 'custnum' => $1 } ) +    or eidiot "Customer $1 not found"; +  my $depth; +  if ( $cgi->param('referral_depth') ) { +    $cgi->param('referral_depth') =~ /^(\d+)$/ +      or eidiot "Illegal referral_depth"; +    $depth = $1; +  } else { +    $depth = 1; +  } +  [ $cust_main->referral_cust_main($depth) ]; +} + +sub lastsearch { +  my(%last_type); +  my @cust_main; +  foreach ( $cgi->param('last_type') ) { +    $last_type{$_}++; +  } + +  $cgi->param('last_text') =~ /^([\w \,\.\-\']*)$/ +    or eidiot "Illegal last name"; +  my($last)=$1; + +#  if ( $last_type{'Exact'} +#       && ! $last_type{'Fuzzy'}  +#     #  && ! $last_type{'Sound-alike'} +#  ) { + +    push @cust_main, qsearch('cust_main',{'last'=>$last}); + +    push @cust_main, qsearch('cust_main',{'ship_last'=>$last}) +      if defined dbdef->table('cust_main')->column('ship_last'); + +#  } else { +  if ( $last_type{'Fuzzy'} ) { + +    &FS::cust_main::check_and_rebuild_fuzzyfiles; +    my $all_last = &FS::cust_main::all_last; + +    my %last; +    if ($last_type{'Fuzzy'}) {  +      foreach ( amatch($last, [ qw(i) ], @$all_last) ) { +        $last{$_}++;  +      } +    } + +    #if ($last_type{'Sound-alike'}) { +    #} + +    foreach ( keys %last ) { +      push @cust_main, qsearch('cust_main',{'last'=>$_}); +      push @cust_main, qsearch('cust_main',{'ship_last'=>$_}) +        if defined dbdef->table('cust_main')->column('ship_last'); +    } + +  } +  \@cust_main; +} + +sub companysearch { + +  my(%company_type); +  my @cust_main; +  foreach ( $cgi->param('company_type') ) { +    $company_type{$_}++  +  }; + +  $cgi->param('company_text') =~ /^([\w \,\.\-\']*)$/ +    or eidiot "Illegal company"; +  my($company)=$1; + +#  if ( $company_type{'Exact'} +#       && ! $company_type{'Fuzzy'}  +#     #  && ! $company_type{'Sound-alike'} +#  ) { + +    push @cust_main, qsearch('cust_main',{'company'=>$company}); + +    push @cust_main, qsearch('cust_main',{'ship_company'=>$company}) +      if defined dbdef->table('cust_main')->column('ship_last'); + +#  } else { +  if ( $company_type{'Fuzzy'} ) { + +    &FS::cust_main::check_and_rebuild_fuzzyfiles; +    my $all_company = &FS::cust_main::all_company; + +    my %company; +    if ($company_type{'Fuzzy'}) {  +      foreach ( amatch($company, [ qw(i) ], @$all_company ) ) { +        $company{$_}++; +      } +    } + +    #if ($company_type{'Sound-alike'}) { +    #} + +    foreach ( keys %company ) { +      push @cust_main, qsearch('cust_main',{'company'=>$_}); +      push @cust_main, qsearch('cust_main',{'ship_company'=>$_}) +        if defined dbdef->table('cust_main')->column('ship_last'); +    } + +  } + +  \@cust_main; +} +%> diff --git a/httemplate/search/cust_main.html b/httemplate/search/cust_main.html new file mode 100755 index 000000000..1e91adee9 --- /dev/null +++ b/httemplate/search/cust_main.html @@ -0,0 +1,36 @@ +<HTML> +  <HEAD> +    <TITLE>Customer Search</TITLE> +  </HEAD> +  <BODY BGCOLOR="#e8e8e8"> +    <FONT SIZE=7> +      Customer Search +    </FONT> +    <BR><BR> +    <FORM ACTION="cust_main.cgi" METHOD="post"> +      <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>Fuzzy +        <OPTION>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>Fuzzy +        <OPTION>Exact +      </SELECT> + +      <P><INPUT TYPE="submit" VALUE="Search"> Note: Fuzzy searching can take a while.  Please be patient. + +    </FORM> + +  <HR>Explanation of search methods: +  <UL> +    <LI><B>Fuzzy</B> - Searches for matches that are close to your text. +    <LI><B>Exact</B> - Finds exact matches only, but much faster than the other search methods. +  </UL> +  </BODY> +</HTML> + diff --git a/httemplate/search/cust_pay.cgi b/httemplate/search/cust_pay.cgi new file mode 100755 index 000000000..b5bdf8296 --- /dev/null +++ b/httemplate/search/cust_pay.cgi @@ -0,0 +1,103 @@ +<% + +$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, +                                     'payby'   => $payby    } ); +my $sortby = \*date_sort; + +if (0) { +#if ( scalar(@cust_pay) == 1 ) { +#  my $invnum = $cust_bill[0]->invnum; +#  print $cgi->redirect(popurl(2). "view/cust_bill.cgi?$invnum");  #redirect +} elsif ( scalar(@cust_pay) == 0 ) { +%> +<!-- mason kludge --> +<% +  idiot("Check # not found."); +  #exit; +} else { +  my $total = scalar(@cust_pay); +  my $s = $total > 1 ? 's' : ''; +%> +<!-- mason kludge --> +<% +  print header("Check # Search Results", menubar( +          'Main Menu', popurl(2) +        )), "$total matching check$s found<BR>", &table(), <<END; +      <TR> +        <TH></TH> +        <TH>Amount</TH> +        <TH>Date</TH> +        <TH>Contact name</TH> +        <TH>Company</TH> +      </TR> +END + +  my(%saw, $cust_pay); +  foreach my $cust_pay ( +    sort $sortby grep(!$saw{$_->paynum}++, @cust_pay) +  ) { +    my($paynum, $custnum, $payinfo, $amount, $date ) = ( +      $cust_pay->paynum, +      $cust_pay->custnum, +      $cust_pay->payinfo, +      sprintf("%.2f", $cust_pay->paid), +      $cust_pay->_date, +    ); +    my $pdate = time2str("%b %d %Y", $date); + +    my $rowspan = 1; + +    my $view = popurl(2). "view/cust_main.cgi?". $custnum.  +               "#". $payby. $payinfo; + +    print <<END; +      <TR> +        <TD ROWSPAN=$rowspan><A HREF="$view"><FONT SIZE=-1>$payinfo</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 +    my $cust_main = qsearchs('cust_main', { 'custnum' => $custnum } ); +    if ( $cust_main ) { +      #my $cview = popurl(2). "view/cust_main.cgi?". $cust_main->custnum; +      my ( $name, $company ) = ( +        $cust_main->last. ', '. $cust_main->first, +        $cust_main->company, +      ); +      print <<END; +        <TD ROWSPAN=$rowspan><A HREF="$view"><FONT SIZE=-1>$name</FONT></A></TD> +        <TD ROWSPAN=$rowspan><A HREF="$view"><FONT SIZE=-1>$company</FONT></A></TD> +END +    } else { +      print <<END +        <TD ROWSPAN=$rowspan COLSPAN=2>WARNING: couldn't find cust_main.custnum $custnum (cust_pay.paynum $paynum)</TD> +END +    } + +    print "</TR>"; +  } +  print <<END; +    </TABLE> +  </BODY> +</HTML> +END + +} + +# + +#sub invnum_sort { +#  $a->invnum <=> $b->invnum; +#} +# +#sub custnum_sort { +#  $a->custnum <=> $b->custnum || $a->invnum <=> $b->invnum; +#} + +sub date_sort { +  $a->_date <=> $b->_date || $a->invnum <=> $b->invnum; +} +%> diff --git a/httemplate/search/cust_pay.html b/httemplate/search/cust_pay.html new file mode 100755 index 000000000..3848d66f7 --- /dev/null +++ b/httemplate/search/cust_pay.html @@ -0,0 +1,18 @@ +<HTML> +  <HEAD> +    <TITLE>Check # Search</TITLE> +  </HEAD> +  <BODY BGCOLOR="#e8e8e8"> +    <FONT SIZE=7> +      Check # Search +    </FONT> +    <BR><BR> +    <FORM ACTION="cust_pay.cgi" METHOD="post"> +      Search for <B>check #</B>: +      <INPUT TYPE="text" NAME="payinfo"> +      <INPUT TYPE="hidden" NAME="payby" VALUE="BILL"> +      <BR><BR><INPUT TYPE="submit" VALUE="Search"> +    </FORM> +  </BODY> +</HTML> + diff --git a/httemplate/search/cust_pkg.cgi b/httemplate/search/cust_pkg.cgi new file mode 100755 index 000000000..54e02ce9d --- /dev/null +++ b/httemplate/search/cust_pkg.cgi @@ -0,0 +1,246 @@ +<% + +my $conf = new FS::Conf; +my $maxrecords = $conf->config('maxsearchrecordsperpage'); + +my %part_pkg = map { $_->pkgpart => $_ } qsearch('part_pkg', {}); + +my $limit = ''; +$limit .= "LIMIT $maxrecords" if $maxrecords; + +my $offset = $cgi->param('offset') || 0; +$limit .= " OFFSET $offset" if $offset; + +my $total; + +my $unconf = ''; +my($query) = $cgi->keywords; +my $sortby; +if ( $query eq 'pkgnum' ) { +  $sortby=\*pkgnum_sort; + +} elsif ( $query eq 'APKG_pkgnum' ) { + +  $sortby=\*pkgnum_sort; + +  $unconf = " +    WHERE 0 < +      ( SELECT count(*) FROM pkg_svc +          WHERE pkg_svc.pkgpart = cust_pkg.pkgpart +            AND pkg_svc.quantity > ( SELECT count(*) FROM cust_svc +                                       WHERE cust_svc.pkgnum = cust_pkg.pkgnum +                                         AND cust_svc.svcpart = pkg_svc.svcpart +                                   ) +      ) +  "; + +  #@cust_pkg=(); +  ##perhaps this should go in cust_pkg as a qsearch-like constructor? +  #my($cust_pkg); +  #foreach $cust_pkg ( +  #  qsearch('cust_pkg',{}, '', "ORDER BY pkgnum $limit" ) +  #) { +  #  my($flag)=0; +  #  my($pkg_svc); +  #  PKG_SVC:  +  #  foreach $pkg_svc (qsearch('pkg_svc',{ 'pkgpart' => $cust_pkg->pkgpart })) { +  #    if ( $pkg_svc->quantity  +  #         > scalar(qsearch('cust_svc',{ +  #             'pkgnum' => $cust_pkg->pkgnum, +  #             'svcpart' => $pkg_svc->svcpart, +  #           })) +  #       ) +  #    { +  #      $flag=1; +  #      last PKG_SVC; +  #    } +  #  } +  #  push @cust_pkg, $cust_pkg if $flag; +  #} +   +} else { +  die "Empty QUERY_STRING!"; +} + +my $statement = "SELECT COUNT(*) FROM cust_pkg $unconf"; +my $sth = dbh->prepare($statement) +  or die dbh->errstr. " doing $statement"; +$sth->execute or die "Error executing \"$statement\": ". $sth->errstr; + +$total = $sth->fetchrow_arrayref->[0]; + +my @cust_pkg = qsearch('cust_pkg',{}, '', "$unconf ORDER BY pkgnum $limit" ); + + +if ( scalar(@cust_pkg) == 1 ) { +  my($pkgnum)=$cust_pkg[0]->pkgnum; +  print $cgi->redirect(popurl(2). "view/cust_pkg.cgi?$pkgnum"); +  #exit; +} elsif ( scalar(@cust_pkg) == 0 ) { #error +%> +<!-- mason kludge --> +<% +  eidiot("No packages found"); +} else { +%> +<!-- mason kludge --> +<% +  $total ||= scalar(@cust_pkg); + +  #begin pager +  my $pager = ''; +  if ( $total != scalar(@cust_pkg) && $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('Package Search Results',''), +        "$total matching packages found<BR><BR>$pager", &table(), <<END; +      <TR> +        <TH>Package</TH> +        <TH><FONT SIZE=-1>Setup</FONT></TH> +        <TH><FONT SIZE=-1>Next<BR>bill</FONT></TH> +        <TH><FONT SIZE=-1>Susp.</FONT></TH> +        <TH><FONT SIZE=-1>Expire</FONT></TH> +        <TH><FONT SIZE=-1>Cancel</FONT></TH> +        <TH><FONT SIZE=-1>Cust#</FONT></TH> +        <TH>(bill) name</TH> +        <TH>company</TH> +END + +if ( defined dbdef->table('cust_main')->column('ship_last') ) { +  print <<END; +      <TH>(service) name</TH> +      <TH>company</TH> +END +} + +print <<END; +        <TH COLSPAN=2>Services</TH> +      </TR> +END + +  my $n1 = '<TR>'; +  my(%saw,$cust_pkg); +  foreach $cust_pkg ( +    sort $sortby grep(!$saw{$_->pkgnum}++, @cust_pkg) +  ) { +    my($cust_main)=qsearchs('cust_main',{'custnum'=>$cust_pkg->custnum}); +    my($pkgnum, $setup, $bill, $susp, $expire, $cancel, +       $custnum, $last, $first, $company ) = ( +      $cust_pkg->pkgnum, +      $cust_pkg->getfield('setup') +        ? time2str("%D", $cust_pkg->getfield('setup') ) +        : '', +      $cust_pkg->getfield('bill') +        ? time2str("%D", $cust_pkg->getfield('bill') ) +        : '', +      $cust_pkg->getfield('susp') +        ? time2str("%D", $cust_pkg->getfield('susp') ) +        : '', +      $cust_pkg->getfield('expire') +        ? time2str("%D", $cust_pkg->getfield('expire') ) +        : '', +      $cust_pkg->getfield('cancel') +        ? time2str("%D", $cust_pkg->getfield('cancel') ) +        : '', +      $cust_pkg->custnum, +      $cust_main ? $cust_main->last : '', +      $cust_main ? $cust_main->first : '', +      $cust_main ? $cust_main->company : '', +    ); +    my($ship_last, $ship_first, $ship_company); +    if ( defined dbdef->table('cust_main')->column('ship_last') ) { +      ($ship_last, $ship_first, $ship_company) = ( +        $cust_main +          ? ( $cust_main->ship_last || $cust_main->getfield('last') ) +          : '', +        $cust_main  +          ? ( $cust_main->ship_last +              ? $cust_main->ship_first +              : $cust_main->first ) +          : '', +        $cust_main  +          ? ( $cust_main->ship_last +              ? $cust_main->ship_company +              : $cust_main->company ) +          : '', +      ); +    } +    my $pkg = $part_pkg{$cust_pkg->pkgpart}->pkg; +    #$pkg .= ' - '. $part_pkg{$cust_pkg->pkgpart}->comment; +    my @cust_svc = qsearch( 'cust_svc', { 'pkgnum' => $pkgnum } ); +    my $rowspan = scalar(@cust_svc) || 1; +    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> +END +    if ( $cust_main ) { +      print <<END; +      <TD ROWSPAN=$rowspan><FONT SIZE=-1><A HREF="${p}view/cust_main.cgi?$custnum">$custnum</A></FONT></TD> +      <TD ROWSPAN=$rowspan><FONT SIZE=-1><A HREF="${p}view/cust_main.cgi?$custnum">$last, $first</A></FONT></TD> +      <TD ROWSPAN=$rowspan><FONT SIZE=-1><A HREF="${p}view/cust_main.cgi?$custnum">$company</A></FONT></TD> +END +      if ( defined dbdef->table('cust_main')->column('ship_last') ) { +        print <<END; +      <TD ROWSPAN=$rowspan><FONT SIZE=-1><A HREF="${p}view/cust_main.cgi?$custnum">$ship_last, $ship_first</A></FONT></TD> +      <TD ROWSPAN=$rowspan><FONT SIZE=-1><A HREF="${p}view/cust_main.cgi?$custnum">$ship_company</A></FONT></TD> +END +      } +    } else { +      my $colspan = defined dbdef->table('cust_main')->column('ship_last') +                    ? 5 : 3; +      print <<END; +      <TD ROWSPAN=$rowspan COLSPAN=$colspan>WARNING: couldn't find cust_main.custnum $custnum (cust_pkg.pkgnum $pkgnum)</TD> +END +    } + +    my $n2 = ''; +    foreach my $cust_svc ( @cust_svc ) { +      my($label, $value, $svcdb) = $cust_svc->label; +      my $svcnum = $cust_svc->svcnum; +      my $sview = $p. "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>!; +      $n2="</TR><TR>"; +    } + +    $n1 = "</TR><TR>"; + +  } +    print '</TR>'; +  +  print "</TABLE>$pager</BODY></HTML>"; + +} + +sub pkgnum_sort { +  $a->getfield('pkgnum') <=> $b->getfield('pkgnum'); +} + +%> diff --git a/httemplate/search/svc_acct.cgi b/httemplate/search/svc_acct.cgi new file mode 100755 index 000000000..0918275d7 --- /dev/null +++ b/httemplate/search/svc_acct.cgi @@ -0,0 +1,244 @@ +<% + +my $mydomain = ''; + +my $conf = new FS::Conf; +my $maxrecords = $conf->config('maxsearchrecordsperpage'); + +my $orderby = ''; #removeme + +my $limit = ''; +$limit .= "LIMIT $maxrecords" if $maxrecords; + +my $offset = $cgi->param('offset') || 0; +$limit .= " OFFSET $offset" if $offset; + +my $total; + +my($query)=$cgi->keywords; +$query ||= ''; #to avoid use of unitialized value errors + +my $unlinked = ''; +if ( $query =~ /^UN_(.*)$/ ) { +  $query = $1; +  $unlinked = ' +    WHERE 0 < +      ( SELECT count(*) FROM cust_svc +          WHERE cust_svc.svcnum = svc_acct.svcnum +            AND pkgnum IS NULL +      ) +  '; +} + +my(@svc_acct, $sortby); +if ( $query eq 'svcnum' ) { +  $sortby=\*svcnum_sort; +  $orderby = 'ORDER BY svcnum'; +} elsif ( $query eq 'username' ) { +  $sortby=\*username_sort; +  $orderby = 'ORDER BY username'; +} elsif ( $query eq 'uid' ) { +  $sortby=\*uid_sort; +  $orderby = ( $unlinked ? 'AND' : 'WHERE' ). ' uid IS NOT NULL ORDER BY uid'; +} else { +  $sortby=\*uid_sort; +  @svc_acct = @{&usernamesearch}; +} + +if ( $query eq 'svcnum' || $query eq 'username' || $query eq 'uid' ) { + +  my $statement = "SELECT COUNT(*) FROM svc_acct $unlinked"; +  my $sth = dbh->prepare($statement) +    or die dbh->errstr. " doing $statement"; +  $sth->execute or die "Error executing \"$statement\": ". $sth->errstr; + +  $total = $sth->fetchrow_arrayref->[0]; + +  @svc_acct = qsearch('svc_acct', {}, '', "$unlinked $orderby $limit"); + +} + +if ( scalar(@svc_acct) == 1 ) { +  my($svcnum)=$svc_acct[0]->svcnum; +  print $cgi->redirect(popurl(2). "view/svc_acct.cgi?$svcnum");  #redirect +  #exit; +} elsif ( scalar(@svc_acct) == 0 ) { #error +%> +<!-- mason kludge --> +<% +  idiot("Account not found"); +} else { +%> +<!-- mason kludge --> +<% +  $total ||= scalar(@svc_acct); + +  #begin pager +  my $pager = ''; +  if ( $total != scalar(@svc_acct) && $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("Account Search Results",''), +        "$total matching accounts found<BR><BR>$pager", +        &table(), <<END; +      <TR> +        <TH><FONT SIZE=-1>#</FONT></TH> +        <TH><FONT SIZE=-1>Username</FONT></TH> +        <TH><FONT SIZE=-1>Domain</FONT></TH> +        <TH><FONT SIZE=-1>UID</FONT></TH> +        <TH><FONT SIZE=-1>Service</FONT></TH> +        <TH><FONT SIZE=-1>Cust#</FONT></TH> +        <TH><FONT SIZE=-1>(bill) name</FONT></TH> +        <TH><FONT SIZE=-1>company</FONT></TH> +END +  if ( defined dbdef->table('cust_main')->column('ship_last') ) { +    print <<END; +        <TH><FONT SIZE=-1>(service) name</FONT></TH> +        <TH><FONT SIZE=-1>company</FONT></TH> +END +  } +  print "</TR>"; + +  my(%saw,$svc_acct); +  my $p = popurl(2); +  foreach $svc_acct ( +    sort $sortby grep(!$saw{$_->svcnum}++, @svc_acct) +  ) { +    my $cust_svc = qsearchs('cust_svc', { 'svcnum' => $svc_acct->svcnum }) +      or die "No cust_svc record for svcnum ". $svc_acct->svcnum; +    my $part_svc = qsearchs('part_svc', { 'svcpart' => $cust_svc->svcpart }) +      or die "No part_svc record for svcpart ". $cust_svc->svcpart; + +    my $domain; +    my $svc_domain = qsearchs('svc_domain', { 'svcnum' => $svc_acct->domsvc }); +    if ( $svc_domain ) { +      $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: ". $cust_svc->domsvc; +        } +      } +      $domain = "<i>$mydomain</i><FONT COLOR=\"#FF0000\">*</FONT>"; +    } +    my($cust_pkg,$cust_main); +    if ( $cust_svc->pkgnum ) { +      $cust_pkg = qsearchs('cust_pkg', { 'pkgnum' => $cust_svc->pkgnum }) +        or die "No cust_pkg record for pkgnum ". $cust_svc->pkgnum; +      $cust_main = qsearchs('cust_main', { 'custnum' => $cust_pkg->custnum }) +        or die "No cust_main record for custnum ". $cust_pkg->custnum; +    } +    my($svcnum, $username, $uid, $svc, $custnum, $last, $first, $company) = ( +      $svc_acct->svcnum, +      $svc_acct->getfield('username'), +      $svc_acct->getfield('uid'), +      $part_svc->svc, +      $cust_svc->pkgnum ? $cust_main->custnum : '', +      $cust_svc->pkgnum ? $cust_main->getfield('last') : '', +      $cust_svc->pkgnum ? $cust_main->getfield('first') : '', +      $cust_svc->pkgnum ? $cust_main->company : '', +    ); +    my($pcustnum) = $custnum +      ? "<A HREF=\"${p}view/cust_main.cgi?$custnum\"><FONT SIZE=-1>$custnum</FONT></A>" +      : "<I>(unlinked)</I>" +    ; +    my $pname = $custnum ? "<A HREF=\"${p}view/cust_main.cgi?$custnum\">$last, $first</A>" : ''; +    my $pcompany = $custnum ? "<A HREF=\"${p}view/cust_main.cgi?$custnum\">$company</A>" : ''; +    my($pship_name, $pship_company); +    if ( defined dbdef->table('cust_main')->column('ship_last') ) { +      my($ship_last, $ship_first, $ship_company) = ( +        $cust_svc->pkgnum ? ( $cust_main->ship_last || $last ) : '', +        $cust_svc->pkgnum ? ( $cust_main->ship_last +                              ? $cust_main->ship_first +                              : $first +                            ) : '', +        $cust_svc->pkgnum ? ( $cust_main->ship_last +                              ? $cust_main->ship_company +                              : $company +                            ) : '', +      ); +      $pship_name = $custnum ? "<A HREF=\"${p}view/cust_main.cgi?$custnum\">$ship_last, $ship_first</A>" : ''; +      $pship_company = $custnum ? "<A HREF=\"${p}view/cust_main.cgi?$custnum\">$ship_company</A>" : ''; +    } +    print <<END; +    <TR> +      <TD><A HREF="${p}view/svc_acct.cgi?$svcnum"><FONT SIZE=-1>$svcnum</FONT></A></TD> +      <TD><A HREF="${p}view/svc_acct.cgi?$svcnum"><FONT SIZE=-1>$username</FONT></A></TD> +      <TD><FONT SIZE=-1>$domain</FONT></TD> +      <TD><A HREF="${p}view/svc_acct.cgi?$svcnum"><FONT SIZE=-1>$uid</FONT></A></TD> +      <TD><FONT SIZE=-1>$svc</FONT></TH> +      <TD><FONT SIZE=-1>$pcustnum</FONT></TH> +      <TD><FONT SIZE=-1>$pname<FONT></TH> +      <TD><FONT SIZE=-1>$pcompany</FONT></TH> +END +    if ( defined dbdef->table('cust_main')->column('ship_last') ) { +      print <<END; +      <TD><FONT SIZE=-1>$pship_name<FONT></TH> +      <TD><FONT SIZE=-1>$pship_company</FONT></TH> +END +    } +    print "</TR>"; + +  } +  +  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>'; + +} + +sub svcnum_sort { +  $a->getfield('svcnum') <=> $b->getfield('svcnum'); +} + +sub username_sort { +  $a->getfield('username') cmp $b->getfield('username'); +} + +sub uid_sort { +  $a->getfield('uid') <=> $b->getfield('uid'); +} + +sub usernamesearch { + +  $cgi->param('username') =~ /^([\w\d\-]+)$/; #untaint username_text +  my($username)=$1; + +  [ qsearch('svc_acct',{'username'=>$username}) ]; + +} + +%> diff --git a/httemplate/search/svc_acct.html b/httemplate/search/svc_acct.html new file mode 100755 index 000000000..742360596 --- /dev/null +++ b/httemplate/search/svc_acct.html @@ -0,0 +1,19 @@ +<HTML> +  <HEAD> +    <TITLE>Account Search</TITLE> +  </HEAD> +  <BODY BGCOLOR="#e8e8e8"> +    <FONT SIZE=7> +      Account Search +    </FONT> +    <BR><BR> +    <FORM ACTION="svc_acct.cgi" METHOD="post"> +      Search for <B>username</B>:  +      <INPUT TYPE="text" NAME="username"> + +      <P><INPUT TYPE="submit" VALUE="Search"> + +    </FORM> +  </BODY> +</HTML> + diff --git a/httemplate/search/svc_acct_sm.cgi b/httemplate/search/svc_acct_sm.cgi new file mode 100755 index 000000000..4ee300612 --- /dev/null +++ b/httemplate/search/svc_acct_sm.cgi @@ -0,0 +1,84 @@ +<% + +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 new file mode 100755 index 000000000..0719856db --- /dev/null +++ b/httemplate/search/svc_acct_sm.html @@ -0,0 +1,23 @@ +<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 new file mode 100755 index 000000000..fb372db14 --- /dev/null +++ b/httemplate/search/svc_domain.cgi @@ -0,0 +1,163 @@ +<% + +my $conf = new FS::Conf; +my $mydomain = $conf->config('domain'); + +my($query)=$cgi->keywords; +$query ||= ''; #to avoid use of unitialized value errors +my(@svc_domain,$sortby); +if ( $query eq 'svcnum' ) { +  $sortby=\*svcnum_sort; +  @svc_domain=qsearch('svc_domain',{}); +} elsif ( $query eq 'domain' ) { +  $sortby=\*domain_sort; +  @svc_domain=qsearch('svc_domain',{}); +} elsif ( $query eq 'UN_svcnum' ) { +  $sortby=\*svcnum_sort; +  @svc_domain = grep qsearchs('cust_svc',{ +      'svcnum' => $_->svcnum, +      'pkgnum' => '', +    }), qsearch('svc_domain',{}); +} elsif ( $query eq 'UN_domain' ) { +  $sortby=\*domain_sort; +  @svc_domain = grep qsearchs('cust_svc',{ +      'svcnum' => $_->svcnum, +      'pkgnum' => '', +    }), qsearch('svc_domain',{}); +} else { +  $cgi->param('domain') =~ /^([\w\-\.]+)$/;  +  my($domain)=$1; +  #push @svc_domain, qsearchs('svc_domain',{'domain'=>$domain}); +  @svc_domain = qsearchs('svc_domain',{'domain'=>$domain}); +} + +if ( scalar(@svc_domain) == 1 ) { +  print $cgi->redirect(popurl(2). "view/svc_domain.cgi?". $svc_domain[0]->svcnum); +  #exit; +} elsif ( scalar(@svc_domain) == 0 ) { +%> +<!-- mason kludge --> +<% +  eidiot "No matching domains found!\n"; +} else { +%> +<!-- mason kludge --> +<% +  my($total)=scalar(@svc_domain); +  print header("Domain Search Results",''), <<END; + +    $total matching domains found +    <TABLE BORDER=4 CELLSPACING=0 CELLPADDING=0> +      <TR> +        <TH>Service #</TH> +        <TH>Domain</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 + +#  my(%saw);                 # if we've multiple domains with the same +                             # svcnum, then we've a corrupt database + +  foreach my $svc_domain ( +#    sort $sortby grep(!$saw{$_->svcnum}++, @svc_domain) +    sort $sortby (@svc_domain) +  ) { +    my($svcnum,$domain)=( +      $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>| +    #  ); +    #} else { +    #  $malias=''; +    #} + +    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 + + + +    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> +END + +    print @rows; +    print "</TR>"; + +  } +  +  print <<END; +    </TABLE> +  </BODY> +</HTML> +END + +} + +sub svcnum_sort { +  $a->getfield('svcnum') <=> $b->getfield('svcnum'); +} + +sub domain_sort { +  $a->getfield('domain') cmp $b->getfield('domain'); +} + + +%> diff --git a/httemplate/search/svc_domain.html b/httemplate/search/svc_domain.html new file mode 100755 index 000000000..94bb9a66d --- /dev/null +++ b/httemplate/search/svc_domain.html @@ -0,0 +1,19 @@ +<HTML> +  <HEAD> +    <TITLE>Domain Search</TITLE> +  </HEAD> +  <BODY BGCOLOR="#e8e8e8"> +    <FONT SIZE=7> +      Domain Search +    </FONT> +    <BR><BR> +    <FORM ACTION="svc_domain.cgi" METHOD="post"> +      Search for <B>domain</B>:  +      <INPUT TYPE="text" NAME="domain"> + +      <P><INPUT TYPE="submit" VALUE="Search"> + +    </FORM> +  </BODY> +</HTML> + diff --git a/httemplate/view/cust_bill.cgi b/httemplate/view/cust_bill.cgi new file mode 100755 index 000000000..7c2af06a7 --- /dev/null +++ b/httemplate/view/cust_bill.cgi @@ -0,0 +1,42 @@ +<!-- mason kludge --> +<% + +#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; +my $custnum = $cust_bill->getfield('custnum'); + +#my $printed = $cust_bill->printed; + +print header('Invoice View', menubar( +  "Main Menu" => $p, +  "View this customer (#$custnum)" => "${p}view/cust_main.cgi?$custnum", +)); + +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>'; + +foreach my $cust_bill_event ( +  sort { $a->_date <=> $b->_date } $cust_bill->cust_bill_event +) { +  print time2str("%a %b %e %T %Y", $cust_bill_event->_date). ' - '. +        $cust_bill_event->part_bill_event->event. '<BR>'; +} +print '<BR><PRE>'; + +print $cust_bill->print_text; + +	#formatting +	print <<END; +    </PRE></FONT> +  </BODY> +</HTML> +END + +%> diff --git a/httemplate/view/cust_main.cgi b/httemplate/view/cust_main.cgi new file mode 100755 index 000000000..90299e4dd --- /dev/null +++ b/httemplate/view/cust_main.cgi @@ -0,0 +1,528 @@ +<!-- mason kludge --> +<% + +my $conf = new FS::Conf; + +print header("Customer View", menubar( +  'Main Menu' => popurl(2) +)); + +die "No customer specified (bad URL)!" unless $cgi->keywords; +my($query) = $cgi->keywords; # needs parens with my, ->keywords returns array +$query =~ /^(\d+)$/; +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>! +  if $conf->exists('deletecustomers'); + +unless ( $conf->exists('disable_customer_referrals') ) { +  print qq! | <A HREF="!, popurl(2), +        qq!edit/cust_main.cgi?referral_custnum=$custnum">!, +        qq!Refer a new customer</A>!; + +  print qq! | <A HREF="!, popurl(2), +        qq!search/cust_main.cgi?referral_custnum=$custnum">!, +        qq!View this customer's referrals</A>!; +} + +print '<BR><BR>'; + +my $signupurl = $conf->config('signupurl'); +if ( $signupurl ) { +print "This customer's signup URL: ". +      "<a href=\"$signupurl?ref=$custnum\">$signupurl?ref=$custnum</a><BR><BR>"; +} + +print '<A NAME="cust_main"></A>'; + +print &itable(), '<TR>'; + +print '<TD VALIGN="top">'; + +  print "Billing address", &ntable("#cccccc"), "<TR><TD>", +        &ntable("#cccccc",2), +    '<TR><TD ALIGN="right">Contact name</TD>', +      '<TD COLSPAN=3 BGCOLOR="#ffffff">', +      $cust_main->last, ', ', $cust_main->first, +      '</TD>'; +print '<TD ALIGN="right">SS#</TD><TD BGCOLOR="#ffffff">', +      $cust_main->ss || ' ', '</TD>' +  if $conf->exists('show_ss'); + +print '</TR>', +    '<TR><TD ALIGN="right">Company</TD><TD COLSPAN=5 BGCOLOR="#ffffff">', +      $cust_main->company, +      '</TD></TR>', +    '<TR><TD ALIGN="right">Address</TD><TD COLSPAN=5 BGCOLOR="#ffffff">', +      $cust_main->address1, +      '</TD></TR>', +  ; +  print '<TR><TD ALIGN="right"> </TD><TD COLSPAN=5 BGCOLOR="#ffffff">', +        $cust_main->address2, '</TD></TR>' +    if $cust_main->address2; +  print '<TR><TD ALIGN="right">City</TD><TD BGCOLOR="#ffffff">', +          $cust_main->city, +          '</TD><TD ALIGN="right">State</TD><TD BGCOLOR="#ffffff">', +          $cust_main->state, +          '</TD><TD ALIGN="right">Zip</TD><TD BGCOLOR="#ffffff">', +          $cust_main->zip, '</TD></TR>', +        '<TR><TD ALIGN="right">Country</TD><TD BGCOLOR="#ffffff">', +          $cust_main->country, +          '</TD></TR>', +  ; +  print '<TR><TD ALIGN="right">Day Phone</TD><TD COLSPAN=5 BGCOLOR="#ffffff">', +          $cust_main->daytime || ' ', '</TD></TR>', +       '<TR><TD ALIGN="right">Night Phone</TD><TD COLSPAN=5 BGCOLOR="#ffffff">', +          $cust_main->night || ' ', '</TD></TR>', +        '<TR><TD ALIGN="right">Fax</TD><TD COLSPAN=5 BGCOLOR="#ffffff">', +          $cust_main->fax || ' ', '</TD></TR>', +        '</TABLE>', "</TD></TR></TABLE>" +  ; + +  if ( defined $cust_main->dbdef_table->column('ship_last') ) { + +    my $pre = $cust_main->ship_last ? 'ship_' : ''; + +    print "<BR>Service address", &ntable("#cccccc"), "<TR><TD>", +          &ntable("#cccccc",2), +      '<TR><TD ALIGN="right">Contact name</TD>', +        '<TD COLSPAN=5 BGCOLOR="#ffffff">', +        $cust_main->get("${pre}last"), ', ', $cust_main->get("${pre}first"), +        '</TD></TR>', +      '<TR><TD ALIGN="right">Company</TD><TD COLSPAN=5 BGCOLOR="#ffffff">', +        $cust_main->get("${pre}company"), +        '</TD></TR>', +      '<TR><TD ALIGN="right">Address</TD><TD COLSPAN=5 BGCOLOR="#ffffff">', +        $cust_main->get("${pre}address1"), +        '</TD></TR>', +    ; +    print '<TR><TD ALIGN="right"> </TD><TD COLSPAN=5 BGCOLOR="#ffffff">', +          $cust_main->get("${pre}address2"), '</TD></TR>' +      if $cust_main->get("${pre}address2"); +    print '<TR><TD ALIGN="right">City</TD><TD BGCOLOR="#ffffff">', +            $cust_main->get("${pre}city"), +            '</TD><TD ALIGN="right">State</TD><TD BGCOLOR="#ffffff">', +            $cust_main->get("${pre}state"), +            '</TD><TD ALIGN="right">Zip</TD><TD BGCOLOR="#ffffff">', +            $cust_main->get("${pre}zip"), '</TD></TR>', +          '<TR><TD ALIGN="right">Country</TD><TD BGCOLOR="#ffffff">', +            $cust_main->get("${pre}country"), +            '</TD></TR>', +    ; +    print '<TR><TD ALIGN="right">Day Phone</TD>', +          '<TD COLSPAN=5 BGCOLOR="#ffffff">', +            $cust_main->get("${pre}daytime") || ' ', '</TD></TR>', +          '<TR><TD ALIGN="right">Night Phone</TD>'. +          '<TD COLSPAN=5 BGCOLOR="#ffffff">', +            $cust_main->get("${pre}night") || ' ', '</TD></TR>', +          '<TR><TD ALIGN="right">Fax</TD><TD COLSPAN=5 BGCOLOR="#ffffff">', +            $cust_main->get("${pre}fax") || ' ', '</TD></TR>', +          '</TABLE>', "</TD></TR></TABLE>" +    ; + +  } + +print '</TD>'; + +print '<TD VALIGN="top">'; + +  print &ntable("#cccccc"), "<TR><TD>", &ntable("#cccccc",2), +        '<TR><TD ALIGN="right">Customer number</TD><TD BGCOLOR="#ffffff">', +        $custnum, '</TD></TR>', +  ; + +  my @agents = qsearch( 'agent', {} ); +  my $agent; +  unless ( scalar(@agents) == 1 ) { +    $agent = qsearchs('agent',{ 'agentnum' => $cust_main->agentnum } ); +    print '<TR><TD ALIGN="right">Agent</TD><TD BGCOLOR="#ffffff">', +        $agent->agentnum, ": ", $agent->agent, '</TD></TR>'; +  } else { +    $agent = $agents[0]; +  } +  my @referrals = qsearch( 'part_referral', {} ); +  unless ( scalar(@referrals) == 1 ) { +    my $referral = qsearchs('part_referral', { +      'refnum' => $cust_main->refnum +    } ); +    print '<TR><TD ALIGN="right">Referral</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">'; +  my $referring_cust_main = ''; +  if ( $cust_main->referral_custnum +       && ( $referring_cust_main = +            qsearchs('cust_main', { custnum => $cust_main->referral_custnum } ) +          ) +     ) { +    print '<A HREF="'. popurl(1). 'cust_main.cgi?'. +          $cust_main->referral_custnum. '">'. +          $cust_main->referral_custnum. ': '. +          ( $referring_cust_main->company +              ? $referring_cust_main->company. ' ('. +                  $referring_cust_main->last. ', '. $referring_cust_main->first. +                  ')' +              : $referring_cust_main->last. ', '. $referring_cust_main->first +          ). +          '</A>'; +  } +  print '</TD></TR>'; + +  print '</TABLE></TD></TR></TABLE>'; + +print '<BR>'; + +  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">', +        $cust_main->tax ? 'yes' : 'no', +        '</TD></TR>', +        '<TR><TD ALIGN="right">Postal invoices</TD><TD BGCOLOR="#ffffff">', +        ( grep { $_ eq 'POST' } @invoicing_list ) ? 'yes' : 'no', +        '</TD></TR>', +        '<TR><TD ALIGN="right">Email invoices</TD><TD BGCOLOR="#ffffff">', +        join(', ', grep { $_ ne 'POST' } @invoicing_list ) || 'no', +        '</TD></TR>', +        '<TR><TD ALIGN="right">Billing type</TD><TD BGCOLOR="#ffffff">', +  ; + +  if ( $cust_main->payby eq 'CARD' ) { +    print 'Credit card</TD></TR>', +          '<TR><TD ALIGN="right">Card number</TD><TD BGCOLOR="#ffffff">', +          $cust_main->payinfo, '</TD></TR>', +          '<TR><TD ALIGN="right">Expiration</TD><TD BGCOLOR="#ffffff">', +          $cust_main->paydate, '</TD></TR>', +          '<TR><TD ALIGN="right">Name on card</TD><TD BGCOLOR="#ffffff">', +          $cust_main->payname, '</TD></TR>' +    ; +  } elsif ( $cust_main->payby eq 'BILL' ) { +    print 'Billing</TD></TR>'; +    print '<TR><TD ALIGN="right">P.O. </TD><TD BGCOLOR="#ffffff">', +          $cust_main->payinfo, '</TD></TR>', +      if $cust_main->payinfo; +    print '<TR><TD ALIGN="right">Expiration</TD><TD BGCOLOR="#ffffff">', +          $cust_main->paydate, '</TD></TR>', +          '<TR><TD ALIGN="right">Attention</TD><TD BGCOLOR="#ffffff">', +          $cust_main->payname, '</TD></TR>', +    ; +  } elsif ( $cust_main->payby eq 'COMP' ) { +    print 'Complimentary</TD></TR>', +          '<TR><TD ALIGN="right">Authorized by</TD><TD BGCOLOR="#ffffff">', +          $cust_main->payinfo, '</TD></TR>', +          '<TR><TD ALIGN="right">Expiration</TD><TD BGCOLOR="#ffffff">', +          $cust_main->paydate, '</TD></TR>', +    ; +  } + +  print "</TABLE></TD></TR></TABLE>"; + +print '</TD></TR></TABLE>'; + +if ( defined $cust_main->dbdef_table->column('comments') +     && $cust_main->comments ) +{ +  print "<BR>Comments", &ntable("#cccccc"), "<TR><TD>", +        &ntable("#cccccc",2), +        '<TR><TD BGCOLOR="#ffffff"><PRE>', $cust_main->comments, +        '</PRE></TD></TR></TABLE></TABLE>'; +} + +print '</TD></TR></TABLE>'; + +print '<BR>'. +  '<FORM ACTION="'.popurl(2).'edit/process/quick-cust_pkg.cgi" METHOD="POST">'. +  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. ' - '. +        $part_pkg->comment; +} + +print '</SELECT><INPUT TYPE="submit" VALUE="Order Package"><BR>'; + +print qq!<BR><A NAME="cust_pkg">Packages</A> !, +#      qq!<BR>Click on package number to view/edit package.!, +      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!; + +#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 @cust_svc = qsearch( 'cust_svc', { 'pkgnum' => $pkgnum } ); +  my $rowspan = scalar(@cust_svc) || 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!, +        qq! ( <A HREF="$pkgview">Edit</A> | <A HREF="$button_url">Customize pricing</A> )</FONT></TD>!, +  ; +  for ( qw( setup bill susp expire cancel ) ) { +    print "<TD ROWSPAN=$rowspan><FONT SIZE=-1>", ( $package->getfield($_) +            ? time2str("%D", $package->getfield($_) ) +            :  ' ' +          ), '</FONT></TD>', +    ; +  } + +  my $n2 = ''; +  foreach my $cust_svc ( @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>!; +     $n2="</TR><TR>"; +  } +  $n1="</TR><TR>"; +}   +print "</TR>"; + +#formatting +print "</TABLE>"; + +print <<END; +<SCRIPT> +function areyousure(href) { +    if (confirm("Are you sure you want to delete this payment?") + == 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, +                      ); +    $payinfo = substr($payinfo,0,4). 'x'x(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: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"; +  } + +  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"; +  } +} + +my @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"; +} + +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 = substr($payinfo,0,4). 'x'x(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: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"; +} + +        #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 ? "- \$".sprintf("%.2f",$payment) : '' ), +        "</FONT></TD>", +	"<TD><FONT SIZE=-1>", +        ( $credit ? "- \$".sprintf("%.2f",$credit) : '' ), +        "</FONT></TD>", +	"<TD><FONT SIZE=-1>", +        ( $refund ? "\$".sprintf("%.2f",$refund) : '' ), +        "</FONT></TD>", +	"<TD><FONT SIZE=-1>\$" . $balance, +        "</FONT></TD>", +        "\n"; +} + +#formatting +print "</TABLE>"; + +#end + +#formatting +print <<END; + +  </BODY> +</HTML> +END + +#subroutiens +sub keyfield_numerically { (split(/\t/,$a))[0] <=> (split(/\t/,$b))[0] ; } + +%> diff --git a/httemplate/view/cust_pkg.cgi b/httemplate/view/cust_pkg.cgi new file mode 100755 index 000000000..e9670cd22 --- /dev/null +++ b/httemplate/view/cust_pkg.cgi @@ -0,0 +1,153 @@ +<!-- mason kludge --> +<% + +my %uiview = (); +my %uiadd = (); +foreach my $part_svc ( qsearch('part_svc',{}) ) { +  $uiview{$part_svc->svcpart} = popurl(2). "view/". $part_svc->svcdb . ".cgi"; +  $uiadd{$part_svc->svcpart}= popurl(2). "edit/". $part_svc->svcdb . ".cgi"; +} + +my ($query) = $cgi->keywords; +$query =~ /^(\d+)$/; +my $pkgnum = $1; + +#get package record +my $cust_pkg = qsearchs('cust_pkg',{'pkgnum'=>$pkgnum}); +die "No package!" unless $cust_pkg; +my $part_pkg = qsearchs('part_pkg',{'pkgpart'=>$cust_pkg->getfield('pkgpart')}); + +my $custnum = $cust_pkg->getfield('custnum'); +print header('Package View', menubar( +  "View this customer (#$custnum)" => popurl(2). "view/cust_main.cgi?$custnum", +  'Main Menu' => popurl(2) +)); + +#print info +my ($susp,$cancel,$expire)=( +  $cust_pkg->getfield('susp'), +  $cust_pkg->getfield('cancel'), +  $cust_pkg->getfield('expire'), +); +my($pkg,$comment)=($part_pkg->getfield('pkg'),$part_pkg->getfield('comment')); +my($setup,$bill)=($cust_pkg->getfield('setup'),$cust_pkg->getfield('bill')); +my $otaker = $cust_pkg->getfield('otaker'); + +print <<END; +<SCRIPT> +function areyousure(href) { +    if (confirm("Permanantly delete included services and cancel this package?") == true) +        window.location.href = href; +} +</SCRIPT> +END + +print "Package information"; +print ' (<A HREF="'. popurl(2). 'misc/unsusp_pkg.cgi?'. $pkgnum. +      '">unsuspend</A>)' +  if ( $susp && ! $cancel ); + +print ' (<A HREF="'. popurl(2). 'misc/susp_pkg.cgi?'. $pkgnum. +      '">suspend</A>)' +  unless ( $susp || $cancel ); + +print ' (<A HREF="javascript:areyousure(\''. popurl(2). 'misc/cancel_pkg.cgi?'. +      $pkgnum.  '\')">cancel</A>)' +  unless $cancel; + +print ' (<A HREF="'. popurl(2). 'edit/REAL_cust_pkg.cgi?'. $pkgnum. +      '">edit dates</A>)'; + +print &ntable("#cccccc"), '<TR><TD>', &ntable("#cccccc",2), +      '<TR><TD ALIGN="right">Package number</TD><TD BGCOLOR="#ffffff">', +      $pkgnum, '</TD></TR>', +      '<TR><TD ALIGN="right">Package</TD><TD BGCOLOR="#ffffff">', +      $pkg,  '</TD></TR>', +      '<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) : " " ), '</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">', +       time2str("%D",$expire), '</TD></TR>' if $expire; +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 + +unless ($cancel) { + +  #services +  print '<BR>Service Information', &table(); + +  #list of services this pkgpart includes +  my $pkg_svc; +  my %pkg_svc = (); +  foreach $pkg_svc ( qsearch('pkg_svc',{'pkgpart'=> $cust_pkg->pkgpart }) ) { +    $pkg_svc{$pkg_svc->svcpart} = $pkg_svc->quantity if $pkg_svc->quantity; +  } + +  #list of records from cust_svc +  my $svcpart; +  foreach $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, +                                     }); + +    my($enum); +    for $enum ( 1 .. $pkg_svc{$svcpart} ) { + +      my($cust_svc); +      if ( $cust_svc=shift @cust_svc ) { +        my($svcnum)=$cust_svc->svcnum; +        my($label, $value, $svcdb) = $cust_svc->label; +        print <<END; +<TR><TD><A HREF="$uiview{$svcpart}?$svcnum">(View) $svc: $value<A></TD></TR> +END +      } else { +        print <<END; +<TR> +  <TD><A HREF="$uiadd{$svcpart}?pkgnum$pkgnum-svcpart$svcpart"> +      (Add) $svc</A> +   or <A HREF="../misc/link.cgi?pkgnum$pkgnum-svcpart$svcpart"> +      (Link to existing) $svc</A> +  </TD> +</TR> +END +      } + +    } +    warn "WARNING: Leftover services pkgnum $pkgnum!" if @cust_svc;;  +  } + +  print "</TABLE><FONT SIZE=-1>", +        "Choose (View) to view or edit an existing service<BR>", +        "Choose (Add) to setup a new service<BR>", +        "Choose (Link to existing) to link to a legacy (pre-Freeside) service", +        "</FONT>" +  ; +} + +#formatting +print <<END; +  </BODY> +</HTML> +END + +%> diff --git a/httemplate/view/svc_acct.cgi b/httemplate/view/svc_acct.cgi new file mode 100755 index 000000000..90ca1a240 --- /dev/null +++ b/httemplate/view/svc_acct.cgi @@ -0,0 +1,134 @@ +<!-- mason kludge --> +<% + +my $conf = new FS::Conf; +my $mydomain = $conf->config('domain'); + +my($query) = $cgi->keywords; +$query =~ /^(\d+)$/; +my $svcnum = $1; +my $svc_acct = qsearchs('svc_acct',{'svcnum'=>$svcnum}); +die "Unknown svcnum" unless $svc_acct; + +#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 $part_svc = qsearchs('part_svc',{'svcpart'=> $cust_svc->svcpart } ); +die "Unknown svcpart" unless $part_svc; + +my $domain; +if ( $svc_acct->domsvc ) { +  my $svc_domain = qsearchs('svc_domain', { 'svcnum' => $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; +} + +print header('Account 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, +)); + +#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>!. +      &ntable("#cccccc"). '<TR><TD>'. &ntable("#cccccc",2). +      "<TR><TD ALIGN=\"right\">Service number</TD>". +        "<TD BGCOLOR=\"#ffffff\">$svcnum</TD></TR>". +      "<TR><TD ALIGN=\"right\">Service</TD>". +        "<TD BGCOLOR=\"#ffffff\">". $part_svc->svc. "</TD></TR>". +      "<TR><TD ALIGN=\"right\">Username</TD>". +        "<TD BGCOLOR=\"#ffffff\">". $svc_acct->username. "</TD></TR>" +; + +print "<TR><TD ALIGN=\"right\">Domain</TD>". +        "<TD BGCOLOR=\"#ffffff\">". $domain, "</TD></TR>"; + +print "<TR><TD ALIGN=\"right\">Password</TD><TD BGCOLOR=\"#ffffff\">"; +my $password = $svc_acct->_password; +if ( $password =~ /^\*\w+\* (.*)$/ ) { +  $password = $1; +  print "<I>(login disabled)</I> "; +} +if ( $conf->exists('showpasswords') ) { +  print "$password"; +} else { +  print "<I>(hidden)</I>"; +} +print "</TR></TD>"; +$password = ''; + +my $svc_acct_pop = 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; + +if ($svc_acct->uid ne '') { +  print "<TR><TD ALIGN=\"right\">Uid</TD>". +          "<TD BGCOLOR=\"#ffffff\">". $svc_acct->uid. "</TD></TR>", +        "<TR><TD ALIGN=\"right\">Gid</TD>". +          "<TD BGCOLOR=\"#ffffff\">". $svc_acct->gid. "</TD></TR>", +        "<TR><TD ALIGN=\"right\">GECOS</TD>". +          "<TD BGCOLOR=\"#ffffff\">". $svc_acct->finger. "</TD></TR>", +        "<TR><TD ALIGN=\"right\">Home directory</TD>". +          "<TD BGCOLOR=\"#ffffff\">". $svc_acct->dir. "</TD></TR>", +        "<TR><TD ALIGN=\"right\">Shell</TD>". +          "<TD BGCOLOR=\"#ffffff\">". $svc_acct->shell. "</TD></TR>", +        "<TR><TD ALIGN=\"right\">Quota</TD>". +          "<TD BGCOLOR=\"#ffffff\">". $svc_acct->quota. "</TD></TR>" +  ; +} else { +  print "<TR><TH COLSPAN=2>(No shell account)</TH></TR>"; +} + +if ($svc_acct->slipip) { +  print "<TR><TD ALIGN=\"right\">IP address</TD><TD BGCOLOR=\"#ffffff\">". +        ( ( $svc_acct->slipip eq "0.0.0.0" || $svc_acct->slipip eq '0e0' ) +          ? "<I>(Dynamic)</I>" +          : $svc_acct->slipip +        ). "</TD>"; +  my($attribute); +  foreach $attribute ( grep /^radius_/, fields('svc_acct') ) { +    #warn $attribute; +    $attribute =~ /^radius_(.*)$/; +    my $pattribute = $FS::raddb::attrib{$1}; +    print "<TR><TD ALIGN=\"right\">Radius (reply) $pattribute</TD>". +          "<TD BGCOLOR=\"#ffffff\">". $svc_acct->getfield($attribute). +          "</TD></TR>"; +  } +  foreach $attribute ( grep /^rc_/, fields('svc_acct') ) { +    #warn $attribute; +    $attribute =~ /^rc_(.*)$/; +    my $pattribute = $FS::raddb::attrib{$1}; +    print "<TR><TD ALIGN=\"right\">Radius (check) $pattribute: </TD>". +          "<TD BGCOLOR=\"#ffffff\">". $svc_acct->getfield($attribute). +          "</TD></TR>"; +  } +} else { +  print "<TR><TH COLSPAN=2>(No SLIP/PPP account)</TH></TR>"; +} + +print "</TABLE></TD></TR></TABLE></BODY></HTML>"; + +%> diff --git a/httemplate/view/svc_acct_sm.cgi b/httemplate/view/svc_acct_sm.cgi new file mode 100755 index 000000000..4e5acc427 --- /dev/null +++ b/httemplate/view/svc_acct_sm.cgi @@ -0,0 +1,58 @@ +<!-- 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_domain.cgi b/httemplate/view/svc_domain.cgi new file mode 100755 index 000000000..f086cda1a --- /dev/null +++ b/httemplate/view/svc_domain.cgi @@ -0,0 +1,62 @@ +<!-- mason kludge --> +<% + +my($query) = $cgi->keywords; +$query =~ /^(\d+)$/; +my $svcnum = $1; +my $svc_domain = qsearchs('svc_domain',{'svcnum'=>$svcnum}); +die "Unknown svcnum" unless $svc_domain; + +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 } ); +die "Unknown svcpart" unless $part_svc; + +my $email = ''; +if ($svc_domain->catchall) { +  my $svc_acct = qsearchs('svc_acct',{'svcnum'=> $svc_domain->catchall } ); +  die "Unknown svcpart" unless $svc_acct; +  $email = $svc_acct->email; +} + +my $domain = $svc_domain->domain; + +print header('Domain 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, +)), +      "Service #$svcnum", +      "<BR>Service: <B>", $part_svc->svc, "</B>", +      "<BR>Domain name: <B>$domain</B>.", +      qq!<BR>Catch all email <A HREF="${p}misc/catchall.cgi?$svcnum">(change)</A>:!, +      $email ? "<B>$email</B>." : "<I>(none)<I>", +      qq!<BR><BR><A HREF="http://www.geektools.com/cgi-bin/proxy.cgi?query=$domain;targetnic=auto">View whois information.</A>!, +      '<BR><BR>', ntable("",2), +      '<tr><th>Zone</th><th>Type</th><th>Data</th></tr>', +; + +foreach my $domain_record ( qsearch('domain_record', { svcnum => $svcnum } ) ) { +  print '<tr><td>'. $domain_record->reczone. '</td>'. +        '<td>'. $domain_record->recaf. ' '. $domain_record->rectype. '</td>'. +        '<td>'. $domain_record->recdata. '</td></tr>'; +} +print '</table>'; + +print '</BODY></HTML>'; + +%> diff --git a/httemplate/view/svc_forward.cgi b/httemplate/view/svc_forward.cgi new file mode 100755 index 000000000..cafb9e5b8 --- /dev/null +++ b/httemplate/view/svc_forward.cgi @@ -0,0 +1,62 @@ +<!-- mason kludge --> +<% + +my $conf = new FS::Conf; + +my($query) = $cgi->keywords; +$query =~ /^(\d+)$/; +my $svcnum = $1; +my $svc_forward = qsearchs('svc_forward',{'svcnum'=>$svcnum}); +die "Unknown svcnum" unless $svc_forward; + +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 Forward 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($srcsvc,$dstsvc,$dst) = ( +  $svc_forward->srcsvc, +  $svc_forward->dstsvc, +  $svc_forward->dst, +); +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 $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{ +  $destination = $dst; +} + +print qq!<A HREF="${p}edit/svc_forward.cgi?$svcnum">Edit this information</A>!, +      "<BR>Service #$svcnum", +      "<BR>Service: <B>$svc</B>", +      qq!<BR>Mail to <B>$source</B> forwards to <B>$destination</B> mailbox.!, +      '</BODY></HTML>' +; + +%> diff --git a/httemplate/view/svc_www.cgi b/httemplate/view/svc_www.cgi new file mode 100644 index 000000000..a82921f1b --- /dev/null +++ b/httemplate/view/svc_www.cgi @@ -0,0 +1,46 @@ +<!-- mason kludge --> +<% + +my($query) = $cgi->keywords; +$query =~ /^(\d+)$/; +my $svcnum = $1; +my $svc_www = qsearchs( 'svc_www', { 'svcnum' => $svcnum } ) +  or die "svc_www: 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 $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; +} + +print header('Website 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, +)), +      "Service #$svcnum", +      qq!<BR>Website name: <B><A HREF="http://$www">$www</A></B>!, +      '</BODY></HTML>',                 +; +%>  | 
