allow all legal packages to be chosen in selfservice quotations
[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 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   # code reuse ftw
73   my $null_escape = sub { @_ };
74   my ($sections) = $quotation->_items_sections(escape => $null_escape);
75   foreach my $section (@$sections) {
76     $section->{'detail_items'} =
77       [ $quotation->_items_pkg('section' => $section, escape_function => $null_escape) ]; 
78   }
79   return { 'error' => '', 'sections' => $sections }
80 }
81
82 =item quotation_print { session, 'format' }
83
84 Renders the quotation. 'format' can be either 'html' or 'pdf'; the resulting
85 hashref will contain 'document' => the HTML or PDF contents.
86
87 =cut
88
89 sub quotation_print {
90   my $p = shift;
91
92   my($context, $session, $custnum) = _custoragent_session_custnum($p);
93   return { 'error' => $session } if $context eq 'error';
94
95   my $quotation = _quotation($session);
96   return { 'error' => "No current quotation for this customer" } if !$quotation;
97   warn "quotation_print #".$quotation->quotationnum
98     if $DEBUG;
99
100   my $format = $p->{'format'}
101    or return { 'error' => "No rendering format specified" };
102
103   my $document;
104   if ($format eq 'html') {
105     $document = $quotation->print_html;
106   } elsif ($format eq 'pdf') {
107     $document = $quotation->print_pdf;
108   }
109   warn "$format, ".length($document)." bytes\n"
110     if $DEBUG;
111   return { 'error' => '', 'document' => $document };
112 }
113
114 =item quotation_add_pkg { session, 'pkgpart', 'quantity', [ location opts ] }
115
116 Adds a package to the user's current quotation. Session info and 'pkgpart' are
117 required. 'quantity' defaults to 1.
118
119 Location can be specified as 'locationnum' to use an existing location, or
120 'address1', 'address2', 'city', 'state', 'zip', 'country' to create a new one,
121 or it will default to the customer's service location.
122
123 =cut
124
125 sub quotation_add_pkg {
126   my $p = shift;
127
128   my($context, $session, $custnum) = _custoragent_session_custnum($p);
129   return { 'error' => $session } if $context eq 'error';
130   
131   my $quotation = _quotation($session);
132   my $cust_main = $quotation->cust_main;
133
134   my $pkgpart = $p->{'pkgpart'};
135   my $allowed_pkgpart = $cust_main->agent->pkgpart_hashref;
136
137   my $part_pkg = FS::part_pkg->by_key($pkgpart);
138
139   if (!$part_pkg or
140       (!$allowed_pkgpart->{$pkgpart} and 
141        $cust_main->agentnum != ($part_pkg->agentnum || 0))
142   ) {
143     warn "disallowed quotation_pkg pkgpart $pkgpart\n"
144       if $DEBUG;
145     return { 'error' => "unknown package $pkgpart" };
146   }
147
148   warn "creating quotation_pkg with pkgpart $pkgpart\n"
149     if $DEBUG;
150   my $quotation_pkg = FS::quotation_pkg->new({
151     'quotationnum'  => $quotation->quotationnum,
152     'pkgpart'       => $p->{'pkgpart'},
153     'quantity'      => $p->{'quantity'} || 1,
154   });
155   if ( $p->{locationnum} > 0 ) {
156     $quotation_pkg->set('locationnum', $p->{locationnum});
157   } elsif ( $p->{address1} ) {
158     my $location = FS::cust_location->find_or_insert(
159       'custnum' => $cust_main->custnum,
160       map { $_ => $p->{$_} }
161         qw( address1 address2 city county state zip country )
162     );
163     $quotation_pkg->set('locationnum', $location->locationnum);
164   }
165
166   my $error = $quotation_pkg->insert
167            || $quotation->estimate;
168
169   { 'error'         => $error,
170     'quotationnum'  => $quotation->quotationnum };
171 }
172  
173 =item quotation_remove_pkg { session, 'pkgnum' }
174
175 Removes the package from the user's current quotation. 'pkgnum' is required.
176
177 =cut
178
179 sub quotation_remove_pkg {
180   my $p = shift;
181
182   my($context, $session, $custnum) = _custoragent_session_custnum($p);
183   return { 'error' => $session } if $context eq 'error';
184   
185   my $quotation = _quotation($session);
186   my $quotationpkgnum = $p->{pkgnum};
187   my $quotation_pkg = FS::quotation_pkg->by_key($quotationpkgnum);
188   if (!$quotation_pkg
189       or $quotation_pkg->quotationnum != $quotation->quotationnum) {
190     return { 'error' => "unknown quotation item $quotationpkgnum" };
191   }
192   warn "removing quotation_pkg with pkgpart ".$quotation_pkg->pkgpart."\n"
193     if $DEBUG;
194
195   my $error = $quotation_pkg->delete
196            || $quotation->estimate;
197
198   { 'error'         => $error,
199     'quotationnum'  => $quotation->quotationnum };
200 }
201
202 =item quotation_order
203
204 Convert the current quotation to a package order.
205
206 =cut
207
208 sub quotation_order {
209   my $p = shift;
210
211   my($context, $session, $custnum) = _custoragent_session_custnum($p);
212   return { 'error' => $session } if $context eq 'error';
213   
214   my $quotation = _quotation($session);
215
216   my $error = $quotation->order;
217
218   return { 'error' => $error };
219 }
220
221 1;