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