Revision 270
- Date:
- 2013/02/01 20:48:45
- Files:
-
- /utf8/plugins/webshop/comps/contenido/webshop/components/block_coupons.msn (Diff) (Checkout)
- /utf8/plugins/webshop/comps/contenido/webshop/components/coupon_browse.msn (Diff) (Checkout)
- /utf8/plugins/webshop/comps/contenido/webshop/components/order_browse.msn (Diff) (Checkout)
- /utf8/plugins/webshop/comps/contenido/webshop/components/order_list.msn (Diff) (Checkout)
- /utf8/plugins/webshop/comps/contenido/webshop/coupon.html (Diff) (Checkout)
- /utf8/plugins/webshop/comps/contenido/webshop/coupons.html (Diff) (Checkout)
- /utf8/plugins/webshop/comps/contenido/webshop/index.html (Diff) (Checkout)
- /utf8/plugins/webshop/lib/webshop/Coupon.pm (Diff) (Checkout)
- /utf8/plugins/webshop/lib/webshop/CouponItemLink.pm (Diff) (Checkout)
- /utf8/plugins/webshop/lib/webshop/Init.pm (Diff) (Checkout)
- /utf8/plugins/webshop/lib/webshop/Keeper.pm (Diff) (Checkout)
- /utf8/plugins/webshop/lib/webshop/Order.pm (Diff) (Checkout)
- /utf8/plugins/webshop/lib/webshop/OrderCouponLink.pm (Diff) (Checkout)
- /utf8/plugins/webshop/lib/webshop/SQL/CouponLinkTable.pm (Diff) (Checkout)
- /utf8/plugins/webshop/lib/webshop/SQL/CouponsTable.pm (Diff) (Checkout)
- /utf8/plugins/webshop/lib/webshop/SQL/OrderCouponLinkTable.pm (Diff) (Checkout)
- /utf8/plugins/webshop/lib/webshop/State.pm.proto (Diff) (Checkout)
- /utf8/plugins/webshop/sql/TOAST/coupon_links.sql (Diff) (Checkout)
- /utf8/plugins/webshop/sql/TOAST/coupons.sql (Diff) (Checkout)
- /utf8/plugins/webshop/sql/TOAST/order_coupons.sql (Diff) (Checkout)
Legend:
- Added
- Removed
- Modified
-
utf8/plugins/webshop/comps/contenido/webshop/components/block_coupons.msn
1 <fieldset> 2 <legend>Актуальные купоны</legend> 3 4 <table width="100%" border="0" cellpadding="3" cellspacing="0" class="tlistdocs"> 5 <tr class="<% $status == 1 ? 'inverted' : '' %>"> 6 <td><a href="/contenido/webshop/coupons.html">Активные:</a></td> 7 <td><b><% $stats{1} || 0 %></b></td></tr> 8 <tr class="<% $status == 0 ? 'inverted' : '' %>"> 9 <td><a href="/contenido/webshop/coupons.html?cst=0" style="<% $status == 0 ? '' : 'color:gray' %>">Неактивные:</a></td> 10 <td><b><% $stats{0} || 0 %></b></td></tr> 11 <tr class="<% $status == 3 ? 'inverted' : '' %>"> 12 <td><a href="/contenido/webshop/coupons.html?cst=3" style="<% $status == 3 ? '' : 'color:black' %>">Использованные:</a></td> 13 <td><b><% $stats{3} || 0 %></b></td></tr> 14 <tr class="<% $status == 2 ? 'inverted' : '' %>"> 15 <td><a href="/contenido/webshop/coupons.html?cst=2" style="<% $status == 2 ? '' : 'color:red' %>">Прототипы:</a></td> 16 <td><b><% $stats{2} || 0 %></b></td></tr> 17 <tr class="<% $status == 4 ? 'inverted' : '' %>"> 18 <td><a href="/contenido/webshop/coupons.html?cst=4" style="<% $status == 4 ? '' : 'color:olive' %>">Обработанные:</a></td> 19 <td><b><% $stats{4} || 0 %></b></td></tr> 20 </table> 21 22 <table cellspacing="2" cellpadding="0" border="0" class="tform"> 23 <tr><td height="3"></td></tr> 24 <tr><td colspan="2"><b><a href="/contenido/webshop/coupon.html">Зарегистрировать »</a></b></td></tr> 25 26 </table> 27 28 </fieldset> 29 <%args> 30 31 $status => undef 32 33 </%args> 34 <%init> 35 36 my $now = Contenido::DateTime->new; 37 my $sql = $keeper->SQL->prepare( 'select status, count(id) as cnt from webshop_coupons where ? <= etime group by status' ); 38 $sql->execute( $now->ymd('-').' '.$now->hms ); 39 40 my %stats; 41 while ( my $ln = $sql->fetchrow_hashref ) { 42 $stats{$ln->{status}} = $ln->{cnt}; 43 } 44 # my $active = $keeper->get_documents ( 45 # class => 'webshop::Coupon', 46 # status => 1, 47 # count => 1, 48 # interval => [$now, $now], 49 # ); 50 # my $used = $keeper->get_documents ( 51 # class => 'webshop::Coupon', 52 # status => 3, 53 # count => 1, 54 # interval => [$now, $now], 55 # ); 56 57 </%init> -
utf8/plugins/webshop/comps/contenido/webshop/components/coupon_browse.msn
1 <script type="text/javascript"> 2 <!-- 3 var aIDs = new Array (<% scalar @$documents ? join (',', map { $_->id } @$documents) : '' %>); 4 function delete_check () { 5 for ( var i = 0; i < aIDs.length; i++ ) { 6 var sFieldName = 'delete_' + aIDs[i] + '_id'; 7 var oField = document.forms['section_browse'].elements[sFieldName]; 8 if ( oField.checked ) { 9 oField.checked = 0; 10 } else { 11 oField.checked = 1; 12 } 13 } 14 } 15 //--> 16 </script> 17 <form name="section_browse" action="coupons.html" method="POST"> 18 <table width="100%" border="0" cellpadding="4" cellspacing="0" class="tlistdocs"> 19 <tr bgcolor="#efefef"> 20 <th><a href="javascript:delete_check()" onclick="delete_check(); return false;"><img src="/contenido/i/actions/delete.gif" width="14" height="17" alt="Удаление документов" align="absmiddle" border="0" hspace="1"></a></th> 21 % 22 % foreach (@$columns) { 23 <th><% $_->{shortname} || $_->{rusname} %></th> 24 % } 25 % 26 </tr> 27 % 28 % unless (@$documents) { 29 <tr><td align="center" colspan="<% scalar @$columns %>">Документы не найдены</td></tr> 30 % } 31 % foreach my $document (@$documents) { 32 % 33 % next unless ref($document); 34 % my $document_access = $user->section_accesses($user, $document->section); 35 % 36 <tr valign="top"> 37 <td nowrap>\ 38 % if ($document_access == 2) { 39 % $delete_status = 1; 40 <input type="checkbox" name="<% 'delete_'.$document->id.'_id' %>"> 41 % } else { 42 43 % } 44 </td> 45 % 46 % for my $col (@$columns) { 47 % if ($col->{attr} eq '_sort_') { 48 % 49 <td width="20px"><% $document->{sorder} %> <a 50 href="document_move.html?id=<% $document->{id} %>&move=up&s=<% $id %><% $params_unsection ? '&'.$params_unsection : '' %>"><img 51 src="/contenido/i/ico-up-9x10.gif" border=0 alt="Переместить документ на шаг вверх"></a> <a 52 href="document_move.html?id=<% $document->{id} %>&move=down&s=<% $id %><% $params_unsection ? '&'.$params_unsection : '' %>"><img 53 src="/contenido/i/ico-down-9x10.gif" border=0 alt="Переместить документ на шаг вниз"></a>\ 54 % 55 % } elsif ($col->{attr} eq 'dtime') { 56 % 57 <td nowrap><& "/contenido/components/show_dtime.msn", dtime=>$document->{dtime} &>\ 58 % 59 % } elsif ($col->{attr} eq 'name') { 60 % 61 <td><span<% $document->contenido_status_style ? ' style="' . $document->contenido_status_style . '"' : '' %> class="<% $document->status ? '':'hiddensect' %>">\ 62 % 63 % my $name=$document->name ? $document->name : 'Безымянный документ N'.$document->id; 64 % if ($document_access == 2) { 65 % 66 <a<% $document->contenido_status_style ? ' style="' . $document->contenido_status_style . '"' : '' %> href="./coupon.html?id=<% $document->id %><% $params_unclassed ? '&'.$params_unclassed : '' %>"><% $name | h %> </a>\ 67 % 68 % } else { 69 % 70 <% $name | h %> \ 71 % 72 % } 73 % 74 </span>\ 75 % 76 % } elsif ($col->{attr} eq 'id') { 77 % 78 <td><span class="<% $document->status ? '':'hiddensect' %>">\ 79 % if ($document_access == 2) { 80 <a href="document.html?id=<% $document->id %>&class=<% $document->class %><% $params_unclassed ? '&'.$params_unclassed : '' %>"><% $document->id %> </a>\ 81 % } else { 82 <% $document->id %> \ 83 % } 84 </span>\ 85 % 86 % } elsif ( exists $col->{inline} && $col->{inline} ) { 87 % $inline_status = 1; 88 % my $attr = $col->{attr}; 89 % if ( $col->{type} =~ /^(string|integer|float)$/ && $col->{inline} ) { 90 % my $style = $col->{inline_style} ? $col->{inline_style} : ($col->{type} =~ /^(integer|float)$/ ? 'text-align:right; ' : '' ); 91 <td><input type="text" name="<% 'update_'.$document->id.'_'.$attr %>" value="<% $document->$attr %>" style="<% $col->{inline_width} ? 'width:'.$col->{inline_width}.'px;' : 'width:65px; ' %> <% $col->{inline_style} || '' %>"> 92 % } elsif ($col->{type} eq 'checkbox') { 93 % my $checked = $document->$attr ? ' checked' : ''; 94 <td align="center"><input type="checkbox" name="<% 'update_'.$document->id.'_'.$attr %>"<% $checked %>> 95 % } elsif ($col->{type} eq 'select') { 96 % my $options = {}; 97 % if ($toopi && (ref($toopi) eq 'HASH') && (exists($toopi->{$document->class}))) { 98 % %{ $options } = %{ $toopi->{$document->class} }; 99 % } 100 % my $values = $options->{$col->{attr}}; 101 <td><select name="<% 'update_'.$document->id.'_'.$attr %>"> 102 % if ( ref $values eq 'ARRAY' ) { 103 % foreach my $val ( @$values ) { 104 % my $selected = $val eq $document->$attr ? ' selected' : ''; 105 <option value="<% $val %>"<% $selected %>><% $val %> 106 % } 107 % } 108 </select> 109 % } elsif ($col->{type} eq 'status') { 110 % my $cases = $col->{cases}; 111 % if ( ref $cases eq 'ARRAY' ) { 112 <td><select name="<% 'update_'.$document->id.'_'.$attr %>" style="<% $col->{inline_width} ? 'width:'.$col->{inline_width}.'px;' : '' %> <% $col->{inline_style} || '' %>"> 113 % foreach my $case ( @$cases ) { 114 % my $selected = $case->[0] eq $document->$attr ? ' selected' : ''; 115 <option value="<% $case->[0] %>"<% $selected %>><% $case->[1] %> 116 % } 117 </select> 118 % } 119 % } 120 % 121 % } elsif ($col->{attr} eq 'class') { 122 % 123 <td><% $document->class_name %> <font color="#999999">(<% $document->class %>)</font>\ 124 % 125 % } elsif ($col->{type} eq 'datetime') { 126 % 127 <td nowrap><& "/contenido/components/show_dtime.msn", dtime=>$document->{$col->{attr}} &>\ 128 % 129 % } elsif ($col->{attr} eq '_act_') { 130 % 131 <td nowrap>\ 132 % if ($document_access == 2) { 133 % 134 <a href="./coupon.html?id=<% $document->id %><% $params_unclassed ? '&'.$params_unclassed : '' %>" title="Редактировать документ"><img 135 src="/contenido/i/actions/edit.gif" width="15" height="17" alt="Редактировать документ" align="absmiddle" border="0" hspace="1">редактировать</a>\ 136 %#<a href="confirm.html?id=<% $document->id %>&action=documents_deletion&from=<% $section->id %>&class=<% $document->class %><% $params_unclassed ? '&'.$params_unclassed : '' %>" title="Удалить документ"><img 137 %# src="/contenido/i/actions/delete.gif" width="14" height="17" alt="Удалить документ" align="absmiddle" border="0" hspace="1"></a>\ 138 <br>\ 139 % 140 % } else { 141 \ 142 % } 143 % if ( $inline_status ) { 144 <input type="hidden" name="update_<% $document->id %>_class" value="<% $document->class %>"> 145 % } 146 % if ( $delete_status ) { 147 <input type="hidden" name="delete_<% $document->id %>_class" value="<% $document->class %>"> 148 % } 149 % 150 % } else { 151 % 152 % if ($col->{type} eq 'date') { 153 % my $date=$document->{$col->{attr}}; 154 % $date=~/(\d{4}-\d{2}-\d{2})/; 155 <td nowrap align="right"><% $1 || ' ' %>\ 156 % } elsif ($col->{type} eq 'datetime') { 157 <td nowrap align="right"><% $document->{$col->{attr}} || ' ' %>\ 158 % } elsif ($col->{type} eq 'integer') { 159 <td align="right"><% $document->{$col->{attr}} %> \ 160 % } elsif ($col->{type} eq 'lookup' || $col->{type} eq 'pickup') { 161 <td align="left">\ 162 % my $id = $document->{$col->{attr}}; 163 % if ($id) { 164 % my ($doc)=$keeper->get_documents( ( ref($col->{lookup_opts}) ? %{$col->{lookup_opts}} : () ), id=>$id); 165 % if ($doc) { 166 <a href="document.html?id=<% $doc->id %>&class=<% $doc->class %><% $params_unclassed ? '&'.$params_unclassed : '' %>"><% $doc->name || $doc->id %></a> \ 167 % } else { 168 <span class="hiddensect"><% $document->{$col->{attr}} %>???</span>\ 169 % } 170 % } else { 171 <span class="hiddensect">NULL</span>\ 172 % } 173 % } elsif ($col->{type} eq 'status') { 174 % my $status_map = ref $col->{cases} eq 'ARRAY' ? $col->{cases} : $keeper->default_status(); 175 % my ($doc_status) = grep { $_->[0] eq $document->{$col->{attr}} } @$status_map; 176 % $doc_status ||= [$document->{$col->{attr}}, 'Неизвестный']; 177 <td nowrap><% $doc_status->[1].'('.$doc_status->[0].')' %>\ 178 % } else { 179 <td><% defined($document->{$col->{attr}}) ? $document->{$col->{attr}} : ' ' %>\ 180 % } 181 % } 182 </td> 183 % 184 % } #- for @columns 185 % 186 </tr> 187 % } #- foreach @documents 188 </table> 189 % if ( ref $filter eq 'HASH' ) { 190 % while ( my ($key, $value) = each %$filter ) { 191 % next if $key eq 's'; 192 <input type="hidden" name="<% $key %>" value="<% $value |h %>"> 193 % } 194 % } 195 % if ( $inline_status || $delete_status ) { 196 <div style="text-align:right; padding:10px 0;"> 197 % if ( $inline_status ) { 198 <input type="submit" name="update" value="Сохранить изменения" class="input_btn"> 199 % } 200 % if ( $delete_status ) { 201 <input type="submit" name="delete" value="Удалить выбранные" class="input_btn" onclick="return confirm('Все отмеченные позиции будут удалены');"> 202 % } 203 </div> 204 % } 205 </form> 206 <%args> 207 208 $documents => undef 209 $columns => undef 210 $id => undef 211 $filter => undef 212 213 </%args> 214 <%init> 215 216 return unless ref $documents eq 'ARRAY'; 217 return unless ref $columns eq 'ARRAY'; 218 219 my $toopi = $project->documents(); 220 my $inline_status = 0; 221 my $delete_status = 0; 222 my $params = ref $filter eq 'HASH' ? join ('&', map { $_.'='.$filter->{$_} } keys %$filter ) : ''; 223 my $params_unclassed = ref $filter eq 'HASH' ? join ('&', map { $_.'='.$filter->{$_} } grep { $_ ne 'class' } keys %$filter ) : ''; 224 my $params_unsection = ref $filter eq 'HASH' ? join ('&', map { $_.'='.$filter->{$_} } grep { $_ ne 's' } keys %$filter ) : ''; 225 226 227 </%init> -
utf8/plugins/webshop/comps/contenido/webshop/components/order_browse.msn
48 48 <td><span<% $style %> class="<% $document->status ? '':'hiddensect' %>"><% $a1.$document->id.$a2 %></span></td> 49 49 % 50 50 % for my $col (@$columns) { 51 % if ($col->{attr} eq 'dtime') { 51 % my $attr = $col->{attr}; 52 % if ($attr eq 'dtime') { 52 53 % 53 54 <td nowrap><& "/contenido/components/show_dtime.msn", dtime=>$document->{dtime} &>\ 54 55 % if ($document->{ctime} ne $document->{mtime}) { … … 56 57 <div style="color:<% $colortime %>;"><& "/contenido/components/show_dtime.msn", dtime=>$document->{mtime} &></div>\ 57 58 % } 58 59 % 59 % } elsif ($col->{attr} eq 'name') { 60 % } elsif ($attr eq 'name') { 60 61 <td><span<% $style %> class="<% $document->status ? '':'hiddensect' %>">\ 61 62 % 62 63 % my $name=$document->name ? $document->name : 'Безымянный документ N'.$document->id; … … 64 65 % 65 66 </span>\ 66 67 % 67 % } elsif ($col->{attr} eq 'id') { 68 % } elsif ($attr eq 'id') { 68 69 % 69 70 <td><span class="<% $document->status ? '':'hiddensect' %>">\ 70 71 % if ($document_access == 2) { … … 76 77 % 77 78 % } elsif ( exists $col->{inline} && $col->{inline} ) { 78 79 % $inline_status = 1; 79 % my $attr = $col->{attr}; 80 80 % if ( $col->{type} =~ /^(string|integer|float)$/ && $col->{inline} ) { 81 81 % my $style = $col->{inline_style} ? $col->{inline_style} : ($col->{type} =~ /^(integer|float)$/ ? 'text-align:right; ' : '' ); 82 82 <td><input type="text" name="<% 'update_'.$document->id.'_'.$attr %>" value="<% $document->$attr %>" style="<% $col->{inline_width} ? 'width:'.$col->{inline_width}.'px;' : 'width:65px; ' %> <% $col->{inline_style} || '' %>"> … … 88 88 % if ($toopi && (ref($toopi) eq 'HASH') && (exists($toopi->{$document->class}))) { 89 89 % %{ $options } = %{ $toopi->{$document->class} }; 90 90 % } 91 % my $values = $options->{$col->{attr}}; 91 % my $values = $options->{$attr}; 92 92 <td><select name="<% 'update_'.$document->id.'_'.$attr %>"> 93 93 % if ( ref $values eq 'ARRAY' ) { 94 94 % foreach my $val ( @$values ) { … … 109 109 % } 110 110 % } 111 111 % 112 % } elsif ($col->{attr} eq 'class') { 112 % } elsif ($attr eq 'class') { 113 113 % 114 114 <td><% $document->class_name %> <font color="#999999">(<% $document->class %>)</font>\ 115 115 % 116 116 % } elsif ($col->{type} eq 'datetime') { 117 117 % 118 <td nowrap><& "/contenido/components/show_dtime.msn", dtime=>$document->{$col->{attr}} &>\ 118 <td nowrap><& "/contenido/components/show_dtime.msn", dtime=>$document->$attr &>\ 119 119 % 120 % } elsif ($col->{attr} eq '_act_') { 120 % } elsif ($attr eq '_act_') { 121 121 % my $actions; 122 122 % if ( $document->$user_id == $user->id ) { 123 123 % $actions = join (' | ', map { '<a href="./'.$_->{href}.'?id='.$document->id.($params_unclassed ? '&'.$params_unclassed : '').'">'.$_->{name}.'</a>' } @actions); … … 144 144 % } else { 145 145 % 146 146 % if ($col->{type} eq 'date') { 147 % my $date=$document->{$col->{attr}}; 147 % my $date=$document->$attr; 148 148 % $date=~/(\d{4}-\d{2}-\d{2})/; 149 149 <td nowrap align="right"><% $1 || ' ' %>\ 150 150 % } elsif ($col->{type} eq 'datetime') { 151 <td nowrap align="right"><% $document->{$col->{attr}} || ' ' %>\ 151 <td nowrap align="right"><% $document->$attr || ' ' %>\ 152 152 % } elsif ($col->{type} eq 'integer') { 153 <td align="right"><% $document->{$col->{attr}} %> \ 153 <td align="right"><% $document->$attr %> \ 154 154 % } elsif ($col->{type} eq 'lookup') { 155 155 <td align="left">\ 156 % my $id = $document->{$col->{attr}}; 156 % my $id = $document->$attr; 157 157 % if ($id) { 158 158 % my ($doc)=$keeper->get_documents( ( ref($col->{lookup_opts}) ? %{$col->{lookup_opts}} : () ), id=>$id); 159 159 % if ($doc) { 160 <a href="document.html?id=<% $doc->id %>&class=<% $doc->class %><% $params_unclassed ? '&'.$params_unclassed : '' %>"><% $doc->name || $doc->id %></a> \ 160 <a href="/contenido/document.html?id=<% $doc->id %>&class=<% $doc->class %><% $params_unclassed ? '&'.$params_unclassed : '' %>"><% $doc->name || $doc->id %></a> \ 161 161 % } else { 162 <span class="hiddensect"><% $document->{$col->{attr}} %>???</span>\ 162 <span class="hiddensect"><% $document->$attr %>???</span>\ 163 163 % } 164 164 % } else { 165 165 <span class="hiddensect">NULL</span>\ 166 166 % } 167 167 % } elsif ($col->{type} eq 'status') { 168 168 % my $status_map = ref $col->{cases} eq 'ARRAY' ? $col->{cases} : $keeper->default_status(); 169 % my ($doc_status) = grep { $_->[0] eq $document->{$col->{attr}} } @$status_map; 170 % $doc_status ||= [$document->{$col->{attr}}, 'Неизвестный']; 169 % my ($doc_status) = grep { $_->[0] eq $document->$attr } @$status_map; 170 % $doc_status ||= [$document->$attr, 'Неизвестный']; 171 171 <td nowrap><% $doc_status->[1].'('.$doc_status->[0].')' %>\ 172 172 % } else { 173 <td><% defined($document->{$col->{attr}}) ? $document->{$col->{attr}} : ' ' %>\ 173 <td><% defined($document->$attr) ? $document->$attr : ' ' %>\ 174 174 % } 175 175 % } 176 176 </td> -
utf8/plugins/webshop/comps/contenido/webshop/components/order_list.msn
54 54 <td class="number" colspan="2"><b class="blue"><% $order->sum_delivery %></b></td> 55 55 </tr> 56 56 <tr> 57 <td colspan="5" align="right"><b>Скидка</b></td> 58 <td class="number" colspan="2"><b class="blue"><% $order->sum_discount || 0 %></b></td> 59 </tr> 60 <tr> 57 61 <td colspan="5" align="right"><b>Итого с доставкой</b></td> 58 <td class="number" colspan="2"><b class="blue"><% $total_sum + ($order->sum_delivery || 0) %></b></td> 62 <td class="number" colspan="2"><b class="blue"><% $order->sum_total %></b></td> 59 63 </tr> 60 64 61 65 </table> … … 69 73 % } 70 74 % } 71 75 <input type="hidden" name="id" value="<% $order->id %>"> 76 </form> 72 77 78 % if ( @coupons ) { 79 <table width="100%" border="0" cellpadding="4" cellspacing="0" class="tlistdocs" bgcolor="white"> 80 <tr bgcolor="#efefef"> 81 <th>Использованные купоны</th> 82 <th>Скидка</th> 83 </tr> 73 84 74 </form> 85 % foreach my $coupon ( @coupons ) { 86 <tr> 87 <td><% $coupon->code %></td> 88 <td class="number" colspan="2"><b class="blue"><% $coupon->discount %></b></td> 89 </tr> 90 % } 91 92 </table> 93 % } 94 75 95 % } else { 76 96 <div style="color:red; font-size:110%; margin:20px 0;">Заказ пустой</div> 77 97 % } … … 93 113 my $total_sum = 0; 94 114 my $total_num = 0; 95 115 116 my @coupons = $keeper->get_documents ( 117 class => 'webshop::Coupon', 118 lclass => 'webshop::OrderCouponLink', 119 lsource => $order->id, 120 ) if $order->sum_discount; 121 96 122 </%init> -
utf8/plugins/webshop/comps/contenido/webshop/coupon.html
1 <& "/contenido/components/header.msn" &> 2 3 <% spacer(h=>10) %> 4 5 % if ($error) { 6 <div align="center" style="font-size:110%; color:red;"> 7 <% $error %> 8 </div> 9 <br><br> 10 % } 11 12 <!-- Форма для редактирования объекта --> 13 <a name="top"></a> 14 <form action="" method="POST" name="form" onSubmit=""> 15 16 <table border="0" width="100%" cellspacing="0" cellpadding="6"> 17 <tr> 18 <td style="font-size:110%;"> 19 <b><% $object->id ? 'Редактирование' : 'Регистрация нового' %> купона</b> 20 </td> 21 <td align="right"> 22 <input type="submit" value="Сохранить" class="input_btn"> 23 </td> 24 </tr> 25 </table> 26 27 28 <center> 29 <table width="100%" cellpadding="0" cellspacing="0" border="0"> 30 <tr> 31 <td bgcolor="#999999"> 32 <table width="100%" cellpadding="0" cellspacing="1" border="0"> 33 <tr><td valign="top" bgcolor="#ffffff" width="80%"> 34 <center> 35 36 <table width="98%" cellpadding="1" cellspacing="0" border="0"> 37 38 % foreach my $prop ( @props ) { 39 % next if $prop->{hidden} == 1; 40 % next if $prop->{attr} eq 'uid_proto' || $prop->{attr} eq 'uid_condition'; 41 % my $name = $prop->{attr}; 42 % my $type = $prop->{type}; 43 <tr><td height="8"></td></tr> 44 <tr><td nowrap> 45 <table cellpadding="0" cellspacing="0" border="0"> 46 <tr> 47 <td nowrap><b><% $prop->{rusname} %></b> /</td> 48 <td align="right" nowrap><font color="#888888" size="-1"> name="<% $prop->{attr} %>"</font></td> 49 % if( $prop->{readonly} ) { 50 <td align="right" nowrap> / <font color="#CC0000" size="-1">Значение нельзя изменить</font></td> 51 % } 52 </tr> 53 </table> 54 </td></tr> 55 % if ( $prop->{attr} eq 'uid' ) { 56 % #### Прототипирование 57 % ################################################################### 58 <script type="text/javascript"> 59 <!-- 60 function change_uid_proto ( oSelect ) { 61 var sUidDiv = document.getElementById('uid_block'); 62 var sUidEq = document.getElementById('uid_equation'); 63 if ( oSelect.value == '3' ) { 64 sUidDiv.style.display = 'block'; 65 } else { 66 sUidDiv.style.display = 'none'; 67 uid_undo(); 68 } 69 if ( oSelect.value == '2' ) { 70 sUidEq.style.display = 'block'; 71 } else { 72 sUidEq.style.display = 'none'; 73 uid_undo(); 74 } 75 } 76 //--> 77 </script> 78 <tr><td style="border:1px solid gray; padding:5px; background:#e0ffe0"> 79 80 <select name="uid_proto" onchange="change_uid_proto(this)"> 81 <option value="0">Купон доступен всем пользователям</option> 82 <option value="1" <% $object->uid_proto == 1 ? 'selected' : '' %>>Одноразовый купон, создается для всех зарегистрированных пользователей</option> 83 <option value="2" <% $object->uid_proto == 2 ? 'selected' : '' %>>Одноразовый купон, пользователи по выбору</option> 84 <option value="3" <% $object->uid_proto == 3 ? 'selected' : '' %>>Одноразовый купон для одного пользователя</option> 85 </select> 86 87 <div id="uid_block" style="padding-top:10px; display:<% $object->uid_proto == 3 ? 'block' : 'none' %>;"> 88 <fieldset> 89 <legend>Выберите пользователя, нажав на знак бинокля</legend> 90 <& "/contenido/components/inputs/$type.msn", prop => $prop, object => $object, name => $name, 91 options => $options, id => ($object->id() || 0), 92 check => ($object->$name || $object->{$name}), 93 &> 94 </fieldset> 95 </div> 96 97 98 <div id="uid_equation" style="padding-top:10px; display:<% $object->uid_proto == 2 ? 'block' : 'none' %>;"> 99 <fieldset> 100 <legend>Выберите условие:</legend> 101 102 <table width="100%" cellspacing="0" cellpadding="3"> 103 104 <tr valign="top" bgcolor="white"> 105 <td width="1%"><input type="radio" name="uid_condition" value="positive" 106 <% $object->uid_condition && $object->uid_condition eq 'loyal' ? 'checked' : '' %>></td> 107 <td width="49%">Все пользователи с позитивной историей</td> 108 <td width="50%"></td> 109 </tr> 110 111 <tr valign="top" bgcolor="#e0e0e0"> 112 <td><input type="radio" name="uid_condition" value="total" 113 <% $object->uid_condition && $object->uid_condition =~ /^from/ ? 'checked' : '' %>></td> 114 <td>Все пользователи, купившие на сумму более:</td> 115 <td><input type="text" name="uid_condition.total" style="width:95%" 116 value="<% $object->uid_condition && $object->uid_condition =~ /^from (\d+)/ ? $1 : '' %>"></td> 117 </tr> 118 119 <tr valign="top" bgcolor="white"> 120 <td><input type="radio" name="uid_condition" value="activity" 121 <% $object->uid_condition && $object->uid_condition =~ /^last/ ? 'checked' : '' %>></td> 122 <td>Все пользователи, проявлявшие активность за:</td> 123 % my $activity = $object->uid_condition && $object->uid_condition =~ /^last (\w+)/ ? $1 : ''; 124 <td><select name="uid_condition.period"> 125 <option value="month">месяц</option> 126 <option value="quarter" <% $activity eq 'quarter' ? 'selected' : '' %>>3 месяца</option> 127 <option value="half" <% $activity eq 'half' ? 'selected' : '' %>>6 месяцев</option> 128 <option value="year" <% $activity eq 'year' ? 'selected' : '' %>>год</option> 129 </select></td> 130 </tr> 131 132 </table> 133 </fieldset> 134 </div> 135 136 </div></td></tr> 137 % #### /Прототипирование 138 139 % } else { 140 <tr><td><& "/contenido/components/inputs/$type.msn", prop => $prop, object => $object, name => $name, 141 options => $options, id => ($object->id() || 0), 142 check => ($object->$name || $object->{$name}), 143 &></td></tr> 144 % } 145 % } 146 147 <tr> 148 <td><br> 149 % foreach my $prop ( @props ) { 150 % next if $prop->{hidden} != 1; 151 <input type="hidden" name="<% $prop->{attr} %>" value="<% html_escape($object->{ $prop->{attr} }) %>"> 152 % } 153 <input type="hidden" name="save" value="1"> 154 </td> 155 </tr> 156 </table> 157 158 </td> 159 <td valign="top" bgcolor="#efefef"> 160 <% spacer(w=>270) %> 161 <div><iframe name="DocFinder" id="DocFinder" src="/contenido/find_document.html" frameborder="0" 162 marginheight="0" marginwidth="0" width="100%" height="0"></iframe></div> 163 164 </td> 165 </tr> 166 </table> 167 </center> 168 169 </td></tr></table> 170 </center> 171 172 <div align="center"> 173 <input type="submit" value="Сохранить" class="input_btn"> 174 % while ( my ($key, $value) = each %filter_params ) { 175 % next if exists $props{$key}; 176 <input type="hidden" name="<% $key %>" value="<% $value %>"> 177 % } 178 <input type="hidden" name="control_charset" value="Контроль"> 179 <input type="submit" name="_save_and_leave" value="Сохранить и выйти" class="input_btn"> 180 </div> 181 182 </form> 183 184 %#<pre><% Dumper($object) %></pre> 185 186 </body> 187 </html> 188 <%args> 189 190 $id => undef 191 $save => undef 192 193 </%args> 194 <%INIT> 195 196 my $error=''; 197 198 my %filter_params; 199 my $return_params = join ('&', map { $_.'='.$filter_params{$_} } grep { $_ ne 's' } keys %filter_params ); 200 201 my $object = $id ? webshop::Coupon->new( $keeper, $id ) : webshop::Coupon->new( $keeper ); 202 my @props = $object->structure; 203 my %props = map { $_->{attr} => $_ } @props; 204 205 my $toopi = $project->documents(); 206 my $options = {}; 207 if ($toopi && (ref($toopi) eq 'HASH') && (exists($toopi->{ $object->class }))) { 208 %{ $options } = %{ $toopi->{ $object->class } }; 209 } 210 211 if ( $save ) { 212 foreach my $prop ( @props ) { 213 my $name = $prop->{attr}; 214 my $type = $prop->{type}; 215 216 next if ($name eq 'sections' || $name eq 'id' || $name eq 'uid' || $name eq 'uid_condition'); 217 218 if ( $name eq 'uid_proto' ) { 219 my $uid_proto = $ARGS{uid_proto} || 0; 220 $object->uid_proto( $uid_proto ); 221 if ( $uid_proto == 3 ) { 222 $object->uid( $ARGS{uid} ); 223 } elsif ( $uid_proto == 1 ) { 224 $object->uid( 0 ); 225 $object->uid_condition( 'all' ); 226 } elsif ( $uid_proto == 2 ) { 227 $object->uid( 0 ); 228 if ( $ARGS{uid_condition} eq 'total' ) { 229 $object->uid_condition( 'from '.($ARGS{"uid_condition.total"} || 0) ); 230 } elsif ( $ARGS{uid_condition} eq 'activity' ) { 231 $object->uid_condition( 'last '.($ARGS{"uid_condition.period"} || 'month') ); 232 } else { 233 $object->uid_condition( 'loyal' ); 234 } 235 } 236 } elsif ($m->comp_exists("/contenido/components/outputs/$type.msn")) { 237 $object->{$name} = $m->comp("/contenido/components/outputs/$type.msn", SETS => \%ARGS, name => $name, object => $object ); 238 } else { 239 $object->{$name} = $m->comp('/contenido/components/filter.msn', str => $ARGS{$name} ); 240 } 241 } 242 $object->pid( 0 ) unless defined $object->pid; 243 unless ( $object->id ) { 244 $object->status( 2 ) if $object->status && $object->uid_proto; 245 } 246 if ( !$object->code && ($object->uid_proto == 3 || $object->uid_proto == 0 ) ) { 247 my $code = `apg -q -d -a 1 -m 8 -x 8 -M nc -n 1`; 248 $object->code( $code ); 249 } 250 unless ( $object->store ) { 251 $error="Ошибка сохранения ($keeper->{last_error})"; 252 } else { 253 if ($ARGS{_save_and_leave}) { 254 $m->redirect('/contenido/webshop/coupons.html?cst='.$object->status); 255 } else { 256 $m->redirect('coupon.html?id='.$object->id.($return_params ? '&'.$return_params : '')); 257 } 258 } 259 } 260 261 </%INIT> -
utf8/plugins/webshop/comps/contenido/webshop/coupons.html
1 <& "/contenido/components/header.msn" &> 2 3 <style> 4 <!-- 5 .inverted td { font-weight:bold; color:white; background-color:#8093B0; } 6 .inverted td a { color:white; font-weight:bold; } 7 //--> 8 </style> 9 10 <% spacer( h=>10 ) %> 11 12 <table width="100%" cellspacing="0" cellpadding="0" border="0"> 13 <tr valign="top"> 14 <td width="35%"> 15 16 <& /contenido/webshop/components/block_order_status_changer.msn &> 17 18 <& /contenido/webshop/components/block_coupons.msn, status => $cst &> 19 20 </td> 21 <td width="1%"> </td> 22 <td width="65%"> 23 24 <fieldset> 25 <legend>Купоны со статусом "<span style="color:yellow"><% $current_status->[1] %></span>"</legend> 26 27 % if ( $total ) { 28 29 <div style="font-size:75%; font-family:Arial;"> 30 <& /inc/pages_.msn, p=>$p, n=>$size, total=>$total, params=>\%filter_params, &> 31 <div style="height:5px"><spacer type="block" height="5"></div> 32 </div> 33 34 <& /contenido/webshop/components/coupon_browse.msn, documents => \@documents, columns => \@columns, filter => \%filter_params, status => $cst, %ARGS &> 35 36 <div style="font-size:75%; font-family:Arial;"> 37 <& /inc/pages_.msn, p=>$p, n=>$size, total=>$total, params=>\%filter_params, &> 38 <div style="height:5px"><spacer type="block" height="5"></div> 39 </div> 40 41 % } else { 42 <h4 align="center"><i>---- Нет документов -----</i></h4> 43 % } 44 45 </td> 46 <td width="1%"> </td> 47 </tr> 48 </table> 49 50 </body> 51 </html> 52 <%args> 53 54 $cst => 1 55 $p => 1 56 $delete => undef 57 58 </%args> 59 <%init> 60 61 my %filter_params; 62 63 my (@documents, $total); 64 $filter_params{cst} = $cst if $cst != 1; 65 my $size = 40; 66 67 @documents = $keeper->get_documents( 68 class => 'webshop::Coupon', 69 status => $cst, 70 limit => $size, 71 offset => ($p-1)*$size, 72 order_by => 'dtime desc', 73 ); 74 $total = $keeper->get_documents( 75 class => 'webshop::Coupon', 76 status => $cst, 77 count => 1, 78 ); 79 my @structure = webshop::Coupon->new( $keeper->{webshop} )->structure; 80 my @columns = sort { $a->{column} <=> $b->{column} } 81 grep { $_->{column} } @structure; 82 push @columns, {attr => '_act_', rusname => 'Действия'}; 83 my ($status_map) = grep { $_->{attr} eq 'status' } @structure; 84 my ($current_status) = grep { $_->[0] == $cst } @{$status_map->{cases}}; 85 86 my $active_rights = $m->comp('/contenido/webshop/subs/user_rights.msn'); 87 if ( !$active_rights && $delete ) { 88 my $return_params = join ('&', map { $_.'='.$filter_params{$_} } grep { $_ ne 's' } keys %filter_params ); 89 my %deleted; 90 while ( my ($field, $value) = each %ARGS ) { 91 if ( $field =~ /^delete_(\d+)_(\w+)$/ ) { 92 my $oid = $1; 93 my $attr = $2; 94 $deleted{$oid}{$attr} = $value; 95 } 96 } 97 my %classes = map { $_->{class} => 1 } values %deleted; 98 foreach my $delete_class ( keys %classes ) { 99 my @ids; 100 while ( my ($oid, $attr) = each %deleted) { 101 push @ids, $oid if exists $attr->{id} && $attr->{id} && ($attr->{class} eq $delete_class); 102 } 103 my @objects = $keeper->get_documents ( 104 id => \@ids, 105 class => $delete_class 106 ) if @ids; 107 foreach my $object ( @objects ) { 108 my $document_access = $user->section_accesses($user, $object->section); 109 next unless $document_access == 2; 110 $object->delete; 111 } 112 } 113 $m->redirect("/contenido/webshop/".($return_params ? '?'.$return_params : '')); 114 } 115 116 </%init> -
utf8/plugins/webshop/comps/contenido/webshop/index.html
7 7 //--> 8 8 </style> 9 9 10 <% spacer( h=>10 ) %> 11 10 12 <table width="100%" cellspacing="0" cellpadding="0" border="0"> 11 13 <tr valign="top"> 12 14 <td width="35%"> 13 15 14 16 <& /contenido/webshop/components/block_order_status_changer.msn, status => $ost &> 15 17 18 <& /contenido/webshop/components/block_coupons.msn &> 19 16 20 </td> 17 21 <td width="1%"> </td> 18 22 <td width="65%"> -
utf8/plugins/webshop/lib/webshop/Coupon.pm
1 package webshop::Coupon; 2 3 use strict; 4 use warnings 'all'; 5 6 use Contenido::Globals; 7 use base "Contenido::Document"; 8 use Data::Dumper; 9 10 sub extra_properties 11 { 12 return ( 13 { 'attr' => 'class', 'column' => undef }, 14 { 'attr' => 'status', 'type' => 'status', 'rusname' => 'Статус', 15 'cases' => [ 16 [0, 'Купон не активен'], 17 [1, 'Купон активен и разослан'], 18 [2, 'Прототип ждет обработки'], 19 [3, 'Купон использован'], 20 [4, 'Прототип обработан роботом'], 21 ], 22 }, 23 { 'attr' => 'uid_proto','type' => 'status', 'rusname' => 'Прототип пользовательского доступа', 24 'cases' => [ 25 [0, 'Купон доступен всем пользователям'], 26 [1, 'Персональный купон, создается для всех зарегистрированных пользователей'], 27 [2, 'Персональный купон, пользователи по выбору'], 28 [3, 'Персональный купон для одного пользователя'], 29 ], 30 'rem' => 'Заполняется при создании прототипа купона', 31 }, 32 { 'attr' => 'uid_condition', 'type' => 'string', 'rusname' => 'Условие для прототипа' }, 33 { 'attr' => 'uid', 'type' => 'pickup', 'rusname' => 'Пользователь', 34 lookup_opts => { class => $state->{users}->profile_document_class, order_by => 'email', search_by => 'email' }, 35 allow_null => 1, default => 0, 36 rem => '0 - действует для всех пользователей, многократно. id - для одного пользователя, однократно.' 37 }, 38 { 'attr' => 'groups', 'rusname' => 'Группы товаров', 39 lookup_opts => { class => $state->{webshop}->{item_section_class}, }, 40 allow_null => 1, 41 rem => 'Список разделов, на содержимое которых распространяется скидка по купону', 42 }, 43 { 'attr' => 'discount', 'type' => 'string', 'rusname' => 'Скидка на сумму заказа (число или процент)', shortname => 'Скидка', 44 default => 0, column => 2 }, 45 { 'attr' => 'min_sum', 'type' => 'string', 'rusname' => 'Минимальная сумма, на которую действует скидка', default => 0 }, 46 ) 47 } 48 49 sub class_name 50 { 51 return 'Webshop: купон'; 52 } 53 54 sub class_description 55 { 56 return 'Webshop: купон'; 57 } 58 59 sub class_table 60 { 61 return 'webshop::SQL::CouponsTable'; 62 } 63 64 sub contenido_status_style 65 { 66 my $self = shift; 67 if ( $self->status == 3 ) { 68 return 'color:black;'; 69 } elsif ( $self->status == 2 ) { 70 return 'color:red;'; 71 } elsif ( $self->status == 4 ) { 72 return 'color:olive;'; 73 } 74 } 75 76 77 78 sub get_discount 79 { 80 my $self = shift; 81 82 my (%opts) = @_; 83 my $basket = delete $opts{basket}; 84 return 0 unless exists $opts{uid} && $opts{uid} || exists $opts{session} && $opts{session}; 85 return 0 unless $self->discount; 86 87 $basket ||= $keeper->{webshop}->get_basket ( %opts ); 88 return 0 unless ref $basket eq 'ARRAY' && @$basket; 89 90 my ($number, $sum_total) = (0, 0); 91 map { $number += $_->number; $sum_total += $_->number * $_->price } @$basket; 92 warn "BASKET: $number Items of $sum_total Value\n"; 93 94 my $discount_counted = 0; 95 my $items; 96 if ( $self->groups ) { 97 $items = $self->keeper->get_documents ( 98 s => [$self->groups], 99 class => $state->{webshop}->{item_document_class}, 100 light => 1, 101 return_mode => 'array_ref', 102 ); 103 } else { 104 $items = $self->keeper->get_documents ( 105 class => $state->{webshop}->{item_document_class}, 106 lclass => 'webshop::CouponItemLink', 107 lsource => $self->id, 108 light => 1, 109 return_mode => 'array_ref', 110 ); 111 } 112 113 if ( ref $items eq 'ARRAY' && @$items ) { 114 my $found_sum = 0; 115 foreach my $bi ( @$basket ) { 116 warn "BASKET: Item id [".$bi->item_id."]\n"; 117 foreach ( @$items) { 118 warn "BASKET: Item [".$_->name."] id [".$_->id."]\n"; 119 if ( $bi->item_id == $_->id ) { 120 $found_sum += $bi->number * $bi->price; 121 last; 122 } 123 } 124 } 125 warn "Sum found: [$found_sum]\n"; 126 return $self->can_discount( $found_sum ); 127 } elsif ( $self->groups ) { 128 return 0; 129 } 130 131 return $self->can_discount( $sum_total ); 132 } 133 134 135 sub can_discount 136 { 137 my $self = shift; 138 my $sum = shift; 139 return 0 unless $sum; 140 141 my $discount = $self->discount; 142 my $min_sum = $self->min_sum || 0; 143 my $count = 0; 144 if ( $discount =~ /([\d\.]+)%/ ) { 145 my $proc = $1; 146 return 0 unless $proc; 147 $count = $sum / 100 * $proc; 148 } else { 149 $count = $discount; 150 } 151 my $rest = $sum - $count; 152 $count = 0 if $rest < $min_sum || $rest == 0; 153 return $count; 154 } 155 156 157 158 159 #sub table_links 160 #{ 161 # return [ 162 # { name => 'Города', class => 'webshop::Town', filter => 'pid', field => 'pid' }, 163 # ]; 164 #} 165 166 sub pre_store 167 { 168 my $self = shift; 169 170 my $default_section = $project->s_alias->{webshop_coupons} if ref $project->s_alias eq 'HASH'; 171 if ( $default_section && !$self->sections ) { 172 $self->sections($default_section); 173 } 174 175 return 1; 176 } 177 178 1; -
utf8/plugins/webshop/lib/webshop/CouponItemLink.pm
1 package webshop::CouponItemLink; 2 3 use base 'Contenido::Link'; 4 use Contenido::Globals; 5 6 sub class_name 7 { 8 return 'Купон к товару'; 9 } 10 11 sub class_description 12 { 13 return 'Купон к товару'; 14 } 15 16 sub extra_properties 17 { 18 return (); 19 } 20 21 sub available_sources 22 { 23 return [ qw(webshop::Coupon) ]; 24 } 25 26 sub available_destinations 27 { 28 my $self = shift; 29 return $state->{webshop}->{item_document_class}; 30 } 31 32 1; -
utf8/plugins/webshop/lib/webshop/Init.pm
14 14 Contenido::Init::load_classes(qw( 15 15 webshop::SQL::Basket 16 16 webshop::SQL::Order 17 webshop::SQL::CouponsTable 18 webshop::SQL::CouponLinkTable 17 19 18 20 webshop::Address 19 21 webshop::Basket 20 22 webshop::Order 21 23 webshop::Delivery 24 webshop::Coupon 22 25 23 26 webshop::Country 24 27 webshop::Area … … 26 29 27 30 webshop::DeliverySection 28 31 webshop::RegionSection 32 33 webshop::CouponItemLink 34 webshop::OrderCouponLink 29 35 )); 30 36 31 37 sub init { 32 push @{ $state->{'available_documents'} }, qw(webshop::Basket webshop::Order webshop::Delivery webshop::Country webshop::Area webshop::Town); 38 push @{ $state->{'available_documents'} }, qw(webshop::Basket webshop::Order webshop::Delivery webshop::Coupon webshop::Country webshop::Area webshop::Town); 33 39 push @{ $state->{'available_sections'} }, qw(webshop::DeliverySection webshop::RegionSection); 40 push @{ $state->{'available_links'} }, qw(webshop::CouponItemLink webshop::OrderCouponLink); 34 41 0; 35 42 } 36 43 -
utf8/plugins/webshop/lib/webshop/Keeper.pm
137 137 } 138 138 139 139 140 sub basket_count { 141 my $self = shift; 142 my $basket = shift; 143 return (0,0) unless ref $basket eq 'ARRAY' && @$basket; 144 145 my $total = 0; 146 my $sum = 0; 147 foreach my $item ( @$basket ) { 148 if ( $item->status == 1 ) { 149 $total += $item->number; 150 $sum += $item->number * $item->price; 151 } 152 } 153 return ($total, $sum); 154 } 155 156 140 157 sub clear_basket { 141 158 my $self = shift; 142 159 my (%opts) = @_; … … 296 313 return \@items; 297 314 } 298 315 316 317 ### Метод приведения купонов для пользователя в момент логина 318 ############################################################# 319 sub merge_coupons { 320 my $self = shift; 321 my (%opts) = @_; 322 323 warn "Merge (coupons) begin: ".Dumper(\%opts)."\n" if $DEBUG; 324 return unless $opts{uid} && $opts{session}; 325 326 my @items = $keeper->get_links ( 327 class => 'webshop::OrderCouponLink', 328 session => $opts{session}, 329 source_id => 0, 330 ); 331 my %merge_to = $keeper->get_links ( 332 class => 'webshop::OrderCouponLink', 333 uid => $opts{uid}, 334 source_id => 0, 335 return_mode => 'hash', 336 hash_by => 'dest_id', 337 ); 338 foreach my $item ( @items ) { 339 if ( exists $merge_to{$item->dest_id} ) { 340 $item->delete; 341 } else { 342 $item->session( undef ); 343 $item->uid( $opts{uid} ); 344 $item->store; 345 } 346 } 347 } 348 349 350 299 351 sub price_format { 300 352 my $self = shift; 301 353 my $price = shift; -
utf8/plugins/webshop/lib/webshop/Order.pm
1 1 package webshop::Order; 2 2 3 3 use base "Contenido::Document"; 4 use Contenido::Globals; 5 4 6 sub extra_properties 5 7 { 6 8 return ( … … 29 31 manshow => 1, postshow => 1, facilshow => 1 }, 30 32 { 'attr' => 'sum', 'type' => 'string', 'rusname' => 'Сумма (total)', shortname => 'Сумма', 31 33 column => 5, postshow => 1, facilshow => 1 }, 34 { 'attr' => 'sum_discount', 'type' => 'string', 'rusname' => 'Сумма скидки', shortname => 'Скидка', 35 column => 6, postshow => 1, facilshow => 1 }, 32 36 { 'attr' => 'sum_delivery', 'type' => 'string', 'rusname' => 'Стоимость доставки', shortname => 'Доставка', 33 column => 6, postshow => 1, facilshow => 1 }, 37 column => 7, postshow => 1, facilshow => 1 }, 38 { 'attr' => 'sum_total', 'type' => 'string', 'rusname' => 'Сумма общая', shortname => 'Total', 39 column => 8, virtual => 1, postshow => 1, facilshow => 1 }, 34 40 { 'attr' => 'contact', 'type' => 'string', 'rusname' => 'Контактное лицо', facilshow => 1 }, 35 41 { 'attr' => 'email', 'type' => 'string', 'rusname' => 'E-mail для связи', shortname => 'E-mail', 36 42 column => 3, postshow => 1, facilshow => 1, mandatory => 1, }, … … 59 65 ) 60 66 } 61 67 68 69 sub sum_total { 70 my $self = shift; 71 72 return ($self->sum || 0) - ($self->sum_discount || 0) + ($self->sum_delivery || 0); 73 } 74 75 62 76 sub sum_formatted { 63 77 my $self = shift; 64 78 … … 73 87 sub total_formatted { 74 88 my $self = shift; 75 89 76 my $price = $self->sum + ($self->sum_delivery || 0); 77 $price = reverse $price; 78 $price =~ s/(\d{3})/$1\ /g; 79 $price = reverse $price; 80 81 return $price; 90 my $price = $self->sum_total; 91 return $keeper->{webshop}->price_format( $price ); 82 92 } 83 93 84 94 … … 117 127 foreach my $item ( @items ) { 118 128 $item->delete( attachments => 1 ); 119 129 } 130 my $sql = $self->keeper->SQL->prepare('DELETE FROM webshop_order_coupons where source_id = ?'); 131 $sql->execute( $self->id ); 132 120 133 1; 121 134 } 122 135 -
utf8/plugins/webshop/lib/webshop/OrderCouponLink.pm
1 package webshop::OrderCouponLink; 2 3 use base 'Contenido::Link'; 4 use Contenido::Globals; 5 6 sub class_name 7 { 8 return 'Купон в заказе'; 9 } 10 11 sub class_description 12 { 13 return 'Купон в заказе'; 14 } 15 16 sub extra_properties 17 { 18 return ( 19 { 'attr' => 'status', 'type' => 'status', 'rusname' => 'Статус купона в заказе', 20 'cases' => [ 21 [0, 'зарегистрирован'], 22 [1, 'задействован'], 23 ], 24 }, 25 ); 26 } 27 28 sub class_table 29 { 30 return 'webshop::SQL::OrderCouponLinkTable'; 31 } 32 33 sub available_sources 34 { 35 return [ qw(webshop::Order) ]; 36 } 37 38 sub available_destinations 39 { 40 return [ qw(webshop::Coupon) ]; 41 } 42 43 1; -
utf8/plugins/webshop/lib/webshop/SQL/CouponLinkTable.pm
1 package webshop::SQL::CouponLinkTable; 2 3 use base 'SQL::LinkTable'; 4 5 sub db_table 6 { 7 return 'webshop_coupon_links'; 8 } 9 10 my $available_filters = [qw( 11 12 _class_filter 13 _status_filter 14 _in_id_filter 15 _id_filter 16 _name_filter 17 _class_excludes_filter 18 _excludes_filter 19 _datetime_filter 20 _date_equal_filter 21 _date_filter 22 23 _dest_id_filter 24 _source_id_filter 25 _source_class_filter 26 _dest_class_filter 27 28 )]; 29 30 sub available_filters { 31 return $available_filters; 32 } 33 34 35 # ---------------------------------------------------------------------------- 36 # Свойства храним в массивах, потому что порядок важен! 37 # Это общие свойства - одинаковые для всех документов. 38 # 39 # attr - обязательный параметр, название атрибута; 40 # type - тип аттрибута, требуется для отображдения; 41 # rusname - русское название, опять же требуется для отображения; 42 # hidden - равен 1, когда 43 # readonly - инициализации при записи только без изменения в дальнейшем 44 # db_field - поле в таблице 45 # default - значение по умолчанию (поле всегда имеет это значение) 46 # ---------------------------------------------------------------------------- 47 sub required_properties 48 { 49 my $self = shift; 50 51 my @parent_properties = $self->SUPER::required_properties; 52 return ( 53 @parent_properties, 54 ); 55 } 56 57 ########### FILTERS DESCRIPTION ############################################################################### 58 59 1; -
utf8/plugins/webshop/lib/webshop/SQL/CouponsTable.pm
1 package webshop::SQL::CouponsTable; 2 3 use base 'SQL::DocumentTable'; 4 5 sub db_table 6 { 7 return 'webshop_coupons'; 8 } 9 10 my $available_filters = [qw( 11 12 _class_filter 13 _status_filter 14 _in_id_filter 15 _id_filter 16 _name_filter 17 _class_excludes_filter 18 _sfilter_filter 19 _excludes_filter 20 _datetime_filter 21 _date_equal_filter 22 _date_filter 23 _previous_days_filter 24 _s_filter 25 _link_filter 26 27 _uid_filter 28 _code_filter 29 )]; 30 31 sub available_filters { 32 return $available_filters; 33 } 34 35 36 # ---------------------------------------------------------------------------- 37 # Свойства храним в массивах, потому что порядок важен! 38 # Это общие свойства - одинаковые для всех документов. 39 # 40 # attr - обязательный параметр, название атрибута; 41 # type - тип аттрибута, требуется для отображдения; 42 # rusname - русское название, опять же требуется для отображения; 43 # hidden - равен 1, когда 44 # readonly - инициализации при записи только без изменения в дальнейшем 45 # db_field - поле в таблице 46 # default - значение по умолчанию (поле всегда имеет это значение) 47 # ---------------------------------------------------------------------------- 48 sub required_properties 49 { 50 my $self = shift; 51 52 my @parent_properties = grep { $_->{attr} ne 'sections' && $_->{attr} ne 'dtime' } $self->SUPER::required_properties; 53 return ( 54 @parent_properties, 55 { 56 'attr' => 'code', 57 'type' => 'string', 58 'rusname' => 'Код купона', 59 'shortname' => 'Код', 60 'column' => 1, 61 'rem' => 'Оставьте незаполненным для автоматической генерации', 62 'db_field' => 'code', 63 'db_type' => 'text', 64 }, 65 { 66 'attr' => 'dtime', 67 'type' => 'datetime', 68 'rusname' => 'Начало действия купона', 69 'shortname' => 'Начало', 70 'column' => 4, 71 'db_field' => 'dtime', 72 'db_type' => 'timestamp', 73 'db_opts' => 'not null default now()', 74 'default' => 'CURRENT_TIMESTAMP', 75 }, 76 { 77 'attr' => 'etime', 78 'type' => 'datetime', 79 'rusname' => 'Окончание действия купона', 80 'shortname' => 'Конец', 81 'column' => 5, 82 'db_field' => 'etime', 83 'db_type' => 'timestamp', 84 'db_opts' => 'not null default now()', 85 'default' => 'CURRENT_TIMESTAMP', 86 }, 87 { 88 'attr' => 'pid', 89 'type' => 'integer', 90 'rusname' => 'ID прототипа', 91 'hidden' => 1, 92 'db_field' => 'pid', 93 'db_type' => 'integer', 94 'db_opts' => "not null default 0", 95 'default' => 0, 96 }, 97 { # User ID 98 'attr' => 'uid', 99 'type' => 'integer', 100 'rusname' => 'ID пользователя', 101 'db_field' => 'uid', 102 'db_type' => 'integer', 103 'db_opts' => "not null default 0", 104 'default' => 0, 105 }, 106 { 107 'attr' => 'groups', 108 'type' => 'lookup_multi', 109 'rusname' => 'Группы товаров', 110 'db_field' => 'groups', 111 'db_type' => 'integer[]', 112 }, 113 { 114 'attr' => 'sections', 115 'type' => 'sections_list', 116 'rusname' => 'Секции', 117 'hidden' => 1, 118 'db_field' => 'sections', 119 'db_type' => 'integer', 120 }, 121 ); 122 } 123 124 ########### FILTERS DESCRIPTION ############################################################################### 125 sub _s_filter { 126 my ($self,%opts)=@_; 127 return undef unless ( exists $opts{s} ); 128 return &SQL::Common::_generic_int_filter('d.sections', $opts{s}); 129 } 130 131 sub _uid_filter { 132 my ($self,%opts)=@_; 133 return undef unless ( exists $opts{uid} ); 134 return &SQL::Common::_generic_int_filter('d.uid', $opts{uid}); 135 } 136 137 sub _code_filter { 138 my ($self,%opts)=@_; 139 return undef unless ( exists $opts{code} ); 140 return &SQL::Common::_generic_name_filter('d.code', $opts{code}, undef, \%opts); 141 } 142 143 sub _interval_filter { 144 my ($self,%opts)=@_; 145 return undef unless ( exists $opts{interval} && ref $opts{interval} eq 'ARRAY' && scalar @{$opts{interval}} == 2 ); 146 return undef unless ref $opts{interval}->[0] && ref $opts{interval}->[1]; 147 return undef if DateTime->compare($opts{interval}->[0], $opts{interval}->[1]) == 1; 148 149 my $wheres = "d.dtime <= ? and d.etime >= ?"; 150 my @values = ($opts{interval}->[1]->ymd('-'), $opts{interval}->[0]->ymd('-')); 151 152 return ($wheres, \@values); 153 } 154 155 sub _link_filter { 156 my ($self,%opts)=@_; 157 158 my @wheres=(); 159 my @binds=(); 160 161 # Связь определенного класса 162 if (exists($opts{lclass})) { 163 my ($where, $values) = SQL::Common::_generic_text_filter('l.class', $opts{lclass}); 164 push (@wheres, $where); 165 push (@binds, ref($values) ? @$values:$values) if (defined $values); 166 } 167 168 my $lclass = $opts{lclass} || 'Contenido::Link'; 169 my $link_table = $lclass->_get_table->db_table(); 170 171 # Ограничение по статусу связи 172 if ( exists $opts{lstatus} ) { 173 my ($where, $values) = SQL::Common::_generic_int_filter('l.status', $opts{lstatus}); 174 push (@wheres, $where); 175 push (@binds, ref($values) ? @$values:$values) if (defined $values); 176 } 177 178 # Ограничение по uid 179 if ( exists $opts{luid} ) { 180 my ($where, $values) = SQL::Common::_generic_int_filter('l.uid', $opts{luid}); 181 push (@wheres, $where); 182 push (@binds, ref($values) ? @$values:$values) if (defined $values); 183 } 184 185 # Ограничение по сессии 186 if ( exists $opts{lsession} ) { 187 my ($where, $values) = SQL::Common::_generic_text_filter('l.session', $opts{lsession}); 188 push (@wheres, $where); 189 push (@binds, ref($values) ? @$values:$values) if (defined $values); 190 } 191 192 # Связь с определенным документ(ом/тами) по цели линка 193 if ( exists $opts{ldest} ) { 194 my ($where, $values) = SQL::Common::_generic_int_filter('l.dest_id', $opts{ldest}); 195 push (@wheres, $where); 196 push (@binds, ref($values) ? @$values:$values) if (defined $values); 197 if ($self->_single_class) { 198 return (\@wheres, \@binds, " join $link_table as l on l.source_id=d.id"); 199 } else { 200 return (\@wheres, \@binds, " join $link_table as l on l.source_id=d.id and l.source_class=d.class"); 201 } 202 } 203 # Связь с определенным документ(ом/тами) по источнику линка 204 if ( exists $opts{lsource} ) { 205 my ($where, $values) = SQL::Common::_generic_int_filter('l.source_id', $opts{lsource}); 206 push (@wheres, $where); 207 push (@binds, ref($values) ? @$values:$values) if (defined $values); 208 if ($self->_single_class) { 209 return (\@wheres, \@binds, " join $link_table as l on l.dest_id=d.id"); 210 } else { 211 return (\@wheres, \@binds, " join $link_table as l on l.dest_id=d.id and l.dest_class=d.class"); 212 } 213 } 214 215 return (undef); 216 } 217 218 219 1; -
utf8/plugins/webshop/lib/webshop/SQL/OrderCouponLinkTable.pm
1 package webshop::SQL::OrderCouponLinkTable; 2 3 use base 'SQL::LinkTable'; 4 use Contenido::Globals; 5 6 sub db_table 7 { 8 return 'webshop_order_coupons'; 9 } 10 11 my $available_filters = [qw( 12 13 _class_filter 14 _status_filter 15 _in_id_filter 16 _id_filter 17 _name_filter 18 _class_excludes_filter 19 _excludes_filter 20 _datetime_filter 21 _date_equal_filter 22 _date_filter 23 24 _dest_id_filter 25 _source_id_filter 26 _source_class_filter 27 _dest_class_filter 28 29 _uid_filter 30 _session_filter 31 )]; 32 33 sub available_filters { 34 return $available_filters; 35 } 36 37 38 # ---------------------------------------------------------------------------- 39 # Свойства храним в массивах, потому что порядок важен! 40 # Это общие свойства - одинаковые для всех документов. 41 # 42 # attr - обязательный параметр, название атрибута; 43 # type - тип аттрибута, требуется для отображдения; 44 # rusname - русское название, опять же требуется для отображения; 45 # hidden - равен 1, когда 46 # readonly - инициализации при записи только без изменения в дальнейшем 47 # db_field - поле в таблице 48 # default - значение по умолчанию (поле всегда имеет это значение) 49 # ---------------------------------------------------------------------------- 50 sub required_properties 51 { 52 my $self = shift; 53 54 my @parent_properties = $self->SUPER::required_properties; 55 return ( 56 @parent_properties, 57 { # User ID 58 'attr' => 'uid', 59 'type' => 'pickup', 60 'rusname' => 'ID пользователя', 61 'lookup_opts' => { 62 class => $state->{users}->profile_document_class, 63 order_by => 'email', 64 search_by => 'email' 65 }, 66 'db_field' => 'uid', 67 'db_type' => 'integer', 68 'db_opts' => "default 0", 69 'default' => 0, 70 }, 71 { # ID Сессии 72 'attr' => 'session', 73 'type' => 'string', 74 'rusname' => 'ID Сессии пользователя', 75 'db_field' => 'session', 76 'db_type' => 'text', 77 }, 78 ); 79 } 80 81 ########### FILTERS DESCRIPTION ############################################################################### 82 sub _uid_filter { 83 my ($self,%opts)=@_; 84 return undef unless ( exists $opts{uid} ); 85 return &SQL::Common::_generic_int_filter('d.uid', $opts{uid}); 86 } 87 88 sub _session_filter { 89 my ($self,%opts)=@_; 90 return undef unless ( exists $opts{session} ); 91 return &SQL::Common::_generic_text_filter('d.session', $opts{session}); 92 } 93 94 1; -
utf8/plugins/webshop/lib/webshop/State.pm.proto
20 20 # $self->{db_password} = ''; 21 21 # $self->{db_port} = ''; 22 22 $self->{profile_document_class} = '@PROFILE_DOCUMENT_CLASS@' || 'users::UserProfile'; 23 $self->{item_document_class} = '@ITEM_DOCUMENT_CLASS@' || 'webshop::Item'; 23 $self->{item_document_class} = '@ITEM_DOCUMENT_CLASS@' ? [qw( @ITEM_DOCUMENT_CLASS@ )] : ['webshop::Item']; 24 $self->{item_section_class} = '@ITEM_SECTION_CLASS@' ? [qw( @ITEM_SECTION_CLASS@ )] : ['webshop::ItemSection']; 24 25 25 26 $self->_init_(); 26 27 $self; -
utf8/plugins/webshop/sql/TOAST/coupon_links.sql
1 create table webshop_coupon_links 2 ( 3 id integer not null primary key default nextval('public.documents_id_seq'::text), 4 class text not null, 5 ctime timestamp not null default now(), 6 mtime timestamp not null default now(), 7 status smallint not null default 1, 8 source_id integer not null, 9 source_class text not null default 'webshop::Coupon', 10 dest_id integer not null, 11 dest_class text not null, 12 data text 13 ); 14 create index webshop_coupon_links_source on webshop_coupon_links (source_id); 15 create index webshop_coupon_links_dest on webshop_coupon_links (dest_id); -
utf8/plugins/webshop/sql/TOAST/coupons.sql
1 CREATE TABLE webshop_coupons ( 2 id integer DEFAULT nextval(('public.documents_id_seq'::text)::regclass) NOT NULL, 3 ctime timestamp without time zone DEFAULT now() NOT NULL, 4 mtime timestamp without time zone DEFAULT now() NOT NULL, 5 dtime timestamp without time zone DEFAULT now() NOT NULL, 6 etime timestamp without time zone DEFAULT now() NOT NULL, 7 class text DEFAULT 'webshop::Coupon'::text NOT NULL, 8 status smallint default 0 NOT NULL, 9 sections integer, 10 pid integer default 0, 11 uid integer default 0, 12 groups integer[], 13 name text, 14 code text not null, 15 data text 16 ); 17 18 CREATE INDEX webshop_coupons_sections ON webshop_coupons USING btree (sections); 19 CREATE INDEX webshop_coupons_uid ON webshop_coupons USING btree (uid); 20 CREATE INDEX webshop_coupons_code ON webshop_coupons USING btree (code); -
utf8/plugins/webshop/sql/TOAST/order_coupons.sql
1 CREATE TABLE webshop_order_coupons ( 2 id integer DEFAULT nextval(('public.documents_id_seq'::text)::regclass) NOT NULL, 3 ctime timestamp without time zone DEFAULT now() NOT NULL, 4 mtime timestamp without time zone DEFAULT now() NOT NULL, 5 class text DEFAULT 'webshop::OrderCouponLink'::text NOT NULL, 6 status smallint default 0 NOT NULL, 7 uid integer default 0, 8 session text, 9 source_id integer, 10 source_class text default 'webshop::Order'::text, 11 dest_id integer, 12 dest_class text default 'webshop::Coupon'::text, 13 data text 14 ); 15 16 CREATE INDEX webshop_order_coupons_user ON webshop_order_coupons USING btree (uid, session); 17 create index webshop_order_coupons_source on webshop_order_coupons (source_id); 18 create index webshop_order_coupons_dest on webshop_order_coupons (dest_id);