configureTimeouts(); $context = $this->transferContext($request); try { $response = $this->processTransfer($context); } catch (\Throwable $throwable) { $response = $this->exceptionResponse($context, $throwable, $context['json_in'] ?? ''); } return $response; } public function UpdateShipmentNumber(Request $request) { $this->configureTimeouts(); $context = $this->shipmentContext($request); try { $response = $this->processShipmentUpdate($context); } catch (\Throwable $throwable) { $response = $this->exceptionResponse($context, $throwable, $context['json'] ?? ''); } return $response; } private function configureTimeouts(): void { ini_set('max_execution_time', Config::get('arrays.PHPTimeOut')); ini_set('max_input_time', Config::get('arrays.PHPTimeOut')); ini_set('default_socket_timeout', Config::get('arrays.PHPTimeOut')); } private function transferContext(Request $request): array { $params = $request->all(); return [ 'params' => $params, 'user' => m3User() !== '' ? Str::upper(m3User()) : 'WebUser', 'source' => 'TransferAPIURL', 'company' => $params['COMPANY'] ?? '', 'warehouse' => $params['WHS'] ?? '', 'delivery' => $params['DELIVERYNO'] ?? self::ZERO_PAD, ]; } private function shipmentContext(Request $request): array { $params = $request->all(); return [ 'params' => $params, 'user' => m3User() !== '' ? Str::upper(m3User()) : 'WebUser', 'source' => 'UpdateShipmentNumber', 'company' => $params['COMPANY'] ?? '', 'warehouse' => $params['WHS'] ?? '', 'plant' => $params['PLANT'] ?? '', 'delivery' => $params['DELIVERYNO'] ?? '', ]; } private function processTransfer(array &$context) { if ($context['params'] === []) { $this->log($context, self::NO_URL_PARAMETERS, 'X', 'M3-Res-out'); return $this->errorResponse('405', self::NO_URL_PARAMETERS); } $context = array_merge($context, $this->transferFields($context['params'])); $this->restoreRetryPayload($context); if ($context['part'] === '') { return $this->errorResponse('405', 'Json Payload Not Valid, possible cause: Retry butto was clicked by another user.'); } return $this->executeTransfer($context); } private function transferFields(array $params): array { return [ 'part' => $params['PART'] ?? '', 'from' => $params['LOCATIONFROM'] ?? '', 'to' => $params['LOCATIONTO'] ?? '', 'quantity' => $params['QUANTITY'] ?? '', 'order_category' => $params['ORDERCATEGORY'] ?? '', 'lot' => $params['LOTNUMBER'] ?? '', 'co_number' => $params['CONUMBER'] ?? '', 'co_line' => $params['COLINENO'] ?? '', 'delivery' => $params['DELIVERYNO'] ?? self::ZERO_PAD, 'pick_suffix' => $params['PICKLISTSUFFIX'] ?? '', 'end_flag' => $params['ENDFLAG'] ?? '', 'type' => $params['TYPE'] ?? '', 'co_line_suffix' => $params['COLINESUFFIX'] ?? '', 'trans_type' => $params['TRANSTYPE'] ?? '', 'shdt' => $params['SHDT'] ?? '', 'shtime' => $params['SHTIME'] ?? '', 'shtype' => $params['SHTYPE'] ?? '', ]; } private function restoreRetryPayload(array &$context): void { if ($context['trans_type'] !== 'R') { return; } $record = DB::connection(config('m3.connection', 'pgsql'))->table('icm699m') ->select('SHDATA')->where('SHMACPKR', $context['delivery'])->where('SHDT', $context['shdt']) ->where('SHTIME', $context['shtime'])->where('SHCO', $context['company'])->where('SHWHS', $context['warehouse']) ->where('SHSOURCE', $context['source'])->where('SHERROR', 'X')->where('SHTYPE', $context['shtype'])->first(); $payload = $this->retryPayload($record->SHDATA ?? ''); if ($payload !== []) { $context = array_merge($context, $this->transferFields($payload)); } DB::connection(config('m3.connection', 'pgsql'))->table('icm699m') ->where('SHMACPKR', $context['delivery'])->where('SHDT', $context['shdt'])->where('SHTIME', $context['shtime']) ->where('SHCO', $context['company'])->where('SHWHS', $context['warehouse'])->where('SHSOURCE', $context['source']) ->where('SHERROR', 'X')->where('SHTYPE', $context['shtype']) ->update(['SHERROR' => 'Z', 'SHACKUSER' => $context['user'], 'SHACKDATE' => date('Y-m-d'), 'SHACKTIME' => date('H:i:s')]); } private function retryPayload(string $data): array { $json = ''; $marker = 'M3-JSN-out'; $urlMarker = 'url:http://127.0.0.1:10005/api/TransferAPIURL?'; $position = strpos($data, $marker); $urlPosition = strpos($data, $urlMarker); if ($position !== false) { $json = substr($data, $position + strlen($marker)); } elseif ($urlPosition !== false) { parse_str(substr($data, $urlPosition + strlen($urlMarker)), $payload); return is_array($payload) ? $payload : []; } $start = strpos($json, '{'); $end = strrpos($json, '}'); if ($start === false || $end === false || $end < $start) { return []; } $decoded = json_decode(substr($json, $start, $end - $start + 1), true); return is_array($decoded) ? $decoded : []; } private function executeTransfer(array &$context) { $line = $this->transferLine($context); $context['brad_line'] = $line->A5BRADLN ?? ''; $context['co_line_suffix'] = $line->A5PKSFX ?? ''; $context['plant'] = $line->A5PLANT ?? ''; $context['sent_picker'] = $line->A5SENTPKR ?? ''; $context['json_in'] = $this->transferJson($context, false); $json = $this->transferJson($context, true); $this->log($context, $json, '', 'M3-Req-out'); $token = $this->token($context); if (! is_string($token)) { return $token; } $result = $this->callM3($token, $json); return $this->transferResult($context, $result); } private function transferLine(array $context): ?object { $query = DB::connection(config('m3.connection', 'pgsql'))->table('icm688m2') ->select('A5BRADLN', 'A5PKSFX', 'A5PLANT', 'A5SENTPKR')->distinct() ->where('A5CO', $context['company'])->where('A5WHS', $context['warehouse'])->where('A5MACPKR', $context['delivery']); if ($context['co_line'] !== '') { $query->where('A5PKLN', $context['co_line']); } return $query->first(); } private function transferJson(array $c, bool $useBradLine): string { return (string) json_encode([ 'DIVI' => $c['company'], 'WHS' => $c['warehouse'], 'PART' => $c['part'], 'LOCATIONFROM' => $c['from'], 'LOCATIONTO' => $c['to'], 'QUANTITY' => $c['quantity'], 'ORDERCATEGORY' => $c['order_category'], 'LOTNUMBER' => $c['lot'], 'CONUMBER' => $c['co_number'], 'COLINENO' => $useBradLine ? $c['brad_line'] : $c['co_line'], 'DELIVERYNO' => $c['delivery'], 'PICKLISTSUFFIX' => $c['pick_suffix'], 'ENDFLAG' => $c['end_flag'], 'TYPE' => $c['type'], 'COLINESUFFIX' => $c['co_line_suffix'], ], JSON_UNESCAPED_SLASHES); } private function processShipmentUpdate(array &$context) { $shipment = $context['params']['A4SPR15A01'] ?? ''; $pickers = $this->shipmentPickers($context, $shipment); $invalid = $this->shipmentValidationResponse($context, $shipment, $pickers); if ($invalid !== null) { return $invalid; } $context['type'] = 'UpdateShipmentNumber'; $context['json'] = (string) json_encode([ 'DIVI' => $context['company'], 'WHS' => $context['warehouse'], 'ShipmentNumber' => ['Number' => $shipment, 'DeliveryNumbers' => $pickers], 'TYPE' => $context['type'], 'ENDFLAG' => '', ], JSON_UNESCAPED_SLASHES); $this->log($context, $context['json'], '', 'M3-Req-out'); $token = $this->token($context); if (! is_string($token)) { return $token; } return $this->shipmentResult($context, $this->callM3($token, $context['json'])); } private function shipmentValidationResponse(array $context, string $shipment, array $pickers) { if ($context['params'] === []) { $this->log($context, self::NO_URL_PARAMETERS, 'X', 'M3-Res-out'); return $this->errorResponse('405', self::NO_URL_PARAMETERS); } if ($shipment === '' || $pickers === []) { return $this->errorResponse('405', 'Json Payload Not Valid, missing Parameter or no Macpicker # found.'); } return null; } private function shipmentPickers(array $context, string $shipment): array { if ($shipment === '') { return []; } $query = DB::connection(config('m3.connection', 'pgsql'))->table('icm688m1')->select('A4MACPKR') ->where('A4CO', $context['company'])->where('A4WHS', $context['warehouse'])->where('A4PLANT', $context['plant']) ->where('A4SPR15A01', $shipment); if ($context['delivery'] !== '') { $query->where('A4MACPKR', $context['delivery']); } return $query->get()->pluck('A4MACPKR')->all(); } private function token(array $context) { $response = Helper::checkReceiveTokenV2(); $data = $response->getData(); if ((string) ($data->code ?? '') === '200') { return (string) ($data->token ?? ''); } $this->log($context, (string) ($data->message ?? ''), '', 'M3-Res-out'); return $response; } private function callM3(string $token, string $json): array { $response = Http::withHeaders(['accept' => self::JSON_CONTENT_TYPE, 'Content-Type' => self::JSON_CONTENT_TYPE]) ->withToken($token)->withBody($json, self::JSON_CONTENT_TYPE)->timeout(Config::get('arrays.curlTimeOut')) ->post(env('ENDPOINTURL') . 'APIFLOWS/Sentinel/TransferAPI?ionapiRespStyle=sync'); $status = $response->getStatusCode(); $jsonResponse = ''; $errors = 'Error: No Response from M3'; if ((string) $status === '200') { $contents = json_decode($response->getBody()->getContents()); $jsonResponse = json_encode($contents); $errors = Helper::checkM3Errors($contents->result ?? null); } return ['status' => $status, 'json' => $jsonResponse, 'errors' => $errors, 'metrics' => $this->responseMetrics($response)]; } private function responseMetrics($response): string { $session = $response->hasHeader('x-transaction-id') ? '-sessionID:' . $response->header('x-transaction-id') : '-sessionID'; $raw = $response->hasHeader('x-response-time') ? ltrim($response->header('x-response-time'), '-') : '0'; $dateTime = new DateTime; $dateTime->setTimestamp(floatval($raw) / 1000); $responseTime = $raw !== '' ? '-M3ResponseTime:' . $dateTime->format('d/m/Y H:i:s.v') : '-M3ResponseTime'; $transfer = $response->transferStats->getTransferTime(); return $session . $responseTime . ($transfer !== null ? '-M3transferTime:' . $transfer : '-M3transferTime'); } private function transferResult(array $c, array $result) { $prefix = $result['status'] . 'TYPE :' . $c['type'] . self::ORDER_CATEGORY_LABEL . $c['order_category'] . '-'; if ($result['errors'] !== '') { $this->log($c, $result['status'] . '-' . $result['errors'] . '-TYPE:' . $c['type'] . self::ORDER_CATEGORY_LABEL . $c['order_category'] . '-' . $result['json'] . 'M3-JSN-out' . $c['json_in'] . $result['metrics'], 'X', 'M3-Res-out'); return $this->errorResponse('206', $result['status'] . '-' . $result['errors'] . '- TYPE :' . $c['type'] . self::ORDER_CATEGORY_LABEL . $c['order_category'] . '-' . $result['json']); } $this->log($c, $prefix . $result['json'] . $result['metrics'], '', 'M3-Res-out'); $this->insertTransferAudit($c); return response()->json(['code' => $result['status'], 'status' => 'Success']); } private function shipmentResult(array $c, array $result) { if ($result['errors'] !== '') { $this->log($c, $result['status'] . '-' . $result['errors'] . '-TYPE:' . $c['type'] . '-' . $result['json'] . 'M3-JSN-out' . $c['json'] . $result['metrics'], 'X', 'M3-Res-out'); return $this->errorResponse('206', $result['status'] . '-' . $result['errors'] . '- TYPE :' . $c['type'] . '-' . $result['json']); } $this->log($c, $result['status'] . 'TYPE :' . $c['type'] . '-' . $result['json'] . $result['metrics'], '', 'M3-Res-out'); return response()->json(['code' => $result['status'], 'status' => 'Success']); } private function insertTransferAudit(array $c): void { DB::connection(config('m3.connection', 'pgsql'))->table('icm799m')->insert([ 'IMDIV' => $c['company'], 'IMPLANT' => $c['plant'], 'IMWHS' => $c['warehouse'], 'IMSENTPKR' => $c['sent_picker'], 'IMDELNO' => $c['delivery'], 'IMORDER' => $c['co_number'], 'IMLINE' => $c['co_line'], 'IMBRADLN' => $c['brad_line'], 'IMSUFFIX' => $c['pick_suffix'], 'IMPART' => $c['part'], 'IMLOT' => $c['lot'], 'IMQTY' => $c['quantity'], 'IMFRLOC' => $c['from'], 'IMTOLOC' => $c['to'], 'IMDATE' => date('Ymd'), 'IMTIME' => date('His'), ]); } private function log(array $c, string $message, string $error, string $type): void { Helper::logToICM699M([ 'SHMACPKR' => $c['delivery'] ?: self::ZERO_PAD, 'SHDT' => date('Y-m-d'), 'SHTIME' => date('H:i:s'), 'SHERROR' => $error, 'SHINTBOX' => '', 'SHTYPE' => $type, 'SHDATA' => $message, 'SHUSER' => $c['user'], 'SHSOURCE' => $c['source'], 'SHCO' => $c['company'], 'SHWHS' => $c['warehouse'], ]); } private function exceptionResponse(array $context, \Throwable $throwable, string $json) { if (str_contains($throwable->getMessage(), 'cURL error')) { $this->log($context, 'Curl Error: Communication with M3 Timed Out M3-JSN-out' . $json, 'X', 'M3-Res-out'); return response()->json(['code' => '405', 'status' => 'error', 'message' => 'Curl Error: Communication with M3 Timed Out.']); } $this->log($context, 'An Error Occured - ' . substr((string) $throwable, 0, 300), 'X', 'M3-Err-out'); Log::error($throwable); return response()->json(['code' => '405', 'status' => 'error', 'message' => 'Uncaught Error']); } private function errorResponse(string $code, string $message) { return response()->json(['code' => $code, 'status' => 'Error', 'message' => $message]); } }