per-agent disable_previous_balance, #15863
[freeside.git] / FS / FS / part_virtual_field.pm
1 package FS::part_virtual_field;
2
3 use strict;
4 use vars qw( @ISA );
5 use FS::Record;
6 use FS::Schema qw( dbdef );
7 use CGI qw(escapeHTML);
8
9 @ISA = qw( FS::Record );
10
11 =head1 NAME
12
13 FS::part_virtual_field - Object methods for part_virtual_field records
14
15 =head1 SYNOPSIS
16
17   use FS::part_virtual_field;
18
19   $record = new FS::part_virtual_field \%hash;
20   $record = new FS::part_virtual_field { 'column' => 'value' };
21
22   $error = $record->insert;
23
24   $error = $new_record->replace($old_record);
25
26   $error = $record->delete;
27
28   $error = $record->check;
29
30 =head1 DESCRIPTION
31
32 An FS::part_virtual_field object represents the definition of a virtual field 
33 (see the BACKGROUND section).  FS::part_virtual_field contains the name and 
34 base table of the field, as well as validation rules and UI hints about the 
35 display of the field.  The actual data is stored in FS::virtual_field; see 
36 its manpage for details.
37
38 FS::part_virtual_field inherits from FS::Record.  The following fields are 
39 currently supported:
40
41 =over 2
42
43 =item vfieldpart - primary key (assigned automatically)
44
45 =item name - name of the field
46
47 =item dbtable - table for which this virtual field is defined
48
49 =item check_block - Perl code to validate/normalize data
50
51 =item list_source - Perl code to generate a list of values (UI hint)
52
53 =item length - expected length of the value (UI hint)
54
55 =item label - descriptive label for the field (UI hint)
56
57 =item sequence - sort key (UI hint; unimplemented)
58
59 =back
60
61 =head1 BACKGROUND
62
63 "Form is none other than emptiness,
64  and emptiness is none other than form."
65 -- Heart Sutra
66
67 The virtual field mechanism allows site admins to make trivial changes to 
68 the Freeside database schema without modifying the code.  Specifically, the 
69 user can add custom-defined 'fields' to the set of data tracked by Freeside 
70 about objects such as customers and services.  These fields are not associated 
71 with any logic in the core Freeside system, but may be referenced in peripheral 
72 code such as exports, price calculations, or alternate interfaces, or may just 
73 be stored in the database for future reference.
74
75 This system was originally devised for svc_broadband, which (by necessity) 
76 comprises such a wide range of access technologies that no static set of fields 
77 could contain all the information needed by the exports.  In an appalling 
78 display of False Laziness, a parallel mechanism was implemented for the 
79 router table, to store properties such as passwords to configure routers.
80
81 The original system treated svc_broadband custom fields (sb_fields) as records 
82 in a completely separate table.  Any code that accessed or manipulated these 
83 fields had to be aware that they were I<not> fields in svc_broadband, but 
84 records in sb_field.  For example, code that inserted a svc_broadband with 
85 several custom fields had to create an FS::svc_broadband object, call its 
86 insert() method, and then create several FS::sb_field objects and call I<their>
87 insert() methods.
88
89 This created a problem for exports.  The insert method on any FS::svc_Common 
90 object (including svc_broadband) automatically triggers exports after the 
91 record has been inserted.  However, at this point, the sb_fields had not yet 
92 been inserted, so the export could not rely on their presence, which was the 
93 original purpose of sb_fields.
94
95 Hence the new system.  Virtual fields are appended to the field list of every 
96 record at the FS::Record level, whether the object is created ex nihilo with 
97 new() or fetched with qsearch().  The fields() method now returns a list of 
98 both real and virtual fields.  The insert(), replace(), and delete() methods 
99 now update both the base table and the virtual fields, in a single transaction.
100
101 A new method is provided, virtual_fields(), which gives only the virtual 
102 fields.  UI code that dynamically generates form widgets to edit virtual field
103 data should use this to figure out what fields are defined.  (See below.)
104
105 Subclasses may override virtual_fields() to restrict the set of virtual 
106 fields available.  Some discipline and sanity on the part of the programmer 
107 are required; in particular, this function should probably not depend on any 
108 fields in the record other than the primary key, since the others may change 
109 after the object is instantiated.  (Making it depend on I<virtual> fields is 
110 just asking for pain.)  One use of this is seen in FS::svc_Common; another 
111 possibility is field-level access control based on FS::UID::getotaker().
112
113 As a trivial case, a subclass may opt out of supporting virtual fields with 
114 the following code:
115
116 sub virtual_fields { () }
117
118 =head1 METHODS
119
120 =over 4
121
122 =item new HASHREF
123
124 Create a new record.  To add the record to the database, see "insert".
125
126 =cut
127
128 sub table { 'part_virtual_field'; }
129 sub virtual_fields { () }
130
131 =item insert
132
133 Adds this record to the database.  If there is an error, returns the error,
134 otherwise returns false.
135
136 =item delete
137
138 Deletes this record from the database.  If there is an error, returns the
139 error, otherwise returns false.
140
141 =item replace OLD_RECORD
142
143 Replaces OLD_RECORD with this one in the database.  If there is an error,
144 returns the error, otherwise returns false.
145
146 =item check
147
148 If there is an error, returns the error, otherwise returns false.
149 Called by the insert and replace methods.
150
151 =back
152
153 =cut
154
155 sub check {
156   my $self = shift;
157
158   my $error = $self->ut_text('name') ||
159               $self->ut_text('dbtable') ||
160               $self->ut_number('length')
161               ;
162   return $error if $error;
163
164   # Make sure it's a real table with a numeric primary key
165   my ($table, $pkey);
166   if($table = dbdef->table($self->dbtable)) {
167     if($pkey = $table->primary_key) {
168       if($table->column($pkey)->type =~ /int/i) {
169         # this is what it should be
170       } else {
171         $error = "$table.$pkey is not an integer";
172       }
173     } else {
174       $error = "$table does not have a single-field primary key";
175     }
176   } else {
177     $error = "$table does not exist in the schema";
178   }
179   return $error if $error;
180
181   # Possibly some sanity checks for check_block and list_source?
182
183   $self->SUPER::check;  
184 }
185
186 =item list
187
188 Evaluates list_source.
189
190 =cut
191
192 sub list {
193   my $self = shift;
194   return () unless $self->list_source;
195
196   my @opts = eval($self->list_source);
197   if($@) {
198     warn $@;
199     return ();
200   } else {
201     return @opts;
202   }
203 }
204
205 =item widget UI_TYPE MODE [ VALUE ]
206
207 Generates UI code for a widget suitable for editing/viewing the field, based on 
208 list_source and length.  
209
210 The only UI_TYPE currently supported is 'HTML', and the only MODE is 'view'.
211 Others will be added later.
212
213 In HTML, all widgets are assumed to be table rows.  View widgets look like
214 <TR><TD ALIGN="right">Label</TD><TD BGCOLOR="#ffffff">Value</TD></TR>
215
216 (Most of the display style stuff, such as the colors, should probably go into 
217 a separate module specific to the UI.  That can wait, though.  The API for 
218 this function won't change.)
219
220 VALUE (optional) is the current value of the field.
221
222 =cut
223
224 sub widget {
225   my $self = shift;
226   my ($ui_type, $mode, $value) = @_;
227   my $text;
228   my $label = $self->label || $self->name;
229
230   if ($ui_type eq 'HTML') {
231     if ($mode eq 'view') {
232       $text = q!<TR><TD ALIGN="right">! . $label . 
233               q!</TD><TD BGCOLOR="#ffffff">! . $value .
234               q!</TD></TR>! . "\n";
235     } elsif ($mode eq 'edit') {
236       $text = q!<TR><TD ALIGN="right">! . $label .
237               q!</TD><TD>!;
238       if ($self->list_source) {
239         $text .= q!<SELECT NAME="! . $self->name . 
240                 q!" SIZE=1>! . "\n";
241         foreach ($self->list) {
242           $text .= q!<OPTION VALUE="! . $_ . q!"!;
243           $text .= ' SELECTED' if ($_ eq $value);
244           $text .= '>' . $_ . '</OPTION>' . "\n";
245         }
246       } else {
247         $text .= q!<INPUT NAME="! . $self->name .
248                 q!" VALUE="! . escapeHTML($value) . q!"!;
249         if ($self->length) {
250           $text .= q! SIZE="! . $self->length . q!"!;
251         }
252         $text .= '>';
253       }
254       $text .= q!</TD></TR>! . "\n";
255     } else {
256       return '';
257     }
258   } else {
259     return '';
260   }
261   return $text;
262 }
263
264 =head1 NOTES
265
266 =head2 Semantics of check_block:
267
268 This has been changed from the sb_field implementation to make check_blocks 
269 simpler and more natural to Perl programmers who work on things other than 
270 Freeside.
271
272 The check_block is eval'd with the (proposed) new value of the field in $_, 
273 and the object to be updated in $self.  Its return value is ignored.  The 
274 check_block may change the value of $_ to override the proposed value, or 
275 call die() (with an appropriate error message) to reject the update entirely;
276 the error string will be returned as the output of the check() method.
277
278 This makes check_blocks like
279
280 C<s/foo/bar/>
281
282 do what you expect.
283
284 The check_block is expected NOT to do anything freaky to $self, like modifying 
285 other fields or calling $self->check().  You have been warned.
286
287 (FIXME: Rewrite some of the warnings from part_sb_field and insert here.)
288
289 =head1 BUGS
290
291 None.  It's absolutely falwless.
292
293 =head1 SEE ALSO
294
295 L<FS::Record>, L<FS::virtual_field>
296
297 =cut
298
299 1;
300
301