disable quotation after ordering, #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 = 0;
9
10 sub _custoragent_session_custnum {
11   FS::ClientAPI::MyAccount::_custoragent_session_custnum(@_);
12 }
13
14 sub _quotation {
15   # the currently active quotation
16   my $session = shift;
17   my $quotation;
18   if ( my $quotationnum = $session->{'quotationnum'} ) {
19     $quotation = FS::quotation->by_key($quotationnum);
20   } 
21   if ( !$quotation ) {
22     # find the last quotation created through selfservice
23     $quotation = qsearchs( 'quotation', {
24         'custnum'   => $session->{'custnum'},
25         'usernum'   => $FS::CurrentUser::CurrentUser->usernum,
26         'disabled'  => '',
27     }); 
28     warn "found selfservice quotation #". $quotation->quotationnum."\n"
29       if $quotation and $DEBUG;
30   } 
31   if ( !$quotation ) {
32     $quotation = FS::quotation->new({
33         'custnum'   => $session->{'custnum'},
34         'usernum'   => $FS::CurrentUser::CurrentUser->usernum,
35         '_date'     => time,
36     }); 
37     $quotation->insert; # what to do on error? call the police?
38     warn "started new selfservice quotation #". $quotation->quotationnum."\n"
39       if $quotation and $DEBUG;
40   } 
41   $session->{'quotationnum'} = $quotation->quotationnum;
42   return $quotation;
43 }
44
45 =item quotation_info { session }
46
47 Returns a hashref describing the current quotation, containing:
48
49 - "sections", an arrayref containing one section for each billing frequency.
50   Each one will have:
51   - "description"
52   - "subtotal"
53   - "detail_items", an arrayref of detail items, each with:
54     - "pkgnum", the reference number (actually the quotationpkgnum field)
55     - "description", the package name (or tax name)
56     - "quantity"
57     - "amount"
58
59 =cut
60
61 sub quotation_info {
62   my $p = shift;
63
64   my($context, $session, $custnum) = _custoragent_session_custnum($p);
65   return { 'error' => $session } if $context eq 'error';
66
67   my $quotation = _quotation($session);
68   return { 'error' => "No current quotation for this customer" } if !$quotation;
69   warn "quotation_info #".$quotation->quotationnum
70     if $DEBUG;
71
72   my $null_escape = sub { @_ };
73   # 3.x only; 4.x quotation redesign uses actual sections for this
74   # and isn't a weird hack
75   my @items =
76     map { $_->{'pkgnum'} = $_->{'preref_html'}; $_ }
77     $quotation->_items_pkg(escape_function => $null_escape,
78                            preref_callback => sub { shift->quotationpkgnum });
79   push @items, $quotation->_items_total();
80
81   my $sections = [
82     { 'description' => 'Estimated Charges',
83       'detail_items' => \@items
84     }
85   ];
86
87   return { 'error' => '', 'sections' => $sections }
88 }
89
90 =item quotation_print { session, 'format' }
91
92 Renders the quotation. 'format' can be either 'html' or 'pdf'; the resulting
93 hashref will contain 'document' => the HTML or PDF contents.
94
95 =cut
96
97 sub quotation_print {
98   my $p = shift;
99
100   my($context, $session, $custnum) = _custoragent_session_custnum($p);
101   return { 'error' => $session } if $context eq 'error';
102
103   my $quotation = _quotation($session);
104   return { 'error' => "No current quotation for this customer" } if !$quotation;
105   warn "quotation_print #".$quotation->quotationnum
106     if $DEBUG;
107
108   my $format = $p->{'format'}
109    or return { 'error' => "No rendering format specified" };
110
111   my $document;
112   if ($format eq 'html') {
113     $document = $quotation->print_html;
114   } elsif ($format eq 'pdf') {
115     $document = $quotation->print_pdf;
116   }
117   warn "$format, ".length($document)." bytes\n"
118     if $DEBUG;
119   return { 'error' => '', 'document' => $document };
120 }
121
122 =item quotation_add_pkg { session, 'pkgpart', 'quantity', [ location opts ] }
123
124 Adds a package to the user's current quotation. Session info and 'pkgpart' are
125 required. 'quantity' defaults to 1.
126
127 Location can be specified as 'locationnum' to use an existing location, or
128 'address1', 'address2', 'city', 'state', 'zip', 'country' to create a new one,
129 or it will default to the customer's service location.
130
131 =cut
132
133 sub quotation_add_pkg {
134   my $p = shift;
135
136   my($context, $session, $custnum) = _custoragent_session_custnum($p);
137   return { 'error' => $session } if $context eq 'error';
138   
139   my $quotation = _quotation($session);
140   my $cust_main = $quotation->cust_main;
141
142   my $pkgpart = $p->{'pkgpart'};
143   my $allowed_pkgpart = $cust_main->agent->pkgpart_hashref;
144
145   my $part_pkg = FS::part_pkg->by_key($pkgpart);
146
147   if (!$part_pkg or
148       (!$allowed_pkgpart->{$pkgpart} and 
149        $cust_main->agentnum != ($part_pkg->agentnum || 0))
150   ) {
151     warn "disallowed quotation_pkg pkgpart $pkgpart\n"
152       if $DEBUG;
153     return { 'error' => "unknown package $pkgpart" };
154   }
155
156   warn "creating quotation_pkg with pkgpart $pkgpart\n"
157     if $DEBUG;
158   my $quotation_pkg = FS::quotation_pkg->new({
159     'quotationnum'  => $quotation->quotationnum,
160     'pkgpart'       => $p->{'pkgpart'},
161     'quantity'      => $p->{'quantity'} || 1,
162   });
163   if ( $p->{locationnum} > 0 ) {
164     $quotation_pkg->set('locationnum', $p->{locationnum});
165   } elsif ( $p->{address1} ) {
166     my $location = FS::cust_location->find_or_insert(
167       'custnum' => $cust_main->custnum,
168       map { $_ => $p->{$_} }
169         qw( address1 address2 city county state zip country )
170     );
171     $quotation_pkg->set('locationnum', $location->locationnum);
172   }
173
174   my $error = $quotation_pkg->insert
175            || $quotation->estimate;
176
177   { 'error'         => $error,
178     'quotationnum'  => $quotation->quotationnum };
179 }
180  
181 =item quotation_remove_pkg { session, 'pkgnum' }
182
183 Removes the package from the user's current quotation. 'pkgnum' is required.
184
185 =cut
186
187 sub quotation_remove_pkg {
188   my $p = shift;
189
190   my($context, $session, $custnum) = _custoragent_session_custnum($p);
191   return { 'error' => $session } if $context eq 'error';
192   
193   my $quotation = _quotation($session);
194   my $quotationpkgnum = $p->{pkgnum};
195   my $quotation_pkg = FS::quotation_pkg->by_key($quotationpkgnum);
196   if (!$quotation_pkg
197       or $quotation_pkg->quotationnum != $quotation->quotationnum) {
198     return { 'error' => "unknown quotation item $quotationpkgnum" };
199   }
200   warn "removing quotation_pkg with pkgpart ".$quotation_pkg->pkgpart."\n"
201     if $DEBUG;
202
203   my $error = $quotation_pkg->delete
204            || $quotation->estimate;
205
206   { 'error'         => $error,
207     'quotationnum'  => $quotation->quotationnum };
208 }
209
210 =item quotation_order
211
212 Convert the current quotation to a package order.
213
214 =cut
215
216 sub quotation_order {
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);
223
224   my $error = $quotation->order;
225
226   $quotation->set('disabled' => 'Y');
227   $error ||= $quotation->replace;
228
229   return { 'error' => $error };
230 }
231
232 1;