Commit | Line | Data |
---|---|---|
d415d0ba | 1 | package BSE::Importer::Target::Product; |
3709451d | 2 | use strict; |
d415d0ba | 3 | use base 'BSE::Importer::Target::Article'; |
3709451d TC |
4 | use BSE::API qw(bse_make_product bse_make_catalog bse_add_image); |
5 | use Articles; | |
6 | use Products; | |
0cca6ce6 TC |
7 | use BSE::TB::ProductOptions; |
8 | use BSE::TB::ProductOptionValues; | |
df2663f0 | 9 | use BSE::TB::PriceTiers; |
3709451d | 10 | |
57e4a9c7 | 11 | our $VERSION = "1.003"; |
d415d0ba TC |
12 | |
13 | =head1 NAME | |
14 | ||
15 | BSE::Importer::Target::Product - import target for products | |
16 | ||
17 | =head1 SYNOPSIS | |
18 | ||
19 | [import profile foo] | |
20 | ... | |
21 | ; these are the defaults | |
22 | codes=0 | |
23 | code_field=product_code | |
24 | parent=3 | |
25 | ignore_missing=1 | |
26 | reset_images=0 | |
27 | reset_steps=0 | |
28 | price_dollar=0 | |
29 | prodopt_value_sep=| | |
30 | reset_prodopts=1 | |
31 | ||
32 | # done by the importer | |
33 | my $target = BSE::Importer::Target::Product->new | |
34 | (importer => $importer, opts => \%opts) | |
35 | ... | |
36 | $target->start($imp); | |
37 | # for each row: | |
38 | $target->row($imp, \%entry, \@parents); | |
39 | ||
40 | =head1 DESCRIPTION | |
41 | ||
42 | Provides a target for importing BSE products. | |
43 | ||
44 | The import profile must provide C<title> and C<retailPrice> mappings. | |
45 | ||
46 | =head1 CONFIGURATION | |
47 | ||
48 | This is in addition to the configuration in | |
49 | L<BSE::Importer::Target::Article/CONFIGURATION>. | |
50 | ||
51 | =over | |
52 | ||
53 | =item * | |
54 | ||
55 | C<code_field> - the default changes to C<product_code> | |
56 | ||
57 | =item * | |
58 | ||
59 | C<parent> - the default changes to the id of the shop article. | |
60 | ||
61 | =item * | |
62 | ||
63 | C<price_dollar> - if true, the C<retailPrice> field and tier prices | |
64 | are treated as dollar amounts rather than cents. Default: 0. | |
65 | ||
66 | =item * | |
67 | ||
68 | C<prodopt_value_sep> - the separator between product options. | |
69 | Default: C<|>. | |
70 | ||
71 | =item * | |
72 | ||
73 | C<reset_prodopts> - if true, product options are reset when updating a | |
74 | product. Default: 1. | |
75 | ||
76 | =back | |
77 | ||
78 | =head1 SPECIAL FIELDS | |
79 | ||
80 | In addition to those in L<BSE::Importer::Target::Article/SPECIAL | |
81 | FIELDS>, the following fields are used to import extra information | |
82 | into products: | |
83 | ||
84 | =over | |
85 | ||
86 | =item * | |
87 | ||
88 | C<< prodoptI<index>_name >> - define the name of a product option. | |
89 | C<index> can be from 1 to 10. | |
90 | ||
91 | =item * | |
92 | ||
93 | C<< prodoptI<index>_values >> - define the values for a product | |
94 | option, separated by the configured C<prodop_value_sep>. | |
95 | ||
96 | =item * | |
97 | ||
98 | C<< tier_proce_I<tier_id> >> - set the product price for the specified | |
99 | tier. | |
100 | ||
101 | =back | |
102 | ||
103 | =head1 METHODS | |
104 | ||
105 | =over | |
106 | ||
107 | =item new() | |
108 | ||
109 | Create a new article import target. Follows the protocol specified by | |
110 | L<BSE::Importer::Target::Base>. | |
111 | ||
112 | =cut | |
cb7fd78d | 113 | |
3709451d TC |
114 | sub new { |
115 | my ($class, %opts) = @_; | |
116 | ||
117 | my $self = $class->SUPER::new(%opts); | |
118 | ||
119 | my $importer = delete $opts{importer}; | |
120 | ||
121 | $self->{price_dollar} = $importer->cfg_entry('price_dollar', 0); | |
122 | $self->{product_template} = $importer->cfg_entry('product_template'); | |
123 | $self->{catalog_template} = $importer->cfg_entry('catalog_template'); | |
0cca6ce6 TC |
124 | $self->{prodopt_value_sep} = $importer->cfg_entry("prodopt_separator", "|"); |
125 | $self->{reset_prodopts} = $importer->cfg_entry("reset_prodopts", 1); | |
3709451d | 126 | |
0cca6ce6 | 127 | my $map = $importer->maps; |
57e4a9c7 TC |
128 | unless ($importer->update_only) { |
129 | defined $map->{retailPrice} | |
130 | or die "No retailPrice mapping found\n"; | |
131 | } | |
3709451d | 132 | |
df2663f0 TC |
133 | $self->{price_tiers} = +{ map { $_->id => $_ } BSE::TB::PriceTiers->all }; |
134 | ||
3709451d TC |
135 | return $self; |
136 | } | |
137 | ||
d415d0ba TC |
138 | =item xform_entry() |
139 | ||
140 | Called by row() to perform an extra data transformation needed. | |
141 | ||
142 | Currently this forces non-blank code fields if C<codes> is set, | |
143 | removes the dollar sign if any from the retail prices, transforms the | |
144 | retail price from dollars to cents if C<price_dollar> is configured | |
145 | and warns if no price is set. | |
146 | ||
147 | =cut | |
148 | ||
3709451d TC |
149 | sub xform_entry { |
150 | my ($self, $importer, $entry) = @_; | |
151 | ||
152 | $self->SUPER::xform_entry($importer, $entry); | |
153 | ||
154 | if ($self->{use_codes}) { | |
57e4a9c7 TC |
155 | $entry->{$self->{code_field}} =~ /\S/ |
156 | or die "$self->{code_field} blank with use_codes\n"; | |
3709451d | 157 | } |
3709451d | 158 | |
57e4a9c7 TC |
159 | if (exists $entry->{retailPrice}) { |
160 | $entry->{retailPrice} =~ s/\$//; # in case | |
161 | ||
162 | if ($entry->{retailPrice} =~ /\d/) { | |
163 | $self->{price_dollar} | |
164 | and $entry->{retailPrice} *= 100; | |
165 | } | |
166 | else { | |
167 | $importer->warn("Warning: no price"); | |
168 | $entry->{retailPrice} = 0; | |
169 | } | |
3709451d TC |
170 | } |
171 | } | |
172 | ||
d415d0ba TC |
173 | =item children_of() |
174 | ||
175 | Returns catalogs that are a child of the specified article. | |
176 | ||
3709451d TC |
177 | sub children_of { |
178 | my ($self, $parent) = @_; | |
179 | ||
180 | return grep $_->{generator} eq 'Generate::Catalog', | |
181 | Articles->children($parent); | |
182 | } | |
183 | ||
d415d0ba TC |
184 | =item make_parent() |
185 | ||
186 | Create a catalog. | |
187 | ||
188 | =cut | |
189 | ||
3709451d TC |
190 | sub make_parent { |
191 | my ($self, $importer, %entry) = @_; | |
192 | ||
3709451d TC |
193 | return bse_make_catalog(%entry); |
194 | } | |
195 | ||
d415d0ba TC |
196 | =item find_leaf() |
197 | ||
198 | Find an existing product matching the code. | |
199 | ||
200 | =cut | |
201 | ||
3709451d | 202 | sub find_leaf { |
57e4a9c7 | 203 | my ($self, $leaf_id, $importer) = @_; |
3709451d | 204 | |
0cca6ce6 | 205 | my ($leaf) = Products->getBy($self->{code_field}, $leaf_id) |
3709451d TC |
206 | or return; |
207 | ||
57e4a9c7 TC |
208 | $importer->event(find_leaf => { id => $leaf_id, leaf => $leaf }); |
209 | ||
0cca6ce6 TC |
210 | if ($self->{reset_prodopts}) { |
211 | my @options = $leaf->db_options; | |
212 | for my $option (@options) { | |
213 | $option->remove; | |
214 | } | |
215 | } | |
216 | ||
3709451d TC |
217 | return $leaf; |
218 | } | |
219 | ||
d415d0ba TC |
220 | =item make_leaf() |
221 | ||
222 | Make a new product. | |
223 | ||
224 | =cut | |
225 | ||
3709451d TC |
226 | sub make_leaf { |
227 | my ($self, $importer, %entry) = @_; | |
228 | ||
57e4a9c7 TC |
229 | my $leaf = bse_make_product(%entry); |
230 | ||
231 | $importer->event(make_leaf => { leaf => $leaf }); | |
232 | ||
233 | return $leaf; | |
3709451d TC |
234 | } |
235 | ||
d415d0ba TC |
236 | =item fill_leaf() |
237 | ||
238 | Fill in the product with the new data. | |
239 | ||
240 | =cut | |
241 | ||
0cca6ce6 TC |
242 | sub fill_leaf { |
243 | my ($self, $importer, $leaf, %entry) = @_; | |
3709451d | 244 | |
0cca6ce6 TC |
245 | my $ordering = time; |
246 | for my $opt_num (1 .. 5) { | |
247 | my $name = $entry{"prodopt${opt_num}_name"}; | |
248 | my $values = $entry{"prodopt${opt_num}_values"}; | |
3709451d | 249 | |
0cca6ce6 TC |
250 | defined $name && $name =~ /\S/ && $values =~ /\S/ |
251 | or next; | |
252 | my @values = split /\Q$self->{prodopt_value_sep}/, $values | |
3709451d | 253 | or next; |
3709451d | 254 | |
0cca6ce6 | 255 | my $option = BSE::TB::ProductOptions->make |
3709451d | 256 | ( |
0cca6ce6 TC |
257 | product_id => $leaf->id, |
258 | name => $name, | |
259 | display_order => $ordering++, | |
3709451d | 260 | ); |
3709451d | 261 | |
0cca6ce6 TC |
262 | for my $value (@values) { |
263 | my $entry = BSE::TB::ProductOptionValues->make | |
264 | ( | |
265 | product_option_id => $option->id, | |
266 | value => $value, | |
267 | display_order => $ordering++, | |
268 | ); | |
269 | } | |
3709451d | 270 | } |
df2663f0 TC |
271 | |
272 | my %prices; | |
273 | for my $tier_id (keys %{$self->{price_tiers}}) { | |
274 | my $price = $entry{"tier_price_$tier_id"}; | |
275 | if (defined $price && $price =~ /\d/) { | |
276 | $price =~ s/\$//; # in case | |
277 | $price *= 100 if $self->{price_dollar}; | |
278 | ||
279 | $prices{$tier_id} = $price; | |
280 | } | |
281 | } | |
282 | ||
283 | $leaf->set_prices(\%prices); | |
284 | ||
0cca6ce6 | 285 | return $self->SUPER::fill_leaf($importer, $leaf, %entry); |
3709451d TC |
286 | } |
287 | ||
d415d0ba TC |
288 | =item default_parent() |
289 | ||
290 | Overrides the default parent. | |
291 | ||
292 | =cut | |
293 | ||
3709451d TC |
294 | sub default_parent { 3 } |
295 | ||
d415d0ba TC |
296 | =item default_code_field() |
297 | ||
298 | Overrides the default code field. | |
299 | ||
300 | =cut | |
301 | ||
3709451d TC |
302 | sub default_code_field { "product_code" } |
303 | ||
57e4a9c7 TC |
304 | =item key_fields |
305 | ||
306 | Fields that can act as key fields. | |
307 | ||
308 | =cut | |
309 | ||
310 | sub key_fields { | |
311 | my ($class) = @_; | |
312 | ||
313 | return ( $class->SUPER::key_fields(), "product_code" ); | |
314 | } | |
315 | ||
3709451d | 316 | 1; |
d415d0ba TC |
317 | |
318 | =back | |
319 | ||
320 | =head1 AUTHOR | |
321 | ||
322 | Tony Cook <tony@develop-help.com> | |
323 | ||
324 | =cut |