summaryrefslogtreecommitdiff
path: root/httemplate
diff options
context:
space:
mode:
Diffstat (limited to 'httemplate')
-rwxr-xr-xhttemplate/browse/part_pkg.cgi30
-rw-r--r--httemplate/browse/part_pkg_usage.html112
-rw-r--r--httemplate/edit/cust_main/top_misc.html48
-rwxr-xr-xhttemplate/edit/process/cust_main.cgi12
-rw-r--r--httemplate/edit/process/part_pkg_usage.html67
-rw-r--r--httemplate/elements/auto-table.html59
-rw-r--r--httemplate/elements/location.html5
-rw-r--r--httemplate/pref/pref-process.html2
-rw-r--r--httemplate/pref/pref.html4
-rwxr-xr-xhttemplate/view/cust_main/packages.html9
-rw-r--r--httemplate/view/cust_main/packages/package.html33
11 files changed, 365 insertions, 16 deletions
diff --git a/httemplate/browse/part_pkg.cgi b/httemplate/browse/part_pkg.cgi
index 5dee5b8d1..bb5bc5215 100755
--- a/httemplate/browse/part_pkg.cgi
+++ b/httemplate/browse/part_pkg.cgi
@@ -1,5 +1,6 @@
<% include( 'elements/browse.html',
'title' => 'Package Definitions',
+ 'menubar' => \@menubar,
'html_init' => $html_init,
'html_form' => $html_form,
'html_posttotal' => $html_posttotal,
@@ -517,6 +518,8 @@ push @fields,
sub {
my $part_pkg = shift;
+ my @part_pkg_usage = sort { $a->priority <=> $b->priority }
+ $part_pkg->part_pkg_usage;
[
(map {
@@ -559,7 +562,27 @@ push @fields,
]
}
$part_pkg->svc_part_pkg_link
- )
+ ),
+ ( scalar(@part_pkg_usage) ?
+ [ { data => 'Usage minutes',
+ align => 'center',
+ colspan => 2,
+ data_style => 'b',
+ link => $p.'browse/part_pkg_usage.html#pkgpart'.
+ $part_pkg->pkgpart
+ } ]
+ : ()
+ ),
+ ( map {
+ [ { data => $_->minutes,
+ align => 'right'
+ },
+ { data => $_->description,
+ align => 'left'
+ },
+ ]
+ } @part_pkg_usage
+ ),
];
};
@@ -590,4 +613,9 @@ if ( $acl_edit_bulk ) {
) . '</FORM>';
}
+my @menubar;
+# show this if there are any voip_cdr packages defined
+if ( FS::part_pkg->count("plan = 'voip_cdr'") ) {
+ push @menubar, 'Per-package usage minutes' => $p.'browse/part_pkg_usage.html';
+}
</%init>
diff --git a/httemplate/browse/part_pkg_usage.html b/httemplate/browse/part_pkg_usage.html
new file mode 100644
index 000000000..209fd3a01
--- /dev/null
+++ b/httemplate/browse/part_pkg_usage.html
@@ -0,0 +1,112 @@
+<& /elements/header.html, 'Package usage minutes' &>
+<& /elements/menubar.html, 'Package definitions', $p.'browse/part_pkg.cgi' &>
+<STYLE TYPE="text/css">
+.pkg_head {
+ background-color: #dddddd;
+ font-style: italic;
+}
+.pkg_head > td {
+ border-style: solid;
+ border-radius: 3px;
+ border-color: #555555;
+ border-width: 1px;
+}
+.usage > td {
+ text-align: center;
+}
+.error {
+ color: #ff0000;
+}
+</STYLE>
+<FORM METHOD="POST" ACTION="<%$fsurl%>edit/process/part_pkg_usage.html">
+ <TABLE STYLE="margin-top: 1em">
+ <TR>
+ <TH>Minutes</TH>
+ <TH>Shared</TH>
+ <TH>Rollover</TH>
+ <TH>Description</TH>
+ <TH>Priority</TH>
+% foreach my $class (@usage_class) {
+ <TH><% $class->classname %></TH>
+% }
+ </TR>
+
+% my $error = $cgi->param('error');
+% foreach my $part_pkg (@part_pkg) {
+% my $pkgpart = $part_pkg->pkgpart;
+% my @part_pkg_usage;
+% if ( $error ) {
+% @part_pkg_usage = @{ $error->{$pkgpart} };
+% } else {
+% @part_pkg_usage = $part_pkg->part_pkg_usage;
+% foreach my $usage (@part_pkg_usage) {
+% foreach ($usage->classnums) {
+% $usage->set("class$_".'_', 'Y');
+% }
+% }
+% }
+ <TR CLASS="pkg_head" ID="pkgpart<%$pkgpart%>">
+ <TD COLSPAN=<%$n_cols%>><% $part_pkg->pkg_comment %></TD>
+% # make it easy to enumerate the pkgparts later
+ <INPUT TYPE="hidden" NAME="pkgpart" VALUE="<% $pkgpart %>">
+ </TR>
+% # template row
+ <TR id="pkgpart<%$pkgpart%>_template" CLASS="usage">
+ <TD>
+ <INPUT TYPE="hidden" NAME="pkgusagepart">
+ <INPUT TYPE="text" NAME="minutes" ID="minutes" SIZE=7>
+ </TD>
+% foreach (qw(shared rollover)) {
+ <TD>
+ <INPUT TYPE="checkbox" NAME="<% $_ %>" ID="<% $_ %>" VALUE="Y">
+ </TD>
+% }
+ <TD>
+ <INPUT TYPE="text" NAME="description" ID="description" SIZE=20>
+ </TD>
+ <TD>
+ <INPUT TYPE="text" NAME="priority" ID="priority" SIZE=3>
+ </TD>
+% foreach (@usage_class) {
+% my $classnum = 'class' . $_->classnum . '_';
+ <TD>
+ <INPUT TYPE="checkbox" NAME="<% $classnum %>" ID="<% $classnum %>" VALUE="Y">
+ </TD>
+% }
+ </TR>
+ <& /elements/auto-table.html,
+ table => "pkgpart$pkgpart",
+ template_row => "pkgpart$pkgpart".'_template',
+ data => \@part_pkg_usage,
+ &>
+% }
+ </TABLE>
+ <BR>
+ <INPUT TYPE="submit">
+</FORM>
+<& /elements/footer.html &>
+<%init>
+my $curuser = $FS::CurrentUser::CurrentUser;
+die "access denied"
+ unless $curuser->access_right(
+ ['Edit package definitions', 'Edit global package definitions']
+ );
+
+my @where = ("(plan = 'voip_cdr' OR plan = 'voip_inbound')",
+ "freq != '0'",
+ "disabled IS NULL");
+push @where, FS::part_pkg->curuser_pkgs_sql
+ unless $curuser->access_right('Edit global package definitions');
+my $extra_sql = ' WHERE '.join(' AND ', @where);
+my @part_pkg = qsearch({
+ 'table' => 'part_pkg',
+ 'extra_sql' => $extra_sql,
+ 'order_by' => ' ORDER BY pkgpart',
+});
+
+my @usage_class = sort { $a->weight <=> $b->weight }
+ qsearch('usage_class', { disabled => '' });
+
+my $n_usage_classes = scalar(@usage_class);
+my $n_cols = $n_usage_classes + 5; # minutes, shared, rollover, desc, prio
+</%init>
diff --git a/httemplate/edit/cust_main/top_misc.html b/httemplate/edit/cust_main/top_misc.html
index cfed8e4f6..f2f584fc5 100644
--- a/httemplate/edit/cust_main/top_misc.html
+++ b/httemplate/edit/cust_main/top_misc.html
@@ -32,6 +32,35 @@
document.getElementById('contacts_div').style.display = 'none';
}
}
+
+ var ship_locked_agents = <% encode_json(\%ship_locked_agents) %>;
+ var ship_fields = ['address1', 'city', 'state', 'zip', 'country',
+ 'latitude', 'longitude', 'district'];
+ function agent_changed(what) {
+ var agentnum = what.value;
+ var f = what.form;
+ if ( ship_locked_agents[agentnum] ) {
+ for(var x in ship_locked_agents[agentnum]) {
+ f['ship_'+x].value = ship_locked_agents[agentnum][x];
+ f['ship_'+x].disabled = true;
+ }
+ f['same'].checked = false;
+ f['same'].disabled = true;
+ } else {
+ for(var i=0; i<ship_fields.length; i++) {
+ x = ship_fields[i];
+ f['ship_'+x].value = '';
+ f['ship_'+x].disabled = false;
+ }
+ f['same'].checked = true;
+ f['same'].disabled = false;
+ }
+ samechanged(f['same']);
+ }
+ window.onload = function() {
+ agent_changed(document.getElementById('agentnum'));
+ }
+
</SCRIPT>
% foreach my $field ($cust_main->virtual_fields) {
@@ -51,12 +80,13 @@
% $cust_main->agentnum($agentnum);
<INPUT TYPE="hidden" NAME="lock_agentnum" VALUE="<% $agentnum %>">
- <INPUT TYPE="hidden" NAME="agentnum" VALUE="<% $agentnum %>">
+ <INPUT TYPE="hidden" NAME="agentnum" ID="agentnum"
+ VALUE="<% $agentnum %>">
<TR>
<TD ALIGN="right"><% mt('Agent') |h %></TD>
<TD CLASS="fsdisabled"><% $cust_main->agent->agent |h %></TD>
</TR>
-
+
% } else {
<& /elements/tr-select-agent.html,
@@ -65,6 +95,7 @@
'empty_label' => emt('Select agent'),
'disable_empty' => ( $cust_main->agentnum ? 1 : 0 ),
'viewall_right' => emt('None'),
+ 'onchange' => 'agent_changed(this)',
&>
% }
@@ -201,4 +232,17 @@ my $curuser = $FS::CurrentUser::CurrentUser;
my $r = qq!<font color="#ff0000">*</font>&nbsp;!;
+# which agents lock the service address, if any
+my %ship_locked_agents;
+foreach (qsearch('agent',{})) {
+ my $agentnum = $_->agentnum;
+ next unless $conf->exists('agent-ship_address', $_->agentnum);
+ my $cust_main = $_->agent_cust_main or next;
+ my $agent_ship_location = $cust_main->ship_location;
+ $ship_locked_agents{$agentnum} = +{
+ map { $_ => $agent_ship_location->$_ }
+ qw(address1 city state zip country latitude longitude district)
+ };
+}
+
</%init>
diff --git a/httemplate/edit/process/cust_main.cgi b/httemplate/edit/process/cust_main.cgi
index 584462c8c..054973f23 100755
--- a/httemplate/edit/process/cust_main.cgi
+++ b/httemplate/edit/process/cust_main.cgi
@@ -62,6 +62,18 @@ $cgi->param('invoicing_list', join(',', @invoicing_list) );
$cgi->param('duplicate_of_custnum') =~ /^(\d+)$/;
my $duplicate_of = $1;
+# if this is enabled, enforce it
+if ( $conf->exists('agent-ship_address', $cgi->param('agentnum')) ) {
+ my $agent = FS::agent->by_key($cgi->param('agentnum'));
+ my $agent_cust_main = $agent->agent_cust_main;
+ if ( $agent_cust_main ) {
+ my $agent_location = $agent_cust_main->ship_location;
+ foreach (qw(address1 city state zip country latitude longitude district)) {
+ $cgi->param("ship_$_", $agent_location->get($_));
+ }
+ }
+}
+
my %locations;
for my $pre (qw(bill ship)) {
diff --git a/httemplate/edit/process/part_pkg_usage.html b/httemplate/edit/process/part_pkg_usage.html
new file mode 100644
index 000000000..eb6c37b82
--- /dev/null
+++ b/httemplate/edit/process/part_pkg_usage.html
@@ -0,0 +1,67 @@
+% if ( $is_error ) {
+% $cgi->param('error' => \%part_pkg_usage);
+% # internal redirect, because it's a lot of state to pass through
+<& /browse/part_pkg_usage.html &>
+% } else {
+% # uh, not quite sure...
+<% $cgi->redirect($fsurl.'browse/part_pkg.cgi') %>
+% }
+<%init>
+my %vars = $cgi->Vars;
+my %part_pkg_usage;
+my $is_error;
+foreach my $pkgpart ($cgi->param('pkgpart')) {
+ next unless $pkgpart =~ /^\d+$/;
+ my $part_pkg = FS::part_pkg->by_key($pkgpart)
+ or die "unknown pkgpart $pkgpart";
+ my %old = map { $_->pkgusagepart => $_ } $part_pkg->part_pkg_usage;
+ $part_pkg_usage{$pkgpart} ||= [];
+ my @rows;
+ foreach (grep /^pkgpart$pkgpart/, keys %vars) {
+ /^pkgpart\d+_(\w+\D)(\d+)$/ or die "misspelled field name '$_'";
+ my $value = delete $vars{$_};
+ my $field = $1;
+ my $row = $2;
+ $rows[$row] ||= {};
+ $rows[$row]->{$field} = $value;
+ }
+
+ foreach my $row (@rows) {
+ next if !defined($row);
+ my $error;
+ my %classes;
+ foreach my $class (grep /^class/, keys %$row) {
+ $class =~ /^class(\d+)_$/;
+ my $classnum = $1;
+ $classes{$classnum} = delete $row->{$class};
+ }
+ my $usage = FS::part_pkg_usage->new($row);
+ $usage->set('pkgpart', $pkgpart);
+ if ( $usage->pkgusagepart and $row->{minutes} > 0 ) {
+ $error = $usage->replace(\%classes);
+ # and don't delete the existing one
+ delete($old{$usage->pkgusagepart});
+ } elsif ( $row->{minutes} > 0 ) {
+ $error = $usage->insert(\%classes);
+ } else {
+ next;
+ }
+ if ( $error ) {
+ $usage->set('error', $error);
+ $is_error = 1;
+ }
+ push @{ $part_pkg_usage{$pkgpart} }, $usage;
+ }
+
+ foreach my $usage (values %old) {
+ # all of these were not sent back by the client, so delete them
+ my $error = $usage->delete;
+ if ( $error ) {
+ $usage->set('error', $error);
+ $is_error = 1;
+ unshift @{ $part_pkg_usage{$pkgpart} }, $usage;
+ }
+ }
+
+}
+</%init>
diff --git a/httemplate/elements/auto-table.html b/httemplate/elements/auto-table.html
index 9aff94e67..3a3bd405d 100644
--- a/httemplate/elements/auto-table.html
+++ b/httemplate/elements/auto-table.html
@@ -70,8 +70,8 @@ function <%$pre%>set_rownum(obj, rownum) {
if ( obj.id ) {
obj.id = obj.id + rownum;
}
- if ( obj.name ) {
- obj.name = obj.name + rownum;
+ if ( obj.getAttribute('name') ) {
+ obj.setAttribute('name', obj.getAttribute('name') + rownum);
// also, in this case it's a form field that will be part of the record
// so set up an onchange handler
obj.onchange = <%$pre%>possiblyAddRow_factory(obj);
@@ -96,17 +96,32 @@ function <%$pre%>addRow(data) {
<%$pre%>set_rownum(row, this_rownum);
if(data instanceof Array) {
for (i = 0; i < data.length && i < <%$pre%>fieldorder.length; i++) {
- var el = document.getElementsByName(<%$pre%>fieldorder[i] + this_rownum)[0];
+ var el = document.getElementsByName(<%$pre |js_string%> +
+ <%$pre%>fieldorder[i] +
+ this_rownum)[0];
if (el) {
- el.value = data[i];
+ if ( el.tagName.toLowerCase() == 'span' ) {
+ el.innerHTML = data[i];
+ } else if ( el.type == 'checkbox' ) {
+ el.checked = (el.value == data[i]);
+ } else {
+ el.value = data[i];
+ }
}
}
} else if (data instanceof Object) {
for (var field in data) {
- var el = document.getElementsByName(field + this_rownum)[0];
+ var el = document.getElementsByName(<%$pre |js_string%> +
+ field +
+ this_rownum)[0];
if (el) {
- el.value = data[field];
-% # doesn't work for checkbox
+ if ( el.tagName.toLowerCase() == 'span' ) {
+ el.innerHTML = data[field];
+ } else if ( el.type == 'checkbox' ) {
+ el.checked = (el.value == data[field]);
+ } else {
+ el.value = data[field];
+ }
}
}
} // else nothing
@@ -123,6 +138,20 @@ function <%$pre%>deleteRow(rownum) {
<%$pre%>tbody.removeChild(r);
}
+function <%$pre%>set_prefix(obj) {
+ if ( obj.id ) {
+ obj.id = <%$pre |js_string%> + obj.id;
+ }
+ if ( obj.getAttribute('name') ) {
+ obj.setAttribute('name', <%$pre |js_string%> + obj.getAttribute('name'));
+ }
+ for (var i = 0; i < obj.children.length; i++) {
+ if ( obj.children[i] instanceof Node ) {
+ <%$pre%>set_prefix(obj.children[i]);
+ }
+ }
+}
+
function <%$pre%>init() {
<%$pre%>template = document.getElementById(<% $template_row |js_string%>);
<%$pre%>tbody = document.getElementById('<%$pre%>autotable');
@@ -131,8 +160,10 @@ function <%$pre%>init() {
var table = <%$pre%>template.parentNode;
table.removeChild(<%$pre%>template);
// give it an id
- <%$pre%>template.id = <%$pre |js_string%> + 'row';
- // and a magic identifier so we know it's been submitted
+ <%$pre%>template.id = 'row';
+ // prefix the ids and names of the TR object and all its descendants
+ <%$pre%>set_prefix(<%$pre%>template);
+ // add a magic identifier so we know it's been submitted
var magic = document.createElement('INPUT');
magic.setAttribute('type', 'hidden');
magic.setAttribute('name', '<%$pre%>magic');
@@ -140,14 +171,22 @@ function <%$pre%>init() {
// and a delete button
%# should this be enclosed in an actual <button> for aesthetics?
var delete_button = document.createElement('IMG');
- delete_button.id = 'delete_button';
+ delete_button.id = '<%$pre%>delete_button';
delete_button.src = '<%$fsurl%>images/cross.png';
delete_button.alt = 'X';
// use an inline string for this so that it will be cloned properly
delete_button.setAttribute('onclick', "<%$pre%>deleteRow(this.rownum);");
+ // and an error display
+ var error_span = document.createElement('SPAN');
+ error_span.className = 'error';
+ error_span.style.color = '#FF0000';
+ error_span.setAttribute('name', '<%$pre%>error');
+ error_span.style.padding = '5px';
var delete_cell = document.createElement('TD');
+ delete_cell.style.textAlign = 'left';
delete_cell.appendChild(delete_button);
delete_cell.appendChild(magic); // it has to go somewhere
+ delete_cell.appendChild(error_span);
<%$pre%>template.appendChild(delete_cell);
// preload rows
diff --git a/httemplate/elements/location.html b/httemplate/elements/location.html
index 873fe1621..b142aa690 100644
--- a/httemplate/elements/location.html
+++ b/httemplate/elements/location.html
@@ -200,7 +200,7 @@ Example:
</TR>
% } else {
% foreach (qw(latitude longitude)) {
-<INPUT TYPE="hidden" NAME="<% $_ %>" VALUE="<% $object->get($_) |h%>">
+<INPUT TYPE="hidden" NAME="<% $_ %>" ID="<% $_ %>" VALUE="<% $object->get($_) |h%>">
% }
% }
<INPUT TYPE="hidden" NAME="<%$pre%>coord_auto" VALUE="<% $object->coord_auto %>">
@@ -226,12 +226,13 @@ Example:
<TD COLSPAN=8>
<INPUT TYPE="text" SIZE=15
NAME="<%$pre%>district"
+ ID="<%$pre%>district"
VALUE="<% $object->district |h %>">
<% '(automatic)' %>
</TD>
</TR>
% } else {
- <INPUT TYPE="hidden" NAME="<%$pre%>district" VALUE="<% $object->district %>">
+ <INPUT TYPE="hidden" ID="<%$pre%>" NAME="<%$pre%>district" VALUE="<% $object->district %>">
% }
% }
diff --git a/httemplate/pref/pref-process.html b/httemplate/pref/pref-process.html
index c4fef0311..84f0832bf 100644
--- a/httemplate/pref/pref-process.html
+++ b/httemplate/pref/pref-process.html
@@ -57,7 +57,7 @@ unless ( $error ) { # if ($access_user) {
vonage-fromnumber vonage-username vonage-password
cust_pkg-display_times
show_pkgnum show_confitem_counts export_getsettings
- show_db_profile save_db_profile
+ show_db_profile save_db_profile save_tmp_typesetting
height width availHeight availWidth colorDepth
);
diff --git a/httemplate/pref/pref.html b/httemplate/pref/pref.html
index 1e9671dcc..9537fed34 100644
--- a/httemplate/pref/pref.html
+++ b/httemplate/pref/pref.html
@@ -157,6 +157,10 @@ Development
<TH>Save database profiling logs (when available): </TH>
<TD><INPUT TYPE="checkbox" NAME="save_db_profile" VALUE="1" <% $curuser->option('save_db_profile') ? 'CHECKED' : '' %>></TD>
</TR>
+ <TR>
+ <TH>Save temporary invoice typesetting files: </TH>
+ <TD><INPUT TYPE="checkbox" NAME="save_tmp_typesetting" VALUE="1" <% $curuser->option('save_tmp_typesetting') ? 'CHECKED' : '' %>></TD>
+ </TR>
</TABLE>
<BR>
diff --git a/httemplate/view/cust_main/packages.html b/httemplate/view/cust_main/packages.html
index da4d587f2..24a12cc46 100755
--- a/httemplate/view/cust_main/packages.html
+++ b/httemplate/view/cust_main/packages.html
@@ -11,6 +11,15 @@ table.package {
border-spacing: 0;
width: 100%;
}
+table.usage {
+ border: 1px solid black;
+ margin: auto;
+ width: 60%;
+ border-spacing: 0px;
+}
+.shared > * {
+ background-color: #ffffaa;
+}
.row0 { background-color: #eeeeee; }
.row1 { background-color: #ffffff; }
diff --git a/httemplate/view/cust_main/packages/package.html b/httemplate/view/cust_main/packages/package.html
index 3a362b6fa..d0fc182cb 100644
--- a/httemplate/view/cust_main/packages/package.html
+++ b/httemplate/view/cust_main/packages/package.html
@@ -190,6 +190,28 @@
% }
% }
</TABLE>
+% if ( @cust_pkg_usage ) {
+ <TABLE CLASS="usage inv">
+ <TR><TH COLSPAN=4><% mt('Included usage') %></TH></TR>
+% foreach my $usage (@cust_pkg_usage) {
+% my $part = $usage->part_pkg_usage;
+% my $ratio = 255 * ($usage->minutes / $part->minutes);
+% $ratio = 255 if $ratio > 255; # because rollover
+% my $color = sprintf('STYLE="font-weight: bold; color: #%02x%02x00"', 255 - $ratio, $ratio);
+% my $trstyle = '';
+% $trstyle = ' CLASS="shared"' if $part->shared;
+ <TR<%$trstyle%>>
+ <TD ALIGN="right"><% $part->description %>: </TD>
+ <TD <%$color%> ALIGN="right"><% $usage->minutes %></TD>
+ <TD <%$color%>> / </TD>
+ <TD <%$color%>><% $part->minutes %></TD>
+% if ( $part->shared ) {
+ <TD><I>(shared)</I></TD>
+% }
+ </TR>
+% }
+ </TABLE>
+% }
</TD>
@@ -208,6 +230,17 @@ my $statedefault = $opt{'statedefault'}
|| ($countrydefault eq 'US' ? 'CA' : '');
my $supplemental = $opt{'supplemental'} || 0;
+
+$cust_pkg->pkgnum =~ /^(\d+)$/;
+my $pkgnum = $1;
+my @cust_pkg_usage = qsearch({
+ 'select' => 'cust_pkg_usage.*',
+ 'table' => 'cust_pkg_usage',
+ 'addl_from' => ' JOIN part_pkg_usage USING (pkgusagepart)',
+ 'extra_sql' => " WHERE pkgnum = $1",
+ 'order_by' => ' ORDER BY priority ASC, description ASC',
+});
+
#subroutines
#false laziness w/status.html