1 package FS::TicketSystem;
4 use vars qw( $conf $system $AUTOLOAD );
6 use FS::UID qw( dbh driver_name );
7 use FS::Record qw( dbdef );
9 FS::UID->install_callback( sub {
11 $system = $conf->config('ticket_system');
20 my $conf = new FS::Conf;
21 die "FS::TicketSystem::$AUTOLOAD called, but no ticket system configured\n"
24 eval "use FS::TicketSystem::$system;";
34 WillResolve => { type => 'timestamp', null => 1, default => '', },
37 Required => { type => 'integer', default => 0, null => 0 },
42 my $system = FS::Conf->new->config('ticket_system');
43 return if !defined($system) || $system ne 'RT_Internal';
44 my ($class, %opts) = @_;
48 my $case = driver_name eq 'mysql' ? sub {@_} : sub {map lc, @_};
49 foreach my $tablename (keys %columns) {
50 my $table = dbdef->table(&$case($tablename));
53 "$tablename table does not exist. Your RT installation is incomplete.\n";
56 foreach my $colname (keys %{ $columns{$tablename} }) {
57 if ( !$table->column(&$case($colname)) ) {
58 my $col = new DBIx::DBSchema::Column {
60 name => &$case($colname),
61 %{ $columns{$tablename}->{$colname} }
63 $col->table_obj($table);
64 push @sql, $col->sql_add_column($dbh);
70 warn "Upgrading RT schema:\n";
71 foreach my $statement (@sql) {
73 $dbh->do( $statement )
74 or die "Error: ". $dbh->errstr. "\n executing: $statement";
80 return if !defined($system) || $system ne 'RT_Internal';
81 my ($class, %opts) = @_;
83 # go ahead and use the RT API for this
85 FS::TicketSystem->init;
86 my $session = FS::TicketSystem->session();
87 # bypass RT ACLs--we're going to do lots of things
88 my $CurrentUser = $RT::SystemUser;
92 # selfservice and cron users
93 foreach my $username ('%%%SELFSERVICE_USER%%%', 'fs_daily') {
94 my $User = RT::User->new($CurrentUser);
95 $User->Load($username);
96 if (!defined($User->Id)) {
97 my ($val, $msg) = $User->Create(
101 # any other fields needed?
105 my $Principal = $User->PrincipalObj; # can this ever fail?
106 my @rights = ( qw(ShowTicket SeeQueue ModifyTicket ReplyToTicket
107 CreateTicket SeeCustomField) );
109 next if $Principal->HasRight( 'Right' => $_, Object => $RT::System );
110 my ($val, $msg) = $Principal->GrantRight(
112 'Object' => $RT::System,
118 # EscalateQueue custom field and friends
119 my $CF = RT::CustomField->new($CurrentUser);
120 $CF->Load('EscalateQueue');
121 if (!defined($CF->Id)) {
122 my ($val, $msg) = $CF->Create(
123 'Name' => 'EscalateQueue',
126 'LookupType' => 'RT::Queue',
127 'Description' => 'Escalate to Queue',
128 'ValuesClass' => 'RT::CustomFieldValues::Queues', #magic!
131 my $OCF = RT::ObjectCustomField->new($CurrentUser);
132 ($val, $msg) = $OCF->Create(
133 'CustomField' => $CF->Id,
139 # Load from RT data file
140 our (@Groups, @Users, @ACL, @Queues, @ScripActions, @ScripConditions,
141 @Templates, @CustomFields, @Scrips, @Attributes, @Initial, @Final,
143 my $datafile = '%%%RT_PATH%%%/etc/initialdata';
144 eval { require $datafile };
146 warn "Couldn't load RT data from '$datafile': $@\n(skipping)\n";
150 # Cache existing ScripCondition, ScripAction, and Template IDs.
151 # Complicated because we don't want to just step on multiple IDs
152 # with the same name.
154 my ($class, $hash) = @_;
155 my $search = $class->new($CurrentUser);
157 while ( my $item = $search->Next ) {
158 my $ids = $hash->{lc($item->Name)} ||= [];
159 if ( $item->Creator == 1 ) { # RT::SystemUser
160 unshift @$ids, $item->Id;
163 push @$ids, $item->Id;
168 my (%condition, %action, %template);
169 &$cachify('RT::ScripConditions', \%condition);
170 &$cachify('RT::ScripActions', \%action);
171 &$cachify('RT::Templates', \%template);
172 # $condition{name} = [ ids... ]
173 # with the id of the system-created object first, if there is one
176 my $ScripCondition = RT::ScripCondition->new($CurrentUser);
177 foreach my $sc (@ScripConditions) {
178 # $sc: Name, Description, ApplicableTransTypes, ExecModule, Argument
179 next if exists( $condition{ lc($sc->{Name}) } );
180 my ($val, $msg) = $ScripCondition->Create( %$sc );
182 $condition{ lc($ScripCondition->Name) } = [ $ScripCondition->Id ];
186 my $ScripAction = RT::ScripAction->new($CurrentUser);
187 foreach my $sa (@ScripActions) {
188 # $sa: Name, Description, ExecModule, Argument
189 next if exists( $action{ lc($sa->{Name}) } );
190 my ($val, $msg) = $ScripAction->Create( %$sa );
192 $action{ lc($ScripAction->Name) } = [ $ScripAction->Id ];
196 my $Template = RT::Template->new($CurrentUser);
197 foreach my $t (@Templates) {
198 # $t: Queue, Name, Description, Content
199 next if exists( $template{ lc($t->{Name}) } );
200 my ($val, $msg) = $Template->Create( %$t );
202 $template{ lc($Template->Name) } = [ $Template->Id ];
206 my %scrip; # $scrips{condition}{action}{template} = id
207 my $search = RT::Scrips->new($CurrentUser);
208 $search->Limit(FIELD => 'Queue', VALUE => 0);
209 while (my $item = $search->Next) {
210 my ($c, $a, $t) = map {lc $item->$_->Name}
211 ('ScripConditionObj', 'ScripActionObj', 'TemplateObj');
212 if ( exists $scrip{$c}{$a}{$t} and $item->Creator == 1 ) {
213 warn "Deleting duplicate scrip $c $a [$t]\n";
214 my ($val, $msg) = $item->Delete;
215 warn "error deleting scrip: $msg\n" if !$val;
217 elsif ( exists $Delete_Scrips{$c}{$a}{$t} and $item->Creator == 1 ) {
218 warn "Deleting obsolete scrip $c $a [$t]\n";
219 my ($val, $msg) = $item->Delete;
220 warn "error deleting scrip: $msg\n" if !$val;
223 $scrip{$c}{$a}{$t} = $item->id;
226 my $Scrip = RT::Scrip->new($CurrentUser);
227 foreach my $s ( @Scrips ) {
228 my $desc = $s->{'Description'};
229 my ($c, $a, $t) = map lc,
230 @{ $s }{'ScripCondition', 'ScripAction', 'Template'};
231 # skip existing scrips
232 next if ( exists($scrip{$c}{$a}{$t}) );
233 if ( !exists($condition{$c}) ) {
234 warn "ScripCondition '$c' not found.\n";
237 if ( !exists($action{$a}) ) {
238 warn "ScripAction '$a' not found.\n";
241 if ( !exists($template{$t}) ) {
242 warn "Template '$t' not found.\n";
246 ScripCondition => $condition{$c}->[0],
247 ScripAction => $action{$a}->[0],
248 Template => $template{$t}->[0],
250 Description => $desc,
252 warn "Creating scrip: $c $a [$t]\n";
253 my ($val, $msg) = $Scrip->Create(%new_param);
257 # one-time fix: accumulator fields (support time, etc.) that had values
258 # entered on ticket creation need OCFV records attached to their Create
260 my $sql = 'SELECT first_ocfv.ObjectId, first_ocfv.Created, Content '.
261 'FROM ObjectCustomFieldValues as first_ocfv '.
263 # subquery to get the first OCFV with a certain name for each ticket
264 'SELECT min(ObjectCustomFieldValues.Id) AS Id '.
265 'FROM ObjectCustomFieldValues '.
266 'JOIN CustomFields '.
267 'ON (ObjectCustomFieldValues.CustomField = CustomFields.Id) '.
268 'WHERE ObjectType = \'RT::Ticket\' '.
269 'AND CustomFields.Name = ? '.
271 ') AS first_ocfv_id USING (Id) '.
273 # subquery to get the first transaction date for each ticket
274 # other than the Create
275 'SELECT ObjectId, min(Created) AS Created FROM Transactions '.
276 'WHERE ObjectType = \'RT::Ticket\' '.
277 'AND Type != \'Create\' '.
279 ') AS first_txn ON (first_ocfv.ObjectId = first_txn.ObjectId) '.
280 # where the ticket custom field acquired a value before any transactions
281 # on the ticket (i.e. it was set on ticket creation)
282 'WHERE first_ocfv.Created < first_txn.Created '.
283 # and we haven't already fixed the ticket
285 'SELECT 1 FROM Transactions JOIN ObjectCustomFieldValues '.
286 'ON (Transactions.Id = ObjectCustomFieldValues.ObjectId) '.
287 'JOIN CustomFields '.
288 'ON (ObjectCustomFieldValues.CustomField = CustomFields.Id) '.
289 'WHERE ObjectCustomFieldValues.ObjectType = \'RT::Transaction\' '.
290 'AND CustomFields.Name = ? '.
291 'AND Transactions.Type = \'Create\''.
292 'AND Transactions.ObjectType = \'RT::Ticket\''.
293 'AND Transactions.ObjectId = first_ocfv.ObjectId'.
297 # prior to this fix, the only name an accumulate field could possibly have
298 # was "Support time".
299 my $sth = $dbh->prepare($sql);
300 $sth->execute('Support time', 'Support time');
301 my $rows = $sth->rows;
302 warn "Fixing support time on $rows rows...\n" if $rows > 0;
303 while ( my $row = $sth->fetchrow_arrayref ) {
304 my ($tid, $created, $content) = @$row;
305 my $Txns = RT::Transactions->new($CurrentUser);
306 $Txns->Limit(FIELD => 'ObjectId', VALUE => $tid);
307 $Txns->Limit(FIELD => 'ObjectType', VALUE => 'RT::Ticket');
308 $Txns->Limit(FIELD => 'Type', VALUE => 'Create');
309 my $CreateTxn = $Txns->First;
311 my ($val, $msg) = $CreateTxn->AddCustomFieldValue(
312 Field => 'Support time',
314 RecordTransaction => 0,
316 warn "Error setting transaction support time: $msg\n" unless $val;
318 warn "Create transaction not found for ticket $tid.\n";