explicitly pass quotationnum as an API param and other fixes, #33852
[freeside.git] / FS / FS / ClientAPI / MyAccount / quotation.pm
1 package FS::ClientAPI::MyAccount::quotation;
2
3 use strict;
4 use FS::Record qw(qsearch qsearchs);
5 use FS::quotation;
6 use FS::quotation_pkg;
7
8 our $DEBUG = 1;
9
10 sub _custoragent_session_custnum {
11   FS::ClientAPI::MyAccount::_custoragent_session_custnum(@_);
12 }
13
14 # _quotation(session, quotationnum)
15 # returns that quotation, or '' if it doesn't exist and belong to this
16 # customer
17
18 sub _quotation {
19   my $session = shift;
20   my $quotationnum = shift;
21   my $quotation;
22
23   if ( $quotationnum =~ /^(\d+)$/ ) {
24     $quotation = qsearchs( 'quotation', {
25         'custnum'       => $session->{'custnum'},
26         'usernum'       => $FS::CurrentUser::CurrentUser->usernum,
27         'disabled'      => '',
28         'quotationnum'  => $1,
29     }); 
30     warn "found selfservice quotation #". $quotation->quotationnum."\n"
31       if $quotation and $DEBUG;
32
33     return $quotation;
34   }
35   '';
36 }
37
38 =item list_quotations { session }
39
40 Returns a hashref listing this customer's active self-service quotations.
41 Contents are:
42
43 - 'quotations', an arrayref containing an element for each quotation.
44   - quotationnum, the primary key
45   - _date, the date it was started
46   - num_pkgs, the number of packages
47   - total_setup, the sum of setup fees
48   - total_recur, the sum of recurring charges
49
50 =cut
51
52 sub list_quotations {
53   my $p = shift;
54
55   my($context, $session, $custnum) = _custoragent_session_custnum($p);
56   return { 'error' => $session } if $context eq 'error';
57
58   my @quotations = qsearch('quotation', {
59       'custnum'   => $session->{'custnum'},
60       'usernum'   => $FS::CurrentUser::CurrentUser->usernum,
61       'disabled'  => '',
62   });
63   my @q;
64   foreach my $quotation (@quotations) {
65     warn "found selfservice quotation #". $quotation->quotationnum."\n"
66       if $quotation and $DEBUG;
67     push @q, { 'quotationnum' => $quotation->quotationnum,
68                '_date'        => $quotation->_date,
69                'num_pkgs'     => scalar($quotation->quotation_pkg),
70                'total_setup'  => $quotation->total_setup,
71                'total_recur'  => $quotation->total_recur,
72              };
73   }
74   return { 'quotations' => \@q, 'error' => '' };
75 }
76
77 =item quotation_new { session }
78
79 Creates a quotation and returns its quotationnum.
80
81 =cut
82
83 sub quotation_new {
84   my $p = shift;
85
86   my($context, $session, $custnum) = _custoragent_session_custnum($p);
87   return { 'error' => $session } if $context eq 'error';
88
89   my $quotation = FS::quotation->new({
90       'custnum'   => $session->{'custnum'},
91       'usernum'   => $FS::CurrentUser::CurrentUser->usernum,
92       '_date'     => time,
93   }); 
94   my $error = $quotation->insert;
95   if ( $error ) {
96     warn "failed to create selfservice quotation for custnum #" .
97       $session->{custnum} . "\n";
98     return { 'error' => $error };
99   } else {
100     warn "started new selfservice quotation #". $quotation->quotationnum."\n"
101       if $DEBUG;
102     return { 'error' => $error, 'quotationnum' => $quotation->quotationnum };
103   }
104 }
105
106 =item quotation_delete { session, quotationnum }
107
108 Disables (doesn't actually delete) the specified quotationnum.
109
110 =cut
111
112 sub quotation_delete {
113   my $p = shift;
114
115   my($context, $session, $custnum) = _custoragent_session_custnum($p);
116   return { 'error' => $session } if $context eq 'error';
117
118   my $quotation = _quotation($session, $p->{quotationnum})
119     or return { 'error' => "Quotation not found" };
120   warn "quotation_delete #".$quotation->quotationnum
121     if $DEBUG;
122
123   $quotation->set('disabled' => 'Y');
124   my $error = $quotation->replace;
125   return { 'error' => $error };
126 }
127
128 =item quotation_info { session, quotationnum }
129
130 Returns a hashref describing the specified quotation, containing:
131
132 - "sections", an arrayref containing one section for each billing frequency.
133   Each one will have:
134   - "description"
135   - "subtotal"
136   - "detail_items", an arrayref of detail items, each with:
137     - "pkgnum", the reference number (actually the quotationpkgnum field)
138     - "description", the package name (or tax name)
139     - "quantity"
140     - "amount"
141 - "num_pkgs", the number of packages in the quotation
142 - "total_setup", the sum of setup/one-time charges and their taxes
143 - "total_recur", the sum of all recurring charges and their taxes
144
145 =cut
146
147 sub quotation_info {
148   my $p = shift;
149
150   my($context, $session, $custnum) = _custoragent_session_custnum($p);
151   return { 'error' => $session } if $context eq 'error';
152
153   my $quotation = _quotation($session, $p->{quotationnum})
154     or return { 'error' => "Quotation not found" };
155   warn "quotation_info #".$quotation->quotationnum
156     if $DEBUG;
157
158   # code reuse ftw
159   my $null_escape = sub { @_ };
160   my ($sections) = $quotation->_items_sections(escape => $null_escape);
161   foreach my $section (@$sections) {
162     $section->{'detail_items'} =
163       [ $quotation->_items_pkg('section' => $section, escape_function => $null_escape) ]; 
164   }
165   return { 'error'        => '',
166            'sections'     => $sections,
167            'num_pkgs'     => scalar($quotation->quotation_pkg),
168            'total_setup'  => $quotation->total_setup,
169            'total_recur'  => $quotation->total_recur,
170          };
171 }
172
173 =item quotation_print { session, 'format' }
174
175 Renders the quotation. 'format' can be either 'html' or 'pdf'; the resulting
176 hashref will contain 'document' => the HTML or PDF contents.
177
178 =cut
179
180 sub quotation_print {
181   my $p = shift;
182
183   my($context, $session, $custnum) = _custoragent_session_custnum($p);
184   return { 'error' => $session } if $context eq 'error';
185
186   my $quotation = _quotation($session, $p->{quotationnum})
187     or return { 'error' => "Quotation not found" };
188   warn "quotation_print #".$quotation->quotationnum
189     if $DEBUG;
190
191   my $format = $p->{'format'}
192    or return { 'error' => "No rendering format specified" };
193
194   my $document;
195   if ($format eq 'html') {
196     $document = $quotation->print_html;
197   } elsif ($format eq 'pdf') {
198     $document = $quotation->print_pdf;
199   }
200   warn "$format, ".length($document)." bytes\n"
201     if $DEBUG;
202   return { 'error' => '', 'document' => $document };
203 }
204
205 =item quotation_add_pkg { session, 'pkgpart', 'quantity', [ location opts ] }
206
207 Adds a package to the user's current quotation. Session info and 'pkgpart' are
208 required. 'quantity' defaults to 1.
209
210 Location can be specified as 'locationnum' to use an existing location, or
211 'address1', 'address2', 'city', 'state', 'zip', 'country' to create a new one,
212 or it will default to the customer's service location.
213
214 =cut
215
216 sub quotation_add_pkg {
217   my $p = shift;
218
219   my($context, $session, $custnum) = _custoragent_session_custnum($p);
220   return { 'error' => $session } if $context eq 'error';
221   
222   my $quotation = _quotation($session, $p->{quotationnum})
223     or return { 'error' => "Quotation not found" };
224   my $cust_main = $quotation->cust_main;
225
226   my $pkgpart = $p->{'pkgpart'};
227   my $allowed_pkgpart = $cust_main->agent->pkgpart_hashref;
228
229   my $part_pkg = FS::part_pkg->by_key($pkgpart);
230
231   if (!$part_pkg or !$allowed_pkgpart->{$pkgpart}) {
232     warn "disallowed quotation_pkg pkgpart $pkgpart\n"
233       if $DEBUG;
234     return { 'error' => "unknown package $pkgpart" };
235   }
236
237   warn "creating quotation_pkg with pkgpart $pkgpart\n"
238     if $DEBUG;
239   my $quotation_pkg = FS::quotation_pkg->new({
240     'quotationnum'  => $quotation->quotationnum,
241     'pkgpart'       => $p->{'pkgpart'},
242     'quantity'      => $p->{'quantity'} || 1,
243   });
244   if ( $p->{locationnum} > 0 ) {
245     $quotation_pkg->set('locationnum', $p->{locationnum});
246   } elsif ( $p->{address1} ) {
247     my $location = FS::cust_location->find_or_insert(
248       'custnum' => $cust_main->custnum,
249       map { $_ => $p->{$_} }
250         qw( address1 address2 city county state zip country )
251     );
252     $quotation_pkg->set('locationnum', $location->locationnum);
253   }
254
255   my $error = $quotation_pkg->insert
256            || $quotation->estimate
257            || '';
258
259   { 'error'         => $error,
260     'quotationnum'  => $quotation->quotationnum };
261 }
262  
263 =item quotation_remove_pkg { session, 'pkgnum' }
264
265 Removes the package from the user's current quotation. 'pkgnum' is required.
266
267 =cut
268
269 sub quotation_remove_pkg {
270   my $p = shift;
271
272   my($context, $session, $custnum) = _custoragent_session_custnum($p);
273   return { 'error' => $session } if $context eq 'error';
274   
275   my $quotation = _quotation($session, $p->{quotationnum})
276     or return { 'error' => "Quotation not found" };
277   my $quotationpkgnum = $p->{pkgnum};
278   my $quotation_pkg = FS::quotation_pkg->by_key($quotationpkgnum);
279   if (!$quotation_pkg
280       or $quotation_pkg->quotationnum != $quotation->quotationnum) {
281     return { 'error' => "unknown quotation item $quotationpkgnum" };
282   }
283   warn "removing quotation_pkg with pkgpart ".$quotation_pkg->pkgpart."\n"
284     if $DEBUG;
285
286   my $error = $quotation_pkg->delete
287            || $quotation->estimate;
288
289   { 'error'         => $error,
290     'quotationnum'  => $quotation->quotationnum };
291 }
292
293 =item quotation_order
294
295 Convert the current quotation to a package order.
296
297 =cut
298
299 sub quotation_order {
300   my $p = shift;
301
302   my($context, $session, $custnum) = _custoragent_session_custnum($p);
303   return { 'error' => $session } if $context eq 'error';
304   
305   my $quotation = _quotation($session, $p->{quotationnum})
306     or return { 'error' => "Quotation not found" };
307
308   my $error = $quotation->order;
309   $quotation->set('disabled' => 'Y');
310   $error ||= $quotation->replace;
311
312   return { 'error' => $error };
313 }
314
315 1;