]>
Commit | Line | Data |
---|---|---|
4d764c34 TC |
1 | package BSE::UI::AdminShop; |
2 | use strict; | |
3 | use base 'BSE::UI::AdminDispatch'; | |
4 | use Products; | |
5 | use Product; | |
6 | use BSE::TB::Orders; | |
7 | use BSE::TB::OrderItems; | |
8 | use BSE::Template; | |
4d764c34 | 9 | use Constants qw(:shop $SHOPID $PRODUCTPARENT |
771ab646 | 10 | $SHOP_URI $CGI_URI $AUTO_GENERATE); |
f40af7e2 | 11 | use BSE::TB::Images; |
4d764c34 TC |
12 | use Articles; |
13 | use BSE::Sort; | |
ea71fc0d | 14 | use BSE::Util::Tags qw(tag_hash tag_error_img tag_object_plain tag_object tag_article); |
4d764c34 TC |
15 | use BSE::Util::Iterate; |
16 | use BSE::WebUtil 'refresh_to_admin'; | |
3f9c8a96 | 17 | use BSE::Util::HTML qw(:default popup_menu); |
4d764c34 | 18 | use BSE::Arrows; |
b62cae00 | 19 | use BSE::Shop::Util qw(:payment order_item_opts nice_options payment_types); |
771ab646 | 20 | use BSE::CfgInfo qw(cfg_dist_image_uri); |
023761bd TC |
21 | use BSE::Util::SQL qw/now_sqldate sql_to_date date_to_sql sql_date sql_datetime/; |
22 | use BSE::Util::Valid qw/valid_date/; | |
4d764c34 | 23 | |
78e38142 | 24 | our $VERSION = "1.024"; |
cb7fd78d | 25 | |
4d764c34 TC |
26 | my %actions = |
27 | ( | |
28 | order_list => 'shop_order_list', | |
29 | order_list_filled => 'shop_order_list', | |
30 | order_list_unfilled => 'shop_order_list', | |
31 | order_list_unpaid => 'shop_order_list', | |
32 | order_list_incomplete => 'shop_order_list', | |
33 | order_detail => 'shop_order_detail', | |
34 | order_filled => 'shop_order_filled', | |
14604ada TC |
35 | order_paid => 'shop_order_paid', |
36 | order_unpaid => 'shop_order_unpaid', | |
080fc207 | 37 | order_save => 'shop_order_save', |
4d764c34 TC |
38 | product_detail => '', |
39 | product_list => '', | |
13a986ee | 40 | paypal_refund => 'bse_shop_order_refund_paypal', |
023761bd TC |
41 | coupon_list => 'bse_shop_coupon_list', |
42 | coupon_addform => 'bse_shop_coupon_add', | |
43 | coupon_add => 'bse_shop_coupon_add', | |
44 | coupon_edit => 'bse_shop_coupon_edit', | |
45 | coupon_save => 'bse_shop_coupon_edit', | |
46 | coupon_deleteform => 'bse_shop_coupon_delete', | |
47 | coupon_delete => 'bse_shop_coupon_delete', | |
4d764c34 TC |
48 | ); |
49 | ||
50 | sub actions { | |
51 | \%actions; | |
52 | } | |
53 | ||
54 | sub rights { | |
55 | \%actions; | |
56 | } | |
57 | ||
58 | sub default_action { | |
59 | 'product_list' | |
60 | } | |
61 | ||
62 | sub action_prefix { | |
63 | '' | |
64 | } | |
65 | ||
023761bd TC |
66 | my %csrfp = |
67 | ( | |
68 | coupon_add => { token => "admin_bse_coupon_add", target => "coupon_addform" }, | |
69 | coupon_save => { token => "admin_bse_coupon_edit", target => "coupon_edit" }, | |
446ab035 | 70 | coupon_delete => { token => "admin_bse_coupon_delete", target => "coupon_deleteform" }, |
023761bd TC |
71 | ); |
72 | ||
73 | sub csrfp_tokens { | |
74 | \%csrfp; | |
75 | } | |
76 | ||
4d764c34 TC |
77 | ##################### |
78 | # product management | |
79 | ||
80 | sub embedded_catalog { | |
81 | my ($req, $catalog, $template) = @_; | |
82 | ||
83 | my $session = $req->session; | |
84 | use POSIX 'strftime'; | |
85 | my $products = Products->new; | |
86 | my @list; | |
87 | if ($session->{showstepkids}) { | |
88 | my @allkids = $catalog->allkids; | |
89 | my %allgen = map { $_->{generator} => 1 } @allkids; | |
90 | for my $gen (keys %allgen) { | |
91 | (my $file = $gen . ".pm") =~ s!::!/!g; | |
92 | require $file; | |
93 | } | |
94 | @list = grep UNIVERSAL::isa($_->{generator}, 'Generate::Product'), $catalog->allkids; | |
95 | @list = map { $products->getByPkey($_->{id}) } @list; | |
96 | } | |
97 | else { | |
98 | @list = sort { $b->{displayOrder} <=> $a->{displayOrder} } | |
99 | $products->getBy(parentid=>$catalog->{id}); | |
100 | } | |
101 | my $list_index = -1; | |
102 | my $subcat_index = -1; | |
103 | my @subcats = sort { $b->{displayOrder} <=> $a->{displayOrder} } | |
104 | grep $_->{generator} eq 'Generate::Catalog', | |
105 | Articles->children($catalog->{id}); | |
106 | ||
771ab646 TC |
107 | my $image_uri = cfg_dist_image_uri(); |
108 | my $blank = qq!<img src="$image_uri/trans_pixel.gif" width="17" height="13" border="0" align="absbottom" />!; | |
4d764c34 TC |
109 | |
110 | my %acts; | |
111 | %acts = | |
112 | ( | |
88c41621 | 113 | $req->admin_tags, |
4d764c34 | 114 | catalog => [ \&tag_hash, $catalog ], |
4d764c34 TC |
115 | iterate_products_reset => sub { $list_index = -1; }, |
116 | iterate_products => | |
117 | sub { | |
118 | return ++$list_index < @list; | |
119 | }, | |
21fa9a5c TC |
120 | product => |
121 | sub { | |
122 | $list_index >= 0 && $list_index < @list | |
123 | or return '** outside products iterator **'; | |
ea71fc0d TC |
124 | my $product = $list[$list_index]; |
125 | return tag_article($product, $req->cfg, $_[0]); | |
21fa9a5c | 126 | }, |
4d764c34 TC |
127 | ifProducts => sub { @list }, |
128 | iterate_subcats_reset => | |
129 | sub { | |
130 | $subcat_index = -1; | |
131 | }, | |
132 | iterate_subcats => sub { ++$subcat_index < @subcats }, | |
ea71fc0d | 133 | subcat => sub { tag_article($subcats[$subcat_index], $req->cfg, $_[0]) }, |
4d764c34 TC |
134 | ifSubcats => sub { @subcats }, |
135 | hiddenNote => | |
136 | sub { $list[$list_index]{listed} == 0 ? "Hidden" : " " }, | |
137 | move => | |
138 | sub { | |
139 | my ($arg, $acts, $funcname, $templater) = @_; | |
140 | ||
141 | $req->user_can(edit_reorder_children => $catalog) | |
142 | or return ''; | |
143 | my ($img_prefix, $urladd) = DevHelp::Tags->get_parms($arg, $acts, $templater); | |
144 | defined $img_prefix or $img_prefix = ''; | |
145 | defined $urladd or $urladd = ''; | |
146 | @list > 1 or return ''; | |
147 | # links to move products up/down | |
148 | my $refreshto = $ENV{SCRIPT_NAME}."$urladd#cat".$catalog->{id}; | |
149 | my $down_url = ''; | |
150 | if ($list_index < $#list) { | |
151 | if ($session->{showstepkids}) { | |
152 | $down_url = "$CGI_URI/admin/move.pl?stepparent=$catalog->{id}&d=swap&id=$list[$list_index]{id}&other=$list[$list_index+1]{id}"; | |
153 | } | |
154 | else { | |
155 | $down_url = "$CGI_URI/admin/move.pl?id=$list[$list_index]{id}&d=swap&other=$list[$list_index+1]{id}"; | |
156 | } | |
157 | } | |
158 | my $up_url = ''; | |
159 | if ($list_index > 0) { | |
160 | if ($session->{showstepkids}) { | |
161 | $up_url = "$CGI_URI/admin/move.pl?stepparent=$catalog->{id}&d=swap&id=$list[$list_index]{id}&other=$list[$list_index-1]{id}"; | |
162 | } | |
163 | else { | |
164 | $up_url = "$CGI_URI/admin/move.pl?id=$list[$list_index]{id}&d=swap&other=$list[$list_index-1]{id}"; | |
165 | } | |
166 | } | |
167 | return make_arrows($req->cfg, $down_url, $up_url, $refreshto, $img_prefix); | |
168 | }, | |
169 | script=>sub { $ENV{SCRIPT_NAME} }, | |
170 | embed => | |
171 | sub { | |
172 | my ($which, $template) = split ' ', $_[0]; | |
173 | $which eq 'subcat' or return "Unknown object $which embedded"; | |
174 | return embedded_catalog($req, $subcats[$subcat_index], $template); | |
175 | }, | |
176 | movecat => | |
177 | sub { | |
178 | my ($arg, $acts, $funcname, $templater) = @_; | |
179 | ||
180 | $req->user_can(edit_reorder_children => $catalog) | |
181 | or return ''; | |
182 | @subcats > 1 or return ''; | |
183 | # links to move catalogs up/down | |
184 | my ($img_prefix, $urladd) = DevHelp::Tags->get_parms($arg, $acts, $templater); | |
185 | defined $img_prefix or $img_prefix = ''; | |
186 | defined $urladd or $urladd = ''; | |
187 | my $refreshto = $ENV{SCRIPT_NAME}.$urladd; | |
188 | my $down_url = ""; | |
189 | if ($subcat_index < $#subcats) { | |
190 | $down_url = "$CGI_URI/admin/move.pl?id=$subcats[$subcat_index]{id}&d=swap&other=$subcats[$subcat_index+1]{id}&all=1"; | |
191 | } | |
192 | my $up_url = ""; | |
193 | if ($subcat_index > 0) { | |
194 | $up_url = "$CGI_URI/admin/move.pl?id=$subcats[$subcat_index]{id}&d=swap&other=$subcats[$subcat_index-1]{id}&all=1"; | |
195 | } | |
196 | return make_arrows($req->cfg, $down_url, $up_url, $refreshto, $img_prefix); | |
197 | }, | |
198 | ); | |
199 | ||
200 | return BSE::Template->get_page('admin/'.$template, $req->cfg, \%acts); | |
201 | } | |
202 | ||
203 | sub req_product_list { | |
204 | my ($class, $req, $message) = @_; | |
205 | ||
206 | my $cgi = $req->cgi; | |
207 | my $session = $req->session; | |
208 | my $shopid = $req->cfg->entryErr('articles', 'shop'); | |
209 | my $shop = Articles->getByPkey($shopid); | |
210 | my @catalogs = sort { $b->{displayOrder} <=> $a->{displayOrder} } | |
211 | grep $_->{generator} eq 'Generate::Catalog', Articles->children($shopid); | |
212 | my $catalog_index = -1; | |
023761bd | 213 | $message = $req->message($message); |
4d764c34 TC |
214 | if (defined $cgi->param('showstepkids')) { |
215 | $session->{showstepkids} = $cgi->param('showstepkids'); | |
216 | } | |
217 | exists $session->{showstepkids} or $session->{showstepkids} = 1; | |
218 | my $products = Products->new; | |
219 | my @products = sort { $b->{displayOrder} <=> $a->{displayOrder} } | |
220 | $products->getBy(parentid => $shopid); | |
221 | my $product_index; | |
222 | ||
771ab646 TC |
223 | my $image_uri = cfg_dist_image_uri(); |
224 | my $blank = qq!<img src="$image_uri/trans_pixel.gif" width="17" height="13" border="0" align="absbottom" />!; | |
4d764c34 TC |
225 | |
226 | my $it = BSE::Util::Iterate->new; | |
227 | ||
228 | my %acts; | |
229 | %acts = | |
230 | ( | |
88c41621 | 231 | $req->admin_tags, |
ea71fc0d | 232 | catalog=> sub { tag_article($catalogs[$catalog_index], $req->cfg, $_[0]) }, |
4d764c34 TC |
233 | iterate_catalogs => sub { ++$catalog_index < @catalogs }, |
234 | shopid=>sub { $shopid }, | |
235 | shop => [ \&tag_hash, $shop ], | |
236 | script=>sub { $ENV{SCRIPT_NAME} }, | |
237 | message => sub { $message }, | |
238 | embed => | |
239 | sub { | |
240 | my ($which, $template) = split ' ', $_[0]; | |
241 | $which eq 'catalog' or return "Unknown object $which embedded"; | |
242 | return embedded_catalog($req, $catalogs[$catalog_index], $template); | |
243 | }, | |
244 | movecat => | |
245 | sub { | |
246 | my ($arg, $acts, $funcname, $templater) = @_; | |
247 | ||
248 | $req->user_can(edit_reorder_children => $shopid) | |
249 | or return ''; | |
250 | @catalogs > 1 or return ''; | |
251 | # links to move catalogs up/down | |
252 | my ($img_prefix, $urladd) = DevHelp::Tags->get_parms($arg, $acts, $templater); | |
253 | defined $img_prefix or $img_prefix = ''; | |
254 | defined $urladd or $urladd = ''; | |
255 | my $refreshto = $ENV{SCRIPT_NAME} . $urladd; | |
256 | my $down_url = ''; | |
257 | if ($catalog_index < $#catalogs) { | |
258 | $down_url = "$CGI_URI/admin/move.pl?id=$catalogs[$catalog_index]{id}&d=swap&other=$catalogs[$catalog_index+1]{id}"; | |
259 | } | |
260 | my $up_url = ''; | |
261 | if ($catalog_index > 0) { | |
262 | $up_url = "$CGI_URI/admin/move.pl?id=$catalogs[$catalog_index]{id}&d=swap&other=$catalogs[$catalog_index-1]{id}"; | |
263 | } | |
264 | return make_arrows($req->cfg, $down_url, $up_url, $refreshto, $img_prefix); | |
265 | }, | |
266 | ifShowStepKids => sub { $session->{showstepkids} }, | |
267 | $it->make_iterator(undef, 'product', 'products', \@products, \$product_index), | |
268 | move => | |
269 | sub { | |
270 | my ($arg, $acts, $funcname, $templater) = @_; | |
271 | ||
272 | $req->user_can(edit_reorder_children => $shop) | |
273 | or return ''; | |
274 | my ($img_prefix, $urladd) = DevHelp::Tags->get_parms($arg, $acts, $templater); | |
275 | defined $img_prefix or $img_prefix = ''; | |
276 | defined $urladd or $urladd = ''; | |
277 | @products > 1 or return ''; | |
278 | # links to move products up/down | |
279 | my $refreshto = $ENV{SCRIPT_NAME}."$urladd#cat".$shop->{id}; | |
280 | my $down_url = ''; | |
281 | if ($product_index < $#products) { | |
282 | if ($session->{showstepkids}) { | |
283 | $down_url = "$CGI_URI/admin/move.pl?stepparent=$shop->{id}&d=swap&id=$products[$product_index]{id}&other=$products[$product_index+1]{id}"; | |
284 | } | |
285 | else { | |
286 | $down_url = "$CGI_URI/admin/move.pl?id=$products[$product_index]{id}&d=swap&other=$products[$product_index+1]{id}"; | |
287 | } | |
288 | } | |
289 | my $up_url = ''; | |
290 | if ($product_index > 0) { | |
291 | if ($session->{showstepkids}) { | |
292 | $up_url = "$CGI_URI/admin/move.pl?stepparent=$shop->{id}&d=swap&id=$products[$product_index]{id}&other=$products[$product_index-1]{id}"; | |
293 | } | |
294 | else { | |
295 | $up_url = "$CGI_URI/admin/move.pl?id=$products[$product_index]{id}&d=swap&other=$products[$product_index-1]{id}"; | |
296 | } | |
297 | } | |
298 | return make_arrows($req->cfg, $down_url, $up_url, $refreshto, $img_prefix); | |
299 | }, | |
300 | ); | |
301 | ||
302 | return $req->dyn_response('admin/product_list', \%acts); | |
303 | } | |
304 | ||
305 | sub req_product_detail { | |
306 | my ($class, $req) = @_; | |
307 | ||
308 | my $cgi = $req->cgi; | |
309 | my $id = $cgi->param('id'); | |
310 | if ($id and | |
311 | my $product = Products->getByPkey($id)) { | |
312 | return product_form($req, $product, '', '', 'admin/product_detail'); | |
313 | } | |
314 | else { | |
315 | return $class->req_product_list($req); | |
316 | } | |
317 | } | |
318 | ||
319 | sub product_form { | |
320 | my ($req, $product, $action, $message, $template) = @_; | |
321 | ||
322 | my $cgi = $req->cgi; | |
323 | $message ||= $cgi->param('m') || $cgi->param('message') || ''; | |
7a4ea074 | 324 | $template ||= 'admin/product_detail'; |
4d764c34 TC |
325 | my @catalogs; |
326 | my $shopid = $req->cfg->entryErr('articles', 'shop'); | |
327 | my @work = [ $shopid, '' ]; | |
328 | while (@work) { | |
329 | my ($parent, $title) = @{shift @work}; | |
330 | ||
331 | push(@catalogs, { id=>$parent, display=>$title }) if $title; | |
332 | my @kids = sort { $b->{displayOrder} <=> $a->{displayOrder} } | |
333 | grep $_->{generator} eq 'Generate::Catalog', | |
334 | Articles->children($parent); | |
335 | $title .= ' / ' if $title; | |
336 | unshift(@work, map [ $_->{id}, $title.$_->{title} ], @kids); | |
337 | } | |
338 | my @files; | |
339 | if ($product->{id}) { | |
6430ee52 TC |
340 | require BSE::TB::ArticleFiles; |
341 | @files = BSE::TB::ArticleFiles->getBy(articleId=>$product->{id}); | |
4d764c34 TC |
342 | } |
343 | my $file_index; | |
344 | ||
345 | my @templates; | |
346 | push(@templates, "shopitem.tmpl") | |
347 | if grep -e "$_/shopitem.tmpl", BSE::Template->template_dirs($req->cfg); | |
348 | for my $dir (BSE::Template->template_dirs($req->cfg)) { | |
349 | if (opendir PROD_TEMPL, "$dir/products") { | |
350 | push @templates, map "products/$_", | |
351 | grep -f "$dir/products/$_" && /\.tmpl$/i, readdir PROD_TEMPL; | |
352 | closedir PROD_TEMPL; | |
353 | } | |
354 | } | |
355 | my %seen_templates; | |
356 | @templates = sort { lc($a) cmp lc($b) } | |
357 | grep !$seen_templates{$_}++, @templates; | |
358 | ||
359 | my $stepcat_index; | |
360 | use OtherParents; | |
361 | # ugh | |
362 | my $realproduct; | |
363 | $realproduct = UNIVERSAL::isa($product, 'Product') ? $product : Products->getByPkey($product->{id}); | |
364 | my @stepcats; | |
365 | @stepcats = OtherParents->getBy(childId=>$product->{id}) | |
366 | if $product->{id}; | |
367 | my @stepcat_targets = $realproduct->step_parents if $realproduct; | |
368 | my %stepcat_targets = map { $_->{id}, $_ } @stepcat_targets; | |
369 | my @stepcat_possibles = grep !$stepcat_targets{$_->{id}}, @catalogs; | |
370 | my @images; | |
371 | @images = $product->images | |
372 | if $product->{id}; | |
373 | # @images = $imageEditor->images() | |
374 | # if $product->{id}; | |
375 | my $image_index; | |
4d764c34 | 376 | |
771ab646 TC |
377 | my $image_uri = cfg_dist_image_uri(); |
378 | my $blank = qq!<img src="$image_uri/trans_pixel.gif" width="17" height="13" border="0" align="absbottom" />!; | |
4d764c34 TC |
379 | |
380 | my %acts; | |
381 | %acts = | |
382 | ( | |
573f6f84 | 383 | $req->admin_tags, |
4d764c34 TC |
384 | catalogs => |
385 | sub { | |
386 | return popup_menu(-name=>'parentid', | |
387 | -values=>[ map $_->{id}, @catalogs ], | |
388 | -labels=>{ map { @$_{qw/id display/} } @catalogs }, | |
389 | -default=>($product->{parentid} || $PRODUCTPARENT)); | |
390 | }, | |
ea71fc0d | 391 | product => [ \&tag_article, $product, $req->cfg ], |
4d764c34 TC |
392 | action => sub { $action }, |
393 | message => sub { $message }, | |
394 | script=>sub { $ENV{SCRIPT_NAME} }, | |
395 | ifImage => sub { $product->{imageName} }, | |
396 | hiddenNote => sub { $product->{listed} ? " " : "Hidden" }, | |
4d764c34 TC |
397 | templates => |
398 | sub { | |
399 | return popup_menu(-name=>'template', -values=>\@templates, | |
400 | -default=>$product->{id} ? $product->{template} : | |
401 | $templates[0]); | |
402 | }, | |
403 | ifStepcats => sub { @stepcats }, | |
404 | iterate_stepcats_reset => sub { $stepcat_index = -1; }, | |
405 | iterate_stepcats => sub { ++$stepcat_index < @stepcats }, | |
406 | stepcat => sub { escape_html($stepcats[$stepcat_index]{$_[0]}) }, | |
407 | stepcat_targ => | |
408 | sub { | |
409 | escape_html($stepcat_targets[$stepcat_index]{$_[0]}); | |
410 | }, | |
411 | movestepcat => | |
412 | sub { | |
413 | my ($arg, $acts, $funcname, $templater) = @_; | |
414 | return '' | |
415 | unless $req->user_can(edit_reorder_stepparents => $product), | |
416 | @stepcats > 1 or return ''; | |
417 | my ($img_prefix, $urladd) = DevHelp::Tags->get_parms($arg, $acts, $templater); | |
418 | $img_prefix = '' unless defined $img_prefix; | |
419 | $urladd = '' unless defined $urladd; | |
420 | my $refreshto = escape_uri($ENV{SCRIPT_NAME} | |
421 | ."?id=$product->{id}&$template=1$urladd#step"); | |
422 | my $down_url = ""; | |
423 | if ($stepcat_index < $#stepcats) { | |
424 | $down_url = "$CGI_URI/admin/move.pl?stepchild=$product->{id}&id=$stepcats[$stepcat_index]{parentId}&d=swap&other=$stepcats[$stepcat_index+1]{parentId}&all=1"; | |
425 | } | |
426 | my $up_url = ""; | |
427 | if ($stepcat_index > 0) { | |
428 | $up_url = "$CGI_URI/admin/move.pl?stepchild=$product->{id}&id=$stepcats[$stepcat_index]{parentId}&d=swap&other=$stepcats[$stepcat_index-1]{parentId}&all=1"; | |
429 | } | |
430 | return make_arrows($req->cfg, $down_url, $up_url, $refreshto, $img_prefix); | |
431 | }, | |
432 | ifStepcatPossibles => sub { @stepcat_possibles }, | |
433 | stepcat_possibles => sub { | |
434 | popup_menu(-name=>'stepcat', | |
435 | -values=>[ map $_->{id}, @stepcat_possibles ], | |
436 | -labels=>{ map { $_->{id}, $_->{display}} @catalogs }); | |
437 | }, | |
438 | BSE::Util::Tags-> | |
439 | make_iterator(\@files, 'file', 'files', \$file_index), | |
440 | BSE::Util::Tags-> | |
441 | make_iterator(\@images, 'image', 'images', \$image_index), | |
442 | ); | |
443 | ||
444 | return $req->dyn_response($template, \%acts); | |
445 | } | |
446 | ||
833289fc TC |
447 | =item tag all_order_count |
448 | X<tags, shop admin, all_order_count>C<all_order_count> | |
449 | ||
450 | Returns a count of orders matching a set of conditions. | |
451 | ||
452 | =cut | |
453 | ||
454 | sub tag_all_order_count { | |
455 | my ($args, $acts, $funcname, $templater) = @_; | |
456 | ||
457 | my $query; | |
458 | if ($args =~ /\S/) { | |
459 | if (eval "\$query = [ $args ]; 1 ") { | |
460 | return BSE::TB::Orders->getCount($query); | |
461 | } | |
462 | else { | |
463 | return "<!-- error handling args: $@ -->"; | |
464 | } | |
465 | } | |
466 | else { | |
467 | return BSE::TB::Orders->getCount(); | |
468 | } | |
469 | } | |
470 | ||
4d764c34 TC |
471 | ##################### |
472 | # order management | |
473 | ||
474 | sub order_list_low { | |
833289fc | 475 | my ($req, $template, $title, $conds, $options) = @_; |
4d764c34 TC |
476 | |
477 | my $cgi = $req->cgi; | |
478 | ||
833289fc TC |
479 | $options ||= {}; |
480 | my $order = delete $options->{order}; | |
481 | defined $order or $order = 'id desc'; | |
482 | my $datelimit = delete $options->{datelimit}; | |
483 | defined $datelimit or $datelimit = 1; | |
484 | ||
4d764c34 TC |
485 | my $from = $cgi->param('from'); |
486 | my $to = $cgi->param('to'); | |
4d764c34 TC |
487 | my $today = now_sqldate(); |
488 | for my $what ($from, $to) { | |
489 | if (defined $what) { | |
490 | if ($what eq 'today') { | |
491 | $what = $today; | |
492 | } | |
493 | elsif (valid_date($what)) { | |
494 | $what = date_to_sql($what); | |
495 | } | |
496 | else { | |
497 | undef $what; | |
498 | } | |
499 | } | |
500 | } | |
833289fc TC |
501 | if ($datelimit) { |
502 | $from ||= sql_date(time() - 30 * 86_400); | |
503 | } | |
4d764c34 TC |
504 | if (defined $from || defined $to) { |
505 | $from ||= '1900-01-01'; | |
506 | $to ||= '2999-12-31'; | |
507 | $cgi->param('from', sql_to_date($from)); | |
508 | $cgi->param('to', sql_to_date($to)); | |
833289fc TC |
509 | push @$conds, |
510 | [ between => 'orderDate', $from, $to." 23:59:59" ]; | |
4d764c34 | 511 | } |
173020d7 TC |
512 | |
513 | my @simple_search_fields = qw/userId billEmail billFirstName billLastName billOrganization/; | |
514 | for my $key (@simple_search_fields) { | |
515 | my $value = $cgi->param($key); | |
516 | if (defined $value && $value =~ /\S/) { | |
517 | push @$conds, [ like => $key => '%' . $value . '%' ]; | |
518 | } | |
519 | } | |
520 | ||
521 | my @stage = grep /\S/, map split (",", $_), $cgi->param("stage"); | |
522 | if (@stage) { | |
523 | push @$conds, | |
524 | [ or => | |
525 | map [ stage => $_ ], @stage | |
526 | ]; | |
527 | } | |
528 | ||
529 | my $name = $cgi->param("name"); | |
530 | if (defined $name && $name =~ /\S/) { | |
531 | push @$conds, | |
532 | [ or => | |
533 | map [ like => $_ => '%' . $name . '%' ], | |
534 | qw(billFirstName billLastName billEmail userId) | |
535 | ]; | |
536 | } | |
537 | ||
833289fc TC |
538 | my @ids = BSE::TB::Orders->getColumnBy |
539 | ( | |
540 | "id", | |
541 | $conds, | |
542 | { order => $order } | |
543 | ); | |
544 | ||
545 | my $search_param; | |
546 | { | |
547 | my @param; | |
173020d7 TC |
548 | for my $key (qw(from to stage name), @simple_search_fields) { |
549 | for my $value (grep /\S/, $cgi->param($key)) { | |
833289fc TC |
550 | push @param, "$key=" . escape_uri($value); |
551 | } | |
552 | } | |
553 | $search_param = join('&', map escape_html($_), @param); | |
554 | } | |
555 | ||
556 | my $message = $cgi->param('m'); | |
557 | defined $message or $message = ''; | |
558 | $message = escape_html($message); | |
559 | ||
560 | my $it = BSE::Util::Iterate::Objects->new; | |
4d764c34 TC |
561 | my %acts; |
562 | %acts = | |
563 | ( | |
833289fc TC |
564 | $req->admin_tags, |
565 | $it->make_paged | |
566 | ( | |
567 | data => \@ids, | |
568 | fetch => [ getByPkey => 'BSE::TB::Orders' ], | |
569 | cgi => $req->cgi, | |
570 | single => "order", | |
571 | plural => "orders", | |
572 | session => $req->session, | |
573 | name => "orderlist", | |
574 | perpage_parm => "pp=50", | |
575 | ), | |
4d764c34 TC |
576 | title => sub { $title }, |
577 | ifHaveParam => sub { defined $cgi->param($_[0]) }, | |
578 | ifParam => sub { $cgi->param($_[0]) }, | |
4d764c34 | 579 | message => $message, |
833289fc TC |
580 | ifError => 0, |
581 | all_order_count => \&tag_all_order_count, | |
582 | search_param => $search_param, | |
173020d7 TC |
583 | query => sub { |
584 | require JSON; | |
585 | my $json = JSON->new; | |
586 | return $json->encode($conds); | |
587 | }, | |
588 | stage_select => [ \&tag_stage_select_search, $req ], | |
4d764c34 TC |
589 | ); |
590 | $req->dyn_response("admin/$template", \%acts); | |
591 | } | |
592 | ||
173020d7 TC |
593 | =item tag stage_select (search) |
594 | ||
595 | stage_select for order list filtering. | |
596 | ||
597 | =cut | |
598 | ||
599 | sub tag_stage_select_search { | |
600 | my ($req) = @_; | |
601 | ||
602 | my @stages = BSE::TB::Orders->settable_stages; | |
603 | unshift @stages, ""; | |
604 | ||
605 | my %stage_labels = BSE::TB::Orders->stage_labels; | |
606 | $stage_labels{""} = "(No stage filter)"; | |
607 | my $stage = $req->cgi->param("stage") || ""; | |
608 | return popup_menu | |
609 | ( | |
610 | -name => "stage", | |
611 | -values => \@stages, | |
612 | -default => $stage, | |
613 | -labels => \%stage_labels, | |
614 | ); | |
615 | } | |
616 | ||
4d764c34 TC |
617 | sub iter_orders { |
618 | my ($orders, $args) = @_; | |
619 | ||
620 | return bse_sort({ id => 'n', total => 'n', filled=>'n' }, $args, @$orders); | |
621 | } | |
622 | ||
833289fc TC |
623 | =item target order_list |
624 | X<shopadmin targets, order_list>X<order_list target> | |
625 | ||
626 | List all completed orders. | |
627 | ||
628 | By default limits to the last 30 days. | |
629 | ||
630 | =cut | |
631 | ||
4d764c34 TC |
632 | sub req_order_list { |
633 | my ($class, $req) = @_; | |
634 | ||
4d764c34 TC |
635 | my $template = $req->cgi->param('template'); |
636 | unless (defined $template && $template =~ /^\w+$/) { | |
637 | $template = 'order_list'; | |
638 | } | |
639 | ||
833289fc TC |
640 | my @conds = |
641 | ( | |
642 | [ '<>', complete => 0 ], | |
643 | ); | |
644 | ||
645 | return order_list_low($req, $template, 'Order list', \@conds); | |
4d764c34 TC |
646 | } |
647 | ||
833289fc TC |
648 | =item target order_list_filled |
649 | X<shopadmin targets, order_list_filled>X<order_list_filled target> | |
650 | ||
651 | List all filled orders. | |
652 | ||
653 | By default limits to the last 30 days. | |
654 | ||
655 | =cut | |
656 | ||
4d764c34 TC |
657 | sub req_order_list_filled { |
658 | my ($class, $req) = @_; | |
659 | ||
833289fc TC |
660 | my @conds = |
661 | ( | |
662 | [ '<>', complete => 0 ], | |
663 | [ '<>', filled => 0 ], | |
664 | #[ '<>', paidFor => 0 ], | |
665 | ); | |
4d764c34 | 666 | |
833289fc TC |
667 | return order_list_low($req, 'order_list_filled', 'Order list - Filled orders', |
668 | \@conds); | |
4d764c34 TC |
669 | } |
670 | ||
833289fc TC |
671 | =item target order_list_unfilled |
672 | X<shopadmin targets, order_list_unfilled>X<order_list_unfilled target> | |
673 | ||
674 | List completed but unfilled orders. | |
675 | ||
676 | Unlike the other order lists, this lists oldest order first, and does | |
677 | not limit to the last 30 days. | |
678 | ||
679 | =cut | |
680 | ||
4d764c34 TC |
681 | sub req_order_list_unfilled { |
682 | my ($class, $req) = @_; | |
683 | ||
833289fc TC |
684 | my @conds = |
685 | ( | |
686 | [ '<>', complete => 0 ], | |
687 | [ filled => 0 ], | |
688 | ); | |
4d764c34 TC |
689 | |
690 | return order_list_low($req, 'order_list_unfilled', | |
833289fc TC |
691 | 'Order list - Unfilled orders', |
692 | \@conds, { order => 'id asc', datelimit => 0 }); | |
4d764c34 TC |
693 | } |
694 | ||
695 | sub req_order_list_unpaid { | |
696 | my ($class, $req) = @_; | |
697 | ||
833289fc TC |
698 | my @conds = |
699 | ( | |
700 | [ '<>', complete => 0 ], | |
701 | [ paidFor => 0 ], | |
702 | ); | |
4d764c34 TC |
703 | |
704 | return order_list_low($req, 'order_list_unpaid', | |
833289fc | 705 | 'Order list - Unpaid orders', \@conds); |
4d764c34 TC |
706 | } |
707 | ||
833289fc TC |
708 | =item target order_list_incomplete |
709 | X<shopadmin targets, order_list_incomplete>X<order_list_incomplete> | |
710 | ||
711 | List incomplete orders, ie. orders that the user abandoned before the | |
712 | payment step was complete. | |
713 | ||
714 | By default limits to the last 30 days. | |
715 | ||
716 | =cut | |
717 | ||
4d764c34 TC |
718 | sub req_order_list_incomplete { |
719 | my ($class, $req) = @_; | |
720 | ||
833289fc TC |
721 | my @conds = |
722 | ( | |
723 | [ complete => 0 ] | |
724 | ); | |
4d764c34 TC |
725 | |
726 | return order_list_low($req, 'order_list_incomplete', | |
833289fc | 727 | 'Order list - Incomplete orders', \@conds); |
4d764c34 TC |
728 | } |
729 | ||
4d764c34 TC |
730 | sub tag_siteuser { |
731 | my ($order, $rsiteuser, $arg) = @_; | |
732 | ||
733 | unless ($$rsiteuser) { | |
734 | $$rsiteuser = $order->siteuser || {}; | |
735 | } | |
736 | ||
737 | my $siteuser = $$rsiteuser; | |
738 | return '' unless $siteuser->{id}; | |
739 | ||
740 | my $value = $siteuser->{$arg}; | |
741 | defined $value or $value = ''; | |
742 | ||
743 | return escape_html($value); | |
744 | } | |
745 | ||
080fc207 TC |
746 | sub tag_shipping_method_select { |
747 | my ($self, $order) = @_; | |
748 | ||
6f0799a4 | 749 | my @methods = all_shippers(); |
080fc207 TC |
750 | |
751 | return popup_menu | |
752 | ( | |
753 | -name => "shipping_name", | |
754 | -values => [ map $_->{id}, @methods ], | |
755 | -labels => { map { $_->{id} => $_->{name} } @methods }, | |
756 | -id => "shipping_name", | |
757 | -default => $order->shipping_name, | |
758 | ); | |
759 | } | |
760 | ||
f0722dd2 TC |
761 | sub tag_stage_select { |
762 | my ($self, $req, $order) = @_; | |
763 | ||
764 | my @stages = BSE::TB::Orders->settable_stages; | |
765 | ||
766 | my %stage_labels = BSE::TB::Orders->stage_labels; | |
767 | return popup_menu | |
768 | ( | |
769 | -name => "stage", | |
770 | -values => \@stages, | |
771 | -default => $order->stage, | |
772 | -labels => \%stage_labels, | |
773 | ); | |
774 | } | |
775 | ||
b62cae00 TC |
776 | =item target order_detail |
777 | ||
778 | Display the details of an order. | |
779 | ||
780 | Variables set: | |
781 | ||
782 | =over | |
783 | ||
784 | =item * | |
785 | ||
786 | order - the order being displayed | |
787 | ||
788 | =item * | |
789 | ||
790 | payment_types - a list of configured payment types | |
791 | ||
792 | =item * | |
793 | ||
794 | payment_type_desc - a description of the current payment type | |
795 | ||
796 | =back | |
797 | ||
798 | =cut | |
799 | ||
4d764c34 | 800 | sub req_order_detail { |
080fc207 | 801 | my ($class, $req, $errors) = @_; |
4d764c34 TC |
802 | |
803 | my $cgi = $req->cgi; | |
804 | my $id = $cgi->param('id'); | |
805 | if ($id and | |
806 | my $order = BSE::TB::Orders->getByPkey($id)) { | |
080fc207 | 807 | my $message = $req->message($errors); |
4d764c34 TC |
808 | my @lines = $order->items; |
809 | my @products = map { Products->getByPkey($_->{productId}) } @lines; | |
810 | my $line_index = -1; | |
811 | my $product; | |
812 | my @options; | |
813 | my $option_index = -1; | |
814 | my $siteuser; | |
080fc207 TC |
815 | my $it = BSE::Util::Iterate->new; |
816 | ||
b62cae00 TC |
817 | $req->set_variable(order => $order); |
818 | my @pay_types = payment_types(); | |
819 | $req->set_variable(payment_types => \@pay_types); | |
820 | my ($pay_type) = grep $_->{id} == $order->paymentType, @pay_types; | |
821 | $req->set_variable(payment_type_desc => $pay_type ? $pay_type->{desc} : "Unknown"); | |
4d764c34 TC |
822 | my %acts; |
823 | %acts = | |
824 | ( | |
080fc207 | 825 | $req->admin_tags, |
4d764c34 TC |
826 | item => sub { escape_html($lines[$line_index]{$_[0]}) }, |
827 | iterate_items_reset => sub { $line_index = -1 }, | |
828 | iterate_items => | |
829 | sub { | |
830 | if (++$line_index < @lines ) { | |
831 | $option_index = -1; | |
58baa27b TC |
832 | @options = order_item_opts($req, |
833 | $lines[$line_index], | |
834 | $products[$line_index]); | |
4d764c34 TC |
835 | return 1; |
836 | } | |
837 | return 0; | |
838 | }, | |
f0722dd2 | 839 | order => [ \&tag_object, $order ], |
4d764c34 TC |
840 | extension => |
841 | sub { | |
842 | sprintf("%.2f", $lines[$line_index]{units} * $lines[$line_index]{$_[0]}/100.0) | |
843 | }, | |
65c27d05 | 844 | product => sub { tag_article($products[$line_index], $req->cfg, $_[0]) }, |
4d764c34 TC |
845 | script => sub { $ENV{SCRIPT_NAME} }, |
846 | iterate_options_reset => sub { $option_index = -1 }, | |
847 | iterate_options => sub { ++$option_index < @options }, | |
c4f0aab9 | 848 | option => sub { escape_html($options[$option_index]{$_[0]}) }, |
4d764c34 TC |
849 | ifOptions => sub { @options }, |
850 | options => sub { nice_options(@options) }, | |
080fc207 TC |
851 | message => $message, |
852 | error_img => [ \&tag_error_img, $errors ], | |
4d764c34 | 853 | siteuser => [ \&tag_siteuser, $order, \$siteuser, ], |
080fc207 TC |
854 | $it->make |
855 | ( | |
856 | single => "shipping_method", | |
857 | plural => "shipping_methods", | |
6f0799a4 | 858 | code => \&all_shippers, |
080fc207 TC |
859 | ), |
860 | shipping_method_select => | |
861 | [ tag_shipping_method_select => $class, $order ], | |
f0722dd2 TC |
862 | stage_select => |
863 | [ tag_stage_select => $class, $req, $order ], | |
864 | stage_description => escape_html($order->stage_description($req->language)), | |
4d764c34 TC |
865 | ); |
866 | ||
867 | return $req->dyn_response('admin/order_detail', \%acts); | |
868 | } | |
869 | else { | |
870 | return $class->req_order_list($req); | |
871 | } | |
872 | } | |
873 | ||
874 | sub req_order_filled { | |
875 | my ($class, $req) = @_; | |
876 | ||
877 | my $id = $req->cgi->param('id'); | |
878 | if ($id and | |
879 | my $order = BSE::TB::Orders->getByPkey($id)) { | |
880 | my $filled = $req->cgi->param('filled'); | |
881 | $order->{filled} = $filled; | |
882 | if ($order->{filled}) { | |
883 | $order->{whenFilled} = epoch_to_sql_datetime(time); | |
884 | my $user = $req->user; | |
885 | if ($user) { | |
886 | $order->{whoFilled} = $user->{logon}; | |
887 | } | |
888 | else { | |
889 | $order->{whoFilled} = defined($ENV{REMOTE_USER}) | |
890 | ? $ENV{REMOTE_USER} : "-unknown-"; | |
891 | } | |
892 | } | |
893 | $order->save(); | |
894 | if ($req->cgi->param('detail')) { | |
895 | return $class->req_order_detail($req); | |
896 | } | |
897 | else { | |
898 | return $class->req_order_list($req); | |
899 | } | |
900 | } | |
901 | else { | |
902 | return $class->req_order_list($req); | |
903 | } | |
904 | } | |
905 | ||
b62cae00 TC |
906 | =item target order_paid |
907 | ||
908 | Mark the order identified by C<id> as paid. | |
909 | ||
910 | Optionally accepts C<paymentType> which replaces the current payment | |
911 | type. | |
912 | ||
913 | Requires csrfp token C<shop_order_paid>. | |
914 | ||
915 | =cut | |
916 | ||
14604ada | 917 | sub req_order_paid { |
b62cae00 | 918 | my ($self, $req) = @_; |
14604ada | 919 | |
b62cae00 TC |
920 | $req->check_csrf("shop_order_paid") |
921 | or return $self->req_order_list($req, "Bad or missing csrf token: " . $req->csrf_error); | |
922 | ||
923 | return $self->_set_order_paid($req, 1); | |
14604ada TC |
924 | } |
925 | ||
b62cae00 TC |
926 | =item target order_unpaid |
927 | ||
928 | Mark the order identified by C<id> as unpaid. | |
929 | ||
930 | Requires csrfp token C<shop_order_unpaid>. | |
931 | ||
932 | =cut | |
933 | ||
14604ada | 934 | sub req_order_unpaid { |
b62cae00 | 935 | my ($self, $req) = @_; |
14604ada | 936 | |
b62cae00 TC |
937 | $req->check_csrf("shop_order_unpaid") |
938 | or return $self->req_order_list($req, "Bad or missing csrf token: " . $req->csrf_error); | |
939 | ||
940 | return $self->_set_order_paid($req, 0); | |
14604ada TC |
941 | } |
942 | ||
943 | sub _set_order_paid { | |
944 | my ($class, $req, $value) = @_; | |
945 | ||
946 | my $id = $req->cgi->param('id'); | |
947 | if ($id and | |
948 | my $order = BSE::TB::Orders->getByPkey($id)) { | |
949 | if ($order->paidFor != $value) { | |
950 | if ($value) { | |
b62cae00 TC |
951 | my $pay_type = $req->cgi->param("paymentType"); |
952 | if (defined $pay_type && $pay_type =~ /^[0-9]+$/) { | |
953 | $order->set_paymentType($pay_type); | |
954 | } | |
14604ada TC |
955 | } |
956 | else { | |
b62cae00 | 957 | $order->is_manually_paid |
14604ada TC |
958 | or return $class->req_order_detail($req, "You can only unpay manually paid orders"); |
959 | } | |
960 | ||
961 | $order->set_paidFor($value); | |
b62cae00 | 962 | |
7070c3db TC |
963 | # we want to reset paid_manually if we reset paidFor, so if the |
964 | # customer pays via the public interface the order doesn't get | |
965 | # treated as manually paid | |
966 | $order->set_paid_manually($value); | |
967 | ||
b62cae00 TC |
968 | if ($value) { |
969 | $req->audit | |
970 | ( | |
971 | component => "shopadmin:order:paid", | |
d9c45dcc | 972 | level => "notice", |
b62cae00 | 973 | object => $order, |
68d44fe0 | 974 | msg => "Mark Order No. " . $order->id . " as Paid", |
b62cae00 TC |
975 | ); |
976 | } | |
977 | else { | |
978 | $req->audit | |
979 | ( | |
980 | component => "shopadmin:order:unpaid", | |
d9c45dcc | 981 | level => "notice", |
b62cae00 | 982 | object => $order, |
68d44fe0 | 983 | msg => "Mark Order No. " . $order->id . " as Unpaid", |
b62cae00 TC |
984 | ); |
985 | } | |
14604ada TC |
986 | $order->save(); |
987 | } | |
988 | ||
1b0b485d TC |
989 | return $req->get_refresh |
990 | ($req->url("shopadmin", { a_order_detail => 1, id => $id })); | |
14604ada TC |
991 | } |
992 | else { | |
993 | return $class->req_order_list($req); | |
994 | } | |
995 | } | |
996 | ||
13a986ee TC |
997 | sub req_paypal_refund { |
998 | my ($self, $req) = @_; | |
999 | ||
1000 | my $id = $req->cgi->param('id'); | |
1001 | if ($id and | |
1002 | my $order = BSE::TB::Orders->getByPkey($id)) { | |
1003 | require BSE::PayPal; | |
1004 | my $msg; | |
1005 | unless (BSE::PayPal->refund_order(order => $order, | |
1006 | req => $req, | |
1007 | msg => \$msg)) { | |
1008 | return $self->req_order_detail($req, $msg); | |
1009 | } | |
1010 | ||
1011 | return $req->get_refresh($req->url(shopadmin => { "a_order_detail" => 1, id => $id })); | |
1012 | } | |
1013 | else { | |
1014 | $req->flash_error("Missing or invalid order id"); | |
1015 | return $self->req_order_list($req); | |
1016 | } | |
1017 | } | |
1018 | ||
080fc207 TC |
1019 | =item order_save |
1020 | ||
1021 | Make changes to an order, only a limited set of fields can be changed. | |
1022 | ||
f0722dd2 | 1023 | Parameters, all optional: |
080fc207 TC |
1024 | |
1025 | =over | |
1026 | ||
1027 | =item * | |
1028 | ||
1029 | id - id of the order. Required. | |
1030 | ||
1031 | =item * | |
1032 | ||
1033 | shipping_method - if automated shipping calculations are disabled, the | |
1034 | id of the dummy shipping method to set for the order. | |
1035 | ||
1036 | =item * | |
1037 | ||
1038 | freight_tracking - the freight tracking code for the shipment. | |
1039 | ||
f0722dd2 TC |
1040 | =item * |
1041 | ||
1042 | stage - order stage, one of unprocessed, backorder, picked, shipped, | |
1043 | cancelled. | |
1044 | ||
080fc207 TC |
1045 | =back |
1046 | ||
1047 | Requires csrfp token C<shop_order_save>. | |
1048 | ||
1049 | =cut | |
1050 | ||
1051 | sub req_order_save { | |
1052 | my ($self, $req) = @_; | |
1053 | ||
1054 | $req->check_csrf("shop_order_save") | |
1055 | or return $self->req_product_list($req, "Bad or missing csrf token: " . $req->csrf_error); | |
1056 | ||
1057 | my $cgi = $req->cgi; | |
1058 | my $id = $cgi->param("id"); | |
1059 | $id && $id =~ /^[0-9]+$/ | |
1060 | or return $self->req_product_list($req, "No order id supplied"); | |
1061 | ||
1062 | my $order = BSE::TB::Orders->getByPkey($id) | |
1063 | or return $self->req_product_list($req, "No such order id"); | |
1064 | ||
1065 | my %errors; | |
1066 | my $save = 0; | |
1067 | ||
1068 | my $new_freight_tracking = 0; | |
1069 | my $code = $cgi->param("freight_tracking"); | |
1070 | if (defined $code && $code ne $order->freight_tracking) { | |
1071 | $order->set_freight_tracking($code); | |
1072 | ++$new_freight_tracking; | |
1073 | ++$save; | |
1074 | } | |
1075 | ||
1076 | my $new_shipping_name = 0; | |
6f0799a4 TC |
1077 | my $shipping_name = $cgi->param("shipping_name"); |
1078 | if (defined $shipping_name | |
1079 | && $shipping_name ne $order->shipping_name) { | |
1080 | my @ship = all_shippers(); | |
1081 | my ($entry) = grep $_->{id} eq $shipping_name, @ship; | |
1082 | if ($entry) { | |
1083 | $order->set_shipping_name($entry->{id}); | |
1084 | $order->set_shipping_method($entry->{name}); | |
1085 | ++$new_shipping_name; | |
1086 | ++$save; | |
1087 | } | |
1088 | else { | |
1089 | $errors{shipping_method} = "msg:bse/admin/shop/saveorder/badmethod:$shipping_name"; | |
080fc207 TC |
1090 | } |
1091 | } | |
1092 | ||
f0722dd2 TC |
1093 | my $new_stage = 0; |
1094 | my ($stage) = $cgi->param("stage"); | |
1095 | my ($stage_note) = $cgi->param("stage_note"); | |
1096 | if (defined $stage && $stage ne $order->stage | |
1097 | || defined $stage_note && $stage_note =~ /\S/) { | |
1098 | my @stages = BSE::TB::Orders->settable_stages; | |
1099 | if (grep $_ eq $stage, @stages) { | |
1100 | ++$new_stage; | |
1101 | ++$save; | |
1102 | } | |
1103 | else { | |
1104 | $errors{stage} = "msg:bse/admin/shop/saveorder/badstage:$stage"; | |
1105 | } | |
1106 | } | |
1107 | ||
080fc207 TC |
1108 | keys %errors |
1109 | and return $self->req_order_detail($req, \%errors); | |
1110 | ||
1111 | if ($save) { | |
080fc207 TC |
1112 | if ($new_freight_tracking) { |
1113 | $req->audit | |
1114 | ( | |
1115 | component => "shopadmin:orders:saveorder", | |
1116 | object => $order, | |
68d44fe0 | 1117 | msg => "Set Order No. " . $order->id . " freight tracking code to '" . $order->freight_tracking . "'", |
d9c45dcc | 1118 | level => "notice", |
080fc207 TC |
1119 | ); |
1120 | } | |
1121 | if ($new_shipping_name) { | |
1122 | $req->audit | |
1123 | ( | |
1124 | component => "shopadmin:orders:saveorder", | |
1125 | object => $order, | |
68d44fe0 | 1126 | msg => "Set Order No. " . $order->id . " shippping method to '" . $order->shipping_name . "/" . $order->shipping_method . "'", |
d9c45dcc | 1127 | level => "notice", |
080fc207 TC |
1128 | ); |
1129 | } | |
f0722dd2 TC |
1130 | if ($new_stage) { |
1131 | $order->new_stage(scalar $req->user, $stage, $stage_note); | |
1132 | } | |
1133 | ||
1134 | $order->save; | |
1135 | $req->flash("msg:bse/admin/shop/saveorder/saved"); | |
080fc207 TC |
1136 | } |
1137 | else { | |
1138 | $req->flash("msg:bse/admin/shop/saveorder/nochanges"); | |
1139 | } | |
1140 | ||
1141 | my $url = $cgi->param("r") || $req->url("shopadmin", { a_order_detail => 1, id => $order->id }); | |
1142 | ||
1143 | return $req->get_refresh($url); | |
1144 | } | |
1145 | ||
023761bd TC |
1146 | my %coupon_sorts = |
1147 | ( | |
1148 | expiry => "expiry desc", | |
1149 | release => "release desc", | |
1150 | code => "code asc", | |
1151 | ); | |
1152 | ||
1153 | =item coupon_list | |
1154 | ||
1155 | Display a list of coupons. | |
1156 | ||
1157 | Accepts two optional parameters: | |
1158 | ||
1159 | =over | |
1160 | ||
1161 | =item * | |
1162 | ||
1163 | C<sort> which can be any of: | |
1164 | ||
1165 | =over | |
1166 | ||
1167 | =item * | |
1168 | ||
1169 | C<expiry> - sort by expiry date descending | |
1170 | ||
1171 | =item * | |
1172 | ||
1173 | C<release> - sort by release date descending | |
1174 | ||
1175 | =item * | |
1176 | ||
1177 | C<code> - sort by code ascending | |
1178 | ||
1179 | =back | |
1180 | ||
1181 | The default and fallback for unknown values is C<expiry>. | |
1182 | ||
1183 | =item * | |
1184 | ||
1185 | C<all> - if a true value, returns all coupons, otherwise only coupons | |
1186 | modified in the last 60 days, or with a release or expiry date in the | |
1187 | last 60 days are returned. | |
1188 | ||
1189 | =back | |
1190 | ||
1191 | Allows standard admin tags and variables with the following additional | |
1192 | variable: | |
1193 | ||
1194 | =over | |
1195 | ||
1196 | =item * | |
1197 | ||
1198 | C<coupons> - an array of coupons | |
1199 | ||
1200 | =item * | |
1201 | ||
1202 | C<coupons_all> - true if all coupons were requested | |
1203 | ||
1204 | =item * | |
1205 | ||
1206 | C<coupons_sort> - the | |
1207 | ||
1208 | =back | |
1209 | ||
1210 | In ajax context returns: | |
1211 | ||
1212 | { | |
1213 | success => 1, | |
1214 | coupons => [ coupon, ... ] | |
1215 | } | |
1216 | ||
1217 | where each coupon is a hash containing the coupon data, and the key | |
1218 | tiers is a list of tier ids. | |
1219 | ||
1220 | Template: F<admin/coupons/list> | |
1221 | ||
1222 | =cut | |
1223 | ||
1224 | sub req_coupon_list { | |
1225 | my ($self, $req) = @_; | |
1226 | ||
1227 | my $sort = $req->cgi->param('sort') || 'expiry'; | |
1228 | $sort =~ /^(expiry|code|release)/ or $sort = 'expiry'; | |
1229 | my $all = $req->cgi->param('all') || 0; | |
1230 | my @cond; | |
1231 | unless ($all) { | |
1232 | my $past_60_days = sql_datetime(time() - 60 * 86_400); | |
1233 | @cond = | |
1234 | ( | |
1235 | [ or => | |
1236 | [ '>', last_modified => $past_60_days ], | |
1237 | [ '>', expiry => $past_60_days ], | |
1238 | [ '>', release => $past_60_days ], | |
1239 | ] | |
1240 | ); | |
1241 | } | |
1242 | my $scode = $req->cgi->param('scode'); | |
1243 | if ($scode) { | |
1244 | if ($scode =~ /^=(.*)/) { | |
1245 | push @cond, [ '=', code => $1 ]; | |
1246 | } | |
1247 | else { | |
1248 | push @cond, [ 'like', code => $scode . '%' ]; | |
1249 | } | |
1250 | } | |
1251 | require BSE::TB::Coupons; | |
1252 | my @coupons = BSE::TB::Coupons->getBy2 | |
1253 | ( | |
1254 | \@cond, | |
1255 | { order => $coupon_sorts{$sort} } | |
1256 | ); | |
1257 | ||
1258 | if ($req->is_ajax) { | |
1259 | return $req->json_content | |
1260 | ( | |
1261 | success => 1, | |
1262 | coupons => [ map $_->json_data, @coupons ], | |
1263 | ); | |
1264 | } | |
1265 | ||
1266 | $req->set_variable(coupons => \@coupons); | |
1267 | $req->set_variable(coupons_all => $all); | |
1268 | $req->set_variable(coupons_sort => $sort); | |
1269 | ||
1270 | my %acts = $req->admin_tags; | |
1271 | ||
1272 | return $req->dyn_response('admin/coupons/list', \%acts); | |
1273 | } | |
1274 | ||
1275 | =item coupon_addform | |
1276 | ||
1277 | Display a form for adding new coupons. | |
1278 | ||
1279 | Template: F<admin/coupons/add> | |
1280 | ||
78e38142 TC |
1281 | Template variables: |
1282 | ||
1283 | =over | |
1284 | ||
1285 | =item * | |
1286 | ||
1287 | C<fields> - coupon fields. | |
1288 | ||
1289 | =item * | |
1290 | ||
1291 | C<coupon> - set to undef. | |
1292 | ||
1293 | =item * | |
1294 | ||
1295 | C<errors> - an errors from an attempted save. | |
1296 | ||
1297 | =item * | |
1298 | ||
1299 | C<tiers> - a list of defined price tiers. | |
1300 | ||
1301 | =back | |
1302 | ||
023761bd TC |
1303 | =cut |
1304 | ||
1305 | sub req_coupon_addform { | |
1306 | my ($self, $req, $errors) = @_; | |
1307 | ||
1308 | my %acts = $req->admin_tags; | |
1309 | ||
1310 | $req->message($errors); | |
1311 | ||
1312 | require BSE::TB::Coupons; | |
1313 | $req->set_variable(fields => BSE::TB::Coupon->fields); | |
1314 | $req->set_variable(coupon => undef); | |
1315 | $req->set_variable(errors => $errors || {}); | |
78e38142 TC |
1316 | require BSE::TB::PriceTiers; |
1317 | $req->set_variable(tiers => [ BSE::TB::PriceTiers->all ]); | |
023761bd TC |
1318 | |
1319 | return $req->dyn_response("admin/coupons/add", \%acts); | |
1320 | } | |
1321 | ||
1322 | =item coupon_add | |
1323 | ||
1324 | Add a new coupon. | |
1325 | ||
1326 | Accepts coupon fields. | |
1327 | ||
1328 | Tiers are accepted as separate values for the tiers field. | |
1329 | ||
1330 | CSRF token: C<admin_bse_coupon_add> | |
1331 | ||
1332 | =cut | |
1333 | ||
1334 | sub req_coupon_add { | |
1335 | my ($self, $req) = @_; | |
1336 | ||
1337 | require BSE::TB::Coupons; | |
1338 | my $fields = BSE::TB::Coupon->fields; | |
1339 | my %errors; | |
1340 | $req->validate(fields => $fields, errors => \%errors, | |
1341 | rules => BSE::TB::Coupon->rules); | |
1342 | ||
1343 | my $values = $req->cgi_fields(fields => $fields); | |
1344 | ||
1345 | unless ($errors{code}) { | |
1346 | my ($other) = BSE::TB::Coupons->getBy(code => $values->{code}); | |
1347 | $other | |
1348 | and $errors{code} = "msg:bse/admin/shop/coupons/adddup:$values->{code}"; | |
1349 | } | |
1350 | ||
1351 | if (keys %errors) { | |
1352 | $req->is_ajax | |
1353 | and return $req->field_error(\%errors); | |
1354 | return $self->req_coupon_addform($req, \%errors); | |
1355 | } | |
1356 | ||
1357 | my $coupon = BSE::TB::Coupons->make(%$values); | |
1358 | ||
7f8a63a1 TC |
1359 | $req->audit |
1360 | ( | |
1361 | component => "shopadmin:coupon:add", | |
88c9c249 | 1362 | level => "notice", |
7f8a63a1 TC |
1363 | msg => "Coupon '" . $coupon->code . "' created", |
1364 | object => $coupon, | |
1365 | dump => $coupon->json_data, | |
1366 | ); | |
1367 | ||
023761bd TC |
1368 | if ($req->is_ajax) { |
1369 | return $req->json_content | |
1370 | ( | |
1371 | success => 1, | |
1372 | coupon => $coupon->json_data, | |
1373 | ); | |
1374 | } | |
1375 | else { | |
1376 | $req->flash_notice("msg:bse/admin/shop/coupons/add", [ $coupon ]); | |
1377 | ||
1378 | return $req->get_def_refresh($req->cfg->admin_url2("shopadmin", "coupon_list")); | |
1379 | } | |
1380 | } | |
1381 | ||
1382 | sub _get_coupon { | |
1383 | my ($self, $req, $rresult) = @_; | |
1384 | ||
1385 | my $cgi = $req->cgi; | |
1386 | my $id = $cgi->param("id"); | |
1387 | require BSE::TB::Coupons; | |
1388 | my $coupon; | |
1389 | if ($id) { | |
1390 | $coupon = BSE::TB::Coupons->getByPkey($id); | |
1391 | } | |
1392 | else { | |
1393 | my $code = $cgi->param("code"); | |
1394 | if ($code) { | |
1395 | ($coupon) = BSE::TB::Coupons->getBy(code => $code); | |
1396 | } | |
1397 | } | |
1398 | unless ($coupon) { | |
1399 | $$rresult = $self->req_coupon_list($req, { id => "Missing id or code" }); | |
1400 | return; | |
1401 | } | |
1402 | ||
1403 | return $coupon; | |
1404 | } | |
1405 | ||
1406 | sub _get_coupon_id { | |
1407 | my ($self, $req, $rresult) = @_; | |
1408 | ||
1409 | my $cgi = $req->cgi; | |
1410 | my $id = $cgi->param("id"); | |
1411 | require BSE::TB::Coupons; | |
1412 | my $coupon; | |
1413 | if ($id) { | |
1414 | $coupon = BSE::TB::Coupons->getByPkey($id); | |
1415 | } | |
1416 | unless ($coupon) { | |
1417 | $$rresult = $self->req_coupon_list($req, { id => "Missing id or code" }); | |
1418 | return; | |
1419 | } | |
1420 | ||
1421 | return $coupon; | |
1422 | } | |
1423 | ||
1424 | =item coupon_edit | |
1425 | ||
1426 | Edit a coupon. | |
1427 | ||
1428 | Requires C<id> as a coupon id to edit. | |
1429 | ||
1430 | Template: F<admin/coupons/edit> | |
1431 | ||
78e38142 TC |
1432 | Template variables: |
1433 | ||
1434 | =over | |
1435 | ||
1436 | =item * | |
1437 | ||
1438 | C<fields> - coupon fields. | |
1439 | ||
1440 | =item * | |
1441 | ||
1442 | C<coupon> - the coupon being edited | |
1443 | ||
1444 | =item * | |
1445 | ||
1446 | C<errors> - an errors from an attempted save. | |
1447 | ||
1448 | =item * | |
1449 | ||
1450 | C<tiers> - a list of defined price tiers. | |
1451 | ||
1452 | =back | |
1453 | ||
023761bd TC |
1454 | =cut |
1455 | ||
1456 | sub req_coupon_edit { | |
1457 | my ($self, $req, $errors) = @_; | |
1458 | ||
1459 | my $result; | |
1460 | my $coupon = $self->_get_coupon_id($req, \$result) | |
1461 | or return $result; | |
1462 | ||
1463 | my %acts = $req->admin_tags; | |
1464 | ||
1465 | $req->message($errors); | |
1466 | ||
1467 | require BSE::TB::Coupons; | |
2ced88e0 | 1468 | $req->set_variable(fields => $coupon->fields); |
023761bd TC |
1469 | $req->set_variable(coupon => $coupon); |
1470 | $req->set_variable(errors => $errors || {}); | |
78e38142 TC |
1471 | require BSE::TB::PriceTiers; |
1472 | $req->set_variable(tiers => [ BSE::TB::PriceTiers->all ]); | |
023761bd TC |
1473 | |
1474 | return $req->dyn_response("admin/coupons/edit", \%acts); | |
1475 | } | |
1476 | ||
1477 | =item coupon_save | |
1478 | ||
1479 | Save changes to a coupon, accepts: | |
1480 | ||
1481 | =over | |
1482 | ||
1483 | =item * | |
1484 | ||
1485 | C<id> - id of the coupon to save. | |
1486 | ||
1487 | =item * | |
1488 | ||
1489 | other coupon fields. | |
1490 | ||
1491 | =back | |
1492 | ||
1493 | CSRF token: C<admin_bse_coupon_save> | |
1494 | ||
1495 | =cut | |
1496 | ||
1497 | sub req_coupon_save { | |
1498 | my ($self, $req) = @_; | |
1499 | ||
1500 | my $result; | |
1501 | my $coupon = $self->_get_coupon_id($req, \$result) | |
1502 | or return $result; | |
1503 | ||
1504 | require BSE::TB::Coupons; | |
2ced88e0 | 1505 | my $fields = $coupon->fields; |
023761bd TC |
1506 | my %errors; |
1507 | $req->validate(fields => $fields, errors => \%errors, | |
1508 | rules => BSE::TB::Coupon->rules); | |
1509 | ||
1510 | my $values = $req->cgi_fields(fields => $fields); | |
1511 | ||
1512 | unless ($errors{code}) { | |
1513 | my ($other) = BSE::TB::Coupons->getBy(code => $values->{code}); | |
1514 | $other && $other->id != $coupon->id | |
1515 | and $errors{code} = "msg:bse/admin/shop/coupons/editdup:$values->{code}"; | |
1516 | } | |
1517 | ||
1518 | if (keys %errors) { | |
1519 | $req->is_ajax | |
1520 | and return $req->field_error(\%errors); | |
1521 | return $self->req_coupon_edit($req, \%errors); | |
1522 | } | |
1523 | ||
7f8a63a1 TC |
1524 | my $old = $coupon->json_data; |
1525 | ||
023761bd TC |
1526 | my $tiers = delete $values->{tiers}; |
1527 | for my $key (keys %$values) { | |
1528 | $coupon->set($key => $values->{$key}); | |
1529 | } | |
1530 | $coupon->set_tiers($tiers); | |
1531 | $coupon->save; | |
1532 | ||
7f8a63a1 TC |
1533 | $req->audit |
1534 | ( | |
1535 | component => "shopadmin:coupon:edit", | |
88c9c249 | 1536 | level => "notice", |
7f8a63a1 TC |
1537 | msg => "Coupon '" . $coupon->code . "' modified", |
1538 | object => $coupon, | |
1539 | dump => | |
1540 | { | |
1541 | old => $old, | |
1542 | new => $coupon->json_data, | |
1543 | type => "edit", | |
1544 | } | |
1545 | ); | |
1546 | ||
023761bd TC |
1547 | if ($req->is_ajax) { |
1548 | return $req->json_content | |
1549 | ( | |
1550 | success => 1, | |
1551 | coupon => $coupon->json_data, | |
1552 | ); | |
1553 | } | |
1554 | else { | |
1555 | $req->flash_notice("msg:bse/admin/shop/coupons/save", [ $coupon ]); | |
1556 | ||
1557 | return $req->get_def_refresh($req->cfg->admin_url2("shopadmin", "coupon_list")); | |
1558 | } | |
1559 | } | |
1560 | ||
1561 | =item coupon_deleteform | |
1562 | ||
1563 | Prompt for deletion of a coupon | |
1564 | ||
1565 | Requires C<id> as a coupon id to elete. | |
1566 | ||
1567 | Template: F<admin/coupons/delete> | |
1568 | ||
1569 | =cut | |
1570 | ||
1571 | sub req_coupon_deleteform { | |
1572 | my ($self, $req) = @_; | |
1573 | ||
1574 | my $result; | |
1575 | my $coupon = $self->_get_coupon_id($req, \$result) | |
1576 | or return $result; | |
1577 | ||
2ced88e0 TC |
1578 | unless ($coupon->is_removable) { |
1579 | $req->flash_error("msg:bse/admin/shop/coupons/not_deletable", [ $coupon ]); | |
1580 | return $self->req_coupon_list($req); | |
1581 | } | |
1582 | ||
023761bd TC |
1583 | my %acts = $req->admin_tags; |
1584 | ||
1585 | require BSE::TB::Coupons; | |
1586 | $req->set_variable(fields => BSE::TB::Coupon->fields); | |
1587 | $req->set_variable(coupon => $coupon); | |
1588 | ||
1589 | return $req->dyn_response("admin/coupons/delete", \%acts); | |
1590 | } | |
1591 | ||
1592 | =item coupon_delete | |
1593 | ||
1594 | Delete a coupon | |
1595 | ||
1596 | Requires C<id> as a coupon id to delete. | |
1597 | ||
1598 | CSRF token: C<admin_bse_coupon_delete> | |
1599 | ||
1600 | =cut | |
1601 | ||
1602 | sub req_coupon_delete { | |
1603 | my ($self, $req) = @_; | |
1604 | ||
1605 | my $result; | |
1606 | my $coupon = $self->_get_coupon_id($req, \$result) | |
1607 | or return $result; | |
1608 | ||
2ced88e0 TC |
1609 | unless ($coupon->is_removable) { |
1610 | $req->flash_error("msg:bse/admin/shop/coupons/not_deletable", [ $coupon ]); | |
1611 | return $self->req_coupon_list($req); | |
1612 | } | |
1613 | ||
023761bd | 1614 | my $code = $coupon->code; |
7f8a63a1 TC |
1615 | |
1616 | $req->audit | |
1617 | ( | |
1618 | component => "shopadmin:coupon:delete", | |
88c9c249 | 1619 | level => "notice", |
7f8a63a1 TC |
1620 | msg => "Coupon '$code' deleted", |
1621 | object => $coupon, | |
1622 | dump => $coupon->json_data, | |
1623 | ); | |
1624 | ||
023761bd TC |
1625 | $coupon->remove; |
1626 | ||
1627 | if ($req->is_ajax) { | |
1628 | return $req->json_content(success => 1); | |
1629 | } | |
1630 | else { | |
1631 | $req->flash_notice("msg:bse/admin/shop/coupons/delete", [ $code ]); | |
1632 | ||
1633 | return $req->get_def_refresh($req->cfg->admin_url2("shopadmin", "coupon_list")); | |
1634 | } | |
1635 | } | |
1636 | ||
4d764c34 TC |
1637 | ##################### |
1638 | # utilities | |
1639 | # perhaps some of these belong in a class... | |
1640 | ||
4d764c34 TC |
1641 | |
1642 | # convert an epoch time to sql format | |
1643 | sub epoch_to_sql { | |
1644 | use POSIX 'strftime'; | |
1645 | my ($time) = @_; | |
1646 | ||
1647 | return strftime('%Y-%m-%d', localtime $time); | |
1648 | } | |
1649 | ||
1650 | # convert an epoch time to sql format | |
1651 | sub epoch_to_sql_datetime { | |
1652 | use POSIX 'strftime'; | |
1653 | my ($time) = @_; | |
1654 | ||
1655 | return strftime('%Y-%m-%d %H:%M', localtime $time); | |
1656 | } | |
1657 | ||
6f0799a4 TC |
1658 | |
1659 | sub all_shippers { | |
1660 | require BSE::Shipping; | |
1661 | ||
1662 | my $cfg = BSE::Cfg->single; | |
1663 | my @shippers = BSE::TB::Orders->dummy_shipping_methods; | |
1664 | if ($cfg->entry("shop", "shipping", 0)) { | |
1665 | my @normal = BSE::Shipping->get_couriers($cfg); | |
1666 | push @shippers, map | |
1667 | +{ | |
1668 | id => $_->name, | |
1669 | name => $_->description | |
1670 | }, @normal; | |
1671 | } | |
1672 | ||
1673 | return @shippers; | |
1674 | } | |
1675 | ||
4d764c34 TC |
1676 | 1; |
1677 | ||
1678 | __END__ | |
1679 | ||
1680 | =head1 NAME | |
1681 | ||
1682 | shopadmin.pl - administration for the online-store tables | |
1683 | ||
1684 | =head1 SYNOPSYS | |
1685 | ||
1686 | (This is a CGI script.) | |
1687 | ||
1688 | =head1 DESCRIPTION | |
1689 | ||
1690 | shopadmin.pl gives a UI to edit the product table, and view the orders and | |
1691 | order_item tables. | |
1692 | ||
1693 | =head1 TEMPLATES | |
1694 | ||
1695 | shopadmin.pl uses a few templates from the templates/admin directory. | |
1696 | ||
1697 | =head2 product_list.tmpl | |
1698 | ||
1699 | =over 4 | |
1700 | ||
1701 | =item product I<name> | |
1702 | ||
1703 | Access to product fields. | |
1704 | ||
1705 | =item date I<name> | |
1706 | ||
1707 | Formats the I<name> field of the product as a date. | |
1708 | ||
1709 | =item money I<name> | |
1710 | ||
1711 | Formats the I<name> integer field as a 2 decimal place money value. | |
1712 | ||
1713 | =item iterator ... products | |
1714 | ||
1715 | Iterates over the products database in reverse expire order. | |
1716 | ||
1717 | =item script | |
1718 | ||
1719 | The name of the current script for use in URLs. | |
1720 | ||
1721 | =item message | |
1722 | ||
1723 | An error message that may have been passed in the 'message' parameter. | |
1724 | ||
1725 | =item hiddenNote | |
1726 | ||
1727 | 'Deleted' if the expire date of the current product has passed. | |
1728 | ||
1729 | =back | |
1730 | ||
1731 | =head2 add_product.tmpl | |
1732 | =head2 edit_product.tmpl | |
1733 | =head2 product_detail.tmpl | |
1734 | ||
1735 | These use the same tags. | |
1736 | ||
1737 | =over 4 | |
1738 | ||
1739 | =item product I<name> | |
1740 | ||
1741 | The specified field of the product. | |
1742 | ||
1743 | =item date I<name> | |
1744 | ||
1745 | Formats the given field of the product as a date. | |
1746 | ||
1747 | =item money I<name> | |
1748 | ||
1749 | Formats the given integer field of the product as money. | |
1750 | ||
1751 | =item action | |
1752 | ||
1753 | Either 'Add New' or 'Edit'. | |
1754 | ||
1755 | =item message | |
1756 | ||
1757 | The message parameter passed into the script. | |
1758 | ||
1759 | =item script | |
1760 | ||
1761 | The name of the script, for use in urls. | |
1762 | ||
1763 | =item ifImage | |
1764 | ||
1765 | Conditional, true if the product has an image. | |
1766 | ||
1767 | =item hiddenNote | |
1768 | ||
1769 | "Hidden" if the product is hidden. | |
1770 | ||
1771 | =back | |
1772 | ||
1773 | =head2 order_list.tmpl | |
1774 | ||
1775 | Used to display the list of orders. You can also specify a template | |
1776 | parameter to the order_list target, and perform filtering and sorting | |
1777 | within the template. | |
1778 | ||
1779 | =over 4 | |
1780 | ||
1781 | =item order I<name> | |
1782 | ||
1783 | The given field of the order. | |
1784 | ||
1785 | =item iterator ... orders [filter-sort-spec] | |
1786 | ||
1787 | Iterates over the orders in reverse orderDate order. | |
1788 | ||
1789 | The [filter-sort-spec] can contain none, either or both of the following: | |
1790 | ||
1791 | =over | |
1792 | ||
1793 | =item filter= field op value, ... | |
1794 | ||
1795 | filter the data by checking the given expression. | |
1796 | ||
1797 | eg. filter= filled == 0 | |
1798 | ||
1799 | =item sort= [+|-] keyword, ... | |
1800 | ||
1801 | Sorts the result by the specified fields, in reverse if preceded by '-'. | |
1802 | ||
1803 | =back | |
1804 | ||
1805 | =item money I<name> | |
1806 | ||
1807 | The given field of the current order formatted as money. | |
1808 | ||
1809 | =item date I<name> | |
1810 | ||
1811 | The given field of the current order formatted as a date. | |
1812 | ||
1813 | =item script | |
1814 | ||
1815 | The name of the script, for use in urls. | |
1816 | ||
1817 | =back | |
1818 | ||
1819 | =head2 order_detail.tmpl | |
1820 | ||
1821 | Used to display the details for an order. | |
1822 | ||
1823 | =over 4 | |
1824 | ||
1825 | =item item I<name> | |
1826 | ||
1827 | Displays the given field of a line item | |
1828 | ||
1829 | =item iterator ... items | |
1830 | ||
1831 | Iterates over the line items in the order. | |
1832 | ||
1833 | =item order I<name> | |
1834 | ||
1835 | The given field of the order. | |
1836 | ||
1837 | =item money I<func> I<args> | |
1838 | ||
1839 | Formats the given functions return value as money. | |
1840 | ||
1841 | =item date I<func> I<args> | |
1842 | ||
1843 | Formats the given function return value as a date. | |
1844 | ||
1845 | =item extension I<name> | |
1846 | ||
1847 | Takes the given field for the current item multiplied by the units column. | |
1848 | ||
1849 | =item product I<name> | |
1850 | ||
1851 | The given product field of the product for the current item. | |
1852 | ||
1853 | =item script | |
1854 | ||
1855 | The name of the current script (for use in urls). | |
1856 | ||
1857 | =item iterator ... options | |
1858 | ||
1859 | Iterates over the options set for the current order item. | |
1860 | ||
1861 | =item option I<field> | |
1862 | ||
1863 | Access to a field of the option, any of id, value, desc or label. | |
1864 | ||
1865 | =item ifOptions | |
1866 | ||
1867 | Conditional tag, true if the current product has any options. | |
1868 | ||
1869 | =item options | |
1870 | ||
1871 | A laid-out list of the options set for the current order item. | |
1872 | ||
1873 | =back | |
1874 | ||
1875 | =cut | |
1876 |