M3 Integration fixes pass 20
This commit is contained in:
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,225 @@
|
||||
<?php
|
||||
|
||||
namespace Modules\M3\Http\Controllers\API;
|
||||
|
||||
use Config;
|
||||
use DB;
|
||||
use File;
|
||||
use Illuminate\Http\Request;
|
||||
use Modules\M3\Http\Controllers\GetContentsController;
|
||||
|
||||
final class CloseShipmentPackageBuilder
|
||||
{
|
||||
public function __construct(private readonly array $context, private readonly mixed $freightTerms)
|
||||
{
|
||||
}
|
||||
|
||||
public function build(array $shipment, array $trailer): array
|
||||
{
|
||||
$state = ['packages' => [], 'errors' => '', 'coCharge' => [], 'details' => []];
|
||||
foreach ($this->packageRows($shipment) as $row) {
|
||||
$details = $this->packageDetails($shipment, $trailer, $row);
|
||||
$state['errors'] .= $details['errors'];
|
||||
$state['packages'][] = $details['package'];
|
||||
$state['details'] = $details;
|
||||
$state['coCharge'] = $this->coCharge($details);
|
||||
}
|
||||
|
||||
return $state;
|
||||
}
|
||||
|
||||
public function closeShipmentJson(array $shipment, array $trailer, array $state, int $endFlag): string
|
||||
{
|
||||
$details = $state['details'];
|
||||
$payload = [
|
||||
'DIVI' => $shipment['company'], 'WHS' => $shipment['whs'], 'DELIVERYNO' => $shipment['delivery'],
|
||||
'PICKLISTSUFFIX' => $shipment['release'], 'TYPE' => 'CloseShipment', 'SCAC' => $details['scac'],
|
||||
'CARRIER' => $details['carrier'], 'TRANSPORTMODE' => $details['transportMode'],
|
||||
'SERVICETYPE' => $details['serviceType'], 'TRACKINGNUMBER' => $details['tracking'],
|
||||
'PRONUMBER' => $trailer['pro'], 'BILLOFLADING' => $details['bol'], 'TRAILERNUMBER' => $trailer['trailer'],
|
||||
'PACKAGES' => $state['packages'], 'DELIVERYNOTEDOC' => $this->documentUrl('PackingList', $shipment['delivery'], $shipment['delivery']),
|
||||
'COMMODITIES' => $details['commodities']['value'], 'ENDFLAG' => $endFlag,
|
||||
];
|
||||
$serviceFields = $state['coCharge'] !== []
|
||||
? ['shipType' => $details['shipType'], 'carrierServiceDescription' => $details['serviceName'], 'modeOfTransport' => $details['shipVia1']]
|
||||
: ['ServiceType' => $details['shipType'], 'ServiceLevelCode' => $details['serviceName'], 'TransportationMode' => $details['shipVia1']];
|
||||
|
||||
return json_encode(array_merge($state['coCharge'], $payload, $serviceFields));
|
||||
}
|
||||
|
||||
private function packageRows(array $shipment)
|
||||
{
|
||||
return DB::connection(config('m3.connection', 'pgsql'))->table('icm695m1')
|
||||
->selectRaw('AYMACPKR,AYINTBOX,AYHEIGHT,AYWIDTH,AYDEPTH,AYTYPE,AYLNWGHT,AYID,AYSPR1A01,TRIM(AYSPR15A01) AS SCAC,TRIM(AYSPR30A01) AS TRACKING,AYTRUCK AS AGGREGATION,A4RATETCP AS FREIGHT,A4OFSTRM AS FREIGHTTERMS,A4RATEM3,A4CLOSEM3,A4SHIPTYPE,A4REFORD,A4ONO,A4SHIPVIA,A42SSVCNAM,A4SHIPVIA1')
|
||||
->leftJoin('ICM688M1', function ($join) {
|
||||
$join->on('AYCO', '=', 'A4CO')->on('AYWHS', '=', 'A4WHS')->on('AYMACPKR', '=', 'A4MACPKR');
|
||||
})
|
||||
->where('AYMACPKR', $shipment['delivery'])->where('AYCO', $shipment['company'])
|
||||
->where('AYWHS', $shipment['whs'])->orderBy('AYINTBOX')->get();
|
||||
}
|
||||
|
||||
private function packageDetails(array $shipment, array $trailer, object $row): array
|
||||
{
|
||||
$details = $this->rowDetails($row);
|
||||
$details['carrier'] = $this->carrierName($details['scac']);
|
||||
$details['bol'] = $details['scac'] === 'RETR' ? $trailer['pro'] : $details['bol'];
|
||||
$details['errors'] = $this->shippingErrors($details, $trailer['pro']);
|
||||
$details['errors'] .= $this->previousTransmissionError($shipment);
|
||||
$details['bundle'] = $this->bundleType($details['bundle'], $row);
|
||||
$details['commodities'] = $this->commodities($shipment['delivery'], $details['errors']);
|
||||
$details['errors'] = $details['commodities']['errors'];
|
||||
if ($details['billable'] === 'Y' && (float) $details['freight'] == 0 && $details['rateM3'] === '') {
|
||||
$details['errors'] .= ' Please manually add a freight charge for this Prepaid and Add order; ';
|
||||
}
|
||||
$labels = $this->labels($shipment, $details['box'], $details['gs1Required']);
|
||||
$details['errors'] .= $labels['error'];
|
||||
$details['package'] = $this->packageArray($details, $labels);
|
||||
|
||||
return $details;
|
||||
}
|
||||
|
||||
private function rowDetails(object $row): array
|
||||
{
|
||||
$freightTerms = trim($row->FREIGHTTERMS);
|
||||
$currentFreightTerms = $this->freightTerms->where('FREIGHTTERMS', $freightTerms)->first();
|
||||
|
||||
return [
|
||||
'shipType' => trim($row->A4SHIPTYPE), 'serviceName' => trim($row->A42SSVCNAM),
|
||||
'shipVia1' => trim($row->A4SHIPVIA1), 'freightTerms' => $freightTerms,
|
||||
'rateM3' => trim($row->A4RATEM3), 'freight' => trim($row->FREIGHT),
|
||||
'refOrder' => trim($row->A4REFORD), 'orderNo' => trim($row->A4ONO),
|
||||
'delivery' => trim($row->AYMACPKR), 'box' => trim($row->AYINTBOX),
|
||||
'tracking' => trim($row->TRACKING), 'scac' => trim($row->SCAC),
|
||||
'bundle' => trim($row->AYID) !== '' ? trim($row->AYID) : '',
|
||||
'weight' => trim($row->AYLNWGHT), 'height' => trim($row->AYHEIGHT),
|
||||
'width' => trim($row->AYWIDTH), 'depth' => trim($row->AYDEPTH),
|
||||
'bol' => isset($row->AYSPR1A01) ? trim($row->AYSPR1A01) : '',
|
||||
'transportMode' => trim(str_replace('&', '_', $row->A4SHIPVIA ?? '')),
|
||||
'serviceType' => trim(str_replace('&', '_', $row->A4SHIPVIA ?? '')),
|
||||
'billable' => isset($currentFreightTerms->BILLABLE) ? trim($currentFreightTerms->BILLABLE) : 'N',
|
||||
'gs1Required' => $this->gs1Required($row), 'errors' => '',
|
||||
];
|
||||
}
|
||||
|
||||
private function shippingErrors(array $details, string $proNumber): string
|
||||
{
|
||||
$missingTracking = $details['scac'] === '' || $details['tracking'] === '';
|
||||
$missingLtl = $details['shipType'] === 'LTL' && ($missingTracking || $details['bol'] === '');
|
||||
$errors = ($missingTracking || $missingLtl)
|
||||
? " Missing one or more of tracking number, BOL, or carrier. Please enter this data manually in 'invoicing' screen and retry this API call;"
|
||||
: '';
|
||||
|
||||
if ($proNumber === '' && $details['scac'] === 'RETR') {
|
||||
$errors .= ' Missing PARS info for this RETRans order. Please contact MPBA to ask them to enter the value for you, and then you can retry; ';
|
||||
}
|
||||
return $errors;
|
||||
}
|
||||
|
||||
private function carrierName(string $scac): string
|
||||
{
|
||||
if ($scac === '') {
|
||||
return '';
|
||||
}
|
||||
$row = DB::connection(config('m3.connection', 'pgsql'))->table('icm160m')
|
||||
->select('CANAM')->where('CANUM', substr($scac, 0, 4))->where('CACO', $this->context['company'])->first();
|
||||
|
||||
return isset($row->CANAM) ? trim(str_replace('&', '_', $row->CANAM)) : '';
|
||||
}
|
||||
|
||||
private function previousTransmissionError(array $shipment): string
|
||||
{
|
||||
$row = DB::connection(config('m3.connection', 'pgsql'))->table('icm699m')
|
||||
->select('SHMACPKR')->where('SHMACPKR', $shipment['delivery'])
|
||||
->where('SHCO', $shipment['company'])->where('SHWHS', $shipment['whs'])
|
||||
->whereIn('SHSOURCE', ['TransferAPIURL', 'CloseShipmentCoChargeAPI', 'PackSyst4tray'])
|
||||
->where('SHERROR', 'X')->whereIn('SHTYPE', ['M3-Res-out', 'M3-QtyChg'])->first();
|
||||
|
||||
return isset($row->SHMACPKR)
|
||||
? ' There are previous M3 transmissions in error for this delivery note that have not been addressed; '
|
||||
: '';
|
||||
}
|
||||
|
||||
private function bundleType(string $bundle, object $row): string
|
||||
{
|
||||
if ($bundle !== '') {
|
||||
return $bundle;
|
||||
}
|
||||
$record = DB::connection(config('m3.connection', 'pgsql'))->table('icm693m')
|
||||
->select('AEID')->where('AESPR1A01', 'D')->where('AETYPE', trim($row->AYTYPE))->first();
|
||||
|
||||
return isset($record->AEID) ? trim($record->AEID) : '';
|
||||
}
|
||||
|
||||
private function commodities(string $delivery, string $errors): array
|
||||
{
|
||||
$request = new Request;
|
||||
$request->setMethod('POST');
|
||||
$request->request->add(['MACPKR' => $delivery]);
|
||||
$responseData = (new GetContentsController)->processShipment($request)->getData();
|
||||
$commodities = empty($responseData->error) ? ($responseData->commodities ?? '') : '';
|
||||
|
||||
return ['value' => $commodities, 'errors' => $commodities !== '' ? $errors : $errors . ' Could not find Commodities; '];
|
||||
}
|
||||
|
||||
private function labels(array $shipment, string $box, bool $gs1Required): array
|
||||
{
|
||||
$carrier = $this->documentUrl('CarrierLabel', $shipment['delivery'], $box);
|
||||
$content = $this->documentUrl('ContentLabel', $shipment['delivery'], $box);
|
||||
$gs1 = '';
|
||||
if ($gs1Required) {
|
||||
$row = DB::connection(config('m3.connection', 'pgsql'))->table('icm695m3')
|
||||
->select('A2GS1LABEL')->where('A2CO', $shipment['company'])->where('A2WHS', $shipment['whs'])
|
||||
->where('A2INTBOX', $box)->first();
|
||||
$gs1 = isset($row->A2GS1LABEL) ? trim($row->A2GS1LABEL) : '';
|
||||
}
|
||||
|
||||
return [
|
||||
'carrier' => $carrier, 'content' => $content, 'gs1' => $gs1,
|
||||
'error' => $gs1Required && $gs1 === ''
|
||||
? ' Missing G1-128 Label. Please contact MPBA to ask them to enter the value for you, and then you can retry; '
|
||||
: '',
|
||||
];
|
||||
}
|
||||
|
||||
private function documentUrl(string $type, string $delivery, string $documentNumber): string
|
||||
{
|
||||
$key = $type . '/' . $type . '_' . $delivery . '_' . $documentNumber . '_' . $this->context['company'] . '_' . $this->context['whs'];
|
||||
if (! File::exists(Config::get('arrays.PdfPath') . $key . '.pdf')) {
|
||||
return '';
|
||||
}
|
||||
|
||||
return route('m3.M3GetPDFFromCache.index', ['MACPKR' => $delivery, 'DOCTYPE' => $type]);
|
||||
}
|
||||
|
||||
private function gs1Required(object $row): bool
|
||||
{
|
||||
$record = DB::connection(config('m3.connection', 'pgsql'))->table('icm666m')
|
||||
->select('A1SPR1A01')->where('A1CO', $this->context['company'])->where('A1WHS', $this->context['whs'])
|
||||
->where('A1DELNO', trim($row->AYMACPKR))->first();
|
||||
|
||||
return (int) ($record->A1SPR1A01 ?? 0) === 1;
|
||||
}
|
||||
|
||||
private function packageArray(array $details, array $labels): array
|
||||
{
|
||||
return [
|
||||
'TRACKINGID' => $details['tracking'], 'BUNDLES' => $details['bundle'],
|
||||
'WEIGHT' => $details['weight'], 'HEIGHT' => $details['height'], 'WIDTH' => $details['width'],
|
||||
'LENGTH' => $details['depth'], 'SSCC' => $labels['gs1'],
|
||||
'SHIPLABELDOC' => $labels['carrier'], 'CONTENTLABELDOC' => $labels['content'],
|
||||
];
|
||||
}
|
||||
|
||||
private function coCharge(array $details): array
|
||||
{
|
||||
if ($details['billable'] !== 'Y' || (float) $details['freight'] <= 0 || $details['rateM3'] !== '') {
|
||||
return [];
|
||||
}
|
||||
$discountKey = $details['shipType'] === 'LTL'
|
||||
? 'arrays.misc.discountCarrierRateForCustomerPriceLTL'
|
||||
: 'arrays.misc.discountCarrierRateForCustomerPriceParcel';
|
||||
$shippingToBill = (float) $details['freight'] * (100.00 - Config::get($discountKey)) / 100;
|
||||
|
||||
return ['QUANTITY' => round($shippingToBill, 2), 'ORDERCATEGORY' => $details['refOrder'], 'CONUMBER' => $details['orderNo']];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,258 @@
|
||||
<?php
|
||||
|
||||
namespace Modules\M3\Http\Controllers\API;
|
||||
|
||||
use Config;
|
||||
use DB;
|
||||
use File;
|
||||
use Http;
|
||||
use Illuminate\Http\Request;
|
||||
use Log;
|
||||
use Modules\M3\Helpers\Helper;
|
||||
use Modules\M3\Http\Controllers\GetContentsController;
|
||||
use Str;
|
||||
use Throwable;
|
||||
|
||||
final class CloseShipmentProcessor
|
||||
{
|
||||
private const ZERO_PAD = '000000';
|
||||
private const JSON_CONTENT_TYPE = 'application/json';
|
||||
private const NULL_M3_RESPONSE = 'M3 API responded with null value';
|
||||
private const SOURCE = 'CloseShipmentCoChargeAPI';
|
||||
|
||||
private array $context;
|
||||
|
||||
public function __construct(private readonly Request $request)
|
||||
{
|
||||
$this->context = $this->makeContext();
|
||||
}
|
||||
|
||||
public function handle()
|
||||
{
|
||||
try {
|
||||
return $this->process();
|
||||
} catch (Throwable $throwable) {
|
||||
return $this->exceptionResponse($throwable);
|
||||
}
|
||||
}
|
||||
|
||||
private function process()
|
||||
{
|
||||
if ($this->context['params'] === []) {
|
||||
$this->log('M3-Res-out', 'No URL Parameters Sent', 'X');
|
||||
|
||||
return response()->json([
|
||||
'code' => '405',
|
||||
'status' => 'Error',
|
||||
'message' => 'No URL Parameters Sent',
|
||||
]);
|
||||
}
|
||||
|
||||
$this->logInitialRequest();
|
||||
$tokenResult = $this->receiveToken();
|
||||
if ($tokenResult['response'] !== null) {
|
||||
return $tokenResult['response'];
|
||||
}
|
||||
|
||||
$shipments = $this->loadShipments();
|
||||
$shipments = $this->removeAlreadyClosed($shipments);
|
||||
$freightTerms = DB::connection(config('m3.connection', 'pgsql'))->table('icm321m')
|
||||
->selectRaw('TRIM(ARCODE) as FREIGHTTERMS,ARCHARGE as BILLABLE')
|
||||
->get();
|
||||
|
||||
return $this->dispatchShipments($shipments, $freightTerms, $tokenResult['token']);
|
||||
}
|
||||
|
||||
private function makeContext(): array
|
||||
{
|
||||
$params = $this->request->all();
|
||||
$manifest = (string) ($params['MANIFEST'] ?? '');
|
||||
$transType = (string) ($params['TRANSTYPE'] ?? 'M');
|
||||
|
||||
return [
|
||||
'params' => $params,
|
||||
'whs' => (string) ($params['WHS'] ?? ''),
|
||||
'company' => (string) ($params['COMPANY'] ?? ''),
|
||||
'manifest' => $manifest,
|
||||
'transType' => $transType !== '' ? $transType : 'M',
|
||||
'shdt' => (string) ($params['SHDT'] ?? ''),
|
||||
'shtime' => (string) ($params['SHTIME'] ?? ''),
|
||||
'shtype' => (string) ($params['SHTYPE'] ?? ''),
|
||||
'user' => m3_user() !== '' ? Str::upper(m3_user()) : 'WebUser',
|
||||
'delivery' => $manifest !== '' ? $manifest : self::ZERO_PAD,
|
||||
'jsonIn' => '',
|
||||
];
|
||||
}
|
||||
|
||||
private function logInitialRequest(): void
|
||||
{
|
||||
$this->context['jsonIn'] = json_encode([
|
||||
'WHS' => $this->context['whs'],
|
||||
'COMPANY' => $this->context['company'],
|
||||
'MANIFEST-MACPKR' => $this->context['manifest'],
|
||||
'TRANSTYPE' => $this->context['transType'],
|
||||
]);
|
||||
$this->log('M3-Req-out', $this->context['jsonIn']);
|
||||
}
|
||||
|
||||
private function receiveToken(): array
|
||||
{
|
||||
$response = Helper::checkReceiveTokenV2();
|
||||
$data = $response->getData();
|
||||
if ((string) ($data->code ?? '') === '200') {
|
||||
return ['token' => (string) ($data->token ?? ''), 'response' => null];
|
||||
}
|
||||
|
||||
$this->log('M3-Res-out', (string) ($data->message ?? ''), 'X');
|
||||
|
||||
return ['token' => '', 'response' => $response];
|
||||
}
|
||||
|
||||
private function loadShipments()
|
||||
{
|
||||
$type = $this->context['transType'];
|
||||
if (! in_array($type, ['M', 'P', 'Z'], true)) {
|
||||
return collect();
|
||||
}
|
||||
if ($type === 'P') {
|
||||
$this->acknowledgeRetry();
|
||||
}
|
||||
|
||||
$query = $type === 'Z' ? $this->zeroPickQuery() : $this->standardShipmentQuery($type);
|
||||
|
||||
return $query->get();
|
||||
}
|
||||
|
||||
private function standardShipmentQuery(string $type)
|
||||
{
|
||||
$query = DB::connection(config('m3.connection', 'pgsql'))->table('icm695m1')
|
||||
->select('AYCO', 'AYWHS', 'AYMACPKR', 'AYRLNO', 'AYAPRF1551')
|
||||
->distinct()
|
||||
->where('AYCO', $this->context['company'])
|
||||
->where('AYWHS', $this->context['whs'])
|
||||
->orderBy('AYMACPKR');
|
||||
|
||||
$field = $type === 'P' ? 'AYMACPKR' : 'AYAPRF1551';
|
||||
|
||||
return $query->where($field, $this->context['manifest']);
|
||||
}
|
||||
|
||||
private function zeroPickQuery()
|
||||
{
|
||||
return DB::connection(config('m3.connection', 'pgsql'))->table('icm688m1')
|
||||
->select()->distinct()
|
||||
->where('A4CO', $this->context['company'])
|
||||
->where('A4WHS', $this->context['whs'])
|
||||
->where('A4MACPKR', $this->context['manifest'])
|
||||
->orderBy('A4MACPKR');
|
||||
}
|
||||
|
||||
private function acknowledgeRetry(): void
|
||||
{
|
||||
if ($this->context['shdt'] !== '' && $this->context['shtime'] !== '' && $this->context['shtype'] !== '') {
|
||||
DB::connection(config('m3.connection', 'pgsql'))->table('icm699m')
|
||||
->where('SHMACPKR', $this->context['manifest'])
|
||||
->where('SHDT', $this->context['shdt'])->where('SHTIME', $this->context['shtime'])
|
||||
->where('SHCO', $this->context['company'])->where('SHWHS', $this->context['whs'])
|
||||
->where('SHSOURCE', self::SOURCE)->where('SHERROR', 'X')
|
||||
->where('SHTYPE', $this->context['shtype'])
|
||||
->update($this->acknowledgementValues());
|
||||
}
|
||||
|
||||
$this->context['lineAck'] = $this->acknowledgedLines();
|
||||
}
|
||||
|
||||
private function acknowledgedLines(): array
|
||||
{
|
||||
$rows = DB::connection(config('m3.connection', 'pgsql'))->table('icm699m')
|
||||
->where('SHMACPKR', $this->context['manifest'])
|
||||
->where('SHCO', $this->context['company'])->where('SHWHS', $this->context['whs'])
|
||||
->where('SHSOURCE', 'PackSyst4tray')->where('SHERROR', 'Z')->where('SHTYPE', 'M3-QtyChg')
|
||||
->get();
|
||||
|
||||
return $rows->map(fn ($row) => Helper::getSubstringBetween($row->SHDATA, 'PickerLine:', ' -'))->all();
|
||||
}
|
||||
|
||||
private function removeAlreadyClosed($shipments)
|
||||
{
|
||||
$field = $this->context['transType'] === 'Z' ? 'A4MACPKR' : 'AYMACPKR';
|
||||
$deliveryNumbers = $shipments->map(fn ($row) => trim($row->{$field}))->all();
|
||||
if ($deliveryNumbers === []) {
|
||||
return $shipments;
|
||||
}
|
||||
|
||||
$closed = DB::connection(config('m3.connection', 'pgsql'))->table('icm699m')
|
||||
->whereIn('SHMACPKR', $deliveryNumbers)
|
||||
->where('SHCO', $this->context['company'])->where('SHWHS', $this->context['whs'])
|
||||
->where('SHSOURCE', self::SOURCE)->where('SHERROR', '')
|
||||
->where('SHTYPE', 'M3-Res-out')->where('SHDATA', 'like', '%Success%')
|
||||
->pluck('SHMACPKR')->all();
|
||||
|
||||
return $shipments->reject(fn ($row) => in_array($row->{$field}, $closed));
|
||||
}
|
||||
|
||||
private function dispatchShipments($shipments, $freightTerms, string $token)
|
||||
{
|
||||
if ($shipments->isEmpty()) {
|
||||
return $this->noRecordsResponse();
|
||||
}
|
||||
|
||||
if ($this->context['transType'] === 'Z') {
|
||||
return (new CloseShipmentZeroPickProcessor($this->context, $token))->process($shipments);
|
||||
}
|
||||
|
||||
return (new CloseShipmentStandardProcessor($this->context, $token, $freightTerms))->process($shipments);
|
||||
}
|
||||
|
||||
private function noRecordsResponse()
|
||||
{
|
||||
$delivery = $this->context['manifest'] !== '' ? $this->context['manifest'] : self::ZERO_PAD;
|
||||
$this->context['delivery'] = $delivery;
|
||||
$message = 'No records found in ICM695M1,or URL parameters incomplete';
|
||||
$this->log('M3-Req-out', $message, 'X');
|
||||
|
||||
return response()->json([
|
||||
'code' => '405',
|
||||
'status' => 'Error',
|
||||
'message' => $message . ' For MacPkr/Manifest:' . $delivery,
|
||||
]);
|
||||
}
|
||||
|
||||
private function exceptionResponse(Throwable $throwable)
|
||||
{
|
||||
$isCurlError = str_contains($throwable->getMessage(), 'cURL error');
|
||||
$message = $isCurlError
|
||||
? 'Curl Error: Communication with M3 Timed Out'
|
||||
: 'An Error Occured - ' . substr((string) $throwable, 0, 300);
|
||||
$this->log('M3-Res-out', $message . ($isCurlError ? ' M3-JSN-out' . $this->context['jsonIn'] : ''), 'X');
|
||||
|
||||
if (! $isCurlError) {
|
||||
Log::error($throwable);
|
||||
}
|
||||
|
||||
return response()->json([
|
||||
'code' => '405',
|
||||
'status' => 'error',
|
||||
'message' => $isCurlError ? $message . '.' : 'Catched Error: ' . $throwable,
|
||||
]);
|
||||
}
|
||||
|
||||
private function log(string $type, string $message, string $error = ''): void
|
||||
{
|
||||
Helper::logToICM699M([
|
||||
'SHMACPKR' => $this->context['delivery'],
|
||||
'SHDT' => date('Y-m-d'), 'SHTIME' => date('H:i:s'),
|
||||
'SHERROR' => $error, 'SHINTBOX' => '', 'SHTYPE' => $type, 'SHDATA' => $message,
|
||||
'SHUSER' => $this->context['user'], 'SHSOURCE' => self::SOURCE,
|
||||
'SHCO' => $this->context['company'], 'SHWHS' => $this->context['whs'],
|
||||
]);
|
||||
}
|
||||
|
||||
private function acknowledgementValues(): array
|
||||
{
|
||||
return [
|
||||
'SHERROR' => 'Z', 'SHACKUSER' => $this->context['user'],
|
||||
'SHACKDATE' => date('Y-m-d'), 'SHACKTIME' => date('H:i:s'),
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,207 @@
|
||||
<?php
|
||||
|
||||
namespace Modules\M3\Http\Controllers\API;
|
||||
|
||||
use Config;
|
||||
use DB;
|
||||
use Http;
|
||||
use Log;
|
||||
use Modules\M3\Helpers\Helper;
|
||||
|
||||
final class CloseShipmentStandardProcessor
|
||||
{
|
||||
private const SOURCE = 'CloseShipmentCoChargeAPI';
|
||||
private const JSON_CONTENT_TYPE = 'application/json';
|
||||
private const NULL_M3_RESPONSE = 'M3 API responded with null value';
|
||||
|
||||
public function __construct(
|
||||
private array $context,
|
||||
private readonly string $token,
|
||||
private readonly mixed $freightTerms,
|
||||
) {
|
||||
}
|
||||
|
||||
public function process($shipments)
|
||||
{
|
||||
$allErrors = '';
|
||||
$lastShipment = null;
|
||||
foreach ($shipments as $shipment) {
|
||||
$lastShipment = $this->shipmentContext($shipment);
|
||||
$allErrors .= $this->processShipment($lastShipment);
|
||||
}
|
||||
|
||||
if ($lastShipment !== null) {
|
||||
$this->closeFalsePositiveErrors($lastShipment);
|
||||
}
|
||||
$this->markManifestProcessed();
|
||||
|
||||
return $this->resultResponse($allErrors);
|
||||
}
|
||||
|
||||
private function shipmentContext(object $shipment): array
|
||||
{
|
||||
return [
|
||||
'whs' => trim($shipment->AYWHS),
|
||||
'company' => trim($shipment->AYCO),
|
||||
'delivery' => trim($shipment->AYMACPKR),
|
||||
'release' => trim($shipment->AYRLNO),
|
||||
'manifest' => trim($shipment->AYAPRF1551),
|
||||
];
|
||||
}
|
||||
|
||||
private function processShipment(array $shipment): string
|
||||
{
|
||||
$endFlag = $this->shortPickEndFlag($shipment);
|
||||
$trailer = $this->trailerContext($shipment);
|
||||
$builder = new CloseShipmentPackageBuilder($this->context, $this->freightTerms);
|
||||
$state = $builder->build($shipment, $trailer);
|
||||
|
||||
if ($state['packages'] === []) {
|
||||
return $state['errors'];
|
||||
}
|
||||
|
||||
$json = $builder->closeShipmentJson($shipment, $trailer, $state, $endFlag);
|
||||
if ($state['errors'] !== '') {
|
||||
$this->logValidationErrors($shipment, $state['errors']);
|
||||
|
||||
return $state['errors'];
|
||||
}
|
||||
|
||||
return $this->sendCloseRequest($shipment, $json, $state['coCharge']);
|
||||
}
|
||||
|
||||
private function shortPickEndFlag(array $shipment): int
|
||||
{
|
||||
$rows = DB::connection(config('m3.connection', 'pgsql'))->table('icm688m2')
|
||||
->select('A5PART', 'A5PACKED', 'A5PICKED', 'A5PKLN', 'A5RSVQTY')->distinct()
|
||||
->where('A5CO', $shipment['company'])->where('A5WHS', $shipment['whs'])
|
||||
->where('A5MACPKR', $shipment['delivery'])->where('A5RLNO', $shipment['release'])
|
||||
->orderBy('A5PART')->get();
|
||||
|
||||
foreach ($rows as $row) {
|
||||
if ((int) ($row->A5RSVQTY ?? 0) - (int) ($row->A5PICKED ?? 0) > 0) {
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
private function trailerContext(array $shipment): array
|
||||
{
|
||||
$row = DB::connection(config('m3.connection', 'pgsql'))->table('icm705m1')
|
||||
->select('APTRUCKID', 'APTRACKNO', 'APMANIFEST')
|
||||
->where('APCO', $this->context['company'])->where('APMANIFEST', $shipment['manifest'])
|
||||
->where('APWHS', $this->context['whs'])->first();
|
||||
|
||||
return [
|
||||
'trailer' => isset($row->APTRUCKID) ? trim($row->APTRUCKID) : '',
|
||||
'pro' => isset($row->APTRACKNO) ? trim($row->APTRACKNO) : '',
|
||||
];
|
||||
}
|
||||
|
||||
private function sendCloseRequest(array $shipment, string $json, array $coCharge): string
|
||||
{
|
||||
Log::channel('dailyCron')->info('1 >>> JSON Sent for Manifest: ' . (string) (int) $shipment['manifest'] . ' ,Macpicker: ' . $shipment['delivery']);
|
||||
$this->log($shipment, 'M3-JSN-out', $json);
|
||||
$response = Http::withHeaders(['accept' => self::JSON_CONTENT_TYPE, 'Content-Type' => self::JSON_CONTENT_TYPE, 'Connection' => 'close'])
|
||||
->withToken($this->token)->withBody($json, self::JSON_CONTENT_TYPE)
|
||||
->timeout(Config::get('arrays.curlTimeOut'))->retry(3, 100)
|
||||
->post(env('ENDPOINTURL') . 'APIFLOWS/Sentinel/TransferAPI?ionapiRespStyle=sync');
|
||||
Log::channel('dailyCron')->info('2 >>> Close API M3 Responded...');
|
||||
$result = $this->parseM3Response($response, $shipment, $coCharge !== []);
|
||||
|
||||
if ($result['success']) {
|
||||
$this->log($shipment, 'M3-Res-out', 'Success...' . json_encode($result['responseResult']) . $result['session']);
|
||||
|
||||
return '';
|
||||
}
|
||||
|
||||
$this->markShipmentFailed($shipment);
|
||||
$this->log($shipment, 'M3-Res-out', 'Important-Error: ' . $result['errors'] . json_encode($result['message']) . $result['session'], 'X');
|
||||
|
||||
return $result['errors'] . ' ';
|
||||
}
|
||||
|
||||
private function parseM3Response($response, array $shipment, bool $hasCoCharge): array
|
||||
{
|
||||
$status = $response->getStatusCode();
|
||||
$session = $response->hasHeader('x-transaction-id') ? '-sessionID:' . $response->header('x-transaction-id') : '-sessionID';
|
||||
$contents = json_decode(Helper::sanityzeString($response->getBody()->getContents()));
|
||||
if ($contents === null || (string) $status !== '200') {
|
||||
return ['success' => false, 'message' => self::NULL_M3_RESPONSE, 'responseResult' => '', 'errors' => $status, 'session' => $session];
|
||||
}
|
||||
|
||||
$message = $contents->result->CloseShipment;
|
||||
$apiResult = $message->PickingListClose->Result;
|
||||
$responseResult = $contents->result;
|
||||
$errors = Helper::checkM3Errors($responseResult);
|
||||
if ($apiResult !== 'Error') {
|
||||
$this->markShipmentSuccessful($shipment, $hasCoCharge);
|
||||
}
|
||||
|
||||
return ['success' => $apiResult !== 'Error', 'message' => $message, 'responseResult' => $responseResult, 'errors' => $errors, 'session' => $session];
|
||||
}
|
||||
|
||||
private function markShipmentSuccessful(array $shipment, bool $hasCoCharge): void
|
||||
{
|
||||
$values = $hasCoCharge ? ['A4RATEM3' => 'X', 'A4PICKSTS' => 'A'] : ['A4PICKSTS' => 'A'];
|
||||
DB::connection(config('m3.connection', 'pgsql'))->table('icm688m1')
|
||||
->where('A4MACPKR', $shipment['delivery'])->where('A4CO', $shipment['company'])
|
||||
->where('A4WHS', $shipment['whs'])->update($values);
|
||||
}
|
||||
|
||||
private function markShipmentFailed(array $shipment): void
|
||||
{
|
||||
DB::connection(config('m3.connection', 'pgsql'))->table('icm688m1')
|
||||
->where('A4MACPKR', $shipment['delivery'])->where('A4CO', $shipment['company'])
|
||||
->where('A4WHS', $shipment['whs'])->update(['A4PICKSTS' => 'a']);
|
||||
}
|
||||
|
||||
private function logValidationErrors(array $shipment, string $errors): void
|
||||
{
|
||||
$this->log($shipment, 'M3-Res-out', Helper::removeDuplicateSubstrings($errors), 'X');
|
||||
}
|
||||
|
||||
private function closeFalsePositiveErrors(array $shipment): void
|
||||
{
|
||||
DB::connection(config('m3.connection', 'pgsql'))->table('icm699m')
|
||||
->where('SHMACPKR', $shipment['delivery'])->where('SHCO', $this->context['company'])
|
||||
->where('SHWHS', $this->context['whs'])->where('SHSOURCE', self::SOURCE)->where('SHERROR', 'X')
|
||||
->where('SHTYPE', 'M3-General')->where('SHDATA', 'like', '%CommTCP_read%')
|
||||
->update(['SHERROR' => 'Z', 'SHACKUSER' => $this->context['user'], 'SHACKDATE' => date('Y-m-d'),
|
||||
'SHACKTIME' => date('H:i:s'), 'SHACKNOTE' => 'Sentinel automatically closed the error (false positive CommTCP_read)']);
|
||||
}
|
||||
|
||||
private function markManifestProcessed(): void
|
||||
{
|
||||
if ($this->context['transType'] !== 'M') {
|
||||
return;
|
||||
}
|
||||
DB::connection(config('m3.connection', 'pgsql'))->table('icm705m1')
|
||||
->where('APMANIFEST', $this->context['manifest'])->where('APCO', $this->context['company'])
|
||||
->where('APWHS', $this->context['whs'])->update(['APSPARE2' => 'X']);
|
||||
}
|
||||
|
||||
private function resultResponse(string $errors)
|
||||
{
|
||||
if ($errors === '') {
|
||||
return response()->json(['code' => '200', 'status' => 'Success', 'message' => 'All Operations Successful']);
|
||||
}
|
||||
|
||||
return response()->json([
|
||||
'code' => '206', 'status' => 'Success With Warnings',
|
||||
'message' => Helper::removeDuplicateSubstrings($errors) . '; Check Transaction Logs for Errors or Warnings.',
|
||||
]);
|
||||
}
|
||||
|
||||
private function log(array $shipment, string $type, string $message, string $error = ''): void
|
||||
{
|
||||
Helper::logToICM699M([
|
||||
'SHMACPKR' => $shipment['delivery'], 'SHDT' => date('Y-m-d'), 'SHTIME' => date('H:i:s'),
|
||||
'SHERROR' => $error, 'SHINTBOX' => '', 'SHTYPE' => $type, 'SHDATA' => $message,
|
||||
'SHUSER' => $this->context['user'], 'SHSOURCE' => self::SOURCE,
|
||||
'SHCO' => $this->context['company'], 'SHWHS' => $this->context['whs'],
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,133 @@
|
||||
<?php
|
||||
|
||||
namespace Modules\M3\Http\Controllers\API;
|
||||
|
||||
use Config;
|
||||
use DB;
|
||||
use Http;
|
||||
use Modules\M3\Helpers\Helper;
|
||||
|
||||
final class CloseShipmentZeroPickProcessor
|
||||
{
|
||||
private const SOURCE = 'CloseShipmentCoChargeAPI';
|
||||
private const JSON_CONTENT_TYPE = 'application/json';
|
||||
private const NULL_M3_RESPONSE = 'M3 API responded with null value';
|
||||
|
||||
public function __construct(private readonly array $context, private readonly string $token)
|
||||
{
|
||||
}
|
||||
|
||||
public function process($shipments)
|
||||
{
|
||||
$errors = '';
|
||||
foreach ($shipments as $row) {
|
||||
$shipment = $this->shipmentContext($row);
|
||||
$errors .= $this->closeShipment($shipment);
|
||||
$this->closeFalsePositiveErrors($shipment);
|
||||
}
|
||||
|
||||
return $this->resultResponse($errors);
|
||||
}
|
||||
|
||||
private function shipmentContext(object $row): array
|
||||
{
|
||||
return [
|
||||
'delivery' => trim($row->A4MACPKR),
|
||||
'whs' => trim($row->A4WHS),
|
||||
'company' => trim($row->A4CO),
|
||||
];
|
||||
}
|
||||
|
||||
private function closeShipment(array $shipment): string
|
||||
{
|
||||
$json = json_encode([
|
||||
'DIVI' => $shipment['company'], 'WHS' => $shipment['whs'],
|
||||
'DELIVERYNO' => $shipment['delivery'], 'TYPE' => 'CloseShipment',
|
||||
'PICKLISTSUFFIX' => '1', 'ENDFLAG' => '1',
|
||||
]);
|
||||
$this->log($shipment, 'M3-JSN-out', $json);
|
||||
$response = Http::withHeaders([
|
||||
'accept' => self::JSON_CONTENT_TYPE, 'Content-Type' => self::JSON_CONTENT_TYPE, 'Connection' => 'close',
|
||||
])->withToken($this->token)->withBody($json, self::JSON_CONTENT_TYPE)
|
||||
->timeout(Config::get('arrays.curlTimeOut'))->retry(2, 100)
|
||||
->post(env('ENDPOINTURL') . 'APIFLOWS/Sentinel/TransferAPI?ionapiRespStyle=sync');
|
||||
$result = $this->parseResponse($response, $shipment);
|
||||
|
||||
if ($result['success']) {
|
||||
$this->log($shipment, 'M3-Res-out', 'Success' . $result['session']);
|
||||
|
||||
return '';
|
||||
}
|
||||
|
||||
$this->markFailed($shipment);
|
||||
$this->log($shipment, 'M3-Res-out', 'Important-Error: ' . $result['errors'] . json_encode($result['message']) . $result['session'], 'X');
|
||||
|
||||
return $result['errors'] . '-';
|
||||
}
|
||||
|
||||
private function parseResponse($response, array $shipment): array
|
||||
{
|
||||
$contents = json_decode($response->getBody()->getContents());
|
||||
$session = $response->hasHeader('x-transaction-id') ? '-sessionID:' . $response->header('x-transaction-id') : '-sessionID';
|
||||
if ($contents === null) {
|
||||
return ['success' => false, 'message' => self::NULL_M3_RESPONSE, 'errors' => '', 'session' => $session];
|
||||
}
|
||||
|
||||
$message = $contents->result->CloseShipment;
|
||||
$apiResult = $message->PickingListClose->Result;
|
||||
$errors = Helper::checkM3Errors($contents->result);
|
||||
if ($apiResult !== 'Error') {
|
||||
$this->markSuccessful($shipment);
|
||||
}
|
||||
|
||||
return ['success' => $apiResult !== 'Error', 'message' => $message, 'errors' => $errors, 'session' => $session];
|
||||
}
|
||||
|
||||
private function markSuccessful(array $shipment): void
|
||||
{
|
||||
DB::connection(config('m3.connection', 'pgsql'))->table('icm688m1')
|
||||
->where('A4MACPKR', $shipment['delivery'])->where('A4CO', $shipment['company'])
|
||||
->where('A4WHS', $shipment['whs'])->where('A4PICKSTS', '!=', 'X')->update(['A4PICKSTS' => 'A']);
|
||||
}
|
||||
|
||||
private function markFailed(array $shipment): void
|
||||
{
|
||||
DB::connection(config('m3.connection', 'pgsql'))->table('icm688m1')
|
||||
->where('A4MACPKR', $shipment['delivery'])->where('A4CO', $shipment['company'])
|
||||
->where('A4WHS', $shipment['whs'])->where('A4PICKSTS', '!=', 'X')->update(['A4PICKSTS' => 'a']);
|
||||
}
|
||||
|
||||
private function closeFalsePositiveErrors(array $shipment): void
|
||||
{
|
||||
DB::connection(config('m3.connection', 'pgsql'))->table('icm699m')
|
||||
->where('SHMACPKR', $shipment['delivery'])->where('SHCO', $shipment['company'])
|
||||
->where('SHWHS', $shipment['whs'])->where('SHSOURCE', self::SOURCE)->where('SHERROR', 'X')
|
||||
->where('SHTYPE', 'M3-General')->where('SHDATA', 'like', '%CommTCP_read%')
|
||||
->update([
|
||||
'SHERROR' => 'Z', 'SHACKUSER' => $this->context['user'], 'SHACKDATE' => date('Y-m-d'),
|
||||
'SHACKTIME' => date('H:i:s'), 'SHACKNOTE' => 'Sentinel automatically closed the error (false positive CommTCP_read)',
|
||||
]);
|
||||
}
|
||||
|
||||
private function resultResponse(string $errors)
|
||||
{
|
||||
if ($errors === '') {
|
||||
return response()->json(['code' => '200', 'status' => 'Success', 'message' => 'All Operations Successful']);
|
||||
}
|
||||
|
||||
return response()->json([
|
||||
'code' => '206', 'status' => 'Success With Warnings',
|
||||
'message' => $errors . '; Check The Logs for Errors or Warnings.',
|
||||
]);
|
||||
}
|
||||
|
||||
private function log(array $shipment, string $type, string $message, string $error = ''): void
|
||||
{
|
||||
Helper::logToICM699M([
|
||||
'SHMACPKR' => $shipment['delivery'], 'SHDT' => date('Y-m-d'), 'SHTIME' => date('H:i:s'),
|
||||
'SHERROR' => $error, 'SHINTBOX' => '', 'SHTYPE' => $type, 'SHDATA' => $message,
|
||||
'SHUSER' => $this->context['user'], 'SHSOURCE' => self::SOURCE,
|
||||
'SHCO' => $shipment['company'], 'SHWHS' => $shipment['whs'],
|
||||
]);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user