27272b8a37e2f399c6ddaaf96f2ec1a6efbebf46
[freeside.git] / FS / FS / cust_msg.pm
1 package FS::cust_msg;
2
3 use strict;
4 use base qw( FS::cust_main_Mixin FS::Record );
5 use FS::Record qw( qsearch qsearchs );
6 use MIME::Parser;
7 use vars qw( @statuses );
8
9 =head1 NAME
10
11 FS::cust_msg - Object methods for cust_msg records
12
13 =head1 SYNOPSIS
14
15   use FS::cust_msg;
16
17   $record = new FS::cust_msg \%hash;
18   $record = new FS::cust_msg { 'column' => 'value' };
19
20   $error = $record->insert;
21
22   $error = $record->check;
23
24 =head1 DESCRIPTION
25
26 An FS::cust_msg object represents an email message generated by Freeside 
27 and sent to a customer (see L<FS::msg_template>).  FS::cust_msg inherits 
28 from FS::Record.  The following fields are currently supported:
29
30 =over 4
31
32 =item custmsgnum - primary key
33
34 =item custnum - customer number
35
36 =item msgnum - template number
37
38 =item msgtype - the message type
39
40 =item _date - the time the message was sent
41
42 =item env_from - envelope From address
43
44 =item env_to - envelope To addresses, including Bcc, separated by newlines
45
46 =item header - message header
47
48 =item body - message body (as a complete MIME document)
49
50 =item preview - HTML fragment to show as a preview of the message
51
52 =item error - Email::Sender error message (or null for success)
53
54 =item status - "prepared", "sent", or "failed"
55
56 =back
57
58 =head1 METHODS
59
60 =over 4
61
62 =item new HASHREF
63
64 Creates a new 
65
66 =cut
67
68 # the new method can be inherited from FS::Record, if a table method is defined
69
70 sub table { 'cust_msg'; }
71
72 sub nohistory_fields { ('header', 'body'); } 
73 # history is kind of pointless on this table
74
75 @statuses = qw( prepared sent failed );
76
77 =item insert
78
79 Adds this record to the database.  If there is an error, returns the error 
80 and emits a warning; otherwise returns false.
81
82 =cut
83
84 sub insert {
85   # warn of all errors here; failing to insert/update one of these should 
86   # cause a warning at worst
87   my $self = shift;
88   my $error = $self->SUPER::insert;
89   warn "[cust_msg] error logging message status: $error\n" if $error;
90   return $error;
91 }
92
93 =item delete
94
95 Delete this record from the database.  There's no reason to do this.
96
97 =cut
98
99 sub delete {
100   my $self = shift;
101   warn "[cust_msg] log entry deleted\n";
102   return $self->SUPER::delete;
103 }
104
105 =item replace OLD_RECORD
106
107 Replaces the OLD_RECORD with this one in the database.  If there is an error,
108 returns the error and emits a warning, otherwise returns false.
109
110 =cut
111
112 sub replace {
113   my $self = shift;
114   my $error = $self->SUPER::replace(@_);
115   warn "[cust_msg] error logging message status: $error\n" if $error;
116   return $error;
117 }
118
119 =item check
120
121 Checks all fields to make sure this is a valid example.  If there is
122 an error, returns the error, otherwise returns false.  Called by the insert
123 and replace methods.
124
125 =cut
126
127 # the check method should currently be supplied - FS::Record contains some
128 # data checking routines
129
130 sub check {
131   my $self = shift;
132
133   my $error = 
134     $self->ut_numbern('custmsgnum')
135     || $self->ut_numbern('custnum')
136     || $self->ut_foreign_keyn('custnum', 'cust_main', 'custnum')
137     || $self->ut_numbern('msgnum')
138     || $self->ut_foreign_keyn('msgnum', 'msg_template', 'msgnum')
139     || $self->ut_numbern('_date')
140     || $self->ut_textn('env_from')
141     || $self->ut_textn('env_to')
142     || $self->ut_anything('header')
143     || $self->ut_anything('body')
144     || $self->ut_anything('preview')
145     || $self->ut_enum('status', \@statuses)
146     || $self->ut_textn('error')
147     || $self->ut_enum('msgtype', [  '',
148                                     'invoice',
149                                     'receipt',
150                                     'admin',
151                                     'report',
152                                  ])
153   ;
154   return $error if $error;
155
156   $self->SUPER::check;
157 }
158
159 =item send
160
161 Sends the message through its parent L<FS::msg_template>. Returns an error
162 message on error, or an empty string.
163
164 =cut
165
166 sub send {
167   my $self = shift;
168   # it's still allowed to have cust_msgs without message templates, but only 
169   # for email.
170   my $msg_template = $self->msg_template || 'FS::msg_template::email';
171   $msg_template->send_prepared($self);
172 }
173
174 =item entity
175
176 Returns the complete message as a L<MIME::Entity>.
177
178 XXX this only works if the message in fact contains a MIME entity. Messages
179 created by external APIs may not look like that.
180
181 =item parts
182
183 Returns a list of the MIME parts contained in the message, as L<MIME::Entity>
184 objects.
185
186 =cut
187
188 sub entity {
189   my $self = shift;
190   if ( !exists($self->{entity}) ) {
191     my $parser = MIME::Parser->new;
192     my $output_dir = "$FS::UID::cache_dir/cache.$FS::UID::datasrc/mimeparts";
193     mkdir($output_dir) unless -d $output_dir;
194     $parser->output_under($output_dir);
195     $self->{entity} =
196       $parser->parse_data( $self->header . "\n" . $self->body );
197   }
198   $self->{entity};
199 }
200
201 sub parts {
202   my $self = shift;
203   # return only the parts with bodies, not the multipart containers
204   grep { $_->bodyhandle } $self->entity->parts_DFS;
205 }
206
207 =back
208
209 =head1 SUBROUTINES
210
211 =over 4
212
213 =item process_send CUSTMSGNUM
214
215 Given a C<cust_msg.custmsgnum> value, sends the message. It must already
216 have been prepared (via L<FS::msg_template/prepare>).
217
218 =cut
219
220 sub process_send {
221   my $custmsgnum = shift;
222   my $cust_msg = FS::cust_msg->by_key($custmsgnum)
223     or die "cust_msg #$custmsgnum not found";
224   my $error = $cust_msg->send;
225   die $error if $error;
226 }
227
228 =head1 SEE ALSO
229
230 L<FS::msg_template>, L<FS::cust_main>, L<FS::Record>.
231
232 =cut
233
234 1;
235