#!/usr/bin/perl -Ilibs

package API::CycleCount;

use strict;
use warnings;
use autodie;

use API::Config;
use lib ($API::Config::Config->{'cycleCountLibs'});
use lib ($API::Config::Config->{'Website_libs'});
use Digest::MD5;

use Moose;
use ECS::Site;
use ECS::Search::Product;

use ECS::CycleCount::User;
use ECS::CycleCount::Product;
use ECS::CycleCount::Brand;
use ECS::CycleCount::User::ScannerUser;
use ECS::CycleCount::User::UserLocation;
use ECS::CycleCount::User::UserLocation::Location;
use ECS::CycleCount::User::UserLocation::Done;
use ECS::CycleCount::User::LocationFunction;
use ECS::CycleCount::User::Scan;
use ECS::CycleCount::ScanLog;

use ECS::Product;

use Scalar::Util qw(blessed reftype);
use List::Util qw/all any none first/;
use API::Controller ();
extends 'API::Controller';

__PACKAGE__->meta->make_immutable;


=pod

=head1 NAME

API::CycleCount - API for Cycle Count

=head1 SYNOPSIS

=head1 DESCRIPTION

Provides the back-end functions for the Cycle Count Application

=head1 EXTENDS

=over

=item * L<API::Controller>

=back

=cut

=pod

=head1 API METHODS

=head2 index

Returns the template for the Cycle Count Index page

=cut

###############################################################################
sub index {
	my $self = (shift)->new({'input' => shift || {}});
	$self->data->{templates} = ['index.html'];
	return $self->result;
}

=pod

=head1 API METHODS

=head2 logInOut

Handles logging in and logging out
Activity is logged 
The routine attempts to create the cycle count user and validate that it exists
and is active.

=cut

sub logInOut {
	my $self = (shift)->new({'input' => shift || {}});

    my $p = {};
    $p->{scannerUserID} = cleanScannerUserID($self->{input}->{scannerUserID});
    
    # load the cycle count user 
    cycleCountUser($p);

    # validate cycle count user, scanner user, active
    my $result = validateUsers($p);
    if ($result->{success} == 0) {
        $self->data->{status} = {'message' => $result->{message}, 'success' => 0};
        $self->{outputType} = 'error';
       	$self->data->{templates} = ['index.html'];
        $self->data->{scannerUserID} = $p->{scannerUserID};
        logLogin($p, $result->{message});
        return $self->result;
    }

    my $message;
    if ($p->{cycleCountUser}->loggedIn eq 'Y') {  # if logged in, log them out.
        $p->{cycleCountUser}->loggedIn('N');
        if ($self->{input}->{userLocationKey} and $p->{cycleCountUser}{userLocation}{$self->{input}->{userLocationKey}}) {
            # if we are NOT done with this location and have active scans, put it on hold
            my $scans = getScans({locationScanKey=>$self->{input}->{userLocationKey}});
            if (($p->{cycleCountUser}{userLocation}{$self->{input}->{userLocationKey}}->status !~ /DONE/) and (scalar keys %{$scans} > 0)) {
                    $p->{cycleCountUser}{userLocation}{$self->{input}->{userLocationKey}}->status('HOLD');
                    $p->{cycleCountUser}{userLocation}{$self->{input}->{userLocationKey}}->save();
            }
        }
        $message = "User Logged Out";
        $self->data->{templates} = ['index.html'];
    } else {
        $p->{cycleCountUser}->loggedIn('Y'); # if logged out, log them in.
        $message = "User Logged In";
        $self->data->{templates} = ['index.html'];
        
        if (allLocationsCounted($self,$p)) {
            $self->data->{templates} = ['index.html'];
        }
        setOutput($self,$p);
    }
    $p->{cycleCountUser}->save();
    logLogin($p, $message);
    delete $p->{scannerUserID} if ($message eq "User Logged Out");

    $self->data->{status} = {'message' => $message, 'success' => 1 };
    $self->{outputType} = 'html';
    $self->data->{scannerUserID} = $p->{scannerUserID} if ($p->{scannerUserID});
    $self->data->{userName} = $p->{cycleCountUser}->scannerUser->name if ($p->{scannerUserID});
    $self->data->{loggedIn} = $p->{cycleCountUser}->loggedIn;
    
    return $self->result;
}

=pod

=head1 API METHODS

=head2 inputScan

Handles selecting of locations and scanning of products.
The cycle count user must select a location assigned to them before they can 
start counting products.  Products can be counted by a simple scan, or by
entering a qty first and then scan the product.

=cut

sub inputScan {
	my $self = (shift)->new({'input' => shift || {}});

    my $site = ECS::Site::fromName("ECS");
    my $p = {};
    my $selectedLocationKey = $self->{input}{userLocationKey}||'';
	my @selectedLocationKeys = (
		$selectedLocationKey ? ($selectedLocationKey) : ()
	);


    my $cycleCountScanLog = cycleCountScanLog($self,$p);
    logInput($self,$cycleCountScanLog);
    
    # error if no input 
    if (!$self->{input}{inputField}) {
        $p->{otherOutput} = ({outputType=>'error',
                              templates=>['index.html'],
                              filter=>$self->{input}{filter},
                              status=>{'message' => "Input data is required", 'success' => 0}});
        setOutput($self,$p);
        return $self->result;
    }

    $p->{scannerUserID} = cleanScannerUserID($self->{input}{scannerUserID});

    # error if scannerUserID not defined.

    if ($p->{scannerUserID} eq 'UNDEFINED') {
        $p->{otherOutput} = ({outputType=>'error',
                              templates=>['index.html'],
                              filter=>$self->{input}{filter},
                              status=>{'message' => "Invalid Scanner User", 'success' => 0}});
        setOutput($self,$p);
        return $self->result;
    }

    # error if userLocationKey is not defined.
    if (uc $self->{input}{userLocationKey} eq 'UNDEFINED') {
        $p->{otherOutput} = ({outputType=>'html',
                              templates=>['index.html'],
                              filter=>$self->{input}{filter},
                              status=>{'message' => "No location selected, Invalid Location", 'success' => 0}});
        setOutput($self,$p);
        return $self->result;
    }

    # create the cycle count user, user locations, locations, etc.
    cycleCountUser($p);
    
    my $result = validateUsers($p);
    if ($result->{success} == 0) {
        $self->data->{status} = {'message' => $result->{message}, 'success' => 0};
        $self->{outputType} = 'error';
       	$self->data->{templates} = ['index.html'];
        $self->data->{scannerUserID} = $p->{scannerUserID};
        logLogin($p, $result->{message});
        return $self->result;
    }

    # location that we are currently counting
    my $userLocation = $p->{cycleCountUser}->userLocation->{$self->{input}{userLocationKey}} if ($self->{input}{userLocationKey})||0;
   
    # if location scanned...
    my $locResult = itIsALocation($self->{input}{inputField});
    if ($locResult->{success} == 1) {
        my $prettyLocationNoCheckDigit = $locResult->{locString};
        @selectedLocationKeys = findUserLocation($p->{cycleCountUser},uc($self->{input}{inputField}));     # set selectedLocationKey to the userLocationKey value otherwise 0
        if (!$userLocation) {  # do I need a location to count?
            if (!@selectedLocationKeys) { # need one, but didn't find a location that is mine.
                $p->{otherOutput} = ({outputType=>'error',
                                    templates=>['index.html'],
                                    filter=>$self->{input}{filter},
                                    status=>{'message' => "Selected Location ($locResult->{locString}) is not valid for this Cycle Count User", 'success' => 0}});
                setOutput($self,$p);
                return $self->result;
            }
            # did find the location, check if it's already DONE
			if (
				all { $_->status =~ m/DONE/ } 
				map $p->{cycleCountUser}{userLocation}{$_},
				@selectedLocationKeys
			) {
                $p->{otherOutput} = ({outputType=>'error',
                                    templates=>['index.html'],
                                    filter=>$self->{input}{filter},
                                    status=>{'message' => "Selected Location ($locResult->{locString}) is DONE", 'success' => 0}});
                setOutput($self,$p);
                return $self->result;
            }
            # did find the location
			$selectedLocationKey ||= first {
				$p->{cycleCountUser}{userLocation}{$_} !~ m/DONE/
			} @selectedLocationKeys;
            $p->{otherOutput} = ({outputType=>'html',
                                templates=>['index.html'],
                                status=>{'message' => '', 'success' => 1 },
                                filter=>$self->{input}{filter},
                                location=>$prettyLocationNoCheckDigit,
                                locationStatus=>$p->{cycleCountUser}{userLocation}{$selectedLocationKey}->status,
                                userLocationKey=>$selectedLocationKey,
                                scans=>getScans({locationScanKey => $selectedLocationKey})});
            setOutput($self,$p);
            return $self->result;
        } else { # don't need a location to count, but check if we got one of ours, if so, and it's the one we are counting,
                 # we are done, (pending verification)
                 # if not put on hold and switch locations.
            if ($selectedLocationKey) { # we got a location and it is ours.
                # are we switching?
                if (($userLocation->cycleCountUserLocationID) ne $selectedLocationKey) {
                    # yes just switch, if switching to a location that isn't DONE.
                    if ($p->{cycleCountUser}{userLocation}{$selectedLocationKey}->status =~ /DONE/) {
                        $p->{otherOutput} = ({outputType=>'error',
                                            templates=>['index.html'],
                                            filter=>$self->{input}{filter},
                                            status=>{'message' => "Can't switch, Location ($locResult->{locString}) is DONE", 'success' => 0}
                                            });
                        setOutput($self,$p);
                        return $self->result;
                    }
                    # if we have active scans, place on hold.
                    my $scans = getScans({locationScanKey=>$userLocation->cycleCountUserLocationID});
                    if (scalar keys %{$scans} > 0) {
                        $userLocation->status('HOLD');
                        $userLocation->save();
                    }
                    my ($res) = findUserLocation($p->{cycleCountUser},uc($self->{input}{inputField}));
                    $p->{otherOutput} = ({outputType=>'html',
                                        templates=>['index.html'],
                                        filter=>$self->{input}{filter},
                                        status=>{'message' => '', 'success' => 1 },
                                        location=>$prettyLocationNoCheckDigit,
                                        locationStatus=>$p->{cycleCountUser}{userLocation}{$res}->status,
                                        userLocationKey=>$res,
                                        scans=>getScans({locationScanKey => $selectedLocationKey})});
                    setOutput($self,$p);
                    return $self->result;
                } else {
                    my $done = ECS::CycleCount::User::UserLocation::Done->new({
                        'locID'         => $userLocation->locID,
                        'location'      => $userLocation->{location},
						'scannerUserID' => cleanScannerUserID($self->{input}{scannerUserID}),
                    });
                    if (!$self->{input}{missingProduct}) {
                        if (keys (%{$done->missingProduct}) > 0) {
                            # allow for attempt to scan missing product.
                            my $missingProductInfo = $done->missingProduct;
                            $p->{otherOutput} = ({outputType=>'html',
                                            templates=>['index.html'],
                                            filter=>$self->{input}{filter},
                                            status=>{'message' => "Missing product", 'success' => 0 },
                                            products=>$missingProductInfo,
                                            location=>$prettyLocationNoCheckDigit,
                                            locationStatus=>$userLocation->status,
                                            missingProduct=>1,
                                            userLocationKey=>$self->{input}{userLocationKey},
                                            });
                            setOutput($self,$p);
                            return $self->result;
                        }
                    }
                    # we might be 'DONE', if already in a recount status, don't verify.
                    if ($userLocation->status eq 'RECOUNT') {
                        $userLocation->status('RECOUNTDONE');
                        $userLocation->save();
                        $done->_setDiscrepancySummary;
                        $p->{otherOutput} = ({outputType=>'html',
                                        templates=>['index.html'],
                                        filter=>$self->{input}{filter},
                                        status=>{'message' => '', 'success' => 1 },
                                        location=>$prettyLocationNoCheckDigit,
                                        locationStatus=>'RECOUNTDONE',
                                        userLocationKey=>''});
                        setOutput($self,$p);
                        return $self->result;
                    }
                    # we might be 'DONE' try to verify 
                    my $results = $done->_verify;
                    if ($results->{success} == 0) { # verify was NOT ok.
                        $done->_setInactive;    # sets all scans for this location to inactive
                        $userLocation->status('RECOUNT');
                        $userLocation->save();
                        $p->{otherOutput} = ({outputType=>'html',
                                        templates=>['index.html'],
                                        filter=>$self->{input}{filter},
                                        status=>{'message' => $results->{message}, 'success' => 0 },
                                        location=>$prettyLocationNoCheckDigit,
                                        locationStatus=>'RECOUNT'});
                        setOutput($self,$p);
                        return $self->result;
                    } else {
                        # we are DONE.
                        $userLocation->status('DONE');
                        $userLocation->save();
                        $done->_setDiscrepancySummary;
                        $p->{otherOutput} = ({outputType=>'html',
                                            templates=>['index.html'],
                                            filter=>$self->{input}{filter},
                                            status=>{'message' => '', 'success' => 1 },
                                            location=>$prettyLocationNoCheckDigit,
                                            locationStatus=>'DONE',
                                            userLocationKey=>''});
                        setOutput($self,$p);
                        return $self->result;
                    }
                }
            } else {
                # this location is not ours
                $p->{otherOutput} = ({outputType=>'error',
                                    templates=>['index.html'],
                                    filter=>$self->{input}{filter},
                                    status=>{'message' => "Selected Location ($locResult->{locString}) is not valid for this Cycle Count User", 'success' => 0}});
                setOutput($self,$p);
                return $self->result;
            }
        }
    } else {
        if (!$userLocation) {
             $p->{otherOutput} = ({outputType=>'error',
                                    templates=>['index.html'],
                                    status=>{'message' => "No Location selected.", 'success' => 0}});
                setOutput($self,$p);
                return $self->result;
        }
        my $cycleCountUserScan = cycleCountUserScan($p);
        my @productMatches = $self->getProductMatches({
            'dbh'           => $site->estore_dbhr,
            'productLookup' => uc($self->{input}{inputField}),
        });

        # need to check for multiple products from the search. ie pick a brand.
        if (scalar @productMatches > 1) {
            my @productBrands;
            foreach my $product (@productMatches) {
                push @productBrands,   ({  productID=>$product->productID,
                                    partnumber=>$product->partnumber,
                                    description=>$product->description,
                                    mainImageURLs=>$product->mainImageURLs,
                                    ($product->brandID ? (brand=>cycleCountBrandName($product->brandID)) : ()),
									});
            }
            $p->{otherOutput} = ({outputType=>'html',
                                templates=>['index.html'],
                                status=>{'message' => '', 'success' => 1},
                                filter=>$self->{input}{filter},
                                scannedProduct=>[@productBrands],
                                location=>$userLocation->location->{$userLocation->locID}->prettyLocation,
                                locationStatus=>$p->{cycleCountUser}{userLocation}{$selectedLocationKey}->status,
                                qty=>$self->{input}{qty},
                                userLocationKey=>$self->{input}{userLocationKey},
                                scans=>getScans({locationScanKey => $selectedLocationKey})});
           setOutput($self,$p);
            return $self->result;
        }

        my @productsInProperLocation;
        my @productsNotInProperLocation;
        my @productsWithNoLocation;
        foreach (@productMatches) {
            # we need to find out if this is primary location or overstock.
            if ($userLocation->{location}->{$userLocation->{locID}}->primary) {
                # primary location, so we can use product->locID
                if (($_->locID) && ($_->locID == $userLocation->locID)) {
                    push @productsInProperLocation, $_;
                } else {
                    if ($_->locID) {
                        push @productsNotInProperLocation, $_;
                    } else {
                        push @productsWithNoLocation, $_; 
                    }
                }
            } else {
                if ($userLocation->{location}->{$userLocation->{locID}}->overstock) {
                    # overstock location, so we can't use product->locID
                    if ($_->overstockLocationsQty->{$userLocation->locID}) {
                        # this product is in this overstock location, so we're good.
                        push @productsInProperLocation, $_;
                    } else {
                        # this product is NOT in this overstock location.
                        push @productsNotInProperLocation, $_;
                    }
                } else {
                    # check offsite location
                    if ($userLocation->{location}->{$userLocation->{locID}}->offsite) {
                        # offsite location, so we can't use product->locID
                        if ($_->offsiteLocationsQty->{$userLocation->locID}) {
                            # this product is in this offsite location, so we're good.
                            push @productsInProperLocation, $_;
                        } else {
                            # this product is NOT in this offsite location.
                            push @productsNotInProperLocation, $_;
                        }
                    }
                }
            }
        }

        if (!@productsInProperLocation && !@productsNotInProperLocation && !@productsWithNoLocation){
            $p->{otherOutput} = ({outputType=>'error',
                                templates=>['index.html'],
                                filter=>$self->{input}{filter},
                                status=>{'message' => "Scanned product doesn't exist." . uc($self->{input}{inputField}),'success' => 0},
                                location=>$userLocation->location->{$userLocation->locID}->prettyLocation,
                                locationStatus=>$p->{cycleCountUser}{userLocation}{$selectedLocationKey}->status,
                                userLocationKey=>$self->{input}{userLocationKey},
                                scans=>getScans({locationScanKey => $selectedLocationKey})});
            setOutput($self,$p);

            $cycleCountUserScan->scannerUserID(cleanScannerUserID($self->{input}{scannerUserID}));
            $cycleCountUserScan->scannedInput($self->{input}{inputField});
            $cycleCountUserScan->cycleCountUserLocationID($userLocation->cycleCountUserLocationID);
            $cycleCountUserScan->status('INVALIDPRODUCT');
            $cycleCountUserScan->qty($self->{input}{qty}||1);
            $cycleCountUserScan->save();
            return $self->result;
        }

        countIt($self,$p,({  productsList=>\@productsNotInProperLocation,
                                    status=>"INCORRECTLOCATION",
                                    userLocation=>$userLocation,
                                    selectedLocationKey=>$selectedLocationKey,
                                    cycleCountUserScan=>$cycleCountUserScan
                          })) if (scalar @productsNotInProperLocation);
        countIt($self,$p,({ productsList=>\@productsInProperLocation,
                            status=>"CORRECTLOCATION",
                            userLocation=>$userLocation,
                            selectedLocationKey=>$selectedLocationKey,
                            cycleCountUserScan=>$cycleCountUserScan
                          })) if (scalar @productsInProperLocation);
        countIt($self,$p,({ productsList=>\@productsWithNoLocation,
                            status=>"NOLOCATION",
                            userLocation=>$userLocation,
                            selectedLocationKey=>$selectedLocationKey,
                            cycleCountUserScan=>$cycleCountUserScan
                          })) if (scalar @productsWithNoLocation);
        return $self->result;
    }
}

=pod

=head1 API Supporting Subs

=head2 countIt

Ultimately adds records into the cycleCountUserScan table.

=cut

sub countIt {
    my $self = shift;
    my $p = shift;
    my $parms = shift;

    my $productList = $parms->{productsList};
    my $status = $parms->{status};
    my $userLocation = $parms->{userLocation};
    my $cycleCountUserScan = $parms->{cycleCountUserScan};
    my $selectedLocationKey = $parms->{selectedLocationKey};
    
    my @scannedProduct;
    foreach my $product (@{$productList}) {
        push @scannedProduct,   ({  productID=>$product->productID,
                                    partnumber=>$product->partnumber,
                                    description=>$product->description,
                                    mainImageURLs=>$product->mainImageURLs,
                                    ($product->brandID ? (brand=>cycleCountBrandName($product->brandID)) : ()),
									});
        # if single product, count it
        if (scalar @{$productList} == 1) {
            # save into the user scan table
            $cycleCountUserScan->cycleCountUserLocationID($userLocation->cycleCountUserLocationID);
            $cycleCountUserScan->scannerUserID(cleanScannerUserID($self->{input}{scannerUserID}));
            $cycleCountUserScan->productID($product->productID);
            $cycleCountUserScan->scannedInput($self->{input}{inputField});
            $cycleCountUserScan->status($status);
            $cycleCountUserScan->qty($self->{input}{qty}||1);
            if ($userLocation->{location}->{$userLocation->{locID}}->primary) {
                $cycleCountUserScan->onStockShelf($product->onStockShelf);
                $cycleCountUserScan->onOverstockShelf($product->onOverstockShelf);
            } else {
                # check overstock
                if ($userLocation->{location}->{$userLocation->{locID}}->overstock) {
                    $cycleCountUserScan->onStockShelf(0);
                    $cycleCountUserScan->onOverstockShelf($product->overstockLocationsQty->{$userLocation->{locID}});
                } else {
                    #check offsite
                    if ($userLocation->{location}->{$userLocation->{locID}}->offsite) {
                        $cycleCountUserScan->onStockShelf(0);
                        $cycleCountUserScan->onOverstockShelf($product->offsiteLocationsQty->{$userLocation->{locID}});
                    } else {
                        # shouldn't get here.
                        $cycleCountUserScan->onStockShelf(0);
                        $cycleCountUserScan->onOverstockShelf(0);
                    }
                }
            }
            $cycleCountUserScan->productGPCost($product->gpCost);
            $cycleCountUserScan->save();
            # after counting 1 product for a location on HOLD, put it back into COUNT status.
            if ($userLocation->status eq 'HOLD') {
                $userLocation->status("COUNT");
                $userLocation->save();
            }
            # do this after saving the cycleCountUserScan so we get the latest count.
            $p->{otherOutput} = ({outputType=>'html',
                                templates=>['index.html'],
                                filter=>$self->{input}{filter},
                                status=>{'message' => '', 'success' => 1},
                                scannedProduct=>[@scannedProduct],
                                location=>$userLocation->location->{$userLocation->locID}->prettyLocation,
                                locationStatus=>$p->{cycleCountUser}{userLocation}{$selectedLocationKey}->status,
                                userLocationKey=>$self->{input}{userLocationKey},
                                scans=>getScans({locationScanKey => $selectedLocationKey})});
            setOutput($self,$p);

            # calc total qty scanned for this product.
            $cycleCountUserScan->_clear_scanTotal;
            $self->data->{scannedProduct}[0]{qtyScanned} = $cycleCountUserScan->scanTotal;
            $userLocation->save();
        } else {
            $p->{otherOutput} = ({outputType=>'html',
                                templates=>['index.html'],
                                status=>{'message' => 'Error, should only have 1 product listed.', 'success' => 0},
                                filter=>$self->{input}{filter},
                                scannedProduct=>[@scannedProduct],
                                location=>$userLocation->location->{$userLocation->locID}->prettyLocation,
                                locationStatus=>$p->{cycleCountUser}{userLocation}{$selectedLocationKey}->status,
                                qty=>$self->{input}{qty},
                                userLocationKey=>$self->{input}{userLocationKey}});

            setOutput($self,$p);
        }
    }
    return;
}

sub cleanScannerUserID {
    my $scannerUserID = shift;
    
    $scannerUserID =~ s/USERID//;
    $scannerUserID =~ s/^0+//g;
    return $scannerUserID;
}

=pod

=head1 API Supporting Subs

=head2 findUserLocation

Returns key values list if pretty location is in cycle count user's locations.
Returns () if pretty location is not for this cycle count user.

Sort by dissimilary to DONE and then id.

=cut

sub findUserLocation {
    my $cycleCountUser = shift;
    my $prettyLocation = shift;
    
    my $selectedLocationKey = 0;
    my $locResult = itIsALocation($prettyLocation);
	my @keys;
    if ($locResult->{success} == '1')  {
		@keys = sort {
			# sort .+done to the end
			(($cycleCountUser->userLocation->{$a}->status =~ m/DONE/) <=> ($cycleCountUser->userLocation->{$b}->status =~ m/DONE/)) ||
			($a <=> $b)
		} grep {
			my $key = $_;
			my ($location) = first {
				$_->prettyLocation eq $locResult->{locString}
			} values %{ $cycleCountUser->userLocation->{$key}->location };
			defined $location;
		} keys %{ $cycleCountUser->userLocation };
    }
    return @keys;
}

=pod

=head1 API Supporting Subs

=head2 allLocationsCounted

Check all my assigned locations and see if any are not in the DONE status

=cut

sub allLocationsCounted {
    my $self = shift;
    my $p = shift;
    
    foreach my $key (sort keys %{$p->{cycleCountUser}->userLocation }) {
        if (!$p->{cycleCountUser}->userLocation->{$key}->status =~ /DONE/) {
                return 0;
        }
    }
    return 1;
}

=pod

=head1 API Supporting Subs

=head2 logInput

logs the raw input data from the scanner, also manual qty.

=cut

sub logInput {
    my $self = shift;
    my $cycleCountScanLog = shift;

    $cycleCountScanLog->scannerUserID(cleanScannerUserID($self->{input}{scannerUserID}));
    $cycleCountScanLog->scannedData($self->{input}{rawScan});
    $cycleCountScanLog->manualQtyInput($self->{input}{qty});
    $cycleCountScanLog->save();
}

=pod

=head1 API Supporting Subs

=head2 cycleCountBrandName

returns a brand name from brandID.

=cut

sub cycleCountBrandName {
    my $brandID = shift;

	return '' unless $brandID;
    my $site = ECS::Site::fromName("ECS");
    my $productBrand = ECS::CycleCount::Brand->load({
        'DBH' => $site->estore_dbhr,
        'brandID' => $brandID
    });
    return $productBrand->name;
}

=pod

=head1 API Supporting Subs

=head2 itIsALocation

Regex to determine if this is a Location. Valid locations follow the following
format Isle, Line, Row, Position.  Isle and Row are A-Z, Line and Position are
0-9.  Each can be two characters long. ex AA11BB22
Scanned locations include a check digit.
Returns success = 0,1 and the location string minus the check digit or none.

=cut

sub itIsALocation {
    my $inputField = shift;
    
    # chop off check digit.
    my $code = $inputField;
    my $checkDigitChar = chop($code);

    my $res = qr{(?: ^[A-Z]{1,2}(?:[0-9]{1,2}(?:[A-Z]{1,2}(?:[0-9]{1,2})?)?)?$ )}x;
    return ({success => '1',locString => $code}) if ($code =~ $res);
    return ({success => '0',locString => ''});
}

=pod

=head1 API Supporting Subs

=head2 cycleCountUserScan

returns a cycleCountUserScan object

=cut

sub cycleCountUserScan {
    my $p = shift;

    my $site = ECS::Site::fromName("ECS");
    $p->{DBH} = $site->estore_dbh;
    my $userScan = ECS::CycleCount::User::Scan->new({
        'DBH' => $p->{DBH},
    });
    return $userScan;
}

=pod

=head1 API Supporting Subs

=head2 cycleCountScanLog

returns a cycleCountScanLog object

=cut

sub cycleCountScanLog {
    my $self = shift;
    my $p = shift;

    my $site = ECS::Site::fromName("ECS");
    $p->{DBH} = $site->estore_dbh;
    my $userScan = ECS::CycleCount::ScanLog->new({
        'DBH' => $p->{DBH},
    });
    return $userScan;
}

=pod

=head1 API Supporting Subs

=head2 setOutput

sets output data to be used by the html template

=cut

sub setOutput {
    my $self = shift;
    my $p = shift;
    
    foreach my $key (keys %{$p->{otherOutput}}) {
        if ($key eq 'outputType') {
            $self->{outputType} = $p->{otherOutput}->{$key};
            next;
        }
        $self->data->{$key} = $p->{otherOutput}->{$key};
    }
    
    $self->data->{scannerUserID} = $p->{scannerUserID} if ($p->{scannerUserID});
    if ($p->{cycleCountUser}) {
        $self->data->{userName} = $p->{cycleCountUser}->scannerUser->name;
        $self->data->{loggedIn} = $p->{cycleCountUser}->loggedIn;
        foreach my $key (sort keys %{$p->{cycleCountUser}->userLocation }) {
            my $locID = $p->{cycleCountUser}->userLocation->{$key}->locID;
            $self->data->{locations}->{$locID}->{isle} =
                $p->{cycleCountUser}->userLocation->{$key}->{location}->{$locID}->isle;
            $self->data->{locations}->{$locID}->{line} =
                $p->{cycleCountUser}->userLocation->{$key}->{location}->{$locID}->line;
            $self->data->{locations}->{$locID}->{row} = 
                $p->{cycleCountUser}->userLocation->{$key}->{location}->{$locID}->row;
            $self->data->{locations}->{$locID}->{position} = 
                $p->{cycleCountUser}->userLocation->{$key}->{location}->{$locID}->position;
            $self->data->{locations}->{$locID}->{locationFunction} =
                $p->{cycleCountUser}->{locationFunction}->{$p->{cycleCountUser}->userLocation->{$key}->{location}->{$locID}->locFunctionID}->name;
            $self->data->{locations}->{$locID}->{status} =
                $p->{cycleCountUser}->userLocation->{$key}->{status};
            $self->data->{locations}->{$locID}->{priority} =
                $p->{cycleCountUser}->userLocation->{$key}->{priority};
        }
    }
}

=pod

=head1 API Supporting Subs

=head2 validateUsers

check validity of the cycle count user, ie exists, active.
returns success or not and proper message.

=cut

sub validateUsers {
    my $p = shift;

    if (!$p->{cycleCountUser}) {
        return {
           'success' => 0,
           'message' => "Cycle Count User does not exist.",
       };
    }
    if (!$p->{cycleCountUser}->scannerUser) {
        return {
           'success' => 0,
           'message' => "Scanner User does not exist.",
       };
    }
    if ($p->{cycleCountUser}->active ne 'Y') {
         return {
            'success' => 0,
            'message' => "Cycle Count User is not active.",
        };
     }
    if ($p->{cycleCountUser}->scannerUser->active ne 'Y') {
         return {
            'success' => 0,
            'message' => "Scanner User is not active.",
        };
     }
    return {
        'success' => 1,
        'message' => "Cycle Count User is valid.",
    };
}

=pod

=head1 API Supporting Subs

=head2 cycleCountUser

loads a cycleCountUser object into $p

Cycle Count User Object $VAR1 = bless( {
    'active' => 'Y',
    'cycleCountUserID' => 2,
    'loggedIn' => 'N',
    'userLocation' => {
        '7' => bless( {
            'location' => {
                '42255' => bless( {
                    'position' => '1',
                    'dynamic' => 'YES',
                    'active' => 'Y',
                    'locFunctionID' => 1,
                    'warehouseZoneID' => 2,
                    'locID' => 42255,
                    'size' => 'A',
                    'warehouseID' => 1,
                    'isle' => 'B',
                    'row' => 'B',
                    'type' => 'Bin',
                    'sortOrder' => 16490,
                    'mixed' => 'NO',
                    'line' => '1'
                    }, 'ECS::CycleCount::User::UserLocation::Location' )
                },
            'site' => bless( {
                'estore_dbh' => bless( {}, 'ECS::DB::db' ),
                'domain' => 'ecstuning.com',
                'siteName' => 'ECS Tuning',
                'primary' => 1,
                'name' => 'ECS',
                'phone' => '800.924.5172',
                'siteID' => 1,
                'packingImage' => 'http://davidr.ws7.www.sandbox2.ecstuning.local/img/brands/ECS.gif'
            }, 'ECS::Site' ),
            'DBH' => $VAR1->{'userLocation'}{'7'}{'site'}{'estore_dbh'},
            'scannerUserID' => 1333,
            'status' => 'COUNT',
            'cycleCountUserLocationID' => 7,
            'locID' => 42255
        }, 'ECS::CycleCount::User::UserLocation' )
    },
    'DBH' => $VAR1->{'userLocation'}{'7'}{'site'}{'estore_dbh'},
    'site' => bless( {
        'estore_dbh' => $VAR1->{'userLocation'}{'7'}{'site'}{'estore_dbh'},
        'domain' => 'ecstuning.com',
        'siteName' => 'ECS Tuning',
        'primary' => 1,
        'name' => 'ECS',
        'phone' => '800.924.5172',
        'siteID' => 1,
        'packingImage' => 'http://davidr.ws7.www.sandbox2.ecstuning.local/img/brands/ECS.gif'
    }, 'ECS::Site' ),
    'scannerUserID' => 1333,
    'dailyCycleCount' => 11,
    'locationFunction' => {
        '4' => bless( {
            'name' => 'Parking Spot',
            'locFunctionID' => 4
            }, 'ECS::CycleCount::User::LocationFunction' ),
        '1' => bless( {
            'name' => 'Stock',
            'locFunctionID' => 1
            }, 'ECS::CycleCount::User::LocationFunction' ),
        '3' => bless( {
            'name' => 'Offsite',
            'locFunctionID' => 3
            }, 'ECS::CycleCount::User::LocationFunction' ),
        '2' => bless( {
            'name' => 'Overstock',
            'locFunctionID' => 2
            }, 'ECS::CycleCount::User::LocationFunction' )
    }
}, 'ECS::CycleCount::User' );

=cut

sub cycleCountUser {
    my $p = shift;

    my $site = ECS::Site::fromName("ECS");
    $p->{DBH} = $site->estore_dbh if (!$p->{DBH});
    my $cycleCountUser = ECS::CycleCount::User->load({
        'DBH' => $p->{DBH},
        'scannerUserID' => $p->{'scannerUserID'}
    });
    if ($cycleCountUser) {
        $cycleCountUser->scannerUser;
        $cycleCountUser->userLocation;     # load all locations assigned to this Cycle Count User.
        $cycleCountUser->locationFunction; # get locationFunction names, stock, overstock...
        # load data for template toolkit.
        foreach my $key (sort keys %{$cycleCountUser->userLocation }) {
            $cycleCountUser->userLocation->{$key}->location;
            #$cycleCountUser->userLocation->{$key}->scan;
            my $locID = $cycleCountUser->userLocation->{$key}->locID;
            $cycleCountUser->userLocation->{$key}->{location}->{$locID}->isle;
            $cycleCountUser->userLocation->{$key}->{location}->{$locID}->line;
            $cycleCountUser->userLocation->{$key}->{location}->{$cycleCountUser->userLocation->{$key}->{locID}}->row;
            $cycleCountUser->userLocation->{$key}->{location}->{$locID}->position;
            $cycleCountUser->{locationFunction}->{$cycleCountUser->userLocation->{$key}->{location}->{$locID}->locFunctionID}->name;
        }
    }
    $p->{cycleCountUser} = $cycleCountUser;
}

=pod

=head1 API Supporting Subs

=head2 logLogin

log all login logout activity

=cut

sub logLogin {
    my $p = shift;
    my $message = shift;
  	#track the login information
	my $sqlLogLogin = qq~insert into cycleCountLogin (cycleCountUserID, scannerUserID, ipAddress, loginMessage) values (?, ?, INET_ATON(?), ?)~;
	my $sthLogLogin = $p->{DBH}->prepare($sqlLogLogin);
    my $ccUser = $p->{cycleCountUser}->cycleCountUserID if ($p->{cycleCountUser});
	$sthLogLogin->execute($ccUser||0, $p->{scannerUserID}||0, $ENV{REMOTE_ADDR}, $message);
}

=pod

=head1 API Supporting Subs

=head2 getProductMatches

copied/stripped from scanner/libs/Scanner/RNI.pm
Does elasticSearch for products that match our conditions, productID, UPCCode, etc.

=cut

sub getProductMatches {
    my $self = shift;
    my ($p) = @_;
    
    
    my $productLookup = $p->{'productLookup'};
    my $manualEntry;
    my $site = ECS::Site::fromName("ECS");
    my %matchedProductIDs;

    my $productLookupPartNumber = $productLookup;
    # Remove spaces, because some OEMPartNumber physical barcodes are space-formatted
    $productLookup =~ s/\s//g;

    my $exactProductID = $productLookup;
    my @queryShoulds;
    # The product barcodes might have leading zeros, so get rid of those
    $exactProductID =~ s/^0+//g;
    if (($exactProductID =~ /^\d{1,7}$/) && (trulyAProductID($exactProductID))){
        $matchedProductIDs{$exactProductID} = 1;
		#
		# There are products in the estore db that have UPC codes defined which are other product productIDs 
		# ex. productID 1080996 has 6153 as a UPC code.  This causes problems when product 6153 is scanned because cycle count
		# thinks 1080996 should be included as well, but it really shouldn't.  Different Vendors, different brands and clearly
		# different parts. So, if I know I have a productID, only search for that product, nothing else.
		#
		push(@queryShoulds, {
			'term' => { 'productID' => $exactProductID },
		});
    } else {
        undef $exactProductID;
		push(@queryShoulds, {
			'term' => { 'UPCCode' => $productLookup },
		});
    } 
    #
    # If I know this is a productID then don't search for other Shoulds
    #
    if (!$self->{input}{productID} && (!keys %matchedProductIDs)) {
        push(@queryShoulds, {
            'term' => { 'OEMPartNumber' => $productLookup },
        });
        push(@queryShoulds, {
            'prefix' => { 'partnumber' => $productLookupPartNumber },
        });
    }

    my $searchResult = ECS::Search::Product::search({
        'size' => 10,
        '_source' => "false",
        'query' => {
            'bool' => {
                'should' => \@queryShoulds,
            },
        },
    });

    # no match, (if audi/vw), are the last 5 characters digits?  remove them and any special characters (handle space specially)
    my $alias;
    my $alnum_alias;
    unless (%matchedProductIDs ||  ($searchResult->{hits} && $searchResult->{hits}{hits} && @{ $searchResult->{hits}{hits} })) {{
        last unless $productLookup =~ m{\d{5}$}ms;
        # at least 2 dashes so we can avoid confusing productID barcodes here
        last unless $productLookupPartNumber =~ m{^.+-.+-}ms;
        @queryShoulds = ();
        # remove last 5 digits (Quantity)
        ($alias = $productLookupPartNumber) =~ s{\d{5}$}{}ms;
        # remove special characters (not A-Za-z0-9)
        ($alnum_alias = $alias) =~ y{A-Za-z0-9}{}dc;
        push @queryShoulds, {
            term    => { OEMPartNumber => $alnum_alias }
        }; 
        $searchResult = ECS::Search::Product::search({
            'size' => 10,
            '_source' => "false",
           'query' => {
                'bool' => {
                    'should' => \@queryShoulds,
                },
            },
        });
    }}

    for my $hit (@{ $searchResult->{'hits'}->{'hits'} }) {
        $matchedProductIDs{$hit->{'_id'}} = 1;
    }
    my @products;
    for my $productID (keys %matchedProductIDs) {
        my $product = ECS::Product->new({
            'dbh' => $site->estore_dbhr,
            'productID' => $productID,
        });
        push @products, $product;
    }

    ECS::Product::loadMulti({
        'dbh' => $p->{'dbh'},
        'products' => \@products,
    });

    return @products;
}

=pod

=head1 API Supporting Subs

=head2 trulyAProductID

Trys to read the product record to validate the 'productID' because building a product object
with a bad ID dies.

=cut
sub trulyAProductID {
    my $productID = shift;

    my $site = ECS::Site::fromName("ECS");
    my $DBH = $site->estore_dbhr;
   
    my $product = ECS::CycleCount::Product->load({
        'DBH' => $DBH,
        'productID' => $productID,
    });
   
    return 1 if ($product);
    return 0;
}

=pod

=head1 API Supporting Subs

=head2 getScans

Returns a summary hash of current product scans for this location or {}

=cut
sub getScans {
    my $p = shift;
    return {} unless $p->{locationScanKey};
    
    my $DBHr = $p->{DBHr} || ECS::Site::fromName("ECS")->estore_dbhr;
    my $scans = ECS::CycleCount::User::Scan->new({
        'DBH' => $DBHr,
    });
    $scans->cycleCountUserLocationID($p->{locationScanKey});
    my $allScans = $scans->locationScans;
    my $summary;    
    while (my ($key, $scan) = each(%{$allScans})) {
        next if $scan->status eq "INVALIDPRODUCT";
        my $product = ECS::Product->new({
            'dbh' => $DBHr,
            'productID' => $scan->productID,
        });
        $summary->{$product->productID}->{qty} += $scan->qty if ($product);
        $summary->{$product->productID}->{productID} = $product->productID if ($product);
        $summary->{$product->productID}->{partnumber} = $product->partnumber if ($product);
        $summary->{$product->productID}->{description} = $product->description if ($product);
        $summary->{$product->productID}->{mainImageURLs} = $product->mainImageURLs if ($product);
    } 
    return $summary||{};
}
1;
