diff options
Diffstat (limited to 'httemplate/edit/elements')
-rw-r--r-- | httemplate/edit/elements/ApplicationCommon.html | 552 | ||||
-rw-r--r-- | httemplate/edit/elements/category_Common.html | 24 | ||||
-rw-r--r-- | httemplate/edit/elements/class_Common.html | 32 | ||||
-rw-r--r-- | httemplate/edit/elements/edit.html | 859 | ||||
-rw-r--r-- | httemplate/edit/elements/rate_detail.html | 239 | ||||
-rw-r--r-- | httemplate/edit/elements/svc_Common.html | 211 |
6 files changed, 1917 insertions, 0 deletions
diff --git a/httemplate/edit/elements/ApplicationCommon.html b/httemplate/edit/elements/ApplicationCommon.html new file mode 100644 index 000000000..7b1050ade --- /dev/null +++ b/httemplate/edit/elements/ApplicationCommon.html @@ -0,0 +1,552 @@ +<%doc> + +Examples: + + #cust_bill_pay + include('elements/ApplicationCommon.html', + 'form_action' => 'process/cust_bill_pay.cgi', + 'src_table' => 'cust_pay', + 'src_thing' => 'payment', + 'dst_table' => 'cust_bill', + 'dst_thing' => 'invoice', + ) + + #cust_credit_bill + include('elements/ApplicationCommon.html', + 'form_action' => 'process/cust_credit_bill.cgi', + 'src_table' => 'cust_credit', + 'src_thing' => 'credit', + 'dst_table' => 'cust_bill', + 'dst_thing' => 'invoice', + ) + + #cust_pay_refund + include('elements/ApplicationCommon.html', + 'form_action' => 'process/cust_pay_refund.cgi', + 'src_table' => 'cust_pay', + 'src_thing' => 'payment', + 'dst_table' => 'cust_refund', + 'dst_thing' => 'refund', + ) + + #cust_credit_refund + include('elements/ApplicationCommon.html', + 'form_action' => 'process/cust_credit_refund.cgi', + 'src_table' => 'cust_credit', + 'src_thing' => 'credit', + 'dst_table' => 'cust_refund', + 'dst_thing' => 'refund', + ) + +</%doc> + +<% include('/elements/header-popup.html', "Apply $src_thing$to", '', 'onLoad="myOnLoadFunction();"') %> + +<% include('/elements/error.html') %> + +<P ID="ErrorMessage"></P> +<FORM ACTION="<% $p1. $opt{'form_action'} %>" NAME="ApplicationForm" ID="ApplicationForm" METHOD=POST> + +<% $src_thing %> #<B><% $src_pkeyvalue %></B><BR> +<INPUT TYPE="hidden" NAME="<% $src_pkey %>" VALUE="<% $src_pkeyvalue %>"> + +<TABLE BGCOLOR="#cccccc" BORDER=0 CELLSPACING=0> + +<TR> + <TD ALIGN="right">Date: </TD> + <TD><B><% time2str($date_format, $src->_date) %></B></TD> +</TR> + +<TR> + <TD ALIGN="right">Amount: </TD> + <TD ID="original_amount"><B><% $money_char %><% $src_amount %></B> + </TD> + <TD> +% if ($use_sub_dst_thing && $can_change_credit) { + <INPUT TYPE="hidden" NAME="src_amount" VALUE="<% $src_amount %>" > + <BUTTON TYPE="button" NAME="expand_button" ID="expand_button" onClick="do_change_amount(this);">Change</BUTTON> +% } + </TD> + +</TR> + +<TR> + <TD ALIGN="right">Unapplied amount: </TD> + <TD ID="unapplied_amount"><B><% $money_char %><% $unapplied %></B></TD> +</TR> + +% if ( $src_table eq 'cust_credit' ) { + <TR> + <TD ALIGN="right">Reason: </TD> + <TD COLSPAN=2><B><% $src->reason %></B></TD> + </TR> +% } + +</TABLE> +<BR> + +<SCRIPT TYPE="text/javascript"> +function clear_amounts() { + var rownum=0 + var table = document.getElementById('ApplicationTable'); + for (var row = 2; table.rows[row]; row++) + { + var inputs = table.rows[row].getElementsByTagName('input'); + if ( !inputs.length ) { + break; + } + inputs.item(0).value = ''; // amount + } + +} + +function changed(what) { + dst = what.options[what.selectedIndex].value; + + if ( dst == '' ) { + what.form.submit.disabled=true; +%if ($use_sub_dst_thing && $src_pkey eq 'crednum') { + what.form.tax_button.disabled=true; + what.form.clear_button.disabled=true; +%} + return true; + } + + what.form.submit.disabled=false; +%if ($use_sub_dst_thing && $src_pkey eq 'crednum') { + what.form.tax_button.disabled=false; + what.form.clear_button.disabled=false; +%} + +% foreach my $dst ( @dst ) { + + if ( dst == <% $dst->$dst_pkey %> ) { + what.form.amount.value = "<% min($dst->$dst_unapplied, $unapplied) %>"; +% if ($use_sub_dst_thing) { + what.form.display_amount.value = "<% min($dst->$dst_unapplied, $unapplied) %>"; + + var rownum=0 + var table = document.getElementById('ApplicationTable'); + while(table.rows[2]) { + table.deleteRow(2); + } +% my $app_class = "FS::$link_table"; +% my $temp_app = $app_class->new( +% { $src_pkey => $src_pkeyvalue, +% $dst_pkey => $dst->$dst_pkey, +% 'amount' => min($dst->$dst_unapplied, $unapplied), +% } +% ); +% my %apphash = (); +% my $listref_or_error = $temp_app->calculate_applications; +% %apphash = map { &{$key_generator}($_), $_ } @$listref_or_error +% if ref($listref_or_error); +% foreach my $cbp ( $dst->open_cust_bill_pkg ) { +% my $desc = $cbp->desc; +% my $total_owed = $cbp->owed_setup + $cbp->owed_recur; +% my $key = &{$key_generator}([ $cbp, 0, {} ]); +% my $amount = exists($apphash{ $key }) ? $apphash{ $key }->[1] : 0; +% unless ( $cbp->pkgnum ) { +% foreach my $taxX ( $cbp->cust_bill_pkg_tax_Xlocation ) { +% my $pkey = $taxX->primary_key; +% my $owed = $taxX->owed; +% my $key = &{$key_generator}([ $cbp, 0, { $pkey => $taxX->$pkey } ]); +% my $toapp = exists($apphash{ $key }) ? $apphash{ $key }->[1] : 0; + <% &{$row_generator}( $key, $cbp, $taxX->desc, $owed, $toapp, $taxX->$pkey ) %> +% $total_owed -= $owed; +% $amount -= $toapp; +% } +% $desc .= ' (default)'; +% } +% if ( $total_owed > 0 ) { + <% &{$row_generator}($key, $cbp, $desc, $total_owed, $amount, '') %> +% } +% } +% } + } + +% } + +} + +function sub_changed(what) { + + var amount = 0; + var table = document.getElementById('ApplicationTable'); + var i = table.rows.length; + while(i-- > 2) { + var inputs = table.rows[i].getElementsByTagName('input'); + if (! inputs.length) { + continue; + } + amount += parseFloat( inputs.item(0).value ) || 0; + } + what.form.amount.value = parseFloat(amount).toFixed(2); + what.form.display_amount.value = parseFloat(amount).toFixed(2); + set_amount_color(what); + +} + +function set_amount_color(what) { + if (what.form.src_amount.value < what.form.amount.value) { + what.form.display_amount.style.color = '#ff0000'; + } else { + what.form.display_amount.style.color = '#00ff00'; + } +} + +</SCRIPT> + +Apply to: + +% if ($use_sub_dst_thing && $src_pkey eq 'crednum') { +<CENTER> + <TABLE> + <TR> + <TD> + <BUTTON TYPE="button" NAME="tax_button" ID="tax_button" onClick="do_calculate_tax(this);" DISABLED>Calculate Tax</BUTTON> + </TD> + <TD> + <BUTTON TYPE="button" NAME="clear_button" ID="clear_button" onClick="clear_amounts(this);" DISABLED>Clear Amounts</BUTTON> + </TD> + </TR> + </TABLE> +</CENTER> +<% include( '/elements/xmlhttp.html', + 'url' => $p.'misc/xmlhttp-calculate_taxes.html', + 'subs' => [ 'calculate_taxes' ], + ) + %> +<SCRIPT TYPE="text/javascript"> + +function show_taxes(arg) { + var argsHash = eval('(' + arg + ')'); + + var button = document.getElementById('tax_button'); + button.disabled = false; + button.innerHTML = 'Calculate Tax'; + button = document.getElementById('clear_button'); + button.disabled = false; + + var error = argsHash['error']; + + var paragraph = document.getElementById('ErrorMessage'); + if (error) { + paragraph.innerHTML = 'Error: ' + error; + paragraph.style.color = '#ff0000'; + } else { + paragraph.innerHTML = ''; + } + var taxlines = argsHash['taxlines']; + + var table = document.getElementById('ApplicationTable'); + + var aFoundRow = 0; + for (i = 0; taxlines[i]; i++) { + var itemdesc = taxlines[i][0]; + var locnum = taxlines[i][2]; + if (taxlines[i][3]) { + locnum = taxlines[i][3]; + } + + var found = 0; + for (var row = 2; table.rows[row]; row++) { + var inputs = table.rows[row].getElementsByTagName('input'); + if (! inputs.length) { + while ( table.rows[row] ) { + table.deleteRow(row); + } + break; + } + if ( inputs.item(4).value == itemdesc && inputs.item(2).value == locnum ) + { + inputs.item(0).value = taxlines[i][1]; + aFoundRow = found = row; + break; + } + } + if (! found) { + var row = table.insertRow(table.rows.length); + var warning_cell = document.createElement('TD'); + warning_cell.style.color = '#ff0000'; + warning_cell.colSpan = 2; + warning_cell.innerHTML = 'Calculated Tax - ' + itemdesc + ' - ' + + taxlines[i][1] + ' will not be applied'; + row.appendChild(warning_cell); + } + } + + if (aFoundRow) { + sub_changed(table.rows[aFoundRow].getElementsByTagName('input').item(0)); + } + +} + +function do_calculate_tax (what) { + what.innerHTML = 'Calculating....'; + what.disabled = true; + var button = document.getElementById('clear_button'); + button.disabled = true; + var taxed_items = new Array(); + var table = document.getElementById('ApplicationTable'); + for (var row = 2; table.rows[row]; row++) + { + var inputs = table.rows[row].getElementsByTagName('input'); + if ( !inputs.length ) { + break; + } + var taxed_item = new Array( + inputs.item(1).value, // billpkgnum + inputs.item(3).value, // s_or_r + inputs.item(0).value || 0 // amount + ); + taxed_items.push(taxed_item); + } + + var args = new Array( + 'crednum', '<% $src_pkeyvalue %>', + 'items', taxed_items + ); + calculate_taxes ( args, show_taxes ); +} + +function do_change_amount (what) { + var amount_cell = document.getElementById('original_amount'); + var inputs = amount_cell.getElementsByTagName('input'); + if (inputs.length) { + src_amount_changed(); + amount_cell.innerHTML = '<B><% $money_char %></B>' + inputs.item(0).value; + } else { + amount_cell.innerHTML = '<% $money_char %>'; + var amount_input = document.createElement('INPUT'); + amount_input.setAttribute('name', 'entered_amount'); + amount_input.setAttribute('id', 'entered_amount'); + amount_input.style.textAlign = 'right'; + amount_input.setAttribute('size', 8); + amount_input.setAttribute('maxlength', 8); + amount_input.setAttribute('value', what.form.src_amount.value); + amount_input.setAttribute('onChange', "src_amount_changed(this);"); + amount_cell.appendChild(amount_input); + } +} + +function src_amount_changed () { + //alert('src_amount_changed called'); + var entered_amount = document.getElementById('entered_amount'); + if ( entered_amount ) { + entered_amount.form.src_amount.value = entered_amount.value; + var unapplied_cell = document.getElementById('unapplied_amount'); + unapplied_cell.innerHTML = '<B><% $money_char %>' + entered_amount.value + '</B>'; + set_amount_color(entered_amount); + } + return true; +} + +</SCRIPT> + +%} + +<TABLE ID="ApplicationTable" BGCOLOR="#cccccc" BORDER=0 CELLSPACING=0> + +<TR> + <TD ALIGN="right"><% $dst_thing %>: </TD> + <TD><SELECT NAME="<% $dst_pkey %>" SIZE=1 onChange="changed(this)"> +<OPTION VALUE="">Select <% $dst_thing %> + +% foreach my $dst ( @dst ) { + <OPTION<% $dst->$dst_pkey eq $dst_pkeyvalue ? ' SELECTED' : '' %> VALUE="<% $dst->$dst_pkey %>">#<% $dst->$dst_pkey %> - <% time2str($date_format, $dst->_date) %> - $<% $dst->$dst_unapplied %> +% } + +</SELECT> + </TD> +</TR> + +<TR> + <TD ALIGN="right">Amount: </TD> + <TD><% $money_char %><INPUT TYPE="text" NAME="<% $use_sub_dst_thing ? 'display_' : '' %>amount" VALUE="<% $amount %>" SIZE=8 MAXLENGTH=8 <% $use_sub_dst_thing ? 'DISABLED' : '' %> STYLE="text-align:right;"></TD> +% if ($use_sub_dst_thing) { + <INPUT TYPE="hidden" NAME="amount" VALUE="<% $amount %>" > +% } +</TR> + +</TABLE> + +<BR> +<CENTER><INPUT TYPE="submit" + VALUE="Apply" + NAME="submit" + ID="submit" +% if ($use_sub_dst_thing && $can_change_credit) { + onClick="src_amount_changed()" +% } + DISABLED +></CENTER> + +</FORM> + +<SCRIPT TYPE="text/javascript"> + +function myOnLoadFunction () { + <% $onload %> +} + +</SCRIPT> + +<% include('/elements/footer.html') %> + +<%init> + +my %opt = @_; + +my $conf = new FS::Conf; +my $money_char = $conf->config('money_char') || '$'; +my $date_format = $conf->config('date_format') || '%m/%d/%Y'; + +my $src_thing = ucfirst($opt{'src_thing'}); +my $src_table = $opt{'src_table'}; +my $src_pkey = dbdef->table($src_table)->primary_key; + +my $dst_thing = ucfirst($opt{'dst_thing'}); +my $dst_table = $opt{'dst_table'}; +my $dst_pkey = dbdef->table($dst_table)->primary_key; +my $dst_unapplied = $dst_table eq 'cust_bill' ? 'owed' : 'unapplied'; + +$opt{form_action} =~ /^process\/(.*)\./ or die "bad form action"; +my $link_table = $1; + +my $use_sub_dst_thing = 0; +$use_sub_dst_thing = 1 + if ( $dst_table eq 'cust_bill' && $conf->exists("${link_table}_pkg-manual") ); + +my $can_change_credit = 0; +$can_change_credit = 1 + if ( $src_table eq 'cust_credit' && + $FS::CurrentUser::CurrentUser->access_right('Post credit') && + $FS::CurrentUser::CurrentUser->access_right('Delete credit') + ); + +my $to = $dst_table eq 'cust_refund' ? ' to Refund' : ''; + +my($src_pkeyvalue, $amount, $dst_pkeyvalue, $src_amount); +if ( $cgi->param('error') ) { + $src_pkeyvalue = $cgi->param($src_pkey); + $amount = $cgi->param('amount'); + $dst_pkeyvalue = $cgi->param($dst_pkey); + $src_amount = $cgi->param('src_amount'); +} else { + my($query) = $cgi->keywords; + $query =~ /^(\d+)$/; + $src_pkeyvalue = $1; + $amount = ''; + $dst_pkeyvalue = ''; +} + +my $otaker = getotaker; + +my $p1 = popurl(1); + +my $src = qsearchs($src_table, { $src_pkey => $src_pkeyvalue } ); +die "$src_thing $src_pkeyvalue not found!" unless $src; + +$src_amount = $src->amount unless $cgi->param('error'); + +my $unapplied = $src->unapplied; + +my @dst = sort { $a->_date <=> $b->_date + or $a->$dst_pkey <=> $b->$dst_pkey + } + grep { $_->$dst_unapplied != 0 } + qsearch($dst_table, { 'custnum' => $src->custnum } ); + +my $row_generator = sub { + my ($key, $cust_bill_pkg, $desc, $owed, $amount, $taxXnum) = @_; + my ($num, $s_or_r, $taxlinenum) = split(':', $key); + my $id = $cust_bill_pkg->pkgnum || 'Tax'; + my $billpkgnum = $cust_bill_pkg->billpkgnum; + my $s_or_r = $cust_bill_pkg->setup > 0 ? 'setup' : 'recur'; + + $amount = sprintf("%.2f", $amount); + qq! + var tablebody = document.getElementsByTagName('tbody').item(0); + var row = table.insertRow(rownum+2); + var pkg_cell = document.createElement('TD'); + pkg_cell.style.textAlign = 'right'; + pkg_cell.innerHTML = "$id - $desc - $owed:"; + var amount_cell = document.createElement('TD'); + amount_cell.innerHTML = "$money_char"; + var amount_input = document.createElement('INPUT'); + amount_input.setAttribute('name', 'subamount'+rownum); + amount_input.setAttribute('id', 'subamount'+rownum); + amount_input.style.textAlign = 'right'; + amount_input.setAttribute('size', 8); + amount_input.setAttribute('maxlength', 8); + amount_input.setAttribute('rownum', rownum); + amount_input.setAttribute('value', "$amount"); + amount_input.setAttribute('onChange', "sub_changed(this);"); + amount_cell.appendChild(amount_input); + var subnum_input = document.createElement('INPUT'); + subnum_input.setAttribute('name', 'subnum'+rownum); + subnum_input.setAttribute('id', 'subnum'+rownum); + subnum_input.setAttribute('type', 'hidden'); + subnum_input.setAttribute('rownum', rownum); + subnum_input.setAttribute('value', "$billpkgnum"); + amount_cell.appendChild(subnum_input); + var taxnum_input = document.createElement('INPUT'); + taxnum_input.setAttribute('name', 'taxXlocationnum'+rownum); + taxnum_input.setAttribute('id', 'taxXlocationnum'+rownum); + taxnum_input.setAttribute('type', 'hidden'); + taxnum_input.setAttribute('rownum', rownum); + taxnum_input.setAttribute('value', "$taxXnum"); + amount_cell.appendChild(taxnum_input); + var s_or_r_input = document.createElement('INPUT'); + s_or_r_input.setAttribute('name', 's_or_r'+rownum); + s_or_r_input.setAttribute('id', 's_or_r'+rownum); + s_or_r_input.setAttribute('type', 'hidden'); + s_or_r_input.setAttribute('rownum', rownum); + s_or_r_input.setAttribute('value', "$s_or_r"); + amount_cell.appendChild(s_or_r_input); + var itemdesc_input = document.createElement('INPUT'); + itemdesc_input.setAttribute('name', 'itemdesc'+rownum); + itemdesc_input.setAttribute('id', 'itemdesc'+rownum); + itemdesc_input.setAttribute('type', 'hidden'); + itemdesc_input.setAttribute('rownum', rownum); + itemdesc_input.setAttribute('value', "$desc"); + amount_cell.appendChild(itemdesc_input); + row.appendChild(pkg_cell); + row.appendChild(amount_cell); + rownum++; + !; +}; + +my $key_generator = sub { + my $listref = shift; + my ($cust_bill_pkg, $amount, $hashref) = @$listref; + my $setup_or_recur = $cust_bill_pkg->setup > 0 ? 'setup' : 'recur'; + my $taxlinenum = $hashref->{billpkgtaxlocationnum} || + $hashref->{billpkgtaxratelocationnum} || + ''; + + join(':', $cust_bill_pkg->billpkgnum, $setup_or_recur, $taxlinenum); +}; + +my $onload = 'return true;'; + +if ($cgi->param('error')) { + + my $set_sub_amounts = + join(';', map { "myform.subamount$_.value = ". $cgi->param("subamount$_") } + grep { /.+/ } + map { /^subnum(\d+)$/ ? $1 : '' } + $cgi->param + ); + $set_sub_amounts &&= "$set_sub_amounts;sub_changed(myform.subamount0)"; + + $onload = qq! + var myform = document.getElementById('ApplicationForm'); + changed(myform.elements['$dst_pkey']); + $set_sub_amounts; + return true; + !; +} + +</%init> diff --git a/httemplate/edit/elements/category_Common.html b/httemplate/edit/elements/category_Common.html new file mode 100644 index 000000000..8bbdcd15b --- /dev/null +++ b/httemplate/edit/elements/category_Common.html @@ -0,0 +1,24 @@ +<% include( 'edit.html', + 'fields' => [ + 'categoryname', + 'weight', + { field=>'disabled', type=>'checkbox', value=>'Y', }, + ], + 'labels' => { + 'categorynum' => 'Category number', + 'categoryname' => 'Category name', + 'weight' => 'Weight', + 'disabled' => 'Disable category', + }, + 'viewall_dir' => 'browse', + %opt, + ) +%> +<%init> + +die "access denied" + unless $FS::CurrentUser::CurrentUser->access_right('Configuration'); + +my %opt = @_; + +</%init> diff --git a/httemplate/edit/elements/class_Common.html b/httemplate/edit/elements/class_Common.html new file mode 100644 index 000000000..b5f493991 --- /dev/null +++ b/httemplate/edit/elements/class_Common.html @@ -0,0 +1,32 @@ +<% include( 'edit.html', + 'fields' => [ + 'classname', + (scalar(@category) + ? { field=>'categorynum', type=>'select-table', 'empty_label'=>'(none)', 'table'=>'pkg_category', 'name_col'=>'categoryname' } + : { field=>'categorynum', type=>'hidden' } + ), + { field=>'disabled', type=>'checkbox', value=>'Y', }, + ], + 'labels' => { + 'classnum' => 'Class number', + 'classname' => 'Class name', + 'categorynum' => 'Category', + 'disabled' => 'Disable class', + }, + 'viewall_dir' => 'browse', + %opt, + ) + +%> +<%init> + +die "access denied" + unless $FS::CurrentUser::CurrentUser->access_right('Configuration'); + +my %opt = @_; + +my $table = $opt{'table'}; +( my $category_table = $table ) =~ s/class/category/ or die; + +my @category = qsearch($category_table, { 'disabled' => '' }); +</%init> diff --git a/httemplate/edit/elements/edit.html b/httemplate/edit/elements/edit.html new file mode 100644 index 000000000..b19b3618c --- /dev/null +++ b/httemplate/edit/elements/edit.html @@ -0,0 +1,859 @@ +<%doc> + +Example: + + include( 'elements/edit.html', + 'name_singular' => #singular name for the record + # (preferred, will be pluralized automatically) + 'name' => #name for the record + # (deprecated, will be pluralized simplistically) + 'table' => + + #? 'primary_key' => #required when the dbdef doesn't know...??? + 'labels' => { + 'column' => 'Label', + } + + #listref - each item is a literal column name (or method) or hashref + # or (notyet) coderef + #if not specified all columns (except for the primary key) will be editable + 'fields' => [ + 'columname', + { 'field' => 'another_columname', + 'type' => 'text', #text + #password + #money + #percentage + #checkbox + #select + #selectlayers (can now use after a tablebreak-tr-title... but not inside columnstart/columnnext/columnend) + #title + #tablebreak-tr-title + #columnstart + #columnnext + #columnend + #hidden - hidden value from object + #fixed - display fixed value from object or here + #fixed-country + #fixed-state + 'value' => 'Y', #for checkbox, title, fixed, hidden + 'disabled' => 0, + 'onchange' => 'javascript_function', + + 'include_opt_callback' => sub { my $object = @_; + ( 'option' => 'value', ); + }, + + 'm2name_table' => 'table_name', + 'm2name_namecol' => 'name_column', + #OR# + 'm2m_method' => + #'m2m_srccol' => #opt, if not the same as this table + 'm2m_dstcol' => #required for now, eventuaully opt, if not the same as target table + #OR# + 'o2m_table' => + + 'm2_label' => 'Label', # + 'm2_new_default' => \@table_name_objects, #default + #m2 objects for + #new records + 'm2_error_callback' => sub { my($cgi, $object) = @_; }, + 'm2_remove_warnings' => \%warnings, #hashref of warning + #messages for m2 + #removal + 'm2_new_js' => 'function_name', #javascript function called + #on spawned rows (one arg: + #new_element) + 'm2_remove_js' => 'function_name', #js function called when + #a row is deleted (three + #args: value, text, + #'no_match') + #layer_fields & layer_values_callback only for selectlayer + 'layer_fields' => [ + 'fieldname' => 'Label', + 'another_field' => { + label=>'Label', + type =>'text', #text, money + }, + ], + 'layer_values_callback' => + sub { + my( $cgi, $object ) = @_; + { 'layer' => { 'fieldname' => 'current_value', + 'fieldname2' => 'field2value', + ... + }, + 'layer2' => { 'l2fieldname' => 'l2value', + ... + }, + ... + }; + }, + }, + ] + + 'menubar' => '', #menubar arrayref + + #agent virtualization + 'agent_virt' => 1, + 'agent_null' => 1, #if true, always allow no-agentnum globals + 'agent_null_right' => 'Access Right Name', + 'agent_clone_extra_sql' => '', #if provided, this overrides the extra_sql + #implementing agent virt, for clone + #operations. i.e. pass "1=1" to allow + #cloning anything + + 'viewall_dir' => '', #'search' or 'browse', defaults to 'search' + + # overrides default popurl(1)."process/$table.html" + 'post_url' => popurl(1).'process/something', + + #we're in a popup (no title/menu/searchboxes) + 'popup' => 1, + + ### + # HTML callbacks + ### + + 'body_etc' => '', # Additional BODY attributes, i.e. onLoad="" + + 'html_init' => '', #after the header/menubar + + #string or coderef of additional HTML to add before </TABLE> + 'html_table_bottom' => '', + + #after </TABLE> but before the submit + 'html_bottom' => '', #string + 'html_bottom' => sub { + my $object = shift; + # ... + "html_string"; + }, + + #javascript function name, will be called with form name as arg + 'onsubmit' => 'check_form_data', + + #at the very bottom (well, as low as you can go from here) + 'html_foot' => '', + + ### + # initialization callbacks + ### + + ###global callbacks, always run if provided + + #after decoding long CGI "redirect=" responses but + # before object creation/search + # (useful if you have a long form that might trigger redirect= and you need + # to do things with $cgi params - they're not decoded in the calling + # <%init> block yet) + 'begin_callback' = sub { my( $cgi, $fields_listref, $opt_hashref ) = @_; }, + + #after the mode-specific object creation/search + 'end_callback' = sub { my( $cgi, $object, $fields_listref, $opt_hashref ) = @_; }, + + ###mode-specific callbacks. one (and only one) of these four is called + + #run when adding + 'new_callback' => sub { my( $cgi, $object, $fields_listref, $opt_hashref ) = @_; }, + + #run when editing + 'edit_callback' => sub { my( $cgi, $object, $fields_listref, $opt_hashref ) = @_; }, + + #run when re-displaying with an error + 'error_callback' => sub { my( $cgi, $object, $fields_listref, $opt_hashref ) = @_; }, + + #run when cloning + 'clone_callback' => sub { my( $cgi, $object, $fields_listref, $opt_hashref ) = @_; }, + + ###callbacks called in new mode only + + # returns a hashref for the new object + 'new_hashref_callback' + + # returns the new object iself (otherwise, ->new is called) + 'new_object_callback' + + ###display callbacks + + #run before display to return a different value + 'value_callback' => sub { my( $columname, $value ) = @_; }, + + #run before display to manipulate element of the 'fields' arrayref + 'field_callback' => sub { my( $cgi, $object, $field_hashref ) = @_; }, + + ); + +</%doc> + +<% include('/elements/header'. ( $opt{popup} ? '-popup' : '' ). '.html', + $title, + include( '/elements/menubar.html', @menubar ), + $opt{'body_etc'}, + ) +%> + +<% defined($opt{'html_init'}) + ? ( ref($opt{'html_init'}) + ? &{$opt{'html_init'}}() + : $opt{'html_init'} + ) + : '' +%> + +<% include('/elements/error.html') %> + +% my $url = $opt{'post_url'} || popurl(1)."process/$table.html"; + +<FORM NAME = "edit_topform" + METHOD = POST + ACTION = "<% $url %>" + <% $opt{onsubmit} ? 'onSubmit="return '.$opt{onsubmit}.'(this)"' : '' %> +> + +<INPUT TYPE="hidden" NAME="svcdb" VALUE="<% $table %>"> +<INPUT TYPE="hidden" NAME="<% $pkey %>" VALUE="<% $clone ? '' : $object->$pkey() %>"> + +<FONT SIZE="+1"><B> +<% ( $opt{labels} && exists $opt{labels}->{$pkey} ) + ? $opt{labels}->{$pkey} + : $pkey +%> +</B></FONT> +#<% ( !$clone && $object->$pkey() ) || "(NEW)" %> + +% my $tablenum = 0; +<TABLE ID="TableNumber<% $tablenum++ %>" BGCOLOR="#cccccc" BORDER=0 CELLSPACING=0> + +% my $g_row = 0; +% my @g_row_stack = (); +% foreach my $f ( map { ref($_) ? $_ : {'field'=>$_} } +% @$fields +% ) { +% +% my $trash = &{ $opt{'field_callback'} }( $cgi, $object, $f ) +% if $opt{'field_callback'}; +% +% my $field = $f->{'field'}; +% my $type = $f->{'type'} ||= 'text'; +% +% my $label = ( $opt{labels} && exists $opt{labels}->{$field} ) +% ? $opt{labels}->{$field} +% : $field; +% +% my $onchange = $f->{'onchange'}; +% +% my $layer_values = {}; +% $layer_values = &{ $f->{'layer_values_callback'} }( $cgi, $object ) +% if $f->{'layer_values_callback'} +% && ! $f->{'m2name_table'} +% && ! $f->{'o2m_table'} +% && ! $f->{'m2m_method'}; +% +% warn "layer values: ". Dumper($layer_values) +% if $opt{'debug'}; +% +% my %include_common = ( +% +% #text and derivitives +% 'size' => $f->{'size'}, +% 'maxlength' => $f->{'maxlength'}, +% 'postfix' => $f->{'postfix'}, +% +% #checkbox, title, fixed, hidden +% #& deprecated weird value hashref used only by reason.html +% 'value' => $f->{'value'}, +% +% #select(-*) +% 'options' => $f->{'options'}, +% 'labels' => $f->{'labels'}, +% 'multiple' => $f->{'multiple'}, +% 'disable_empty' => $f->{'disable_empty'}, +% #select-reason +% 'reason_class' => $f->{'reason_class'}, +% +% #selectlayers +% 'layer_fields' => $f->{'layer_fields'}, +% 'layer_values' => $layer_values, +% 'html_between' => $f->{'html_between'}, +% +% #umm. for select-agent_types at least +% 'disabled' => $f->{'disabled'}, +% ); +% +% #selectlayers, others? +% $include_common{$_} = $f->{$_} +% foreach grep exists($f->{$_}), +% qw( js_only html_only select_only layers_only cell_style); +% +% #select-* +% $include_common{$_} = $f->{$_} +% foreach grep exists($f->{$_}), qw( empty_label ); +% +% #select-table +% $include_common{$_} = $f->{$_} +% foreach grep exists($f->{$_}), qw( value_col extra_sql ); +% +% #select-table, checkboxes-table +% $include_common{$_} = $f->{$_} +% foreach grep exists($f->{$_}), qw( table name_col ); +% +% #checkboxes-table +% $include_common{$_} = $f->{$_} +% foreach grep exists($f->{$_}), qw( target_table link_table ); +% +% #*-table +% $include_common{$_} = $f->{$_} +% foreach grep exists($f->{$_}), +% qw( hashref agent_virt agent_null agent_null_right ); +% +% #htmlarea +% $include_common{$_} = $f->{$_} +% foreach grep exists($f->{$_}), qw( width height ); +% +% +% if ( $type eq 'tablebreak-tr-title' ) { +% $include_common{'table_id'} = 'TableNumber'. $tablenum++; +% } +% if ( $type eq 'tablebreak-tr-title' || $type eq 'title' ) { +% $include_common{'colspan'} = $f->{colspan} if $f->{colspan}; +% } +% +% if ( $f->{include_opt_callback} ) { +% %include_common = ( %include_common, +% &{ $f->{include_opt_callback} }( $object ) +% ); +% } +% +% my $layer_prefix_on = ''; +% +% my $include_sub = sub { +% my %opt = @_; +% +% my $fieldnum = delete $opt{'fieldnum'}; +% +% my $include = $type; +% $include = "input-$include" if $include =~ /^(text|money|percentage)$/; +% $include = "tr-$include" unless $include =~ /^(hidden|tablebreak|column)/; +% +% $include_common{'layer_prefix'} = "$field$fieldnum." +% if $layer_prefix_on; +% +% my @include = +% ( "/elements/$include.html", +% 'field' => "$field$fieldnum", +% 'id' => "$field$fieldnum", #separate? +% 'label_id' => $field."_label$fieldnum", #don't want field0_label0... +% %include_common, +% %opt, +% ); +% @include; +% }; +% +% my $column_sub = sub { +% my %opt = @_; +% +% my $column = delete($opt{field}); +% my $fieldnum = delete($opt{fieldnum}); +% my $include = delete($opt{type}) || 'text'; +% $include = "input-$include" if $include =~ /^(text|money|percentage)$/; +% +% ( "/elements/$include.html", +% 'field' => $field.'__'.$column.$fieldnum, +% 'id' => $field.'__'.$column.$fieldnum, +% 'layer_prefix' => $field.'__'.$column.$fieldnum.".", +% ( $fieldnum +% ? ('cell_style' => 'border-top:1px solid black') +% : () +% ), +% 'cgi' => $cgi, +% %opt, +% ); +% }; +% +% unless ( $type =~ /^column/ ) { +% $g_row = 1 if $type eq 'tablebreak-tr-title'; +% $g_row++; +% $g_row++ if $type eq 'title'; +% } else { +% if ( $type eq 'columnstart' ) { +% push @g_row_stack, $g_row; +% $g_row = 0; +% #} elsif ( $type eq 'columnnext' ) { +% } elsif ( $type eq 'columnend' ) { +% $g_row = pop @g_row_stack; +% } +% +% } +% +% my $fieldnum = ''; +% my $curr_value = ''; +% if ( $f->{'m2name_table'} || $f->{'o2m_table'} || $f->{'m2m_method'} ) { +% +% my($table, $col); +% if ( $f->{'m2name_table'} ) { +% $table = $f->{'m2name_table'}; +% $col = $f->{'m2name_namecol'}; +% } elsif ( $f->{'o2m_table'} ) { +% $table = $f->{'o2m_table'}; +% $col = dbdef->table($f->{'o2m_table'})->primary_key; +% } elsif ( $f->{'m2m_method'} ) { +% $table = $f->{'m2m_method'}; +% $col = $f->{'m2m_dstcol'}; +% } +% $fieldnum = 0; +% $layer_prefix_on = 1; +% #print out the fields for the existing m2s +% my @existing = (); +% if ( $mode eq 'error' ) { +% @existing = &{ $f->{'m2_error_callback'} }( $cgi, $object ); +% } elsif ( $object->$pkey() ) { # $mode eq 'edit'||'clone' +% @existing = $object->$table(); +% warn scalar(@existing). " from $object->$table: ". join('/', @existing) +% if $opt{'debug'}; +% } elsif ( $f->{'m2_new_default'} ) { # && $mode eq 'new' +% @existing = @{ $f->{'m2_new_default'} }; +% } +% foreach my $name_obj ( @existing ) { +% +% my $ex_label = '<INPUT TYPE="button" VALUE="X" TITLE="Remove this '. +% lc($f->{'m2_label'}). +% qq(" onClick="remove_$field($fieldnum);"). +% ' STYLE="color:#ff0000;font-weight:bold;'. +% 'padding-left:2px;padding-right:2px"'. +% '> '. ($f->{'m2_label'} || $field ). ' '; +% +% if ( $f->{'layer_values_callback'} ) { +% my %switches = ( 'mode' => $mode ); +% $layer_values = +% &{ $f->{'layer_values_callback'} }( $cgi, $name_obj, \%switches ); +% } +% warn "layer values: ". Dumper($layer_values) +% if $opt{'debug'}; +% +% my @existing = &{ $include_sub }( +% 'label' => $ex_label, +% 'fieldnum' => $fieldnum, +% 'curr_value' => $name_obj->$col(), +% 'onchange' => $onchange, +% 'layer_values' => $layer_values, +% 'cell_style' => ( $fieldnum ? 'border-top:1px solid black' : '' ), +% ); +% $existing[0] =~ s(^/elements/tr-)(/elements/); +% my @label = @existing; +% $label[0] = '/elements/tr-td-label.html'; + + <% include( @label ) %> + <TD COLSPAN="<% $f->{'colspan'} || 1 %>"> + <% include( @existing ) %> + </TD> + +% if ( $f->{'m2_fields'} ) { +% foreach my $c ( @{ $f->{'m2_fields'} } ) { +% my $column = $c->{field}; +% my @column = &{ $column_sub }( %$c, +% 'fieldnum' => $fieldnum, +% 'curr_value' => $name_obj->$column() +% ); + + <TD id='<% $field %>__<% $column %>_label<% $fieldnum %>' + style='text-align:right;vertical-align:top; + border-top:1px solid black;padding-top:5px;'> + <% $c->{'label'} || '' %> + </TD> + <TD style='border-top:1px solid black;padding-top:3px;'> + <% include( @column ) %> + </TD> +% } +% } + + </TR> + +% $fieldnum++; +% $g_row++; +% } +% #$field .= $fieldnum; +% $onchange .= "\nspawn_$field(what);"; +% } else { +% if ( $f->{curr_value_callback} ) { +% $curr_value = &{ $f->{curr_value_callback} }( $cgi, $object, $field ), +% } else { +% $curr_value = $object->$field(); +% } +% $curr_value = &{ $opt{'value_callback'} }( $f->{'field'}, $curr_value ) +% if $opt{'value_callback'} && $mode ne 'error'; +% } +% +% my @include = &{ $include_sub }( +% 'label' => $label, +% 'fieldnum' => $fieldnum, +% 'curr_value' => $curr_value, +% 'object' => $object, +% 'cgi' => $cgi, +% 'onchange' => $onchange, +% ( $fieldnum ? ('cell_style' => 'border-top:1px solid black') : () ), +% ); +% +% if ( $f->{'m2name_table'} || $f->{'o2m_table'} || $f->{'m2m_method'} ) { +% $include[0] =~ s(^/elements/tr-)(/elements/); +% my @label = @include; +% $label[0] = '/elements/tr-td-label.html'; + + <% include( @label ) %> + <TD COLSPAN="<% $f->{'colspan'} || 1 %>"> + <% include( @include ) %> + </TD> + +% if ( $f->{'m2_fields'} ) { +% foreach my $c ( @{ $f->{'m2_fields'} } ) { +% my $column = $c->{field}; +% my @column = &{ $column_sub }( %$c, 'fieldnum' => $fieldnum ); + + <TD id='<% $field %>__<% $column %>_label<% $fieldnum %>' + style='text-align:right;vertical-align:top; + border-top:1px solid black;padding-top:5px;'> + <% $c->{'label'} || '' %> + </TD> + <TD style='border-top:1px solid black;padding-top:3px;'> + <% include( @column ) %> + </TD> +% } +% } + + </TR> + +% } else { + + <% include( @include ) %> + +% } +% if ( $f->{'m2name_table'} || $f->{'o2m_table'} || $f->{'m2m_method'} ) { + + <SCRIPT TYPE="text/javascript"> + + var <%$field%>_rownum = <% $g_row %>; + var <%$field%>_fieldnum = <% $fieldnum %>; + + function spawn_<%$field%>(what) { + + // only spawn if we're the last element... return if not + + var field_regex = /(\d+)(_[a-z]+)?$/; + var match = field_regex.exec(what.name); + if ( !match ) { + alert(what.name + " didn't match for " + what); + return; + } + if ( match[1] != <%$field%>_fieldnum ) { + return; + } + + // change the label on the last entry & add a remove button + var prev_label = document.getElementById('<% $field %>_label' + <%$field%>_fieldnum ); + prev_label.innerHTML = '<INPUT TYPE="button" VALUE="X" TITLE="Remove this <% lc($f->{'m2_label'}) %>" onClick="remove_<% $field %>(' + <%$field%>_fieldnum + ');" STYLE="color:#ff0000;font-weight:bold;padding-left:2px;padding-right:2px" > <% $f->{'m2_label'} || $field %>'; + + <%$field%>_fieldnum++; + + //get the new widget + +% $include[0] =~ s(^/elements/tr-)(/elements/); +% my @layer_opt = ( @include, +% 'field' => $field."MAGIC_NUMBER", +% 'id' => $field."MAGIC_NUMBER", +% 'layer_prefix' => $field."MAGIC_NUMBER.", +% ); +% warn @layer_opt if $opt{'debug'}; + + var newrow = <% include(@layer_opt, html_only=>1) |js_string %>; + +% #until the rest have html/js_only +% if ( $type eq 'selectlayers' || $type =~ /^select-cgp_rule_/ ) { + var newfunc = <% include(@layer_opt, js_only=>1) |js_string %>; +% } else { + var newfunc = ''; +% } + + // substitute in the new field name + var magic_regex = /MAGIC_NUMBER/g; + newrow = newrow.replace( magic_regex, <%$field%>_fieldnum ); + newfunc = newfunc.replace( magic_regex, <%$field%>_fieldnum ); + + // evaluate new_func + if (window.ActiveXObject) { + window.execScript(newfunc); + } else { /* (window.XMLHttpRequest) */ + //window.eval(newfunc); + setTimeout(newfunc, 0); + } + + // add new row + + //hmm, can't use selectlayers after a tablebreak-title for now + var table = document.getElementById('TableNumber<% $tablenum-1 %>'); + + var row = table.insertRow(<%$field%>_rownum++); + + var label_cell = document.createElement('TD'); + + label_cell.id = '<% $field %>_label' + <%$field%>_fieldnum; + + label_cell.style.textAlign = "right"; + label_cell.style.verticalAlign = "top"; + label_cell.style.borderTop = "1px solid black"; + label_cell.style.paddingTop = "5px"; + + label_cell.innerHTML = '<% $label %>'; + + row.appendChild(label_cell); + + var widget_cell = document.createElement('TD'); + + widget_cell.style.borderTop = "1px solid black"; + widget_cell.style.paddingTop = "3px"; + widget_cell.colSpan = "<% $f->{'colspan'} || 1 %>" + + widget_cell.innerHTML = newrow; + + row.appendChild(widget_cell); + +% if ( $f->{'m2_fields'} ) { +% foreach my $c ( @{ $f->{'m2_fields'} } ) { +% my $column = $c->{field}; +% my @column = &{ $column_sub }(%$c, 'fieldnum' => 'MAGIC_NUMBER'); + + var column = <% include(@column, html_only=>1) |js_string %>; + column = column.replace( magic_regex, <%$field%>_fieldnum ); + + var column_label = document.createElement('TD'); + column_label.id = + '<% $field %>__<% $column %>_label' + <%$field%>_fieldnum; + + column_label.style.textAlign = "right"; + column_label.style.verticalAlign = "top"; + column_label.style.borderTop = "1px solid black"; + column_label.style.paddingTop = "5px"; + + column_label.innerHTML = '<% $c->{'label'} || '' %>'; + + row.appendChild(column_label); + + var column_widget = document.createElement('TD'); + + column_widget.style.borderTop = "1px solid black"; + column_widget.style.paddingTop = "3px"; + + column_widget.innerHTML = column; + + row.appendChild(column_widget); + +% } +% } + +% if ( $f->{'m2_new_js'} ) { + // take out items selected in previous dropdowns + var new_element = document.getElementById("<%$field%>" + <%$field%>_fieldnum ); + <% $f->{'m2_new_js'} %>(new_element); + + if ( new_element.length < 2 ) { + //just the ** Select new **, so don't display the row + row.style.display = 'none'; + } +% } + + } + + function remove_<%$field%>(remove_fieldnum) { + //alert("remove <%$field%> " + remove_fieldnum); + var select = document.getElementById('<%$field%>' + remove_fieldnum); + + if ( ! select ) { + alert("can't find element <%$field%>" + remove_fieldnum); + return; + } + +% my $warnings = $f->{'m2_remove_warnings'}; +% if ( $warnings ) { + var sel_value = select.options[select.selectedIndex].value; +% foreach my $value ( keys %$warnings ) { + if ( sel_value == '<% $value %>' ) { + if ( ! confirm( <% $warnings->{$value} |js_string %> ) ) { + return; + } + } +% } +% } + + select.disabled = 'disabled'; // this seems to prevent it from being submitted on tested browsers so far (IE, moz, konq at least) + var label_td = document.getElementById('<%$field%>_label' + remove_fieldnum ); + label_td.parentNode.style.display = 'none'; + +% if ( $f->{m2_remove_js} ) { + var opt = select.options[select.selectedIndex]; + <% $f->{m2_remove_js} %>( opt.value, opt.text, 'no_match'); +% } + + } + + </SCRIPT> + +% } + +% } + +<% ref( $opt{'html_table_bottom'} ) + ? &{ $opt{'html_table_bottom'} }( $object ) + : $opt{'html_table_bottom'} +%> + +</TABLE> + +<% ref( $opt{'html_bottom'} ) + ? &{ $opt{'html_bottom'} }( $object ) + : $opt{'html_bottom'} +%> + +<BR> + +<INPUT TYPE = "submit" + ID = "submit" + VALUE = "<% ( !$clone && $object->$pkey() ) + ? "Apply changes" + : "Add ". ( $opt{'name'} || $opt{'name_singular'} ) + %>" +> + +</FORM> + +<% ref( $opt{'html_foot'} ) + ? &{ $opt{'html_foot'} }( $object ) + : $opt{'html_foot'} +%> + +<% include("/elements/footer.html") %> +<%init> + +my(%opt) = @_; + +my $curuser = $FS::CurrentUser::CurrentUser; + +#false laziness w/process.html +my $table = $opt{'table'}; +my $class = "FS::$table"; +my $pkey = dbdef->table($table)->primary_key; #? $opt{'primary_key'} || +my $fields = $opt{'fields'} + #|| [ grep { $_ ne $pkey } dbdef->table($table)->columns ]; + || [ grep { $_ ne $pkey } fields($table) ]; +#my @actualfields = map { ref($_) ? $_->{'field'} : $_ } @$fields; + +if ( $cgi->param('redirect') ) { + my $session = $cgi->param('redirect'); + my $pref = $curuser->option("redirect$session"); + die "unknown redirect session $session\n" unless length($pref); + $cgi = new CGI($pref); +} + +&{$opt{'begin_callback'}}( $cgi, $fields, \%opt ) + if $opt{'begin_callback'}; + +my %qsearch = ( + 'table' => $table, + 'extra_sql' => ( $opt{'agent_virt'} + ? ' AND '. $curuser->agentnums_sql( + 'null_right' => $opt{'agent_null_right'} + ) + : '' + ), +); + +my $mode; +my $object; +my $clone = ''; +if ( $cgi->param('error') ) { + + $mode = 'error'; + + $object = $class->new( { + map { $_ => scalar($cgi->param($_)) } fields($table) + }); + + &{$opt{'error_callback'}}( $cgi, $object, $fields, \%opt ) + if $opt{'error_callback'}; + +} elsif ( $cgi->param('clone') =~ /^(\d+)$/ ) { + + $mode = 'clone'; + + $clone = $1; + + $qsearch{'extra_sql'} = ' AND '. $opt{'agent_clone_extra_sql'} + if $opt{'agent_clone_extra_sql'}; + + $object = qsearchs({ %qsearch, 'hashref' => { $pkey => $clone } }) + or die "$pkey $clone not found in $table"; + + &{$opt{'clone_callback'}}( $cgi, $object, $fields, \%opt ) + if $opt{'clone_callback'}; + + #$object->$pkey(''); + + $opt{action} ||= 'Add'; + +} elsif ( $cgi->keywords || $cgi->param($pkey) ) { #editing + + $mode = 'edit'; + + my $value; + if ( $cgi->param($pkey) ) { + $value = $cgi->param($pkey) + } else { + my( $query ) = $cgi->keywords; + $value = $query; + } + $value =~ /^(\d+)$/ or die "unparsable $pkey"; + $object = qsearchs({ %qsearch, 'hashref' => { $pkey => $1 } }) + or die "$pkey $1 not found in $table"; + + warn "$table $pkey => $1" + if $opt{'debug'}; + + &{$opt{'edit_callback'}}( $cgi, $object, $fields, \%opt ) + if $opt{'edit_callback'}; + +} else { #adding + + $mode = 'new'; + + my $hashref = $opt{'new_hashref_callback'} + ? &{$opt{'new_hashref_callback'}} + : {}; + + $object = $opt{'new_object_callback'} + ? &{$opt{'new_object_callback'}}( $cgi, $hashref, $fields, \%opt ) + : $class->new( $hashref ); + + &{$opt{'new_callback'}}( $cgi, $object, $fields, \%opt ) + if $opt{'new_callback'}; + +} + +&{$opt{'end_callback'}}( $cgi, $object, $fields, \%opt ) + if $opt{'end_callback'}; + +$opt{action} ||= $object->$pkey() ? 'Edit' : 'Add'; + +my $title = $opt{action}. ' '. ( $opt{name} || $opt{'name_singular'} ); + +my $viewall_url = $p . ( $opt{'viewall_dir'} || 'search' ) . "/$table.html"; +$viewall_url = $opt{'viewall_url'} if $opt{'viewall_url'}; + +my @menubar = (); +if ( $opt{'menubar'} ) { + @menubar = @{ $opt{'menubar'} }; +} else { + my $items = $opt{'name'} ? $opt{'name'}.'s' : PL($opt{'name_singular'}); + @menubar = ( + "View all $items" => $viewall_url, + ); +} + +</%init> diff --git a/httemplate/edit/elements/rate_detail.html b/httemplate/edit/elements/rate_detail.html new file mode 100644 index 000000000..faf11f844 --- /dev/null +++ b/httemplate/edit/elements/rate_detail.html @@ -0,0 +1,239 @@ +<%doc> +<% include('/edit/elements/rate_detail.html', + # required + 'ratenum' => '1', + + # optional + 'regionnum' => '25', + # or + 'countrycode' => '237', +) %> + +If regionnum is specified, this produces column headers plus +one row of rate details for that region (in all time periods). +Otherwise, there's one row for each region in the specified +countrycode (or each region anywhere, if there is no countrycode), +with row headers showing the region name and prefixes. + +</%doc> +<% include('/elements/table-grid.html') %> +<TR> +% my $col = 0; +% foreach (@header) { +% my $hlink = $hlinks[$col]; + <TH CLASS = "grid", + BGCOLOR = "#cccccc"> + <% $hlink ? qq!<A HREF="$hlink">$_</A>! : $_ %> + </TH> +% $col++; +% } #foreach @header +</TR><TR> +% my $row = 0; +% foreach my $r (@rows) { +% $col = 0; +% if ( !$opt{'regionnum'} ) { +% $region = $r; +% foreach ($r->regionname, $r->prefixes_short) { + <TD> + <A HREF="<% $p.'edit/rate_region.cgi?'.$r->regionnum %>"><% $_ %></A> + </TD> +% } +% } +% elsif ( !$opt{'ratenum'} ) { +% $rate = $r; + <TD> + <A HREF="<% $p.'edit/rate.cgi?'.$r->ratenum %>"><% $r->ratename %></A> + </TD> +% } +% foreach my $rate_time (@rate_time, '') { + <TD> +% my $detail = $details[$row][$col]; +% if($detail) { + <TABLE CLASS="inv" STYLE="border:none"> + <TR><TD><% edit_link($detail) %><% $money_char.$detail->min_charge %> + <% $detail->sec_granularity ? ' / minute':' / call' %> + <% $edit_hint %></A> + </TD></TR> + <% granularity_detail($detail) %> + <% min_included_detail($detail) %> + <% conn_charge_detail($detail) %> + <TR><TD><% $rate_time ? delete_link($detail) : '' %></TD></TR> + </TABLE> +% } +% else { #!$detail + <% add_link($rate, $region, $rate_time) %> +% } +% $col++; + </TD> +% } # foreach @rate_time +</TR> +% $row++; +% }# foreach @rate_region +</TABLE> + +<%once> + +tie my %granularity, 'Tie::IxHash', FS::rate_detail::granularities(); +tie my %conn_secs, 'Tie::IxHash', FS::rate_detail::conn_secs(); + +my $conf = new FS::Conf; +my $money_char = $conf->config('money_char') || '$'; + +sub small { + '<FONT SIZE="-1">'.shift.'</FONT>' +} +my $edit_hint = small('(edit)'); + +sub edit_link { + my $rate_detail = shift; + my $ratedetailnum = $rate_detail->ratedetailnum; + '<A HREF="javascript:void(0);" onclick="'. + include( '/elements/popup_link_onclick.html', + 'action' => "${p}edit/rate_detail.html?$ratedetailnum", + 'actionlabel' => 'Edit rate', + 'height' => 420, + #default# 'width' => 540, + #default# 'color' => '#333399', + ) . '">' +} + +sub add_link { + my ($rate, $region, $rate_time) = @_; + '<A HREF="javascript:void(0);" onclick="'. + include( '/elements/popup_link_onclick.html', + 'action' => "${p}edit/rate_detail.html?ratenum=". + $rate->ratenum. + ';dest_regionnum='. + $region->regionnum. + ($rate_time ? + ';ratetimenum='.$rate_time->ratetimenum : + ''), + 'actionlabel' => 'Add rate', + 'height' => 420, + ).'">'.small('(add)').'</A>' +} + +sub delete_link { + my $rate_detail = shift; + my $ratedetailnum = $rate_detail->ratedetailnum; + my $onclick = include( '/elements/popup_link_onclick.html', + 'action' => "${p}misc/delete-rate_detail.html?$ratedetailnum", + 'actionlabel' => 'Delete rate', + 'width' => 510, + 'height' => 315, + 'frame' => 'top', + ); + $onclick = "if(confirm('Delete this rate?')) { $onclick }"; + qq!<A HREF="javascript:void(0);" onclick="$onclick">!.small('(delete)').'</A>' +} + +sub granularity_detail { + my $rate_detail = shift; + if($rate_detail->sec_granularity != 60 && $rate_detail->sec_granularity > 0) { + '<TR><TD>'. + small('in '.$granularity{$rate_detail->sec_granularity}.' increments'). + '</TD></TR>'; + } + else { '' } +} + +sub min_included_detail { + my $rate_detail = shift; + if($rate_detail->min_included) { + '<TR><TD>'. + small( $rate_detail->min_included . + ($rate_detail->sec_granularity ? + ' minutes included' : + ' calls included') ). + '</TD></TR>' + } + else { '' } +} + +sub conn_charge_detail { + my $rate_detail = shift; + if($rate_detail->conn_charge > 0) { + #return '' unless $rate_detail->conn_charge > 0 || $rate_detail->conn_sec; + '<TR><TD>'. + small( $money_char. $rate_detail->conn_charge. + ' for '.$conn_secs{$rate_detail->conn_sec} + ). + '</TD></TR>' + } + else { '' } +} + +</%once> +<%init> + +die "access denied" + unless $FS::CurrentUser::CurrentUser->access_right('Configuration'); + +my %opt = @_; +my $ratenum = $opt{'ratenum'} || ''; +my $regionnum = $opt{'regionnum'} || ''; + +# either of these, if the $opt isn't passed, will be set to the +# correct object when generating each row. +my $rate = qsearchs('rate', { 'ratenum' => $ratenum } ) if $ratenum; +my $region = qsearchs('rate_region', { 'regionnum' => $regionnum }) if $regionnum; + +my @rate_time = qsearch('rate_time', {}); +my @header = ( + map( { $_->ratetimename } @rate_time ), + 'Default rate'); +my @hlinks = map( {$p.'edit/rate_time.cgi?'.$_->ratetimenum} @rate_time ), ''; +my @rtns = ( map( { $_->ratetimenum } @rate_time ), '' ); + +my @details; +my @rows; +if ( $ratenum ) { + if ( $regionnum ) { + @rows = qsearch('rate_region', + { ratenum => $ratenum, regionnum => $regionnum }); + } + else { + my $where = ''; + if ( $opt{'countrycode'} ) { + $where = "WHERE 0 < ( + SELECT COUNT(*) FROM rate_prefix + WHERE rate_prefix.regionnum = rate_region.regionnum + AND countrycode = '$opt{countrycode}' + )"; + } + @rows = qsearch({ table => 'rate_region', + hashref => { }, + extra_sql => $where, + }); + die "no region found" if !@rows; + + unshift @header, 'Region', 'Prefix(es)'; + unshift @hlinks, '', ''; + } + foreach my $region (@rows) { + push @details, [ map { qsearchs('rate_detail', + { 'ratenum' => $ratenum, + 'dest_regionnum' => $region->regionnum, + 'ratetimenum' => $_ } ) or '' + } @rtns + ]; + } +} +elsif ( $regionnum ) { + @rows = qsearch('rate', {}) or die "no rate plans found"; + unshift @header, 'Rate plan'; + unshift @hlinks, ''; + foreach my $rate (@rows) { + push @details, [ map { qsearchs('rate_detail', + { 'ratenum' => $rate->ratenum, + 'dest_regionnum' => $regionnum, + 'ratetimenum' => $_ } ) or '' + } @rtns + ]; + } +} +else { + die "no ratenum or regionnum specified"; +} + +</%init> diff --git a/httemplate/edit/elements/svc_Common.html b/httemplate/edit/elements/svc_Common.html new file mode 100644 index 000000000..e74f44276 --- /dev/null +++ b/httemplate/edit/elements/svc_Common.html @@ -0,0 +1,211 @@ +<% include( 'edit.html', + + 'menubar' => [], + + 'error_callback' => sub { + my( $cgi, $svc_x, $fields, $opt ) = @_; + #$svcnum = $svc_x->svcnum; + $pkgnum = $cgi->param('pkgnum'); + $svcpart = $cgi->param('svcpart'); + + $part_svc = qsearchs( 'part_svc', { svcpart=>$svcpart }); + die "No part_svc entry!" unless $part_svc; + + label_fixup($part_svc, $opt); + + $svc_x->setfield('svcpart', $svcpart); + }, + + 'edit_callback' => sub { + my( $cgi, $svc_x, $fields, $opt ) = @_; + #$svcnum = $svc_x->svcnum; + my $cust_svc = $svc_x->cust_svc + or die "Unknown (cust_svc) svcnum!"; + + $pkgnum = $cust_svc->pkgnum; + $svcpart = $cust_svc->svcpart; + + $part_svc = qsearchs ('part_svc', { svcpart=>$svcpart }); + die "No part_svc entry!" unless $part_svc; + + label_fixup($part_svc, $opt); + }, + + 'new_hashref_callback' => sub { + #my( $cgi, $svc_x ) = @_; + + { pkgnum => $pkgnum, + svcpart => $svcpart, + }; + + }, + + 'new_callback' => sub { + my( $cgi, $svc_x, $fields, $opt ) = @_; + + $part_svc = qsearchs( 'part_svc', { svcpart=>$svcpart }); + die "No part_svc entry!" unless $part_svc; + + label_fixup($part_svc, $opt); + + #$svcnum=''; + + if ( my $cb = $opt{'svc_new_callback'} ) { + my $cust_pkg = $pkgnum + ? qsearchs('cust_pkg', {pkgnum=>$pkgnum}) + : ''; #? + &{ $cb }( $cgi,$svc_x, $part_svc,$cust_pkg, $fields,$opt); + } + + $svc_x->set_default_and_fixed; + + }, + + 'field_callback' => sub { + my ($cgi, $object, $f) = @_; + + my $columndef = $part_svc->part_svc_column($f->{'field'}); + my $flag = $columndef->columnflag; + if ( $flag eq 'F' ) { + $f->{'type'} = length($columndef->columnvalue) + ? 'fixed' + : 'hidden'; + $f->{'value'} = $columndef->columnvalue; + } elsif ( $flag eq 'A' ) { + $f->{'type'} = 'hidden'; + } elsif ( $flag eq 'M' ) { + $f->{'empty_label'} = 'Select inventory item'; + $f->{'type'} = 'select-table'; + $f->{'table'} = 'inventory_item'; + $f->{'name_col'} = 'item'; + $f->{'value_col'} = 'item'; + $f->{'agent_virt'} = 1; + $f->{'agent_null'} = 1; + $f->{'hashref'} = { + 'classnum'=>$columndef->columnvalue, + #'svcnum' => '', + }; + $f->{'extra_sql'} = 'AND ( svcnum IS NULL '; + $f->{'extra_sql'} .= ' OR svcnum = '. $object->svcnum + if $object->svcnum; + $f->{'extra_sql'} .= ' ) '; + $f->{'disable_empty'} = $object->svcnum ? 1 : 0, + } + + if ( $f->{'type'} eq 'select-svc_pbx' + || $f->{'type'} eq 'select-svc-domain' + ) + { + $f->{'include_opt_callback'} = + sub { ( 'pkgnum' => $pkgnum, + 'svcpart' => $svcpart, + ); + }; + } + + if ( $f->{'field'} eq 'custnum' && $pkgnum ) { + my $cust_pkg = qsearchs('cust_pkg', {'pkgnum' => $pkgnum}); + $object->set('custnum', $cust_pkg->custnum); + } + + }, + + 'html_init' => sub { + my $cust_main; + if ( $pkgnum ) { + my $cust_pkg = qsearchs('cust_pkg', {'pkgnum' => $pkgnum}); + $cust_main = $cust_pkg->cust_main if $cust_pkg; + } + $cust_main + ? include( '/elements/small_custview.html', + $cust_main, + '', + 1, + popurl(2). "view/cust_main.cgi" + ). '<BR>' + : ''; + + }, + + 'html_table_bottom' => sub { + my $svc_x = shift; + my $html = ''; + foreach my $field ($svc_x->virtual_fields) { + if ($part_svc->part_svc_column($field)->columnflag ne 'F'){ + # If the flag is X, it won't even show up + # in $svc_acct->virtual_fields. + $html .= + $svc_x->pvf($field)->widget( 'HTML', + 'edit', + $svc_x->getfield($field) + ); + } + } + $html; + }, + + 'html_bottom' => sub { + qq!<INPUT TYPE="hidden" NAME="pkgnum" VALUE="$pkgnum">!. + qq!<INPUT TYPE="hidden" NAME="svcpart" VALUE="$svcpart">!; + }, + + %opt #pass through/override params + ) +%> +<%once> + +sub label_fixup { + my( $part_svc, $opt ) = @_; + + $opt->{'name'} ||= $part_svc->svc; + + my $svcdb = $part_svc->svcdb; + require "FS/$svcdb.pm"; + + if ( UNIVERSAL::can("FS::$svcdb", 'table_info') ) { + #$opt->{'name'} ||= "FS::$svcdb"->table_info->{'name'}; + + my $fields = "FS::$svcdb"->table_info->{'fields'}; + $opt->{'fields'} ||= [ grep { $_ ne 'svcnum' } keys %$fields ]; + + $opt->{labels} ||= { + map { $_ => ( ref($fields->{$_}) + ? $fields->{$_}{'label'} + : $fields->{$_} + ); + } + keys %$fields + }; + } + + #false laziness w/view/svc_Common.html + #override default labels with service-definition labels if applicable + my $labels = $opt->{labels}; # with -> here + foreach my $field ( keys %{ $opt->{labels} } ) { + my $col = $part_svc->part_svc_column($field); + $labels->{$field} = $col->columnlabel if $col->columnlabel !~ /^\s*$/; + } + +} + +</%once> +<%init> + +my %opt = @_; + +#my( $svcnum, $pkgnum, $svcpart, $part_svc ); +my( $pkgnum, $svcpart, $part_svc ); + +#get & untaint pkgnum & svcpart +if ( ! $cgi->param('error') + && $cgi->param('pkgnum') && $cgi->param('svcpart') + ) +{ + $cgi->param('pkgnum') =~ /^(\d+)$/ or die 'unparsable pkgnum'; + $pkgnum = $1; + $cgi->param('svcpart') =~ /^(\d+)$/ or die 'unparsable svcpart'; + $svcpart = $1; + #$cgi->delete_all(); #so edit.html treats this correctly as new?? +} + +</%init> |