From 9db796b8e143f79aba2a722590ac5c58acf9fdb6 Mon Sep 17 00:00:00 2001 From: morteza-mortezai Date: Fri, 29 May 2026 16:21:46 +0330 Subject: [PATCH] discount --- .dockerignore | 21 +++++ .env | 2 +- Dockerfile | 38 +++++---- package-lock.json | 14 ++++ src/pages/invoice/Create.tsx | 47 ++++++++--- src/pages/invoice/DetailPerfomaInvoice.tsx | 16 ++-- src/pages/invoice/Update.tsx | 23 +++-- .../invoice/components/InvoiceItemRow.tsx | 84 ++++++++++++++++--- src/pages/invoice/types/Types.ts | 16 +++- src/pages/invoice/utils/invoiceItem.ts | 48 +++++++++++ src/pages/requests/components/RequestItem.tsx | 40 ++++++--- src/pages/requests/types/Types.ts | 7 +- 12 files changed, 285 insertions(+), 71 deletions(-) create mode 100644 .dockerignore create mode 100644 src/pages/invoice/utils/invoiceItem.ts diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..7b465ba --- /dev/null +++ b/.dockerignore @@ -0,0 +1,21 @@ +node_modules +dist +.git +.gitignore +.env* +*.md +coverage +.nyc_output +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +.DS_Store +.idea +.vscode +*.swp +*.swo +.eslintcache +Dockerfile +.dockerignore diff --git a/.env b/.env index 217f754..492da99 100644 --- a/.env +++ b/.env @@ -1,4 +1,4 @@ # VITE_API_BASE_URL = 'https://negareh-api.dev.danakcorp.com' -VITE_API_BASE_URL = 'http://10.23.26.88:4000' +VITE_API_BASE_URL = 'http://localhost:4000' VITE_TOKEN_NAME = 'negareh_at' VITE_REFRESH_TOKEN_NAME = 'negareh_art' diff --git a/Dockerfile b/Dockerfile index 5941bac..4933ff6 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,32 +1,38 @@ -FROM node:20-alpine AS builder +# syntax=docker/dockerfile:1 -# Install tzdata to support timezone settings -RUN apk add --no-cache tzdata +FROM node:22-alpine AS base +ENV TZ=Asia/Tehran -# Set the timezone to Asia/Tehran -RUN cp /usr/share/zoneinfo/Asia/Tehran /etc/localtime && echo "Asia/Tehran" > /etc/timezone +FROM base AS deps +WORKDIR /app +COPY package.json package-lock.json ./ +RUN --mount=type=cache,target=/root/.npm \ + npm ci --ignore-scripts --no-fund --no-audit -WORKDIR /build -COPY package*.json ./ -RUN npm install -COPY . ./ +FROM base AS builder +WORKDIR /app +COPY --from=deps /app/node_modules ./node_modules +COPY . . ARG VITE_API_BASE_URL ARG VITE_TOKEN_NAME ARG VITE_REFRESH_TOKEN_NAME ENV VITE_API_BASE_URL=$VITE_API_BASE_URL \ VITE_TOKEN_NAME=$VITE_TOKEN_NAME \ - VITE_REFRESH_TOKEN_NAME=$VITE_REFRESH_TOKEN_NAME + VITE_REFRESH_TOKEN_NAME=$VITE_REFRESH_TOKEN_NAME \ + NODE_ENV=production RUN npm run build -FROM nginx:stable-alpine AS production-stage +FROM nginx:stable-alpine AS production +ENV TZ=Asia/Tehran -COPY --from=builder /build/dist /usr/share/nginx/html - -COPY --from=builder /build/nginx.con[f] /etc/nginx/conf.d/default.conf -RUN cat /etc/nginx/conf.d/default.conf +COPY --from=builder /app/dist /usr/share/nginx/html +COPY nginx.conf /etc/nginx/conf.d/default.conf EXPOSE 80 -CMD ["nginx", "-g", "daemon off;"] \ No newline at end of file +HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \ + CMD wget -q --spider http://127.0.0.1/health || exit 1 + +CMD ["nginx", "-g", "daemon off;"] diff --git a/package-lock.json b/package-lock.json index 6d14430..3ed0570 100644 --- a/package-lock.json +++ b/package-lock.json @@ -82,6 +82,7 @@ "integrity": "sha512-2BCOP7TN8M+gVDj7/ht3hsaO/B/n5oDbiAyyvnRlNOs+u1o+JWNYTQrmpuNp1/Wq2gcFrI01JAW+paEKDMx/CA==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@babel/code-frame": "^7.27.1", "@babel/generator": "^7.28.3", @@ -2142,6 +2143,7 @@ "integrity": "sha512-/NbVmcGTP+lj5oa4yiYxxeBjRivKQ5Ns1eSZeB99ExsEQ6rX5XYU1Zy/gGxY/ilqtD4Etx9mKyrPxZRetiahhA==", "devOptional": true, "license": "MIT", + "peer": true, "dependencies": { "undici-types": "~7.14.0" } @@ -2151,6 +2153,7 @@ "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.2.tgz", "integrity": "sha512-6mDvHUFSjyT2B2yeNx2nUgMxh9LtOWvkhIU3uePn2I2oyNymUAX1NIsdgviM4CH+JSrp2D2hsMvJOkxY+0wNRA==", "license": "MIT", + "peer": true, "dependencies": { "csstype": "^3.0.2" } @@ -2161,6 +2164,7 @@ "integrity": "sha512-9KQPoO6mZCi7jcIStSnlOWn2nEF3mNmyr3rIAsGnAbQKYbRLyqmeSc39EVgtxXVia+LMT8j3knZLAZAh+xLmrw==", "devOptional": true, "license": "MIT", + "peer": true, "peerDependencies": { "@types/react": "^19.2.0" } @@ -2211,6 +2215,7 @@ "integrity": "sha512-n1H6IcDhmmUEG7TNVSspGmiHHutt7iVKtZwRppD7e04wha5MrkV1h3pti9xQLcCMt6YWsncpoT0HMjkH1FNwWQ==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@typescript-eslint/scope-manager": "8.46.0", "@typescript-eslint/types": "8.46.0", @@ -2463,6 +2468,7 @@ "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", "dev": true, "license": "MIT", + "peer": true, "bin": { "acorn": "bin/acorn" }, @@ -2617,6 +2623,7 @@ } ], "license": "MIT", + "peer": true, "dependencies": { "baseline-browser-mapping": "^2.8.9", "caniuse-lite": "^1.0.30001746", @@ -2990,6 +2997,7 @@ "integrity": "sha512-XyLmROnACWqSxiGYArdef1fItQd47weqB7iwtfr9JHwRrqIXZdcFMvvEcL9xHCmL0SNsOvF0c42lWyM1U5dgig==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@eslint-community/eslint-utils": "^4.8.0", "@eslint-community/regexpp": "^4.12.1", @@ -4415,6 +4423,7 @@ "resolved": "https://registry.npmjs.org/react/-/react-19.2.0.tgz", "integrity": "sha512-tmbWg6W31tQLeB5cdIBOicJDJRR2KzXsV7uSK9iNfLWQ5bIZfxuPEHp7M8wiHyHnn0DD1i7w3Zmin0FtkrwoCQ==", "license": "MIT", + "peer": true, "engines": { "node": ">=0.10.0" } @@ -4430,6 +4439,7 @@ "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.0.tgz", "integrity": "sha512-UlbRu4cAiGaIewkPyiRGJk0imDN2T3JjieT6spoL2UeSf5od4n5LB/mQ4ejmxhCFT1tYe8IvaFulzynWovsEFQ==", "license": "MIT", + "peer": true, "dependencies": { "scheduler": "^0.27.0" }, @@ -4896,6 +4906,7 @@ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", "license": "MIT", + "peer": true, "engines": { "node": ">=12" }, @@ -4992,6 +5003,7 @@ "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", "devOptional": true, "license": "Apache-2.0", + "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -5086,6 +5098,7 @@ "resolved": "https://registry.npmjs.org/vite/-/vite-7.3.1.tgz", "integrity": "sha512-w+N7Hifpc3gRjZ63vYBXA56dvvRlNWRczTdmCBBa+CotUzAPf5b7YMdMR/8CQoeYE5LX3W4wj6RYTgonm1b9DA==", "license": "MIT", + "peer": true, "dependencies": { "esbuild": "^0.27.0", "fdir": "^6.5.0", @@ -5196,6 +5209,7 @@ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", "license": "MIT", + "peer": true, "engines": { "node": ">=12" }, diff --git a/src/pages/invoice/Create.tsx b/src/pages/invoice/Create.tsx index 37f7586..93efdf9 100644 --- a/src/pages/invoice/Create.tsx +++ b/src/pages/invoice/Create.tsx @@ -1,4 +1,4 @@ -import { useState, type FC } from "react"; +import { useEffect, useRef, useState, type FC } from "react"; import Button from "@/components/Button"; import Select from "@/components/Select"; import { TickSquare } from "iconsax-react"; @@ -16,14 +16,17 @@ import UploadBox from "@/components/UploadBox"; import Textarea from "@/components/Textarea"; import DatePickerComponent from "@/components/DatePicker"; import { useCreateInvoice } from "./hooks/useInvoiceData"; +import { useGetRequestDetail } from "@/pages/requests/hooks/useRequestData"; import { useMultiUpload } from "../uploader/hooks/useUploader"; import moment from "moment-jalaali"; +import { getItemLineTotal } from "./utils/invoiceItem"; const createEmptyItem = (): CreatePreInvoiceItemType => ({ productId: "", quantity: 1, unitPrice: 0, discount: 0, + discountPercent: null, description: "", }); @@ -35,6 +38,8 @@ const CreateInvoice: FC = () => { const { mutate: submitInvoice, isPending } = useCreateInvoice(); const { data: usersData } = useGetUsers(); + const { data: requestData, isLoading: isLoadingRequest } = useGetRequestDetail(requestId ?? ""); + const hydratedRequestId = useRef(null); const multiUpload = useMultiUpload() const [items, setItems] = useState([createEmptyItem()]); @@ -81,7 +86,9 @@ const CreateInvoice: FC = () => { productId: i.productId, quantity: i.quantity, unitPrice: i.unitPrice, - discount: i.discount || 0, + ...(i.discountPercent != null && i.discountPercent > 0 + ? { discountPercent: i.discountPercent } + : { discount: i.discount || 0 }), description: i.description || "", })), enableTax: values.enableTax, @@ -103,14 +110,35 @@ const CreateInvoice: FC = () => { }, }); + useEffect(() => { + const request = requestData?.data; + if (!requestId || !request || hydratedRequestId.current === requestId) return; + + const mappedItems: CreatePreInvoiceItemType[] = request.items?.length + ? request.items.map((item) => ({ + productId: item.product?.id ?? "", + quantity: item.quantity ?? 1, + unitPrice: 0, + discount: 0, + discountPercent: null, + description: item.description ?? "", + })) + : [createEmptyItem()]; + + setItems(mappedItems); + hydratedRequestId.current = requestId; + }, [requestData, requestId]); + const handleItemChange = ( index: number, field: keyof CreatePreInvoiceItemType, - value: string | number + value: string | number | null ) => { - const next = [...items]; - next[index] = { ...next[index], [field]: value }; - setItems(next); + setItems((prev) => + prev.map((item, itemIndex) => + itemIndex === index ? { ...item, [field]: value } : item + ) + ); }; const addItem = (currentIndex: number) => { @@ -131,10 +159,7 @@ const CreateInvoice: FC = () => { setItems(items.filter((_, i) => i !== index)); }; - const totalAmount = items.reduce( - (sum, i) => sum + (i.quantity || 0) * (i.unitPrice || 0) - (i.discount || 0), - 0 - ); + const totalAmount = items.reduce((sum, i) => sum + getItemLineTotal(i), 0); const users = usersData?.data ?? []; @@ -145,7 +170,7 @@ const CreateInvoice: FC = () => {