allow fetching products by linkAlias
[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
d415d0ba
TC
11our $VERSION = "1.002";
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;
3709451d
TC
128 defined $map->{retailPrice}
129 or die "No retailPrice mapping found\n";
130
df2663f0
TC
131 $self->{price_tiers} = +{ map { $_->id => $_ } BSE::TB::PriceTiers->all };
132
3709451d
TC
133 return $self;
134}
135
d415d0ba
TC
136=item xform_entry()
137
138Called by row() to perform an extra data transformation needed.
139
140Currently this forces non-blank code fields if C<codes> is set,
141removes the dollar sign if any from the retail prices, transforms the
142retail price from dollars to cents if C<price_dollar> is configured
143and warns if no price is set.
144
145=cut
146
3709451d
TC
147sub xform_entry {
148 my ($self, $importer, $entry) = @_;
149
150 $self->SUPER::xform_entry($importer, $entry);
151
152 if ($self->{use_codes}) {
153 $entry->{product_code} =~ /\S/
154 or die "product_code blank with use_codes\n";
155 }
156 $entry->{retailPrice} =~ s/\$//; # in case
157
158 if ($entry->{retailPrice} =~ /\d/) {
159 $self->{price_dollar}
160 and $entry->{retailPrice} *= 100;
161 }
162 else {
163 $importer->warn("Warning: no price");
164 $entry->{retailPrice} = 0;
165 }
166}
167
d415d0ba
TC
168=item children_of()
169
170Returns catalogs that are a child of the specified article.
171
3709451d
TC
172sub children_of {
173 my ($self, $parent) = @_;
174
175 return grep $_->{generator} eq 'Generate::Catalog',
176 Articles->children($parent);
177}
178
d415d0ba
TC
179=item make_parent()
180
181Create a catalog.
182
183=cut
184
3709451d
TC
185sub make_parent {
186 my ($self, $importer, %entry) = @_;
187
3709451d
TC
188 return bse_make_catalog(%entry);
189}
190
d415d0ba
TC
191=item find_leaf()
192
193Find an existing product matching the code.
194
195=cut
196
3709451d
TC
197sub find_leaf {
198 my ($self, $leaf_id) = @_;
199
0cca6ce6 200 my ($leaf) = Products->getBy($self->{code_field}, $leaf_id)
3709451d
TC
201 or return;
202
0cca6ce6
TC
203 if ($self->{reset_prodopts}) {
204 my @options = $leaf->db_options;
205 for my $option (@options) {
206 $option->remove;
207 }
208 }
209
3709451d
TC
210 return $leaf;
211}
212
d415d0ba
TC
213=item make_leaf()
214
215Make a new product.
216
217=cut
218
3709451d
TC
219sub make_leaf {
220 my ($self, $importer, %entry) = @_;
221
3709451d
TC
222 return bse_make_product(%entry);
223}
224
d415d0ba
TC
225=item fill_leaf()
226
227Fill in the product with the new data.
228
229=cut
230
0cca6ce6
TC
231sub fill_leaf {
232 my ($self, $importer, $leaf, %entry) = @_;
3709451d 233
0cca6ce6
TC
234 my $ordering = time;
235 for my $opt_num (1 .. 5) {
236 my $name = $entry{"prodopt${opt_num}_name"};
237 my $values = $entry{"prodopt${opt_num}_values"};
3709451d 238
0cca6ce6
TC
239 defined $name && $name =~ /\S/ && $values =~ /\S/
240 or next;
241 my @values = split /\Q$self->{prodopt_value_sep}/, $values
3709451d 242 or next;
3709451d 243
0cca6ce6 244 my $option = BSE::TB::ProductOptions->make
3709451d 245 (
0cca6ce6
TC
246 product_id => $leaf->id,
247 name => $name,
248 display_order => $ordering++,
3709451d 249 );
3709451d 250
0cca6ce6
TC
251 for my $value (@values) {
252 my $entry = BSE::TB::ProductOptionValues->make
253 (
254 product_option_id => $option->id,
255 value => $value,
256 display_order => $ordering++,
257 );
258 }
3709451d 259 }
df2663f0
TC
260
261 my %prices;
262 for my $tier_id (keys %{$self->{price_tiers}}) {
263 my $price = $entry{"tier_price_$tier_id"};
264 if (defined $price && $price =~ /\d/) {
265 $price =~ s/\$//; # in case
266 $price *= 100 if $self->{price_dollar};
267
268 $prices{$tier_id} = $price;
269 }
270 }
271
272 $leaf->set_prices(\%prices);
273
0cca6ce6 274 return $self->SUPER::fill_leaf($importer, $leaf, %entry);
3709451d
TC
275}
276
d415d0ba
TC
277=item default_parent()
278
279Overrides the default parent.
280
281=cut
282
3709451d
TC
283sub default_parent { 3 }
284
d415d0ba
TC
285=item default_code_field()
286
287Overrides the default code field.
288
289=cut
290
3709451d
TC
291sub default_code_field { "product_code" }
292
2931;
d415d0ba
TC
294
295=back
296
297=head1 AUTHOR
298
299Tony Cook <tony@develop-help.com>
300
301=cut