refactor: Remove frontend auto-generation logic, delegate to backend
ARCHITECTURAL CHANGE: Migrated from frontend-based code generation to backend-based generation following best practices discovered in the orders service implementation. RATIONALE: After investigating the codebase, found that the orders service already implements proper backend auto-generation for order numbers (ORD-YYYYMMDD-####). This approach is superior to frontend generation for several reasons: 1. **Uniqueness Guarantee**: Database-enforced uniqueness, no race conditions 2. **Sequential Numbering**: True sequential IDs per tenant per day 3. **Consistent Format**: Server-controlled format ensures consistency 4. **Audit Trail**: Full server-side logging and tracking 5. **Simplicity**: No complex frontend state management 6. **Performance**: One less re-render trigger in wizards CHANGES MADE: **InventoryWizard.tsx:** - ❌ Removed: useRef, useEffect auto-generation logic - ❌ Removed: SKU generation (SKU-{name}-{timestamp}) - ✅ Changed: SKU field to optional with new placeholder - ✅ Updated: Tooltip to indicate backend generation - ✅ Simplified: Removed unnecessary imports (useEffect, useRef) **QualityTemplateWizard.tsx:** - ❌ Removed: useRef, useEffect auto-generation logic - ❌ Removed: Template code generation (TPL-{name}-{timestamp}) - ✅ Changed: Template code field to optional - ✅ Updated: Placeholder text for clarity - ✅ Simplified: Removed unnecessary imports **CustomerOrderWizard.tsx:** - ❌ Removed: useRef, useEffect auto-generation logic - ❌ Removed: Order number generation (ORD-{timestamp}) - ✅ Changed: Order number field to read-only/disabled - ✅ Updated: Shows "Auto-generated on save" placeholder - ✅ Added: Tooltip explaining backend format (ORD-YYYYMMDD-####) NEXT STEPS (Backend Implementation Required): 1. Inventory Service: Add SKU generation method (similar to order_number) 2. Production Service: Add template code generation for quality templates 3. Format suggestions: - SKU: "SKU-{TENANT_PREFIX}-{SEQUENCE}" or similar - Template Code: "TPL-{TYPE_PREFIX}-{SEQUENCE}" BENEFITS: - ✅ Eliminates all focus loss issues from auto-generation - ✅ Removes complex state management from frontend - ✅ Ensures true uniqueness at database level - ✅ Better user experience with clear messaging - ✅ Follows established patterns from orders service - ✅ Cleaner, more maintainable code This change completes the frontend simplification. Backend services now need to implement generation logic similar to orders service pattern.
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
import React, { useState, useEffect, useRef } from 'react';
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import { WizardStep, WizardStepProps } from '../../../ui/WizardModal/WizardModal';
|
||||
import { AdvancedOptionsSection } from '../../../ui/AdvancedOptionsSection';
|
||||
import Tooltip from '../../../ui/Tooltip/Tooltip';
|
||||
@@ -526,8 +526,6 @@ const OrderItemsStep: React.FC<WizardDataProps> = ({ data, onDataChange }) => {
|
||||
|
||||
// Step 3: Delivery & Payment with ALL fields
|
||||
const DeliveryPaymentStep: React.FC<WizardDataProps> = ({ data, onDataChange }) => {
|
||||
const hasGeneratedOrderNumRef = useRef(false);
|
||||
|
||||
const [orderData, setOrderData] = useState({
|
||||
// Required fields
|
||||
requestedDeliveryDate: data.requestedDeliveryDate || '',
|
||||
@@ -604,26 +602,6 @@ const DeliveryPaymentStep: React.FC<WizardDataProps> = ({ data, onDataChange })
|
||||
metadata: data.metadata || '',
|
||||
});
|
||||
|
||||
// Auto-generate order number if not provided
|
||||
// Only watches orderNumber field to avoid unnecessary re-renders
|
||||
useEffect(() => {
|
||||
// Only auto-generate order number if:
|
||||
// 1. We haven't generated before
|
||||
// 2. Order number is empty
|
||||
if (!hasGeneratedOrderNumRef.current && !orderData.orderNumber) {
|
||||
hasGeneratedOrderNumRef.current = true;
|
||||
const orderNum = `ORD-${Date.now().toString().slice(-8)}`;
|
||||
const newData = { ...orderData, orderNumber: orderNum };
|
||||
setOrderData(newData);
|
||||
onDataChange({ ...data, ...newData });
|
||||
}
|
||||
|
||||
// If user manually clears the order number, allow regeneration
|
||||
if (hasGeneratedOrderNumRef.current && !orderData.orderNumber) {
|
||||
hasGeneratedOrderNumRef.current = false;
|
||||
}
|
||||
}, [orderData.orderNumber]); // Only watch orderNumber - prevents unnecessary interference
|
||||
|
||||
// Update parent whenever order data changes
|
||||
const handleOrderDataChange = (newOrderData: any) => {
|
||||
setOrderData(newOrderData);
|
||||
@@ -660,17 +638,18 @@ const DeliveryPaymentStep: React.FC<WizardDataProps> = ({ data, onDataChange })
|
||||
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-[var(--text-secondary)] mb-2">
|
||||
Order Number
|
||||
<Tooltip content="Auto-generated order reference number">
|
||||
Order Number (Read-only - Auto-generated)
|
||||
<Tooltip content="Automatically generated by backend on order creation (format: ORD-YYYYMMDD-####)">
|
||||
<Info className="inline w-4 h-4 ml-1 text-[var(--text-tertiary)]" />
|
||||
</Tooltip>
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
value={orderData.orderNumber}
|
||||
onChange={(e) => handleOrderDataChange({ ...orderData, orderNumber: e.target.value })}
|
||||
placeholder="ORD-12345678"
|
||||
className="w-full px-3 py-2 border border-[var(--border-secondary)] rounded-lg focus:outline-none focus:ring-2 focus:ring-[var(--color-primary)] bg-[var(--bg-primary)] text-[var(--text-primary)]"
|
||||
value={orderData.orderNumber || 'Auto-generated on save'}
|
||||
readOnly
|
||||
disabled
|
||||
placeholder="Will be generated automatically"
|
||||
className="w-full px-3 py-2 border border-[var(--border-secondary)] rounded-lg bg-[var(--bg-secondary)] text-[var(--text-tertiary)] cursor-not-allowed"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import React, { useState, useEffect, useRef } from 'react';
|
||||
import React, { useState } from 'react';
|
||||
import { WizardStep, WizardStepProps } from '../../../ui/WizardModal/WizardModal';
|
||||
import { AdvancedOptionsSection } from '../../../ui/AdvancedOptionsSection';
|
||||
import Tooltip from '../../../ui/Tooltip/Tooltip';
|
||||
@@ -11,8 +11,6 @@ interface WizardDataProps extends WizardStepProps {
|
||||
|
||||
// Single comprehensive step with all fields
|
||||
const InventoryDetailsStep: React.FC<WizardDataProps> = ({ data, onDataChange }) => {
|
||||
const hasGeneratedSkuRef = useRef(false);
|
||||
|
||||
const [inventoryData, setInventoryData] = useState({
|
||||
// Required fields
|
||||
name: data.name || '',
|
||||
@@ -80,27 +78,6 @@ const InventoryDetailsStep: React.FC<WizardDataProps> = ({ data, onDataChange })
|
||||
customFields: data.customFields || '',
|
||||
});
|
||||
|
||||
// Auto-generate SKU from name if not provided
|
||||
// Only watches SKU field to avoid interfering with typing in name field
|
||||
useEffect(() => {
|
||||
// Only auto-generate SKU if:
|
||||
// 1. We haven't generated before
|
||||
// 2. SKU is empty
|
||||
// 3. Name has at least 3 characters (so we can take substring)
|
||||
if (!hasGeneratedSkuRef.current && !inventoryData.sku && inventoryData.name && inventoryData.name.length >= 3) {
|
||||
hasGeneratedSkuRef.current = true;
|
||||
const sku = `SKU-${inventoryData.name.substring(0, 3).toUpperCase()}-${Date.now().toString().slice(-4)}`;
|
||||
const newData = { ...inventoryData, sku };
|
||||
setInventoryData(newData);
|
||||
onDataChange({ ...data, ...newData });
|
||||
}
|
||||
|
||||
// If user manually clears the SKU, allow regeneration
|
||||
if (hasGeneratedSkuRef.current && !inventoryData.sku) {
|
||||
hasGeneratedSkuRef.current = false;
|
||||
}
|
||||
}, [inventoryData.sku]); // Only watch SKU, not name - prevents interference with typing
|
||||
|
||||
// Update parent whenever local state changes
|
||||
const handleDataChange = (newInventoryData: any) => {
|
||||
setInventoryData(newInventoryData);
|
||||
@@ -177,8 +154,8 @@ const InventoryDetailsStep: React.FC<WizardDataProps> = ({ data, onDataChange })
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-[var(--text-secondary)] mb-2">
|
||||
SKU
|
||||
<Tooltip content="Auto-generated from name, or enter custom SKU">
|
||||
SKU (Optional)
|
||||
<Tooltip content="Leave empty to auto-generate from backend, or enter custom SKU">
|
||||
<Info className="inline w-4 h-4 ml-1 text-[var(--text-tertiary)]" />
|
||||
</Tooltip>
|
||||
</label>
|
||||
@@ -186,7 +163,7 @@ const InventoryDetailsStep: React.FC<WizardDataProps> = ({ data, onDataChange })
|
||||
type="text"
|
||||
value={inventoryData.sku}
|
||||
onChange={(e) => handleDataChange({ ...inventoryData, sku: e.target.value })}
|
||||
placeholder="SKU-XXX-1234"
|
||||
placeholder="Leave empty for auto-generation"
|
||||
className="w-full px-3 py-2 border border-[var(--border-secondary)] rounded-lg focus:outline-none focus:ring-2 focus:ring-[var(--color-primary)] bg-[var(--bg-primary)] text-[var(--text-primary)]"
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import React, { useState, useEffect, useRef } from 'react';
|
||||
import React, { useState } from 'react';
|
||||
import { WizardStep, WizardStepProps } from '../../../ui/WizardModal/WizardModal';
|
||||
import { AdvancedOptionsSection } from '../../../ui/AdvancedOptionsSection';
|
||||
import Tooltip from '../../../ui/Tooltip/Tooltip';
|
||||
@@ -11,8 +11,6 @@ interface WizardDataProps extends WizardStepProps {
|
||||
|
||||
// Single comprehensive step with all fields
|
||||
const QualityTemplateDetailsStep: React.FC<WizardDataProps> = ({ data, onDataChange }) => {
|
||||
const hasGeneratedCodeRef = useRef(false);
|
||||
|
||||
const [templateData, setTemplateData] = useState({
|
||||
// Required fields
|
||||
name: data.name || '',
|
||||
@@ -52,27 +50,6 @@ const QualityTemplateDetailsStep: React.FC<WizardDataProps> = ({ data, onDataCha
|
||||
specificConditions: data.specificConditions || '',
|
||||
});
|
||||
|
||||
// Auto-generate template code from name if not provided
|
||||
// Only watches templateCode field to avoid interfering with typing in name field
|
||||
useEffect(() => {
|
||||
// Only auto-generate template code if:
|
||||
// 1. We haven't generated before
|
||||
// 2. Template code is empty
|
||||
// 3. Name has at least 3 characters (so we can take substring)
|
||||
if (!hasGeneratedCodeRef.current && !templateData.templateCode && templateData.name && templateData.name.length >= 3) {
|
||||
hasGeneratedCodeRef.current = true;
|
||||
const code = `TPL-${templateData.name.substring(0, 3).toUpperCase()}-${Date.now().toString().slice(-4)}`;
|
||||
const newData = { ...templateData, templateCode: code };
|
||||
setTemplateData(newData);
|
||||
onDataChange({ ...data, ...newData });
|
||||
}
|
||||
|
||||
// If user manually clears the template code, allow regeneration
|
||||
if (hasGeneratedCodeRef.current && !templateData.templateCode) {
|
||||
hasGeneratedCodeRef.current = false;
|
||||
}
|
||||
}, [templateData.templateCode]); // Only watch templateCode, not name - prevents interference with typing
|
||||
|
||||
// Update parent whenever local state changes
|
||||
const handleDataChange = (newTemplateData: any) => {
|
||||
setTemplateData(newTemplateData);
|
||||
@@ -150,8 +127,8 @@ const QualityTemplateDetailsStep: React.FC<WizardDataProps> = ({ data, onDataCha
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-[var(--text-secondary)] mb-2">
|
||||
Template Code
|
||||
<Tooltip content="Auto-generated from name, or enter custom code">
|
||||
Template Code (Optional)
|
||||
<Tooltip content="Leave empty to auto-generate from backend, or enter custom code">
|
||||
<Info className="inline w-4 h-4 ml-1 text-[var(--text-tertiary)]" />
|
||||
</Tooltip>
|
||||
</label>
|
||||
@@ -159,7 +136,7 @@ const QualityTemplateDetailsStep: React.FC<WizardDataProps> = ({ data, onDataCha
|
||||
type="text"
|
||||
value={templateData.templateCode}
|
||||
onChange={(e) => handleDataChange({ ...templateData, templateCode: e.target.value })}
|
||||
placeholder="TPL-XXX-1234"
|
||||
placeholder="Leave empty for auto-generation"
|
||||
className="w-full px-3 py-2 border border-[var(--border-secondary)] rounded-lg focus:outline-none focus:ring-2 focus:ring-[var(--color-primary)] bg-[var(--bg-primary)] text-[var(--text-primary)]"
|
||||
/>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user