RT#17480: Freeside Cancel Reason
authorJonathan Prykop <jonathan@freeside.biz>
Fri, 13 Nov 2015 07:12:37 +0000 (01:12 -0600)
committerJonathan Prykop <jonathan@freeside.biz>
Fri, 13 Nov 2015 07:12:37 +0000 (01:12 -0600)
FS/FS/reason.pm
httemplate/browse/reason.html
httemplate/browse/reason_type.html
httemplate/search/elements/checkbox-foot.html

index 6f4bf62..e62bf34 100644 (file)
@@ -155,6 +155,80 @@ sub reasontype {
   qsearchs( 'reason_type', { 'typenum' => shift->reason_type } );
 }
 
+=item merge
+
+Accepts an arrayref of reason objects, to be merged into this reason.
+Reasons must all have the same reason_type class as this one.
+Matching reasonnums will be replaced in the following tables:
+
+  cust_bill_void
+  cust_bill_pkg_void
+  cust_credit
+  cust_credit_void
+  cust_pay_void
+  cust_pkg_reason
+  cust_refund
+
+=cut
+
+sub merge {
+  my ($self,$reasons) = @_;
+  return "Bad input for merge" unless ref($reasons) eq 'ARRAY';
+
+  my $class = $self->reasontype->class;
+
+  local $SIG{HUP} = 'IGNORE';
+  local $SIG{INT} = 'IGNORE';
+  local $SIG{QUIT} = 'IGNORE';
+  local $SIG{TERM} = 'IGNORE';
+  local $SIG{TSTP} = 'IGNORE';
+  local $SIG{PIPE} = 'IGNORE';
+
+  my $oldAutoCommit = $FS::UID::AutoCommit;
+  local $FS::UID::AutoCommit = 0;
+  my $dbh = dbh;
+
+  my $error;
+  foreach my $reason (@$reasons) {
+    last if $error;
+    next if $reason->reasonnum eq $self->reasonnum;
+    $error = "Mismatched reason type class"    
+      unless $reason->reasontype->class eq $class;
+    foreach my $table ( qw(
+      cust_bill_void
+      cust_bill_pkg_void
+      cust_credit
+      cust_credit_void
+      cust_pay_void
+      cust_pkg_reason
+      cust_refund
+    )) {
+      last if $error;
+      my @fields = ('reasonnum');
+      push(@fields, 'void_reasonnum') if $table eq 'cust_credit_void';
+      foreach my $field (@fields) {
+        last if $error;
+        foreach my $obj ( qsearch($table,{ $field => $reason->reasonnum }) ) {
+          last if $error;
+          $obj->set($field,$self->reasonnum);
+          $error = $obj->replace;
+        }
+      }
+    }
+    $error ||= $reason->delete;
+  }
+
+  if ( $error ) {
+    $dbh->rollback if $oldAutoCommit;
+    return $error;
+  }
+
+  $dbh->commit or die $dbh->errstr if $oldAutoCommit;
+
+  '';
+
+}
+
 =back
 
 =head1 CLASS METHODS
index 8af88a9..bdbcf37 100644 (file)
@@ -18,6 +18,8 @@
                  'fields'      => \@fields,
                  'links'       => \@links,
                  'align'       => $align,
+                 'html_form'   => qq!<FORM ACTION="${p}misc/reason_merge.html" METHOD="POST">!,
+                 'html_foot'   => $html_foot,
              )
 %>
 <%init>
@@ -31,7 +33,8 @@ my $class = $1;
 my $classname = $FS::reason_type::class_name{$class};
 my $classpurpose = $FS::reason_type::class_purpose{$class};
 
-my $html_init = ucfirst($classname).  " reasons $classpurpose.<BR><BR>".
+my $html_init = include('/elements/init_overlib.html').
+ucfirst($classname).  " reasons $classpurpose.<BR><BR>".
 qq!<A HREF="${p}edit/reason.html?class=$class">!.
 "<I>Add a $classname reason</I></A><BR><BR>";
 
@@ -107,5 +110,22 @@ if ( $class eq 'S' ) {
   $align .= 'cl';
 }
 
+# reason merge handling
+push @header, '';
+push @fields, sub {
+  my $reason = shift;
+  my $reasonnum = $reason->reasonnum;
+  qq!<INPUT TYPE="checkbox" NAME="reasonnum" VALUE="$reasonnum">!;
+};
+push @links, '';
+$align .= 'l';
+my $html_foot = include('/search/elements/checkbox-foot.html',
+  onclick  => include( '/elements/popup_link_onclick.html',
+                js_action   => q!'! . "${p}misc/reason-merge.html?" . q!' + toCGIString()!,
+                actionlabel => 'Merge reasons',
+              ),
+  label    => 'merge selected reasons',
+  minboxes => 2,
+) . '</FORM>';
 
 </%init>
index 0cb6e7a..e5f42e8 100644 (file)
@@ -21,6 +21,8 @@
                     '',
                   ],
   'disable_total' => 1,
+  'html_form'   => qq!<FORM ACTION="${p}misc/reason_merge.html" METHOD="POST">!,
+  'html_foot'   => $html_foot,
 &>
 <%init>
 
@@ -44,7 +46,8 @@ my $html_init = 'Reasons: ' .
     } keys (%FS::reason_type::class_name)
   );
 
-$html_init .= '<BR><P>' .
+$html_init .= include('/elements/init_overlib.html').
+  '<BR><P>' .
   $classname . ' reasons ' .
   $FS::reason_type::class_purpose{$class} .
   '. Reason types allow reasons to be grouped for reporting purposes.' .
@@ -64,6 +67,10 @@ my $reasons_sub = sub {
               'link'  => $p. "edit/reason.html?class=$class&reasonnum=".
                              $_->reasonnum,
             },
+            {
+              'data'  => q!<INPUT TYPE="checkbox" NAME="reasonnum" VALUE="! . $_->reasonnum . q!">!,
+              'align' => 'right',
+            },
           ];
         }
     $reason_type->enabled_reasons ),
@@ -73,7 +80,8 @@ my $reasons_sub = sub {
         'align' => 'left',
         'link'  => $p. "edit/reason.html?class=$class",
         'data_style' => 'i',
-      }
+      },
+      { 'data'  => '' },
     ]
 
   ];
@@ -86,4 +94,13 @@ $count_query .= $where_clause;
 
 my $link = [ $p.'edit/reason_type.html?class='.$class.'&typenum=', 'typenum' ];
 
+my $html_foot = include('/search/elements/checkbox-foot.html',
+  onclick  => include( '/elements/popup_link_onclick.html',
+                js_action   => q!'! . "${p}misc/reason-merge.html?" . q!' + toCGIString()!,
+                actionlabel => 'Merge reasons',
+              ),
+  label    => 'merge selected reasons',
+  minboxes => 2,
+) . '</FORM>';
+
 </%init>
index c470094..ae8b794 100644 (file)
@@ -11,6 +11,7 @@
                     },
                   ],
                   filter        => '.name = "pkgpart"', # see below
+                  minboxes      => 2, #will remove checkboxes if there aren't at least this many
                ),
 &>
 
@@ -67,6 +68,14 @@ for (var i = 0; i < inputs.length; i++) {
   }
 }
 %# avoid the need for "$areboxes" late-evaluation hackery
+% if ($opt{'minboxes'}) {
+if ( checkboxes.length < <% $opt{'minboxes'} %> ) {
+  for (i = 0; i < checkboxes.length; i++) {
+    checkboxes[i].parentNode.removeChild(checkboxes[i]);
+  }
+  checkboxes = [];
+}
+% }
 if ( checkboxes.length == 0 ) {
   document.getElementById('checkbox_footer').style.display = 'none';
 }