#!/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
}