diff --git a/frontend/src/i18n/locales/en.json b/frontend/src/i18n/locales/en.json
index 88cb16d..e55c73a 100644
--- a/frontend/src/i18n/locales/en.json
+++ b/frontend/src/i18n/locales/en.json
@@ -186,5 +186,294 @@
"lastMonth": "Last Month",
"thisYear": "This Year",
"lastYear": "Last Year"
+ },
+ "stock": {
+ "title": "Stock Order",
+ "order": "Stock Order",
+ "final": "Final Stock Order",
+ "entry": "Stock Entry",
+ "list": "Stock Order List",
+ "detail": "Stock Order Detail",
+ "edit": "Edit Stock Order",
+ "create": "Create Stock Order",
+ "createFinal": "Create Final",
+ "view": "View",
+ "viewFinal": "View Final",
+ "orderNo": "Order No.",
+ "piNo": "Alibaba Order/PI No.",
+ "customer": "Customer",
+ "customerName": "Customer Name",
+ "customerSource": "Customer Source",
+ "customerCountry": "Customer Country",
+ "salesperson": "Salesperson",
+ "bizDate": "Date",
+ "clearanceCountry": "Clearance Destination Country",
+ "remark": "Remark",
+ "approvalHistory": "Approval History",
+ "contractFile": "Contract File",
+ "piFile": "PI File",
+ "transactionSlipFile": "Transaction Slip File",
+ "vehicle": "Vehicle",
+ "fileN": "File {{num}}",
+ "totalRecords": "Total {{count}} records",
+ "deleteFailed": "Failed to delete",
+ "printFailed": "Failed to print",
+ "recallFailed": "Failed to recall",
+ "voidFailed": "Failed to void",
+ "voidSuccess": "Voided successfully",
+ "noContractFiles": "No contract files",
+ "noDetail": "No details",
+ "noPiFiles": "No PI files",
+ "noTransactionSlipFiles": "No transaction slip files",
+ "finalList": "Final List",
+ "finalDetail": "Final Detail",
+ "finalDetailTitle": "Final Stock Order Detail",
+ "viewDetails": "View Details",
+ "viewApproval": "View Approval",
+ "recallApproval": "Recall Approval",
+ "status": {
+ "draft": "Draft",
+ "pending": "Pending Approval",
+ "approved": "Approved",
+ "rejected": "Rejected",
+ "recalled": "Recalled",
+ "voided": "Voided"
+ },
+ "actions": {
+ "create": "Create",
+ "edit": "Edit",
+ "delete": "Delete",
+ "submit": "Submit",
+ "recall": "Recall",
+ "void": "Void",
+ "print": "Print",
+ "copy": "Copy",
+ "view": "View",
+ "save": "Save",
+ "cancel": "Cancel",
+ "add": "Add",
+ "recreate": "Recreate"
+ },
+ "detail": {
+ "baseInfo": "Basic Information",
+ "orderDetail": "Order Details",
+ "transportInfo": "Transport Details",
+ "paymentInfo": "Planned Payment",
+ "attachments": "Attachments",
+ "contract": "Contract Files",
+ "piDoc": "PI Files",
+ "transactionSlip": "Transaction Slip Files",
+ "totalCif": "Total CIF",
+ "contractRate": "Contract Exchange Rate"
+ },
+ "item": {
+ "model": "Model",
+ "exteriorColor": "Exterior Color",
+ "interiorColor": "Interior Color",
+ "mileage": "Mileage",
+ "vin": "VIN",
+ "unitPrice": "Collection Price",
+ "clearancePrice": "Clearance Price",
+ "carPrice": "Car Price",
+ "taxRefund": "Tax Refund",
+ "profit": "Profit",
+ "officialPrice": "Official Guide Price",
+ "discount": "Discount",
+ "listingPrice": "Used Listing Price",
+ "reduction": "Used Reduction",
+ "totalPrice": "Total Price",
+ "increasePrice": "Price Increase"
+ },
+ "transport": {
+ "declarant": "Clearance Agent",
+ "etaShipDate": "Estimated Ship Date",
+ "domesticPort": "Domestic Port",
+ "shippingMethod": "Shipping Method",
+ "tradeTerm": "Trade Term",
+ "clearancePort": "Clearance Port",
+ "latestArrival": "Latest Arrival Date"
+ },
+ "payment": {
+ "remitter": "Remitter",
+ "remitCountry": "Remitter Country",
+ "remitDate": "Remittance Date",
+ "account": "Account",
+ "amount": "Amount",
+ "paymentItem": "Payment {{num}}"
+ },
+ "fees": {
+ "handlingFee": "Handling Fee",
+ "retrofitFee": "Retrofit Fee",
+ "fobTransportFee": "FOB Freight",
+ "portMiscFee": "Port Miscellaneous Fee",
+ "oceanFreight": "Ocean Freight",
+ "domesticFreight": "Domestic Freight",
+ "commission": "Commission",
+ "purchaseTax": "Purchase Tax",
+ "taxRefund": "Tax Refund",
+ "accessoryPrice": "Accessory Price",
+ "retentionPrice": "Retention Account",
+ "carPrice": "Car Price",
+ "increasePrice": "Price Increase",
+ "clearancePrice": "Clearance Price",
+ "profit": "Profit",
+ "officialPrice": "Official Guide Price",
+ "discount": "Discount",
+ "listingPrice": "Used Listing Price",
+ "reduction": "Used Reduction",
+ "totalPrice": "Total"
+ },
+ "summary": {
+ "summaryInfo": "Summary Information",
+ "profit": "Profit",
+ "totalProfit": "Total Profit"
+ },
+ "dates": {
+ "pickUpDate": "Pick-up Date",
+ "collectAllDate": "All Collected Date"
+ },
+ "otherAttachments": "Other Attachments",
+ "searchPlaceholder": "Search customer name",
+ "empty": "No stock orders",
+ "emptyFinal": "No final stock orders",
+ "totalRecords": "Total {{count}} records",
+ "confirmDelete": "Are you sure to delete this stock order?",
+ "confirmRecall": "Are you sure to recall this stock order?",
+ "confirmVoid": "Are you sure to void this stock order?",
+ "confirmSubmit": "Confirm to submit?",
+ "deleteSuccess": "Deleted successfully",
+ "submitSuccess": "Submitted successfully",
+ "recallSuccess": "Recalled successfully",
+ "voidSuccess": "Voided successfully",
+ "saveSuccess": "Saved successfully",
+ "loadFailed": "Failed to load",
+ "loadOrderFailed": "Failed to load original order data",
+ "loadOrderSuccess": "Original order data loaded",
+ "saveFailed": "Failed to save",
+ "submitFailed": "Failed to submit",
+ "fileUploadSuccess": "File uploaded successfully!",
+ "fileUploadFailed": "File upload failed!",
+ "fileDeleted": "File deleted",
+ "uploading": "Uploading...",
+ "submitting": "Submitting...",
+ "atLeastOneItem": "At least one item required",
+ "atLeastOnePayment": "At least one payment required",
+ "deleteFailed": "Failed to delete",
+ "recallFailed": "Failed to recall",
+ "voidFailed": "Failed to void",
+ "printFailed": "Failed to print",
+ "noPrintFile": "No print file available",
+ "viewDetails": "View Details",
+ "viewApproval": "View Approval",
+ "submitForApproval": "Submit for Approval",
+ "recallApproval": "Recall Approval",
+ "loading": "Loading...",
+ "fileN": "File {{num}}",
+ "contractFile": "Contract File",
+ "piFile": "PI File",
+ "transactionSlipFile": "Transaction Slip File",
+ "fileUpload": {
+ "contract": "Upload Contract",
+ "piDoc": "Upload PI",
+ "transactionSlip": "Upload Transaction Slip",
+ "clickToUpload": "Click to upload",
+ "supportMultiple": "Support multiple file upload",
+ "preview": "Preview",
+ "download": "Download",
+ "delete": "Delete",
+ "fileTypeAccept": "Only {{accept}} format files are allowed!",
+ "fileSizeMax": "File size cannot exceed {{maxSize}}MB!"
+ },
+ "validation": {
+ "enterPiNo": "Please enter Alibaba Order/PI No.",
+ "selectDate": "Please select date",
+ "selectClearanceCountry": "Please select clearance destination country",
+ "selectCustomer": "Please select customer",
+ "enterDeclarant": "Please enter clearance agent",
+ "selectEtaShipDate": "Please select estimated ship date",
+ "selectDomesticPort": "Please select domestic port",
+ "selectShippingMethod": "Please select shipping method",
+ "selectTradeTerm": "Please select trade term",
+ "selectClearancePort": "Please select clearance port",
+ "selectLatestArrival": "Please select latest arrival date",
+ "selectAllVehicles": "Please select vehicles for all models",
+ "uploadContract": "Please upload contract file",
+ "uploadPiDoc": "Please upload PI file",
+ "uploadTransactionSlip": "Please upload transaction slip file",
+ "selectPickUpDate": "Please select pick-up date",
+ "selectCollectAllDate": "Please select all collected date"
+ },
+ "placeholder": {
+ "autoCalculate": "Auto Calculate",
+ "enterPiNo": "Please enter Alibaba Order/PI No.",
+ "selectDate": "Please select date",
+ "selectClearanceCountry": "Please select clearance destination country",
+ "selectCustomer": "Please select customer",
+ "autoFillCustomerSource": "Auto-filled after selecting customer",
+ "autoFillCustomerCountry": "Auto-filled after selecting customer",
+ "selectVehicle": "Please select vehicle model",
+ "selectExteriorColor": "Please select exterior color",
+ "selectInteriorColor": "Please select interior color",
+ "newCarMileage": "0 for new car",
+ "enterVin": "Please enter VIN",
+ "enterOfficialPrice": "Please enter official guide price",
+ "enterListingPrice": "Please enter used listing price",
+ "enterDiscount": "Please enter discount amount",
+ "enterAmount": "Please enter",
+ "enterIncreasePrice": "Please enter price increase",
+ "enterUnitPrice": "Please enter collection price",
+ "calculatingClearancePrice": "Calculating clearance price",
+ "calculatingCarPrice": "Calculating car price",
+ "calculatingTaxRefund": "Calculating estimated tax refund",
+ "calculatingProfit": "Calculating profit",
+ "enterRemark": "Please enter remark",
+ "enterHandlingFee": "Please enter",
+ "enterContractRate": "Please enter contract exchange rate",
+ "enterDeclarant": "Please enter clearance agent",
+ "selectDomesticPort": "Please select domestic port",
+ "selectShippingMethod": "Please select shipping method",
+ "selectTradeTerm": "Please select trade term",
+ "selectClearancePort": "Please select clearance port",
+ "enterRemitter": "Please enter remitter",
+ "selectRemitCountry": "Please select remitter country",
+ "selectAccount": "Please select account",
+ "enterAmount": "Please enter amount",
+ "searchCustomer": "Search customer",
+ "searchVehicle": "Search vehicle",
+ "selectPickUpDate": "Please select pick-up date",
+ "selectCollectAllDate": "Please select all collected date",
+ "enterMileage": "Please enter mileage"
+ },
+ "entry": {
+ "edit": "Edit Stock Order",
+ "copy": "Copy Stock Order",
+ "title": "Stock Entry",
+ "baseInfo": "Basic Information",
+ "baseInfoReadOnly": "Basic Information (Read Only)",
+ "orderDetail": "Order Details",
+ "transportInfo": "Transport Details",
+ "paymentInfo": "Planned Payment",
+ "fileUpload": "File Upload",
+ "saveDraft": "Save Draft",
+ "submitApproval": "Submit for Approval",
+ "createFinal": "Create Final",
+ "detail": "Detail",
+ "totalPrice": "Total",
+ "unitPrice": "Unit Price",
+ "carPrice": "Car Price",
+ "profit": "Profit",
+ "officialPrice": "Official Guide Price",
+ "discount": "Discount",
+ "listingPrice": "Used Listing Price",
+ "reduction": "Used Reduction",
+ "add": "Add",
+ "copy": "Copy"
+ },
+ "select": {
+ "customer": "Select Customer",
+ "vehicle": "Select Vehicle",
+ "year": "Year",
+ "msrp": "Guide Price"
+ }
}
}
diff --git a/frontend/src/i18n/locales/es.json b/frontend/src/i18n/locales/es.json
index d8fcaf7..f145a1a 100644
--- a/frontend/src/i18n/locales/es.json
+++ b/frontend/src/i18n/locales/es.json
@@ -161,5 +161,294 @@
"lastMonth": "Mes Pasado",
"thisYear": "Este Año",
"lastYear": "Año Pasado"
+ },
+ "stock": {
+ "title": "Pedido de Stock",
+ "order": "Pedido de Stock",
+ "final": "Pedido Final",
+ "entry": "Entrada de Stock",
+ "list": "Lista de Pedidos",
+ "detail": "Detalle del Pedido",
+ "edit": "Editar Pedido",
+ "create": "Crear Pedido",
+ "createFinal": "Crear Final",
+ "view": "Ver",
+ "viewFinal": "Ver Final",
+ "orderNo": "Nº Pedido",
+ "piNo": "Pedido Alibaba/Nº PI",
+ "customer": "Cliente",
+ "customerName": "Nombre del Cliente",
+ "customerSource": "Fuente del Cliente",
+ "customerCountry": "País del Cliente",
+ "salesperson": "Vendedor",
+ "bizDate": "Fecha",
+ "clearanceCountry": "País de Destino Aduanero",
+ "remark": "Observación",
+ "approvalHistory": "Historial de Aprobación",
+ "contractFile": "Archivo de Contrato",
+ "piFile": "Archivo PI",
+ "transactionSlipFile": "Justificante de Transacción",
+ "vehicle": "Vehículo",
+ "fileN": "Archivo {{num}}",
+ "totalRecords": "{{count}} registros en total",
+ "deleteFailed": "Error al eliminar",
+ "printFailed": "Error al imprimir",
+ "recallFailed": "Error al recuperar",
+ "voidFailed": "Error al anular",
+ "voidSuccess": "Anulado con éxito",
+ "noContractFiles": "Sin archivos de contrato",
+ "noDetail": "Sin detalles",
+ "noPiFiles": "Sin archivos PI",
+ "noTransactionSlipFiles": "Sin justificantes de transacción",
+ "finalList": "Lista Final",
+ "finalDetail": "Detalle Final",
+ "finalDetailTitle": "Detalle del Pedido Final",
+ "viewDetails": "Ver Detalles",
+ "viewApproval": "Ver Aprobación",
+ "recallApproval": "Recuperar Aprobación",
+ "status": {
+ "draft": "Borrador",
+ "pending": "Pendiente de Aprobación",
+ "approved": "Aprobado",
+ "rejected": "Rechazado",
+ "recalled": "Recuperado",
+ "voided": "Anulado"
+ },
+ "actions": {
+ "create": "Crear",
+ "edit": "Editar",
+ "delete": "Eliminar",
+ "submit": "Enviar",
+ "recall": "Recuperar",
+ "void": "Anular",
+ "print": "Imprimir",
+ "copy": "Copiar",
+ "view": "Ver",
+ "save": "Guardar",
+ "cancel": "Cancelar",
+ "add": "Agregar",
+ "recreate": "Recrear"
+ },
+ "detail": {
+ "baseInfo": "Información Básica",
+ "orderDetail": "Detalles del Pedido",
+ "transportInfo": "Detalles de Transporte",
+ "paymentInfo": "Pago Planificado",
+ "attachments": "Adjuntos",
+ "contract": "Archivos de Contrato",
+ "piDoc": "Archivos PI",
+ "transactionSlip": "Justificantes de Transacción",
+ "totalCif": "CIF Total",
+ "contractRate": "Tipo de Cambio del Contrato"
+ },
+ "item": {
+ "model": "Modelo",
+ "exteriorColor": "Color Exterior",
+ "interiorColor": "Color Interior",
+ "mileage": "Kilometraje",
+ "vin": "VIN",
+ "unitPrice": "Precio de Cobro",
+ "clearancePrice": "Precio de Aduana",
+ "carPrice": "Precio del Coche",
+ "taxRefund": "Devolución de Impuestos",
+ "profit": "Beneficio",
+ "officialPrice": "Precio Oficial",
+ "discount": "Descuento",
+ "listingPrice": "Precio de Segunda Mano",
+ "reduction": "Reducción Segunda Mano",
+ "totalPrice": "Precio Total",
+ "increasePrice": "Incremento de Precio"
+ },
+ "transport": {
+ "declarant": "Agente de Aduanas",
+ "etaShipDate": "Fecha Estimada de Envío",
+ "domesticPort": "Puerto Nacional",
+ "shippingMethod": "Método de Envío",
+ "tradeTerm": "Incoterms",
+ "clearancePort": "Puerto de Aduana",
+ "latestArrival": "Última Fecha de Llegada"
+ },
+ "payment": {
+ "remitter": "Remitente",
+ "remitCountry": "País del Remitente",
+ "remitDate": "Fecha de Transferencia",
+ "account": "Cuenta",
+ "amount": "Importe",
+ "paymentItem": "Pago {{num}}"
+ },
+ "fees": {
+ "handlingFee": "Tarifa de Gestión",
+ "retrofitFee": "Tarifa de Adaptación",
+ "fobTransportFee": "Transporte FOB",
+ "portMiscFee": "Gastos Portuarios",
+ "oceanFreight": "Flete Marítimo",
+ "domesticFreight": "Flete Nacional",
+ "commission": "Comisión",
+ "purchaseTax": "Impuesto de Compra",
+ "taxRefund": "Devolución de Impuestos",
+ "accessoryPrice": "Precio de Accesorios",
+ "retentionPrice": "Retención en Cuenta",
+ "carPrice": "Precio del Coche",
+ "increasePrice": "Incremento de Precio",
+ "clearancePrice": "Precio de Aduana",
+ "profit": "Beneficio",
+ "officialPrice": "Precio Oficial",
+ "discount": "Descuento",
+ "listingPrice": "Precio de Segunda Mano",
+ "reduction": "Reducción Segunda Mano",
+ "totalPrice": "Precio Total"
+ },
+ "summary": {
+ "summaryInfo": "Resumen de Información",
+ "profit": "Beneficio",
+ "totalProfit": "Beneficio Total"
+ },
+ "dates": {
+ "pickUpDate": "Fecha de Recogida",
+ "collectAllDate": "Fecha de Recolección Completa"
+ },
+ "otherAttachments": "Otros Adjuntos",
+ "searchPlaceholder": "Buscar nombre del cliente",
+ "empty": "Sin pedidos",
+ "emptyFinal": "Sin pedidos finales",
+ "totalRecords": "{{count}} registros en total",
+ "confirmDelete": "¿Está seguro de eliminar este pedido?",
+ "confirmRecall": "¿Está seguro de recuperar este pedido?",
+ "confirmVoid": "¿Está seguro de anular este pedido?",
+ "confirmSubmit": "¿Confirmar envío?",
+ "deleteSuccess": "Eliminado con éxito",
+ "submitSuccess": "Enviado con éxito",
+ "recallSuccess": "Recuperado con éxito",
+ "voidSuccess": "Anulado con éxito",
+ "saveSuccess": "Guardado con éxito",
+ "loadFailed": "Error al cargar",
+ "loadOrderFailed": "Error al cargar datos del pedido original",
+ "loadOrderSuccess": "Datos del pedido original cargados",
+ "saveFailed": "Error al guardar",
+ "submitFailed": "Error al enviar",
+ "fileUploadSuccess": "¡Archivo subido con éxito!",
+ "fileUploadFailed": "¡Error al subir el archivo!",
+ "fileDeleted": "Archivo eliminado",
+ "uploading": "Subiendo...",
+ "submitting": "Enviando...",
+ "atLeastOneItem": "Se requiere al menos un artículo",
+ "atLeastOnePayment": "Se requiere al menos un pago",
+ "deleteFailed": "Error al eliminar",
+ "recallFailed": "Error al recuperar",
+ "voidFailed": "Error al anular",
+ "printFailed": "Error al imprimir",
+ "noPrintFile": "Archivo de impresión no disponible",
+ "viewDetails": "Ver Detalles",
+ "viewApproval": "Ver Aprobación",
+ "submitForApproval": "Enviar para Aprobación",
+ "recallApproval": "Recuperar Aprobación",
+ "loading": "Cargando...",
+ "fileN": "Archivo {{num}}",
+ "contractFile": "Archivo de Contrato",
+ "piFile": "Archivo PI",
+ "transactionSlipFile": "Justificante de Transacción",
+ "fileUpload": {
+ "contract": "Subir Contrato",
+ "piDoc": "Subir PI",
+ "transactionSlip": "Subir Justificante",
+ "clickToUpload": "Haz clic para subir",
+ "supportMultiple": "Soporte para carga múltiple",
+ "preview": "Vista Previa",
+ "download": "Descargar",
+ "delete": "Eliminar",
+ "fileTypeAccept": "¡Solo se permiten archivos {{accept}}!",
+ "fileSizeMax": "¡El tamaño del archivo no puede exceder {{maxSize}}MB!"
+ },
+ "validation": {
+ "enterPiNo": "Por favor ingrese el Pedido Alibaba/Nº PI",
+ "selectDate": "Por favor seleccione fecha",
+ "selectClearanceCountry": "Por favor seleccione país de destino aduanero",
+ "selectCustomer": "Por favor seleccione cliente",
+ "enterDeclarant": "Por favor ingrese el agente de aduanas",
+ "selectEtaShipDate": "Por favor seleccione fecha estimada de envío",
+ "selectDomesticPort": "Por favor seleccione puerto nacional",
+ "selectShippingMethod": "Por favor seleccione método de envío",
+ "selectTradeTerm": "Por favor seleccione Incoterms",
+ "selectClearancePort": "Por favor seleccione puerto de aduana",
+ "selectLatestArrival": "Por favor seleccione última fecha de llegada",
+ "selectAllVehicles": "Por favor seleccione vehículos para todos los modelos",
+ "uploadContract": "Por favor suba el archivo de contrato",
+ "uploadPiDoc": "Por favor suba el archivo PI",
+ "uploadTransactionSlip": "Por favor suba el justificante de transacción",
+ "selectPickUpDate": "Por favor seleccione fecha de recogida",
+ "selectCollectAllDate": "Por favor seleccione fecha de recolección completa"
+ },
+ "placeholder": {
+ "autoCalculate": "Cálculo Automático",
+ "enterPiNo": "Por favor ingrese el Pedido Alibaba/Nº PI",
+ "selectDate": "Por favor seleccione fecha",
+ "selectClearanceCountry": "Por favor seleccione país de destino aduanero",
+ "selectCustomer": "Por favor seleccione cliente",
+ "autoFillCustomerSource": "Autocompletado al seleccionar cliente",
+ "autoFillCustomerCountry": "Autocompletado al seleccionar cliente",
+ "selectVehicle": "Por favor seleccione modelo de vehículo",
+ "selectExteriorColor": "Por favor seleccione color exterior",
+ "selectInteriorColor": "Por favor seleccione color interior",
+ "newCarMileage": "0 para vehículo nuevo",
+ "enterVin": "Por favor ingrese VIN",
+ "enterOfficialPrice": "Por favor ingrese precio oficial",
+ "enterListingPrice": "Por favor ingrese precio de segunda mano",
+ "enterDiscount": "Por favor ingrese monto de descuento",
+ "enterAmount": "Por favor ingrese",
+ "enterIncreasePrice": "Por favor ingrese incremento de precio",
+ "enterUnitPrice": "Por favor ingrese precio de cobro",
+ "calculatingClearancePrice": "Calculando precio de aduana",
+ "calculatingCarPrice": "Calculando precio del coche",
+ "calculatingTaxRefund": "Calculando devolución de impuestos estimada",
+ "calculatingProfit": "Calculando beneficio",
+ "enterRemark": "Por favor ingrese observación",
+ "enterHandlingFee": "Por favor ingrese",
+ "enterContractRate": "Por favor ingrese tipo de cambio del contrato",
+ "enterDeclarant": "Por favor ingrese agente de aduanas",
+ "selectDomesticPort": "Por favor seleccione puerto nacional",
+ "selectShippingMethod": "Por favor seleccione método de envío",
+ "selectTradeTerm": "Por favor seleccione Incoterms",
+ "selectClearancePort": "Por favor seleccione puerto de aduana",
+ "enterRemitter": "Por favor ingrese remitente",
+ "selectRemitCountry": "Por favor seleccione país del remitente",
+ "selectAccount": "Por favor seleccione cuenta",
+ "enterAmount": "Por favor ingrese monto",
+ "searchCustomer": "Buscar cliente",
+ "searchVehicle": "Buscar vehículo",
+ "selectPickUpDate": "Por favor seleccione fecha de recogida",
+ "selectCollectAllDate": "Por favor seleccione fecha de recolección completa",
+ "enterMileage": "Por favor ingrese kilometraje"
+ },
+ "entry": {
+ "edit": "Editar Pedido",
+ "copy": "Copiar Pedido",
+ "title": "Entrada de Stock",
+ "baseInfo": "Información Básica",
+ "baseInfoReadOnly": "Información Básica (Solo Lectura)",
+ "orderDetail": "Detalles del Pedido",
+ "transportInfo": "Detalles de Transporte",
+ "paymentInfo": "Pago Planificado",
+ "fileUpload": "Subir Archivos",
+ "saveDraft": "Guardar Borrador",
+ "submitApproval": "Enviar para Aprobación",
+ "createFinal": "Crear Final",
+ "detail": "Detalle",
+ "totalPrice": "Total",
+ "unitPrice": "Precio Unitario",
+ "carPrice": "Precio del Coche",
+ "profit": "Beneficio",
+ "officialPrice": "Precio Oficial",
+ "discount": "Descuento",
+ "listingPrice": "Precio de Segunda Mano",
+ "reduction": "Reducción Segunda Mano",
+ "add": "Agregar",
+ "copy": "Copiar"
+ },
+ "select": {
+ "customer": "Seleccionar Cliente",
+ "vehicle": "Seleccionar Vehículo",
+ "year": "Año",
+ "msrp": "Precio de Catálogo"
+ }
}
}
diff --git a/frontend/src/i18n/locales/fr.json b/frontend/src/i18n/locales/fr.json
index 0922bdd..5163cae 100644
--- a/frontend/src/i18n/locales/fr.json
+++ b/frontend/src/i18n/locales/fr.json
@@ -161,5 +161,294 @@
"lastMonth": "Mois Dernier",
"thisYear": "Cette Année",
"lastYear": "Année Dernière"
+ },
+ "stock": {
+ "title": "Commande de Stock",
+ "order": "Commande de Stock",
+ "final": "Commande Finale",
+ "entry": "Saisie de Stock",
+ "list": "Liste des Commandes",
+ "detail": "Détail de la Commande",
+ "edit": "Modifier la Commande",
+ "create": "Créer une Commande",
+ "createFinal": "Créer Finale",
+ "view": "Voir",
+ "viewFinal": "Voir Finale",
+ "orderNo": "Nº Commande",
+ "piNo": "Commande Alibaba/Nº PI",
+ "customer": "Client",
+ "customerName": "Nom du Client",
+ "customerSource": "Source du Client",
+ "customerCountry": "Pays du Client",
+ "salesperson": "Commercial",
+ "bizDate": "Date",
+ "clearanceCountry": "Pays de Dédouanement",
+ "remark": "Remarque",
+ "approvalHistory": "Historique d'Approbation",
+ "contractFile": "Fichier de Contrat",
+ "piFile": "Fichier PI",
+ "transactionSlipFile": "Justificatif de Transaction",
+ "vehicle": "Véhicule",
+ "fileN": "Fichier {{num}}",
+ "totalRecords": "{{count}} enregistrements au total",
+ "deleteFailed": "Échec de la suppression",
+ "printFailed": "Échec de l'impression",
+ "recallFailed": "Échec du rappel",
+ "voidFailed": "Échec de l'annulation",
+ "voidSuccess": "Annulé avec succès",
+ "noContractFiles": "Aucun fichier de contrat",
+ "noDetail": "Aucun détail",
+ "noPiFiles": "Aucun fichier PI",
+ "noTransactionSlipFiles": "Aucun justificatif de transaction",
+ "finalList": "Liste Finale",
+ "finalDetail": "Détail Final",
+ "finalDetailTitle": "Détail de la Commande Finale",
+ "viewDetails": "Voir les Détails",
+ "viewApproval": "Voir l'Approbation",
+ "recallApproval": "Rappeler l'Approbation",
+ "status": {
+ "draft": "Brouillon",
+ "pending": "En Attente d'Approbation",
+ "approved": "Approuvé",
+ "rejected": "Rejeté",
+ "recalled": "Rappelé",
+ "voided": "Annulé"
+ },
+ "actions": {
+ "create": "Créer",
+ "edit": "Modifier",
+ "delete": "Supprimer",
+ "submit": "Soumettre",
+ "recall": "Rappeler",
+ "void": "Annuler",
+ "print": "Imprimer",
+ "copy": "Copier",
+ "view": "Voir",
+ "save": "Enregistrer",
+ "cancel": "Annuler",
+ "add": "Ajouter",
+ "recreate": "Recréer"
+ },
+ "detail": {
+ "baseInfo": "Informations de Base",
+ "orderDetail": "Détails de la Commande",
+ "transportInfo": "Détails du Transport",
+ "paymentInfo": "Paiement Planifié",
+ "attachments": "Pièces Jointes",
+ "contract": "Fichiers de Contrat",
+ "piDoc": "Fichiers PI",
+ "transactionSlip": "Justificatifs de Transaction",
+ "totalCif": "CIF Total",
+ "contractRate": "Taux de Change du Contrat"
+ },
+ "item": {
+ "model": "Modèle",
+ "exteriorColor": "Couleur Extérieure",
+ "interiorColor": "Couleur Intérieure",
+ "mileage": "Kilométrage",
+ "vin": "VIN",
+ "unitPrice": "Prix d'Encaissement",
+ "clearancePrice": "Prix de Douane",
+ "carPrice": "Prix du Véhicule",
+ "taxRefund": "Remboursement Fiscal",
+ "profit": "Bénéfice",
+ "officialPrice": "Prix Officiel",
+ "discount": "Remise",
+ "listingPrice": "Prix d'Occasion",
+ "reduction": "Réduction d'Occasion",
+ "totalPrice": "Prix Total",
+ "increasePrice": "Augmentation de Prix"
+ },
+ "transport": {
+ "declarant": "Transitaire",
+ "etaShipDate": "Date d'Expédition Estimée",
+ "domesticPort": "Port National",
+ "shippingMethod": "Mode de Transport",
+ "tradeTerm": "Incoterms",
+ "clearancePort": "Port de Dédouanement",
+ "latestArrival": "Dernière Date d'Arrivée"
+ },
+ "payment": {
+ "remitter": "Émetteur",
+ "remitCountry": "Pays de l'Émetteur",
+ "remitDate": "Date du Virement",
+ "account": "Compte",
+ "amount": "Montant",
+ "paymentItem": "Paiement {{num}}"
+ },
+ "fees": {
+ "handlingFee": "Frais de Traitement",
+ "retrofitFee": "Frais d'Équipement",
+ "fobTransportFee": "Transport FOB",
+ "portMiscFee": "Frais Portuaires",
+ "oceanFreight": "Fret Maritime",
+ "domesticFreight": "Fret National",
+ "commission": "Commission",
+ "purchaseTax": "Taxe d'Achat",
+ "taxRefund": "Remboursement Fiscal",
+ "accessoryPrice": "Prix des Accessoires",
+ "retentionPrice": "Retenue sur Compte",
+ "carPrice": "Prix du Véhicule",
+ "increasePrice": "Augmentation de Prix",
+ "clearancePrice": "Prix de Douane",
+ "profit": "Bénéfice",
+ "officialPrice": "Prix Officiel",
+ "discount": "Remise",
+ "listingPrice": "Prix d'Occasion",
+ "reduction": "Réduction d'Occasion",
+ "totalPrice": "Prix Total"
+ },
+ "summary": {
+ "summaryInfo": "Résumé des Informations",
+ "profit": "Bénéfice",
+ "totalProfit": "Bénéfice Total"
+ },
+ "dates": {
+ "pickUpDate": "Date de Prise en Charge",
+ "collectAllDate": "Date de Collecte Complète"
+ },
+ "otherAttachments": "Autres Pièces Jointes",
+ "searchPlaceholder": "Rechercher nom du client",
+ "empty": "Aucune commande",
+ "emptyFinal": "Aucune commande finale",
+ "totalRecords": "{{count}} enregistrements au total",
+ "confirmDelete": "Confirmer la suppression de cette commande?",
+ "confirmRecall": "Confirmer le rappel de cette commande?",
+ "confirmVoid": "Confirmer l'annulation de cette commande?",
+ "confirmSubmit": "Confirmer la soumission?",
+ "deleteSuccess": "Supprimé avec succès",
+ "submitSuccess": "Soumis avec succès",
+ "recallSuccess": "Rappelé avec succès",
+ "voidSuccess": "Annulé avec succès",
+ "saveSuccess": "Enregistré avec succès",
+ "loadFailed": "Échec du chargement",
+ "loadOrderFailed": "Échec du chargement des données de la commande originale",
+ "loadOrderSuccess": "Données de la commande originale chargées",
+ "saveFailed": "Échec de l'enregistrement",
+ "submitFailed": "Échec de la soumission",
+ "fileUploadSuccess": "Fichier téléchargé avec succès!",
+ "fileUploadFailed": "Échec du téléchargement du fichier!",
+ "fileDeleted": "Fichier supprimé",
+ "uploading": "Téléchargement...",
+ "submitting": "Soumission...",
+ "atLeastOneItem": "Au moins un article requis",
+ "atLeastOnePayment": "Au moins un paiement requis",
+ "deleteFailed": "Échec de la suppression",
+ "recallFailed": "Échec du rappel",
+ "voidFailed": "Échec de l'annulation",
+ "printFailed": "Échec de l'impression",
+ "noPrintFile": "Fichier d'impression non disponible",
+ "viewDetails": "Voir les Détails",
+ "viewApproval": "Voir l'Approbation",
+ "submitForApproval": "Soumettre pour Approbation",
+ "recallApproval": "Rappeler l'Approbation",
+ "loading": "Chargement...",
+ "fileN": "Fichier {{num}}",
+ "contractFile": "Fichier de Contrat",
+ "piFile": "Fichier PI",
+ "transactionSlipFile": "Justificatif de Transaction",
+ "fileUpload": {
+ "contract": "Télécharger un Contrat",
+ "piDoc": "Télécharger un PI",
+ "transactionSlip": "Télécharger un Justificatif",
+ "clickToUpload": "Cliquez pour télécharger",
+ "supportMultiple": "Support du téléchargement multiple",
+ "preview": "Aperçu",
+ "download": "Télécharger",
+ "delete": "Supprimer",
+ "fileTypeAccept": "Seuls les fichiers {{accept}} sont autorisés!",
+ "fileSizeMax": "La taille du fichier ne peut pas dépasser {{maxSize}}Mo!"
+ },
+ "validation": {
+ "enterPiNo": "Veuillez entrer la Commande Alibaba/Nº PI",
+ "selectDate": "Veuillez sélectionner la date",
+ "selectClearanceCountry": "Veuillez sélectionner le pays de dédouanement",
+ "selectCustomer": "Veuillez sélectionner le client",
+ "enterDeclarant": "Veuillez entrer le transitaire",
+ "selectEtaShipDate": "Veuillez sélectionner la date d'expédition estimée",
+ "selectDomesticPort": "Veuillez sélectionner le port national",
+ "selectShippingMethod": "Veuillez sélectionner le mode de transport",
+ "selectTradeTerm": "Veuillez sélectionner les Incoterms",
+ "selectClearancePort": "Veuillez sélectionner le port de dédouanement",
+ "selectLatestArrival": "Veuillez sélectionner la dernière date d'arrivée",
+ "selectAllVehicles": "Veuillez sélectionner les véhicules pour tous les modèles",
+ "uploadContract": "Veuillez télécharger le fichier de contrat",
+ "uploadPiDoc": "Veuillez télécharger le fichier PI",
+ "uploadTransactionSlip": "Veuillez télécharger le justificatif de transaction",
+ "selectPickUpDate": "Veuillez sélectionner la date de prise en charge",
+ "selectCollectAllDate": "Veuillez sélectionner la date de collecte complète"
+ },
+ "placeholder": {
+ "autoCalculate": "Calcul Automatique",
+ "enterPiNo": "Veuillez entrer la Commande Alibaba/Nº PI",
+ "selectDate": "Veuillez sélectionner la date",
+ "selectClearanceCountry": "Veuillez sélectionner le pays de dédouanement",
+ "selectCustomer": "Veuillez sélectionner le client",
+ "autoFillCustomerSource": "Rempli automatiquement après sélection du client",
+ "autoFillCustomerCountry": "Rempli automatiquement après sélection du client",
+ "selectVehicle": "Veuillez sélectionner le modèle de véhicule",
+ "selectExteriorColor": "Veuillez sélectionner la couleur extérieure",
+ "selectInteriorColor": "Veuillez sélectionner la couleur intérieure",
+ "newCarMileage": "0 pour véhicule neuf",
+ "enterVin": "Veuillez entrer le VIN",
+ "enterOfficialPrice": "Veuillez entrer le prix officiel",
+ "enterListingPrice": "Veuillez entrer le prix d'occasion",
+ "enterDiscount": "Veuillez entrer le montant de la remise",
+ "enterAmount": "Veuillez entrer",
+ "enterIncreasePrice": "Veuillez entrer l'augmentation de prix",
+ "enterUnitPrice": "Veuillez entrer le prix d'encaissement",
+ "calculatingClearancePrice": "Calcul du prix de douane",
+ "calculatingCarPrice": "Calcul du prix du véhicule",
+ "calculatingTaxRefund": "Calcul du remboursement fiscal estimé",
+ "calculatingProfit": "Calcul du bénéfice",
+ "enterRemark": "Veuillez entrer une remarque",
+ "enterHandlingFee": "Veuillez entrer",
+ "enterContractRate": "Veuillez entrer le taux de change du contrat",
+ "enterDeclarant": "Veuillez entrer le transitaire",
+ "selectDomesticPort": "Veuillez sélectionner le port national",
+ "selectShippingMethod": "Veuillez sélectionner le mode de transport",
+ "selectTradeTerm": "Veuillez sélectionner les Incoterms",
+ "selectClearancePort": "Veuillez sélectionner le port de dédouanement",
+ "enterRemitter": "Veuillez entrer l'émetteur",
+ "selectRemitCountry": "Veuillez sélectionner le pays de l'émetteur",
+ "selectAccount": "Veuillez sélectionner le compte",
+ "enterAmount": "Veuillez entrer le montant",
+ "searchCustomer": "Rechercher client",
+ "searchVehicle": "Rechercher véhicule",
+ "selectPickUpDate": "Veuillez sélectionner la date de prise en charge",
+ "selectCollectAllDate": "Veuillez sélectionner la date de collecte complète",
+ "enterMileage": "Veuillez entrer le kilométrage"
+ },
+ "entry": {
+ "edit": "Modifier la Commande",
+ "copy": "Copier la Commande",
+ "title": "Saisie de Stock",
+ "baseInfo": "Informations de Base",
+ "baseInfoReadOnly": "Informations de Base (Lecture Seule)",
+ "orderDetail": "Détails de la Commande",
+ "transportInfo": "Détails du Transport",
+ "paymentInfo": "Paiement Planifié",
+ "fileUpload": "Télécharger Fichiers",
+ "saveDraft": "Enregistrer le Brouillon",
+ "submitApproval": "Soumettre pour Approbation",
+ "createFinal": "Créer Finale",
+ "detail": "Détail",
+ "totalPrice": "Total",
+ "unitPrice": "Prix Unitaire",
+ "carPrice": "Prix du Véhicule",
+ "profit": "Bénéfice",
+ "officialPrice": "Prix Officiel",
+ "discount": "Remise",
+ "listingPrice": "Prix d'Occasion",
+ "reduction": "Réduction d'Occasion",
+ "add": "Ajouter",
+ "copy": "Copier"
+ },
+ "select": {
+ "customer": "Sélectionner Client",
+ "vehicle": "Sélectionner Véhicule",
+ "year": "Année",
+ "msrp": "Prix Catalogue"
+ }
}
}
diff --git a/frontend/src/i18n/locales/ru.json b/frontend/src/i18n/locales/ru.json
index 206daa5..67bf754 100644
--- a/frontend/src/i18n/locales/ru.json
+++ b/frontend/src/i18n/locales/ru.json
@@ -161,5 +161,294 @@
"lastMonth": "В прошлом месяце",
"thisYear": "В этом году",
"lastYear": "В прошлом году"
+ },
+ "stock": {
+ "title": "Заказ на поставку",
+ "order": "Заказ на поставку",
+ "final": "Финальный заказ",
+ "entry": "Ввод заказа",
+ "list": "Список заказов",
+ "detail": "Детали заказа",
+ "edit": "Редактировать заказ",
+ "create": "Создать заказ",
+ "createFinal": "Создать финальный",
+ "view": "Просмотр",
+ "viewFinal": "Просмотр финального",
+ "orderNo": "№ Заказа",
+ "piNo": "Заказ Alibaba/№ PI",
+ "customer": "Клиент",
+ "customerName": "Имя клиента",
+ "customerSource": "Источник клиента",
+ "customerCountry": "Страна клиента",
+ "salesperson": "Менеджер",
+ "bizDate": "Дата",
+ "clearanceCountry": "Страна таможенной очистки",
+ "remark": "Примечание",
+ "approvalHistory": "История согласования",
+ "contractFile": "Файл контракта",
+ "piFile": "Файл PI",
+ "transactionSlipFile": "Банковская квитанция",
+ "vehicle": "Транспортное средство",
+ "fileN": "Файл {{num}}",
+ "totalRecords": "Всего {{count}} записей",
+ "deleteFailed": "Ошибка удаления",
+ "printFailed": "Ошибка печати",
+ "recallFailed": "Ошибка отзыва",
+ "voidFailed": "Ошибка аннулирования",
+ "voidSuccess": "Аннулировано успешно",
+ "noContractFiles": "Нет файлов контракта",
+ "noDetail": "Нет деталей",
+ "noPiFiles": "Нет файлов PI",
+ "noTransactionSlipFiles": "Нет банковских квитанций",
+ "finalList": "Список финальных",
+ "finalDetail": "Детали финального",
+ "finalDetailTitle": "Детали финального заказа",
+ "viewDetails": "Просмотр деталей",
+ "viewApproval": "Просмотр согласования",
+ "recallApproval": "Отозвать согласование",
+ "status": {
+ "draft": "Черновик",
+ "pending": "На согласовании",
+ "approved": "Согласовано",
+ "rejected": "Отклонено",
+ "recalled": "Отозвано",
+ "voided": "Аннулировано"
+ },
+ "actions": {
+ "create": "Создать",
+ "edit": "Редактировать",
+ "delete": "Удалить",
+ "submit": "Отправить",
+ "recall": "Отозвать",
+ "void": "Аннулировать",
+ "print": "Печать",
+ "copy": "Копировать",
+ "view": "Просмотр",
+ "save": "Сохранить",
+ "cancel": "Отмена",
+ "add": "Добавить",
+ "recreate": "Пересоздать"
+ },
+ "detail": {
+ "baseInfo": "Основная информация",
+ "orderDetail": "Детали заказа",
+ "transportInfo": "Детали транспортировки",
+ "paymentInfo": "Планируемая оплата",
+ "attachments": "Вложения",
+ "contract": "Файлы контракта",
+ "piDoc": "Файлы PI",
+ "transactionSlip": "Банковские квитанции",
+ "totalCif": "Итого CIF",
+ "contractRate": "Курс обмена по контракту"
+ },
+ "item": {
+ "model": "Модель",
+ "exteriorColor": "Цвет экстерьера",
+ "interiorColor": "Цвет интерьера",
+ "mileage": "Пробег",
+ "vin": "VIN",
+ "unitPrice": "Цена получения",
+ "clearancePrice": "Таможенная цена",
+ "carPrice": "Цена автомобиля",
+ "taxRefund": "Налоговый возврат",
+ "profit": "Прибыль",
+ "officialPrice": "Рекомендуемая цена",
+ "discount": "Скидка",
+ "listingPrice": "Цена подержанного",
+ "reduction": "Уценка подержанного",
+ "totalPrice": "Общая цена",
+ "increasePrice": "Наценка"
+ },
+ "transport": {
+ "declarant": "Таможенный брокер",
+ "etaShipDate": "Ожидаемая дата отправки",
+ "domesticPort": "Внутренний порт",
+ "shippingMethod": "Способ доставки",
+ "tradeTerm": "Торговые условия",
+ "clearancePort": "Порт таможенной очистки",
+ "latestArrival": "Последняя дата прибытия"
+ },
+ "payment": {
+ "remitter": "Отправитель",
+ "remitCountry": "Страна отправителя",
+ "remitDate": "Дата перевода",
+ "account": "Счет",
+ "amount": "Сумма",
+ "paymentItem": "Оплата {{num}}"
+ },
+ "fees": {
+ "handlingFee": "Комиссия за обработку",
+ "retrofitFee": "Плата за доработку",
+ "fobTransportFee": "Транспорт FOB",
+ "portMiscFee": "Портовые сборы",
+ "oceanFreight": "Морской фрахт",
+ "domesticFreight": "Внутренняя перевозка",
+ "commission": "Комиссия",
+ "purchaseTax": "Налог на покупку",
+ "taxRefund": "Налоговый возврат",
+ "accessoryPrice": "Цена аксессуаров",
+ "retentionPrice": "Удержание на счете",
+ "carPrice": "Цена автомобиля",
+ "increasePrice": "Наценка",
+ "clearancePrice": "Таможенная цена",
+ "profit": "Прибыль",
+ "officialPrice": "Рекомендуемая цена",
+ "discount": "Скидка",
+ "listingPrice": "Цена подержанного",
+ "reduction": "Уценка подержанного",
+ "totalPrice": "Общая цена"
+ },
+ "summary": {
+ "summaryInfo": "Сводная информация",
+ "profit": "Прибыль",
+ "totalProfit": "Общая прибыль"
+ },
+ "dates": {
+ "pickUpDate": "Дата получения",
+ "collectAllDate": "Дата полного сбора"
+ },
+ "otherAttachments": "Другие вложения",
+ "searchPlaceholder": "Поиск по имени клиента",
+ "empty": "Нет заказов",
+ "emptyFinal": "Нет финальных заказов",
+ "totalRecords": "Всего {{count}} записей",
+ "confirmDelete": "Вы уверены, что хотите удалить этот заказ?",
+ "confirmRecall": "Вы уверены, что хотите отозвать этот заказ?",
+ "confirmVoid": "Вы уверены, что хотите аннулировать этот заказ?",
+ "confirmSubmit": "Подтвердить отправку?",
+ "deleteSuccess": "Успешно удалено",
+ "submitSuccess": "Успешно отправлено",
+ "recallSuccess": "Успешно отозвано",
+ "voidSuccess": "Успешно аннулировано",
+ "saveSuccess": "Успешно сохранено",
+ "loadFailed": "Ошибка загрузки",
+ "loadOrderFailed": "Ошибка загрузки данных оригинального заказа",
+ "loadOrderSuccess": "Данные оригинального заказа загружены",
+ "saveFailed": "Ошибка сохранения",
+ "submitFailed": "Ошибка отправки",
+ "fileUploadSuccess": "Файл успешно загружен!",
+ "fileUploadFailed": "Ошибка загрузки файла!",
+ "fileDeleted": "Файл удален",
+ "uploading": "Загрузка...",
+ "submitting": "Отправка...",
+ "atLeastOneItem": "Требуется хотя бы одна позиция",
+ "atLeastOnePayment": "Требуется хотя бы один платеж",
+ "deleteFailed": "Ошибка удаления",
+ "recallFailed": "Ошибка отзыва",
+ "voidFailed": "Ошибка аннулирования",
+ "printFailed": "Ошибка печати",
+ "noPrintFile": "Файл для печати недоступен",
+ "viewDetails": "Просмотр деталей",
+ "viewApproval": "Просмотр согласования",
+ "submitForApproval": "Отправить на согласование",
+ "recallApproval": "Отозвать согласование",
+ "loading": "Загрузка...",
+ "fileN": "Файл {{num}}",
+ "contractFile": "Файл контракта",
+ "piFile": "Файл PI",
+ "transactionSlipFile": "Банковская квитанция",
+ "fileUpload": {
+ "contract": "Загрузить контракт",
+ "piDoc": "Загрузить PI",
+ "transactionSlip": "Загрузить банковскую квитанцию",
+ "clickToUpload": "Нажмите для загрузки",
+ "supportMultiple": "Поддержка загрузки нескольких файлов",
+ "preview": "Предпросмотр",
+ "download": "Скачать",
+ "delete": "Удалить",
+ "fileTypeAccept": "Разрешены только файлы формата {{accept}}!",
+ "fileSizeMax": "Размер файла не может превышать {{maxSize}}МБ!"
+ },
+ "validation": {
+ "enterPiNo": "Пожалуйста, введите номер заказа Alibaba/PI",
+ "selectDate": "Пожалуйста, выберите дату",
+ "selectClearanceCountry": "Пожалуйста, выберите страну таможенной очистки",
+ "selectCustomer": "Пожалуйста, выберите клиента",
+ "enterDeclarant": "Пожалуйста, введите таможенного брокера",
+ "selectEtaShipDate": "Пожалуйста, выберите ожидаемую дату отправки",
+ "selectDomesticPort": "Пожалуйста, выберите внутренний порт",
+ "selectShippingMethod": "Пожалуйста, выберите способ доставки",
+ "selectTradeTerm": "Пожалуйста, выберите торговые условия",
+ "selectClearancePort": "Пожалуйста, выберите порт таможенной очистки",
+ "selectLatestArrival": "Пожалуйста, выберите последнюю дату прибытия",
+ "selectAllVehicles": "Пожалуйста, выберите транспортные средства для всех моделей",
+ "uploadContract": "Пожалуйста, загрузите файл контракта",
+ "uploadPiDoc": "Пожалуйста, загрузите файл PI",
+ "uploadTransactionSlip": "Пожалуйста, загрузите банковскую квитанцию",
+ "selectPickUpDate": "Пожалуйста, выберите дату получения",
+ "selectCollectAllDate": "Пожалуйста, выберите дату полного сбора"
+ },
+ "placeholder": {
+ "autoCalculate": "Автоматический расчет",
+ "enterPiNo": "Пожалуйста, введите номер заказа Alibaba/PI",
+ "selectDate": "Пожалуйста, выберите дату",
+ "selectClearanceCountry": "Пожалуйста, выберите страну таможенной очистки",
+ "selectCustomer": "Пожалуйста, выберите клиента",
+ "autoFillCustomerSource": "Автозаполнение после выбора клиента",
+ "autoFillCustomerCountry": "Автозаполнение после выбора клиента",
+ "selectVehicle": "Пожалуйста, выберите модель транспортного средства",
+ "selectExteriorColor": "Пожалуйста, выберите цвет экстерьера",
+ "selectInteriorColor": "Пожалуйста, выберите цвет интерьера",
+ "newCarMileage": "0 для нового автомобиля",
+ "enterVin": "Пожалуйста, введите VIN",
+ "enterOfficialPrice": "Пожалуйста, введите рекомендованную цену",
+ "enterListingPrice": "Пожалуйста, введите цену подержанного",
+ "enterDiscount": "Пожалуйста, введите сумму скидки",
+ "enterAmount": "Пожалуйста, введите",
+ "enterIncreasePrice": "Пожалуйста, введите наценку",
+ "enterUnitPrice": "Пожалуйста, введите цену получения",
+ "calculatingClearancePrice": "Расчет таможенной цены",
+ "calculatingCarPrice": "Расчет цены автомобиля",
+ "calculatingTaxRefund": "Расчет ожидаемого налогового возврата",
+ "calculatingProfit": "Расчет прибыли",
+ "enterRemark": "Пожалуйста, введите примечание",
+ "enterHandlingFee": "Пожалуйста, введите",
+ "enterContractRate": "Пожалуйста, введите курс обмена по контракту",
+ "enterDeclarant": "Пожалуйста, введите таможенного брокера",
+ "selectDomesticPort": "Пожалуйста, выберите внутренний порт",
+ "selectShippingMethod": "Пожалуйста, выберите способ доставки",
+ "selectTradeTerm": "Пожалуйста, выберите торговые условия",
+ "selectClearancePort": "Пожалуйста, выберите порт таможенной очистки",
+ "enterRemitter": "Пожалуйста, введите отправителя",
+ "selectRemitCountry": "Пожалуйста, выберите страну отправителя",
+ "selectAccount": "Пожалуйста, выберите счет",
+ "enterAmount": "Пожалуйста, введите сумму",
+ "searchCustomer": "Поиск клиента",
+ "searchVehicle": "Поиск транспортного средства",
+ "selectPickUpDate": "Пожалуйста, выберите дату получения",
+ "selectCollectAllDate": "Пожалуйста, выберите дату полного сбора",
+ "enterMileage": "Пожалуйста, введите пробег"
+ },
+ "entry": {
+ "edit": "Редактировать заказ",
+ "copy": "Копировать заказ",
+ "title": "Ввод заказа",
+ "baseInfo": "Основная информация",
+ "baseInfoReadOnly": "Основная информация (только для чтения)",
+ "orderDetail": "Детали заказа",
+ "transportInfo": "Детали транспортировки",
+ "paymentInfo": "Планируемая оплата",
+ "fileUpload": "Загрузка файлов",
+ "saveDraft": "Сохранить черновик",
+ "submitApproval": "Отправить на согласование",
+ "createFinal": "Создать финальный",
+ "detail": "Детали",
+ "totalPrice": "Итого",
+ "unitPrice": "Цена за единицу",
+ "carPrice": "Цена автомобиля",
+ "profit": "Прибыль",
+ "officialPrice": "Рекомендуемая цена",
+ "discount": "Скидка",
+ "listingPrice": "Цена подержанного",
+ "reduction": "Уценка подержанного",
+ "add": "Добавить",
+ "copy": "Копировать"
+ },
+ "select": {
+ "customer": "Выберите клиента",
+ "vehicle": "Выберите транспортное средство",
+ "year": "Год",
+ "msrp": "Рекомендуемая цена"
+ }
}
}
diff --git a/frontend/src/i18n/locales/zh.json b/frontend/src/i18n/locales/zh.json
index 9209488..26a3489 100644
--- a/frontend/src/i18n/locales/zh.json
+++ b/frontend/src/i18n/locales/zh.json
@@ -186,5 +186,278 @@
"lastMonth": "上月",
"thisYear": "今年",
"lastYear": "去年"
+ },
+ "stock": {
+ "title": "备货单",
+ "order": "备货单",
+ "final": "终版备货单",
+ "entry": "备货录入",
+ "list": "备货单列表",
+ "detail": "备货单详情",
+ "edit": "编辑备货单",
+ "create": "创建备货单",
+ "createFinal": "创建终版",
+ "view": "查看",
+ "viewFinal": "查看终版",
+ "orderNo": "备货单号",
+ "piNo": "阿里订单号/PI号",
+ "customer": "客户",
+ "customerName": "客户名称",
+ "customerSource": "客户来源",
+ "customerCountry": "客户所在国",
+ "salesperson": "业务员",
+ "bizDate": "日期",
+ "clearanceCountry": "清关目的国",
+ "remark": "备注",
+ "approvalHistory": "审批历史",
+ "contractFile": "合同文件",
+ "piFile": "PI文件",
+ "transactionSlipFile": "水单文件",
+ "vehicle": "车辆",
+ "fileN": "文件{{num}}",
+ "totalRecords": "共{{count}}条记录",
+ "deleteFailed": "删除失败",
+ "printFailed": "打印失败",
+ "recallFailed": "撤回失败",
+ "voidFailed": "作废失败",
+ "voidSuccess": "作废成功",
+ "noContractFiles": "暂无合同文件",
+ "noDetail": "暂无详情",
+ "noPiFiles": "暂无PI文件",
+ "noTransactionSlipFiles": "暂无水单文件",
+ "finalList": "终版列表",
+ "finalDetail": "终版详情",
+ "finalDetailTitle": "终版备货单详情",
+ "viewDetails": "查看详情",
+ "viewApproval": "查看审批",
+ "recallApproval": "撤回审批",
+ "status": {
+ "draft": "草稿",
+ "pending": "审批中",
+ "approved": "已通过",
+ "rejected": "已拒绝",
+ "recalled": "已撤回",
+ "voided": "已作废"
+ },
+ "actions": {
+ "create": "创建",
+ "edit": "编辑",
+ "delete": "删除",
+ "submit": "提交",
+ "recall": "撤回",
+ "void": "作废",
+ "print": "打印",
+ "copy": "复制",
+ "view": "查看",
+ "save": "保存",
+ "cancel": "取消",
+ "add": "添加",
+ "recreate": "重建"
+ },
+ "detail": {
+ "baseInfo": "基础信息",
+ "orderDetail": "订单明细",
+ "transportInfo": "运输详情",
+ "paymentInfo": "计划收款信息",
+ "attachments": "附件",
+ "contract": "合同文件",
+ "piDoc": "PI文件",
+ "transactionSlip": "水单文件",
+ "totalCif": "合计到岸价",
+ "contractRate": "合同汇率"
+ },
+ "item": {
+ "model": "车型",
+ "exteriorColor": "外饰颜色",
+ "interiorColor": "内饰颜色",
+ "mileage": "里程",
+ "vin": "车架号",
+ "unitPrice": "收款价",
+ "clearancePrice": "报关价",
+ "carPrice": "车价",
+ "taxRefund": "退税额",
+ "profit": "利润",
+ "officialPrice": "官方指导价",
+ "discount": "优惠",
+ "listingPrice": "二手挂牌价",
+ "reduction": "二手减价",
+ "totalPrice": "总价",
+ "increasePrice": "加价"
+ },
+ "transport": {
+ "declarant": "清关人",
+ "etaShipDate": "预计发送日",
+ "domesticPort": "国内收货港",
+ "shippingMethod": "运输方式",
+ "tradeTerm": "贸易条款",
+ "clearancePort": "清关目的港",
+ "latestArrival": "最迟到港时间"
+ },
+ "dates": {
+ "pickUpDate": "提车日期",
+ "collectAllDate": "全部收齐日期"
+ },
+ "loading": "加载中...",
+ "empty": "暂无备货单",
+ "emptyFinal": "暂无终版备货单",
+ "confirmDelete": "确定要删除此备货单吗?",
+ "confirmRecall": "确定要撤回此备货单吗?",
+ "confirmVoid": "确定要作废此备货单吗?",
+ "confirmSubmit": "确认提交?",
+ "deleteSuccess": "删除成功",
+ "submitSuccess": "提交成功",
+ "recallSuccess": "撤回成功",
+ "voidSuccess": "作废成功",
+ "saveSuccess": "保存成功",
+ "loadFailed": "加载失败",
+ "loadOrderFailed": "加载原订单数据失败",
+ "loadOrderSuccess": "已加载原订单数据",
+ "saveFailed": "保存失败",
+ "submitFailed": "提交失败",
+ "fileUploadSuccess": "文件上传成功!",
+ "fileUploadFailed": "文件上传失败!",
+ "fileDeleted": "文件已删除",
+ "uploading": "上传中...",
+ "submitting": "提交中...",
+ "atLeastOneItem": "至少保留一条明细",
+ "atLeastOnePayment": "至少保留一条收款信息",
+ "fees": {
+ "handlingFee": "手续费",
+ "retrofitFee": "改装费",
+ "fobTransportFee": "FOB运费",
+ "portMiscFee": "港杂费",
+ "oceanFreight": "海运费",
+ "domesticFreight": "国内运费",
+ "commission": "佣金",
+ "purchaseTax": "购置税",
+ "taxRefund": "退税",
+ "accessoryPrice": "配件价",
+ "retentionPrice": "挂账",
+ "carPrice": "车价",
+ "increasePrice": "加价",
+ "clearancePrice": "报关价",
+ "profit": "利润",
+ "officialPrice": "官方指导价",
+ "discount": "优惠额",
+ "listingPrice": "挂牌价",
+ "reduction": "二手减价",
+ "totalPrice": "总价"
+ },
+ "payment": {
+ "remitter": "汇款人",
+ "remitCountry": "汇款国",
+ "remitDate": "汇款日期",
+ "account": "收款账户",
+ "amount": "金额",
+ "paymentItem": "收款{{num}}"
+ },
+ "summary": {
+ "summaryInfo": "汇总信息",
+ "profit": "利润"
+ },
+ "searchPlaceholder": "搜索客户名称",
+ "fileUpload": {
+ "contract": "上传合同",
+ "piDoc": "上传PI",
+ "transactionSlip": "上传水单",
+ "clickToUpload": "点击上传",
+ "supportMultiple": "支持多文件上传",
+ "preview": "预览",
+ "download": "下载",
+ "delete": "删除",
+ "fileTypeAccept": "只能上传{{accept}}格式的文件!",
+ "fileSizeMax": "文件大小不能超过 {{maxSize}}MB!"
+ },
+ "validation": {
+ "enterPiNo": "请输入阿里订单号/PI号",
+ "selectDate": "请选择日期",
+ "selectClearanceCountry": "请选择清关目的国",
+ "selectCustomer": "请选择客户",
+ "enterDeclarant": "请输入清关人",
+ "selectEtaShipDate": "请选择预计发送日",
+ "selectDomesticPort": "请选择国内收货港",
+ "selectShippingMethod": "请选择运输方式",
+ "selectTradeTerm": "请选择贸易条款",
+ "selectClearancePort": "请选择清关目的港",
+ "selectLatestArrival": "请选择最迟到港时间",
+ "selectAllVehicles": "请选择所有车型的车辆",
+ "uploadContract": "请上传合同文件",
+ "uploadPiDoc": "请上传PI文件",
+ "uploadTransactionSlip": "请上传水单文件",
+ "selectPickUpDate": "请选择提车日期",
+ "selectCollectAllDate": "请选择全部收齐日期"
+ },
+ "placeholder": {
+ "autoCalculate": "自动计算",
+ "enterPiNo": "请输入阿里订单号/PI号",
+ "selectDate": "请选择日期",
+ "selectClearanceCountry": "请选择清关目的国",
+ "selectCustomer": "请选择客户",
+ "autoFillCustomerSource": "选择客户后自动填充",
+ "autoFillCustomerCountry": "选择客户后自动填充",
+ "selectVehicle": "请选择车型",
+ "selectExteriorColor": "请选择外饰颜色",
+ "selectInteriorColor": "请选择内饰颜色",
+ "newCarMileage": "新车为0",
+ "enterVin": "请输入车架号",
+ "enterOfficialPrice": "请输入官方指导价",
+ "enterListingPrice": "请输入二手挂牌价",
+ "enterDiscount": "请输入优惠额",
+ "enterAmount": "请输入",
+ "enterIncreasePrice": "请输入加价",
+ "enterUnitPrice": "请输入收款价",
+ "calculatingClearancePrice": "正在计算报关价",
+ "calculatingCarPrice": "正在计算车价",
+ "calculatingTaxRefund": "正在计算预估退税额",
+ "calculatingProfit": "正在计算利润",
+ "enterRemark": "请输入备注",
+ "enterHandlingFee": "请输入",
+ "enterContractRate": "请输入合同汇率",
+ "enterDeclarant": "请输入清关人",
+ "selectDomesticPort": "请选择国内收货港",
+ "selectShippingMethod": "请选择运输方式",
+ "selectTradeTerm": "请选择贸易条款",
+ "selectClearancePort": "请选择清关目的港",
+ "enterRemitter": "请输入汇款人",
+ "selectRemitCountry": "请选择汇款国",
+ "selectAccount": "请选择账户",
+ "enterAmount": "请输入金额",
+ "searchCustomer": "搜索客户",
+ "searchVehicle": "搜索车型",
+ "selectPickUpDate": "请选择提车日期",
+ "selectCollectAllDate": "请选择全部收齐日期",
+ "enterMileage": "请输入里程"
+ },
+ "entry": {
+ "edit": "编辑备货单",
+ "copy": "复制备货单",
+ "title": "备货录入",
+ "baseInfo": "基础信息",
+ "baseInfoReadOnly": "基础信息(只读)",
+ "orderDetail": "订单明细",
+ "transportInfo": "运输详情",
+ "paymentInfo": "计划收款",
+ "fileUpload": "文件上传",
+ "saveDraft": "保存草稿",
+ "submitApproval": "提交审批",
+ "createFinal": "创建终版",
+ "detail": "详情",
+ "totalPrice": "合计",
+ "unitPrice": "单价",
+ "carPrice": "车价",
+ "profit": "利润",
+ "officialPrice": "官方指导价",
+ "discount": "优惠额",
+ "listingPrice": "挂牌价",
+ "reduction": "二手减价",
+ "add": "添加",
+ "copy": "复制"
+ },
+ "select": {
+ "customer": "选择客户",
+ "vehicle": "选择车型",
+ "year": "年款",
+ "msrp": "指导价"
+ }
}
}
diff --git a/frontend/src/main.tsx b/frontend/src/main.tsx
index 7d84477..9978701 100644
--- a/frontend/src/main.tsx
+++ b/frontend/src/main.tsx
@@ -27,9 +27,7 @@ function App() {
}
createRoot(document.getElementById('root')!).render(
-
-
-
-
-
+
+
+
)
diff --git a/frontend/src/pages/approval/list.tsx b/frontend/src/pages/approval/list.tsx
index 25af7d3..fb607da 100644
--- a/frontend/src/pages/approval/list.tsx
+++ b/frontend/src/pages/approval/list.tsx
@@ -67,16 +67,6 @@ export default function ApprovalList() {
const currentTab = tab || activeTab
- console.log('🔍 Loading approval list START:', {
- requestId,
- currentTab,
- activeTab,
- tab,
- pageNum,
- isRefresh,
- requestIdRefNow: requestIdRef.current
- })
-
setLoading(true)
try {
@@ -86,31 +76,19 @@ export default function ApprovalList() {
page_size: 20,
})
- console.log('📦 API response received:', {
- requestId,
- currentTab,
- requestIdRefNow: requestIdRef.current,
- count: result?.list?.length
- })
-
// 检查是否是最新的请求
if (requestIdRef.current !== requestId) {
- console.log('⏭️ Skipping outdated response:', {
- requestId,
- latestRequestId: requestIdRef.current
- })
return
}
const newList = result?.list || []
// 直接更新数据
- console.log('✅ Updating list for tab:', currentTab, 'items:', newList.length)
setList(newList)
setHasMore(newList.length >= 20)
setPage(pageNum)
} catch (error: any) {
- console.error('❌ Failed to load approvals:', error)
+ // 静默处理错误
} finally {
setLoading(false)
}
@@ -118,7 +96,6 @@ export default function ApprovalList() {
// 切换标签
const handleTabChange = (key: string) => {
- console.log('🔄 Tab changing from', activeTab, 'to', key)
setActiveTab(key as 'my' | 'pending' | 'approved' | 'cc')
// 保存 tab 到 sessionStorage
sessionStorage.setItem('approval-tab', key)
@@ -141,12 +118,7 @@ export default function ApprovalList() {
// 初始加载
useEffect(() => {
- console.log('🚀 Initial load effect')
loadData(1, true)
-
- return () => {
- console.log('🧹 Cleanup function called')
- }
}, [])
// 查看详情
diff --git a/frontend/src/pages/dashboard/index.tsx b/frontend/src/pages/dashboard/index.tsx
index fba237f..61f55f2 100644
--- a/frontend/src/pages/dashboard/index.tsx
+++ b/frontend/src/pages/dashboard/index.tsx
@@ -33,8 +33,10 @@ interface ActivityItem {
// 从应用页面迁移的完整应用列表
const quickActions: QuickAction[] = [
{ id: '1', icon: '✈️', label: '出差申请', path: '/business-trip/apply', color: '#1677ff', description: '提交出差申请' },
- { id: '2', icon: '📝', label: '采购申请', path: '/purchase/apply', color: '#52c41a', description: '物品采购审批', disabled: true },
- { id: '3', icon: '📦', label: '备货录入', path: '/stock/entry', color: '#faad14', description: '库存备货录入', disabled: true },
+ { id: '2', icon: '📦', label: '备货单', path: '/stock/order', color: '#52c41a', description: '备货单管理' },
+ { id: '3', icon: '📋', label: '终版备货单', path: '/stock/final', color: '#722ed1', description: '终版备货单管理' },
+ { id: '4', icon: '📝', label: '采购申请', path: '/purchase/apply', color: '#13c2c2', description: '物品采购审批', disabled: true },
+ { id: '5', icon: '📥', label: '备货录入', path: '/stock/entry', color: '#faad14', description: '库存备货录入' },
]
const allApps: QuickAction[] = [
diff --git a/frontend/src/pages/login-simple/index.tsx b/frontend/src/pages/login-simple/index.tsx
index aea3ba6..0017a4a 100644
--- a/frontend/src/pages/login-simple/index.tsx
+++ b/frontend/src/pages/login-simple/index.tsx
@@ -2,6 +2,7 @@ import { useState } from 'react'
import { useNavigate } from 'react-router-dom'
import { Button, Input, Toast } from 'antd-mobile'
import { useUserStore } from '@/stores/user'
+import { fdeskApiObj } from '@/services/api'
import styles from './login-simple.module.css'
export default function SimpleLogin() {
@@ -30,9 +31,35 @@ export default function SimpleLogin() {
const result = await response.json()
if (result.code === 0) {
+ const { user, token } = result.data
// 登录成功,保存到 store
- login(result.data.user, result.data.token)
+ login(user, token)
localStorage.setItem('i18nextLng', 'zh')
+
+ // 获取用户系统映射,然后获取 fdesk 系统的独立 token
+ if (user.company?.abbr) {
+ fdeskApiObj.getSystemMapping()
+ .then((mappingResult: any) => {
+ // api 响应拦截器返回 data.data,所以直接访问 system_user_id
+ if (mappingResult?.system_user_id) {
+ return fdeskApiObj.getSystemToken({
+ system_user_id: mappingResult.system_user_id,
+ corp_abbr: user.company.abbr,
+ })
+ }
+ return Promise.resolve(null)
+ })
+ .then((fdeskResult: any) => {
+ if (fdeskResult?.access_token) {
+ const { setFdeskToken } = useUserStore.getState()
+ setFdeskToken(fdeskResult.access_token)
+ }
+ })
+ .catch(() => {
+ // 不阻塞登录流程,继续执行
+ })
+ }
+
Toast.show('登录成功')
// 跳转到首页
navigate('/dashboard')
diff --git a/frontend/src/pages/login/QRCodeLogin.tsx b/frontend/src/pages/login/QRCodeLogin.tsx
index ec9a1c2..8611e32 100644
--- a/frontend/src/pages/login/QRCodeLogin.tsx
+++ b/frontend/src/pages/login/QRCodeLogin.tsx
@@ -2,6 +2,8 @@ import { useState, useEffect, useRef } from 'react'
import { Dialog, Toast } from 'antd-mobile'
import { QRCodeSVG } from 'qrcode.react'
import { useNavigate } from 'react-router-dom'
+import { useUserStore } from '@/stores/user'
+import { fdeskApiObj } from '@/services/api'
import styles from './QRCodeLogin.module.css'
// 声明飞书 QR SDK 类型
@@ -247,25 +249,46 @@ export default function QRCodeLogin({ visible, onClose }: QRCodeLoginProps) {
if (result.code === 0) {
if (result.data.status === 'success') {
- console.log('✅ Login success detected, navigating to dashboard...')
setStatus('success')
// 登录成功,保存 token
const { token, user } = result.data
localStorage.setItem('token', token)
localStorage.setItem('i18nextLng', 'zh')
- // 触发登录成功事件,让 store 更新
- window.dispatchEvent(new CustomEvent('feishu-login-success', { detail: { user, token } }))
+ // 更新 store
+ const { login, setFdeskToken } = useUserStore.getState()
+ login(user, token)
+
+ // 获取用户系统映射,然后获取 fdesk 系统的独立 token
+ if (user.company?.abbr) {
+ fdeskApiObj.getSystemMapping()
+ .then((mappingResult: any) => {
+ // api 响应拦截器返回 data.data,所以直接访问 system_user_id
+ if (mappingResult?.system_user_id) {
+ return fdeskApiObj.getSystemToken({
+ system_user_id: mappingResult.system_user_id,
+ corp_abbr: user.company.abbr,
+ })
+ }
+ return Promise.resolve(null)
+ })
+ .then((fdeskResult: any) => {
+ if (fdeskResult?.access_token) {
+ setFdeskToken(fdeskResult.access_token)
+ }
+ })
+ .catch(() => {
+ // 不阻塞登录流程,继续执行
+ })
+ }
Toast.show('登录成功')
setTimeout(() => {
- console.log('🚀 Navigating...')
onClose()
// 检查是否有 redirect 参数
const urlParams = new URLSearchParams(window.location.search)
const redirect = urlParams.get('redirect')
if (redirect) {
- console.log('🔗 登录成功,跳转到:', redirect)
window.location.href = redirect
} else {
navigate('/dashboard')
diff --git a/frontend/src/pages/login/index.tsx b/frontend/src/pages/login/index.tsx
index f84827d..9401ab3 100644
--- a/frontend/src/pages/login/index.tsx
+++ b/frontend/src/pages/login/index.tsx
@@ -2,6 +2,7 @@ import { useEffect, useState } from 'react'
import { useNavigate } from 'react-router-dom'
import { Toast } from 'antd-mobile'
import { useUserStore } from '@/stores/user'
+import { fdeskApiObj } from '@/services/api'
import QRCodeLogin from './QRCodeLogin'
import styles from './login.module.css'
@@ -219,7 +220,33 @@ export default function Login() {
const result = await response.json()
if (result.code === 0) {
- login(result.data.user, result.data.token)
+ const { user, token } = result.data
+ login(user, token)
+
+ // 获取用户系统映射,然后获取 fdesk 系统的独立 token
+ if (user.company?.abbr) {
+ fdeskApiObj.getSystemMapping()
+ .then((mappingResult: any) => {
+ // api 响应拦截器返回 data.data,所以直接访问 system_user_id
+ if (mappingResult?.system_user_id) {
+ return fdeskApiObj.getSystemToken({
+ system_user_id: mappingResult.system_user_id,
+ corp_abbr: user.company.abbr,
+ })
+ }
+ return Promise.resolve(null)
+ })
+ .then((fdeskResult: any) => {
+ if (fdeskResult?.access_token) {
+ const { setFdeskToken } = useUserStore.getState()
+ setFdeskToken(fdeskResult.access_token)
+ }
+ })
+ .catch(() => {
+ // 不阻塞登录流程,继续执行
+ })
+ }
+
Toast.show('登录成功')
const urlParams = new URLSearchParams(window.location.search)
diff --git a/frontend/src/router/index.tsx b/frontend/src/router/index.tsx
index db03efe..a436cc1 100644
--- a/frontend/src/router/index.tsx
+++ b/frontend/src/router/index.tsx
@@ -17,6 +17,14 @@ import Login from '@/pages/login'
import SimpleLogin from '@/pages/login-simple'
import DebugFeishu from '@/pages/debug-feishu'
import FeishuEnvTest from '@/pages/feishu-env-test'
+import StockEntry from '@/pages/stock/entry'
+import StockOrder from '@/pages/stock/order'
+import StockShow from '@/pages/stock/show'
+import StockEdit from '@/pages/stock/edit'
+import StockFinal from '@/pages/stock/final'
+import StockFinalShow from '@/pages/stock/final-show'
+import StockFinalEdit from '@/pages/stock/final-edit'
+import StockFinalEntry from '@/pages/stock/final-entry'
const router = createBrowserRouter([
{
@@ -36,6 +44,14 @@ const router = createBrowserRouter([
{ path: 'business-trip/:id', element: },
{ path: 'approval', element: },
{ path: 'approval/:id', element: },
+ { path: 'stock/entry', element: },
+ { path: 'stock/order', element: },
+ { path: 'stock/show', element: },
+ { path: 'stock/edit', element: },
+ { path: 'stock/final', element: },
+ { path: 'stock/final-show', element: },
+ { path: 'stock/final-edit', element: },
+ { path: 'stock/final-entry', element: },
],
},
// 详情页移到顶层,支持未登录访问(页面自己处理登录检查)
diff --git a/frontend/src/services/api.ts b/frontend/src/services/api.ts
index ad361b6..85ab4aa 100644
--- a/frontend/src/services/api.ts
+++ b/frontend/src/services/api.ts
@@ -1,5 +1,6 @@
import axios from 'axios'
import { Toast } from 'antd-mobile'
+import HmacMD5 from 'crypto-js/hmac-md5'
// 声明飞书 JSAPI 类型
declare global {
@@ -42,6 +43,9 @@ export interface LoginResponse {
const API_BASE = '/api/v1'
+// ========== Fdesk API 配置 ==========
+const FDESK_BASE = 'https://fdesk.hnxinyouhe.com/'
+
// 标记是否正在刷新 token
let isRefreshing = false
let failedQueue: Array<{
@@ -240,6 +244,89 @@ const api = axios.create({
},
})
+// 创建 fdesk axios 实例(独立的一套请求系统)
+const fdeskApi = axios.create({
+ baseURL: FDESK_BASE,
+ timeout: 30000,
+ headers: {
+ 'Content-Type': 'application/json',
+ },
+})
+
+// 获取公司标识的辅助函数
+// zustand persist 存储结构: { state: { user, currentCompany, token, isLoggedIn }, version: 0 }
+const getCompanyAbbr = (): string | undefined => {
+ try {
+ const userStorage = localStorage.getItem('user-storage')
+ if (userStorage) {
+ const stored = JSON.parse(userStorage)
+ // zustand persist 存储在 state 对象下
+ const abbr = stored.state?.currentCompany?.abbr
+ console.log('🔍 [getCompanyAbbr] abbr:', abbr)
+ return abbr
+ }
+ } catch (e) {
+ console.error('❌ [getCompanyAbbr] 获取公司标识失败:', e)
+ }
+ return undefined
+}
+
+// fdesk 请求拦截器:添加特殊请求头
+fdeskApi.interceptors.request.use(
+ (config) => {
+ // 使用独立的 fdesk_token(不是本地登录的 token)
+ const fdeskToken = localStorage.getItem('fdesk_token')
+ if (fdeskToken) {
+ config.headers['Fdesk-Auth-Token'] = `Bearer ${fdeskToken}`
+ }
+ // 添加公司标识
+ const companyAbbr = getCompanyAbbr()
+ if (companyAbbr) {
+ config.headers['Fdesk-Corp-Abbr'] = companyAbbr
+ }
+ // 添加语言头
+ const lang = localStorage.getItem('i18nextLng') || 'zh'
+ config.headers['Fdesk-Language'] = lang
+ // 固定头
+ config.headers['Fdesk-Login-Type'] = 'feishu'
+ config.headers['Fdesk-System-Code'] = 'fdesk'
+ return config
+ },
+ (error) => {
+ return Promise.reject(error)
+ }
+)
+
+// fdesk 响应拦截器:处理错误
+// 参考项目使用 code: 200 表示成功,同时兼容 code: 0 和直接返回数据的情况
+fdeskApi.interceptors.response.use(
+ (response) => {
+ const { data } = response
+
+ // 情况1: 标准 fdesk 响应 { code: 200, data: {...} }
+ if (data.code === 200) {
+ return data.data !== undefined ? data.data : data
+ }
+
+ // 情况2: 兼容 code: 0 的响应
+ if (data.code === 0 || data.success === true) {
+ return data.data !== undefined ? data.data : data
+ }
+
+ // 情况3: 直接返回数据(没有 code 字段),如数组等
+ if (data.code === undefined) {
+ return data
+ }
+
+ // 错误响应
+ return Promise.reject(new Error(data.message || data.msg || '请求失败'))
+ },
+ async (error) => {
+ const message = error.response?.data?.message || error.response?.data?.msg || error.message || '网络错误'
+ return Promise.reject(new Error(message))
+ }
+)
+
// 请求拦截器:添加 token 和语言
api.interceptors.request.use(
(config) => {
@@ -401,4 +488,129 @@ export const approvalApi = {
cancel: (id: number) => api.post(`/approval/cancel/${id}`),
}
+// ========== Fdesk API ==========
+// Fdesk API 接口(使用独立的请求头和 baseURL)
+export const fdeskApiObj = {
+ // 示例:获取用户信息
+ getUserInfo: () => fdeskApi.get('/api/user/info'),
+
+ // 示例:获取列表
+ getList: (params?: any) => fdeskApi.get('/api/list', { params }),
+
+ // 示例:创建数据
+ create: (data: any) => fdeskApi.post('/api/create', data),
+
+ // ========== 备货单 API ==========
+ // 备货单列表
+ stockingList: (params?: any) => fdeskApi.get('/crm/stock/order/list', { params }),
+
+ // 备货单详情
+ stockingDetail: (id: string | number) => fdeskApi.get('/crm/stock/order/detail', { params: { id } }),
+ stockingShow: (id: string | number) => fdeskApi.get('/crm/stock/order/show', { params: { id } }),
+ checkStock: (data: { id: string | number }) => fdeskApi.get('/crm/stock/order/check', { params: data }),
+
+ // 添加备货单
+ stockingAdd: (data: unknown) => fdeskApi.post('/crm/stock/order/store', data),
+
+ // 编辑备货单
+ stockingUpdate: (data: any) => fdeskApi.post('/crm/stock/order/update', data),
+
+ // 提交备货单审批
+ stockingSubmit: (data: any) => fdeskApi.post('/crm/stock/order/approval', data),
+
+ // 备货单打印
+ stockingPrint: (id: string | number) => fdeskApi.get('/crm/stock/order/print', { params: { id } }),
+
+ // 备货单审批记录
+ stockingApprovalRecords: (params?: any) => fdeskApi.get('/crm/approval/records/todo', { params }),
+
+ // 备货单审批通过/拒绝
+ stockingApprovalAction: (data: any) => fdeskApi.post('/crm/approval/records/action', data),
+
+ // 备货单审批历史
+ stockingApprovalLogs: (params?: any) => fdeskApi.get('/crm/approval/records/logs', { params }),
+
+ // 终极备货单提交
+ stockingUltimateApproval: (data: any) => fdeskApi.post('/crm/stock/order/ultimate/approval', data),
+
+ // 备货单撤回
+ stockingRecall: (data: any) => fdeskApi.post('/crm/stock/order/revoke/approval', data),
+
+ // 备货单作废
+ stockingInvalid: (data: any) => fdeskApi.post('/crm/stock/order/nullify/approval', data),
+
+ // 备货单删除
+ stockingDelete: (data: any) => fdeskApi.post('/crm/stock/order/destroy', data),
+
+ // 获取客户列表(分页)
+ getCustomerList: (params?: any) => fdeskApi.get('/crm/customer/list', { params }),
+
+ // 获取国家列表
+ getCountryList: () => fdeskApi.get('/crm/customer/getCountry'),
+
+ // 获取渠道列表
+ getChannelList: () => fdeskApi.get('/crm/customer/getChannel'),
+
+ // 获取港口下拉选项(用于表单选择)
+ getPortSelect: (params?: any) => fdeskApi.get('/common/preset/port', { params }),
+
+ // 获取车型列表(分页,用于选择车型)
+ getVehicleList: (params?: any) => fdeskApi.get('/crm/vehicle/sku/list', { params }),
+
+ // 获取币种列表
+ getCurrencyList: () => fdeskApi.get('/common/currency/list'),
+
+ // 获取币种下拉选项(用于表单选择)
+ getCurrencySelect: () => fdeskApi.get('/common/preset/currency'),
+
+ // 获取账户下拉选项(用于表单选择)
+ getAccountSelect: () => fdeskApi.get('/common/preset/account'),
+
+ // 获取颜色下拉选项(type: 1=外饰, 2=内饰)
+ getColorSelect: (params: any) => fdeskApi.get('/common/preset/color', { params }),
+
+ // 获取字典数据(运输方式、贸易条款等)
+ // scope: '2,15' -> 2=运输方式(transport), 15=贸易条款(trade_terms)
+ getDictSelect: (params?: any) => fdeskApi.get('/admin/dict/scope', { params }),
+
+ // 获取运输方式列表(使用 getDictSelect({ scope: '2,15' }) 返回的 transport 字段)
+ getTransportList: () => fdeskApi.get('/admin/dict/scope', { params: { scope: '2,15' } }),
+
+ // 获取贸易条款列表(使用 getDictSelect({ scope: '2,15' }) 返回的 trade_terms 字段)
+ getTradeTermList: () => fdeskApi.get('/admin/dict/scope', { params: { scope: '2,15' } }),
+
+ // 上传文件
+ uploadFile: (file: File, scene: string = 'stock_orders') => {
+ const formData = new FormData()
+ formData.append('file', file)
+ formData.append('scene', scene)
+ return fdeskApi.post('/ticket/upload', formData, {
+ headers: { 'Content-Type': 'multipart/form-data' }
+ })
+ },
+
+ // 获取审批时间线(用于显示审批历史)
+ stockingApprovalTimeline: (params?: any) => fdeskApi.get('/admin/approval/records/timeline', { params }),
+
+ // ========== Fdesk 系统 Token API ==========
+ // 获取用户系统映射(返回 system_user_id)
+ getSystemMapping: () => api.get('/user/system-mapping', { params: { system: 'fdesk' } }),
+
+ // 获取 fdesk 系统的独立 token
+ getSystemToken: (params: { system_user_id: string; corp_abbr: string }) => {
+ const timestamp = Math.floor(Date.now() / 1000)
+ // 使用 HMAC-MD5 算法生成签名
+ const signature = HmacMD5(`${params.system_user_id}${timestamp}`, 'a7H9kL2pQ5sW8xZ1').toString()
+
+ return fdeskApi.post('/api/system/auth', {
+ user_id: params.system_user_id,
+ timestamp,
+ signature,
+ system_code: 'fdesk',
+ corp_abbr: params.corp_abbr,
+ })
+ }
+}
+
+export { fdeskApi }
export default api
diff --git a/frontend/src/stores/user.ts b/frontend/src/stores/user.ts
index 28d482b..551e034 100644
--- a/frontend/src/stores/user.ts
+++ b/frontend/src/stores/user.ts
@@ -7,6 +7,7 @@ interface UserState {
token: string | null
currentCompany: Company | null
isLoggedIn: boolean
+ fdeskToken: string | null // Fdesk 系统的独立 token
// Actions
setUser: (user: User | null) => void
@@ -14,6 +15,7 @@ interface UserState {
setCurrentCompany: (company: Company | null) => void
login: (user: User, token: string) => void
logout: () => void
+ setFdeskToken: (token: string | null) => void
}
export const useUserStore = create()(
@@ -23,6 +25,7 @@ export const useUserStore = create()(
token: null,
currentCompany: null,
isLoggedIn: false,
+ fdeskToken: null,
setUser: (user) => set({ user }),
setToken: (token) => set({ token }),
@@ -30,12 +33,22 @@ export const useUserStore = create()(
login: (user, token) => {
localStorage.setItem('token', token)
- set({ user, token, isLoggedIn: true })
+ set({ user, token, isLoggedIn: true, currentCompany: user.company || null })
},
logout: () => {
localStorage.removeItem('token')
- set({ user: null, token: null, currentCompany: null, isLoggedIn: false })
+ localStorage.removeItem('fdesk_token')
+ set({ user: null, token: null, currentCompany: null, isLoggedIn: false, fdeskToken: null })
+ },
+
+ setFdeskToken: (token) => {
+ if (token) {
+ localStorage.setItem('fdesk_token', token)
+ } else {
+ localStorage.removeItem('fdesk_token')
+ }
+ set({ fdeskToken: token })
},
}),
{
@@ -45,6 +58,7 @@ export const useUserStore = create()(
token: state.token,
currentCompany: state.currentCompany,
isLoggedIn: state.isLoggedIn,
+ fdeskToken: state.fdeskToken,
}),
}
)
diff --git a/frontend/src/types/index.ts b/frontend/src/types/index.ts
index 4025ce8..19c29c4 100644
--- a/frontend/src/types/index.ts
+++ b/frontend/src/types/index.ts
@@ -24,6 +24,7 @@ export type ApprovalNodeType = (typeof ApprovalNodeType)[keyof typeof ApprovalNo
export interface User {
id: number
company_id: number
+ company?: Company // 用户所属公司信息(登录时返回)
sn?: string
name: string
email?: string
@@ -38,7 +39,11 @@ export interface User {
export interface Company {
id: string
name: string
+ abbr?: string // 公司标识,用于 fdesk API 请求头 Fdesk-Corp-Abbr
+ tenant_key?: string
feishuAppId?: string
+ feishu_enabled?: boolean
+ sort?: number
status: number
}