Compare commits
2 Commits
8f2d21c0e1
...
text-trasl
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
eb5ac47186 | ||
|
|
4b0c2477f3 |
128
.gitea/workflows/cicd.yaml
Normal file
128
.gitea/workflows/cicd.yaml
Normal file
@@ -0,0 +1,128 @@
|
|||||||
|
name: Build, Test and Deploy
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches: [main, int, dev]
|
||||||
|
|
||||||
|
concurrency:
|
||||||
|
group: print-calculator-${{ gitea.ref }}
|
||||||
|
cancel-in-progress: true
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
test-backend:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Set up JDK 21
|
||||||
|
uses: actions/setup-java@v4
|
||||||
|
with:
|
||||||
|
java-version: '21'
|
||||||
|
distribution: 'temurin'
|
||||||
|
|
||||||
|
- name: Run Tests with Gradle
|
||||||
|
run: |
|
||||||
|
cd backend
|
||||||
|
chmod +x gradlew
|
||||||
|
./gradlew test
|
||||||
|
|
||||||
|
build-and-push:
|
||||||
|
needs: test-backend
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Set TAG + OWNER lowercase
|
||||||
|
shell: bash
|
||||||
|
run: |
|
||||||
|
if [[ "${{ gitea.ref }}" == "refs/heads/main" ]]; then
|
||||||
|
echo "TAG=prod" >> "$GITHUB_ENV"
|
||||||
|
elif [[ "${{ gitea.ref }}" == "refs/heads/int" ]]; then
|
||||||
|
echo "TAG=int" >> "$GITHUB_ENV"
|
||||||
|
else
|
||||||
|
echo "TAG=dev" >> "$GITHUB_ENV"
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "OWNER_LOWER=$(echo '${{ gitea.repository_owner }}' | tr '[:upper:]' '[:lower:]')" >> "$GITHUB_ENV"
|
||||||
|
|
||||||
|
- name: Ensure docker CLI exists
|
||||||
|
shell: bash
|
||||||
|
run: |
|
||||||
|
if ! command -v docker >/dev/null 2>&1; then
|
||||||
|
apt-get update
|
||||||
|
apt-get install -y --no-install-recommends docker.io
|
||||||
|
fi
|
||||||
|
docker version
|
||||||
|
|
||||||
|
- name: Login to Gitea Registry
|
||||||
|
shell: bash
|
||||||
|
run: |
|
||||||
|
set -euo pipefail
|
||||||
|
printf '%s' "${{ secrets.REGISTRY_TOKEN }}" | docker login "${{ secrets.REGISTRY_URL }}" \
|
||||||
|
-u "${{ secrets.REGISTRY_USER }}" --password-stdin
|
||||||
|
|
||||||
|
- name: Build & Push Backend
|
||||||
|
shell: bash
|
||||||
|
run: |
|
||||||
|
BACKEND_IMAGE="${{ secrets.REGISTRY_URL }}/${{ env.OWNER_LOWER }}/print-calculator-backend:${{ env.TAG }}"
|
||||||
|
docker build -t "$BACKEND_IMAGE" ./backend
|
||||||
|
docker push "$BACKEND_IMAGE"
|
||||||
|
|
||||||
|
- name: Build & Push Frontend
|
||||||
|
shell: bash
|
||||||
|
run: |
|
||||||
|
FRONTEND_IMAGE="${{ secrets.REGISTRY_URL }}/${{ env.OWNER_LOWER }}/print-calculator-frontend:${{ env.TAG }}"
|
||||||
|
docker build -t "$FRONTEND_IMAGE" ./frontend
|
||||||
|
docker push "$FRONTEND_IMAGE"
|
||||||
|
|
||||||
|
deploy:
|
||||||
|
needs: build-and-push
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Set ENV
|
||||||
|
shell: bash
|
||||||
|
run: |
|
||||||
|
if [[ "${{ gitea.ref }}" == "refs/heads/main" ]]; then
|
||||||
|
echo "ENV=prod" >> "$GITHUB_ENV"
|
||||||
|
elif [[ "${{ gitea.ref }}" == "refs/heads/int" ]]; then
|
||||||
|
echo "ENV=int" >> "$GITHUB_ENV"
|
||||||
|
else
|
||||||
|
echo "ENV=dev" >> "$GITHUB_ENV"
|
||||||
|
fi
|
||||||
|
|
||||||
|
- name: Trigger deploy on Unraid (forced command key)
|
||||||
|
shell: bash
|
||||||
|
run: |
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
apt-get update
|
||||||
|
apt-get install -y --no-install-recommends openssh-client
|
||||||
|
|
||||||
|
mkdir -p ~/.ssh
|
||||||
|
chmod 700 ~/.ssh
|
||||||
|
|
||||||
|
# 1) Prende il secret base64 e rimuove spazi/newline/CR
|
||||||
|
printf '%s' "${{ secrets.SSH_PRIVATE_KEY_B64 }}" | tr -d '\r\n\t ' > /tmp/key.b64
|
||||||
|
|
||||||
|
# 2) (debug sicuro) stampa solo la lunghezza della base64
|
||||||
|
echo "b64_len=$(wc -c < /tmp/key.b64)"
|
||||||
|
|
||||||
|
# 3) Decodifica in chiave privata
|
||||||
|
base64 -d /tmp/key.b64 > ~/.ssh/id_ed25519
|
||||||
|
|
||||||
|
# 4) Rimuove eventuali CRLF dentro la chiave (se proviene da Windows)
|
||||||
|
tr -d '\r' < ~/.ssh/id_ed25519 > ~/.ssh/id_ed25519.clean
|
||||||
|
mv ~/.ssh/id_ed25519.clean ~/.ssh/id_ed25519
|
||||||
|
chmod 600 ~/.ssh/id_ed25519
|
||||||
|
|
||||||
|
# 5) Validazione: se fallisce qui, la chiave NON è valida/corrotta
|
||||||
|
ssh-keygen -y -f ~/.ssh/id_ed25519 >/dev/null
|
||||||
|
|
||||||
|
# ... (resto del codice uguale)
|
||||||
|
ssh-keyscan -H "${{ secrets.SERVER_HOST }}" >> ~/.ssh/known_hosts 2>/dev/null
|
||||||
|
|
||||||
|
# Aggiungiamo le opzioni di verbosità se dovesse fallire ancora,
|
||||||
|
# e assicuriamoci che l'input sia pulito
|
||||||
|
ssh -i ~/.ssh/id_ed25519 -o BatchMode=yes "${{ secrets.SERVER_USER }}@${{ secrets.SERVER_HOST }}" "${{ env.ENV }}"
|
||||||
@@ -1,222 +0,0 @@
|
|||||||
name: Build and Deploy
|
|
||||||
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
branches: [main, int, dev]
|
|
||||||
|
|
||||||
concurrency:
|
|
||||||
group: print-calculator-deploy-${{ gitea.ref }}
|
|
||||||
cancel-in-progress: true
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
test-backend:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- name: Checkout
|
|
||||||
uses: actions/checkout@v4
|
|
||||||
|
|
||||||
- name: Set up JDK 21
|
|
||||||
uses: actions/setup-java@v4
|
|
||||||
with:
|
|
||||||
java-version: "21"
|
|
||||||
distribution: "temurin"
|
|
||||||
cache: gradle
|
|
||||||
|
|
||||||
- name: Run Tests with Gradle
|
|
||||||
run: |
|
|
||||||
cd backend
|
|
||||||
chmod +x gradlew
|
|
||||||
./gradlew test
|
|
||||||
|
|
||||||
test-frontend:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- name: Checkout
|
|
||||||
uses: actions/checkout@v4
|
|
||||||
|
|
||||||
- name: Setup Node 22
|
|
||||||
uses: actions/setup-node@v4
|
|
||||||
with:
|
|
||||||
node-version: "22"
|
|
||||||
cache: "npm"
|
|
||||||
cache-dependency-path: "frontend/package-lock.json"
|
|
||||||
|
|
||||||
- name: Resolve Chrome binary
|
|
||||||
shell: bash
|
|
||||||
run: |
|
|
||||||
set -euo pipefail
|
|
||||||
if command -v chromium >/dev/null 2>&1; then
|
|
||||||
CHROME_PATH="$(command -v chromium)"
|
|
||||||
elif command -v chromium-browser >/dev/null 2>&1; then
|
|
||||||
CHROME_PATH="$(command -v chromium-browser)"
|
|
||||||
elif command -v google-chrome >/dev/null 2>&1; then
|
|
||||||
CHROME_PATH="$(command -v google-chrome)"
|
|
||||||
else
|
|
||||||
apt-get update
|
|
||||||
apt-get install -y --no-install-recommends chromium
|
|
||||||
CHROME_PATH="$(command -v chromium)"
|
|
||||||
fi
|
|
||||||
|
|
||||||
echo "CHROME_BIN=$CHROME_PATH" >> "$GITHUB_ENV"
|
|
||||||
echo "Using CHROME_BIN=$CHROME_PATH"
|
|
||||||
|
|
||||||
- name: Install frontend dependencies
|
|
||||||
shell: bash
|
|
||||||
run: |
|
|
||||||
cd frontend
|
|
||||||
npm ci --no-audit --no-fund --prefer-offline
|
|
||||||
|
|
||||||
- name: Run frontend tests (headless)
|
|
||||||
shell: bash
|
|
||||||
env:
|
|
||||||
CI: "true"
|
|
||||||
run: |
|
|
||||||
cd frontend
|
|
||||||
echo "Karma CHROME_BIN=$CHROME_BIN"
|
|
||||||
npm run test -- --watch=false --browsers=ChromeHeadlessNoSandbox
|
|
||||||
|
|
||||||
build-and-push:
|
|
||||||
needs: [test-backend, test-frontend]
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- name: Checkout
|
|
||||||
uses: actions/checkout@v4
|
|
||||||
|
|
||||||
- name: Set TAG + OWNER lowercase
|
|
||||||
shell: bash
|
|
||||||
run: |
|
|
||||||
if [[ "${{ gitea.ref }}" == "refs/heads/main" ]]; then
|
|
||||||
echo "TAG=prod" >> "$GITHUB_ENV"
|
|
||||||
elif [[ "${{ gitea.ref }}" == "refs/heads/int" ]]; then
|
|
||||||
echo "TAG=int" >> "$GITHUB_ENV"
|
|
||||||
else
|
|
||||||
echo "TAG=dev" >> "$GITHUB_ENV"
|
|
||||||
fi
|
|
||||||
|
|
||||||
echo "OWNER_LOWER=$(echo '${{ gitea.repository_owner }}' | tr '[:upper:]' '[:lower:]')" >> "$GITHUB_ENV"
|
|
||||||
|
|
||||||
- name: Ensure docker CLI exists
|
|
||||||
shell: bash
|
|
||||||
run: |
|
|
||||||
if ! command -v docker >/dev/null 2>&1; then
|
|
||||||
apt-get update
|
|
||||||
apt-get install -y --no-install-recommends docker.io
|
|
||||||
fi
|
|
||||||
docker version
|
|
||||||
|
|
||||||
- name: Login to Gitea Registry
|
|
||||||
shell: bash
|
|
||||||
run: |
|
|
||||||
set -euo pipefail
|
|
||||||
printf '%s' "${{ secrets.REGISTRY_TOKEN }}" | docker login "${{ secrets.REGISTRY_URL }}" \
|
|
||||||
-u "${{ secrets.REGISTRY_USER }}" --password-stdin
|
|
||||||
|
|
||||||
- name: Build & Push Backend
|
|
||||||
shell: bash
|
|
||||||
run: |
|
|
||||||
BACKEND_IMAGE="${{ secrets.REGISTRY_URL }}/${{ env.OWNER_LOWER }}/print-calculator-backend:${{ env.TAG }}"
|
|
||||||
docker build -t "$BACKEND_IMAGE" ./backend
|
|
||||||
docker push "$BACKEND_IMAGE"
|
|
||||||
|
|
||||||
- name: Build & Push Frontend
|
|
||||||
shell: bash
|
|
||||||
run: |
|
|
||||||
FRONTEND_IMAGE="${{ secrets.REGISTRY_URL }}/${{ env.OWNER_LOWER }}/print-calculator-frontend:${{ env.TAG }}"
|
|
||||||
docker build -t "$FRONTEND_IMAGE" ./frontend
|
|
||||||
docker push "$FRONTEND_IMAGE"
|
|
||||||
|
|
||||||
deploy:
|
|
||||||
needs: build-and-push
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- name: Checkout
|
|
||||||
uses: actions/checkout@v4
|
|
||||||
|
|
||||||
- name: Set ENV
|
|
||||||
shell: bash
|
|
||||||
run: |
|
|
||||||
if [[ "${{ gitea.ref }}" == "refs/heads/main" ]]; then
|
|
||||||
echo "ENV=prod" >> "$GITHUB_ENV"
|
|
||||||
elif [[ "${{ gitea.ref }}" == "refs/heads/int" ]]; then
|
|
||||||
echo "ENV=int" >> "$GITHUB_ENV"
|
|
||||||
else
|
|
||||||
echo "ENV=dev" >> "$GITHUB_ENV"
|
|
||||||
fi
|
|
||||||
|
|
||||||
- name: Setup SSH key
|
|
||||||
shell: bash
|
|
||||||
run: |
|
|
||||||
set -euo pipefail
|
|
||||||
|
|
||||||
apt-get update
|
|
||||||
apt-get install -y --no-install-recommends openssh-client
|
|
||||||
|
|
||||||
mkdir -p ~/.ssh
|
|
||||||
chmod 700 ~/.ssh
|
|
||||||
|
|
||||||
printf '%s' "${{ secrets.SSH_PRIVATE_KEY_B64 }}" | tr -d '\r\n\t ' > /tmp/key.b64
|
|
||||||
echo "b64_len=$(wc -c < /tmp/key.b64)"
|
|
||||||
|
|
||||||
base64 -d /tmp/key.b64 > ~/.ssh/id_ed25519
|
|
||||||
|
|
||||||
tr -d '\r' < ~/.ssh/id_ed25519 > ~/.ssh/id_ed25519.clean
|
|
||||||
mv ~/.ssh/id_ed25519.clean ~/.ssh/id_ed25519
|
|
||||||
chmod 600 ~/.ssh/id_ed25519
|
|
||||||
|
|
||||||
ssh-keygen -y -f ~/.ssh/id_ed25519 >/dev/null
|
|
||||||
|
|
||||||
ssh-keyscan -H "${{ secrets.SERVER_HOST }}" >> ~/.ssh/known_hosts 2>/dev/null
|
|
||||||
|
|
||||||
- name: Write env and compose to server
|
|
||||||
shell: bash
|
|
||||||
run: |
|
|
||||||
if [[ "${{ gitea.ref }}" == "refs/heads/main" ]]; then
|
|
||||||
DEPLOY_TAG="prod"
|
|
||||||
elif [[ "${{ gitea.ref }}" == "refs/heads/int" ]]; then
|
|
||||||
DEPLOY_TAG="int"
|
|
||||||
else
|
|
||||||
DEPLOY_TAG="dev"
|
|
||||||
fi
|
|
||||||
DEPLOY_OWNER=$(echo '${{ gitea.repository_owner }}' | tr '[:upper:]' '[:lower:]')
|
|
||||||
|
|
||||||
cat "deploy/envs/${{ env.ENV }}.env" > /tmp/full_env.env
|
|
||||||
|
|
||||||
if [[ "${{ env.ENV }}" == "prod" ]]; then
|
|
||||||
DB_URL="${{ secrets.DB_URL_PROD }}"
|
|
||||||
DB_USER="${{ secrets.DB_USERNAME_PROD }}"
|
|
||||||
DB_PASS="${{ secrets.DB_PASSWORD_PROD }}"
|
|
||||||
elif [[ "${{ env.ENV }}" == "int" ]]; then
|
|
||||||
DB_URL="${{ secrets.DB_URL_INT }}"
|
|
||||||
DB_USER="${{ secrets.DB_USERNAME_INT }}"
|
|
||||||
DB_PASS="${{ secrets.DB_PASSWORD_INT }}"
|
|
||||||
else
|
|
||||||
DB_URL="${{ secrets.DB_URL_DEV }}"
|
|
||||||
DB_USER="${{ secrets.DB_USERNAME_DEV }}"
|
|
||||||
DB_PASS="${{ secrets.DB_PASSWORD_DEV }}"
|
|
||||||
fi
|
|
||||||
|
|
||||||
printf '\nDB_URL="%s"\nDB_USERNAME="%s"\nDB_PASSWORD="%s"\n' \
|
|
||||||
"$DB_URL" "$DB_USER" "$DB_PASS" >> /tmp/full_env.env
|
|
||||||
|
|
||||||
printf 'REGISTRY_URL="%s"\nREPO_OWNER="%s"\nTAG="%s"\n' \
|
|
||||||
"${{ secrets.REGISTRY_URL }}" "$DEPLOY_OWNER" "$DEPLOY_TAG" >> /tmp/full_env.env
|
|
||||||
|
|
||||||
ADMIN_TTL="${{ secrets.ADMIN_SESSION_TTL_MINUTES }}"
|
|
||||||
ADMIN_TTL="${ADMIN_TTL:-480}"
|
|
||||||
printf 'ADMIN_PASSWORD="%s"\nADMIN_SESSION_SECRET="%s"\nADMIN_SESSION_TTL_MINUTES="%s"\n' \
|
|
||||||
"${{ secrets.ADMIN_PASSWORD }}" "${{ secrets.ADMIN_SESSION_SECRET }}" "$ADMIN_TTL" >> /tmp/full_env.env
|
|
||||||
|
|
||||||
echo "Preparing to send env file with variables:"
|
|
||||||
grep -Ev "PASSWORD|SECRET" /tmp/full_env.env || true
|
|
||||||
|
|
||||||
ssh -i ~/.ssh/id_ed25519 -o BatchMode=yes "${{ secrets.SERVER_USER }}@${{ secrets.SERVER_HOST }}" \
|
|
||||||
"setenv ${{ env.ENV }}" < /tmp/full_env.env
|
|
||||||
|
|
||||||
ssh -i ~/.ssh/id_ed25519 -o BatchMode=yes "${{ secrets.SERVER_USER }}@${{ secrets.SERVER_HOST }}" \
|
|
||||||
"setcompose ${{ env.ENV }}" < docker-compose.deploy.yml
|
|
||||||
|
|
||||||
- name: Trigger deploy on Unraid (forced command key)
|
|
||||||
shell: bash
|
|
||||||
run: |
|
|
||||||
set -euo pipefail
|
|
||||||
ssh -i ~/.ssh/id_ed25519 -o BatchMode=yes "${{ secrets.SERVER_USER }}@${{ secrets.SERVER_HOST }}" "deploy ${{ env.ENV }}"
|
|
||||||
@@ -1,185 +0,0 @@
|
|||||||
name: PR Checks
|
|
||||||
|
|
||||||
on:
|
|
||||||
pull_request:
|
|
||||||
branches: [main, int, dev]
|
|
||||||
|
|
||||||
concurrency:
|
|
||||||
group: print-calculator-pr-${{ gitea.ref }}
|
|
||||||
cancel-in-progress: true
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
prettier-autofix:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- name: Checkout
|
|
||||||
uses: actions/checkout@v4
|
|
||||||
with:
|
|
||||||
fetch-depth: 0
|
|
||||||
|
|
||||||
- name: Setup Node 22
|
|
||||||
uses: actions/setup-node@v4
|
|
||||||
with:
|
|
||||||
node-version: "22"
|
|
||||||
|
|
||||||
- name: Apply formatting with Prettier
|
|
||||||
shell: bash
|
|
||||||
run: |
|
|
||||||
npx --yes prettier@3.6.2 --write \
|
|
||||||
"frontend/src/**/*.{ts,html,scss,css,json}" \
|
|
||||||
".gitea/workflows/*.{yml,yaml}"
|
|
||||||
|
|
||||||
- name: Commit and push formatting changes
|
|
||||||
shell: bash
|
|
||||||
run: |
|
|
||||||
if git diff --quiet; then
|
|
||||||
echo "No formatting changes to commit."
|
|
||||||
exit 0
|
|
||||||
fi
|
|
||||||
|
|
||||||
if ! command -v jq >/dev/null 2>&1; then
|
|
||||||
apt-get update
|
|
||||||
apt-get install -y --no-install-recommends jq
|
|
||||||
fi
|
|
||||||
|
|
||||||
EVENT_FILE="${GITHUB_EVENT_PATH:-}"
|
|
||||||
if [[ -z "$EVENT_FILE" || ! -f "$EVENT_FILE" ]]; then
|
|
||||||
echo "Event payload not found, skipping auto-push."
|
|
||||||
exit 0
|
|
||||||
fi
|
|
||||||
|
|
||||||
HEAD_REPO="$(jq -r '.pull_request.head.repo.full_name // empty' "$EVENT_FILE")"
|
|
||||||
BASE_REPO="$(jq -r '.repository.full_name // empty' "$EVENT_FILE")"
|
|
||||||
PR_BRANCH="$(jq -r '.pull_request.head.ref // empty' "$EVENT_FILE")"
|
|
||||||
|
|
||||||
if [[ -z "$PR_BRANCH" ]]; then
|
|
||||||
echo "PR branch not found in event payload, skipping auto-push."
|
|
||||||
exit 0
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [[ -n "$HEAD_REPO" && -n "$BASE_REPO" && "$HEAD_REPO" != "$BASE_REPO" ]]; then
|
|
||||||
echo "PR from fork ($HEAD_REPO), skipping auto-push."
|
|
||||||
exit 0
|
|
||||||
fi
|
|
||||||
|
|
||||||
git config user.name "printcalc-ci"
|
|
||||||
git config user.email "ci@printcalculator.local"
|
|
||||||
|
|
||||||
git add frontend/src .gitea/workflows
|
|
||||||
git commit -m "style: apply prettier formatting"
|
|
||||||
git push origin "HEAD:${PR_BRANCH}"
|
|
||||||
|
|
||||||
security-sast:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- name: Checkout
|
|
||||||
uses: actions/checkout@v4
|
|
||||||
with:
|
|
||||||
fetch-depth: 0
|
|
||||||
|
|
||||||
- name: Install Python and Semgrep
|
|
||||||
shell: bash
|
|
||||||
run: |
|
|
||||||
apt-get update
|
|
||||||
apt-get install -y --no-install-recommends python3 python3-pip
|
|
||||||
python3 -m pip install --upgrade pip
|
|
||||||
python3 -m pip install semgrep
|
|
||||||
|
|
||||||
- name: Run Semgrep (SAST)
|
|
||||||
shell: bash
|
|
||||||
run: |
|
|
||||||
semgrep --version
|
|
||||||
semgrep --config auto --error \
|
|
||||||
--exclude frontend/node_modules \
|
|
||||||
--exclude backend/build \
|
|
||||||
backend/src frontend/src
|
|
||||||
|
|
||||||
- name: Install Gitleaks
|
|
||||||
shell: bash
|
|
||||||
run: |
|
|
||||||
set -euo pipefail
|
|
||||||
VERSION="8.24.2"
|
|
||||||
curl -sSL "https://github.com/gitleaks/gitleaks/releases/download/v${VERSION}/gitleaks_${VERSION}_linux_x64.tar.gz" \
|
|
||||||
-o /tmp/gitleaks.tar.gz
|
|
||||||
tar -xzf /tmp/gitleaks.tar.gz -C /tmp
|
|
||||||
install -m 0755 /tmp/gitleaks /usr/local/bin/gitleaks
|
|
||||||
gitleaks version
|
|
||||||
|
|
||||||
- name: Run Gitleaks (secrets scan)
|
|
||||||
shell: bash
|
|
||||||
run: |
|
|
||||||
set +e
|
|
||||||
gitleaks detect --source . --no-git --redact --exit-code 1 \
|
|
||||||
--report-format json --report-path /tmp/gitleaks-report.json
|
|
||||||
rc=$?
|
|
||||||
if [[ $rc -ne 0 ]]; then
|
|
||||||
echo "Gitleaks findings:"
|
|
||||||
cat /tmp/gitleaks-report.json
|
|
||||||
fi
|
|
||||||
exit $rc
|
|
||||||
|
|
||||||
test-backend:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- name: Checkout
|
|
||||||
uses: actions/checkout@v4
|
|
||||||
|
|
||||||
- name: Set up JDK 21
|
|
||||||
uses: actions/setup-java@v4
|
|
||||||
with:
|
|
||||||
java-version: "21"
|
|
||||||
distribution: "temurin"
|
|
||||||
cache: gradle
|
|
||||||
|
|
||||||
- name: Run Tests with Gradle
|
|
||||||
run: |
|
|
||||||
cd backend
|
|
||||||
chmod +x gradlew
|
|
||||||
./gradlew test
|
|
||||||
|
|
||||||
test-frontend:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- name: Checkout
|
|
||||||
uses: actions/checkout@v4
|
|
||||||
|
|
||||||
- name: Setup Node 22
|
|
||||||
uses: actions/setup-node@v4
|
|
||||||
with:
|
|
||||||
node-version: "22"
|
|
||||||
cache: "npm"
|
|
||||||
cache-dependency-path: "frontend/package-lock.json"
|
|
||||||
|
|
||||||
- name: Resolve Chrome binary
|
|
||||||
shell: bash
|
|
||||||
run: |
|
|
||||||
set -euo pipefail
|
|
||||||
if command -v chromium >/dev/null 2>&1; then
|
|
||||||
CHROME_PATH="$(command -v chromium)"
|
|
||||||
elif command -v chromium-browser >/dev/null 2>&1; then
|
|
||||||
CHROME_PATH="$(command -v chromium-browser)"
|
|
||||||
elif command -v google-chrome >/dev/null 2>&1; then
|
|
||||||
CHROME_PATH="$(command -v google-chrome)"
|
|
||||||
else
|
|
||||||
apt-get update
|
|
||||||
apt-get install -y --no-install-recommends chromium
|
|
||||||
CHROME_PATH="$(command -v chromium)"
|
|
||||||
fi
|
|
||||||
|
|
||||||
echo "CHROME_BIN=$CHROME_PATH" >> "$GITHUB_ENV"
|
|
||||||
echo "Using CHROME_BIN=$CHROME_PATH"
|
|
||||||
|
|
||||||
- name: Install frontend dependencies
|
|
||||||
shell: bash
|
|
||||||
run: |
|
|
||||||
cd frontend
|
|
||||||
npm ci --no-audit --no-fund --prefer-offline
|
|
||||||
|
|
||||||
- name: Run frontend tests (headless)
|
|
||||||
shell: bash
|
|
||||||
env:
|
|
||||||
CI: "true"
|
|
||||||
run: |
|
|
||||||
cd frontend
|
|
||||||
echo "Karma CHROME_BIN=$CHROME_BIN"
|
|
||||||
npm run test -- --watch=false --browsers=ChromeHeadlessNoSandbox
|
|
||||||
8
.gitignore
vendored
8
.gitignore
vendored
@@ -41,11 +41,3 @@ target/
|
|||||||
build/
|
build/
|
||||||
.gradle/
|
.gradle/
|
||||||
.mvn/
|
.mvn/
|
||||||
|
|
||||||
./storage_orders
|
|
||||||
./storage_quotes
|
|
||||||
storage_orders
|
|
||||||
storage_quotes
|
|
||||||
|
|
||||||
# Qodana local reports/artifacts
|
|
||||||
backend/.qodana/
|
|
||||||
|
|||||||
49
GEMINI.md
49
GEMINI.md
@@ -4,42 +4,35 @@ Questo file serve a dare contesto all'AI (Antigravity/Gemini) sulla struttura e
|
|||||||
|
|
||||||
## Project Overview
|
## Project Overview
|
||||||
**Nome**: Print Calculator
|
**Nome**: Print Calculator
|
||||||
**Scopo**: Calcolare costi e tempi di stampa 3D da file STL in modo preciso tramite slicing reale.
|
**Scopo**: Calcolare costi e tempi di stampa 3D da file STL.
|
||||||
**Stack**:
|
**Stack**:
|
||||||
- **Backend**: Java 21 (Spring Boot 3.4), PostgreSQL, Flyway.
|
- **Backend**: Python (FastAPI), libreria `trimesh` per analisi geometrica.
|
||||||
- **Frontend**: Angular 19 (TypeScript), Angular Material, Three.js per visualizzazione 3D.
|
- **Frontend**: Angular 19 (TypeScript).
|
||||||
|
|
||||||
## Architecture
|
## Architecture
|
||||||
|
|
||||||
### Backend (`/backend`)
|
### Backend (`/backend`)
|
||||||
- **`BackendApplication.java`**: Entrypoint dell'applicazione Spring Boot.
|
- **`main.py`**: Entrypoint dell'applicazione FastAPI.
|
||||||
- **`controller/`**: Espone le API REST per l'upload e il calcolo dei preventivi.
|
- Definisce l'API `POST /calculate/stl`.
|
||||||
- **`service/SlicerService.java`**: Wrappa l'eseguibile di **OrcaSlicer** per effettuare lo slicing reale del modello.
|
- Gestisce l'upload del file, invoca lo slicer e restituisce il preventivo.
|
||||||
- Gestisce i profili di stampa (Macchina, Processo, Filamento) caricati da file JSON.
|
- Configura CORS per permettere chiamate dal frontend.
|
||||||
- Crea configurazioni on-the-fly e invoca OrcaSlicer in modalità headless.
|
- **`slicer.py`**: Wrappa l'eseguibile di **OrcaSlicer** per effettuare lo slicing reale del modello.
|
||||||
- **`service/GCodeParser.java`**: Analizza il G-Code generato per estrarre tempo di stampa e peso del materiale dai metadati del file.
|
- Gestisce i profili di stampa (Macchina, Processo, Filamento).
|
||||||
- **`service/QuoteCalculator.java`**: Calcola il prezzo finale basandosi su politiche di prezzo salvate nel database.
|
- Crea configurazioni on-the-fly per supportare mesh di grandi dimensioni.
|
||||||
- Gestisce costi macchina a scaglioni (tiered pricing).
|
- **`calculator.py`**: Analizza il G-Code generato.
|
||||||
- Calcola costi energetici basati sulla potenza della stampante e costo del kWh.
|
- `GCodeParser`: Estrae tempo di stampa e materiale usato dai metadati del G-Code.
|
||||||
- Applica markup percentuali e fee fissi per job.
|
- `QuoteCalculator`: Applica i costi (orari, energia, materiale) per generare il prezzo finale.
|
||||||
|
|
||||||
### Frontend (`/frontend`)
|
### Frontend (`/frontend`)
|
||||||
- Applicazione Angular 19 con architettura modulare (core, features, shared).
|
- Applicazione Angular standard.
|
||||||
- **Three.js**: Utilizzato per il rendering dei file STL caricati dall'utente.
|
- Usa Angular Material.
|
||||||
- **Angular Material**: Per l'interfaccia utente.
|
- Service per upload STL e visualizzazione preventivo.
|
||||||
- **ngx-translate**: Per il supporto multilingua.
|
|
||||||
|
|
||||||
## Key Concepts
|
## Key Concepts
|
||||||
- **Real Slicing**: Il backend esegue un vero slicing usando OrcaSlicer. Questo garantisce stime di tempo e materiale estremamente precise.
|
- **Real Slicing**: Il backend esegue un vero slicing usando OrcaSlicer in modalità headless. Questo garantisce stime di tempo e materiale estremamente precise, identiche a quelle che si otterrebbero preparando il file per la stampa.
|
||||||
- **Database-Driven Pricing**: A differenza di versioni precedenti, il calcolo del preventivo è ora guidato da entità DB (`PricingPolicy`, `PrinterMachine`, `FilamentVariant`).
|
- **G-Code Parsing**: Invece di stimare geometricamente, l'applicazione legge direttamene i commenti generati dallo slicer nel G-Code (es. `estimated printing time`, `filament used`).
|
||||||
- **G-Code Metadata**: L'applicazione legge direttamene i commenti generati dallo slicer nel G-Code (es. `; estimated printing time`, `; filament used [g]`).
|
|
||||||
|
|
||||||
## Development Notes
|
## Development Notes
|
||||||
- **Backend**: Richiede JDK 21. Si avvia con `./gradlew bootRun`.
|
- Per eseguire il backend serve `uvicorn`.
|
||||||
- **Database**: Richiede PostgreSQL. Le migrazioni sono gestite da Flyway.
|
- Il frontend richiede `npm install` al primo avvio.
|
||||||
- **Frontend**: Richiede Node.js 22. Si avvia con `npm start`.
|
- Le configurazioni di stampa (layer height, wall thickness, infill) sono attualmente hardcoded o con valori di default nel backend, ma potrebbero essere esposte come parametri API in futuro.
|
||||||
- **OrcaSlicer**: Deve essere installato sul sistema e il percorso configurato in `application.properties` o tramite variabile d'ambiente `SLICER_PATH`.
|
|
||||||
|
|
||||||
## AI Agent Rules
|
|
||||||
- **No Inline Code**: Tutti i componenti Angular DEVONO usare file separati per HTML (`templateUrl`) e SCSS (`styleUrl`). È vietato usare `template` o `styles` inline nel decoratore `@Component`.
|
|
||||||
- **Spring Boot Conventions**: Seguire i pattern standard di Spring Boot (Service-Repository-Controller).
|
|
||||||
|
|||||||
10
Makefile
Normal file
10
Makefile
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
.PHONY: install s
|
||||||
|
install:
|
||||||
|
@echo "Installing Backend dependencies..."
|
||||||
|
cd backend && pip install -r requirements.txt || pip install fastapi uvicorn trimesh python-multipart numpy
|
||||||
|
@echo "Installing Frontend dependencies..."
|
||||||
|
cd frontend && npm install
|
||||||
|
|
||||||
|
start:
|
||||||
|
@echo "Starting development environment..."
|
||||||
|
./start.sh
|
||||||
93
README.md
93
README.md
@@ -1,67 +1,70 @@
|
|||||||
# Print Calculator (OrcaSlicer Edition)
|
# Print Calculator (OrcaSlicer Edition)
|
||||||
|
|
||||||
Un'applicazione Full Stack (Angular + Spring Boot) per calcolare preventivi di stampa 3D precisi utilizzando **OrcaSlicer** in modalità headless.
|
Un'applicazione Full Stack (Angular + Python/FastAPI) per calcolare preventivi di stampa 3D precisi utilizzando **OrcaSlicer** in modalità headless.
|
||||||
|
|
||||||
## Funzionalità
|
## Funzionalità
|
||||||
|
|
||||||
* **Slicing Reale**: Usa il motore di OrcaSlicer per stimare tempo e materiale, garantendo la massima precisione.
|
* **Slicing Reale**: Usa il motore di OrcaSlicer per stimare tempo e materiale, non semplici approssimazioni geometriche.
|
||||||
* **Preventivazione Database-Driven**: Calcolo basato su politiche di prezzo configurabili nel database (costo materiale, ammortamento macchina a scaglioni, energia e markup).
|
* **Preventivazione Completa**: Calcola costo materiale, ammortamento macchina, energia e ricarico.
|
||||||
* **Visualizzazione 3D**: Anteprima del file STL caricato tramite Three.js.
|
* **Configurabile**: Prezzi e parametri macchina modificabili via variabili d'ambiente.
|
||||||
* **Multi-Profilo**: Supporto per diverse stampanti, materiali e profili di processo.
|
* **Docker Ready**: Tutto containerizzato per un facile deployment.
|
||||||
|
|
||||||
## Stack Tecnologico
|
|
||||||
|
|
||||||
- **Backend**: Java 21, Spring Boot 3.4, PostgreSQL, Flyway.
|
|
||||||
- **Frontend**: Angular 19, Angular Material, Three.js.
|
|
||||||
- **Slicer**: OrcaSlicer (invocato via CLI).
|
|
||||||
|
|
||||||
## Prerequisiti
|
## Prerequisiti
|
||||||
|
|
||||||
* **Java 21** installato.
|
* Docker Desktop & Docker Compose installati.
|
||||||
* **Node.js 22** e **npm** installati.
|
|
||||||
* **PostgreSQL** attivo.
|
|
||||||
* **OrcaSlicer** installato sul sistema.
|
|
||||||
|
|
||||||
## Avvio Rapido
|
## Avvio Rapido
|
||||||
|
|
||||||
### 1. Database
|
1. Clona il repository.
|
||||||
Crea un database PostgreSQL chiamato `printcalc`. Le tabelle verranno create automaticamente al primo avvio tramite Flyway.
|
2. Esegui lo script di avvio o docker-compose:
|
||||||
|
```bash
|
||||||
|
docker-compose up --build
|
||||||
|
```
|
||||||
|
*Nota: La prima build impiegherà alcuni minuti per scaricare OrcaSlicer (~200MB) e compilare il Frontend.*
|
||||||
|
|
||||||
### 2. Backend
|
3. Accedi all'applicazione:
|
||||||
Configura il percorso di OrcaSlicer in `backend/src/main/resources/application.properties` o tramite la variabile d'ambiente `SLICER_PATH`.
|
* **Frontend**: [http://localhost](http://localhost)
|
||||||
|
* **API Docs**: [http://localhost:8000/docs](http://localhost:8000/docs)
|
||||||
|
|
||||||
|
## Configurazione Prezzi
|
||||||
|
|
||||||
|
Puoi modificare i prezzi nel file `docker-compose.yml` (sezione `environment` del servizio backend):
|
||||||
|
|
||||||
|
* `FILAMENT_COST_PER_KG`: Costo filamento al kg (es. 25.0).
|
||||||
|
* `MACHINE_COST_PER_HOUR`: Costo orario macchina (ammortamento/manutenzione).
|
||||||
|
* `ENERGY_COST_PER_KWH`: Costo energia elettrica.
|
||||||
|
* `MARKUP_PERCENT`: Margine di profitto percentuale (es. 20 = +20%).
|
||||||
|
|
||||||
|
## Struttura del Progetto
|
||||||
|
|
||||||
|
* `/backend`: API Python FastAPI. Include Dockerfile che scarica OrcaSlicer AppImage.
|
||||||
|
* `/frontend`: Applicazione Angular 19+ con Material Design.
|
||||||
|
* `/backend/profiles`: Contiene i profili di slicing (.ini). Attualmente configurato per una stima generica simil-Bambu Lab A1.
|
||||||
|
|
||||||
|
## Troubleshooting
|
||||||
|
|
||||||
|
### Errore Download OrcaSlicer
|
||||||
|
Se la build del backend fallisce durante il download di `OrcaSlicer.AppImage`, verifica la tua connessione internet o aggiorna l'URL nel `backend/Dockerfile`.
|
||||||
|
|
||||||
|
### Slicing Fallito (Costo 0 o Errore)
|
||||||
|
Se l'API ritorna errore o valori nulli:
|
||||||
|
1. Controlla che il file STL sia valido (manifold).
|
||||||
|
2. Controlla i log del backend: `docker logs print-calculator-backend`.
|
||||||
|
|
||||||
|
## Sviluppo Locale (Senza Docker)
|
||||||
|
|
||||||
|
**Backend**:
|
||||||
|
Richiede Linux (o WSL2) per eseguire l'AppImage di OrcaSlicer.
|
||||||
```bash
|
```bash
|
||||||
cd backend
|
cd backend
|
||||||
./gradlew bootRun
|
pip install -r requirements.txt
|
||||||
|
# Assicurati di avere OrcaSlicer installato e nel PATH o aggiorna SLICER_PATH in slicer.py
|
||||||
|
uvicorn main:app --reload
|
||||||
```
|
```
|
||||||
|
|
||||||
### 3. Frontend
|
**Frontend**:
|
||||||
```bash
|
```bash
|
||||||
cd frontend
|
cd frontend
|
||||||
npm install
|
npm install
|
||||||
npm start
|
npm start
|
||||||
```
|
```
|
||||||
|
|
||||||
Accedi a [http://localhost:4200](http://localhost:4200).
|
|
||||||
|
|
||||||
## Configurazione Prezzi
|
|
||||||
|
|
||||||
I prezzi non sono più gestiti tramite variabili d'ambiente fisse ma tramite tabelle nel database:
|
|
||||||
- `pricing_policy`: Definisce markup, fee fissi e costi elettrici.
|
|
||||||
- `pricing_policy_machine_hour_tier`: Definisce i costi orari delle macchine in base alla durata della stampa.
|
|
||||||
- `printer_machine`: Anagrafica stampanti e consumi energetici.
|
|
||||||
- `filament_material_type` / `filament_variant`: Listino prezzi materiali.
|
|
||||||
|
|
||||||
## Struttura del Progetto
|
|
||||||
|
|
||||||
* `/backend`: API Spring Boot.
|
|
||||||
* `/frontend`: Applicazione Angular.
|
|
||||||
* `/backend/profiles`: Contiene i file di configurazione per OrcaSlicer.
|
|
||||||
|
|
||||||
## Troubleshooting
|
|
||||||
|
|
||||||
### Percorso OrcaSlicer
|
|
||||||
Assicurati che `slicer.path` punti al binario corretto. Su macOS è solitamente `/Applications/OrcaSlicer.app/Contents/MacOS/OrcaSlicer`. Su Linux è il percorso all'AppImage (estratta o meno).
|
|
||||||
|
|
||||||
### Database connection
|
|
||||||
Verifica le credenziali in `application.properties`. Se usi Docker, puoi passare `DB_URL`, `DB_USERNAME` e `DB_PASSWORD` come variabili d'ambiente.
|
|
||||||
|
|||||||
@@ -10,58 +10,28 @@ RUN ./gradlew bootJar -x test --no-daemon
|
|||||||
|
|
||||||
# Stage 2: Runtime Environment
|
# Stage 2: Runtime Environment
|
||||||
FROM eclipse-temurin:21-jre-jammy
|
FROM eclipse-temurin:21-jre-jammy
|
||||||
ARG ORCA_VERSION=2.3.1
|
|
||||||
ARG ORCA_DOWNLOAD_URL
|
|
||||||
|
|
||||||
# Install system dependencies for OrcaSlicer (same as before)
|
# Install system dependencies for OrcaSlicer (same as before)
|
||||||
RUN apt-get update && apt-get install -y \
|
RUN apt-get update && apt-get install -y \
|
||||||
wget \
|
wget \
|
||||||
assimp-utils \
|
p7zip-full \
|
||||||
libgl1 \
|
libgl1 \
|
||||||
libglib2.0-0 \
|
libglib2.0-0 \
|
||||||
libgtk-3-0 \
|
libgtk-3-0 \
|
||||||
libdbus-1-3 \
|
libdbus-1-3 \
|
||||||
libwebkit2gtk-4.0-37 \
|
libwebkit2gtk-4.1-0 \
|
||||||
&& rm -rf /var/lib/apt/lists/*
|
&& rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
# Install OrcaSlicer
|
# Install OrcaSlicer
|
||||||
WORKDIR /opt
|
WORKDIR /opt
|
||||||
RUN set -eux; \
|
RUN wget -q https://github.com/SoftFever/OrcaSlicer/releases/download/v2.2.0/OrcaSlicer_Linux_V2.2.0.AppImage -O OrcaSlicer.AppImage \
|
||||||
ORCA_URL="${ORCA_DOWNLOAD_URL:-}"; \
|
&& 7z x OrcaSlicer.AppImage -o/opt/orcaslicer \
|
||||||
if [ -n "${ORCA_URL}" ]; then \
|
|
||||||
wget -q "${ORCA_URL}" -O OrcaSlicer.AppImage; \
|
|
||||||
else \
|
|
||||||
CANDIDATES="\
|
|
||||||
https://github.com/OrcaSlicer/OrcaSlicer/releases/download/v${ORCA_VERSION}/OrcaSlicer_Linux_AppImage_Ubuntu2204_V${ORCA_VERSION}.AppImage \
|
|
||||||
https://github.com/SoftFever/OrcaSlicer/releases/download/v${ORCA_VERSION}/OrcaSlicer_Linux_AppImage_Ubuntu2204_V${ORCA_VERSION}.AppImage \
|
|
||||||
https://github.com/SoftFever/OrcaSlicer/releases/download/v2.2.0/OrcaSlicer_Linux_V2.2.0.AppImage"; \
|
|
||||||
ok=0; \
|
|
||||||
for url in $CANDIDATES; do \
|
|
||||||
if wget -q --spider "$url"; then \
|
|
||||||
echo "Using OrcaSlicer URL: $url"; \
|
|
||||||
wget -q "$url" -O OrcaSlicer.AppImage; \
|
|
||||||
ok=1; \
|
|
||||||
break; \
|
|
||||||
fi; \
|
|
||||||
done; \
|
|
||||||
if [ "$ok" -ne 1 ]; then \
|
|
||||||
echo "Failed to find OrcaSlicer AppImage for version ${ORCA_VERSION}" >&2; \
|
|
||||||
echo "Tried URLs:" >&2; \
|
|
||||||
for url in $CANDIDATES; do echo " - $url" >&2; done; \
|
|
||||||
exit 1; \
|
|
||||||
fi; \
|
|
||||||
fi \
|
|
||||||
&& chmod +x OrcaSlicer.AppImage \
|
|
||||||
&& rm -rf /opt/orcaslicer /opt/squashfs-root \
|
|
||||||
&& ./OrcaSlicer.AppImage --appimage-extract >/dev/null \
|
|
||||||
&& mv /opt/squashfs-root /opt/orcaslicer \
|
|
||||||
&& chmod -R +x /opt/orcaslicer \
|
&& chmod -R +x /opt/orcaslicer \
|
||||||
&& rm OrcaSlicer.AppImage
|
&& rm OrcaSlicer.AppImage
|
||||||
|
|
||||||
ENV PATH="/opt/orcaslicer/usr/bin:${PATH}"
|
ENV PATH="/opt/orcaslicer/usr/bin:${PATH}"
|
||||||
# Set Slicer Path env variable for Java app
|
# Set Slicer Path env variable for Java app
|
||||||
ENV SLICER_PATH="/opt/orcaslicer/AppRun"
|
ENV SLICER_PATH="/opt/orcaslicer/AppRun"
|
||||||
ENV ASSIMP_PATH="assimp"
|
|
||||||
|
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
# Copy JAR from build stage
|
# Copy JAR from build stage
|
||||||
@@ -71,6 +41,4 @@ COPY profiles ./profiles
|
|||||||
|
|
||||||
EXPOSE 8080
|
EXPOSE 8080
|
||||||
|
|
||||||
COPY entrypoint.sh .
|
CMD ["java", "-jar", "app.jar"]
|
||||||
RUN chmod +x entrypoint.sh
|
|
||||||
ENTRYPOINT ["./entrypoint.sh"]
|
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
plugins {
|
plugins {
|
||||||
id 'java'
|
id 'java'
|
||||||
id 'application'
|
|
||||||
id 'org.springframework.boot' version '3.4.1'
|
id 'org.springframework.boot' version '3.4.1'
|
||||||
id 'io.spring.dependency-management' version '1.1.7'
|
id 'io.spring.dependency-management' version '1.1.7'
|
||||||
}
|
}
|
||||||
@@ -14,56 +13,17 @@ java {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
application {
|
|
||||||
mainClass = 'com.printcalculator.BackendApplication'
|
|
||||||
}
|
|
||||||
|
|
||||||
repositories {
|
repositories {
|
||||||
mavenCentral()
|
mavenCentral()
|
||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
implementation 'org.springframework.boot:spring-boot-starter-web'
|
implementation 'org.springframework.boot:spring-boot-starter-web'
|
||||||
implementation 'org.springframework.boot:spring-boot-starter-security'
|
|
||||||
implementation 'org.springframework.boot:spring-boot-starter-validation'
|
|
||||||
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
|
|
||||||
implementation 'xyz.capybara:clamav-client:2.1.2'
|
|
||||||
implementation 'org.springframework.boot:spring-boot-starter-actuator'
|
|
||||||
runtimeOnly 'org.postgresql:postgresql'
|
|
||||||
developmentOnly 'org.springframework.boot:spring-boot-devtools'
|
developmentOnly 'org.springframework.boot:spring-boot-devtools'
|
||||||
testImplementation 'org.springframework.boot:spring-boot-starter-test'
|
testImplementation 'org.springframework.boot:spring-boot-starter-test'
|
||||||
testImplementation 'org.springframework.security:spring-security-test'
|
|
||||||
testRuntimeOnly 'com.h2database:h2'
|
|
||||||
testRuntimeOnly 'org.junit.platform:junit-platform-launcher'
|
testRuntimeOnly 'org.junit.platform:junit-platform-launcher'
|
||||||
compileOnly 'org.projectlombok:lombok'
|
|
||||||
annotationProcessor 'org.projectlombok:lombok'
|
|
||||||
implementation 'io.github.openhtmltopdf:openhtmltopdf-pdfbox:1.1.37'
|
|
||||||
implementation 'io.github.openhtmltopdf:openhtmltopdf-svg-support:1.1.37'
|
|
||||||
implementation 'org.springframework.boot:spring-boot-starter-thymeleaf'
|
|
||||||
implementation 'net.codecrete.qrbill:qrbill-generator:3.4.0'
|
|
||||||
implementation 'org.springframework.boot:spring-boot-starter-mail'
|
|
||||||
implementation platform('org.lwjgl:lwjgl-bom:3.3.4')
|
|
||||||
implementation 'org.lwjgl:lwjgl'
|
|
||||||
implementation 'org.lwjgl:lwjgl-assimp'
|
|
||||||
runtimeOnly 'org.lwjgl:lwjgl::natives-linux'
|
|
||||||
runtimeOnly 'org.lwjgl:lwjgl::natives-macos'
|
|
||||||
runtimeOnly 'org.lwjgl:lwjgl::natives-macos-arm64'
|
|
||||||
runtimeOnly 'org.lwjgl:lwjgl-assimp::natives-linux'
|
|
||||||
runtimeOnly 'org.lwjgl:lwjgl-assimp::natives-macos'
|
|
||||||
runtimeOnly 'org.lwjgl:lwjgl-assimp::natives-macos-arm64'
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
tasks.named('test') {
|
tasks.named('test') {
|
||||||
useJUnitPlatform()
|
useJUnitPlatform()
|
||||||
}
|
}
|
||||||
|
|
||||||
tasks.named('bootRun') {
|
|
||||||
args = ["--spring.profiles.active=local"]
|
|
||||||
}
|
|
||||||
|
|
||||||
application {
|
|
||||||
applicationDefaultJvmArgs = ["-Dspring.profiles.active=local"]
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,25 +0,0 @@
|
|||||||
#!/bin/sh
|
|
||||||
echo "----------------------------------------------------------------"
|
|
||||||
echo "Starting Backend Application"
|
|
||||||
echo "DB_URL: $DB_URL"
|
|
||||||
echo "DB_USERNAME: $DB_USERNAME"
|
|
||||||
echo "SPRING_DATASOURCE_URL: $SPRING_DATASOURCE_URL"
|
|
||||||
echo "SLICER_PATH: $SLICER_PATH"
|
|
||||||
echo "----------------------------------------------------------------"
|
|
||||||
|
|
||||||
# Determine which environment variables to use for database connection
|
|
||||||
# This allows compatibility with different docker-compose configurations
|
|
||||||
FINAL_DB_URL="${DB_URL:-$SPRING_DATASOURCE_URL}"
|
|
||||||
FINAL_DB_USER="${DB_USERNAME:-$SPRING_DATASOURCE_USERNAME}"
|
|
||||||
FINAL_DB_PASS="${DB_PASSWORD:-$SPRING_DATASOURCE_PASSWORD}"
|
|
||||||
|
|
||||||
if [ -n "$FINAL_DB_URL" ]; then
|
|
||||||
echo "Using database URL: $FINAL_DB_URL"
|
|
||||||
exec java -jar app.jar \
|
|
||||||
--spring.datasource.url="${FINAL_DB_URL}" \
|
|
||||||
--spring.datasource.username="${FINAL_DB_USER}" \
|
|
||||||
--spring.datasource.password="${FINAL_DB_PASS}"
|
|
||||||
else
|
|
||||||
echo "No database URL specified in environment, relying on application.properties defaults."
|
|
||||||
exec java -jar app.jar
|
|
||||||
fi
|
|
||||||
File diff suppressed because one or more lines are too long
@@ -1,48 +0,0 @@
|
|||||||
#-------------------------------------------------------------------------------#
|
|
||||||
# Qodana analysis is configured by qodana.yaml file #
|
|
||||||
# https://www.jetbrains.com/help/qodana/qodana-yaml.html #
|
|
||||||
#-------------------------------------------------------------------------------#
|
|
||||||
|
|
||||||
#################################################################################
|
|
||||||
# WARNING: Do not store sensitive information in this file, #
|
|
||||||
# as its contents will be included in the Qodana report. #
|
|
||||||
#################################################################################
|
|
||||||
version: "1.0"
|
|
||||||
|
|
||||||
#Specify inspection profile for code analysis
|
|
||||||
profile:
|
|
||||||
name: qodana.starter
|
|
||||||
|
|
||||||
#Enable inspections
|
|
||||||
#include:
|
|
||||||
# - name: <SomeEnabledInspectionId>
|
|
||||||
|
|
||||||
#Disable inspections
|
|
||||||
#exclude:
|
|
||||||
# - name: <SomeDisabledInspectionId>
|
|
||||||
# paths:
|
|
||||||
# - <path/where/not/run/inspection>
|
|
||||||
|
|
||||||
projectJDK: "21" #(Applied in CI/CD pipeline)
|
|
||||||
|
|
||||||
#Execute shell command before Qodana execution (Applied in CI/CD pipeline)
|
|
||||||
#bootstrap: sh ./prepare-qodana.sh
|
|
||||||
|
|
||||||
#Install IDE plugins before Qodana execution (Applied in CI/CD pipeline)
|
|
||||||
#plugins:
|
|
||||||
# - id: <plugin.id> #(plugin id can be found at https://plugins.jetbrains.com)
|
|
||||||
|
|
||||||
# Quality gate. Will fail the CI/CD pipeline if any condition is not met
|
|
||||||
# severityThresholds - configures maximum thresholds for different problem severities
|
|
||||||
# testCoverageThresholds - configures minimum code coverage on a whole project and newly added code
|
|
||||||
# Code Coverage is available in Ultimate and Ultimate Plus plans
|
|
||||||
#failureConditions:
|
|
||||||
# severityThresholds:
|
|
||||||
# any: 15
|
|
||||||
# critical: 5
|
|
||||||
# testCoverageThresholds:
|
|
||||||
# fresh: 70
|
|
||||||
# total: 50
|
|
||||||
|
|
||||||
#Specify Qodana linter for analysis (Applied in CI/CD pipeline)
|
|
||||||
linter: jetbrains/qodana-jvm:2025.3
|
|
||||||
@@ -2,15 +2,8 @@ package com.printcalculator;
|
|||||||
|
|
||||||
import org.springframework.boot.SpringApplication;
|
import org.springframework.boot.SpringApplication;
|
||||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||||
import org.springframework.boot.autoconfigure.security.servlet.UserDetailsServiceAutoConfiguration;
|
|
||||||
import org.springframework.scheduling.annotation.EnableScheduling;
|
|
||||||
import org.springframework.scheduling.annotation.EnableAsync;
|
|
||||||
import org.springframework.transaction.annotation.EnableTransactionManagement;
|
|
||||||
|
|
||||||
@SpringBootApplication(exclude = {UserDetailsServiceAutoConfiguration.class})
|
@SpringBootApplication
|
||||||
@EnableTransactionManagement
|
|
||||||
@EnableScheduling
|
|
||||||
@EnableAsync
|
|
||||||
public class BackendApplication {
|
public class BackendApplication {
|
||||||
|
|
||||||
public static void main(String[] args) {
|
public static void main(String[] args) {
|
||||||
|
|||||||
@@ -0,0 +1,43 @@
|
|||||||
|
package com.printcalculator.config;
|
||||||
|
|
||||||
|
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||||
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
|
||||||
|
@Configuration
|
||||||
|
@ConfigurationProperties(prefix = "pricing")
|
||||||
|
public class AppProperties {
|
||||||
|
|
||||||
|
private double filamentCostPerKg;
|
||||||
|
private double machineCostPerHour;
|
||||||
|
private double energyCostPerKwh;
|
||||||
|
private double printerPowerWatts;
|
||||||
|
private double markupPercent;
|
||||||
|
|
||||||
|
private String slicerPath;
|
||||||
|
private String profilesRoot;
|
||||||
|
|
||||||
|
// Getters and Setters needed for Spring binding
|
||||||
|
|
||||||
|
public double getFilamentCostPerKg() { return filamentCostPerKg; }
|
||||||
|
public void setFilamentCostPerKg(double filamentCostPerKg) { this.filamentCostPerKg = filamentCostPerKg; }
|
||||||
|
|
||||||
|
public double getMachineCostPerHour() { return machineCostPerHour; }
|
||||||
|
public void setMachineCostPerHour(double machineCostPerHour) { this.machineCostPerHour = machineCostPerHour; }
|
||||||
|
|
||||||
|
public double getEnergyCostPerKwh() { return energyCostPerKwh; }
|
||||||
|
public void setEnergyCostPerKwh(double energyCostPerKwh) { this.energyCostPerKwh = energyCostPerKwh; }
|
||||||
|
|
||||||
|
public double getPrinterPowerWatts() { return printerPowerWatts; }
|
||||||
|
public void setPrinterPowerWatts(double printerPowerWatts) { this.printerPowerWatts = printerPowerWatts; }
|
||||||
|
|
||||||
|
public double getMarkupPercent() { return markupPercent; }
|
||||||
|
public void setMarkupPercent(double markupPercent) { this.markupPercent = markupPercent; }
|
||||||
|
|
||||||
|
// Slicer props are not under "pricing" prefix in properties file?
|
||||||
|
// Wait, in application.properties I put them at root level/custom.
|
||||||
|
// Let's fix this class to map correctly or change prefix.
|
||||||
|
// I'll make a separate section or just bind manually.
|
||||||
|
// Actually, I'll just add @Value in services for simplicity or fix the prefix structure.
|
||||||
|
// Let's stick to standard @Value for simple paths if this is messy.
|
||||||
|
// Or better, creating a dedicated SlicerProperties.
|
||||||
|
}
|
||||||
@@ -1,27 +0,0 @@
|
|||||||
package com.printcalculator.config;
|
|
||||||
|
|
||||||
import org.springframework.context.annotation.Configuration;
|
|
||||||
import org.springframework.context.annotation.Profile;
|
|
||||||
import org.springframework.web.servlet.config.annotation.CorsRegistry;
|
|
||||||
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
|
|
||||||
|
|
||||||
@Configuration
|
|
||||||
public class CorsConfig implements WebMvcConfigurer {
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void addCorsMappings(CorsRegistry registry) {
|
|
||||||
registry.addMapping("/**")
|
|
||||||
.allowedOrigins(
|
|
||||||
"http://localhost",
|
|
||||||
"http://localhost:4200",
|
|
||||||
"http://localhost:80",
|
|
||||||
"http://127.0.0.1",
|
|
||||||
"https://dev.3d-fab.ch",
|
|
||||||
"https://int.3d-fab.ch",
|
|
||||||
"https://3d-fab.ch"
|
|
||||||
)
|
|
||||||
.allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS", "PATCH")
|
|
||||||
.allowedHeaders("*")
|
|
||||||
.allowCredentials(true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,47 +0,0 @@
|
|||||||
package com.printcalculator.config;
|
|
||||||
|
|
||||||
import com.printcalculator.security.AdminSessionAuthenticationFilter;
|
|
||||||
import org.springframework.context.annotation.Bean;
|
|
||||||
import org.springframework.context.annotation.Configuration;
|
|
||||||
import org.springframework.http.HttpMethod;
|
|
||||||
import org.springframework.http.MediaType;
|
|
||||||
import org.springframework.security.config.Customizer;
|
|
||||||
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
|
|
||||||
import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
|
|
||||||
import org.springframework.security.config.http.SessionCreationPolicy;
|
|
||||||
import org.springframework.security.web.SecurityFilterChain;
|
|
||||||
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
|
|
||||||
|
|
||||||
@Configuration
|
|
||||||
public class SecurityConfig {
|
|
||||||
|
|
||||||
@Bean
|
|
||||||
public SecurityFilterChain securityFilterChain(
|
|
||||||
HttpSecurity http,
|
|
||||||
AdminSessionAuthenticationFilter adminSessionAuthenticationFilter
|
|
||||||
) throws Exception {
|
|
||||||
http
|
|
||||||
.csrf(AbstractHttpConfigurer::disable)
|
|
||||||
.cors(Customizer.withDefaults())
|
|
||||||
.sessionManagement(sm -> sm.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
|
|
||||||
.httpBasic(AbstractHttpConfigurer::disable)
|
|
||||||
.formLogin(AbstractHttpConfigurer::disable)
|
|
||||||
.logout(AbstractHttpConfigurer::disable)
|
|
||||||
.authorizeHttpRequests(auth -> auth
|
|
||||||
.requestMatchers(HttpMethod.OPTIONS, "/**").permitAll()
|
|
||||||
.requestMatchers("/actuator/health", "/actuator/health/**").permitAll()
|
|
||||||
.requestMatchers("/actuator/**").denyAll()
|
|
||||||
.requestMatchers("/api/admin/auth/login").permitAll()
|
|
||||||
.requestMatchers("/api/admin/**").authenticated()
|
|
||||||
.anyRequest().permitAll()
|
|
||||||
)
|
|
||||||
.exceptionHandling(ex -> ex.authenticationEntryPoint((request, response, authException) -> {
|
|
||||||
response.setStatus(401);
|
|
||||||
response.setContentType(MediaType.APPLICATION_JSON_VALUE);
|
|
||||||
response.getWriter().write("{\"error\":\"UNAUTHORIZED\"}");
|
|
||||||
}))
|
|
||||||
.addFilterBefore(adminSessionAuthenticationFilter, UsernamePasswordAuthenticationFilter.class);
|
|
||||||
|
|
||||||
return http.build();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
package com.printcalculator.config;
|
||||||
|
|
||||||
|
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||||
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
|
||||||
|
@Configuration
|
||||||
|
@ConfigurationProperties(prefix = "")
|
||||||
|
// Hack: standard prefix is usually required. I'll use @Value in service or correct this.
|
||||||
|
// Better: make SlicerConfig class.
|
||||||
|
public class SlicerConfig {
|
||||||
|
// Intentionally empty, will use @Value in service for simplicity
|
||||||
|
// or fix in next step.
|
||||||
|
}
|
||||||
@@ -1,520 +0,0 @@
|
|||||||
package com.printcalculator.controller;
|
|
||||||
|
|
||||||
import com.printcalculator.dto.QuoteRequestDto;
|
|
||||||
import com.printcalculator.entity.CustomQuoteRequest;
|
|
||||||
import com.printcalculator.entity.CustomQuoteRequestAttachment;
|
|
||||||
import com.printcalculator.repository.CustomQuoteRequestAttachmentRepository;
|
|
||||||
import com.printcalculator.repository.CustomQuoteRequestRepository;
|
|
||||||
import com.printcalculator.service.storage.ClamAVService;
|
|
||||||
import com.printcalculator.service.email.EmailNotificationService;
|
|
||||||
import jakarta.validation.Valid;
|
|
||||||
import org.slf4j.Logger;
|
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
import org.springframework.beans.factory.annotation.Value;
|
|
||||||
import org.springframework.http.HttpStatus;
|
|
||||||
import org.springframework.http.MediaType;
|
|
||||||
import org.springframework.http.ResponseEntity;
|
|
||||||
import org.springframework.transaction.annotation.Transactional;
|
|
||||||
import org.springframework.util.StringUtils;
|
|
||||||
import org.springframework.web.bind.annotation.*;
|
|
||||||
import org.springframework.web.server.ResponseStatusException;
|
|
||||||
import org.springframework.web.multipart.MultipartFile;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.InputStream;
|
|
||||||
import java.nio.file.Files;
|
|
||||||
import java.nio.file.InvalidPathException;
|
|
||||||
import java.nio.file.Path;
|
|
||||||
import java.nio.file.Paths;
|
|
||||||
import java.nio.file.StandardCopyOption;
|
|
||||||
import java.time.OffsetDateTime;
|
|
||||||
import java.time.Year;
|
|
||||||
import java.time.format.DateTimeFormatter;
|
|
||||||
import java.time.format.FormatStyle;
|
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Locale;
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.Set;
|
|
||||||
import java.util.UUID;
|
|
||||||
import java.util.regex.Pattern;
|
|
||||||
|
|
||||||
@RestController
|
|
||||||
@RequestMapping("/api/custom-quote-requests")
|
|
||||||
public class CustomQuoteRequestController {
|
|
||||||
|
|
||||||
private static final Logger logger = LoggerFactory.getLogger(CustomQuoteRequestController.class);
|
|
||||||
private final CustomQuoteRequestRepository requestRepo;
|
|
||||||
private final CustomQuoteRequestAttachmentRepository attachmentRepo;
|
|
||||||
private final ClamAVService clamAVService;
|
|
||||||
private final EmailNotificationService emailNotificationService;
|
|
||||||
|
|
||||||
@Value("${app.mail.contact-request.admin.enabled:true}")
|
|
||||||
private boolean contactRequestAdminMailEnabled;
|
|
||||||
|
|
||||||
@Value("${app.mail.contact-request.admin.address:infog@3d-fab.ch}")
|
|
||||||
private String contactRequestAdminMailAddress;
|
|
||||||
|
|
||||||
@Value("${app.mail.contact-request.customer.enabled:true}")
|
|
||||||
private boolean contactRequestCustomerMailEnabled;
|
|
||||||
|
|
||||||
// TODO: Inject Storage Service
|
|
||||||
private static final Path STORAGE_ROOT = Paths.get("storage_requests").toAbsolutePath().normalize();
|
|
||||||
private static final Pattern SAFE_EXTENSION_PATTERN = Pattern.compile("^[a-z0-9]{1,10}$");
|
|
||||||
private static final Set<String> FORBIDDEN_COMPRESSED_EXTENSIONS = Set.of(
|
|
||||||
"zip", "rar", "7z", "tar", "gz", "tgz", "bz2", "tbz2", "xz", "txz", "zst"
|
|
||||||
);
|
|
||||||
private static final Set<String> FORBIDDEN_COMPRESSED_MIME_TYPES = Set.of(
|
|
||||||
"application/zip",
|
|
||||||
"application/x-zip-compressed",
|
|
||||||
"application/x-rar-compressed",
|
|
||||||
"application/vnd.rar",
|
|
||||||
"application/x-7z-compressed",
|
|
||||||
"application/gzip",
|
|
||||||
"application/x-gzip",
|
|
||||||
"application/x-tar",
|
|
||||||
"application/x-bzip2",
|
|
||||||
"application/x-xz",
|
|
||||||
"application/zstd",
|
|
||||||
"application/x-zstd"
|
|
||||||
);
|
|
||||||
|
|
||||||
public CustomQuoteRequestController(CustomQuoteRequestRepository requestRepo,
|
|
||||||
CustomQuoteRequestAttachmentRepository attachmentRepo,
|
|
||||||
ClamAVService clamAVService,
|
|
||||||
EmailNotificationService emailNotificationService) {
|
|
||||||
this.requestRepo = requestRepo;
|
|
||||||
this.attachmentRepo = attachmentRepo;
|
|
||||||
this.clamAVService = clamAVService;
|
|
||||||
this.emailNotificationService = emailNotificationService;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 1. Create Custom Quote Request
|
|
||||||
@PostMapping(consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
|
|
||||||
@Transactional
|
|
||||||
public ResponseEntity<CustomQuoteRequest> createCustomQuoteRequest(
|
|
||||||
@Valid @RequestPart("request") QuoteRequestDto requestDto,
|
|
||||||
@RequestPart(value = "files", required = false) List<MultipartFile> files
|
|
||||||
) throws IOException {
|
|
||||||
if (!requestDto.isAcceptTerms() || !requestDto.isAcceptPrivacy()) {
|
|
||||||
throw new ResponseStatusException(
|
|
||||||
HttpStatus.BAD_REQUEST,
|
|
||||||
"Accettazione Termini e Privacy obbligatoria."
|
|
||||||
);
|
|
||||||
}
|
|
||||||
String language = normalizeLanguage(requestDto.getLanguage());
|
|
||||||
|
|
||||||
// 1. Create Request
|
|
||||||
CustomQuoteRequest request = new CustomQuoteRequest();
|
|
||||||
request.setRequestType(requestDto.getRequestType());
|
|
||||||
request.setCustomerType(requestDto.getCustomerType());
|
|
||||||
request.setEmail(requestDto.getEmail());
|
|
||||||
request.setPhone(requestDto.getPhone());
|
|
||||||
request.setName(requestDto.getName());
|
|
||||||
request.setCompanyName(requestDto.getCompanyName());
|
|
||||||
request.setContactPerson(requestDto.getContactPerson());
|
|
||||||
request.setMessage(requestDto.getMessage());
|
|
||||||
request.setStatus("PENDING");
|
|
||||||
request.setCreatedAt(OffsetDateTime.now());
|
|
||||||
request.setUpdatedAt(OffsetDateTime.now());
|
|
||||||
|
|
||||||
request = requestRepo.save(request);
|
|
||||||
|
|
||||||
// 2. Handle Attachments
|
|
||||||
int attachmentsCount = 0;
|
|
||||||
if (files != null && !files.isEmpty()) {
|
|
||||||
if (files.size() > 15) {
|
|
||||||
throw new IOException("Too many files. Max 15 allowed.");
|
|
||||||
}
|
|
||||||
|
|
||||||
for (MultipartFile file : files) {
|
|
||||||
if (file.isEmpty()) continue;
|
|
||||||
|
|
||||||
if (isCompressedFile(file)) {
|
|
||||||
throw new ResponseStatusException(
|
|
||||||
HttpStatus.BAD_REQUEST,
|
|
||||||
"Compressed files are not allowed."
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Scan for virus
|
|
||||||
clamAVService.scan(file.getInputStream());
|
|
||||||
|
|
||||||
CustomQuoteRequestAttachment attachment = new CustomQuoteRequestAttachment();
|
|
||||||
attachment.setRequest(request);
|
|
||||||
attachment.setOriginalFilename(file.getOriginalFilename());
|
|
||||||
attachment.setMimeType(file.getContentType());
|
|
||||||
attachment.setFileSizeBytes(file.getSize());
|
|
||||||
attachment.setCreatedAt(OffsetDateTime.now());
|
|
||||||
|
|
||||||
// Generate path
|
|
||||||
UUID fileUuid = UUID.randomUUID();
|
|
||||||
String storedFilename = fileUuid + ".upload";
|
|
||||||
|
|
||||||
// Note: We don't have attachment ID yet.
|
|
||||||
// We'll save attachment first to get ID.
|
|
||||||
attachment.setStoredFilename(storedFilename);
|
|
||||||
attachment.setStoredRelativePath("PENDING");
|
|
||||||
|
|
||||||
attachment = attachmentRepo.save(attachment);
|
|
||||||
|
|
||||||
Path relativePath = Path.of(
|
|
||||||
"quote-requests",
|
|
||||||
request.getId().toString(),
|
|
||||||
"attachments",
|
|
||||||
attachment.getId().toString(),
|
|
||||||
storedFilename
|
|
||||||
);
|
|
||||||
attachment.setStoredRelativePath(relativePath.toString());
|
|
||||||
attachmentRepo.save(attachment);
|
|
||||||
|
|
||||||
// Save file to disk
|
|
||||||
Path absolutePath = resolveWithinStorageRoot(relativePath);
|
|
||||||
Files.createDirectories(absolutePath.getParent());
|
|
||||||
try (InputStream inputStream = file.getInputStream()) {
|
|
||||||
Files.copy(inputStream, absolutePath, StandardCopyOption.REPLACE_EXISTING);
|
|
||||||
}
|
|
||||||
attachmentsCount++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
sendAdminContactRequestNotification(request, attachmentsCount);
|
|
||||||
sendCustomerContactRequestConfirmation(request, attachmentsCount, language);
|
|
||||||
|
|
||||||
return ResponseEntity.ok(request);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 2. Get Request
|
|
||||||
@GetMapping("/{id}")
|
|
||||||
public ResponseEntity<CustomQuoteRequest> getCustomQuoteRequest(@PathVariable UUID id) {
|
|
||||||
return requestRepo.findById(id)
|
|
||||||
.map(ResponseEntity::ok)
|
|
||||||
.orElse(ResponseEntity.notFound().build());
|
|
||||||
}
|
|
||||||
|
|
||||||
// Helper
|
|
||||||
private String getExtension(String filename) {
|
|
||||||
if (filename == null) return "dat";
|
|
||||||
String cleaned = StringUtils.cleanPath(filename);
|
|
||||||
if (cleaned.contains("..")) {
|
|
||||||
return "dat";
|
|
||||||
}
|
|
||||||
int i = cleaned.lastIndexOf('.');
|
|
||||||
if (i > 0 && i < cleaned.length() - 1) {
|
|
||||||
String ext = cleaned.substring(i + 1).toLowerCase(Locale.ROOT);
|
|
||||||
if (SAFE_EXTENSION_PATTERN.matcher(ext).matches()) {
|
|
||||||
return ext;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return "dat";
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean isCompressedFile(MultipartFile file) {
|
|
||||||
String ext = getExtension(file.getOriginalFilename());
|
|
||||||
if (FORBIDDEN_COMPRESSED_EXTENSIONS.contains(ext)) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
String mime = file.getContentType();
|
|
||||||
return mime != null && FORBIDDEN_COMPRESSED_MIME_TYPES.contains(mime.toLowerCase());
|
|
||||||
}
|
|
||||||
|
|
||||||
private Path resolveWithinStorageRoot(Path relativePath) {
|
|
||||||
try {
|
|
||||||
Path normalizedRelative = relativePath.normalize();
|
|
||||||
if (normalizedRelative.isAbsolute()) {
|
|
||||||
throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "Invalid attachment path");
|
|
||||||
}
|
|
||||||
Path absolutePath = STORAGE_ROOT.resolve(normalizedRelative).normalize();
|
|
||||||
if (!absolutePath.startsWith(STORAGE_ROOT)) {
|
|
||||||
throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "Invalid attachment path");
|
|
||||||
}
|
|
||||||
return absolutePath;
|
|
||||||
} catch (InvalidPathException e) {
|
|
||||||
throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "Invalid attachment path");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void sendAdminContactRequestNotification(CustomQuoteRequest request, int attachmentsCount) {
|
|
||||||
if (!contactRequestAdminMailEnabled) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (contactRequestAdminMailAddress == null || contactRequestAdminMailAddress.isBlank()) {
|
|
||||||
logger.warn("Contact request admin notification enabled but no admin address configured.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
Map<String, Object> templateData = new HashMap<>();
|
|
||||||
templateData.put("requestId", request.getId());
|
|
||||||
templateData.put("createdAt", request.getCreatedAt());
|
|
||||||
templateData.put("requestType", safeValue(request.getRequestType()));
|
|
||||||
templateData.put("customerType", safeValue(request.getCustomerType()));
|
|
||||||
templateData.put("name", safeValue(request.getName()));
|
|
||||||
templateData.put("companyName", safeValue(request.getCompanyName()));
|
|
||||||
templateData.put("contactPerson", safeValue(request.getContactPerson()));
|
|
||||||
templateData.put("email", safeValue(request.getEmail()));
|
|
||||||
templateData.put("phone", safeValue(request.getPhone()));
|
|
||||||
templateData.put("message", safeValue(request.getMessage()));
|
|
||||||
templateData.put("attachmentsCount", attachmentsCount);
|
|
||||||
templateData.put("currentYear", Year.now().getValue());
|
|
||||||
|
|
||||||
emailNotificationService.sendEmail(
|
|
||||||
contactRequestAdminMailAddress,
|
|
||||||
"Nuova richiesta di contatto #" + request.getId(),
|
|
||||||
"contact-request-admin",
|
|
||||||
templateData
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void sendCustomerContactRequestConfirmation(CustomQuoteRequest request, int attachmentsCount, String language) {
|
|
||||||
if (!contactRequestCustomerMailEnabled) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (request.getEmail() == null || request.getEmail().isBlank()) {
|
|
||||||
logger.warn("Contact request confirmation skipped: missing customer email for request {}", request.getId());
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
Map<String, Object> templateData = new HashMap<>();
|
|
||||||
templateData.put("requestId", request.getId());
|
|
||||||
templateData.put(
|
|
||||||
"createdAt",
|
|
||||||
request.getCreatedAt().format(
|
|
||||||
DateTimeFormatter.ofLocalizedDateTime(FormatStyle.MEDIUM).withLocale(localeForLanguage(language))
|
|
||||||
)
|
|
||||||
);
|
|
||||||
templateData.put("recipientName", resolveRecipientName(request, language));
|
|
||||||
templateData.put("requestType", localizeRequestType(request.getRequestType(), language));
|
|
||||||
templateData.put("customerType", localizeCustomerType(request.getCustomerType(), language));
|
|
||||||
templateData.put("name", safeValue(request.getName()));
|
|
||||||
templateData.put("companyName", safeValue(request.getCompanyName()));
|
|
||||||
templateData.put("contactPerson", safeValue(request.getContactPerson()));
|
|
||||||
templateData.put("email", safeValue(request.getEmail()));
|
|
||||||
templateData.put("phone", safeValue(request.getPhone()));
|
|
||||||
templateData.put("message", safeValue(request.getMessage()));
|
|
||||||
templateData.put("attachmentsCount", attachmentsCount);
|
|
||||||
templateData.put("currentYear", Year.now().getValue());
|
|
||||||
String subject = applyCustomerContactRequestTexts(templateData, language, request.getId());
|
|
||||||
|
|
||||||
emailNotificationService.sendEmail(
|
|
||||||
request.getEmail(),
|
|
||||||
subject,
|
|
||||||
"contact-request-customer",
|
|
||||||
templateData
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
private String applyCustomerContactRequestTexts(
|
|
||||||
Map<String, Object> templateData,
|
|
||||||
String language,
|
|
||||||
UUID requestId
|
|
||||||
) {
|
|
||||||
return switch (language) {
|
|
||||||
case "en" -> {
|
|
||||||
templateData.put("emailTitle", "Contact request received");
|
|
||||||
templateData.put("headlineText", "We received your contact request");
|
|
||||||
templateData.put("greetingText", "Hi " + templateData.get("recipientName") + ",");
|
|
||||||
templateData.put("introText", "Thank you for contacting us. Our team will reply as soon as possible.");
|
|
||||||
templateData.put("requestIdHintText", "Please keep this request ID for future order references:");
|
|
||||||
templateData.put("detailsTitleText", "Request details");
|
|
||||||
templateData.put("labelRequestId", "Request ID");
|
|
||||||
templateData.put("labelDate", "Date");
|
|
||||||
templateData.put("labelRequestType", "Request type");
|
|
||||||
templateData.put("labelCustomerType", "Customer type");
|
|
||||||
templateData.put("labelName", "Name");
|
|
||||||
templateData.put("labelCompany", "Company");
|
|
||||||
templateData.put("labelContactPerson", "Contact person");
|
|
||||||
templateData.put("labelEmail", "Email");
|
|
||||||
templateData.put("labelPhone", "Phone");
|
|
||||||
templateData.put("labelMessage", "Message");
|
|
||||||
templateData.put("labelAttachments", "Attachments");
|
|
||||||
templateData.put("supportText", "If you need help, reply to this email.");
|
|
||||||
templateData.put("footerText", "Automated request-receipt confirmation from 3D-Fab.");
|
|
||||||
yield "We received your contact request #" + requestId + " - 3D-Fab";
|
|
||||||
}
|
|
||||||
case "de" -> {
|
|
||||||
templateData.put("emailTitle", "Kontaktanfrage erhalten");
|
|
||||||
templateData.put("headlineText", "Wir haben Ihre Kontaktanfrage erhalten");
|
|
||||||
templateData.put("greetingText", "Hallo " + templateData.get("recipientName") + ",");
|
|
||||||
templateData.put("introText", "Vielen Dank fuer Ihre Anfrage. Unser Team antwortet Ihnen so schnell wie moeglich.");
|
|
||||||
templateData.put("requestIdHintText", "Bitte speichern Sie diese Anfrage-ID fuer zukuenftige Bestellreferenzen:");
|
|
||||||
templateData.put("detailsTitleText", "Anfragedetails");
|
|
||||||
templateData.put("labelRequestId", "Anfrage-ID");
|
|
||||||
templateData.put("labelDate", "Datum");
|
|
||||||
templateData.put("labelRequestType", "Anfragetyp");
|
|
||||||
templateData.put("labelCustomerType", "Kundentyp");
|
|
||||||
templateData.put("labelName", "Name");
|
|
||||||
templateData.put("labelCompany", "Firma");
|
|
||||||
templateData.put("labelContactPerson", "Kontaktperson");
|
|
||||||
templateData.put("labelEmail", "E-Mail");
|
|
||||||
templateData.put("labelPhone", "Telefon");
|
|
||||||
templateData.put("labelMessage", "Nachricht");
|
|
||||||
templateData.put("labelAttachments", "Anhaenge");
|
|
||||||
templateData.put("supportText", "Wenn Sie Hilfe brauchen, antworten Sie auf diese E-Mail.");
|
|
||||||
templateData.put("footerText", "Automatische Bestaetigung des Anfrageeingangs von 3D-Fab.");
|
|
||||||
yield "Wir haben Ihre Kontaktanfrage erhalten #" + requestId + " - 3D-Fab";
|
|
||||||
}
|
|
||||||
case "fr" -> {
|
|
||||||
templateData.put("emailTitle", "Demande de contact recue");
|
|
||||||
templateData.put("headlineText", "Nous avons recu votre demande de contact");
|
|
||||||
templateData.put("greetingText", "Bonjour " + templateData.get("recipientName") + ",");
|
|
||||||
templateData.put("introText", "Merci pour votre message. Notre equipe vous repondra des que possible.");
|
|
||||||
templateData.put("requestIdHintText", "Veuillez conserver cet ID de demande pour vos futures references de commande :");
|
|
||||||
templateData.put("detailsTitleText", "Details de la demande");
|
|
||||||
templateData.put("labelRequestId", "ID de demande");
|
|
||||||
templateData.put("labelDate", "Date");
|
|
||||||
templateData.put("labelRequestType", "Type de demande");
|
|
||||||
templateData.put("labelCustomerType", "Type de client");
|
|
||||||
templateData.put("labelName", "Nom");
|
|
||||||
templateData.put("labelCompany", "Entreprise");
|
|
||||||
templateData.put("labelContactPerson", "Contact");
|
|
||||||
templateData.put("labelEmail", "Email");
|
|
||||||
templateData.put("labelPhone", "Telephone");
|
|
||||||
templateData.put("labelMessage", "Message");
|
|
||||||
templateData.put("labelAttachments", "Pieces jointes");
|
|
||||||
templateData.put("supportText", "Si vous avez besoin d'aide, repondez a cet email.");
|
|
||||||
templateData.put("footerText", "Confirmation automatique de reception de demande par 3D-Fab.");
|
|
||||||
yield "Nous avons recu votre demande de contact #" + requestId + " - 3D-Fab";
|
|
||||||
}
|
|
||||||
default -> {
|
|
||||||
templateData.put("emailTitle", "Richiesta di contatto ricevuta");
|
|
||||||
templateData.put("headlineText", "Abbiamo ricevuto la tua richiesta di contatto");
|
|
||||||
templateData.put("greetingText", "Ciao " + templateData.get("recipientName") + ",");
|
|
||||||
templateData.put("introText", "Grazie per averci contattato. Il nostro team ti rispondera' il prima possibile.");
|
|
||||||
templateData.put("requestIdHintText", "Conserva questo ID richiesta per i futuri riferimenti d'ordine:");
|
|
||||||
templateData.put("detailsTitleText", "Dettagli richiesta");
|
|
||||||
templateData.put("labelRequestId", "ID richiesta");
|
|
||||||
templateData.put("labelDate", "Data");
|
|
||||||
templateData.put("labelRequestType", "Tipo richiesta");
|
|
||||||
templateData.put("labelCustomerType", "Tipo cliente");
|
|
||||||
templateData.put("labelName", "Nome");
|
|
||||||
templateData.put("labelCompany", "Azienda");
|
|
||||||
templateData.put("labelContactPerson", "Contatto");
|
|
||||||
templateData.put("labelEmail", "Email");
|
|
||||||
templateData.put("labelPhone", "Telefono");
|
|
||||||
templateData.put("labelMessage", "Messaggio");
|
|
||||||
templateData.put("labelAttachments", "Allegati");
|
|
||||||
templateData.put("supportText", "Se hai bisogno, rispondi direttamente a questa email.");
|
|
||||||
templateData.put("footerText", "Conferma automatica di ricezione richiesta da 3D-Fab.");
|
|
||||||
yield "Abbiamo ricevuto la tua richiesta di contatto #" + requestId + " - 3D-Fab";
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
private String localizeRequestType(String requestType, String language) {
|
|
||||||
if (requestType == null || requestType.isBlank()) {
|
|
||||||
return "-";
|
|
||||||
}
|
|
||||||
|
|
||||||
String normalized = requestType.trim().toLowerCase(Locale.ROOT);
|
|
||||||
return switch (language) {
|
|
||||||
case "en" -> switch (normalized) {
|
|
||||||
case "custom", "print_service" -> "Custom part request";
|
|
||||||
case "series" -> "Series production request";
|
|
||||||
case "consult", "design_service" -> "Consultation request";
|
|
||||||
case "question" -> "General question";
|
|
||||||
default -> requestType;
|
|
||||||
};
|
|
||||||
case "de" -> switch (normalized) {
|
|
||||||
case "custom", "print_service" -> "Anfrage fuer Einzelteil";
|
|
||||||
case "series" -> "Anfrage fuer Serienproduktion";
|
|
||||||
case "consult", "design_service" -> "Beratungsanfrage";
|
|
||||||
case "question" -> "Allgemeine Frage";
|
|
||||||
default -> requestType;
|
|
||||||
};
|
|
||||||
case "fr" -> switch (normalized) {
|
|
||||||
case "custom", "print_service" -> "Demande de piece personnalisee";
|
|
||||||
case "series" -> "Demande de production en serie";
|
|
||||||
case "consult", "design_service" -> "Demande de conseil";
|
|
||||||
case "question" -> "Question generale";
|
|
||||||
default -> requestType;
|
|
||||||
};
|
|
||||||
default -> switch (normalized) {
|
|
||||||
case "custom", "print_service" -> "Richiesta pezzo personalizzato";
|
|
||||||
case "series" -> "Richiesta produzione in serie";
|
|
||||||
case "consult", "design_service" -> "Richiesta consulenza";
|
|
||||||
case "question" -> "Domanda generale";
|
|
||||||
default -> requestType;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
private String localizeCustomerType(String customerType, String language) {
|
|
||||||
if (customerType == null || customerType.isBlank()) {
|
|
||||||
return "-";
|
|
||||||
}
|
|
||||||
String normalized = customerType.trim().toLowerCase(Locale.ROOT);
|
|
||||||
return switch (language) {
|
|
||||||
case "en" -> switch (normalized) {
|
|
||||||
case "private" -> "Private";
|
|
||||||
case "business" -> "Business";
|
|
||||||
default -> customerType;
|
|
||||||
};
|
|
||||||
case "de" -> switch (normalized) {
|
|
||||||
case "private" -> "Privat";
|
|
||||||
case "business" -> "Unternehmen";
|
|
||||||
default -> customerType;
|
|
||||||
};
|
|
||||||
case "fr" -> switch (normalized) {
|
|
||||||
case "private" -> "Prive";
|
|
||||||
case "business" -> "Entreprise";
|
|
||||||
default -> customerType;
|
|
||||||
};
|
|
||||||
default -> switch (normalized) {
|
|
||||||
case "private" -> "Privato";
|
|
||||||
case "business" -> "Azienda";
|
|
||||||
default -> customerType;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
private Locale localeForLanguage(String language) {
|
|
||||||
return switch (language) {
|
|
||||||
case "en" -> Locale.ENGLISH;
|
|
||||||
case "de" -> Locale.GERMAN;
|
|
||||||
case "fr" -> Locale.FRENCH;
|
|
||||||
default -> Locale.ITALIAN;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
private String normalizeLanguage(String language) {
|
|
||||||
if (language == null || language.isBlank()) {
|
|
||||||
return "it";
|
|
||||||
}
|
|
||||||
String normalized = language.toLowerCase(Locale.ROOT).trim();
|
|
||||||
if (normalized.startsWith("en")) {
|
|
||||||
return "en";
|
|
||||||
}
|
|
||||||
if (normalized.startsWith("de")) {
|
|
||||||
return "de";
|
|
||||||
}
|
|
||||||
if (normalized.startsWith("fr")) {
|
|
||||||
return "fr";
|
|
||||||
}
|
|
||||||
return "it";
|
|
||||||
}
|
|
||||||
|
|
||||||
private String resolveRecipientName(CustomQuoteRequest request, String language) {
|
|
||||||
if (request.getName() != null && !request.getName().isBlank()) {
|
|
||||||
return request.getName().trim();
|
|
||||||
}
|
|
||||||
if (request.getContactPerson() != null && !request.getContactPerson().isBlank()) {
|
|
||||||
return request.getContactPerson().trim();
|
|
||||||
}
|
|
||||||
if (request.getCompanyName() != null && !request.getCompanyName().isBlank()) {
|
|
||||||
return request.getCompanyName().trim();
|
|
||||||
}
|
|
||||||
return switch (language) {
|
|
||||||
case "en" -> "customer";
|
|
||||||
case "de" -> "Kunde";
|
|
||||||
case "fr" -> "client";
|
|
||||||
default -> "cliente";
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
private String safeValue(String value) {
|
|
||||||
if (value == null || value.isBlank()) {
|
|
||||||
return "-";
|
|
||||||
}
|
|
||||||
return value;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,248 +0,0 @@
|
|||||||
package com.printcalculator.controller;
|
|
||||||
|
|
||||||
import com.printcalculator.dto.OptionsResponse;
|
|
||||||
import com.printcalculator.entity.FilamentMaterialType;
|
|
||||||
import com.printcalculator.entity.FilamentVariant;
|
|
||||||
import com.printcalculator.entity.MaterialOrcaProfileMap;
|
|
||||||
import com.printcalculator.entity.NozzleOption;
|
|
||||||
import com.printcalculator.entity.PrinterMachine;
|
|
||||||
import com.printcalculator.entity.PrinterMachineProfile;
|
|
||||||
import com.printcalculator.repository.FilamentMaterialTypeRepository;
|
|
||||||
import com.printcalculator.repository.FilamentVariantRepository;
|
|
||||||
import com.printcalculator.repository.MaterialOrcaProfileMapRepository;
|
|
||||||
import com.printcalculator.repository.NozzleOptionRepository;
|
|
||||||
import com.printcalculator.repository.PrinterMachineRepository;
|
|
||||||
import com.printcalculator.service.NozzleLayerHeightPolicyService;
|
|
||||||
import com.printcalculator.service.OrcaProfileResolver;
|
|
||||||
import org.springframework.http.ResponseEntity;
|
|
||||||
import org.springframework.transaction.annotation.Transactional;
|
|
||||||
import org.springframework.web.bind.annotation.GetMapping;
|
|
||||||
import org.springframework.web.bind.annotation.RequestParam;
|
|
||||||
import org.springframework.web.bind.annotation.RestController;
|
|
||||||
|
|
||||||
import java.math.BigDecimal;
|
|
||||||
import java.util.Comparator;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.Set;
|
|
||||||
import java.util.stream.Collectors;
|
|
||||||
|
|
||||||
@RestController
|
|
||||||
public class OptionsController {
|
|
||||||
|
|
||||||
private final FilamentMaterialTypeRepository materialRepo;
|
|
||||||
private final FilamentVariantRepository variantRepo;
|
|
||||||
private final NozzleOptionRepository nozzleRepo;
|
|
||||||
private final PrinterMachineRepository printerMachineRepo;
|
|
||||||
private final MaterialOrcaProfileMapRepository materialOrcaMapRepo;
|
|
||||||
private final OrcaProfileResolver orcaProfileResolver;
|
|
||||||
private final NozzleLayerHeightPolicyService nozzleLayerHeightPolicyService;
|
|
||||||
|
|
||||||
public OptionsController(FilamentMaterialTypeRepository materialRepo,
|
|
||||||
FilamentVariantRepository variantRepo,
|
|
||||||
NozzleOptionRepository nozzleRepo,
|
|
||||||
PrinterMachineRepository printerMachineRepo,
|
|
||||||
MaterialOrcaProfileMapRepository materialOrcaMapRepo,
|
|
||||||
OrcaProfileResolver orcaProfileResolver,
|
|
||||||
NozzleLayerHeightPolicyService nozzleLayerHeightPolicyService) {
|
|
||||||
this.materialRepo = materialRepo;
|
|
||||||
this.variantRepo = variantRepo;
|
|
||||||
this.nozzleRepo = nozzleRepo;
|
|
||||||
this.printerMachineRepo = printerMachineRepo;
|
|
||||||
this.materialOrcaMapRepo = materialOrcaMapRepo;
|
|
||||||
this.orcaProfileResolver = orcaProfileResolver;
|
|
||||||
this.nozzleLayerHeightPolicyService = nozzleLayerHeightPolicyService;
|
|
||||||
}
|
|
||||||
|
|
||||||
@GetMapping("/api/calculator/options")
|
|
||||||
@Transactional(readOnly = true)
|
|
||||||
public ResponseEntity<OptionsResponse> getOptions(
|
|
||||||
@RequestParam(value = "printerMachineId", required = false) Long printerMachineId,
|
|
||||||
@RequestParam(value = "nozzleDiameter", required = false) Double nozzleDiameter
|
|
||||||
) {
|
|
||||||
List<FilamentMaterialType> types = materialRepo.findAll();
|
|
||||||
List<FilamentVariant> allVariants = variantRepo.findByIsActiveTrue().stream()
|
|
||||||
.sorted(Comparator
|
|
||||||
.comparing((FilamentVariant v) -> safeMaterialCode(v.getFilamentMaterialType()), String.CASE_INSENSITIVE_ORDER)
|
|
||||||
.thenComparing(v -> safeString(v.getVariantDisplayName()), String.CASE_INSENSITIVE_ORDER))
|
|
||||||
.toList();
|
|
||||||
|
|
||||||
Set<Long> compatibleMaterialTypeIds = resolveCompatibleMaterialTypeIds(printerMachineId, nozzleDiameter);
|
|
||||||
|
|
||||||
List<OptionsResponse.MaterialOption> materialOptions = types.stream()
|
|
||||||
.sorted(Comparator.comparing(t -> safeString(t.getMaterialCode()), String.CASE_INSENSITIVE_ORDER))
|
|
||||||
.map(type -> {
|
|
||||||
if (!compatibleMaterialTypeIds.isEmpty() && !compatibleMaterialTypeIds.contains(type.getId())) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
List<OptionsResponse.VariantOption> variants = allVariants.stream()
|
|
||||||
.filter(v -> v.getFilamentMaterialType() != null
|
|
||||||
&& v.getFilamentMaterialType().getId().equals(type.getId()))
|
|
||||||
.map(v -> new OptionsResponse.VariantOption(
|
|
||||||
v.getId(),
|
|
||||||
v.getVariantDisplayName(),
|
|
||||||
v.getColorName(),
|
|
||||||
resolveHexColor(v),
|
|
||||||
v.getFinishType() != null ? v.getFinishType() : "GLOSSY",
|
|
||||||
v.getStockSpools() != null ? v.getStockSpools().doubleValue() : 0d,
|
|
||||||
toStockFilamentGrams(v),
|
|
||||||
v.getStockSpools() == null || v.getStockSpools().doubleValue() <= 0
|
|
||||||
))
|
|
||||||
.collect(Collectors.toList());
|
|
||||||
|
|
||||||
if (variants.isEmpty()) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return new OptionsResponse.MaterialOption(
|
|
||||||
type.getMaterialCode(),
|
|
||||||
type.getMaterialCode() + (Boolean.TRUE.equals(type.getIsFlexible()) ? " (Flexible)" : " (Standard)"),
|
|
||||||
variants
|
|
||||||
);
|
|
||||||
})
|
|
||||||
.filter(m -> m != null)
|
|
||||||
.toList();
|
|
||||||
|
|
||||||
List<OptionsResponse.QualityOption> qualities = List.of(
|
|
||||||
new OptionsResponse.QualityOption("draft", "Draft"),
|
|
||||||
new OptionsResponse.QualityOption("standard", "Standard"),
|
|
||||||
new OptionsResponse.QualityOption("extra_fine", "High Definition")
|
|
||||||
);
|
|
||||||
|
|
||||||
List<OptionsResponse.InfillPatternOption> patterns = List.of(
|
|
||||||
new OptionsResponse.InfillPatternOption("grid", "Grid"),
|
|
||||||
new OptionsResponse.InfillPatternOption("gyroid", "Gyroid"),
|
|
||||||
new OptionsResponse.InfillPatternOption("cubic", "Cubic")
|
|
||||||
);
|
|
||||||
|
|
||||||
List<OptionsResponse.NozzleOptionDTO> nozzles = nozzleRepo.findAll().stream()
|
|
||||||
.filter(n -> Boolean.TRUE.equals(n.getIsActive()))
|
|
||||||
.sorted(Comparator.comparing(NozzleOption::getNozzleDiameterMm))
|
|
||||||
.map(n -> new OptionsResponse.NozzleOptionDTO(
|
|
||||||
n.getNozzleDiameterMm().doubleValue(),
|
|
||||||
String.format("%.1f mm%s", n.getNozzleDiameterMm(),
|
|
||||||
n.getExtraNozzleChangeFeeChf().doubleValue() > 0
|
|
||||||
? String.format(" (+ %.2f CHF)", n.getExtraNozzleChangeFeeChf())
|
|
||||||
: " (Standard)")
|
|
||||||
))
|
|
||||||
.toList();
|
|
||||||
|
|
||||||
Map<BigDecimal, List<BigDecimal>> rulesByNozzle = nozzleLayerHeightPolicyService.getActiveRulesByNozzle();
|
|
||||||
BigDecimal selectedNozzle = nozzleLayerHeightPolicyService.resolveNozzle(
|
|
||||||
nozzleDiameter != null ? BigDecimal.valueOf(nozzleDiameter) : null
|
|
||||||
);
|
|
||||||
|
|
||||||
List<OptionsResponse.LayerHeightOptionDTO> layers = toLayerDtos(rulesByNozzle.getOrDefault(selectedNozzle, List.of()));
|
|
||||||
if (layers.isEmpty()) {
|
|
||||||
layers = rulesByNozzle.values().stream().findFirst().map(this::toLayerDtos).orElse(List.of());
|
|
||||||
}
|
|
||||||
|
|
||||||
List<OptionsResponse.NozzleLayerHeightOptionsDTO> layerHeightsByNozzle = rulesByNozzle.entrySet().stream()
|
|
||||||
.map(entry -> new OptionsResponse.NozzleLayerHeightOptionsDTO(
|
|
||||||
entry.getKey().doubleValue(),
|
|
||||||
toLayerDtos(entry.getValue())
|
|
||||||
))
|
|
||||||
.toList();
|
|
||||||
|
|
||||||
return ResponseEntity.ok(new OptionsResponse(
|
|
||||||
materialOptions,
|
|
||||||
qualities,
|
|
||||||
patterns,
|
|
||||||
layers,
|
|
||||||
nozzles,
|
|
||||||
layerHeightsByNozzle
|
|
||||||
));
|
|
||||||
}
|
|
||||||
|
|
||||||
private Set<Long> resolveCompatibleMaterialTypeIds(Long printerMachineId, Double nozzleDiameter) {
|
|
||||||
PrinterMachine machine = null;
|
|
||||||
if (printerMachineId != null) {
|
|
||||||
machine = printerMachineRepo.findById(printerMachineId).orElse(null);
|
|
||||||
}
|
|
||||||
if (machine == null) {
|
|
||||||
machine = printerMachineRepo.findFirstByIsActiveTrue().orElse(null);
|
|
||||||
}
|
|
||||||
if (machine == null) {
|
|
||||||
return Set.of();
|
|
||||||
}
|
|
||||||
|
|
||||||
BigDecimal nozzle = nozzleLayerHeightPolicyService.resolveNozzle(
|
|
||||||
nozzleDiameter != null ? BigDecimal.valueOf(nozzleDiameter) : null
|
|
||||||
);
|
|
||||||
|
|
||||||
PrinterMachineProfile machineProfile = orcaProfileResolver
|
|
||||||
.resolveMachineProfile(machine, nozzle)
|
|
||||||
.orElse(null);
|
|
||||||
|
|
||||||
if (machineProfile == null) {
|
|
||||||
return Set.of();
|
|
||||||
}
|
|
||||||
|
|
||||||
List<MaterialOrcaProfileMap> maps = materialOrcaMapRepo.findByPrinterMachineProfileAndIsActiveTrue(machineProfile);
|
|
||||||
return maps.stream()
|
|
||||||
.map(MaterialOrcaProfileMap::getFilamentMaterialType)
|
|
||||||
.filter(m -> m != null && m.getId() != null)
|
|
||||||
.map(FilamentMaterialType::getId)
|
|
||||||
.collect(Collectors.toSet());
|
|
||||||
}
|
|
||||||
|
|
||||||
private List<OptionsResponse.LayerHeightOptionDTO> toLayerDtos(List<BigDecimal> layers) {
|
|
||||||
return layers.stream()
|
|
||||||
.sorted(Comparator.naturalOrder())
|
|
||||||
.map(layer -> new OptionsResponse.LayerHeightOptionDTO(
|
|
||||||
layer.doubleValue(),
|
|
||||||
String.format("%.2f mm", layer)
|
|
||||||
))
|
|
||||||
.toList();
|
|
||||||
}
|
|
||||||
|
|
||||||
private String resolveHexColor(FilamentVariant variant) {
|
|
||||||
if (variant.getColorHex() != null && !variant.getColorHex().isBlank()) {
|
|
||||||
return variant.getColorHex();
|
|
||||||
}
|
|
||||||
return getColorHex(variant.getColorName());
|
|
||||||
}
|
|
||||||
|
|
||||||
private double toStockFilamentGrams(FilamentVariant variant) {
|
|
||||||
if (variant.getStockSpools() == null || variant.getSpoolNetKg() == null) {
|
|
||||||
return 0d;
|
|
||||||
}
|
|
||||||
return variant.getStockSpools()
|
|
||||||
.multiply(variant.getSpoolNetKg())
|
|
||||||
.multiply(BigDecimal.valueOf(1000))
|
|
||||||
.doubleValue();
|
|
||||||
}
|
|
||||||
|
|
||||||
private String safeMaterialCode(FilamentMaterialType type) {
|
|
||||||
if (type == null || type.getMaterialCode() == null) {
|
|
||||||
return "";
|
|
||||||
}
|
|
||||||
return type.getMaterialCode();
|
|
||||||
}
|
|
||||||
|
|
||||||
private String safeString(String value) {
|
|
||||||
return value == null ? "" : value;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Temporary helper for legacy values where color hex is not yet set in DB
|
|
||||||
private String getColorHex(String colorName) {
|
|
||||||
if (colorName == null) {
|
|
||||||
return "#9e9e9e";
|
|
||||||
}
|
|
||||||
String lower = colorName.toLowerCase();
|
|
||||||
if (lower.contains("black") || lower.contains("nero")) return "#1a1a1a";
|
|
||||||
if (lower.contains("white") || lower.contains("bianco")) return "#f5f5f5";
|
|
||||||
if (lower.contains("blue") || lower.contains("blu")) return "#1976d2";
|
|
||||||
if (lower.contains("red") || lower.contains("rosso")) return "#d32f2f";
|
|
||||||
if (lower.contains("green") || lower.contains("verde")) return "#388e3c";
|
|
||||||
if (lower.contains("orange") || lower.contains("arancione")) return "#ffa726";
|
|
||||||
if (lower.contains("grey") || lower.contains("gray") || lower.contains("grigio")) {
|
|
||||||
if (lower.contains("dark") || lower.contains("scuro")) return "#424242";
|
|
||||||
return "#bdbdbd";
|
|
||||||
}
|
|
||||||
if (lower.contains("purple") || lower.contains("viola")) return "#7b1fa2";
|
|
||||||
if (lower.contains("yellow") || lower.contains("giallo")) return "#fbc02d";
|
|
||||||
return "#9e9e9e";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,389 +0,0 @@
|
|||||||
package com.printcalculator.controller;
|
|
||||||
|
|
||||||
import com.printcalculator.dto.*;
|
|
||||||
import com.printcalculator.entity.*;
|
|
||||||
import com.printcalculator.repository.*;
|
|
||||||
import com.printcalculator.service.payment.InvoicePdfRenderingService;
|
|
||||||
import com.printcalculator.service.OrderService;
|
|
||||||
import com.printcalculator.service.payment.PaymentService;
|
|
||||||
import com.printcalculator.service.payment.QrBillService;
|
|
||||||
import com.printcalculator.service.storage.StorageService;
|
|
||||||
import com.printcalculator.service.payment.TwintPaymentService;
|
|
||||||
import org.springframework.http.MediaType;
|
|
||||||
import org.springframework.http.ResponseEntity;
|
|
||||||
import org.springframework.util.StringUtils;
|
|
||||||
import org.springframework.transaction.annotation.Transactional;
|
|
||||||
import org.springframework.web.bind.annotation.*;
|
|
||||||
import org.springframework.web.multipart.MultipartFile;
|
|
||||||
import jakarta.validation.Valid;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
|
|
||||||
import java.nio.file.InvalidPathException;
|
|
||||||
import java.nio.file.Path;
|
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.UUID;
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.Base64;
|
|
||||||
import java.util.Set;
|
|
||||||
import java.util.stream.Collectors;
|
|
||||||
import java.net.URI;
|
|
||||||
import java.util.Locale;
|
|
||||||
import java.util.regex.Pattern;
|
|
||||||
|
|
||||||
@RestController
|
|
||||||
@RequestMapping("/api/orders")
|
|
||||||
public class OrderController {
|
|
||||||
private static final Pattern SAFE_EXTENSION_PATTERN = Pattern.compile("^[a-z0-9]{1,10}$");
|
|
||||||
private static final Set<String> PERSONAL_DATA_REDACTED_STATUSES = Set.of(
|
|
||||||
"IN_PRODUCTION",
|
|
||||||
"SHIPPED",
|
|
||||||
"COMPLETED"
|
|
||||||
);
|
|
||||||
|
|
||||||
private final OrderService orderService;
|
|
||||||
private final OrderRepository orderRepo;
|
|
||||||
private final OrderItemRepository orderItemRepo;
|
|
||||||
private final QuoteSessionRepository quoteSessionRepo;
|
|
||||||
private final QuoteLineItemRepository quoteLineItemRepo;
|
|
||||||
private final CustomerRepository customerRepo;
|
|
||||||
private final StorageService storageService;
|
|
||||||
private final InvoicePdfRenderingService invoiceService;
|
|
||||||
private final QrBillService qrBillService;
|
|
||||||
private final TwintPaymentService twintPaymentService;
|
|
||||||
private final PaymentService paymentService;
|
|
||||||
private final PaymentRepository paymentRepo;
|
|
||||||
|
|
||||||
|
|
||||||
public OrderController(OrderService orderService,
|
|
||||||
OrderRepository orderRepo,
|
|
||||||
OrderItemRepository orderItemRepo,
|
|
||||||
QuoteSessionRepository quoteSessionRepo,
|
|
||||||
QuoteLineItemRepository quoteLineItemRepo,
|
|
||||||
CustomerRepository customerRepo,
|
|
||||||
StorageService storageService,
|
|
||||||
InvoicePdfRenderingService invoiceService,
|
|
||||||
QrBillService qrBillService,
|
|
||||||
TwintPaymentService twintPaymentService,
|
|
||||||
PaymentService paymentService,
|
|
||||||
PaymentRepository paymentRepo) {
|
|
||||||
this.orderService = orderService;
|
|
||||||
this.orderRepo = orderRepo;
|
|
||||||
this.orderItemRepo = orderItemRepo;
|
|
||||||
this.quoteSessionRepo = quoteSessionRepo;
|
|
||||||
this.quoteLineItemRepo = quoteLineItemRepo;
|
|
||||||
this.customerRepo = customerRepo;
|
|
||||||
this.storageService = storageService;
|
|
||||||
this.invoiceService = invoiceService;
|
|
||||||
this.qrBillService = qrBillService;
|
|
||||||
this.twintPaymentService = twintPaymentService;
|
|
||||||
this.paymentService = paymentService;
|
|
||||||
this.paymentRepo = paymentRepo;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// 1. Create Order from Quote
|
|
||||||
@PostMapping("/from-quote/{quoteSessionId}")
|
|
||||||
@Transactional
|
|
||||||
public ResponseEntity<OrderDto> createOrderFromQuote(
|
|
||||||
@PathVariable UUID quoteSessionId,
|
|
||||||
@Valid @RequestBody com.printcalculator.dto.CreateOrderRequest request
|
|
||||||
) {
|
|
||||||
Order order = orderService.createOrderFromQuote(quoteSessionId, request);
|
|
||||||
List<OrderItem> items = orderItemRepo.findByOrder_Id(order.getId());
|
|
||||||
return ResponseEntity.ok(convertToDto(order, items));
|
|
||||||
}
|
|
||||||
|
|
||||||
@PostMapping(value = "/{orderId}/items/{orderItemId}/file", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
|
|
||||||
@Transactional
|
|
||||||
public ResponseEntity<Void> uploadOrderItemFile(
|
|
||||||
@PathVariable UUID orderId,
|
|
||||||
@PathVariable UUID orderItemId,
|
|
||||||
@RequestParam("file") MultipartFile file
|
|
||||||
) throws IOException {
|
|
||||||
|
|
||||||
OrderItem item = orderItemRepo.findById(orderItemId)
|
|
||||||
.orElseThrow(() -> new RuntimeException("OrderItem not found"));
|
|
||||||
|
|
||||||
if (!item.getOrder().getId().equals(orderId)) {
|
|
||||||
return ResponseEntity.badRequest().build();
|
|
||||||
}
|
|
||||||
|
|
||||||
String relativePath = item.getStoredRelativePath();
|
|
||||||
Path destinationRelativePath;
|
|
||||||
if (relativePath == null || relativePath.equals("PENDING")) {
|
|
||||||
String ext = getExtension(file.getOriginalFilename());
|
|
||||||
String storedFilename = UUID.randomUUID() + "." + ext;
|
|
||||||
destinationRelativePath = Path.of("orders", orderId.toString(), "3d-files", orderItemId.toString(), storedFilename);
|
|
||||||
item.setStoredRelativePath(destinationRelativePath.toString());
|
|
||||||
item.setStoredFilename(storedFilename);
|
|
||||||
} else {
|
|
||||||
destinationRelativePath = resolveOrderItemRelativePath(relativePath, orderId, orderItemId);
|
|
||||||
if (destinationRelativePath == null) {
|
|
||||||
return ResponseEntity.badRequest().build();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
storageService.store(file, destinationRelativePath);
|
|
||||||
item.setFileSizeBytes(file.getSize());
|
|
||||||
item.setMimeType(file.getContentType());
|
|
||||||
orderItemRepo.save(item);
|
|
||||||
|
|
||||||
return ResponseEntity.ok().build();
|
|
||||||
}
|
|
||||||
|
|
||||||
@GetMapping("/{orderId}")
|
|
||||||
public ResponseEntity<OrderDto> getOrder(@PathVariable UUID orderId) {
|
|
||||||
return orderRepo.findById(orderId)
|
|
||||||
.map(o -> {
|
|
||||||
List<OrderItem> items = orderItemRepo.findByOrder_Id(o.getId());
|
|
||||||
return ResponseEntity.ok(convertToDto(o, items));
|
|
||||||
})
|
|
||||||
.orElse(ResponseEntity.notFound().build());
|
|
||||||
}
|
|
||||||
|
|
||||||
@PostMapping("/{orderId}/payments/report")
|
|
||||||
@Transactional
|
|
||||||
public ResponseEntity<OrderDto> reportPayment(
|
|
||||||
@PathVariable UUID orderId,
|
|
||||||
@RequestBody Map<String, String> payload
|
|
||||||
) {
|
|
||||||
String method = payload.get("method");
|
|
||||||
paymentService.reportPayment(orderId, method);
|
|
||||||
return getOrder(orderId);
|
|
||||||
}
|
|
||||||
|
|
||||||
@GetMapping("/{orderId}/confirmation")
|
|
||||||
public ResponseEntity<byte[]> getConfirmation(@PathVariable UUID orderId) {
|
|
||||||
return generateDocument(orderId, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
@GetMapping("/{orderId}/invoice")
|
|
||||||
public ResponseEntity<byte[]> getInvoice(@PathVariable UUID orderId) {
|
|
||||||
// Paid invoices are sent by email after back-office payment confirmation.
|
|
||||||
// The public endpoint must not expose a "paid" invoice download.
|
|
||||||
return ResponseEntity.notFound().build();
|
|
||||||
}
|
|
||||||
|
|
||||||
private ResponseEntity<byte[]> generateDocument(UUID orderId, boolean isConfirmation) {
|
|
||||||
Order order = orderRepo.findById(orderId)
|
|
||||||
.orElseThrow(() -> new RuntimeException("Order not found"));
|
|
||||||
|
|
||||||
if (isConfirmation) {
|
|
||||||
Path relativePath = buildConfirmationPdfRelativePath(order);
|
|
||||||
try {
|
|
||||||
byte[] existingPdf = storageService.loadAsResource(relativePath).getInputStream().readAllBytes();
|
|
||||||
return ResponseEntity.ok()
|
|
||||||
.header("Content-Disposition", "attachment; filename=\"confirmation-" + getDisplayOrderNumber(order) + ".pdf\"")
|
|
||||||
.contentType(MediaType.APPLICATION_PDF)
|
|
||||||
.body(existingPdf);
|
|
||||||
} catch (Exception ignored) {
|
|
||||||
// Fallback to on-the-fly generation if the stored file is missing or unreadable.
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
List<OrderItem> items = orderItemRepo.findByOrder_Id(orderId);
|
|
||||||
Payment payment = paymentRepo.findByOrder_Id(orderId).orElse(null);
|
|
||||||
|
|
||||||
byte[] pdf = invoiceService.generateDocumentPdf(order, items, isConfirmation, qrBillService, payment);
|
|
||||||
String typePrefix = isConfirmation ? "confirmation-" : "invoice-";
|
|
||||||
String truncatedUuid = order.getId().toString().substring(0, 8);
|
|
||||||
return ResponseEntity.ok()
|
|
||||||
.header("Content-Disposition", "attachment; filename=\"" + typePrefix + truncatedUuid + ".pdf\"")
|
|
||||||
.contentType(MediaType.APPLICATION_PDF)
|
|
||||||
.body(pdf);
|
|
||||||
}
|
|
||||||
|
|
||||||
private Path buildConfirmationPdfRelativePath(Order order) {
|
|
||||||
return Path.of(
|
|
||||||
"orders",
|
|
||||||
order.getId().toString(),
|
|
||||||
"documents",
|
|
||||||
"confirmation-" + getDisplayOrderNumber(order) + ".pdf"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
@GetMapping("/{orderId}/twint")
|
|
||||||
public ResponseEntity<Map<String, String>> getTwintPayment(@PathVariable UUID orderId) {
|
|
||||||
Order order = orderRepo.findById(orderId).orElse(null);
|
|
||||||
if (order == null) {
|
|
||||||
return ResponseEntity.notFound().build();
|
|
||||||
}
|
|
||||||
|
|
||||||
byte[] qrPng = twintPaymentService.generateQrPng(order, 360);
|
|
||||||
String qrDataUri = "data:image/png;base64," + Base64.getEncoder().encodeToString(qrPng);
|
|
||||||
|
|
||||||
Map<String, String> data = new HashMap<>();
|
|
||||||
data.put("paymentUrl", twintPaymentService.getTwintPaymentUrl(order));
|
|
||||||
data.put("openUrl", "/api/orders/" + orderId + "/twint/open");
|
|
||||||
data.put("qrImageUrl", "/api/orders/" + orderId + "/twint/qr");
|
|
||||||
data.put("qrImageDataUri", qrDataUri);
|
|
||||||
return ResponseEntity.ok(data);
|
|
||||||
}
|
|
||||||
|
|
||||||
@GetMapping("/{orderId}/twint/open")
|
|
||||||
public ResponseEntity<Void> openTwintPayment(@PathVariable UUID orderId) {
|
|
||||||
Order order = orderRepo.findById(orderId).orElse(null);
|
|
||||||
if (order == null) {
|
|
||||||
return ResponseEntity.notFound().build();
|
|
||||||
}
|
|
||||||
|
|
||||||
return ResponseEntity.status(302)
|
|
||||||
.location(URI.create(twintPaymentService.getTwintPaymentUrl(order)))
|
|
||||||
.build();
|
|
||||||
}
|
|
||||||
|
|
||||||
@GetMapping("/{orderId}/twint/qr")
|
|
||||||
public ResponseEntity<byte[]> getTwintQr(
|
|
||||||
@PathVariable UUID orderId,
|
|
||||||
@RequestParam(defaultValue = "320") int size
|
|
||||||
) {
|
|
||||||
Order order = orderRepo.findById(orderId).orElse(null);
|
|
||||||
if (order == null) {
|
|
||||||
return ResponseEntity.notFound().build();
|
|
||||||
}
|
|
||||||
|
|
||||||
int normalizedSize = Math.max(200, Math.min(size, 600));
|
|
||||||
byte[] png = twintPaymentService.generateQrPng(order, normalizedSize);
|
|
||||||
|
|
||||||
return ResponseEntity.ok()
|
|
||||||
.contentType(MediaType.IMAGE_PNG)
|
|
||||||
.body(png);
|
|
||||||
}
|
|
||||||
|
|
||||||
private String getExtension(String filename) {
|
|
||||||
if (filename == null) return "stl";
|
|
||||||
String cleaned = StringUtils.cleanPath(filename);
|
|
||||||
if (cleaned.contains("..")) {
|
|
||||||
return "stl";
|
|
||||||
}
|
|
||||||
int i = cleaned.lastIndexOf('.');
|
|
||||||
if (i > 0 && i < cleaned.length() - 1) {
|
|
||||||
String ext = cleaned.substring(i + 1).toLowerCase(Locale.ROOT);
|
|
||||||
if (SAFE_EXTENSION_PATTERN.matcher(ext).matches()) {
|
|
||||||
return ext;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return "stl";
|
|
||||||
}
|
|
||||||
|
|
||||||
private Path resolveOrderItemRelativePath(String storedRelativePath, UUID orderId, UUID orderItemId) {
|
|
||||||
try {
|
|
||||||
Path candidate = Path.of(storedRelativePath).normalize();
|
|
||||||
if (candidate.isAbsolute()) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
Path expectedPrefix = Path.of("orders", orderId.toString(), "3d-files", orderItemId.toString());
|
|
||||||
if (!candidate.startsWith(expectedPrefix)) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return candidate;
|
|
||||||
} catch (InvalidPathException e) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private OrderDto convertToDto(Order order, List<OrderItem> items) {
|
|
||||||
OrderDto dto = new OrderDto();
|
|
||||||
dto.setId(order.getId());
|
|
||||||
dto.setOrderNumber(getDisplayOrderNumber(order));
|
|
||||||
dto.setStatus(order.getStatus());
|
|
||||||
|
|
||||||
paymentRepo.findByOrder_Id(order.getId()).ifPresent(p -> {
|
|
||||||
dto.setPaymentStatus(p.getStatus());
|
|
||||||
dto.setPaymentMethod(p.getMethod());
|
|
||||||
});
|
|
||||||
|
|
||||||
boolean redactPersonalData = shouldRedactPersonalData(order.getStatus());
|
|
||||||
if (!redactPersonalData) {
|
|
||||||
dto.setCustomerEmail(order.getCustomerEmail());
|
|
||||||
dto.setCustomerPhone(order.getCustomerPhone());
|
|
||||||
dto.setBillingCustomerType(order.getBillingCustomerType());
|
|
||||||
}
|
|
||||||
dto.setPreferredLanguage(order.getPreferredLanguage());
|
|
||||||
dto.setCurrency(order.getCurrency());
|
|
||||||
dto.setSetupCostChf(order.getSetupCostChf());
|
|
||||||
dto.setShippingCostChf(order.getShippingCostChf());
|
|
||||||
dto.setDiscountChf(order.getDiscountChf());
|
|
||||||
dto.setSubtotalChf(order.getSubtotalChf());
|
|
||||||
dto.setIsCadOrder(order.getIsCadOrder());
|
|
||||||
dto.setSourceRequestId(order.getSourceRequestId());
|
|
||||||
dto.setCadHours(order.getCadHours());
|
|
||||||
dto.setCadHourlyRateChf(order.getCadHourlyRateChf());
|
|
||||||
dto.setCadTotalChf(order.getCadTotalChf());
|
|
||||||
dto.setTotalChf(order.getTotalChf());
|
|
||||||
dto.setCreatedAt(order.getCreatedAt());
|
|
||||||
dto.setShippingSameAsBilling(order.getShippingSameAsBilling());
|
|
||||||
|
|
||||||
if (!redactPersonalData) {
|
|
||||||
AddressDto billing = new AddressDto();
|
|
||||||
billing.setFirstName(order.getBillingFirstName());
|
|
||||||
billing.setLastName(order.getBillingLastName());
|
|
||||||
billing.setCompanyName(order.getBillingCompanyName());
|
|
||||||
billing.setContactPerson(order.getBillingContactPerson());
|
|
||||||
billing.setAddressLine1(order.getBillingAddressLine1());
|
|
||||||
billing.setAddressLine2(order.getBillingAddressLine2());
|
|
||||||
billing.setZip(order.getBillingZip());
|
|
||||||
billing.setCity(order.getBillingCity());
|
|
||||||
billing.setCountryCode(order.getBillingCountryCode());
|
|
||||||
dto.setBillingAddress(billing);
|
|
||||||
|
|
||||||
if (!order.getShippingSameAsBilling()) {
|
|
||||||
AddressDto shipping = new AddressDto();
|
|
||||||
shipping.setFirstName(order.getShippingFirstName());
|
|
||||||
shipping.setLastName(order.getShippingLastName());
|
|
||||||
shipping.setCompanyName(order.getShippingCompanyName());
|
|
||||||
shipping.setContactPerson(order.getShippingContactPerson());
|
|
||||||
shipping.setAddressLine1(order.getShippingAddressLine1());
|
|
||||||
shipping.setAddressLine2(order.getShippingAddressLine2());
|
|
||||||
shipping.setZip(order.getShippingZip());
|
|
||||||
shipping.setCity(order.getShippingCity());
|
|
||||||
shipping.setCountryCode(order.getShippingCountryCode());
|
|
||||||
dto.setShippingAddress(shipping);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
List<OrderItemDto> itemDtos = items.stream().map(i -> {
|
|
||||||
OrderItemDto idto = new OrderItemDto();
|
|
||||||
idto.setId(i.getId());
|
|
||||||
idto.setOriginalFilename(i.getOriginalFilename());
|
|
||||||
idto.setMaterialCode(i.getMaterialCode());
|
|
||||||
idto.setColorCode(i.getColorCode());
|
|
||||||
idto.setQuality(i.getQuality());
|
|
||||||
idto.setNozzleDiameterMm(i.getNozzleDiameterMm());
|
|
||||||
idto.setLayerHeightMm(i.getLayerHeightMm());
|
|
||||||
idto.setInfillPercent(i.getInfillPercent());
|
|
||||||
idto.setInfillPattern(i.getInfillPattern());
|
|
||||||
idto.setSupportsEnabled(i.getSupportsEnabled());
|
|
||||||
idto.setQuantity(i.getQuantity());
|
|
||||||
idto.setPrintTimeSeconds(i.getPrintTimeSeconds());
|
|
||||||
idto.setMaterialGrams(i.getMaterialGrams());
|
|
||||||
idto.setUnitPriceChf(i.getUnitPriceChf());
|
|
||||||
idto.setLineTotalChf(i.getLineTotalChf());
|
|
||||||
return idto;
|
|
||||||
}).collect(Collectors.toList());
|
|
||||||
dto.setItems(itemDtos);
|
|
||||||
|
|
||||||
return dto;
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean shouldRedactPersonalData(String status) {
|
|
||||||
if (status == null || status.isBlank()) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return PERSONAL_DATA_REDACTED_STATUSES.contains(status.trim().toUpperCase(Locale.ROOT));
|
|
||||||
}
|
|
||||||
|
|
||||||
private String getDisplayOrderNumber(Order order) {
|
|
||||||
String orderNumber = order.getOrderNumber();
|
|
||||||
if (orderNumber != null && !orderNumber.isBlank()) {
|
|
||||||
return orderNumber;
|
|
||||||
}
|
|
||||||
return order.getId() != null ? order.getId().toString() : "unknown";
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,113 +1,43 @@
|
|||||||
package com.printcalculator.controller;
|
package com.printcalculator.controller;
|
||||||
|
|
||||||
import com.printcalculator.entity.PrinterMachine;
|
|
||||||
import com.printcalculator.model.PrintStats;
|
import com.printcalculator.model.PrintStats;
|
||||||
import com.printcalculator.model.QuoteResult;
|
import com.printcalculator.model.QuoteResult;
|
||||||
import com.printcalculator.repository.PrinterMachineRepository;
|
|
||||||
import com.printcalculator.service.NozzleLayerHeightPolicyService;
|
|
||||||
import com.printcalculator.service.QuoteCalculator;
|
import com.printcalculator.service.QuoteCalculator;
|
||||||
import com.printcalculator.service.SlicerService;
|
import com.printcalculator.service.SlicerService;
|
||||||
import com.printcalculator.service.storage.ClamAVService;
|
|
||||||
import org.springframework.http.ResponseEntity;
|
import org.springframework.http.ResponseEntity;
|
||||||
import org.springframework.web.bind.annotation.*;
|
import org.springframework.web.bind.annotation.*;
|
||||||
import org.springframework.web.multipart.MultipartFile;
|
import org.springframework.web.multipart.MultipartFile;
|
||||||
import org.springframework.web.server.ResponseStatusException;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.math.BigDecimal;
|
|
||||||
import java.nio.file.Files;
|
import java.nio.file.Files;
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
import static org.springframework.http.HttpStatus.BAD_REQUEST;
|
|
||||||
|
|
||||||
@RestController
|
@RestController
|
||||||
|
@CrossOrigin(origins = "*") // Allow all for development
|
||||||
public class QuoteController {
|
public class QuoteController {
|
||||||
|
|
||||||
private final SlicerService slicerService;
|
private final SlicerService slicerService;
|
||||||
private final QuoteCalculator quoteCalculator;
|
private final QuoteCalculator quoteCalculator;
|
||||||
private final PrinterMachineRepository machineRepo;
|
|
||||||
private final ClamAVService clamAVService;
|
|
||||||
private final NozzleLayerHeightPolicyService nozzleLayerHeightPolicyService;
|
|
||||||
|
|
||||||
// Defaults (using aliases defined in ProfileManager)
|
// Defaults
|
||||||
private static final String DEFAULT_FILAMENT = "pla_basic";
|
private static final String DEFAULT_MACHINE = "Bambu_Lab_A1_machine";
|
||||||
private static final String DEFAULT_PROCESS = "standard";
|
private static final String DEFAULT_FILAMENT = "Bambu_PLA_Basic";
|
||||||
|
private static final String DEFAULT_PROCESS = "Bambu_Process_0.20_Standard";
|
||||||
|
|
||||||
public QuoteController(SlicerService slicerService,
|
public QuoteController(SlicerService slicerService, QuoteCalculator quoteCalculator) {
|
||||||
QuoteCalculator quoteCalculator,
|
|
||||||
PrinterMachineRepository machineRepo,
|
|
||||||
ClamAVService clamAVService,
|
|
||||||
NozzleLayerHeightPolicyService nozzleLayerHeightPolicyService) {
|
|
||||||
this.slicerService = slicerService;
|
this.slicerService = slicerService;
|
||||||
this.quoteCalculator = quoteCalculator;
|
this.quoteCalculator = quoteCalculator;
|
||||||
this.machineRepo = machineRepo;
|
|
||||||
this.clamAVService = clamAVService;
|
|
||||||
this.nozzleLayerHeightPolicyService = nozzleLayerHeightPolicyService;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@PostMapping("/api/quote")
|
@PostMapping("/api/quote")
|
||||||
public ResponseEntity<QuoteResult> calculateQuote(
|
public ResponseEntity<QuoteResult> calculateQuote(
|
||||||
@RequestParam("file") MultipartFile file,
|
@RequestParam("file") MultipartFile file,
|
||||||
@RequestParam(value = "filament", required = false, defaultValue = DEFAULT_FILAMENT) String filament,
|
@RequestParam(value = "machine", defaultValue = DEFAULT_MACHINE) String machine,
|
||||||
@RequestParam(value = "process", required = false) String process,
|
@RequestParam(value = "filament", defaultValue = DEFAULT_FILAMENT) String filament,
|
||||||
@RequestParam(value = "quality", required = false) String quality,
|
@RequestParam(value = "process", defaultValue = DEFAULT_PROCESS) String process
|
||||||
// Advanced Options
|
|
||||||
@RequestParam(value = "infill_density", required = false) Integer infillDensity,
|
|
||||||
@RequestParam(value = "infill_pattern", required = false) String infillPattern,
|
|
||||||
@RequestParam(value = "layer_height", required = false) Double layerHeight,
|
|
||||||
@RequestParam(value = "nozzle_diameter", required = false) Double nozzleDiameter,
|
|
||||||
@RequestParam(value = "support_enabled", required = false) Boolean supportEnabled
|
|
||||||
) throws IOException {
|
) throws IOException {
|
||||||
|
|
||||||
// ... process selection logic ...
|
return processRequest(file, machine, filament, process);
|
||||||
String actualProcess = process;
|
|
||||||
if (actualProcess == null || actualProcess.isEmpty()) {
|
|
||||||
if (quality != null && !quality.isEmpty()) {
|
|
||||||
actualProcess = quality;
|
|
||||||
} else {
|
|
||||||
actualProcess = DEFAULT_PROCESS;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Prepare Overrides
|
|
||||||
Map<String, String> processOverrides = new HashMap<>();
|
|
||||||
Map<String, String> machineOverrides = new HashMap<>();
|
|
||||||
|
|
||||||
if (infillDensity != null) {
|
|
||||||
processOverrides.put("sparse_infill_density", infillDensity + "%");
|
|
||||||
}
|
|
||||||
if (infillPattern != null && !infillPattern.isEmpty()) {
|
|
||||||
processOverrides.put("sparse_infill_pattern", infillPattern);
|
|
||||||
}
|
|
||||||
BigDecimal normalizedNozzle = nozzleLayerHeightPolicyService.resolveNozzle(
|
|
||||||
nozzleDiameter != null ? BigDecimal.valueOf(nozzleDiameter) : null
|
|
||||||
);
|
|
||||||
if (layerHeight != null) {
|
|
||||||
BigDecimal normalizedLayer = nozzleLayerHeightPolicyService.normalizeLayer(BigDecimal.valueOf(layerHeight));
|
|
||||||
if (!nozzleLayerHeightPolicyService.isAllowed(normalizedNozzle, normalizedLayer)) {
|
|
||||||
throw new ResponseStatusException(
|
|
||||||
BAD_REQUEST,
|
|
||||||
"Layer height " + normalizedLayer.stripTrailingZeros().toPlainString()
|
|
||||||
+ " is not allowed for nozzle " + normalizedNozzle.stripTrailingZeros().toPlainString()
|
|
||||||
+ ". Allowed: " + nozzleLayerHeightPolicyService.allowedLayersLabel(normalizedNozzle)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
processOverrides.put("layer_height", normalizedLayer.stripTrailingZeros().toPlainString());
|
|
||||||
}
|
|
||||||
if (supportEnabled != null) {
|
|
||||||
processOverrides.put("enable_support", supportEnabled ? "1" : "0");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (nozzleDiameter != null) {
|
|
||||||
machineOverrides.put("nozzle_diameter", normalizedNozzle.stripTrailingZeros().toPlainString());
|
|
||||||
// Also need to ensure the printer profile is compatible or just override?
|
|
||||||
// Usually nozzle diameter changes require a different printer profile or deep overrides.
|
|
||||||
// For now, we trust the override key works on the base profile.
|
|
||||||
}
|
|
||||||
|
|
||||||
return processRequest(file, filament, actualProcess, machineOverrides, processOverrides);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@PostMapping("/calculate/stl")
|
@PostMapping("/calculate/stl")
|
||||||
@@ -115,40 +45,30 @@ public class QuoteController {
|
|||||||
@RequestParam("file") MultipartFile file
|
@RequestParam("file") MultipartFile file
|
||||||
) throws IOException {
|
) throws IOException {
|
||||||
// Legacy endpoint uses defaults
|
// Legacy endpoint uses defaults
|
||||||
return processRequest(file, DEFAULT_FILAMENT, DEFAULT_PROCESS, null, null);
|
return processRequest(file, DEFAULT_MACHINE, DEFAULT_FILAMENT, DEFAULT_PROCESS);
|
||||||
}
|
}
|
||||||
|
|
||||||
private ResponseEntity<QuoteResult> processRequest(MultipartFile file, String filament, String process,
|
private ResponseEntity<QuoteResult> processRequest(MultipartFile file, String machine, String filament, String process) throws IOException {
|
||||||
Map<String, String> machineOverrides,
|
|
||||||
Map<String, String> processOverrides) throws IOException {
|
|
||||||
if (file.isEmpty()) {
|
if (file.isEmpty()) {
|
||||||
return ResponseEntity.badRequest().build();
|
return ResponseEntity.badRequest().build();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Scan for virus
|
|
||||||
clamAVService.scan(file.getInputStream());
|
|
||||||
|
|
||||||
// Fetch Default Active Machine
|
|
||||||
PrinterMachine machine = machineRepo.findFirstByIsActiveTrue()
|
|
||||||
.orElseThrow(() -> new IOException("No active printer found in database"));
|
|
||||||
|
|
||||||
// Save uploaded file temporarily
|
// Save uploaded file temporarily
|
||||||
Path tempInput = Files.createTempFile("upload_", "_" + file.getOriginalFilename());
|
Path tempInput = Files.createTempFile("upload_", "_" + file.getOriginalFilename());
|
||||||
try {
|
try {
|
||||||
file.transferTo(tempInput.toFile());
|
file.transferTo(tempInput.toFile());
|
||||||
|
|
||||||
String slicerMachineProfile = "bambu_a1"; // TODO: Add to PrinterMachine entity
|
// Slice
|
||||||
|
PrintStats stats = slicerService.slice(tempInput.toFile(), machine, filament, process);
|
||||||
|
|
||||||
PrintStats stats = slicerService.slice(tempInput.toFile(), slicerMachineProfile, filament, process, machineOverrides, processOverrides);
|
// Calculate Quote
|
||||||
|
QuoteResult result = quoteCalculator.calculate(stats);
|
||||||
// Calculate Quote (Pass machine display name for pricing lookup)
|
|
||||||
QuoteResult result = quoteCalculator.calculate(stats, machine.getPrinterDisplayName(), filament);
|
|
||||||
|
|
||||||
return ResponseEntity.ok(result);
|
return ResponseEntity.ok(result);
|
||||||
|
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
e.printStackTrace();
|
e.printStackTrace();
|
||||||
return ResponseEntity.internalServerError().build();
|
return ResponseEntity.internalServerError().build(); // Simplify error handling for now
|
||||||
} finally {
|
} finally {
|
||||||
Files.deleteIfExists(tempInput);
|
Files.deleteIfExists(tempInput);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,242 +0,0 @@
|
|||||||
package com.printcalculator.controller;
|
|
||||||
|
|
||||||
import com.printcalculator.dto.PrintSettingsDto;
|
|
||||||
import com.printcalculator.entity.QuoteLineItem;
|
|
||||||
import com.printcalculator.entity.QuoteSession;
|
|
||||||
import com.printcalculator.repository.QuoteLineItemRepository;
|
|
||||||
import com.printcalculator.repository.QuoteSessionRepository;
|
|
||||||
import com.printcalculator.service.QuoteCalculator;
|
|
||||||
import com.printcalculator.service.QuoteSessionTotalsService;
|
|
||||||
import com.printcalculator.service.quote.QuoteSessionItemService;
|
|
||||||
import com.printcalculator.service.quote.QuoteSessionResponseAssembler;
|
|
||||||
import com.printcalculator.service.quote.QuoteStorageService;
|
|
||||||
import org.springframework.core.io.Resource;
|
|
||||||
import org.springframework.core.io.UrlResource;
|
|
||||||
import org.springframework.http.MediaType;
|
|
||||||
import org.springframework.http.ResponseEntity;
|
|
||||||
import org.springframework.transaction.annotation.Transactional;
|
|
||||||
import org.springframework.web.bind.annotation.*;
|
|
||||||
import org.springframework.web.multipart.MultipartFile;
|
|
||||||
import org.springframework.web.server.ResponseStatusException;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.time.OffsetDateTime;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.UUID;
|
|
||||||
|
|
||||||
import static org.springframework.http.HttpStatus.BAD_REQUEST;
|
|
||||||
|
|
||||||
@RestController
|
|
||||||
@RequestMapping("/api/quote-sessions")
|
|
||||||
public class QuoteSessionController {
|
|
||||||
private final QuoteSessionRepository sessionRepo;
|
|
||||||
private final QuoteLineItemRepository lineItemRepo;
|
|
||||||
private final QuoteCalculator quoteCalculator;
|
|
||||||
private final com.printcalculator.repository.PricingPolicyRepository pricingRepo;
|
|
||||||
private final QuoteSessionTotalsService quoteSessionTotalsService;
|
|
||||||
private final QuoteSessionItemService quoteSessionItemService;
|
|
||||||
private final QuoteStorageService quoteStorageService;
|
|
||||||
private final QuoteSessionResponseAssembler quoteSessionResponseAssembler;
|
|
||||||
|
|
||||||
public QuoteSessionController(QuoteSessionRepository sessionRepo,
|
|
||||||
QuoteLineItemRepository lineItemRepo,
|
|
||||||
QuoteCalculator quoteCalculator,
|
|
||||||
com.printcalculator.repository.PricingPolicyRepository pricingRepo,
|
|
||||||
QuoteSessionTotalsService quoteSessionTotalsService,
|
|
||||||
QuoteSessionItemService quoteSessionItemService,
|
|
||||||
QuoteStorageService quoteStorageService,
|
|
||||||
QuoteSessionResponseAssembler quoteSessionResponseAssembler) {
|
|
||||||
this.sessionRepo = sessionRepo;
|
|
||||||
this.lineItemRepo = lineItemRepo;
|
|
||||||
this.quoteCalculator = quoteCalculator;
|
|
||||||
this.pricingRepo = pricingRepo;
|
|
||||||
this.quoteSessionTotalsService = quoteSessionTotalsService;
|
|
||||||
this.quoteSessionItemService = quoteSessionItemService;
|
|
||||||
this.quoteStorageService = quoteStorageService;
|
|
||||||
this.quoteSessionResponseAssembler = quoteSessionResponseAssembler;
|
|
||||||
}
|
|
||||||
|
|
||||||
@PostMapping(value = "")
|
|
||||||
@Transactional
|
|
||||||
public ResponseEntity<QuoteSession> createSession() {
|
|
||||||
QuoteSession session = new QuoteSession();
|
|
||||||
session.setStatus("ACTIVE");
|
|
||||||
session.setPricingVersion("v1");
|
|
||||||
session.setMaterialCode("PLA");
|
|
||||||
session.setSupportsEnabled(false);
|
|
||||||
session.setCreatedAt(OffsetDateTime.now());
|
|
||||||
session.setExpiresAt(OffsetDateTime.now().plusDays(30));
|
|
||||||
|
|
||||||
var policy = pricingRepo.findFirstByIsActiveTrueOrderByValidFromDesc();
|
|
||||||
session.setSetupCostChf(quoteCalculator.calculateSessionSetupFee(policy));
|
|
||||||
|
|
||||||
session = sessionRepo.save(session);
|
|
||||||
return ResponseEntity.ok(session);
|
|
||||||
}
|
|
||||||
|
|
||||||
@PostMapping(value = "/{id}/line-items", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
|
|
||||||
@Transactional
|
|
||||||
public ResponseEntity<QuoteLineItem> addItemToExistingSession(@PathVariable UUID id,
|
|
||||||
@RequestPart("settings") PrintSettingsDto settings,
|
|
||||||
@RequestPart("file") MultipartFile file) throws IOException {
|
|
||||||
QuoteSession session = sessionRepo.findById(id)
|
|
||||||
.orElseThrow(() -> new RuntimeException("Session not found"));
|
|
||||||
|
|
||||||
QuoteLineItem item = quoteSessionItemService.addItemToSession(session, file, settings);
|
|
||||||
return ResponseEntity.ok(item);
|
|
||||||
}
|
|
||||||
|
|
||||||
@PatchMapping("/line-items/{lineItemId}")
|
|
||||||
@Transactional
|
|
||||||
public ResponseEntity<QuoteLineItem> updateLineItem(@PathVariable UUID lineItemId,
|
|
||||||
@RequestBody Map<String, Object> updates) {
|
|
||||||
QuoteLineItem item = lineItemRepo.findById(lineItemId)
|
|
||||||
.orElseThrow(() -> new RuntimeException("Item not found"));
|
|
||||||
|
|
||||||
QuoteSession session = item.getQuoteSession();
|
|
||||||
if ("CONVERTED".equals(session.getStatus())) {
|
|
||||||
throw new ResponseStatusException(BAD_REQUEST, "Cannot modify a converted session");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (updates.containsKey("quantity")) {
|
|
||||||
item.setQuantity(parsePositiveQuantity(updates.get("quantity")));
|
|
||||||
}
|
|
||||||
if (updates.containsKey("color_code")) {
|
|
||||||
Object colorValue = updates.get("color_code");
|
|
||||||
if (colorValue != null) {
|
|
||||||
item.setColorCode(String.valueOf(colorValue));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
item.setUpdatedAt(OffsetDateTime.now());
|
|
||||||
return ResponseEntity.ok(lineItemRepo.save(item));
|
|
||||||
}
|
|
||||||
|
|
||||||
@DeleteMapping("/{sessionId}/line-items/{lineItemId}")
|
|
||||||
@Transactional
|
|
||||||
public ResponseEntity<Void> deleteLineItem(@PathVariable UUID sessionId,
|
|
||||||
@PathVariable UUID lineItemId) {
|
|
||||||
QuoteLineItem item = lineItemRepo.findById(lineItemId)
|
|
||||||
.orElseThrow(() -> new RuntimeException("Item not found"));
|
|
||||||
|
|
||||||
if (!item.getQuoteSession().getId().equals(sessionId)) {
|
|
||||||
return ResponseEntity.badRequest().build();
|
|
||||||
}
|
|
||||||
|
|
||||||
lineItemRepo.delete(item);
|
|
||||||
return ResponseEntity.noContent().build();
|
|
||||||
}
|
|
||||||
|
|
||||||
@GetMapping("/{id}")
|
|
||||||
public ResponseEntity<Map<String, Object>> getQuoteSession(@PathVariable UUID id) {
|
|
||||||
QuoteSession session = sessionRepo.findById(id)
|
|
||||||
.orElseThrow(() -> new RuntimeException("Session not found"));
|
|
||||||
|
|
||||||
List<QuoteLineItem> items = lineItemRepo.findByQuoteSessionId(id);
|
|
||||||
QuoteSessionTotalsService.QuoteSessionTotals totals = quoteSessionTotalsService.compute(session, items);
|
|
||||||
return ResponseEntity.ok(quoteSessionResponseAssembler.assemble(session, items, totals));
|
|
||||||
}
|
|
||||||
|
|
||||||
@GetMapping(value = "/{sessionId}/line-items/{lineItemId}/content")
|
|
||||||
public ResponseEntity<Resource> downloadLineItemContent(@PathVariable UUID sessionId,
|
|
||||||
@PathVariable UUID lineItemId,
|
|
||||||
@RequestParam(name = "preview", required = false, defaultValue = "false") boolean preview)
|
|
||||||
throws IOException {
|
|
||||||
QuoteLineItem item = lineItemRepo.findById(lineItemId)
|
|
||||||
.orElseThrow(() -> new RuntimeException("Item not found"));
|
|
||||||
|
|
||||||
if (!item.getQuoteSession().getId().equals(sessionId)) {
|
|
||||||
return ResponseEntity.badRequest().build();
|
|
||||||
}
|
|
||||||
|
|
||||||
String targetStoredPath = item.getStoredPath();
|
|
||||||
if (preview) {
|
|
||||||
String convertedPath = quoteStorageService.extractConvertedStoredPath(item);
|
|
||||||
if (convertedPath != null && !convertedPath.isBlank()) {
|
|
||||||
targetStoredPath = convertedPath;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (targetStoredPath == null) {
|
|
||||||
return ResponseEntity.notFound().build();
|
|
||||||
}
|
|
||||||
|
|
||||||
java.nio.file.Path path = quoteStorageService.resolveStoredQuotePath(targetStoredPath, sessionId);
|
|
||||||
if (path == null || !java.nio.file.Files.exists(path)) {
|
|
||||||
return ResponseEntity.notFound().build();
|
|
||||||
}
|
|
||||||
|
|
||||||
Resource resource = new UrlResource(path.toUri());
|
|
||||||
String downloadName = preview ? path.getFileName().toString() : item.getOriginalFilename();
|
|
||||||
|
|
||||||
return ResponseEntity.ok()
|
|
||||||
.contentType(MediaType.APPLICATION_OCTET_STREAM)
|
|
||||||
.header(org.springframework.http.HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=\"" + downloadName + "\"")
|
|
||||||
.body(resource);
|
|
||||||
}
|
|
||||||
|
|
||||||
@GetMapping(value = "/{sessionId}/line-items/{lineItemId}/stl-preview")
|
|
||||||
public ResponseEntity<Resource> downloadLineItemStlPreview(@PathVariable UUID sessionId,
|
|
||||||
@PathVariable UUID lineItemId)
|
|
||||||
throws IOException {
|
|
||||||
QuoteLineItem item = lineItemRepo.findById(lineItemId)
|
|
||||||
.orElseThrow(() -> new RuntimeException("Item not found"));
|
|
||||||
|
|
||||||
if (!item.getQuoteSession().getId().equals(sessionId)) {
|
|
||||||
return ResponseEntity.badRequest().build();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!"stl".equals(quoteStorageService.getSafeExtension(item.getOriginalFilename(), ""))) {
|
|
||||||
return ResponseEntity.notFound().build();
|
|
||||||
}
|
|
||||||
|
|
||||||
String targetStoredPath = item.getStoredPath();
|
|
||||||
if (targetStoredPath == null || targetStoredPath.isBlank()) {
|
|
||||||
return ResponseEntity.notFound().build();
|
|
||||||
}
|
|
||||||
|
|
||||||
java.nio.file.Path path = quoteStorageService.resolveStoredQuotePath(targetStoredPath, sessionId);
|
|
||||||
if (path == null || !java.nio.file.Files.exists(path)) {
|
|
||||||
return ResponseEntity.notFound().build();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!"stl".equals(quoteStorageService.getSafeExtension(path.getFileName().toString(), ""))) {
|
|
||||||
return ResponseEntity.notFound().build();
|
|
||||||
}
|
|
||||||
|
|
||||||
Resource resource = new UrlResource(path.toUri());
|
|
||||||
String downloadName = path.getFileName().toString();
|
|
||||||
|
|
||||||
return ResponseEntity.ok()
|
|
||||||
.contentType(MediaType.parseMediaType("model/stl"))
|
|
||||||
.header(org.springframework.http.HttpHeaders.CONTENT_DISPOSITION, "inline; filename=\"" + downloadName + "\"")
|
|
||||||
.body(resource);
|
|
||||||
}
|
|
||||||
|
|
||||||
private int parsePositiveQuantity(Object raw) {
|
|
||||||
if (raw == null) {
|
|
||||||
throw new ResponseStatusException(BAD_REQUEST, "Quantity is required");
|
|
||||||
}
|
|
||||||
|
|
||||||
int quantity;
|
|
||||||
if (raw instanceof Number number) {
|
|
||||||
double numericValue = number.doubleValue();
|
|
||||||
if (!Double.isFinite(numericValue)) {
|
|
||||||
throw new ResponseStatusException(BAD_REQUEST, "Quantity must be a finite number");
|
|
||||||
}
|
|
||||||
quantity = (int) Math.floor(numericValue);
|
|
||||||
} else {
|
|
||||||
try {
|
|
||||||
quantity = Integer.parseInt(String.valueOf(raw).trim());
|
|
||||||
} catch (NumberFormatException ex) {
|
|
||||||
throw new ResponseStatusException(BAD_REQUEST, "Quantity must be an integer");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (quantity < 1) {
|
|
||||||
throw new ResponseStatusException(BAD_REQUEST, "Quantity must be >= 1");
|
|
||||||
}
|
|
||||||
return quantity;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,83 +0,0 @@
|
|||||||
package com.printcalculator.controller.admin;
|
|
||||||
|
|
||||||
import com.printcalculator.dto.AdminLoginRequest;
|
|
||||||
import com.printcalculator.security.AdminLoginThrottleService;
|
|
||||||
import com.printcalculator.security.AdminSessionService;
|
|
||||||
import jakarta.servlet.http.HttpServletRequest;
|
|
||||||
import jakarta.servlet.http.HttpServletResponse;
|
|
||||||
import jakarta.validation.Valid;
|
|
||||||
import org.springframework.http.HttpHeaders;
|
|
||||||
import org.springframework.http.ResponseEntity;
|
|
||||||
import org.springframework.web.bind.annotation.GetMapping;
|
|
||||||
import org.springframework.web.bind.annotation.PostMapping;
|
|
||||||
import org.springframework.web.bind.annotation.RequestBody;
|
|
||||||
import org.springframework.web.bind.annotation.RequestMapping;
|
|
||||||
import org.springframework.web.bind.annotation.RestController;
|
|
||||||
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.OptionalLong;
|
|
||||||
|
|
||||||
@RestController
|
|
||||||
@RequestMapping("/api/admin/auth")
|
|
||||||
public class AdminAuthController {
|
|
||||||
|
|
||||||
private final AdminSessionService adminSessionService;
|
|
||||||
private final AdminLoginThrottleService adminLoginThrottleService;
|
|
||||||
|
|
||||||
public AdminAuthController(
|
|
||||||
AdminSessionService adminSessionService,
|
|
||||||
AdminLoginThrottleService adminLoginThrottleService
|
|
||||||
) {
|
|
||||||
this.adminSessionService = adminSessionService;
|
|
||||||
this.adminLoginThrottleService = adminLoginThrottleService;
|
|
||||||
}
|
|
||||||
|
|
||||||
@PostMapping("/login")
|
|
||||||
public ResponseEntity<Map<String, Object>> login(
|
|
||||||
@Valid @RequestBody AdminLoginRequest request,
|
|
||||||
HttpServletRequest httpRequest,
|
|
||||||
HttpServletResponse response
|
|
||||||
) {
|
|
||||||
String clientKey = adminLoginThrottleService.resolveClientKey(httpRequest);
|
|
||||||
OptionalLong remainingLock = adminLoginThrottleService.getRemainingLockSeconds(clientKey);
|
|
||||||
if (remainingLock.isPresent()) {
|
|
||||||
long retryAfter = remainingLock.getAsLong();
|
|
||||||
return ResponseEntity.status(429)
|
|
||||||
.header(HttpHeaders.RETRY_AFTER, String.valueOf(retryAfter))
|
|
||||||
.body(Map.of(
|
|
||||||
"authenticated", false,
|
|
||||||
"retryAfterSeconds", retryAfter
|
|
||||||
));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!adminSessionService.isPasswordValid(request.getPassword())) {
|
|
||||||
long retryAfter = adminLoginThrottleService.registerFailure(clientKey);
|
|
||||||
return ResponseEntity.status(401)
|
|
||||||
.header(HttpHeaders.RETRY_AFTER, String.valueOf(retryAfter))
|
|
||||||
.body(Map.of(
|
|
||||||
"authenticated", false,
|
|
||||||
"retryAfterSeconds", retryAfter
|
|
||||||
));
|
|
||||||
}
|
|
||||||
|
|
||||||
adminLoginThrottleService.reset(clientKey);
|
|
||||||
String token = adminSessionService.createSessionToken();
|
|
||||||
response.addHeader(HttpHeaders.SET_COOKIE, adminSessionService.buildLoginCookie(token).toString());
|
|
||||||
|
|
||||||
return ResponseEntity.ok(Map.of(
|
|
||||||
"authenticated", true,
|
|
||||||
"expiresInMinutes", adminSessionService.getSessionTtlMinutes()
|
|
||||||
));
|
|
||||||
}
|
|
||||||
|
|
||||||
@PostMapping("/logout")
|
|
||||||
public ResponseEntity<Map<String, Object>> logout(HttpServletResponse response) {
|
|
||||||
response.addHeader(HttpHeaders.SET_COOKIE, adminSessionService.buildLogoutCookie().toString());
|
|
||||||
return ResponseEntity.ok(Map.of("authenticated", false));
|
|
||||||
}
|
|
||||||
|
|
||||||
@GetMapping("/me")
|
|
||||||
public ResponseEntity<Map<String, Object>> me() {
|
|
||||||
return ResponseEntity.ok(Map.of("authenticated", true));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,355 +0,0 @@
|
|||||||
package com.printcalculator.controller.admin;
|
|
||||||
|
|
||||||
import com.printcalculator.dto.AdminFilamentMaterialTypeDto;
|
|
||||||
import com.printcalculator.dto.AdminFilamentVariantDto;
|
|
||||||
import com.printcalculator.dto.AdminUpsertFilamentMaterialTypeRequest;
|
|
||||||
import com.printcalculator.dto.AdminUpsertFilamentVariantRequest;
|
|
||||||
import com.printcalculator.entity.FilamentMaterialType;
|
|
||||||
import com.printcalculator.entity.FilamentVariant;
|
|
||||||
import com.printcalculator.repository.FilamentMaterialTypeRepository;
|
|
||||||
import com.printcalculator.repository.FilamentVariantRepository;
|
|
||||||
import com.printcalculator.repository.OrderItemRepository;
|
|
||||||
import com.printcalculator.repository.QuoteLineItemRepository;
|
|
||||||
import org.springframework.http.ResponseEntity;
|
|
||||||
import org.springframework.transaction.annotation.Transactional;
|
|
||||||
import org.springframework.web.bind.annotation.*;
|
|
||||||
import org.springframework.web.server.ResponseStatusException;
|
|
||||||
|
|
||||||
import java.math.BigDecimal;
|
|
||||||
import java.time.OffsetDateTime;
|
|
||||||
import java.util.Comparator;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Locale;
|
|
||||||
import java.util.Set;
|
|
||||||
import java.util.regex.Pattern;
|
|
||||||
|
|
||||||
import static org.springframework.http.HttpStatus.BAD_REQUEST;
|
|
||||||
import static org.springframework.http.HttpStatus.CONFLICT;
|
|
||||||
import static org.springframework.http.HttpStatus.NOT_FOUND;
|
|
||||||
|
|
||||||
@RestController
|
|
||||||
@RequestMapping("/api/admin/filaments")
|
|
||||||
@Transactional(readOnly = true)
|
|
||||||
public class AdminFilamentController {
|
|
||||||
private static final BigDecimal MAX_NUMERIC_6_3 = new BigDecimal("999.999");
|
|
||||||
private static final Pattern HEX_COLOR_PATTERN = Pattern.compile("^#[0-9A-Fa-f]{6}$");
|
|
||||||
private static final Set<String> ALLOWED_FINISH_TYPES = Set.of(
|
|
||||||
"GLOSSY", "MATTE", "MARBLE", "SILK", "TRANSLUCENT", "SPECIAL"
|
|
||||||
);
|
|
||||||
|
|
||||||
private final FilamentMaterialTypeRepository materialRepo;
|
|
||||||
private final FilamentVariantRepository variantRepo;
|
|
||||||
private final QuoteLineItemRepository quoteLineItemRepo;
|
|
||||||
private final OrderItemRepository orderItemRepo;
|
|
||||||
|
|
||||||
public AdminFilamentController(
|
|
||||||
FilamentMaterialTypeRepository materialRepo,
|
|
||||||
FilamentVariantRepository variantRepo,
|
|
||||||
QuoteLineItemRepository quoteLineItemRepo,
|
|
||||||
OrderItemRepository orderItemRepo
|
|
||||||
) {
|
|
||||||
this.materialRepo = materialRepo;
|
|
||||||
this.variantRepo = variantRepo;
|
|
||||||
this.quoteLineItemRepo = quoteLineItemRepo;
|
|
||||||
this.orderItemRepo = orderItemRepo;
|
|
||||||
}
|
|
||||||
|
|
||||||
@GetMapping("/materials")
|
|
||||||
public ResponseEntity<List<AdminFilamentMaterialTypeDto>> getMaterials() {
|
|
||||||
List<AdminFilamentMaterialTypeDto> response = materialRepo.findAll().stream()
|
|
||||||
.sorted(Comparator.comparing(FilamentMaterialType::getMaterialCode, String.CASE_INSENSITIVE_ORDER))
|
|
||||||
.map(this::toMaterialDto)
|
|
||||||
.toList();
|
|
||||||
return ResponseEntity.ok(response);
|
|
||||||
}
|
|
||||||
|
|
||||||
@GetMapping("/variants")
|
|
||||||
public ResponseEntity<List<AdminFilamentVariantDto>> getVariants() {
|
|
||||||
List<AdminFilamentVariantDto> response = variantRepo.findAll().stream()
|
|
||||||
.sorted(Comparator
|
|
||||||
.comparing((FilamentVariant v) -> {
|
|
||||||
FilamentMaterialType type = v.getFilamentMaterialType();
|
|
||||||
return type != null && type.getMaterialCode() != null ? type.getMaterialCode() : "";
|
|
||||||
}, String.CASE_INSENSITIVE_ORDER)
|
|
||||||
.thenComparing(v -> v.getVariantDisplayName() != null ? v.getVariantDisplayName() : "", String.CASE_INSENSITIVE_ORDER))
|
|
||||||
.map(this::toVariantDto)
|
|
||||||
.toList();
|
|
||||||
return ResponseEntity.ok(response);
|
|
||||||
}
|
|
||||||
|
|
||||||
@PostMapping("/materials")
|
|
||||||
@Transactional
|
|
||||||
public ResponseEntity<AdminFilamentMaterialTypeDto> createMaterial(
|
|
||||||
@RequestBody AdminUpsertFilamentMaterialTypeRequest payload
|
|
||||||
) {
|
|
||||||
String materialCode = normalizeAndValidateMaterialCode(payload);
|
|
||||||
ensureMaterialCodeAvailable(materialCode, null);
|
|
||||||
|
|
||||||
FilamentMaterialType material = new FilamentMaterialType();
|
|
||||||
applyMaterialPayload(material, payload, materialCode);
|
|
||||||
FilamentMaterialType saved = materialRepo.save(material);
|
|
||||||
return ResponseEntity.ok(toMaterialDto(saved));
|
|
||||||
}
|
|
||||||
|
|
||||||
@PutMapping("/materials/{materialTypeId}")
|
|
||||||
@Transactional
|
|
||||||
public ResponseEntity<AdminFilamentMaterialTypeDto> updateMaterial(
|
|
||||||
@PathVariable Long materialTypeId,
|
|
||||||
@RequestBody AdminUpsertFilamentMaterialTypeRequest payload
|
|
||||||
) {
|
|
||||||
FilamentMaterialType material = materialRepo.findById(materialTypeId)
|
|
||||||
.orElseThrow(() -> new ResponseStatusException(NOT_FOUND, "Filament material not found"));
|
|
||||||
|
|
||||||
String materialCode = normalizeAndValidateMaterialCode(payload);
|
|
||||||
ensureMaterialCodeAvailable(materialCode, materialTypeId);
|
|
||||||
|
|
||||||
applyMaterialPayload(material, payload, materialCode);
|
|
||||||
FilamentMaterialType saved = materialRepo.save(material);
|
|
||||||
return ResponseEntity.ok(toMaterialDto(saved));
|
|
||||||
}
|
|
||||||
|
|
||||||
@PostMapping("/variants")
|
|
||||||
@Transactional
|
|
||||||
public ResponseEntity<AdminFilamentVariantDto> createVariant(
|
|
||||||
@RequestBody AdminUpsertFilamentVariantRequest payload
|
|
||||||
) {
|
|
||||||
FilamentMaterialType material = validateAndResolveMaterial(payload);
|
|
||||||
String normalizedDisplayName = normalizeAndValidateVariantDisplayName(payload.getVariantDisplayName());
|
|
||||||
String normalizedColorName = normalizeAndValidateColorName(payload.getColorName());
|
|
||||||
validateNumericPayload(payload);
|
|
||||||
ensureVariantDisplayNameAvailable(material, normalizedDisplayName, null);
|
|
||||||
|
|
||||||
FilamentVariant variant = new FilamentVariant();
|
|
||||||
variant.setCreatedAt(OffsetDateTime.now());
|
|
||||||
applyVariantPayload(variant, payload, material, normalizedDisplayName, normalizedColorName);
|
|
||||||
FilamentVariant saved = variantRepo.save(variant);
|
|
||||||
return ResponseEntity.ok(toVariantDto(saved));
|
|
||||||
}
|
|
||||||
|
|
||||||
@PutMapping("/variants/{variantId}")
|
|
||||||
@Transactional
|
|
||||||
public ResponseEntity<AdminFilamentVariantDto> updateVariant(
|
|
||||||
@PathVariable Long variantId,
|
|
||||||
@RequestBody AdminUpsertFilamentVariantRequest payload
|
|
||||||
) {
|
|
||||||
FilamentVariant variant = variantRepo.findById(variantId)
|
|
||||||
.orElseThrow(() -> new ResponseStatusException(NOT_FOUND, "Filament variant not found"));
|
|
||||||
|
|
||||||
FilamentMaterialType material = validateAndResolveMaterial(payload);
|
|
||||||
String normalizedDisplayName = normalizeAndValidateVariantDisplayName(payload.getVariantDisplayName());
|
|
||||||
String normalizedColorName = normalizeAndValidateColorName(payload.getColorName());
|
|
||||||
validateNumericPayload(payload);
|
|
||||||
ensureVariantDisplayNameAvailable(material, normalizedDisplayName, variantId);
|
|
||||||
|
|
||||||
applyVariantPayload(variant, payload, material, normalizedDisplayName, normalizedColorName);
|
|
||||||
FilamentVariant saved = variantRepo.save(variant);
|
|
||||||
return ResponseEntity.ok(toVariantDto(saved));
|
|
||||||
}
|
|
||||||
|
|
||||||
@DeleteMapping("/variants/{variantId}")
|
|
||||||
@Transactional
|
|
||||||
public ResponseEntity<Void> deleteVariant(@PathVariable Long variantId) {
|
|
||||||
FilamentVariant variant = variantRepo.findById(variantId)
|
|
||||||
.orElseThrow(() -> new ResponseStatusException(NOT_FOUND, "Filament variant not found"));
|
|
||||||
|
|
||||||
if (quoteLineItemRepo.existsByFilamentVariant_Id(variantId) || orderItemRepo.existsByFilamentVariant_Id(variantId)) {
|
|
||||||
throw new ResponseStatusException(CONFLICT, "Variant is already used in quotes/orders and cannot be deleted");
|
|
||||||
}
|
|
||||||
|
|
||||||
variantRepo.delete(variant);
|
|
||||||
return ResponseEntity.noContent().build();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void applyMaterialPayload(
|
|
||||||
FilamentMaterialType material,
|
|
||||||
AdminUpsertFilamentMaterialTypeRequest payload,
|
|
||||||
String normalizedMaterialCode
|
|
||||||
) {
|
|
||||||
boolean isFlexible = payload != null && Boolean.TRUE.equals(payload.getIsFlexible());
|
|
||||||
boolean isTechnical = payload != null && Boolean.TRUE.equals(payload.getIsTechnical());
|
|
||||||
String technicalTypeLabel = payload != null && payload.getTechnicalTypeLabel() != null
|
|
||||||
? payload.getTechnicalTypeLabel().trim()
|
|
||||||
: null;
|
|
||||||
|
|
||||||
material.setMaterialCode(normalizedMaterialCode);
|
|
||||||
material.setIsFlexible(isFlexible);
|
|
||||||
material.setIsTechnical(isTechnical);
|
|
||||||
material.setTechnicalTypeLabel(isTechnical && technicalTypeLabel != null && !technicalTypeLabel.isBlank()
|
|
||||||
? technicalTypeLabel
|
|
||||||
: null);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void applyVariantPayload(
|
|
||||||
FilamentVariant variant,
|
|
||||||
AdminUpsertFilamentVariantRequest payload,
|
|
||||||
FilamentMaterialType material,
|
|
||||||
String normalizedDisplayName,
|
|
||||||
String normalizedColorName
|
|
||||||
) {
|
|
||||||
String normalizedColorHex = normalizeAndValidateColorHex(payload.getColorHex());
|
|
||||||
String normalizedFinishType = normalizeAndValidateFinishType(payload.getFinishType(), payload.getIsMatte());
|
|
||||||
String normalizedBrand = normalizeOptional(payload.getBrand());
|
|
||||||
|
|
||||||
variant.setFilamentMaterialType(material);
|
|
||||||
variant.setVariantDisplayName(normalizedDisplayName);
|
|
||||||
variant.setColorName(normalizedColorName);
|
|
||||||
variant.setColorHex(normalizedColorHex);
|
|
||||||
variant.setFinishType(normalizedFinishType);
|
|
||||||
variant.setBrand(normalizedBrand);
|
|
||||||
variant.setIsMatte(Boolean.TRUE.equals(payload.getIsMatte()) || "MATTE".equals(normalizedFinishType));
|
|
||||||
variant.setIsSpecial(Boolean.TRUE.equals(payload.getIsSpecial()));
|
|
||||||
variant.setCostChfPerKg(payload.getCostChfPerKg());
|
|
||||||
variant.setStockSpools(payload.getStockSpools());
|
|
||||||
variant.setSpoolNetKg(payload.getSpoolNetKg());
|
|
||||||
variant.setIsActive(payload.getIsActive() == null || payload.getIsActive());
|
|
||||||
}
|
|
||||||
|
|
||||||
private String normalizeAndValidateMaterialCode(AdminUpsertFilamentMaterialTypeRequest payload) {
|
|
||||||
if (payload == null || payload.getMaterialCode() == null || payload.getMaterialCode().isBlank()) {
|
|
||||||
throw new ResponseStatusException(BAD_REQUEST, "Material code is required");
|
|
||||||
}
|
|
||||||
return payload.getMaterialCode().trim().toUpperCase();
|
|
||||||
}
|
|
||||||
|
|
||||||
private String normalizeAndValidateVariantDisplayName(String value) {
|
|
||||||
if (value == null || value.isBlank()) {
|
|
||||||
throw new ResponseStatusException(BAD_REQUEST, "Variant display name is required");
|
|
||||||
}
|
|
||||||
return value.trim();
|
|
||||||
}
|
|
||||||
|
|
||||||
private String normalizeAndValidateColorName(String value) {
|
|
||||||
if (value == null || value.isBlank()) {
|
|
||||||
throw new ResponseStatusException(BAD_REQUEST, "Color name is required");
|
|
||||||
}
|
|
||||||
return value.trim();
|
|
||||||
}
|
|
||||||
|
|
||||||
private String normalizeAndValidateColorHex(String value) {
|
|
||||||
if (value == null || value.isBlank()) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
String normalized = value.trim();
|
|
||||||
if (!HEX_COLOR_PATTERN.matcher(normalized).matches()) {
|
|
||||||
throw new ResponseStatusException(BAD_REQUEST, "Color hex must be in format #RRGGBB");
|
|
||||||
}
|
|
||||||
return normalized.toUpperCase(Locale.ROOT);
|
|
||||||
}
|
|
||||||
|
|
||||||
private String normalizeAndValidateFinishType(String finishType, Boolean isMatte) {
|
|
||||||
String normalized = finishType == null || finishType.isBlank()
|
|
||||||
? (Boolean.TRUE.equals(isMatte) ? "MATTE" : "GLOSSY")
|
|
||||||
: finishType.trim().toUpperCase(Locale.ROOT);
|
|
||||||
if (!ALLOWED_FINISH_TYPES.contains(normalized)) {
|
|
||||||
throw new ResponseStatusException(BAD_REQUEST, "Invalid finish type");
|
|
||||||
}
|
|
||||||
return normalized;
|
|
||||||
}
|
|
||||||
|
|
||||||
private String normalizeOptional(String value) {
|
|
||||||
if (value == null) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
String normalized = value.trim();
|
|
||||||
return normalized.isBlank() ? null : normalized;
|
|
||||||
}
|
|
||||||
|
|
||||||
private FilamentMaterialType validateAndResolveMaterial(AdminUpsertFilamentVariantRequest payload) {
|
|
||||||
if (payload == null || payload.getMaterialTypeId() == null) {
|
|
||||||
throw new ResponseStatusException(BAD_REQUEST, "Material type id is required");
|
|
||||||
}
|
|
||||||
|
|
||||||
return materialRepo.findById(payload.getMaterialTypeId())
|
|
||||||
.orElseThrow(() -> new ResponseStatusException(BAD_REQUEST, "Material type not found"));
|
|
||||||
}
|
|
||||||
|
|
||||||
private void validateNumericPayload(AdminUpsertFilamentVariantRequest payload) {
|
|
||||||
if (payload.getCostChfPerKg() == null || payload.getCostChfPerKg().compareTo(BigDecimal.ZERO) < 0) {
|
|
||||||
throw new ResponseStatusException(BAD_REQUEST, "Cost CHF/kg must be >= 0");
|
|
||||||
}
|
|
||||||
validateNumeric63(payload.getStockSpools(), "Stock spools", true);
|
|
||||||
validateNumeric63(payload.getSpoolNetKg(), "Spool net kg", false);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void validateNumeric63(BigDecimal value, String fieldName, boolean allowZero) {
|
|
||||||
if (value == null) {
|
|
||||||
throw new ResponseStatusException(BAD_REQUEST, fieldName + " is required");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (allowZero) {
|
|
||||||
if (value.compareTo(BigDecimal.ZERO) < 0) {
|
|
||||||
throw new ResponseStatusException(BAD_REQUEST, fieldName + " must be >= 0");
|
|
||||||
}
|
|
||||||
} else if (value.compareTo(BigDecimal.ZERO) <= 0) {
|
|
||||||
throw new ResponseStatusException(BAD_REQUEST, fieldName + " must be > 0");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (value.scale() > 3) {
|
|
||||||
throw new ResponseStatusException(BAD_REQUEST, fieldName + " must have at most 3 decimal places");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (value.compareTo(MAX_NUMERIC_6_3) > 0) {
|
|
||||||
throw new ResponseStatusException(BAD_REQUEST, fieldName + " must be <= 999.999");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void ensureMaterialCodeAvailable(String materialCode, Long currentMaterialId) {
|
|
||||||
materialRepo.findByMaterialCode(materialCode).ifPresent(existing -> {
|
|
||||||
if (currentMaterialId == null || !existing.getId().equals(currentMaterialId)) {
|
|
||||||
throw new ResponseStatusException(BAD_REQUEST, "Material code already exists");
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
private void ensureVariantDisplayNameAvailable(FilamentMaterialType material, String displayName, Long currentVariantId) {
|
|
||||||
variantRepo.findByFilamentMaterialTypeAndVariantDisplayName(material, displayName).ifPresent(existing -> {
|
|
||||||
if (currentVariantId == null || !existing.getId().equals(currentVariantId)) {
|
|
||||||
throw new ResponseStatusException(BAD_REQUEST, "Variant display name already exists for this material");
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
private AdminFilamentMaterialTypeDto toMaterialDto(FilamentMaterialType material) {
|
|
||||||
AdminFilamentMaterialTypeDto dto = new AdminFilamentMaterialTypeDto();
|
|
||||||
dto.setId(material.getId());
|
|
||||||
dto.setMaterialCode(material.getMaterialCode());
|
|
||||||
dto.setIsFlexible(material.getIsFlexible());
|
|
||||||
dto.setIsTechnical(material.getIsTechnical());
|
|
||||||
dto.setTechnicalTypeLabel(material.getTechnicalTypeLabel());
|
|
||||||
return dto;
|
|
||||||
}
|
|
||||||
|
|
||||||
private AdminFilamentVariantDto toVariantDto(FilamentVariant variant) {
|
|
||||||
AdminFilamentVariantDto dto = new AdminFilamentVariantDto();
|
|
||||||
dto.setId(variant.getId());
|
|
||||||
|
|
||||||
FilamentMaterialType material = variant.getFilamentMaterialType();
|
|
||||||
if (material != null) {
|
|
||||||
dto.setMaterialTypeId(material.getId());
|
|
||||||
dto.setMaterialCode(material.getMaterialCode());
|
|
||||||
dto.setMaterialIsFlexible(material.getIsFlexible());
|
|
||||||
dto.setMaterialIsTechnical(material.getIsTechnical());
|
|
||||||
dto.setMaterialTechnicalTypeLabel(material.getTechnicalTypeLabel());
|
|
||||||
}
|
|
||||||
|
|
||||||
dto.setVariantDisplayName(variant.getVariantDisplayName());
|
|
||||||
dto.setColorName(variant.getColorName());
|
|
||||||
dto.setColorHex(variant.getColorHex());
|
|
||||||
dto.setFinishType(variant.getFinishType());
|
|
||||||
dto.setBrand(variant.getBrand());
|
|
||||||
dto.setIsMatte(variant.getIsMatte());
|
|
||||||
dto.setIsSpecial(variant.getIsSpecial());
|
|
||||||
dto.setCostChfPerKg(variant.getCostChfPerKg());
|
|
||||||
dto.setStockSpools(variant.getStockSpools());
|
|
||||||
dto.setSpoolNetKg(variant.getSpoolNetKg());
|
|
||||||
BigDecimal stockKg = BigDecimal.ZERO;
|
|
||||||
if (variant.getStockSpools() != null && variant.getSpoolNetKg() != null) {
|
|
||||||
stockKg = variant.getStockSpools().multiply(variant.getSpoolNetKg());
|
|
||||||
}
|
|
||||||
dto.setStockKg(stockKg);
|
|
||||||
dto.setStockFilamentGrams(stockKg.multiply(BigDecimal.valueOf(1000)));
|
|
||||||
dto.setIsActive(variant.getIsActive());
|
|
||||||
dto.setCreatedAt(variant.getCreatedAt());
|
|
||||||
return dto;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,509 +0,0 @@
|
|||||||
package com.printcalculator.controller.admin;
|
|
||||||
|
|
||||||
import com.printcalculator.dto.AdminContactRequestDto;
|
|
||||||
import com.printcalculator.dto.AdminContactRequestAttachmentDto;
|
|
||||||
import com.printcalculator.dto.AdminContactRequestDetailDto;
|
|
||||||
import com.printcalculator.dto.AdminCadInvoiceCreateRequest;
|
|
||||||
import com.printcalculator.dto.AdminCadInvoiceDto;
|
|
||||||
import com.printcalculator.dto.AdminFilamentStockDto;
|
|
||||||
import com.printcalculator.dto.AdminQuoteSessionDto;
|
|
||||||
import com.printcalculator.dto.AdminUpdateContactRequestStatusRequest;
|
|
||||||
import com.printcalculator.entity.CustomQuoteRequest;
|
|
||||||
import com.printcalculator.entity.CustomQuoteRequestAttachment;
|
|
||||||
import com.printcalculator.entity.FilamentVariant;
|
|
||||||
import com.printcalculator.entity.FilamentVariantStockKg;
|
|
||||||
import com.printcalculator.entity.Order;
|
|
||||||
import com.printcalculator.entity.QuoteLineItem;
|
|
||||||
import com.printcalculator.entity.QuoteSession;
|
|
||||||
import com.printcalculator.repository.CustomQuoteRequestAttachmentRepository;
|
|
||||||
import com.printcalculator.repository.CustomQuoteRequestRepository;
|
|
||||||
import com.printcalculator.repository.FilamentVariantRepository;
|
|
||||||
import com.printcalculator.repository.FilamentVariantStockKgRepository;
|
|
||||||
import com.printcalculator.repository.OrderRepository;
|
|
||||||
import com.printcalculator.repository.PricingPolicyRepository;
|
|
||||||
import com.printcalculator.repository.QuoteLineItemRepository;
|
|
||||||
import com.printcalculator.repository.QuoteSessionRepository;
|
|
||||||
import com.printcalculator.service.QuoteSessionTotalsService;
|
|
||||||
import org.slf4j.Logger;
|
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
import org.springframework.core.io.Resource;
|
|
||||||
import org.springframework.core.io.UrlResource;
|
|
||||||
import org.springframework.data.domain.Sort;
|
|
||||||
import org.springframework.http.ContentDisposition;
|
|
||||||
import org.springframework.http.HttpHeaders;
|
|
||||||
import org.springframework.http.MediaType;
|
|
||||||
import org.springframework.http.ResponseEntity;
|
|
||||||
import org.springframework.transaction.annotation.Transactional;
|
|
||||||
import org.springframework.web.bind.annotation.DeleteMapping;
|
|
||||||
import org.springframework.web.bind.annotation.GetMapping;
|
|
||||||
import org.springframework.web.bind.annotation.PatchMapping;
|
|
||||||
import org.springframework.web.bind.annotation.PathVariable;
|
|
||||||
import org.springframework.web.bind.annotation.PostMapping;
|
|
||||||
import org.springframework.web.bind.annotation.RequestBody;
|
|
||||||
import org.springframework.web.bind.annotation.RequestMapping;
|
|
||||||
import org.springframework.web.bind.annotation.RestController;
|
|
||||||
import org.springframework.web.server.ResponseStatusException;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.UncheckedIOException;
|
|
||||||
import java.math.BigDecimal;
|
|
||||||
import java.math.RoundingMode;
|
|
||||||
import java.net.MalformedURLException;
|
|
||||||
import java.nio.charset.StandardCharsets;
|
|
||||||
import java.nio.file.Files;
|
|
||||||
import java.nio.file.Path;
|
|
||||||
import java.nio.file.Paths;
|
|
||||||
import java.time.OffsetDateTime;
|
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.Comparator;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Locale;
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.Set;
|
|
||||||
import java.util.UUID;
|
|
||||||
import java.util.stream.Collectors;
|
|
||||||
import java.util.stream.Stream;
|
|
||||||
|
|
||||||
import static org.springframework.http.HttpStatus.BAD_REQUEST;
|
|
||||||
import static org.springframework.http.HttpStatus.CONFLICT;
|
|
||||||
import static org.springframework.http.HttpStatus.INTERNAL_SERVER_ERROR;
|
|
||||||
import static org.springframework.http.HttpStatus.NOT_FOUND;
|
|
||||||
|
|
||||||
@RestController
|
|
||||||
@RequestMapping("/api/admin")
|
|
||||||
@Transactional(readOnly = true)
|
|
||||||
public class AdminOperationsController {
|
|
||||||
private static final Logger logger = LoggerFactory.getLogger(AdminOperationsController.class);
|
|
||||||
private static final Path CONTACT_ATTACHMENTS_ROOT = Paths.get("storage_requests").toAbsolutePath().normalize();
|
|
||||||
private static final Set<String> CONTACT_REQUEST_ALLOWED_STATUSES = Set.of(
|
|
||||||
"NEW", "PENDING", "IN_PROGRESS", "DONE", "CLOSED"
|
|
||||||
);
|
|
||||||
|
|
||||||
private final FilamentVariantStockKgRepository filamentStockRepo;
|
|
||||||
private final FilamentVariantRepository filamentVariantRepo;
|
|
||||||
private final CustomQuoteRequestRepository customQuoteRequestRepo;
|
|
||||||
private final CustomQuoteRequestAttachmentRepository customQuoteRequestAttachmentRepo;
|
|
||||||
private final QuoteSessionRepository quoteSessionRepo;
|
|
||||||
private final QuoteLineItemRepository quoteLineItemRepo;
|
|
||||||
private final OrderRepository orderRepo;
|
|
||||||
private final PricingPolicyRepository pricingRepo;
|
|
||||||
private final QuoteSessionTotalsService quoteSessionTotalsService;
|
|
||||||
|
|
||||||
public AdminOperationsController(
|
|
||||||
FilamentVariantStockKgRepository filamentStockRepo,
|
|
||||||
FilamentVariantRepository filamentVariantRepo,
|
|
||||||
CustomQuoteRequestRepository customQuoteRequestRepo,
|
|
||||||
CustomQuoteRequestAttachmentRepository customQuoteRequestAttachmentRepo,
|
|
||||||
QuoteSessionRepository quoteSessionRepo,
|
|
||||||
QuoteLineItemRepository quoteLineItemRepo,
|
|
||||||
OrderRepository orderRepo,
|
|
||||||
PricingPolicyRepository pricingRepo,
|
|
||||||
QuoteSessionTotalsService quoteSessionTotalsService
|
|
||||||
) {
|
|
||||||
this.filamentStockRepo = filamentStockRepo;
|
|
||||||
this.filamentVariantRepo = filamentVariantRepo;
|
|
||||||
this.customQuoteRequestRepo = customQuoteRequestRepo;
|
|
||||||
this.customQuoteRequestAttachmentRepo = customQuoteRequestAttachmentRepo;
|
|
||||||
this.quoteSessionRepo = quoteSessionRepo;
|
|
||||||
this.quoteLineItemRepo = quoteLineItemRepo;
|
|
||||||
this.orderRepo = orderRepo;
|
|
||||||
this.pricingRepo = pricingRepo;
|
|
||||||
this.quoteSessionTotalsService = quoteSessionTotalsService;
|
|
||||||
}
|
|
||||||
|
|
||||||
@GetMapping("/filament-stock")
|
|
||||||
public ResponseEntity<List<AdminFilamentStockDto>> getFilamentStock() {
|
|
||||||
List<FilamentVariantStockKg> stocks = filamentStockRepo.findAll(Sort.by(Sort.Direction.ASC, "stockKg"));
|
|
||||||
Set<Long> variantIds = stocks.stream()
|
|
||||||
.map(FilamentVariantStockKg::getFilamentVariantId)
|
|
||||||
.collect(Collectors.toSet());
|
|
||||||
|
|
||||||
Map<Long, FilamentVariant> variantsById;
|
|
||||||
if (variantIds.isEmpty()) {
|
|
||||||
variantsById = Collections.emptyMap();
|
|
||||||
} else {
|
|
||||||
variantsById = filamentVariantRepo.findAllById(variantIds).stream()
|
|
||||||
.collect(Collectors.toMap(FilamentVariant::getId, variant -> variant));
|
|
||||||
}
|
|
||||||
|
|
||||||
List<AdminFilamentStockDto> response = stocks.stream().map(stock -> {
|
|
||||||
FilamentVariant variant = variantsById.get(stock.getFilamentVariantId());
|
|
||||||
AdminFilamentStockDto dto = new AdminFilamentStockDto();
|
|
||||||
dto.setFilamentVariantId(stock.getFilamentVariantId());
|
|
||||||
dto.setStockSpools(stock.getStockSpools());
|
|
||||||
dto.setSpoolNetKg(stock.getSpoolNetKg());
|
|
||||||
dto.setStockKg(stock.getStockKg());
|
|
||||||
BigDecimal grams = stock.getStockKg() != null
|
|
||||||
? stock.getStockKg().multiply(BigDecimal.valueOf(1000))
|
|
||||||
: BigDecimal.ZERO;
|
|
||||||
dto.setStockFilamentGrams(grams);
|
|
||||||
|
|
||||||
if (variant != null) {
|
|
||||||
dto.setMaterialCode(
|
|
||||||
variant.getFilamentMaterialType() != null
|
|
||||||
? variant.getFilamentMaterialType().getMaterialCode()
|
|
||||||
: "UNKNOWN"
|
|
||||||
);
|
|
||||||
dto.setVariantDisplayName(variant.getVariantDisplayName());
|
|
||||||
dto.setColorName(variant.getColorName());
|
|
||||||
dto.setActive(variant.getIsActive());
|
|
||||||
} else {
|
|
||||||
dto.setMaterialCode("UNKNOWN");
|
|
||||||
dto.setVariantDisplayName("Variant " + stock.getFilamentVariantId());
|
|
||||||
dto.setColorName("-");
|
|
||||||
dto.setActive(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
return dto;
|
|
||||||
}).toList();
|
|
||||||
|
|
||||||
return ResponseEntity.ok(response);
|
|
||||||
}
|
|
||||||
|
|
||||||
@GetMapping("/contact-requests")
|
|
||||||
public ResponseEntity<List<AdminContactRequestDto>> getContactRequests() {
|
|
||||||
List<AdminContactRequestDto> response = customQuoteRequestRepo.findAll(
|
|
||||||
Sort.by(Sort.Direction.DESC, "createdAt")
|
|
||||||
)
|
|
||||||
.stream()
|
|
||||||
.map(this::toContactRequestDto)
|
|
||||||
.toList();
|
|
||||||
|
|
||||||
return ResponseEntity.ok(response);
|
|
||||||
}
|
|
||||||
|
|
||||||
@GetMapping("/contact-requests/{requestId}")
|
|
||||||
public ResponseEntity<AdminContactRequestDetailDto> getContactRequestDetail(@PathVariable UUID requestId) {
|
|
||||||
CustomQuoteRequest request = customQuoteRequestRepo.findById(requestId)
|
|
||||||
.orElseThrow(() -> new ResponseStatusException(NOT_FOUND, "Contact request not found"));
|
|
||||||
|
|
||||||
List<AdminContactRequestAttachmentDto> attachments = customQuoteRequestAttachmentRepo
|
|
||||||
.findByRequest_IdOrderByCreatedAtAsc(requestId)
|
|
||||||
.stream()
|
|
||||||
.map(this::toContactRequestAttachmentDto)
|
|
||||||
.toList();
|
|
||||||
|
|
||||||
return ResponseEntity.ok(toContactRequestDetailDto(request, attachments));
|
|
||||||
}
|
|
||||||
|
|
||||||
@PatchMapping("/contact-requests/{requestId}/status")
|
|
||||||
@Transactional
|
|
||||||
public ResponseEntity<AdminContactRequestDetailDto> updateContactRequestStatus(
|
|
||||||
@PathVariable UUID requestId,
|
|
||||||
@RequestBody AdminUpdateContactRequestStatusRequest payload
|
|
||||||
) {
|
|
||||||
CustomQuoteRequest request = customQuoteRequestRepo.findById(requestId)
|
|
||||||
.orElseThrow(() -> new ResponseStatusException(NOT_FOUND, "Contact request not found"));
|
|
||||||
|
|
||||||
String requestedStatus = payload != null && payload.getStatus() != null
|
|
||||||
? payload.getStatus().trim().toUpperCase(Locale.ROOT)
|
|
||||||
: "";
|
|
||||||
|
|
||||||
if (!CONTACT_REQUEST_ALLOWED_STATUSES.contains(requestedStatus)) {
|
|
||||||
throw new ResponseStatusException(
|
|
||||||
BAD_REQUEST,
|
|
||||||
"Invalid status. Allowed: " + String.join(", ", CONTACT_REQUEST_ALLOWED_STATUSES)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
request.setStatus(requestedStatus);
|
|
||||||
request.setUpdatedAt(OffsetDateTime.now());
|
|
||||||
CustomQuoteRequest saved = customQuoteRequestRepo.save(request);
|
|
||||||
|
|
||||||
List<AdminContactRequestAttachmentDto> attachments = customQuoteRequestAttachmentRepo
|
|
||||||
.findByRequest_IdOrderByCreatedAtAsc(requestId)
|
|
||||||
.stream()
|
|
||||||
.map(this::toContactRequestAttachmentDto)
|
|
||||||
.toList();
|
|
||||||
|
|
||||||
return ResponseEntity.ok(toContactRequestDetailDto(saved, attachments));
|
|
||||||
}
|
|
||||||
|
|
||||||
@GetMapping("/contact-requests/{requestId}/attachments/{attachmentId}/file")
|
|
||||||
public ResponseEntity<Resource> downloadContactRequestAttachment(
|
|
||||||
@PathVariable UUID requestId,
|
|
||||||
@PathVariable UUID attachmentId
|
|
||||||
) {
|
|
||||||
CustomQuoteRequestAttachment attachment = customQuoteRequestAttachmentRepo.findById(attachmentId)
|
|
||||||
.orElseThrow(() -> new ResponseStatusException(NOT_FOUND, "Attachment not found"));
|
|
||||||
|
|
||||||
if (!attachment.getRequest().getId().equals(requestId)) {
|
|
||||||
throw new ResponseStatusException(NOT_FOUND, "Attachment not found for request");
|
|
||||||
}
|
|
||||||
|
|
||||||
String relativePath = attachment.getStoredRelativePath();
|
|
||||||
if (relativePath == null || relativePath.isBlank() || "PENDING".equals(relativePath)) {
|
|
||||||
throw new ResponseStatusException(NOT_FOUND, "Attachment file not available");
|
|
||||||
}
|
|
||||||
|
|
||||||
String expectedPrefix = "quote-requests/" + requestId + "/attachments/" + attachmentId + "/";
|
|
||||||
if (!relativePath.startsWith(expectedPrefix)) {
|
|
||||||
throw new ResponseStatusException(NOT_FOUND, "Attachment file not available");
|
|
||||||
}
|
|
||||||
|
|
||||||
Path filePath = CONTACT_ATTACHMENTS_ROOT.resolve(relativePath).normalize();
|
|
||||||
if (!filePath.startsWith(CONTACT_ATTACHMENTS_ROOT)) {
|
|
||||||
throw new ResponseStatusException(NOT_FOUND, "Attachment file not available");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!Files.exists(filePath)) {
|
|
||||||
throw new ResponseStatusException(NOT_FOUND, "Attachment file not available");
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
Resource resource = new UrlResource(filePath.toUri());
|
|
||||||
if (!resource.exists() || !resource.isReadable()) {
|
|
||||||
throw new ResponseStatusException(NOT_FOUND, "Attachment file not available");
|
|
||||||
}
|
|
||||||
|
|
||||||
MediaType mediaType = MediaType.APPLICATION_OCTET_STREAM;
|
|
||||||
String mimeType = attachment.getMimeType();
|
|
||||||
if (mimeType != null && !mimeType.isBlank()) {
|
|
||||||
try {
|
|
||||||
mediaType = MediaType.parseMediaType(mimeType);
|
|
||||||
} catch (Exception ignored) {
|
|
||||||
mediaType = MediaType.APPLICATION_OCTET_STREAM;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
String filename = attachment.getOriginalFilename();
|
|
||||||
if (filename == null || filename.isBlank()) {
|
|
||||||
filename = attachment.getStoredFilename() != null && !attachment.getStoredFilename().isBlank()
|
|
||||||
? attachment.getStoredFilename()
|
|
||||||
: "attachment-" + attachmentId;
|
|
||||||
}
|
|
||||||
|
|
||||||
return ResponseEntity.ok()
|
|
||||||
.contentType(mediaType)
|
|
||||||
.header(HttpHeaders.CONTENT_DISPOSITION, ContentDisposition.attachment()
|
|
||||||
.filename(filename, StandardCharsets.UTF_8)
|
|
||||||
.build()
|
|
||||||
.toString())
|
|
||||||
.body(resource);
|
|
||||||
} catch (MalformedURLException e) {
|
|
||||||
throw new ResponseStatusException(NOT_FOUND, "Attachment file not available");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@GetMapping("/sessions")
|
|
||||||
public ResponseEntity<List<AdminQuoteSessionDto>> getQuoteSessions() {
|
|
||||||
List<AdminQuoteSessionDto> response = quoteSessionRepo.findAll(
|
|
||||||
Sort.by(Sort.Direction.DESC, "createdAt")
|
|
||||||
)
|
|
||||||
.stream()
|
|
||||||
.map(this::toQuoteSessionDto)
|
|
||||||
.toList();
|
|
||||||
|
|
||||||
return ResponseEntity.ok(response);
|
|
||||||
}
|
|
||||||
|
|
||||||
@GetMapping("/cad-invoices")
|
|
||||||
public ResponseEntity<List<AdminCadInvoiceDto>> getCadInvoices() {
|
|
||||||
List<AdminCadInvoiceDto> response = quoteSessionRepo.findByStatusInOrderByCreatedAtDesc(List.of("CAD_ACTIVE", "CONVERTED"))
|
|
||||||
.stream()
|
|
||||||
.filter(this::isCadSessionRecord)
|
|
||||||
.map(this::toCadInvoiceDto)
|
|
||||||
.toList();
|
|
||||||
return ResponseEntity.ok(response);
|
|
||||||
}
|
|
||||||
|
|
||||||
@PostMapping("/cad-invoices")
|
|
||||||
@Transactional
|
|
||||||
public ResponseEntity<AdminCadInvoiceDto> createOrUpdateCadInvoice(
|
|
||||||
@RequestBody AdminCadInvoiceCreateRequest payload
|
|
||||||
) {
|
|
||||||
if (payload == null || payload.getCadHours() == null) {
|
|
||||||
throw new ResponseStatusException(BAD_REQUEST, "cadHours is required");
|
|
||||||
}
|
|
||||||
|
|
||||||
BigDecimal cadHours = payload.getCadHours().setScale(2, RoundingMode.HALF_UP);
|
|
||||||
if (cadHours.compareTo(BigDecimal.ZERO) <= 0) {
|
|
||||||
throw new ResponseStatusException(BAD_REQUEST, "cadHours must be > 0");
|
|
||||||
}
|
|
||||||
|
|
||||||
BigDecimal cadRate = payload.getCadHourlyRateChf();
|
|
||||||
if (cadRate == null || cadRate.compareTo(BigDecimal.ZERO) <= 0) {
|
|
||||||
var policy = pricingRepo.findFirstByIsActiveTrueOrderByValidFromDesc();
|
|
||||||
cadRate = policy != null && policy.getCadCostChfPerHour() != null
|
|
||||||
? policy.getCadCostChfPerHour()
|
|
||||||
: BigDecimal.ZERO;
|
|
||||||
}
|
|
||||||
cadRate = cadRate.setScale(2, RoundingMode.HALF_UP);
|
|
||||||
|
|
||||||
QuoteSession session;
|
|
||||||
if (payload.getSessionId() != null) {
|
|
||||||
session = quoteSessionRepo.findById(payload.getSessionId())
|
|
||||||
.orElseThrow(() -> new ResponseStatusException(NOT_FOUND, "Session not found"));
|
|
||||||
} else {
|
|
||||||
session = new QuoteSession();
|
|
||||||
session.setStatus("CAD_ACTIVE");
|
|
||||||
session.setPricingVersion("v1");
|
|
||||||
session.setMaterialCode("PLA");
|
|
||||||
session.setNozzleDiameterMm(BigDecimal.valueOf(0.4));
|
|
||||||
session.setLayerHeightMm(BigDecimal.valueOf(0.2));
|
|
||||||
session.setInfillPattern("grid");
|
|
||||||
session.setInfillPercent(20);
|
|
||||||
session.setSupportsEnabled(false);
|
|
||||||
session.setSetupCostChf(BigDecimal.ZERO);
|
|
||||||
session.setCreatedAt(OffsetDateTime.now());
|
|
||||||
session.setExpiresAt(OffsetDateTime.now().plusDays(30));
|
|
||||||
}
|
|
||||||
|
|
||||||
if ("CONVERTED".equals(session.getStatus())) {
|
|
||||||
throw new ResponseStatusException(CONFLICT, "Session already converted to order");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (payload.getSourceRequestId() != null) {
|
|
||||||
if (!customQuoteRequestRepo.existsById(payload.getSourceRequestId())) {
|
|
||||||
throw new ResponseStatusException(NOT_FOUND, "Source request not found");
|
|
||||||
}
|
|
||||||
session.setSourceRequestId(payload.getSourceRequestId());
|
|
||||||
} else {
|
|
||||||
session.setSourceRequestId(null);
|
|
||||||
}
|
|
||||||
|
|
||||||
session.setStatus("CAD_ACTIVE");
|
|
||||||
session.setCadHours(cadHours);
|
|
||||||
session.setCadHourlyRateChf(cadRate);
|
|
||||||
if (payload.getNotes() != null) {
|
|
||||||
String trimmedNotes = payload.getNotes().trim();
|
|
||||||
session.setNotes(trimmedNotes.isEmpty() ? null : trimmedNotes);
|
|
||||||
}
|
|
||||||
|
|
||||||
QuoteSession saved = quoteSessionRepo.save(session);
|
|
||||||
return ResponseEntity.ok(toCadInvoiceDto(saved));
|
|
||||||
}
|
|
||||||
|
|
||||||
@DeleteMapping("/sessions/{sessionId}")
|
|
||||||
@Transactional
|
|
||||||
public ResponseEntity<Void> deleteQuoteSession(@PathVariable UUID sessionId) {
|
|
||||||
QuoteSession session = quoteSessionRepo.findById(sessionId)
|
|
||||||
.orElseThrow(() -> new ResponseStatusException(NOT_FOUND, "Session not found"));
|
|
||||||
|
|
||||||
if (orderRepo.existsBySourceQuoteSession_Id(sessionId)) {
|
|
||||||
throw new ResponseStatusException(CONFLICT, "Cannot delete session already linked to an order");
|
|
||||||
}
|
|
||||||
|
|
||||||
deleteSessionFiles(sessionId);
|
|
||||||
quoteSessionRepo.delete(session);
|
|
||||||
return ResponseEntity.noContent().build();
|
|
||||||
}
|
|
||||||
|
|
||||||
private AdminContactRequestDto toContactRequestDto(CustomQuoteRequest request) {
|
|
||||||
AdminContactRequestDto dto = new AdminContactRequestDto();
|
|
||||||
dto.setId(request.getId());
|
|
||||||
dto.setRequestType(request.getRequestType());
|
|
||||||
dto.setCustomerType(request.getCustomerType());
|
|
||||||
dto.setEmail(request.getEmail());
|
|
||||||
dto.setPhone(request.getPhone());
|
|
||||||
dto.setName(request.getName());
|
|
||||||
dto.setCompanyName(request.getCompanyName());
|
|
||||||
dto.setStatus(request.getStatus());
|
|
||||||
dto.setCreatedAt(request.getCreatedAt());
|
|
||||||
return dto;
|
|
||||||
}
|
|
||||||
|
|
||||||
private AdminContactRequestAttachmentDto toContactRequestAttachmentDto(CustomQuoteRequestAttachment attachment) {
|
|
||||||
AdminContactRequestAttachmentDto dto = new AdminContactRequestAttachmentDto();
|
|
||||||
dto.setId(attachment.getId());
|
|
||||||
dto.setOriginalFilename(attachment.getOriginalFilename());
|
|
||||||
dto.setMimeType(attachment.getMimeType());
|
|
||||||
dto.setFileSizeBytes(attachment.getFileSizeBytes());
|
|
||||||
dto.setCreatedAt(attachment.getCreatedAt());
|
|
||||||
return dto;
|
|
||||||
}
|
|
||||||
|
|
||||||
private AdminContactRequestDetailDto toContactRequestDetailDto(
|
|
||||||
CustomQuoteRequest request,
|
|
||||||
List<AdminContactRequestAttachmentDto> attachments
|
|
||||||
) {
|
|
||||||
AdminContactRequestDetailDto dto = new AdminContactRequestDetailDto();
|
|
||||||
dto.setId(request.getId());
|
|
||||||
dto.setRequestType(request.getRequestType());
|
|
||||||
dto.setCustomerType(request.getCustomerType());
|
|
||||||
dto.setEmail(request.getEmail());
|
|
||||||
dto.setPhone(request.getPhone());
|
|
||||||
dto.setName(request.getName());
|
|
||||||
dto.setCompanyName(request.getCompanyName());
|
|
||||||
dto.setContactPerson(request.getContactPerson());
|
|
||||||
dto.setMessage(request.getMessage());
|
|
||||||
dto.setStatus(request.getStatus());
|
|
||||||
dto.setCreatedAt(request.getCreatedAt());
|
|
||||||
dto.setUpdatedAt(request.getUpdatedAt());
|
|
||||||
dto.setAttachments(attachments);
|
|
||||||
return dto;
|
|
||||||
}
|
|
||||||
|
|
||||||
private AdminQuoteSessionDto toQuoteSessionDto(QuoteSession session) {
|
|
||||||
AdminQuoteSessionDto dto = new AdminQuoteSessionDto();
|
|
||||||
dto.setId(session.getId());
|
|
||||||
dto.setStatus(session.getStatus());
|
|
||||||
dto.setMaterialCode(session.getMaterialCode());
|
|
||||||
dto.setCreatedAt(session.getCreatedAt());
|
|
||||||
dto.setExpiresAt(session.getExpiresAt());
|
|
||||||
dto.setConvertedOrderId(session.getConvertedOrderId());
|
|
||||||
dto.setSourceRequestId(session.getSourceRequestId());
|
|
||||||
dto.setCadHours(session.getCadHours());
|
|
||||||
dto.setCadHourlyRateChf(session.getCadHourlyRateChf());
|
|
||||||
dto.setCadTotalChf(quoteSessionTotalsService.calculateCadTotal(session));
|
|
||||||
return dto;
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean isCadSessionRecord(QuoteSession session) {
|
|
||||||
if ("CAD_ACTIVE".equals(session.getStatus())) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
if (!"CONVERTED".equals(session.getStatus())) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
BigDecimal cadHours = session.getCadHours() != null ? session.getCadHours() : BigDecimal.ZERO;
|
|
||||||
return cadHours.compareTo(BigDecimal.ZERO) > 0 || session.getSourceRequestId() != null;
|
|
||||||
}
|
|
||||||
|
|
||||||
private AdminCadInvoiceDto toCadInvoiceDto(QuoteSession session) {
|
|
||||||
List<QuoteLineItem> items = quoteLineItemRepo.findByQuoteSessionId(session.getId());
|
|
||||||
QuoteSessionTotalsService.QuoteSessionTotals totals = quoteSessionTotalsService.compute(session, items);
|
|
||||||
|
|
||||||
AdminCadInvoiceDto dto = new AdminCadInvoiceDto();
|
|
||||||
dto.setSessionId(session.getId());
|
|
||||||
dto.setSessionStatus(session.getStatus());
|
|
||||||
dto.setSourceRequestId(session.getSourceRequestId());
|
|
||||||
dto.setCadHours(session.getCadHours() != null ? session.getCadHours() : BigDecimal.ZERO);
|
|
||||||
dto.setCadHourlyRateChf(session.getCadHourlyRateChf() != null ? session.getCadHourlyRateChf() : BigDecimal.ZERO);
|
|
||||||
dto.setCadTotalChf(totals.cadTotalChf());
|
|
||||||
dto.setPrintItemsTotalChf(totals.printItemsTotalChf());
|
|
||||||
dto.setSetupCostChf(totals.setupCostChf());
|
|
||||||
dto.setShippingCostChf(totals.shippingCostChf());
|
|
||||||
dto.setGrandTotalChf(totals.grandTotalChf());
|
|
||||||
dto.setConvertedOrderId(session.getConvertedOrderId());
|
|
||||||
dto.setCheckoutPath("/checkout/cad?session=" + session.getId());
|
|
||||||
dto.setNotes(session.getNotes());
|
|
||||||
dto.setCreatedAt(session.getCreatedAt());
|
|
||||||
|
|
||||||
if (session.getConvertedOrderId() != null) {
|
|
||||||
Order order = orderRepo.findById(session.getConvertedOrderId()).orElse(null);
|
|
||||||
dto.setConvertedOrderStatus(order != null ? order.getStatus() : null);
|
|
||||||
}
|
|
||||||
return dto;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void deleteSessionFiles(UUID sessionId) {
|
|
||||||
Path sessionDir = Paths.get("storage_quotes", sessionId.toString());
|
|
||||||
if (!Files.exists(sessionDir)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
try (Stream<Path> walk = Files.walk(sessionDir)) {
|
|
||||||
walk.sorted(Comparator.reverseOrder()).forEach(path -> {
|
|
||||||
try {
|
|
||||||
Files.deleteIfExists(path);
|
|
||||||
} catch (IOException e) {
|
|
||||||
throw new UncheckedIOException(e);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
} catch (IOException | UncheckedIOException e) {
|
|
||||||
logger.error("Failed to delete files for session {}", sessionId, e);
|
|
||||||
throw new ResponseStatusException(INTERNAL_SERVER_ERROR, "Unable to delete session files");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,443 +0,0 @@
|
|||||||
package com.printcalculator.controller.admin;
|
|
||||||
|
|
||||||
import com.printcalculator.dto.AddressDto;
|
|
||||||
import com.printcalculator.dto.AdminOrderStatusUpdateRequest;
|
|
||||||
import com.printcalculator.dto.OrderDto;
|
|
||||||
import com.printcalculator.dto.OrderItemDto;
|
|
||||||
import com.printcalculator.entity.*;
|
|
||||||
import com.printcalculator.event.OrderShippedEvent;
|
|
||||||
import com.printcalculator.repository.OrderItemRepository;
|
|
||||||
import com.printcalculator.repository.OrderRepository;
|
|
||||||
import com.printcalculator.repository.PaymentRepository;
|
|
||||||
import com.printcalculator.repository.QuoteLineItemRepository;
|
|
||||||
import com.printcalculator.service.payment.InvoicePdfRenderingService;
|
|
||||||
import com.printcalculator.service.payment.PaymentService;
|
|
||||||
import com.printcalculator.service.payment.QrBillService;
|
|
||||||
import com.printcalculator.service.storage.StorageService;
|
|
||||||
import org.springframework.context.ApplicationEventPublisher;
|
|
||||||
import org.springframework.core.io.Resource;
|
|
||||||
import org.springframework.core.io.UrlResource;
|
|
||||||
import org.springframework.http.ContentDisposition;
|
|
||||||
import org.springframework.http.HttpHeaders;
|
|
||||||
import org.springframework.http.MediaType;
|
|
||||||
import org.springframework.http.ResponseEntity;
|
|
||||||
import org.springframework.transaction.annotation.Transactional;
|
|
||||||
import org.springframework.web.bind.annotation.GetMapping;
|
|
||||||
import org.springframework.web.bind.annotation.PathVariable;
|
|
||||||
import org.springframework.web.bind.annotation.PostMapping;
|
|
||||||
import org.springframework.web.bind.annotation.RequestBody;
|
|
||||||
import org.springframework.web.bind.annotation.RequestMapping;
|
|
||||||
import org.springframework.web.bind.annotation.RestController;
|
|
||||||
import org.springframework.web.server.ResponseStatusException;
|
|
||||||
|
|
||||||
import java.nio.charset.StandardCharsets;
|
|
||||||
import java.nio.file.Files;
|
|
||||||
import java.nio.file.InvalidPathException;
|
|
||||||
import java.nio.file.Path;
|
|
||||||
import java.nio.file.Paths;
|
|
||||||
import java.util.Comparator;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Locale;
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.UUID;
|
|
||||||
|
|
||||||
import static org.springframework.http.HttpStatus.BAD_REQUEST;
|
|
||||||
import static org.springframework.http.HttpStatus.NOT_FOUND;
|
|
||||||
|
|
||||||
@RestController
|
|
||||||
@RequestMapping("/api/admin/orders")
|
|
||||||
@Transactional(readOnly = true)
|
|
||||||
public class AdminOrderController {
|
|
||||||
private static final Path QUOTE_STORAGE_ROOT = Paths.get("storage_quotes").toAbsolutePath().normalize();
|
|
||||||
private static final List<String> ALLOWED_ORDER_STATUSES = List.of(
|
|
||||||
"PENDING_PAYMENT",
|
|
||||||
"PAID",
|
|
||||||
"IN_PRODUCTION",
|
|
||||||
"SHIPPED",
|
|
||||||
"COMPLETED",
|
|
||||||
"CANCELLED"
|
|
||||||
);
|
|
||||||
|
|
||||||
private final OrderRepository orderRepo;
|
|
||||||
private final OrderItemRepository orderItemRepo;
|
|
||||||
private final PaymentRepository paymentRepo;
|
|
||||||
private final QuoteLineItemRepository quoteLineItemRepo;
|
|
||||||
private final PaymentService paymentService;
|
|
||||||
private final StorageService storageService;
|
|
||||||
private final InvoicePdfRenderingService invoiceService;
|
|
||||||
private final QrBillService qrBillService;
|
|
||||||
private final ApplicationEventPublisher eventPublisher;
|
|
||||||
|
|
||||||
public AdminOrderController(
|
|
||||||
OrderRepository orderRepo,
|
|
||||||
OrderItemRepository orderItemRepo,
|
|
||||||
PaymentRepository paymentRepo,
|
|
||||||
QuoteLineItemRepository quoteLineItemRepo,
|
|
||||||
PaymentService paymentService,
|
|
||||||
StorageService storageService,
|
|
||||||
InvoicePdfRenderingService invoiceService,
|
|
||||||
QrBillService qrBillService,
|
|
||||||
ApplicationEventPublisher eventPublisher
|
|
||||||
) {
|
|
||||||
this.orderRepo = orderRepo;
|
|
||||||
this.orderItemRepo = orderItemRepo;
|
|
||||||
this.paymentRepo = paymentRepo;
|
|
||||||
this.quoteLineItemRepo = quoteLineItemRepo;
|
|
||||||
this.paymentService = paymentService;
|
|
||||||
this.storageService = storageService;
|
|
||||||
this.invoiceService = invoiceService;
|
|
||||||
this.qrBillService = qrBillService;
|
|
||||||
this.eventPublisher = eventPublisher;
|
|
||||||
}
|
|
||||||
|
|
||||||
@GetMapping
|
|
||||||
public ResponseEntity<List<OrderDto>> listOrders() {
|
|
||||||
List<OrderDto> response = orderRepo.findAllByOrderByCreatedAtDesc()
|
|
||||||
.stream()
|
|
||||||
.map(this::toOrderDto)
|
|
||||||
.toList();
|
|
||||||
return ResponseEntity.ok(response);
|
|
||||||
}
|
|
||||||
|
|
||||||
@GetMapping("/{orderId}")
|
|
||||||
public ResponseEntity<OrderDto> getOrder(@PathVariable UUID orderId) {
|
|
||||||
return ResponseEntity.ok(toOrderDto(getOrderOrThrow(orderId)));
|
|
||||||
}
|
|
||||||
|
|
||||||
@PostMapping("/{orderId}/payments/confirm")
|
|
||||||
@Transactional
|
|
||||||
public ResponseEntity<OrderDto> updatePaymentMethod(
|
|
||||||
@PathVariable UUID orderId,
|
|
||||||
@RequestBody(required = false) Map<String, String> payload
|
|
||||||
) {
|
|
||||||
getOrderOrThrow(orderId);
|
|
||||||
String method = payload != null ? payload.get("method") : null;
|
|
||||||
if (method == null || method.isBlank()) {
|
|
||||||
throw new ResponseStatusException(BAD_REQUEST, "Payment method is required");
|
|
||||||
}
|
|
||||||
paymentService.updatePaymentMethod(orderId, method);
|
|
||||||
return ResponseEntity.ok(toOrderDto(getOrderOrThrow(orderId)));
|
|
||||||
}
|
|
||||||
|
|
||||||
@PostMapping("/{orderId}/status")
|
|
||||||
@Transactional
|
|
||||||
public ResponseEntity<OrderDto> updateOrderStatus(
|
|
||||||
@PathVariable UUID orderId,
|
|
||||||
@RequestBody AdminOrderStatusUpdateRequest payload
|
|
||||||
) {
|
|
||||||
if (payload == null || payload.getStatus() == null || payload.getStatus().isBlank()) {
|
|
||||||
throw new ResponseStatusException(org.springframework.http.HttpStatus.BAD_REQUEST, "Status is required");
|
|
||||||
}
|
|
||||||
|
|
||||||
Order order = getOrderOrThrow(orderId);
|
|
||||||
String normalizedStatus = payload.getStatus().trim().toUpperCase(Locale.ROOT);
|
|
||||||
if (!ALLOWED_ORDER_STATUSES.contains(normalizedStatus)) {
|
|
||||||
throw new ResponseStatusException(
|
|
||||||
BAD_REQUEST,
|
|
||||||
"Invalid order status. Allowed values: " + String.join(", ", ALLOWED_ORDER_STATUSES)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
String previousStatus = order.getStatus();
|
|
||||||
order.setStatus(normalizedStatus);
|
|
||||||
Order savedOrder = orderRepo.save(order);
|
|
||||||
|
|
||||||
// Notify customer only on transition to SHIPPED.
|
|
||||||
if (!"SHIPPED".equals(previousStatus) && "SHIPPED".equals(normalizedStatus)) {
|
|
||||||
eventPublisher.publishEvent(new OrderShippedEvent(this, savedOrder));
|
|
||||||
}
|
|
||||||
|
|
||||||
return ResponseEntity.ok(toOrderDto(savedOrder));
|
|
||||||
}
|
|
||||||
|
|
||||||
@GetMapping("/{orderId}/items/{orderItemId}/file")
|
|
||||||
public ResponseEntity<Resource> downloadOrderItemFile(
|
|
||||||
@PathVariable UUID orderId,
|
|
||||||
@PathVariable UUID orderItemId
|
|
||||||
) {
|
|
||||||
OrderItem item = orderItemRepo.findById(orderItemId)
|
|
||||||
.orElseThrow(() -> new ResponseStatusException(NOT_FOUND, "Order item not found"));
|
|
||||||
|
|
||||||
if (!item.getOrder().getId().equals(orderId)) {
|
|
||||||
throw new ResponseStatusException(NOT_FOUND, "Order item not found for order");
|
|
||||||
}
|
|
||||||
|
|
||||||
String relativePath = item.getStoredRelativePath();
|
|
||||||
if (relativePath == null || relativePath.isBlank() || "PENDING".equals(relativePath)) {
|
|
||||||
throw new ResponseStatusException(NOT_FOUND, "File not available");
|
|
||||||
}
|
|
||||||
Path safeRelativePath = resolveOrderItemRelativePath(relativePath, orderId, orderItemId);
|
|
||||||
if (safeRelativePath == null) {
|
|
||||||
throw new ResponseStatusException(NOT_FOUND, "File not available");
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
Resource resource = storageService.loadAsResource(safeRelativePath);
|
|
||||||
MediaType contentType = MediaType.APPLICATION_OCTET_STREAM;
|
|
||||||
if (item.getMimeType() != null && !item.getMimeType().isBlank()) {
|
|
||||||
try {
|
|
||||||
contentType = MediaType.parseMediaType(item.getMimeType());
|
|
||||||
} catch (Exception ignored) {
|
|
||||||
contentType = MediaType.APPLICATION_OCTET_STREAM;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
String filename = item.getOriginalFilename() != null && !item.getOriginalFilename().isBlank()
|
|
||||||
? item.getOriginalFilename()
|
|
||||||
: "order-item-" + orderItemId;
|
|
||||||
|
|
||||||
return ResponseEntity.ok()
|
|
||||||
.contentType(contentType)
|
|
||||||
.header(HttpHeaders.CONTENT_DISPOSITION, ContentDisposition.attachment()
|
|
||||||
.filename(filename, StandardCharsets.UTF_8)
|
|
||||||
.build()
|
|
||||||
.toString())
|
|
||||||
.body(resource);
|
|
||||||
} catch (Exception e) {
|
|
||||||
throw new ResponseStatusException(NOT_FOUND, "File not available");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@GetMapping("/{orderId}/documents/confirmation")
|
|
||||||
public ResponseEntity<byte[]> downloadOrderConfirmation(@PathVariable UUID orderId) {
|
|
||||||
return generateDocument(getOrderOrThrow(orderId), true);
|
|
||||||
}
|
|
||||||
|
|
||||||
@GetMapping("/{orderId}/documents/invoice")
|
|
||||||
public ResponseEntity<byte[]> downloadOrderInvoice(@PathVariable UUID orderId) {
|
|
||||||
return generateDocument(getOrderOrThrow(orderId), false);
|
|
||||||
}
|
|
||||||
|
|
||||||
private Order getOrderOrThrow(UUID orderId) {
|
|
||||||
return orderRepo.findById(orderId)
|
|
||||||
.orElseThrow(() -> new ResponseStatusException(NOT_FOUND, "Order not found"));
|
|
||||||
}
|
|
||||||
|
|
||||||
private OrderDto toOrderDto(Order order) {
|
|
||||||
List<OrderItem> items = orderItemRepo.findByOrder_Id(order.getId());
|
|
||||||
OrderDto dto = new OrderDto();
|
|
||||||
dto.setId(order.getId());
|
|
||||||
dto.setOrderNumber(getDisplayOrderNumber(order));
|
|
||||||
dto.setStatus(order.getStatus());
|
|
||||||
|
|
||||||
paymentRepo.findByOrder_Id(order.getId()).ifPresent(p -> {
|
|
||||||
dto.setPaymentStatus(p.getStatus());
|
|
||||||
dto.setPaymentMethod(p.getMethod());
|
|
||||||
});
|
|
||||||
|
|
||||||
dto.setCustomerEmail(order.getCustomerEmail());
|
|
||||||
dto.setCustomerPhone(order.getCustomerPhone());
|
|
||||||
dto.setPreferredLanguage(order.getPreferredLanguage());
|
|
||||||
dto.setBillingCustomerType(order.getBillingCustomerType());
|
|
||||||
dto.setCurrency(order.getCurrency());
|
|
||||||
dto.setSetupCostChf(order.getSetupCostChf());
|
|
||||||
dto.setShippingCostChf(order.getShippingCostChf());
|
|
||||||
dto.setDiscountChf(order.getDiscountChf());
|
|
||||||
dto.setSubtotalChf(order.getSubtotalChf());
|
|
||||||
dto.setIsCadOrder(order.getIsCadOrder());
|
|
||||||
dto.setSourceRequestId(order.getSourceRequestId());
|
|
||||||
dto.setCadHours(order.getCadHours());
|
|
||||||
dto.setCadHourlyRateChf(order.getCadHourlyRateChf());
|
|
||||||
dto.setCadTotalChf(order.getCadTotalChf());
|
|
||||||
dto.setTotalChf(order.getTotalChf());
|
|
||||||
dto.setCreatedAt(order.getCreatedAt());
|
|
||||||
dto.setShippingSameAsBilling(order.getShippingSameAsBilling());
|
|
||||||
QuoteSession sourceSession = order.getSourceQuoteSession();
|
|
||||||
if (sourceSession != null) {
|
|
||||||
dto.setPrintMaterialCode(sourceSession.getMaterialCode());
|
|
||||||
dto.setPrintNozzleDiameterMm(sourceSession.getNozzleDiameterMm());
|
|
||||||
dto.setPrintLayerHeightMm(sourceSession.getLayerHeightMm());
|
|
||||||
dto.setPrintInfillPattern(sourceSession.getInfillPattern());
|
|
||||||
dto.setPrintInfillPercent(sourceSession.getInfillPercent());
|
|
||||||
dto.setPrintSupportsEnabled(sourceSession.getSupportsEnabled());
|
|
||||||
}
|
|
||||||
|
|
||||||
AddressDto billing = new AddressDto();
|
|
||||||
billing.setFirstName(order.getBillingFirstName());
|
|
||||||
billing.setLastName(order.getBillingLastName());
|
|
||||||
billing.setCompanyName(order.getBillingCompanyName());
|
|
||||||
billing.setContactPerson(order.getBillingContactPerson());
|
|
||||||
billing.setAddressLine1(order.getBillingAddressLine1());
|
|
||||||
billing.setAddressLine2(order.getBillingAddressLine2());
|
|
||||||
billing.setZip(order.getBillingZip());
|
|
||||||
billing.setCity(order.getBillingCity());
|
|
||||||
billing.setCountryCode(order.getBillingCountryCode());
|
|
||||||
dto.setBillingAddress(billing);
|
|
||||||
|
|
||||||
if (!Boolean.TRUE.equals(order.getShippingSameAsBilling())) {
|
|
||||||
AddressDto shipping = new AddressDto();
|
|
||||||
shipping.setFirstName(order.getShippingFirstName());
|
|
||||||
shipping.setLastName(order.getShippingLastName());
|
|
||||||
shipping.setCompanyName(order.getShippingCompanyName());
|
|
||||||
shipping.setContactPerson(order.getShippingContactPerson());
|
|
||||||
shipping.setAddressLine1(order.getShippingAddressLine1());
|
|
||||||
shipping.setAddressLine2(order.getShippingAddressLine2());
|
|
||||||
shipping.setZip(order.getShippingZip());
|
|
||||||
shipping.setCity(order.getShippingCity());
|
|
||||||
shipping.setCountryCode(order.getShippingCountryCode());
|
|
||||||
dto.setShippingAddress(shipping);
|
|
||||||
}
|
|
||||||
|
|
||||||
List<OrderItemDto> itemDtos = items.stream().map(i -> {
|
|
||||||
OrderItemDto idto = new OrderItemDto();
|
|
||||||
idto.setId(i.getId());
|
|
||||||
idto.setOriginalFilename(i.getOriginalFilename());
|
|
||||||
idto.setMaterialCode(i.getMaterialCode());
|
|
||||||
idto.setColorCode(i.getColorCode());
|
|
||||||
idto.setQuantity(i.getQuantity());
|
|
||||||
idto.setPrintTimeSeconds(i.getPrintTimeSeconds());
|
|
||||||
idto.setMaterialGrams(i.getMaterialGrams());
|
|
||||||
idto.setUnitPriceChf(i.getUnitPriceChf());
|
|
||||||
idto.setLineTotalChf(i.getLineTotalChf());
|
|
||||||
return idto;
|
|
||||||
}).toList();
|
|
||||||
dto.setItems(itemDtos);
|
|
||||||
|
|
||||||
return dto;
|
|
||||||
}
|
|
||||||
|
|
||||||
private String getDisplayOrderNumber(Order order) {
|
|
||||||
String orderNumber = order.getOrderNumber();
|
|
||||||
if (orderNumber != null && !orderNumber.isBlank()) {
|
|
||||||
return orderNumber;
|
|
||||||
}
|
|
||||||
return order.getId() != null ? order.getId().toString() : "unknown";
|
|
||||||
}
|
|
||||||
|
|
||||||
private ResponseEntity<byte[]> generateDocument(Order order, boolean isConfirmation) {
|
|
||||||
String displayOrderNumber = getDisplayOrderNumber(order);
|
|
||||||
if (isConfirmation) {
|
|
||||||
Path relativePath = buildConfirmationPdfRelativePath(order.getId(), displayOrderNumber);
|
|
||||||
try {
|
|
||||||
byte[] existingPdf = storageService.loadAsResource(relativePath).getInputStream().readAllBytes();
|
|
||||||
return ResponseEntity.ok()
|
|
||||||
.header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=\"confirmation-" + displayOrderNumber + ".pdf\"")
|
|
||||||
.contentType(MediaType.APPLICATION_PDF)
|
|
||||||
.body(existingPdf);
|
|
||||||
} catch (Exception ignored) {
|
|
||||||
// fallback to generated confirmation document
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
List<OrderItem> items = orderItemRepo.findByOrder_Id(order.getId());
|
|
||||||
Payment payment = paymentRepo.findByOrder_Id(order.getId()).orElse(null);
|
|
||||||
byte[] pdf = invoiceService.generateDocumentPdf(order, items, isConfirmation, qrBillService, payment);
|
|
||||||
|
|
||||||
String prefix = isConfirmation ? "confirmation-" : "invoice-";
|
|
||||||
return ResponseEntity.ok()
|
|
||||||
.header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=\"" + prefix + displayOrderNumber + ".pdf\"")
|
|
||||||
.contentType(MediaType.APPLICATION_PDF)
|
|
||||||
.body(pdf);
|
|
||||||
}
|
|
||||||
|
|
||||||
private Path resolveOrderItemRelativePath(String storedRelativePath, UUID orderId, UUID orderItemId) {
|
|
||||||
try {
|
|
||||||
Path candidate = Path.of(storedRelativePath).normalize();
|
|
||||||
if (candidate.isAbsolute()) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
Path expectedPrefix = Path.of("orders", orderId.toString(), "3d-files", orderItemId.toString());
|
|
||||||
if (!candidate.startsWith(expectedPrefix)) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
return candidate;
|
|
||||||
} catch (InvalidPathException e) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private Resource loadOrderItemResourceWithRecovery(OrderItem item, Path safeRelativePath) {
|
|
||||||
try {
|
|
||||||
return storageService.loadAsResource(safeRelativePath);
|
|
||||||
} catch (Exception primaryFailure) {
|
|
||||||
Path sourceQuotePath = resolveFallbackQuoteItemPath(item);
|
|
||||||
if (sourceQuotePath == null) {
|
|
||||||
throw new ResponseStatusException(NOT_FOUND, "File not available");
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
storageService.store(sourceQuotePath, safeRelativePath);
|
|
||||||
return storageService.loadAsResource(safeRelativePath);
|
|
||||||
} catch (Exception copyFailure) {
|
|
||||||
try {
|
|
||||||
Resource quoteResource = new UrlResource(sourceQuotePath.toUri());
|
|
||||||
if (quoteResource.exists() || quoteResource.isReadable()) {
|
|
||||||
return quoteResource;
|
|
||||||
}
|
|
||||||
} catch (Exception ignored) {
|
|
||||||
// fall through to 404
|
|
||||||
}
|
|
||||||
throw new ResponseStatusException(NOT_FOUND, "File not available");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private Path resolveFallbackQuoteItemPath(OrderItem orderItem) {
|
|
||||||
Order order = orderItem.getOrder();
|
|
||||||
QuoteSession sourceSession = order != null ? order.getSourceQuoteSession() : null;
|
|
||||||
UUID sourceSessionId = sourceSession != null ? sourceSession.getId() : null;
|
|
||||||
if (sourceSessionId == null) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
String targetFilename = normalizeFilename(orderItem.getOriginalFilename());
|
|
||||||
if (targetFilename == null) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return quoteLineItemRepo.findByQuoteSessionId(sourceSessionId).stream()
|
|
||||||
.filter(q -> targetFilename.equals(normalizeFilename(q.getOriginalFilename())))
|
|
||||||
.sorted(Comparator.comparingInt((QuoteLineItem q) -> scoreQuoteMatch(orderItem, q)).reversed())
|
|
||||||
.map(q -> resolveStoredQuotePath(q.getStoredPath(), sourceSessionId))
|
|
||||||
.filter(path -> path != null && Files.exists(path))
|
|
||||||
.findFirst()
|
|
||||||
.orElse(null);
|
|
||||||
}
|
|
||||||
|
|
||||||
private int scoreQuoteMatch(OrderItem orderItem, QuoteLineItem quoteItem) {
|
|
||||||
int score = 0;
|
|
||||||
if (orderItem.getQuantity() != null && orderItem.getQuantity().equals(quoteItem.getQuantity())) {
|
|
||||||
score += 4;
|
|
||||||
}
|
|
||||||
if (orderItem.getPrintTimeSeconds() != null && orderItem.getPrintTimeSeconds().equals(quoteItem.getPrintTimeSeconds())) {
|
|
||||||
score += 3;
|
|
||||||
}
|
|
||||||
if (orderItem.getMaterialCode() != null
|
|
||||||
&& quoteItem.getMaterialCode() != null
|
|
||||||
&& orderItem.getMaterialCode().equalsIgnoreCase(quoteItem.getMaterialCode())) {
|
|
||||||
score += 3;
|
|
||||||
}
|
|
||||||
if (orderItem.getMaterialGrams() != null
|
|
||||||
&& quoteItem.getMaterialGrams() != null
|
|
||||||
&& orderItem.getMaterialGrams().compareTo(quoteItem.getMaterialGrams()) == 0) {
|
|
||||||
score += 2;
|
|
||||||
}
|
|
||||||
return score;
|
|
||||||
}
|
|
||||||
|
|
||||||
private String normalizeFilename(String filename) {
|
|
||||||
if (filename == null || filename.isBlank()) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
return filename.trim();
|
|
||||||
}
|
|
||||||
|
|
||||||
private Path resolveStoredQuotePath(String storedPath, UUID expectedSessionId) {
|
|
||||||
if (storedPath == null || storedPath.isBlank()) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
Path raw = Path.of(storedPath).normalize();
|
|
||||||
Path resolved = raw.isAbsolute() ? raw : QUOTE_STORAGE_ROOT.resolve(raw).normalize();
|
|
||||||
Path expectedSessionRoot = QUOTE_STORAGE_ROOT.resolve(expectedSessionId.toString()).normalize();
|
|
||||||
if (!resolved.startsWith(expectedSessionRoot)) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
return resolved;
|
|
||||||
} catch (InvalidPathException e) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private Path buildConfirmationPdfRelativePath(UUID orderId, String orderNumber) {
|
|
||||||
return Path.of("orders", orderId.toString(), "documents", "confirmation-" + orderNumber + ".pdf");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,16 +0,0 @@
|
|||||||
package com.printcalculator.dto;
|
|
||||||
|
|
||||||
import lombok.Data;
|
|
||||||
|
|
||||||
@Data
|
|
||||||
public class AddressDto {
|
|
||||||
private String firstName;
|
|
||||||
private String lastName;
|
|
||||||
private String companyName;
|
|
||||||
private String contactPerson;
|
|
||||||
private String addressLine1;
|
|
||||||
private String addressLine2;
|
|
||||||
private String zip;
|
|
||||||
private String city;
|
|
||||||
private String countryCode;
|
|
||||||
}
|
|
||||||
@@ -1,52 +0,0 @@
|
|||||||
package com.printcalculator.dto;
|
|
||||||
|
|
||||||
import java.math.BigDecimal;
|
|
||||||
import java.util.UUID;
|
|
||||||
|
|
||||||
public class AdminCadInvoiceCreateRequest {
|
|
||||||
private UUID sessionId;
|
|
||||||
private UUID sourceRequestId;
|
|
||||||
private BigDecimal cadHours;
|
|
||||||
private BigDecimal cadHourlyRateChf;
|
|
||||||
private String notes;
|
|
||||||
|
|
||||||
public UUID getSessionId() {
|
|
||||||
return sessionId;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setSessionId(UUID sessionId) {
|
|
||||||
this.sessionId = sessionId;
|
|
||||||
}
|
|
||||||
|
|
||||||
public UUID getSourceRequestId() {
|
|
||||||
return sourceRequestId;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setSourceRequestId(UUID sourceRequestId) {
|
|
||||||
this.sourceRequestId = sourceRequestId;
|
|
||||||
}
|
|
||||||
|
|
||||||
public BigDecimal getCadHours() {
|
|
||||||
return cadHours;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setCadHours(BigDecimal cadHours) {
|
|
||||||
this.cadHours = cadHours;
|
|
||||||
}
|
|
||||||
|
|
||||||
public BigDecimal getCadHourlyRateChf() {
|
|
||||||
return cadHourlyRateChf;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setCadHourlyRateChf(BigDecimal cadHourlyRateChf) {
|
|
||||||
this.cadHourlyRateChf = cadHourlyRateChf;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getNotes() {
|
|
||||||
return notes;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setNotes(String notes) {
|
|
||||||
this.notes = notes;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,143 +0,0 @@
|
|||||||
package com.printcalculator.dto;
|
|
||||||
|
|
||||||
import java.math.BigDecimal;
|
|
||||||
import java.time.OffsetDateTime;
|
|
||||||
import java.util.UUID;
|
|
||||||
|
|
||||||
public class AdminCadInvoiceDto {
|
|
||||||
private UUID sessionId;
|
|
||||||
private String sessionStatus;
|
|
||||||
private UUID sourceRequestId;
|
|
||||||
private BigDecimal cadHours;
|
|
||||||
private BigDecimal cadHourlyRateChf;
|
|
||||||
private BigDecimal cadTotalChf;
|
|
||||||
private BigDecimal printItemsTotalChf;
|
|
||||||
private BigDecimal setupCostChf;
|
|
||||||
private BigDecimal shippingCostChf;
|
|
||||||
private BigDecimal grandTotalChf;
|
|
||||||
private UUID convertedOrderId;
|
|
||||||
private String convertedOrderStatus;
|
|
||||||
private String checkoutPath;
|
|
||||||
private String notes;
|
|
||||||
private OffsetDateTime createdAt;
|
|
||||||
|
|
||||||
public UUID getSessionId() {
|
|
||||||
return sessionId;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setSessionId(UUID sessionId) {
|
|
||||||
this.sessionId = sessionId;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getSessionStatus() {
|
|
||||||
return sessionStatus;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setSessionStatus(String sessionStatus) {
|
|
||||||
this.sessionStatus = sessionStatus;
|
|
||||||
}
|
|
||||||
|
|
||||||
public UUID getSourceRequestId() {
|
|
||||||
return sourceRequestId;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setSourceRequestId(UUID sourceRequestId) {
|
|
||||||
this.sourceRequestId = sourceRequestId;
|
|
||||||
}
|
|
||||||
|
|
||||||
public BigDecimal getCadHours() {
|
|
||||||
return cadHours;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setCadHours(BigDecimal cadHours) {
|
|
||||||
this.cadHours = cadHours;
|
|
||||||
}
|
|
||||||
|
|
||||||
public BigDecimal getCadHourlyRateChf() {
|
|
||||||
return cadHourlyRateChf;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setCadHourlyRateChf(BigDecimal cadHourlyRateChf) {
|
|
||||||
this.cadHourlyRateChf = cadHourlyRateChf;
|
|
||||||
}
|
|
||||||
|
|
||||||
public BigDecimal getCadTotalChf() {
|
|
||||||
return cadTotalChf;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setCadTotalChf(BigDecimal cadTotalChf) {
|
|
||||||
this.cadTotalChf = cadTotalChf;
|
|
||||||
}
|
|
||||||
|
|
||||||
public BigDecimal getPrintItemsTotalChf() {
|
|
||||||
return printItemsTotalChf;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setPrintItemsTotalChf(BigDecimal printItemsTotalChf) {
|
|
||||||
this.printItemsTotalChf = printItemsTotalChf;
|
|
||||||
}
|
|
||||||
|
|
||||||
public BigDecimal getSetupCostChf() {
|
|
||||||
return setupCostChf;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setSetupCostChf(BigDecimal setupCostChf) {
|
|
||||||
this.setupCostChf = setupCostChf;
|
|
||||||
}
|
|
||||||
|
|
||||||
public BigDecimal getShippingCostChf() {
|
|
||||||
return shippingCostChf;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setShippingCostChf(BigDecimal shippingCostChf) {
|
|
||||||
this.shippingCostChf = shippingCostChf;
|
|
||||||
}
|
|
||||||
|
|
||||||
public BigDecimal getGrandTotalChf() {
|
|
||||||
return grandTotalChf;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setGrandTotalChf(BigDecimal grandTotalChf) {
|
|
||||||
this.grandTotalChf = grandTotalChf;
|
|
||||||
}
|
|
||||||
|
|
||||||
public UUID getConvertedOrderId() {
|
|
||||||
return convertedOrderId;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setConvertedOrderId(UUID convertedOrderId) {
|
|
||||||
this.convertedOrderId = convertedOrderId;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getConvertedOrderStatus() {
|
|
||||||
return convertedOrderStatus;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setConvertedOrderStatus(String convertedOrderStatus) {
|
|
||||||
this.convertedOrderStatus = convertedOrderStatus;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getCheckoutPath() {
|
|
||||||
return checkoutPath;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setCheckoutPath(String checkoutPath) {
|
|
||||||
this.checkoutPath = checkoutPath;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getNotes() {
|
|
||||||
return notes;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setNotes(String notes) {
|
|
||||||
this.notes = notes;
|
|
||||||
}
|
|
||||||
|
|
||||||
public OffsetDateTime getCreatedAt() {
|
|
||||||
return createdAt;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setCreatedAt(OffsetDateTime createdAt) {
|
|
||||||
this.createdAt = createdAt;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,52 +0,0 @@
|
|||||||
package com.printcalculator.dto;
|
|
||||||
|
|
||||||
import java.time.OffsetDateTime;
|
|
||||||
import java.util.UUID;
|
|
||||||
|
|
||||||
public class AdminContactRequestAttachmentDto {
|
|
||||||
private UUID id;
|
|
||||||
private String originalFilename;
|
|
||||||
private String mimeType;
|
|
||||||
private Long fileSizeBytes;
|
|
||||||
private OffsetDateTime createdAt;
|
|
||||||
|
|
||||||
public UUID getId() {
|
|
||||||
return id;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setId(UUID id) {
|
|
||||||
this.id = id;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getOriginalFilename() {
|
|
||||||
return originalFilename;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setOriginalFilename(String originalFilename) {
|
|
||||||
this.originalFilename = originalFilename;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getMimeType() {
|
|
||||||
return mimeType;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setMimeType(String mimeType) {
|
|
||||||
this.mimeType = mimeType;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Long getFileSizeBytes() {
|
|
||||||
return fileSizeBytes;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setFileSizeBytes(Long fileSizeBytes) {
|
|
||||||
this.fileSizeBytes = fileSizeBytes;
|
|
||||||
}
|
|
||||||
|
|
||||||
public OffsetDateTime getCreatedAt() {
|
|
||||||
return createdAt;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setCreatedAt(OffsetDateTime createdAt) {
|
|
||||||
this.createdAt = createdAt;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,125 +0,0 @@
|
|||||||
package com.printcalculator.dto;
|
|
||||||
|
|
||||||
import java.time.OffsetDateTime;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.UUID;
|
|
||||||
|
|
||||||
public class AdminContactRequestDetailDto {
|
|
||||||
private UUID id;
|
|
||||||
private String requestType;
|
|
||||||
private String customerType;
|
|
||||||
private String email;
|
|
||||||
private String phone;
|
|
||||||
private String name;
|
|
||||||
private String companyName;
|
|
||||||
private String contactPerson;
|
|
||||||
private String message;
|
|
||||||
private String status;
|
|
||||||
private OffsetDateTime createdAt;
|
|
||||||
private OffsetDateTime updatedAt;
|
|
||||||
private List<AdminContactRequestAttachmentDto> attachments;
|
|
||||||
|
|
||||||
public UUID getId() {
|
|
||||||
return id;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setId(UUID id) {
|
|
||||||
this.id = id;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getRequestType() {
|
|
||||||
return requestType;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setRequestType(String requestType) {
|
|
||||||
this.requestType = requestType;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getCustomerType() {
|
|
||||||
return customerType;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setCustomerType(String customerType) {
|
|
||||||
this.customerType = customerType;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getEmail() {
|
|
||||||
return email;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setEmail(String email) {
|
|
||||||
this.email = email;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getPhone() {
|
|
||||||
return phone;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setPhone(String phone) {
|
|
||||||
this.phone = phone;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getName() {
|
|
||||||
return name;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setName(String name) {
|
|
||||||
this.name = name;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getCompanyName() {
|
|
||||||
return companyName;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setCompanyName(String companyName) {
|
|
||||||
this.companyName = companyName;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getContactPerson() {
|
|
||||||
return contactPerson;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setContactPerson(String contactPerson) {
|
|
||||||
this.contactPerson = contactPerson;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getMessage() {
|
|
||||||
return message;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setMessage(String message) {
|
|
||||||
this.message = message;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getStatus() {
|
|
||||||
return status;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setStatus(String status) {
|
|
||||||
this.status = status;
|
|
||||||
}
|
|
||||||
|
|
||||||
public OffsetDateTime getCreatedAt() {
|
|
||||||
return createdAt;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setCreatedAt(OffsetDateTime createdAt) {
|
|
||||||
this.createdAt = createdAt;
|
|
||||||
}
|
|
||||||
|
|
||||||
public OffsetDateTime getUpdatedAt() {
|
|
||||||
return updatedAt;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setUpdatedAt(OffsetDateTime updatedAt) {
|
|
||||||
this.updatedAt = updatedAt;
|
|
||||||
}
|
|
||||||
|
|
||||||
public List<AdminContactRequestAttachmentDto> getAttachments() {
|
|
||||||
return attachments;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setAttachments(List<AdminContactRequestAttachmentDto> attachments) {
|
|
||||||
this.attachments = attachments;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,88 +0,0 @@
|
|||||||
package com.printcalculator.dto;
|
|
||||||
|
|
||||||
import java.time.OffsetDateTime;
|
|
||||||
import java.util.UUID;
|
|
||||||
|
|
||||||
public class AdminContactRequestDto {
|
|
||||||
private UUID id;
|
|
||||||
private String requestType;
|
|
||||||
private String customerType;
|
|
||||||
private String email;
|
|
||||||
private String phone;
|
|
||||||
private String name;
|
|
||||||
private String companyName;
|
|
||||||
private String status;
|
|
||||||
private OffsetDateTime createdAt;
|
|
||||||
|
|
||||||
public UUID getId() {
|
|
||||||
return id;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setId(UUID id) {
|
|
||||||
this.id = id;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getRequestType() {
|
|
||||||
return requestType;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setRequestType(String requestType) {
|
|
||||||
this.requestType = requestType;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getCustomerType() {
|
|
||||||
return customerType;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setCustomerType(String customerType) {
|
|
||||||
this.customerType = customerType;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getEmail() {
|
|
||||||
return email;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setEmail(String email) {
|
|
||||||
this.email = email;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getPhone() {
|
|
||||||
return phone;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setPhone(String phone) {
|
|
||||||
this.phone = phone;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getName() {
|
|
||||||
return name;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setName(String name) {
|
|
||||||
this.name = name;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getCompanyName() {
|
|
||||||
return companyName;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setCompanyName(String companyName) {
|
|
||||||
this.companyName = companyName;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getStatus() {
|
|
||||||
return status;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setStatus(String status) {
|
|
||||||
this.status = status;
|
|
||||||
}
|
|
||||||
|
|
||||||
public OffsetDateTime getCreatedAt() {
|
|
||||||
return createdAt;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setCreatedAt(OffsetDateTime createdAt) {
|
|
||||||
this.createdAt = createdAt;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,49 +0,0 @@
|
|||||||
package com.printcalculator.dto;
|
|
||||||
|
|
||||||
public class AdminFilamentMaterialTypeDto {
|
|
||||||
private Long id;
|
|
||||||
private String materialCode;
|
|
||||||
private Boolean isFlexible;
|
|
||||||
private Boolean isTechnical;
|
|
||||||
private String technicalTypeLabel;
|
|
||||||
|
|
||||||
public Long getId() {
|
|
||||||
return id;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setId(Long id) {
|
|
||||||
this.id = id;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getMaterialCode() {
|
|
||||||
return materialCode;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setMaterialCode(String materialCode) {
|
|
||||||
this.materialCode = materialCode;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Boolean getIsFlexible() {
|
|
||||||
return isFlexible;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setIsFlexible(Boolean isFlexible) {
|
|
||||||
this.isFlexible = isFlexible;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Boolean getIsTechnical() {
|
|
||||||
return isTechnical;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setIsTechnical(Boolean isTechnical) {
|
|
||||||
this.isTechnical = isTechnical;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getTechnicalTypeLabel() {
|
|
||||||
return technicalTypeLabel;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setTechnicalTypeLabel(String technicalTypeLabel) {
|
|
||||||
this.technicalTypeLabel = technicalTypeLabel;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,87 +0,0 @@
|
|||||||
package com.printcalculator.dto;
|
|
||||||
|
|
||||||
import java.math.BigDecimal;
|
|
||||||
|
|
||||||
public class AdminFilamentStockDto {
|
|
||||||
private Long filamentVariantId;
|
|
||||||
private String materialCode;
|
|
||||||
private String variantDisplayName;
|
|
||||||
private String colorName;
|
|
||||||
private BigDecimal stockSpools;
|
|
||||||
private BigDecimal spoolNetKg;
|
|
||||||
private BigDecimal stockKg;
|
|
||||||
private BigDecimal stockFilamentGrams;
|
|
||||||
private Boolean active;
|
|
||||||
|
|
||||||
public Long getFilamentVariantId() {
|
|
||||||
return filamentVariantId;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setFilamentVariantId(Long filamentVariantId) {
|
|
||||||
this.filamentVariantId = filamentVariantId;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getMaterialCode() {
|
|
||||||
return materialCode;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setMaterialCode(String materialCode) {
|
|
||||||
this.materialCode = materialCode;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getVariantDisplayName() {
|
|
||||||
return variantDisplayName;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setVariantDisplayName(String variantDisplayName) {
|
|
||||||
this.variantDisplayName = variantDisplayName;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getColorName() {
|
|
||||||
return colorName;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setColorName(String colorName) {
|
|
||||||
this.colorName = colorName;
|
|
||||||
}
|
|
||||||
|
|
||||||
public BigDecimal getStockSpools() {
|
|
||||||
return stockSpools;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setStockSpools(BigDecimal stockSpools) {
|
|
||||||
this.stockSpools = stockSpools;
|
|
||||||
}
|
|
||||||
|
|
||||||
public BigDecimal getSpoolNetKg() {
|
|
||||||
return spoolNetKg;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setSpoolNetKg(BigDecimal spoolNetKg) {
|
|
||||||
this.spoolNetKg = spoolNetKg;
|
|
||||||
}
|
|
||||||
|
|
||||||
public BigDecimal getStockKg() {
|
|
||||||
return stockKg;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setStockKg(BigDecimal stockKg) {
|
|
||||||
this.stockKg = stockKg;
|
|
||||||
}
|
|
||||||
|
|
||||||
public BigDecimal getStockFilamentGrams() {
|
|
||||||
return stockFilamentGrams;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setStockFilamentGrams(BigDecimal stockFilamentGrams) {
|
|
||||||
this.stockFilamentGrams = stockFilamentGrams;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Boolean getActive() {
|
|
||||||
return active;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setActive(Boolean active) {
|
|
||||||
this.active = active;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,187 +0,0 @@
|
|||||||
package com.printcalculator.dto;
|
|
||||||
|
|
||||||
import java.math.BigDecimal;
|
|
||||||
import java.time.OffsetDateTime;
|
|
||||||
|
|
||||||
public class AdminFilamentVariantDto {
|
|
||||||
private Long id;
|
|
||||||
private Long materialTypeId;
|
|
||||||
private String materialCode;
|
|
||||||
private Boolean materialIsFlexible;
|
|
||||||
private Boolean materialIsTechnical;
|
|
||||||
private String materialTechnicalTypeLabel;
|
|
||||||
private String variantDisplayName;
|
|
||||||
private String colorName;
|
|
||||||
private String colorHex;
|
|
||||||
private String finishType;
|
|
||||||
private String brand;
|
|
||||||
private Boolean isMatte;
|
|
||||||
private Boolean isSpecial;
|
|
||||||
private BigDecimal costChfPerKg;
|
|
||||||
private BigDecimal stockSpools;
|
|
||||||
private BigDecimal spoolNetKg;
|
|
||||||
private BigDecimal stockKg;
|
|
||||||
private BigDecimal stockFilamentGrams;
|
|
||||||
private Boolean isActive;
|
|
||||||
private OffsetDateTime createdAt;
|
|
||||||
|
|
||||||
public Long getId() {
|
|
||||||
return id;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setId(Long id) {
|
|
||||||
this.id = id;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Long getMaterialTypeId() {
|
|
||||||
return materialTypeId;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setMaterialTypeId(Long materialTypeId) {
|
|
||||||
this.materialTypeId = materialTypeId;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getMaterialCode() {
|
|
||||||
return materialCode;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setMaterialCode(String materialCode) {
|
|
||||||
this.materialCode = materialCode;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Boolean getMaterialIsFlexible() {
|
|
||||||
return materialIsFlexible;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setMaterialIsFlexible(Boolean materialIsFlexible) {
|
|
||||||
this.materialIsFlexible = materialIsFlexible;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Boolean getMaterialIsTechnical() {
|
|
||||||
return materialIsTechnical;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setMaterialIsTechnical(Boolean materialIsTechnical) {
|
|
||||||
this.materialIsTechnical = materialIsTechnical;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getMaterialTechnicalTypeLabel() {
|
|
||||||
return materialTechnicalTypeLabel;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setMaterialTechnicalTypeLabel(String materialTechnicalTypeLabel) {
|
|
||||||
this.materialTechnicalTypeLabel = materialTechnicalTypeLabel;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getVariantDisplayName() {
|
|
||||||
return variantDisplayName;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setVariantDisplayName(String variantDisplayName) {
|
|
||||||
this.variantDisplayName = variantDisplayName;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getColorName() {
|
|
||||||
return colorName;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setColorName(String colorName) {
|
|
||||||
this.colorName = colorName;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getColorHex() {
|
|
||||||
return colorHex;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setColorHex(String colorHex) {
|
|
||||||
this.colorHex = colorHex;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getFinishType() {
|
|
||||||
return finishType;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setFinishType(String finishType) {
|
|
||||||
this.finishType = finishType;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getBrand() {
|
|
||||||
return brand;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setBrand(String brand) {
|
|
||||||
this.brand = brand;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Boolean getIsMatte() {
|
|
||||||
return isMatte;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setIsMatte(Boolean isMatte) {
|
|
||||||
this.isMatte = isMatte;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Boolean getIsSpecial() {
|
|
||||||
return isSpecial;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setIsSpecial(Boolean isSpecial) {
|
|
||||||
this.isSpecial = isSpecial;
|
|
||||||
}
|
|
||||||
|
|
||||||
public BigDecimal getCostChfPerKg() {
|
|
||||||
return costChfPerKg;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setCostChfPerKg(BigDecimal costChfPerKg) {
|
|
||||||
this.costChfPerKg = costChfPerKg;
|
|
||||||
}
|
|
||||||
|
|
||||||
public BigDecimal getStockSpools() {
|
|
||||||
return stockSpools;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setStockSpools(BigDecimal stockSpools) {
|
|
||||||
this.stockSpools = stockSpools;
|
|
||||||
}
|
|
||||||
|
|
||||||
public BigDecimal getSpoolNetKg() {
|
|
||||||
return spoolNetKg;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setSpoolNetKg(BigDecimal spoolNetKg) {
|
|
||||||
this.spoolNetKg = spoolNetKg;
|
|
||||||
}
|
|
||||||
|
|
||||||
public BigDecimal getStockKg() {
|
|
||||||
return stockKg;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setStockKg(BigDecimal stockKg) {
|
|
||||||
this.stockKg = stockKg;
|
|
||||||
}
|
|
||||||
|
|
||||||
public BigDecimal getStockFilamentGrams() {
|
|
||||||
return stockFilamentGrams;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setStockFilamentGrams(BigDecimal stockFilamentGrams) {
|
|
||||||
this.stockFilamentGrams = stockFilamentGrams;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Boolean getIsActive() {
|
|
||||||
return isActive;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setIsActive(Boolean isActive) {
|
|
||||||
this.isActive = isActive;
|
|
||||||
}
|
|
||||||
|
|
||||||
public OffsetDateTime getCreatedAt() {
|
|
||||||
return createdAt;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setCreatedAt(OffsetDateTime createdAt) {
|
|
||||||
this.createdAt = createdAt;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,17 +0,0 @@
|
|||||||
package com.printcalculator.dto;
|
|
||||||
|
|
||||||
import jakarta.validation.constraints.NotBlank;
|
|
||||||
|
|
||||||
public class AdminLoginRequest {
|
|
||||||
|
|
||||||
@NotBlank
|
|
||||||
private String password;
|
|
||||||
|
|
||||||
public String getPassword() {
|
|
||||||
return password;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setPassword(String password) {
|
|
||||||
this.password = password;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,13 +0,0 @@
|
|||||||
package com.printcalculator.dto;
|
|
||||||
|
|
||||||
public class AdminOrderStatusUpdateRequest {
|
|
||||||
private String status;
|
|
||||||
|
|
||||||
public String getStatus() {
|
|
||||||
return status;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setStatus(String status) {
|
|
||||||
this.status = status;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,98 +0,0 @@
|
|||||||
package com.printcalculator.dto;
|
|
||||||
|
|
||||||
import java.time.OffsetDateTime;
|
|
||||||
import java.math.BigDecimal;
|
|
||||||
import java.util.UUID;
|
|
||||||
|
|
||||||
public class AdminQuoteSessionDto {
|
|
||||||
private UUID id;
|
|
||||||
private String status;
|
|
||||||
private String materialCode;
|
|
||||||
private OffsetDateTime createdAt;
|
|
||||||
private OffsetDateTime expiresAt;
|
|
||||||
private UUID convertedOrderId;
|
|
||||||
private UUID sourceRequestId;
|
|
||||||
private BigDecimal cadHours;
|
|
||||||
private BigDecimal cadHourlyRateChf;
|
|
||||||
private BigDecimal cadTotalChf;
|
|
||||||
|
|
||||||
public UUID getId() {
|
|
||||||
return id;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setId(UUID id) {
|
|
||||||
this.id = id;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getStatus() {
|
|
||||||
return status;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setStatus(String status) {
|
|
||||||
this.status = status;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getMaterialCode() {
|
|
||||||
return materialCode;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setMaterialCode(String materialCode) {
|
|
||||||
this.materialCode = materialCode;
|
|
||||||
}
|
|
||||||
|
|
||||||
public OffsetDateTime getCreatedAt() {
|
|
||||||
return createdAt;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setCreatedAt(OffsetDateTime createdAt) {
|
|
||||||
this.createdAt = createdAt;
|
|
||||||
}
|
|
||||||
|
|
||||||
public OffsetDateTime getExpiresAt() {
|
|
||||||
return expiresAt;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setExpiresAt(OffsetDateTime expiresAt) {
|
|
||||||
this.expiresAt = expiresAt;
|
|
||||||
}
|
|
||||||
|
|
||||||
public UUID getConvertedOrderId() {
|
|
||||||
return convertedOrderId;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setConvertedOrderId(UUID convertedOrderId) {
|
|
||||||
this.convertedOrderId = convertedOrderId;
|
|
||||||
}
|
|
||||||
|
|
||||||
public UUID getSourceRequestId() {
|
|
||||||
return sourceRequestId;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setSourceRequestId(UUID sourceRequestId) {
|
|
||||||
this.sourceRequestId = sourceRequestId;
|
|
||||||
}
|
|
||||||
|
|
||||||
public BigDecimal getCadHours() {
|
|
||||||
return cadHours;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setCadHours(BigDecimal cadHours) {
|
|
||||||
this.cadHours = cadHours;
|
|
||||||
}
|
|
||||||
|
|
||||||
public BigDecimal getCadHourlyRateChf() {
|
|
||||||
return cadHourlyRateChf;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setCadHourlyRateChf(BigDecimal cadHourlyRateChf) {
|
|
||||||
this.cadHourlyRateChf = cadHourlyRateChf;
|
|
||||||
}
|
|
||||||
|
|
||||||
public BigDecimal getCadTotalChf() {
|
|
||||||
return cadTotalChf;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setCadTotalChf(BigDecimal cadTotalChf) {
|
|
||||||
this.cadTotalChf = cadTotalChf;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,13 +0,0 @@
|
|||||||
package com.printcalculator.dto;
|
|
||||||
|
|
||||||
public class AdminUpdateContactRequestStatusRequest {
|
|
||||||
private String status;
|
|
||||||
|
|
||||||
public String getStatus() {
|
|
||||||
return status;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setStatus(String status) {
|
|
||||||
this.status = status;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,40 +0,0 @@
|
|||||||
package com.printcalculator.dto;
|
|
||||||
|
|
||||||
public class AdminUpsertFilamentMaterialTypeRequest {
|
|
||||||
private String materialCode;
|
|
||||||
private Boolean isFlexible;
|
|
||||||
private Boolean isTechnical;
|
|
||||||
private String technicalTypeLabel;
|
|
||||||
|
|
||||||
public String getMaterialCode() {
|
|
||||||
return materialCode;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setMaterialCode(String materialCode) {
|
|
||||||
this.materialCode = materialCode;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Boolean getIsFlexible() {
|
|
||||||
return isFlexible;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setIsFlexible(Boolean isFlexible) {
|
|
||||||
this.isFlexible = isFlexible;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Boolean getIsTechnical() {
|
|
||||||
return isTechnical;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setIsTechnical(Boolean isTechnical) {
|
|
||||||
this.isTechnical = isTechnical;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getTechnicalTypeLabel() {
|
|
||||||
return technicalTypeLabel;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setTechnicalTypeLabel(String technicalTypeLabel) {
|
|
||||||
this.technicalTypeLabel = technicalTypeLabel;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,114 +0,0 @@
|
|||||||
package com.printcalculator.dto;
|
|
||||||
|
|
||||||
import java.math.BigDecimal;
|
|
||||||
|
|
||||||
public class AdminUpsertFilamentVariantRequest {
|
|
||||||
private Long materialTypeId;
|
|
||||||
private String variantDisplayName;
|
|
||||||
private String colorName;
|
|
||||||
private String colorHex;
|
|
||||||
private String finishType;
|
|
||||||
private String brand;
|
|
||||||
private Boolean isMatte;
|
|
||||||
private Boolean isSpecial;
|
|
||||||
private BigDecimal costChfPerKg;
|
|
||||||
private BigDecimal stockSpools;
|
|
||||||
private BigDecimal spoolNetKg;
|
|
||||||
private Boolean isActive;
|
|
||||||
|
|
||||||
public Long getMaterialTypeId() {
|
|
||||||
return materialTypeId;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setMaterialTypeId(Long materialTypeId) {
|
|
||||||
this.materialTypeId = materialTypeId;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getVariantDisplayName() {
|
|
||||||
return variantDisplayName;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setVariantDisplayName(String variantDisplayName) {
|
|
||||||
this.variantDisplayName = variantDisplayName;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getColorName() {
|
|
||||||
return colorName;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setColorName(String colorName) {
|
|
||||||
this.colorName = colorName;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getColorHex() {
|
|
||||||
return colorHex;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setColorHex(String colorHex) {
|
|
||||||
this.colorHex = colorHex;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getFinishType() {
|
|
||||||
return finishType;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setFinishType(String finishType) {
|
|
||||||
this.finishType = finishType;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getBrand() {
|
|
||||||
return brand;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setBrand(String brand) {
|
|
||||||
this.brand = brand;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Boolean getIsMatte() {
|
|
||||||
return isMatte;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setIsMatte(Boolean isMatte) {
|
|
||||||
this.isMatte = isMatte;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Boolean getIsSpecial() {
|
|
||||||
return isSpecial;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setIsSpecial(Boolean isSpecial) {
|
|
||||||
this.isSpecial = isSpecial;
|
|
||||||
}
|
|
||||||
|
|
||||||
public BigDecimal getCostChfPerKg() {
|
|
||||||
return costChfPerKg;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setCostChfPerKg(BigDecimal costChfPerKg) {
|
|
||||||
this.costChfPerKg = costChfPerKg;
|
|
||||||
}
|
|
||||||
|
|
||||||
public BigDecimal getStockSpools() {
|
|
||||||
return stockSpools;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setStockSpools(BigDecimal stockSpools) {
|
|
||||||
this.stockSpools = stockSpools;
|
|
||||||
}
|
|
||||||
|
|
||||||
public BigDecimal getSpoolNetKg() {
|
|
||||||
return spoolNetKg;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setSpoolNetKg(BigDecimal spoolNetKg) {
|
|
||||||
this.spoolNetKg = spoolNetKg;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Boolean getIsActive() {
|
|
||||||
return isActive;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setIsActive(Boolean isActive) {
|
|
||||||
this.isActive = isActive;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,19 +0,0 @@
|
|||||||
package com.printcalculator.dto;
|
|
||||||
|
|
||||||
import lombok.Data;
|
|
||||||
import jakarta.validation.constraints.AssertTrue;
|
|
||||||
|
|
||||||
@Data
|
|
||||||
public class CreateOrderRequest {
|
|
||||||
private CustomerDto customer;
|
|
||||||
private AddressDto billingAddress;
|
|
||||||
private AddressDto shippingAddress;
|
|
||||||
private String language;
|
|
||||||
private boolean shippingSameAsBilling;
|
|
||||||
|
|
||||||
@AssertTrue(message = "L'accettazione dei Termini e Condizioni e obbligatoria.")
|
|
||||||
private boolean acceptTerms;
|
|
||||||
|
|
||||||
@AssertTrue(message = "L'accettazione dell'Informativa Privacy e obbligatoria.")
|
|
||||||
private boolean acceptPrivacy;
|
|
||||||
}
|
|
||||||
@@ -1,10 +0,0 @@
|
|||||||
package com.printcalculator.dto;
|
|
||||||
|
|
||||||
import lombok.Data;
|
|
||||||
|
|
||||||
@Data
|
|
||||||
public class CustomerDto {
|
|
||||||
private String email;
|
|
||||||
private String phone;
|
|
||||||
private String customerType; // "PRIVATE", "BUSINESS"
|
|
||||||
}
|
|
||||||
@@ -1,29 +0,0 @@
|
|||||||
package com.printcalculator.dto;
|
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
public record OptionsResponse(
|
|
||||||
List<MaterialOption> materials,
|
|
||||||
List<QualityOption> qualities,
|
|
||||||
List<InfillPatternOption> infillPatterns,
|
|
||||||
List<LayerHeightOptionDTO> layerHeights,
|
|
||||||
List<NozzleOptionDTO> nozzleDiameters,
|
|
||||||
List<NozzleLayerHeightOptionsDTO> layerHeightsByNozzle
|
|
||||||
) {
|
|
||||||
public record MaterialOption(String code, String label, List<VariantOption> variants) {}
|
|
||||||
public record VariantOption(
|
|
||||||
Long id,
|
|
||||||
String name,
|
|
||||||
String colorName,
|
|
||||||
String hexColor,
|
|
||||||
String finishType,
|
|
||||||
Double stockSpools,
|
|
||||||
Double stockFilamentGrams,
|
|
||||||
boolean isOutOfStock
|
|
||||||
) {}
|
|
||||||
public record QualityOption(String id, String label) {}
|
|
||||||
public record InfillPatternOption(String id, String label) {}
|
|
||||||
public record LayerHeightOptionDTO(double value, String label) {}
|
|
||||||
public record NozzleOptionDTO(double value, String label) {}
|
|
||||||
public record NozzleLayerHeightOptionsDTO(double nozzleDiameter, List<LayerHeightOptionDTO> layerHeights) {}
|
|
||||||
}
|
|
||||||
@@ -1,134 +0,0 @@
|
|||||||
package com.printcalculator.dto;
|
|
||||||
|
|
||||||
import java.math.BigDecimal;
|
|
||||||
import java.time.OffsetDateTime;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.UUID;
|
|
||||||
|
|
||||||
public class OrderDto {
|
|
||||||
private UUID id;
|
|
||||||
private String orderNumber;
|
|
||||||
private String status;
|
|
||||||
private String paymentStatus;
|
|
||||||
private String paymentMethod;
|
|
||||||
private String customerEmail;
|
|
||||||
private String customerPhone;
|
|
||||||
private String preferredLanguage;
|
|
||||||
private String billingCustomerType;
|
|
||||||
private AddressDto billingAddress;
|
|
||||||
private AddressDto shippingAddress;
|
|
||||||
private Boolean shippingSameAsBilling;
|
|
||||||
private String currency;
|
|
||||||
private BigDecimal setupCostChf;
|
|
||||||
private BigDecimal shippingCostChf;
|
|
||||||
private BigDecimal discountChf;
|
|
||||||
private BigDecimal subtotalChf;
|
|
||||||
private Boolean isCadOrder;
|
|
||||||
private UUID sourceRequestId;
|
|
||||||
private BigDecimal cadHours;
|
|
||||||
private BigDecimal cadHourlyRateChf;
|
|
||||||
private BigDecimal cadTotalChf;
|
|
||||||
private BigDecimal totalChf;
|
|
||||||
private OffsetDateTime createdAt;
|
|
||||||
private String printMaterialCode;
|
|
||||||
private BigDecimal printNozzleDiameterMm;
|
|
||||||
private BigDecimal printLayerHeightMm;
|
|
||||||
private String printInfillPattern;
|
|
||||||
private Integer printInfillPercent;
|
|
||||||
private Boolean printSupportsEnabled;
|
|
||||||
private List<OrderItemDto> items;
|
|
||||||
|
|
||||||
// Getters and Setters
|
|
||||||
public UUID getId() { return id; }
|
|
||||||
public void setId(UUID id) { this.id = id; }
|
|
||||||
|
|
||||||
public String getOrderNumber() { return orderNumber; }
|
|
||||||
public void setOrderNumber(String orderNumber) { this.orderNumber = orderNumber; }
|
|
||||||
|
|
||||||
public String getStatus() { return status; }
|
|
||||||
public void setStatus(String status) { this.status = status; }
|
|
||||||
|
|
||||||
public String getPaymentStatus() { return paymentStatus; }
|
|
||||||
public void setPaymentStatus(String paymentStatus) { this.paymentStatus = paymentStatus; }
|
|
||||||
|
|
||||||
public String getPaymentMethod() { return paymentMethod; }
|
|
||||||
public void setPaymentMethod(String paymentMethod) { this.paymentMethod = paymentMethod; }
|
|
||||||
|
|
||||||
public String getCustomerEmail() { return customerEmail; }
|
|
||||||
public void setCustomerEmail(String customerEmail) { this.customerEmail = customerEmail; }
|
|
||||||
|
|
||||||
public String getCustomerPhone() { return customerPhone; }
|
|
||||||
public void setCustomerPhone(String customerPhone) { this.customerPhone = customerPhone; }
|
|
||||||
|
|
||||||
public String getPreferredLanguage() { return preferredLanguage; }
|
|
||||||
public void setPreferredLanguage(String preferredLanguage) { this.preferredLanguage = preferredLanguage; }
|
|
||||||
|
|
||||||
public String getBillingCustomerType() { return billingCustomerType; }
|
|
||||||
public void setBillingCustomerType(String billingCustomerType) { this.billingCustomerType = billingCustomerType; }
|
|
||||||
|
|
||||||
public AddressDto getBillingAddress() { return billingAddress; }
|
|
||||||
public void setBillingAddress(AddressDto billingAddress) { this.billingAddress = billingAddress; }
|
|
||||||
|
|
||||||
public AddressDto getShippingAddress() { return shippingAddress; }
|
|
||||||
public void setShippingAddress(AddressDto shippingAddress) { this.shippingAddress = shippingAddress; }
|
|
||||||
|
|
||||||
public Boolean getShippingSameAsBilling() { return shippingSameAsBilling; }
|
|
||||||
public void setShippingSameAsBilling(Boolean shippingSameAsBilling) { this.shippingSameAsBilling = shippingSameAsBilling; }
|
|
||||||
|
|
||||||
public String getCurrency() { return currency; }
|
|
||||||
public void setCurrency(String currency) { this.currency = currency; }
|
|
||||||
|
|
||||||
public BigDecimal getSetupCostChf() { return setupCostChf; }
|
|
||||||
public void setSetupCostChf(BigDecimal setupCostChf) { this.setupCostChf = setupCostChf; }
|
|
||||||
|
|
||||||
public BigDecimal getShippingCostChf() { return shippingCostChf; }
|
|
||||||
public void setShippingCostChf(BigDecimal shippingCostChf) { this.shippingCostChf = shippingCostChf; }
|
|
||||||
|
|
||||||
public BigDecimal getDiscountChf() { return discountChf; }
|
|
||||||
public void setDiscountChf(BigDecimal discountChf) { this.discountChf = discountChf; }
|
|
||||||
|
|
||||||
public BigDecimal getSubtotalChf() { return subtotalChf; }
|
|
||||||
public void setSubtotalChf(BigDecimal subtotalChf) { this.subtotalChf = subtotalChf; }
|
|
||||||
|
|
||||||
public Boolean getIsCadOrder() { return isCadOrder; }
|
|
||||||
public void setIsCadOrder(Boolean isCadOrder) { this.isCadOrder = isCadOrder; }
|
|
||||||
|
|
||||||
public UUID getSourceRequestId() { return sourceRequestId; }
|
|
||||||
public void setSourceRequestId(UUID sourceRequestId) { this.sourceRequestId = sourceRequestId; }
|
|
||||||
|
|
||||||
public BigDecimal getCadHours() { return cadHours; }
|
|
||||||
public void setCadHours(BigDecimal cadHours) { this.cadHours = cadHours; }
|
|
||||||
|
|
||||||
public BigDecimal getCadHourlyRateChf() { return cadHourlyRateChf; }
|
|
||||||
public void setCadHourlyRateChf(BigDecimal cadHourlyRateChf) { this.cadHourlyRateChf = cadHourlyRateChf; }
|
|
||||||
|
|
||||||
public BigDecimal getCadTotalChf() { return cadTotalChf; }
|
|
||||||
public void setCadTotalChf(BigDecimal cadTotalChf) { this.cadTotalChf = cadTotalChf; }
|
|
||||||
|
|
||||||
public BigDecimal getTotalChf() { return totalChf; }
|
|
||||||
public void setTotalChf(BigDecimal totalChf) { this.totalChf = totalChf; }
|
|
||||||
|
|
||||||
public OffsetDateTime getCreatedAt() { return createdAt; }
|
|
||||||
public void setCreatedAt(OffsetDateTime createdAt) { this.createdAt = createdAt; }
|
|
||||||
|
|
||||||
public String getPrintMaterialCode() { return printMaterialCode; }
|
|
||||||
public void setPrintMaterialCode(String printMaterialCode) { this.printMaterialCode = printMaterialCode; }
|
|
||||||
|
|
||||||
public BigDecimal getPrintNozzleDiameterMm() { return printNozzleDiameterMm; }
|
|
||||||
public void setPrintNozzleDiameterMm(BigDecimal printNozzleDiameterMm) { this.printNozzleDiameterMm = printNozzleDiameterMm; }
|
|
||||||
|
|
||||||
public BigDecimal getPrintLayerHeightMm() { return printLayerHeightMm; }
|
|
||||||
public void setPrintLayerHeightMm(BigDecimal printLayerHeightMm) { this.printLayerHeightMm = printLayerHeightMm; }
|
|
||||||
|
|
||||||
public String getPrintInfillPattern() { return printInfillPattern; }
|
|
||||||
public void setPrintInfillPattern(String printInfillPattern) { this.printInfillPattern = printInfillPattern; }
|
|
||||||
|
|
||||||
public Integer getPrintInfillPercent() { return printInfillPercent; }
|
|
||||||
public void setPrintInfillPercent(Integer printInfillPercent) { this.printInfillPercent = printInfillPercent; }
|
|
||||||
|
|
||||||
public Boolean getPrintSupportsEnabled() { return printSupportsEnabled; }
|
|
||||||
public void setPrintSupportsEnabled(Boolean printSupportsEnabled) { this.printSupportsEnabled = printSupportsEnabled; }
|
|
||||||
|
|
||||||
public List<OrderItemDto> getItems() { return items; }
|
|
||||||
public void setItems(List<OrderItemDto> items) { this.items = items; }
|
|
||||||
}
|
|
||||||
@@ -1,68 +0,0 @@
|
|||||||
package com.printcalculator.dto;
|
|
||||||
|
|
||||||
import java.math.BigDecimal;
|
|
||||||
import java.util.UUID;
|
|
||||||
|
|
||||||
public class OrderItemDto {
|
|
||||||
private UUID id;
|
|
||||||
private String originalFilename;
|
|
||||||
private String materialCode;
|
|
||||||
private String colorCode;
|
|
||||||
private String quality;
|
|
||||||
private BigDecimal nozzleDiameterMm;
|
|
||||||
private BigDecimal layerHeightMm;
|
|
||||||
private Integer infillPercent;
|
|
||||||
private String infillPattern;
|
|
||||||
private Boolean supportsEnabled;
|
|
||||||
private Integer quantity;
|
|
||||||
private Integer printTimeSeconds;
|
|
||||||
private BigDecimal materialGrams;
|
|
||||||
private BigDecimal unitPriceChf;
|
|
||||||
private BigDecimal lineTotalChf;
|
|
||||||
|
|
||||||
// Getters and Setters
|
|
||||||
public UUID getId() { return id; }
|
|
||||||
public void setId(UUID id) { this.id = id; }
|
|
||||||
|
|
||||||
public String getOriginalFilename() { return originalFilename; }
|
|
||||||
public void setOriginalFilename(String originalFilename) { this.originalFilename = originalFilename; }
|
|
||||||
|
|
||||||
public String getMaterialCode() { return materialCode; }
|
|
||||||
public void setMaterialCode(String materialCode) { this.materialCode = materialCode; }
|
|
||||||
|
|
||||||
public String getColorCode() { return colorCode; }
|
|
||||||
public void setColorCode(String colorCode) { this.colorCode = colorCode; }
|
|
||||||
|
|
||||||
public String getQuality() { return quality; }
|
|
||||||
public void setQuality(String quality) { this.quality = quality; }
|
|
||||||
|
|
||||||
public BigDecimal getNozzleDiameterMm() { return nozzleDiameterMm; }
|
|
||||||
public void setNozzleDiameterMm(BigDecimal nozzleDiameterMm) { this.nozzleDiameterMm = nozzleDiameterMm; }
|
|
||||||
|
|
||||||
public BigDecimal getLayerHeightMm() { return layerHeightMm; }
|
|
||||||
public void setLayerHeightMm(BigDecimal layerHeightMm) { this.layerHeightMm = layerHeightMm; }
|
|
||||||
|
|
||||||
public Integer getInfillPercent() { return infillPercent; }
|
|
||||||
public void setInfillPercent(Integer infillPercent) { this.infillPercent = infillPercent; }
|
|
||||||
|
|
||||||
public String getInfillPattern() { return infillPattern; }
|
|
||||||
public void setInfillPattern(String infillPattern) { this.infillPattern = infillPattern; }
|
|
||||||
|
|
||||||
public Boolean getSupportsEnabled() { return supportsEnabled; }
|
|
||||||
public void setSupportsEnabled(Boolean supportsEnabled) { this.supportsEnabled = supportsEnabled; }
|
|
||||||
|
|
||||||
public Integer getQuantity() { return quantity; }
|
|
||||||
public void setQuantity(Integer quantity) { this.quantity = quantity; }
|
|
||||||
|
|
||||||
public Integer getPrintTimeSeconds() { return printTimeSeconds; }
|
|
||||||
public void setPrintTimeSeconds(Integer printTimeSeconds) { this.printTimeSeconds = printTimeSeconds; }
|
|
||||||
|
|
||||||
public BigDecimal getMaterialGrams() { return materialGrams; }
|
|
||||||
public void setMaterialGrams(BigDecimal materialGrams) { this.materialGrams = materialGrams; }
|
|
||||||
|
|
||||||
public BigDecimal getUnitPriceChf() { return unitPriceChf; }
|
|
||||||
public void setUnitPriceChf(BigDecimal unitPriceChf) { this.unitPriceChf = unitPriceChf; }
|
|
||||||
|
|
||||||
public BigDecimal getLineTotalChf() { return lineTotalChf; }
|
|
||||||
public void setLineTotalChf(BigDecimal lineTotalChf) { this.lineTotalChf = lineTotalChf; }
|
|
||||||
}
|
|
||||||
@@ -1,148 +0,0 @@
|
|||||||
package com.printcalculator.dto;
|
|
||||||
|
|
||||||
public class PrintSettingsDto {
|
|
||||||
// Mode: "BASIC" or "ADVANCED"
|
|
||||||
private String complexityMode;
|
|
||||||
|
|
||||||
// Common
|
|
||||||
private String material; // e.g. "PLA", "PLA TOUGH", "PETG"
|
|
||||||
private String color; // e.g. "White", "#FFFFFF"
|
|
||||||
private Long filamentVariantId;
|
|
||||||
private Long printerMachineId;
|
|
||||||
|
|
||||||
// Basic Mode
|
|
||||||
private String quality; // "draft", "standard", "high"
|
|
||||||
|
|
||||||
// Advanced Mode (Optional in Basic)
|
|
||||||
private Double nozzleDiameter;
|
|
||||||
private Double layerHeight;
|
|
||||||
private Double infillDensity;
|
|
||||||
private String infillPattern;
|
|
||||||
private Boolean supportsEnabled;
|
|
||||||
private String notes;
|
|
||||||
|
|
||||||
// Dimensions
|
|
||||||
private Double boundingBoxX;
|
|
||||||
private Double boundingBoxY;
|
|
||||||
private Double boundingBoxZ;
|
|
||||||
|
|
||||||
public String getComplexityMode() {
|
|
||||||
return complexityMode;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setComplexityMode(String complexityMode) {
|
|
||||||
this.complexityMode = complexityMode;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getMaterial() {
|
|
||||||
return material;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setMaterial(String material) {
|
|
||||||
this.material = material;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getColor() {
|
|
||||||
return color;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setColor(String color) {
|
|
||||||
this.color = color;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Long getFilamentVariantId() {
|
|
||||||
return filamentVariantId;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setFilamentVariantId(Long filamentVariantId) {
|
|
||||||
this.filamentVariantId = filamentVariantId;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Long getPrinterMachineId() {
|
|
||||||
return printerMachineId;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setPrinterMachineId(Long printerMachineId) {
|
|
||||||
this.printerMachineId = printerMachineId;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getQuality() {
|
|
||||||
return quality;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setQuality(String quality) {
|
|
||||||
this.quality = quality;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Double getNozzleDiameter() {
|
|
||||||
return nozzleDiameter;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setNozzleDiameter(Double nozzleDiameter) {
|
|
||||||
this.nozzleDiameter = nozzleDiameter;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Double getLayerHeight() {
|
|
||||||
return layerHeight;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setLayerHeight(Double layerHeight) {
|
|
||||||
this.layerHeight = layerHeight;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Double getInfillDensity() {
|
|
||||||
return infillDensity;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setInfillDensity(Double infillDensity) {
|
|
||||||
this.infillDensity = infillDensity;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getInfillPattern() {
|
|
||||||
return infillPattern;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setInfillPattern(String infillPattern) {
|
|
||||||
this.infillPattern = infillPattern;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Boolean getSupportsEnabled() {
|
|
||||||
return supportsEnabled;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setSupportsEnabled(Boolean supportsEnabled) {
|
|
||||||
this.supportsEnabled = supportsEnabled;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getNotes() {
|
|
||||||
return notes;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setNotes(String notes) {
|
|
||||||
this.notes = notes;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Double getBoundingBoxX() {
|
|
||||||
return boundingBoxX;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setBoundingBoxX(Double boundingBoxX) {
|
|
||||||
this.boundingBoxX = boundingBoxX;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Double getBoundingBoxY() {
|
|
||||||
return boundingBoxY;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setBoundingBoxY(Double boundingBoxY) {
|
|
||||||
this.boundingBoxY = boundingBoxY;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Double getBoundingBoxZ() {
|
|
||||||
return boundingBoxZ;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setBoundingBoxZ(Double boundingBoxZ) {
|
|
||||||
this.boundingBoxZ = boundingBoxZ;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,23 +0,0 @@
|
|||||||
package com.printcalculator.dto;
|
|
||||||
|
|
||||||
import lombok.Data;
|
|
||||||
import jakarta.validation.constraints.AssertTrue;
|
|
||||||
|
|
||||||
@Data
|
|
||||||
public class QuoteRequestDto {
|
|
||||||
private String requestType; // "PRINT_SERVICE" or "DESIGN_SERVICE"
|
|
||||||
private String customerType; // "PRIVATE" or "BUSINESS"
|
|
||||||
private String language; // "it" | "en" | "de" | "fr"
|
|
||||||
private String email;
|
|
||||||
private String phone;
|
|
||||||
private String name;
|
|
||||||
private String companyName;
|
|
||||||
private String contactPerson;
|
|
||||||
private String message;
|
|
||||||
|
|
||||||
@AssertTrue(message = "L'accettazione dei Termini e Condizioni e obbligatoria.")
|
|
||||||
private boolean acceptTerms;
|
|
||||||
|
|
||||||
@AssertTrue(message = "L'accettazione dell'Informativa Privacy e obbligatoria.")
|
|
||||||
private boolean acceptPrivacy;
|
|
||||||
}
|
|
||||||
@@ -1,149 +0,0 @@
|
|||||||
package com.printcalculator.entity;
|
|
||||||
|
|
||||||
import jakarta.persistence.*;
|
|
||||||
import org.hibernate.annotations.ColumnDefault;
|
|
||||||
|
|
||||||
import java.time.OffsetDateTime;
|
|
||||||
import java.util.UUID;
|
|
||||||
|
|
||||||
@Entity
|
|
||||||
@Table(name = "custom_quote_requests", indexes = {@Index(name = "ix_custom_quote_requests_status",
|
|
||||||
columnList = "status")})
|
|
||||||
public class CustomQuoteRequest {
|
|
||||||
@Id
|
|
||||||
@GeneratedValue(strategy = GenerationType.AUTO)
|
|
||||||
@Column(name = "request_id", nullable = false)
|
|
||||||
private UUID id;
|
|
||||||
|
|
||||||
@Column(name = "request_type", nullable = false, length = Integer.MAX_VALUE)
|
|
||||||
private String requestType;
|
|
||||||
|
|
||||||
@Column(name = "customer_type", nullable = false, length = Integer.MAX_VALUE)
|
|
||||||
private String customerType;
|
|
||||||
|
|
||||||
@Column(name = "email", nullable = false, length = Integer.MAX_VALUE)
|
|
||||||
private String email;
|
|
||||||
|
|
||||||
@Column(name = "phone", length = Integer.MAX_VALUE)
|
|
||||||
private String phone;
|
|
||||||
|
|
||||||
@Column(name = "name", length = Integer.MAX_VALUE)
|
|
||||||
private String name;
|
|
||||||
|
|
||||||
@Column(name = "company_name", length = Integer.MAX_VALUE)
|
|
||||||
private String companyName;
|
|
||||||
|
|
||||||
@Column(name = "contact_person", length = Integer.MAX_VALUE)
|
|
||||||
private String contactPerson;
|
|
||||||
|
|
||||||
@Column(name = "message", nullable = false, length = Integer.MAX_VALUE)
|
|
||||||
private String message;
|
|
||||||
|
|
||||||
@Column(name = "status", nullable = false, length = Integer.MAX_VALUE)
|
|
||||||
private String status;
|
|
||||||
|
|
||||||
@ColumnDefault("now()")
|
|
||||||
@Column(name = "created_at", nullable = false)
|
|
||||||
private OffsetDateTime createdAt;
|
|
||||||
|
|
||||||
@ColumnDefault("now()")
|
|
||||||
@Column(name = "updated_at", nullable = false)
|
|
||||||
private OffsetDateTime updatedAt;
|
|
||||||
|
|
||||||
public UUID getId() {
|
|
||||||
return id;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setId(UUID id) {
|
|
||||||
this.id = id;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getRequestType() {
|
|
||||||
return requestType;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setRequestType(String requestType) {
|
|
||||||
this.requestType = requestType;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getCustomerType() {
|
|
||||||
return customerType;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setCustomerType(String customerType) {
|
|
||||||
this.customerType = customerType;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getEmail() {
|
|
||||||
return email;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setEmail(String email) {
|
|
||||||
this.email = email;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getPhone() {
|
|
||||||
return phone;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setPhone(String phone) {
|
|
||||||
this.phone = phone;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getName() {
|
|
||||||
return name;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setName(String name) {
|
|
||||||
this.name = name;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getCompanyName() {
|
|
||||||
return companyName;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setCompanyName(String companyName) {
|
|
||||||
this.companyName = companyName;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getContactPerson() {
|
|
||||||
return contactPerson;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setContactPerson(String contactPerson) {
|
|
||||||
this.contactPerson = contactPerson;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getMessage() {
|
|
||||||
return message;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setMessage(String message) {
|
|
||||||
this.message = message;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getStatus() {
|
|
||||||
return status;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setStatus(String status) {
|
|
||||||
this.status = status;
|
|
||||||
}
|
|
||||||
|
|
||||||
public OffsetDateTime getCreatedAt() {
|
|
||||||
return createdAt;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setCreatedAt(OffsetDateTime createdAt) {
|
|
||||||
this.createdAt = createdAt;
|
|
||||||
}
|
|
||||||
|
|
||||||
public OffsetDateTime getUpdatedAt() {
|
|
||||||
return updatedAt;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setUpdatedAt(OffsetDateTime updatedAt) {
|
|
||||||
this.updatedAt = updatedAt;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,121 +0,0 @@
|
|||||||
package com.printcalculator.entity;
|
|
||||||
|
|
||||||
import jakarta.persistence.*;
|
|
||||||
import org.hibernate.annotations.ColumnDefault;
|
|
||||||
import org.hibernate.annotations.OnDelete;
|
|
||||||
import org.hibernate.annotations.OnDeleteAction;
|
|
||||||
|
|
||||||
import java.time.OffsetDateTime;
|
|
||||||
import java.util.UUID;
|
|
||||||
|
|
||||||
@Entity
|
|
||||||
@Table(name = "custom_quote_request_attachments", indexes = {@Index(name = "ix_custom_quote_attachments_request",
|
|
||||||
columnList = "request_id")})
|
|
||||||
public class CustomQuoteRequestAttachment {
|
|
||||||
@Id
|
|
||||||
@GeneratedValue(strategy = GenerationType.AUTO)
|
|
||||||
@Column(name = "attachment_id", nullable = false)
|
|
||||||
private UUID id;
|
|
||||||
|
|
||||||
@ManyToOne(fetch = FetchType.LAZY, optional = false)
|
|
||||||
@OnDelete(action = OnDeleteAction.CASCADE)
|
|
||||||
@JoinColumn(name = "request_id", nullable = false)
|
|
||||||
private CustomQuoteRequest request;
|
|
||||||
|
|
||||||
@Column(name = "original_filename", nullable = false, length = Integer.MAX_VALUE)
|
|
||||||
private String originalFilename;
|
|
||||||
|
|
||||||
@Column(name = "stored_relative_path", nullable = false, length = Integer.MAX_VALUE)
|
|
||||||
private String storedRelativePath;
|
|
||||||
|
|
||||||
@Column(name = "stored_filename", nullable = false, length = Integer.MAX_VALUE)
|
|
||||||
private String storedFilename;
|
|
||||||
|
|
||||||
@Column(name = "file_size_bytes")
|
|
||||||
private Long fileSizeBytes;
|
|
||||||
|
|
||||||
@Column(name = "mime_type", length = Integer.MAX_VALUE)
|
|
||||||
private String mimeType;
|
|
||||||
|
|
||||||
@Column(name = "sha256_hex", length = Integer.MAX_VALUE)
|
|
||||||
private String sha256Hex;
|
|
||||||
|
|
||||||
@ColumnDefault("now()")
|
|
||||||
@Column(name = "created_at", nullable = false)
|
|
||||||
private OffsetDateTime createdAt;
|
|
||||||
|
|
||||||
public UUID getId() {
|
|
||||||
return id;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setId(UUID id) {
|
|
||||||
this.id = id;
|
|
||||||
}
|
|
||||||
|
|
||||||
public CustomQuoteRequest getRequest() {
|
|
||||||
return request;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setRequest(CustomQuoteRequest request) {
|
|
||||||
this.request = request;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getOriginalFilename() {
|
|
||||||
return originalFilename;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setOriginalFilename(String originalFilename) {
|
|
||||||
this.originalFilename = originalFilename;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getStoredRelativePath() {
|
|
||||||
return storedRelativePath;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setStoredRelativePath(String storedRelativePath) {
|
|
||||||
this.storedRelativePath = storedRelativePath;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getStoredFilename() {
|
|
||||||
return storedFilename;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setStoredFilename(String storedFilename) {
|
|
||||||
this.storedFilename = storedFilename;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Long getFileSizeBytes() {
|
|
||||||
return fileSizeBytes;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setFileSizeBytes(Long fileSizeBytes) {
|
|
||||||
this.fileSizeBytes = fileSizeBytes;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getMimeType() {
|
|
||||||
return mimeType;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setMimeType(String mimeType) {
|
|
||||||
this.mimeType = mimeType;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getSha256Hex() {
|
|
||||||
return sha256Hex;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setSha256Hex(String sha256Hex) {
|
|
||||||
this.sha256Hex = sha256Hex;
|
|
||||||
}
|
|
||||||
|
|
||||||
public OffsetDateTime getCreatedAt() {
|
|
||||||
return createdAt;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setCreatedAt(OffsetDateTime createdAt) {
|
|
||||||
this.createdAt = createdAt;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setCustomQuoteRequest(CustomQuoteRequest request) {
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,126 +0,0 @@
|
|||||||
package com.printcalculator.entity;
|
|
||||||
|
|
||||||
import jakarta.persistence.*;
|
|
||||||
import org.hibernate.annotations.ColumnDefault;
|
|
||||||
|
|
||||||
import java.time.OffsetDateTime;
|
|
||||||
import java.util.UUID;
|
|
||||||
|
|
||||||
@Entity
|
|
||||||
@Table(name = "customers")
|
|
||||||
public class Customer {
|
|
||||||
@Id
|
|
||||||
@GeneratedValue(strategy = GenerationType.AUTO)
|
|
||||||
@Column(name = "customer_id", nullable = false)
|
|
||||||
private UUID id;
|
|
||||||
|
|
||||||
@Column(name = "customer_type", nullable = false, length = Integer.MAX_VALUE)
|
|
||||||
private String customerType;
|
|
||||||
|
|
||||||
@Column(name = "email", nullable = false, length = Integer.MAX_VALUE)
|
|
||||||
private String email;
|
|
||||||
|
|
||||||
@Column(name = "phone", length = Integer.MAX_VALUE)
|
|
||||||
private String phone;
|
|
||||||
|
|
||||||
@Column(name = "first_name", length = Integer.MAX_VALUE)
|
|
||||||
private String firstName;
|
|
||||||
|
|
||||||
@Column(name = "last_name", length = Integer.MAX_VALUE)
|
|
||||||
private String lastName;
|
|
||||||
|
|
||||||
@Column(name = "company_name", length = Integer.MAX_VALUE)
|
|
||||||
private String companyName;
|
|
||||||
|
|
||||||
@Column(name = "contact_person", length = Integer.MAX_VALUE)
|
|
||||||
private String contactPerson;
|
|
||||||
|
|
||||||
@ColumnDefault("now()")
|
|
||||||
@Column(name = "created_at", nullable = false)
|
|
||||||
private OffsetDateTime createdAt;
|
|
||||||
|
|
||||||
@ColumnDefault("now()")
|
|
||||||
@Column(name = "updated_at", nullable = false)
|
|
||||||
private OffsetDateTime updatedAt;
|
|
||||||
|
|
||||||
public UUID getId() {
|
|
||||||
return id;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setId(UUID id) {
|
|
||||||
this.id = id;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getCustomerType() {
|
|
||||||
return customerType;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setCustomerType(String customerType) {
|
|
||||||
this.customerType = customerType;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getEmail() {
|
|
||||||
return email;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setEmail(String email) {
|
|
||||||
this.email = email;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getPhone() {
|
|
||||||
return phone;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setPhone(String phone) {
|
|
||||||
this.phone = phone;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getFirstName() {
|
|
||||||
return firstName;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setFirstName(String firstName) {
|
|
||||||
this.firstName = firstName;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getLastName() {
|
|
||||||
return lastName;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setLastName(String lastName) {
|
|
||||||
this.lastName = lastName;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getCompanyName() {
|
|
||||||
return companyName;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setCompanyName(String companyName) {
|
|
||||||
this.companyName = companyName;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getContactPerson() {
|
|
||||||
return contactPerson;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setContactPerson(String contactPerson) {
|
|
||||||
this.contactPerson = contactPerson;
|
|
||||||
}
|
|
||||||
|
|
||||||
public OffsetDateTime getCreatedAt() {
|
|
||||||
return createdAt;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setCreatedAt(OffsetDateTime createdAt) {
|
|
||||||
this.createdAt = createdAt;
|
|
||||||
}
|
|
||||||
|
|
||||||
public OffsetDateTime getUpdatedAt() {
|
|
||||||
return updatedAt;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setUpdatedAt(OffsetDateTime updatedAt) {
|
|
||||||
this.updatedAt = updatedAt;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,68 +0,0 @@
|
|||||||
package com.printcalculator.entity;
|
|
||||||
|
|
||||||
import jakarta.persistence.*;
|
|
||||||
import org.hibernate.annotations.ColumnDefault;
|
|
||||||
|
|
||||||
@Entity
|
|
||||||
@Table(name = "filament_material_type")
|
|
||||||
public class FilamentMaterialType {
|
|
||||||
@Id
|
|
||||||
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
|
||||||
@Column(name = "filament_material_type_id", nullable = false)
|
|
||||||
private Long id;
|
|
||||||
|
|
||||||
@Column(name = "material_code", nullable = false, length = Integer.MAX_VALUE)
|
|
||||||
private String materialCode;
|
|
||||||
|
|
||||||
@ColumnDefault("false")
|
|
||||||
@Column(name = "is_flexible", nullable = false)
|
|
||||||
private Boolean isFlexible;
|
|
||||||
|
|
||||||
@ColumnDefault("false")
|
|
||||||
@Column(name = "is_technical", nullable = false)
|
|
||||||
private Boolean isTechnical;
|
|
||||||
|
|
||||||
@Column(name = "technical_type_label", length = Integer.MAX_VALUE)
|
|
||||||
private String technicalTypeLabel;
|
|
||||||
|
|
||||||
public Long getId() {
|
|
||||||
return id;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setId(Long id) {
|
|
||||||
this.id = id;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getMaterialCode() {
|
|
||||||
return materialCode;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setMaterialCode(String materialCode) {
|
|
||||||
this.materialCode = materialCode;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Boolean getIsFlexible() {
|
|
||||||
return isFlexible;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setIsFlexible(Boolean isFlexible) {
|
|
||||||
this.isFlexible = isFlexible;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Boolean getIsTechnical() {
|
|
||||||
return isTechnical;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setIsTechnical(Boolean isTechnical) {
|
|
||||||
this.isTechnical = isTechnical;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getTechnicalTypeLabel() {
|
|
||||||
return technicalTypeLabel;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setTechnicalTypeLabel(String technicalTypeLabel) {
|
|
||||||
this.technicalTypeLabel = technicalTypeLabel;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,176 +0,0 @@
|
|||||||
package com.printcalculator.entity;
|
|
||||||
|
|
||||||
import jakarta.persistence.*;
|
|
||||||
import org.hibernate.annotations.ColumnDefault;
|
|
||||||
|
|
||||||
import java.math.BigDecimal;
|
|
||||||
import java.time.OffsetDateTime;
|
|
||||||
|
|
||||||
@Entity
|
|
||||||
@Table(name = "filament_variant")
|
|
||||||
public class FilamentVariant {
|
|
||||||
@Id
|
|
||||||
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
|
||||||
@Column(name = "filament_variant_id", nullable = false)
|
|
||||||
private Long id;
|
|
||||||
|
|
||||||
@ManyToOne(fetch = FetchType.LAZY, optional = false)
|
|
||||||
@JoinColumn(name = "filament_material_type_id", nullable = false)
|
|
||||||
private FilamentMaterialType filamentMaterialType;
|
|
||||||
|
|
||||||
@Column(name = "variant_display_name", nullable = false, length = Integer.MAX_VALUE)
|
|
||||||
private String variantDisplayName;
|
|
||||||
|
|
||||||
@Column(name = "color_name", nullable = false, length = Integer.MAX_VALUE)
|
|
||||||
private String colorName;
|
|
||||||
|
|
||||||
@Column(name = "color_hex", length = Integer.MAX_VALUE)
|
|
||||||
private String colorHex;
|
|
||||||
|
|
||||||
@ColumnDefault("'GLOSSY'")
|
|
||||||
@Column(name = "finish_type", length = Integer.MAX_VALUE)
|
|
||||||
private String finishType;
|
|
||||||
|
|
||||||
@Column(name = "brand", length = Integer.MAX_VALUE)
|
|
||||||
private String brand;
|
|
||||||
|
|
||||||
@ColumnDefault("false")
|
|
||||||
@Column(name = "is_matte", nullable = false)
|
|
||||||
private Boolean isMatte;
|
|
||||||
|
|
||||||
@ColumnDefault("false")
|
|
||||||
@Column(name = "is_special", nullable = false)
|
|
||||||
private Boolean isSpecial;
|
|
||||||
|
|
||||||
@Column(name = "cost_chf_per_kg", nullable = false, precision = 10, scale = 2)
|
|
||||||
private BigDecimal costChfPerKg;
|
|
||||||
|
|
||||||
@ColumnDefault("0.000")
|
|
||||||
@Column(name = "stock_spools", nullable = false, precision = 6, scale = 3)
|
|
||||||
private BigDecimal stockSpools;
|
|
||||||
|
|
||||||
@ColumnDefault("1.000")
|
|
||||||
@Column(name = "spool_net_kg", nullable = false, precision = 6, scale = 3)
|
|
||||||
private BigDecimal spoolNetKg;
|
|
||||||
|
|
||||||
@ColumnDefault("true")
|
|
||||||
@Column(name = "is_active", nullable = false)
|
|
||||||
private Boolean isActive;
|
|
||||||
|
|
||||||
@ColumnDefault("now()")
|
|
||||||
@Column(name = "created_at", nullable = false)
|
|
||||||
private OffsetDateTime createdAt;
|
|
||||||
|
|
||||||
public Long getId() {
|
|
||||||
return id;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setId(Long id) {
|
|
||||||
this.id = id;
|
|
||||||
}
|
|
||||||
|
|
||||||
public FilamentMaterialType getFilamentMaterialType() {
|
|
||||||
return filamentMaterialType;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setFilamentMaterialType(FilamentMaterialType filamentMaterialType) {
|
|
||||||
this.filamentMaterialType = filamentMaterialType;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getVariantDisplayName() {
|
|
||||||
return variantDisplayName;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setVariantDisplayName(String variantDisplayName) {
|
|
||||||
this.variantDisplayName = variantDisplayName;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getColorName() {
|
|
||||||
return colorName;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setColorName(String colorName) {
|
|
||||||
this.colorName = colorName;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getColorHex() {
|
|
||||||
return colorHex;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setColorHex(String colorHex) {
|
|
||||||
this.colorHex = colorHex;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getFinishType() {
|
|
||||||
return finishType;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setFinishType(String finishType) {
|
|
||||||
this.finishType = finishType;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getBrand() {
|
|
||||||
return brand;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setBrand(String brand) {
|
|
||||||
this.brand = brand;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Boolean getIsMatte() {
|
|
||||||
return isMatte;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setIsMatte(Boolean isMatte) {
|
|
||||||
this.isMatte = isMatte;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Boolean getIsSpecial() {
|
|
||||||
return isSpecial;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setIsSpecial(Boolean isSpecial) {
|
|
||||||
this.isSpecial = isSpecial;
|
|
||||||
}
|
|
||||||
|
|
||||||
public BigDecimal getCostChfPerKg() {
|
|
||||||
return costChfPerKg;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setCostChfPerKg(BigDecimal costChfPerKg) {
|
|
||||||
this.costChfPerKg = costChfPerKg;
|
|
||||||
}
|
|
||||||
|
|
||||||
public BigDecimal getStockSpools() {
|
|
||||||
return stockSpools;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setStockSpools(BigDecimal stockSpools) {
|
|
||||||
this.stockSpools = stockSpools;
|
|
||||||
}
|
|
||||||
|
|
||||||
public BigDecimal getSpoolNetKg() {
|
|
||||||
return spoolNetKg;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setSpoolNetKg(BigDecimal spoolNetKg) {
|
|
||||||
this.spoolNetKg = spoolNetKg;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Boolean getIsActive() {
|
|
||||||
return isActive;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setIsActive(Boolean isActive) {
|
|
||||||
this.isActive = isActive;
|
|
||||||
}
|
|
||||||
|
|
||||||
public OffsetDateTime getCreatedAt() {
|
|
||||||
return createdAt;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setCreatedAt(OffsetDateTime createdAt) {
|
|
||||||
this.createdAt = createdAt;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,72 +0,0 @@
|
|||||||
package com.printcalculator.entity;
|
|
||||||
|
|
||||||
import jakarta.persistence.*;
|
|
||||||
import org.hibernate.annotations.ColumnDefault;
|
|
||||||
|
|
||||||
@Entity
|
|
||||||
@Table(name = "filament_variant_orca_override", uniqueConstraints = {
|
|
||||||
@UniqueConstraint(name = "ux_filament_variant_orca_override_variant_machine", columnNames = {
|
|
||||||
"filament_variant_id", "printer_machine_profile_id"
|
|
||||||
})
|
|
||||||
})
|
|
||||||
public class FilamentVariantOrcaOverride {
|
|
||||||
@Id
|
|
||||||
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
|
||||||
@Column(name = "filament_variant_orca_override_id", nullable = false)
|
|
||||||
private Long id;
|
|
||||||
|
|
||||||
@ManyToOne(fetch = FetchType.LAZY, optional = false)
|
|
||||||
@JoinColumn(name = "filament_variant_id", nullable = false)
|
|
||||||
private FilamentVariant filamentVariant;
|
|
||||||
|
|
||||||
@ManyToOne(fetch = FetchType.LAZY, optional = false)
|
|
||||||
@JoinColumn(name = "printer_machine_profile_id", nullable = false)
|
|
||||||
private PrinterMachineProfile printerMachineProfile;
|
|
||||||
|
|
||||||
@Column(name = "orca_filament_profile_name", nullable = false, length = Integer.MAX_VALUE)
|
|
||||||
private String orcaFilamentProfileName;
|
|
||||||
|
|
||||||
@ColumnDefault("true")
|
|
||||||
@Column(name = "is_active", nullable = false)
|
|
||||||
private Boolean isActive;
|
|
||||||
|
|
||||||
public Long getId() {
|
|
||||||
return id;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setId(Long id) {
|
|
||||||
this.id = id;
|
|
||||||
}
|
|
||||||
|
|
||||||
public FilamentVariant getFilamentVariant() {
|
|
||||||
return filamentVariant;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setFilamentVariant(FilamentVariant filamentVariant) {
|
|
||||||
this.filamentVariant = filamentVariant;
|
|
||||||
}
|
|
||||||
|
|
||||||
public PrinterMachineProfile getPrinterMachineProfile() {
|
|
||||||
return printerMachineProfile;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setPrinterMachineProfile(PrinterMachineProfile printerMachineProfile) {
|
|
||||||
this.printerMachineProfile = printerMachineProfile;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getOrcaFilamentProfileName() {
|
|
||||||
return orcaFilamentProfileName;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setOrcaFilamentProfileName(String orcaFilamentProfileName) {
|
|
||||||
this.orcaFilamentProfileName = orcaFilamentProfileName;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Boolean getIsActive() {
|
|
||||||
return isActive;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setIsActive(Boolean isActive) {
|
|
||||||
this.isActive = isActive;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,44 +0,0 @@
|
|||||||
package com.printcalculator.entity;
|
|
||||||
|
|
||||||
import jakarta.persistence.Column;
|
|
||||||
import jakarta.persistence.Entity;
|
|
||||||
import jakarta.persistence.Id;
|
|
||||||
import jakarta.persistence.Table;
|
|
||||||
import org.hibernate.annotations.Immutable;
|
|
||||||
|
|
||||||
import java.math.BigDecimal;
|
|
||||||
|
|
||||||
@Entity
|
|
||||||
@Immutable
|
|
||||||
@Table(name = "filament_variant_stock_kg")
|
|
||||||
public class FilamentVariantStockKg {
|
|
||||||
@Id
|
|
||||||
@Column(name = "filament_variant_id")
|
|
||||||
private Long filamentVariantId;
|
|
||||||
|
|
||||||
@Column(name = "stock_spools", precision = 6, scale = 3)
|
|
||||||
private BigDecimal stockSpools;
|
|
||||||
|
|
||||||
@Column(name = "spool_net_kg", precision = 6, scale = 3)
|
|
||||||
private BigDecimal spoolNetKg;
|
|
||||||
|
|
||||||
@Column(name = "stock_kg")
|
|
||||||
private BigDecimal stockKg;
|
|
||||||
|
|
||||||
public Long getFilamentVariantId() {
|
|
||||||
return filamentVariantId;
|
|
||||||
}
|
|
||||||
|
|
||||||
public BigDecimal getStockSpools() {
|
|
||||||
return stockSpools;
|
|
||||||
}
|
|
||||||
|
|
||||||
public BigDecimal getSpoolNetKg() {
|
|
||||||
return spoolNetKg;
|
|
||||||
}
|
|
||||||
|
|
||||||
public BigDecimal getStockKg() {
|
|
||||||
return stockKg;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,56 +0,0 @@
|
|||||||
package com.printcalculator.entity;
|
|
||||||
|
|
||||||
import jakarta.persistence.*;
|
|
||||||
import org.hibernate.annotations.ColumnDefault;
|
|
||||||
|
|
||||||
@Entity
|
|
||||||
@Table(name = "infill_pattern")
|
|
||||||
public class InfillPattern {
|
|
||||||
@Id
|
|
||||||
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
|
||||||
@Column(name = "infill_pattern_id", nullable = false)
|
|
||||||
private Long id;
|
|
||||||
|
|
||||||
@Column(name = "pattern_code", nullable = false, length = Integer.MAX_VALUE)
|
|
||||||
private String patternCode;
|
|
||||||
|
|
||||||
@Column(name = "display_name", nullable = false, length = Integer.MAX_VALUE)
|
|
||||||
private String displayName;
|
|
||||||
|
|
||||||
@ColumnDefault("true")
|
|
||||||
@Column(name = "is_active", nullable = false)
|
|
||||||
private Boolean isActive;
|
|
||||||
|
|
||||||
public Long getId() {
|
|
||||||
return id;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setId(Long id) {
|
|
||||||
this.id = id;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getPatternCode() {
|
|
||||||
return patternCode;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setPatternCode(String patternCode) {
|
|
||||||
this.patternCode = patternCode;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getDisplayName() {
|
|
||||||
return displayName;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setDisplayName(String displayName) {
|
|
||||||
this.displayName = displayName;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Boolean getIsActive() {
|
|
||||||
return isActive;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setIsActive(Boolean isActive) {
|
|
||||||
this.isActive = isActive;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,59 +0,0 @@
|
|||||||
package com.printcalculator.entity;
|
|
||||||
|
|
||||||
import jakarta.persistence.*;
|
|
||||||
import org.hibernate.annotations.ColumnDefault;
|
|
||||||
|
|
||||||
import java.math.BigDecimal;
|
|
||||||
|
|
||||||
@Entity
|
|
||||||
@Table(name = "layer_height_option")
|
|
||||||
public class LayerHeightOption {
|
|
||||||
@Id
|
|
||||||
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
|
||||||
@Column(name = "layer_height_option_id", nullable = false)
|
|
||||||
private Long id;
|
|
||||||
|
|
||||||
@Column(name = "layer_height_mm", nullable = false, precision = 5, scale = 3)
|
|
||||||
private BigDecimal layerHeightMm;
|
|
||||||
|
|
||||||
@ColumnDefault("1.000")
|
|
||||||
@Column(name = "time_multiplier", nullable = false, precision = 6, scale = 3)
|
|
||||||
private BigDecimal timeMultiplier;
|
|
||||||
|
|
||||||
@ColumnDefault("true")
|
|
||||||
@Column(name = "is_active", nullable = false)
|
|
||||||
private Boolean isActive;
|
|
||||||
|
|
||||||
public Long getId() {
|
|
||||||
return id;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setId(Long id) {
|
|
||||||
this.id = id;
|
|
||||||
}
|
|
||||||
|
|
||||||
public BigDecimal getLayerHeightMm() {
|
|
||||||
return layerHeightMm;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setLayerHeightMm(BigDecimal layerHeightMm) {
|
|
||||||
this.layerHeightMm = layerHeightMm;
|
|
||||||
}
|
|
||||||
|
|
||||||
public BigDecimal getTimeMultiplier() {
|
|
||||||
return timeMultiplier;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setTimeMultiplier(BigDecimal timeMultiplier) {
|
|
||||||
this.timeMultiplier = timeMultiplier;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Boolean getIsActive() {
|
|
||||||
return isActive;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setIsActive(Boolean isActive) {
|
|
||||||
this.isActive = isActive;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,80 +0,0 @@
|
|||||||
package com.printcalculator.entity;
|
|
||||||
|
|
||||||
import jakarta.persistence.*;
|
|
||||||
import org.hibernate.annotations.ColumnDefault;
|
|
||||||
|
|
||||||
import java.math.BigDecimal;
|
|
||||||
|
|
||||||
@Entity
|
|
||||||
@Table(name = "layer_height_profile")
|
|
||||||
public class LayerHeightProfile {
|
|
||||||
@Id
|
|
||||||
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
|
||||||
@Column(name = "layer_height_profile_id", nullable = false)
|
|
||||||
private Long id;
|
|
||||||
|
|
||||||
@Column(name = "profile_name", nullable = false, length = Integer.MAX_VALUE)
|
|
||||||
private String profileName;
|
|
||||||
|
|
||||||
@Column(name = "min_layer_height_mm", nullable = false, precision = 5, scale = 3)
|
|
||||||
private BigDecimal minLayerHeightMm;
|
|
||||||
|
|
||||||
@Column(name = "max_layer_height_mm", nullable = false, precision = 5, scale = 3)
|
|
||||||
private BigDecimal maxLayerHeightMm;
|
|
||||||
|
|
||||||
@Column(name = "default_layer_height_mm", nullable = false, precision = 5, scale = 3)
|
|
||||||
private BigDecimal defaultLayerHeightMm;
|
|
||||||
|
|
||||||
@ColumnDefault("1.000")
|
|
||||||
@Column(name = "time_multiplier", nullable = false, precision = 6, scale = 3)
|
|
||||||
private BigDecimal timeMultiplier;
|
|
||||||
|
|
||||||
public Long getId() {
|
|
||||||
return id;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setId(Long id) {
|
|
||||||
this.id = id;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getProfileName() {
|
|
||||||
return profileName;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setProfileName(String profileName) {
|
|
||||||
this.profileName = profileName;
|
|
||||||
}
|
|
||||||
|
|
||||||
public BigDecimal getMinLayerHeightMm() {
|
|
||||||
return minLayerHeightMm;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setMinLayerHeightMm(BigDecimal minLayerHeightMm) {
|
|
||||||
this.minLayerHeightMm = minLayerHeightMm;
|
|
||||||
}
|
|
||||||
|
|
||||||
public BigDecimal getMaxLayerHeightMm() {
|
|
||||||
return maxLayerHeightMm;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setMaxLayerHeightMm(BigDecimal maxLayerHeightMm) {
|
|
||||||
this.maxLayerHeightMm = maxLayerHeightMm;
|
|
||||||
}
|
|
||||||
|
|
||||||
public BigDecimal getDefaultLayerHeightMm() {
|
|
||||||
return defaultLayerHeightMm;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setDefaultLayerHeightMm(BigDecimal defaultLayerHeightMm) {
|
|
||||||
this.defaultLayerHeightMm = defaultLayerHeightMm;
|
|
||||||
}
|
|
||||||
|
|
||||||
public BigDecimal getTimeMultiplier() {
|
|
||||||
return timeMultiplier;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setTimeMultiplier(BigDecimal timeMultiplier) {
|
|
||||||
this.timeMultiplier = timeMultiplier;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,72 +0,0 @@
|
|||||||
package com.printcalculator.entity;
|
|
||||||
|
|
||||||
import jakarta.persistence.*;
|
|
||||||
import org.hibernate.annotations.ColumnDefault;
|
|
||||||
|
|
||||||
@Entity
|
|
||||||
@Table(name = "material_orca_profile_map", uniqueConstraints = {
|
|
||||||
@UniqueConstraint(name = "ux_material_orca_profile_map_machine_material", columnNames = {
|
|
||||||
"printer_machine_profile_id", "filament_material_type_id"
|
|
||||||
})
|
|
||||||
})
|
|
||||||
public class MaterialOrcaProfileMap {
|
|
||||||
@Id
|
|
||||||
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
|
||||||
@Column(name = "material_orca_profile_map_id", nullable = false)
|
|
||||||
private Long id;
|
|
||||||
|
|
||||||
@ManyToOne(fetch = FetchType.LAZY, optional = false)
|
|
||||||
@JoinColumn(name = "printer_machine_profile_id", nullable = false)
|
|
||||||
private PrinterMachineProfile printerMachineProfile;
|
|
||||||
|
|
||||||
@ManyToOne(fetch = FetchType.LAZY, optional = false)
|
|
||||||
@JoinColumn(name = "filament_material_type_id", nullable = false)
|
|
||||||
private FilamentMaterialType filamentMaterialType;
|
|
||||||
|
|
||||||
@Column(name = "orca_filament_profile_name", nullable = false, length = Integer.MAX_VALUE)
|
|
||||||
private String orcaFilamentProfileName;
|
|
||||||
|
|
||||||
@ColumnDefault("true")
|
|
||||||
@Column(name = "is_active", nullable = false)
|
|
||||||
private Boolean isActive;
|
|
||||||
|
|
||||||
public Long getId() {
|
|
||||||
return id;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setId(Long id) {
|
|
||||||
this.id = id;
|
|
||||||
}
|
|
||||||
|
|
||||||
public PrinterMachineProfile getPrinterMachineProfile() {
|
|
||||||
return printerMachineProfile;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setPrinterMachineProfile(PrinterMachineProfile printerMachineProfile) {
|
|
||||||
this.printerMachineProfile = printerMachineProfile;
|
|
||||||
}
|
|
||||||
|
|
||||||
public FilamentMaterialType getFilamentMaterialType() {
|
|
||||||
return filamentMaterialType;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setFilamentMaterialType(FilamentMaterialType filamentMaterialType) {
|
|
||||||
this.filamentMaterialType = filamentMaterialType;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getOrcaFilamentProfileName() {
|
|
||||||
return orcaFilamentProfileName;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setOrcaFilamentProfileName(String orcaFilamentProfileName) {
|
|
||||||
this.orcaFilamentProfileName = orcaFilamentProfileName;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Boolean getIsActive() {
|
|
||||||
return isActive;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setIsActive(Boolean isActive) {
|
|
||||||
this.isActive = isActive;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,63 +0,0 @@
|
|||||||
package com.printcalculator.entity;
|
|
||||||
|
|
||||||
import jakarta.persistence.*;
|
|
||||||
import org.hibernate.annotations.ColumnDefault;
|
|
||||||
|
|
||||||
import java.math.BigDecimal;
|
|
||||||
|
|
||||||
@Entity
|
|
||||||
@Table(
|
|
||||||
name = "nozzle_layer_height_option",
|
|
||||||
uniqueConstraints = @UniqueConstraint(
|
|
||||||
name = "ux_nozzle_layer_height_option_nozzle_layer",
|
|
||||||
columnNames = {"nozzle_diameter_mm", "layer_height_mm"}
|
|
||||||
)
|
|
||||||
)
|
|
||||||
public class NozzleLayerHeightOption {
|
|
||||||
@Id
|
|
||||||
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
|
||||||
@Column(name = "nozzle_layer_height_option_id", nullable = false)
|
|
||||||
private Long id;
|
|
||||||
|
|
||||||
@Column(name = "nozzle_diameter_mm", nullable = false, precision = 4, scale = 2)
|
|
||||||
private BigDecimal nozzleDiameterMm;
|
|
||||||
|
|
||||||
@Column(name = "layer_height_mm", nullable = false, precision = 5, scale = 3)
|
|
||||||
private BigDecimal layerHeightMm;
|
|
||||||
|
|
||||||
@ColumnDefault("true")
|
|
||||||
@Column(name = "is_active", nullable = false)
|
|
||||||
private Boolean isActive;
|
|
||||||
|
|
||||||
public Long getId() {
|
|
||||||
return id;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setId(Long id) {
|
|
||||||
this.id = id;
|
|
||||||
}
|
|
||||||
|
|
||||||
public BigDecimal getNozzleDiameterMm() {
|
|
||||||
return nozzleDiameterMm;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setNozzleDiameterMm(BigDecimal nozzleDiameterMm) {
|
|
||||||
this.nozzleDiameterMm = nozzleDiameterMm;
|
|
||||||
}
|
|
||||||
|
|
||||||
public BigDecimal getLayerHeightMm() {
|
|
||||||
return layerHeightMm;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setLayerHeightMm(BigDecimal layerHeightMm) {
|
|
||||||
this.layerHeightMm = layerHeightMm;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Boolean getIsActive() {
|
|
||||||
return isActive;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setIsActive(Boolean isActive) {
|
|
||||||
this.isActive = isActive;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,84 +0,0 @@
|
|||||||
package com.printcalculator.entity;
|
|
||||||
|
|
||||||
import jakarta.persistence.*;
|
|
||||||
import org.hibernate.annotations.ColumnDefault;
|
|
||||||
|
|
||||||
import java.math.BigDecimal;
|
|
||||||
import java.time.OffsetDateTime;
|
|
||||||
|
|
||||||
@Entity
|
|
||||||
@Table(name = "nozzle_option")
|
|
||||||
public class NozzleOption {
|
|
||||||
@Id
|
|
||||||
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
|
||||||
@Column(name = "nozzle_option_id", nullable = false)
|
|
||||||
private Long id;
|
|
||||||
|
|
||||||
@Column(name = "nozzle_diameter_mm", nullable = false, precision = 4, scale = 2)
|
|
||||||
private BigDecimal nozzleDiameterMm;
|
|
||||||
|
|
||||||
@ColumnDefault("0")
|
|
||||||
@Column(name = "owned_quantity", nullable = false)
|
|
||||||
private Integer ownedQuantity;
|
|
||||||
|
|
||||||
@ColumnDefault("0.00")
|
|
||||||
@Column(name = "extra_nozzle_change_fee_chf", nullable = false, precision = 10, scale = 2)
|
|
||||||
private BigDecimal extraNozzleChangeFeeChf;
|
|
||||||
|
|
||||||
@ColumnDefault("true")
|
|
||||||
@Column(name = "is_active", nullable = false)
|
|
||||||
private Boolean isActive;
|
|
||||||
|
|
||||||
@ColumnDefault("now()")
|
|
||||||
@Column(name = "created_at", nullable = false)
|
|
||||||
private OffsetDateTime createdAt;
|
|
||||||
|
|
||||||
public Long getId() {
|
|
||||||
return id;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setId(Long id) {
|
|
||||||
this.id = id;
|
|
||||||
}
|
|
||||||
|
|
||||||
public BigDecimal getNozzleDiameterMm() {
|
|
||||||
return nozzleDiameterMm;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setNozzleDiameterMm(BigDecimal nozzleDiameterMm) {
|
|
||||||
this.nozzleDiameterMm = nozzleDiameterMm;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Integer getOwnedQuantity() {
|
|
||||||
return ownedQuantity;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setOwnedQuantity(Integer ownedQuantity) {
|
|
||||||
this.ownedQuantity = ownedQuantity;
|
|
||||||
}
|
|
||||||
|
|
||||||
public BigDecimal getExtraNozzleChangeFeeChf() {
|
|
||||||
return extraNozzleChangeFeeChf;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setExtraNozzleChangeFeeChf(BigDecimal extraNozzleChangeFeeChf) {
|
|
||||||
this.extraNozzleChangeFeeChf = extraNozzleChangeFeeChf;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Boolean getIsActive() {
|
|
||||||
return isActive;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setIsActive(Boolean isActive) {
|
|
||||||
this.isActive = isActive;
|
|
||||||
}
|
|
||||||
|
|
||||||
public OffsetDateTime getCreatedAt() {
|
|
||||||
return createdAt;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setCreatedAt(OffsetDateTime createdAt) {
|
|
||||||
this.createdAt = createdAt;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,492 +0,0 @@
|
|||||||
package com.printcalculator.entity;
|
|
||||||
|
|
||||||
import jakarta.persistence.*;
|
|
||||||
import org.hibernate.annotations.ColumnDefault;
|
|
||||||
|
|
||||||
import java.math.BigDecimal;
|
|
||||||
import java.time.OffsetDateTime;
|
|
||||||
import java.util.UUID;
|
|
||||||
|
|
||||||
@Entity
|
|
||||||
@Table(name = "orders", indexes = {@Index(name = "ix_orders_status",
|
|
||||||
columnList = "status")})
|
|
||||||
public class Order {
|
|
||||||
@Id
|
|
||||||
@GeneratedValue(strategy = GenerationType.AUTO)
|
|
||||||
@Column(name = "order_id", nullable = false)
|
|
||||||
private UUID id;
|
|
||||||
|
|
||||||
@ManyToOne(fetch = FetchType.LAZY)
|
|
||||||
@JoinColumn(name = "source_quote_session_id")
|
|
||||||
private QuoteSession sourceQuoteSession;
|
|
||||||
|
|
||||||
@Column(name = "status", nullable = false, length = Integer.MAX_VALUE)
|
|
||||||
private String status;
|
|
||||||
|
|
||||||
@ManyToOne(fetch = FetchType.LAZY)
|
|
||||||
@JoinColumn(name = "customer_id")
|
|
||||||
private Customer customer;
|
|
||||||
|
|
||||||
@Column(name = "customer_email", nullable = false, length = Integer.MAX_VALUE)
|
|
||||||
private String customerEmail;
|
|
||||||
|
|
||||||
@Column(name = "customer_phone", length = Integer.MAX_VALUE)
|
|
||||||
private String customerPhone;
|
|
||||||
|
|
||||||
@Column(name = "billing_customer_type", nullable = false, length = Integer.MAX_VALUE)
|
|
||||||
private String billingCustomerType;
|
|
||||||
|
|
||||||
@Column(name = "billing_first_name", length = Integer.MAX_VALUE)
|
|
||||||
private String billingFirstName;
|
|
||||||
|
|
||||||
@Column(name = "billing_last_name", length = Integer.MAX_VALUE)
|
|
||||||
private String billingLastName;
|
|
||||||
|
|
||||||
@Column(name = "billing_company_name", length = Integer.MAX_VALUE)
|
|
||||||
private String billingCompanyName;
|
|
||||||
|
|
||||||
@Column(name = "billing_contact_person", length = Integer.MAX_VALUE)
|
|
||||||
private String billingContactPerson;
|
|
||||||
|
|
||||||
@Column(name = "billing_address_line1", nullable = false, length = Integer.MAX_VALUE)
|
|
||||||
private String billingAddressLine1;
|
|
||||||
|
|
||||||
@Column(name = "billing_address_line2", length = Integer.MAX_VALUE)
|
|
||||||
private String billingAddressLine2;
|
|
||||||
|
|
||||||
@Column(name = "billing_zip", nullable = false, length = Integer.MAX_VALUE)
|
|
||||||
private String billingZip;
|
|
||||||
|
|
||||||
@Column(name = "billing_city", nullable = false, length = Integer.MAX_VALUE)
|
|
||||||
private String billingCity;
|
|
||||||
|
|
||||||
@ColumnDefault("'CH'")
|
|
||||||
@Column(name = "billing_country_code", nullable = false, length = 2)
|
|
||||||
private String billingCountryCode;
|
|
||||||
|
|
||||||
@ColumnDefault("true")
|
|
||||||
@Column(name = "shipping_same_as_billing", nullable = false)
|
|
||||||
private Boolean shippingSameAsBilling;
|
|
||||||
|
|
||||||
@Column(name = "shipping_first_name", length = Integer.MAX_VALUE)
|
|
||||||
private String shippingFirstName;
|
|
||||||
|
|
||||||
@Column(name = "shipping_last_name", length = Integer.MAX_VALUE)
|
|
||||||
private String shippingLastName;
|
|
||||||
|
|
||||||
@Column(name = "shipping_company_name", length = Integer.MAX_VALUE)
|
|
||||||
private String shippingCompanyName;
|
|
||||||
|
|
||||||
@Column(name = "shipping_contact_person", length = Integer.MAX_VALUE)
|
|
||||||
private String shippingContactPerson;
|
|
||||||
|
|
||||||
@Column(name = "shipping_address_line1", length = Integer.MAX_VALUE)
|
|
||||||
private String shippingAddressLine1;
|
|
||||||
|
|
||||||
@Column(name = "shipping_address_line2", length = Integer.MAX_VALUE)
|
|
||||||
private String shippingAddressLine2;
|
|
||||||
|
|
||||||
@Column(name = "shipping_zip", length = Integer.MAX_VALUE)
|
|
||||||
private String shippingZip;
|
|
||||||
|
|
||||||
@Column(name = "shipping_city", length = Integer.MAX_VALUE)
|
|
||||||
private String shippingCity;
|
|
||||||
|
|
||||||
@Column(name = "shipping_country_code", length = 2)
|
|
||||||
private String shippingCountryCode;
|
|
||||||
|
|
||||||
@ColumnDefault("'it'")
|
|
||||||
@Column(name = "preferred_language", length = 2)
|
|
||||||
private String preferredLanguage;
|
|
||||||
|
|
||||||
@ColumnDefault("'CHF'")
|
|
||||||
@Column(name = "currency", nullable = false, length = 3)
|
|
||||||
private String currency;
|
|
||||||
|
|
||||||
@ColumnDefault("0.00")
|
|
||||||
@Column(name = "setup_cost_chf", nullable = false, precision = 12, scale = 2)
|
|
||||||
private BigDecimal setupCostChf;
|
|
||||||
|
|
||||||
@ColumnDefault("0.00")
|
|
||||||
@Column(name = "shipping_cost_chf", nullable = false, precision = 12, scale = 2)
|
|
||||||
private BigDecimal shippingCostChf;
|
|
||||||
|
|
||||||
@ColumnDefault("0.00")
|
|
||||||
@Column(name = "discount_chf", nullable = false, precision = 12, scale = 2)
|
|
||||||
private BigDecimal discountChf;
|
|
||||||
|
|
||||||
@ColumnDefault("0.00")
|
|
||||||
@Column(name = "subtotal_chf", nullable = false, precision = 12, scale = 2)
|
|
||||||
private BigDecimal subtotalChf;
|
|
||||||
|
|
||||||
@ColumnDefault("false")
|
|
||||||
@Column(name = "is_cad_order", nullable = false)
|
|
||||||
private Boolean isCadOrder;
|
|
||||||
|
|
||||||
@Column(name = "source_request_id")
|
|
||||||
private UUID sourceRequestId;
|
|
||||||
|
|
||||||
@Column(name = "cad_hours", precision = 10, scale = 2)
|
|
||||||
private BigDecimal cadHours;
|
|
||||||
|
|
||||||
@Column(name = "cad_hourly_rate_chf", precision = 10, scale = 2)
|
|
||||||
private BigDecimal cadHourlyRateChf;
|
|
||||||
|
|
||||||
@ColumnDefault("0.00")
|
|
||||||
@Column(name = "cad_total_chf", nullable = false, precision = 12, scale = 2)
|
|
||||||
private BigDecimal cadTotalChf;
|
|
||||||
|
|
||||||
@ColumnDefault("0.00")
|
|
||||||
@Column(name = "total_chf", nullable = false, precision = 12, scale = 2)
|
|
||||||
private BigDecimal totalChf;
|
|
||||||
|
|
||||||
@ColumnDefault("now()")
|
|
||||||
@Column(name = "created_at", nullable = false)
|
|
||||||
private OffsetDateTime createdAt;
|
|
||||||
|
|
||||||
@ColumnDefault("now()")
|
|
||||||
@Column(name = "updated_at", nullable = false)
|
|
||||||
private OffsetDateTime updatedAt;
|
|
||||||
|
|
||||||
@Column(name = "paid_at")
|
|
||||||
private OffsetDateTime paidAt;
|
|
||||||
|
|
||||||
public UUID getId() {
|
|
||||||
return id;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setId(UUID id) {
|
|
||||||
this.id = id;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Transient
|
|
||||||
public String getOrderNumber() {
|
|
||||||
if (id == null) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
String rawId = id.toString();
|
|
||||||
int dashIndex = rawId.indexOf('-');
|
|
||||||
return dashIndex > 0 ? rawId.substring(0, dashIndex) : rawId;
|
|
||||||
}
|
|
||||||
|
|
||||||
public QuoteSession getSourceQuoteSession() {
|
|
||||||
return sourceQuoteSession;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setSourceQuoteSession(QuoteSession sourceQuoteSession) {
|
|
||||||
this.sourceQuoteSession = sourceQuoteSession;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getStatus() {
|
|
||||||
return status;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setStatus(String status) {
|
|
||||||
this.status = status;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Customer getCustomer() {
|
|
||||||
return customer;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setCustomer(Customer customer) {
|
|
||||||
this.customer = customer;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getCustomerEmail() {
|
|
||||||
return customerEmail;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setCustomerEmail(String customerEmail) {
|
|
||||||
this.customerEmail = customerEmail;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getCustomerPhone() {
|
|
||||||
return customerPhone;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setCustomerPhone(String customerPhone) {
|
|
||||||
this.customerPhone = customerPhone;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getBillingCustomerType() {
|
|
||||||
return billingCustomerType;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setBillingCustomerType(String billingCustomerType) {
|
|
||||||
this.billingCustomerType = billingCustomerType;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getBillingFirstName() {
|
|
||||||
return billingFirstName;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setBillingFirstName(String billingFirstName) {
|
|
||||||
this.billingFirstName = billingFirstName;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getBillingLastName() {
|
|
||||||
return billingLastName;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setBillingLastName(String billingLastName) {
|
|
||||||
this.billingLastName = billingLastName;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getBillingCompanyName() {
|
|
||||||
return billingCompanyName;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setBillingCompanyName(String billingCompanyName) {
|
|
||||||
this.billingCompanyName = billingCompanyName;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getBillingContactPerson() {
|
|
||||||
return billingContactPerson;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setBillingContactPerson(String billingContactPerson) {
|
|
||||||
this.billingContactPerson = billingContactPerson;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getBillingAddressLine1() {
|
|
||||||
return billingAddressLine1;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setBillingAddressLine1(String billingAddressLine1) {
|
|
||||||
this.billingAddressLine1 = billingAddressLine1;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getBillingAddressLine2() {
|
|
||||||
return billingAddressLine2;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setBillingAddressLine2(String billingAddressLine2) {
|
|
||||||
this.billingAddressLine2 = billingAddressLine2;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getBillingZip() {
|
|
||||||
return billingZip;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setBillingZip(String billingZip) {
|
|
||||||
this.billingZip = billingZip;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getBillingCity() {
|
|
||||||
return billingCity;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setBillingCity(String billingCity) {
|
|
||||||
this.billingCity = billingCity;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getBillingCountryCode() {
|
|
||||||
return billingCountryCode;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setBillingCountryCode(String billingCountryCode) {
|
|
||||||
this.billingCountryCode = billingCountryCode;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Boolean getShippingSameAsBilling() {
|
|
||||||
return shippingSameAsBilling;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setShippingSameAsBilling(Boolean shippingSameAsBilling) {
|
|
||||||
this.shippingSameAsBilling = shippingSameAsBilling;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getShippingFirstName() {
|
|
||||||
return shippingFirstName;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setShippingFirstName(String shippingFirstName) {
|
|
||||||
this.shippingFirstName = shippingFirstName;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getShippingLastName() {
|
|
||||||
return shippingLastName;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setShippingLastName(String shippingLastName) {
|
|
||||||
this.shippingLastName = shippingLastName;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getShippingCompanyName() {
|
|
||||||
return shippingCompanyName;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setShippingCompanyName(String shippingCompanyName) {
|
|
||||||
this.shippingCompanyName = shippingCompanyName;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getShippingContactPerson() {
|
|
||||||
return shippingContactPerson;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setShippingContactPerson(String shippingContactPerson) {
|
|
||||||
this.shippingContactPerson = shippingContactPerson;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getShippingAddressLine1() {
|
|
||||||
return shippingAddressLine1;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setShippingAddressLine1(String shippingAddressLine1) {
|
|
||||||
this.shippingAddressLine1 = shippingAddressLine1;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getShippingAddressLine2() {
|
|
||||||
return shippingAddressLine2;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setShippingAddressLine2(String shippingAddressLine2) {
|
|
||||||
this.shippingAddressLine2 = shippingAddressLine2;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getShippingZip() {
|
|
||||||
return shippingZip;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setShippingZip(String shippingZip) {
|
|
||||||
this.shippingZip = shippingZip;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getShippingCity() {
|
|
||||||
return shippingCity;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setShippingCity(String shippingCity) {
|
|
||||||
this.shippingCity = shippingCity;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getShippingCountryCode() {
|
|
||||||
return shippingCountryCode;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setShippingCountryCode(String shippingCountryCode) {
|
|
||||||
this.shippingCountryCode = shippingCountryCode;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getCurrency() {
|
|
||||||
return currency;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setCurrency(String currency) {
|
|
||||||
this.currency = currency;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getPreferredLanguage() {
|
|
||||||
return preferredLanguage;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setPreferredLanguage(String preferredLanguage) {
|
|
||||||
this.preferredLanguage = preferredLanguage;
|
|
||||||
}
|
|
||||||
|
|
||||||
public BigDecimal getSetupCostChf() {
|
|
||||||
return setupCostChf;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setSetupCostChf(BigDecimal setupCostChf) {
|
|
||||||
this.setupCostChf = setupCostChf;
|
|
||||||
}
|
|
||||||
|
|
||||||
public BigDecimal getShippingCostChf() {
|
|
||||||
return shippingCostChf;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setShippingCostChf(BigDecimal shippingCostChf) {
|
|
||||||
this.shippingCostChf = shippingCostChf;
|
|
||||||
}
|
|
||||||
|
|
||||||
public BigDecimal getDiscountChf() {
|
|
||||||
return discountChf;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setDiscountChf(BigDecimal discountChf) {
|
|
||||||
this.discountChf = discountChf;
|
|
||||||
}
|
|
||||||
|
|
||||||
public BigDecimal getSubtotalChf() {
|
|
||||||
return subtotalChf;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setSubtotalChf(BigDecimal subtotalChf) {
|
|
||||||
this.subtotalChf = subtotalChf;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Boolean getIsCadOrder() {
|
|
||||||
return isCadOrder;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setIsCadOrder(Boolean isCadOrder) {
|
|
||||||
this.isCadOrder = isCadOrder;
|
|
||||||
}
|
|
||||||
|
|
||||||
public UUID getSourceRequestId() {
|
|
||||||
return sourceRequestId;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setSourceRequestId(UUID sourceRequestId) {
|
|
||||||
this.sourceRequestId = sourceRequestId;
|
|
||||||
}
|
|
||||||
|
|
||||||
public BigDecimal getCadHours() {
|
|
||||||
return cadHours;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setCadHours(BigDecimal cadHours) {
|
|
||||||
this.cadHours = cadHours;
|
|
||||||
}
|
|
||||||
|
|
||||||
public BigDecimal getCadHourlyRateChf() {
|
|
||||||
return cadHourlyRateChf;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setCadHourlyRateChf(BigDecimal cadHourlyRateChf) {
|
|
||||||
this.cadHourlyRateChf = cadHourlyRateChf;
|
|
||||||
}
|
|
||||||
|
|
||||||
public BigDecimal getCadTotalChf() {
|
|
||||||
return cadTotalChf;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setCadTotalChf(BigDecimal cadTotalChf) {
|
|
||||||
this.cadTotalChf = cadTotalChf;
|
|
||||||
}
|
|
||||||
|
|
||||||
public BigDecimal getTotalChf() {
|
|
||||||
return totalChf;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setTotalChf(BigDecimal totalChf) {
|
|
||||||
this.totalChf = totalChf;
|
|
||||||
}
|
|
||||||
|
|
||||||
public OffsetDateTime getCreatedAt() {
|
|
||||||
return createdAt;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setCreatedAt(OffsetDateTime createdAt) {
|
|
||||||
this.createdAt = createdAt;
|
|
||||||
}
|
|
||||||
|
|
||||||
public OffsetDateTime getUpdatedAt() {
|
|
||||||
return updatedAt;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setUpdatedAt(OffsetDateTime updatedAt) {
|
|
||||||
this.updatedAt = updatedAt;
|
|
||||||
}
|
|
||||||
|
|
||||||
public OffsetDateTime getPaidAt() {
|
|
||||||
return paidAt;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setPaidAt(OffsetDateTime paidAt) {
|
|
||||||
this.paidAt = paidAt;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,319 +0,0 @@
|
|||||||
package com.printcalculator.entity;
|
|
||||||
|
|
||||||
import jakarta.persistence.*;
|
|
||||||
import org.hibernate.annotations.ColumnDefault;
|
|
||||||
import org.hibernate.annotations.OnDelete;
|
|
||||||
import org.hibernate.annotations.OnDeleteAction;
|
|
||||||
|
|
||||||
import java.math.BigDecimal;
|
|
||||||
import java.time.OffsetDateTime;
|
|
||||||
import java.util.UUID;
|
|
||||||
|
|
||||||
@Entity
|
|
||||||
@Table(name = "order_items", indexes = {@Index(name = "ix_order_items_order",
|
|
||||||
columnList = "order_id")})
|
|
||||||
public class OrderItem {
|
|
||||||
@Id
|
|
||||||
@GeneratedValue(strategy = GenerationType.AUTO)
|
|
||||||
@Column(name = "order_item_id", nullable = false)
|
|
||||||
private UUID id;
|
|
||||||
|
|
||||||
@ManyToOne(fetch = FetchType.LAZY, optional = false)
|
|
||||||
@OnDelete(action = OnDeleteAction.CASCADE)
|
|
||||||
@JoinColumn(name = "order_id", nullable = false)
|
|
||||||
private Order order;
|
|
||||||
|
|
||||||
@Column(name = "original_filename", nullable = false, length = Integer.MAX_VALUE)
|
|
||||||
private String originalFilename;
|
|
||||||
|
|
||||||
@Column(name = "stored_relative_path", nullable = false, length = Integer.MAX_VALUE)
|
|
||||||
private String storedRelativePath;
|
|
||||||
|
|
||||||
@Column(name = "stored_filename", nullable = false, length = Integer.MAX_VALUE)
|
|
||||||
private String storedFilename;
|
|
||||||
|
|
||||||
@Column(name = "file_size_bytes")
|
|
||||||
private Long fileSizeBytes;
|
|
||||||
|
|
||||||
@Column(name = "mime_type", length = Integer.MAX_VALUE)
|
|
||||||
private String mimeType;
|
|
||||||
|
|
||||||
@Column(name = "sha256_hex", length = Integer.MAX_VALUE)
|
|
||||||
private String sha256Hex;
|
|
||||||
|
|
||||||
@Column(name = "material_code", nullable = false, length = Integer.MAX_VALUE)
|
|
||||||
private String materialCode;
|
|
||||||
|
|
||||||
@Column(name = "quality", length = Integer.MAX_VALUE)
|
|
||||||
private String quality;
|
|
||||||
|
|
||||||
@Column(name = "nozzle_diameter_mm", precision = 4, scale = 2)
|
|
||||||
private BigDecimal nozzleDiameterMm;
|
|
||||||
|
|
||||||
@Column(name = "layer_height_mm", precision = 5, scale = 3)
|
|
||||||
private BigDecimal layerHeightMm;
|
|
||||||
|
|
||||||
@Column(name = "infill_percent")
|
|
||||||
private Integer infillPercent;
|
|
||||||
|
|
||||||
@Column(name = "infill_pattern", length = Integer.MAX_VALUE)
|
|
||||||
private String infillPattern;
|
|
||||||
|
|
||||||
@Column(name = "supports_enabled")
|
|
||||||
private Boolean supportsEnabled;
|
|
||||||
|
|
||||||
@ManyToOne(fetch = FetchType.LAZY)
|
|
||||||
@JoinColumn(name = "filament_variant_id")
|
|
||||||
private FilamentVariant filamentVariant;
|
|
||||||
|
|
||||||
@Column(name = "color_code", length = Integer.MAX_VALUE)
|
|
||||||
private String colorCode;
|
|
||||||
|
|
||||||
@ColumnDefault("1")
|
|
||||||
@Column(name = "quantity", nullable = false)
|
|
||||||
private Integer quantity;
|
|
||||||
|
|
||||||
@Column(name = "print_time_seconds")
|
|
||||||
private Integer printTimeSeconds;
|
|
||||||
|
|
||||||
@Column(name = "material_grams", precision = 12, scale = 2)
|
|
||||||
private BigDecimal materialGrams;
|
|
||||||
|
|
||||||
@Column(name = "bounding_box_x_mm", precision = 10, scale = 3)
|
|
||||||
private BigDecimal boundingBoxXMm;
|
|
||||||
|
|
||||||
@Column(name = "bounding_box_y_mm", precision = 10, scale = 3)
|
|
||||||
private BigDecimal boundingBoxYMm;
|
|
||||||
|
|
||||||
@Column(name = "bounding_box_z_mm", precision = 10, scale = 3)
|
|
||||||
private BigDecimal boundingBoxZMm;
|
|
||||||
|
|
||||||
@Column(name = "unit_price_chf", nullable = false, precision = 12, scale = 2)
|
|
||||||
private BigDecimal unitPriceChf;
|
|
||||||
|
|
||||||
@Column(name = "line_total_chf", nullable = false, precision = 12, scale = 2)
|
|
||||||
private BigDecimal lineTotalChf;
|
|
||||||
|
|
||||||
@ColumnDefault("now()")
|
|
||||||
@Column(name = "created_at", nullable = false)
|
|
||||||
private OffsetDateTime createdAt;
|
|
||||||
|
|
||||||
@PrePersist
|
|
||||||
private void onCreate() {
|
|
||||||
if (createdAt == null) {
|
|
||||||
createdAt = OffsetDateTime.now();
|
|
||||||
}
|
|
||||||
if (quantity == null) {
|
|
||||||
quantity = 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public UUID getId() {
|
|
||||||
return id;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setId(UUID id) {
|
|
||||||
this.id = id;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Order getOrder() {
|
|
||||||
return order;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setOrder(Order order) {
|
|
||||||
this.order = order;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getOriginalFilename() {
|
|
||||||
return originalFilename;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setOriginalFilename(String originalFilename) {
|
|
||||||
this.originalFilename = originalFilename;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getStoredRelativePath() {
|
|
||||||
return storedRelativePath;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setStoredRelativePath(String storedRelativePath) {
|
|
||||||
this.storedRelativePath = storedRelativePath;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getStoredFilename() {
|
|
||||||
return storedFilename;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setStoredFilename(String storedFilename) {
|
|
||||||
this.storedFilename = storedFilename;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Long getFileSizeBytes() {
|
|
||||||
return fileSizeBytes;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setFileSizeBytes(Long fileSizeBytes) {
|
|
||||||
this.fileSizeBytes = fileSizeBytes;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getMimeType() {
|
|
||||||
return mimeType;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setMimeType(String mimeType) {
|
|
||||||
this.mimeType = mimeType;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getSha256Hex() {
|
|
||||||
return sha256Hex;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setSha256Hex(String sha256Hex) {
|
|
||||||
this.sha256Hex = sha256Hex;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getMaterialCode() {
|
|
||||||
return materialCode;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setMaterialCode(String materialCode) {
|
|
||||||
this.materialCode = materialCode;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getQuality() {
|
|
||||||
return quality;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setQuality(String quality) {
|
|
||||||
this.quality = quality;
|
|
||||||
}
|
|
||||||
|
|
||||||
public BigDecimal getNozzleDiameterMm() {
|
|
||||||
return nozzleDiameterMm;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setNozzleDiameterMm(BigDecimal nozzleDiameterMm) {
|
|
||||||
this.nozzleDiameterMm = nozzleDiameterMm;
|
|
||||||
}
|
|
||||||
|
|
||||||
public BigDecimal getLayerHeightMm() {
|
|
||||||
return layerHeightMm;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setLayerHeightMm(BigDecimal layerHeightMm) {
|
|
||||||
this.layerHeightMm = layerHeightMm;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Integer getInfillPercent() {
|
|
||||||
return infillPercent;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setInfillPercent(Integer infillPercent) {
|
|
||||||
this.infillPercent = infillPercent;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getInfillPattern() {
|
|
||||||
return infillPattern;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setInfillPattern(String infillPattern) {
|
|
||||||
this.infillPattern = infillPattern;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Boolean getSupportsEnabled() {
|
|
||||||
return supportsEnabled;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setSupportsEnabled(Boolean supportsEnabled) {
|
|
||||||
this.supportsEnabled = supportsEnabled;
|
|
||||||
}
|
|
||||||
|
|
||||||
public FilamentVariant getFilamentVariant() {
|
|
||||||
return filamentVariant;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setFilamentVariant(FilamentVariant filamentVariant) {
|
|
||||||
this.filamentVariant = filamentVariant;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getColorCode() {
|
|
||||||
return colorCode;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setColorCode(String colorCode) {
|
|
||||||
this.colorCode = colorCode;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Integer getQuantity() {
|
|
||||||
return quantity;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setQuantity(Integer quantity) {
|
|
||||||
this.quantity = quantity;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Integer getPrintTimeSeconds() {
|
|
||||||
return printTimeSeconds;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setPrintTimeSeconds(Integer printTimeSeconds) {
|
|
||||||
this.printTimeSeconds = printTimeSeconds;
|
|
||||||
}
|
|
||||||
|
|
||||||
public BigDecimal getMaterialGrams() {
|
|
||||||
return materialGrams;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setMaterialGrams(BigDecimal materialGrams) {
|
|
||||||
this.materialGrams = materialGrams;
|
|
||||||
}
|
|
||||||
|
|
||||||
public BigDecimal getBoundingBoxXMm() {
|
|
||||||
return boundingBoxXMm;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setBoundingBoxXMm(BigDecimal boundingBoxXMm) {
|
|
||||||
this.boundingBoxXMm = boundingBoxXMm;
|
|
||||||
}
|
|
||||||
|
|
||||||
public BigDecimal getBoundingBoxYMm() {
|
|
||||||
return boundingBoxYMm;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setBoundingBoxYMm(BigDecimal boundingBoxYMm) {
|
|
||||||
this.boundingBoxYMm = boundingBoxYMm;
|
|
||||||
}
|
|
||||||
|
|
||||||
public BigDecimal getBoundingBoxZMm() {
|
|
||||||
return boundingBoxZMm;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setBoundingBoxZMm(BigDecimal boundingBoxZMm) {
|
|
||||||
this.boundingBoxZMm = boundingBoxZMm;
|
|
||||||
}
|
|
||||||
|
|
||||||
public BigDecimal getUnitPriceChf() {
|
|
||||||
return unitPriceChf;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setUnitPriceChf(BigDecimal unitPriceChf) {
|
|
||||||
this.unitPriceChf = unitPriceChf;
|
|
||||||
}
|
|
||||||
|
|
||||||
public BigDecimal getLineTotalChf() {
|
|
||||||
return lineTotalChf;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setLineTotalChf(BigDecimal lineTotalChf) {
|
|
||||||
this.lineTotalChf = lineTotalChf;
|
|
||||||
}
|
|
||||||
|
|
||||||
public OffsetDateTime getCreatedAt() {
|
|
||||||
return createdAt;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setCreatedAt(OffsetDateTime createdAt) {
|
|
||||||
this.createdAt = createdAt;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,157 +0,0 @@
|
|||||||
package com.printcalculator.entity;
|
|
||||||
|
|
||||||
import jakarta.persistence.*;
|
|
||||||
import org.hibernate.annotations.ColumnDefault;
|
|
||||||
import org.hibernate.annotations.OnDelete;
|
|
||||||
import org.hibernate.annotations.OnDeleteAction;
|
|
||||||
|
|
||||||
import java.math.BigDecimal;
|
|
||||||
import java.time.OffsetDateTime;
|
|
||||||
import java.util.UUID;
|
|
||||||
|
|
||||||
@Entity
|
|
||||||
@Table(name = "payments", indexes = {
|
|
||||||
@Index(name = "ix_payments_order",
|
|
||||||
columnList = "order_id"),
|
|
||||||
@Index(name = "ix_payments_reference",
|
|
||||||
columnList = "payment_reference")})
|
|
||||||
public class Payment {
|
|
||||||
@Id
|
|
||||||
@GeneratedValue(strategy = GenerationType.AUTO)
|
|
||||||
@Column(name = "payment_id", nullable = false)
|
|
||||||
private UUID id;
|
|
||||||
|
|
||||||
@ManyToOne(fetch = FetchType.LAZY, optional = false)
|
|
||||||
@OnDelete(action = OnDeleteAction.CASCADE)
|
|
||||||
@JoinColumn(name = "order_id", nullable = false)
|
|
||||||
private Order order;
|
|
||||||
|
|
||||||
@Column(name = "method", nullable = false, length = Integer.MAX_VALUE)
|
|
||||||
private String method;
|
|
||||||
|
|
||||||
@Column(name = "status", nullable = false, length = Integer.MAX_VALUE)
|
|
||||||
private String status;
|
|
||||||
|
|
||||||
@ColumnDefault("'CHF'")
|
|
||||||
@Column(name = "currency", nullable = false, length = 3)
|
|
||||||
private String currency;
|
|
||||||
|
|
||||||
@Column(name = "amount_chf", nullable = false, precision = 12, scale = 2)
|
|
||||||
private BigDecimal amountChf;
|
|
||||||
|
|
||||||
@Column(name = "payment_reference", length = Integer.MAX_VALUE)
|
|
||||||
private String paymentReference;
|
|
||||||
|
|
||||||
@Column(name = "provider_transaction_id", length = Integer.MAX_VALUE)
|
|
||||||
private String providerTransactionId;
|
|
||||||
|
|
||||||
@Column(name = "qr_payload", length = Integer.MAX_VALUE)
|
|
||||||
private String qrPayload;
|
|
||||||
|
|
||||||
@ColumnDefault("now()")
|
|
||||||
@Column(name = "initiated_at", nullable = false)
|
|
||||||
private OffsetDateTime initiatedAt;
|
|
||||||
|
|
||||||
@Column(name = "reported_at")
|
|
||||||
private OffsetDateTime reportedAt;
|
|
||||||
|
|
||||||
@Column(name = "received_at")
|
|
||||||
private OffsetDateTime receivedAt;
|
|
||||||
|
|
||||||
public UUID getId() {
|
|
||||||
return id;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setId(UUID id) {
|
|
||||||
this.id = id;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Order getOrder() {
|
|
||||||
return order;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setOrder(Order order) {
|
|
||||||
this.order = order;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getMethod() {
|
|
||||||
return method;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setMethod(String method) {
|
|
||||||
this.method = method;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getStatus() {
|
|
||||||
return status;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setStatus(String status) {
|
|
||||||
this.status = status;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getCurrency() {
|
|
||||||
return currency;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setCurrency(String currency) {
|
|
||||||
this.currency = currency;
|
|
||||||
}
|
|
||||||
|
|
||||||
public BigDecimal getAmountChf() {
|
|
||||||
return amountChf;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setAmountChf(BigDecimal amountChf) {
|
|
||||||
this.amountChf = amountChf;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getPaymentReference() {
|
|
||||||
return paymentReference;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setPaymentReference(String paymentReference) {
|
|
||||||
this.paymentReference = paymentReference;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getProviderTransactionId() {
|
|
||||||
return providerTransactionId;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setProviderTransactionId(String providerTransactionId) {
|
|
||||||
this.providerTransactionId = providerTransactionId;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getQrPayload() {
|
|
||||||
return qrPayload;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setQrPayload(String qrPayload) {
|
|
||||||
this.qrPayload = qrPayload;
|
|
||||||
}
|
|
||||||
|
|
||||||
public OffsetDateTime getInitiatedAt() {
|
|
||||||
return initiatedAt;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setInitiatedAt(OffsetDateTime initiatedAt) {
|
|
||||||
this.initiatedAt = initiatedAt;
|
|
||||||
}
|
|
||||||
|
|
||||||
public OffsetDateTime getReportedAt() {
|
|
||||||
return reportedAt;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setReportedAt(OffsetDateTime reportedAt) {
|
|
||||||
this.reportedAt = reportedAt;
|
|
||||||
}
|
|
||||||
|
|
||||||
public OffsetDateTime getReceivedAt() {
|
|
||||||
return receivedAt;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setReceivedAt(OffsetDateTime receivedAt) {
|
|
||||||
this.receivedAt = receivedAt;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,141 +0,0 @@
|
|||||||
package com.printcalculator.entity;
|
|
||||||
|
|
||||||
import jakarta.persistence.*;
|
|
||||||
import org.hibernate.annotations.ColumnDefault;
|
|
||||||
|
|
||||||
import java.math.BigDecimal;
|
|
||||||
import java.time.OffsetDateTime;
|
|
||||||
|
|
||||||
@Entity
|
|
||||||
@Table(name = "pricing_policy")
|
|
||||||
public class PricingPolicy {
|
|
||||||
@Id
|
|
||||||
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
|
||||||
@Column(name = "pricing_policy_id", nullable = false)
|
|
||||||
private Long id;
|
|
||||||
|
|
||||||
@Column(name = "policy_name", nullable = false, length = Integer.MAX_VALUE)
|
|
||||||
private String policyName;
|
|
||||||
|
|
||||||
@Column(name = "valid_from", nullable = false)
|
|
||||||
private OffsetDateTime validFrom;
|
|
||||||
|
|
||||||
@Column(name = "valid_to")
|
|
||||||
private OffsetDateTime validTo;
|
|
||||||
|
|
||||||
@Column(name = "electricity_cost_chf_per_kwh", nullable = false, precision = 10, scale = 6)
|
|
||||||
private BigDecimal electricityCostChfPerKwh;
|
|
||||||
|
|
||||||
@ColumnDefault("20.000")
|
|
||||||
@Column(name = "markup_percent", nullable = false, precision = 6, scale = 3)
|
|
||||||
private BigDecimal markupPercent;
|
|
||||||
|
|
||||||
@ColumnDefault("0.00")
|
|
||||||
@Column(name = "fixed_job_fee_chf", nullable = false, precision = 10, scale = 2)
|
|
||||||
private BigDecimal fixedJobFeeChf;
|
|
||||||
|
|
||||||
@ColumnDefault("0.00")
|
|
||||||
@Column(name = "nozzle_change_base_fee_chf", nullable = false, precision = 10, scale = 2)
|
|
||||||
private BigDecimal nozzleChangeBaseFeeChf;
|
|
||||||
|
|
||||||
@ColumnDefault("0.00")
|
|
||||||
@Column(name = "cad_cost_chf_per_hour", nullable = false, precision = 10, scale = 2)
|
|
||||||
private BigDecimal cadCostChfPerHour;
|
|
||||||
|
|
||||||
@ColumnDefault("true")
|
|
||||||
@Column(name = "is_active", nullable = false)
|
|
||||||
private Boolean isActive;
|
|
||||||
|
|
||||||
@ColumnDefault("now()")
|
|
||||||
@Column(name = "created_at", nullable = false)
|
|
||||||
private OffsetDateTime createdAt;
|
|
||||||
|
|
||||||
public Long getId() {
|
|
||||||
return id;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setId(Long id) {
|
|
||||||
this.id = id;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getPolicyName() {
|
|
||||||
return policyName;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setPolicyName(String policyName) {
|
|
||||||
this.policyName = policyName;
|
|
||||||
}
|
|
||||||
|
|
||||||
public OffsetDateTime getValidFrom() {
|
|
||||||
return validFrom;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setValidFrom(OffsetDateTime validFrom) {
|
|
||||||
this.validFrom = validFrom;
|
|
||||||
}
|
|
||||||
|
|
||||||
public OffsetDateTime getValidTo() {
|
|
||||||
return validTo;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setValidTo(OffsetDateTime validTo) {
|
|
||||||
this.validTo = validTo;
|
|
||||||
}
|
|
||||||
|
|
||||||
public BigDecimal getElectricityCostChfPerKwh() {
|
|
||||||
return electricityCostChfPerKwh;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setElectricityCostChfPerKwh(BigDecimal electricityCostChfPerKwh) {
|
|
||||||
this.electricityCostChfPerKwh = electricityCostChfPerKwh;
|
|
||||||
}
|
|
||||||
|
|
||||||
public BigDecimal getMarkupPercent() {
|
|
||||||
return markupPercent;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setMarkupPercent(BigDecimal markupPercent) {
|
|
||||||
this.markupPercent = markupPercent;
|
|
||||||
}
|
|
||||||
|
|
||||||
public BigDecimal getFixedJobFeeChf() {
|
|
||||||
return fixedJobFeeChf;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setFixedJobFeeChf(BigDecimal fixedJobFeeChf) {
|
|
||||||
this.fixedJobFeeChf = fixedJobFeeChf;
|
|
||||||
}
|
|
||||||
|
|
||||||
public BigDecimal getNozzleChangeBaseFeeChf() {
|
|
||||||
return nozzleChangeBaseFeeChf;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setNozzleChangeBaseFeeChf(BigDecimal nozzleChangeBaseFeeChf) {
|
|
||||||
this.nozzleChangeBaseFeeChf = nozzleChangeBaseFeeChf;
|
|
||||||
}
|
|
||||||
|
|
||||||
public BigDecimal getCadCostChfPerHour() {
|
|
||||||
return cadCostChfPerHour;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setCadCostChfPerHour(BigDecimal cadCostChfPerHour) {
|
|
||||||
this.cadCostChfPerHour = cadCostChfPerHour;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Boolean getIsActive() {
|
|
||||||
return isActive;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setIsActive(Boolean isActive) {
|
|
||||||
this.isActive = isActive;
|
|
||||||
}
|
|
||||||
|
|
||||||
public OffsetDateTime getCreatedAt() {
|
|
||||||
return createdAt;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setCreatedAt(OffsetDateTime createdAt) {
|
|
||||||
this.createdAt = createdAt;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,68 +0,0 @@
|
|||||||
package com.printcalculator.entity;
|
|
||||||
|
|
||||||
import jakarta.persistence.*;
|
|
||||||
|
|
||||||
import java.math.BigDecimal;
|
|
||||||
|
|
||||||
@Entity
|
|
||||||
@Table(name = "pricing_policy_machine_hour_tier")
|
|
||||||
public class PricingPolicyMachineHourTier {
|
|
||||||
@Id
|
|
||||||
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
|
||||||
@Column(name = "pricing_policy_machine_hour_tier_id", nullable = false)
|
|
||||||
private Long id;
|
|
||||||
|
|
||||||
@ManyToOne(fetch = FetchType.LAZY, optional = false)
|
|
||||||
@JoinColumn(name = "pricing_policy_id", nullable = false)
|
|
||||||
private PricingPolicy pricingPolicy;
|
|
||||||
|
|
||||||
@Column(name = "tier_start_hours", nullable = false, precision = 10, scale = 2)
|
|
||||||
private BigDecimal tierStartHours;
|
|
||||||
|
|
||||||
@Column(name = "tier_end_hours", precision = 10, scale = 2)
|
|
||||||
private BigDecimal tierEndHours;
|
|
||||||
|
|
||||||
@Column(name = "machine_cost_chf_per_hour", nullable = false, precision = 10, scale = 2)
|
|
||||||
private BigDecimal machineCostChfPerHour;
|
|
||||||
|
|
||||||
public Long getId() {
|
|
||||||
return id;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setId(Long id) {
|
|
||||||
this.id = id;
|
|
||||||
}
|
|
||||||
|
|
||||||
public PricingPolicy getPricingPolicy() {
|
|
||||||
return pricingPolicy;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setPricingPolicy(PricingPolicy pricingPolicy) {
|
|
||||||
this.pricingPolicy = pricingPolicy;
|
|
||||||
}
|
|
||||||
|
|
||||||
public BigDecimal getTierStartHours() {
|
|
||||||
return tierStartHours;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setTierStartHours(BigDecimal tierStartHours) {
|
|
||||||
this.tierStartHours = tierStartHours;
|
|
||||||
}
|
|
||||||
|
|
||||||
public BigDecimal getTierEndHours() {
|
|
||||||
return tierEndHours;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setTierEndHours(BigDecimal tierEndHours) {
|
|
||||||
this.tierEndHours = tierEndHours;
|
|
||||||
}
|
|
||||||
|
|
||||||
public BigDecimal getMachineCostChfPerHour() {
|
|
||||||
return machineCostChfPerHour;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setMachineCostChfPerHour(BigDecimal machineCostChfPerHour) {
|
|
||||||
this.machineCostChfPerHour = machineCostChfPerHour;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,46 +0,0 @@
|
|||||||
package com.printcalculator.entity;
|
|
||||||
|
|
||||||
import jakarta.persistence.Column;
|
|
||||||
import jakarta.persistence.Entity;
|
|
||||||
import jakarta.persistence.Table;
|
|
||||||
import org.hibernate.annotations.Immutable;
|
|
||||||
|
|
||||||
import jakarta.persistence.Id;
|
|
||||||
|
|
||||||
@Entity
|
|
||||||
@Immutable
|
|
||||||
@Table(name = "printer_fleet_current")
|
|
||||||
public class PrinterFleetCurrent {
|
|
||||||
@Id
|
|
||||||
@Column(name = "fleet_id")
|
|
||||||
private Long id;
|
|
||||||
|
|
||||||
@Column(name = "weighted_average_power_watts")
|
|
||||||
private Integer weightedAveragePowerWatts;
|
|
||||||
|
|
||||||
@Column(name = "fleet_max_build_x_mm")
|
|
||||||
private Integer fleetMaxBuildXMm;
|
|
||||||
|
|
||||||
@Column(name = "fleet_max_build_y_mm")
|
|
||||||
private Integer fleetMaxBuildYMm;
|
|
||||||
|
|
||||||
@Column(name = "fleet_max_build_z_mm")
|
|
||||||
private Integer fleetMaxBuildZMm;
|
|
||||||
|
|
||||||
public Integer getWeightedAveragePowerWatts() {
|
|
||||||
return weightedAveragePowerWatts;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Integer getFleetMaxBuildXMm() {
|
|
||||||
return fleetMaxBuildXMm;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Integer getFleetMaxBuildYMm() {
|
|
||||||
return fleetMaxBuildYMm;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Integer getFleetMaxBuildZMm() {
|
|
||||||
return fleetMaxBuildZMm;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,116 +0,0 @@
|
|||||||
package com.printcalculator.entity;
|
|
||||||
|
|
||||||
import jakarta.persistence.*;
|
|
||||||
import org.hibernate.annotations.ColumnDefault;
|
|
||||||
|
|
||||||
import java.math.BigDecimal;
|
|
||||||
import java.time.OffsetDateTime;
|
|
||||||
|
|
||||||
@Entity
|
|
||||||
@Table(name = "printer_machine")
|
|
||||||
public class PrinterMachine {
|
|
||||||
@Id
|
|
||||||
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
|
||||||
@Column(name = "printer_machine_id", nullable = false)
|
|
||||||
private Long id;
|
|
||||||
|
|
||||||
@Column(name = "printer_display_name", nullable = false, length = Integer.MAX_VALUE)
|
|
||||||
private String printerDisplayName;
|
|
||||||
|
|
||||||
@Column(name = "build_volume_x_mm", nullable = false)
|
|
||||||
private Integer buildVolumeXMm;
|
|
||||||
|
|
||||||
@Column(name = "build_volume_y_mm", nullable = false)
|
|
||||||
private Integer buildVolumeYMm;
|
|
||||||
|
|
||||||
@Column(name = "build_volume_z_mm", nullable = false)
|
|
||||||
private Integer buildVolumeZMm;
|
|
||||||
|
|
||||||
@Column(name = "power_watts", nullable = false)
|
|
||||||
private Integer powerWatts;
|
|
||||||
|
|
||||||
@ColumnDefault("1.000")
|
|
||||||
@Column(name = "fleet_weight", nullable = false, precision = 6, scale = 3)
|
|
||||||
private BigDecimal fleetWeight;
|
|
||||||
|
|
||||||
@ColumnDefault("true")
|
|
||||||
@Column(name = "is_active", nullable = false)
|
|
||||||
private Boolean isActive;
|
|
||||||
|
|
||||||
@ColumnDefault("now()")
|
|
||||||
@Column(name = "created_at", nullable = false)
|
|
||||||
private OffsetDateTime createdAt;
|
|
||||||
|
|
||||||
public Long getId() {
|
|
||||||
return id;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setId(Long id) {
|
|
||||||
this.id = id;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getPrinterDisplayName() {
|
|
||||||
return printerDisplayName;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setPrinterDisplayName(String printerDisplayName) {
|
|
||||||
this.printerDisplayName = printerDisplayName;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Integer getBuildVolumeXMm() {
|
|
||||||
return buildVolumeXMm;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setBuildVolumeXMm(Integer buildVolumeXMm) {
|
|
||||||
this.buildVolumeXMm = buildVolumeXMm;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Integer getBuildVolumeYMm() {
|
|
||||||
return buildVolumeYMm;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setBuildVolumeYMm(Integer buildVolumeYMm) {
|
|
||||||
this.buildVolumeYMm = buildVolumeYMm;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Integer getBuildVolumeZMm() {
|
|
||||||
return buildVolumeZMm;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setBuildVolumeZMm(Integer buildVolumeZMm) {
|
|
||||||
this.buildVolumeZMm = buildVolumeZMm;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Integer getPowerWatts() {
|
|
||||||
return powerWatts;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setPowerWatts(Integer powerWatts) {
|
|
||||||
this.powerWatts = powerWatts;
|
|
||||||
}
|
|
||||||
|
|
||||||
public BigDecimal getFleetWeight() {
|
|
||||||
return fleetWeight;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setFleetWeight(BigDecimal fleetWeight) {
|
|
||||||
this.fleetWeight = fleetWeight;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Boolean getIsActive() {
|
|
||||||
return isActive;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setIsActive(Boolean isActive) {
|
|
||||||
this.isActive = isActive;
|
|
||||||
}
|
|
||||||
|
|
||||||
public OffsetDateTime getCreatedAt() {
|
|
||||||
return createdAt;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setCreatedAt(OffsetDateTime createdAt) {
|
|
||||||
this.createdAt = createdAt;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,85 +0,0 @@
|
|||||||
package com.printcalculator.entity;
|
|
||||||
|
|
||||||
import jakarta.persistence.*;
|
|
||||||
import org.hibernate.annotations.ColumnDefault;
|
|
||||||
|
|
||||||
import java.math.BigDecimal;
|
|
||||||
|
|
||||||
@Entity
|
|
||||||
@Table(name = "printer_machine_profile", uniqueConstraints = {
|
|
||||||
@UniqueConstraint(name = "ux_printer_machine_profile_machine_nozzle", columnNames = {
|
|
||||||
"printer_machine_id", "nozzle_diameter_mm"
|
|
||||||
})
|
|
||||||
})
|
|
||||||
public class PrinterMachineProfile {
|
|
||||||
@Id
|
|
||||||
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
|
||||||
@Column(name = "printer_machine_profile_id", nullable = false)
|
|
||||||
private Long id;
|
|
||||||
|
|
||||||
@ManyToOne(fetch = FetchType.LAZY, optional = false)
|
|
||||||
@JoinColumn(name = "printer_machine_id", nullable = false)
|
|
||||||
private PrinterMachine printerMachine;
|
|
||||||
|
|
||||||
@Column(name = "nozzle_diameter_mm", nullable = false, precision = 4, scale = 2)
|
|
||||||
private BigDecimal nozzleDiameterMm;
|
|
||||||
|
|
||||||
@Column(name = "orca_machine_profile_name", nullable = false, length = Integer.MAX_VALUE)
|
|
||||||
private String orcaMachineProfileName;
|
|
||||||
|
|
||||||
@ColumnDefault("false")
|
|
||||||
@Column(name = "is_default", nullable = false)
|
|
||||||
private Boolean isDefault;
|
|
||||||
|
|
||||||
@ColumnDefault("true")
|
|
||||||
@Column(name = "is_active", nullable = false)
|
|
||||||
private Boolean isActive;
|
|
||||||
|
|
||||||
public Long getId() {
|
|
||||||
return id;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setId(Long id) {
|
|
||||||
this.id = id;
|
|
||||||
}
|
|
||||||
|
|
||||||
public PrinterMachine getPrinterMachine() {
|
|
||||||
return printerMachine;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setPrinterMachine(PrinterMachine printerMachine) {
|
|
||||||
this.printerMachine = printerMachine;
|
|
||||||
}
|
|
||||||
|
|
||||||
public BigDecimal getNozzleDiameterMm() {
|
|
||||||
return nozzleDiameterMm;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setNozzleDiameterMm(BigDecimal nozzleDiameterMm) {
|
|
||||||
this.nozzleDiameterMm = nozzleDiameterMm;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getOrcaMachineProfileName() {
|
|
||||||
return orcaMachineProfileName;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setOrcaMachineProfileName(String orcaMachineProfileName) {
|
|
||||||
this.orcaMachineProfileName = orcaMachineProfileName;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Boolean getIsDefault() {
|
|
||||||
return isDefault;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setIsDefault(Boolean isDefault) {
|
|
||||||
this.isDefault = isDefault;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Boolean getIsActive() {
|
|
||||||
return isActive;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setIsActive(Boolean isActive) {
|
|
||||||
this.isActive = isActive;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,305 +0,0 @@
|
|||||||
package com.printcalculator.entity;
|
|
||||||
|
|
||||||
import jakarta.persistence.*;
|
|
||||||
import org.hibernate.annotations.ColumnDefault;
|
|
||||||
import org.hibernate.annotations.JdbcTypeCode;
|
|
||||||
import org.hibernate.annotations.OnDelete;
|
|
||||||
import org.hibernate.annotations.OnDeleteAction;
|
|
||||||
import org.hibernate.type.SqlTypes;
|
|
||||||
|
|
||||||
import java.math.BigDecimal;
|
|
||||||
import java.time.OffsetDateTime;
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.UUID;
|
|
||||||
|
|
||||||
@Entity
|
|
||||||
@Table(name = "quote_line_items", indexes = {@Index(name = "ix_quote_line_items_session",
|
|
||||||
columnList = "quote_session_id")})
|
|
||||||
public class QuoteLineItem {
|
|
||||||
@Id
|
|
||||||
@GeneratedValue(strategy = GenerationType.AUTO)
|
|
||||||
@Column(name = "quote_line_item_id", nullable = false)
|
|
||||||
private UUID id;
|
|
||||||
|
|
||||||
@ManyToOne(fetch = FetchType.LAZY, optional = false)
|
|
||||||
@OnDelete(action = OnDeleteAction.CASCADE)
|
|
||||||
@JoinColumn(name = "quote_session_id", nullable = false)
|
|
||||||
@com.fasterxml.jackson.annotation.JsonIgnore
|
|
||||||
private QuoteSession quoteSession;
|
|
||||||
|
|
||||||
@Column(name = "status", nullable = false, length = Integer.MAX_VALUE)
|
|
||||||
private String status;
|
|
||||||
|
|
||||||
@Column(name = "original_filename", nullable = false, length = Integer.MAX_VALUE)
|
|
||||||
private String originalFilename;
|
|
||||||
|
|
||||||
@ColumnDefault("1")
|
|
||||||
@Column(name = "quantity", nullable = false)
|
|
||||||
private Integer quantity;
|
|
||||||
|
|
||||||
@Column(name = "color_code", length = Integer.MAX_VALUE)
|
|
||||||
private String colorCode;
|
|
||||||
|
|
||||||
@ManyToOne(fetch = FetchType.LAZY)
|
|
||||||
@JoinColumn(name = "filament_variant_id")
|
|
||||||
@com.fasterxml.jackson.annotation.JsonIgnore
|
|
||||||
private FilamentVariant filamentVariant;
|
|
||||||
|
|
||||||
@Column(name = "material_code", length = Integer.MAX_VALUE)
|
|
||||||
private String materialCode;
|
|
||||||
|
|
||||||
@Column(name = "quality", length = Integer.MAX_VALUE)
|
|
||||||
private String quality;
|
|
||||||
|
|
||||||
@Column(name = "nozzle_diameter_mm", precision = 5, scale = 2)
|
|
||||||
private BigDecimal nozzleDiameterMm;
|
|
||||||
|
|
||||||
@Column(name = "layer_height_mm", precision = 6, scale = 3)
|
|
||||||
private BigDecimal layerHeightMm;
|
|
||||||
|
|
||||||
@Column(name = "infill_percent")
|
|
||||||
private Integer infillPercent;
|
|
||||||
|
|
||||||
@Column(name = "infill_pattern", length = Integer.MAX_VALUE)
|
|
||||||
private String infillPattern;
|
|
||||||
|
|
||||||
@Column(name = "supports_enabled")
|
|
||||||
private Boolean supportsEnabled;
|
|
||||||
|
|
||||||
@Column(name = "bounding_box_x_mm", precision = 10, scale = 3)
|
|
||||||
private BigDecimal boundingBoxXMm;
|
|
||||||
|
|
||||||
@Column(name = "bounding_box_y_mm", precision = 10, scale = 3)
|
|
||||||
private BigDecimal boundingBoxYMm;
|
|
||||||
|
|
||||||
@Column(name = "bounding_box_z_mm", precision = 10, scale = 3)
|
|
||||||
private BigDecimal boundingBoxZMm;
|
|
||||||
|
|
||||||
@Column(name = "print_time_seconds")
|
|
||||||
private Integer printTimeSeconds;
|
|
||||||
|
|
||||||
@Column(name = "material_grams", precision = 12, scale = 2)
|
|
||||||
private BigDecimal materialGrams;
|
|
||||||
|
|
||||||
@Column(name = "unit_price_chf", precision = 12, scale = 2)
|
|
||||||
private BigDecimal unitPriceChf;
|
|
||||||
|
|
||||||
@JdbcTypeCode(SqlTypes.JSON)
|
|
||||||
@Column(name = "pricing_breakdown")
|
|
||||||
private Map<String, Object> pricingBreakdown;
|
|
||||||
|
|
||||||
@Column(name = "error_message", length = Integer.MAX_VALUE)
|
|
||||||
private String errorMessage;
|
|
||||||
|
|
||||||
@Column(name = "stored_path", length = Integer.MAX_VALUE)
|
|
||||||
private String storedPath;
|
|
||||||
|
|
||||||
@ColumnDefault("now()")
|
|
||||||
@Column(name = "created_at", nullable = false)
|
|
||||||
private OffsetDateTime createdAt;
|
|
||||||
|
|
||||||
@ColumnDefault("now()")
|
|
||||||
@Column(name = "updated_at", nullable = false)
|
|
||||||
private OffsetDateTime updatedAt;
|
|
||||||
|
|
||||||
public UUID getId() {
|
|
||||||
return id;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setId(UUID id) {
|
|
||||||
this.id = id;
|
|
||||||
}
|
|
||||||
|
|
||||||
public QuoteSession getQuoteSession() {
|
|
||||||
return quoteSession;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setQuoteSession(QuoteSession quoteSession) {
|
|
||||||
this.quoteSession = quoteSession;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getStatus() {
|
|
||||||
return status;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setStatus(String status) {
|
|
||||||
this.status = status;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getOriginalFilename() {
|
|
||||||
return originalFilename;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setOriginalFilename(String originalFilename) {
|
|
||||||
this.originalFilename = originalFilename;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Integer getQuantity() {
|
|
||||||
return quantity;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setQuantity(Integer quantity) {
|
|
||||||
this.quantity = quantity;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getColorCode() {
|
|
||||||
return colorCode;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setColorCode(String colorCode) {
|
|
||||||
this.colorCode = colorCode;
|
|
||||||
}
|
|
||||||
|
|
||||||
public FilamentVariant getFilamentVariant() {
|
|
||||||
return filamentVariant;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setFilamentVariant(FilamentVariant filamentVariant) {
|
|
||||||
this.filamentVariant = filamentVariant;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getMaterialCode() {
|
|
||||||
return materialCode;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setMaterialCode(String materialCode) {
|
|
||||||
this.materialCode = materialCode;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getQuality() {
|
|
||||||
return quality;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setQuality(String quality) {
|
|
||||||
this.quality = quality;
|
|
||||||
}
|
|
||||||
|
|
||||||
public BigDecimal getNozzleDiameterMm() {
|
|
||||||
return nozzleDiameterMm;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setNozzleDiameterMm(BigDecimal nozzleDiameterMm) {
|
|
||||||
this.nozzleDiameterMm = nozzleDiameterMm;
|
|
||||||
}
|
|
||||||
|
|
||||||
public BigDecimal getLayerHeightMm() {
|
|
||||||
return layerHeightMm;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setLayerHeightMm(BigDecimal layerHeightMm) {
|
|
||||||
this.layerHeightMm = layerHeightMm;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Integer getInfillPercent() {
|
|
||||||
return infillPercent;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setInfillPercent(Integer infillPercent) {
|
|
||||||
this.infillPercent = infillPercent;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getInfillPattern() {
|
|
||||||
return infillPattern;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setInfillPattern(String infillPattern) {
|
|
||||||
this.infillPattern = infillPattern;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Boolean getSupportsEnabled() {
|
|
||||||
return supportsEnabled;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setSupportsEnabled(Boolean supportsEnabled) {
|
|
||||||
this.supportsEnabled = supportsEnabled;
|
|
||||||
}
|
|
||||||
|
|
||||||
public BigDecimal getBoundingBoxXMm() {
|
|
||||||
return boundingBoxXMm;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setBoundingBoxXMm(BigDecimal boundingBoxXMm) {
|
|
||||||
this.boundingBoxXMm = boundingBoxXMm;
|
|
||||||
}
|
|
||||||
|
|
||||||
public BigDecimal getBoundingBoxYMm() {
|
|
||||||
return boundingBoxYMm;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setBoundingBoxYMm(BigDecimal boundingBoxYMm) {
|
|
||||||
this.boundingBoxYMm = boundingBoxYMm;
|
|
||||||
}
|
|
||||||
|
|
||||||
public BigDecimal getBoundingBoxZMm() {
|
|
||||||
return boundingBoxZMm;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setBoundingBoxZMm(BigDecimal boundingBoxZMm) {
|
|
||||||
this.boundingBoxZMm = boundingBoxZMm;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Integer getPrintTimeSeconds() {
|
|
||||||
return printTimeSeconds;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setPrintTimeSeconds(Integer printTimeSeconds) {
|
|
||||||
this.printTimeSeconds = printTimeSeconds;
|
|
||||||
}
|
|
||||||
|
|
||||||
public BigDecimal getMaterialGrams() {
|
|
||||||
return materialGrams;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setMaterialGrams(BigDecimal materialGrams) {
|
|
||||||
this.materialGrams = materialGrams;
|
|
||||||
}
|
|
||||||
|
|
||||||
public BigDecimal getUnitPriceChf() {
|
|
||||||
return unitPriceChf;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setUnitPriceChf(BigDecimal unitPriceChf) {
|
|
||||||
this.unitPriceChf = unitPriceChf;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Map<String, Object> getPricingBreakdown() {
|
|
||||||
return pricingBreakdown;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setPricingBreakdown(Map<String, Object> pricingBreakdown) {
|
|
||||||
this.pricingBreakdown = pricingBreakdown;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getErrorMessage() {
|
|
||||||
return errorMessage;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setErrorMessage(String errorMessage) {
|
|
||||||
this.errorMessage = errorMessage;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getStoredPath() {
|
|
||||||
return storedPath;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setStoredPath(String storedPath) {
|
|
||||||
this.storedPath = storedPath;
|
|
||||||
}
|
|
||||||
|
|
||||||
public OffsetDateTime getCreatedAt() {
|
|
||||||
return createdAt;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setCreatedAt(OffsetDateTime createdAt) {
|
|
||||||
this.createdAt = createdAt;
|
|
||||||
}
|
|
||||||
|
|
||||||
public OffsetDateTime getUpdatedAt() {
|
|
||||||
return updatedAt;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setUpdatedAt(OffsetDateTime updatedAt) {
|
|
||||||
this.updatedAt = updatedAt;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,209 +0,0 @@
|
|||||||
package com.printcalculator.entity;
|
|
||||||
|
|
||||||
import jakarta.persistence.*;
|
|
||||||
import org.hibernate.annotations.ColumnDefault;
|
|
||||||
|
|
||||||
import java.math.BigDecimal;
|
|
||||||
import java.time.OffsetDateTime;
|
|
||||||
import java.util.UUID;
|
|
||||||
|
|
||||||
@Entity
|
|
||||||
@Table(name = "quote_sessions", indexes = {
|
|
||||||
@Index(name = "ix_quote_sessions_status",
|
|
||||||
columnList = "status"),
|
|
||||||
@Index(name = "ix_quote_sessions_expires_at",
|
|
||||||
columnList = "expires_at")})
|
|
||||||
public class QuoteSession {
|
|
||||||
@Id
|
|
||||||
@GeneratedValue(strategy = GenerationType.AUTO)
|
|
||||||
@Column(name = "quote_session_id", nullable = false)
|
|
||||||
private UUID id;
|
|
||||||
|
|
||||||
@Column(name = "status", nullable = false, length = Integer.MAX_VALUE)
|
|
||||||
private String status;
|
|
||||||
|
|
||||||
@Column(name = "pricing_version", nullable = false, length = Integer.MAX_VALUE)
|
|
||||||
private String pricingVersion;
|
|
||||||
|
|
||||||
@Column(name = "material_code", nullable = false, length = Integer.MAX_VALUE)
|
|
||||||
private String materialCode;
|
|
||||||
|
|
||||||
@Column(name = "nozzle_diameter_mm", precision = 5, scale = 2)
|
|
||||||
private BigDecimal nozzleDiameterMm;
|
|
||||||
|
|
||||||
@Column(name = "layer_height_mm", precision = 6, scale = 3)
|
|
||||||
private BigDecimal layerHeightMm;
|
|
||||||
|
|
||||||
@Column(name = "infill_pattern", length = Integer.MAX_VALUE)
|
|
||||||
private String infillPattern;
|
|
||||||
|
|
||||||
@Column(name = "infill_percent")
|
|
||||||
private Integer infillPercent;
|
|
||||||
|
|
||||||
@ColumnDefault("false")
|
|
||||||
@Column(name = "supports_enabled", nullable = false)
|
|
||||||
private Boolean supportsEnabled;
|
|
||||||
|
|
||||||
@Column(name = "notes", length = Integer.MAX_VALUE)
|
|
||||||
private String notes;
|
|
||||||
|
|
||||||
@ColumnDefault("0.00")
|
|
||||||
@Column(name = "setup_cost_chf", nullable = false, precision = 12, scale = 2)
|
|
||||||
private BigDecimal setupCostChf;
|
|
||||||
|
|
||||||
@ColumnDefault("now()")
|
|
||||||
@Column(name = "created_at", nullable = false)
|
|
||||||
private OffsetDateTime createdAt;
|
|
||||||
|
|
||||||
@Column(name = "expires_at", nullable = false)
|
|
||||||
private OffsetDateTime expiresAt;
|
|
||||||
|
|
||||||
@Column(name = "converted_order_id")
|
|
||||||
private UUID convertedOrderId;
|
|
||||||
|
|
||||||
@Column(name = "source_request_id")
|
|
||||||
private UUID sourceRequestId;
|
|
||||||
|
|
||||||
@Column(name = "cad_hours", precision = 10, scale = 2)
|
|
||||||
private BigDecimal cadHours;
|
|
||||||
|
|
||||||
@Column(name = "cad_hourly_rate_chf", precision = 10, scale = 2)
|
|
||||||
private BigDecimal cadHourlyRateChf;
|
|
||||||
|
|
||||||
public UUID getId() {
|
|
||||||
return id;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setId(UUID id) {
|
|
||||||
this.id = id;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getStatus() {
|
|
||||||
return status;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setStatus(String status) {
|
|
||||||
this.status = status;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getPricingVersion() {
|
|
||||||
return pricingVersion;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setPricingVersion(String pricingVersion) {
|
|
||||||
this.pricingVersion = pricingVersion;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getMaterialCode() {
|
|
||||||
return materialCode;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setMaterialCode(String materialCode) {
|
|
||||||
this.materialCode = materialCode;
|
|
||||||
}
|
|
||||||
|
|
||||||
public BigDecimal getNozzleDiameterMm() {
|
|
||||||
return nozzleDiameterMm;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setNozzleDiameterMm(BigDecimal nozzleDiameterMm) {
|
|
||||||
this.nozzleDiameterMm = nozzleDiameterMm;
|
|
||||||
}
|
|
||||||
|
|
||||||
public BigDecimal getLayerHeightMm() {
|
|
||||||
return layerHeightMm;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setLayerHeightMm(BigDecimal layerHeightMm) {
|
|
||||||
this.layerHeightMm = layerHeightMm;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getInfillPattern() {
|
|
||||||
return infillPattern;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setInfillPattern(String infillPattern) {
|
|
||||||
this.infillPattern = infillPattern;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Integer getInfillPercent() {
|
|
||||||
return infillPercent;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setInfillPercent(Integer infillPercent) {
|
|
||||||
this.infillPercent = infillPercent;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Boolean getSupportsEnabled() {
|
|
||||||
return supportsEnabled;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setSupportsEnabled(Boolean supportsEnabled) {
|
|
||||||
this.supportsEnabled = supportsEnabled;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getNotes() {
|
|
||||||
return notes;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setNotes(String notes) {
|
|
||||||
this.notes = notes;
|
|
||||||
}
|
|
||||||
|
|
||||||
public BigDecimal getSetupCostChf() {
|
|
||||||
return setupCostChf;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setSetupCostChf(BigDecimal setupCostChf) {
|
|
||||||
this.setupCostChf = setupCostChf;
|
|
||||||
}
|
|
||||||
|
|
||||||
public OffsetDateTime getCreatedAt() {
|
|
||||||
return createdAt;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setCreatedAt(OffsetDateTime createdAt) {
|
|
||||||
this.createdAt = createdAt;
|
|
||||||
}
|
|
||||||
|
|
||||||
public OffsetDateTime getExpiresAt() {
|
|
||||||
return expiresAt;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setExpiresAt(OffsetDateTime expiresAt) {
|
|
||||||
this.expiresAt = expiresAt;
|
|
||||||
}
|
|
||||||
|
|
||||||
public UUID getConvertedOrderId() {
|
|
||||||
return convertedOrderId;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setConvertedOrderId(UUID convertedOrderId) {
|
|
||||||
this.convertedOrderId = convertedOrderId;
|
|
||||||
}
|
|
||||||
|
|
||||||
public UUID getSourceRequestId() {
|
|
||||||
return sourceRequestId;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setSourceRequestId(UUID sourceRequestId) {
|
|
||||||
this.sourceRequestId = sourceRequestId;
|
|
||||||
}
|
|
||||||
|
|
||||||
public BigDecimal getCadHours() {
|
|
||||||
return cadHours;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setCadHours(BigDecimal cadHours) {
|
|
||||||
this.cadHours = cadHours;
|
|
||||||
}
|
|
||||||
|
|
||||||
public BigDecimal getCadHourlyRateChf() {
|
|
||||||
return cadHourlyRateChf;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setCadHourlyRateChf(BigDecimal cadHourlyRateChf) {
|
|
||||||
this.cadHourlyRateChf = cadHourlyRateChf;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,16 +0,0 @@
|
|||||||
package com.printcalculator.event;
|
|
||||||
|
|
||||||
import com.printcalculator.entity.Order;
|
|
||||||
import lombok.Getter;
|
|
||||||
import org.springframework.context.ApplicationEvent;
|
|
||||||
|
|
||||||
@Getter
|
|
||||||
public class OrderCreatedEvent extends ApplicationEvent {
|
|
||||||
|
|
||||||
private final Order order;
|
|
||||||
|
|
||||||
public OrderCreatedEvent(Object source, Order order) {
|
|
||||||
super(source);
|
|
||||||
this.order = order;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,16 +0,0 @@
|
|||||||
package com.printcalculator.event;
|
|
||||||
|
|
||||||
import com.printcalculator.entity.Order;
|
|
||||||
import lombok.Getter;
|
|
||||||
import org.springframework.context.ApplicationEvent;
|
|
||||||
|
|
||||||
@Getter
|
|
||||||
public class OrderShippedEvent extends ApplicationEvent {
|
|
||||||
|
|
||||||
private final Order order;
|
|
||||||
|
|
||||||
public OrderShippedEvent(Object source, Order order) {
|
|
||||||
super(source);
|
|
||||||
this.order = order;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,24 +0,0 @@
|
|||||||
package com.printcalculator.event;
|
|
||||||
|
|
||||||
import com.printcalculator.entity.Order;
|
|
||||||
import com.printcalculator.entity.Payment;
|
|
||||||
import org.springframework.context.ApplicationEvent;
|
|
||||||
|
|
||||||
public class PaymentConfirmedEvent extends ApplicationEvent {
|
|
||||||
private final Order order;
|
|
||||||
private final Payment payment;
|
|
||||||
|
|
||||||
public PaymentConfirmedEvent(Object source, Order order, Payment payment) {
|
|
||||||
super(source);
|
|
||||||
this.order = order;
|
|
||||||
this.payment = payment;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Order getOrder() {
|
|
||||||
return order;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Payment getPayment() {
|
|
||||||
return payment;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,25 +0,0 @@
|
|||||||
package com.printcalculator.event;
|
|
||||||
|
|
||||||
import com.printcalculator.entity.Order;
|
|
||||||
import com.printcalculator.entity.Payment;
|
|
||||||
import org.springframework.context.ApplicationEvent;
|
|
||||||
|
|
||||||
public class PaymentReportedEvent extends ApplicationEvent {
|
|
||||||
|
|
||||||
private final Order order;
|
|
||||||
private final Payment payment;
|
|
||||||
|
|
||||||
public PaymentReportedEvent(Object source, Order order, Payment payment) {
|
|
||||||
super(source);
|
|
||||||
this.order = order;
|
|
||||||
this.payment = payment;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Order getOrder() {
|
|
||||||
return order;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Payment getPayment() {
|
|
||||||
return payment;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,582 +0,0 @@
|
|||||||
package com.printcalculator.event.listener;
|
|
||||||
|
|
||||||
import com.printcalculator.entity.Order;
|
|
||||||
import com.printcalculator.entity.OrderItem;
|
|
||||||
import com.printcalculator.entity.Payment;
|
|
||||||
import com.printcalculator.event.OrderCreatedEvent;
|
|
||||||
import com.printcalculator.event.OrderShippedEvent;
|
|
||||||
import com.printcalculator.event.PaymentConfirmedEvent;
|
|
||||||
import com.printcalculator.event.PaymentReportedEvent;
|
|
||||||
import com.printcalculator.repository.OrderItemRepository;
|
|
||||||
import com.printcalculator.service.payment.InvoicePdfRenderingService;
|
|
||||||
import com.printcalculator.service.payment.QrBillService;
|
|
||||||
import com.printcalculator.service.storage.StorageService;
|
|
||||||
import com.printcalculator.service.email.EmailNotificationService;
|
|
||||||
import lombok.RequiredArgsConstructor;
|
|
||||||
import lombok.extern.slf4j.Slf4j;
|
|
||||||
import org.springframework.beans.factory.annotation.Value;
|
|
||||||
import org.springframework.context.event.EventListener;
|
|
||||||
import org.springframework.scheduling.annotation.Async;
|
|
||||||
import org.springframework.stereotype.Component;
|
|
||||||
|
|
||||||
import java.text.NumberFormat;
|
|
||||||
import java.time.Year;
|
|
||||||
import java.time.format.DateTimeFormatter;
|
|
||||||
import java.time.format.FormatStyle;
|
|
||||||
import java.util.Currency;
|
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Locale;
|
|
||||||
import java.util.Map;
|
|
||||||
import java.nio.file.Paths;
|
|
||||||
|
|
||||||
@Slf4j
|
|
||||||
@Component
|
|
||||||
@RequiredArgsConstructor
|
|
||||||
public class OrderEmailListener {
|
|
||||||
|
|
||||||
private static final String DEFAULT_LANGUAGE = "it";
|
|
||||||
|
|
||||||
private final EmailNotificationService emailNotificationService;
|
|
||||||
private final InvoicePdfRenderingService invoicePdfRenderingService;
|
|
||||||
private final OrderItemRepository orderItemRepository;
|
|
||||||
private final QrBillService qrBillService;
|
|
||||||
private final StorageService storageService;
|
|
||||||
|
|
||||||
@Value("${app.mail.admin.enabled:true}")
|
|
||||||
private boolean adminMailEnabled;
|
|
||||||
|
|
||||||
@Value("${app.mail.admin.address:}")
|
|
||||||
private String adminMailAddress;
|
|
||||||
|
|
||||||
@Value("${app.frontend.base-url:http://localhost:4200}")
|
|
||||||
private String frontendBaseUrl;
|
|
||||||
|
|
||||||
@Async
|
|
||||||
@EventListener
|
|
||||||
public void handleOrderCreatedEvent(OrderCreatedEvent event) {
|
|
||||||
Order order = event.getOrder();
|
|
||||||
log.info("Processing OrderCreatedEvent for order id: {}", order.getId());
|
|
||||||
|
|
||||||
try {
|
|
||||||
sendCustomerConfirmationEmail(order);
|
|
||||||
|
|
||||||
if (adminMailEnabled && adminMailAddress != null && !adminMailAddress.isEmpty()) {
|
|
||||||
sendAdminNotificationEmail(order);
|
|
||||||
}
|
|
||||||
} catch (Exception e) {
|
|
||||||
log.error("Failed to process email notifications for order id: {}", order.getId(), e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Async
|
|
||||||
@EventListener
|
|
||||||
public void handlePaymentReportedEvent(PaymentReportedEvent event) {
|
|
||||||
Order order = event.getOrder();
|
|
||||||
log.info("Processing PaymentReportedEvent for order id: {}", order.getId());
|
|
||||||
|
|
||||||
try {
|
|
||||||
sendPaymentReportedEmail(order);
|
|
||||||
} catch (Exception e) {
|
|
||||||
log.error("Failed to send payment reported email for order id: {}", order.getId(), e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Async
|
|
||||||
@EventListener
|
|
||||||
public void handlePaymentConfirmedEvent(PaymentConfirmedEvent event) {
|
|
||||||
Order order = event.getOrder();
|
|
||||||
Payment payment = event.getPayment();
|
|
||||||
log.info("Processing PaymentConfirmedEvent for order id: {}", order.getId());
|
|
||||||
|
|
||||||
try {
|
|
||||||
sendPaidInvoiceEmail(order, payment);
|
|
||||||
} catch (Exception e) {
|
|
||||||
log.error("Failed to send paid invoice email for order id: {}", order.getId(), e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Async
|
|
||||||
@EventListener
|
|
||||||
public void handleOrderShippedEvent(OrderShippedEvent event) {
|
|
||||||
Order order = event.getOrder();
|
|
||||||
log.info("Processing OrderShippedEvent for order id: {}", order.getId());
|
|
||||||
|
|
||||||
try {
|
|
||||||
sendOrderShippedEmail(order);
|
|
||||||
} catch (Exception e) {
|
|
||||||
log.error("Failed to send order shipped email for order id: {}", order.getId(), e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void sendCustomerConfirmationEmail(Order order) {
|
|
||||||
String language = resolveLanguage(order.getPreferredLanguage());
|
|
||||||
String orderNumber = getDisplayOrderNumber(order);
|
|
||||||
|
|
||||||
Map<String, Object> templateData = buildBaseTemplateData(order, language);
|
|
||||||
String subject = applyOrderConfirmationTexts(templateData, language, orderNumber);
|
|
||||||
byte[] confirmationPdf = loadOrGenerateConfirmationPdf(order);
|
|
||||||
|
|
||||||
emailNotificationService.sendEmailWithAttachment(
|
|
||||||
order.getCustomer().getEmail(),
|
|
||||||
subject,
|
|
||||||
"order-confirmation",
|
|
||||||
templateData,
|
|
||||||
buildConfirmationAttachmentName(language, orderNumber),
|
|
||||||
confirmationPdf
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void sendPaymentReportedEmail(Order order) {
|
|
||||||
String language = resolveLanguage(order.getPreferredLanguage());
|
|
||||||
String orderNumber = getDisplayOrderNumber(order);
|
|
||||||
|
|
||||||
Map<String, Object> templateData = buildBaseTemplateData(order, language);
|
|
||||||
String subject = applyPaymentReportedTexts(templateData, language, orderNumber);
|
|
||||||
|
|
||||||
emailNotificationService.sendEmail(
|
|
||||||
order.getCustomer().getEmail(),
|
|
||||||
subject,
|
|
||||||
"payment-reported",
|
|
||||||
templateData
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void sendPaidInvoiceEmail(Order order, Payment payment) {
|
|
||||||
String language = resolveLanguage(order.getPreferredLanguage());
|
|
||||||
String orderNumber = getDisplayOrderNumber(order);
|
|
||||||
|
|
||||||
Map<String, Object> templateData = buildBaseTemplateData(order, language);
|
|
||||||
String subject = applyPaymentConfirmedTexts(templateData, language, orderNumber);
|
|
||||||
|
|
||||||
byte[] pdf = null;
|
|
||||||
try {
|
|
||||||
List<OrderItem> items = orderItemRepository.findByOrder_Id(order.getId());
|
|
||||||
pdf = invoicePdfRenderingService.generateDocumentPdf(order, items, false, qrBillService, payment);
|
|
||||||
} catch (Exception e) {
|
|
||||||
log.error("Failed to generate PDF for paid invoice email: {}", e.getMessage(), e);
|
|
||||||
}
|
|
||||||
|
|
||||||
emailNotificationService.sendEmailWithAttachment(
|
|
||||||
order.getCustomer().getEmail(),
|
|
||||||
subject,
|
|
||||||
"payment-confirmed",
|
|
||||||
templateData,
|
|
||||||
buildPaidInvoiceAttachmentName(language, orderNumber),
|
|
||||||
pdf
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void sendOrderShippedEmail(Order order) {
|
|
||||||
String language = resolveLanguage(order.getPreferredLanguage());
|
|
||||||
String orderNumber = getDisplayOrderNumber(order);
|
|
||||||
|
|
||||||
Map<String, Object> templateData = buildBaseTemplateData(order, language);
|
|
||||||
String subject = applyOrderShippedTexts(templateData, language, orderNumber);
|
|
||||||
|
|
||||||
emailNotificationService.sendEmail(
|
|
||||||
order.getCustomer().getEmail(),
|
|
||||||
subject,
|
|
||||||
"order-shipped",
|
|
||||||
templateData
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void sendAdminNotificationEmail(Order order) {
|
|
||||||
String orderNumber = getDisplayOrderNumber(order);
|
|
||||||
Map<String, Object> templateData = buildBaseTemplateData(order, DEFAULT_LANGUAGE);
|
|
||||||
templateData.put("customerName", buildCustomerFullName(order));
|
|
||||||
|
|
||||||
templateData.put("emailTitle", "Nuovo ordine ricevuto");
|
|
||||||
templateData.put("headlineText", "Nuovo ordine #" + orderNumber);
|
|
||||||
templateData.put("greetingText", "Ciao team,");
|
|
||||||
templateData.put("introText", "Un nuovo ordine e' stato creato dal cliente.");
|
|
||||||
templateData.put("detailsTitleText", "Dettagli ordine");
|
|
||||||
templateData.put("labelOrderNumber", "Numero ordine");
|
|
||||||
templateData.put("labelDate", "Data");
|
|
||||||
templateData.put("labelTotal", "Totale");
|
|
||||||
templateData.put("orderDetailsCtaText", "Apri dettaglio ordine");
|
|
||||||
templateData.put("attachmentHintText", "La conferma cliente e il QR bill sono stati salvati nella cartella documenti dell'ordine.");
|
|
||||||
templateData.put("supportText", "Controlla i dettagli e procedi con la gestione operativa.");
|
|
||||||
templateData.put("footerText", "Notifica automatica sistema ordini.");
|
|
||||||
|
|
||||||
emailNotificationService.sendEmail(
|
|
||||||
adminMailAddress,
|
|
||||||
"Nuovo Ordine Ricevuto #" + orderNumber + " - " + buildCustomerFullName(order),
|
|
||||||
"order-confirmation",
|
|
||||||
templateData
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
private Map<String, Object> buildBaseTemplateData(Order order, String language) {
|
|
||||||
Locale locale = localeForLanguage(language);
|
|
||||||
NumberFormat currencyFormatter = NumberFormat.getCurrencyInstance(locale);
|
|
||||||
currencyFormatter.setCurrency(Currency.getInstance("CHF"));
|
|
||||||
|
|
||||||
Map<String, Object> templateData = new HashMap<>();
|
|
||||||
templateData.put("customerName", buildCustomerFirstName(order, language));
|
|
||||||
templateData.put("orderId", order.getId());
|
|
||||||
templateData.put("orderNumber", getDisplayOrderNumber(order));
|
|
||||||
templateData.put("orderDetailsUrl", buildOrderDetailsUrl(order, language));
|
|
||||||
templateData.put(
|
|
||||||
"orderDate",
|
|
||||||
order.getCreatedAt().format(DateTimeFormatter.ofLocalizedDateTime(FormatStyle.MEDIUM).withLocale(locale))
|
|
||||||
);
|
|
||||||
templateData.put("totalCost", currencyFormatter.format(order.getTotalChf()));
|
|
||||||
templateData.put("currentYear", Year.now().getValue());
|
|
||||||
return templateData;
|
|
||||||
}
|
|
||||||
|
|
||||||
private String applyOrderConfirmationTexts(Map<String, Object> templateData, String language, String orderNumber) {
|
|
||||||
return switch (language) {
|
|
||||||
case "en" -> {
|
|
||||||
templateData.put("emailTitle", "Order Confirmation");
|
|
||||||
templateData.put("headlineText", "Thank you for your order #" + orderNumber);
|
|
||||||
templateData.put("greetingText", "Hi " + templateData.get("customerName") + ",");
|
|
||||||
templateData.put("introText", "We received your order and started processing it.");
|
|
||||||
templateData.put("detailsTitleText", "Order details");
|
|
||||||
templateData.put("labelOrderNumber", "Order number");
|
|
||||||
templateData.put("labelDate", "Date");
|
|
||||||
templateData.put("labelTotal", "Total");
|
|
||||||
templateData.put("orderDetailsCtaText", "View order status");
|
|
||||||
templateData.put("attachmentHintText", "Attached you can find the order confirmation PDF with the QR bill.");
|
|
||||||
templateData.put("supportText", "If you have questions, reply to this email and we will help you.");
|
|
||||||
templateData.put("footerText", "Automated message from 3D-Fab.");
|
|
||||||
yield "Order Confirmation #" + orderNumber + " - 3D-Fab";
|
|
||||||
}
|
|
||||||
case "de" -> {
|
|
||||||
templateData.put("emailTitle", "Bestellbestaetigung");
|
|
||||||
templateData.put("headlineText", "Danke fuer Ihre Bestellung #" + orderNumber);
|
|
||||||
templateData.put("greetingText", "Hallo " + templateData.get("customerName") + ",");
|
|
||||||
templateData.put("introText", "Wir haben Ihre Bestellung erhalten und mit der Bearbeitung begonnen.");
|
|
||||||
templateData.put("detailsTitleText", "Bestelldetails");
|
|
||||||
templateData.put("labelOrderNumber", "Bestellnummer");
|
|
||||||
templateData.put("labelDate", "Datum");
|
|
||||||
templateData.put("labelTotal", "Gesamtbetrag");
|
|
||||||
templateData.put("orderDetailsCtaText", "Bestellstatus ansehen");
|
|
||||||
templateData.put("attachmentHintText", "Im Anhang finden Sie die Bestellbestaetigung mit QR-Rechnung.");
|
|
||||||
templateData.put("supportText", "Bei Fragen antworten Sie einfach auf diese E-Mail.");
|
|
||||||
templateData.put("footerText", "Automatische Nachricht von 3D-Fab.");
|
|
||||||
yield "Bestellbestaetigung #" + orderNumber + " - 3D-Fab";
|
|
||||||
}
|
|
||||||
case "fr" -> {
|
|
||||||
templateData.put("emailTitle", "Confirmation de commande");
|
|
||||||
templateData.put("headlineText", "Merci pour votre commande #" + orderNumber);
|
|
||||||
templateData.put("greetingText", "Bonjour " + templateData.get("customerName") + ",");
|
|
||||||
templateData.put("introText", "Nous avons recu votre commande et commence son traitement.");
|
|
||||||
templateData.put("detailsTitleText", "Details de commande");
|
|
||||||
templateData.put("labelOrderNumber", "Numero de commande");
|
|
||||||
templateData.put("labelDate", "Date");
|
|
||||||
templateData.put("labelTotal", "Total");
|
|
||||||
templateData.put("orderDetailsCtaText", "Voir le statut de la commande");
|
|
||||||
templateData.put("attachmentHintText", "Vous trouverez en piece jointe la confirmation de commande avec la facture QR.");
|
|
||||||
templateData.put("supportText", "Si vous avez des questions, repondez a cet email.");
|
|
||||||
templateData.put("footerText", "Message automatique de 3D-Fab.");
|
|
||||||
yield "Confirmation de commande #" + orderNumber + " - 3D-Fab";
|
|
||||||
}
|
|
||||||
default -> {
|
|
||||||
templateData.put("emailTitle", "Conferma ordine");
|
|
||||||
templateData.put("headlineText", "Grazie per il tuo ordine #" + orderNumber);
|
|
||||||
templateData.put("greetingText", "Ciao " + templateData.get("customerName") + ",");
|
|
||||||
templateData.put("introText", "Abbiamo ricevuto il tuo ordine e iniziato l'elaborazione.");
|
|
||||||
templateData.put("detailsTitleText", "Dettagli ordine");
|
|
||||||
templateData.put("labelOrderNumber", "Numero ordine");
|
|
||||||
templateData.put("labelDate", "Data");
|
|
||||||
templateData.put("labelTotal", "Totale");
|
|
||||||
templateData.put("orderDetailsCtaText", "Visualizza stato ordine");
|
|
||||||
templateData.put("attachmentHintText", "In allegato trovi la conferma ordine in PDF con QR bill.");
|
|
||||||
templateData.put("supportText", "Se hai domande, rispondi a questa email e ti aiutiamo subito.");
|
|
||||||
templateData.put("footerText", "Messaggio automatico di 3D-Fab.");
|
|
||||||
yield "Conferma Ordine #" + orderNumber + " - 3D-Fab";
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
private String applyPaymentReportedTexts(Map<String, Object> templateData, String language, String orderNumber) {
|
|
||||||
return switch (language) {
|
|
||||||
case "en" -> {
|
|
||||||
templateData.put("emailTitle", "Payment Reported");
|
|
||||||
templateData.put("headlineText", "Payment reported for order #" + orderNumber);
|
|
||||||
templateData.put("greetingText", "Hi " + templateData.get("customerName") + ",");
|
|
||||||
templateData.put("introText", "We received your payment report and our team is now verifying it.");
|
|
||||||
templateData.put("statusText", "Current status: Payment under verification.");
|
|
||||||
templateData.put("orderDetailsCtaText", "Check order status");
|
|
||||||
templateData.put("supportText", "You will receive another email as soon as the payment is confirmed.");
|
|
||||||
templateData.put("footerText", "Automated message from 3D-Fab.");
|
|
||||||
templateData.put("labelOrderNumber", "Order number");
|
|
||||||
templateData.put("labelTotal", "Total");
|
|
||||||
yield "We are verifying your payment (Order #" + orderNumber + ")";
|
|
||||||
}
|
|
||||||
case "de" -> {
|
|
||||||
templateData.put("emailTitle", "Zahlung gemeldet");
|
|
||||||
templateData.put("headlineText", "Zahlung fuer Bestellung #" + orderNumber + " gemeldet");
|
|
||||||
templateData.put("greetingText", "Hallo " + templateData.get("customerName") + ",");
|
|
||||||
templateData.put("introText", "Wir haben Ihre Zahlungsmitteilung erhalten und pruefen sie aktuell.");
|
|
||||||
templateData.put("statusText", "Aktueller Status: Zahlung in Pruefung.");
|
|
||||||
templateData.put("orderDetailsCtaText", "Bestellstatus ansehen");
|
|
||||||
templateData.put("supportText", "Sobald die Zahlung bestaetigt ist, erhalten Sie eine weitere E-Mail.");
|
|
||||||
templateData.put("footerText", "Automatische Nachricht von 3D-Fab.");
|
|
||||||
templateData.put("labelOrderNumber", "Bestellnummer");
|
|
||||||
templateData.put("labelTotal", "Gesamtbetrag");
|
|
||||||
yield "Wir pruefen Ihre Zahlung (Bestellung #" + orderNumber + ")";
|
|
||||||
}
|
|
||||||
case "fr" -> {
|
|
||||||
templateData.put("emailTitle", "Paiement signale");
|
|
||||||
templateData.put("headlineText", "Paiement signale pour la commande #" + orderNumber);
|
|
||||||
templateData.put("greetingText", "Bonjour " + templateData.get("customerName") + ",");
|
|
||||||
templateData.put("introText", "Nous avons recu votre signalement de paiement et nous le verifions.");
|
|
||||||
templateData.put("statusText", "Statut actuel: Paiement en verification.");
|
|
||||||
templateData.put("orderDetailsCtaText", "Consulter le statut de la commande");
|
|
||||||
templateData.put("supportText", "Vous recevrez un nouvel email des que le paiement sera confirme.");
|
|
||||||
templateData.put("footerText", "Message automatique de 3D-Fab.");
|
|
||||||
templateData.put("labelOrderNumber", "Numero de commande");
|
|
||||||
templateData.put("labelTotal", "Total");
|
|
||||||
yield "Nous verifions votre paiement (Commande #" + orderNumber + ")";
|
|
||||||
}
|
|
||||||
default -> {
|
|
||||||
templateData.put("emailTitle", "Pagamento segnalato");
|
|
||||||
templateData.put("headlineText", "Pagamento segnalato per ordine #" + orderNumber);
|
|
||||||
templateData.put("greetingText", "Ciao " + templateData.get("customerName") + ",");
|
|
||||||
templateData.put("introText", "Abbiamo ricevuto la tua segnalazione di pagamento e la stiamo verificando.");
|
|
||||||
templateData.put("statusText", "Stato attuale: pagamento in verifica.");
|
|
||||||
templateData.put("orderDetailsCtaText", "Controlla lo stato ordine");
|
|
||||||
templateData.put("supportText", "Riceverai una nuova email non appena il pagamento sara' confermato.");
|
|
||||||
templateData.put("footerText", "Messaggio automatico di 3D-Fab.");
|
|
||||||
templateData.put("labelOrderNumber", "Numero ordine");
|
|
||||||
templateData.put("labelTotal", "Totale");
|
|
||||||
yield "Stiamo verificando il tuo pagamento (Ordine #" + orderNumber + ")";
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
private String applyPaymentConfirmedTexts(Map<String, Object> templateData, String language, String orderNumber) {
|
|
||||||
return switch (language) {
|
|
||||||
case "en" -> {
|
|
||||||
templateData.put("emailTitle", "Payment Confirmed");
|
|
||||||
templateData.put("headlineText", "Payment confirmed for order #" + orderNumber);
|
|
||||||
templateData.put("greetingText", "Hi " + templateData.get("customerName") + ",");
|
|
||||||
templateData.put("introText", "Your payment has been confirmed and the order moved into production.");
|
|
||||||
templateData.put("statusText", "Current status: In production.");
|
|
||||||
templateData.put("attachmentHintText", "The paid invoice PDF is attached to this email.");
|
|
||||||
templateData.put("orderDetailsCtaText", "View order status");
|
|
||||||
templateData.put("supportText", "We will notify you again when the shipment is ready.");
|
|
||||||
templateData.put("footerText", "Automated message from 3D-Fab.");
|
|
||||||
templateData.put("labelOrderNumber", "Order number");
|
|
||||||
templateData.put("labelTotal", "Total");
|
|
||||||
yield "Payment confirmed (Order #" + orderNumber + ") - 3D-Fab";
|
|
||||||
}
|
|
||||||
case "de" -> {
|
|
||||||
templateData.put("emailTitle", "Zahlung bestaetigt");
|
|
||||||
templateData.put("headlineText", "Zahlung fuer Bestellung #" + orderNumber + " bestaetigt");
|
|
||||||
templateData.put("greetingText", "Hallo " + templateData.get("customerName") + ",");
|
|
||||||
templateData.put("introText", "Ihre Zahlung wurde bestaetigt und die Bestellung ist jetzt in Produktion.");
|
|
||||||
templateData.put("statusText", "Aktueller Status: In Produktion.");
|
|
||||||
templateData.put("attachmentHintText", "Die bezahlte Rechnung als PDF ist dieser E-Mail beigefuegt.");
|
|
||||||
templateData.put("orderDetailsCtaText", "Bestellstatus ansehen");
|
|
||||||
templateData.put("supportText", "Wir informieren Sie erneut, sobald der Versand bereit ist.");
|
|
||||||
templateData.put("footerText", "Automatische Nachricht von 3D-Fab.");
|
|
||||||
templateData.put("labelOrderNumber", "Bestellnummer");
|
|
||||||
templateData.put("labelTotal", "Gesamtbetrag");
|
|
||||||
yield "Zahlung bestaetigt (Bestellung #" + orderNumber + ") - 3D-Fab";
|
|
||||||
}
|
|
||||||
case "fr" -> {
|
|
||||||
templateData.put("emailTitle", "Paiement confirme");
|
|
||||||
templateData.put("headlineText", "Paiement confirme pour la commande #" + orderNumber);
|
|
||||||
templateData.put("greetingText", "Bonjour " + templateData.get("customerName") + ",");
|
|
||||||
templateData.put("introText", "Votre paiement est confirme et la commande est passe en production.");
|
|
||||||
templateData.put("statusText", "Statut actuel: En production.");
|
|
||||||
templateData.put("attachmentHintText", "La facture payee en PDF est jointe a cet email.");
|
|
||||||
templateData.put("orderDetailsCtaText", "Voir le statut de la commande");
|
|
||||||
templateData.put("supportText", "Nous vous informerons a nouveau des que l'expedition sera prete.");
|
|
||||||
templateData.put("footerText", "Message automatique de 3D-Fab.");
|
|
||||||
templateData.put("labelOrderNumber", "Numero de commande");
|
|
||||||
templateData.put("labelTotal", "Total");
|
|
||||||
yield "Paiement confirme (Commande #" + orderNumber + ") - 3D-Fab";
|
|
||||||
}
|
|
||||||
default -> {
|
|
||||||
templateData.put("emailTitle", "Pagamento confermato");
|
|
||||||
templateData.put("headlineText", "Pagamento confermato per ordine #" + orderNumber);
|
|
||||||
templateData.put("greetingText", "Ciao " + templateData.get("customerName") + ",");
|
|
||||||
templateData.put("introText", "Il tuo pagamento e' stato confermato e l'ordine e' entrato in produzione.");
|
|
||||||
templateData.put("statusText", "Stato attuale: in produzione.");
|
|
||||||
templateData.put("attachmentHintText", "In allegato trovi la fattura saldata in PDF.");
|
|
||||||
templateData.put("orderDetailsCtaText", "Visualizza stato ordine");
|
|
||||||
templateData.put("supportText", "Ti aggiorneremo di nuovo quando la spedizione sara' pronta.");
|
|
||||||
templateData.put("footerText", "Messaggio automatico di 3D-Fab.");
|
|
||||||
templateData.put("labelOrderNumber", "Numero ordine");
|
|
||||||
templateData.put("labelTotal", "Totale");
|
|
||||||
yield "Pagamento confermato (Ordine #" + orderNumber + ") - 3D-Fab";
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
private String applyOrderShippedTexts(Map<String, Object> templateData, String language, String orderNumber) {
|
|
||||||
return switch (language) {
|
|
||||||
case "en" -> {
|
|
||||||
templateData.put("emailTitle", "Order Shipped");
|
|
||||||
templateData.put("headlineText", "Your order #" + orderNumber + " has been shipped");
|
|
||||||
templateData.put("greetingText", "Hi " + templateData.get("customerName") + ",");
|
|
||||||
templateData.put("introText", "Good news: your package has left our workshop and is on its way.");
|
|
||||||
templateData.put("statusText", "Current status: Shipped.");
|
|
||||||
templateData.put("orderDetailsCtaText", "View order status");
|
|
||||||
templateData.put("supportText", "If you need assistance, reply to this email.");
|
|
||||||
templateData.put("footerText", "Automated message from 3D-Fab.");
|
|
||||||
templateData.put("labelOrderNumber", "Order number");
|
|
||||||
templateData.put("labelTotal", "Total");
|
|
||||||
yield "Your order has been shipped (Order #" + orderNumber + ") - 3D-Fab";
|
|
||||||
}
|
|
||||||
case "de" -> {
|
|
||||||
templateData.put("emailTitle", "Bestellung versandt");
|
|
||||||
templateData.put("headlineText", "Ihre Bestellung #" + orderNumber + " wurde versandt");
|
|
||||||
templateData.put("greetingText", "Hallo " + templateData.get("customerName") + ",");
|
|
||||||
templateData.put("introText", "Gute Nachricht: Ihr Paket hat unsere Werkstatt verlassen und ist unterwegs.");
|
|
||||||
templateData.put("statusText", "Aktueller Status: Versandt.");
|
|
||||||
templateData.put("orderDetailsCtaText", "Bestellstatus ansehen");
|
|
||||||
templateData.put("supportText", "Wenn Sie Hilfe benoetigen, antworten Sie auf diese E-Mail.");
|
|
||||||
templateData.put("footerText", "Automatische Nachricht von 3D-Fab.");
|
|
||||||
templateData.put("labelOrderNumber", "Bestellnummer");
|
|
||||||
templateData.put("labelTotal", "Gesamtbetrag");
|
|
||||||
yield "Ihre Bestellung wurde versandt (Bestellung #" + orderNumber + ") - 3D-Fab";
|
|
||||||
}
|
|
||||||
case "fr" -> {
|
|
||||||
templateData.put("emailTitle", "Commande expediee");
|
|
||||||
templateData.put("headlineText", "Votre commande #" + orderNumber + " a ete expediee");
|
|
||||||
templateData.put("greetingText", "Bonjour " + templateData.get("customerName") + ",");
|
|
||||||
templateData.put("introText", "Bonne nouvelle: votre colis a quitte notre atelier et est en route.");
|
|
||||||
templateData.put("statusText", "Statut actuel: Expediee.");
|
|
||||||
templateData.put("orderDetailsCtaText", "Voir le statut de la commande");
|
|
||||||
templateData.put("supportText", "Si vous avez besoin d'aide, repondez a cet email.");
|
|
||||||
templateData.put("footerText", "Message automatique de 3D-Fab.");
|
|
||||||
templateData.put("labelOrderNumber", "Numero de commande");
|
|
||||||
templateData.put("labelTotal", "Total");
|
|
||||||
yield "Votre commande a ete expediee (Commande #" + orderNumber + ") - 3D-Fab";
|
|
||||||
}
|
|
||||||
default -> {
|
|
||||||
templateData.put("emailTitle", "Ordine spedito");
|
|
||||||
templateData.put("headlineText", "Il tuo ordine #" + orderNumber + " e' stato spedito");
|
|
||||||
templateData.put("greetingText", "Ciao " + templateData.get("customerName") + ",");
|
|
||||||
templateData.put("introText", "Buone notizie: il tuo pacco e' partito dal nostro laboratorio ed e' in viaggio.");
|
|
||||||
templateData.put("statusText", "Stato attuale: spedito.");
|
|
||||||
templateData.put("orderDetailsCtaText", "Visualizza stato ordine");
|
|
||||||
templateData.put("supportText", "Se hai bisogno di assistenza, rispondi a questa email.");
|
|
||||||
templateData.put("footerText", "Messaggio automatico di 3D-Fab.");
|
|
||||||
templateData.put("labelOrderNumber", "Numero ordine");
|
|
||||||
templateData.put("labelTotal", "Totale");
|
|
||||||
yield "Il tuo ordine e' stato spedito (Ordine #" + orderNumber + ") - 3D-Fab";
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
private String getDisplayOrderNumber(Order order) {
|
|
||||||
String orderNumber = order.getOrderNumber();
|
|
||||||
if (orderNumber != null && !orderNumber.isBlank()) {
|
|
||||||
return orderNumber;
|
|
||||||
}
|
|
||||||
return order.getId() != null ? order.getId().toString() : "unknown";
|
|
||||||
}
|
|
||||||
|
|
||||||
private String buildOrderDetailsUrl(Order order, String language) {
|
|
||||||
String baseUrl = frontendBaseUrl == null ? "" : frontendBaseUrl.replaceAll("/+$", "");
|
|
||||||
return baseUrl + "/" + language + "/co/" + order.getId();
|
|
||||||
}
|
|
||||||
|
|
||||||
private String buildConfirmationAttachmentName(String language, String orderNumber) {
|
|
||||||
return switch (language) {
|
|
||||||
case "en" -> "Order-Confirmation-" + orderNumber + ".pdf";
|
|
||||||
case "de" -> "Bestellbestaetigung-" + orderNumber + ".pdf";
|
|
||||||
case "fr" -> "Confirmation-Commande-" + orderNumber + ".pdf";
|
|
||||||
default -> "Conferma-Ordine-" + orderNumber + ".pdf";
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
private String buildPaidInvoiceAttachmentName(String language, String orderNumber) {
|
|
||||||
return switch (language) {
|
|
||||||
case "en" -> "Paid-Invoice-" + orderNumber + ".pdf";
|
|
||||||
case "de" -> "Bezahlte-Rechnung-" + orderNumber + ".pdf";
|
|
||||||
case "fr" -> "Facture-Payee-" + orderNumber + ".pdf";
|
|
||||||
default -> "Fattura-Pagata-" + orderNumber + ".pdf";
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
private byte[] loadOrGenerateConfirmationPdf(Order order) {
|
|
||||||
byte[] stored = loadStoredConfirmationPdf(order);
|
|
||||||
if (stored != null) {
|
|
||||||
return stored;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
List<OrderItem> items = orderItemRepository.findByOrder_Id(order.getId());
|
|
||||||
return invoicePdfRenderingService.generateDocumentPdf(order, items, true, qrBillService, null);
|
|
||||||
} catch (Exception e) {
|
|
||||||
log.error("Failed to generate fallback confirmation PDF for order id: {}", order.getId(), e);
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private byte[] loadStoredConfirmationPdf(Order order) {
|
|
||||||
String relativePath = buildConfirmationPdfRelativePath(order);
|
|
||||||
try {
|
|
||||||
return storageService.loadAsResource(Paths.get(relativePath)).getInputStream().readAllBytes();
|
|
||||||
} catch (Exception e) {
|
|
||||||
log.warn("Confirmation PDF not found for order id {} at {}", order.getId(), relativePath);
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private String buildConfirmationPdfRelativePath(Order order) {
|
|
||||||
return "orders/" + order.getId() + "/documents/confirmation-" + getDisplayOrderNumber(order) + ".pdf";
|
|
||||||
}
|
|
||||||
|
|
||||||
private String buildCustomerFirstName(Order order, String language) {
|
|
||||||
if (order.getCustomer() != null && order.getCustomer().getFirstName() != null && !order.getCustomer().getFirstName().isBlank()) {
|
|
||||||
return order.getCustomer().getFirstName();
|
|
||||||
}
|
|
||||||
if (order.getBillingFirstName() != null && !order.getBillingFirstName().isBlank()) {
|
|
||||||
return order.getBillingFirstName();
|
|
||||||
}
|
|
||||||
return switch (language) {
|
|
||||||
case "en" -> "Customer";
|
|
||||||
case "de" -> "Kunde";
|
|
||||||
case "fr" -> "Client";
|
|
||||||
default -> "Cliente";
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
private String buildCustomerFullName(Order order) {
|
|
||||||
String firstName = order.getCustomer() != null ? order.getCustomer().getFirstName() : null;
|
|
||||||
String lastName = order.getCustomer() != null ? order.getCustomer().getLastName() : null;
|
|
||||||
if (firstName != null && !firstName.isBlank() && lastName != null && !lastName.isBlank()) {
|
|
||||||
return firstName + " " + lastName;
|
|
||||||
}
|
|
||||||
if (order.getBillingFirstName() != null && !order.getBillingFirstName().isBlank()
|
|
||||||
&& order.getBillingLastName() != null && !order.getBillingLastName().isBlank()) {
|
|
||||||
return order.getBillingFirstName() + " " + order.getBillingLastName();
|
|
||||||
}
|
|
||||||
return "Cliente";
|
|
||||||
}
|
|
||||||
|
|
||||||
private Locale localeForLanguage(String language) {
|
|
||||||
return switch (language) {
|
|
||||||
case "en" -> Locale.ENGLISH;
|
|
||||||
case "de" -> Locale.GERMAN;
|
|
||||||
case "fr" -> Locale.FRENCH;
|
|
||||||
default -> Locale.ITALIAN;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
private String resolveLanguage(String language) {
|
|
||||||
if (language == null || language.isBlank()) {
|
|
||||||
return DEFAULT_LANGUAGE;
|
|
||||||
}
|
|
||||||
|
|
||||||
String normalized = language.trim().toLowerCase(Locale.ROOT);
|
|
||||||
if (normalized.length() > 2) {
|
|
||||||
normalized = normalized.substring(0, 2);
|
|
||||||
}
|
|
||||||
|
|
||||||
return switch (normalized) {
|
|
||||||
case "it", "en", "de", "fr" -> normalized;
|
|
||||||
default -> DEFAULT_LANGUAGE;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,83 +0,0 @@
|
|||||||
package com.printcalculator.exception;
|
|
||||||
|
|
||||||
import org.springframework.http.HttpStatus;
|
|
||||||
import org.springframework.http.ResponseEntity;
|
|
||||||
import org.springframework.validation.FieldError;
|
|
||||||
import org.springframework.web.bind.MethodArgumentNotValidException;
|
|
||||||
import org.springframework.web.bind.annotation.ControllerAdvice;
|
|
||||||
import org.springframework.web.bind.annotation.ExceptionHandler;
|
|
||||||
import org.springframework.web.context.request.WebRequest;
|
|
||||||
|
|
||||||
import java.time.LocalDateTime;
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.LinkedHashMap;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
@ControllerAdvice
|
|
||||||
public class GlobalExceptionHandler {
|
|
||||||
|
|
||||||
@ExceptionHandler(ModelProcessingException.class)
|
|
||||||
public ResponseEntity<Object> handleModelProcessingException(
|
|
||||||
ModelProcessingException ex, WebRequest request) {
|
|
||||||
Map<String, Object> body = new LinkedHashMap<>();
|
|
||||||
body.put("timestamp", LocalDateTime.now());
|
|
||||||
body.put("status", HttpStatus.UNPROCESSABLE_ENTITY.value());
|
|
||||||
body.put("error", "Unprocessable Entity");
|
|
||||||
body.put("code", ex.getCode());
|
|
||||||
body.put("message", ex.getMessage());
|
|
||||||
body.put("path", extractPath(request));
|
|
||||||
|
|
||||||
return new ResponseEntity<>(body, HttpStatus.UNPROCESSABLE_ENTITY);
|
|
||||||
}
|
|
||||||
|
|
||||||
@ExceptionHandler(VirusDetectedException.class)
|
|
||||||
public ResponseEntity<Object> handleVirusDetectedException(
|
|
||||||
VirusDetectedException ex, WebRequest request) {
|
|
||||||
|
|
||||||
Map<String, Object> body = new LinkedHashMap<>();
|
|
||||||
body.put("timestamp", LocalDateTime.now());
|
|
||||||
body.put("message", ex.getMessage());
|
|
||||||
body.put("error", "Virus Detected");
|
|
||||||
|
|
||||||
return new ResponseEntity<>(body, HttpStatus.BAD_REQUEST);
|
|
||||||
}
|
|
||||||
|
|
||||||
@ExceptionHandler(MethodArgumentNotValidException.class)
|
|
||||||
public ResponseEntity<Object> handleValidationException(
|
|
||||||
MethodArgumentNotValidException ex, WebRequest request) {
|
|
||||||
|
|
||||||
List<String> details = new ArrayList<>();
|
|
||||||
for (FieldError fieldError : ex.getBindingResult().getFieldErrors()) {
|
|
||||||
details.add(fieldError.getField() + ": " + fieldError.getDefaultMessage());
|
|
||||||
}
|
|
||||||
|
|
||||||
Map<String, Object> body = new LinkedHashMap<>();
|
|
||||||
body.put("timestamp", LocalDateTime.now());
|
|
||||||
body.put("message", "Dati non validi.");
|
|
||||||
body.put("error", "Validation Error");
|
|
||||||
body.put("details", details);
|
|
||||||
|
|
||||||
return new ResponseEntity<>(body, HttpStatus.BAD_REQUEST);
|
|
||||||
}
|
|
||||||
|
|
||||||
@ExceptionHandler(IllegalArgumentException.class)
|
|
||||||
public ResponseEntity<Object> handleIllegalArgumentException(
|
|
||||||
IllegalArgumentException ex, WebRequest request) {
|
|
||||||
|
|
||||||
Map<String, Object> body = new LinkedHashMap<>();
|
|
||||||
body.put("timestamp", LocalDateTime.now());
|
|
||||||
body.put("message", ex.getMessage());
|
|
||||||
body.put("error", "Bad Request");
|
|
||||||
|
|
||||||
return new ResponseEntity<>(body, HttpStatus.BAD_REQUEST);
|
|
||||||
}
|
|
||||||
|
|
||||||
private String extractPath(WebRequest request) {
|
|
||||||
String raw = request.getDescription(false);
|
|
||||||
if (raw == null) {
|
|
||||||
return "";
|
|
||||||
}
|
|
||||||
return raw.startsWith("uri=") ? raw.substring(4) : raw;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,21 +0,0 @@
|
|||||||
package com.printcalculator.exception;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
|
|
||||||
public class ModelProcessingException extends IOException {
|
|
||||||
private final String code;
|
|
||||||
|
|
||||||
public ModelProcessingException(String code, String message) {
|
|
||||||
super(message);
|
|
||||||
this.code = code;
|
|
||||||
}
|
|
||||||
|
|
||||||
public ModelProcessingException(String code, String message, Throwable cause) {
|
|
||||||
super(message, cause);
|
|
||||||
this.code = code;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getCode() {
|
|
||||||
return code;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,12 +0,0 @@
|
|||||||
package com.printcalculator.exception;
|
|
||||||
|
|
||||||
public class StorageException extends RuntimeException {
|
|
||||||
|
|
||||||
public StorageException(String message) {
|
|
||||||
super(message);
|
|
||||||
}
|
|
||||||
|
|
||||||
public StorageException(String message, Throwable cause) {
|
|
||||||
super(message, cause);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
package com.printcalculator.exception;
|
|
||||||
|
|
||||||
public class VirusDetectedException extends RuntimeException {
|
|
||||||
public VirusDetectedException(String message) {
|
|
||||||
super(message);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
package com.printcalculator.model;
|
||||||
|
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
|
||||||
|
public record CostBreakdown(
|
||||||
|
BigDecimal materialCost,
|
||||||
|
BigDecimal machineCost,
|
||||||
|
BigDecimal energyCost,
|
||||||
|
BigDecimal subtotal,
|
||||||
|
BigDecimal markupAmount
|
||||||
|
) {}
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
package com.printcalculator.model;
|
|
||||||
|
|
||||||
public record ModelDimensions(
|
|
||||||
double xMm,
|
|
||||||
double yMm,
|
|
||||||
double zMm
|
|
||||||
) {}
|
|
||||||
@@ -1,24 +1,12 @@
|
|||||||
package com.printcalculator.model;
|
package com.printcalculator.model;
|
||||||
|
|
||||||
public class QuoteResult {
|
import java.math.BigDecimal;
|
||||||
private double totalPrice;
|
import java.util.List;
|
||||||
private String currency;
|
|
||||||
private PrintStats stats;
|
|
||||||
public QuoteResult(double totalPrice, String currency, PrintStats stats) {
|
|
||||||
this.totalPrice = totalPrice;
|
|
||||||
this.currency = currency;
|
|
||||||
this.stats = stats;
|
|
||||||
}
|
|
||||||
|
|
||||||
public double getTotalPrice() {
|
public record QuoteResult(
|
||||||
return totalPrice;
|
BigDecimal totalPrice,
|
||||||
}
|
String currency,
|
||||||
|
PrintStats stats,
|
||||||
public String getCurrency() {
|
CostBreakdown breakdown,
|
||||||
return currency;
|
List<String> notes
|
||||||
}
|
) {}
|
||||||
|
|
||||||
public PrintStats getStats() {
|
|
||||||
return stats;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,11 +0,0 @@
|
|||||||
package com.printcalculator.repository;
|
|
||||||
|
|
||||||
import com.printcalculator.entity.CustomQuoteRequestAttachment;
|
|
||||||
import org.springframework.data.jpa.repository.JpaRepository;
|
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.UUID;
|
|
||||||
|
|
||||||
public interface CustomQuoteRequestAttachmentRepository extends JpaRepository<CustomQuoteRequestAttachment, UUID> {
|
|
||||||
List<CustomQuoteRequestAttachment> findByRequest_IdOrderByCreatedAtAsc(UUID requestId);
|
|
||||||
}
|
|
||||||
@@ -1,9 +0,0 @@
|
|||||||
package com.printcalculator.repository;
|
|
||||||
|
|
||||||
import com.printcalculator.entity.CustomQuoteRequest;
|
|
||||||
import org.springframework.data.jpa.repository.JpaRepository;
|
|
||||||
|
|
||||||
import java.util.UUID;
|
|
||||||
|
|
||||||
public interface CustomQuoteRequestRepository extends JpaRepository<CustomQuoteRequest, UUID> {
|
|
||||||
}
|
|
||||||
@@ -1,11 +0,0 @@
|
|||||||
package com.printcalculator.repository;
|
|
||||||
|
|
||||||
import com.printcalculator.entity.Customer;
|
|
||||||
import org.springframework.data.jpa.repository.JpaRepository;
|
|
||||||
|
|
||||||
import java.util.Optional;
|
|
||||||
import java.util.UUID;
|
|
||||||
|
|
||||||
public interface CustomerRepository extends JpaRepository<Customer, UUID> {
|
|
||||||
Optional<Customer> findByEmail(String email);
|
|
||||||
}
|
|
||||||
@@ -1,10 +0,0 @@
|
|||||||
package com.printcalculator.repository;
|
|
||||||
|
|
||||||
import com.printcalculator.entity.FilamentMaterialType;
|
|
||||||
import org.springframework.data.jpa.repository.JpaRepository;
|
|
||||||
|
|
||||||
import java.util.Optional;
|
|
||||||
|
|
||||||
public interface FilamentMaterialTypeRepository extends JpaRepository<FilamentMaterialType, Long> {
|
|
||||||
Optional<FilamentMaterialType> findByMaterialCode(String materialCode);
|
|
||||||
}
|
|
||||||
@@ -1,15 +0,0 @@
|
|||||||
package com.printcalculator.repository;
|
|
||||||
|
|
||||||
import com.printcalculator.entity.FilamentVariant;
|
|
||||||
import com.printcalculator.entity.FilamentVariantOrcaOverride;
|
|
||||||
import com.printcalculator.entity.PrinterMachineProfile;
|
|
||||||
import org.springframework.data.jpa.repository.JpaRepository;
|
|
||||||
|
|
||||||
import java.util.Optional;
|
|
||||||
|
|
||||||
public interface FilamentVariantOrcaOverrideRepository extends JpaRepository<FilamentVariantOrcaOverride, Long> {
|
|
||||||
Optional<FilamentVariantOrcaOverride> findByFilamentVariantAndPrinterMachineProfileAndIsActiveTrue(
|
|
||||||
FilamentVariant filamentVariant,
|
|
||||||
PrinterMachineProfile printerMachineProfile
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -1,19 +0,0 @@
|
|||||||
package com.printcalculator.repository;
|
|
||||||
|
|
||||||
import com.printcalculator.entity.FilamentVariant;
|
|
||||||
import org.springframework.data.jpa.repository.JpaRepository;
|
|
||||||
import org.springframework.data.jpa.repository.EntityGraph;
|
|
||||||
|
|
||||||
import com.printcalculator.entity.FilamentMaterialType;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Optional;
|
|
||||||
|
|
||||||
public interface FilamentVariantRepository extends JpaRepository<FilamentVariant, Long> {
|
|
||||||
@EntityGraph(attributePaths = {"filamentMaterialType"})
|
|
||||||
List<FilamentVariant> findByIsActiveTrue();
|
|
||||||
|
|
||||||
// We try to match by color name if possible, or get first active
|
|
||||||
Optional<FilamentVariant> findByFilamentMaterialTypeAndColorName(FilamentMaterialType type, String colorName);
|
|
||||||
Optional<FilamentVariant> findByFilamentMaterialTypeAndVariantDisplayName(FilamentMaterialType type, String variantDisplayName);
|
|
||||||
Optional<FilamentVariant> findFirstByFilamentMaterialTypeAndIsActiveTrue(FilamentMaterialType type);
|
|
||||||
}
|
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
package com.printcalculator.repository;
|
|
||||||
|
|
||||||
import com.printcalculator.entity.FilamentVariantStockKg;
|
|
||||||
import org.springframework.data.jpa.repository.JpaRepository;
|
|
||||||
|
|
||||||
public interface FilamentVariantStockKgRepository extends JpaRepository<FilamentVariantStockKg, Long> {
|
|
||||||
}
|
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
package com.printcalculator.repository;
|
|
||||||
|
|
||||||
import com.printcalculator.entity.InfillPattern;
|
|
||||||
import org.springframework.data.jpa.repository.JpaRepository;
|
|
||||||
|
|
||||||
public interface InfillPatternRepository extends JpaRepository<InfillPattern, Long> {
|
|
||||||
}
|
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
package com.printcalculator.repository;
|
|
||||||
|
|
||||||
import com.printcalculator.entity.LayerHeightOption;
|
|
||||||
import org.springframework.data.jpa.repository.JpaRepository;
|
|
||||||
|
|
||||||
public interface LayerHeightOptionRepository extends JpaRepository<LayerHeightOption, Long> {
|
|
||||||
}
|
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
package com.printcalculator.repository;
|
|
||||||
|
|
||||||
import com.printcalculator.entity.LayerHeightProfile;
|
|
||||||
import org.springframework.data.jpa.repository.JpaRepository;
|
|
||||||
|
|
||||||
public interface LayerHeightProfileRepository extends JpaRepository<LayerHeightProfile, Long> {
|
|
||||||
}
|
|
||||||
@@ -1,20 +0,0 @@
|
|||||||
package com.printcalculator.repository;
|
|
||||||
|
|
||||||
import com.printcalculator.entity.FilamentMaterialType;
|
|
||||||
import com.printcalculator.entity.MaterialOrcaProfileMap;
|
|
||||||
import com.printcalculator.entity.PrinterMachineProfile;
|
|
||||||
import org.springframework.data.jpa.repository.EntityGraph;
|
|
||||||
import org.springframework.data.jpa.repository.JpaRepository;
|
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Optional;
|
|
||||||
|
|
||||||
public interface MaterialOrcaProfileMapRepository extends JpaRepository<MaterialOrcaProfileMap, Long> {
|
|
||||||
Optional<MaterialOrcaProfileMap> findByPrinterMachineProfileAndFilamentMaterialTypeAndIsActiveTrue(
|
|
||||||
PrinterMachineProfile printerMachineProfile,
|
|
||||||
FilamentMaterialType filamentMaterialType
|
|
||||||
);
|
|
||||||
|
|
||||||
@EntityGraph(attributePaths = {"filamentMaterialType"})
|
|
||||||
List<MaterialOrcaProfileMap> findByPrinterMachineProfileAndIsActiveTrue(PrinterMachineProfile printerMachineProfile);
|
|
||||||
}
|
|
||||||
@@ -1,10 +0,0 @@
|
|||||||
package com.printcalculator.repository;
|
|
||||||
|
|
||||||
import com.printcalculator.entity.NozzleLayerHeightOption;
|
|
||||||
import org.springframework.data.jpa.repository.JpaRepository;
|
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
public interface NozzleLayerHeightOptionRepository extends JpaRepository<NozzleLayerHeightOption, Long> {
|
|
||||||
List<NozzleLayerHeightOption> findByIsActiveTrueOrderByNozzleDiameterMmAscLayerHeightMmAsc();
|
|
||||||
}
|
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
package com.printcalculator.repository;
|
|
||||||
|
|
||||||
import com.printcalculator.entity.NozzleOption;
|
|
||||||
import org.springframework.data.jpa.repository.JpaRepository;
|
|
||||||
|
|
||||||
public interface NozzleOptionRepository extends JpaRepository<NozzleOption, Long> {
|
|
||||||
}
|
|
||||||
@@ -1,12 +0,0 @@
|
|||||||
package com.printcalculator.repository;
|
|
||||||
|
|
||||||
import com.printcalculator.entity.OrderItem;
|
|
||||||
import org.springframework.data.jpa.repository.JpaRepository;
|
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.UUID;
|
|
||||||
|
|
||||||
public interface OrderItemRepository extends JpaRepository<OrderItem, UUID> {
|
|
||||||
List<OrderItem> findByOrder_Id(UUID orderId);
|
|
||||||
boolean existsByFilamentVariant_Id(Long filamentVariantId);
|
|
||||||
}
|
|
||||||
@@ -1,13 +0,0 @@
|
|||||||
package com.printcalculator.repository;
|
|
||||||
|
|
||||||
import com.printcalculator.entity.Order;
|
|
||||||
import org.springframework.data.jpa.repository.JpaRepository;
|
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.UUID;
|
|
||||||
|
|
||||||
public interface OrderRepository extends JpaRepository<Order, UUID> {
|
|
||||||
List<Order> findAllByOrderByCreatedAtDesc();
|
|
||||||
|
|
||||||
boolean existsBySourceQuoteSession_Id(UUID sourceQuoteSessionId);
|
|
||||||
}
|
|
||||||
@@ -1,11 +0,0 @@
|
|||||||
package com.printcalculator.repository;
|
|
||||||
|
|
||||||
import com.printcalculator.entity.Payment;
|
|
||||||
import org.springframework.data.jpa.repository.JpaRepository;
|
|
||||||
|
|
||||||
import java.util.Optional;
|
|
||||||
import java.util.UUID;
|
|
||||||
|
|
||||||
public interface PaymentRepository extends JpaRepository<Payment, UUID> {
|
|
||||||
Optional<Payment> findByOrder_Id(UUID orderId);
|
|
||||||
}
|
|
||||||
@@ -1,11 +0,0 @@
|
|||||||
package com.printcalculator.repository;
|
|
||||||
|
|
||||||
import com.printcalculator.entity.PricingPolicyMachineHourTier;
|
|
||||||
import org.springframework.data.jpa.repository.JpaRepository;
|
|
||||||
|
|
||||||
import com.printcalculator.entity.PricingPolicy;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
public interface PricingPolicyMachineHourTierRepository extends JpaRepository<PricingPolicyMachineHourTier, Long> {
|
|
||||||
List<PricingPolicyMachineHourTier> findAllByPricingPolicyOrderByTierStartHoursAsc(PricingPolicy pricingPolicy);
|
|
||||||
}
|
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user