X-Git-Url: http://git.freeside.biz/gitweb/?a=blobdiff_plain;f=FS%2FFS%2Fpart_pkg.pm;h=8cfd6143c157b599dfe5727f0d2a1ee47dbb8d49;hb=bbac9227cd1dc41358fdad933997a113373d3e49;hp=3eb3bc13948c95eb25369a6a4db82ba683890676;hpb=42132c9a86d36d7fefa7ba5f058f764ba6e7ad5b;p=freeside.git diff --git a/FS/FS/part_pkg.pm b/FS/FS/part_pkg.pm index 3eb3bc139..8cfd6143c 100644 --- a/FS/FS/part_pkg.pm +++ b/FS/FS/part_pkg.pm @@ -1,9 +1,10 @@ package FS::part_pkg; use strict; -use vars qw( @ISA %plans $DEBUG ); +use vars qw( @ISA %plans $DEBUG $setup_hack ); use Carp qw(carp cluck confess); use Scalar::Util qw( blessed ); +use Time::Local qw( timelocal_nocheck ); use Tie::IxHash; use FS::Conf; use FS::Record qw( qsearch qsearchs dbh dbdef ); @@ -21,6 +22,7 @@ use FS::part_pkg_link; @ISA = qw( FS::m2m_Common FS::option_Common ); $DEBUG = 0; +$setup_hack = 0; =head1 NAME @@ -431,6 +433,12 @@ sub check { $self->freq($1); } + my @null_agentnum_right = ( 'Edit global package definitions' ); + push @null_agentnum_right, 'One-time charge' + if $self->freq =~ /^0/; + push @null_agentnum_right, 'Customize customer package' + if $self->disabled eq 'Y'; #good enough + my $error = $self->ut_numbern('pkgpart') || $self->ut_text('pkg') || $self->ut_text('comment') @@ -447,7 +455,10 @@ sub check { 'part_pkg_taxproduct', 'taxproductnum' ) - || $self->ut_agentnum_acl('agentnum', 'Edit global package definitions') + || ( $setup_hack + ? $self->ut_foreign_keyn('agentnum', 'agent', 'agentnum' ) + : $self->ut_agentnum_acl('agentnum', \@null_agentnum_right) + ) || $self->SUPER::check ; return $error if $error; @@ -553,6 +564,18 @@ packages. =cut +=item type_pkgs + +Returns all FS::type_pkgs objects (see L) for this package +definition. + +=cut + +sub type_pkgs { + my $self = shift; + qsearch('type_pkgs', { 'pkgpart' => $self->pkgpart } ); +} + sub pkg_svc { my $self = shift; @@ -590,22 +613,60 @@ Returns the svcpart of the primary service definition (see L) associated with this package definition (see L). Returns false if there not a primary service definition or exactly one service definition with quantity 1, or if SVCDB is specified and does not match the -svcdb of the service definition, +svcdb of the service definition. SVCDB can be specified as a scalar table +name, such as 'svc_acct', or as an arrayref of possible table names. =cut sub svcpart { + my $pkg_svc = shift->_primary_pkg_svc(@_); + $pkg_svc ? $pkg_svc->svcpart : ''; +} + +=item part_svc [ SVCDB ] + +Like the B method, but returns the FS::part_svc object (see +L). + +=cut + +sub part_svc { + my $pkg_svc = shift->_primary_pkg_svc(@_); + $pkg_svc ? $pkg_svc->part_svc : ''; +} + +sub _primary_pkg_svc { my $self = shift; - my $svcdb = scalar(@_) ? shift : ''; + + my $svcdb = scalar(@_) ? shift : []; + $svcdb = ref($svcdb) ? $svcdb : [ $svcdb ]; + my %svcdb = map { $_=>1 } @$svcdb; + my @svcdb_pkg_svc = - grep { ( $svcdb eq $_->part_svc->svcdb || !$svcdb ) } $self->pkg_svc; - my @pkg_svc = (); - @pkg_svc = grep { $_->primary_svc =~ /^Y/i } @svcdb_pkg_svc - if dbdef->table('pkg_svc')->column('primary_svc'); + grep { !scalar(@$svcdb) || $svcdb{ $_->part_svc->svcdb } } + $self->pkg_svc; + + my @pkg_svc = grep { $_->primary_svc =~ /^Y/i } @svcdb_pkg_svc; @pkg_svc = grep {$_->quantity == 1 } @svcdb_pkg_svc unless @pkg_svc; return '' if scalar(@pkg_svc) != 1; - $pkg_svc[0]->svcpart; + $pkg_svc[0]; +} + +=item svcpart_unique_svcdb SVCDB + +Returns the svcpart of a service definition (see L) matching +SVCDB associated with this package definition (see L). Returns +false if there not a primary service definition for SVCDB or there are multiple +service definitions for SVCDB. + +=cut + +sub svcpart_unique_svcdb { + my( $self, $svcdb ) = @_; + my @svcdb_pkg_svc = grep { ( $svcdb eq $_->part_svc->svcdb ) } $self->pkg_svc; + return '' if scalar(@svcdb_pkg_svc) != 1; + $svcdb_pkg_svc[0]->svcpart; } =item payby @@ -714,6 +775,41 @@ sub freq_pretty { } } +=item add_freq TIMESTAMP + +Adds the frequency of this package to the provided timestamp and returns +the resulting timestamp, or -1 if the frequency of this package could not be +parsed (shouldn't happen). + +=cut + +sub add_freq { + my( $self, $date ) = @_; + my $freq = $self->freq; + + #change this bit to use Date::Manip? CAREFUL with timezones (see + # mailing list archive) + my ($sec,$min,$hour,$mday,$mon,$year) = (localtime($date) )[0,1,2,3,4,5]; + + if ( $self->freq =~ /^\d+$/ ) { + $mon += $self->freq; + until ( $mon < 12 ) { $mon -= 12; $year++; } + } elsif ( $self->freq =~ /^(\d+)w$/ ) { + my $weeks = $1; + $mday += $weeks * 7; + } elsif ( $self->freq =~ /^(\d+)d$/ ) { + my $days = $1; + $mday += $days; + } elsif ( $self->freq =~ /^(\d+)h$/ ) { + my $hours = $1; + $hour += $hours; + } else { + return -1; + } + + timelocal_nocheck($sec,$min,$hour,$mday,$mon,$year); +} + =item plandata For backwards compatibility, returns the plandata field as well as all options @@ -858,7 +954,9 @@ sub has_taxproduct { my $self = shift; $self->taxproductnum || - scalar(grep { $_ =~/^usage_taxproductnum_/ } keys %{ {$self->options} } ) + scalar( grep { $_ =~/^usage_taxproductnum_/ && $self->option($_) } + keys %{ {$self->options} } + ) } @@ -929,6 +1027,7 @@ sub _expand_cch_taxproductnum { ? ( split ':', $part_pkg_taxproduct->taxproduct ) : () ); + $a = '' unless $a; $b = '' unless $b; $c = '' unless $c; $d = '' unless $d; my $extra_sql = "AND ( taxproduct = '$a:$b:$c:$d' OR taxproduct = '$a:$b:$c:' OR taxproduct = '$a:$b:".":$d' @@ -955,11 +1054,14 @@ sub part_pkg_taxrate { ). ')'; # much more CCH oddness in m2m -- this is kludgy - $extra_sql .= ' AND ('. - join(' OR ', map{ "taxproductnum = $_" } - $self->_expand_cch_taxproductnum($class) - ). - ')'; + my @tpnums = $self->_expand_cch_taxproductnum($class); + if (scalar(@tpnums)) { + $extra_sql .= ' AND ('. + join(' OR ', map{ "taxproductnum = $_" } @tpnums ). + ')'; + } else { + $extra_sql .= ' AND ( 0 = 1 )'; + } my $addl_from = 'LEFT JOIN part_pkg_taxproduct USING ( taxproductnum )'; my $order_by = 'ORDER BY taxclassnum, length(geocode) desc, length(taxproduct) desc'; @@ -1038,10 +1140,51 @@ sub calc_remain { 0; } sub calc_cancel { 0; } sub calc_units { 0; } +#fallback for everything except bulk.pm +sub hide_svc_detail { 0; } + +=item format OPTION DATA + +Returns data formatted according to the function 'format' described +in the plan info. Returns DATA if no such function exists. + +=cut + +sub format { + my ($self, $option, $data) = (shift, shift, shift); + if (exists($plans{$self->plan}->{fields}->{$option}{format})) { + &{$plans{$self->plan}->{fields}->{$option}{format}}($data); + }else{ + $data; + } +} + +=item parse OPTION DATA + +Returns data parsed according to the function 'parse' described +in the plan info. Returns DATA if no such function exists. + +=cut + +sub parse { + my ($self, $option, $data) = (shift, shift, shift); + if (exists($plans{$self->plan}->{fields}->{$option}{parse})) { + &{$plans{$self->plan}->{fields}->{$option}{parse}}($data); + }else{ + $data; + } +} + =back =cut +=head1 CLASS METHODS + +=over 4 + +=cut + # _upgrade_data # # Used by FS::Upgrade to migrate to a new database. @@ -1108,6 +1251,59 @@ sub _upgrade_data { # class method } +=item curuser_pkgs_sql + +Returns an SQL fragment for searching for packages the current user can +use, either via part_pkg.agentnum directly, or via agent type (see +L). + +=cut + +sub curuser_pkgs_sql { + my $class = shift; + + $class->_pkgs_sql( $FS::CurrentUser::CurrentUser->agentnums ); + +} + +=item agent_pkgs_sql AGENT | AGENTNUM, ... + +Returns an SQL fragment for searching for packages the provided agent or agents +can use, either via part_pkg.agentnum directly, or via agent type (see +L). + +=cut + +sub agent_pkgs_sql { + my $class = shift; #i'm a class method, not a sub (the question is... why??) + my @agentnums = map { ref($_) ? $_->agentnum : $_ } @_; + + $class->_pkgs_sql(@agentnums); #is this why + +} + +sub _pkgs_sql { + my( $class, @agentnums ) = @_; + my $agentnums = join(',', @agentnums); + + " + ( + agentnum IS NOT NULL + OR + 0 < ( SELECT COUNT(*) + FROM type_pkgs + LEFT JOIN agent_type USING ( typenum ) + LEFT JOIN agent AS typeagent USING ( typenum ) + WHERE type_pkgs.pkgpart = part_pkg.pkgpart + AND typeagent.agentnum IN ($agentnums) + ) + ) + "; + +} + +=back + =head1 SUBROUTINES =over 4 @@ -1155,38 +1351,6 @@ sub plan_info { \%plans; } -=item format OPTION DATA - -Returns data formatted according to the function 'format' described -in the plan info. Returns DATA if no such function exists. - -=cut - -sub format { - my ($self, $option, $data) = (shift, shift, shift); - if (exists($plans{$self->plan}->{fields}->{$option}{format})) { - &{$plans{$self->plan}->{fields}->{$option}{format}}($data); - }else{ - $data; - } -} - -=item parse OPTION DATA - -Returns data parsed according to the function 'parse' described -in the plan info. Returns DATA if no such function exists. - -=cut - -sub parse { - my ($self, $option, $data) = (shift, shift, shift); - if (exists($plans{$self->plan}->{fields}->{$option}{parse})) { - &{$plans{$self->plan}->{fields}->{$option}{parse}}($data); - }else{ - $data; - } -} - =back