Report abuse

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
#!/usr/bin/perl
#
# Author    : BigDiver
# Copyright : 2009 all rights reserved.
#
# Please do not remove this copyright notice and credit the original author
#
# import-products is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# import-products is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with import-products.  If not, see <http://www.gnu.org/licenses/>.
#

# Perl Strictness, pretty vars, and version
use strict;
use warnings;
use English qw( -no_match_vars );
use version; my $VERSION = qv('0.01');

# Needed CPAN Modules
use WWW::Salesforce::Simple;
use Getopt::Long;
use Text::CSV_XS;
use Scalar::Util qw( looks_like_number );

# Command Line processing
my %cli_options = command_line();

# Parse CSV product file and setup quantities HASH
# Hash Keys   : Product SKU
# hash Values : Product Quantity
my %quantity_of = get_products_from( $cli_options{csv} );

# Login to Salesforce with user credentials
my $sf = {};
eval {
    $sf = WWW::Salesforce::Simple->new(
        'username' => $cli_options{ user },
        'password' => $cli_options{ pass },
    );
};
die $EVAL_ERROR if ( $EVAL_ERROR );

die "Error: user $cli_options{user} could not login to Salesforce.com"
    if( !$sf );
    
# Check if the user asked to delete all products before importing file
if ( $cli_options{ delete } ) {
    salesforce_delete_products_from( $sf, $cli_options{ opportunity } );
}

# Get the necessary product information from Salesforce.com
# We need the PricebookEntryId, UnitPrice and ProductCode
for my $product_code ( keys %quantity_of ) {
    my $salesforce_product_ref =
        salesforce_get_product_wth_code($sf, $product_code);

    # If the product is not Active in Salesforce do not add it
    next if ( !$salesforce_product_ref->{ IsActive } );
    
    if ( !$salesforce_product_ref->{ Pricebook2Id } ) {
        print "Product $product_code not found in Salesforce.com. Skipping \n";
        next;
    }
    
    # Add product to opportunity
    print "Adding - SKU: $salesforce_product_ref->{ProductCode}"
        . ", Quantity: $quantity_of{$product_code}\n";
        
    eval {
        $sf->create(
            type=> "OpportunityLineItem",
            OpportunityId    => $cli_options{ opportunity },
            Quantity         => $quantity_of{ $product_code },
            Description      => $salesforce_product_ref->{ Name },
            UnitPrice        => $salesforce_product_ref->{ UnitPrice },
            PricebookEntryId => $salesforce_product_ref->{ Id }->[0],
        );
    };
    
    die $EVAL_ERROR if( $EVAL_ERROR );
    
}

print "Done\n";
### End Main ###

# Get products from CSV file
# Arguments: $filename - the name of the CSV file
# Returns: Hash with the products
sub get_products_from {
    my ($file_name ) = @_;
    
    my @rows;
    my %products;
    
    my $csv = Text::CSV_XS->new ({ binary => 1 }) or
        die "Cannot use CSV: ".Text::CSV->error_diag ();
    
    open my $fh, "<:encoding(utf8)", $file_name
        or die "$file_name: $!";
    
    while (my $row = $csv->getline ($fh)) {
        
        if ( looks_like_number $row->[1] ) { 
            $products{ $row->[0] } = $row->[1];
        }
        else {
            die "$file_name: Bad quantity $row->[1]\n";
        }
    }
    $csv->eof or $csv->error_diag ();
    close $fh;
    
    return %products;
}

# Get product information from Salesforce.com based on the product code or SKU
# Arguments
#      $sf          - WWW::Salesforce object
#      $product_sku - Product code
# Returns: Hash ref with all the product information. Check SOQL for more info
sub salesforce_get_product_wth_code {
    my ($sforce, $product_sku) = @_;
    
    my $query =
          qq{SELECT Id, Name, Pricebook2Id, Product2Id, UnitPrice, IsActive, }
        . qq{UseStandardPrice, CreatedDate, CreatedById, LastModifiedDate, }
        . qq{LastModifiedById, SystemModstamp, ProductCode, IsDeleted }
        . qq{FROM PricebookEntry WHERE ProductCode = '$product_sku'};
    
    my $query_result_ref;
    eval {
        $query_result_ref = $sforce->do_query($query, 1);
    };
    
    die $EVAL_ERROR if( $EVAL_ERROR );
    
    # If no records were found just return empty hash
    return {} if( !$query_result_ref);
    
    return $query_result_ref->[0];
}

# Delete all products associated with an opportunity
# Arguments
#      $sf             - WWW::Salesforce object
#      $opportunity_id - Salesforce.com opportunity ID
# Returns: nothing
sub salesforce_delete_products_from {
    my ($sforce, $opportunity_id) = @_;
    
    # SOQL Query to get the product list
    my $query =
          qq{SELECT Id FROM OpportunityLineItem }
        . qq{WHERE OpportunityId = '$opportunity_id'};
    
    # Send the query to Salesforce.com
    my $query_result_ref;
    eval {
        $query_result_ref = $sforce->do_query($query, 1);
    };
    die $EVAL_ERROR if( $EVAL_ERROR );
    
    # If no records were found just return
    return if( !$query_result_ref);
    
    # Save the product IDs that we got back
    my @products_to_delete = ();
    for my $result_ref ( @{ $query_result_ref } ) {
        push @products_to_delete, $result_ref->{Id}->[0];
    }
    
    # Now delete all the products from the OpportunityLineItem table
    eval {
        $sforce->delete( @products_to_delete );
    };
    die $EVAL_ERROR if( $EVAL_ERROR );
}

# Simple command line processing
sub command_line {
    my %cli;
    print "\nimport-products $VERSION";
    print "\nAuthor: BigDiver (bigdiver.wordpress.com)\n\n";

    ## process and check cmd line args ##
    GetOptions(\%cli,
           "help",
           "version",
           "quiet",
           "csv=s",
           "opportunity=s",
           "user=s",
           "pass=s",
           "delete",
          );

    if ( $cli{ help } || $cli{ version })  {
        usage();
        exit;
    }
    
    # Check validity of the must have parameters
    if (    !$cli{ csv }
         || !$cli{ opportunity }
         || !$cli{ user }
         || !$cli{ pass } )  {
        
        usage(); exit(0);
    
    }

    return %cli;
}

# Just pring script usage
sub usage {
    print <<"HERE";

Import products from CSV formated file into Salesforce.com Opportunity

    import-products --csv=<product-file.csv> --opportunity=<opportunityID>
                    --user=<user name> --pass=<password>
                    [--delete]

        product-file.csv
            MUST be a Comma Separated file with two columns (SKU, Quantity).
            No header line.
       
        opportunityID
            MUST be an existant Salesforce Opportunity to which you want to add
            the products from the <products-file.csv> to.
          
        user, pass
            Salesforce.com user name and password.
          
        delete
            Delete all of the opportunities products before adding any new ones.
          
HERE
}