5 use FS::Record qw( qsearch dbh );
11 @ISA = qw( FS::Record );
15 FS::part_pkg - Object methods for part_pkg objects
21 $record = new FS::part_pkg \%hash
22 $record = new FS::part_pkg { 'column' => 'value' };
24 $custom_record = $template_record->clone;
26 $error = $record->insert;
28 $error = $new_record->replace($old_record);
30 $error = $record->delete;
32 $error = $record->check;
34 @pkg_svc = $record->pkg_svc;
36 $svcnum = $record->svcpart;
37 $svcnum = $record->svcpart( 'svc_acct' );
41 An FS::part_pkg object represents a billing item definition. FS::part_pkg
42 inherits from FS::Record. The following fields are currently supported:
46 =item pkgpart - primary key (assigned automatically for new billing item definitions)
48 =item pkg - Text name of this billing item definition (customer-viewable)
50 =item comment - Text name of this billing item definition (non-customer-viewable)
52 =item setup - Setup fee expression
54 =item freq - Frequency of recurring fee
56 =item recur - Recurring fee expression
58 =item setuptax - Setup fee tax exempt flag, empty or `Y'
60 =item recurtax - Recurring fee tax exempt flag, empty or `Y'
62 =item taxclass - Tax class flag
64 =item plan - Price plan
66 =item plandata - Price plan data
68 =item disabled - Disabled flag, empty or `Y'
72 setup and recur are evaluated as Safe perl expressions. You can use numbers
73 just as you would normally. More advanced semantics are not yet defined.
81 Creates a new billing item definition. To add the billing item definition to
82 the database, see L<"insert">.
86 sub table { 'part_pkg'; }
90 An alternate constructor. Creates a new billing item definition by duplicating
91 an existing definition. A new pkgpart is assigned and `(CUSTOM) ' is prepended
92 to the comment field. To add the billing item definition to the database, see
99 my $class = ref($self);
100 my %hash = $self->hash;
101 $hash{'pkgpart'} = '';
102 $hash{'comment'} = "(CUSTOM) ". $hash{'comment'}
103 unless $hash{'comment'} =~ /^\(CUSTOM\) /;
104 #new FS::part_pkg ( \%hash ); # ?
105 new $class ( \%hash ); # ?
110 Adds this billing item definition to the database. If there is an error,
111 returns the error, otherwise returns false.
118 local $SIG{HUP} = 'IGNORE';
119 local $SIG{INT} = 'IGNORE';
120 local $SIG{QUIT} = 'IGNORE';
121 local $SIG{TERM} = 'IGNORE';
122 local $SIG{TSTP} = 'IGNORE';
123 local $SIG{PIPE} = 'IGNORE';
125 my $oldAutoCommit = $FS::UID::AutoCommit;
126 local $FS::UID::AutoCommit = 0;
129 my $error = $self->SUPER::insert;
131 $dbh->rollback if $oldAutoCommit;
135 my $conf = new FS::Conf;
137 if ( $conf->exists('agent_defaultpkg') ) {
138 foreach my $agent_type ( qsearch('agent_type', {} ) ) {
139 my $type_pkgs = new FS::type_pkgs({
140 'typenum' => $agent_type->typenum,
141 'pkgpart' => $self->pkgpart,
143 my $error = $type_pkgs->insert;
145 $dbh->rollback if $oldAutoCommit;
151 $dbh->commit or die $dbh->errstr if $oldAutoCommit;
158 Currently unimplemented.
163 return "Can't (yet?) delete package definitions.";
164 # check & make sure the pkgpart isn't in cust_pkg or type_pkgs?
167 =item replace OLD_RECORD
169 Replaces OLD_RECORD with this one in the database. If there is an error,
170 returns the error, otherwise returns false.
174 Checks all fields to make sure this is a valid billing item definition. If
175 there is an error, returns the error, otherwise returns false. Called by the
176 insert and replace methods.
183 for (qw(setup recur)) { $self->set($_=>0) if $self->get($_) =~ /^\s*$/; }
185 my $conf = new FS::Conf;
186 if ( $conf->exists('safe-part_pkg') ) {
188 my $error = $self->ut_anything('setup')
189 || $self->ut_anything('recur');
190 return $error if $error;
192 my $s = $self->setup;
194 $s =~ /^\s*\d*\.?\d*\s*$/
196 or $s =~ /^my \$d = \$cust_pkg->bill || \$time; \$d += 86400 \* \s*\d+\s*; \$cust_pkg->bill\(\$d\); \$cust_pkg_mod_flag=1; \s*\d*\.?\d*\s*$/
200 return "illegal setup: $s";
203 my $r = $self->recur;
205 $r =~ /^\s*\d*\.?\d*\s*$/
207 #or $r =~ /^\$sdate += 86400 \* \s*\d+\s*; \s*\d*\.?\d*\s*$/
209 or $r =~ /^my \$mnow = \$sdate; my \(\$sec,\$min,\$hour,\$mday,\$mon,\$year\) = \(localtime\(\$sdate\) \)\[0,1,2,3,4,5\]; my \$mstart = timelocal\(0,0,0,1,\$mon,\$year\); my \$mend = timelocal\(0,0,0,1, \$mon == 11 \? 0 : \$mon\+1, \$year\+\(\$mon==11\)\); \$sdate = \$mstart; \( \$part_pkg->freq \- 1 \) \* \d*\.?\d* \/ \$part_pkg\-\>freq \+ \d*\.?\d* \/ \$part_pkg\-\>freq \* \(\$mend\-\$mnow\) \/ \(\$mend\-\$mstart\) ;\s*$/
211 or $r =~ /^my \$mnow = \$sdate; my \(\$sec,\$min,\$hour,\$mday,\$mon,\$year\) = \(localtime\(\$sdate\) \)\[0,1,2,3,4,5\]; \$sdate = timelocal\(0,0,0,1,\$mon,\$year\); \s*\d*\.?\d*\s*;\s*$/
213 or $r =~ /^my \$error = \$cust_pkg\->cust_main\->credit\( \s*\d*\.?\d*\s* \* scalar\(\$cust_pkg\->cust_main\->referral_cust_main_ncancelled\(\s*\d+\s*\)\), "commission" \); die \$error if \$error; \s*\d*\.?\d*\s*;\s*$/
215 or $r =~ /^my \$error = \$cust_pkg\->cust_main\->credit\( \s*\d*\.?\d*\s* \* scalar\(\$cust_pkg\->cust_main->referral_cust_pkg\(\s*\d+\s*\)\), "commission" \); die \$error if \$error; \s*\d*\.?\d*\s*;\s*$/
217 or $r =~ /^my \$error = \$cust_pkg\->cust_main\->credit\( \s*\d*\.?\d*\s* \* scalar\( grep \{ my \$pkgpart = \$_\->pkgpart; grep \{ \$_ == \$pkgpart \} \(\s*(\s*\d+,\s*)*\s*\) \} \$cust_pkg\->cust_main->referral_cust_pkg\(\s*\d+\s*\)\), "commission" \); die \$error if \$error; \s*\d*\.?\d*\s*;\s*$/
219 or $r =~ /^my \$hours = \$cust_pkg\->seconds_since\(\$cust_pkg\->bill \|\| 0\) \/ 3600 \- \s*\d*\.?\d*\s*; \$hours = 0 if \$hours < 0; \s*\d*\.?\d*\s* \+ \s*\d*\.?\d*\s* \* \$hours;\s*$/
221 or $r =~ /^my \$min = \$cust_pkg\->seconds_since\(\$cust_pkg\->bill \|\| 0\) \/ 60 \- \s*\d*\.?\d*\s*; \$min = 0 if \$min < 0; \s*\d*\.?\d*\s* \+ \s*\d*\.?\d*\s* \* \$min;\s*$/
223 or $r =~ /^my \$last_bill = \$cust_pkg\->last_bill; my \$hours = \$cust_pkg\->seconds_since_sqlradacct\(\$last_bill, \$sdate \) \/ 3600 - \s*\d\.?\d*\s*; \$hours = 0 if \$hours < 0; my \$input = \$cust_pkg\->attribute_since_sqlradacct\(\$last_bill, \$sdate, "AcctInputOctets" \) \/ 1048576; my \$output = \$cust_pkg\->attribute_since_sqlradacct\(\$last_bill, \$sdate, "AcctOutputOctets" \) \/ 1048576; my \$total = \$input \+ \$output \- \s*\d\.?\d*\s*; \$total = 0 if \$total < 0; my \$input = \$input - \s*\d\.?\d*\s*; \$input = 0 if \$input < 0; my \$output = \$output - \s*\d\.?\d*\s*; \$output = 0 if \$output < 0; \s*\d\.?\d*\s* \+ \s*\d\.?\d*\s* \* \$hours \+ \s*\d\.?\d*\s* \* \$input \+ \s*\d\.?\d*\s* \* \$output \+ \s*\d\.?\d*\s* \* \$total *;\s*$/
227 return "illegal recur: $r";
232 if ( $self->dbdef_table->column('freq')->type =~ /(int)/i ) {
233 my $error = $self->ut_number('freq');
234 return $error if $error;
236 $self->freq =~ /^(\d+[dw]?)$/
237 or return "Illegal or empty freq: ". $self->freq;
241 $self->ut_numbern('pkgpart')
242 || $self->ut_text('pkg')
243 || $self->ut_text('comment')
244 || $self->ut_anything('setup')
245 || $self->ut_anything('recur')
246 || $self->ut_alphan('plan')
247 || $self->ut_anything('plandata')
248 || $self->ut_enum('setuptax', [ '', 'Y' ] )
249 || $self->ut_enum('recurtax', [ '', 'Y' ] )
250 || $self->ut_textn('taxclass')
251 || $self->ut_enum('disabled', [ '', 'Y' ] )
257 Returns all FS::pkg_svc objects (see L<FS::pkg_svc>) for this package
258 definition (with non-zero quantity).
264 grep { $_->quantity } qsearch( 'pkg_svc', { 'pkgpart' => $self->pkgpart } );
267 =item svcpart [ SVCDB ]
269 Returns the svcpart of a single service definition (see L<FS::part_svc>)
270 associated with this billing item definition (see L<FS::pkg_svc>). Returns
271 false if there not exactly one service definition with quantity 1, or if
272 SVCDB is specified and does not match the svcdb of the service definition,
278 my $svcdb = scalar(@_) ? shift : '';
281 && ( $svcdb eq $_->part_svc->svcdb || !$svcdb )
283 return '' if scalar(@pkg_svc) != 1;
284 $pkg_svc[0]->svcpart;
289 Returns a list of the acceptable payment types for this package. Eventually
290 this should come out of a database table and be editable, but currently has the
291 following logic instead;
293 If the package has B<0> setup and B<0> recur, the single item B<BILL> is
294 returned, otherwise, the single item B<CARD> is returned.
296 (CHEK? LEC? Probably shouldn't accept those by default, prone to abuse)
302 #if ( $self->setup == 0 && $self->recur == 0 ) {
303 if ( $self->setup =~ /^\s*0+(\.0*)?\s*$/
304 && $self->recur =~ /^\s*0+(\.0*)?\s*$/ ) {
315 The delete method is unimplemented.
317 setup and recur semantics are not yet defined (and are implemented in
318 FS::cust_bill. hmm.).
322 L<FS::Record>, L<FS::cust_pkg>, L<FS::type_pkgs>, L<FS::pkg_svc>, L<Safe>.
323 schema.html from the base documentation.