package FS::Conf;
use strict;
-use vars qw( $base_dir @config_items @base_items @card_types $DEBUG
+use vars qw( $base_dir @config_items @base_items @card_types @invoice_terms
+ $DEBUG
$conf_cache $conf_cache_enabled
);
use Carp;
logo.eps
);
+@invoice_terms = (
+ '',
+ 'Payable upon receipt',
+ 'Net 0', 'Net 3', 'Net 5', 'Net 7', 'Net 9', 'Net 10', 'Net 14',
+ 'Net 15', 'Net 18', 'Net 20', 'Net 21', 'Net 25', 'Net 30', 'Net 45',
+ 'Net 60', 'Net 90'
+);
+
my %msg_template_options = (
'type' => 'select-sub',
'options_sub' => sub {
'description' => 'Optional default invoice term, used to calculate a due date printed on invoices.',
'type' => 'select',
'per_agent' => 1,
- 'select_enum' => [
- '', 'Payable upon receipt', 'Net 0', 'Net 3', 'Net 5', 'Net 7', 'Net 9', 'Net 10', 'Net 14',
- 'Net 15', 'Net 18', 'Net 20', 'Net 21', 'Net 25', 'Net 30', 'Net 45',
- 'Net 60', 'Net 90'
- ], },
+ 'select_enum' => \@invoice_terms,
+ },
{
'key' => 'invoice_show_prior_due_date',
},
{
- 'key' => 'cust_pkg-always_show_location',
- 'section' => 'packages',
- 'description' => "Always display package locations, even when they're all the default service address.",
- 'type' => 'checkbox',
- },
-
- {
'key' => 'cust_pkg-group_by_location',
'section' => 'packages',
'description' => "Group packages by location.",
},
{
+ 'key' => 'invoice-all_pkg_addresses',
+ 'section' => 'invoicing',
+ 'description' => 'Show all package addresses on invoices, even the default.',
+ 'type' => 'checkbox',
+ },
+
+ {
'key' => 'invoice-unitprice',
'section' => 'invoicing',
'description' => 'Enable unit pricing on invoices and quantities on packages.',
use FS::fiber_olt;
use FS::olt_site;
use FS::access_user_page_pref;
+ use FS::part_svc_msgcat;
# Sammath Naur
if ( $FS::Mason::addl_handler_use ) {
'weight', 'int', 'NULL', '', '', '',
'payby', 'char', '', 4, '', '',
'payinfo', 'varchar', 'NULL', 512, '', '',
- 'cardtype', 'varchar', 'NULL', $char_d, '', '',
+ 'paycardtype', 'varchar', 'NULL', $char_d, '', '',
'paycvv', 'varchar', 'NULL', 512, '', '',
'paymask', 'varchar', 'NULL', $char_d, '', '',
#'paydate', @date_type, '', '',
'usernum', 'int', 'NULL', '', '', '',
'payby', 'char', '', 4, '', '',
'payinfo', 'varchar', 'NULL', 512, '', '',
+ 'paycardtype', 'varchar', 'NULL', $char_d, '', '',
'paymask', 'varchar', 'NULL', $char_d, '', '',
'paydate', 'varchar', 'NULL', 10, '', '',
'paybatch', 'varchar', 'NULL', $char_d, '', '',#for auditing purposes
'usernum', 'int', 'NULL', '', '', '',
'payby', 'char', '', 4, '', '',
'payinfo', 'varchar', 'NULL', 512, '', '',
- 'paymask', 'varchar', 'NULL', $char_d, '', '',
+ 'paycardtype', 'varchar', 'NULL', $char_d, '', '',
+ 'paymask', 'varchar', 'NULL', $char_d, '', '',
#'paydate' ?
'paybatch', 'varchar', 'NULL', $char_d, '', '', #for auditing purposes.
'closed', 'char', 'NULL', 1, '', '',
# be index into payby
# table eventually
'payinfo', 'varchar', 'NULL', 512, '', '', #see cust_main above
- 'paymask', 'varchar', 'NULL', $char_d, '', '',
+ 'paycardtype', 'varchar', 'NULL', $char_d, '', '',
+ 'paymask', 'varchar', 'NULL', $char_d, '', '',
'paybatch', 'varchar', 'NULL', $char_d, '', '',
'closed', 'char', 'NULL', 1, '', '',
'source_paynum', 'int', 'NULL', '', '', '', # link to cust_payby, to prevent unapply of gateway-generated refunds
],
},
+ 'part_svc_msgcat' => {
+ 'columns' => [
+ 'svcpartmsgnum', 'serial', '', '', '', '',
+ 'svcpart', 'int', '', '', '', '',
+ 'locale', 'varchar', '', 16, '', '',
+ 'svc', 'varchar', '', $char_d, '', '',
+ ],
+ 'primary_key' => 'svcpartmsgnum',
+ 'unique' => [ [ 'svcpart', 'locale' ] ],
+ 'index' => [],
+ 'foreign_keys' => [
+ { columns => [ 'svcpart' ],
+ table => 'part_svc',
+ },
+ ],
+ },
+
+
#(this should be renamed to part_pop)
'svc_acct_pop' => {
'columns' => [
],
},
+ 'rt_field_charge' => {
+ 'columns' => [
+ 'rtfieldchargenum', 'serial', '', '', '', '',
+ 'pkgnum', 'int', '', '', '', '',
+ 'ticketid', 'int', '', '', '', '',
+ 'rate', @money_type, '', '',
+ 'units', 'decimal', '', '10,4', '', '',
+ 'charge', @money_type, '', '',
+ '_date', @date_type, '', '',
+ ],
+ 'primary_key' => 'rtfieldchargenum',
+ 'unique' => [],
+ 'index' => [ ['pkgnum', 'ticketid'] ],
+ 'foreign_keys' => [
+ { columns => [ 'pkgnum' ],
+ table => 'cust_pkg',
+ },
+ ],
+ },
+
# name type nullability length default local
#'new_table' => {
# for location labels: use default location on the invoice date
my $default_locationnum;
- if ( $self->custnum ) {
+ if ( $conf->exists('invoice-all_pkg_addresses') ) {
+ $default_locationnum = 0; # treat them all as non-default
+ } elsif ( $self->custnum ) {
my $h_cust_main;
my @h_search = FS::h_cust_main->sql_h_search($self->_date);
$h_cust_main = qsearchs({
# append the word 'Setup' to the setup line if there's going to be
# a recur line for the same package (i.e. not a one-time charge)
+ # XXX localization
my $description = $desc;
$description .= ' Setup'
if $cust_bill_pkg->recur != 0
# always pass the svc_label through to the template, even if
# not displaying it as an ext_description
my @svc_labels = map &{$escape_function}($_),
- $cust_pkg->h_labels_short($self->_date, undef, 'I');
-
+ $cust_pkg->h_labels_short($self->_date,
+ undef,
+ 'I',
+ $self->conf->{locale},
+ );
$svc_label = $svc_labels[0];
unless ( $cust_pkg->part_pkg->hide_svc_detail
push @dates, undef if !$prev;
my @svc_labels = map &{$escape_function}($_),
- $cust_pkg->h_labels_short(@dates, 'I');
+ $cust_pkg->h_labels_short(@dates,
+ 'I',
+ $self->conf->{locale});
$svc_label = $svc_labels[0];
# show service labels, unless...
use strict;
use vars qw( @ISA $DEBUG $me );
use Data::Dumper;
+use Date::Format qw( time2str );
use MIME::Entity;
use FS::UID qw(dbh);
use FS::CGI qw(popurl);
warn "$me init: complete" if $DEBUG;
}
-=item customer_tickets CUSTNUM [ LIMIT ] [ PRIORITYVALUE ]
+=item customer_tickets CUSTNUM [ PARAMS ]
Replacement for the one in RT_External so that we can access custom fields
-properly.
+properly. Accepts a hashref with the following parameters:
+
+number - custnum/svcnum
+
+limit
+
+priority
+
+status
+
+queueid
+
+resolved - only return tickets resolved after this timestamp
=cut
# create an RT::Tickets object for a specified custnum or svcnum
sub _tickets_search {
- my( $self, $type, $number, $limit, $priority, $status, $queueid ) = @_;
+ my $self = shift;
+ my $type = shift;
+
+ my( $number, $limit, $priority, $status, $queueid, $opt );
+ if ( ref($_[0]) eq 'HASH' ) {
+ $opt = shift;
+ $number = $$opt{'number'};
+ $limit = $$opt{'limit'};
+ $priority = $$opt{'priority'};
+ $status = $$opt{'status'};
+ $queueid = $$opt{'queueid'};
+ } else {
+ ( $number, $limit, $priority, $status, $queueid ) = @_;
+ $opt = {};
+ }
$type =~ /^Customer|Service$/ or die "invalid type: $type";
$number =~ /^\d+$/ or die "invalid custnum/svcnum: $number";
$rtql .= " AND Queue = $queueid " if $queueid;
+ if ($$opt{'resolved'}) {
+ $rtql .= " AND Resolved >= " . dbh->quote(time2str('%Y-%m-%d %H:%M:%S',$$opt{'resolved'}));
+ }
+
warn "$me _customer_tickets_search:\n$rtql\n" if $DEBUG;
$Tickets->FromSQL($rtql);
}
$ticket_info{'owner'} = $t->OwnerObj->Name;
$ticket_info{'queue'} = $t->QueueObj->Name;
+ $ticket_info{'_cf_sort_order'} = {};
+ my $cf_sort = 0;
foreach my $CF ( @{ $t->CustomFields->ItemsArrayRef } ) {
+ $ticket_info{'_cf_sort_order'}{$CF->Name} = $cf_sort++;
my $name = 'CF.{'.$CF->Name.'}';
$ticket_info{$name} = $t->CustomFieldValuesAsString($CF->Id);
}
}
}
+=item custom_fields
+
+Returns a hash of custom field names and descriptions.
+
+Accepts the following options:
+
+lookuptype - limit results to this lookuptype
+
+valuetype - limit results to this valuetype
+
+Fields must be visible to CurrentUser.
+
+=cut
+
+sub custom_fields {
+ my $self = shift;
+ my %opt = @_;
+ my $lookuptype = $opt{lookuptype};
+ my $valuetype = $opt{valuetype};
+
+ my $CurrentUser = RT::CurrentUser->new();
+ $CurrentUser->LoadByName($FS::CurrentUser::CurrentUser->username);
+ die "RT not configured" unless $CurrentUser->id;
+ my $CFs = RT::CustomFields->new($CurrentUser);
+
+ $CFs->UnLimit;
+
+ $CFs->Limit(FIELD => 'LookupType',
+ OPERATOR => 'ENDSWITH',
+ VALUE => $lookuptype)
+ if $lookuptype;
+
+ $CFs->Limit(FIELD => 'Type',
+ VALUE => $valuetype)
+ if $valuetype;
+
+ my @fields;
+ while (my $CF = $CFs->Next) {
+ push @fields, $CF->Name, ($CF->Description || $CF->Name);
+ }
+
+ return @fields;
+}
+
1;
enable_banned_pay_pad() unless length($conf->config('banned_pay-pad'));
+ # if translate-auto-insert is enabled for a locale, ensure that invoice
+ # terms are in the msgcat (is there a better place for this?)
+ if (my $auto_locale = $conf->config('translate-auto-insert')) {
+ my $lh = FS::L10N->get_handle($auto_locale);
+ foreach (@FS::Conf::invoice_terms) {
+ $lh->maketext($_) if length($_);
+ }
+ }
}
sub upgrade_overlimit_groups {
'cust_refund' => [],
'banned_pay' => [],
+ #paycardtype
+ 'cust_payby' => [],
+
#default namespace
'payment_gateway' => [],
} elsif ( $label_prefix eq '_location' && $self->locationname ) {
$prefix = $self->locationname;
- } elsif ( ( $opt{'cust_main'} || $self->custnum )
- && $self->locationnum == $cust_or_prospect->ship_locationnum ) {
- $prefix = 'Default service location';
+ #} elsif ( ( $opt{'cust_main'} || $self->custnum )
+ # && $self->locationnum == $cust_or_prospect->ship_locationnum ) {
+ # $prefix = 'Default service location';
+ #}
+ } else {
+ $prefix = '';
}
$prefix;
use File::Temp; #qw( tempfile );
use Business::CreditCard 0.28;
use List::Util qw(min);
+use Try::Tiny;
use FS::UID qw( dbh driver_name );
use FS::Record qw( qsearchs qsearch dbdef regexp_sql );
use FS::Cursor;
use FS::sales;
use FS::cust_payby;
use FS::contact;
+use FS::reason;
# 1 is mostly method/subroutine entry and options
# 2 traces progress of some operations
=item quiet - can be set true to supress email cancellation notices.
-=item reason - can be set to a cancellation reason (see L<FS:reason>), either a reasonnum of an existing reason, or passing a hashref will create a new reason. The hashref should have the following keys: typenum - Reason type (see L<FS::reason_type>, reason - Text of the new reason.
+=item reason - can be set to a cancellation reason (see L<FS:reason>), either a
+reasonnum of an existing reason, or passing a hashref will create a new reason.
+The hashref should have the following keys:
+typenum - Reason type (see L<FS::reason_type>)
+reason - Text of the new reason.
=item cust_pkg_reason - can be an arrayref of L<FS::cust_pkg_reason> objects
for the individual packages, parallel to the C<cust_pkg> argument. The
}
dbh->commit;
- $FS::UID::AutoCommit = 1;
my @errors;
- # now cancel all services, the same way we would for individual packages.
- # if any of them fail, cancel the rest anyway.
+ # try to cancel each service, the same way we would for individual packages,
+ # but in cancel weight order.
my @cust_svc = map { $_->cust_svc } @pkgs;
my @sorted_cust_svc =
map { $_->[0] }
foreach my $cust_svc (@sorted_cust_svc) {
my $part_svc = $cust_svc->part_svc;
next if ( defined($part_svc) and $part_svc->preserve );
- my $error = $cust_svc->cancel; # immediate cancel, no date option
- push @errors, $error if $error;
+ # immediate cancel, no date option
+ # transactionize individually
+ my $error = try { $cust_svc->cancel } catch { $_ };
+ if ( $error ) {
+ dbh->rollback;
+ push @errors, $error;
+ } else {
+ dbh->commit;
+ }
}
if (@errors) {
return @errors;
if ($opt{'cust_pkg_reason'}) {
@cprs = @{ delete $opt{'cust_pkg_reason'} };
}
+ my $null_reason;
foreach (@pkgs) {
my %lopt = %opt;
if (@cprs) {
my $cpr = shift @cprs;
- $lopt{'reason'} = $cpr->reasonnum;
- $lopt{'reason_otaker'} = $cpr->otaker;
+ if ( $cpr ) {
+ $lopt{'reason'} = $cpr->reasonnum;
+ $lopt{'reason_otaker'} = $cpr->otaker;
+ } else {
+ warn "no reason found when canceling package ".$_->pkgnum."\n";
+ # we're not actually required to pass a reason to cust_pkg::cancel,
+ # but if we're getting to this point, something has gone awry.
+ $null_reason ||= FS::reason->new_or_existing(
+ reason => 'unknown reason',
+ type => 'Cancel Reason',
+ class => 'C',
+ );
+ $lopt{'reason'} = $null_reason->reasonnum;
+ $lopt{'reason_otaker'} = $FS::CurrentUser::CurrentUser->username;
+ }
}
my $error = $_->cancel(%lopt);
- push @errors, 'pkgnum '.$_->pkgnum.': '.$error if $error;
+ if ( $error ) {
+ dbh->rollback;
+ push @errors, 'pkgnum '.$_->pkgnum.': '.$error;
+ } else {
+ dbh->commit;
+ }
}
return @errors;
Payment Information (See L<FS::payinfo_Mixin> for data format)
+=item paycardtype
+
+Credit card type, if appropriate; autodetected.
+
=item paymask
Masked payinfo (See L<FS::payinfo_Mixin> for how this works)
process_upgrade_paybatch();
}
}
+
+ ###
+ # set paycardtype
+ ###
+ $class->upgrade_set_cardtype;
+
}
sub process_upgrade_paybatch {
card number, check #, or comp issuer (4-8 lowercase alphanumerics; think username), respectively
+=item cardtype
+
+Credit card type, if appropriate.
+
=item paybatch
text field for tracking card processing
payip
+=item paycardtype
+
+The credit card type (deduced from the card number).
=back
# Need some kind of global flag to accept invalid cards, for testing
# on scrubbed data.
#XXX if ( !$import && $check_payinfo && $self->payby =~ /^(CARD|DCRD)$/ ) {
+
+ # In this block: detect card type; reject credit card / account numbers that
+ # are impossible or banned; reject other payment features (date, CVV length)
+ # that are inappropriate for the card type.
+ # However, if the payinfo is encrypted then just detect card type and assume
+ # the other checks were already done.
+
if ( !$ignore_invalid_card &&
$check_payinfo && $self->payby =~ /^(CARD|DCRD)$/ ) {
validate($payinfo)
or return gettext('invalid_card'); # . ": ". $self->payinfo;
- return gettext('unknown_card_type')
- if $self->payinfo !~ /^99\d{14}$/ #token
- && cardtype($self->payinfo) eq "Unknown";
+ my $cardtype = cardtype($payinfo);
+ $cardtype = 'Tokenized' if $self->payinfo =~ /^99\d{14}$/; #token
+
+ return gettext('unknown_card_type') if $cardtype eq "Unknown";
+
+ $self->set('paycardtype', $cardtype);
unless ( $ignore_banned_card ) {
my $ban = FS::banned_pay->ban_search( %{ $self->_banned_pay_hashref } );
}
if (length($self->paycvv) && !$self->is_encrypted($self->paycvv)) {
- if ( cardtype($self->payinfo) eq 'American Express card' ) {
+ if ( $cardtype eq 'American Express card' ) {
$self->paycvv =~ /^(\d{4})$/
or return "CVV2 (CID) for American Express cards is four digits.";
$self->paycvv($1);
$self->paycvv('');
}
- my $cardtype = cardtype($payinfo);
if ( $cardtype =~ /^(Switch|Solo)$/i ) {
return "Start date or issue number is required for $cardtype cards"
}
}
+ } elsif ( $self->payby =~ /^CARD|DCRD$/ and $self->paymask ) {
+ # either ignoring invalid cards, or we can't decrypt the payinfo, but
+ # try to detect the card type anyway. this never returns failure, so
+ # the contract of $ignore_invalid_cards is maintained.
+ $self->set('paycardtype', cardtype($self->paymask));
+ } else {
+ $self->set('paycardtype', '');
+ }
+
# } elsif ( $self->payby eq 'PREPAY' ) {
#
# my $payinfo = $self->payinfo;
# unless qsearchs('prepay_credit', { 'identifier' => $self->payinfo } );
# $self->paycvv('');
- }
-
if ( $self->payby =~ /^(CHEK|DCHK)$/ ) {
$self->paydate('');
} elsif ( $self->payby =~ /^(CARD|DCRD)$/ ) {
# shouldn't payinfo_check do this?
+ # (except we don't ever call payinfo_check from here)
return "Expiration date required"
if $self->paydate eq '' || $self->paydate eq '-';
my $payinfo = $self->payinfo;
$payinfo =~ s/\D//g;
- return '' if $payinfo =~ /^99\d{14}$/; #token
+ if ( $payinfo =~ /^99\d{14}$/ ) {
+ $self->set('paycardtype', 'Tokenized');
+ return '';
+ }
my %bop_card_types = map { $_=>1 } values %{ card_types() };
my $cardtype = cardtype($payinfo);
+ $self->set('paycardtype', $cardtype);
return "$cardtype not accepted" unless $bop_card_types{$cardtype};
my $self = shift;
my $name = $self->payby =~ /^(CARD|DCRD)$/
- && cardtype($self->paymask) || FS::payby->shortname($self->payby);
+ && $self->paycardtype || FS::payby->shortname($self->payby);
( $self->payby =~ /^(CARD|CHEK)$/ ? $weight{$self->weight}. ' automatic '
: 'Manual '
=back
+=cut
+
+sub _upgrade_data {
+
+ my $class = shift;
+ local $ignore_banned_card = 1;
+ local $ignore_expired_card = 1;
+ local $ignore_invalid_card = 1;
+ $class->upgrade_set_cardtype;
+
+}
+
=head1 BUGS
=head1 SEE ALSO
# cust_bill_pay.pkgnum (wtf, shouldn't reference pkgnum)
# cust_pkg_usage.pkgnum
# cust_pkg.uncancel_pkgnum, change_pkgnum, main_pkgnum, and change_to_pkgnum
+ # rt_field_charge.pkgnum
# cust_svc is handled by canceling the package before deleting it
# cust_pkg_option is handled via option_Common
return "transferring package notes: $error";
}
}
+
+ # transfer scheduled expire/adjourn reasons
+ foreach my $action ('expire', 'adjourn') {
+ if ( $cust_pkg->get($action) ) {
+ my $reason = $self->last_cust_pkg_reason($action);
+ if ( $reason ) {
+ $reason->set('pkgnum', $cust_pkg->pkgnum);
+ $error = $reason->replace;
+ if ( $error ) {
+ $dbh->rollback if $oldAutoCommit;
+ return "transferring $action reason: $error";
+ }
+ }
+ }
+ }
my @new_supp_pkgs;
return "canceling old package: $error";
}
+ # transfer rt_field_charge, if we're not changing pkgpart
+ # after billing of old package, before billing of new package
+ if ( $same_pkgpart ) {
+ foreach my $rt_field_charge ($self->rt_field_charge) {
+ $rt_field_charge->set('pkgnum', $cust_pkg->pkgnum);
+ $error = $rt_field_charge->replace;
+ if ( $error ) {
+ $dbh->rollback if $oldAutoCommit;
+ return "transferring rt_field_charge: $error";
+ }
+ }
+ }
+
if ( $conf->exists('cust_pkg-change_pkgpart-bill_now') ) {
#$self->cust_main
my $error = $cust_pkg->cust_main->bill(
map { [ $_->label ] } $self->cust_svc;
}
-=item h_labels END_TIMESTAMP [ START_TIMESTAMP ] [ MODE ]
+=item h_labels END_TIMESTAMP [, START_TIMESTAMP [, MODE [, LOCALE ] ] ]
Like the labels method, but returns historical information on services that
were active as of END_TIMESTAMP and (optionally) not cancelled before
START_TIMESTAMP. If MODE is 'I' (for 'invoice'), services with the
I<pkg_svc.hidden> flag will be omitted.
-Returns a list of lists, calling the label method for all (historical) services
-(see L<FS::h_cust_svc>) of this billing item.
+If LOCALE is passed, service definition names will be localized.
+
+Returns a list of lists, calling the label method for all (historical)
+services (see L<FS::h_cust_svc>) of this billing item.
=cut
sub h_labels {
my $self = shift;
- warn "$me _h_labels called on $self\n"
+ my ($end, $start, $mode, $locale) = @_;
+ warn "$me h_labels\n"
if $DEBUG;
- map { [ $_->label(@_) ] } $self->h_cust_svc(@_);
+ map { [ $_->label($end, $start, $locale) ] }
+ $self->h_cust_svc($end, $start, $mode);
}
=item labels_short
=cut
sub labels_short {
- shift->_labels_short( 'labels', @_ );
+ shift->_labels_short( 'labels' ); # 'labels' takes no further arguments
}
-=item h_labels_short END_TIMESTAMP [ START_TIMESTAMP ]
+=item h_labels_short END_TIMESTAMP [, START_TIMESTAMP [, MODE [, LOCALE ] ] ]
Like h_labels, except returns a simple flat list, and shortens long
-(currently >5 or the cust_bill-max_same_services configuration value) lists of
-identical services to one line that lists the service label and the number of
-individual services rather than individual items.
+(currently >5 or the cust_bill-max_same_services configuration value) lists
+of identical services to one line that lists the service label and the
+number of individual services rather than individual items.
=cut
shift->_labels_short( 'h_labels', @_ );
}
+# takes a method name ('labels' or 'h_labels') and all its arguments;
+# maybe should be "shorten($self->h_labels( ... ) )"
+
sub _labels_short {
my( $self, $method ) = ( shift, shift );
FS::upgrade_journal->set_done('cust_pkg_reason__missing_reason');
}
+ # Fix misplaced expire/suspend reasons due to package change (RT#71623).
+ # These will look like:
+ # - there is an expire reason linked to pkg1
+ # - pkg1 has been canceled before the reason's date
+ # - pkg2 was changed from pkg1, has an expire date equal to the reason's
+ # date, and has no expire reason (check this later)
+
+ my $error;
+ foreach my $action ('expire', 'adjourn') {
+ # Iterate this, because a package could be scheduled to expire, then
+ # changed several times, and we need to walk the reason forward to the
+ # last one.
+ while(1) {
+ my @reasons = qsearch(
+ {
+ select => 'cust_pkg_reason.*',
+ table => 'cust_pkg_reason',
+ addl_from => ' JOIN cust_pkg pkg1 USING (pkgnum)
+ JOIN cust_pkg pkg2 ON (pkg1.pkgnum = pkg2.change_pkgnum)',
+ hashref => { 'action' => uc(substr($action, 0, 1)) },
+ extra_sql => " AND pkg1.cancel IS NOT NULL
+ AND cust_pkg_reason.date > pkg1.cancel
+ AND pkg2.$action = cust_pkg_reason.date"
+ });
+ last if !@reasons;
+ warn "Checking ".scalar(@reasons)." possible misplaced $action reasons.\n";
+ foreach my $cust_pkg_reason (@reasons) {
+ my $new_pkg = qsearchs('cust_pkg', { change_pkgnum => $cust_pkg_reason->pkgnum });
+ my $new_reason = $new_pkg->last_cust_pkg_reason($action);
+ if ($new_reason and $new_reason->_date == $new_pkg->get($action)) {
+ # the expiration reason has been recreated on the new package, so
+ # just delete the old one
+ warn "Cleaning $action reason from canceled pkg#" .
+ $cust_pkg_reason->pkgnum . "\n";
+ $error = $cust_pkg_reason->delete;
+ } else {
+ # then the old reason needs to be transferred
+ warn "Moving $action reason from canceled pkg#" .
+ $cust_pkg_reason->pkgnum .
+ " to new pkg#" . $new_pkg->pkgnum ."\n";
+ $cust_pkg_reason->set('pkgnum' => $new_pkg->pkgnum);
+ $error = $cust_pkg_reason->replace;
+ }
+ die $error if $error;
+ }
+ }
+ }
+
#still can't fill in an action? don't abort the upgrade
local($ignore_empty_action) = 1;
Payment Information (See L<FS::payinfo_Mixin> for data format)
+=item paycardtype
+
+Detected credit card type, if appropriate; autodetected.
+
=item paymask
Masked payinfo (See L<FS::payinfo_Mixin> for how this works)
my ($class, %opts) = @_;
$class->_upgrade_reasonnum(%opts);
$class->_upgrade_otaker(%opts);
+
+ local $ignore_empty_reasonnum = 1;
+ $class->upgrade_set_cardtype;
}
=back
return $cust_pkg->getfield('cancel') || '';
}
-=item label
+=item label [ LOCALE ]
Returns a list consisting of:
-- The name of this service (from part_svc)
+- The name of this service (from part_svc), optionally localized
- A meaningful identifier (username, domain, or mail alias)
- The table name (i.e. svc_domain) for this service
- svcnum
my($label, $value, $svcdb) = $cust_svc->label;
-=item label_long
+=item label_long [ LOCALE ]
Like the B<label> method, except the second item in the list ("meaningful
identifier") may be longer - typically, a full name is included.
sub _label {
my $self = shift;
my $method = shift;
+ my $locale = shift;
my $svc_x = $self->svc_x
or return "can't find ". $self->part_svc->svcdb. '.svcnum '. $self->svcnum;
- $self->$method($svc_x);
+ $self->$method($svc_x, undef, undef, $locale);
}
+# svc_label(_long) takes three arguments: end date, start date, locale
+# and FS::svc_*::label methods must accept those also, if they even care
+
sub svc_label { shift->_svc_label('label', @_); }
sub svc_label_long { shift->_svc_label('label_long', @_); }
sub _svc_label {
my( $self, $method, $svc_x ) = ( shift, shift, shift );
+ my ($end, $start, $locale) = @_;
(
- $self->part_svc->svc,
+ $self->part_svc->svc_locale($locale),
$svc_x->$method(@_),
$self->part_svc->svcdb,
$self->svcnum
$self->h_date('delete');
}
-=item label END_TIMESTAMP [ START_TIMESTAMP ]
+=item label END_TIMESTAMP [ START_TIMESTAMP ] [ LOCALE ]
-Returns a label for this historical service, if the service was created before
-END_TIMESTAMP and (optionally) not deleted before START_TIMESTAMP. Otherwise,
-returns an empty list.
+Returns a label for this historical service, if the service was created
+before END_TIMESTAMP and (optionally) not deleted before START_TIMESTAMP.
+Otherwise, returns an empty list.
If a service is found, returns a list consisting of:
-- The name of this historical service (from part_svc)
+- The name of this historical service (from part_svc), optionally localized
- A meaningful identifier (username, domain, or mail alias)
- The table name (i.e. svc_domain) for this historical service
sub label { shift->_label('svc_label', @_); }
sub label_long { shift->_label('svc_label_long', @_); }
+# Parameters to _label:
+#
+# 1: the cust_svc method we should call to produce the label. (svc_label
+# and svc_label_long are defined in FS::cust_svc, not here, and take a svc_x
+# object as first argument.)
+# 2, 3: date range to use to find the h_svc_x, which will be passed to
+# svc_label(_long) and eventually have ->label called on it.
+# 4: locale, passed to svc_label(_long) also.
+#
+# however, if label is called with a locale only, must DTRT (this is a
+# FS::cust_svc subclass)
+
sub _label {
my $self = shift;
my $method = shift;
+ my ($end, $start, $locale);
+ if (defined($_[0])) {
+ if ( $_[0] =~ /^\d+$/ ) {
+ ($end, $start, $locale) = @_;
+ } else {
+ $locale = shift;
+ $end = $self->history_date;
+ }
+ }
#carp "FS::h_cust_svc::_label called on $self" if $DEBUG;
warn "FS::h_cust_svc::_label called on $self for $method" if $DEBUG;
- my $svc_x = $self->h_svc_x(@_);
+ my $svc_x = $self->h_svc_x($end, $start);
return () unless $svc_x;
my $part_svc = $self->part_svc;
}
my @label;
- eval { @label = $self->$method($svc_x, @_); };
+ eval { @label = $self->$method($svc_x, $end, $start, $locale); };
if ($@) {
carp 'while resolving history record for svcdb/svcnum ' .
=item h_svc_x END_TIMESTAMP [ START_TIMESTAMP ]
-Returns the FS::h_svc_XXX object for this service as of END_TIMESTAMP (i.e. an
-FS::h_svc_acct object or FS::h_svc_domain object, etc.) and (optionally) not
-cancelled before START_TIMESTAMP.
+Returns the FS::h_svc_XXX object for this service as of END_TIMESTAMP (i.e.
+an FS::h_svc_acct object or FS::h_svc_domain object, etc.) and (optionally)
+not cancelled before START_TIMESTAMP.
=cut
sub _rebless {
my $self = shift;
+ return '' unless $self->msgclass;
my $class = 'FS::msg_template::' . $self->msgclass;
eval "use $class;";
bless($self, $class) unless $@;
=item check_options
For a passed I<$options> hashref, validates any options that
-have 'validate' subroutines defined (I<$options> values might
-be altered.) Returns error message, or empty string if valid.
+have 'validate' subroutines defined in the info hash,
+then validates the entire hashref if the price plan has
+its own 'validate' subroutine defined in the info hash
+(I<$options> values might be altered.)
+
+Returns error message, or empty string if valid.
Invoked by L</insert> and L</replace> via the equivalent
methods in L<FS::option_Common>.
}
} # else "option does not exist" error?
}
+ if (exists($plans{$self->plan}->{'validate'})) {
+ my $error = &{$plans{$self->plan}->{'validate'}}($options);
+ return $error if $error;
+ }
return '';
}
--- /dev/null
+package FS::part_pkg::rt_field;
+
+use strict;
+use FS::Conf;
+use FS::TicketSystem;
+use FS::Record qw(qsearchs qsearch);
+use FS::part_pkg::recur_Common;
+use FS::part_pkg::global_Mixin;
+use FS::rt_field_charge;
+
+our @ISA = qw(FS::part_pkg::recur_Common);
+
+our $DEBUG = 0;
+
+use vars qw( $conf $money_char );
+
+FS::UID->install_callback( sub {
+ $conf = new FS::Conf;
+ $money_char = $conf->config('money_char') || '$';
+});
+
+my %custom_field = (
+ 'type' => 'select-rt-customfield',
+ 'lookuptype' => 'RT::Queue-RT::Ticket',
+);
+
+my %multiple = (
+ 'multiple' => 1,
+ 'parse' => sub { @_ }, # because /edit/process/part_pkg.pm doesn't grok select multiple
+);
+
+our %info = (
+ 'name' => 'Bill from custom fields in resolved RT tickets',
+ 'shortname' => 'RT custom rate',
+ 'weight' => 65,
+ 'inherit_fields' => [ 'global_Mixin' ],
+ 'fields' => {
+ 'queueids' => { 'name' => 'Queues',
+ 'type' => 'select-rt-queue',
+ %multiple,
+ },
+ 'unit_field' => { 'name' => 'Units field',
+ %custom_field,
+ 'validate' => sub { return ${$_[1]} ? '' : 'Units field must be specified' },
+ },
+ 'rate_field' => { 'name' => 'Charge per unit (from RT field)',
+ %custom_field,
+ 'empty_label' => '',
+ },
+ 'rate_flat' => { 'name' => 'Charge per unit (flat)',
+ 'validate' => \&FS::part_pkg::global_Mixin::validate_moneyn },
+ 'display_fields' => { 'name' => 'Display fields',
+ %custom_field,
+ %multiple,
+ },
+ # from global_Mixin, but don't get used by this at all
+ 'unused_credit_cancel' => {'disabled' => 1},
+ 'unused_credit_suspend' => {'disabled' => 1},
+ 'unused_credit_change' => {'disabled' => 1},
+ },
+ 'validate' => sub {
+ my $options = shift;
+ return 'Rate must be specified'
+ unless $options->{'rate_field'} or $options->{'rate_flat'};
+ return 'Cannot specify both flat rate and rate field'
+ if $options->{'rate_field'} and $options->{'rate_flat'};
+ return '';
+ },
+ 'fieldorder' => [ 'queueids', 'unit_field', 'rate_field', 'rate_flat', 'display_fields' ]
+);
+
+sub price_info {
+ my $self = shift;
+ my $str = $self->SUPER::price_info;
+ $str .= ' plus ' if $str;
+ $str .= 'charge from RT';
+# takes way too long just to get a package label
+# FS::TicketSystem->init();
+# my %custom_fields = FS::TicketSystem->custom_fields();
+# my $rate = $self->option('rate_flat',1);
+# my $rate_field = $self->option('rate_field',1);
+# my $unit_field = $self->option('unit_field');
+# $str .= $rate
+# ? $money_char . sprintf("%.2",$rate)
+# : $custom_fields{$rate_field};
+# $str .= ' x ' . $custom_fields{$unit_field};
+ return $str;
+}
+
+sub calc_setup {
+ my($self, $cust_pkg ) = @_;
+ $self->option('setup_fee');
+}
+
+sub calc_recur {
+ my $self = shift;
+ my($cust_pkg, $sdate, $details, $param ) = @_;
+
+ my $charges = 0;
+
+ $charges += $self->calc_usage(@_);
+ $charges += ($cust_pkg->quantity || 1) * $self->calc_recur_Common(@_);
+
+ $charges;
+
+}
+
+sub can_discount { 0; }
+
+sub calc_usage {
+ my $self = shift;
+ my($cust_pkg, $sdate, $details, $param ) = @_;
+
+ FS::TicketSystem->init();
+
+ my %queues = FS::TicketSystem->queues(undef,'SeeCustomField');
+
+ my @tickets;
+ foreach my $queueid (
+ split(', ',$self->option('queueids',1) || '')
+ ) {
+
+ die "Insufficient permission to invoice package"
+ unless exists $queues{$queueid};
+
+ # load all resolved tickets since pkg was ordered
+ # will subtract previous charges below
+ # only way to be sure we've caught everything
+ my $tickets = FS::TicketSystem->customer_tickets({
+ number => $cust_pkg->custnum,
+ limit => 10000, # arbitrarily large
+ status => 'resolved',
+ queueid => $queueid,
+ resolved => $cust_pkg->order_date, # or setup? but this is mainly for installations,
+ # and workflow might resolve tickets before first bill...
+ # for now, expect pkg to be ordered before tickets get resolved,
+ # easy enough to make a pkg option to use setup/sdate instead
+ });
+ push @tickets, @$tickets;
+ };
+
+ my $rate = $self->option('rate_flat',1);
+ my $rate_field = $self->option('rate_field',1);
+ my $unit_field = $self->option('unit_field');
+ my @display_fields = split(', ',$self->option('display_fields',1) || '');
+
+ my %custom_fields = FS::TicketSystem->custom_fields();
+ my $rate_label = $rate
+ ? ''
+ : ' ' . $custom_fields{$rate_field};
+ my $unit_label = $custom_fields{$unit_field};
+
+ $rate_field = 'CF.{' . $rate_field . '}' if $rate_field;
+ $unit_field = 'CF.{' . $unit_field . '}';
+
+ my $charges = 0;
+ foreach my $ticket ( @tickets ) {
+ next unless $ticket->{$unit_field};
+ next unless $rate || $ticket->{$rate_field};
+ my $trate = $rate || $ticket->{$rate_field};
+ my $tunit = $ticket->{$unit_field};
+ my $subcharge = sprintf('%.2f', $trate * $tunit);
+ my $precharge = _previous_charges( $cust_pkg->pkgnum, $ticket->{'id'} );
+ $subcharge -= $precharge;
+
+ # if field values for previous charges increased,
+ # we can make additional charges here and now,
+ # but if field values were decreased, we just ignore--
+ # credits will have to be applied manually later, if that's what's intended
+ next if $subcharge <= 0;
+
+ my $rt_field_charge = new FS::rt_field_charge {
+ 'pkgnum' => $cust_pkg->pkgnum,
+ 'ticketid' => $ticket->{'id'},
+ 'rate' => $trate,
+ 'units' => $tunit,
+ 'charge' => $subcharge,
+ '_date' => $$sdate,
+ };
+ my $error = $rt_field_charge->insert;
+ die "Error inserting rt_field_charge: $error" if $error;
+ push @$details, $money_char . sprintf('%.2f',$trate) . $rate_label . ' x ' . $tunit . ' ' . $unit_label;
+ push @$details, ' - ' . $money_char . sprintf('%.2f',$precharge) . ' previously charged' if $precharge;
+ foreach my $field (
+ sort { $ticket->{'_cf_sort_order'}{$a} <=> $ticket->{'_cf_sort_order'}{$b} } @display_fields
+ ) {
+ my $label = $custom_fields{$field};
+ my $value = $ticket->{'CF.{' . $field . '}'};
+ push @$details, $label . ': ' . $value if $value;
+ }
+ $charges += $subcharge;
+ }
+ return $charges;
+}
+
+sub _previous_charges {
+ my ($pkgnum, $ticketid) = @_;
+ my $prev = 0;
+ foreach my $rt_field_charge (
+ qsearch('rt_field_charge', { pkgnum => $pkgnum, ticketid => $ticketid })
+ ) {
+ $prev += $rt_field_charge->charge;
+ }
+ return $prev;
+}
+
+1;
package FS::part_svc;
-use base qw(FS::Record);
+use base qw(FS::o2m_Common FS::Record);
use strict;
use vars qw( $DEBUG );
use FS::export_svc;
use FS::cust_svc;
use FS::part_svc_class;
+use FS::part_svc_msgcat;
FS::UID->install_callback(sub {
# preload the cache and make sure all modules load
map { $_->svc_x } $self->cust_svc;
}
+=item svc_locale LOCALE
+
+Returns a customer-viewable service definition label in the chosen LOCALE.
+If there is no entry for that locale or if LOCALE is empty, returns
+part_svc.svc.
+
+=cut
+
+sub svc_locale {
+ my( $self, $locale ) = @_;
+ return $self->svc unless $locale;
+ my $part_svc_msgcat = qsearchs('part_svc_msgcat', {
+ svcpart => $self->svcpart,
+ locale => $locale
+ }) or return $self->svc;
+ $part_svc_msgcat->svc;
+}
+
=back
=head1 CLASS METHODS
$param->{'svcpart'} = $new->getfield('svcpart');
}
+ $error ||= $new->process_o2m(
+ 'table' => 'part_svc_msgcat',
+ 'params' => $param,
+ 'fields' => [ 'locale', 'svc' ],
+ );
+
die "$error\n" if $error;
}
--- /dev/null
+package FS::part_svc_msgcat;
+use base qw( FS::Record );
+
+use strict;
+use FS::Locales;
+
+=head1 NAME
+
+FS::part_svc_msgcat - Object methods for part_svc_msgcat records
+
+=head1 SYNOPSIS
+
+ use FS::part_svc_msgcat;
+
+ $record = new FS::part_svc_msgcat \%hash;
+ $record = new FS::part_svc_msgcat { 'column' => 'value' };
+
+ $error = $record->insert;
+
+ $error = $new_record->replace($old_record);
+
+ $error = $record->delete;
+
+ $error = $record->check;
+
+=head1 DESCRIPTION
+
+An FS::part_svc_msgcat object represents localized labels of a service
+definition. FS::part_svc_msgcat inherits from FS::Record. The following
+fields are currently supported:
+
+=over 4
+
+=item svcpartmsgnum
+
+primary key
+
+=item svcpart
+
+Service definition
+
+=item locale
+
+locale
+
+=item svc
+
+Localized service name (customer-viewable)
+
+=back
+
+=head1 METHODS
+
+=over 4
+
+=item new HASHREF
+
+Creates a new record. To add the record to the database, see L<"insert">.
+
+Note that this stores the hash reference, not a distinct copy of the hash it
+points to. You can ask the object for a copy with the I<hash> method.
+
+=cut
+
+# the new method can be inherited from FS::Record, if a table method is defined
+
+sub table { 'part_svc_msgcat'; }
+
+=item insert
+
+Adds this record to the database. If there is an error, returns the error,
+otherwise returns false.
+
+=cut
+
+# the insert method can be inherited from FS::Record
+
+=item delete
+
+Delete this record from the database.
+
+=cut
+
+# the delete method can be inherited from FS::Record
+
+=item replace OLD_RECORD
+
+Replaces the OLD_RECORD with this one in the database. If there is an error,
+returns the error, otherwise returns false.
+
+=cut
+
+# the replace method can be inherited from FS::Record
+
+=item check
+
+Checks all fields to make sure this is a valid record. If there is
+an error, returns the error, otherwise returns false. Called by the insert
+and replace methods.
+
+=cut
+
+# the check method should currently be supplied - FS::Record contains some
+# data checking routines
+
+sub check {
+ my $self = shift;
+
+ my $error =
+ $self->ut_numbern('svcpartmsgnum')
+ || $self->ut_foreign_key('svcpart', 'part_svc', 'svcpart')
+ || $self->ut_enum('locale', [ FS::Locales->locales ] )
+ || $self->ut_text('svc')
+ ;
+ return $error if $error;
+
+ $self->SUPER::check;
+}
+
+=back
+
+=head1 BUGS
+
+=head1 SEE ALSO
+
+L<FS::part_svc>, L<FS::Record>, schema.html from the base documentation.
+
+=cut
+
+1;
+
use FS::payby;
use FS::Record qw(qsearch);
use FS::UID qw(driver_name);
+use FS::Cursor;
use Time::Local qw(timelocal);
use vars qw($ignore_masked_payinfo);
or return "Illegal payby: ". $self->payby;
if ( $self->payby eq 'CARD' && ! $self->is_encrypted($self->payinfo) ) {
+
my $payinfo = $self->payinfo;
+ my $cardtype = cardtype($payinfo);
+ $cardtype = 'Tokenized' if $payinfo =~ /^99\d{14}$/;
+ $self->set('paycardtype', $cardtype);
+
if ( $ignore_masked_payinfo and $self->mask_payinfo eq $self->payinfo ) {
# allow it
} else {
or return "Illegal (mistyped?) credit card number (payinfo)";
$self->payinfo($1);
validate($self->payinfo) or return "Illegal credit card number";
- return "Unknown card type" if $self->payinfo !~ /^99\d{14}$/ #token
- && cardtype($self->payinfo) eq "Unknown";
+ return "Unknown card type" if $cardtype eq "Unknown";
} else {
$self->payinfo('N/A'); #???
}
}
} else {
+ if ( $self->payby eq 'CARD' and $self->paymask ) {
+ # if we can't decrypt the card, at least detect the cardtype
+ $self->set('paycardtype', cardtype($self->paymask));
+ } else {
+ $self->set('paycardtype', '');
+ }
if ( $self->is_encrypted($self->payinfo) ) {
#something better? all it would cause is a decryption error anyway?
my $error = $self->ut_anything('payinfo');
END"
}
+=item upgrade_set_cardtype
+
+Find all records with a credit card payment type and no paycardtype, and
+replace them in order to set their paycardtype.
+
+=cut
+
+sub upgrade_set_cardtype {
+ my $class = shift;
+ # assign cardtypes to CARD/DCRDs that need them; check_payinfo_cardtype
+ # will do this. ignore any problems with the cards.
+ local $ignore_masked_payinfo = 1;
+ my $search = FS::Cursor->new({
+ table => $class->table,
+ extra_sql => q[ WHERE payby IN('CARD','DCRD') AND paycardtype IS NULL ],
+ });
+ while (my $record = $search->fetch) {
+ my $error = $record->replace;
+ die $error if $error;
+ }
+}
+
=back
=head1 BUGS
sub enable_previous { 0 }
-=item convert_cust_main
+=item convert_cust_main [ PARAMS ]
If this quotation already belongs to a customer, then returns that customer, as
an FS::cust_main object.
If there is an error, returns an error message, otherwise, returns the
newly-created FS::cust_main object.
+Accepts the same params as L</order>.
+
=cut
sub convert_cust_main {
my $self = shift;
+ my $params = shift || {};
my $cust_main = $self->cust_main;
return $cust_main if $cust_main; #already converted, don't again
$self->prospectnum('');
$self->custnum( $cust_main->custnum );
- my $error = $self->replace || $self->order;
+ my $error = $self->replace || $self->order(undef,$params);
if ( $error ) {
$dbh->rollback if $oldAutoCommit;
return $error;
}
-=item order [ HASHREF ]
+=item order [ HASHREF ] [ PARAMS ]
This method is for use with quotations which are already associated with a customer.
C<quotationpkgnum> of each quoted package to the C<pkgnum> of the package
as ordered.
+If PARAMS hashref is passed, the following params are accepted:
+
+onhold - if true, suspends newly ordered packages
+
=cut
sub order {
my $self = shift;
my $pkgnum_map = shift || {};
+ my $params = shift || {};
my $details_map = {};
tie my %all_cust_pkg, 'Tie::RefHash';
}
}
- foreach my $quotationpkgnum (keys %$pkgnum_map) {
- # convert the objects to just pkgnums
- my $cust_pkg = $pkgnum_map->{$quotationpkgnum};
- $pkgnum_map->{$quotationpkgnum} = $cust_pkg->pkgnum;
+ if ($$params{'onhold'}) {
+ foreach my $quotationpkgnum (keys %$pkgnum_map) {
+ last if $error;
+ $error = $pkgnum_map->{$quotationpkgnum}->suspend();
+ }
}
if ($error) {
}
$dbh->commit or die $dbh->errstr if $oldAutoCommit;
+
+ foreach my $quotationpkgnum (keys %$pkgnum_map) {
+ # convert the objects to just pkgnums
+ my $cust_pkg = $pkgnum_map->{$quotationpkgnum};
+ $pkgnum_map->{$quotationpkgnum} = $cust_pkg->pkgnum;
+ }
+
''; #no error
}
--- /dev/null
+package FS::rt_field_charge;
+use base qw( FS::Record );
+
+use strict;
+use FS::Record qw( qsearch qsearchs );
+
+=head1 NAME
+
+FS::rt_field_charge - Object methods for rt_field_charge records
+
+=head1 SYNOPSIS
+
+ use FS::rt_field_charge;
+
+ $record = new FS::rt_field_charge \%hash;
+ $record = new FS::rt_field_charge { 'column' => 'value' };
+
+ $error = $record->insert;
+
+ $error = $new_record->replace($old_record);
+
+ $error = $record->delete;
+
+ $error = $record->check;
+
+=head1 DESCRIPTION
+
+An FS::rt_field_charge object represents an individual charge
+that has been added to an invoice by a package with the rt_field price plan.
+FS::rt_field_charge inherits from FS::Record.
+The following fields are currently supported:
+
+=over 4
+
+=item rtfieldchargenum - primary key
+
+=item pkgnum - cust_pkg that generated the charge
+
+=item ticketid - RT ticket that generated the charge
+
+=item rate - the rate per unit for the charge
+
+=item units - quantity of units being charged
+
+=item charge - the total amount charged
+
+=item _date - billing date for the charge
+
+=back
+
+=head1 METHODS
+
+=over 4
+
+=item new HASHREF
+
+Creates a new object. To add the object to the database, see L<"insert">.
+
+Note that this stores the hash reference, not a distinct copy of the hash it
+points to. You can ask the object for a copy with the I<hash> method.
+
+=cut
+
+# the new method can be inherited from FS::Record, if a table method is defined
+
+sub table { 'rt_field_charge'; }
+
+=item insert
+
+Adds this record to the database. If there is an error, returns the error,
+otherwise returns false.
+
+=cut
+
+# the insert method can be inherited from FS::Record
+
+=item delete
+
+Delete this record from the database.
+
+=cut
+
+# the delete method can be inherited from FS::Record
+
+=item replace OLD_RECORD
+
+Replaces the OLD_RECORD with this one in the database. If there is an error,
+returns the error, otherwise returns false.
+
+=cut
+
+# the replace method can be inherited from FS::Record
+
+=item check
+
+Checks all fields to make sure this is a valid object. If there is
+an error, returns the error, otherwise returns false. Called by the insert
+and replace methods.
+
+=cut
+
+sub check {
+ my $self = shift;
+
+ my $error =
+ $self->ut_numbern('rtfieldchargenum')
+ || $self->ut_foreign_key('pkgnum', 'cust_pkg', 'pkgnum' )
+ || $self->ut_number('ticketid')
+ || $self->ut_money('rate')
+ || $self->ut_float('units')
+ || $self->ut_money('charge')
+ || $self->ut_number('_date')
+ ;
+ return $error if $error;
+
+ $self->SUPER::check;
+}
+
+=back
+
+=head1 BUGS
+
+
+
+=head1 SEE ALSO
+
+L<FS::Record>
+
+=cut
+
+1;
+
$OUT .= qq! <img src="cust_bill-barcode.cgi?invnum=$invnum;template=$template"><br> !;
}
%>
- <%= $terms ? emt('Terms') . ": $terms" : '' %><BR>
+ <%= $terms ? emt('Terms') . ': ' . emt($terms) : '' %><BR>
<%= $po_line %>
</td>
</tr>
\documentclass[letterpaper]{article}\r
\r
\usepackage{fancyhdr,lastpage,ifthen,array,longtable,afterpage,caption,multirow,bigstrut}\r
+\usepackage[breakwords]{truncate} % to avoid overflowing boxes\r
\usepackage{graphicx} % required for logo graphic\r
\usepackage[utf8]{inputenc} % multilanguage support\r
\usepackage[T1]{fontenc}\r
\hline\r
\rule{0pt}{2.5ex}\r
\makebox[1.4cm]{} &\r
- \multicolumn{\FSdescriptioncolumncount}{l}{\makebox[\FSdescriptionlength][l]{\textbf{[@-- emt('Description') --@]}}}&\r
+ \multicolumn{\FSdescriptioncolumncount}{l}{\r
+ \truncate{\FSdescriptionlength}{\textbf{[@-- emt('Description') --@]}}\r
+ } &\r
\FSunitcolumns\r
\makebox[1.6cm][r]{\textbf{[@-- emt('Amount') --@]}} \\\r
\hline\r
\rule{0pt}{2.5ex}\r
\makebox[1.4cm]{} &\r
\multicolumn{4}{l}{\r
- \makebox[\FSdescriptionlength][l]{\textbf{[@-- emt('Description') --@]}}\r
+ \truncate{\FSdescriptionlength}{\textbf{[@-- emt('Description') --@]}}\r
} &\r
\textbf{~~[@-- emt('Calls') --@]} &\r
\textbf{~~[@-- emt('Duration') --@]} &\r
% ...description...\r
\newcommand{\FSdesc}[5]{\r
\multicolumn{1}{c}{\rule{0pt}{2.5ex}\textbf{#1}} &\r
- \multicolumn{[@-- $unitprices ? '4' : '6' --@]}{l}{\textbf{#2}} &\r
+ \multicolumn{[@-- $unitprices ? '4' : '6' --@]}{l}{\r
+ \truncate{\FSdescriptionlength}{\textbf{#2}}\r
+ } &\r
[@-- $unitprices ? ' \multicolumn{1}{r}{\textbf{#3}} &'."\n".\r
' \multicolumn{1}{r}{\textbf{#4}} &'."\n"\r
: ''\r
% ...extended description...\r
\newcommand{\FSextdesc}[1]{\r
\multicolumn{1}{l}{\rule{0pt}{1.0ex}} &\r
-%% \multicolumn{2}{l}{\small{~-~#1}}\\\r
-#1\\\r
+ \multicolumn{6}{l}{\r
+ \truncate{12.8cm}{\small{~~~#1}}\r
+ } \\\r
}\r
-% ...and total line items.\r
+% ...call detail (multiple columns already)...\r
+\newcommand{\FScalldetail}[1]{\r
+ \multicolumn{1}{l}{\rule{0pt}{1.0ex}} &\r
+ ~~~#1\r
+ \\\r
+}\r
+}\r
+% ...and total line items (which use the full 12.8cm length, ignoring\r
+% unitprice/quantity\r
\newcommand{\FStotaldesc}[2]{\r
- & \multicolumn{6}{l}{#1} & #2\\\r
+ & \multicolumn{6}{l}{\r
+ \truncate{12.8cm}{#1}\r
+ } & #2\\\r
}\r
\r
% ...usage class summary\r
}\r
--@]\r
\begin{flushright}\r
-[@-- $terms ? emt('Terms') .": $terms" : '' --@]\\\r
+[@-- $terms ? emt('Terms') . ': ' . emt($terms) : '' --@]\\\r
[@-- $po_line --@]\\\r
\end{flushright}\r
\end{minipage}}\r
foreach my $ext_desc (@$ext_description) {\r
if ($section->{extended_description_generator}) {\r
$OUT .= &{$section->{extended_description_generator}}($ext_desc);\r
- } else {\r
- if ( $ext_desc !~ /[^\\]&/ ) {\r
- $ext_desc = substr($ext_desc, 0, 80) . '...'\r
- if (length($ext_desc) > 80);\r
- $ext_desc = '\multicolumn{6}{l}{\small{~~~'. $ext_desc. '}}';\r
- }else{\r
- $ext_desc = "~~~$ext_desc";\r
- }\r
- $OUT .= '\FSextdesc{' . $ext_desc . '}' . "${rowbreak}\n";\r
+ } elsif ( $ext_desc !~ /[^\\]&/ ) {\r
+ $OUT .= '\FSextdesc{' . $ext_desc . "}$rowbreak\n";\r
+ } else { # call detail\r
+ $OUT .= '\FScalldetail{' . $ext_desc . "}$rowbreak\n";\r
}\r
}\r
\r
</TD>
% }
- <TD ROWSPAN=<% $rowspan %> CLASS="grid" BGCOLOR="<% $bgcolor %>"><A HREF="<% $url %>">
- <% $part_svc->svc %></A></TD>
+ <TD ROWSPAN=<% $rowspan %> CLASS="grid" BGCOLOR="<% $bgcolor %>">
+ <A HREF="<% $url %>">
+ <% $part_svc->svc %>
+ </A>
+% # any alternate names of the service
+% my %msgcat = map { $_->locale => $_ } $part_svc->part_svc_msgcat;
+% my %labels = map { $_ => FS::Locales->description($_) } keys %msgcat;
+% my @locales = sort { $labels{$a} cmp $labels{$b} } keys %msgcat;
+% if ( @locales ) {
+ <BR>
+ <FONT SIZE="-1">
+% foreach my $locale (@locales) {
+ <% $labels{$locale} %>: <% $msgcat{$locale}->get('svc') %>
+ <BR>
+% }
+ </FONT>
+% }
+ </TD>
<TD ROWSPAN=<% $rowspan %> CLASS="grid" BGCOLOR="<% $bgcolor %>">
<% $svcdb %></TD>
<& /elements/progress-init.html,
$svcdb, #form name
[ # form fields to send
- qw(svc svcpart classnum selfservice_access disabled preserve exportnum),
- @fields
+ 'ALL'
+# qw(svc svcpart classnum selfservice_access disabled preserve exportnum),
+# @fields
],
'process/part_svc.cgi', # target
$p.'browse/part_svc.cgi', # redirect landing
{ type => 'columnstart' },
{ field => 'pkg',
- type => 'text',
+ type => 'input-locale-text',
size => 40, #32
maxlength => 50,
},
my $pkgpart = '';
-my $splice_locale_fields = sub {
- my( $fields, $pkey_value_callback, $pkg_value_callback ) = @_;
-
- my $n = 0;
- my @locale_fields = (
- map {
- my $pkey_value= $pkey_value_callback ? &$pkey_value_callback($_) : '';
- my $pkg_value = $pkg_value_callback
- ? $pkg_value_callback eq 'cgiparam'
- ? $cgi->param('pkgpartmsgnum'. $n. '_pkg')
- : &$pkg_value_callback($_)
- : '';
- (
- { field => 'pkgpartmsgnum'. $n,
- type => 'hidden',
- value => $pkey_value,
- },
- { field => 'pkgpartmsgnum'. $n. '_locale',
- type => 'hidden',
- value => $_,
- },
- { field => 'pkgpartmsgnum'. $n++. '_pkg',
- type => 'text',
- size => 40,
- #maxlength => 50,
- value => $pkg_value,
- },
- );
-
- }
- @locales
- );
- splice(@$fields, 7, 0, @locale_fields); #XXX 7 is arbitrary above
-
-};
-
my $error_callback = sub {
my($cgi, $object, $fields, $opt ) = @_;
$pkgpart = $object->pkgpart;
- &$splice_locale_fields(
- $fields,
- sub {
- my $locale = shift;
- my $part_pkg_msgcat = $object->part_pkg_msgcat($locale);
- $part_pkg_msgcat ? $part_pkg_msgcat->pkgpartmsgnum : '';
- },
- 'cgiparam'
- );
-
if ( $cgi->param('error') =~ / is suggested with / ) {
#yeah, detection is a shitty kludge, but we don't have exception objects
$opt->{form_init} = '<INPUT TYPE="checkbox" NAME="part_pkg_restrict_soft_override" VALUE="Y"> Override suggestion<BR><BR>';
$pkgpart = $object->pkgpart;
- &$splice_locale_fields(
- $fields,
- sub {
- my $locale = shift;
- my $part_pkg_msgcat = $object->part_pkg_msgcat($locale);
- $part_pkg_msgcat ? $part_pkg_msgcat->pkgpartmsgnum : '';
- },
- sub {
- my $locale = shift;
- my $part_pkg_msgcat = $object->part_pkg_msgcat($locale);
- $part_pkg_msgcat ? $part_pkg_msgcat->pkg : '';
- }
- );
-
};
my $new_callback = sub {
$options{'suspend_bill'}=1 if $conf->exists('part_pkg-default_suspend_bill');
- &$splice_locale_fields($fields, '', '');
-
};
my $clone_callback = sub {
foreach keys %part_pkg_currency;
}
- $recur_disabled = $object->freq ? 0 : 1;
-
- &$splice_locale_fields(
- $fields,
- '',
- sub {
- my $locale = shift;
- my $part_pkg_msgcat = $object->part_pkg_msgcat($locale);
- $part_pkg_msgcat ? $part_pkg_msgcat->pkg : '';
- }
- );
};
my $discount_error_callback = sub {
: ''
). '>';
+ } elsif ( $href->{$field}{'type'} =~ /^select-rt-/ ) {
+
+ $html .= include('/elements/'.$href->{$field}{'type'}.'.html',
+ 'name' => $layer.'__'.$field,
+ 'curr_value' => $options{$field},
+ map { $_ => $href->{$field}{$_} }
+ grep { $_ !~ /^(name|type|parse)$/ }
+ keys %{ $href->{$field} }
+ );
+
} elsif ( $href->{$field}{'type'} eq 'select-rate' ) {
$html .= include('/elements/select-rate.html',
}
</STYLE>
<SCRIPT TYPE="text/javascript">
+// copy all fields from the outer form (svc and its localizations, plus
+// preserve, selfservice_access, etc.) into the inner form, creating hidden
+// inputs if needed
function fixup_submit(layer) {
- document.forms[layer].submit.disabled = true;
- fixup(document.forms[layer]);
+ var layer_form = $(document.forms[layer]);
+ var main_form = $(document.forms['SvcEditMain']);
+ var data = main_form.serializeArray();
+ for (var i = 0; i < data.length; i++) {
+ var input = layer_form.children('[name=' + data[i].name + ']');
+ if (input[0]) {
+ input.prop('value', data[i].value);
+ } else {
+ $( '<input type="hidden">' )
+ .attr('name', data[i].name)
+ .prop('value', data[i].value)
+ .appendTo(layer_form);
+ }
+ }
+ layer_form[0]['submit'].disabled = true;
+ //fixup(document.forms[layer]);
window[layer+'process'].call();
}
</SCRIPT>
-<FORM NAME="dummy">
+<FORM NAME="SvcEditMain">
<FONT CLASS="fsinnerbox-title">Service Part #<% $part_svc->svcpart ? $part_svc->svcpart : "(NEW)" %></FONT>
<TABLE CLASS="fsinnerbox">
-<TR>
- <TD ALIGN="right">Service</TD>
- <TD><INPUT TYPE="text" NAME="svc" VALUE="<% $hashref->{svc} %>"></TD>
-<TR>
+<& /elements/tr-input-locale-text.html,
+ 'object' => $part_svc,
+ 'cgi' => $cgi,
+ 'field' => 'svc',
+ 'label' => 'Service',
+ 'curr_value' => $hashref->{svc},
+&>
+%#<TR>
+%# <TD ALIGN="right">Service</TD>
+%# <TD><INPUT TYPE="text" NAME="svc" VALUE="<% $hashref->{svc} %>"></TD>
+%#<TR>
<& /elements/tr-select-part_svc_class.html, curr_value=>$hashref->{classnum} &>
<TR>
- <TD ALIGN="right">Self-service access</TD>
+ <TH ALIGN="right">Self-service access</TD>
<TD>
<SELECT NAME="selfservice_access">
% tie my %selfservice_access, 'Tie::IxHash', #false laziness w/browse/part_svc
<TR>
- <TD ALIGN="right">Disable new orders</TD>
+ <TH ALIGN="right">Disable new orders</TD>
<TD><INPUT TYPE="checkbox" NAME="disabled" VALUE="Y"<% $hashref->{disabled} eq 'Y' ? ' CHECKED' : '' %>></TD>
</TR>
<TR>
- <TD ALIGN="right">Preserve this service on package cancellation</TD>
+ <TH ALIGN="right">Preserve this service on package cancellation</TD>
<TD><INPUT TYPE="checkbox" NAME="preserve" VALUE="Y"<% $hashref->{'preserve'} eq 'Y' ? ' CHECKED' : '' %>> </TD>
</TR>
#'selected_layer' => $p_svcdb,
'selected_layer' => $hashref->{svcdb} || 'svc_acct',
'options' => \%svcdb,
- 'form_name' => 'dummy',
+ 'form_name' => 'SvcEditMain',
#'form_action' => 'process/part_svc.cgi',
'form_action' => 'part_svc.cgi', #self
- 'form_elements' => [qw( svc svcpart classnum selfservice_access
- disabled preserve
- )],
+# 'form_elements' => [qw( svc svcpart classnum selfservice_access
+# disabled preserve
+# )],
'html_between' => $help,
'layer_callback' => sub {
include('elements/part_svc_column.html',
'fields' => [qw( fieldname fieldname2 )],
},
+ 'process_locale' => 'fieldname', # update entries in the _msgcat table
+
'process_upload' => {
'process' => 'misc/mytable-import.html',
# fields to pass to the back end job, besides the
}
- if ( !$error && $opt{'process_o2m'} ) {
-
- my @process_o2m = ref($opt{'process_o2m'}) eq 'ARRAY'
- ? @{ $opt{'process_o2m'} }
- : ( $opt{'process_o2m'} );
+ my @process_o2m;
+ if ( $opt{'process_o2m'} ) {
+ @process_o2m = ref($opt{'process_o2m'}) eq 'ARRAY'
+ ? @{ $opt{'process_o2m'} }
+ : ( $opt{'process_o2m'} );
+ }
+ if ( $opt{'process_locale'} ) {
+ push @process_o2m,
+ {
+ 'table' => $table . '_msgcat',
+ 'fields' => [ 'locale', $opt{'process_locale'} ],
+ };
+ }
+ if ( !$error ) {
foreach my $process_o2m (@process_o2m) {
'edit_ext' => 'cgi',
'precheck_callback' => $precheck_callback,
'args_callback' => $args_callback,
+ 'process_locale' => 'pkg',
'process_m2m' => \@process_m2m,
'process_o2m' => \@process_o2m,
)
my @process_o2m = (
{
- 'table' => 'part_pkg_msgcat',
- 'fields' => [qw( locale pkg )],
- },
- {
'table' => 'part_pkg_usageprice',
'fields' => [qw( price currency action target amount )],
quotationnum => scalar( $cgi->param('quotationnum') ),
} ) or die 'unknown quotationnum';
+my $params = {};
+$$params{'onhold'} = $cgi->param('onhold') ? 1 : 0;
+
my $cust_main = $quotation->cust_main;
if ( $cust_main ) {
- my $error = $quotation->order;
+ my $error = $quotation->order(undef,$params);
errorpage($error) if $error;
#i should be part of the order transaction
$quotation->replace;
} else {
- $cust_main = $quotation->convert_cust_main;
+ $cust_main = $quotation->convert_cust_main( $params );
errorpage($cust_main) unless ref($cust_main);# eq 'FS::cust_main';
}
</TR>
<% include('/elements/tr-td-label.html',
- 'label' => mt('Username'),
+ 'label' => $part_svc->part_svc_column('username')->columnlabel || mt('Username'),
'required' => $part_svc->part_svc_column('username')->required ) %>
% if ( $svcnum && $conf->exists('svc_acct-no_edit_username') ) {
<TD BGCOLOR="#eeeeee"><% $svc_acct->username() %></TD>
%if ( $part_svc->part_svc_column('_password')->columnflag ne 'F' ) {
% #XXX eventually should require "Edit Password" ACL
<% include('/elements/tr-td-label.html',
- 'label' => mt('Password'),
+ 'label' => $part_svc->part_svc_column('_password')->columnlabel || mt('Password'),
'required' => $part_svc->part_svc_column('_password')->required ) %>
<TD>
<INPUT TYPE="text" ID="clear_password" NAME="clear_password" VALUE="<% $password %>" SIZE=<% $pmax2 %> MAXLENGTH=<% $pmax %>>
% && $part_svc->part_svc_column('sec_phrase')->columnflag ne 'F' ) {
<% include('/elements/tr-td-label.html',
- 'label' => mt('Security phrase'),
+ 'label' => $part_svc->part_svc_column('sec_phrase')->columnlabel || mt('Security phrase'),
'required' => $part_svc->part_svc_column('sec_phrase')->required ) %>
<TD>
<INPUT TYPE="text" NAME="sec_phrase" VALUE="<% $sec_phrase %>" SIZE=32>
% );
<% include('/elements/tr-td-label.html',
- 'label' => mt('Domain'),
+ 'label' => $part_svc->part_svc_column('domsvc')->columnlabel || mt('Domain'),
'required' => $part_svc->part_svc_column('domsvc')->required ) %>
<TD>
<SELECT NAME="domsvc" SIZE=1>
'curr_value' => $svc_acct->pbxsvc,
'part_svc' => $part_svc,
'cust_pkg' => $cust_pkg,
+ 'label' => $part_svc->part_svc_column('pbxsvc')->columnlabel || 'PBX',
&>
%#pop
% } else {
<% include('/elements/tr-td-label.html',
- 'label' => mt('Access number'),
+ 'label' => $part_svc->part_svc_column('popnum')->columnlabel || mt('Access number'),
'required' => $part_svc->part_svc_column('popnum')->required ) %>
<TD><% FS::svc_acct_pop::popselector($popnum) %></TD>
</TR>
#'part_svc' => $part_svc,
#'cust_pkg' => $cust_pkg,
'required' => $part_svc->part_svc_column('sectornum')->required,
+ 'label' => $part_svc->part_svc_column('sectornum')->columnlabel || mt('Tower sector'),
&>
%} else {
<INPUT TYPE="hidden" NAME="sectornum" VALUE="<% $svc_acct->sectornum %>">
% if ( length($svc_acct->$xid()) ) {
<% include('/elements/tr-td-label.html',
- 'label' => uc($xid),
+ 'label' => $part_svc->part_svc_column($xid)->columnlabel || uc($xid),
'required' => $part_svc->part_svc_column($xid)->required ) %>
- <TR>
- <TD ALIGN="right"><% uc($xid) %></TD>
+%# <TR>
+%# <TD ALIGN="right"><% uc($xid) %></TD>
<TD BGCOLOR="#eeeeee"><% $svc_acct->$xid() %></TD>
<TD>
</TD>
% } else {
<% include('/elements/tr-td-label.html',
- 'label' => uc($xid),
+ 'label' => $part_svc->part_svc_column($xid)->columnlabel || uc($xid),
'required' => $part_svc->part_svc_column($xid)->required ) %>
<TD>
<INPUT TYPE="text" NAME="<% $xid %>" SIZE=8 MAXLENGTH=6 VALUE="<% $svc_acct->$xid() %>">
<% include('/elements/tr-td-label.html',
- 'label' => mt('Real Name'),
+ 'label' => $part_svc->part_svc_column('finger')->columnlabel || mt('Real Name'),
'required' => $part_svc->part_svc_column('finger')->required ) %>
<TD>
<INPUT TYPE="text" NAME="finger" VALUE="<% $svc_acct->finger %>">
<% include('/elements/tr-td-label.html',
- 'label' => mt('Home directory'),
+ 'label' => $part_svc->part_svc_column('dir')->columnlabel || mt('Home directory'),
'required' => $part_svc->part_svc_column('dir')->required ) %>
<TD><INPUT TYPE="text" NAME="dir" VALUE="<% $svc_acct->dir %>"></TD>
</TR>
<% include('/elements/tr-td-label.html',
- 'label' => mt('Shell'),
+ 'label' => $part_svc->part_svc_column('shell')->columnlabel || mt('Shell'),
'required' => $part_svc->part_svc_column('shell')->required ) %>
<TD>
<SELECT NAME="shell" SIZE=1>
'object' => $svc_acct,
'ip_field' => 'slipip',
'required' => $part_svc->part_svc_column('routernum')->required,
+ 'label' => $part_svc->part_svc_column('routernum')->columnlabel,
'ip_addr_required' => $part_svc->part_svc_column('slipip')->required,
+ 'ip_addr_label' => $part_svc->part_svc_column('slipip')->columnlabel,
&>
% } else {
% # don't expose these to the user--they're only useful in the other case
<INPUT TYPE="hidden" NAME="slipip" VALUE="<% $svc_acct->slipip %>">
% } else {
<% include('/elements/tr-td-label.html',
- 'label' => mt('IP'),
+ 'label' => $part_svc->part_svc_column('slipip')->columnlabel || mt('IP'),
'required' => $part_svc->part_svc_column('slipip')->required ) %>
<TD><INPUT TYPE="text" NAME="slipip" VALUE="<% $svc_acct->slipip %>"></TD>
</TR>
<% include('/elements/tr-td-label.html',
- 'label' => mt('RADIUS groups'),
+ 'label' => $part_svc->part_svc_column('usergroup')->columnlabel || mt('RADIUS groups'),
'required' => $part_svc->part_svc_column('usergroup')->required ) %>
% if ( $part_svc_usergroup->columnflag eq 'F' ) {
<TD BGCOLOR="#eeeeee"><% join('<BR>', @groupnames) %></TD>
% if ( $part_svc->part_svc_column('quota')->columnflag eq 'F' ) {
<INPUT TYPE="hidden" NAME="quota" VALUE="<% $svc_acct->quota %>">
% } else {
-% my $quota_label = $communigate ? 'Mail storage limit' : 'Quota';
+% my $quota_label = $communigate ? 'Mail storage limit' : ($part_svc->part_svc_column('quota')->columnlabel || 'Quota');
<% include('/elements/tr-td-label.html',
'label' => $quota_label,
'required' => $part_svc->part_svc_column('quota')->required ) %>
.fsinnerbox th {
font-weight:normal;
font-size:80%;
- valign: bottom;
+ vertical-align: bottom;
color: #666666;
}
<link rel="stylesheet" href="<% $fsurl %>elements/jquery-ui.min.css">
<SCRIPT SRC="<% $fsurl %>elements/jquery.js"></SCRIPT>
<SCRIPT SRC="<% $fsurl %>elements/jquery-ui.min.js"></SCRIPT>
+% if ( $FS::CurrentUser::CurrentUser->option('printtofit') ) {
+ <SCRIPT SRC="<% $fsurl %>elements/printtofit.js"></SCRIPT>
+% }
% }
<% include('init_overlib.html') |n %>
<% include('rs_init_object.html') |n %>
<META HTTP-Equiv="Expires" Content="0">
% unless ( $no_jquery ) {
<SCRIPT SRC="<% $fsurl %>elements/jquery.js"></SCRIPT>
+% if ( $FS::CurrentUser::CurrentUser->option('printtofit') ) {
+ <SCRIPT SRC="<% $fsurl %>elements/printtofit.js"></SCRIPT>
+% }
% }
<% $head |n %>
</HEAD>
--- /dev/null
+$().ready(function() {
+ var beforePrint = function() {
+ if ($('body').width() > 0) {
+ // 7.5 inches * 96 DPI; maybe make the width a user pref?
+ var maxwidth = 7.5 * 96;
+ $('body').css('zoom', maxwidth / $('body').width());
+ }
+ };
+ var afterPrint = function() {
+ $('body').css('zoom', 1);
+ }
+
+ if (window.matchMedia) { // chrome, most importantly; also IE10?
+ window.matchMedia('print').addListener(
+ function(mq) {
+ mq.matches ? beforePrint() : afterPrint();
+ }
+ );
+ } else { // other IE
+ $(window).on('beforeprint', beforePrint);
+ $(window).on('afterprint', afterPrint);
+ }
+ // got nothing for firefox
+ // https://bugzilla.mozilla.org/show_bug.cgi?id=774398
+ // but firefox already has "shrink to fit"
+});
overlib( 'Submitting job to server...', WIDTH, 444, HEIGHT, 168, CAPTION, 'Please wait...', STICKY, AUTOSTATUSCAP, CLOSETEXT, '', CLOSECLICK, MIDX, 0, MIDY, 0 );
+ // jQuery .serializeArray() maybe?
+ var copy_fields = <% encode_json(\%copy_fields) %>;
var Hash = new Array();
var x = 0;
var fieldName;
for (var i = 0; i<document.<%$formname%>.elements.length; i++) {
field = document.<%$formname%>.elements[i];
- if ( <% join(' || ', map { "(field.name.indexOf('$_') > -1)" } @$fields ) %>
- )
- {
+ if ( <% $all_fields %> || copy_fields[ field.name ] ) {
if ( field.type == 'select-multiple' ) {
//alert('select-multiple ' + field.name);
for (var j=0; j < field.options.length; j++) {
%dest_info,
);
+my $all_fields = 0;
+my %copy_fields;
+if (grep '/^ALL$/', @$fields) {
+ $all_fields = 1;
+} else {
+ %copy_fields = map { $_ => 1 } @$fields;
+}
+
#stupid safari is caching the "location" of popup iframs, and submitting them
#instead of displaying them. this should prevent that.
my $popup_name = 'popup-'.random_id();
-<SELECT NAME="<% $opt{name} %>">
+<SELECT NAME="<% $opt{'name'} %>"<% $opt{'multiple'} ? ' MULTIPLE' : '' %>>
% while ( @fields ) {
-<OPTION VALUE="<% shift @fields %>"><% shift @fields %></OPTION>
+% my $value = shift @fields;
+% my $label = shift @fields;
+<OPTION VALUE="<% $value %>"<% $curr_value{$value} ? ' SELECTED' : '' %>><% $label %></OPTION>
% }
</SELECT>
<%init>
my %opt = @_;
-my $lookuptype = $opt{lookuptype};
-my $valuetype = $opt{valuetype};
-# get a list of TimeValue-type custom fields
-my $CurrentUser = RT::CurrentUser->new();
-$CurrentUser->LoadByName($FS::CurrentUser::CurrentUser->username);
-die "RT not configured" unless $CurrentUser->id;
-my $CFs = RT::CustomFields->new($CurrentUser);
-$CFs->Limit(FIELD => 'LookupType',
- OPERATOR => 'ENDSWITH',
- VALUE => $lookuptype)
- if $lookuptype;
-
-$CFs->Limit(FIELD => 'Type',
- VALUE => $valuetype)
- if $valuetype;
+my %curr_value = map { $_ => 1 } split(', ',$opt{'curr_value'});
my @fields;
push @fields, '', $opt{empty_label} if exists($opt{empty_label});
-while (my $CF = $CFs->Next) {
- push @fields, $CF->Name, ($CF->Description || $CF->Name);
+my $conf = new FS::Conf;
+
+if ($conf->config('ticket_system') eq 'RT_Internal') {
+
+ push @fields, FS::TicketSystem->custom_fields(
+ lookuptype => $opt{lookuptype},
+ valuetype => $opt{valuetype},
+ );
+
}
+
</%init>
--- /dev/null
+<SELECT NAME="<% $opt{'name'} %>"<% $opt{'multiple'} ? ' MULTIPLE' : '' %>>
+% while ( @fields ) {
+% my $value = shift @fields;
+% my $label = shift @fields;
+<OPTION VALUE="<% $value %>"<% $curr_value{$value} ? ' SELECTED' : '' %>><% $label %></OPTION>
+% }
+</SELECT>
+<%init>
+my %opt = @_;
+
+my %curr_value = map { $_ => 1 } split(', ',$opt{'curr_value'});
+
+my @fields;
+push @fields, '', $opt{empty_label} if exists($opt{empty_label});
+
+my $conf = new FS::Conf;
+
+if ($conf->config('ticket_system') eq 'RT_Internal') {
+
+ push @fields, FS::TicketSystem->queues();
+
+}
+
+</%init>
my $empty_value = $opt{'empty_value'} || '';
-my @terms = ( emt('Payable upon receipt'),
- ( map "Net $_",
- 0, 3, 5, 7, 9, 10, 14, 15, 18, 20, 21, 25, 30, 45, 60, 90 ),
- );
+my @terms = map emt($_), @FS::Conf::invoice_terms;
my @pre_options = $opt{pre_options} ? @{ $opt{pre_options} } : ();
--- /dev/null
+<%doc>
+Usage:
+
+In edit/foo.html:
+
+<& /elements/tr-input-locale-text.html,
+ cgi => $cgi, # needed to preserve values in error redirect
+ object => $record,
+ field => 'myfield',
+ label => 'My Field',
+&>
+
+And in edit/process/foo.html:
+<& elements/process.html,
+ ...
+ process_locale => 'myfield',
+&>
+
+'object' needs to be an FS::Record subclass instance for a table that has
+a '_msgcat' localization table. For a table "foo" where "foo.myfield"
+contains some customer-visible label (in the default locale),
+"foo_msgcat.myfield" contains the translation of that label for a customer
+locale. The foreign key in foo_msgcat must have the same name as the primary
+key of foo.
+
+Currently only a single field can be localized this way; including this
+element more than once in the form will lead to conflicts. This is how
+it should work; if at some point we need to localize several fields of the
+same record, we should modify this element to show multiple inputs for each
+locale.
+
+</%doc>
+<%init>
+
+my %opt = @_;
+my $object = delete $opt{object};
+my $field = delete $opt{field};
+
+# identify our locales
+my $conf = FS::Conf->new;
+my $default_locale = $conf->config('locale') || 'en_';
+my @locales = grep { ! /^$default_locale/ } $conf->config('available-locales');
+
+my $label = delete $opt{label};
+my %labels = map { $_ => "$label—".FS::Locales->description($_) }
+ @locales;
+@locales = sort { $labels{$a} cmp $labels{$b} } @locales;
+my %curr_values;
+
+# where are the msgcat records?
+my $msgcat_table = $object->table . '_msgcat';
+my $msgcat_pkey = dbdef->table($msgcat_table)->primary_key;
+my %msgcat_pkeyvals;
+
+# find existing msgcat records, if any, and record their message values
+# and pkeys
+my $pkey = $object->primary_key;
+my $pkeyval = $object->get($pkey);
+if ($pkeyval) { # of course if this is a new record there won't be any
+ my @linked = qsearch($msgcat_table, { $pkey => $pkeyval });
+ foreach (@linked) {
+ $curr_values{ $_->locale } = $_->get( $field );
+ $msgcat_pkeyvals{ $_->locale } = $_->get( $msgcat_pkey );
+ }
+}
+
+# sticky-on-error the locale inputs
+if( my $cgi = $opt{cgi} ) {
+ my $i = 0;
+ # they're named 'foomsgnum0_locale' and 'foomsgnum0_myfield'
+ while ( my $locale = $cgi->param($msgcat_pkey . $i . '_locale') ) {
+ my $value = $cgi->param($msgcat_pkey . $i . '_' . $field);
+ $curr_values{ $locale } = $value;
+ $i++;
+ }
+}
+
+# compat with tr-input-text for styling
+my $cell_style = $opt{'cell_style'} ? 'STYLE="'. $opt{'cell_style'}. '"' : '';
+
+my $colspan = $opt{'colspan'} ? 'COLSPAN="'.$opt{'colspan'}.'"' : '';
+
+
+</%init>
+% # pass through %opt on all of these to retain formatting
+% # one tr, td, and input for the default locale
+<& tr-input-text.html,
+ %opt,
+ 'label' => $label,
+ 'field' => $field
+&>
+% # and one for each of the others
+% my $i = 0;
+% foreach my $locale (@locales) {
+% my $basename = $msgcat_pkey . $i;
+% my $lfield = $basename . '_' . $field;
+<& tr-td-label.html,
+ %opt,
+ 'id' => $lfield, # uniqueness
+ 'label' => $labels{$locale}
+&>
+ <TD <% $colspan %><% $cell_style %> ID="<% $lfield %>_input0">
+ <& hidden.html,
+ 'field' => $basename,
+ 'curr_value' => $msgcat_pkeyvals{$locale},
+ # will be empty if this is a new record and/or new locale, that's fine
+ &>
+ <& hidden.html,
+ 'field' => $basename . '_locale',
+ 'curr_value' => $locale,
+ &>
+ <& input-text.html,
+ %opt,
+ 'field' => $lfield,
+ 'curr_value' => $curr_values{$locale},
+ &>
+ </TD>
+</TR>
+% $i++;
+% } # foreach $locale
]
&>
</td></tr>
-<& /elements/tr-td-label.html, label => 'IP address', required => $opt{'ip_addr_required'} &>
+<& /elements/tr-td-label.html, label => ($opt{'ip_addr_label'} || 'IP address'), required => $opt{'ip_addr_required'} &>
<td>
% #warn Dumper \%fixed;
% if ( exists $fixed{$ip_field} ) {
len = args.length - 1;
}
for (var i = 0; i < len; i++)
- content = content + "&arg=" + escape(args[i]);
+ content = content + "&arg=" + encodeURIComponent(args[i]);
content = content.replace( /[+]/g, '%2B'); // fix unescaped plus signs
if ( '<%$method%>' == 'GET' ) {
disable_html_editor disable_enter_submit_onetimecharge
enable_mask_clipboard_hack dashboard_customers
customer_view_emails
+ printtofit
email_address
snom-ip snom-username snom-password
vonage-fromnumber vonage-username vonage-password
</TR>
<TR>
+ <TH ALIGN="right">Scale documents to fit on a letter-size page</TH>
+ <TD ALIGN="left">
+ <INPUT TYPE="checkbox" NAME="printtofit" VALUE="Y" <% $curuser->option('printtofit') ? 'CHECKED' : '' %>>
+ </TD>
+ </TR>
+
+ <TR>
<TH ALIGN="right">How many recently-modified customers displayed on dashboard</TH>
<TD ALIGN="left" COLSPAN=2>
<INPUT TYPE="text" NAME="dashboard_customers" VALUE="<% $curuser->option('dashboard_customers') %>"></TD>
</TD>
</TR>
-
-
</TABLE>
<BR>
],
'show_combined' => 1,
&>
+<%shared>
+# canonicalize the payby subtype string to an SQL-quoted list
+my %cardtype_of = (
+ 'VisaMC' => q['VISA card', 'MasterCard'],
+ 'Amex' => q['American Express card'],
+ 'Discover' => q['Discover card'],
+ 'Maestro' => q['Switch', 'Solo', 'Laser'],
+);
+</%shared>
<%init>
my %opt = @_;
push @header, emt('Card Type');
$align .= 'r';
push @links, '';
- push @fields, sub {
- (($_[0]->payby eq 'CARD') && ($_[0]->paymask !~ /N\/A/)) ? cardtype($_[0]->paymask) : ''
- };
- push @sort_fields, '';
+ push @fields, 'paycardtype';
+ push @sort_fields, 'paycardtype';
}
if ( $unapplied ) {
if ( $cgi->param('payby') ) {
my @all_payby_search = ();
- foreach my $payby ( $cgi->param('payby') ) {
-
- $payby =~
- /^(CARD|CHEK|BILL|CASH|PPAL|APPL|ANRD|PREP|WIRE|WEST|IDTP|EDI|MCRD|MCHK)(-(VisaMC|Amex|Discover|Maestro|Tokenized))?$/
- or die "illegal payby $payby";
-
- my $payby_search = "$table.payby = '$1'";
-
- if ( $3 ) {
-
- my $cardtype = $3;
-
- my $similar_to = dbh->{Driver}->{Name} =~ /^mysql/i
- ? 'REGEXP' #doesn't behave exactly the same, but
- #should work for our patterns
- : 'SIMILAR TO';
-
- my $search;
- if ( $cardtype eq 'VisaMC' ) {
-
- #avoid posix regexes for portability
- $search =
- # Visa
- " ( ( substring($table.payinfo from 1 for 1) = '4' ".
- # is not Switch
- " AND substring($table.payinfo from 1 for 4) != '4936' ".
- " AND substring($table.payinfo from 1 for 6) ".
- " NOT $similar_to '49030[2-9]' ".
- " AND substring($table.payinfo from 1 for 6) ".
- " NOT $similar_to '49033[5-9]' ".
- " AND substring($table.payinfo from 1 for 6) ".
- " NOT $similar_to '49110[1-2]' ".
- " AND substring($table.payinfo from 1 for 6) ".
- " NOT $similar_to '49117[4-9]' ".
- " AND substring($table.payinfo from 1 for 6) ".
- " NOT $similar_to '49118[1-2]' ".
- " )".
- # MasterCard
- " OR substring($table.payinfo from 1 for 2) = '51' ".
- " OR substring($table.payinfo from 1 for 2) = '52' ".
- " OR substring($table.payinfo from 1 for 2) = '53' ".
- " OR substring($table.payinfo from 1 for 2) = '54' ".
- " OR substring($table.payinfo from 1 for 2) = '54' ".
- " OR substring($table.payinfo from 1 for 2) = '55' ".
- " OR substring($table.payinfo from 1 for 4) $similar_to '222[1-9]' ".
- " OR substring($table.payinfo from 1 for 3) $similar_to '22[3-9]' ".
- " OR substring($table.payinfo from 1 for 2) $similar_to '2[3-6]' ".
- " OR substring($table.payinfo from 1 for 3) $similar_to '27[0-1]' ".
- " OR substring($table.payinfo from 1 for 4) = '2720' ".
- " OR substring($table.payinfo from 1 for 3) = '2[2-7]x' ".
- " ) ";
-
- } elsif ( $cardtype eq 'Amex' ) {
-
- $search =
- " ( substring($table.payinfo from 1 for 2 ) = '34' ".
- " OR substring($table.payinfo from 1 for 2 ) = '37' ".
- " ) ";
-
- } elsif ( $cardtype eq 'Discover' ) {
-
- my $country = $conf->config('countrydefault') || 'US';
-
- $search =
- " ( substring($table.payinfo from 1 for 4 ) = '6011' ".
- " OR substring($table.payinfo from 1 for 3 ) = '60x' ".
- " OR substring($table.payinfo from 1 for 2 ) = '65' ".
-
- # diner's 300-305 / 3095
- " OR substring($table.payinfo from 1 for 3 ) = '300' ".
- " OR substring($table.payinfo from 1 for 3 ) = '301' ".
- " OR substring($table.payinfo from 1 for 3 ) = '302' ".
- " OR substring($table.payinfo from 1 for 3 ) = '303' ".
- " OR substring($table.payinfo from 1 for 3 ) = '304' ".
- " OR substring($table.payinfo from 1 for 3 ) = '305' ".
- " OR substring($table.payinfo from 1 for 4 ) = '3095' ".
- " OR substring($table.payinfo from 1 for 3 ) = '30x' ".
-
- # diner's 36, 38, 39
- " OR substring($table.payinfo from 1 for 2 ) = '36' ".
- " OR substring($table.payinfo from 1 for 2 ) = '38' ".
- " OR substring($table.payinfo from 1 for 2 ) = '39' ".
-
- " OR substring($table.payinfo from 1 for 3 ) = '644' ".
- " OR substring($table.payinfo from 1 for 3 ) = '645' ".
- " OR substring($table.payinfo from 1 for 3 ) = '646' ".
- " OR substring($table.payinfo from 1 for 3 ) = '647' ".
- " OR substring($table.payinfo from 1 for 3 ) = '648' ".
- " OR substring($table.payinfo from 1 for 3 ) = '649' ".
- " OR substring($table.payinfo from 1 for 3 ) = '64x' ".
-
- # JCB cards in the 3528-3589 range identified as Discover inside US & territories (NOT Canada)
- ( $country =~ /^(US|PR|VI|MP|PW|GU)$/
- ?" OR substring($table.payinfo from 1 for 4 ) = '3528' ".
- " OR substring($table.payinfo from 1 for 4 ) = '3529' ".
- " OR substring($table.payinfo from 1 for 3 ) = '353' ".
- " OR substring($table.payinfo from 1 for 3 ) = '354' ".
- " OR substring($table.payinfo from 1 for 3 ) = '355' ".
- " OR substring($table.payinfo from 1 for 3 ) = '356' ".
- " OR substring($table.payinfo from 1 for 3 ) = '357' ".
- " OR substring($table.payinfo from 1 for 3 ) = '358' ".
- " OR substring($table.payinfo from 1 for 3 ) = '35x' "
- :""
- ).
-
- #China Union Pay processed as Discover in US, Mexico and Caribbean
- ( $country =~ /^(US|MX|AI|AG|AW|BS|BB|BM|BQ|VG|KY|CW|DM|DO|GD|GP|JM|MQ|MS|BL|KN|LC|VC|MF|SX|TT|TC)$/
- ?" OR substring($table.payinfo from 1 for 3 ) $similar_to '62[24-68x]' "
- :""
- ).
-
- " ) ";
-
- } elsif ( $cardtype eq 'Maestro' ) {
-
- $search =
- " ( substring($table.payinfo from 1 for 2 ) = '63' ".
- " OR substring($table.payinfo from 1 for 2 ) = '67' ".
- " OR substring($table.payinfo from 1 for 6 ) = '564182' ".
- " OR substring($table.payinfo from 1 for 4 ) = '4936' ".
- " OR substring($table.payinfo from 1 for 6 ) ".
- " $similar_to '49030[2-9]' ".
- " OR substring($table.payinfo from 1 for 6 ) ".
- " $similar_to '49033[5-9]' ".
- " OR substring($table.payinfo from 1 for 6 ) ".
- " $similar_to '49110[1-2]' ".
- " OR substring($table.payinfo from 1 for 6 ) ".
- " $similar_to '49117[4-9]' ".
- " OR substring($table.payinfo from 1 for 6 ) ".
- " $similar_to '49118[1-2]' ".
- " ) ";
-
- } elsif ( $cardtype eq 'Tokenized' ) {
-
- $search = " substring($table.payinfo from 1 for 2 ) = '99' ";
+ foreach my $payby_string ( $cgi->param('payby') ) {
+
+ my $payby_search;
+
+ my ($payby, $subtype) = split('-', $payby_string);
+ # make sure it exists and is a transaction type
+ if ( FS::payby->payment_payby2longname($payby) ) {
+ $payby_search = "$table.payby = " . dbh->quote($payby);
+ } else {
+ die "illegal payby $payby_string";
+ }
+
+ if ( $subtype ) {
+
+ if ( $subtype eq 'Tokenized' ) {
+
+ $payby_search .= " AND substring($table.payinfo from 1 for 2 ) = '99' ";
+ # XXX should store the cardtype as 'Tokenized' in this case?
} else {
- die "unknown card type $cardtype";
- }
- my $masksearch = $search;
- $masksearch =~ s/$table\.payinfo/$table.paymask/gi;
+ my $in_cardtype = $cardtype_of{$subtype}
+ or die "unknown card type $subtype";
+ $payby_search .= " AND $table.paycardtype IN($in_cardtype)";
- $payby_search = "( $payby_search AND ( $search OR ( $table.paymask IS NOT NULL AND $masksearch ) ) )";
+ }
}
'addl_from' => $addl_from,
};
+warn Dumper \$sql_query;
+
} else {
#hmm... is this still used?
<BR><BR>
% if ( $curuser->access_right('New customer') && $quotation->quotation_pkg ) {
+% # if we end up with more than one option, combine these links and add an interstitial screen
<A HREF="<%$p%>edit/process/quotation_convert.html?quotationnum=<% $quotation->quotationnum %>">Place order</A>
+ | <A HREF="<%$p%>edit/process/quotation_convert.html?quotationnum=<% $quotation->quotationnum %>&onhold=1">Order on hold</A>
<BR><BR>
% }
<% &ntable("#cccccc") %><TR><TD><% &ntable("#cccccc",2) %>
-<& /view/elements/tr.html, label=>mt('Service'), value=>$part_svc->svc &>
+<& /view/elements/tr.html, label=> $part_svc->part_svc_column('svc')->columnlabel || mt('Service'), value=>$part_svc->svc &>
% if ( $opt{cust_svc}->agent_svcid ) {
<& /view/elements/tr.html, label=>mt('Legacy ID'), value=>$opt{cust_svc}->agent_svcid &>
% }
-<& /view/elements/tr.html, label=>mt('Username'), value=>$svc_acct->username &>
-<& /view/elements/tr.html, label=>mt('Domain'), value=>$domain &>
+<& /view/elements/tr.html, label=> $part_svc->part_svc_column('username')->columnlabel || mt('Username'), value=>$svc_acct->username &>
+<& /view/elements/tr.html, label=> $part_svc->part_svc_column('domsvc')->columnlabel || mt('Domain'), value=>$domain &>
% if ( $opt{'communigate'} ) {
<& /view/elements/tr.html, label=>mt('Aliases'), value=>$svc_acct->cgp_aliases &>
%}
% if ( $svc_acct->pbxsvc ) {
- <& /view/elements/tr.html, label=>mt('PBX'), value=>$svc_acct->pbx_title &>
+ <& /view/elements/tr.html, label=> $part_svc->part_svc_column('pbxsvc')->columnlabel || mt('PBX'), value=>$svc_acct->pbx_title &>
%}
% my $show_pw = '';
% # show nothing
% } else {
<TR>
- <TD ALIGN="right"><% mt('Password') %></TD>
+ <TD ALIGN="right"><% $psc->columnlabel || mt('Password') %></TD>
<TD STYLE="background-color: #ffffff; white-space: nowrap">
<% $show_pw %>
% my $curuser = $FS::CurrentUser::CurrentUser;
% }
% if ( $conf->exists('security_phrase') ) {
- <& /view/elements/tr.html, label=>mt('Security phrase'), value=>$svc_acct->sec_phrase &>
+ <& /view/elements/tr.html, label=> $part_svc->part_svc_column('sec_phrase')->columnlabel || mt('Security phrase'), value=>$svc_acct->sec_phrase &>
% }
% if ( $svc_acct->popnum ) {
% my $svc_acct_pop = qsearchs('svc_acct_pop',{'popnum'=>$svc_acct->popnum});
- <& /view/elements/tr.html, label=>mt('Access number'), value=>$svc_acct_pop->text &>
+ <& /view/elements/tr.html, label=> $part_svc->part_svc_column('popnum')->columnlabel || mt('Access number'), value=>$svc_acct_pop->text &>
% }
% if ( $svc_acct->sectornum && $conf->exists('svc_acct-tower_sector') ) {
% ? '<A HREF="http://'. $tower_sector->ip_addr. '">'
% : '';
<& /view/elements/tr.html,
- label => mt('Tower sector'),
+ label => $part_svc->part_svc_column('sectornum')->columnlabel || mt('Tower sector'),
value => $link. $tower_sector->description. ($link ? '</A>' : ''),
&>
% }
&>
% if ($svc_acct->uid ne '') {
- <& /view/elements/tr.html, label=>mt('UID'), value=>$svc_acct->uid &>
+ <& /view/elements/tr.html, label=> $part_svc->part_svc_column('uid')->columnlabel || mt('UID'), value=>$svc_acct->uid &>
% }
% if ($svc_acct->gid ne '') {
- <& /view/elements/tr.html, label=>mt('GID'), value=>$svc_acct->gid &>
+ <& /view/elements/tr.html, label=> $part_svc->part_svc_column('gid')->columnlabel || mt('GID'), value=>$svc_acct->gid &>
% }
% if ($svc_acct->finger ne '') {
- <& /view/elements/tr.html, label=>mt('Real Name'), value=>$svc_acct->finger &>
+ <& /view/elements/tr.html, label=> $part_svc->part_svc_column('finger')->columnlabel || mt('Real Name'), value=>$svc_acct->finger &>
% }
% if ($svc_acct->dir ne '') {
- <& /view/elements/tr.html, label=>mt('Home directory'), value=>$svc_acct->dir &>
+ <& /view/elements/tr.html, label=> $part_svc->part_svc_column('dir')->columnlabel || mt('Home directory'), value=>$svc_acct->dir &>
% }
% if ($svc_acct->shell ne '') {
- <& /view/elements/tr.html, label=>mt('Shell'), value=>$svc_acct->shell &>
+ <& /view/elements/tr.html, label=> $part_svc->part_svc_column('shell')->columnlabel || mt('Shell'), value=>$svc_acct->shell &>
% }
% if ($svc_acct->quota ne '' && ! $opt{'communigate'} ) {
- <& /view/elements/tr.html, label=>mt('Quota'), value=>$svc_acct->quota &>
+ <& /view/elements/tr.html, label=> $part_svc->part_svc_column('quota')->columnlabel || mt('Quota'), value=>$svc_acct->quota &>
% } elsif ( $opt{'communigate'} ) {
% if ($svc_acct->slipip) {
<& /view/elements/tr.html,
- label=>mt('IP address'),
+ label=> $part_svc->part_svc_column('slipip')->columnlabel || mt('IP address'),
value=> slipip($svc_acct)
&>
% }
&>
% }
-<& /view/elements/tr.html, label=>mt('RADIUS groups'),
+<& /view/elements/tr.html, label=> $part_svc->part_svc_column('usergroup')->columnlabel || mt('RADIUS groups'),
value=>join('<BR>', $svc_acct->radius_groups('long_description')) &>
<& router.html, 'svc_acct' => $svc_acct &>
VALUE => $TicketsObj->CurrentUser->id
);
}
- $TicketsObj->Limit(
- FIELD => 'Status',
- OPERATOR => '!=',
- ENTRYAGGREGATOR => 'AND',
- VALUE => 'resolved'
- );
- $TicketsObj->Limit(
- FIELD => 'Status',
- OPERATOR => '!=',
- ENTRYAGGREGATOR => 'AND',
- VALUE => 'rejected',
- );
+ foreach my $status (qw(resolved rejected deleted)) {
+ $TicketsObj->Limit(
+ FIELD => 'Status',
+ OPERATOR => '!=',
+ ENTRYAGGREGATOR => 'AND',
+ VALUE => $status,
+ );
+ }
my $txn_alias = $TicketsObj->JoinTransactions;
$TicketsObj->Limit(
ALIAS => $txn_alias,