don't abort an article import just because one file failed
[bse.git] / site / cgi-bin / modules / BSE / Importer / Target / Product.pm
CommitLineData
d415d0ba 1package BSE::Importer::Target::Product;
3709451d 2use strict;
d415d0ba 3use base 'BSE::Importer::Target::Article';
3709451d
TC
4use BSE::API qw(bse_make_product bse_make_catalog bse_add_image);
5use Articles;
6use Products;
0cca6ce6
TC
7use BSE::TB::ProductOptions;
8use BSE::TB::ProductOptionValues;
df2663f0 9use BSE::TB::PriceTiers;
3709451d 10
57e4a9c7 11our $VERSION = "1.003";
d415d0ba
TC
12
13=head1 NAME
14
15BSE::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
42Provides a target for importing BSE products.
43
44The import profile must provide C<title> and C<retailPrice> mappings.
45
46=head1 CONFIGURATION
47
48This is in addition to the configuration in
49L<BSE::Importer::Target::Article/CONFIGURATION>.
50
51=over
52
53=item *
54
55C<code_field> - the default changes to C<product_code>
56
57=item *
58
59C<parent> - the default changes to the id of the shop article.
60
61=item *
62
63C<price_dollar> - if true, the C<retailPrice> field and tier prices
64are treated as dollar amounts rather than cents. Default: 0.
65
66=item *
67
68C<prodopt_value_sep> - the separator between product options.
69Default: C<|>.
70
71=item *
72
73C<reset_prodopts> - if true, product options are reset when updating a
74product. Default: 1.
75
76=back
77
78=head1 SPECIAL FIELDS
79
80In addition to those in L<BSE::Importer::Target::Article/SPECIAL
81FIELDS>, the following fields are used to import extra information
82into products:
83
84=over
85
86=item *
87
88C<< prodoptI<index>_name >> - define the name of a product option.
89C<index> can be from 1 to 10.
90
91=item *
92
93C<< prodoptI<index>_values >> - define the values for a product
94option, separated by the configured C<prodop_value_sep>.
95
96=item *
97
98C<< tier_proce_I<tier_id> >> - set the product price for the specified
99tier.
100
101=back
102
103=head1 METHODS
104
105=over
106
107=item new()
108
109Create a new article import target. Follows the protocol specified by
110L<BSE::Importer::Target::Base>.
111
112=cut
cb7fd78d 113
3709451d
TC
114sub 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
140Called by row() to perform an extra data transformation needed.
141
142Currently this forces non-blank code fields if C<codes> is set,
143removes the dollar sign if any from the retail prices, transforms the
144retail price from dollars to cents if C<price_dollar> is configured
145and warns if no price is set.
146
147=cut
148
3709451d
TC
149sub 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
175Returns catalogs that are a child of the specified article.
176
3709451d
TC
177sub 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
186Create a catalog.
187
188=cut
189
3709451d
TC
190sub 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
198Find an existing product matching the code.
199
200=cut
201
3709451d 202sub 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
222Make a new product.
223
224=cut
225
3709451d
TC
226sub 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
238Fill in the product with the new data.
239
240=cut
241
0cca6ce6
TC
242sub 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
290Overrides the default parent.
291
292=cut
293
3709451d
TC
294sub default_parent { 3 }
295
d415d0ba
TC
296=item default_code_field()
297
298Overrides the default code field.
299
300=cut
301
3709451d
TC
302sub default_code_field { "product_code" }
303
57e4a9c7
TC
304=item key_fields
305
306Fields that can act as key fields.
307
308=cut
309
310sub key_fields {
311 my ($class) = @_;
312
313 return ( $class->SUPER::key_fields(), "product_code" );
314}
315
3709451d 3161;
d415d0ba
TC
317
318=back
319
320=head1 AUTHOR
321
322Tony Cook <tony@develop-help.com>
323
324=cut