init
This commit is contained in:
commit
59945ea99c
7287
03-01-25-2025-01-03.log
Normal file
7287
03-01-25-2025-01-03.log
Normal file
File diff suppressed because it is too large
Load Diff
142
docker-compose.yml
Normal file
142
docker-compose.yml
Normal file
@ -0,0 +1,142 @@
|
||||
version: '3'
|
||||
|
||||
networks:
|
||||
laravel:
|
||||
# mysql:
|
||||
# external: true
|
||||
# name: mysql
|
||||
traefik:
|
||||
external: true
|
||||
name: traefik
|
||||
|
||||
services:
|
||||
nginx:
|
||||
image: nginx:alpine-slim
|
||||
restart: always
|
||||
ports:
|
||||
- '8080:80'
|
||||
container_name: shipping-nginx
|
||||
volumes:
|
||||
# Please update below path as per your environment.
|
||||
- ./docker/nginx/default.conf:/etc/nginx/conf.d/default.conf
|
||||
depends_on:
|
||||
- php
|
||||
- laravel-redis
|
||||
# - laravel-queue
|
||||
# - laravel-cron
|
||||
# - phpmyadmin
|
||||
networks:
|
||||
- laravel
|
||||
- traefik
|
||||
labels:
|
||||
traefik.enable: true
|
||||
# https
|
||||
traefik.http.routers.shipping.entrypoints: https
|
||||
traefik.http.routers.shipping.rule: Host(`shipping.dalkove-ovladace.cz`)
|
||||
# traefik.http.routers.shipping.service: shipping
|
||||
traefik.http.services.shipping.loadbalancer.server.port: 80
|
||||
traefik.http.routers.shipping.tls.certresolver: mytlschallenge
|
||||
|
||||
php:
|
||||
container_name: shipping-php
|
||||
user: "www-data"
|
||||
# restart: always
|
||||
platform: linux/amd64
|
||||
build:
|
||||
# Please update below path as per your environment.
|
||||
context: .
|
||||
dockerfile: ./docker/php/Dockerfile
|
||||
ports:
|
||||
- '9001:9001'
|
||||
volumes:
|
||||
- ./src:/var/www/html
|
||||
- ./src/.env:/var/www/html/.env
|
||||
- ./docker/roadrunner.yaml:/var/www/html/.rr.yaml
|
||||
- ./docker/policy.xml:/etc/ImageMagick-6/policy.xml
|
||||
networks:
|
||||
- laravel
|
||||
- traefik
|
||||
depends_on:
|
||||
- laravel-redis
|
||||
|
||||
laravel-redis:
|
||||
image: redis:alpine
|
||||
container_name: laravel-redis
|
||||
restart: unless-stopped
|
||||
ports:
|
||||
- 6380:6379
|
||||
networks:
|
||||
- laravel
|
||||
- traefik
|
||||
|
||||
npm:
|
||||
image: node:alpine
|
||||
# container_name: npm
|
||||
volumes:
|
||||
- ./src:/var/www/html
|
||||
- ./src/.env:/var/www/html/.env
|
||||
ports:
|
||||
- 3000:3000
|
||||
- 3001:3001
|
||||
working_dir: /var/www/html
|
||||
profiles: [ "npm" ]
|
||||
entrypoint: [ 'npm' ]
|
||||
networks:
|
||||
- laravel
|
||||
- traefik
|
||||
|
||||
horizon:
|
||||
container_name: shipping-horizon
|
||||
platform: linux/arm64/v8
|
||||
build:
|
||||
context: .
|
||||
dockerfile: ./docker/horizon.dockerfile
|
||||
restart: unless-stopped
|
||||
volumes:
|
||||
- ./src:/var/www/html
|
||||
- ./src/.env:/var/www/html/.env
|
||||
- ./docker/supervisor/horizon.conf:/etc/supervisor/conf.d/horizon.conf
|
||||
- ./docker/policy.xml:/etc/ImageMagick-6/policy.xml
|
||||
depends_on:
|
||||
- laravel-redis
|
||||
networks:
|
||||
- laravel
|
||||
- traefik
|
||||
|
||||
|
||||
|
||||
pdf_manager:
|
||||
container_name: shipping-pdf_manager
|
||||
build:
|
||||
context: .
|
||||
dockerfile: ./docker/python/Dockerfile
|
||||
ports:
|
||||
- "5001:5000"
|
||||
volumes:
|
||||
- ./src/scripts/pdf_manage.py:/app/pdf_manage.py
|
||||
- ./src/scripts/pdf_data:/app/pdfs
|
||||
networks:
|
||||
- laravel
|
||||
- traefik
|
||||
|
||||
|
||||
|
||||
|
||||
laravel-cron:
|
||||
build:
|
||||
context: ./docker
|
||||
dockerfile: php.dockerfile
|
||||
args:
|
||||
- UID=${UID:-1001}
|
||||
- GID=${GID:-1001}
|
||||
- USER=${USER:-laravel}
|
||||
container_name: laravel-cron
|
||||
volumes:
|
||||
- ./src:/var/www/html
|
||||
- ./src/.env:/var/www/html/.env
|
||||
|
||||
working_dir: /var/www/html
|
||||
entrypoint: [ 'php', '/var/www/html/artisan', 'schedule:work' ]
|
||||
networks:
|
||||
- laravel
|
||||
- traefik
|
19
docker/composer.dockerfile
Normal file
19
docker/composer.dockerfile
Normal file
@ -0,0 +1,19 @@
|
||||
FROM composer:latest as composer
|
||||
|
||||
# environment arguments
|
||||
ARG UID
|
||||
ARG GID
|
||||
ARG USER
|
||||
|
||||
ENV UID=${UID}
|
||||
ENV GID=${GID}
|
||||
ENV USER=${USER}
|
||||
|
||||
# Dialout group in alpine linux conflicts with MacOS staff group's gid, whis is 20. So we remove it.
|
||||
RUN delgroup dialout
|
||||
|
||||
# Creating user and group
|
||||
RUN addgroup -g ${GID} --system ${USER}
|
||||
RUN adduser -G ${USER} --system -D -s /bin/sh -u ${UID} ${USER}
|
||||
|
||||
WORKDIR /var/www/html
|
25
docker/horizon.dockerfile
Normal file
25
docker/horizon.dockerfile
Normal file
@ -0,0 +1,25 @@
|
||||
FROM t0is/laravel-roadrunner-base:latest-amd64
|
||||
|
||||
RUN apt update -y && apt upgrade -y
|
||||
RUN apt-get install -y supervisor
|
||||
RUN docker-php-ext-enable redis
|
||||
|
||||
WORKDIR /var/www/html
|
||||
|
||||
|
||||
COPY src/. /var/www/html
|
||||
COPY src/.env /var/www/html/.env
|
||||
COPY docker/roadrunner.yaml /var/www/html/.rr.yaml
|
||||
COPY docker/supervisor/horizon.conf /etc/supervisor/conf.d/horizon.conf
|
||||
|
||||
RUN composer install --optimize-autoloader --no-dev
|
||||
|
||||
RUN #php artisan optimize:clear && php artisan optimize
|
||||
|
||||
RUN chown -R www-data:www-data /var/www/html
|
||||
RUN chmod -R 2755 /var/www/html/storage
|
||||
|
||||
RUN php artisan octane:install --server=roadrunner
|
||||
|
||||
|
||||
CMD ["supervisord", "-c", "/etc/supervisor/conf.d/horizon.conf"]
|
7
docker/init_db.sql
Normal file
7
docker/init_db.sql
Normal file
@ -0,0 +1,7 @@
|
||||
CREATE USER IF NOT EXISTS 'shipping'@'%' IDENTIFIED BY 'shipping';
|
||||
CREATE DATABASE IF NOT EXISTS shipping CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
|
||||
GRANT ALL ON shipping.* TO shipping@'%';
|
||||
GRANT ALL ON pps.* TO shipping@'%';
|
||||
GRANT ALL ON vat_warehouse.* TO shipping@'%';
|
||||
GRANT SELECT,DELETE,DROP ON mysql.general_log TO shipping@'%';
|
||||
flush privileges;
|
26
docker/nginx.dockerfile
Normal file
26
docker/nginx.dockerfile
Normal file
@ -0,0 +1,26 @@
|
||||
FROM nginx:stable-alpine
|
||||
|
||||
# environment arguments
|
||||
ARG UID
|
||||
ARG GID
|
||||
ARG USER
|
||||
|
||||
ENV UID=${UID}
|
||||
ENV GID=${GID}
|
||||
ENV USER=${USER}
|
||||
|
||||
# Dialout group in alpine linux conflicts with MacOS staff group's gid, whis is 20. So we remove it.
|
||||
RUN delgroup dialout
|
||||
|
||||
# Creating user and group
|
||||
RUN addgroup -g ${GID} --system ${USER}
|
||||
RUN adduser -G ${USER} --system -D -s /bin/sh -u ${UID} ${USER}
|
||||
|
||||
# Modify nginx configuration to use the new user's priviledges for starting it.
|
||||
RUN sed -i "s/user nginx/user '${USER}'/g" /etc/nginx/nginx.conf
|
||||
|
||||
# Copies nginx configurations to override the default.
|
||||
ADD ./nginx/*.conf /etc/nginx/conf.d/
|
||||
|
||||
# Make html directory
|
||||
RUN mkdir -p /var/www/html
|
129
docker/nginx/default.conf
Normal file
129
docker/nginx/default.conf
Normal file
@ -0,0 +1,129 @@
|
||||
# Nginx default.conf
|
||||
|
||||
gzip on;
|
||||
gzip_disable "msie6";
|
||||
gzip_vary on;
|
||||
gzip_proxied any;
|
||||
gzip_comp_level 6;
|
||||
gzip_buffers 16 8k;
|
||||
gzip_http_version 1.1;
|
||||
gzip_min_length 256;
|
||||
gzip_types text/plain text/css application/json application/x-javascript text/xml application/xml application/xml+rss text/javascript application/vnd.ms-fontobject application/x-font-ttf font/opentype image/svg+xml image/x-icon;
|
||||
|
||||
server_tokens off;
|
||||
|
||||
client_body_buffer_size 200;
|
||||
client_max_body_size 200m;
|
||||
|
||||
fastcgi_buffers 16 16k;
|
||||
fastcgi_buffer_size 32k;
|
||||
|
||||
map $http_upgrade $connection_upgrade {
|
||||
default upgrade;
|
||||
'' close;
|
||||
}
|
||||
|
||||
upstream upstream_backend_php {
|
||||
server shipping-php:9001;
|
||||
}
|
||||
|
||||
server {
|
||||
listen 80;
|
||||
listen [::]:80;
|
||||
server_name my_backend;
|
||||
|
||||
add_header X-Frame-Options "SAMEORIGIN";
|
||||
add_header X-XSS-Protection "1; mode=block";
|
||||
add_header X-Content-Type-Options "nosniff";
|
||||
|
||||
index index.php index.html index.htm;
|
||||
|
||||
charset utf-8;
|
||||
server_tokens off;
|
||||
|
||||
error_log /var/log/nginx/error.log;
|
||||
access_log /var/log/nginx/access.log;
|
||||
|
||||
# SEO trailing slash problem fix
|
||||
# rewrite ^/(.*)/$ /$1 permanent; # remove trailing slash
|
||||
# rewrite ^(.*[^/])$ $1/ permanent; # add a trailing slash
|
||||
|
||||
############################
|
||||
# Reference: https://gist.github.com/Ellrion/4eb5df00173f0fb13a76
|
||||
############################
|
||||
|
||||
location ~* \.(jpg|jpeg|png|gif|svg|webp|html|txt|json|ico|css|js)$ {
|
||||
expires 1d;
|
||||
add_header Cache-Control public;
|
||||
access_log off;
|
||||
|
||||
try_files $uri $uri/ @octane;
|
||||
}
|
||||
|
||||
location ~ /\.(?!well-known).* {
|
||||
deny all;
|
||||
}
|
||||
|
||||
# /etc/nginx/global/php-restrictions.conf
|
||||
# Don't throw any errors for missing favicons and don't display them in the logs
|
||||
location = /favicon.ico { log_not_found off; access_log off; try_files $uri $uri/ @octane;}
|
||||
|
||||
# Don't log missing robots or show them in the nginx logs
|
||||
location = /robots.txt { allow all; log_not_found off; access_log off; try_files $uri $uri/ @octane;}
|
||||
|
||||
# Deny all attempts to access hidden files such as .htaccess, .htpasswd, .DS_Store (Mac).
|
||||
# Keep logging the requests to parse later (or to pass to firewall utilities such as fail2ban)
|
||||
location ~ /\. {
|
||||
deny all;
|
||||
}
|
||||
|
||||
# Deny access to any files with a .php extension in the uploads directory
|
||||
# Works in sub-directory installs and also in multisite network
|
||||
# Keep logging the requests to parse later (or to pass to firewall utilities such as fail2ban)
|
||||
location ~* /(?:uploads|files)/.*\.php$ {
|
||||
deny all;
|
||||
}
|
||||
|
||||
############################
|
||||
# Customize
|
||||
############################
|
||||
location /index.php {
|
||||
try_files /not_exists @octane;
|
||||
}
|
||||
|
||||
location / {
|
||||
if ($request_method !~ ^(GET|POST|HEAD|OPTIONS|PUT|DELETE)$) {
|
||||
return 405;
|
||||
}
|
||||
|
||||
try_files $uri $uri/ @octane;
|
||||
}
|
||||
|
||||
location @octane {
|
||||
set $suffix "";
|
||||
|
||||
if ($uri = /index.php) {
|
||||
set $suffix ?$query_string;
|
||||
}
|
||||
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Http_Host $http_host;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header Scheme $scheme;
|
||||
proxy_set_header SERVER_PORT $server_port;
|
||||
proxy_set_header REMOTE_ADDR $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header Upgrade $http_upgrade;
|
||||
proxy_set_header Connection $connection_upgrade;
|
||||
proxy_set_header X-CSRF-TOKEN $http_x_csrf_token;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
|
||||
# Add timeout settings
|
||||
proxy_read_timeout 300s; # Increase read timeout
|
||||
proxy_send_timeout 300s; # Increase send timeout
|
||||
proxy_connect_timeout 300s; # Increase connect timeout
|
||||
send_timeout 300s; # Increase overall send timeout
|
||||
|
||||
proxy_pass http://upstream_backend_php$suffix;
|
||||
}
|
||||
}
|
50
docker/php.dockerfile
Normal file
50
docker/php.dockerfile
Normal file
@ -0,0 +1,50 @@
|
||||
FROM php:8.3-fpm
|
||||
|
||||
# Environment arguments
|
||||
ARG UID
|
||||
ARG GID
|
||||
ARG USER
|
||||
|
||||
ENV UID=${UID}
|
||||
ENV GID=${GID}
|
||||
ENV USER=${USER}
|
||||
|
||||
# Creating user and group
|
||||
RUN groupadd -g ${GID} ${USER} \
|
||||
&& useradd -m -u ${UID} -g ${GID} -s /bin/bash ${USER}
|
||||
|
||||
# Modify PHP-FPM configuration to use the new user's privileges.
|
||||
RUN sed -i "s/user = www-data/user = '${USER}'/g" /usr/local/etc/php-fpm.d/www.conf \
|
||||
&& sed -i "s/group = www-data/group = '${USER}'/g" /usr/local/etc/php-fpm.d/www.conf \
|
||||
&& echo "php_admin_flag[log_errors] = on" >> /usr/local/etc/php-fpm.d/www.conf
|
||||
|
||||
# Update and install necessary packages
|
||||
RUN apt-get update && apt-get install -y \
|
||||
bash \
|
||||
git \
|
||||
curl \
|
||||
libxml2-dev \
|
||||
unzip \
|
||||
python3 python3-venv python3-pip libmupdf-dev texlive-extra-utils \
|
||||
libfreetype6-dev \
|
||||
libjpeg62-turbo-dev \
|
||||
libpng-dev \
|
||||
imagemagick \
|
||||
&& docker-php-ext-install pdo pdo_mysql bcmath soap intl gd \
|
||||
&& docker-php-ext-configure gd --with-freetype --with-jpeg
|
||||
|
||||
# Installing Redis extension
|
||||
RUN mkdir -p /usr/src/php/ext/redis \
|
||||
&& curl -fsSL https://github.com/phpredis/phpredis/archive/5.3.4.tar.gz | tar xvz -C /usr/src/php/ext/redis --strip 1 \
|
||||
&& echo 'redis' >> /usr/src/php-available-exts \
|
||||
&& docker-php-ext-install redis
|
||||
|
||||
|
||||
# Set PHP configurations for memory, execution time, and file upload sizes
|
||||
RUN echo "memory_limit = 1024M" > /usr/local/etc/php/conf.d/custom-php-memlimit.ini; \
|
||||
echo "max_execution_time = 500" > /usr/local/etc/php/conf.d/custom-php-time.ini; \
|
||||
echo "post_max_size = 100M" > /usr/local/etc/php/conf.d/custom-php-size.ini;
|
||||
|
||||
|
||||
# Set the default command to run PHP-FPM
|
||||
CMD ["php-fpm", "-y", "/usr/local/etc/php-fpm.conf", "-R"]
|
31
docker/php/Dockerfile
Normal file
31
docker/php/Dockerfile
Normal file
@ -0,0 +1,31 @@
|
||||
FROM t0is/laravel-roadrunner-base:latest-amd64
|
||||
|
||||
RUN apt update -y && apt upgrade -y
|
||||
|
||||
|
||||
WORKDIR /var/www/html
|
||||
RUN docker-php-ext-enable redis
|
||||
|
||||
RUN python3 -m venv /var/www/html/shipping_env
|
||||
|
||||
# Activate the virtual environment and install pymupdf
|
||||
RUN /var/www/html/shipping_env/bin/pip install --upgrade pip && \
|
||||
/var/www/html/shipping_env/bin/pip install pymupdf
|
||||
|
||||
COPY src/. /var/www/html
|
||||
COPY src/.env /var/www/html/.env
|
||||
COPY docker/roadrunner.yaml /var/www/html/.rr.yaml
|
||||
|
||||
RUN #composer update --optimize-autoloader
|
||||
RUN composer install --optimize-autoloader --no-dev
|
||||
|
||||
RUN #php artisan optimize:clear && php artisan optimize
|
||||
|
||||
RUN chown -R www-data:www-data /var/www/html
|
||||
RUN chmod -R 2755 /var/www/html/storage
|
||||
|
||||
RUN php artisan octane:install --server=roadrunner
|
||||
|
||||
EXPOSE 9001
|
||||
|
||||
CMD ["php", "artisan", "octane:start", "--server=roadrunner", "--host=0.0.0.0", "--port=9001", "--log-level=debug", "--rr-config=/var/www/html/.rr.yaml"]
|
59
docker/php/base/Dockerfile
Normal file
59
docker/php/base/Dockerfile
Normal file
@ -0,0 +1,59 @@
|
||||
|
||||
FROM php:8.3-cli
|
||||
|
||||
RUN apt update -y && apt upgrade -y
|
||||
|
||||
RUN apt install -y --no-install-recommends \
|
||||
libfreetype-dev \
|
||||
libjpeg62-turbo-dev \
|
||||
libpng-dev \
|
||||
imagemagick \
|
||||
libmagickwand-dev \
|
||||
git \
|
||||
zip unzip \
|
||||
openssl libssl-dev libcurl4-openssl-dev \
|
||||
autoconf zlib1g-dev \
|
||||
python3 python3-venv python3-pip ghostscript\
|
||||
&& docker-php-ext-configure gd --with-freetype --with-jpeg \
|
||||
&& docker-php-ext-install -j$(nproc) gd
|
||||
|
||||
RUN pecl install redis
|
||||
RUN docker-php-ext-install opcache
|
||||
RUN docker-php-ext-install pdo pdo_mysql
|
||||
RUN docker-php-ext-install mysqli
|
||||
RUN docker-php-ext-install sockets
|
||||
RUN docker-php-ext-install soap
|
||||
RUN docker-php-ext-install intl
|
||||
|
||||
# https://github.com/viest/php-ext-xlswriter
|
||||
RUN pecl install xlswriter
|
||||
|
||||
# https://dev.to/kakisoft/php-docker-how-to-enable-pcntlprocess-control-extensions-1afk
|
||||
RUN docker-php-ext-configure pcntl --enable-pcntl && docker-php-ext-install pcntl
|
||||
|
||||
RUN pecl install apcu
|
||||
|
||||
RUN curl -sS https://getcomposer.org/installer | php -- --install-dir=/usr/local/bin --filename=composer
|
||||
|
||||
RUN rm -rf /var/cache/apt/lists
|
||||
|
||||
# Please refer to the README.md for updating these 2 files.
|
||||
#COPY src/composer.json /tmp
|
||||
|
||||
#RUN cd /tmp && composer install && composer update --optimize-autoloader
|
||||
|
||||
#RUN mv /tmp/vendor /var/www/html
|
||||
#RUN cp /tmp/composer.* /var/www/html
|
||||
|
||||
#RUN composer clear-cache
|
||||
|
||||
RUN curl -L https://github.com/roadrunner-server/roadrunner/releases/download/v2024.3.0/roadrunner-2024.3.0-linux-amd64.tar.gz -o /tmp/roadrunner.tar.gz \
|
||||
&& tar -xvzf /tmp/roadrunner.tar.gz -C /tmp \
|
||||
&& mv /tmp/roadrunner-2024.3.0-linux-amd64/rr /var/www/html/rr \
|
||||
&& chmod +x /var/www/html/rr \
|
||||
&& rm -rf /tmp/roadrunner.tar.gz /tmp/roadrunner-2024.3.0-linux-amd64
|
||||
|
||||
# Set PHP configurations for memory, execution time, and file upload sizes
|
||||
RUN echo "memory_limit = 1024M" > /usr/local/etc/php/conf.d/custom-php-memlimit.ini; \
|
||||
echo "max_execution_time = 500" > /usr/local/etc/php/conf.d/custom-php-time.ini; \
|
||||
echo "post_max_size = 100M" > /usr/local/etc/php/conf.d/custom-php-size.ini;
|
90
docker/policy.xml
Normal file
90
docker/policy.xml
Normal file
@ -0,0 +1,90 @@
|
||||
<!--
|
||||
Configure ImageMagick policies.
|
||||
|
||||
Domains include system, delegate, coder, filter, path, or resource.
|
||||
|
||||
Rights include none, read, write, execute and all. Use | to combine them,
|
||||
for example: "read | write" to permit read from, or write to, a path.
|
||||
|
||||
Use a glob expression as a pattern.
|
||||
|
||||
Suppose we do not want users to process MPEG video images:
|
||||
|
||||
<policy domain="delegate" rights="none" pattern="mpeg:decode" />
|
||||
|
||||
Here we do not want users reading images from HTTP:
|
||||
|
||||
<policy domain="coder" rights="none" pattern="HTTP" />
|
||||
|
||||
The /repository file system is restricted to read only. We use a glob
|
||||
expression to match all paths that start with /repository:
|
||||
|
||||
<policy domain="path" rights="read" pattern="/repository/*" />
|
||||
|
||||
Lets prevent users from executing any image filters:
|
||||
|
||||
<policy domain="filter" rights="none" pattern="*" />
|
||||
|
||||
Any large image is cached to disk rather than memory:
|
||||
|
||||
<policy domain="resource" name="area" value="1GP"/>
|
||||
|
||||
Use the default system font unless overwridden by the application:
|
||||
|
||||
<policy domain="system" name="font" value="/usr/share/fonts/favorite.ttf"/>
|
||||
|
||||
Define arguments for the memory, map, area, width, height and disk resources
|
||||
with SI prefixes (.e.g 100MB). In addition, resource policies are maximums
|
||||
for each instance of ImageMagick (e.g. policy memory limit 1GB, -limit 2GB
|
||||
exceeds policy maximum so memory limit is 1GB).
|
||||
|
||||
Rules are processed in order. Here we want to restrict ImageMagick to only
|
||||
read or write a small subset of proven web-safe image types:
|
||||
|
||||
<policy domain="delegate" rights="none" pattern="*" />
|
||||
<policy domain="filter" rights="none" pattern="*" />
|
||||
<policy domain="coder" rights="none" pattern="*" />
|
||||
<policy domain="coder" rights="read|write" pattern="{GIF,JPEG,PNG,WEBP}" />
|
||||
-->
|
||||
<policymap>
|
||||
<!-- <policy domain="resource" name="temporary-path" value="/tmp"/> -->
|
||||
<policy domain="resource" name="memory" value="256MiB"/>
|
||||
<policy domain="resource" name="map" value="512MiB"/>
|
||||
<policy domain="resource" name="width" value="16KP"/>
|
||||
<policy domain="resource" name="height" value="16KP"/>
|
||||
<!-- <policy domain="resource" name="list-length" value="128"/> -->
|
||||
<policy domain="resource" name="area" value="128MP"/>
|
||||
<policy domain="resource" name="disk" value="1GiB"/>
|
||||
<!-- <policy domain="resource" name="file" value="768"/> -->
|
||||
<!-- <policy domain="resource" name="thread" value="4"/> -->
|
||||
<!-- <policy domain="resource" name="throttle" value="0"/> -->
|
||||
<!-- <policy domain="resource" name="time" value="3600"/> -->
|
||||
<!-- <policy domain="coder" rights="none" pattern="MVG" /> -->
|
||||
<!-- <policy domain="module" rights="none" pattern="{PS,PDF,XPS}" /> -->
|
||||
<!-- <policy domain="path" rights="none" pattern="@*" /> -->
|
||||
<!-- <policy domain="cache" name="memory-map" value="anonymous"/> -->
|
||||
<!-- <policy domain="cache" name="synchronize" value="True"/> -->
|
||||
<!-- <policy domain="cache" name="shared-secret" value="passphrase" stealth="true"/>
|
||||
<!-- <policy domain="system" name="max-memory-request" value="256MiB"/> -->
|
||||
<!-- <policy domain="system" name="shred" value="2"/> -->
|
||||
<!-- <policy domain="system" name="precision" value="6"/> -->
|
||||
<!-- <policy domain="system" name="font" value="/path/to/font.ttf"/> -->
|
||||
<!-- <policy domain="system" name="pixel-cache-memory" value="anonymous"/> -->
|
||||
<!-- <policy domain="system" name="shred" value="2"/> -->
|
||||
<!-- <policy domain="system" name="precision" value="6"/> -->
|
||||
<!-- not needed due to the need to use explicitly by mvg: -->
|
||||
<!-- <policy domain="delegate" rights="none" pattern="MVG" /> -->
|
||||
<!-- use curl -->
|
||||
<policy domain="delegate" rights="none" pattern="URL" />
|
||||
<policy domain="delegate" rights="none" pattern="HTTPS" />
|
||||
<policy domain="delegate" rights="none" pattern="HTTP" />
|
||||
<!-- in order to avoid to get image with password text -->
|
||||
<policy domain="path" rights="none" pattern="@*"/>
|
||||
<!-- disable ghostscript format types -->
|
||||
<policy domain="coder" rights="none" pattern="PS" />
|
||||
<policy domain="coder" rights="none" pattern="PS2" />
|
||||
<policy domain="coder" rights="none" pattern="PS3" />
|
||||
<policy domain="coder" rights="none" pattern="EPS" />
|
||||
<policy domain="coder" rights="read|write" pattern="PDF" />
|
||||
<policy domain="coder" rights="none" pattern="XPS" />
|
||||
</policymap>
|
28
docker/python/Dockerfile
Normal file
28
docker/python/Dockerfile
Normal file
@ -0,0 +1,28 @@
|
||||
FROM python:3.11-slim
|
||||
|
||||
ENV PYTHONUNBUFFERED=1
|
||||
|
||||
# Set working directory
|
||||
WORKDIR /app
|
||||
|
||||
# Install system dependencies for WeasyPrint
|
||||
RUN apt-get update && apt-get install -y \
|
||||
python3-pip \
|
||||
libpango-1.0-0 \
|
||||
libharfbuzz0b \
|
||||
libpangoft2-1.0-0 \
|
||||
libharfbuzz-subset0 \
|
||||
&& apt-get clean \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
# Install Python dependencies
|
||||
RUN pip install --no-cache-dir Flask PyMuPDF weasyprint
|
||||
|
||||
# Copy application files
|
||||
COPY ./src/scripts/pdf_manage.py /app/pdf_manage.py
|
||||
|
||||
# Expose the Flask port
|
||||
EXPOSE 5000
|
||||
|
||||
# Run Flask server
|
||||
CMD ["python", "pdf_manage.py"]
|
54
docker/roadrunner.yaml
Normal file
54
docker/roadrunner.yaml
Normal file
@ -0,0 +1,54 @@
|
||||
version: '3'
|
||||
|
||||
rpc:
|
||||
# RPC interface for internal communication
|
||||
listen: tcp://127.0.0.1:6001
|
||||
|
||||
server:
|
||||
# Command to start Laravel Octane server
|
||||
command: "php artisan octane:start --server=roadrunner --host=0.0.0.0 --port=9001"
|
||||
|
||||
http:
|
||||
# HTTP server configuration
|
||||
address: 0.0.0.0:9001
|
||||
|
||||
pool:
|
||||
# Worker pool configuration
|
||||
num_workers: 8
|
||||
max_jobs: 1000
|
||||
allocate_timeout: 60s
|
||||
destroy_timeout: 60s
|
||||
|
||||
static:
|
||||
# Serve static files
|
||||
dir: ./public
|
||||
forbid: [ ".php", ".env", ".htaccess" ]
|
||||
|
||||
jobs:
|
||||
num_pollers: 64
|
||||
timeout: 60
|
||||
pipeline_size: 100000
|
||||
driver: redis
|
||||
redis:
|
||||
addr: laravel-redis:6379 # Redis host and port
|
||||
stream: [ "fetch_tracking", "callback_retry", "default", "storage_management" ]
|
||||
timeout: 10s # Fetch timeout
|
||||
prefetch: 10
|
||||
|
||||
pool:
|
||||
num_workers: 10
|
||||
allocate_timeout: 60s
|
||||
destroy_timeout: 60s
|
||||
|
||||
logs:
|
||||
# Logging configuration
|
||||
level: debug
|
||||
mode: development
|
||||
|
||||
metrics:
|
||||
# Metrics for monitoring
|
||||
address: 0.0.0.0:2112
|
||||
|
||||
health:
|
||||
# Health check endpoint
|
||||
address: 0.0.0.0:2113
|
15
docker/supervisor/horizon.conf
Normal file
15
docker/supervisor/horizon.conf
Normal file
@ -0,0 +1,15 @@
|
||||
[supervisord]
|
||||
nodaemon=true
|
||||
|
||||
|
||||
[program:horizon]
|
||||
process_name=%(program_name)s
|
||||
command=php /var/www/html/artisan horizon
|
||||
autostart=true
|
||||
autorestart=true
|
||||
stopasgroup=true
|
||||
killasgroup=true
|
||||
user=www-data
|
||||
redirect_stderr=true
|
||||
stdout_logfile=/var/www/html/storage/logs/horizon.log
|
||||
stopwaitsecs=3600
|
16
docker/xdebug.ini
Normal file
16
docker/xdebug.ini
Normal file
@ -0,0 +1,16 @@
|
||||
zend_extension=xdebug.so
|
||||
xdebug.remote_enable=1
|
||||
xdebug.mode=debug
|
||||
xdebug.start_with_request=yes
|
||||
xdebug.idekey=shipping
|
||||
xdebug.client_host=host.docker.internal
|
||||
xdebug.client_port=9003
|
||||
xdebug.remote_autostart=1
|
||||
;xdebug.remote_connect_back=1
|
||||
xdebug.remote_handler = dbgp
|
||||
xdebug.remote_mode = req
|
||||
xdebug.log = /var/www/html/xdebug.log
|
||||
xdebug.log_level = 10
|
||||
|
||||
|
||||
|
63
src/.env
Normal file
63
src/.env
Normal file
@ -0,0 +1,63 @@
|
||||
|
||||
APP_NAME=vat_shipping
|
||||
VITE_APP_NAME="${APP_NAME}"
|
||||
APP_URL=https://shipping.dalkove-ovladace.cz
|
||||
ASSET_URL=https://shipping.dalkove-ovladace.cz
|
||||
|
||||
DB_CONNECTION=mariadb
|
||||
DB_QUEUE_CONNECTION=mariadb
|
||||
DB_HOST=afrodite.my-devbox.cloud
|
||||
DB_PORT=3307
|
||||
DB_DATABASE=shipping
|
||||
DB_DATABASE_PPS=pps
|
||||
DB_USERNAME=shipping
|
||||
DB_PASSWORD=onOa8C263ajmWdRltmLD
|
||||
APP_KEY=base64:ZbNIu92nsvQmghttxsgjENh6Aqk4xR+o6LU7Wt9mpy8=
|
||||
|
||||
QUEUE_CONNECTION=redis
|
||||
WWWUSER=1000
|
||||
|
||||
SAIL_XDEBUG_MODE=debug
|
||||
|
||||
LOCATIONIQ_API_KEY=pk.adc1013eb5dbc390a94878110d8b658d
|
||||
OPENAI_API_KEY=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
|
||||
|
||||
SENTRY_LARAVEL_DSN=https://4bf0c732f5474b4518bbae14bd396bbd@o4508397333577728.ingest.de.sentry.io/4508397336789072
|
||||
SENTRY_TRACES_SAMPLE_RATE=1.0
|
||||
|
||||
REDIS_CLIENT=phpredis
|
||||
CACHE_STORE=redis
|
||||
REDIS_HOST=laravel-redis
|
||||
REDIS_PORT=6379
|
||||
REDIS_PASSWORD=null
|
||||
REDIS_CACHE_CONNECTION=cache
|
||||
REDIS_CACHE_DB=1
|
||||
|
||||
|
||||
|
||||
OCTANE_SERVER=roadrunner
|
||||
OCTANE_HTTPS=true
|
||||
|
||||
REVERB_APP_ID=246524
|
||||
REVERB_APP_KEY=w1fhion06ydoahnt5nol
|
||||
REVERB_APP_SECRET=eamu1je7hrq2gwgocqwc
|
||||
REVERB_HOST="${APP_URL}"
|
||||
REVERB_PORT=8080
|
||||
REVERB_SCHEME=https
|
||||
|
||||
VITE_REVERB_APP_KEY="${REVERB_APP_KEY}"
|
||||
VITE_REVERB_HOST="${REVERB_HOST}"
|
||||
VITE_REVERB_PORT="${REVERB_PORT}"
|
||||
VITE_REVERB_SCHEME="${REVERB_SCHEME}"
|
||||
|
||||
BROADCAST_CONNECTION=pusher
|
||||
PUSHER_APP_ID=1914025
|
||||
PUSHER_APP_KEY=c13c74b64f48f3cfad10
|
||||
PUSHER_APP_SECRET=dc41870ec7bd07bdea88
|
||||
PUSHER_APP_CLUSTER=eu
|
||||
PUSHER_SCHEME=https
|
||||
|
||||
|
||||
VITE_PUSHER_APP_KEY="${PUSHER_APP_KEY}"
|
||||
VITE_PUSHER_SCHEME="${PUSHER_SCHEME}"
|
||||
VITE_PUSHER_APP_CLUSTER="${PUSHER_APP_CLUSTER}"
|
8
src/.gitignore
vendored
Normal file
8
src/.gitignore
vendored
Normal file
@ -0,0 +1,8 @@
|
||||
/vendor/
|
||||
node_modules/
|
||||
public/build
|
||||
.idea
|
||||
/scripts/shipping_env
|
||||
|
||||
rr
|
||||
.rr.yaml
|
43
src/README.md
Normal file
43
src/README.md
Normal file
@ -0,0 +1,43 @@
|
||||
composer require laravel/breeze --dev
|
||||
php artisan breeze:install
|
||||
|
||||
npm install -D tailwindcss postcss autoprefixer flowbite flowbite-react
|
||||
|
||||
|
||||
php artisan db:seed --class=CarrierUserCredentialSeeder
|
||||
|
||||
|
||||
npm i @heroicons/react
|
||||
npm i @material-tailwind/react
|
||||
|
||||
npm install date-fns
|
||||
|
||||
|
||||
composer require spatie/laravel-permission
|
||||
|
||||
npm i --save @fortawesome/fontawesome-svg-core
|
||||
npm install --save @fortawesome/free-solid-svg-icons
|
||||
npm install --save @fortawesome/free-regular-svg-icons
|
||||
npm install --save @fortawesome/react-fontawesome
|
||||
|
||||
sudo apt-get install imagemagick
|
||||
sudo apt-get install php8.3-soap
|
||||
composer dump-autoload
|
||||
|
||||
npm i material-react-table @mui/material @mui/x-date-pickers @mui/icons-material @emotion/react @emotion/styled use-react-countries
|
||||
|
||||
npm install dayjs
|
||||
|
||||
|
||||
|
||||
CONVERT COMMAND REQUIREMENT
|
||||
|
||||
installed imagemagick
|
||||
modify policy
|
||||
|
||||
/etc/ImageMagick-6/policy.xml
|
||||
<policy domain="coder" rights="read|write" pattern="PDF" />
|
||||
|
||||
INSIDE REDIS CONTAINER
|
||||
|
||||
chown -R redis:redis /data
|
32
src/app/Events/LabelsProcessed.php
Normal file
32
src/app/Events/LabelsProcessed.php
Normal file
@ -0,0 +1,32 @@
|
||||
<?php
|
||||
|
||||
namespace App\Events;
|
||||
|
||||
use Illuminate\Broadcasting\Channel;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
use Illuminate\Broadcasting\InteractsWithSockets;
|
||||
use Illuminate\Contracts\Broadcasting\ShouldBroadcast;
|
||||
|
||||
class LabelsProcessed implements ShouldBroadcast
|
||||
{
|
||||
use InteractsWithSockets, SerializesModels;
|
||||
|
||||
public $userId;
|
||||
public $result;
|
||||
|
||||
public function __construct($userId, $result)
|
||||
{
|
||||
$this->userId = $userId;
|
||||
$this->result = $result;
|
||||
}
|
||||
|
||||
public function broadcastOn()
|
||||
{
|
||||
return new Channel('user.' . $this->userId);
|
||||
}
|
||||
|
||||
public function broadcastAs()
|
||||
{
|
||||
return 'label.processed';
|
||||
}
|
||||
}
|
16
src/app/Exceptions/ExternalApiException.php
Normal file
16
src/app/Exceptions/ExternalApiException.php
Normal file
@ -0,0 +1,16 @@
|
||||
<?php
|
||||
|
||||
namespace App\Exceptions;
|
||||
|
||||
use Exception;
|
||||
|
||||
class ExternalApiException extends Exception
|
||||
{
|
||||
protected $message;
|
||||
protected $code;
|
||||
|
||||
public function __construct($message = "External API Error", $reference=null, $code = 500)
|
||||
{
|
||||
parent::__construct($message, $code);
|
||||
}
|
||||
}
|
16
src/app/Exceptions/LabelPrintException.php
Normal file
16
src/app/Exceptions/LabelPrintException.php
Normal file
@ -0,0 +1,16 @@
|
||||
<?php
|
||||
|
||||
namespace App\Exceptions;
|
||||
|
||||
use Exception;
|
||||
|
||||
class LabelPrintException extends Exception
|
||||
{
|
||||
protected $message;
|
||||
protected $code;
|
||||
|
||||
public function __construct($message = "Label Print API Error", $reference=null, $code = 500)
|
||||
{
|
||||
parent::__construct($message, $code);
|
||||
}
|
||||
}
|
16
src/app/Exceptions/ShipmentException.php
Normal file
16
src/app/Exceptions/ShipmentException.php
Normal file
@ -0,0 +1,16 @@
|
||||
<?php
|
||||
|
||||
namespace App\Exceptions;
|
||||
|
||||
use Exception;
|
||||
|
||||
class ShipmentException extends Exception
|
||||
{
|
||||
protected $message;
|
||||
protected $code;
|
||||
|
||||
public function __construct($message = "Shipment Error", $reference=null, $code = 400)
|
||||
{
|
||||
parent::__construct($message, $code);
|
||||
}
|
||||
}
|
53
src/app/Factories/CarrierServiceFactory.php
Normal file
53
src/app/Factories/CarrierServiceFactory.php
Normal file
@ -0,0 +1,53 @@
|
||||
<?php
|
||||
|
||||
namespace App\Factories;
|
||||
|
||||
use App\Services\AllegroService;
|
||||
use App\Services\CPostService;
|
||||
use App\Services\DHLService;
|
||||
use App\Services\EurohermesService;
|
||||
use App\Services\GLSService;
|
||||
use App\Services\InpostService;
|
||||
use App\Services\KolosService;
|
||||
use App\Services\PacketaService;
|
||||
use App\Services\PPLService;
|
||||
use App\Services\QDLService;
|
||||
use App\Services\SpringService;
|
||||
use App\Services\UPSService;
|
||||
use GuzzleHttp\Client;
|
||||
|
||||
class CarrierServiceFactory
|
||||
{
|
||||
public static function create($carrier)
|
||||
{
|
||||
$client = new Client();
|
||||
|
||||
switch ($carrier->carrier_shortname) {
|
||||
case 'ups':
|
||||
return new UPSService($client);
|
||||
case 'dhl':
|
||||
return new DHLService($client);
|
||||
case "gls":
|
||||
return new GLSService($client);
|
||||
case "packeta":
|
||||
return new PacketaService($client);
|
||||
case "post":
|
||||
case "balikovna":
|
||||
return new CPostService($client);
|
||||
case "eurohermes":
|
||||
return new EurohermesService($client);
|
||||
case "inpost":
|
||||
return new InpostService($client);
|
||||
case "qdl":
|
||||
return new QDLService($client);
|
||||
case "ppl":
|
||||
return new PPLService($client);
|
||||
case "kolos":
|
||||
return new KolosService($client);
|
||||
case "spring":
|
||||
return new SpringService($client);
|
||||
case "allegro":
|
||||
return new AllegroService($client);
|
||||
}
|
||||
}
|
||||
}
|
117
src/app/Helpers/ProductHelper.php
Normal file
117
src/app/Helpers/ProductHelper.php
Normal file
@ -0,0 +1,117 @@
|
||||
<?php
|
||||
|
||||
|
||||
namespace App\Helpers;
|
||||
|
||||
|
||||
use Illuminate\Support\Facades\DB;
|
||||
|
||||
function isDuplicate($pID)
|
||||
{
|
||||
$duplicate_check = DB::table('vat_warehouse.mapping as m')
|
||||
->join('vat_warehouse.old_mapping2property as m2p', 'm.id', '=', 'm2p.old_mapping_id')
|
||||
->where('m2p.physical_item_property_id', 1)
|
||||
->where('m.id', $pID)
|
||||
->exists();
|
||||
|
||||
return $duplicate_check;
|
||||
}
|
||||
|
||||
|
||||
function isKaravan($pID)
|
||||
{
|
||||
$query_check = DB::table('vat_warehouse.mapping as m')
|
||||
->join('vat_warehouse.old_mapping2property as m2p', 'm.id', '=', 'm2p.old_mapping_id')
|
||||
->where('m2p.physical_item_property_id', 62)
|
||||
->where('m.id', $pID)
|
||||
->exists();
|
||||
|
||||
return $query_check;
|
||||
}
|
||||
|
||||
function isProgrammable($pID)
|
||||
{
|
||||
$query_check = DB::table('vat_warehouse.mapping as m')
|
||||
->join('vat_warehouse.old_mapping2property as m2p', 'm.id', '=', 'm2p.old_mapping_id')
|
||||
->where('m2p.physical_item_property_id', 58)
|
||||
->where('m.id', $pID)
|
||||
->exists();
|
||||
|
||||
return $query_check;
|
||||
}
|
||||
|
||||
function isEmptyGeneral($pID)
|
||||
{
|
||||
$query_check = DB::table('vat_warehouse.mapping as m')
|
||||
->join('vat_warehouse.old_mapping2property as m2p', 'm.id', '=', 'm2p.old_mapping_id')
|
||||
->where('m2p.physical_item_property_id', 3)
|
||||
->where('m.id', $pID)
|
||||
->exists();
|
||||
|
||||
return $query_check;
|
||||
}
|
||||
|
||||
function isGate($pID)
|
||||
{
|
||||
$query_check = DB::table('vat_warehouse.mapping as m')
|
||||
->join('vat_warehouse.old_mapping2property as m2p', 'm.id', '=', 'm2p.old_mapping_id')
|
||||
->where('m2p.physical_item_property_id', 20)
|
||||
->where('m.id', $pID)
|
||||
->exists();
|
||||
|
||||
return $query_check;
|
||||
}
|
||||
|
||||
function isGateProgramming($pID)
|
||||
{
|
||||
$query_check = DB::table('vat_warehouse.mapping as m')
|
||||
->join('vat_warehouse.old_mapping2property as m2p', 'm.id', '=', 'm2p.old_mapping_id')
|
||||
->where('m2p.physical_item_property_id', 32)
|
||||
->where('m.id', $pID)
|
||||
->exists();
|
||||
|
||||
return $query_check;
|
||||
}
|
||||
|
||||
function isRolo($pID)
|
||||
{
|
||||
$query_check = DB::table('vat_warehouse.mapping as m')
|
||||
->where('m.physical_item_type_id', 25)
|
||||
->where('m.id', $pID)
|
||||
->exists();
|
||||
|
||||
return $query_check;
|
||||
}
|
||||
|
||||
function isProductRepair($pID)
|
||||
{
|
||||
$query_check = DB::table('vat_warehouse.mapping as m')
|
||||
->where('m.physical_item_type_id', 48)
|
||||
->where('m.id', $pID)
|
||||
->exists();
|
||||
|
||||
return $query_check;
|
||||
}
|
||||
function getProductID($mID)
|
||||
{
|
||||
$externalID = DB::table('vat_warehouse.external_id as ei')
|
||||
->where('ei.mapping_id', $mID)
|
||||
->where('ei.eshop_id', 2)
|
||||
->value('external_id');
|
||||
|
||||
return $externalID ?? null;
|
||||
}
|
||||
|
||||
|
||||
|
||||
function isGeneralBrand($pID)
|
||||
{
|
||||
$query_check = DB::table('vat_warehouse.mapping as m')
|
||||
->whereIn('m.physical_item_type_id', [6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 24, 25, 64, 65, 66, 67, 69, 72, 76, 77, 78, 79])
|
||||
->where('m.id', $pID)
|
||||
->exists();
|
||||
|
||||
return $query_check;
|
||||
}
|
||||
|
||||
|
29
src/app/Helpers/ShipmentHelper.php
Normal file
29
src/app/Helpers/ShipmentHelper.php
Normal file
@ -0,0 +1,29 @@
|
||||
<?php
|
||||
|
||||
|
||||
namespace App\Helpers;
|
||||
|
||||
function splitStreetAddress($address)
|
||||
{
|
||||
|
||||
$complexPattern = '/^(?:(\d+)\s+(.*?)|(.*?)(\d+(?:[\/-]\d+)?\w*))$/';
|
||||
|
||||
if (preg_match($complexPattern, $address, $matches)) {
|
||||
if (!empty($matches[1])) {
|
||||
// Number is at the beginning
|
||||
return ['street' => trim($matches[2]), 'number' => trim($matches[1])];
|
||||
} else {
|
||||
// Number is at the end
|
||||
return ['street' => trim($matches[3]), 'number' => trim($matches[4])];
|
||||
}
|
||||
} else {
|
||||
// Try with the simpler pattern
|
||||
$simplePattern = '/^(.*?)(\d+.*)$/';
|
||||
if (preg_match($simplePattern, $address, $matches)) {
|
||||
return ['street' => trim($matches[1]), 'number' => trim($matches[2])];
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
|
||||
}
|
13
src/app/Http/Controllers/ActivityLogController.php
Normal file
13
src/app/Http/Controllers/ActivityLogController.php
Normal file
@ -0,0 +1,13 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use Inertia\Inertia;
|
||||
|
||||
class ActivityLogController extends Controller
|
||||
{
|
||||
public function index()
|
||||
{
|
||||
return Inertia::render('Admin/ActivityLog');
|
||||
}
|
||||
}
|
74
src/app/Http/Controllers/AdminController.php
Normal file
74
src/app/Http/Controllers/AdminController.php
Normal file
@ -0,0 +1,74 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Http\Requests\ProfileUpdateRequest;
|
||||
use Illuminate\Contracts\Auth\MustVerifyEmail;
|
||||
use Illuminate\Http\RedirectResponse;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Illuminate\Support\Facades\Redirect;
|
||||
use Inertia\Inertia;
|
||||
use Inertia\Response;
|
||||
|
||||
class AdminController extends Controller
|
||||
{
|
||||
/**
|
||||
* Display the user's profile form.
|
||||
*/
|
||||
public function edit(Request $request): Response
|
||||
{
|
||||
return Inertia::render('Profile/Edit', [
|
||||
'mustVerifyEmail' => $request->user() instanceof MustVerifyEmail,
|
||||
'status' => session('status'),
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the user's profile information.
|
||||
*/
|
||||
public function update(ProfileUpdateRequest $request): RedirectResponse
|
||||
{
|
||||
$request->user()->fill($request->validated());
|
||||
|
||||
if ($request->user()->isDirty('email')) {
|
||||
$request->user()->email_verified_at = null;
|
||||
}
|
||||
|
||||
$request->user()->save();
|
||||
|
||||
return Redirect::route('profile.edit');
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete the user's account.
|
||||
*/
|
||||
public function destroy(Request $request): RedirectResponse
|
||||
{
|
||||
$request->validate([
|
||||
'password' => ['required', 'current_password'],
|
||||
]);
|
||||
|
||||
$user = $request->user();
|
||||
|
||||
Auth::logout();
|
||||
|
||||
$user->delete();
|
||||
|
||||
$request->session()->invalidate();
|
||||
$request->session()->regenerateToken();
|
||||
|
||||
return Redirect::to('/');
|
||||
}
|
||||
|
||||
public function logout(Request $request): RedirectResponse
|
||||
{
|
||||
|
||||
Auth::guard("web")->logout();
|
||||
|
||||
$request->session()->invalidate();
|
||||
$request->session()->regenerateToken();
|
||||
|
||||
return Redirect::to('/');
|
||||
}
|
||||
}
|
@ -0,0 +1,65 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Auth;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Http\Requests\Auth\LoginRequest;
|
||||
use Illuminate\Http\RedirectResponse;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Illuminate\Support\Facades\Route;
|
||||
use Inertia\Inertia;
|
||||
use Inertia\Response;
|
||||
|
||||
class AuthenticatedSessionController extends Controller
|
||||
{
|
||||
/**
|
||||
* Display the login view.
|
||||
*/
|
||||
public function create(): Response
|
||||
{
|
||||
return Inertia::render('Auth/Login', [
|
||||
'canResetPassword' => Route::has('password.request'),
|
||||
'status' => session('status'),
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle an incoming authentication request.
|
||||
*/
|
||||
public function store(LoginRequest $request): RedirectResponse
|
||||
{
|
||||
|
||||
$request->authenticate();
|
||||
|
||||
$request->session()->regenerate();
|
||||
|
||||
$user = Auth::user();
|
||||
|
||||
$redirectRoute = match ($user->roles->pluck('name')->first()) {
|
||||
'admin' => 'expedice',
|
||||
'expedice' => 'expedice',
|
||||
'support' => 'expedice',
|
||||
'customer' => 'dashboard',
|
||||
default => 'dashboard'
|
||||
};
|
||||
|
||||
return redirect()->intended(route($redirectRoute, absolute: false));
|
||||
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Destroy an authenticated session.
|
||||
*/
|
||||
public function destroy(Request $request): RedirectResponse
|
||||
{
|
||||
Auth::guard('web')->logout();
|
||||
|
||||
$request->session()->invalidate();
|
||||
|
||||
$request->session()->regenerateToken();
|
||||
|
||||
return redirect('/');
|
||||
}
|
||||
}
|
@ -0,0 +1,41 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Auth;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use Illuminate\Http\RedirectResponse;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Illuminate\Validation\ValidationException;
|
||||
use Inertia\Inertia;
|
||||
use Inertia\Response;
|
||||
|
||||
class ConfirmablePasswordController extends Controller
|
||||
{
|
||||
/**
|
||||
* Show the confirm password view.
|
||||
*/
|
||||
public function show(): Response
|
||||
{
|
||||
return Inertia::render('Auth/ConfirmPassword');
|
||||
}
|
||||
|
||||
/**
|
||||
* Confirm the user's password.
|
||||
*/
|
||||
public function store(Request $request): RedirectResponse
|
||||
{
|
||||
if (! Auth::guard('web')->validate([
|
||||
'email' => $request->user()->email,
|
||||
'password' => $request->password,
|
||||
])) {
|
||||
throw ValidationException::withMessages([
|
||||
'password' => __('auth.password'),
|
||||
]);
|
||||
}
|
||||
|
||||
$request->session()->put('auth.password_confirmed_at', time());
|
||||
|
||||
return redirect()->intended(route('dashboard', absolute: false));
|
||||
}
|
||||
}
|
@ -0,0 +1,24 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Auth;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use Illuminate\Http\RedirectResponse;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class EmailVerificationNotificationController extends Controller
|
||||
{
|
||||
/**
|
||||
* Send a new email verification notification.
|
||||
*/
|
||||
public function store(Request $request): RedirectResponse
|
||||
{
|
||||
if ($request->user()->hasVerifiedEmail()) {
|
||||
return redirect()->intended(route('dashboard', absolute: false));
|
||||
}
|
||||
|
||||
$request->user()->sendEmailVerificationNotification();
|
||||
|
||||
return back()->with('status', 'verification-link-sent');
|
||||
}
|
||||
}
|
@ -0,0 +1,22 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Auth;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use Illuminate\Http\RedirectResponse;
|
||||
use Illuminate\Http\Request;
|
||||
use Inertia\Inertia;
|
||||
use Inertia\Response;
|
||||
|
||||
class EmailVerificationPromptController extends Controller
|
||||
{
|
||||
/**
|
||||
* Display the email verification prompt.
|
||||
*/
|
||||
public function __invoke(Request $request): RedirectResponse|Response
|
||||
{
|
||||
return $request->user()->hasVerifiedEmail()
|
||||
? redirect()->intended(route('dashboard', absolute: false))
|
||||
: Inertia::render('Auth/VerifyEmail', ['status' => session('status')]);
|
||||
}
|
||||
}
|
69
src/app/Http/Controllers/Auth/NewPasswordController.php
Normal file
69
src/app/Http/Controllers/Auth/NewPasswordController.php
Normal file
@ -0,0 +1,69 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Auth;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use Illuminate\Auth\Events\PasswordReset;
|
||||
use Illuminate\Http\RedirectResponse;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Hash;
|
||||
use Illuminate\Support\Facades\Password;
|
||||
use Illuminate\Support\Str;
|
||||
use Illuminate\Validation\Rules;
|
||||
use Illuminate\Validation\ValidationException;
|
||||
use Inertia\Inertia;
|
||||
use Inertia\Response;
|
||||
|
||||
class NewPasswordController extends Controller
|
||||
{
|
||||
/**
|
||||
* Display the password reset view.
|
||||
*/
|
||||
public function create(Request $request): Response
|
||||
{
|
||||
return Inertia::render('Auth/ResetPassword', [
|
||||
'email' => $request->email,
|
||||
'token' => $request->route('token'),
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle an incoming new password request.
|
||||
*
|
||||
* @throws \Illuminate\Validation\ValidationException
|
||||
*/
|
||||
public function store(Request $request): RedirectResponse
|
||||
{
|
||||
$request->validate([
|
||||
'token' => 'required',
|
||||
'email' => 'required|email',
|
||||
'password' => ['required', 'confirmed', Rules\Password::defaults()],
|
||||
]);
|
||||
|
||||
// Here we will attempt to reset the user's password. If it is successful we
|
||||
// will update the password on an actual user model and persist it to the
|
||||
// database. Otherwise we will parse the error and return the response.
|
||||
$status = Password::reset(
|
||||
$request->only('email', 'password', 'password_confirmation', 'token'),
|
||||
function ($user) use ($request) {
|
||||
$user->forceFill([
|
||||
'password' => Hash::make($request->password),
|
||||
'remember_token' => Str::random(60),
|
||||
])->save();
|
||||
|
||||
event(new PasswordReset($user));
|
||||
}
|
||||
);
|
||||
|
||||
// If the password was successfully reset, we will redirect the user back to
|
||||
// the application's home authenticated view. If there is an error we can
|
||||
// redirect them back to where they came from with their error message.
|
||||
if ($status == Password::PASSWORD_RESET) {
|
||||
return redirect()->route('login')->with('status', __($status));
|
||||
}
|
||||
|
||||
throw ValidationException::withMessages([
|
||||
'email' => [trans($status)],
|
||||
]);
|
||||
}
|
||||
}
|
29
src/app/Http/Controllers/Auth/PasswordController.php
Normal file
29
src/app/Http/Controllers/Auth/PasswordController.php
Normal file
@ -0,0 +1,29 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Auth;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use Illuminate\Http\RedirectResponse;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Hash;
|
||||
use Illuminate\Validation\Rules\Password;
|
||||
|
||||
class PasswordController extends Controller
|
||||
{
|
||||
/**
|
||||
* Update the user's password.
|
||||
*/
|
||||
public function update(Request $request): RedirectResponse
|
||||
{
|
||||
$validated = $request->validate([
|
||||
'current_password' => ['required', 'current_password'],
|
||||
'password' => ['required', Password::defaults(), 'confirmed'],
|
||||
]);
|
||||
|
||||
$request->user()->update([
|
||||
'password' => Hash::make($validated['password']),
|
||||
]);
|
||||
|
||||
return back();
|
||||
}
|
||||
}
|
@ -0,0 +1,51 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Auth;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use Illuminate\Http\RedirectResponse;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Password;
|
||||
use Illuminate\Validation\ValidationException;
|
||||
use Inertia\Inertia;
|
||||
use Inertia\Response;
|
||||
|
||||
class PasswordResetLinkController extends Controller
|
||||
{
|
||||
/**
|
||||
* Display the password reset link request view.
|
||||
*/
|
||||
public function create(): Response
|
||||
{
|
||||
return Inertia::render('Auth/ForgotPassword', [
|
||||
'status' => session('status'),
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle an incoming password reset link request.
|
||||
*
|
||||
* @throws \Illuminate\Validation\ValidationException
|
||||
*/
|
||||
public function store(Request $request): RedirectResponse
|
||||
{
|
||||
$request->validate([
|
||||
'email' => 'required|email',
|
||||
]);
|
||||
|
||||
// We will send the password reset link to this user. Once we have attempted
|
||||
// to send the link, we will examine the response then see the message we
|
||||
// need to show to the user. Finally, we'll send out a proper response.
|
||||
$status = Password::sendResetLink(
|
||||
$request->only('email')
|
||||
);
|
||||
|
||||
if ($status == Password::RESET_LINK_SENT) {
|
||||
return back()->with('status', __($status));
|
||||
}
|
||||
|
||||
throw ValidationException::withMessages([
|
||||
'email' => [trans($status)],
|
||||
]);
|
||||
}
|
||||
}
|
51
src/app/Http/Controllers/Auth/RegisteredUserController.php
Normal file
51
src/app/Http/Controllers/Auth/RegisteredUserController.php
Normal file
@ -0,0 +1,51 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Auth;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Models\User;
|
||||
use Illuminate\Auth\Events\Registered;
|
||||
use Illuminate\Http\RedirectResponse;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Illuminate\Support\Facades\Hash;
|
||||
use Illuminate\Validation\Rules;
|
||||
use Inertia\Inertia;
|
||||
use Inertia\Response;
|
||||
|
||||
class RegisteredUserController extends Controller
|
||||
{
|
||||
/**
|
||||
* Display the registration view.
|
||||
*/
|
||||
public function create(): Response
|
||||
{
|
||||
return Inertia::render('Auth/Register');
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle an incoming registration request.
|
||||
*
|
||||
* @throws \Illuminate\Validation\ValidationException
|
||||
*/
|
||||
public function store(Request $request): RedirectResponse
|
||||
{
|
||||
$request->validate([
|
||||
'name' => 'required|string|max:255',
|
||||
'email' => 'required|string|lowercase|email|max:255|unique:'.User::class,
|
||||
'password' => ['required', 'confirmed', Rules\Password::defaults()],
|
||||
]);
|
||||
|
||||
$user = User::create([
|
||||
'name' => $request->name,
|
||||
'email' => $request->email,
|
||||
'password' => Hash::make($request->password),
|
||||
]);
|
||||
|
||||
event(new Registered($user));
|
||||
|
||||
Auth::login($user);
|
||||
|
||||
return redirect(route('dashboard', absolute: false));
|
||||
}
|
||||
}
|
27
src/app/Http/Controllers/Auth/VerifyEmailController.php
Normal file
27
src/app/Http/Controllers/Auth/VerifyEmailController.php
Normal file
@ -0,0 +1,27 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Auth;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use Illuminate\Auth\Events\Verified;
|
||||
use Illuminate\Foundation\Auth\EmailVerificationRequest;
|
||||
use Illuminate\Http\RedirectResponse;
|
||||
|
||||
class VerifyEmailController extends Controller
|
||||
{
|
||||
/**
|
||||
* Mark the authenticated user's email address as verified.
|
||||
*/
|
||||
public function __invoke(EmailVerificationRequest $request): RedirectResponse
|
||||
{
|
||||
if ($request->user()->hasVerifiedEmail()) {
|
||||
return redirect()->intended(route('dashboard', absolute: false).'?verified=1');
|
||||
}
|
||||
|
||||
if ($request->user()->markEmailAsVerified()) {
|
||||
event(new Verified($request->user()));
|
||||
}
|
||||
|
||||
return redirect()->intended(route('dashboard', absolute: false).'?verified=1');
|
||||
}
|
||||
}
|
88
src/app/Http/Controllers/AuthController.php
Normal file
88
src/app/Http/Controllers/AuthController.php
Normal file
@ -0,0 +1,88 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use Illuminate\Http\Request;
|
||||
use App\Models\User;
|
||||
use Illuminate\Support\Facades\Hash;
|
||||
use Illuminate\Validation\ValidationException;
|
||||
|
||||
class AuthController extends Controller
|
||||
{
|
||||
public function register(Request $request)
|
||||
{
|
||||
$request->validate([
|
||||
'name' => 'required|string|max:255',
|
||||
'email' => 'required|string|email|max:255|unique:users',
|
||||
'password' => 'required|string|min:8',
|
||||
]);
|
||||
|
||||
$user = User::create([
|
||||
'name' => $request->name,
|
||||
'email' => $request->email,
|
||||
'password' => Hash::make($request->password),
|
||||
]);
|
||||
|
||||
return response()->json(['message' => 'User registered successfully'], 201);
|
||||
}
|
||||
|
||||
public function generateToken(Request $request)
|
||||
{
|
||||
$request->validate([
|
||||
'email' => 'required|string|email',
|
||||
'password' => 'required|string'
|
||||
]);
|
||||
|
||||
$user = User::where('email', $request->email)->first();
|
||||
|
||||
if (! $user || ! Hash::check($request->password, $user->password)) {
|
||||
throw ValidationException::withMessages([
|
||||
'email' => ['The provided credentials are incorrect.'],
|
||||
]);
|
||||
}
|
||||
|
||||
$token = $user->createToken('auth_token')->plainTextToken;
|
||||
|
||||
return response()->json(['access_token' => $token, 'token_type' => 'Bearer']);
|
||||
}
|
||||
public function addRole(Request $request)
|
||||
{
|
||||
$request->validate([
|
||||
'email' => 'required|string|email'
|
||||
]);
|
||||
|
||||
$user = User::where('email', $request->email)->first();
|
||||
|
||||
try {
|
||||
$user->assignRole($request->get('role'));
|
||||
|
||||
return response()->json(true);
|
||||
}
|
||||
catch (\Exception $e) {
|
||||
return response()->json(false);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public function generateTokenInternal(Request $request)
|
||||
{
|
||||
$request->validate([
|
||||
'email' => 'required|string|email',
|
||||
'password' => 'required|string',
|
||||
'permissions' => 'array'
|
||||
]);
|
||||
|
||||
$user = User::where('id', $request->user()->id)->first();
|
||||
|
||||
$token = $user->createToken('internal_token')->plainTextToken;
|
||||
|
||||
return response()->json(['access_token' => $token, 'token_type' => 'Bearer']);
|
||||
}
|
||||
|
||||
public function revokeToken(Request $request)
|
||||
{
|
||||
$request->user()->tokens()->delete();
|
||||
|
||||
return response()->json(['message' => 'Logged out successfully']);
|
||||
}
|
||||
}
|
227
src/app/Http/Controllers/AuthorisatonController.php
Normal file
227
src/app/Http/Controllers/AuthorisatonController.php
Normal file
@ -0,0 +1,227 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Exceptions\ExternalApiException;
|
||||
use App\Exceptions\LabelPrintException;
|
||||
|
||||
use App\Models\CarrierMaster;
|
||||
use App\Models\CarrierUserCredential;
|
||||
use GuzzleHttp\Client;
|
||||
use GuzzleHttp\Exception\RequestException;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
|
||||
|
||||
class AuthorisatonController extends Controller
|
||||
{
|
||||
/**
|
||||
* @throws LabelPrintException
|
||||
* @throws ExternalApiException
|
||||
*/
|
||||
public function authorizeUPS(Request $request)
|
||||
{
|
||||
|
||||
if ($request->has('code')) {
|
||||
$auth_code = $request->query('code');
|
||||
|
||||
$this->fetch_auth_token_ups($auth_code);
|
||||
}
|
||||
|
||||
return response()->json(['message' => 'Shipments are being processed.'], 202);
|
||||
|
||||
}
|
||||
|
||||
private function fetch_auth_token_ups($auth_code) {
|
||||
$client = new Client();
|
||||
|
||||
// if(config('app.debug')) {
|
||||
$client_id = "I8yNvkcaMZnBBezmIUDjYtazUnmWco5J1SGOHYrwpSWE2ZvS";
|
||||
$client_secret = "hjZgJ29U7kJsvsXAOCfzpWXBuipZUWI6GXtRAYjIUO8xkPE9YFQGTeqspoFD7tMC";
|
||||
$redirect_uri = "https://shipping.dalkove-ovladace.cz/api/authorizeUPS";
|
||||
$sandbox = false;
|
||||
// }
|
||||
// else {
|
||||
// $this->client_id = "QpRcn5vWFOre7mm62mkvf2uluOazmURvElQ5WpMiafNC7tZq";
|
||||
// $this->client_secret = "733ttSYyCJwl2WEH5Q5dVizRlmGkS4WCnC4UIEnYA0MIPzJG27DETSdLHodE2tii";
|
||||
// $this->apiURL = "https://onlinetools.ups.com/api/";
|
||||
// $this->redirect_uri = urlencode("https://remote-control-world.eu/admin2020/ups_api_auth.php");
|
||||
// }
|
||||
|
||||
try {
|
||||
$response = $client->post('https://onlinetools.ups.com/security/v1/oauth/token', [
|
||||
'form_params' => [
|
||||
'grant_type' => 'authorization_code',
|
||||
'code' => $auth_code,
|
||||
'redirect_uri' => $redirect_uri,
|
||||
],
|
||||
'headers' => [
|
||||
'Content-Type' => 'application/x-www-form-urlencoded',
|
||||
'Authorization' => 'Basic ' . base64_encode($client_id . ":" . $client_secret),
|
||||
],
|
||||
]);
|
||||
|
||||
$data = json_decode($response->getBody(), true);
|
||||
|
||||
if (isset($data['response']['errors'])) {
|
||||
echo "fetch_auth_token Error #:";
|
||||
var_dump($data['response']['errors']);
|
||||
return false;
|
||||
} else {
|
||||
$bearer_token = $data['access_token'];
|
||||
$refresh_token = $data['refresh_token'];
|
||||
|
||||
// Update or create the bearer token
|
||||
CarrierUserCredential::updateOrCreate(
|
||||
[
|
||||
'user_id' => 1,
|
||||
'carrier_master_id' => CarrierMaster::where('shortname', 'ups')->first()->id,
|
||||
'type' => 'access_token',
|
||||
'name' => 'UPS access token',
|
||||
"sandbox" => $sandbox
|
||||
],
|
||||
[
|
||||
'value' => $bearer_token,
|
||||
]
|
||||
);
|
||||
|
||||
// Update or create the refresh token
|
||||
CarrierUserCredential::updateOrCreate(
|
||||
[
|
||||
'user_id' => 1,
|
||||
'carrier_master_id' => CarrierMaster::where('shortname', 'ups')->first()->id,
|
||||
'type' => 'refresh_token',
|
||||
'name' => 'UPS refresh token',
|
||||
"sandbox" => $sandbox
|
||||
],
|
||||
[
|
||||
'value' => $refresh_token,
|
||||
]
|
||||
);
|
||||
|
||||
echo "success</br></br>";
|
||||
echo json_encode($data, JSON_PRETTY_PRINT);
|
||||
return true;
|
||||
}
|
||||
} catch (\Exception $e) {
|
||||
echo "fetch_auth_token Error #:" . $e->getMessage();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public function authorizeAllegro(Request $request)
|
||||
{
|
||||
|
||||
// https://allegro.pl/auth/oauth/authorize?response_type=code&client_id=3e2920423d1b483ab2183f5b6ae5e189&redirect_uri=http://localhost:8000/api/authorizeAllegro
|
||||
|
||||
if ($request->has('code')) {
|
||||
$code = $request->query('code');
|
||||
|
||||
$this->fetch_auth_token_allegro($code);
|
||||
|
||||
}
|
||||
|
||||
return response()->json(['message' => 'Shipments are being processed.'], 202);
|
||||
|
||||
}
|
||||
|
||||
|
||||
private function fetch_auth_token_allegro($code) {
|
||||
|
||||
$sandbox = false;
|
||||
|
||||
$token_object = $this->get_access_token_allegro($code);
|
||||
$access_token = $this->extend_token_allegro($token_object['refresh_token']);
|
||||
|
||||
CarrierUserCredential::updateOrCreate(
|
||||
[
|
||||
'user_id' => 1,
|
||||
'carrier_master_id' => CarrierMaster::where('shortname', 'allegro')->first()->id,
|
||||
'type' => 'access_token',
|
||||
'name' => 'Allegro access token',
|
||||
"sandbox" => $sandbox
|
||||
],
|
||||
[
|
||||
'value' => $access_token['access_token'],
|
||||
]
|
||||
);
|
||||
|
||||
// Update or create the refresh token
|
||||
CarrierUserCredential::updateOrCreate(
|
||||
[
|
||||
'user_id' => 1,
|
||||
'carrier_master_id' => CarrierMaster::where('shortname', 'allegro')->first()->id,
|
||||
'type' => 'refresh_token',
|
||||
'name' => 'Allegro refresh token',
|
||||
"sandbox" => $sandbox
|
||||
],
|
||||
[
|
||||
'value' => $access_token['refresh_token'],
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
|
||||
private function get_access_token_allegro($code)
|
||||
{
|
||||
$authUrl = "https://allegro.pl/auth/oauth/token";
|
||||
$redirect_uri = "http://localhost:8000/api/authorizeAllegro";
|
||||
|
||||
$api_credentials = CarrierUserCredential::getCredentialsByCarrier("allegro");
|
||||
|
||||
|
||||
$client = new Client();
|
||||
|
||||
try {
|
||||
$response = $client->post($authUrl, [
|
||||
'query' => [
|
||||
'grant_type' => 'authorization_code',
|
||||
'code' => $code,
|
||||
'redirect_uri' => $redirect_uri
|
||||
],
|
||||
'headers' => [
|
||||
'Authorization' => 'Basic ' . base64_encode($api_credentials->get('client_id')->value . ":" . $api_credentials->get('client_secret')->value),
|
||||
'Accept' => 'application/vnd.allegro.public.v1+json'
|
||||
]
|
||||
]);
|
||||
|
||||
$tokenObject = json_decode($response->getBody(), true);
|
||||
return $tokenObject;
|
||||
} catch (RequestException $e) {
|
||||
exit("Access token failure GET TOKEN: " . $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
private function extend_token_allegro($refresh_token)
|
||||
{
|
||||
$authUrl = "https://allegro.pl/auth/oauth/token";
|
||||
$redirect_uri = "http://localhost:8000/api/authorizeAllegro";
|
||||
|
||||
$api_credentials = CarrierUserCredential::getCredentialsByCarrier("allegro");
|
||||
|
||||
$client = new Client();
|
||||
|
||||
try {
|
||||
$response = $client->post($authUrl, [
|
||||
'query' => [
|
||||
'grant_type' => 'refresh_token',
|
||||
'refresh_token' => $refresh_token,
|
||||
'redirect_uri' => $redirect_uri
|
||||
],
|
||||
'headers' => [
|
||||
'Authorization' => 'Basic ' . base64_encode($api_credentials->get('client_id')->value . ":" . $api_credentials->get('client_secret')->value),
|
||||
'Accept' => 'application/vnd.allegro.public.v1+json'
|
||||
]
|
||||
]);
|
||||
|
||||
$tokenObject = json_decode($response->getBody(), true);
|
||||
return $tokenObject;
|
||||
} catch (RequestException $e) {
|
||||
exit("Access token failure EXTEND TOKEN: " . $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
165
src/app/Http/Controllers/BarcodeScanController.php
Normal file
165
src/app/Http/Controllers/BarcodeScanController.php
Normal file
@ -0,0 +1,165 @@
|
||||
<?php
|
||||
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Models\Carrier;
|
||||
use App\Models\CarrierBasePricing;
|
||||
use App\Models\CarrierBasePricing2Weight;
|
||||
use App\Models\CarrierMaster;
|
||||
use App\Models\ShipmentRequest;
|
||||
use App\Models\ShipmentStatus;
|
||||
use App\Models\ShipmentStatusHistory;
|
||||
use App\Models\User;
|
||||
use App\Models\UserCarrierPricing;
|
||||
use App\Models\UserCarrierPricing2Weight;
|
||||
use App\Models\UserDetails;
|
||||
use Carbon\Carbon;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use Inertia\Inertia;
|
||||
|
||||
class BarcodeScanController extends Controller
|
||||
{
|
||||
|
||||
public function handleScan(Request $request)
|
||||
{
|
||||
|
||||
$scan_action = $request->get('action');
|
||||
|
||||
$scanned_data = $request->get('scanned_data');
|
||||
|
||||
if (isset($scanned_data['action_overwrite'])) {
|
||||
$scan_action = $scanned_data['action_overwrite'];
|
||||
}
|
||||
|
||||
|
||||
if (is_null($scan_action) || is_null($scanned_data)) {
|
||||
return response()->json([
|
||||
'result' => false,
|
||||
'message' => "Invalid request: Input data is empty."
|
||||
]);
|
||||
}
|
||||
try {
|
||||
|
||||
if($scan_action === "carrier_daily_parcel_pickup")
|
||||
{
|
||||
$carrierMaster = CarrierMaster::find($scanned_data['carrier_master_id']);
|
||||
$carrierMaster->last_pickup = now();
|
||||
$carrierMaster->save();
|
||||
|
||||
return response()->json([
|
||||
'result' => true,
|
||||
'message' => "Carrier: " . $carrierMaster->display_name . " --- DAILY PARCEL PICKUP LOGGED"
|
||||
]);
|
||||
}
|
||||
|
||||
$shipmentRequest = ShipmentRequest::with(['items', 'currentShipmentStatus'])->findOrFail($scanned_data['shipment_request_id']);
|
||||
|
||||
if($shipmentRequest->currentShipmentStatus === ShipmentStatus::STATUS_CANCELED) {
|
||||
Log::channel('expedice')->info('Scanner - order canceled, alert given! ', [
|
||||
'shipment_request_id' => $shipmentRequest->id
|
||||
]);
|
||||
return response()->json([
|
||||
'result' => false,
|
||||
'message' => "STORNO - OBJEDNÁVKU NEEXPEDOVAT, ZRUSIT OBALKU, ZLIKVIDOVAT ŠTÍTEK!!!",
|
||||
'alert' => true
|
||||
]);
|
||||
}
|
||||
|
||||
switch ($scan_action) {
|
||||
|
||||
case "parcel_packing":
|
||||
{
|
||||
$packedStatus = $shipmentRequest->shipmentStatuses()
|
||||
->where('shipment_status_id', ShipmentStatus::STATUS_PACKED)
|
||||
->first(['created_at']);
|
||||
|
||||
if ($packedStatus) {
|
||||
Log::channel('expedice')->info('Scanner - DUPLICATE SCANNER ENTRY, alert given! ', [
|
||||
'shipment_request_id' => $shipmentRequest->id
|
||||
]);
|
||||
return response()->json([
|
||||
'result' => false,
|
||||
'message' => "ALREADY SCANNED at " . Carbon::parse($packedStatus->created_at)->format('Y-m-d H:i:s') . ", DUPLICATE ENTRY !!!",
|
||||
'alert' => true,
|
||||
]);
|
||||
}
|
||||
ShipmentStatusHistory::create(
|
||||
[
|
||||
'shipment_request_id' => $shipmentRequest->id,
|
||||
'shipment_status_id' => ShipmentStatus::STATUS_PACKED,
|
||||
]
|
||||
);
|
||||
|
||||
$shipmentRequest->createStorageRemovalEntries();
|
||||
|
||||
return response()->json([
|
||||
'result' => true,
|
||||
'message' => "Shipment request ID: " . $shipmentRequest->id . " --- STATUS PACKING"
|
||||
]);
|
||||
}
|
||||
case "remote_printing":
|
||||
{
|
||||
ShipmentStatusHistory::create(
|
||||
[
|
||||
'shipment_request_id' => $shipmentRequest->id,
|
||||
'shipment_status_id' => ShipmentStatus::STATUS_PRINTING,
|
||||
]
|
||||
);
|
||||
return response()->json([
|
||||
'result' => true,
|
||||
'message' => "Shipment request ID: " . $shipmentRequest->id . " --- STATUS PRINTING"
|
||||
]);
|
||||
}
|
||||
case "remote_printing_remains":
|
||||
{
|
||||
ShipmentStatusHistory::create(
|
||||
[
|
||||
'shipment_request_id' => $shipmentRequest->id,
|
||||
'shipment_status_id' => ShipmentStatus::STATUS_PRINTING_UNDONE,
|
||||
]
|
||||
);
|
||||
return response()->json([
|
||||
'result' => true,
|
||||
'message' => "Shipment request ID: " . $shipmentRequest->id . " --- STATUS AWAITING PRINTING, UNDONE"
|
||||
]);
|
||||
}
|
||||
case "expedice_delay":
|
||||
{
|
||||
ShipmentStatusHistory::create(
|
||||
[
|
||||
'shipment_request_id' => $shipmentRequest->id,
|
||||
'shipment_status_id' => ShipmentStatus::STATUS_EXPEDICE_DELAY,
|
||||
]
|
||||
);
|
||||
return response()->json([
|
||||
'result' => true,
|
||||
'message' => "Shipment request ID: " . $shipmentRequest->id . " --- STATUS EXPEDICE DELAY"
|
||||
]);
|
||||
}
|
||||
case "u_mateje":
|
||||
{
|
||||
ShipmentStatusHistory::create(
|
||||
[
|
||||
'shipment_request_id' => $shipmentRequest->id,
|
||||
'shipment_status_id' => ShipmentStatus::STATUS_EXPEDICE_SPECIAL_HANDLING,
|
||||
]
|
||||
);
|
||||
return response()->json([
|
||||
'result' => true,
|
||||
'message' => "Shipment request ID: " . $shipmentRequest->id . " --- STATUS EXPEDICE U MATEJE"
|
||||
]);
|
||||
}
|
||||
}
|
||||
} catch (\Exception $e) {
|
||||
return response()->json([
|
||||
'result' => false,
|
||||
'message' => $e->getMessage()
|
||||
]);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
}
|
235
src/app/Http/Controllers/BatchController.php
Normal file
235
src/app/Http/Controllers/BatchController.php
Normal file
@ -0,0 +1,235 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Exceptions\ExternalApiException;
|
||||
use App\Exceptions\LabelPrintException;
|
||||
use App\Exceptions\ShipmentException;
|
||||
use App\Models\Carrier;
|
||||
use App\Models\CarrierExtraFeeTypes;
|
||||
use App\Models\ShipmentErrorLog;
|
||||
use App\Models\ShipmentProcessedBaseFee;
|
||||
use App\Models\ShipmentProcessedExtraFee;
|
||||
use App\Models\ShipmentRequest;
|
||||
use App\Models\ShipmentRequestBatch;
|
||||
use App\Models\ShipmentRequestBatchItem;
|
||||
use App\Models\ShipmentRequestInvoice;
|
||||
use App\Models\ShipmentRequestItem;
|
||||
use App\Rules\Base64Pdf;
|
||||
use GuzzleHttp\Exception\GuzzleException;
|
||||
use Illuminate\Http\Request;
|
||||
use App\Models\Shipment;
|
||||
use App\Factories\CarrierServiceFactory;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
use Illuminate\Support\Facades\Validator;
|
||||
use Inertia\Inertia;
|
||||
|
||||
class BatchController extends Controller
|
||||
{
|
||||
public function batchList(Request $request)
|
||||
{
|
||||
$user = $request->user();
|
||||
|
||||
// Get pagination and sorting parameters
|
||||
$pageIndex = $request->get('pageIndex', 0);
|
||||
$pageSize = $request->get('pageSize', 25);
|
||||
$sorting = $request->get('sorting', [["id" => "id", "desc" => "true"]]);
|
||||
|
||||
$globalFilter = $request->get('globalFilter', '');
|
||||
$columnFilters = $request->get('columnFilters', []);
|
||||
|
||||
// Initialize the query with relationships
|
||||
$query = ShipmentRequestBatch::with([
|
||||
'items.shipmentRequest.errors',
|
||||
'user',
|
||||
'carrierMaster',
|
||||
'latestGeneratedFile'
|
||||
]);
|
||||
|
||||
// Apply filtering based on the user's role
|
||||
if ($user->hasRole('customer')) {
|
||||
// Filter by the user's own batches
|
||||
$query->where('user_id', $user->id);
|
||||
}
|
||||
|
||||
// Apply global filter
|
||||
if (!empty($globalFilter)) {
|
||||
$query->where(function ($query) use ($globalFilter) {
|
||||
if (isset($globalFilter['id'])) {
|
||||
$query->where('id', 'like', "%{$globalFilter['id']}%");
|
||||
}
|
||||
|
||||
if (isset($globalFilter['user'])) {
|
||||
$query->orWhereHas('user', function ($q) use ($globalFilter) {
|
||||
$q->where('name', 'like', "%{$globalFilter['user']}%");
|
||||
});
|
||||
}
|
||||
|
||||
if (isset($globalFilter['carrier'])) {
|
||||
$query->orWhereHas('carrierMaster', function ($q) use ($globalFilter) {
|
||||
$q->where('display_name', 'like', "%{$globalFilter['carrier']}%");
|
||||
});
|
||||
}
|
||||
|
||||
if (isset($globalFilter['created_at'])) {
|
||||
$query->where('created_at', 'like', "%{$globalFilter['created_at']}%");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Apply column filters
|
||||
if (!empty($columnFilters)) {
|
||||
foreach ($columnFilters as $key => $col_data) {
|
||||
$column = $col_data['id'];
|
||||
$value = $col_data['value'];
|
||||
if (!empty($value)) {
|
||||
switch ($column) {
|
||||
case 'id':
|
||||
$query->where('id', 'like', "%{$value}%");
|
||||
break;
|
||||
case 'user.name':
|
||||
$query->whereHas('user', function ($q) use ($value) {
|
||||
$q->whereIn('name', $value);
|
||||
});
|
||||
break;
|
||||
case 'carrier_master.display_name':
|
||||
$query->whereHas('carrierMaster', function ($q) use ($value) {
|
||||
foreach($value as $carrier_name) {
|
||||
$q->orWhere('display_name', 'like', "%{$carrier_name}%");
|
||||
}
|
||||
});
|
||||
break;
|
||||
case 'processed':
|
||||
$processedValue = ($value === 'true') ? 1 : 0;
|
||||
$query->where('processed', $processedValue);
|
||||
break;
|
||||
case 'created_at':
|
||||
$query->where('created_at', 'like', "%{$value}%");
|
||||
break;
|
||||
default:
|
||||
// Ignore unknown column filters
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Apply sorting
|
||||
if (!empty($sorting) && is_array($sorting)) {
|
||||
foreach ($sorting as $sort) {
|
||||
if (isset($sort['id']) && isset($sort['desc'])) {
|
||||
$direction = $sort['desc'] === 'true' ? 'desc' : 'asc';
|
||||
$query->orderBy($sort['id'], $direction);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Apply pagination
|
||||
$totalCount = $query->count();
|
||||
$batches = $query->skip($pageIndex * $pageSize)->take($pageSize)->get();
|
||||
|
||||
// Check if the request is an Inertia request
|
||||
if ($request->header('X-Inertia')) {
|
||||
return Inertia::render('Batches', [
|
||||
'batches' => $batches,
|
||||
'totalCount' => $totalCount,
|
||||
]);
|
||||
}
|
||||
|
||||
// For API requests, return a JSON response
|
||||
return response()->json([
|
||||
'batches' => $batches,
|
||||
'totalCount' => $totalCount,
|
||||
]);
|
||||
}
|
||||
|
||||
|
||||
public function batchProcessed(Request $request)
|
||||
{
|
||||
|
||||
Log::channel('expedice')->info("Batch marked as processed by - " . $request->user()->id, [
|
||||
'shipment_request_ids' => $request->get('batchIds')
|
||||
]);
|
||||
|
||||
try {
|
||||
$batchIds = $request->get('batchIds');
|
||||
|
||||
|
||||
$batchIds = ShipmentRequestBatch::whereIn('id', $batchIds)
|
||||
->where('processed', 0)
|
||||
->pluck('id')
|
||||
->toArray();
|
||||
|
||||
// Update only the records that were previously not processed
|
||||
if (!empty($batchIds)) {
|
||||
ShipmentRequestBatch::whereIn('id', $batchIds)
|
||||
->update(['processed' => 1]);
|
||||
|
||||
|
||||
// Fetch the relevant ShipmentRequests and their associated Shipments
|
||||
$shipmentRequests = ShipmentRequest::whereHas('batchItem', function ($query) use ($batchIds) {
|
||||
$query->whereIn('shipment_request_batch_id', $batchIds);
|
||||
})->with('shipment')->get();
|
||||
|
||||
// Iterate through each ShipmentRequest
|
||||
foreach ($shipmentRequests as $shipmentRequest) {
|
||||
$shipment = $shipmentRequest->shipment;
|
||||
|
||||
if (!$shipment) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$shipment_price_object = $shipmentRequest->carrier->userCarrierPricings()->whereHas('user', function ($query) use ($shipmentRequest) {
|
||||
$query->where('id', $shipmentRequest->user->id);
|
||||
})->first();
|
||||
|
||||
|
||||
if (!$shipment_price_object) {
|
||||
$shipment_price_object = $shipmentRequest->carrier->basePricing()->first();
|
||||
$user_carrier_grp_uplift = $shipmentRequest->user->userCarrierPricingGroup->carrierPricingGroup->base_ratio;
|
||||
$shipment_price = round($shipment_price_object->getPriceForWeight((float)$shipmentRequest->weight) + ($shipment_price_object->getPriceForWeight((float)$shipmentRequest->weight) * ($user_carrier_grp_uplift / 100)), 0);
|
||||
} else {
|
||||
$shipment_price = $shipment_price_object->getPriceForWeight((float)$shipmentRequest->weight);
|
||||
}
|
||||
|
||||
|
||||
// Prepare the base fees and extra fees
|
||||
$baseFeesData = [
|
||||
'shipment_id' => $shipment->id,
|
||||
'base_fee_type' => 'shipping',
|
||||
'base_fee_value' => $shipment_price,
|
||||
'base_fee_desc' => 'Shipping price',
|
||||
];
|
||||
|
||||
ShipmentProcessedBaseFee::create($baseFeesData);
|
||||
|
||||
if (isset($shipmentRequest->cod_value) && is_numeric($shipmentRequest->cod_value) && $shipmentRequest->cod_value >= 0) {
|
||||
|
||||
$cod_extra_fee_type = CarrierExtraFeeTypes::where('name', 'COD')->first();
|
||||
$extraFeesData = [
|
||||
'shipment_id' => $shipment->id,
|
||||
'extra_fee_type_id' => $cod_extra_fee_type->id,
|
||||
'extra_fee_type_value' => $shipment_price_object->extraFees()->where('carrier_extra_fee_type_id', $cod_extra_fee_type->id)->value('carrier_extra_fee_value'),
|
||||
'extra_fee_desc' => "COD fee",
|
||||
];
|
||||
|
||||
ShipmentProcessedExtraFee::create($extraFeesData);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
return response()->json(['status' => 'success', 'message' => 'Records updated successfully']);
|
||||
}
|
||||
else {
|
||||
return response()->json(['status' => 'success', 'message' => 'Batches already processed in past...']);
|
||||
}
|
||||
} catch (\Exception $e) {
|
||||
return response()->json(['status' => 'error', 'message' => 'Failed to update records', 'error' => $e->getMessage()]);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
21
src/app/Http/Controllers/CallbackController.php
Normal file
21
src/app/Http/Controllers/CallbackController.php
Normal file
@ -0,0 +1,21 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Jobs\ResendShipmentCallbacks;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
|
||||
class CallbackController extends Controller
|
||||
{
|
||||
/**
|
||||
* Trigger the resending of shipment callbacks.
|
||||
*
|
||||
* @return JsonResponse
|
||||
*/
|
||||
public function resendCallbacks(): JsonResponse
|
||||
{
|
||||
ResendShipmentCallbacks::dispatch();
|
||||
|
||||
return response()->json(['message' => 'Callback resending job dispatched successfully.']);
|
||||
}
|
||||
}
|
143
src/app/Http/Controllers/CarrierController.php
Normal file
143
src/app/Http/Controllers/CarrierController.php
Normal file
@ -0,0 +1,143 @@
|
||||
<?php
|
||||
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Exceptions\ExternalApiException;
|
||||
use App\Exceptions\LabelPrintException;
|
||||
use App\Exceptions\ShipmentException;
|
||||
use App\Models\Carrier;
|
||||
use App\Models\CarrierMaster;
|
||||
use App\Models\ShipmentErrorLog;
|
||||
use App\Models\ShipmentRequest;
|
||||
use App\Models\ShipmentRequestBatch;
|
||||
use App\Models\ShipmentRequestBatchItem;
|
||||
use App\Models\ShipmentRequestInvoice;
|
||||
use App\Models\ShipmentRequestItem;
|
||||
use App\Rules\Base64Pdf;
|
||||
use GuzzleHttp\Exception\GuzzleException;
|
||||
use Illuminate\Http\Request;
|
||||
use App\Models\Shipment;
|
||||
use App\Factories\CarrierServiceFactory;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
use Illuminate\Support\Facades\Validator;
|
||||
use Inertia\Inertia;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
|
||||
class CarrierController extends Controller {
|
||||
|
||||
|
||||
public function update(Request $request)
|
||||
{
|
||||
// Validate the request data
|
||||
$validatedData = $request->validate([
|
||||
'id' => 'required|integer',
|
||||
'internal' => 'required|string|max:255',
|
||||
'ext_id' => 'nullable|string|max:255',
|
||||
'carrier_name' => 'required|string|max:255',
|
||||
'carrier_shortname' => 'nullable|string|max:255',
|
||||
'pickup_points' => 'nullable|string|max:1000',
|
||||
'carrier_contract' => 'nullable|string|max:255',
|
||||
'customs_declarations' => 'nullable|string|max:255',
|
||||
'cod_allowed' => 'required|boolean',
|
||||
'cod_enabled' => 'required|boolean',
|
||||
'cod_price' => 'nullable|numeric|between:0,999999.99',
|
||||
'country' => 'nullable|string|max:255',
|
||||
'zone_id' => 'nullable|string|max:255',
|
||||
'currency' => 'nullable|string|max:3',
|
||||
'img' => 'nullable|string|max:255',
|
||||
'enabled_store' => 'required|boolean',
|
||||
'shipping_price' => 'nullable|numeric|between:0,999999.99',
|
||||
'free_shipping_enabled' => 'required|boolean',
|
||||
'delivery_time' => 'nullable|string|max:255',
|
||||
'display_order' => 'required|integer',
|
||||
'api_allowed' => 'required|boolean',
|
||||
]);
|
||||
|
||||
// Find the carrier by its ID
|
||||
$carrier = Carrier::findOrFail($validatedData['id']);
|
||||
|
||||
// Update the carrier with the validated data
|
||||
$carrier->update($validatedData);
|
||||
|
||||
// Return a success response
|
||||
return response()->json(['message' => 'Carrier updated successfully', 'carrier' => $carrier]);
|
||||
}
|
||||
|
||||
|
||||
public function carrierOptions(Request $request) {
|
||||
|
||||
if(sizeof($request->get('shipmentIds')) > 1) {
|
||||
return response()->json([
|
||||
'error' => "More than 1 shipment selected!"
|
||||
]);
|
||||
}
|
||||
$shipment = ShipmentRequest::where('id', $request->get('shipmentIds')[0])->first();
|
||||
|
||||
|
||||
$carriers = CarrierMaster::with(['carriers' => function ($query) use ($shipment) {
|
||||
$query->where(function ($q) use ($shipment) {
|
||||
$q->where('country', $shipment->delivery_address_country_iso)
|
||||
->where('enabled_store', true)
|
||||
->where('pickup_points', 0)
|
||||
->orWhereIn('carrier_shortname', ['ups', 'post', 'packeta']);
|
||||
});
|
||||
}])
|
||||
->where(function ($query) use ($shipment) {
|
||||
$query->where('carrier_enabled', true)
|
||||
->orWhereIn('shortname', ['ups', 'post', 'packeta']);
|
||||
})
|
||||
->get();
|
||||
|
||||
|
||||
$carriers = $carriers->filter(function ($carrierMaster) {
|
||||
return $carrierMaster->carriers->isNotEmpty(); // Only include CarrierMasters with at least 1 carrier
|
||||
});
|
||||
|
||||
return response()->json($carriers);
|
||||
|
||||
}
|
||||
public function carrierFullList(Request $request) {
|
||||
|
||||
$carriers = Carrier::with(['carrierMaster'])
|
||||
->get();
|
||||
|
||||
|
||||
// Check if the request is an Inertia request
|
||||
if ($request->header('X-Inertia')) {
|
||||
return Inertia::render('Admin/Carriers', [
|
||||
'carriers' => $carriers
|
||||
]);
|
||||
}
|
||||
|
||||
// For API requests, return a JSON response
|
||||
return response()->json($carriers);
|
||||
|
||||
}
|
||||
|
||||
public function carrierFilterList(Request $request) {
|
||||
|
||||
$carriers = Carrier::all()->pluck("carrier_name");
|
||||
|
||||
// For API requests, return a JSON response
|
||||
return response()->json($carriers);
|
||||
|
||||
}
|
||||
|
||||
public function carrierDetail($id) {
|
||||
|
||||
$carrier = Carrier::with(
|
||||
[
|
||||
'carrierMaster',
|
||||
'basePricing.pricingWeights',
|
||||
'basePricing.extraFees.extraFeeType'
|
||||
]
|
||||
)->find($id);
|
||||
|
||||
return Inertia::render('Admin/CarrierDetails', [
|
||||
'carrier' => $carrier,
|
||||
]);
|
||||
|
||||
}
|
||||
}
|
81
src/app/Http/Controllers/CarrierPricingController.php
Normal file
81
src/app/Http/Controllers/CarrierPricingController.php
Normal file
@ -0,0 +1,81 @@
|
||||
<?php
|
||||
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Models\Carrier;
|
||||
use App\Models\CarrierBasePricing;
|
||||
use App\Models\CarrierBasePricing2Weight;
|
||||
use App\Models\User;
|
||||
use App\Models\UserCarrierPricing;
|
||||
use App\Models\UserCarrierPricing2Weight;
|
||||
use App\Models\UserDetails;
|
||||
use Illuminate\Http\Request;
|
||||
use Inertia\Inertia;
|
||||
|
||||
class CarrierPricingController extends Controller {
|
||||
|
||||
public function updatePricing(Request $request, $id)
|
||||
{
|
||||
// Validate the request data
|
||||
$request->validate([
|
||||
'cod_price' => 'required|numeric|min:0', // example validation rule
|
||||
]);
|
||||
|
||||
// Find the record by ID and update the cod_price
|
||||
$record = CarrierBasePricing::findOrFail($id);
|
||||
$record->update([
|
||||
'cod_price' => $request->get('cod_price'),
|
||||
]);
|
||||
|
||||
// Return a success response
|
||||
return response()->json([
|
||||
'success' => true,
|
||||
]);
|
||||
}
|
||||
|
||||
public function updatePricingWeight(Request $request, $id)
|
||||
{
|
||||
|
||||
$record = CarrierBasePricing2Weight::findOrFail($id);
|
||||
$record->update([
|
||||
'weight_max' => $request->get('weight_max'),
|
||||
'shipping_price' => $request->get('shipping_price'),
|
||||
]);
|
||||
|
||||
// Return a success response
|
||||
return response()->json([
|
||||
'success' => true,
|
||||
]);
|
||||
}
|
||||
public function deletePricingWeight($id)
|
||||
{
|
||||
// Retrieve the record by its ID
|
||||
$record = CarrierBasePricing2Weight::findOrFail($id);
|
||||
|
||||
// Delete the record from the database
|
||||
$record->delete();
|
||||
|
||||
// Return a success response
|
||||
return response()->json([
|
||||
'success' => true,
|
||||
]);
|
||||
}
|
||||
|
||||
public function addPricingWeight(Request $request) {
|
||||
|
||||
CarrierBasePricing2Weight::create([
|
||||
'user_carrier_pricing_id' => $request->get('user_carrier_pricing_id'),
|
||||
'weight_max' => $request->get('weight_max'),
|
||||
'shipping_price' => $request->get('shipping_price'),
|
||||
]);
|
||||
|
||||
return response()->json([
|
||||
'success' => true,
|
||||
]);
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
117
src/app/Http/Controllers/ChatGPTController.php
Normal file
117
src/app/Http/Controllers/ChatGPTController.php
Normal file
@ -0,0 +1,117 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use GuzzleHttp\Client;
|
||||
use GuzzleHttp\Exception\RequestException;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class ChatGPTController
|
||||
{
|
||||
protected $client;
|
||||
protected $apiKey;
|
||||
protected $apiUrl;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->client = new Client();
|
||||
$this->apiKey = env('OPENAI_API_KEY');
|
||||
$this->apiUrl = 'https://api.openai.com/v1/chat/completions';
|
||||
}
|
||||
|
||||
public function validateAddress(Request $request)
|
||||
{
|
||||
$address = $request->input('address');
|
||||
$structured = $request->input('structured');
|
||||
if(isset($structured) && $structured) {
|
||||
$formattedAddress = "{$address['streetName']} {$address['streetNumber']} [{$address['additional_info']}], {$address['city']}, {$address['zip']}, {$address['stateISO']}, {$address['countryISO']}";
|
||||
}
|
||||
else {
|
||||
$formattedAddress = "{$address['address_query']}";
|
||||
}
|
||||
|
||||
$payload = [
|
||||
'model' => 'gpt-4o-mini',
|
||||
'messages' => [
|
||||
[
|
||||
'role' => 'system',
|
||||
'content' => "For given address provided try to normalize address. If the address is not correct, try to correct it, but do not make up the data - keep the input data as is, just correct it, do not change it completely.
|
||||
Return just the json, nothing else, no other texts or explanations as this response will be used for API call response. This is the format for the address:
|
||||
'streetName'
|
||||
'streetNumber',
|
||||
'city',
|
||||
'zip',
|
||||
'stateISO',
|
||||
'countryISO'
|
||||
|
||||
if the data is correct, add a 'address_valid': true to the json. If the address was slightly modified, but it is a valid address, use 'address_valid': true and 'address_modified': true. If the address is wrong or very incomplete, unable to be verified or completed,
|
||||
use 'address_valid': false.
|
||||
|
||||
If there are any notes or comments that you think should be made about the address, modification, correction, etc, you can put your comment or reasoning inside 'validation_comments' into the json response.
|
||||
|
||||
Some additional rules:
|
||||
only include state iso if the state is required generaly in postal and delivery APIs, such as US, UK, ES, CA, MX, otherwise you do not need to include it. State ISO is a subiso for the state or region, eq EN in GB (Englang in Great Britain), it is required there.
|
||||
if the state code is missing and not mandatory / crucial, do not include any mentions of it into the 'validation_comments'
|
||||
do not put \n into the response or any newline characters, which destroy the json after I read it from this API call
|
||||
|
||||
In case a crucial part of the address is missing, but the rest of the address is valid - this is not a valid address. Valid address is an address where a parcel could be delivered to - which
|
||||
is not possible without crucial parts such as street name, house number, zip code, etc.
|
||||
In the case a crucial part of the address is missing, add this json object to response:
|
||||
missing_parts: {
|
||||
'streetName': bool
|
||||
'streetNumber': bool,
|
||||
'city': bool,
|
||||
'zip': bool,
|
||||
'stateISO': bool,
|
||||
'countryISO: bool'
|
||||
}
|
||||
The bool should be a boolean value of the result - if the part is missing or significantly incorrect, the bool will be false, otherwise it will be true
|
||||
|
||||
The additional info field (in [] brackets) is optional and can contain information like suburb, block, flat, apt number, etc. Sometimes this is where customers submit their house number by accident
|
||||
take the value from there if you find it helps to determine the address, otherwise ignore it. If you chose to take the value from the additional_info field, please mention in in the 'validation_comments' field.
|
||||
|
||||
In case the address is in a different language using different alphabet, the main output should always be in latin.
|
||||
|
||||
If this happens, mention that the address was normalized to latin characters in the 'validation_comments' field.
|
||||
|
||||
"
|
||||
],
|
||||
[
|
||||
'role' => 'user',
|
||||
'content' => "Normalize the following address: {$formattedAddress}"
|
||||
]
|
||||
]
|
||||
];
|
||||
|
||||
return $this->callAPI($payload);
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
private function callAPI($payload) {
|
||||
|
||||
try {
|
||||
// Make the request
|
||||
$response = $this->client->post($this->apiUrl, [
|
||||
'headers' => [
|
||||
'Authorization' => "Bearer $this->apiKey",
|
||||
'Content-Type' => 'application/json',
|
||||
],
|
||||
'json' => $payload
|
||||
]);
|
||||
|
||||
// Parse the response
|
||||
$responseData = json_decode($response->getBody(), true);
|
||||
// return $responseData['choices'][0]['message']['content'];
|
||||
return response()->json(["response" => json_decode($responseData['choices'][0]['message']['content'])], 201);
|
||||
} catch (RequestException $e) {
|
||||
// Handle exceptions
|
||||
$error = $e->getResponse() ? $e->getResponse()->getBody()->getContents() : $e->getMessage();
|
||||
echo "Error: $error";
|
||||
return response()->json(["error" => $error], 201);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
8
src/app/Http/Controllers/Controller.php
Normal file
8
src/app/Http/Controllers/Controller.php
Normal file
@ -0,0 +1,8 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
abstract class Controller
|
||||
{
|
||||
//
|
||||
}
|
88
src/app/Http/Controllers/ErrorLogController.php
Normal file
88
src/app/Http/Controllers/ErrorLogController.php
Normal file
@ -0,0 +1,88 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Exceptions\ExternalApiException;
|
||||
use App\Exceptions\LabelPrintException;
|
||||
use App\Exceptions\ShipmentException;
|
||||
use App\Models\Carrier;
|
||||
use App\Models\ShipmentErrorLog;
|
||||
use App\Models\ShipmentRequest;
|
||||
use App\Models\ShipmentRequestBatch;
|
||||
use App\Models\ShipmentRequestBatchItem;
|
||||
use App\Models\ShipmentRequestInvoice;
|
||||
use App\Models\ShipmentRequestItem;
|
||||
use App\Rules\Base64Pdf;
|
||||
use GuzzleHttp\Exception\GuzzleException;
|
||||
use Illuminate\Http\Request;
|
||||
use App\Models\Shipment;
|
||||
use App\Factories\CarrierServiceFactory;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
use Illuminate\Support\Facades\Validator;
|
||||
use Inertia\Inertia;
|
||||
|
||||
class ErrorLogController extends Controller
|
||||
{
|
||||
public function errorList(Request $request)
|
||||
{
|
||||
|
||||
$errors = ShipmentErrorLog::with(['shipmentRequest.batch', 'shipmentRequest.carrier'])
|
||||
->where('resolved', '=', false)
|
||||
->get()
|
||||
->map(function ($error) {
|
||||
$batchId = optional($error->shipmentRequest->batch)->id;
|
||||
return [
|
||||
'error' => $error,
|
||||
'shipment_request' => $error->shipmentRequest,
|
||||
'batch_id' => $batchId,
|
||||
];
|
||||
});
|
||||
|
||||
// Check if the request is an Inertia request
|
||||
if ($request->header('X-Inertia')) {
|
||||
return Inertia::render('Errors', [
|
||||
'errors' => $errors,
|
||||
'batchIds' => $request->get('batchIds')
|
||||
]);
|
||||
}
|
||||
|
||||
// For API requests, return a JSON response
|
||||
return response()->json($errors);
|
||||
}
|
||||
|
||||
|
||||
public function markResolved(Request $request) {
|
||||
|
||||
if(is_array($request->get('id'))) {
|
||||
|
||||
foreach($request->get('id') as $errorId) {
|
||||
$error = ShipmentErrorLog::where('id', $errorId)->firstOrFail();
|
||||
|
||||
// Update the shipment with the validated data
|
||||
$error->update([
|
||||
'resolved' => true,
|
||||
]);
|
||||
|
||||
// Return a success response
|
||||
}
|
||||
return response()->json(['message' => 'Shipment updated successfully', 'error' => $request->get('id')]);
|
||||
|
||||
}
|
||||
else {
|
||||
$error = ShipmentErrorLog::where('id', $request->get('id'))->firstOrFail();
|
||||
|
||||
// Update the shipment with the validated data
|
||||
$error->update([
|
||||
'resolved' => true,
|
||||
]);
|
||||
|
||||
// Return a success response
|
||||
return response()->json(['message' => 'Shipment updated successfully', 'error' => $error->id]);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
690
src/app/Http/Controllers/ExpediceStickersController.php
Normal file
690
src/app/Http/Controllers/ExpediceStickersController.php
Normal file
@ -0,0 +1,690 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Models\Carrier;
|
||||
use App\Models\ShipmentRequest;
|
||||
use App\Models\ShipmentStatus;
|
||||
use Barryvdh\DomPDF\Facade\Pdf;
|
||||
use DateTime;
|
||||
use DateTimeZone;
|
||||
use Dompdf\Dompdf;
|
||||
use Dompdf\Options;
|
||||
use Illuminate\Database\Eloquent\Collection;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
use IntlDateFormatter;
|
||||
use Metzli\Encoder\Encoder;
|
||||
use Metzli\Renderer\PngRenderer;
|
||||
|
||||
use function App\Helpers\isDuplicate;
|
||||
use function App\Helpers\isGate;
|
||||
use function App\Helpers\isGateProgramming;
|
||||
use function App\Helpers\isKaravan;
|
||||
use function App\Helpers\isProgrammable;
|
||||
use function App\Helpers\isRolo;
|
||||
|
||||
class ExpediceStickersController extends Controller
|
||||
{
|
||||
|
||||
public function getStickers(Request $request)
|
||||
{
|
||||
if (isset($request->get('carrier')['shortname'])) {
|
||||
$carrierIds = Carrier::where('carrier_shortname', $request->get('carrier')['shortname'])
|
||||
->pluck('id')
|
||||
->toArray();
|
||||
|
||||
$shipmentRequests = ShipmentRequest::with('items')
|
||||
->whereIn('carrier_id', $carrierIds)
|
||||
->whereDoesntHave('shipment')
|
||||
->get();
|
||||
} elseif ($request->get('batchIds') !== null) {
|
||||
$batchIds = $request->get('batchIds');
|
||||
$shipmentRequests = ShipmentRequest::with('items')
|
||||
->whereHas('batch', function ($query) use ($batchIds) {
|
||||
$query->whereIn('id', $batchIds);
|
||||
})
|
||||
->get();
|
||||
} elseif ($request->get('shipmentIds') !== null) {
|
||||
$shipmentRequests = ShipmentRequest::whereIn('id', $request->get('shipmentIds'))->get();
|
||||
}
|
||||
|
||||
|
||||
return response($this->generatePDF($this->generateHTML($shipmentRequests)), 200)
|
||||
->header('Content-Type', 'application/pdf')
|
||||
->header('Content-Disposition', 'attachment; filename="expedice_stickers.pdf"');
|
||||
|
||||
}
|
||||
|
||||
|
||||
public static function getStickerForLabel(ShipmentRequest $shipmentRequest)
|
||||
{
|
||||
|
||||
return (new ExpediceStickersController)->generatePDF((new ExpediceStickersController)->generateHTML_new($shipmentRequest));
|
||||
|
||||
}
|
||||
|
||||
public function generatePDF(string $html_string)
|
||||
{
|
||||
// Initialize Dompdf with options
|
||||
$options = new Options();
|
||||
$options->set('isHtml5ParserEnabled', true);
|
||||
$options->set('isRemoteEnabled', true);
|
||||
$dompdf = new Dompdf($options);
|
||||
|
||||
// Load HTML content
|
||||
$dompdf->loadHtml($html_string);
|
||||
|
||||
// Set paper size and orientation
|
||||
$dompdf->setPaper(array(0, 0, 100, 595), 'landscape');
|
||||
|
||||
// Render the PDF
|
||||
$dompdf->render();
|
||||
return $dompdf->output();
|
||||
|
||||
}
|
||||
|
||||
public function generatePDFForLabel(string $html_string)
|
||||
{
|
||||
// Initialize Dompdf with options
|
||||
$options = new Options();
|
||||
$options->set('isHtml5ParserEnabled', true);
|
||||
$options->set('isRemoteEnabled', true);
|
||||
$dompdf = new Dompdf($options);
|
||||
|
||||
// Load HTML content
|
||||
$dompdf->loadHtml($html_string);
|
||||
|
||||
// Set paper size and orientation
|
||||
$dompdf->setPaper(array(0, 0, 100, 595), 'landscape');
|
||||
|
||||
// Render the PDF
|
||||
$dompdf->render();
|
||||
return $dompdf->output();
|
||||
|
||||
}
|
||||
|
||||
public function generateHTML(Collection $shipmentRequest): string
|
||||
{
|
||||
|
||||
$html_string = "<style>
|
||||
@page {margin: 0px; }
|
||||
body { margin: 0px; }
|
||||
.table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
}
|
||||
.table td {
|
||||
width: 69.5mm;
|
||||
height: 42.2mm;
|
||||
border: 1px solid black;
|
||||
text-align: center;
|
||||
vertical-align: top;
|
||||
}
|
||||
.sticker-content {
|
||||
margin: 2px;
|
||||
}
|
||||
.sticker img {
|
||||
width: 30px;
|
||||
height: auto;
|
||||
filter: grayscale(1);
|
||||
}
|
||||
.sticker-box {
|
||||
position: relative;
|
||||
}
|
||||
.sticker div {
|
||||
display: inline-block;
|
||||
vertical-align: top;
|
||||
}
|
||||
|
||||
tbody > tr:nth-child(1) > td {
|
||||
padding-top: 12px;
|
||||
height: calc(42.2mm - 31px);
|
||||
}
|
||||
|
||||
tbody > tr:nth-last-child(1) > td {
|
||||
padding-bottom: 12px;
|
||||
height: calc(42.2mm - 31px);
|
||||
}
|
||||
|
||||
tbody > tr > td:nth-child(3n+1) {
|
||||
padding-left: 18.43px;
|
||||
width: calc(70mm - 24.5px);
|
||||
}
|
||||
|
||||
/* Targets 3rd, 6th, 9th, etc. */
|
||||
tbody > tr > td:nth-child(3n+3) {
|
||||
padding-right: 18.43px;
|
||||
width: calc(70mm - 24.5px);
|
||||
}
|
||||
</style>";
|
||||
|
||||
$flags = [
|
||||
'is_duplicate' => false,
|
||||
'is_karavan' => false,
|
||||
'is_programmable' => false,
|
||||
'is_rolo' => false,
|
||||
'is_gate' => false,
|
||||
'is_gate_programming' => false,
|
||||
];
|
||||
|
||||
$counter = 0;
|
||||
//$orders_final = array(272721, 272690, 272691);
|
||||
$html_string .= '<table class="table"><tr>';
|
||||
|
||||
foreach ($shipmentRequest as $shipment) {
|
||||
|
||||
// if ($counter % 21 == 0) {
|
||||
// $html_string .= '<div class="page">';
|
||||
// }
|
||||
|
||||
if ($counter % 3 == 0 && $counter != 0) {
|
||||
$html_string .= '</tr><tr>';
|
||||
}
|
||||
|
||||
|
||||
foreach ($shipment->items as $item) {
|
||||
if (!empty($item->item_id_internal_warehouse)) {
|
||||
Log::info($item->item_id_internal_warehouse);
|
||||
// Use logical OR to set the flags
|
||||
$flags['is_duplicate'] |= isDuplicate($item->item_id_internal_warehouse);
|
||||
$flags['is_karavan'] |= isKaravan($item->item_id_internal_warehouse);
|
||||
$flags['is_programmable'] |= isProgrammable($item->item_id_internal_warehouse);
|
||||
$flags['is_rolo'] |= isRolo($item->item_id_internal_warehouse);
|
||||
$flags['is_gate'] |= isGate($item->item_id_internal_warehouse);
|
||||
$flags['is_gate_programming'] |= isGateProgramming($item->item_id_internal_warehouse);
|
||||
}
|
||||
}
|
||||
|
||||
Log::info(var_export($flags, true));
|
||||
|
||||
|
||||
$baterky = 0;
|
||||
$baterky_total = 0;
|
||||
|
||||
|
||||
if (getenv('IS_RCW') == 'true') {
|
||||
$defaultBattery = 86967;
|
||||
} else {
|
||||
$defaultBattery = 18043;
|
||||
}
|
||||
$has_batteries = false;
|
||||
$is_duplicate = false;
|
||||
$is_gate = false;
|
||||
$is_luki = false;
|
||||
$is_programmable = false;
|
||||
$is_karavan = false;
|
||||
$allegro_order = false;
|
||||
|
||||
foreach ($shipment->items as $item) {
|
||||
|
||||
if (($item->HScode == "85068080") || ($item->HScode == "8548102910")) {
|
||||
$has_batteries = true;
|
||||
$baterky += $item->quantity;
|
||||
$baterky_total += round($item->price);
|
||||
continue;
|
||||
}
|
||||
|
||||
$is_duplicate = $flags['is_duplicate'];
|
||||
$is_karavan = $flags['is_karavan'];
|
||||
$is_programmable = $flags['is_programmable'] && !$flags['is_rolo'];
|
||||
$is_gate = $flags['is_gate'];
|
||||
$is_luki = $flags['is_gate_programming'];
|
||||
|
||||
if ($is_gate) {
|
||||
$is_programmable = false;
|
||||
}
|
||||
if ($is_luki) {
|
||||
$is_gate = false;
|
||||
$is_programmable = false;
|
||||
}
|
||||
|
||||
|
||||
if ($shipment->carrier->carrier_shortname === "allegro") {
|
||||
$allegro_order = true;
|
||||
}
|
||||
}
|
||||
|
||||
$html_string .= '<td class="sticker">
|
||||
<div class="sticker-box">
|
||||
<div style="position: absolute;
|
||||
top: 0px;
|
||||
right: 2px;">
|
||||
<img src="/includes/templates/vat_responsive/images/shipping/small_icons/' . $shipment->carrier->img . '"
|
||||
style="width: 30px; height: auto; filter: grayscale(1);">
|
||||
</div>
|
||||
<div style="position: absolute;
|
||||
top: 0px;
|
||||
left: 2px;">';
|
||||
|
||||
if ($is_programmable) {
|
||||
$html_string .= '<div style="width: 23px; height: 23px; font-size: 23px; line-height: 23px;">
|
||||
N
|
||||
</div>';
|
||||
}
|
||||
if ($is_karavan) {
|
||||
|
||||
$html_string .= '<div style="width: 23px; height: 23px; font-size: 23px; line-height: 23px;">
|
||||
K
|
||||
</div>';
|
||||
|
||||
}
|
||||
if ($is_luki) {
|
||||
|
||||
$html_string .= '<div style="width: 37px; height: 23px; font-size: 20px; line-height: 22px;">
|
||||
G-P
|
||||
</div>';
|
||||
|
||||
|
||||
}
|
||||
if ($is_gate) {
|
||||
|
||||
$html_string .= '<div style="width: 23px; height: 23px; font-size: 23px; line-height: 23px;">
|
||||
G
|
||||
</div>';
|
||||
|
||||
|
||||
}
|
||||
if ($is_duplicate) {
|
||||
|
||||
$html_string .= '<div style="width: 23px; height: 23px; font-size: 23px; line-height: 23px;">
|
||||
D
|
||||
</div>';
|
||||
|
||||
}
|
||||
if ($has_batteries) {
|
||||
|
||||
$html_string .= '<div style="width: 50px; height: 23px; font-size: 16px; line-height: 25px; color: red;">
|
||||
B-' . $baterky . 'x
|
||||
</div>';
|
||||
|
||||
}
|
||||
if ($allegro_order) {
|
||||
|
||||
$html_string .= '<div style="width: 23px; height: 23px; font-size: 23px; line-height: 23px;">
|
||||
|A|
|
||||
</div>';
|
||||
|
||||
}
|
||||
|
||||
$today = new DateTime('now', new DateTimeZone('Europe/Prague'));
|
||||
|
||||
// Create an IntlDateFormatter
|
||||
$formatter = new IntlDateFormatter(
|
||||
'cs-CZ',
|
||||
IntlDateFormatter::FULL,
|
||||
IntlDateFormatter::FULL,
|
||||
new DateTimeZone('Europe/Prague'),
|
||||
IntlDateFormatter::GREGORIAN,
|
||||
'EEE' // Pattern to get the full name of the day
|
||||
);
|
||||
|
||||
// Format the timestamp
|
||||
$dayOfWeek = $formatter->format($today);
|
||||
|
||||
|
||||
$html_string .= '<div style="width: 20px; height: 20px; font-size: 10px; line-height: 20px;">
|
||||
' . $dayOfWeek . '
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<div style="font-size: 14px; font-weight: bold; padding-top: 20px;">
|
||||
<div>' . $shipment->shipment_reference . '</div>
|
||||
<div style="margin-left: 1.5em;">' . $shipment->delivery_address['name'] . '</div>
|
||||
</div>';
|
||||
|
||||
$overSize = false;
|
||||
|
||||
if ((sizeof($shipment->items) > 2 && !$has_batteries) || sizeof($shipment->items) > 3) {
|
||||
$overSize = true;
|
||||
}
|
||||
|
||||
if ($overSize) {
|
||||
?>
|
||||
<div
|
||||
style="font-size: 12px; margin-bottom: 0.3em; margin-top: 0.2em; width: 90%; margin-left: auto; margin-right: auto;">
|
||||
V objednavce je <?= sizeof($shipment->items) ?> produktu, je treba overit v systemu.
|
||||
</div>
|
||||
<?php
|
||||
} else {
|
||||
|
||||
foreach ($shipment->items as $item) {
|
||||
|
||||
if (($item->HScode == "85068080") || ($item->HScode == "8548102910")) {
|
||||
$has_batteries = true;
|
||||
$baterky += $item->quantity;
|
||||
$baterky_total += round($item->price);
|
||||
continue;
|
||||
}
|
||||
if ($item->quantity > 1) {
|
||||
$style = "color: #00bf00; font-weight: bold; font-size: 12px;";
|
||||
} else {
|
||||
$style = "";
|
||||
}
|
||||
|
||||
|
||||
$html_string .= '<div
|
||||
style="font-size: 11px; margin-bottom: 0.3em; margin-top: 0.2em; width: 90%; margin-left: auto; margin-right: auto;">
|
||||
<div style="' . $style . '">' . $item->quantity . "x - " . $item->name . '</div>
|
||||
</div>
|
||||
<div style="font-style: italic;border-bottom: 1px solid grey; padding-bottom: 0.2em;">
|
||||
<div>
|
||||
' . $item->model_number . '
|
||||
</div>
|
||||
<div style="margin-left: 1.5em; font-size: 14px;">
|
||||
Price: ' . round($item->price, 0) . " " . $shipment->currency . '
|
||||
</div>
|
||||
</div>';
|
||||
}
|
||||
}
|
||||
|
||||
$html_string .= '</div></td>';
|
||||
|
||||
$counter++;
|
||||
}
|
||||
|
||||
|
||||
while ($counter % 21 != 0) {
|
||||
if ($counter % 3 == 0) {
|
||||
$html_string .= "</tr><tr>";
|
||||
}
|
||||
$html_string .= '<td class="sticker" style="border: none;"></td>';
|
||||
|
||||
$counter++;
|
||||
|
||||
}
|
||||
|
||||
|
||||
$html_string .= '</tr></table>';
|
||||
|
||||
|
||||
return $html_string;
|
||||
}
|
||||
|
||||
public function generateHTML_new(ShipmentRequest $shipment): string
|
||||
{
|
||||
|
||||
$html_string = "<style>
|
||||
@page {margin: 0px; }
|
||||
body { margin: 0px; }
|
||||
|
||||
.sticker-content {
|
||||
margin: 2px;
|
||||
}
|
||||
.sticker img {
|
||||
width: 30px;
|
||||
height: auto;
|
||||
filter: grayscale(1);
|
||||
}
|
||||
.sticker-box {
|
||||
position: relative;
|
||||
}
|
||||
.sticker div {
|
||||
display: inline-block;
|
||||
vertical-align: top;
|
||||
}
|
||||
|
||||
table.sticker-table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
}
|
||||
|
||||
table.sticker-table td {
|
||||
vertical-align: top;
|
||||
padding: 2px;
|
||||
}
|
||||
|
||||
</style>";
|
||||
|
||||
$flags = [
|
||||
'is_duplicate' => false,
|
||||
'is_karavan' => false,
|
||||
'is_programmable' => false,
|
||||
'is_rolo' => false,
|
||||
'is_gate' => false,
|
||||
'is_gate_programming' => false,
|
||||
];
|
||||
|
||||
|
||||
foreach ($shipment->items as $item) {
|
||||
if (!empty($item->item_id_internal_warehouse)) {
|
||||
Log::info($item->item_id_internal_warehouse);
|
||||
// Use logical OR to set the flags
|
||||
$flags['is_duplicate'] |= isDuplicate($item->item_id_internal_warehouse);
|
||||
$flags['is_karavan'] |= isKaravan($item->item_id_internal_warehouse);
|
||||
$flags['is_programmable'] |= isProgrammable($item->item_id_internal_warehouse);
|
||||
$flags['is_rolo'] |= isRolo($item->item_id_internal_warehouse);
|
||||
$flags['is_gate'] |= isGate($item->item_id_internal_warehouse);
|
||||
$flags['is_gate_programming'] |= isGateProgramming($item->item_id_internal_warehouse);
|
||||
}
|
||||
}
|
||||
|
||||
if ($flags['is_duplicate'] && $flags['is_programmable'] && sizeof($shipment->items) > 1) {
|
||||
$flags['is_duplicate'] = false;
|
||||
$flags['is_programmable'] = false;
|
||||
}
|
||||
|
||||
Log::info(var_export($flags, true));
|
||||
|
||||
|
||||
$baterky = 0;
|
||||
$baterky_total = 0;
|
||||
|
||||
|
||||
if (getenv('IS_RCW') == 'true') {
|
||||
$defaultBattery = 86967;
|
||||
} else {
|
||||
$defaultBattery = 18043;
|
||||
}
|
||||
$has_batteries = false;
|
||||
$is_duplicate = false;
|
||||
$is_gate = false;
|
||||
$is_luki = false;
|
||||
$is_programmable = false;
|
||||
$is_karavan = false;
|
||||
$allegro_order = false;
|
||||
|
||||
foreach ($shipment->items as $item) {
|
||||
|
||||
if (($item->HScode == "85068080") || ($item->HScode == "8548102910")) {
|
||||
$has_batteries = true;
|
||||
$baterky += $item->quantity;
|
||||
$baterky_total += round($item->price);
|
||||
continue;
|
||||
}
|
||||
|
||||
$is_duplicate = $flags['is_duplicate'];
|
||||
$is_karavan = $flags['is_karavan'];
|
||||
$is_programmable = $flags['is_programmable'] && !$flags['is_rolo'];
|
||||
$is_gate = $flags['is_gate'];
|
||||
$is_luki = $flags['is_gate_programming'];
|
||||
|
||||
if ($is_gate) {
|
||||
$is_programmable = false;
|
||||
}
|
||||
if ($is_luki) {
|
||||
$is_gate = false;
|
||||
$is_programmable = false;
|
||||
}
|
||||
|
||||
|
||||
if ($shipment->carrier->carrier_shortname === "allegro") {
|
||||
$allegro_order = true;
|
||||
}
|
||||
}
|
||||
|
||||
$code = Encoder::encode(json_encode(
|
||||
[
|
||||
"shipment_request_id" => $shipment->id
|
||||
]
|
||||
));
|
||||
$renderer = new PngRenderer();
|
||||
|
||||
$aztec_code = $renderer->render($code);
|
||||
|
||||
$base64_aztec_img = base64_encode($aztec_code);
|
||||
$base64_aztec_src = "data:image/png;base64,{$base64_aztec_img}";
|
||||
|
||||
|
||||
$html_string .= '<div class="sticker">
|
||||
<div class="sticker-box">
|
||||
|
||||
|
||||
<table class="sticker-table">
|
||||
<tr>
|
||||
|
||||
<td>
|
||||
<img src="' . $base64_aztec_src . '" style="width: 80px; height: 80px; margin-left: 3px;">
|
||||
</td>
|
||||
<td style="font-size: 12px; font-weight: bold; padding-top: 19px;">
|
||||
<table>
|
||||
<tr><td>' . $shipment->shipment_reference . '</td></tr>
|
||||
<tr><td>' . $shipment->delivery_address['name'] . '</td></tr>
|
||||
</table>
|
||||
</td>';
|
||||
|
||||
$overSize = false;
|
||||
|
||||
if ((sizeof($shipment->items) > 2 && !$has_batteries) || sizeof($shipment->items) > 3) {
|
||||
$overSize = true;
|
||||
}
|
||||
|
||||
if ($overSize) {
|
||||
|
||||
$html_string .= '<td
|
||||
style="font-size: 12px; margin-bottom: 0.3em; margin-top: 0.2em; width: 90%; margin-left: auto; margin-right: auto;">
|
||||
V objednavce je ' . sizeof($shipment->items) . ' produktu, je treba overit v systemu.
|
||||
</td>';
|
||||
|
||||
} else {
|
||||
|
||||
$html_string .= "<td><table style='border-collapse: collapse; '>";
|
||||
|
||||
|
||||
foreach ($shipment->items as $item) {
|
||||
|
||||
if (($item->HScode == "85068080") || ($item->HScode == "8548102910")) {
|
||||
$has_batteries = true;
|
||||
$baterky += $item->quantity;
|
||||
$baterky_total += round($item->price);
|
||||
continue;
|
||||
}
|
||||
if ($item->quantity > 1) {
|
||||
$style = "color: #00bf00; font-weight: bold; font-size: 12px;";
|
||||
} else {
|
||||
$style = "";
|
||||
}
|
||||
|
||||
|
||||
$html_string .= '<tr
|
||||
style="font-size: 10px; margin-bottom: 0.3em; margin-top: 0.2em; width: 90%; margin-left: auto; margin-right: auto;">
|
||||
<td style="' . $style . '">' . $item->quantity . "x - " . $item->name . '</td>
|
||||
</tr>
|
||||
<tr style="font-style: italic;border-bottom: 1px solid grey; padding-bottom: 0.2em; font-size: 12px;">
|
||||
<td>
|
||||
' . $item->model_number . '
|
||||
</td>
|
||||
<td style="margin-left: 1.5em; font-size: 14px;">
|
||||
Price: ' . round($item->price, 0) . " " . $shipment->currency . '
|
||||
</td>
|
||||
</tr>';
|
||||
}
|
||||
|
||||
$html_string .= "</table></td>";
|
||||
}
|
||||
|
||||
$img_data = Storage::disk('public')->get("images/shipping/{$shipment->carrier->carrierMaster->img}");
|
||||
$base64_img = base64_encode($img_data);
|
||||
$base64_src = "data:image/png;base64,{$base64_img}";
|
||||
|
||||
$html_string .= '<td><table class="sticker-table">
|
||||
<tr>
|
||||
<td>
|
||||
|
||||
<img src="' . $base64_src . '" style="width: 25px; height: 25px; filter: grayscale(1);">
|
||||
|
||||
</td>
|
||||
<td>';
|
||||
|
||||
if ($is_programmable) {
|
||||
$html_string .= '<div style="width: 23px; height: 23px; font-size: 23px; line-height: 23px;">
|
||||
N
|
||||
</div>';
|
||||
}
|
||||
if ($is_karavan) {
|
||||
|
||||
$html_string .= '<div style="width: 23px; height: 23px; font-size: 23px; line-height: 23px;">
|
||||
K
|
||||
</div>';
|
||||
|
||||
}
|
||||
if ($is_luki) {
|
||||
|
||||
$html_string .= '<div style="width: 37px; height: 23px; font-size: 20px; line-height: 22px;">
|
||||
G-P
|
||||
</div>';
|
||||
|
||||
|
||||
}
|
||||
if ($is_gate) {
|
||||
|
||||
$html_string .= '<div style="width: 23px; height: 23px; font-size: 23px; line-height: 23px;">
|
||||
G
|
||||
</div>';
|
||||
|
||||
|
||||
}
|
||||
if ($is_duplicate) {
|
||||
|
||||
$html_string .= '<div style="width: 23px; height: 23px; font-size: 23px; line-height: 23px;">
|
||||
D
|
||||
</div>';
|
||||
|
||||
}
|
||||
if ($has_batteries) {
|
||||
|
||||
$html_string .= '<div style="width: 50px; height: 23px; font-size: 16px; line-height: 25px; color: red;">
|
||||
B-' . $baterky . 'x
|
||||
</div>';
|
||||
|
||||
}
|
||||
if ($allegro_order) {
|
||||
|
||||
$html_string .= '<div style="width: 23px; height: 23px; font-size: 23px; line-height: 23px;">
|
||||
|A|
|
||||
</div>';
|
||||
|
||||
}
|
||||
|
||||
$today = new DateTime('now', new DateTimeZone('Europe/Prague'));
|
||||
|
||||
// Create an IntlDateFormatter
|
||||
$formatter = new IntlDateFormatter(
|
||||
'cs-CZ',
|
||||
IntlDateFormatter::FULL,
|
||||
IntlDateFormatter::FULL,
|
||||
new DateTimeZone('Europe/Prague'),
|
||||
IntlDateFormatter::GREGORIAN,
|
||||
'EEE' // Pattern to get the full name of the day
|
||||
);
|
||||
|
||||
// Format the timestamp
|
||||
$dayOfWeek = $formatter->format($today);
|
||||
|
||||
|
||||
$html_string .= '<div style="width: 20px; height: 20px; font-size: 10px; line-height: 20px;">
|
||||
' . $dayOfWeek . '
|
||||
</div>
|
||||
|
||||
</td>
|
||||
</tr>
|
||||
</table></td>';
|
||||
|
||||
$html_string .= '</tr></table></div></div>';
|
||||
|
||||
return $html_string;
|
||||
}
|
||||
|
||||
}
|
427
src/app/Http/Controllers/ExpediceStocklistController.php
Normal file
427
src/app/Http/Controllers/ExpediceStocklistController.php
Normal file
@ -0,0 +1,427 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Factories\CarrierServiceFactory;
|
||||
use App\Models\Carrier;
|
||||
use App\Models\CarrierMaster;
|
||||
use App\Models\ShipmentRequest;
|
||||
use App\Models\ShipmentRequestBatch;
|
||||
use App\Models\ShipmentRequestBatchItem;
|
||||
use App\Models\ShipmentStatus;
|
||||
use GuzzleHttp\Client;
|
||||
use Illuminate\Database\Eloquent\Collection;
|
||||
use Illuminate\Http\Request;
|
||||
use Barryvdh\DomPDF\Facade\Pdf;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use function App\Helpers\isGate;
|
||||
use function App\Helpers\isEmptyGeneral;
|
||||
use function App\Helpers\isProductRepair;
|
||||
|
||||
class ExpediceStocklistController extends Controller
|
||||
{
|
||||
private $room_prio = [
|
||||
"FIK" => 100000,
|
||||
"PRD" => 200000,
|
||||
"DLN" => 300000,
|
||||
"XKB" => 400000,
|
||||
"KNCL" => 500000,
|
||||
"KCH" => 600000,
|
||||
"TSK" => 700000,
|
||||
"SKL" => 800000
|
||||
];
|
||||
|
||||
private function evalRank($array)
|
||||
{
|
||||
$rank = 0;
|
||||
try {
|
||||
if (array_key_exists($array[0], $this->room_prio)) {
|
||||
$rank += $this->room_prio[$array[0]];
|
||||
$rank += ord(substr($array[1], 0, 1)) * 1000;
|
||||
$rank += intval(substr($array[1], 1, 1)) * 100;
|
||||
$rank += intval($array[2]);
|
||||
} else {
|
||||
$rank = 1000000;
|
||||
}
|
||||
}
|
||||
catch (\Exception $e) {
|
||||
$rank = 1000000;
|
||||
}
|
||||
|
||||
|
||||
return $rank;
|
||||
}
|
||||
|
||||
private function assignRank(&$orders)
|
||||
{
|
||||
foreach ($orders as $order) {
|
||||
$rank = 1000000;
|
||||
|
||||
foreach($order->items as $item_shipment_request) {
|
||||
try {
|
||||
if($order->user->name === "dalkoveovladace") {
|
||||
$eshop_id = 1;
|
||||
}
|
||||
else {
|
||||
$eshop_id = 2;
|
||||
}
|
||||
$url = "https://sklad.dalkove-ovladace.cz/api/warehouse/".$eshop_id."/";
|
||||
|
||||
$json = file_get_contents($url . $item_shipment_request->item_id_external);
|
||||
$warehouse_obj = json_decode($json);
|
||||
|
||||
// $this->batteries_info = unserialize(stripslashes($query_data->fields['batteries_info']));
|
||||
|
||||
$warehouseData = [];
|
||||
foreach ($warehouse_obj as $item) {
|
||||
|
||||
//$this->warehouseData[$item->modelNumber] = $item->warehouseItems;
|
||||
foreach ($item->warehouseItems as $sklad_pozice) {
|
||||
if (isset($warehouseData[$item->modelNumber])) {
|
||||
$warehouseData[$item->modelNumber]['location'] .= " " . $sklad_pozice->location;
|
||||
|
||||
} else {
|
||||
$warehouseData[$item->modelNumber] = array(
|
||||
"location" => $sklad_pozice->location
|
||||
);
|
||||
}
|
||||
$arr = array(
|
||||
'location' => $sklad_pozice->location,
|
||||
'count' => $sklad_pozice->count,
|
||||
'physicalItemName' => $item->physicalItemName);
|
||||
$stockData[$item->modelNumber][] = $arr;
|
||||
}
|
||||
}
|
||||
$item_shipment_request->setAttribute('stockData', $stockData);
|
||||
unset($stockData);
|
||||
|
||||
$sklad = $warehouseData;
|
||||
|
||||
if (isset($order->items) && sizeof($order->items) > 1) {
|
||||
$rank = 1000000;
|
||||
} else {
|
||||
foreach ($sklad as $s) {
|
||||
$locations = explode(" ", $s['location']);
|
||||
foreach ($locations as &$location) {
|
||||
$location = explode('-', $location);
|
||||
$rank_new = $this->evalRank($location);
|
||||
if ($rank > $rank_new) {
|
||||
$rank = $rank_new;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
$order->rank = $rank;
|
||||
} catch (\Exception $e) {
|
||||
$order->rank = $rank;
|
||||
$item_shipment_request->setAttribute('stockData', []);
|
||||
unset($stockData);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private function osort(&$array, $properties)
|
||||
{
|
||||
if (is_string($properties)) {
|
||||
$properties = [$properties => SORT_ASC];
|
||||
}
|
||||
|
||||
$sorted = $array->sort(function ($a, $b) use ($properties) {
|
||||
foreach ($properties as $k => $v) {
|
||||
if (is_int($k)) {
|
||||
$k = $v;
|
||||
$v = SORT_ASC;
|
||||
}
|
||||
$collapse = function ($node, $props) {
|
||||
foreach ((array)$props as $prop) {
|
||||
$node = $node->$prop ?? null;
|
||||
}
|
||||
return $node;
|
||||
};
|
||||
|
||||
$aProp = $collapse($a, $k);
|
||||
$bProp = $collapse($b, $k);
|
||||
|
||||
if ($aProp != $bProp) {
|
||||
return ($v == SORT_ASC) ? strnatcasecmp($aProp, $bProp) : strnatcasecmp($bProp, $aProp);
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
});
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private function sortByStockPos(&$orders)
|
||||
{
|
||||
$this->assignRank($orders);
|
||||
$this->osort($orders, 'rank');
|
||||
}
|
||||
|
||||
public function sortOrders(Request $request)
|
||||
{
|
||||
|
||||
if (isset($request->get('carrier')['shortname'])){
|
||||
$carrierIds = Carrier::where('carrier_shortname', $request->get('carrier')['shortname'])
|
||||
->pluck('id')
|
||||
->toArray();
|
||||
|
||||
$shipmentRequests = ShipmentRequest::with('items')
|
||||
->whereIn('carrier_id', $carrierIds)
|
||||
->whereDoesntHave('shipment')
|
||||
->get();
|
||||
}
|
||||
elseif($request->get('batchIds') !== null) {
|
||||
$batchIds = $request->get('batchIds');
|
||||
$shipmentRequests = ShipmentRequest::with('items')
|
||||
->whereHas('batch', function ($query) use ($batchIds) {
|
||||
$query->whereIn('id', $batchIds);
|
||||
})
|
||||
->get();
|
||||
}
|
||||
elseif($request->get('shipmentIds') !== null) {
|
||||
$shipmentRequests = ShipmentRequest::whereIn('id', $request->get('shipmentIds'))->get();
|
||||
}
|
||||
|
||||
Log::channel('expedice')->info("Stocklist printed by - " . $request->user()->id, [
|
||||
'shipment_request_ids' =>$shipmentRequests->pluck('id')->toArray()
|
||||
]);
|
||||
|
||||
|
||||
$this->sortByStockPos($shipmentRequests);
|
||||
|
||||
|
||||
return response($this->generatePDF($this->generateHTML($shipmentRequests)), 200)
|
||||
->header('Content-Type', 'application/pdf')
|
||||
->header('Content-Disposition', 'attachment; filename="expedice_stocklist.pdf"');
|
||||
|
||||
|
||||
}
|
||||
|
||||
public function startExpedition(Request $request)
|
||||
{
|
||||
|
||||
$limit = $request->get('limit', 30);
|
||||
$userId = $request->get('userId');
|
||||
$carrier_shortname = $request->get('carrier')['carrier_shortname'];
|
||||
|
||||
if (isset($carrier_shortname)) {
|
||||
$carrierIds = Carrier::where('carrier_shortname', $carrier_shortname)
|
||||
->pluck('id')
|
||||
->toArray();
|
||||
|
||||
$shipmentRequests = ShipmentRequest::with('items')
|
||||
->whereIn('carrier_id', $carrierIds)
|
||||
->when($userId, function ($query, $userId) {
|
||||
// Only include this condition if $userId is not null
|
||||
return $query->where('user_id', $userId);
|
||||
})
|
||||
->whereDoesntHave('shipment')
|
||||
->where('send_again_flag', false)
|
||||
->whereDoesntHave('batch')
|
||||
->whereHas('currentShipmentStatus', function ($query) {
|
||||
$query->whereNotIn('shipment_status_id', [
|
||||
ShipmentStatus::STATUS_ADDRESS_INSUFFICIENT,
|
||||
ShipmentStatus::STATUS_OUT_OF_STOCK,
|
||||
ShipmentStatus::STATUS_IS_REPAIR,
|
||||
ShipmentStatus::STATUS_PROGRAMMING_MODEL_REQUIRED,
|
||||
ShipmentStatus::STATUS_PROGRAMMING_PROCESS,
|
||||
ShipmentStatus::STATUS_CANCELED,
|
||||
]);
|
||||
})
|
||||
->limit($limit)
|
||||
->get();
|
||||
} elseif ($request->get('batchIds') !== null) {
|
||||
$batchIds = $request->get('batchIds');
|
||||
$shipmentRequests = ShipmentRequest::with('items')
|
||||
->whereHas('batch', function ($query) use ($batchIds) {
|
||||
$query->whereIn('id', $batchIds);
|
||||
})
|
||||
->whereHas('currentShipmentStatus', function ($query) {
|
||||
$query->whereNotIn('shipment_status_id', [
|
||||
ShipmentStatus::STATUS_ADDRESS_INSUFFICIENT,
|
||||
ShipmentStatus::STATUS_OUT_OF_STOCK,
|
||||
ShipmentStatus::STATUS_IS_REPAIR,
|
||||
ShipmentStatus::STATUS_PROGRAMMING_MODEL_REQUIRED,
|
||||
ShipmentStatus::STATUS_PROGRAMMING_PROCESS,
|
||||
ShipmentStatus::STATUS_CANCELED,
|
||||
]);
|
||||
})
|
||||
->limit($limit)
|
||||
->get();
|
||||
}
|
||||
elseif($request->get('shipmentIds') !== null) {
|
||||
$shipmentRequests = ShipmentRequest::whereIn('id', $request->get('shipmentIds'))->get();
|
||||
}
|
||||
|
||||
Log::channel('expedice')->info("Expedice started by - " . $request->user()->id, [
|
||||
'shipment_request_ids' =>$shipmentRequests->pluck('id')->toArray()
|
||||
]);
|
||||
|
||||
|
||||
$this->sortByStockPos($shipmentRequests);
|
||||
|
||||
$batch = ShipmentRequestBatch::create([
|
||||
'user_id' => $request->user()->id,
|
||||
'carrier_master_id' => CarrierMaster::where('shortname', $carrier_shortname)->first()->id,
|
||||
]);
|
||||
|
||||
|
||||
foreach ($shipmentRequests as $shipment_request) {
|
||||
|
||||
|
||||
// Instantiate the correct carrier service dynamically for each shipment
|
||||
// $carrierService = CarrierServiceFactory::create($shipment_request->carrier);
|
||||
ShipmentRequestBatchItem::create([
|
||||
'shipment_request_batch_id' => $batch->id,
|
||||
'shipment_request_id' => $shipment_request->id,
|
||||
]);
|
||||
}
|
||||
|
||||
try {
|
||||
return response($this->generatePDF($this->generateHTML($shipmentRequests)), 200)
|
||||
->header('Content-Type', 'application/pdf')
|
||||
->header('Content-Disposition', 'attachment; filename="expedice_stocklist.pdf"');
|
||||
}
|
||||
catch(\Exception $e) {
|
||||
return response()->json(['errors' => [$e->getMessage()]], 400);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public function generateHTML(Collection $shipmentRequest): string
|
||||
{
|
||||
$html_string = "";
|
||||
$count_o = 0;
|
||||
foreach ($shipmentRequest as $shipment_request) {
|
||||
|
||||
$color = "";
|
||||
$hasGate = false;
|
||||
$hasEmptyGeneral = false;
|
||||
$isRepair = false;
|
||||
$isWelux = false;
|
||||
|
||||
|
||||
foreach ($shipment_request->items as $item) {
|
||||
if (!empty($item->item_id_internal_warehouse)) {
|
||||
|
||||
if(isGate($item->item_id_internal_warehouse)) {
|
||||
$hasGate = true;
|
||||
}
|
||||
if(isEmptyGeneral($item->item_id_internal_warehouse)) {
|
||||
$hasEmptyGeneral = true;
|
||||
}
|
||||
if(isProductRepair($item->item_id_internal_warehouse)) {
|
||||
$isRepair = true;
|
||||
}
|
||||
if(in_array($item->item_id_internal_warehouse, [16965, 19791])) {
|
||||
$isWelux = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$bg = $hasGate ? "background: #dbdbdb;" : "";
|
||||
$bg_general = $hasEmptyGeneral ? "background: #ffb2b2;" : "";
|
||||
$bg_repair = $isRepair ? "background: #9cd5f7;" : "";
|
||||
$bg_general = $isWelux ? "background: #ff8080;" : $bg_general;
|
||||
|
||||
$html_string .= '<div class="order" style="padding: 5px 10px 5px 10px; font-size: 12px; page-break-inside: avoid; ' . $bg_general . $bg_repair . '">';
|
||||
$html_string .= '<div style="font-size: 25px; font-weight: bold; ' . $color . ' ' . $bg . '">Obj.: ' . $shipment_request->shipment_reference . '</div>';
|
||||
|
||||
$count_p = 0;
|
||||
foreach ($shipment_request->items as $item) {
|
||||
|
||||
if ($count_o % 2) {
|
||||
$color = $count_p % 2 ? "background-color: #878787;" : "";
|
||||
} else {
|
||||
$color = $count_p % 2 ? "background-color: #ffaa0e;" : "";
|
||||
}
|
||||
|
||||
$html_string .= '
|
||||
<div class="product" style="width: 100%; margin-bottom: 10px; page-break-inside: avoid; ' . $color . '">
|
||||
<div style="width: 100%; page-break-inside: avoid;">
|
||||
<div style="width: 100%; page-break-inside: avoid;">
|
||||
<table style="width: 100%; border-collapse: collapse;">
|
||||
<tr>
|
||||
<td style="border: 1px solid black; padding: 5px 5px 5px 5px; text-align: center;">' . $item->quantity . 'x</td>
|
||||
<td style="border: 1px solid black; padding: 5px 5px 5px 5px; width: 65%; text-align: center;">' . $item->name . '</td>
|
||||
<td style="border: 1px solid black; padding: 5px 5px 5px 5px; width: 25%; text-align: center;">' . $item->model_number . '</td>
|
||||
<td style="border: 1px solid black; padding: 5px 5px 5px 5px; width: 10%; text-align: center;">' . $item->price . '</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
<div style="width: 100%; page-break-inside: avoid;">
|
||||
<div style="border: 1px solid black; padding: 10px 5px 10px 5px; width: 100%; overflow: hidden;">
|
||||
<table style="width: 100%; border-collapse: collapse;">
|
||||
<tr>';
|
||||
foreach ($item->stockData as $item_model => $stock_positions) {
|
||||
foreach ($stock_positions as $stock_data) {
|
||||
$html_string .= '
|
||||
<td style="padding: 5px 5px 5px 5px; width: 50%; vertical-align: top;">
|
||||
<div style="font-weight: bold;">';
|
||||
if (strpos($item_model, $stock_data['physicalItemName']) !== false) {
|
||||
$html_string .= $item_model;
|
||||
} else {
|
||||
$html_string .= $stock_data['physicalItemName'] . " (" . $item_model . ")";
|
||||
}
|
||||
$html_string .= '</div>
|
||||
<div>' . $stock_data['location'] . ", " . $stock_data['count'] . " ks" . '</div>
|
||||
</td>';
|
||||
}
|
||||
}
|
||||
$html_string .= '
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
<div style="padding: 5px 5px 5px 5px; border: none; width: 100%; page-break-inside: avoid;">
|
||||
<table style="width: 100%; border-collapse: collapse;">
|
||||
<tr>
|
||||
<td style="border: 1px solid black; padding: 3px 5px 3px 5px; text-align: center;">' . $item->item_note . '</td>
|
||||
<td style="border: 1px solid black; padding: 3px 5px 3px 5px; text-align: center;">' . $item->shipment->note . '</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>';
|
||||
|
||||
$count_p++;
|
||||
}
|
||||
|
||||
$html_string .= '</div>';
|
||||
|
||||
$count_o++;
|
||||
|
||||
}
|
||||
|
||||
return $html_string;
|
||||
}
|
||||
|
||||
|
||||
public function generatePDF(string $html_string)
|
||||
{
|
||||
try {
|
||||
$client = new Client();
|
||||
|
||||
$response = $client->post('http://pdf_manager:5000/html2pdf', [
|
||||
'headers' => [
|
||||
'Content-Type' => 'application/json',
|
||||
],
|
||||
'json' => [
|
||||
'html_content' => $html_string,
|
||||
],
|
||||
]);
|
||||
|
||||
$pdfContent = $response->getBody()->getContents();
|
||||
|
||||
return $pdfContent;
|
||||
|
||||
} catch (\Exception $e) {
|
||||
throw $e;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
708
src/app/Http/Controllers/LabelController.php
Normal file
708
src/app/Http/Controllers/LabelController.php
Normal file
@ -0,0 +1,708 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Exceptions\ExternalApiException;
|
||||
use App\Exceptions\LabelPrintException;
|
||||
use App\Exceptions\ShipmentException;
|
||||
use App\Jobs\ReprintLabelsJob;
|
||||
use App\Models\ActionLog;
|
||||
use App\Models\Carrier;
|
||||
use App\Models\CarrierExtraFeeTypes;
|
||||
use App\Models\CarrierMaster;
|
||||
use App\Models\Label;
|
||||
use App\Models\ShipmentErrorLog;
|
||||
use App\Models\ShipmentProcessedBaseFee;
|
||||
use App\Models\ShipmentProcessedExtraFee;
|
||||
use App\Models\ShipmentRequest;
|
||||
use App\Models\ShipmentRequestBatch;
|
||||
use App\Models\ShipmentRequestBatchItem;
|
||||
use App\Models\ShipmentRequestInvoice;
|
||||
use App\Models\ShipmentRequestItem;
|
||||
use App\Models\ShipmentStatus;
|
||||
use App\Models\ShipmentStatusHistory;
|
||||
use App\Models\UserDetails;
|
||||
use App\Rules\Base64Pdf;
|
||||
use App\Services\CallbackService;
|
||||
use GuzzleHttp\Client;
|
||||
use GuzzleHttp\Exception\GuzzleException;
|
||||
use Illuminate\Http\Request;
|
||||
use App\Models\Shipment;
|
||||
use App\Factories\CarrierServiceFactory;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
use Illuminate\Support\Facades\Validator;
|
||||
use Inertia\Inertia;
|
||||
|
||||
class LabelController extends Controller
|
||||
{
|
||||
|
||||
|
||||
protected CallbackService $callbackService;
|
||||
|
||||
public function __construct(CallbackService $callbackService)
|
||||
{
|
||||
$this->callbackService = $callbackService;
|
||||
}
|
||||
|
||||
private $validateArray = [
|
||||
'shipments' => 'required|array|min:1',
|
||||
'shipments.*.shipmentReference' => 'required|string|max:255',
|
||||
'shipments.*.note' => 'nullable|string|max:1000',
|
||||
'shipments.*.delivery_address' => 'required|array',
|
||||
'shipments.*.delivery_address.name' => 'required|string|max:255',
|
||||
'shipments.*.delivery_address.companyName' => 'nullable|string|max:255',
|
||||
'shipments.*.delivery_address.streetName' => 'required|string|max:255',
|
||||
'shipments.*.delivery_address.streetNumber' => 'required|string|max:10',
|
||||
'shipments.*.delivery_address.city' => 'required|string|max:255',
|
||||
'shipments.*.delivery_address.zip' => 'required|string|max:20',
|
||||
'shipments.*.delivery_address.stateISO' => 'nullable|string|max:10',
|
||||
'shipments.*.delivery_address.countryISO' => 'required|string|max:3',
|
||||
'shipments.*.pickupPointCode' => 'nullable|string|max:255',
|
||||
'shipments.*.pickupPointName' => 'nullable|string|max:255',
|
||||
'shipments.*.pickupPointAddress.streetName' => 'nullable|string|max:255',
|
||||
'shipments.*.pickupPointAddress.streetNumber' => 'nullable|string|max:10',
|
||||
'shipments.*.pickupPointAddress.addressLine2' => 'nullable|string|max:255',
|
||||
'shipments.*.pickupPointAddress.city' => 'nullable|string|max:255',
|
||||
'shipments.*.pickupPointAddress.zip' => 'nullable|string|max:255',
|
||||
'shipments.*.pickupPointAddress.stateISO' => 'nullable|string|max:255',
|
||||
'shipments.*.pickupPointAddress.countryISO' => 'nullable|string|max:255',
|
||||
'shipments.*.postnummer' => 'nullable|string|max:255',
|
||||
'shipments.*.contactEmail' => 'nullable|email|max:255',
|
||||
'shipments.*.contactTelephone' => 'nullable|string|max:20',
|
||||
'shipments.*.codValue' => 'nullable|numeric',
|
||||
'shipments.*.codVariableSymbol' => 'nullable|string|max:8',
|
||||
'shipments.*.shipmentValue' => 'required|numeric',
|
||||
'shipments.*.currency' => 'required|string|max:3',
|
||||
'shipments.*.weight' => 'required|numeric',
|
||||
'shipments.*.items' => 'required|array|min:1',
|
||||
'shipments.*.items.*.name' => 'required|string|max:255',
|
||||
'shipments.*.items.*.item_note' => 'nullable|string',
|
||||
'shipments.*.items.*.HSCode' => 'nullable|string|max:255',
|
||||
'shipments.*.items.*.model_number' => 'nullable|string|max:255',
|
||||
'shipments.*.items.*.price' => 'required|numeric',
|
||||
'shipments.*.items.*.quantity' => 'required|integer',
|
||||
'shipments.*.items.*.originCountry' => 'required|string|max:3',
|
||||
'shipments.*.items.*.weight' => 'required|numeric',
|
||||
'shipments.*.items.*.item_id_internal_warehouse' => 'nullable|numeric',
|
||||
'shipments.*.items.*.item_id_external' => 'nullable|numeric',
|
||||
'shipments.*.carrier_id' => 'required|string|max:255',
|
||||
'shipments.*.allegro_carrier_id' => 'nullable|string|max:255',
|
||||
'shipments.*.packageType' => 'required|in:ENVELOPE,PACKAGE',
|
||||
'shipments.*.sendAgainFlag' => 'nullable|boolean',
|
||||
'shipments.*.allegroSmart' => 'nullable|boolean',
|
||||
'shipments.*.parcelSize' => 'required|array',
|
||||
'shipments.*.parcelSize.width' => 'required|string|max:255',
|
||||
'shipments.*.parcelSize.length' => 'required|string|max:255',
|
||||
'shipments.*.parcelSize.height' => 'required|string|max:255',
|
||||
'shipments.*.invoice_data' => 'nullable|array',
|
||||
'shipments.*.invoice_data.invoiceNumber' => 'nullable|string|max:255',
|
||||
'shipments.*.invoice_data.date' => 'nullable|string',
|
||||
'shipments.*.invoice_data.currency' => 'nullable|string|max:255',
|
||||
'shipments.*.invoice_data.address.name' => 'nullable|string|max:255',
|
||||
'shipments.*.invoice_data.address.companyName' => 'nullable|string|max:255',
|
||||
'shipments.*.invoice_data.address.companyVatNumber' => 'nullable|string|max:255',
|
||||
'shipments.*.invoice_data.address.streetName' => 'nullable|string|max:255',
|
||||
'shipments.*.invoice_data.address.streetNumber' => 'nullable|string|max:10',
|
||||
'shipments.*.invoice_data.address.city' => 'nullable|string|max:255',
|
||||
'shipments.*.invoice_data.address.zip' => 'nullable|string|max:20',
|
||||
'shipments.*.invoice_data.address.stateISO' => 'nullable|string|max:10',
|
||||
'shipments.*.invoice_data.address.countryISO' => 'nullable|string|max:3'
|
||||
|
||||
];
|
||||
|
||||
//TODO: custom validation
|
||||
// 1. UPS pickup data required for APP only users
|
||||
// 2. UPS invoice pdf required for UPS nonEU
|
||||
/**
|
||||
* @throws LabelPrintException
|
||||
* @throws ExternalApiException
|
||||
*/
|
||||
public function createLabels(Request $request)
|
||||
{
|
||||
// Validate the request for an array of shipments
|
||||
$base64PdfRule = new Base64Pdf();
|
||||
$this->validateArray['shipments.*.invoice_data.invoice_file'] = ['nullable', $base64PdfRule];
|
||||
|
||||
$validator = Validator::make($request->all(), $this->validateArray);
|
||||
|
||||
if ($validator->fails()) {
|
||||
return response()->json([
|
||||
'success' => false,
|
||||
'errors' => $validator->errors()
|
||||
], 422);
|
||||
}
|
||||
|
||||
// Proceed with the validated data
|
||||
$validatedData = $validator->validated();
|
||||
|
||||
$labels = [];
|
||||
foreach ($validatedData['shipments'] as $shipmentData) {
|
||||
|
||||
|
||||
// Create shipment request
|
||||
$shipment_request = ShipmentRequest::create([
|
||||
'shipment_reference' => $shipmentData['shipmentReference'],
|
||||
'user_id' => $request->user()->id,
|
||||
'note' => $shipmentData['note'] ?? null,
|
||||
'delivery_address_name' => $shipmentData['delivery_address']['name'],
|
||||
'delivery_address_company_name' => $shipmentData['delivery_address']['companyName'] ?? null,
|
||||
'delivery_address_street_name' => $shipmentData['delivery_address']['streetName'],
|
||||
'delivery_address_street_number' => $shipmentData['delivery_address']['streetNumber'],
|
||||
'delivery_address_address_line_2' => $shipmentData['delivery_address']['addressLine2'] ?? null,
|
||||
'delivery_address_city' => $shipmentData['delivery_address']['city'],
|
||||
'delivery_address_zip' => $shipmentData['delivery_address']['zip'],
|
||||
'delivery_address_state_iso' => $shipmentData['delivery_address']['stateISO'] ?? null,
|
||||
'delivery_address_country_iso' => $shipmentData['delivery_address']['countryISO'],
|
||||
'pickup_point_code' => $shipmentData['pickupPointCode'] ?? null,
|
||||
'pickup_point_address_street_name' => $shipmentData['pickupPointAddress']['streetName'] ?? null,
|
||||
'pickup_point_address_street_number' => $shipmentData['pickupPointAddress']['streetNumber'] ?? null,
|
||||
'pickup_point_address_city' => $shipmentData['pickupPointAddress']['city'] ?? null,
|
||||
'pickup_point_address_zip' => $shipmentData['pickupPointAddress']['zip'] ?? null,
|
||||
'pickup_point_address_state_iso' => $shipmentData['pickupPointAddress']['stateISO'] ?? null,
|
||||
'pickup_point_address_country_iso' => $shipmentData['pickupPointAddress']['countryISO'] ?? null,
|
||||
'contact_email' => $shipmentData['contactEmail'] ?? null,
|
||||
'contact_telephone' => $shipmentData['contactTelephone'] ?? null,
|
||||
'cod_value' => $shipmentData['codValue'] ?? null,
|
||||
'cod_variable_symbol' => $shipmentData['codVariableSymbol'] ?? null,
|
||||
'shipment_value' => $shipmentData['shipmentValue'],
|
||||
'currency' => $shipmentData['currency'],
|
||||
'weight' => $shipmentData['weight'],
|
||||
'width' => $shipmentData['parcelSize']['width'],
|
||||
'length' => $shipmentData['parcelSize']['length'],
|
||||
'height' => $shipmentData['parcelSize']['height'],
|
||||
'carrier_id' => $shipmentData['carrier_id'],
|
||||
'allegro_carrier_id' => $shipmentData['allegro_carrier_id'] ?? null,
|
||||
'package_type' => $shipmentData['packageType'],
|
||||
'send_again_flag' => $shipmentData['sendAgainFlag'] ?? false,
|
||||
'allegro_smart' => $shipmentData['allegroSmart'] ?? false,
|
||||
'postnummer' => $shipmentData['postnummer'] ?? null,
|
||||
]);
|
||||
|
||||
// Create shipment items
|
||||
foreach ($shipmentData['items'] as $itemData) {
|
||||
ShipmentRequestItem::create([
|
||||
'shipment_request_id' => $shipment_request->id,
|
||||
'name' => $itemData['name'],
|
||||
'item_note' => $itemData['item_note'] ?? null,
|
||||
'model_number' => $itemData['model_number'] ?? null,
|
||||
'HSCode' => $itemData['HSCode'] ?? null,
|
||||
'price' => $itemData['price'],
|
||||
'quantity' => $itemData['quantity'],
|
||||
'originCountry' => $itemData['originCountry'],
|
||||
'weight' => $itemData['weight'],
|
||||
'item_id_internal_warehouse' => $itemData['item_id_internal_warehouse'] ?? null,
|
||||
'item_id_external' => $itemData['item_id_external'] ?? null,
|
||||
]);
|
||||
}
|
||||
if (!empty($shipmentData['invoice_data'])) {
|
||||
ShipmentRequestInvoice::create([
|
||||
'shipment_request_id' => $shipment_request->id,
|
||||
'invoice_number' => $shipmentData['invoice_data']['invoiceNumber'],
|
||||
'invoice_date' => $shipmentData['invoice_data']['date'],
|
||||
'invoice_currency' => $shipmentData['invoice_data']['currency'],
|
||||
'invoice_address_name' => $shipmentData['invoice_data']['address']['name'],
|
||||
'invoice_address_company_name' => $shipmentData['invoice_data']['address']['companyName'] ?? null,
|
||||
'invoice_address_company_vat_number' => $shipmentData['invoice_data']['address']['companyVatNumber'] ?? null,
|
||||
'invoice_address_street_name' => $shipmentData['invoice_data']['address']['streetName'],
|
||||
'invoice_address_street_number' => $shipmentData['invoice_data']['address']['streetNumber'],
|
||||
'invoice_address_city' => $shipmentData['invoice_data']['address']['city'],
|
||||
'invoice_address_zip' => $shipmentData['invoice_data']['address']['zip'],
|
||||
'invoice_address_state_iso' => $shipmentData['invoice_data']['address']['stateISO'] ?? null,
|
||||
'invoice_address_country_iso' => $shipmentData['invoice_data']['address']['countryISO'],
|
||||
'invoice_raw_data' => $shipmentData['invoice_data']['invoice_file'],
|
||||
]);
|
||||
}
|
||||
|
||||
// Instantiate the correct carrier service dynamically for each shipment
|
||||
$carrierService = CarrierServiceFactory::create($shipment_request->carrier);
|
||||
|
||||
try {
|
||||
$label = $carrierService->genShipLabel($shipment_request);
|
||||
if ($label) {
|
||||
$labels[] = $label;
|
||||
}
|
||||
} catch (ExternalApiException|LabelPrintException|ShipmentException|GuzzleException|\Exception $e) {
|
||||
$errors[] = [
|
||||
'shipmentReference' => $shipmentData['shipmentReference'],
|
||||
'error' => $e->getMessage(),
|
||||
];
|
||||
|
||||
ShipmentErrorLog::create(
|
||||
array(
|
||||
"shipment_request_id" => $shipment_request->id,
|
||||
"error_message" => $e->getMessage()
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if (!empty($errors)) {
|
||||
return response()->json(['errors' => $errors], 400);
|
||||
}
|
||||
|
||||
// Placeholder function to merge labels into a file
|
||||
$file = $this->mergeLabelsIntoFile($labels);
|
||||
|
||||
// return response()->json(['file' => $file], 201);
|
||||
return response($file, 200)
|
||||
->header('Content-Type', 'application/pdf')
|
||||
->header('Content-Disposition', 'attachment; filename="labels.pdf"');
|
||||
|
||||
}
|
||||
|
||||
|
||||
public function processShipmentsByCarrier(Request $request)
|
||||
{
|
||||
|
||||
Log::channel('expedice')->info('function processShipmentsByCarrier initiated by ' . $request->user()->name, [
|
||||
'carrier' => $request->get('carrier')
|
||||
]);
|
||||
|
||||
$carrierIds = Carrier::where('carrier_shortname', $request->get('carrier')['shortname'])
|
||||
->pluck('id')
|
||||
->toArray();
|
||||
|
||||
$shipmentRequests = ShipmentRequest::whereIn('carrier_id', $carrierIds)
|
||||
->whereDoesntHave('shipment')
|
||||
->whereDoesntHave('batch')
|
||||
->get();
|
||||
|
||||
$batch = ShipmentRequestBatch::create([
|
||||
'user_id' => $request->user()->id,
|
||||
'carrier_master_id' => CarrierMaster::where('shortname', $request->get('carrier')['shortname'])->first()->id,
|
||||
]);
|
||||
|
||||
|
||||
foreach ($shipmentRequests as $shipment_request) {
|
||||
|
||||
|
||||
// Instantiate the correct carrier service dynamically for each shipment
|
||||
$carrierService = CarrierServiceFactory::create($shipment_request->carrier);
|
||||
ShipmentRequestBatchItem::create([
|
||||
'shipment_request_batch_id' => $batch->id,
|
||||
'shipment_request_id' => $shipment_request->id,
|
||||
]);
|
||||
try {
|
||||
$label = $carrierService->genShipLabel($shipment_request);
|
||||
|
||||
if ($label) {
|
||||
$labels[] = $label;
|
||||
}
|
||||
} catch (ExternalApiException|LabelPrintException|ShipmentException|GuzzleException|\Exception $e) {
|
||||
$errors[] = [
|
||||
'shipmentReference' => $shipment_request->shipment_reference,
|
||||
'error' => $e->getMessage(),
|
||||
];
|
||||
|
||||
ShipmentErrorLog::create(
|
||||
array(
|
||||
"shipment_request_id" => $shipment_request->id,
|
||||
"error_message" => $e->getMessage()
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
|
||||
|
||||
if (empty($labels)) {
|
||||
return response()->json(['errors' => $errors], 400);
|
||||
}
|
||||
|
||||
// Placeholder function to merge labels into a file
|
||||
$file = $this->mergeLabelsIntoFile($labels);
|
||||
|
||||
// return response()->json(['file' => $file], 201);
|
||||
return response($file, 200)
|
||||
->header('Content-Type', 'application/pdf')
|
||||
->header('Content-Disposition', 'attachment; filename="labels.pdf"');
|
||||
|
||||
} catch (\Exception $e) {
|
||||
$errors[] = [
|
||||
'shipmentReference' => 0,
|
||||
'error' => $e->getMessage(),
|
||||
];
|
||||
return response()->json(['errors' => $errors], 400);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public function validateAndStoreShipment(Request $request)
|
||||
{
|
||||
|
||||
$euCountries = [
|
||||
'AT', 'BE', 'BG', 'CY', 'CZ', 'DE', 'DK', 'EE', 'ES', 'FI', 'FR', 'GR', 'HR', 'HU', 'IE',
|
||||
'IT', 'LT', 'LU', 'LV', 'MT', 'NL', 'PL', 'PT', 'RO', 'SE', 'SI', 'SK'
|
||||
];
|
||||
|
||||
$base64PdfRule = new Base64Pdf();
|
||||
|
||||
|
||||
foreach ($request->input('shipments', []) as $index => $shipment) {
|
||||
$carrierId = $shipment['carrier_id'] ?? null;
|
||||
$countryISO = $shipment['delivery_address']['countryISO'] ?? null;
|
||||
|
||||
if (in_array($carrierId, [12, 14, 29, 40968, 40969]) && !in_array($countryISO, $euCountries)) {
|
||||
$this->validateArray['shipments.*.invoice_data.invoice_file'] = ['required', $base64PdfRule];
|
||||
$this->validateArray["shipments.*.invoice_data"] = 'required|array';
|
||||
$this->validateArray["shipments.*.invoice_data.invoiceNumber"] = 'required|string|max:255';
|
||||
$this->validateArray["shipments.*.invoice_data.date"] = 'required|string';
|
||||
$this->validateArray["shipments.*.invoice_data.currency"] = 'required|string|max:255';
|
||||
$this->validateArray["shipments.*.invoice_data.address.name"] = 'required|string|max:255';
|
||||
$this->validateArray["shipments.*.invoice_data.address.companyName"] = 'nullable|string|max:255';
|
||||
$this->validateArray["shipments.*.invoice_data.address.companyVatNumber"] = 'nullable|string|max:255';
|
||||
$this->validateArray["shipments.*.invoice_data.address.streetName"] = 'required|string|max:255';
|
||||
$this->validateArray["shipments.*.invoice_data.address.streetNumber"] = 'required|string|max:10';
|
||||
$this->validateArray["shipments.*.invoice_data.address.city"] = 'required|string|max:255';
|
||||
$this->validateArray["shipments.*.invoice_data.address.zip"] = 'required|string|max:20';
|
||||
$this->validateArray["shipments.*.invoice_data.address.stateISO"] = 'nullable|string|max:10';
|
||||
$this->validateArray["shipments.*.invoice_data.address.countryISO"] = 'required|string|max:3';
|
||||
}
|
||||
}
|
||||
$validator = Validator::make($request->all(), $this->validateArray);
|
||||
|
||||
if ($validator->fails()) {
|
||||
return response()->json([
|
||||
'success' => false,
|
||||
'errors' => $validator->errors()
|
||||
], 422);
|
||||
}
|
||||
|
||||
$validatedData = $validator->validated();
|
||||
|
||||
$return_array = [];
|
||||
$status_code = 200;
|
||||
|
||||
|
||||
foreach ($validatedData['shipments'] as $shipmentData) {
|
||||
|
||||
try {
|
||||
|
||||
DB::transaction(function () use ($shipmentData, $request, &$return_array) {
|
||||
|
||||
|
||||
// Create shipment request
|
||||
$shipment_request = ShipmentRequest::create([
|
||||
'shipment_reference' => $shipmentData['shipmentReference'],
|
||||
'user_id' => $request->user()->id,
|
||||
'note' => $shipmentData['note'] ?? null,
|
||||
'delivery_address_name' => $shipmentData['delivery_address']['name'],
|
||||
'delivery_address_company_name' => $shipmentData['delivery_address']['companyName'] ?? null,
|
||||
'delivery_address_street_name' => $shipmentData['delivery_address']['streetName'],
|
||||
'delivery_address_street_number' => $shipmentData['delivery_address']['streetNumber'],
|
||||
'delivery_address_address_line_2' => $shipmentData['delivery_address']['addressLine2'] ?? null,
|
||||
'delivery_address_city' => $shipmentData['delivery_address']['city'],
|
||||
'delivery_address_zip' => $shipmentData['delivery_address']['zip'],
|
||||
'delivery_address_state_iso' => $shipmentData['delivery_address']['stateISO'] ?? null,
|
||||
'delivery_address_country_iso' => $shipmentData['delivery_address']['countryISO'],
|
||||
'pickup_point_code' => $shipmentData['pickupPointCode'] ?? null,
|
||||
'pickup_point_address_street_name' => $shipmentData['pickupPointAddress']['streetName'] ?? null,
|
||||
'pickup_point_address_street_number' => $shipmentData['pickupPointAddress']['streetNumber'] ?? null,
|
||||
'pickup_point_address_city' => $shipmentData['pickupPointAddress']['city'] ?? null,
|
||||
'pickup_point_address_zip' => $shipmentData['pickupPointAddress']['zip'] ?? null,
|
||||
'pickup_point_address_state_iso' => $shipmentData['pickupPointAddress']['stateISO'] ?? null,
|
||||
'pickup_point_address_country_iso' => $shipmentData['pickupPointAddress']['countryISO'] ?? null,
|
||||
'contact_email' => $shipmentData['contactEmail'] ?? null,
|
||||
'contact_telephone' => $shipmentData['contactTelephone'] ?? null,
|
||||
'cod_value' => $shipmentData['codValue'] ?? null,
|
||||
'cod_variable_symbol' => $shipmentData['codVariableSymbol'] ?? null,
|
||||
'shipment_value' => $shipmentData['shipmentValue'],
|
||||
'currency' => $shipmentData['currency'],
|
||||
'weight' => $shipmentData['weight'],
|
||||
'width' => $shipmentData['parcelSize']['width'],
|
||||
'length' => $shipmentData['parcelSize']['length'],
|
||||
'height' => $shipmentData['parcelSize']['height'],
|
||||
'carrier_id' => $shipmentData['carrier_id'],
|
||||
'allegro_carrier_id' => $shipmentData['allegro_carrier_id'] ?? null,
|
||||
'package_type' => $shipmentData['packageType'],
|
||||
'send_again_flag' => $shipmentData['sendAgainFlag'] ?? false,
|
||||
'allegro_smart' => $shipmentData['allegroSmart'] ?? false,
|
||||
'postnummer' => $shipmentData['postnummer'] ?? null,
|
||||
]);
|
||||
|
||||
ShipmentStatusHistory::create(
|
||||
array(
|
||||
'shipment_request_id' => $shipment_request->id,
|
||||
'shipment_status_id' => ShipmentStatus::STATUS_CREATED,
|
||||
)
|
||||
);
|
||||
|
||||
// Create shipment items
|
||||
foreach ($shipmentData['items'] as $itemData) {
|
||||
ShipmentRequestItem::create([
|
||||
'shipment_request_id' => $shipment_request->id,
|
||||
'name' => $itemData['name'],
|
||||
'item_note' => $itemData['item_note'] ?? null,
|
||||
'model_number' => $itemData['model_number'] ?? null,
|
||||
'HSCode' => $itemData['HSCode'] ?? null,
|
||||
'price' => $itemData['price'],
|
||||
'quantity' => $itemData['quantity'],
|
||||
'originCountry' => $itemData['originCountry'],
|
||||
'weight' => $itemData['weight'],
|
||||
'item_id_internal_warehouse' => $itemData['item_id_internal_warehouse'] ?? null,
|
||||
'item_id_external' => $itemData['item_id_external'] ?? null,
|
||||
]);
|
||||
}
|
||||
|
||||
if (!empty($shipmentData['invoice_data'])) {
|
||||
ShipmentRequestInvoice::create([
|
||||
'shipment_request_id' => $shipment_request->id,
|
||||
'invoice_number' => $shipmentData['invoice_data']['invoiceNumber'],
|
||||
'invoice_date' => $shipmentData['invoice_data']['date'],
|
||||
'invoice_currency' => $shipmentData['invoice_data']['currency'],
|
||||
'invoice_address_name' => $shipmentData['invoice_data']['address']['name'],
|
||||
'invoice_address_company_name' => $shipmentData['invoice_data']['address']['companyName'] ?? null,
|
||||
'invoice_address_company_vat_number' => $shipmentData['invoice_data']['address']['companyVatNumber'] ?? null,
|
||||
'invoice_address_street_name' => $shipmentData['invoice_data']['address']['streetName'],
|
||||
'invoice_address_street_number' => $shipmentData['invoice_data']['address']['streetNumber'],
|
||||
'invoice_address_city' => $shipmentData['invoice_data']['address']['city'],
|
||||
'invoice_address_zip' => $shipmentData['invoice_data']['address']['zip'],
|
||||
'invoice_address_state_iso' => $shipmentData['invoice_data']['address']['stateISO'] ?? null,
|
||||
'invoice_address_country_iso' => $shipmentData['invoice_data']['address']['countryISO'],
|
||||
'invoice_raw_data' => $shipmentData['invoice_data']['invoice_file'],
|
||||
]);
|
||||
}
|
||||
|
||||
$return_array[$shipmentData['shipmentReference']] = "Shipment stored sucessfully. Ready for dispatch.";
|
||||
|
||||
// if ($shipment_request->user->details->apiCallbackURL && $shipment_request->user->details->callbacks_enabled) {
|
||||
// $this->callbackService->sendCallback($shipment_request->user->details->apiCallbackURL, [
|
||||
// 'endpoint' => 'storeShipment',
|
||||
// 'result' => 'true',
|
||||
// 'shipment_reference' => $shipmentData['shipmentReference'],
|
||||
// 'additionalInfo' => null
|
||||
// ]);
|
||||
// }
|
||||
|
||||
});
|
||||
} catch (ExternalApiException|LabelPrintException|ShipmentException|GuzzleException|\Exception $e) {
|
||||
|
||||
$return_array[$shipmentData['shipmentReference']] = $e->getMessage();
|
||||
$status_code = 400;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return response()->json(['response' => $return_array], $status_code);
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
protected function addStickerToLabel(Label $label)
|
||||
{
|
||||
try {
|
||||
$stickerPdfData = ExpediceStickersController::getStickerForLabel($label->shipment->shipmentRequest);
|
||||
$mainPdfData = $label->label_data;
|
||||
|
||||
$client = new Client();
|
||||
|
||||
$response = $client->post('http://pdf_manager:5000/combine-pdfs', [
|
||||
'multipart' => [
|
||||
[
|
||||
'name' => 'main_pdf',
|
||||
'contents' => $mainPdfData,
|
||||
'filename' => 'main.pdf'
|
||||
],
|
||||
[
|
||||
'name' => 'sticker_pdf',
|
||||
'contents' => $stickerPdfData,
|
||||
'filename' => 'sticker.pdf'
|
||||
],
|
||||
[
|
||||
'name' => 'resize',
|
||||
'contents' => in_array($label->shipment->shipmentRequest->carrier->carrierMaster->shortname, ["ups"]) ? 'true' : 'true'
|
||||
]
|
||||
]
|
||||
]);
|
||||
|
||||
return $response->getBody();
|
||||
}
|
||||
catch (\Exception $e) {
|
||||
throw $e;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
// Placeholder function to merge labels into a file
|
||||
protected function mergeLabelsIntoFile(array $labels)
|
||||
{
|
||||
|
||||
$timestamp = uniqid() . "_" . now()->timestamp;
|
||||
|
||||
$outputFilePath = storage_path("app/label_result_" . $timestamp . ".pdf");
|
||||
$cmd_string = "gs -sDEVICE=pdfwrite -dCompatibilityLevel=1.4 -dPDFSETTINGS=/default -dNOPAUSE -dQUIET -dBATCH -dAutoRotatePages=/None -dFIXEDMEDIA -dPDFFitPage -sPAPERSIZE=a4 -sOutputFile=$outputFilePath";
|
||||
$pdf_files = [];
|
||||
|
||||
foreach ($labels as $label) {
|
||||
|
||||
$timestamp = uniqid() . "_" . now()->timestamp;
|
||||
|
||||
// Log::info($label->shipment->shipmentRequest->id . " - starting sticker to label");
|
||||
|
||||
$label_data_final = $this->addStickerToLabel($label);
|
||||
if (is_null($label_data_final)) {
|
||||
$label_data_final = $label->label_data;
|
||||
}
|
||||
|
||||
// Log::info($label->shipment->shipmentRequest->id . " - done sticker");
|
||||
|
||||
$filename = 'labels-' . $timestamp . '.pdf';
|
||||
Storage::disk('local')->put($filename, $label_data_final);
|
||||
|
||||
$pdf_files[] = Storage::disk('local')->path($filename);
|
||||
|
||||
// Log::info($label->shipment->shipmentRequest->id . " - pdf metadata get done");
|
||||
|
||||
ShipmentStatusHistory::create(
|
||||
array(
|
||||
'shipment_request_id' => $label->shipment->shipmentRequest->id,
|
||||
'shipment_status_id' => ShipmentStatus::STATUS_LABEL_CREATED,
|
||||
)
|
||||
);
|
||||
|
||||
// Log::info($label->shipment->shipmentRequest->id . " - status created");
|
||||
|
||||
}
|
||||
|
||||
foreach ($pdf_files as $file) {
|
||||
if (!is_null($file)) {
|
||||
$cmd_string .= " " . $file;
|
||||
}
|
||||
}
|
||||
Log::info($cmd_string);
|
||||
|
||||
// Execute the Ghostscript command to merge PDFs
|
||||
exec($cmd_string);
|
||||
|
||||
// Log::info("command gs done");
|
||||
// Delete temporary PDF files after merging
|
||||
foreach ($pdf_files as $file) {
|
||||
if (file_exists($file)) {
|
||||
// unlink($file);
|
||||
}
|
||||
}
|
||||
|
||||
// Log::info("tmp files deleted");
|
||||
|
||||
$ret_file = file_get_contents($outputFilePath);
|
||||
if (file_exists($outputFilePath)) {
|
||||
unlink($outputFilePath);
|
||||
}
|
||||
// Log::info("output final deleted");
|
||||
return $ret_file;
|
||||
}
|
||||
|
||||
public function labelsList(Request $request)
|
||||
{
|
||||
$shipments = ShipmentRequest::with(['carrier'])
|
||||
->whereDoesntHave('shipment')
|
||||
->get();
|
||||
|
||||
$groupedShipments = $shipments->groupBy('carrier.carrier_shortname')
|
||||
->sortByDesc(function ($group) {
|
||||
return $group->count();
|
||||
});
|
||||
|
||||
|
||||
$result = $groupedShipments->map(function ($items, $key) {
|
||||
$carrier = $items->first()->carrier;
|
||||
return [
|
||||
'carrier_name' => $carrier->carrier_name,
|
||||
'img' => $carrier->img,
|
||||
'carrier_shortname' => $carrier->carrier_shortname,
|
||||
'package_count' => $items->count(),
|
||||
];
|
||||
})->values();
|
||||
|
||||
|
||||
// Check if the request is an Inertia request
|
||||
if ($request->header('X-Inertia')) {
|
||||
return Inertia::render('Expedice', [
|
||||
'carriers' => $result,
|
||||
]);
|
||||
}
|
||||
|
||||
// For API requests, return a JSON response
|
||||
return response()->json($result);
|
||||
}
|
||||
|
||||
|
||||
public function reprintLabels(Request $request)
|
||||
{
|
||||
|
||||
$userId = $request->user()->id;
|
||||
|
||||
// broadcast(new LabelsProcessed($userId, [
|
||||
// 'errors' => "test test", // Pass all collected errors
|
||||
// 'error_code' => 400,
|
||||
// ]));
|
||||
|
||||
// Dispatch the job
|
||||
ReprintLabelsJob::dispatch(
|
||||
$request->get('shipmentIds'),
|
||||
$request->get('batchIds'),
|
||||
$request->get('send_again', false),
|
||||
$userId
|
||||
);
|
||||
|
||||
return response()->json(['message' => 'Label creation queued successfully. Please wait....']);
|
||||
|
||||
}
|
||||
|
||||
public function processViaDifferentCarrier(Request $request)
|
||||
{
|
||||
|
||||
try {
|
||||
|
||||
|
||||
Log::channel('expedice')->info('function processViaDifferentCarrier initiated by ' . $request->user()->name, [
|
||||
'shipment_request_ids' => $request->get('shipmentIds'),
|
||||
'carrier_id' => $request->get('carrierId')
|
||||
]);
|
||||
|
||||
$shipment_request = ShipmentRequest::whereIn('id', $request->get('shipmentIds'))->first();
|
||||
|
||||
$carrier = Carrier::where('id', $request->get('carrierId'))->first();
|
||||
|
||||
$batch = ShipmentRequestBatch::create([
|
||||
'user_id' => $request->user()->id,
|
||||
'carrier_master_id' => $carrier->carrier_master_id,
|
||||
]);
|
||||
|
||||
|
||||
$shipment_request->pickup_point_code = null;
|
||||
$shipment_request->carrier_id = $carrier->id;
|
||||
|
||||
// Save the changes to the database
|
||||
$shipment_request->save();
|
||||
|
||||
// Instantiate the correct carrier service dynamically for each shipment
|
||||
$carrierService = CarrierServiceFactory::create($carrier);
|
||||
|
||||
if (isset($shipment_request->shipment)) {
|
||||
$res = $carrierService->cancelShipment($shipment_request->shipment);
|
||||
}
|
||||
|
||||
ReprintLabelsJob::dispatch(
|
||||
$request->get('shipmentIds'),
|
||||
null,
|
||||
false,
|
||||
$request->user()->id
|
||||
);
|
||||
|
||||
return response()->json(['message' => 'Label creation queued successfully. Please wait....']);
|
||||
}
|
||||
catch (\Exception $e) {
|
||||
return response()->json(['error' => $e->getMessage()]);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
76
src/app/Http/Controllers/LocationIQController.php
Normal file
76
src/app/Http/Controllers/LocationIQController.php
Normal file
@ -0,0 +1,76 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use GuzzleHttp\Client;
|
||||
use GuzzleHttp\Exception\RequestException;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class LocationIQController
|
||||
{
|
||||
protected $client;
|
||||
protected $apiKey;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->client = new Client();
|
||||
$this->apiKey = env('LOCATIONIQ_API_KEY'); // Store your API key in the .env file
|
||||
}
|
||||
|
||||
public function validateAddress(Request $request)
|
||||
{
|
||||
|
||||
$address = $request->input('address');
|
||||
$structured = $request->input('structured');
|
||||
|
||||
try {
|
||||
if($structured) {
|
||||
$response = $this->client->request('GET', 'https://eu1.locationiq.com/v1/search.php', [
|
||||
'query' => [
|
||||
'key' => $this->apiKey,
|
||||
'street' => "{$address['streetName']} {$address['streetNumber']}",
|
||||
'city' => $address['city'],
|
||||
'state' => $address['stateISO'],
|
||||
'country' => $address['countryISO'],
|
||||
'postalcode' => $address['zip'],
|
||||
'format' => 'json',
|
||||
'addressdetails' => 1,
|
||||
'normalizeaddress' => 1,
|
||||
'normalizecity' => 1,
|
||||
'statecode' => 1,
|
||||
],
|
||||
]);
|
||||
}
|
||||
else {
|
||||
$formattedAddress = "{$address['streetName']} {$address['streetNumber']}, {$address['city']}, {$address['zip']}, {$address['stateISO']}, {$address['countryISO']}";
|
||||
|
||||
$response = $this->client->request('GET', 'https://eu1.locationiq.com/v1/search.php', [
|
||||
'query' => [
|
||||
'key' => $this->apiKey,
|
||||
'q' => $formattedAddress,
|
||||
'format' => 'json',
|
||||
'addressdetails' => 1,
|
||||
'normalizeaddress' => 1,
|
||||
'normalizecity' => 1,
|
||||
'statecode' => 1,
|
||||
],
|
||||
]);
|
||||
}
|
||||
|
||||
|
||||
$responseData = json_decode($response->getBody(), true);
|
||||
|
||||
return $responseData;
|
||||
|
||||
} catch (RequestException $e) {
|
||||
// Handle request exceptions, log errors, or return a response
|
||||
if ($e->hasResponse()) {
|
||||
$errorResponse = $e->getResponse();
|
||||
$errorMessage = json_decode($errorResponse->getBody(), true);
|
||||
return json_decode($errorResponse->getBody(), true);
|
||||
}
|
||||
|
||||
return null; // Handle generic errors
|
||||
}
|
||||
}
|
||||
}
|
133
src/app/Http/Controllers/ParcelSubmitValidationController.php
Normal file
133
src/app/Http/Controllers/ParcelSubmitValidationController.php
Normal file
@ -0,0 +1,133 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Rules\Base64Pdf;
|
||||
use GuzzleHttp\Client;
|
||||
use GuzzleHttp\Exception\RequestException;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Validator;
|
||||
|
||||
class ParcelSubmitValidationController
|
||||
{
|
||||
protected $client;
|
||||
protected $apiKey;
|
||||
|
||||
private $validateArray = [
|
||||
'shipments' => 'required|array|min:1',
|
||||
'shipments.*.shipmentReference' => 'required|string|max:255',
|
||||
'shipments.*.note' => 'nullable|string|max:1000',
|
||||
'shipments.*.delivery_address' => 'required|array',
|
||||
'shipments.*.delivery_address.name' => 'required|string|max:255',
|
||||
'shipments.*.delivery_address.companyName' => 'nullable|string|max:255',
|
||||
'shipments.*.delivery_address.streetName' => 'required|string|max:255',
|
||||
'shipments.*.delivery_address.streetNumber' => 'required|string|max:10',
|
||||
'shipments.*.delivery_address.city' => 'required|string|max:255',
|
||||
'shipments.*.delivery_address.zip' => 'required|string|max:20',
|
||||
'shipments.*.delivery_address.stateISO' => 'nullable|string|max:10',
|
||||
'shipments.*.delivery_address.countryISO' => 'required|string|max:3',
|
||||
'shipments.*.pickupPointCode' => 'nullable|string|max:255',
|
||||
'shipments.*.pickupPointName' => 'nullable|string|max:255',
|
||||
'shipments.*.pickupPointAddress.streetName' => 'nullable|string|max:255',
|
||||
'shipments.*.pickupPointAddress.streetNumber' => 'nullable|string|max:10',
|
||||
'shipments.*.pickupPointAddress.addressLine2' => 'nullable|string|max:255',
|
||||
'shipments.*.pickupPointAddress.city' => 'nullable|string|max:255',
|
||||
'shipments.*.pickupPointAddress.zip' => 'nullable|string|max:255',
|
||||
'shipments.*.pickupPointAddress.stateISO' => 'nullable|string|max:255',
|
||||
'shipments.*.pickupPointAddress.countryISO' => 'nullable|string|max:255',
|
||||
'shipments.*.postnummer' => 'nullable|string|max:255',
|
||||
'shipments.*.contactEmail' => 'nullable|email|max:255',
|
||||
'shipments.*.contactTelephone' => 'nullable|string|max:20',
|
||||
'shipments.*.codValue' => 'nullable|numeric',
|
||||
'shipments.*.codVariableSymbol' => 'nullable|string|max:8',
|
||||
'shipments.*.shipmentValue' => 'required|numeric',
|
||||
'shipments.*.currency' => 'required|string|max:3',
|
||||
'shipments.*.weight' => 'required|numeric',
|
||||
'shipments.*.items' => 'required|array|min:1',
|
||||
'shipments.*.items.*.name' => 'required|string|max:255',
|
||||
'shipments.*.items.*.item_note' => 'nullable|string',
|
||||
'shipments.*.items.*.HSCode' => 'nullable|string|max:255',
|
||||
'shipments.*.items.*.model_number' => 'nullable|string|max:255',
|
||||
'shipments.*.items.*.price' => 'required|numeric',
|
||||
'shipments.*.items.*.quantity' => 'required|integer',
|
||||
'shipments.*.items.*.originCountry' => 'required|string|max:3',
|
||||
'shipments.*.items.*.weight' => 'required|numeric',
|
||||
'shipments.*.items.*.item_id_internal_warehouse' => 'nullable|numeric',
|
||||
'shipments.*.items.*.item_id_external' => 'nullable|numeric',
|
||||
'shipments.*.carrier_id' => 'required|string|max:255',
|
||||
'shipments.*.allegro_carrier_id' => 'nullable|string|max:255',
|
||||
'shipments.*.packageType' => 'required|in:ENVELOPE,PACKAGE',
|
||||
'shipments.*.sendAgainFlag' => 'nullable|boolean',
|
||||
'shipments.*.allegroSmart' => 'nullable|boolean',
|
||||
'shipments.*.parcelSize' => 'required|array',
|
||||
'shipments.*.parcelSize.width' => 'required|string|max:255',
|
||||
'shipments.*.parcelSize.length' => 'required|string|max:255',
|
||||
'shipments.*.parcelSize.height' => 'required|string|max:255',
|
||||
'shipments.*.invoice_data' => 'nullable|array',
|
||||
'shipments.*.invoice_data.invoiceNumber' => 'nullable|string|max:255',
|
||||
'shipments.*.invoice_data.date' => 'nullable|string',
|
||||
'shipments.*.invoice_data.currency' => 'nullable|string|max:255',
|
||||
'shipments.*.invoice_data.address.name' => 'nullable|string|max:255',
|
||||
'shipments.*.invoice_data.address.companyName' => 'nullable|string|max:255',
|
||||
'shipments.*.invoice_data.address.companyVatNumber' => 'nullable|string|max:255',
|
||||
'shipments.*.invoice_data.address.streetName' => 'nullable|string|max:255',
|
||||
'shipments.*.invoice_data.address.streetNumber' => 'nullable|string|max:10',
|
||||
'shipments.*.invoice_data.address.city' => 'nullable|string|max:255',
|
||||
'shipments.*.invoice_data.address.zip' => 'nullable|string|max:20',
|
||||
'shipments.*.invoice_data.address.stateISO' => 'nullable|string|max:10',
|
||||
'shipments.*.invoice_data.address.countryISO' => 'nullable|string|max:3'
|
||||
|
||||
];
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public function parcelDataValidate(Request $request)
|
||||
{
|
||||
|
||||
$euCountries = [
|
||||
'AT', 'BE', 'BG', 'CY', 'CZ', 'DE', 'DK', 'EE', 'ES', 'FI', 'FR', 'GR', 'HR', 'HU', 'IE',
|
||||
'IT', 'LT', 'LU', 'LV', 'MT', 'NL', 'PL', 'PT', 'RO', 'SE', 'SI', 'SK'
|
||||
];
|
||||
|
||||
$base64PdfRule = new Base64Pdf();
|
||||
|
||||
|
||||
foreach ($request->input('shipments', []) as $index => $shipment) {
|
||||
$carrierId = $shipment['carrier_id'] ?? null;
|
||||
$countryISO = $shipment['delivery_address']['countryISO'] ?? null;
|
||||
|
||||
if (in_array($carrierId, [12, 14, 29, 40968, 40969]) && !in_array($countryISO, $euCountries)) {
|
||||
$this->validateArray['shipments.*.invoice_data.invoice_file'] = ['required', $base64PdfRule];
|
||||
$this->validateArray["shipments.*.invoice_data"] = 'required|array';
|
||||
$this->validateArray["shipments.*.invoice_data.invoiceNumber"] = 'required|string|max:255';
|
||||
$this->validateArray["shipments.*.invoice_data.date"] = 'required|string';
|
||||
$this->validateArray["shipments.*.invoice_data.currency"] = 'required|string|max:255';
|
||||
$this->validateArray["shipments.*.invoice_data.address.name"] = 'required|string|max:255';
|
||||
$this->validateArray["shipments.*.invoice_data.address.companyName"] = 'nullable|string|max:255';
|
||||
$this->validateArray["shipments.*.invoice_data.address.companyVatNumber"] = 'nullable|string|max:255';
|
||||
$this->validateArray["shipments.*.invoice_data.address.streetName"] = 'required|string|max:255';
|
||||
$this->validateArray["shipments.*.invoice_data.address.streetNumber"] = 'required|string|max:10';
|
||||
$this->validateArray["shipments.*.invoice_data.address.city"] = 'required|string|max:255';
|
||||
$this->validateArray["shipments.*.invoice_data.address.zip"] = 'required|string|max:20';
|
||||
$this->validateArray["shipments.*.invoice_data.address.stateISO"] = 'required|string|max:10';
|
||||
$this->validateArray["shipments.*.invoice_data.address.countryISO"] = 'required|string|max:3';
|
||||
}
|
||||
}
|
||||
$validator = Validator::make($request->all(), $this->validateArray);
|
||||
|
||||
if ($validator->fails()) {
|
||||
return response()->json([
|
||||
'success' => false,
|
||||
'errors' => $validator->errors()
|
||||
], 422);
|
||||
}
|
||||
|
||||
return response()->json([
|
||||
'success' => true
|
||||
], 200);
|
||||
|
||||
}
|
||||
}
|
54
src/app/Http/Controllers/PickupPointController.php
Normal file
54
src/app/Http/Controllers/PickupPointController.php
Normal file
@ -0,0 +1,54 @@
|
||||
<?php
|
||||
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Models\Carrier;
|
||||
use App\Models\CarrierBasePricing;
|
||||
use App\Models\CarrierBasePricing2Weight;
|
||||
use App\Models\PickupPoint;
|
||||
use App\Models\ShipmentRequest;
|
||||
use App\Models\User;
|
||||
use App\Models\UserCarrierPricing;
|
||||
use App\Models\UserCarrierPricing2Weight;
|
||||
use App\Models\UserDetails;
|
||||
use Illuminate\Http\Request;
|
||||
use Inertia\Inertia;
|
||||
|
||||
class PickupPointController extends Controller {
|
||||
|
||||
|
||||
function checkAlternativePickupPoints(Request $request) {
|
||||
$shipment = ShipmentRequest::where('id', $request->get('shipmentId'))->first();
|
||||
|
||||
$pickupPoints = PickupPoint::getNearbyPickupPoints($shipment->pickup_point_code, $shipment->carrier->id)
|
||||
->load('carrier.carrierMaster');
|
||||
|
||||
foreach ($pickupPoints as $pickupPoint) {
|
||||
// Find the shipment price for each pickup point
|
||||
$shipment_price_object = $shipment->carrier->userCarrierPricings()
|
||||
->whereHas('user', function ($query) use ($shipment) {
|
||||
$query->where('id', $shipment->user->id);
|
||||
})->first();
|
||||
|
||||
if (!$shipment_price_object) {
|
||||
$shipment_price_object = $shipment->carrier->basePricing()->first();
|
||||
$user_carrier_grp_uplift = $shipment->user->userCarrierPricingGroup->carrierPricingGroup->base_ratio;
|
||||
$shipment_price = round(
|
||||
$shipment_price_object->getPriceForWeight((float)$shipment->weight) +
|
||||
($shipment_price_object->getPriceForWeight((float)$shipment->weight) * ($user_carrier_grp_uplift / 100)),
|
||||
0
|
||||
);
|
||||
} else {
|
||||
$shipment_price = $shipment_price_object->getPriceForWeight((float)$shipment->weight);
|
||||
}
|
||||
|
||||
// Add the calculated shipment price to the pickup point
|
||||
$pickupPoint->shipment_price = $shipment_price;
|
||||
}
|
||||
|
||||
return response()->json($pickupPoints);
|
||||
}
|
||||
|
||||
|
||||
}
|
100
src/app/Http/Controllers/ProfileController.php
Normal file
100
src/app/Http/Controllers/ProfileController.php
Normal file
@ -0,0 +1,100 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Http\Requests\ProfileUpdateRequest;
|
||||
use Illuminate\Contracts\Auth\MustVerifyEmail;
|
||||
use Illuminate\Http\RedirectResponse;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Illuminate\Support\Facades\Redirect;
|
||||
use Inertia\Inertia;
|
||||
use Inertia\Response;
|
||||
|
||||
class ProfileController extends Controller
|
||||
{
|
||||
/**
|
||||
* Display the user's profile form.
|
||||
*/
|
||||
public function edit(Request $request): Response
|
||||
{
|
||||
$user = $request->user()->load('details');
|
||||
$token = $user->tokens->first()->token ?? null;
|
||||
|
||||
return Inertia::render('Profile/Edit', [
|
||||
'mustVerifyEmail' => $user instanceof MustVerifyEmail,
|
||||
'user' => $user,
|
||||
'status' => session('status'),
|
||||
'apiToken' => $token,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the user's profile information.
|
||||
*/
|
||||
public function update(ProfileUpdateRequest $request): RedirectResponse
|
||||
{
|
||||
$request->user()->fill($request->validated());
|
||||
|
||||
if ($request->user()->isDirty('email')) {
|
||||
$request->user()->email_verified_at = null;
|
||||
}
|
||||
|
||||
$request->user()->save();
|
||||
|
||||
return Redirect::route('profile.edit');
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the user's profile information.
|
||||
*/
|
||||
public function updateApiPreferences(Request $request)
|
||||
{
|
||||
if(isset($request->user()->details)) {
|
||||
if($request->get('apiCallbackURL') !== null) {
|
||||
$request->user()->details->apiCallbackURL = $request->get('apiCallbackURL');
|
||||
}
|
||||
if($request->get('apiCallbacksAllowed') !== null) {
|
||||
$request->user()->details->callbacks_enabled = $request->get('apiCallbacksAllowed');
|
||||
}
|
||||
|
||||
$request->user()->details->save();
|
||||
}
|
||||
|
||||
return response()->json([
|
||||
'user' => $request->user()
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete the user's account.
|
||||
*/
|
||||
public function destroy(Request $request): RedirectResponse
|
||||
{
|
||||
$request->validate([
|
||||
'password' => ['required', 'current_password'],
|
||||
]);
|
||||
|
||||
$user = $request->user();
|
||||
|
||||
Auth::logout();
|
||||
|
||||
$user->delete();
|
||||
|
||||
$request->session()->invalidate();
|
||||
$request->session()->regenerateToken();
|
||||
|
||||
return Redirect::to('/');
|
||||
}
|
||||
|
||||
public function logout(Request $request): RedirectResponse
|
||||
{
|
||||
|
||||
Auth::guard("web")->logout();
|
||||
|
||||
$request->session()->invalidate();
|
||||
$request->session()->regenerateToken();
|
||||
|
||||
return Redirect::to('/');
|
||||
}
|
||||
}
|
1006
src/app/Http/Controllers/ShipmentController.php
Normal file
1006
src/app/Http/Controllers/ShipmentController.php
Normal file
File diff suppressed because it is too large
Load Diff
187
src/app/Http/Controllers/StatusController.php
Normal file
187
src/app/Http/Controllers/StatusController.php
Normal file
@ -0,0 +1,187 @@
|
||||
<?php
|
||||
|
||||
|
||||
// app/Http/Controllers/ShipmentController.php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Factories\CarrierServiceFactory;
|
||||
use App\Models\ShipmentRequest;
|
||||
use App\Models\ShipmentRequestBatchItem;
|
||||
use App\Models\ShipmentStatus;
|
||||
use App\Models\ShipmentStatusHistory;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use Inertia\Inertia;
|
||||
|
||||
class StatusController extends Controller
|
||||
{
|
||||
|
||||
public function statusList()
|
||||
{
|
||||
$statuses = ShipmentStatus::all();
|
||||
|
||||
// For API requests, return a JSON response
|
||||
return response()->json($statuses);
|
||||
}
|
||||
public function statusFilterList()
|
||||
{
|
||||
$statuses = ShipmentStatus::all()->pluck('name');
|
||||
|
||||
// For API requests, return a JSON response
|
||||
return response()->json($statuses);
|
||||
}
|
||||
public function addStatus(Request $request)
|
||||
{
|
||||
|
||||
if(is_array($request->get('shipment_request_id'))) {
|
||||
|
||||
foreach($request->get('shipment_request_id') as $sreq_id) {
|
||||
|
||||
ShipmentStatusHistory::create(
|
||||
array(
|
||||
'shipment_request_id' => $sreq_id,
|
||||
'shipment_status_id' => $request->get('statusId'),
|
||||
)
|
||||
);
|
||||
|
||||
}
|
||||
}
|
||||
else {
|
||||
|
||||
if($request->get('statusId') === ShipmentStatus::STATUS_CANCELED) {
|
||||
return $this->cancelShipment($request);
|
||||
}
|
||||
|
||||
ShipmentStatusHistory::create(
|
||||
array(
|
||||
'shipment_request_id' => $request->get('shipment_request_id'),
|
||||
'shipment_status_id' => $request->get('statusId'),
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
// For API requests, return a JSON response
|
||||
return response()->json(true);
|
||||
}
|
||||
|
||||
public function requeueShipment(Request $request)
|
||||
{
|
||||
|
||||
try {
|
||||
$user = $request->user();
|
||||
if ($user->hasRole('customer')) {
|
||||
$shipmentRequest = ShipmentRequest::where('shipment_reference', $request->get('shipment_reference'))
|
||||
->where('user_id', $user->id)
|
||||
->first();
|
||||
}
|
||||
else {
|
||||
$shipmentRequest = ShipmentRequest::where('shipment_reference', $request->get('shipment_reference'))
|
||||
->first();
|
||||
}
|
||||
|
||||
if(!is_null($shipmentRequest->shipment)) {
|
||||
return response()->json(['result' => false, 'error' => "Label already exists, cannot requeue shipment."]);
|
||||
}
|
||||
|
||||
|
||||
if (!is_null($shipmentRequest->batch)) {
|
||||
ShipmentRequestBatchItem::where('shipment_request_id', $shipmentRequest->id)->delete();
|
||||
}
|
||||
|
||||
if($shipmentRequest->currentShipmentStatus->shipment_status_id === ShipmentStatus::STATUS_IS_REPAIR) {
|
||||
$shipmentRequest->send_again_flag = true;
|
||||
$shipmentRequest->save();
|
||||
ShipmentStatusHistory::create(
|
||||
array(
|
||||
'shipment_request_id' => $shipmentRequest->id,
|
||||
'shipment_status_id' => ShipmentStatus::STATUS_PENDING,
|
||||
)
|
||||
);
|
||||
}
|
||||
else {
|
||||
ShipmentStatusHistory::create(
|
||||
array(
|
||||
'shipment_request_id' => $shipmentRequest->id,
|
||||
'shipment_status_id' => ShipmentStatus::STATUS_PENDING,
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
// For API requests, return a JSON response
|
||||
return response()->json(['result' => true]);
|
||||
}
|
||||
catch (\Exception $e) {
|
||||
return response()->json(['result' => false, 'error' => $e->getMessage()]);
|
||||
}
|
||||
}
|
||||
public function cancelShipment(Request $request)
|
||||
{
|
||||
|
||||
try {
|
||||
if(is_null($request->get('shipment_reference')) && !is_null($request->get('shipment_request_id'))) {
|
||||
$shipmentRequest = ShipmentRequest::where('id', $request->get('shipment_request_id'))
|
||||
->first();
|
||||
}
|
||||
else {
|
||||
$user = $request->user();
|
||||
if ($user->hasRole('customer')) {
|
||||
$shipmentRequest = ShipmentRequest::where('shipment_reference', $request->get('shipment_reference'))
|
||||
->where('user_id', $user->id)
|
||||
->first();
|
||||
}
|
||||
else {
|
||||
$shipmentRequest = ShipmentRequest::where('shipment_reference', $request->get('shipment_reference'))
|
||||
->first();
|
||||
}
|
||||
}
|
||||
|
||||
Log::channel('expedice')->info("Shipment cancel initiated by " . (Auth::user()->name ?? '///'), [
|
||||
'shipment_request_id' => $shipmentRequest->id
|
||||
]);
|
||||
|
||||
|
||||
|
||||
|
||||
if (!is_null($shipmentRequest->batch)) {
|
||||
ShipmentRequestBatchItem::where('shipment_request_id', $shipmentRequest->id)->delete();
|
||||
}
|
||||
|
||||
if(isset($shipmentRequest->shipment)) {
|
||||
$carrierService = CarrierServiceFactory::create($shipmentRequest->carrier);
|
||||
$res = $carrierService->cancelShipment($shipmentRequest->shipment);
|
||||
}
|
||||
|
||||
// $test = $carrierService->testCheckShipment($shipmentRequest->shipment);
|
||||
|
||||
ShipmentStatusHistory::create(
|
||||
array(
|
||||
'shipment_request_id' => $shipmentRequest->id,
|
||||
'shipment_status_id' => ShipmentStatus::STATUS_CANCELED,
|
||||
)
|
||||
);
|
||||
|
||||
if(!$res) {
|
||||
Log::channel('expedice')->info("Shipment cancel (automatic) unsuccessful - " . (Auth::user()->name ?? '///'), [
|
||||
'shipment_request_id' => $shipmentRequest->id
|
||||
]);
|
||||
return response()->json(['result' => true, "message" => "Unable to cancel shipment in the carrier system, please cancel the shipment manually."]);
|
||||
}
|
||||
else {
|
||||
optional($shipmentRequest->shipment?->label)->delete();
|
||||
optional($shipmentRequest->shipment)->delete();
|
||||
Log::channel('expedice')->info("Shipment cancel (automatic) OK - " . (Auth::user()->name ?? '///'), [
|
||||
'shipment_request_id' => $shipmentRequest->id
|
||||
]);
|
||||
return response()->json(['result' => true]);
|
||||
}
|
||||
|
||||
}
|
||||
catch (\Exception $e) {
|
||||
return response()->json(['result' => false, 'error' => $e->getMessage()]);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
49
src/app/Http/Controllers/StockWithdrawalController.php
Normal file
49
src/app/Http/Controllers/StockWithdrawalController.php
Normal file
@ -0,0 +1,49 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
use App\Jobs\SortStockWithdrawal;
|
||||
use App\Jobs\UpdateStockWithdrawalCache;
|
||||
use App\Models\ItemsStorageRemoval;
|
||||
use App\Models\ShipmentRequestItem;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
|
||||
|
||||
class StockWithdrawalController extends Controller
|
||||
{
|
||||
public function markProcessed(Request $request) {
|
||||
|
||||
Log::channel('storage_management')->info("Withdraw attempt from stock by " . $request->user()->name, [
|
||||
'request' => $request->all()
|
||||
]);
|
||||
|
||||
$stock_items_ids = $request->post('selectedStock');
|
||||
|
||||
ItemsStorageRemoval::whereIn('shipment_request_item_id', $stock_items_ids)->update(['entry_processed' => true]);
|
||||
Log::channel('storage_management')->info("Items withdrawn from stock by " . $request->user()->name, [
|
||||
'shipment_request_items_ids' => $stock_items_ids
|
||||
]);
|
||||
|
||||
return response()->json(['message' => 'Stock marked as processed']);
|
||||
|
||||
}
|
||||
|
||||
public function start(): JsonResponse
|
||||
{
|
||||
// Dispatch the job manually
|
||||
UpdateStockWithdrawalCache::dispatch()->onQueue('storage_management');
|
||||
|
||||
return response()->json(['message' => 'Stock withdrawal process started.']);
|
||||
}
|
||||
public function sort(): JsonResponse
|
||||
{
|
||||
// Dispatch the job manually
|
||||
SortStockWithdrawal::dispatch()->onQueue('storage_management');
|
||||
|
||||
return response()->json(['message' => 'Stock sorting process started.']);
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
82
src/app/Http/Controllers/UserController.php
Normal file
82
src/app/Http/Controllers/UserController.php
Normal file
@ -0,0 +1,82 @@
|
||||
<?php
|
||||
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Exceptions\ExternalApiException;
|
||||
use App\Exceptions\LabelPrintException;
|
||||
use App\Exceptions\ShipmentException;
|
||||
use App\Models\Carrier;
|
||||
use App\Models\CarrierMaster;
|
||||
use App\Models\ShipmentErrorLog;
|
||||
use App\Models\ShipmentRequest;
|
||||
use App\Models\ShipmentRequestBatch;
|
||||
use App\Models\ShipmentRequestBatchItem;
|
||||
use App\Models\ShipmentRequestInvoice;
|
||||
use App\Models\ShipmentRequestItem;
|
||||
use App\Models\User;
|
||||
use App\Models\UserDetails;
|
||||
use App\Rules\Base64Pdf;
|
||||
use GuzzleHttp\Exception\GuzzleException;
|
||||
use Illuminate\Http\Request;
|
||||
use App\Models\Shipment;
|
||||
use App\Factories\CarrierServiceFactory;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
use Illuminate\Support\Facades\Validator;
|
||||
use Inertia\Inertia;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
|
||||
class UserController extends Controller {
|
||||
|
||||
|
||||
public function userDetail($id) {
|
||||
|
||||
$userData = User::with([
|
||||
'details.carrierPricings.carrier',
|
||||
'details.carrierPricings.pricingWeights',
|
||||
'details.carrierPricings.extraFees.extraFeeType'
|
||||
])->find($id);
|
||||
|
||||
|
||||
|
||||
return Inertia::render('Admin/UserDetails', [
|
||||
'user' => $userData,
|
||||
]);
|
||||
|
||||
}
|
||||
|
||||
public function update(Request $request)
|
||||
{
|
||||
// Validate the request data
|
||||
$validatedData = $request->validate([
|
||||
'id' => 'required|integer',
|
||||
'name' => 'required|string|max:255',
|
||||
'email' => 'required|email|max:255',
|
||||
'apiCallbackURL' => 'nullable|url|max:255',
|
||||
'callbacks_enabled' => 'required|boolean',
|
||||
]);
|
||||
|
||||
// Find the user by its ID
|
||||
$user = User::findOrFail($validatedData['id']);
|
||||
|
||||
// Update the User model
|
||||
$user->update([
|
||||
'name' => $validatedData['name'],
|
||||
'email' => $validatedData['email'],
|
||||
]);
|
||||
|
||||
// Update the UserDetails model
|
||||
$userDetails = UserDetails::where('user_id', $user->id)->firstOrFail();
|
||||
$userDetails->update([
|
||||
'apiCallbackURL' => $validatedData['apiCallbackURL'],
|
||||
'callbacks_enabled' => $validatedData['callbacks_enabled'],
|
||||
]);
|
||||
|
||||
// Return a success response
|
||||
return response()->json(['message' => 'User and UserDetails updated successfully', 'user' => $user, 'userDetails' => $userDetails]);
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
92
src/app/Http/Controllers/UserPricingController.php
Normal file
92
src/app/Http/Controllers/UserPricingController.php
Normal file
@ -0,0 +1,92 @@
|
||||
<?php
|
||||
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Models\Carrier;
|
||||
use App\Models\User;
|
||||
use App\Models\UserCarrierPricing;
|
||||
use App\Models\UserCarrierPricing2Weight;
|
||||
use App\Models\UserDetails;
|
||||
use Illuminate\Http\Request;
|
||||
use Inertia\Inertia;
|
||||
|
||||
class UserPricingController extends Controller {
|
||||
|
||||
public function updatePricing(Request $request, $id)
|
||||
{
|
||||
// Validate the request data
|
||||
$request->validate([
|
||||
'cod_price' => 'required|numeric|min:0', // example validation rule
|
||||
]);
|
||||
|
||||
// Find the record by ID and update the cod_price
|
||||
$record = UserCarrierPricing::findOrFail($id);
|
||||
$record->update([
|
||||
'cod_price' => $request->get('cod_price'),
|
||||
]);
|
||||
|
||||
// Return a success response
|
||||
return response()->json([
|
||||
'success' => true,
|
||||
]);
|
||||
}
|
||||
public function addPricing(Request $request) {
|
||||
|
||||
UserCarrierPricing::create([
|
||||
'user_id' => $request->get('user_id'),
|
||||
'carrier_id' => $request->get('carrier_id'),
|
||||
'cod_price' => $request->get('cod_price'),
|
||||
]);
|
||||
|
||||
return response()->json([
|
||||
'success' => true,
|
||||
]);
|
||||
|
||||
}
|
||||
|
||||
public function updatePricingWeight(Request $request, $id)
|
||||
{
|
||||
|
||||
$record = UserCarrierPricing2Weight::findOrFail($id);
|
||||
$record->update([
|
||||
'weight_max' => $request->get('weight_max'),
|
||||
'shipping_price' => $request->get('shipping_price'),
|
||||
]);
|
||||
|
||||
// Return a success response
|
||||
return response()->json([
|
||||
'success' => true,
|
||||
]);
|
||||
}
|
||||
public function deletePricingWeight($id)
|
||||
{
|
||||
// Retrieve the record by its ID
|
||||
$record = UserCarrierPricing2Weight::findOrFail($id);
|
||||
|
||||
// Delete the record from the database
|
||||
$record->delete();
|
||||
|
||||
// Return a success response
|
||||
return response()->json([
|
||||
'success' => true,
|
||||
]);
|
||||
}
|
||||
|
||||
public function addPricingWeight(Request $request) {
|
||||
|
||||
UserCarrierPricing2Weight::create([
|
||||
'user_carrier_pricing_id' => $request->get('user_carrier_pricing_id'),
|
||||
'weight_max' => $request->get('weight_max'),
|
||||
'shipping_price' => $request->get('shipping_price'),
|
||||
]);
|
||||
|
||||
return response()->json([
|
||||
'success' => true,
|
||||
]);
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
30
src/app/Http/Middleware/ApiRequestLogger.php
Normal file
30
src/app/Http/Middleware/ApiRequestLogger.php
Normal file
@ -0,0 +1,30 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Middleware;
|
||||
|
||||
use Closure;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
|
||||
class ApiRequestLogger
|
||||
{
|
||||
public function handle($request, Closure $next)
|
||||
{
|
||||
// Log the request data to the custom log file
|
||||
Log::channel('api_requests')->info('API Request', [
|
||||
'method' => $request->getMethod(),
|
||||
'url' => $request->fullUrl(),
|
||||
'headers' => $request->headers->all(),
|
||||
'body' => $request->all(),
|
||||
]);
|
||||
|
||||
$response = $next($request);
|
||||
|
||||
// Log the response data to the custom log file
|
||||
Log::channel('api_requests')->info('API Response', [
|
||||
'status' => $response->getStatusCode(),
|
||||
'body' => $response->getContent(),
|
||||
]);
|
||||
|
||||
return $response;
|
||||
}
|
||||
}
|
39
src/app/Http/Middleware/HandleInertiaRequests.php
Normal file
39
src/app/Http/Middleware/HandleInertiaRequests.php
Normal file
@ -0,0 +1,39 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Middleware;
|
||||
|
||||
use Illuminate\Http\Request;
|
||||
use Inertia\Middleware;
|
||||
|
||||
class HandleInertiaRequests extends Middleware
|
||||
{
|
||||
/**
|
||||
* The root template that is loaded on the first page visit.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $rootView = 'app';
|
||||
|
||||
/**
|
||||
* Determine the current asset version.
|
||||
*/
|
||||
public function version(Request $request): string|null
|
||||
{
|
||||
return parent::version($request);
|
||||
}
|
||||
|
||||
/**
|
||||
* Define the props that are shared by default.
|
||||
*
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
public function share(Request $request): array
|
||||
{
|
||||
return [
|
||||
...parent::share($request),
|
||||
'auth' => [
|
||||
'user' => $request->user(),
|
||||
],
|
||||
];
|
||||
}
|
||||
}
|
85
src/app/Http/Requests/Auth/LoginRequest.php
Normal file
85
src/app/Http/Requests/Auth/LoginRequest.php
Normal file
@ -0,0 +1,85 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Requests\Auth;
|
||||
|
||||
use Illuminate\Auth\Events\Lockout;
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Illuminate\Support\Facades\RateLimiter;
|
||||
use Illuminate\Support\Str;
|
||||
use Illuminate\Validation\ValidationException;
|
||||
|
||||
class LoginRequest extends FormRequest
|
||||
{
|
||||
/**
|
||||
* Determine if the user is authorized to make this request.
|
||||
*/
|
||||
public function authorize(): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the validation rules that apply to the request.
|
||||
*
|
||||
* @return array<string, \Illuminate\Contracts\Validation\Rule|array|string>
|
||||
*/
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
'email' => ['required', 'string', 'email'],
|
||||
'password' => ['required', 'string'],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempt to authenticate the request's credentials.
|
||||
*
|
||||
* @throws \Illuminate\Validation\ValidationException
|
||||
*/
|
||||
public function authenticate(): void
|
||||
{
|
||||
$this->ensureIsNotRateLimited();
|
||||
|
||||
if (! Auth::attempt($this->only('email', 'password'), $this->boolean('remember'))) {
|
||||
RateLimiter::hit($this->throttleKey());
|
||||
|
||||
throw ValidationException::withMessages([
|
||||
'email' => trans('auth.failed'),
|
||||
]);
|
||||
}
|
||||
|
||||
RateLimiter::clear($this->throttleKey());
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensure the login request is not rate limited.
|
||||
*
|
||||
* @throws \Illuminate\Validation\ValidationException
|
||||
*/
|
||||
public function ensureIsNotRateLimited(): void
|
||||
{
|
||||
if (! RateLimiter::tooManyAttempts($this->throttleKey(), 5)) {
|
||||
return;
|
||||
}
|
||||
|
||||
event(new Lockout($this));
|
||||
|
||||
$seconds = RateLimiter::availableIn($this->throttleKey());
|
||||
|
||||
throw ValidationException::withMessages([
|
||||
'email' => trans('auth.throttle', [
|
||||
'seconds' => $seconds,
|
||||
'minutes' => ceil($seconds / 60),
|
||||
]),
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the rate limiting throttle key for the request.
|
||||
*/
|
||||
public function throttleKey(): string
|
||||
{
|
||||
return Str::transliterate(Str::lower($this->string('email')).'|'.$this->ip());
|
||||
}
|
||||
}
|
23
src/app/Http/Requests/ProfileUpdateRequest.php
Normal file
23
src/app/Http/Requests/ProfileUpdateRequest.php
Normal file
@ -0,0 +1,23 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Requests;
|
||||
|
||||
use App\Models\User;
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
use Illuminate\Validation\Rule;
|
||||
|
||||
class ProfileUpdateRequest extends FormRequest
|
||||
{
|
||||
/**
|
||||
* Get the validation rules that apply to the request.
|
||||
*
|
||||
* @return array<string, \Illuminate\Contracts\Validation\Rule|array|string>
|
||||
*/
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
'name' => ['required', 'string', 'max:255'],
|
||||
'email' => ['required', 'string', 'lowercase', 'email', 'max:255', Rule::unique(User::class)->ignore($this->user()->id)],
|
||||
];
|
||||
}
|
||||
}
|
23
src/app/Interfaces/CarrierInterface.php
Normal file
23
src/app/Interfaces/CarrierInterface.php
Normal file
@ -0,0 +1,23 @@
|
||||
<?php
|
||||
|
||||
|
||||
namespace App\Interfaces;
|
||||
|
||||
use App\Models\Label;use App\Models\Shipment;
|
||||
use App\Models\ShipmentRequest;
|
||||
|
||||
interface CarrierInterface {
|
||||
|
||||
public function createShipment(ShipmentRequest $shipment_request) : Shipment;
|
||||
function prepareData(ShipmentRequest $shipment_request): array;
|
||||
|
||||
function savePackageData(ShipmentRequest $shipment_request, mixed $res) : Shipment;
|
||||
|
||||
public function getLabel(Shipment $shipment) : string|null;
|
||||
|
||||
public function genShipLabel(ShipmentRequest $shipment_request) : Label;
|
||||
|
||||
|
||||
}
|
||||
|
||||
?>
|
20
src/app/Jobs/DeleteExpiredFiles.php
Normal file
20
src/app/Jobs/DeleteExpiredFiles.php
Normal file
@ -0,0 +1,20 @@
|
||||
<?php
|
||||
// app/Jobs/DeleteExpiredFiles.php
|
||||
|
||||
namespace App\Jobs;
|
||||
|
||||
use App\Models\GeneratedFile;
|
||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
|
||||
class DeleteExpiredFiles implements ShouldQueue
|
||||
{
|
||||
public function __invoke()
|
||||
{
|
||||
// Delete records where the lifetime has passed
|
||||
GeneratedFile::where('lifetime', '<=', now())->delete();
|
||||
|
||||
Log::channel('jobs')->info('Expired files deleted successfully.');
|
||||
|
||||
}
|
||||
}
|
24
src/app/Jobs/DeleteTemporaryLabels.php
Normal file
24
src/app/Jobs/DeleteTemporaryLabels.php
Normal file
@ -0,0 +1,24 @@
|
||||
<?php
|
||||
// app/Jobs/DeleteExpiredFiles.php
|
||||
|
||||
namespace App\Jobs;
|
||||
|
||||
use App\Models\GeneratedFile;
|
||||
use App\Models\Label;
|
||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
|
||||
class DeleteTemporaryLabels implements ShouldQueue
|
||||
{
|
||||
public function __invoke()
|
||||
{
|
||||
|
||||
$deletedCount = Label::where('temporary', true)
|
||||
->where('created_at', '<', now()->subHours(48))
|
||||
->delete();
|
||||
|
||||
Log::channel('jobs')->info($deletedCount . ' temporary labels older than 48 hours have been deleted.');
|
||||
|
||||
|
||||
}
|
||||
}
|
48
src/app/Jobs/FetchTrackingStatus.php
Normal file
48
src/app/Jobs/FetchTrackingStatus.php
Normal file
@ -0,0 +1,48 @@
|
||||
<?php
|
||||
|
||||
namespace App\Jobs;
|
||||
|
||||
use App\Models\Shipment;
|
||||
use App\Models\ShipmentTrackingStatus;
|
||||
use App\Models\ShipmentTrackingStatusHistory;
|
||||
use Carbon\Carbon;
|
||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
|
||||
class FetchTrackingStatus implements ShouldQueue
|
||||
{
|
||||
public function __invoke()
|
||||
{
|
||||
Log::channel('jobs')->info('----------------------Tracking status fetch STARTED------------------------------');
|
||||
|
||||
Shipment::where('created_at', '>=', Carbon::create(2024, 11, 28))
|
||||
->whereDoesntHave('trackingStatusHistory', function ($query) {
|
||||
$query->where('final_status', true);
|
||||
})
|
||||
->chunk(100, function ($shipments) {
|
||||
foreach ($shipments as $shipment) {
|
||||
// Dispatch a sub-job for each shipment
|
||||
FetchTrackingStatusSubJob::dispatch($shipment)->onQueue('fetch_tracking');
|
||||
Log::channel('jobs')->info('Subjob dispatched', [
|
||||
'shipment_id' => $shipment->id,
|
||||
]);
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
$shipment_results = ShipmentTrackingStatusHistory::whereIn('shipment_tracking_status_id', [
|
||||
ShipmentTrackingStatus::DELIVERED,
|
||||
ShipmentTrackingStatus::CANCELED,
|
||||
ShipmentTrackingStatus::RETURNED,
|
||||
])
|
||||
->where('final_status', false)
|
||||
->get();
|
||||
|
||||
foreach ($shipment_results as $result) {
|
||||
$result->final_status = true;
|
||||
$result->save(); // Triggers the saving event
|
||||
}
|
||||
|
||||
Log::channel('jobs')->info('----------------------Tracking status fetch FINISHED------------------------------');
|
||||
}
|
||||
}
|
45
src/app/Jobs/FetchTrackingStatusSubJob.php
Normal file
45
src/app/Jobs/FetchTrackingStatusSubJob.php
Normal file
@ -0,0 +1,45 @@
|
||||
<?php
|
||||
|
||||
|
||||
namespace App\Jobs;
|
||||
|
||||
use App\Factories\CarrierServiceFactory;
|
||||
use App\Models\Shipment;
|
||||
use Illuminate\Bus\Queueable;
|
||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||
use Illuminate\Foundation\Bus\Dispatchable;
|
||||
use Illuminate\Queue\InteractsWithQueue;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
|
||||
class FetchTrackingStatusSubJob implements ShouldQueue
|
||||
{
|
||||
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
|
||||
|
||||
protected $shipment;
|
||||
|
||||
public function __construct(Shipment $shipment)
|
||||
{
|
||||
$this->shipment = $shipment;
|
||||
}
|
||||
|
||||
public function handle()
|
||||
{
|
||||
try {
|
||||
// Create carrier service and process the tracking history
|
||||
$carrierService = CarrierServiceFactory::create($this->shipment->shipmentRequest->carrier);
|
||||
$status_fetch_result = $carrierService->processTrackingHistory($this->shipment);
|
||||
|
||||
Log::channel('jobs')->info('Tracking status fetch', [
|
||||
'shipment_id' => $this->shipment->id,
|
||||
'new_status_count' => $status_fetch_result,
|
||||
]);
|
||||
} catch (\Exception $exception) {
|
||||
Log::channel('jobs')->error($exception->getMessage(), [
|
||||
'shipment_id' => $this->shipment->id,
|
||||
]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
194
src/app/Jobs/MergeLabelsIntoFileJob.php
Normal file
194
src/app/Jobs/MergeLabelsIntoFileJob.php
Normal file
@ -0,0 +1,194 @@
|
||||
<?php
|
||||
|
||||
namespace App\Jobs;
|
||||
|
||||
use App\Http\Controllers\ExpediceStickersController;
|
||||
use App\Models\ActionLog;
|
||||
use App\Models\GeneratedFile;
|
||||
use App\Models\Label;
|
||||
use App\Models\ShipmentRequestBatch;
|
||||
use App\Models\ShipmentStatus;
|
||||
use App\Models\ShipmentStatusHistory;
|
||||
use GuzzleHttp\Client;
|
||||
use Illuminate\Bus\Queueable;
|
||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||
use Illuminate\Foundation\Bus\Dispatchable;
|
||||
use Illuminate\Queue\InteractsWithQueue;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
|
||||
class MergeLabelsIntoFileJob implements ShouldQueue
|
||||
{
|
||||
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
|
||||
|
||||
protected $labelIds;
|
||||
protected $id;
|
||||
protected $origin;
|
||||
protected $author_id;
|
||||
|
||||
/**
|
||||
* Create a new job instance.
|
||||
*
|
||||
* @param array $labels
|
||||
*/
|
||||
public function __construct(string $origin, string $id, array $labelIds, int $author_id)
|
||||
{
|
||||
$this->labelIds = $labelIds; // Store only the IDs
|
||||
$this->id = $id;
|
||||
$this->origin = $origin;
|
||||
$this->author_id = $author_id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the job.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function handle()
|
||||
{
|
||||
// Retrieve the Label instances using their IDs
|
||||
$labels = Label::whereIn('id', $this->labelIds)->get();
|
||||
$timestamp = uniqid() . "_" . now()->timestamp;
|
||||
$outputFilePath = storage_path("app/label_result_" . $timestamp . ".pdf");
|
||||
$cmd_string = "gs -sDEVICE=pdfwrite -dCompatibilityLevel=1.4 -dPDFSETTINGS=/default -dNOPAUSE -dQUIET -dBATCH -dAutoRotatePages=/None -dFIXEDMEDIA -dPDFFitPage -sPAPERSIZE=a4 -sOutputFile=$outputFilePath";
|
||||
$pdf_files = [];
|
||||
try {
|
||||
|
||||
|
||||
Log::channel('jobs')->info('MergeLabelsIntoFileJob started...');
|
||||
|
||||
foreach ($labels as $label) {
|
||||
|
||||
$timestamp = uniqid() . "_" . now()->timestamp;
|
||||
|
||||
// Log::info("processing - " . $label->id);
|
||||
$label_data_final = $this->addSticker($label);
|
||||
if (is_null($label_data_final)) {
|
||||
$label_data_final = $label->label_data;
|
||||
}
|
||||
|
||||
$filename = 'labels-' . $timestamp . '.pdf';
|
||||
Storage::disk('local')->put($filename, $label_data_final);
|
||||
$pdf_files[] = Storage::disk('local')->path($filename);
|
||||
|
||||
// Record shipment status
|
||||
ShipmentStatusHistory::create([
|
||||
'shipment_request_id' => $label->shipment->shipmentRequest->id,
|
||||
'shipment_status_id' => ShipmentStatus::STATUS_LABEL_CREATED,
|
||||
]);
|
||||
|
||||
}
|
||||
}
|
||||
catch (\Exception $e) {
|
||||
Log::error('MergeLabelsIntoFileJob failed label creation', [
|
||||
'error' => $e->getMessage(),
|
||||
'trace' => $e->getTraceAsString()
|
||||
]);
|
||||
throw $e;
|
||||
}
|
||||
|
||||
foreach ($pdf_files as $file) {
|
||||
if (!is_null($file)) {
|
||||
$cmd_string .= " " . $file;
|
||||
}
|
||||
}
|
||||
|
||||
Log::channel('jobs')->info('MergeLabelsIntoFileJob - gs command ---- ' . $cmd_string);
|
||||
|
||||
exec($cmd_string);
|
||||
|
||||
Log::channel('jobs')->info('MergeLabelsIntoFileJob --- Tmp files delete start');
|
||||
// Delete temporary PDF files after merging
|
||||
foreach ($pdf_files as $file) {
|
||||
if (file_exists($file)) {
|
||||
unlink($file);
|
||||
}
|
||||
}
|
||||
|
||||
Log::channel('jobs')->info('MergeLabelsIntoFileJob --- Tmp files deleted');
|
||||
|
||||
|
||||
$ret_file = file_get_contents($outputFilePath);
|
||||
if (file_exists($outputFilePath)) {
|
||||
unlink($outputFilePath);
|
||||
}
|
||||
|
||||
// Save file to the database
|
||||
GeneratedFile::create([
|
||||
'file_data' => base64_encode($ret_file),
|
||||
'user_id' => $this->author_id ?? 1,
|
||||
'filename' => $this->origin . "_" . $this->id,
|
||||
'associated_id' => $this->origin === "batch" ? $this->id : null,
|
||||
'lifetime' => now()->addHours(12), // Example lifetime, adjust as needed
|
||||
'type' => $this->origin,
|
||||
]);
|
||||
|
||||
ActionLog::create([
|
||||
'name' => "labels_printed",
|
||||
'description' => "Labels printed (file generated) for batch - '{$this->id}'",
|
||||
'user_id' => $this->author_id ?? 1
|
||||
]);
|
||||
|
||||
Label::whereIn('id', $this->labelIds)
|
||||
->where('temporary', true)
|
||||
->delete();
|
||||
|
||||
ActionLog::create([
|
||||
'name' => "labels_deleted_tmp",
|
||||
'description' => "Temporary labels deleted for batch - '{$this->id}'",
|
||||
'user_id' => $this->author_id ?? 1
|
||||
]);
|
||||
|
||||
if($this->origin === "batch") {
|
||||
$batch = ShipmentRequestBatch::where('id', $this->id)->first();
|
||||
|
||||
if ($batch) {
|
||||
$batch->update(['printed' => true]);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Mockup method for adding a sticker to label
|
||||
*
|
||||
* @param object $label
|
||||
* @return string|null
|
||||
*/
|
||||
private function addSticker(Label $label) {
|
||||
|
||||
try {
|
||||
$stickerPdfData = ExpediceStickersController::getStickerForLabel($label->shipment->shipmentRequest);
|
||||
$mainPdfData = $label->label_data;
|
||||
|
||||
$client = new Client();
|
||||
|
||||
// Define the command to execute
|
||||
$response = $client->post('http://pdf_manager:5000/combine-pdfs', [
|
||||
'multipart' => [
|
||||
[
|
||||
'name' => 'main_pdf',
|
||||
'contents' => $mainPdfData,
|
||||
'filename' => 'main.pdf'
|
||||
],
|
||||
[
|
||||
'name' => 'sticker_pdf',
|
||||
'contents' => $stickerPdfData,
|
||||
'filename' => 'sticker.pdf'
|
||||
],
|
||||
[
|
||||
'name' => 'resize',
|
||||
'contents' => in_array($label->shipment->shipmentRequest->carrier->carrierMaster->shortname, ["ups"]) ? 'true' : 'true'
|
||||
]
|
||||
]
|
||||
]);
|
||||
|
||||
return $response->getBody();
|
||||
|
||||
} catch (\Exception $e) {
|
||||
throw $e;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
69
src/app/Jobs/ProcessShipmentsJob.php
Normal file
69
src/app/Jobs/ProcessShipmentsJob.php
Normal file
@ -0,0 +1,69 @@
|
||||
<?php
|
||||
|
||||
namespace App\Jobs;
|
||||
|
||||
use App\Factories\CarrierServiceFactory;
|
||||
use App\Exceptions\ShipmentException;
|
||||
use App\Exceptions\ExternalApiException;
|
||||
use App\Models\ShipmentErrorLog;
|
||||
use Illuminate\Bus\Queueable;
|
||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||
use Illuminate\Foundation\Bus\Dispatchable;
|
||||
use Illuminate\Queue\InteractsWithQueue;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
|
||||
class ProcessShipmentsJob implements ShouldQueue
|
||||
{
|
||||
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
|
||||
|
||||
protected $shipments;
|
||||
|
||||
// public $tries = 5; // Number of retry attempts
|
||||
|
||||
public function __construct(array $shipments)
|
||||
{
|
||||
$this->shipments = $shipments;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine the time at which the job should timeout.
|
||||
*
|
||||
* @return \DateTime
|
||||
*/
|
||||
// public function retryUntil()
|
||||
// {
|
||||
// return now()->addMinutes(1); // Customize the retry time limit
|
||||
// }
|
||||
|
||||
public function handle()
|
||||
{
|
||||
foreach ($this->shipments as $shipmentData) {
|
||||
try {
|
||||
$carrierService = CarrierServiceFactory::create($shipmentData['carrier']);
|
||||
$label = $carrierService->genShipLabel($shipmentData);
|
||||
|
||||
if ($label) {
|
||||
// Store the label in the database or any other persistent storage
|
||||
}
|
||||
} catch (ShipmentException $e) {
|
||||
$this->storeError($shipmentData, $e->getMessage());
|
||||
throw $e;
|
||||
} catch (ExternalApiException $e) {
|
||||
$this->storeError($shipmentData, $e->getMessage());
|
||||
throw $e;
|
||||
} catch (\Exception $e) {
|
||||
$this->storeError($shipmentData, $e->getMessage());
|
||||
throw $e;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected function storeError($shipmentData, $errorMessage)
|
||||
{
|
||||
ShipmentErrorLog::create([
|
||||
'user_id' => 1,
|
||||
'shipment_reference' => 123,
|
||||
'error_message' => $errorMessage,
|
||||
]);
|
||||
}
|
||||
}
|
145
src/app/Jobs/ProcessStockWithdrawalItem.php
Normal file
145
src/app/Jobs/ProcessStockWithdrawalItem.php
Normal file
@ -0,0 +1,145 @@
|
||||
<?php
|
||||
|
||||
namespace App\Jobs;
|
||||
|
||||
use Illuminate\Bus\Queueable;
|
||||
use Illuminate\Support\Facades\Cache;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use Illuminate\Queue\InteractsWithQueue;
|
||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||
use Illuminate\Foundation\Bus\Dispatchable;
|
||||
use function App\Helpers\getProductID;
|
||||
|
||||
class ProcessStockWithdrawalItem implements ShouldQueue
|
||||
{
|
||||
use Dispatchable, InteractsWithQueue, Queueable;
|
||||
|
||||
protected $items;
|
||||
|
||||
/**
|
||||
* Create a new job instance.
|
||||
*
|
||||
* @param object $items
|
||||
*/
|
||||
public function __construct($items)
|
||||
{
|
||||
$this->items = $items;
|
||||
}
|
||||
|
||||
public function handle()
|
||||
{
|
||||
|
||||
$stock_first_array = [];
|
||||
$totalItems = $this->items->count();
|
||||
$processedCount = 0;
|
||||
$lastLoggedPercentage = 0;
|
||||
|
||||
foreach ($this->items as $item) {
|
||||
|
||||
try {
|
||||
|
||||
$processedCount++;
|
||||
|
||||
$groupedData = [];
|
||||
// Get eshop_id using getProductID function
|
||||
$eshop_product_id = getProductID($item->item_id_internal_warehouse);
|
||||
|
||||
if ($eshop_product_id) {
|
||||
// Group data by eshop_id
|
||||
if (!isset($groupedData[$eshop_product_id])) {
|
||||
$groupedData[$eshop_product_id] = [
|
||||
'eshop_id' => $eshop_product_id,
|
||||
'mapping_id' => $item->item_id_internal_warehouse,
|
||||
'total_quantity' => 0,
|
||||
'name' => $item->name,
|
||||
'model_number' => $item->model_number,
|
||||
'shipment_request_item_id' => $item->id,
|
||||
'stock_positions' => [],
|
||||
];
|
||||
}
|
||||
|
||||
// Accumulate total quantity
|
||||
$groupedData[$eshop_product_id]['total_quantity'] += $item->quantity;
|
||||
} else {
|
||||
Log::channel('storage_management')->warning("Item ID (eshop) not found for item", ['item' => $item]);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Fetch warehouse data for each grouped eshop_id
|
||||
foreach ($groupedData as $eshop_product_id => &$data) {
|
||||
$url = "https://sklad.dalkove-ovladace.cz/api/warehouse/2/{$eshop_product_id}?includeOutOfStock=true";
|
||||
$json = file_get_contents($url);
|
||||
$warehouseObj = json_decode($json);
|
||||
|
||||
$stockData = [];
|
||||
foreach ($warehouseObj as $warehouseItem) {
|
||||
foreach ($warehouseItem->warehouseItems as $position) {
|
||||
$arr = [
|
||||
'location' => $position->location,
|
||||
'count' => $position->count,
|
||||
'physicalItemName' => $warehouseItem->physicalItemName,
|
||||
'modelNumber' => $warehouseItem->modelNumber,
|
||||
];
|
||||
$stockData[$warehouseItem->physicalItemId][] = $arr;
|
||||
}
|
||||
}
|
||||
$data['stock_positions'] = $stockData;
|
||||
|
||||
|
||||
if (!isset($stock_first_array[$warehouseItem->physicalItemId])) {
|
||||
$stock_first_array[$warehouseItem->physicalItemId] = [
|
||||
"to_be_withdrawn" => 0,
|
||||
"name" => $warehouseItem->physicalItemName,
|
||||
"model" => $warehouseItem->modelNumber,
|
||||
"physicalItemId" => $warehouseItem->physicalItemId,
|
||||
"mapping_breakdown" => []
|
||||
];
|
||||
}
|
||||
|
||||
$stock_first_array[$warehouseItem->physicalItemId]['to_be_withdrawn'] += $data['total_quantity'];
|
||||
|
||||
|
||||
if (!isset($stock_first_array[$warehouseItem->physicalItemId]['mapping_breakdown'][$data['mapping_id']])) {
|
||||
$stock_first_array[$warehouseItem->physicalItemId]['mapping_breakdown'][$data['mapping_id']] = [
|
||||
'to_be_withdrawn' => 0,
|
||||
'name' => $data['name'],
|
||||
'model' => $data['model_number'],
|
||||
'mapping_id' => $data['mapping_id'],
|
||||
'shipment_request_item_ids' => []
|
||||
];
|
||||
}
|
||||
$stock_first_array[$warehouseItem->physicalItemId]['mapping_breakdown'][$data['mapping_id']]['to_be_withdrawn'] += $data['total_quantity'];
|
||||
$stock_first_array[$warehouseItem->physicalItemId]['mapping_breakdown'][$data['mapping_id']]['shipment_request_item_ids'][] = $data['shipment_request_item_id'];
|
||||
|
||||
$stock_first_array[$warehouseItem->physicalItemId]['mapping_breakdown'] = array_values($stock_first_array[$warehouseItem->physicalItemId]['mapping_breakdown']);
|
||||
}
|
||||
|
||||
$currentPercentage = intval(($processedCount / $totalItems) * 100);
|
||||
if ($currentPercentage >= $lastLoggedPercentage + 5) {
|
||||
$lastLoggedPercentage = $currentPercentage;
|
||||
Log::channel('storage_management')->info("Progress: {$currentPercentage}% completed.");
|
||||
}
|
||||
|
||||
|
||||
// Log::channel('storage_management')->info("Processed item successfully.", ['item' => $this->item]);
|
||||
|
||||
} catch (\Exception $e) {
|
||||
Log::channel('storage_management')->error("Failed to process item.", [
|
||||
'item' => $item,
|
||||
'url' => $url ?? null,
|
||||
'json_sklad' => $json ?? null,
|
||||
'error' => $e->getMessage(),
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
Cache::put('stock_withdrawal_data', $stock_first_array, now()->addDay()); // Cache for a day
|
||||
Cache::put('stock_withdrawal_data_last_update', now()->format('d-m-y H:i:s'), now()->addDay());
|
||||
Log::channel('storage_management')->info("Stock withdrawal items processed...", [
|
||||
'timestamp' => now()->format('d-m-y H:i:s'),
|
||||
]);
|
||||
|
||||
SortStockWithdrawal::dispatch()->onQueue('storage_management');
|
||||
|
||||
}
|
||||
}
|
384
src/app/Jobs/ReprintLabelsJob.php
Normal file
384
src/app/Jobs/ReprintLabelsJob.php
Normal file
@ -0,0 +1,384 @@
|
||||
<?php
|
||||
|
||||
namespace App\Jobs;
|
||||
|
||||
use App\Events\LabelsProcessed;
|
||||
use App\Exceptions\ExternalApiException;
|
||||
use App\Exceptions\LabelPrintException;
|
||||
use App\Exceptions\ShipmentException;
|
||||
use App\Factories\CarrierServiceFactory;
|
||||
use App\Http\Controllers\ExpediceStickersController;
|
||||
use App\Models\CarrierExtraFeeTypes;
|
||||
use App\Models\GeneratedFile;
|
||||
use App\Models\Label;
|
||||
use App\Models\ShipmentErrorLog;
|
||||
use App\Models\ShipmentProcessedBaseFee;
|
||||
use App\Models\ShipmentProcessedExtraFee;
|
||||
use App\Models\ShipmentRequest;
|
||||
use App\Models\ShipmentRequestBatch;
|
||||
use App\Models\ShipmentStatus;
|
||||
use App\Models\ShipmentStatusHistory;
|
||||
use App\Models\User;
|
||||
use GuzzleHttp\Client;
|
||||
use GuzzleHttp\Exception\GuzzleException;
|
||||
use Illuminate\Bus\Queueable;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
use Illuminate\Queue\InteractsWithQueue;
|
||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||
use Illuminate\Foundation\Bus\Dispatchable;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
|
||||
class ReprintLabelsJob implements ShouldQueue
|
||||
{
|
||||
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
|
||||
|
||||
protected $shipmentIds;
|
||||
protected $batchIds;
|
||||
protected $sendAgain;
|
||||
protected $userId;
|
||||
protected $origin;
|
||||
protected $ids_text;
|
||||
|
||||
/**
|
||||
* Create a new job instance.
|
||||
*
|
||||
* @param array|null $shipmentIds
|
||||
* @param array|null $batchIds
|
||||
* @param bool $sendAgain
|
||||
* @param int|null $userId
|
||||
*/
|
||||
public function __construct($shipmentIds = null, $batchIds = null, $sendAgain = false, $userId = null)
|
||||
{
|
||||
$this->shipmentIds = $shipmentIds;
|
||||
$this->batchIds = $batchIds;
|
||||
$this->sendAgain = $sendAgain;
|
||||
$this->userId = $userId;
|
||||
}
|
||||
|
||||
public function handle()
|
||||
{
|
||||
$user = User::find($this->userId);
|
||||
|
||||
Log::channel('expedice')->info('ReprintLabelsJob started by - ' . $user->name, [
|
||||
'shipment_request_ids' => $this->shipmentIds ?? null,
|
||||
'batch_ids' => $this->batchIds ?? null,
|
||||
|
||||
]);
|
||||
|
||||
$this->origin = "";
|
||||
|
||||
|
||||
if ($this->batchIds !== null) {
|
||||
$this->origin = "batch";
|
||||
$batchIds = $this->batchIds;
|
||||
$shipmentRequests = ShipmentRequest::with(['items', 'shipment'])
|
||||
->whereHas('shipment')
|
||||
->whereHas('batch', function ($query) use ($batchIds) {
|
||||
$query->whereIn('id', $batchIds);
|
||||
})
|
||||
->get();
|
||||
} elseif ($this->shipmentIds !== null) {
|
||||
$this->origin = "shipments";
|
||||
$shipmentRequests = ShipmentRequest::with(['items', 'shipment'])
|
||||
->whereHas('shipment')
|
||||
->whereIn('id', $this->shipmentIds)
|
||||
->get();
|
||||
}
|
||||
|
||||
foreach ($shipmentRequests as $shipment_request) {
|
||||
|
||||
|
||||
// Instantiate the correct carrier service dynamically for each shipment
|
||||
$carrierService = CarrierServiceFactory::create($shipment_request->carrier);
|
||||
try {
|
||||
$label = $carrierService->reprintLabel($shipment_request->shipment);
|
||||
// $label = Label::storeEntry($carrierService->getLabel($shipment_request->shipment), $shipment_request->shipment);
|
||||
|
||||
if ($label) {
|
||||
$labels[] = $label;
|
||||
}
|
||||
} catch (ExternalApiException|LabelPrintException|ShipmentException|GuzzleException|\Exception $e) {
|
||||
$errors[] = [
|
||||
'shipmentReference' => $shipment_request->shipment_reference,
|
||||
'error' => $e->getMessage(),
|
||||
];
|
||||
|
||||
ShipmentErrorLog::create(
|
||||
array(
|
||||
"shipment_request_id" => $shipment_request->id,
|
||||
"error_message" => $e->getMessage()
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if ($this->batchIds !== null) {
|
||||
$this->origin = "batch";
|
||||
$batchIds = $this->batchIds;
|
||||
$shipmentRequests = ShipmentRequest::with('items')
|
||||
->whereDoesntHave('shipment')
|
||||
->whereHas('batch', function ($query) use ($batchIds) {
|
||||
$query->whereIn('id', $batchIds);
|
||||
})
|
||||
->get();
|
||||
} elseif ($this->shipmentIds !== null) {
|
||||
$this->origin = "shipments";
|
||||
$shipmentRequests = ShipmentRequest::with('items')
|
||||
->whereDoesntHave('shipment')
|
||||
->whereIn('id', $this->shipmentIds)
|
||||
->get();
|
||||
}
|
||||
|
||||
|
||||
foreach ($shipmentRequests as $shipment_request) {
|
||||
|
||||
|
||||
// Instantiate the correct carrier service dynamically for each shipment
|
||||
$carrierService = CarrierServiceFactory::create($shipment_request->carrier);
|
||||
|
||||
try {
|
||||
$label = $carrierService->genShipLabel($shipment_request);
|
||||
|
||||
if ($label) {
|
||||
$labels[] = $label;
|
||||
}
|
||||
|
||||
if ($this->sendAgain === true) {
|
||||
|
||||
$shipment = $shipment_request->shipment;
|
||||
|
||||
if (!$shipment) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$shipment_price_object = $shipment_request->carrier->userCarrierPricings()->whereHas('user', function ($query) use ($shipment_request) {
|
||||
$query->where('id', $shipment_request->user->id);
|
||||
})->first();
|
||||
|
||||
|
||||
if (!$shipment_price_object) {
|
||||
$shipment_price_object = $shipment_request->carrier->basePricing()->first();
|
||||
$user_carrier_grp_uplift = $shipment_request->user->userCarrierPricingGroup->carrierPricingGroup->base_ratio;
|
||||
$shipment_price = round($shipment_price_object->getPriceForWeight((float)$shipment_request->weight) + ($shipment_price_object->getPriceForWeight((float)$shipment_request->weight) * ($user_carrier_grp_uplift / 100)), 0);
|
||||
} else {
|
||||
$shipment_price = $shipment_price_object->getPriceForWeight((float)$shipment_request->weight);
|
||||
}
|
||||
|
||||
|
||||
// Prepare the base fees and extra fees
|
||||
$baseFeesData = [
|
||||
'shipment_id' => $shipment->id,
|
||||
'base_fee_type' => 'shipping',
|
||||
'base_fee_value' => $shipment_price,
|
||||
'base_fee_desc' => 'Shipping price',
|
||||
];
|
||||
|
||||
ShipmentProcessedBaseFee::create($baseFeesData);
|
||||
|
||||
if (isset($shipment_request->cod_value) && is_numeric($shipment_request->cod_value) && $shipment_request->cod_value >= 0) {
|
||||
|
||||
$cod_extra_fee_type = CarrierExtraFeeTypes::where('name', 'COD')->first();
|
||||
$extraFeesData = [
|
||||
'shipment_id' => $shipment->id,
|
||||
'extra_fee_type_id' => $cod_extra_fee_type->id,
|
||||
'extra_fee_type_value' => $shipment_price_object->extraFees()->where('carrier_extra_fee_type_id', $cod_extra_fee_type->id)->value('carrier_extra_fee_value'),
|
||||
'extra_fee_desc' => "COD fee",
|
||||
];
|
||||
|
||||
ShipmentProcessedExtraFee::create($extraFeesData);
|
||||
}
|
||||
}
|
||||
|
||||
} catch (ExternalApiException|LabelPrintException|ShipmentException|GuzzleException|\Exception $e) {
|
||||
$errors[] = [
|
||||
'shipmentReference' => $shipment_request->shipment_reference,
|
||||
'error' => $e->getMessage(),
|
||||
];
|
||||
|
||||
ShipmentErrorLog::create(
|
||||
array(
|
||||
"shipment_request_id" => $shipment_request->id,
|
||||
"error_message" => $e->getMessage()
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if (!empty($errors)) {
|
||||
broadcast(new LabelsProcessed($this->userId, [
|
||||
'errors' => array_unshift($errors, 'ALERT - Some shipments [ ' . sizeof($errors) . '/' . $shipmentRequests->count() . ' ] had errors --- '),
|
||||
'error_code' => 400,
|
||||
]));
|
||||
|
||||
// Throw an exception to mark the job as failed
|
||||
// throw new \Exception('Job failed due to errors: ' . json_encode($errors));
|
||||
}
|
||||
|
||||
|
||||
if (isset($labels)) {
|
||||
try {
|
||||
if ($this->origin === "batch") {
|
||||
$this->ids_text = implode("_", $this->batchIds);
|
||||
} else {
|
||||
$this->ids_text = implode("_", $this->shipmentIds);
|
||||
}
|
||||
|
||||
$file_id = $this->mergeLabelsIntoFile($labels);
|
||||
|
||||
|
||||
broadcast(new LabelsProcessed($this->userId, [
|
||||
'message' => 'Labels created sucessfully',
|
||||
'file_id' => $file_id,
|
||||
'filename' => $this->ids_text,
|
||||
'origin' => $this->origin,
|
||||
]));
|
||||
return true;
|
||||
} catch (\Exception $e) {
|
||||
broadcast(new LabelsProcessed($this->userId, [
|
||||
'error' => $e->getMessage(),
|
||||
'error_code' => 400
|
||||
]));
|
||||
|
||||
// Throw an exception to mark the job as failed
|
||||
throw new \Exception('Job failed due to Exception: ' . $e->getMessage());
|
||||
}
|
||||
|
||||
} else {
|
||||
broadcast(new LabelsProcessed($this->userId, [
|
||||
'errors' => $errors,
|
||||
'error_code' => 400
|
||||
]));
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private function mergeLabelsIntoFile($labels)
|
||||
{
|
||||
// Retrieve the Label instances using their IDs
|
||||
$timestamp = uniqid() . "_" . now()->timestamp;
|
||||
$outputFilePath = storage_path("app/label_result_" . $timestamp . ".pdf");
|
||||
$cmd_string = "gs -sDEVICE=pdfwrite -dCompatibilityLevel=1.4 -dPDFSETTINGS=/default -dNOPAUSE -dQUIET -dBATCH -dAutoRotatePages=/None -dFIXEDMEDIA -dPDFFitPage -sPAPERSIZE=a4 -sOutputFile=$outputFilePath";
|
||||
$pdf_files = [];
|
||||
try {
|
||||
|
||||
|
||||
Log::channel('expedice')->info('MergeLabelsIntoFile inside ReprintLabelsJob started...');
|
||||
|
||||
foreach ($labels as $label) {
|
||||
|
||||
$timestamp = uniqid() . "_" . now()->timestamp;
|
||||
|
||||
// Log::info("processing - " . $label->id);
|
||||
$label_data_final = $this->addSticker($label);
|
||||
if (is_null($label_data_final)) {
|
||||
$label_data_final = $label->label_data;
|
||||
}
|
||||
|
||||
$filename = 'labels-' . $timestamp . '.pdf';
|
||||
Storage::disk('local')->put($filename, $label_data_final);
|
||||
$pdf_files[] = Storage::disk('local')->path($filename);
|
||||
|
||||
// Record shipment status
|
||||
ShipmentStatusHistory::create([
|
||||
'shipment_request_id' => $label->shipment->shipmentRequest->id,
|
||||
'shipment_status_id' => ShipmentStatus::STATUS_LABEL_CREATED,
|
||||
]);
|
||||
|
||||
}
|
||||
} catch (\Exception $e) {
|
||||
Log::channel('expedice')->error('MergeLabelsIntoFile function in ReprintLabelsJob failed label creation', [
|
||||
'error' => $e->getMessage(),
|
||||
'trace' => $e->getTraceAsString()
|
||||
]);
|
||||
throw $e;
|
||||
}
|
||||
|
||||
foreach ($pdf_files as $file) {
|
||||
if (!is_null($file)) {
|
||||
$cmd_string .= " " . $file;
|
||||
}
|
||||
}
|
||||
|
||||
Log::channel('jobs')->info('MergeLabelsIntoFile function in ReprintLabelsJob - gs command ---- ' . $cmd_string);
|
||||
|
||||
exec($cmd_string);
|
||||
|
||||
Log::channel('jobs')->info('MergeLabelsIntoFile function in ReprintLabelsJob --- Tmp files delete start');
|
||||
// Delete temporary PDF files after merging
|
||||
foreach ($pdf_files as $file) {
|
||||
if (file_exists($file)) {
|
||||
unlink($file);
|
||||
}
|
||||
}
|
||||
|
||||
Log::channel('jobs')->info('MergeLabelsIntoFile function in ReprintLabelsJob --- Tmp files deleted');
|
||||
|
||||
|
||||
$ret_file = file_get_contents($outputFilePath);
|
||||
if (file_exists($outputFilePath)) {
|
||||
unlink($outputFilePath);
|
||||
}
|
||||
|
||||
// Save file to the database
|
||||
$newFile = GeneratedFile::create([
|
||||
'file_data' => base64_encode($ret_file),
|
||||
'user_id' => $this->userId ?? 1,
|
||||
'filename' => $this->origin . "_" . $this->ids_text,
|
||||
'associated_id' => $this->origin === "batch" ? $this->ids_text : null,
|
||||
'lifetime' => now()->addHours(12), // Example lifetime, adjust as needed
|
||||
'type' => $this->origin,
|
||||
]);
|
||||
|
||||
Label::whereIn('id', collect($labels)->pluck('id')->toArray())
|
||||
->where('temporary', true)
|
||||
->delete();
|
||||
|
||||
if ($this->origin === "batch") {
|
||||
$batch = ShipmentRequestBatch::where('id', $this->ids_text)->first();
|
||||
|
||||
if ($batch) {
|
||||
$batch->update(['printed' => true]);
|
||||
}
|
||||
}
|
||||
|
||||
return $newFile->id;
|
||||
}
|
||||
|
||||
private function addSticker(Label $label) {
|
||||
|
||||
try {
|
||||
$stickerPdfData = ExpediceStickersController::getStickerForLabel($label->shipment->shipmentRequest);
|
||||
$mainPdfData = $label->label_data;
|
||||
|
||||
$client = new Client();
|
||||
|
||||
// Define the command to execute
|
||||
$response = $client->post('http://pdf_manager:5000/combine-pdfs', [
|
||||
'multipart' => [
|
||||
[
|
||||
'name' => 'main_pdf',
|
||||
'contents' => $mainPdfData,
|
||||
'filename' => 'main.pdf'
|
||||
],
|
||||
[
|
||||
'name' => 'sticker_pdf',
|
||||
'contents' => $stickerPdfData,
|
||||
'filename' => 'sticker.pdf'
|
||||
],
|
||||
[
|
||||
'name' => 'resize',
|
||||
'contents' => in_array($label->shipment->shipmentRequest->carrier->carrierMaster->shortname, ["ups"]) ? 'true' : 'true'
|
||||
]
|
||||
]
|
||||
]);
|
||||
|
||||
return $response->getBody();
|
||||
|
||||
} catch (\Exception $e) {
|
||||
throw $e;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
54
src/app/Jobs/ResendShipmentCallbacks.php
Normal file
54
src/app/Jobs/ResendShipmentCallbacks.php
Normal file
@ -0,0 +1,54 @@
|
||||
<?php
|
||||
|
||||
namespace App\Jobs;
|
||||
|
||||
use App\Models\Shipment;
|
||||
use App\Models\ShipmentRequest;
|
||||
use App\Services\CallbackService;
|
||||
use Illuminate\Bus\Queueable;
|
||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||
use Illuminate\Foundation\Bus\Dispatchable;
|
||||
use Illuminate\Queue\InteractsWithQueue;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
use Illuminate\Support\Facades\App;
|
||||
use Carbon\Carbon;
|
||||
|
||||
class ResendShipmentCallbacks implements ShouldQueue
|
||||
{
|
||||
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
|
||||
|
||||
/**
|
||||
* Execute the job.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function handle()
|
||||
{
|
||||
$callbackService = App::make(CallbackService::class);
|
||||
|
||||
// 420282, 420900, 421010, 421017, 421332, 421490, 421591, 421649, 421629, 421625, 421630, 421626, 419164, 421615, 421634, 421099, 421638
|
||||
// Query shipments created today
|
||||
// $shipments = Shipment::whereDate('created_at', Carbon::today())->get();
|
||||
$shipmentRequests = ShipmentRequest::whereIn('shipment_reference', [420282, 420900, 421010, 421017, 421332, 421490, 421591, 421649, 421629, 421625, 421630, 421626, 419164, 421615, 421634, 421099, 421638])
|
||||
->with('shipment')
|
||||
->get();
|
||||
|
||||
$shipments = $shipmentRequests->pluck('shipment')->filter();
|
||||
|
||||
|
||||
foreach ($shipments as $shipment) {
|
||||
if ($shipment->user->details->apiCallbackURL && $shipment->user->details->callbacks_enabled) {
|
||||
$callbackService->sendCallback($shipment->user->details->apiCallbackURL, [
|
||||
'endpoint' => 'shipmentProcessed',
|
||||
'result' => 'true',
|
||||
'shipment_reference' => $shipment->shipmentRequest->shipment_reference,
|
||||
'data' => [
|
||||
'trackingNumber' => $shipment->tracking_number,
|
||||
'trackingURL' => $shipment->tracking_url,
|
||||
],
|
||||
'additionalInfo' => null
|
||||
]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
112
src/app/Jobs/RetryCallback.php
Normal file
112
src/app/Jobs/RetryCallback.php
Normal file
@ -0,0 +1,112 @@
|
||||
<?php
|
||||
|
||||
namespace App\Jobs;
|
||||
|
||||
use Illuminate\Bus\Queueable;
|
||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||
use Illuminate\Foundation\Bus\Dispatchable;
|
||||
use Illuminate\Queue\InteractsWithQueue;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
use GuzzleHttp\Client;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
|
||||
class RetryCallback implements ShouldQueue
|
||||
{
|
||||
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
|
||||
|
||||
public $url;
|
||||
public $data;
|
||||
public $retryCount;
|
||||
|
||||
/**
|
||||
* Create a new job instance.
|
||||
*
|
||||
* @param string $url
|
||||
* @param array $data
|
||||
* @param int $retryCount
|
||||
*/
|
||||
public function __construct($url, $data, $retryCount = 0)
|
||||
{
|
||||
$this->url = $url;
|
||||
$this->data = $data;
|
||||
$this->retryCount = $retryCount;
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the job.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function handle()
|
||||
{
|
||||
$client = new Client();
|
||||
try {
|
||||
$response = $client->post($this->url, [
|
||||
'json' => $this->data,
|
||||
'verify' => false,
|
||||
]);
|
||||
|
||||
$responseCode = $response->getStatusCode();
|
||||
$responseBody = trim(strtolower($response->getBody()->getContents()));
|
||||
|
||||
// Remove BOM if present
|
||||
$responseBody = preg_replace('/^\xEF\xBB\xBF/', '', $responseBody);
|
||||
|
||||
if ($responseBody === 'true') {
|
||||
Log::channel('callbacks')->info("Callback confirmed", [
|
||||
'url' => $this->url,
|
||||
'data' => $this->data,
|
||||
'response' => $responseBody,
|
||||
]);
|
||||
} else {
|
||||
Log::channel('callbacks')->warning("Callback not confirmed, adding to queue", [
|
||||
'data' => $this->data,
|
||||
"url" => $this->url,
|
||||
"response" => $responseBody,
|
||||
"response_hex" => bin2hex($responseBody),
|
||||
]);
|
||||
$this->requeueJob();
|
||||
}
|
||||
} catch (\Exception $e) {
|
||||
Log::channel('callbacks')->error("Callback failed", [
|
||||
'url' => $this->url,
|
||||
'data' => $this->data,
|
||||
'error' => $e->getMessage(),
|
||||
]);
|
||||
|
||||
$this->requeueJob();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Requeue the job with an increasing delay.
|
||||
*/
|
||||
private function requeueJob()
|
||||
{
|
||||
if ($this->retryCount < 3) {
|
||||
$delay = match ($this->retryCount) {
|
||||
0 => now()->addHour(),
|
||||
1 => now()->addHours(2),
|
||||
2 => now()->addHours(4),
|
||||
default => null,
|
||||
};
|
||||
|
||||
Log::channel('callbacks')->warning("Re-queuing callback", [
|
||||
'url' => $this->url,
|
||||
'data' => $this->data,
|
||||
'retry_count' => $this->retryCount + 1,
|
||||
'next_attempt_in' => $delay,
|
||||
]);
|
||||
|
||||
// Dispatch to the `callback_retry` queue
|
||||
self::dispatch($this->url, $this->data, $this->retryCount + 1)
|
||||
->delay($delay)
|
||||
->onQueue('callback_retry');
|
||||
} else {
|
||||
Log::channel('callbacks')->error("Callback retries exhausted", [
|
||||
'url' => $this->url,
|
||||
'data' => $this->data,
|
||||
]);
|
||||
}
|
||||
}
|
||||
}
|
51
src/app/Jobs/SortStockWithdrawal.php
Normal file
51
src/app/Jobs/SortStockWithdrawal.php
Normal file
@ -0,0 +1,51 @@
|
||||
<?php
|
||||
|
||||
namespace App\Jobs;
|
||||
|
||||
use Illuminate\Bus\Queueable;
|
||||
use Illuminate\Foundation\Bus\Dispatchable;
|
||||
use Illuminate\Queue\InteractsWithQueue;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
use Illuminate\Support\Facades\Cache;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||
|
||||
class SortStockWithdrawal implements ShouldQueue
|
||||
{
|
||||
|
||||
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
|
||||
/**
|
||||
* Handle the job execution.
|
||||
*/
|
||||
|
||||
public function handle()
|
||||
{
|
||||
try {
|
||||
|
||||
Log::channel('storage_management')->info("Stock withdrawal sorting...", [
|
||||
'timestamp' => now()->format('d-m-y H:i:s'),
|
||||
]);
|
||||
|
||||
$stock_first_array = Cache::get('stock_withdrawal_data', []);
|
||||
|
||||
usort($stock_first_array, function ($a, $b) {
|
||||
return $b['to_be_withdrawn'] <=> $a['to_be_withdrawn'];
|
||||
});
|
||||
|
||||
Cache::put('stock_withdrawal_data', $stock_first_array, now()->addDay());
|
||||
Cache::put('stock_withdrawal_data_last_update', now()->format('d-m-y H:i:s'), now()->addDay());
|
||||
|
||||
Log::channel('storage_management')->info("Stock withdrawal sorting done!", [
|
||||
'timestamp' => now()->format('d-m-y H:i:s'),
|
||||
]);
|
||||
}
|
||||
catch (\Exception $e) {
|
||||
Log::channel('storage_management')->error("FAILED - Stock withdrawal sorting failed!", [
|
||||
'timestamp' => now()->format('d-m-y H:i:s'),
|
||||
'error' => $e->getMessage(),
|
||||
]);
|
||||
throw $e;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
22
src/app/Jobs/StartStockWithdrawal.php
Normal file
22
src/app/Jobs/StartStockWithdrawal.php
Normal file
@ -0,0 +1,22 @@
|
||||
<?php
|
||||
|
||||
namespace App\Jobs;
|
||||
|
||||
use App\Models\Shipment;
|
||||
use App\Models\ShipmentTrackingStatus;
|
||||
use App\Models\ShipmentTrackingStatusHistory;
|
||||
use Carbon\Carbon;
|
||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
|
||||
class StartStockWithdrawal implements ShouldQueue
|
||||
{
|
||||
public function __invoke()
|
||||
{
|
||||
Log::channel('storage_management')->info('----------------------------------------------------');
|
||||
|
||||
UpdateStockWithdrawalCache::dispatch()->onQueue('storage_management');
|
||||
|
||||
Log::channel('storage_management')->info('----------------------------------------------------');
|
||||
}
|
||||
}
|
55
src/app/Jobs/UpdateStockWithdrawalCache.php
Normal file
55
src/app/Jobs/UpdateStockWithdrawalCache.php
Normal file
@ -0,0 +1,55 @@
|
||||
<?php
|
||||
|
||||
namespace App\Jobs;
|
||||
|
||||
use Illuminate\Bus\Queueable;
|
||||
use Illuminate\Foundation\Bus\Dispatchable;
|
||||
use Illuminate\Queue\InteractsWithQueue;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
use Illuminate\Support\Facades\Cache;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||
|
||||
class UpdateStockWithdrawalCache implements ShouldQueue
|
||||
{
|
||||
|
||||
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
|
||||
/**
|
||||
* Handle the job execution.
|
||||
*/
|
||||
|
||||
public function handle()
|
||||
{
|
||||
Log::channel('storage_management')->info("Stock withdrawal cache update start.", [
|
||||
'timestamp' => now()->format('d-m-y H:i'),
|
||||
]);
|
||||
|
||||
// Clear the cache
|
||||
Cache::forget('stock_withdrawal_data');
|
||||
Cache::forget('stock_withdrawal_data_last_update');
|
||||
|
||||
// Retrieve items from the database
|
||||
$items = DB::table('items_storage_removal as isr')
|
||||
->join('shipment_request_items as sri', 'isr.shipment_request_item_id', '=', 'sri.id')
|
||||
->where('isr.entry_processed', 0)
|
||||
->select('sri.item_id_internal_warehouse', 'sri.quantity', 'sri.name', 'sri.model_number', 'sri.id')
|
||||
->get();
|
||||
|
||||
// Dispatch a job for each item
|
||||
ProcessStockWithdrawalItem::dispatch($items)->onQueue('storage_management');
|
||||
|
||||
|
||||
Log::channel('storage_management')->info("Stock withdrawal job dispatched.", [
|
||||
'timestamp' => now()->format('d-m-y H:i'),
|
||||
]);
|
||||
|
||||
// SortStockWithdrawal::dispatch()->onQueue('storage_management')->delay(now()->addMinutes(10));
|
||||
//
|
||||
// Log::channel('storage_management')->info("Stock withdrawal sorting job dispatched to be executed in 10 minutes..", [
|
||||
// 'timestamp' => now()->format('d-m-y H:i'),
|
||||
// ]);
|
||||
|
||||
}
|
||||
|
||||
}
|
31
src/app/Models/ActionLog.php
Normal file
31
src/app/Models/ActionLog.php
Normal file
@ -0,0 +1,31 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
class ActionLog extends Model
|
||||
{
|
||||
use HasFactory;
|
||||
|
||||
protected $table = 'action_log';
|
||||
|
||||
protected $fillable = [
|
||||
'name',
|
||||
'description',
|
||||
'user_id',
|
||||
'shipment_request_id'
|
||||
];
|
||||
|
||||
public function user()
|
||||
{
|
||||
return $this->belongsTo(User::class, 'user_id');
|
||||
}
|
||||
|
||||
public function shipmentRequest()
|
||||
{
|
||||
return $this->belongsTo(ShipmentRequest::class, 'shipment_request_id');
|
||||
}
|
||||
|
||||
}
|
62
src/app/Models/Carrier.php
Normal file
62
src/app/Models/Carrier.php
Normal file
@ -0,0 +1,62 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
class Carrier extends Model
|
||||
{
|
||||
protected $table = 'carrier';
|
||||
|
||||
// Use the other database connection
|
||||
protected $connection = 'vat_pps';
|
||||
|
||||
protected $fillable = [
|
||||
'internal',
|
||||
'ext_id',
|
||||
'carrier_name',
|
||||
'carrier_shortname',
|
||||
'pickup_points',
|
||||
'carrier_contract',
|
||||
'customs_declarations',
|
||||
'cod_allowed',
|
||||
'cod_enabled',
|
||||
'cod_price',
|
||||
'country',
|
||||
'zone_id',
|
||||
'currency',
|
||||
'img',
|
||||
'enabled_store',
|
||||
'shipping_price',
|
||||
'free_shipping_enabled',
|
||||
'delivery_time',
|
||||
'display_order',
|
||||
'api_allowed',
|
||||
'carrier_master_id',
|
||||
'carrier_base_pricing_id',
|
||||
];
|
||||
|
||||
// If you want to disable timestamps (created_at, updated_at)
|
||||
public $timestamps = false;
|
||||
|
||||
public function shipmentRequests()
|
||||
{
|
||||
return $this->hasMany(ShipmentRequest::class, 'carrier_id');
|
||||
}
|
||||
|
||||
public function carrierMaster()
|
||||
{
|
||||
return $this->belongsTo(CarrierMaster::class, 'carrier_master_id');
|
||||
}
|
||||
|
||||
public function userCarrierPricings()
|
||||
{
|
||||
return $this->hasMany(UserCarrierPricing::class, 'carrier_id');
|
||||
}
|
||||
|
||||
public function basePricing()
|
||||
{
|
||||
return $this->hasOne(CarrierBasePricing::class, 'carrier_id');
|
||||
}
|
||||
|
||||
}
|
113
src/app/Models/CarrierBasePricing.php
Normal file
113
src/app/Models/CarrierBasePricing.php
Normal file
@ -0,0 +1,113 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
class CarrierBasePricing extends Model
|
||||
{
|
||||
use HasFactory;
|
||||
|
||||
// Define the table name if it doesn't follow Laravel's naming convention
|
||||
protected $table = 'carrier_base_pricing';
|
||||
|
||||
protected $connection = 'mariadb';
|
||||
|
||||
// Specify the columns that can be mass-assigned
|
||||
protected $fillable = [
|
||||
'carrier_id',
|
||||
'cod_price',
|
||||
];
|
||||
|
||||
|
||||
public function pricingWeights()
|
||||
{
|
||||
return $this->hasMany(CarrierBasePricing2Weight::class, 'carrier_base_pricing_id');
|
||||
}
|
||||
|
||||
public function extraFees()
|
||||
{
|
||||
return $this->hasMany(CarrierBasePricingExtraFees::class, 'carrier_base_pricing_id');
|
||||
}
|
||||
|
||||
public function carrier()
|
||||
{
|
||||
return $this->belongsTo(Carrier::class, 'carrier_id');
|
||||
}
|
||||
|
||||
public function getPriceForWeight($weight)
|
||||
{
|
||||
$price = null;
|
||||
|
||||
$sortedWeights = $this->pricingWeights->sortByDesc('weight_max');
|
||||
|
||||
foreach ($sortedWeights as $pricingWeight) {
|
||||
if ($weight <= $pricingWeight->weight_max) {
|
||||
$price = $pricingWeight->shipping_price;
|
||||
}
|
||||
}
|
||||
|
||||
return $price;
|
||||
}
|
||||
|
||||
|
||||
public function addCodExtraFee()
|
||||
{
|
||||
// Fetch the 'COD' extra fee type ID
|
||||
$codFeeType = CarrierExtraFeeTypes::where('name', 'COD')->first();
|
||||
|
||||
if (!$codFeeType) {
|
||||
// Handle the case where the 'COD' type does not exist
|
||||
throw new \Exception("COD fee type not found.");
|
||||
}
|
||||
|
||||
// Create a new entry in UserCarrierPricingExtraFees
|
||||
CarrierBasePricingExtraFees::create([
|
||||
'carrier_base_pricing_id' => $this->id,
|
||||
'carrier_extra_fee_type_id' => $codFeeType->id,
|
||||
'carrier_extra_fee_value' => $this->cod_price, // Use cod_price from current UserCarrierPricing instance
|
||||
'carrier_extra_fee_value_type' => 'monetary', // Set value type as 'monetary'
|
||||
]);
|
||||
}
|
||||
|
||||
public function updateCodExtraFee()
|
||||
{
|
||||
// Fetch the 'COD' extra fee type ID
|
||||
$codFeeType = CarrierExtraFeeTypes::where('name', 'COD')->first();
|
||||
|
||||
if (!$codFeeType) {
|
||||
// Handle the case where the 'COD' type does not exist
|
||||
throw new \Exception("COD fee type not found.");
|
||||
}
|
||||
|
||||
// Find the existing extra fee entry
|
||||
$extraFee = CarrierBasePricingExtraFees::where('carrier_base_pricing_id', $this->id)
|
||||
->where('carrier_extra_fee_type_id', $codFeeType->id)
|
||||
->first();
|
||||
|
||||
if ($extraFee) {
|
||||
// Update the existing entry
|
||||
$extraFee->update([
|
||||
'carrier_extra_fee_value' => $this->cod_price, // Update with the new cod_price
|
||||
]);
|
||||
} else {
|
||||
// If no entry exists, create a new one
|
||||
$this->addCodExtraFee();
|
||||
}
|
||||
}
|
||||
|
||||
protected static function booted()
|
||||
{
|
||||
parent::boot();
|
||||
|
||||
static::created(function ($userCarrierPricing) {
|
||||
$userCarrierPricing->addCodExtraFee();
|
||||
});
|
||||
|
||||
static::updated(function ($userCarrierPricing) {
|
||||
$userCarrierPricing->updateCodExtraFee();
|
||||
});
|
||||
}
|
||||
|
||||
}
|
29
src/app/Models/CarrierBasePricing2Weight.php
Normal file
29
src/app/Models/CarrierBasePricing2Weight.php
Normal file
@ -0,0 +1,29 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
class CarrierBasePricing2Weight extends Model
|
||||
{
|
||||
use HasFactory;
|
||||
|
||||
// Define the table name if it doesn't follow Laravel's naming convention
|
||||
protected $table = 'carrier_base_pricing2weight';
|
||||
|
||||
protected $connection = 'mariadb';
|
||||
|
||||
// Specify the columns that can be mass-assigned
|
||||
protected $fillable = [
|
||||
'carrier_base_pricing_id',
|
||||
'weight_max',
|
||||
'shipping_price',
|
||||
];
|
||||
|
||||
// Relationship with the CarrierBasePricing model
|
||||
public function carrierBasePricing()
|
||||
{
|
||||
return $this->belongsTo(CarrierBasePricing::class, 'carrier_base_pricing_id');
|
||||
}
|
||||
}
|
37
src/app/Models/CarrierBasePricingExtraFees.php
Normal file
37
src/app/Models/CarrierBasePricingExtraFees.php
Normal file
@ -0,0 +1,37 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
class CarrierBasePricingExtraFees extends Model
|
||||
{
|
||||
use HasFactory;
|
||||
|
||||
// Define the table name if it doesn't follow Laravel's naming convention
|
||||
protected $table = 'carrier_base_pricing_extra_fees';
|
||||
|
||||
protected $connection = 'mariadb';
|
||||
|
||||
// Specify the columns that can be mass-assigned
|
||||
protected $fillable = [
|
||||
'carrier_base_pricing_id',
|
||||
'carrier_extra_fee_type_id',
|
||||
'carrier_extra_fee_value',
|
||||
'carrier_extra_fee_value_type',
|
||||
];
|
||||
|
||||
|
||||
// Relationship with the CarrierBasePricing model
|
||||
public function carrierBasePricing()
|
||||
{
|
||||
return $this->belongsTo(CarrierBasePricing::class, 'carrier_base_pricing_id');
|
||||
}
|
||||
|
||||
public function extraFeeType()
|
||||
{
|
||||
return $this->belongsTo(CarrierExtraFeeTypes::class, 'carrier_extra_fee_type_id');
|
||||
}
|
||||
|
||||
}
|
23
src/app/Models/CarrierExtraFeeTypes.php
Normal file
23
src/app/Models/CarrierExtraFeeTypes.php
Normal file
@ -0,0 +1,23 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
class CarrierExtraFeeTypes extends Model
|
||||
{
|
||||
use HasFactory;
|
||||
|
||||
protected $table = 'carrier_extra_fee_types';
|
||||
|
||||
protected $fillable = [
|
||||
'name',
|
||||
'description',
|
||||
];
|
||||
|
||||
public function extraFees()
|
||||
{
|
||||
return $this->hasMany(UserCarrierPricingExtraFees::class, 'carrier_extra_fee_type_id');
|
||||
}
|
||||
}
|
51
src/app/Models/CarrierMaster.php
Normal file
51
src/app/Models/CarrierMaster.php
Normal file
@ -0,0 +1,51 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
class CarrierMaster extends Model
|
||||
{
|
||||
/**
|
||||
* The table associated with the model.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $table = 'carrier_master';
|
||||
|
||||
protected $connection = 'mariadb';
|
||||
|
||||
/**
|
||||
* The attributes that are mass assignable.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $fillable = [
|
||||
'shortname',
|
||||
'display_name',
|
||||
'img',
|
||||
'api_url',
|
||||
'api_url_sandbox',
|
||||
'label_reprint',
|
||||
'carrier_enabled',
|
||||
'last_pickup'
|
||||
];
|
||||
|
||||
/**
|
||||
* The attributes that should be cast to native types.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $casts = [
|
||||
'label_reprint' => 'boolean',
|
||||
'carrier_enabled' => 'boolean',
|
||||
'created_at' => 'datetime',
|
||||
'updated_at' => 'datetime',
|
||||
];
|
||||
|
||||
public function carriers()
|
||||
{
|
||||
return $this->hasMany(Carrier::class, 'carrier_master_id');
|
||||
}
|
||||
|
||||
}
|
20
src/app/Models/CarrierPricingGroup.php
Normal file
20
src/app/Models/CarrierPricingGroup.php
Normal file
@ -0,0 +1,20 @@
|
||||
<?php
|
||||
|
||||
// app/Models/CarrierPricingGroup.php
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Relations\HasMany;
|
||||
|
||||
class CarrierPricingGroup extends Model
|
||||
{
|
||||
use HasFactory;
|
||||
|
||||
protected $fillable = ['name', 'base_ratio'];
|
||||
|
||||
public function userCarrierPricingGroups(): HasMany
|
||||
{
|
||||
return $this->hasMany(UserCarrierPricingGroup::class);
|
||||
}
|
||||
}
|
69
src/app/Models/CarrierUserCredential.php
Normal file
69
src/app/Models/CarrierUserCredential.php
Normal file
@ -0,0 +1,69 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
class CarrierUserCredential extends Model
|
||||
{
|
||||
use HasFactory;
|
||||
|
||||
protected $table = 'carrier_user_credentials';
|
||||
|
||||
protected $fillable = [
|
||||
'user_id',
|
||||
'name',
|
||||
'type',
|
||||
'sandbox',
|
||||
'carrier_master_id', // Updated field name
|
||||
'value',
|
||||
'expiry_date'
|
||||
];
|
||||
|
||||
// Encrypt the value before saving to the database
|
||||
protected function setValueAttribute($value)
|
||||
{
|
||||
$this->attributes['value'] = encrypt($value);
|
||||
}
|
||||
|
||||
// Decrypt the value when retrieving from the database
|
||||
protected function getValueAttribute($value)
|
||||
{
|
||||
return decrypt($value);
|
||||
}
|
||||
|
||||
// Define the relationship with CarrierMaster
|
||||
public function carrierMaster()
|
||||
{
|
||||
return $this->belongsTo(CarrierMaster::class, 'carrier_master_id');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get credentials by carrier shortname and sandbox status.
|
||||
*
|
||||
* @param string $carrierShortname
|
||||
* @param bool $sandbox
|
||||
* @return \Illuminate\Support\Collection
|
||||
*/
|
||||
public static function getCredentialsByCarrier($carrierShortname, $sandbox = false)
|
||||
{
|
||||
// Get the carrier_master_id using the shortname
|
||||
$carrierMaster = CarrierMaster::where('shortname', $carrierShortname)->first();
|
||||
|
||||
if (!$carrierMaster) {
|
||||
return collect([]);
|
||||
}
|
||||
|
||||
$credentials = self::where('carrier_master_id', $carrierMaster->id)
|
||||
->where('sandbox', $sandbox)
|
||||
->get();
|
||||
|
||||
$creds = [];
|
||||
foreach ($credentials as $credential) {
|
||||
$creds[$credential->type] = $credential;
|
||||
}
|
||||
|
||||
return collect($creds);
|
||||
}
|
||||
}
|
44
src/app/Models/GeneratedFile.php
Normal file
44
src/app/Models/GeneratedFile.php
Normal file
@ -0,0 +1,44 @@
|
||||
<?php
|
||||
|
||||
// app/Models/GeneratedFile.php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
|
||||
class GeneratedFile extends Model
|
||||
{
|
||||
use HasFactory;
|
||||
|
||||
protected $table = 'generated_files';
|
||||
|
||||
|
||||
protected $fillable = [
|
||||
'filename',
|
||||
'user_id',
|
||||
'file_data',
|
||||
'lifetime',
|
||||
'associated_id',
|
||||
'type',
|
||||
'created_at',
|
||||
'updated_at'
|
||||
];
|
||||
|
||||
protected $casts = [
|
||||
'lifetime' => 'datetime',
|
||||
'created_at' => 'datetime',
|
||||
'updated_at' => 'datetime',
|
||||
];
|
||||
|
||||
|
||||
public function user()
|
||||
{
|
||||
return $this->belongsTo(User::class, 'user_id');
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
?>
|
44
src/app/Models/ItemsStorageRemoval.php
Normal file
44
src/app/Models/ItemsStorageRemoval.php
Normal file
@ -0,0 +1,44 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
class ItemsStorageRemoval extends Model
|
||||
{
|
||||
use HasFactory;
|
||||
|
||||
/**
|
||||
* The table associated with the model.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $table = 'items_storage_removal';
|
||||
|
||||
/**
|
||||
* The attributes that are mass assignable.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $fillable = [
|
||||
'shipment_request_item_id',
|
||||
'entry_processed',
|
||||
];
|
||||
|
||||
/**
|
||||
* Indicates if the model should be timestamped.
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
public $timestamps = true;
|
||||
|
||||
/**
|
||||
* Define the relationship with ShipmentRequestItem.
|
||||
*/
|
||||
public function shipmentRequestItem()
|
||||
{
|
||||
return $this->belongsTo(ShipmentRequestItem::class, 'shipment_request_item_id');
|
||||
}
|
||||
|
||||
}
|
79
src/app/Models/Label.php
Normal file
79
src/app/Models/Label.php
Normal file
@ -0,0 +1,79 @@
|
||||
<?php
|
||||
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
|
||||
class Label extends Model
|
||||
{
|
||||
use HasFactory;
|
||||
|
||||
protected $fillable = [
|
||||
'id',
|
||||
'shipment_id',
|
||||
'label_data',
|
||||
'temporary'
|
||||
];
|
||||
|
||||
const CREATED_AT = 'created_at';
|
||||
const UPDATED_AT = 'updated_at';
|
||||
|
||||
/**
|
||||
* Get the shipment that owns the label.
|
||||
*/
|
||||
public function shipment()
|
||||
{
|
||||
return $this->belongsTo(Shipment::class, 'shipment_id');
|
||||
}
|
||||
|
||||
/**
|
||||
* Custom create method to modify data before storing.
|
||||
*
|
||||
* @param array $data
|
||||
* @return static
|
||||
*/
|
||||
public static function storeEntry(string $label, Shipment $shipment): static
|
||||
{
|
||||
return self::create(array(
|
||||
"label_data" => $label,
|
||||
"shipment_id" => $shipment->id
|
||||
));
|
||||
}
|
||||
|
||||
public static function createInstance(string $label, Shipment $shipment): static
|
||||
{
|
||||
return self::create(array(
|
||||
"label_data" => $label,
|
||||
"shipment_id" => $shipment->id,
|
||||
"temporary" => true
|
||||
));
|
||||
}
|
||||
|
||||
protected static function booted()
|
||||
{
|
||||
parent::boot();
|
||||
|
||||
static::saving(function ($labelDetails) {
|
||||
|
||||
ActionLog::create([
|
||||
'name' => "label_created",
|
||||
'description' => "Label created for shipment, reference no: '{$labelDetails->shipment->shipmentRequest->shipment_reference}'",
|
||||
'user_id' => Auth::user()->id ?? 1,
|
||||
'shipment_request_id' => $labelDetails->shipment->shipmentRequest->id
|
||||
]);
|
||||
|
||||
Log::channel('expedice')->info('booted saving initiated in Label model - label created by ' . (Auth::user()->name ?? '///'), [
|
||||
'shipment_request_id' => $labelDetails->shipment->shipmentRequest->id,
|
||||
'user_id' => Auth::user()->id ?? 1,
|
||||
]);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
68
src/app/Models/PickupPoint.php
Normal file
68
src/app/Models/PickupPoint.php
Normal file
@ -0,0 +1,68 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
|
||||
class PickupPoint extends Model
|
||||
{
|
||||
protected $table = 'pickup_point';
|
||||
|
||||
protected $connection = 'vat_pps';
|
||||
|
||||
// Fillable attributes for mass assignment
|
||||
protected $fillable = [
|
||||
'ext_id',
|
||||
'carrier_id',
|
||||
'name',
|
||||
'type',
|
||||
'directions',
|
||||
'place_name',
|
||||
'address',
|
||||
'zip',
|
||||
'city',
|
||||
'country_iso',
|
||||
'status_id',
|
||||
'card',
|
||||
'cod',
|
||||
'wheelchair',
|
||||
'photo_url',
|
||||
'thumbnail_url',
|
||||
'enabled'
|
||||
];
|
||||
|
||||
// The attributes that should be mutated to dates
|
||||
protected $dates = ['updated_at'];
|
||||
|
||||
protected $hidden = ['location'];
|
||||
|
||||
// Define relationship with Carrier
|
||||
public function carrier()
|
||||
{
|
||||
return $this->belongsTo(Carrier::class, 'carrier_id');
|
||||
}
|
||||
|
||||
public static function getNearbyPickupPoints($extId, $carrier_id, $limit = 20)
|
||||
{
|
||||
// Get the pickup point by ext_id
|
||||
$pickupPoint = PickupPoint::where('ext_id', $extId)
|
||||
->where('carrier_id', $carrier_id)
|
||||
->first();
|
||||
|
||||
// If the pickup point exists, find nearby pickup points
|
||||
if ($pickupPoint) {
|
||||
// Use Eloquent to build the query with distance calculation
|
||||
return PickupPoint::selectRaw("*, ST_Distance_Sphere(location, ?) as distance", [$pickupPoint->location])
|
||||
->where('ext_id', '!=', $extId) // Exclude the current pickup point
|
||||
->orderBy('distance') // Order by distance
|
||||
->limit($limit) // Limit the number of results
|
||||
->get(); // Return a collection of PickupPoint models
|
||||
}
|
||||
|
||||
// Return an empty collection if no pickup point was found
|
||||
return collect();
|
||||
}
|
||||
|
||||
|
||||
}
|
109
src/app/Models/Shipment.php
Normal file
109
src/app/Models/Shipment.php
Normal file
@ -0,0 +1,109 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use App\Services\CallbackService;
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Support\Facades\App;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
|
||||
class Shipment extends Model
|
||||
{
|
||||
|
||||
protected $table = 'shipment';
|
||||
|
||||
use HasFactory;
|
||||
|
||||
protected $fillable = [
|
||||
'user_id',
|
||||
'shipment_request_id',
|
||||
'internal_shipment_id',
|
||||
'shipment_id',
|
||||
'tracking_number',
|
||||
'tracking_url',
|
||||
];
|
||||
|
||||
public $timestamps = true;
|
||||
|
||||
const CREATED_AT = 'created_at';
|
||||
const UPDATED_AT = 'updated_at';
|
||||
|
||||
protected static function booted()
|
||||
{
|
||||
parent::boot();
|
||||
|
||||
static::saving(function ($shipmentDetails) {
|
||||
$callbackService = App::make(CallbackService::class);
|
||||
|
||||
ActionLog::create([
|
||||
'name' => "shipment_created",
|
||||
'description' => "Shipment created, no: '{$shipmentDetails->tracking_number}'",
|
||||
'user_id' => Auth::user()->id ?? 1,
|
||||
'shipment_request_id' => $shipmentDetails->shipmentRequest->id
|
||||
]);
|
||||
|
||||
Log::channel('expedice')->info('booted saving initiated in Shipment model - shipment created by ' . (Auth::user()->name ?? '///'), [
|
||||
'shipment_request_id' => $shipmentDetails->shipmentRequest->id,
|
||||
'user_id' => Auth::user()->id ?? 1,
|
||||
]);
|
||||
|
||||
if ($shipmentDetails->user->details->apiCallbackURL && $shipmentDetails->user->details->callbacks_enabled) {
|
||||
$callbackService->sendCallback($shipmentDetails->user->details->apiCallbackURL, [
|
||||
'endpoint' => 'shipmentProcessed',
|
||||
'result' => 'true',
|
||||
'shipment_reference' => $shipmentDetails->shipmentRequest->shipment_reference,
|
||||
'data' => [
|
||||
'trackingNumber' => $shipmentDetails->tracking_number,
|
||||
'trackingURL' => $shipmentDetails->tracking_url,
|
||||
],
|
||||
'additionalInfo' => null
|
||||
]);
|
||||
}
|
||||
foreach ($shipmentDetails->shipmentRequest->errors as $error) {
|
||||
$error->update([
|
||||
'resolved' => true,
|
||||
]);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public function shipmentRequest()
|
||||
{
|
||||
return $this->belongsTo(ShipmentRequest::class, 'shipment_request_id');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the user that owns the shipment.
|
||||
*/
|
||||
public function user()
|
||||
{
|
||||
return $this->belongsTo(User::class, 'user_id');
|
||||
}
|
||||
|
||||
public function label()
|
||||
{
|
||||
return $this->hasOne(Label::class, 'shipment_id');
|
||||
}
|
||||
|
||||
public function processedBaseFees()
|
||||
{
|
||||
return $this->hasMany(ShipmentProcessedBaseFee::class, 'shipment_id');
|
||||
}
|
||||
|
||||
public function processedExtraFees()
|
||||
{
|
||||
return $this->hasMany(ShipmentProcessedExtraFee::class, 'shipment_id');
|
||||
}
|
||||
|
||||
|
||||
public function trackingStatusHistory()
|
||||
{
|
||||
return $this->hasMany(ShipmentTrackingStatusHistory::class, 'shipment_id');
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
36
src/app/Models/ShipmentErrorLog.php
Normal file
36
src/app/Models/ShipmentErrorLog.php
Normal file
@ -0,0 +1,36 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
class ShipmentErrorLog extends Model
|
||||
{
|
||||
use HasFactory;
|
||||
protected $table = 'shipment_error_log';
|
||||
|
||||
protected $fillable = [
|
||||
'shipment_request_id',
|
||||
'error_message',
|
||||
'fetched',
|
||||
'resolved',
|
||||
];
|
||||
|
||||
public function shipmentRequest()
|
||||
{
|
||||
return $this->belongsTo(ShipmentRequest::class, 'shipment_request_id');
|
||||
}
|
||||
|
||||
public function batchItems()
|
||||
{
|
||||
return $this->hasManyThrough(
|
||||
ShipmentRequestBatchItem::class,
|
||||
ShipmentRequest::class,
|
||||
'id', // Foreign key on ShipmentRequest table
|
||||
'shipment_request_id', // Foreign key on ShipmentRequestBatchItem table
|
||||
'shipment_request_id', // Local key on ShipmentErrorLog table
|
||||
'id' // Local key on ShipmentRequest table
|
||||
);
|
||||
}
|
||||
}
|
35
src/app/Models/ShipmentProcessedBaseFee.php
Normal file
35
src/app/Models/ShipmentProcessedBaseFee.php
Normal file
@ -0,0 +1,35 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
class ShipmentProcessedBaseFee extends Model
|
||||
{
|
||||
use HasFactory;
|
||||
|
||||
// Define the table name explicitly
|
||||
protected $table = 'shipment_processed_base_fees';
|
||||
|
||||
// Specify the columns that can be mass-assigned
|
||||
protected $fillable = [
|
||||
'shipment_id',
|
||||
'base_fee_type',
|
||||
'base_fee_value',
|
||||
'base_fee_desc',
|
||||
];
|
||||
|
||||
|
||||
// Define the casts for date attributes
|
||||
protected $casts = [
|
||||
'created_at' => 'datetime',
|
||||
'updated_at' => 'datetime',
|
||||
];
|
||||
|
||||
|
||||
public function shipment()
|
||||
{
|
||||
return $this->belongsTo(Shipment::class, 'shipment_id');
|
||||
}
|
||||
}
|
32
src/app/Models/ShipmentProcessedExtraFee.php
Normal file
32
src/app/Models/ShipmentProcessedExtraFee.php
Normal file
@ -0,0 +1,32 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
class ShipmentProcessedExtraFee extends Model
|
||||
{
|
||||
use HasFactory;
|
||||
|
||||
// Define the table name explicitly
|
||||
protected $table = 'shipment_processed_extra_fees';
|
||||
|
||||
// Specify the columns that can be mass-assigned
|
||||
protected $fillable = [
|
||||
'shipment_id',
|
||||
'extra_fee_type_id',
|
||||
'extra_fee_type_value',
|
||||
'extra_fee_desc',
|
||||
];
|
||||
|
||||
protected $casts = [
|
||||
'created_at' => 'datetime',
|
||||
'updated_at' => 'datetime',
|
||||
];
|
||||
|
||||
public function shipment()
|
||||
{
|
||||
return $this->belongsTo(Shipment::class, 'shipment_id');
|
||||
}
|
||||
}
|
226
src/app/Models/ShipmentRequest.php
Normal file
226
src/app/Models/ShipmentRequest.php
Normal file
@ -0,0 +1,226 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use App\Services\CallbackService;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Support\Facades\App;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use App\Models\ItemsStorageRemoval;
|
||||
|
||||
class ShipmentRequest extends Model
|
||||
{
|
||||
protected $table = 'shipment_request';
|
||||
|
||||
protected $fillable = [
|
||||
'shipment_reference',
|
||||
'user_id',
|
||||
'note',
|
||||
'delivery_address_name',
|
||||
'delivery_address_company_name',
|
||||
'delivery_address_street_name',
|
||||
'delivery_address_street_number',
|
||||
'delivery_address_address_line_2',
|
||||
'delivery_address_city',
|
||||
'delivery_address_zip',
|
||||
'delivery_address_state_iso',
|
||||
'delivery_address_country_iso',
|
||||
'pickup_point_code',
|
||||
'pickup_point_name',
|
||||
'pickup_point_address_street_name',
|
||||
'pickup_point_address_street_number',
|
||||
'pickup_point_address_city',
|
||||
'pickup_point_address_zip',
|
||||
'pickup_point_address_state_iso',
|
||||
'pickup_point_address_country_iso',
|
||||
'postnummer',
|
||||
'contact_email',
|
||||
'contact_telephone',
|
||||
'cod_value',
|
||||
'cod_variable_symbol',
|
||||
'shipment_value',
|
||||
'currency',
|
||||
'weight',
|
||||
'height',
|
||||
'width',
|
||||
'length',
|
||||
'carrier_id',
|
||||
'allegro_carrier_id',
|
||||
'package_type',
|
||||
'send_again_flag',
|
||||
'allegro_smart',
|
||||
'ups_invoice_document',
|
||||
];
|
||||
|
||||
protected $casts = [
|
||||
'cod_value' => 'decimal:2',
|
||||
'shipment_value' => 'decimal:2',
|
||||
'weight' => 'decimal:2',
|
||||
];
|
||||
|
||||
public function items()
|
||||
{
|
||||
return $this->hasMany(ShipmentRequestItem::class, 'shipment_request_id');
|
||||
}
|
||||
|
||||
public function shipment()
|
||||
{
|
||||
return $this->hasOne(Shipment::class, 'shipment_request_id');
|
||||
}
|
||||
|
||||
protected $appends = ['delivery_address'];
|
||||
|
||||
/**
|
||||
* Accessor to get the delivery address as an array.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getDeliveryAddressAttribute()
|
||||
{
|
||||
return [
|
||||
'name' => $this->delivery_address_name,
|
||||
'companyName' => $this->delivery_address_company_name,
|
||||
'streetName' => $this->delivery_address_street_name,
|
||||
'streetNumber' => $this->delivery_address_street_number,
|
||||
'addressLine2' => $this->delivery_address_address_line_2,
|
||||
'city' => $this->delivery_address_city,
|
||||
'zip' => $this->delivery_address_zip,
|
||||
'stateISO' => $this->delivery_address_state_iso,
|
||||
'countryISO' => $this->delivery_address_country_iso,
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
public function carrier()
|
||||
{
|
||||
return $this->belongsTo(Carrier::class, 'carrier_id');
|
||||
}
|
||||
|
||||
public function user()
|
||||
{
|
||||
return $this->belongsTo(User::class, 'user_id');
|
||||
}
|
||||
public function invoice()
|
||||
{
|
||||
return $this->hasOne(ShipmentRequestInvoice::class, 'shipment_request_id');
|
||||
}
|
||||
public function batchItem()
|
||||
{
|
||||
return $this->hasOne(ShipmentRequestBatchItem::class, 'shipment_request_id');
|
||||
}
|
||||
|
||||
public function batch()
|
||||
{
|
||||
return $this->hasOneThrough(
|
||||
ShipmentRequestBatch::class,
|
||||
ShipmentRequestBatchItem::class,
|
||||
'shipment_request_id', // Foreign key on ShipmentRequestBatchItem table
|
||||
'id', // Foreign key on ShipmentRequestBatch table
|
||||
'id', // Local key on ShipmentRequest table
|
||||
'shipment_request_batch_id' // Local key on ShipmentRequestBatchItem table
|
||||
);
|
||||
}
|
||||
public function errors()
|
||||
{
|
||||
return $this->hasMany(ShipmentErrorLog::class, 'shipment_request_id');
|
||||
}
|
||||
|
||||
public function unresolvedErrors()
|
||||
{
|
||||
return $this->hasMany(ShipmentErrorLog::class)->where('resolved', false);
|
||||
}
|
||||
|
||||
|
||||
public function shipmentStatuses()
|
||||
{
|
||||
return $this->hasMany(ShipmentStatusHistory::class, 'shipment_request_id');
|
||||
}
|
||||
|
||||
public function currentShipmentStatus()
|
||||
{
|
||||
return $this->hasOne(ShipmentStatusHistory::class, 'shipment_request_id')
|
||||
->latestOfMany(); // This ensures only the latest record is returned.
|
||||
}
|
||||
|
||||
|
||||
|
||||
protected static function booted()
|
||||
{
|
||||
parent::boot();
|
||||
|
||||
// Handle saving events for both updates and first saves
|
||||
static::saving(function ($shipmentRequest) {
|
||||
$callbackService = App::make(CallbackService::class);
|
||||
|
||||
// Check if the user has an API callback URL and callbacks are enabled
|
||||
if ($shipmentRequest->user->details->apiCallbackURL && $shipmentRequest->user->details->callbacks_enabled) {
|
||||
// Check if the model is being created (i.e., a first-time save)
|
||||
if ($shipmentRequest->isDirty() && !$shipmentRequest->exists) {
|
||||
|
||||
ActionLog::create([
|
||||
'name' => "shipment_request_created",
|
||||
'description' => "Shipment accepted to APP, reference no: '{$shipmentRequest->shipment_reference}'",
|
||||
'user_id' => $shipmentRequest->user->id,
|
||||
'shipment_request_id' => $shipmentRequest->id
|
||||
]);
|
||||
|
||||
|
||||
$callbackService->sendCallback($shipmentRequest->user->details->apiCallbackURL, [
|
||||
'endpoint' => 'shipmentRequestCreated',
|
||||
'result' => 'true',
|
||||
'shipment_reference' => $shipmentRequest->shipment_reference,
|
||||
'data' => [
|
||||
'createdFields' => $shipmentRequest->getAttributes(), // Fields that were initially set
|
||||
],
|
||||
'additionalInfo' => null
|
||||
]);
|
||||
|
||||
}
|
||||
// Check if the model is being updated
|
||||
else {
|
||||
|
||||
ActionLog::create([
|
||||
'name' => "shipment_request_updated",
|
||||
'description' => "Shipment updated, reference no: '{$shipmentRequest->shipment_reference}', changes: '" . var_export($shipmentRequest->getDirty(), true),
|
||||
'user_id' => Auth::user()->id ?? 1,
|
||||
'shipment_request_id' => $shipmentRequest->id
|
||||
]);
|
||||
|
||||
Log::channel('expedice')->info('Shipment updated by ' . (Auth::user()->name ?? '///'), [
|
||||
'shipment_request_id' => $shipmentRequest->id,
|
||||
'changes' => $shipmentRequest->getDirty(),
|
||||
]);
|
||||
|
||||
$callbackService->sendCallback($shipmentRequest->user->details->apiCallbackURL, [
|
||||
'endpoint' => 'shipmentRequestUpdated',
|
||||
'result' => 'true',
|
||||
'shipment_reference' => $shipmentRequest->shipment_reference,
|
||||
'data' => [
|
||||
'updatedFields' => $shipmentRequest->getDirty(), // Fields that were changed
|
||||
],
|
||||
'additionalInfo' => null
|
||||
]);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public function createStorageRemovalEntries()
|
||||
{
|
||||
foreach ($this->items as $item) {
|
||||
// Check if an entry already exists
|
||||
$exists = ItemsStorageRemoval::where('shipment_request_item_id', $item->id)->exists();
|
||||
|
||||
// If it doesn't exist, create a new entry
|
||||
if (!$exists) {
|
||||
ItemsStorageRemoval::create([
|
||||
'shipment_request_item_id' => $item->id,
|
||||
'entry_processed' => false, // Default value
|
||||
]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
60
src/app/Models/ShipmentRequestBatch.php
Normal file
60
src/app/Models/ShipmentRequestBatch.php
Normal file
@ -0,0 +1,60 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
class ShipmentRequestBatch extends Model
|
||||
{
|
||||
use HasFactory;
|
||||
|
||||
protected $table = 'shipment_request_batch';
|
||||
|
||||
protected $fillable = ['user_id', 'carrier_master_id', 'processed', 'printed'];
|
||||
|
||||
protected $appends = ['item_count', 'error_count'];
|
||||
|
||||
public function items()
|
||||
{
|
||||
return $this->hasMany(ShipmentRequestBatchItem::class);
|
||||
}
|
||||
|
||||
public function generatedFiles()
|
||||
{
|
||||
return $this->hasMany(GeneratedFile::class, 'associated_id')
|
||||
->where('type', 'batch');
|
||||
}
|
||||
|
||||
public function latestGeneratedFile()
|
||||
{
|
||||
return $this->hasMany(GeneratedFile::class, 'associated_id')
|
||||
->where('type', 'batch')->latest();
|
||||
}
|
||||
|
||||
|
||||
public function carrierMaster()
|
||||
{
|
||||
return $this->belongsTo(CarrierMaster::class, 'carrier_master_id');
|
||||
}
|
||||
|
||||
public function user()
|
||||
{
|
||||
return $this->belongsTo(User::class, 'user_id');
|
||||
}
|
||||
|
||||
public function getItemCountAttribute()
|
||||
{
|
||||
return $this->items()->count();
|
||||
}
|
||||
|
||||
public function getErrorCountAttribute()
|
||||
{
|
||||
return $this->items()
|
||||
->whereHas('shipmentRequest.errors', function ($query) {
|
||||
$query->where('resolved', false);
|
||||
})
|
||||
->count();
|
||||
}
|
||||
|
||||
}
|
26
src/app/Models/ShipmentRequestBatchItem.php
Normal file
26
src/app/Models/ShipmentRequestBatchItem.php
Normal file
@ -0,0 +1,26 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
class ShipmentRequestBatchItem extends Model
|
||||
{
|
||||
use HasFactory;
|
||||
|
||||
public $timestamps = false;
|
||||
protected $table = 'shipment_request_batch_items';
|
||||
protected $fillable = ['shipment_request_batch_id', 'shipment_request_id'];
|
||||
|
||||
public function batch()
|
||||
{
|
||||
return $this->belongsTo(ShipmentRequestBatch::class, 'shipment_request_batch_id');
|
||||
}
|
||||
|
||||
public function shipmentRequest()
|
||||
{
|
||||
return $this->belongsTo(ShipmentRequest::class, 'shipment_request_id');
|
||||
}
|
||||
|
||||
}
|
45
src/app/Models/ShipmentRequestInvoice.php
Normal file
45
src/app/Models/ShipmentRequestInvoice.php
Normal file
@ -0,0 +1,45 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
class ShipmentRequestInvoice extends Model
|
||||
{
|
||||
use HasFactory;
|
||||
|
||||
// Define the table associated with the model
|
||||
protected $table = 'shipment_request_invoice';
|
||||
|
||||
|
||||
// Define the columns that are mass assignable
|
||||
protected $fillable = [
|
||||
'shipment_request_id',
|
||||
'invoice_number',
|
||||
'invoice_date',
|
||||
'invoice_currency',
|
||||
'invoice_address_name',
|
||||
'invoice_address_company_name',
|
||||
'invoice_address_company_vat_number',
|
||||
'invoice_address_street_name',
|
||||
'invoice_address_street_number',
|
||||
'invoice_address_city',
|
||||
'invoice_address_zip',
|
||||
'invoice_address_state_iso',
|
||||
'invoice_address_country_iso',
|
||||
'invoice_raw_data',
|
||||
];
|
||||
|
||||
// Define the date format for created_at and updated_at columns
|
||||
protected $dates = [
|
||||
'created_at',
|
||||
'updated_at'
|
||||
];
|
||||
|
||||
// Define any relationships if necessary
|
||||
public function shipmentRequest()
|
||||
{
|
||||
return $this->belongsTo(ShipmentRequest::class, 'shipment_request_id');
|
||||
}
|
||||
}
|
32
src/app/Models/ShipmentRequestItem.php
Normal file
32
src/app/Models/ShipmentRequestItem.php
Normal file
@ -0,0 +1,32 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
class ShipmentRequestItem extends Model
|
||||
{
|
||||
protected $table = 'shipment_request_items';
|
||||
|
||||
use HasFactory;
|
||||
|
||||
protected $fillable = [
|
||||
'shipment_request_id',
|
||||
'name',
|
||||
'item_note',
|
||||
'model_number',
|
||||
'HSCode',
|
||||
'price',
|
||||
'quantity',
|
||||
'originCountry',
|
||||
'weight',
|
||||
'item_id_internal_warehouse',
|
||||
'item_id_external',
|
||||
];
|
||||
|
||||
public function shipment()
|
||||
{
|
||||
return $this->belongsTo(ShipmentRequest::class, 'shipment_request_id');
|
||||
}
|
||||
}
|
44
src/app/Models/ShipmentStatus.php
Normal file
44
src/app/Models/ShipmentStatus.php
Normal file
@ -0,0 +1,44 @@
|
||||
<?php
|
||||
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
class ShipmentStatus extends Model
|
||||
{
|
||||
protected $table = 'shipment_status';
|
||||
use HasFactory;
|
||||
|
||||
protected $fillable = [
|
||||
'name',
|
||||
'description',
|
||||
'internal',
|
||||
];
|
||||
|
||||
public $timestamps = false;
|
||||
|
||||
// Define status constants
|
||||
const STATUS_CREATED = 1;
|
||||
const STATUS_PENDING = 2;
|
||||
const STATUS_LABEL_CREATED = 3;
|
||||
const STATUS_PACKED = 4;
|
||||
const STATUS_IN_TRANSIT = 5;
|
||||
const STATUS_DELIVERED = 6;
|
||||
const STATUS_ADDRESS_INSUFFICIENT = 7;
|
||||
const STATUS_OUT_OF_STOCK = 8;
|
||||
const STATUS_PICKUP_UNAVAILABLE = 9;
|
||||
const STATUS_EXT_PARCEL_ID_GENERATED = 10;
|
||||
const STATUS_IS_REPAIR = 11;
|
||||
const STATUS_PROGRAMMING_MODEL_REQUIRED = 12;
|
||||
const STATUS_PROGRAMMING_PROCESS = 13;
|
||||
const STATUS_PICKUP_REROUTE = 14;
|
||||
const STATUS_CANCELED = 15;
|
||||
const STATUS_PRINTING = 16;
|
||||
const STATUS_PRINTING_UNDONE = 17;
|
||||
const STATUS_EXPEDICE_DELAY = 18;
|
||||
const STATUS_EXPEDICE_SPECIAL_HANDLING = 19;
|
||||
|
||||
|
||||
}
|
117
src/app/Models/ShipmentStatusHistory.php
Normal file
117
src/app/Models/ShipmentStatusHistory.php
Normal file
@ -0,0 +1,117 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use App\Factories\CarrierServiceFactory;
|
||||
use App\Services\CallbackService;
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Support\Facades\App;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
|
||||
class ShipmentStatusHistory extends Model
|
||||
{
|
||||
protected $table = 'shipment_status_history';
|
||||
|
||||
use HasFactory;
|
||||
|
||||
protected $fillable = [
|
||||
'shipment_request_id',
|
||||
'shipment_status_id',
|
||||
'status_note',
|
||||
];
|
||||
|
||||
const CREATED_AT = 'created_at';
|
||||
const UPDATED_AT = 'updated_at';
|
||||
|
||||
/**
|
||||
* Get the shipment that owns the status history.
|
||||
*/
|
||||
public function shipmentRequest()
|
||||
{
|
||||
return $this->belongsTo(ShipmentRequest::class, 'shipment_request_id');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the shipment status that owns the status history.
|
||||
*/
|
||||
public function shipmentStatus()
|
||||
{
|
||||
return $this->belongsTo(ShipmentStatus::class, 'shipment_status_id');
|
||||
}
|
||||
|
||||
protected static function booted()
|
||||
{
|
||||
parent::boot();
|
||||
|
||||
static::creating(function ($shipmentStatusHistory) {
|
||||
$callbackService = App::make(CallbackService::class);
|
||||
|
||||
|
||||
ActionLog::create([
|
||||
'name' => "status_changed",
|
||||
'description' => "Status changed to '{$shipmentStatusHistory->shipmentStatus->name}'",
|
||||
'user_id' => Auth::user()->id ?? 1,
|
||||
'shipment_request_id' => $shipmentStatusHistory->shipmentRequest->id
|
||||
]);
|
||||
|
||||
Log::channel('expedice')->info("Status changed to {$shipmentStatusHistory->shipmentStatus->name} by " . (Auth::user()->name ?? '///'), [
|
||||
'shipment_request_id' => $shipmentStatusHistory->shipmentRequest->id
|
||||
]);
|
||||
|
||||
if(in_array($shipmentStatusHistory->shipment_status_id, [
|
||||
ShipmentStatus::STATUS_ADDRESS_INSUFFICIENT,
|
||||
ShipmentStatus::STATUS_OUT_OF_STOCK,
|
||||
ShipmentStatus::STATUS_IS_REPAIR,
|
||||
ShipmentStatus::STATUS_PROGRAMMING_MODEL_REQUIRED,
|
||||
ShipmentStatus::STATUS_PROGRAMMING_PROCESS,
|
||||
ShipmentStatus::STATUS_CANCELED,
|
||||
])) {
|
||||
// auto batch remove statuses
|
||||
|
||||
if (!is_null($shipmentStatusHistory->shipmentRequest->batch)) {
|
||||
ShipmentRequestBatchItem::where('shipment_request_id', $shipmentStatusHistory->shipmentRequest->id)->delete();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
if($shipmentStatusHistory->shipment_status_id === ShipmentStatus::STATUS_CANCELED) {
|
||||
try {
|
||||
if(!empty($shipmentStatusHistory->shipmentRequest->shipment)) {
|
||||
|
||||
$carrierService = CarrierServiceFactory::create($shipmentStatusHistory->shipmentRequest->carrier);
|
||||
$res = $carrierService->cancelShipment($shipmentStatusHistory->shipmentRequest->shipment);
|
||||
if ($res) {
|
||||
|
||||
$shipmentStatusHistory->shipmentRequest?->shipment?->label?->delete();
|
||||
$shipmentStatusHistory->shipmentRequest?->shipment?->delete();
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
catch(\Exception $e) {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
// Example of a callback or logging action when a new status history is created
|
||||
if ($shipmentStatusHistory->shipmentRequest->user->details->apiCallbackURL && $shipmentStatusHistory->shipmentRequest->user->details->callbacks_enabled) {
|
||||
$callbackService->sendCallback($shipmentStatusHistory->shipmentRequest->user->details->apiCallbackURL, [
|
||||
'endpoint' => 'shipmentStatusHistoryCreated',
|
||||
'result' => 'true',
|
||||
'data' => [
|
||||
'trackingNumber' => $shipmentStatusHistory->shipmentRequest->shipment?->tracking_number ?? "",
|
||||
'trackingURL' => $shipmentStatusHistory->shipmentRequest->shipment?->tracking_url ?? "",
|
||||
],
|
||||
'shipment_reference' => $shipmentStatusHistory->shipmentRequest->shipment_reference,
|
||||
'status_id' => $shipmentStatusHistory->shipment_status_id,
|
||||
'status_name' => $shipmentStatusHistory->shipmentStatus->name,
|
||||
'status_desc' => $shipmentStatusHistory->shipmentStatus->description,
|
||||
]);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user