1 package FS::ClientAPI::MyAccount::quotation;
4 use FS::Record qw(qsearch qsearchs);
10 sub _custoragent_session_custnum {
11 FS::ClientAPI::MyAccount::_custoragent_session_custnum(@_);
14 # _quotation(session, quotationnum)
15 # returns that quotation, or '' if it doesn't exist and belong to this
20 my $quotationnum = shift;
23 if ( $quotationnum =~ /^(\d+)$/ ) {
24 $quotation = qsearchs( 'quotation', {
25 'custnum' => $session->{'custnum'},
26 'usernum' => $FS::CurrentUser::CurrentUser->usernum,
30 warn "found selfservice quotation #". $quotation->quotationnum."\n"
31 if $quotation and $DEBUG;
38 =item list_quotations { session }
40 Returns a hashref listing this customer's active self-service quotations.
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
55 my($context, $session, $custnum) = _custoragent_session_custnum($p);
56 return { 'error' => $session } if $context eq 'error';
58 my @quotations = qsearch('quotation', {
59 'custnum' => $session->{'custnum'},
60 'usernum' => $FS::CurrentUser::CurrentUser->usernum,
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,
74 return { 'quotations' => \@q, 'error' => '' };
77 =item quotation_new { session }
79 Creates a quotation and returns its quotationnum.
86 my($context, $session, $custnum) = _custoragent_session_custnum($p);
87 return { 'error' => $session } if $context eq 'error';
89 my $quotation = FS::quotation->new({
90 'custnum' => $session->{'custnum'},
91 'usernum' => $FS::CurrentUser::CurrentUser->usernum,
94 my $error = $quotation->insert;
96 warn "failed to create selfservice quotation for custnum #" .
97 $session->{custnum} . "\n";
98 return { 'error' => $error };
100 warn "started new selfservice quotation #". $quotation->quotationnum."\n"
102 return { 'error' => $error, 'quotationnum' => $quotation->quotationnum };
106 =item quotation_delete { session, quotationnum }
108 Disables (doesn't actually delete) the specified quotationnum.
112 sub quotation_delete {
115 my($context, $session, $custnum) = _custoragent_session_custnum($p);
116 return { 'error' => $session } if $context eq 'error';
118 my $quotation = _quotation($session, $p->{quotationnum})
119 or return { 'error' => "Quotation not found" };
120 warn "quotation_delete #".$quotation->quotationnum
123 $quotation->set('disabled' => 'Y');
124 my $error = $quotation->replace;
125 return { 'error' => $error };
128 =item quotation_info { session, quotationnum }
130 Returns a hashref describing the specified quotation, containing:
132 - "sections", an arrayref containing one section for each billing frequency.
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)
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
150 my($context, $session, $custnum) = _custoragent_session_custnum($p);
151 return { 'error' => $session } if $context eq 'error';
153 my $quotation = _quotation($session, $p->{quotationnum})
154 or return { 'error' => "Quotation not found" };
155 warn "quotation_info #".$quotation->quotationnum
158 my $null_escape = sub { @_ };
159 # 3.x only; 4.x quotation redesign uses actual sections for this
160 # and isn't a weird hack
162 map { $_->{'pkgnum'} = $_->{'preref_html'}; $_ }
163 $quotation->_items_pkg(escape_function => $null_escape,
164 preref_callback => sub { shift->quotationpkgnum });
165 push @items, $quotation->_items_total();
168 { 'description' => 'Estimated Charges',
169 'detail_items' => \@items
173 return { 'error' => '',
174 'sections' => $sections,
175 'num_pkgs' => scalar($quotation->quotation_pkg),
176 'total_setup' => $quotation->total_setup,
177 'total_recur' => $quotation->total_recur,
181 =item quotation_print { session, 'format' }
183 Renders the quotation. 'format' can be either 'html' or 'pdf'; the resulting
184 hashref will contain 'document' => the HTML or PDF contents.
188 sub quotation_print {
191 my($context, $session, $custnum) = _custoragent_session_custnum($p);
192 return { 'error' => $session } if $context eq 'error';
194 my $quotation = _quotation($session, $p->{quotationnum})
195 or return { 'error' => "Quotation not found" };
196 warn "quotation_print #".$quotation->quotationnum
199 my $format = $p->{'format'}
200 or return { 'error' => "No rendering format specified" };
203 if ($format eq 'html') {
204 $document = $quotation->print_html;
205 } elsif ($format eq 'pdf') {
206 $document = $quotation->print_pdf;
208 warn "$format, ".length($document)." bytes\n"
210 return { 'error' => '', 'document' => $document };
213 =item quotation_add_pkg { session, 'pkgpart', 'quantity', [ location opts ] }
215 Adds a package to the user's current quotation. Session info and 'pkgpart' are
216 required. 'quantity' defaults to 1.
218 Location can be specified as 'locationnum' to use an existing location, or
219 'address1', 'address2', 'city', 'state', 'zip', 'country' to create a new one,
220 or it will default to the customer's service location.
224 sub quotation_add_pkg {
227 my($context, $session, $custnum) = _custoragent_session_custnum($p);
228 return { 'error' => $session } if $context eq 'error';
230 my $quotation = _quotation($session, $p->{quotationnum})
231 or return { 'error' => "Quotation not found" };
232 my $cust_main = $quotation->cust_main;
234 my $pkgpart = $p->{'pkgpart'};
235 my $allowed_pkgpart = $cust_main->agent->pkgpart_hashref;
237 my $part_pkg = FS::part_pkg->by_key($pkgpart);
240 (!$allowed_pkgpart->{$pkgpart} and
241 $cust_main->agentnum != ($part_pkg->agentnum || 0))
243 warn "disallowed quotation_pkg pkgpart $pkgpart\n"
245 return { 'error' => "unknown package $pkgpart" };
248 warn "creating quotation_pkg with pkgpart $pkgpart\n"
250 my $quotation_pkg = FS::quotation_pkg->new({
251 'quotationnum' => $quotation->quotationnum,
252 'pkgpart' => $p->{'pkgpart'},
253 'quantity' => $p->{'quantity'} || 1,
255 if ( $p->{locationnum} > 0 ) {
256 $quotation_pkg->set('locationnum', $p->{locationnum});
257 } elsif ( $p->{address1} ) {
258 my $location = FS::cust_location->find_or_insert(
259 'custnum' => $cust_main->custnum,
260 map { $_ => $p->{$_} }
261 qw( address1 address2 city county state zip country )
263 $quotation_pkg->set('locationnum', $location->locationnum);
266 my $error = $quotation_pkg->insert
267 || $quotation->estimate
271 'quotationnum' => $quotation->quotationnum };
274 =item quotation_remove_pkg { session, 'pkgnum' }
276 Removes the package from the user's current quotation. 'pkgnum' is required.
280 sub quotation_remove_pkg {
283 my($context, $session, $custnum) = _custoragent_session_custnum($p);
284 return { 'error' => $session } if $context eq 'error';
286 my $quotation = _quotation($session, $p->{quotationnum})
287 or return { 'error' => "Quotation not found" };
288 my $quotationpkgnum = $p->{pkgnum};
289 my $quotation_pkg = FS::quotation_pkg->by_key($quotationpkgnum);
291 or $quotation_pkg->quotationnum != $quotation->quotationnum) {
292 return { 'error' => "unknown quotation item $quotationpkgnum" };
294 warn "removing quotation_pkg with pkgpart ".$quotation_pkg->pkgpart."\n"
297 my $error = $quotation_pkg->delete
298 || $quotation->estimate;
301 'quotationnum' => $quotation->quotationnum };
304 =item quotation_order
306 Convert the current quotation to a package order.
310 sub quotation_order {
313 my($context, $session, $custnum) = _custoragent_session_custnum($p);
314 return { 'error' => $session } if $context eq 'error';
316 my $quotation = _quotation($session, $p->{quotationnum})
317 or return { 'error' => "Quotation not found" };
319 my $error = $quotation->order;
321 $quotation->set('disabled' => 'Y');
322 $error ||= $quotation->replace;
324 return { 'error' => $error };