add traslations 2
This commit is contained in:
@@ -42,119 +42,64 @@ export interface ItemTypeConfig {
|
|||||||
keywords?: string[];
|
keywords?: string[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export const ITEM_TYPES: ItemTypeConfig[] = [
|
// Base item types with icon and category only - titles/subtitles will be translated
|
||||||
{
|
export const ITEM_TYPE_CONFIGS: Record<ItemType, { icon: React.ComponentType<{ className?: string; style?: React.CSSProperties }>; category: 'daily' | 'common' | 'setup'; badgeColor: string }> = {
|
||||||
id: 'sales-entry',
|
'sales-entry': {
|
||||||
title: 'Registro de Ventas',
|
|
||||||
subtitle: 'Manual o carga masiva',
|
|
||||||
icon: EuroIcon,
|
icon: EuroIcon,
|
||||||
badge: '⭐ Más Común',
|
category: 'daily',
|
||||||
badgeColor: 'bg-gradient-to-r from-amber-100 to-orange-100 text-orange-800 font-semibold',
|
badgeColor: 'bg-gradient-to-r from-amber-100 to-orange-100 text-orange-800 font-semibold',
|
||||||
isHighlighted: true,
|
|
||||||
category: 'daily',
|
|
||||||
keywords: ['ventas', 'sales', 'ingresos', 'caja', 'revenue'],
|
|
||||||
},
|
},
|
||||||
{
|
'inventory': {
|
||||||
id: 'inventory',
|
|
||||||
title: 'Inventario',
|
|
||||||
subtitle: 'Ingrediente o Producto',
|
|
||||||
icon: Package,
|
icon: Package,
|
||||||
badge: 'Configuración',
|
|
||||||
badgeColor: 'bg-blue-100 text-blue-700',
|
|
||||||
category: 'setup',
|
category: 'setup',
|
||||||
keywords: ['inventario', 'inventory', 'stock', 'ingredientes', 'productos'],
|
badgeColor: 'bg-blue-100 text-blue-700',
|
||||||
},
|
},
|
||||||
{
|
'supplier': {
|
||||||
id: 'supplier',
|
|
||||||
title: 'Proveedor',
|
|
||||||
subtitle: 'Relación comercial',
|
|
||||||
icon: Building,
|
icon: Building,
|
||||||
badge: 'Configuración',
|
|
||||||
badgeColor: 'bg-blue-100 text-blue-700',
|
|
||||||
category: 'setup',
|
category: 'setup',
|
||||||
keywords: ['proveedor', 'supplier', 'vendor', 'distribuidor'],
|
badgeColor: 'bg-blue-100 text-blue-700',
|
||||||
},
|
},
|
||||||
{
|
'recipe': {
|
||||||
id: 'recipe',
|
|
||||||
title: 'Receta',
|
|
||||||
subtitle: 'Fórmula de producción',
|
|
||||||
icon: ChefHat,
|
icon: ChefHat,
|
||||||
badge: 'Común',
|
|
||||||
badgeColor: 'bg-green-100 text-green-700',
|
|
||||||
category: 'common',
|
category: 'common',
|
||||||
keywords: ['receta', 'recipe', 'formula', 'producción'],
|
badgeColor: 'bg-green-100 text-green-700',
|
||||||
},
|
},
|
||||||
{
|
'equipment': {
|
||||||
id: 'equipment',
|
|
||||||
title: 'Equipo',
|
|
||||||
subtitle: 'Activo de panadería',
|
|
||||||
icon: Wrench,
|
icon: Wrench,
|
||||||
badge: 'Configuración',
|
|
||||||
badgeColor: 'bg-blue-100 text-blue-700',
|
|
||||||
category: 'setup',
|
category: 'setup',
|
||||||
keywords: ['equipo', 'equipment', 'maquinaria', 'horno', 'mixer'],
|
badgeColor: 'bg-blue-100 text-blue-700',
|
||||||
},
|
},
|
||||||
{
|
'quality-template': {
|
||||||
id: 'quality-template',
|
|
||||||
title: 'Plantilla de Calidad',
|
|
||||||
subtitle: 'Estándares y controles',
|
|
||||||
icon: ClipboardCheck,
|
icon: ClipboardCheck,
|
||||||
badge: 'Configuración',
|
|
||||||
badgeColor: 'bg-blue-100 text-blue-700',
|
|
||||||
category: 'setup',
|
category: 'setup',
|
||||||
keywords: ['calidad', 'quality', 'control', 'estándares', 'inspección'],
|
badgeColor: 'bg-blue-100 text-blue-700',
|
||||||
},
|
},
|
||||||
{
|
'customer-order': {
|
||||||
id: 'customer-order',
|
|
||||||
title: 'Pedido de Cliente',
|
|
||||||
subtitle: 'Nueva orden',
|
|
||||||
icon: ShoppingCart,
|
icon: ShoppingCart,
|
||||||
badge: 'Diario',
|
|
||||||
badgeColor: 'bg-amber-100 text-amber-700',
|
|
||||||
category: 'daily',
|
category: 'daily',
|
||||||
keywords: ['pedido', 'order', 'cliente', 'customer', 'orden'],
|
badgeColor: 'bg-amber-100 text-amber-700',
|
||||||
},
|
},
|
||||||
{
|
'customer': {
|
||||||
id: 'customer',
|
|
||||||
title: 'Cliente',
|
|
||||||
subtitle: 'Perfil de cliente',
|
|
||||||
icon: Users,
|
icon: Users,
|
||||||
badge: 'Común',
|
|
||||||
badgeColor: 'bg-green-100 text-green-700',
|
|
||||||
category: 'common',
|
category: 'common',
|
||||||
keywords: ['cliente', 'customer', 'comprador', 'contacto'],
|
badgeColor: 'bg-green-100 text-green-700',
|
||||||
},
|
},
|
||||||
{
|
'team-member': {
|
||||||
id: 'team-member',
|
|
||||||
title: 'Miembro del Equipo',
|
|
||||||
subtitle: 'Empleado o colaborador',
|
|
||||||
icon: UserPlus,
|
icon: UserPlus,
|
||||||
badge: 'Configuración',
|
|
||||||
badgeColor: 'bg-blue-100 text-blue-700',
|
|
||||||
category: 'setup',
|
category: 'setup',
|
||||||
keywords: ['empleado', 'employee', 'team', 'staff', 'usuario'],
|
badgeColor: 'bg-blue-100 text-blue-700',
|
||||||
},
|
},
|
||||||
{
|
'purchase-order': {
|
||||||
id: 'purchase-order',
|
|
||||||
title: 'Orden de Compra',
|
|
||||||
subtitle: 'Compra a proveedor',
|
|
||||||
icon: FileText,
|
icon: FileText,
|
||||||
badge: 'Diario',
|
|
||||||
badgeColor: 'bg-amber-100 text-amber-700',
|
|
||||||
category: 'daily',
|
category: 'daily',
|
||||||
keywords: ['compra', 'purchase', 'orden', 'proveedor', 'abastecimiento'],
|
badgeColor: 'bg-amber-100 text-amber-700',
|
||||||
},
|
},
|
||||||
{
|
'production-batch': {
|
||||||
id: 'production-batch',
|
|
||||||
title: 'Lote de Producción',
|
|
||||||
subtitle: 'Nueva orden de producción',
|
|
||||||
icon: Factory,
|
icon: Factory,
|
||||||
badge: 'Diario',
|
|
||||||
badgeColor: 'bg-amber-100 text-amber-700',
|
|
||||||
category: 'daily',
|
category: 'daily',
|
||||||
keywords: ['producción', 'production', 'lote', 'batch', 'fabricación'],
|
badgeColor: 'bg-amber-100 text-amber-700',
|
||||||
},
|
},
|
||||||
];
|
};
|
||||||
|
|
||||||
interface ItemTypeSelectorProps {
|
interface ItemTypeSelectorProps {
|
||||||
onSelect: (itemType: ItemType) => void;
|
onSelect: (itemType: ItemType) => void;
|
||||||
@@ -165,9 +110,28 @@ export const ItemTypeSelector: React.FC<ItemTypeSelectorProps> = ({ onSelect })
|
|||||||
const [searchQuery, setSearchQuery] = useState('');
|
const [searchQuery, setSearchQuery] = useState('');
|
||||||
const [selectedCategory, setSelectedCategory] = useState<'all' | 'daily' | 'common' | 'setup'>('all');
|
const [selectedCategory, setSelectedCategory] = useState<'all' | 'daily' | 'common' | 'setup'>('all');
|
||||||
|
|
||||||
|
// Build item types with translations
|
||||||
|
const itemTypes: ItemTypeConfig[] = useMemo(() => {
|
||||||
|
return Object.entries(ITEM_TYPE_CONFIGS).map(([id, config]) => ({
|
||||||
|
id: id as ItemType,
|
||||||
|
title: t(`itemTypeSelector.items.${id}.title`),
|
||||||
|
subtitle: t(`itemTypeSelector.items.${id}.subtitle`),
|
||||||
|
icon: config.icon,
|
||||||
|
badge: config.category === 'daily'
|
||||||
|
? t('itemTypeSelector.badges.daily')
|
||||||
|
: config.category === 'common'
|
||||||
|
? t('itemTypeSelector.badges.common')
|
||||||
|
: t('itemTypeSelector.badges.setup'),
|
||||||
|
badgeColor: config.badgeColor,
|
||||||
|
isHighlighted: id === 'sales-entry',
|
||||||
|
category: config.category,
|
||||||
|
keywords: t(`itemTypeSelector.items.${id}.keywords`, { returnObjects: true }) as string[],
|
||||||
|
}));
|
||||||
|
}, [t]);
|
||||||
|
|
||||||
// Filter items based on search and category
|
// Filter items based on search and category
|
||||||
const filteredItems = useMemo(() => {
|
const filteredItems = useMemo(() => {
|
||||||
return ITEM_TYPES.filter(item => {
|
return itemTypes.filter(item => {
|
||||||
// Category filter
|
// Category filter
|
||||||
if (selectedCategory !== 'all' && item.category !== selectedCategory) {
|
if (selectedCategory !== 'all' && item.category !== selectedCategory) {
|
||||||
return false;
|
return false;
|
||||||
@@ -184,13 +148,13 @@ export const ItemTypeSelector: React.FC<ItemTypeSelectorProps> = ({ onSelect })
|
|||||||
|
|
||||||
return true;
|
return true;
|
||||||
});
|
});
|
||||||
}, [searchQuery, selectedCategory]);
|
}, [itemTypes, searchQuery, selectedCategory]);
|
||||||
|
|
||||||
const categoryLabels = {
|
const categoryLabels = {
|
||||||
all: 'Todos',
|
all: t('itemTypeSelector.categories.all'),
|
||||||
daily: 'Diario',
|
daily: t('itemTypeSelector.categories.daily'),
|
||||||
common: 'Común',
|
common: t('itemTypeSelector.categories.common'),
|
||||||
setup: 'Configuración',
|
setup: t('itemTypeSelector.categories.setup'),
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -219,7 +183,7 @@ export const ItemTypeSelector: React.FC<ItemTypeSelectorProps> = ({ onSelect })
|
|||||||
type="text"
|
type="text"
|
||||||
value={searchQuery}
|
value={searchQuery}
|
||||||
onChange={(e) => setSearchQuery(e.target.value)}
|
onChange={(e) => setSearchQuery(e.target.value)}
|
||||||
placeholder="Buscar por nombre o categoría..."
|
placeholder={t('itemTypeSelector.search.placeholder')}
|
||||||
className="w-full pl-10 pr-10 py-2.5 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)] placeholder-[var(--text-tertiary)]"
|
className="w-full pl-10 pr-10 py-2.5 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)] placeholder-[var(--text-tertiary)]"
|
||||||
/>
|
/>
|
||||||
{searchQuery && (
|
{searchQuery && (
|
||||||
@@ -254,9 +218,9 @@ export const ItemTypeSelector: React.FC<ItemTypeSelectorProps> = ({ onSelect })
|
|||||||
{(searchQuery || selectedCategory !== 'all') && (
|
{(searchQuery || selectedCategory !== 'all') && (
|
||||||
<div className="text-sm text-[var(--text-secondary)]">
|
<div className="text-sm text-[var(--text-secondary)]">
|
||||||
{filteredItems.length === 0 ? (
|
{filteredItems.length === 0 ? (
|
||||||
<p className="text-center py-4">No se encontraron resultados</p>
|
<p className="text-center py-4">{t('itemTypeSelector.search.noResults')}</p>
|
||||||
) : (
|
) : (
|
||||||
<p>{filteredItems.length} {filteredItems.length === 1 ? 'resultado' : 'resultados'}</p>
|
<p>{filteredItems.length} {filteredItems.length === 1 ? t('itemTypeSelector.search.resultSingular') : t('itemTypeSelector.search.resultPlural')}</p>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -22,15 +22,18 @@ import { ProcessStage } from '../../../../api/types/qualityTemplates';
|
|||||||
import { Badge } from '../../../ui';
|
import { Badge } from '../../../ui';
|
||||||
import { Card } from '../../../ui';
|
import { Card } from '../../../ui';
|
||||||
|
|
||||||
// Stage labels for display
|
// Helper to get stage label with translation
|
||||||
const STAGE_LABELS: Record<ProcessStage, string> = {
|
const getStageLabelKey = (stage: ProcessStage): string => {
|
||||||
[ProcessStage.MIXING]: 'Mezclado',
|
const stageKeyMap: Record<ProcessStage, string> = {
|
||||||
[ProcessStage.PROOFING]: 'Fermentación',
|
[ProcessStage.MIXING]: 'mixing',
|
||||||
[ProcessStage.SHAPING]: 'Formado',
|
[ProcessStage.PROOFING]: 'proofing',
|
||||||
[ProcessStage.BAKING]: 'Horneado',
|
[ProcessStage.SHAPING]: 'shaping',
|
||||||
[ProcessStage.COOLING]: 'Enfriado',
|
[ProcessStage.BAKING]: 'baking',
|
||||||
[ProcessStage.PACKAGING]: 'Empaquetado',
|
[ProcessStage.COOLING]: 'cooling',
|
||||||
[ProcessStage.FINISHING]: 'Acabado',
|
[ProcessStage.PACKAGING]: 'packaging',
|
||||||
|
[ProcessStage.FINISHING]: 'finishing',
|
||||||
|
};
|
||||||
|
return stageKeyMap[stage];
|
||||||
};
|
};
|
||||||
|
|
||||||
// Step 1: Product & Recipe Selection
|
// Step 1: Product & Recipe Selection
|
||||||
@@ -103,31 +106,31 @@ const ProductRecipeStep: React.FC<WizardStepProps> = ({ dataRef, onDataChange })
|
|||||||
<div className="text-center pb-4 border-b border-[var(--border-primary)]">
|
<div className="text-center pb-4 border-b border-[var(--border-primary)]">
|
||||||
<Package className="w-12 h-12 mx-auto mb-3 text-[var(--color-primary)]" />
|
<Package className="w-12 h-12 mx-auto mb-3 text-[var(--color-primary)]" />
|
||||||
<h3 className="text-lg font-semibold text-[var(--text-primary)] mb-2">
|
<h3 className="text-lg font-semibold text-[var(--text-primary)] mb-2">
|
||||||
Seleccionar Producto y Receta
|
{t('productionBatch.productRecipe.title')}
|
||||||
</h3>
|
</h3>
|
||||||
<p className="text-sm text-[var(--text-secondary)]">
|
<p className="text-sm text-[var(--text-secondary)]">
|
||||||
Elige el producto a producir y opcionalmente una receta
|
{t('productionBatch.productRecipe.description')}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{ingredientsLoading || recipesLoading ? (
|
{ingredientsLoading || recipesLoading ? (
|
||||||
<div className="flex items-center justify-center py-12">
|
<div className="flex items-center justify-center py-12">
|
||||||
<Loader2 className="w-8 h-8 animate-spin text-[var(--color-primary)]" />
|
<Loader2 className="w-8 h-8 animate-spin text-[var(--color-primary)]" />
|
||||||
<span className="ml-3 text-[var(--text-secondary)]">Cargando información...</span>
|
<span className="ml-3 text-[var(--text-secondary)]">{t('productionBatch.productRecipe.loading')}</span>
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<>
|
<>
|
||||||
<div className="space-y-4">
|
<div className="space-y-4">
|
||||||
<div>
|
<div>
|
||||||
<label className="block text-sm font-medium text-[var(--text-secondary)] mb-2">
|
<label className="block text-sm font-medium text-[var(--text-secondary)] mb-2">
|
||||||
Producto a Producir *
|
{t('productionBatch.productRecipe.productToProduce')} *
|
||||||
</label>
|
</label>
|
||||||
<select
|
<select
|
||||||
value={data.product_id || ''}
|
value={data.product_id || ''}
|
||||||
onChange={(e) => handleProductChange(e.target.value)}
|
onChange={(e) => handleProductChange(e.target.value)}
|
||||||
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)]"
|
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)]"
|
||||||
>
|
>
|
||||||
<option value="">Seleccionar producto...</option>
|
<option value="">{t('productionBatch.productRecipe.selectProduct')}</option>
|
||||||
{finishedProducts.map((product: any) => (
|
{finishedProducts.map((product: any) => (
|
||||||
<option key={product.id} value={product.id}>
|
<option key={product.id} value={product.id}>
|
||||||
{product.name}
|
{product.name}
|
||||||
@@ -138,7 +141,7 @@ const ProductRecipeStep: React.FC<WizardStepProps> = ({ dataRef, onDataChange })
|
|||||||
|
|
||||||
<div>
|
<div>
|
||||||
<label className="block text-sm font-medium text-[var(--text-secondary)] mb-2">
|
<label className="block text-sm font-medium text-[var(--text-secondary)] mb-2">
|
||||||
Receta a Utilizar (Opcional)
|
{t('productionBatch.productRecipe.recipeToUse')}
|
||||||
</label>
|
</label>
|
||||||
<select
|
<select
|
||||||
value={data.recipe_id || ''}
|
value={data.recipe_id || ''}
|
||||||
@@ -146,7 +149,7 @@ const ProductRecipeStep: React.FC<WizardStepProps> = ({ dataRef, onDataChange })
|
|||||||
disabled={loadingRecipe}
|
disabled={loadingRecipe}
|
||||||
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)] disabled:opacity-50"
|
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)] disabled:opacity-50"
|
||||||
>
|
>
|
||||||
<option value="">Sin receta específica</option>
|
<option value="">{t('productionBatch.productRecipe.noSpecificRecipe')}</option>
|
||||||
{recipes.map((recipe: any) => (
|
{recipes.map((recipe: any) => (
|
||||||
<option key={recipe.id} value={recipe.id}>
|
<option key={recipe.id} value={recipe.id}>
|
||||||
{recipe.name}
|
{recipe.name}
|
||||||
@@ -156,7 +159,7 @@ const ProductRecipeStep: React.FC<WizardStepProps> = ({ dataRef, onDataChange })
|
|||||||
{loadingRecipe && (
|
{loadingRecipe && (
|
||||||
<p className="text-sm text-[var(--text-secondary)] mt-1 flex items-center gap-2">
|
<p className="text-sm text-[var(--text-secondary)] mt-1 flex items-center gap-2">
|
||||||
<Loader2 className="w-4 h-4 animate-spin" />
|
<Loader2 className="w-4 h-4 animate-spin" />
|
||||||
Cargando detalles de la receta...
|
{t('productionBatch.productRecipe.loadingRecipeDetails')}
|
||||||
</p>
|
</p>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
@@ -167,7 +170,7 @@ const ProductRecipeStep: React.FC<WizardStepProps> = ({ dataRef, onDataChange })
|
|||||||
<Card className="mt-4 p-4 bg-blue-50 border-blue-200">
|
<Card className="mt-4 p-4 bg-blue-50 border-blue-200">
|
||||||
<h4 className="font-medium text-[var(--text-primary)] mb-3 flex items-center gap-2">
|
<h4 className="font-medium text-[var(--text-primary)] mb-3 flex items-center gap-2">
|
||||||
<ClipboardCheck className="w-5 h-5 text-blue-600" />
|
<ClipboardCheck className="w-5 h-5 text-blue-600" />
|
||||||
Controles de Calidad Requeridos
|
{t('productionBatch.productRecipe.qualityControlsRequired')}
|
||||||
</h4>
|
</h4>
|
||||||
{selectedRecipe.quality_check_configuration && selectedRecipe.quality_check_configuration.stages ? (
|
{selectedRecipe.quality_check_configuration && selectedRecipe.quality_check_configuration.stages ? (
|
||||||
<div className="space-y-3">
|
<div className="space-y-3">
|
||||||
@@ -177,32 +180,31 @@ const ProductRecipeStep: React.FC<WizardStepProps> = ({ dataRef, onDataChange })
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div key={stage} className="flex items-center gap-2 text-sm">
|
<div key={stage} className="flex items-center gap-2 text-sm">
|
||||||
<Badge variant="info">{STAGE_LABELS[stage as ProcessStage]}</Badge>
|
<Badge variant="info">{t(`productionBatch.additionalDetails.stages.${getStageLabelKey(stage as ProcessStage)}`)}</Badge>
|
||||||
<span className="text-[var(--text-secondary)]">
|
<span className="text-[var(--text-secondary)]">
|
||||||
{config.template_ids.length} control{config.template_ids.length > 1 ? 'es' : ''}
|
{config.template_ids.length} {config.template_ids.length > 1 ? t('productionBatch.productRecipe.controls') : t('productionBatch.productRecipe.control')}
|
||||||
</span>
|
</span>
|
||||||
{config.blocking && <Badge variant="warning" size="sm">Bloqueante</Badge>}
|
{config.blocking && <Badge variant="warning" size="sm">{t('productionBatch.productRecipe.blocking')}</Badge>}
|
||||||
{config.is_required && <Badge variant="error" size="sm">Requerido</Badge>}
|
{config.is_required && <Badge variant="error" size="sm">{t('productionBatch.productRecipe.required')}</Badge>}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
)}
|
)}
|
||||||
<div className="mt-3 pt-3 border-t border-blue-200">
|
<div className="mt-3 pt-3 border-t border-blue-200">
|
||||||
<p className="text-sm text-[var(--text-secondary)]">
|
<p className="text-sm text-[var(--text-secondary)]">
|
||||||
<span className="font-medium">Umbral de calidad mínimo:</span>{' '}
|
<span className="font-medium">{t('productionBatch.productRecipe.minimumQualityThreshold')}:</span>{' '}
|
||||||
{selectedRecipe.quality_check_configuration.overall_quality_threshold || 7.0}/10
|
{selectedRecipe.quality_check_configuration.overall_quality_threshold || 7.0}/10
|
||||||
</p>
|
</p>
|
||||||
{selectedRecipe.quality_check_configuration.critical_stage_blocking && (
|
{selectedRecipe.quality_check_configuration.critical_stage_blocking && (
|
||||||
<p className="text-sm text-[var(--text-secondary)] mt-1">
|
<p className="text-sm text-[var(--text-secondary)] mt-1">
|
||||||
<span className="font-medium text-orange-600">⚠️ Bloqueo crítico activado:</span> El lote no
|
<span className="font-medium text-orange-600">⚠️ {t('productionBatch.productRecipe.criticalBlockingActivated')}:</span> {t('productionBatch.productRecipe.batchCannotAdvance')}
|
||||||
puede avanzar si fallan checks críticos
|
|
||||||
</p>
|
</p>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<p className="text-sm text-[var(--text-secondary)]">
|
<p className="text-sm text-[var(--text-secondary)]">
|
||||||
Esta receta no tiene controles de calidad configurados.
|
{t('productionBatch.productRecipe.noQualityControls')}
|
||||||
</p>
|
</p>
|
||||||
)}
|
)}
|
||||||
</Card>
|
</Card>
|
||||||
@@ -257,13 +259,13 @@ const PlanningDetailsStep: React.FC<WizardStepProps> = ({ dataRef, onDataChange
|
|||||||
<div className="space-y-6">
|
<div className="space-y-6">
|
||||||
<div className="text-center pb-4 border-b border-[var(--border-primary)]">
|
<div className="text-center pb-4 border-b border-[var(--border-primary)]">
|
||||||
<Clock className="w-12 h-12 mx-auto mb-3 text-[var(--color-primary)]" />
|
<Clock className="w-12 h-12 mx-auto mb-3 text-[var(--color-primary)]" />
|
||||||
<h3 className="text-lg font-semibold text-[var(--text-primary)] mb-2">Planificación de Producción</h3>
|
<h3 className="text-lg font-semibold text-[var(--text-primary)] mb-2">{t('productionBatch.planningDetails.title')}</h3>
|
||||||
<p className="text-sm text-[var(--text-secondary)]">Define tiempos y cantidades de producción</p>
|
<p className="text-sm text-[var(--text-secondary)]">{t('productionBatch.planningDetails.description')}</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||||
<div>
|
<div>
|
||||||
<label className="block text-sm font-medium text-[var(--text-secondary)] mb-2">Inicio Planificado *</label>
|
<label className="block text-sm font-medium text-[var(--text-secondary)] mb-2">{t('productionBatch.planningDetails.plannedStart')} *</label>
|
||||||
<input
|
<input
|
||||||
type="datetime-local"
|
type="datetime-local"
|
||||||
value={getValue('planned_start_time')}
|
value={getValue('planned_start_time')}
|
||||||
@@ -274,7 +276,7 @@ const PlanningDetailsStep: React.FC<WizardStepProps> = ({ dataRef, onDataChange
|
|||||||
|
|
||||||
<div>
|
<div>
|
||||||
<label className="block text-sm font-medium text-[var(--text-secondary)] mb-2">
|
<label className="block text-sm font-medium text-[var(--text-secondary)] mb-2">
|
||||||
Duración (minutos) *
|
{t('productionBatch.planningDetails.duration')} *
|
||||||
</label>
|
</label>
|
||||||
<input
|
<input
|
||||||
type="number"
|
type="number"
|
||||||
@@ -287,7 +289,7 @@ const PlanningDetailsStep: React.FC<WizardStepProps> = ({ dataRef, onDataChange
|
|||||||
|
|
||||||
<div>
|
<div>
|
||||||
<label className="block text-sm font-medium text-[var(--text-secondary)] mb-2">
|
<label className="block text-sm font-medium text-[var(--text-secondary)] mb-2">
|
||||||
Fin Planificado (Calculado)
|
{t('productionBatch.planningDetails.plannedEnd')}
|
||||||
</label>
|
</label>
|
||||||
<input
|
<input
|
||||||
type="datetime-local"
|
type="datetime-local"
|
||||||
@@ -296,12 +298,12 @@ const PlanningDetailsStep: React.FC<WizardStepProps> = ({ dataRef, onDataChange
|
|||||||
disabled
|
disabled
|
||||||
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"
|
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"
|
||||||
/>
|
/>
|
||||||
<p className="text-xs text-[var(--text-tertiary)] mt-1">Se calcula automáticamente</p>
|
<p className="text-xs text-[var(--text-tertiary)] mt-1">{t('productionBatch.planningDetails.autoCalculated')}</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<label className="block text-sm font-medium text-[var(--text-secondary)] mb-2">
|
<label className="block text-sm font-medium text-[var(--text-secondary)] mb-2">
|
||||||
Cantidad Planificada *
|
{t('productionBatch.planningDetails.plannedQuantity')} *
|
||||||
</label>
|
</label>
|
||||||
<input
|
<input
|
||||||
type="number"
|
type="number"
|
||||||
@@ -319,14 +321,14 @@ const PlanningDetailsStep: React.FC<WizardStepProps> = ({ dataRef, onDataChange
|
|||||||
<div className="flex items-start gap-2">
|
<div className="flex items-start gap-2">
|
||||||
<Clock className="w-5 h-5 text-blue-600 flex-shrink-0 mt-0.5" />
|
<Clock className="w-5 h-5 text-blue-600 flex-shrink-0 mt-0.5" />
|
||||||
<div className="text-sm">
|
<div className="text-sm">
|
||||||
<p className="font-medium text-blue-900">Resumen de Planificación:</p>
|
<p className="font-medium text-blue-900">{t('productionBatch.planningDetails.planningSummary')}:</p>
|
||||||
<p className="text-blue-700 mt-1">
|
<p className="text-blue-700 mt-1">
|
||||||
Inicio: {new Date(getValue('planned_start_time')).toLocaleString('es-ES')}
|
{t('productionBatch.planningDetails.start')}: {new Date(getValue('planned_start_time')).toLocaleString('es-ES')}
|
||||||
</p>
|
</p>
|
||||||
<p className="text-blue-700">Fin: {new Date(getValue('planned_end_time')).toLocaleString('es-ES')}</p>
|
<p className="text-blue-700">{t('productionBatch.planningDetails.end')}: {new Date(getValue('planned_end_time')).toLocaleString('es-ES')}</p>
|
||||||
<p className="text-blue-700">
|
<p className="text-blue-700">
|
||||||
Duración: {getValue('planned_duration_minutes', 0)} minutos (
|
{t('productionBatch.planningDetails.durationSummary')}: {getValue('planned_duration_minutes', 0)} {t('productionBatch.planningDetails.minutes')} (
|
||||||
{(getValue('planned_duration_minutes', 0) / 60).toFixed(1)} horas)
|
{(getValue('planned_duration_minutes', 0) / 60).toFixed(1)} {t('productionBatch.planningDetails.hours')})
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -358,13 +360,13 @@ const PriorityResourcesStep: React.FC<WizardStepProps> = ({ dataRef, onDataChang
|
|||||||
<div className="space-y-6">
|
<div className="space-y-6">
|
||||||
<div className="text-center pb-4 border-b border-[var(--border-primary)]">
|
<div className="text-center pb-4 border-b border-[var(--border-primary)]">
|
||||||
<AlertCircle className="w-12 h-12 mx-auto mb-3 text-[var(--color-primary)]" />
|
<AlertCircle className="w-12 h-12 mx-auto mb-3 text-[var(--color-primary)]" />
|
||||||
<h3 className="text-lg font-semibold text-[var(--text-primary)] mb-2">Prioridad y Recursos</h3>
|
<h3 className="text-lg font-semibold text-[var(--text-primary)] mb-2">{t('productionBatch.priorityResources.title')}</h3>
|
||||||
<p className="text-sm text-[var(--text-secondary)]">Configura la prioridad y asigna recursos</p>
|
<p className="text-sm text-[var(--text-secondary)]">{t('productionBatch.priorityResources.description')}</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||||
<div>
|
<div>
|
||||||
<label className="block text-sm font-medium text-[var(--text-secondary)] mb-2">Prioridad *</label>
|
<label className="block text-sm font-medium text-[var(--text-secondary)] mb-2">{t('productionBatch.priorityResources.priority')} *</label>
|
||||||
<select
|
<select
|
||||||
value={getValue('priority', ProductionPriorityEnum.MEDIUM)}
|
value={getValue('priority', ProductionPriorityEnum.MEDIUM)}
|
||||||
onChange={(e) => handleFieldChange({ priority: e.target.value })}
|
onChange={(e) => handleFieldChange({ priority: e.target.value })}
|
||||||
@@ -379,12 +381,12 @@ const PriorityResourcesStep: React.FC<WizardStepProps> = ({ dataRef, onDataChang
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<label className="block text-sm font-medium text-[var(--text-secondary)] mb-2">Número de Lote</label>
|
<label className="block text-sm font-medium text-[var(--text-secondary)] mb-2">{t('productionBatch.priorityResources.batchNumber')}</label>
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
value={getValue('batch_number')}
|
value={getValue('batch_number')}
|
||||||
onChange={(e) => handleFieldChange({ batch_number: e.target.value })}
|
onChange={(e) => handleFieldChange({ batch_number: e.target.value })}
|
||||||
placeholder="Se generará automáticamente si se deja vacío"
|
placeholder={t('productionBatch.priorityResources.autoGeneratedPlaceholder')}
|
||||||
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)]"
|
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>
|
</div>
|
||||||
@@ -396,7 +398,7 @@ const PriorityResourcesStep: React.FC<WizardStepProps> = ({ dataRef, onDataChang
|
|||||||
onChange={(e) => handleFieldChange({ is_rush_order: e.target.checked })}
|
onChange={(e) => handleFieldChange({ is_rush_order: e.target.checked })}
|
||||||
className="rounded border-[var(--border-secondary)] text-[var(--color-primary)] focus:ring-[var(--color-primary)]"
|
className="rounded border-[var(--border-secondary)] text-[var(--color-primary)] focus:ring-[var(--color-primary)]"
|
||||||
/>
|
/>
|
||||||
<label className="text-sm font-medium text-[var(--text-primary)]">Orden Urgente</label>
|
<label className="text-sm font-medium text-[var(--text-primary)]">{t('productionBatch.priorityResources.rushOrder')}</label>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="flex items-center gap-3 p-3 border border-[var(--border-secondary)] rounded-lg">
|
<div className="flex items-center gap-3 p-3 border border-[var(--border-secondary)] rounded-lg">
|
||||||
@@ -406,69 +408,69 @@ const PriorityResourcesStep: React.FC<WizardStepProps> = ({ dataRef, onDataChang
|
|||||||
onChange={(e) => handleFieldChange({ is_special_recipe: e.target.checked })}
|
onChange={(e) => handleFieldChange({ is_special_recipe: e.target.checked })}
|
||||||
className="rounded border-[var(--border-secondary)] text-[var(--color-primary)] focus:ring-[var(--color-primary)]"
|
className="rounded border-[var(--border-secondary)] text-[var(--color-primary)] focus:ring-[var(--color-primary)]"
|
||||||
/>
|
/>
|
||||||
<label className="text-sm font-medium text-[var(--text-primary)]">Receta Especial</label>
|
<label className="text-sm font-medium text-[var(--text-primary)]">{t('productionBatch.priorityResources.specialRecipe')}</label>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<label className="block text-sm font-medium text-[var(--text-secondary)] mb-2">
|
<label className="block text-sm font-medium text-[var(--text-secondary)] mb-2">
|
||||||
<Users className="w-4 h-4 inline mr-1.5" />
|
<Users className="w-4 h-4 inline mr-1.5" />
|
||||||
Personal Asignado (Opcional)
|
{t('productionBatch.priorityResources.assignedStaff')}
|
||||||
</label>
|
</label>
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
value={getValue('staff_assigned_string', '')}
|
value={getValue('staff_assigned_string', '')}
|
||||||
onChange={(e) => handleFieldChange({ staff_assigned_string: e.target.value })}
|
onChange={(e) => handleFieldChange({ staff_assigned_string: e.target.value })}
|
||||||
placeholder="Separar nombres con comas (Ej: Juan Pérez, María García)"
|
placeholder={t('productionBatch.priorityResources.staffPlaceholder')}
|
||||||
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)]"
|
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)]"
|
||||||
/>
|
/>
|
||||||
<p className="text-xs text-[var(--text-tertiary)] mt-1">Lista de nombres separados por comas</p>
|
<p className="text-xs text-[var(--text-tertiary)] mt-1">{t('productionBatch.priorityResources.staffHint')}</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<label className="block text-sm font-medium text-[var(--text-secondary)] mb-2">
|
<label className="block text-sm font-medium text-[var(--text-secondary)] mb-2">
|
||||||
Notas de Producción (Opcional)
|
{t('productionBatch.priorityResources.productionNotes')}
|
||||||
</label>
|
</label>
|
||||||
<textarea
|
<textarea
|
||||||
value={getValue('production_notes')}
|
value={getValue('production_notes')}
|
||||||
onChange={(e) => handleFieldChange({ production_notes: e.target.value })}
|
onChange={(e) => handleFieldChange({ production_notes: e.target.value })}
|
||||||
placeholder="Instrucciones especiales, observaciones, etc."
|
placeholder={t('productionBatch.priorityResources.notesPlaceholder')}
|
||||||
rows={4}
|
rows={4}
|
||||||
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)]"
|
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>
|
</div>
|
||||||
|
|
||||||
<AdvancedOptionsSection title="Opciones Avanzadas" description="Información adicional de producción">
|
<AdvancedOptionsSection title={t('productionBatch.advancedOptions.title')} description={t('productionBatch.advancedOptions.description')}>
|
||||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||||
<div>
|
<div>
|
||||||
<label className="block text-sm font-medium text-[var(--text-secondary)] mb-2">ID de Estación</label>
|
<label className="block text-sm font-medium text-[var(--text-secondary)] mb-2">{t('productionBatch.advancedOptions.stationId')}</label>
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
value={getValue('station_id')}
|
value={getValue('station_id')}
|
||||||
onChange={(e) => handleFieldChange({ station_id: e.target.value })}
|
onChange={(e) => handleFieldChange({ station_id: e.target.value })}
|
||||||
placeholder="Ej: oven-1, mixer-2"
|
placeholder={t('productionBatch.advancedOptions.stationPlaceholder')}
|
||||||
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)]"
|
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>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<label className="block text-sm font-medium text-[var(--text-secondary)] mb-2">ID de Pedido</label>
|
<label className="block text-sm font-medium text-[var(--text-secondary)] mb-2">{t('productionBatch.advancedOptions.orderId')}</label>
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
value={getValue('order_id')}
|
value={getValue('order_id')}
|
||||||
onChange={(e) => handleFieldChange({ order_id: e.target.value })}
|
onChange={(e) => handleFieldChange({ order_id: e.target.value })}
|
||||||
placeholder="ID del pedido asociado"
|
placeholder={t('productionBatch.advancedOptions.orderPlaceholder')}
|
||||||
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)]"
|
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>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<label className="block text-sm font-medium text-[var(--text-secondary)] mb-2">ID de Pronóstico</label>
|
<label className="block text-sm font-medium text-[var(--text-secondary)] mb-2">{t('productionBatch.advancedOptions.forecastId')}</label>
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
value={getValue('forecast_id')}
|
value={getValue('forecast_id')}
|
||||||
onChange={(e) => handleFieldChange({ forecast_id: e.target.value })}
|
onChange={(e) => handleFieldChange({ forecast_id: e.target.value })}
|
||||||
placeholder="ID del pronóstico asociado"
|
placeholder={t('productionBatch.advancedOptions.forecastPlaceholder')}
|
||||||
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)]"
|
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>
|
</div>
|
||||||
@@ -487,28 +489,28 @@ const ReviewStep: React.FC<WizardStepProps> = ({ dataRef, onDataChange }) => {
|
|||||||
<div className="space-y-6">
|
<div className="space-y-6">
|
||||||
<div className="text-center pb-4 border-b border-[var(--border-primary)]">
|
<div className="text-center pb-4 border-b border-[var(--border-primary)]">
|
||||||
<CheckCircle2 className="w-12 h-12 mx-auto mb-3 text-[var(--color-primary)]" />
|
<CheckCircle2 className="w-12 h-12 mx-auto mb-3 text-[var(--color-primary)]" />
|
||||||
<h3 className="text-lg font-semibold text-[var(--text-primary)] mb-2">Revisar y Confirmar</h3>
|
<h3 className="text-lg font-semibold text-[var(--text-primary)] mb-2">{t('productionBatch.review.title')}</h3>
|
||||||
<p className="text-sm text-[var(--text-secondary)]">Verifica los detalles antes de crear el lote</p>
|
<p className="text-sm text-[var(--text-secondary)]">{t('productionBatch.review.description')}</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Product Info */}
|
{/* Product Info */}
|
||||||
<div className="p-4 bg-[var(--bg-secondary)]/50 rounded-lg border border-[var(--border-secondary)]">
|
<div className="p-4 bg-[var(--bg-secondary)]/50 rounded-lg border border-[var(--border-secondary)]">
|
||||||
<h4 className="font-semibold text-[var(--text-primary)] mb-3 flex items-center gap-2">
|
<h4 className="font-semibold text-[var(--text-primary)] mb-3 flex items-center gap-2">
|
||||||
<Package className="w-5 h-5" />
|
<Package className="w-5 h-5" />
|
||||||
Información del Producto
|
{t('productionBatch.review.productInfo')}
|
||||||
</h4>
|
</h4>
|
||||||
<div className="space-y-2 text-sm">
|
<div className="space-y-2 text-sm">
|
||||||
<div className="flex justify-between">
|
<div className="flex justify-between">
|
||||||
<span className="text-[var(--text-secondary)]">Producto:</span>
|
<span className="text-[var(--text-secondary)]">{t('productionBatch.review.product')}:</span>
|
||||||
<span className="font-medium">{data.product_name || 'N/A'}</span>
|
<span className="font-medium">{data.product_name || 'N/A'}</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex justify-between">
|
<div className="flex justify-between">
|
||||||
<span className="text-[var(--text-secondary)]">Receta:</span>
|
<span className="text-[var(--text-secondary)]">{t('productionBatch.review.recipe')}:</span>
|
||||||
<span className="font-medium">{data.selectedRecipe?.name || 'Sin receta específica'}</span>
|
<span className="font-medium">{data.selectedRecipe?.name || t('productionBatch.review.noSpecificRecipe')}</span>
|
||||||
</div>
|
</div>
|
||||||
{data.batch_number && (
|
{data.batch_number && (
|
||||||
<div className="flex justify-between">
|
<div className="flex justify-between">
|
||||||
<span className="text-[var(--text-secondary)]">Número de Lote:</span>
|
<span className="text-[var(--text-secondary)]">{t('productionBatch.review.batchNumber')}:</span>
|
||||||
<span className="font-medium">{data.batch_number}</span>
|
<span className="font-medium">{data.batch_number}</span>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
@@ -519,32 +521,32 @@ const ReviewStep: React.FC<WizardStepProps> = ({ dataRef, onDataChange }) => {
|
|||||||
<div className="p-4 bg-[var(--bg-secondary)]/50 rounded-lg border border-[var(--border-secondary)]">
|
<div className="p-4 bg-[var(--bg-secondary)]/50 rounded-lg border border-[var(--border-secondary)]">
|
||||||
<h4 className="font-semibold text-[var(--text-primary)] mb-3 flex items-center gap-2">
|
<h4 className="font-semibold text-[var(--text-primary)] mb-3 flex items-center gap-2">
|
||||||
<Clock className="w-5 h-5" />
|
<Clock className="w-5 h-5" />
|
||||||
Planificación de Producción
|
{t('productionBatch.review.productionPlanning')}
|
||||||
</h4>
|
</h4>
|
||||||
<div className="space-y-2 text-sm">
|
<div className="space-y-2 text-sm">
|
||||||
<div className="flex justify-between">
|
<div className="flex justify-between">
|
||||||
<span className="text-[var(--text-secondary)]">Inicio:</span>
|
<span className="text-[var(--text-secondary)]">{t('productionBatch.review.start')}:</span>
|
||||||
<span className="font-medium">
|
<span className="font-medium">
|
||||||
{data.planned_start_time
|
{data.planned_start_time
|
||||||
? new Date(data.planned_start_time).toLocaleString('es-ES')
|
? new Date(data.planned_start_time).toLocaleString('es-ES')
|
||||||
: 'No especificado'}
|
: t('productionBatch.review.notSpecified')}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex justify-between">
|
<div className="flex justify-between">
|
||||||
<span className="text-[var(--text-secondary)]">Fin:</span>
|
<span className="text-[var(--text-secondary)]">{t('productionBatch.review.end')}:</span>
|
||||||
<span className="font-medium">
|
<span className="font-medium">
|
||||||
{data.planned_end_time ? new Date(data.planned_end_time).toLocaleString('es-ES') : 'No especificado'}
|
{data.planned_end_time ? new Date(data.planned_end_time).toLocaleString('es-ES') : t('productionBatch.review.notSpecified')}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex justify-between">
|
<div className="flex justify-between">
|
||||||
<span className="text-[var(--text-secondary)]">Duración:</span>
|
<span className="text-[var(--text-secondary)]">{t('productionBatch.review.duration')}:</span>
|
||||||
<span className="font-medium">
|
<span className="font-medium">
|
||||||
{data.planned_duration_minutes} minutos ({(data.planned_duration_minutes / 60).toFixed(1)} horas)
|
{data.planned_duration_minutes} {t('productionBatch.review.minutes')} ({(data.planned_duration_minutes / 60).toFixed(1)} {t('productionBatch.review.hours')})
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex justify-between">
|
<div className="flex justify-between">
|
||||||
<span className="text-[var(--text-secondary)]">Cantidad:</span>
|
<span className="text-[var(--text-secondary)]">{t('productionBatch.review.quantity')}:</span>
|
||||||
<span className="font-medium">{data.planned_quantity} unidades</span>
|
<span className="font-medium">{data.planned_quantity} {t('productionBatch.review.units')}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -553,30 +555,30 @@ const ReviewStep: React.FC<WizardStepProps> = ({ dataRef, onDataChange }) => {
|
|||||||
<div className="p-4 bg-[var(--bg-secondary)]/50 rounded-lg border border-[var(--border-secondary)]">
|
<div className="p-4 bg-[var(--bg-secondary)]/50 rounded-lg border border-[var(--border-secondary)]">
|
||||||
<h4 className="font-semibold text-[var(--text-primary)] mb-3 flex items-center gap-2">
|
<h4 className="font-semibold text-[var(--text-primary)] mb-3 flex items-center gap-2">
|
||||||
<AlertCircle className="w-5 h-5" />
|
<AlertCircle className="w-5 h-5" />
|
||||||
Prioridad y Recursos
|
{t('productionBatch.review.priorityAndResources')}
|
||||||
</h4>
|
</h4>
|
||||||
<div className="space-y-2 text-sm">
|
<div className="space-y-2 text-sm">
|
||||||
<div className="flex justify-between">
|
<div className="flex justify-between">
|
||||||
<span className="text-[var(--text-secondary)]">Prioridad:</span>
|
<span className="text-[var(--text-secondary)]">{t('productionBatch.review.priority')}:</span>
|
||||||
<span className="font-medium capitalize">{data.priority || ProductionPriorityEnum.MEDIUM}</span>
|
<span className="font-medium capitalize">{data.priority || ProductionPriorityEnum.MEDIUM}</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex justify-between">
|
<div className="flex justify-between">
|
||||||
<span className="text-[var(--text-secondary)]">Orden Urgente:</span>
|
<span className="text-[var(--text-secondary)]">{t('productionBatch.review.rushOrder')}:</span>
|
||||||
<span className="font-medium">{data.is_rush_order ? 'Sí' : 'No'}</span>
|
<span className="font-medium">{data.is_rush_order ? t('productionBatch.review.yes') : t('productionBatch.review.no')}</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex justify-between">
|
<div className="flex justify-between">
|
||||||
<span className="text-[var(--text-secondary)]">Receta Especial:</span>
|
<span className="text-[var(--text-secondary)]">{t('productionBatch.review.specialRecipe')}:</span>
|
||||||
<span className="font-medium">{data.is_special_recipe ? 'Sí' : 'No'}</span>
|
<span className="font-medium">{data.is_special_recipe ? t('productionBatch.review.yes') : t('productionBatch.review.no')}</span>
|
||||||
</div>
|
</div>
|
||||||
{data.staff_assigned_string && (
|
{data.staff_assigned_string && (
|
||||||
<div className="pt-2 border-t border-[var(--border-primary)]">
|
<div className="pt-2 border-t border-[var(--border-primary)]">
|
||||||
<span className="text-[var(--text-secondary)] block mb-1">Personal Asignado:</span>
|
<span className="text-[var(--text-secondary)] block mb-1">{t('productionBatch.review.assignedStaff')}:</span>
|
||||||
<span className="font-medium">{data.staff_assigned_string}</span>
|
<span className="font-medium">{data.staff_assigned_string}</span>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
{data.production_notes && (
|
{data.production_notes && (
|
||||||
<div className="pt-2 border-t border-[var(--border-primary)]">
|
<div className="pt-2 border-t border-[var(--border-primary)]">
|
||||||
<span className="text-[var(--text-secondary)] block mb-1">Notas:</span>
|
<span className="text-[var(--text-secondary)] block mb-1">{t('productionBatch.review.notes')}:</span>
|
||||||
<span className="font-medium">{data.production_notes}</span>
|
<span className="font-medium">{data.production_notes}</span>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
@@ -588,7 +590,7 @@ const ReviewStep: React.FC<WizardStepProps> = ({ dataRef, onDataChange }) => {
|
|||||||
<Card className="p-4 bg-blue-50 border-blue-200">
|
<Card className="p-4 bg-blue-50 border-blue-200">
|
||||||
<h4 className="font-medium text-[var(--text-primary)] mb-3 flex items-center gap-2">
|
<h4 className="font-medium text-[var(--text-primary)] mb-3 flex items-center gap-2">
|
||||||
<ClipboardCheck className="w-5 h-5 text-blue-600" />
|
<ClipboardCheck className="w-5 h-5 text-blue-600" />
|
||||||
Controles de Calidad Requeridos
|
{t('productionBatch.review.qualityControlsRequired')}
|
||||||
</h4>
|
</h4>
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
{Object.entries(data.selectedRecipe.quality_check_configuration.stages).map(
|
{Object.entries(data.selectedRecipe.quality_check_configuration.stages).map(
|
||||||
@@ -597,12 +599,12 @@ const ReviewStep: React.FC<WizardStepProps> = ({ dataRef, onDataChange }) => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div key={stage} className="flex items-center gap-2 text-sm">
|
<div key={stage} className="flex items-center gap-2 text-sm">
|
||||||
<Badge variant="info">{STAGE_LABELS[stage as ProcessStage]}</Badge>
|
<Badge variant="info">{t(`productionBatch.additionalDetails.stages.${getStageLabelKey(stage as ProcessStage)}`)}</Badge>
|
||||||
<span className="text-[var(--text-secondary)]">
|
<span className="text-[var(--text-secondary)]">
|
||||||
{config.template_ids.length} control{config.template_ids.length > 1 ? 'es' : ''}
|
{config.template_ids.length} {config.template_ids.length > 1 ? t('productionBatch.review.controls') : t('productionBatch.review.control')}
|
||||||
</span>
|
</span>
|
||||||
{config.blocking && <Badge variant="warning" size="sm">Bloqueante</Badge>}
|
{config.blocking && <Badge variant="warning" size="sm">{t('productionBatch.review.blocking')}</Badge>}
|
||||||
{config.is_required && <Badge variant="error" size="sm">Requerido</Badge>}
|
{config.is_required && <Badge variant="error" size="sm">{t('productionBatch.review.required')}</Badge>}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -616,7 +618,7 @@ const ReviewStep: React.FC<WizardStepProps> = ({ dataRef, onDataChange }) => {
|
|||||||
|
|
||||||
export const ProductionBatchWizardSteps = (
|
export const ProductionBatchWizardSteps = (
|
||||||
dataRef: React.MutableRefObject<Record<string, any>>,
|
dataRef: React.MutableRefObject<Record<string, any>>,
|
||||||
setData: (data: Record<string, any>) => void
|
_setData: (data: Record<string, any>) => void
|
||||||
): WizardStep[] => {
|
): WizardStep[] => {
|
||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
@@ -626,7 +628,7 @@ export const ProductionBatchWizardSteps = (
|
|||||||
validate: () => {
|
validate: () => {
|
||||||
const data = dataRef.current;
|
const data = dataRef.current;
|
||||||
if (!data.product_id) {
|
if (!data.product_id) {
|
||||||
return 'Debes seleccionar un producto';
|
throw new Error('Debes seleccionar un producto');
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
},
|
},
|
||||||
@@ -638,16 +640,16 @@ export const ProductionBatchWizardSteps = (
|
|||||||
validate: () => {
|
validate: () => {
|
||||||
const data = dataRef.current;
|
const data = dataRef.current;
|
||||||
if (!data.planned_start_time) {
|
if (!data.planned_start_time) {
|
||||||
return 'Debes especificar la fecha y hora de inicio';
|
throw new Error('Debes especificar la fecha y hora de inicio');
|
||||||
}
|
}
|
||||||
if (!data.planned_duration_minutes || data.planned_duration_minutes <= 0) {
|
if (!data.planned_duration_minutes || data.planned_duration_minutes <= 0) {
|
||||||
return 'La duración debe ser mayor a 0 minutos';
|
throw new Error('La duración debe ser mayor a 0 minutos');
|
||||||
}
|
}
|
||||||
if (!data.planned_quantity || data.planned_quantity <= 0) {
|
if (!data.planned_quantity || data.planned_quantity <= 0) {
|
||||||
return 'La cantidad debe ser mayor a 0';
|
throw new Error('La cantidad debe ser mayor a 0');
|
||||||
}
|
}
|
||||||
if (data.planned_end_time && new Date(data.planned_end_time) <= new Date(data.planned_start_time)) {
|
if (data.planned_end_time && new Date(data.planned_end_time) <= new Date(data.planned_start_time)) {
|
||||||
return 'La fecha de fin debe ser posterior a la fecha de inicio';
|
throw new Error('La fecha de fin debe ser posterior a la fecha de inicio');
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -55,23 +55,23 @@ const SupplierSelectionStep: React.FC<WizardStepProps> = ({ dataRef, onDataChang
|
|||||||
<div className="text-center pb-4 border-b border-[var(--border-primary)]">
|
<div className="text-center pb-4 border-b border-[var(--border-primary)]">
|
||||||
<Building2 className="w-12 h-12 mx-auto mb-3 text-[var(--color-primary)]" />
|
<Building2 className="w-12 h-12 mx-auto mb-3 text-[var(--color-primary)]" />
|
||||||
<h3 className="text-lg font-semibold text-[var(--text-primary)] mb-2">
|
<h3 className="text-lg font-semibold text-[var(--text-primary)] mb-2">
|
||||||
Seleccionar Proveedor
|
{t('purchaseOrder.supplierSelection.title')}
|
||||||
</h3>
|
</h3>
|
||||||
<p className="text-sm text-[var(--text-secondary)]">
|
<p className="text-sm text-[var(--text-secondary)]">
|
||||||
Elige el proveedor para esta orden de compra
|
{t('purchaseOrder.supplierSelection.description')}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{isError && (
|
{isError && (
|
||||||
<div className="p-3 bg-red-50 border border-red-200 rounded-lg text-red-700 text-sm">
|
<div className="p-3 bg-red-50 border border-red-200 rounded-lg text-red-700 text-sm">
|
||||||
Error al cargar proveedores
|
{t('purchaseOrder.supplierSelection.errorLoading')}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{isLoading ? (
|
{isLoading ? (
|
||||||
<div className="flex items-center justify-center py-12">
|
<div className="flex items-center justify-center py-12">
|
||||||
<Loader2 className="w-8 h-8 animate-spin text-[var(--color-primary)]" />
|
<Loader2 className="w-8 h-8 animate-spin text-[var(--color-primary)]" />
|
||||||
<span className="ml-3 text-[var(--text-secondary)]">Cargando proveedores...</span>
|
<span className="ml-3 text-[var(--text-secondary)]">{t('purchaseOrder.supplierSelection.loading')}</span>
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<>
|
<>
|
||||||
@@ -82,7 +82,7 @@ const SupplierSelectionStep: React.FC<WizardStepProps> = ({ dataRef, onDataChang
|
|||||||
type="text"
|
type="text"
|
||||||
value={searchQuery}
|
value={searchQuery}
|
||||||
onChange={(e) => setSearchQuery(e.target.value)}
|
onChange={(e) => setSearchQuery(e.target.value)}
|
||||||
placeholder="Buscar proveedor por nombre o código..."
|
placeholder={t('purchaseOrder.supplierSelection.searchPlaceholder')}
|
||||||
className="w-full pl-10 pr-4 py-3 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)]"
|
className="w-full pl-10 pr-4 py-3 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>
|
</div>
|
||||||
@@ -92,8 +92,8 @@ const SupplierSelectionStep: React.FC<WizardStepProps> = ({ dataRef, onDataChang
|
|||||||
{filteredSuppliers.length === 0 ? (
|
{filteredSuppliers.length === 0 ? (
|
||||||
<div className="text-center py-8 border-2 border-dashed border-[var(--border-secondary)] rounded-lg">
|
<div className="text-center py-8 border-2 border-dashed border-[var(--border-secondary)] rounded-lg">
|
||||||
<Building2 className="w-12 h-12 mx-auto mb-3 text-[var(--text-tertiary)]" />
|
<Building2 className="w-12 h-12 mx-auto mb-3 text-[var(--text-tertiary)]" />
|
||||||
<p className="text-[var(--text-secondary)] mb-1">No se encontraron proveedores</p>
|
<p className="text-[var(--text-secondary)] mb-1">{t('purchaseOrder.supplierSelection.noSuppliers')}</p>
|
||||||
<p className="text-sm text-[var(--text-tertiary)]">Intenta con una búsqueda diferente</p>
|
<p className="text-sm text-[var(--text-tertiary)]">{t('purchaseOrder.supplierSelection.tryDifferentSearch')}</p>
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
filteredSuppliers.map((supplier: any) => (
|
filteredSuppliers.map((supplier: any) => (
|
||||||
@@ -250,36 +250,36 @@ const AddItemsStep: React.FC<WizardStepProps> = ({ dataRef, onDataChange }) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const unitOptions = [
|
const unitOptions = [
|
||||||
{ value: 'kg', label: 'Kilogramos' },
|
{ value: 'kg', label: t('purchaseOrder.orderItems.units.kg') },
|
||||||
{ value: 'g', label: 'Gramos' },
|
{ value: 'g', label: t('purchaseOrder.orderItems.units.g') },
|
||||||
{ value: 'l', label: 'Litros' },
|
{ value: 'l', label: t('purchaseOrder.orderItems.units.l') },
|
||||||
{ value: 'ml', label: 'Mililitros' },
|
{ value: 'ml', label: t('purchaseOrder.orderItems.units.ml') },
|
||||||
{ value: 'units', label: 'Unidades' },
|
{ value: 'units', label: t('purchaseOrder.orderItems.units.units') },
|
||||||
{ value: 'boxes', label: 'Cajas' },
|
{ value: 'boxes', label: t('purchaseOrder.orderItems.units.boxes') },
|
||||||
{ value: 'bags', label: 'Bolsas' },
|
{ value: 'bags', label: t('purchaseOrder.orderItems.units.bags') },
|
||||||
];
|
];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="space-y-6">
|
<div className="space-y-6">
|
||||||
<div className="text-center pb-4 border-b border-[var(--border-primary)]">
|
<div className="text-center pb-4 border-b border-[var(--border-primary)]">
|
||||||
<Package className="w-12 h-12 mx-auto mb-3 text-[var(--color-primary)]" />
|
<Package className="w-12 h-12 mx-auto mb-3 text-[var(--color-primary)]" />
|
||||||
<h3 className="text-lg font-semibold text-[var(--text-primary)] mb-2">Productos a Comprar</h3>
|
<h3 className="text-lg font-semibold text-[var(--text-primary)] mb-2">{t('purchaseOrder.orderItems.titleHeader')}</h3>
|
||||||
<p className="text-sm text-[var(--text-secondary)]">
|
<p className="text-sm text-[var(--text-secondary)]">
|
||||||
Proveedor: <span className="font-semibold">{data.supplier?.name || 'N/A'}</span>
|
{t('purchaseOrder.orderItems.supplier')}: <span className="font-semibold">{data.supplier?.name || 'N/A'}</span>
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{isLoadingIngredients || isLoadingSupplierProducts ? (
|
{isLoadingIngredients || isLoadingSupplierProducts ? (
|
||||||
<div className="flex items-center justify-center py-12">
|
<div className="flex items-center justify-center py-12">
|
||||||
<Loader2 className="w-8 h-8 animate-spin text-[var(--color-primary)]" />
|
<Loader2 className="w-8 h-8 animate-spin text-[var(--color-primary)]" />
|
||||||
<span className="ml-3 text-[var(--text-secondary)]">Cargando productos...</span>
|
<span className="ml-3 text-[var(--text-secondary)]">{t('purchaseOrder.orderItems.loadingProducts')}</span>
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<>
|
<>
|
||||||
<div className="space-y-3">
|
<div className="space-y-3">
|
||||||
<div className="flex items-center justify-between">
|
<div className="flex items-center justify-between">
|
||||||
<label className="block text-sm font-medium text-[var(--text-secondary)]">
|
<label className="block text-sm font-medium text-[var(--text-secondary)]">
|
||||||
Productos en la orden
|
{t('purchaseOrder.orderItems.productsInOrder')}
|
||||||
</label>
|
</label>
|
||||||
<button
|
<button
|
||||||
onClick={handleAddItem}
|
onClick={handleAddItem}
|
||||||
@@ -287,7 +287,7 @@ const AddItemsStep: React.FC<WizardStepProps> = ({ dataRef, onDataChange }) => {
|
|||||||
className="px-3 py-1.5 text-sm bg-[var(--color-primary)] text-white rounded-md hover:bg-[var(--color-primary)]/90 transition-colors flex items-center gap-1 disabled:opacity-50 disabled:cursor-not-allowed"
|
className="px-3 py-1.5 text-sm bg-[var(--color-primary)] text-white rounded-md hover:bg-[var(--color-primary)]/90 transition-colors flex items-center gap-1 disabled:opacity-50 disabled:cursor-not-allowed"
|
||||||
>
|
>
|
||||||
<Plus className="w-4 h-4" />
|
<Plus className="w-4 h-4" />
|
||||||
Agregar Producto
|
{t('purchaseOrder.orderItems.addProduct')}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -295,7 +295,7 @@ const AddItemsStep: React.FC<WizardStepProps> = ({ dataRef, onDataChange }) => {
|
|||||||
<div className="p-4 bg-amber-50 border border-amber-200 rounded-lg text-amber-700 text-sm flex items-center gap-2">
|
<div className="p-4 bg-amber-50 border border-amber-200 rounded-lg text-amber-700 text-sm flex items-center gap-2">
|
||||||
<AlertCircle className="w-5 h-5 flex-shrink-0" />
|
<AlertCircle className="w-5 h-5 flex-shrink-0" />
|
||||||
<span>
|
<span>
|
||||||
Este proveedor no tiene ingredientes asignados. Configura la lista de precios del proveedor primero.
|
{t('purchaseOrder.orderItems.noIngredientsForSupplier')}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
@@ -303,8 +303,8 @@ const AddItemsStep: React.FC<WizardStepProps> = ({ dataRef, onDataChange }) => {
|
|||||||
{(data.items || []).length === 0 ? (
|
{(data.items || []).length === 0 ? (
|
||||||
<div className="text-center py-12 border-2 border-dashed border-[var(--border-secondary)] rounded-lg text-[var(--text-tertiary)]">
|
<div className="text-center py-12 border-2 border-dashed border-[var(--border-secondary)] rounded-lg text-[var(--text-tertiary)]">
|
||||||
<Package className="w-12 h-12 mx-auto mb-3 opacity-50" />
|
<Package className="w-12 h-12 mx-auto mb-3 opacity-50" />
|
||||||
<p className="mb-2">No hay productos en la orden</p>
|
<p className="mb-2">{t('purchaseOrder.orderItems.noProducts')}</p>
|
||||||
<p className="text-sm">Haz clic en "Agregar Producto" para comenzar</p>
|
<p className="text-sm">{t('purchaseOrder.orderItems.clickToAdd')}</p>
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<div className="space-y-3">
|
<div className="space-y-3">
|
||||||
@@ -315,7 +315,7 @@ const AddItemsStep: React.FC<WizardStepProps> = ({ dataRef, onDataChange }) => {
|
|||||||
>
|
>
|
||||||
<div className="flex items-center justify-between">
|
<div className="flex items-center justify-between">
|
||||||
<span className="text-sm font-semibold text-[var(--text-primary)]">
|
<span className="text-sm font-semibold text-[var(--text-primary)]">
|
||||||
Producto #{index + 1}
|
{t('purchaseOrder.orderItems.productNumber', { number: index + 1 })}
|
||||||
</span>
|
</span>
|
||||||
<button
|
<button
|
||||||
onClick={() => handleRemoveItem(index)}
|
onClick={() => handleRemoveItem(index)}
|
||||||
@@ -328,14 +328,14 @@ const AddItemsStep: React.FC<WizardStepProps> = ({ dataRef, onDataChange }) => {
|
|||||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-3">
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-3">
|
||||||
<div className="md:col-span-2">
|
<div className="md:col-span-2">
|
||||||
<label className="block text-xs font-medium text-[var(--text-secondary)] mb-1">
|
<label className="block text-xs font-medium text-[var(--text-secondary)] mb-1">
|
||||||
Ingrediente *
|
{t('purchaseOrder.orderItems.ingredient')} *
|
||||||
</label>
|
</label>
|
||||||
<select
|
<select
|
||||||
value={item.inventory_product_id}
|
value={item.inventory_product_id}
|
||||||
onChange={(e) => handleUpdateItem(index, 'inventory_product_id', e.target.value)}
|
onChange={(e) => handleUpdateItem(index, 'inventory_product_id', e.target.value)}
|
||||||
className="w-full px-3 py-2 text-sm 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)]"
|
className="w-full px-3 py-2 text-sm 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)]"
|
||||||
>
|
>
|
||||||
<option value="">Seleccionar ingrediente...</option>
|
<option value="">{t('purchaseOrder.orderItems.selectIngredient')}</option>
|
||||||
{ingredientsData.map((product: any) => (
|
{ingredientsData.map((product: any) => (
|
||||||
<option key={product.id} value={product.id}>
|
<option key={product.id} value={product.id}>
|
||||||
{product.name} - €{(product.last_purchase_price || product.average_cost || 0).toFixed(2)} /{' '}
|
{product.name} - €{(product.last_purchase_price || product.average_cost || 0).toFixed(2)} /{' '}
|
||||||
@@ -347,7 +347,7 @@ const AddItemsStep: React.FC<WizardStepProps> = ({ dataRef, onDataChange }) => {
|
|||||||
|
|
||||||
<div>
|
<div>
|
||||||
<label className="block text-xs font-medium text-[var(--text-secondary)] mb-1">
|
<label className="block text-xs font-medium text-[var(--text-secondary)] mb-1">
|
||||||
Cantidad *
|
{t('purchaseOrder.orderItems.quantity')} *
|
||||||
</label>
|
</label>
|
||||||
<input
|
<input
|
||||||
type="number"
|
type="number"
|
||||||
@@ -362,7 +362,7 @@ const AddItemsStep: React.FC<WizardStepProps> = ({ dataRef, onDataChange }) => {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<label className="block text-xs font-medium text-[var(--text-secondary)] mb-1">Unidad *</label>
|
<label className="block text-xs font-medium text-[var(--text-secondary)] mb-1">{t('purchaseOrder.orderItems.unit')} *</label>
|
||||||
<select
|
<select
|
||||||
value={item.unit_of_measure}
|
value={item.unit_of_measure}
|
||||||
onChange={(e) => handleUpdateItem(index, 'unit_of_measure', e.target.value)}
|
onChange={(e) => handleUpdateItem(index, 'unit_of_measure', e.target.value)}
|
||||||
@@ -378,7 +378,7 @@ const AddItemsStep: React.FC<WizardStepProps> = ({ dataRef, onDataChange }) => {
|
|||||||
|
|
||||||
<div className="md:col-span-2">
|
<div className="md:col-span-2">
|
||||||
<label className="block text-xs font-medium text-[var(--text-secondary)] mb-1">
|
<label className="block text-xs font-medium text-[var(--text-secondary)] mb-1">
|
||||||
Precio Unitario (€) *
|
{t('purchaseOrder.orderItems.unitPrice')} *
|
||||||
</label>
|
</label>
|
||||||
<input
|
<input
|
||||||
type="number"
|
type="number"
|
||||||
@@ -393,7 +393,7 @@ const AddItemsStep: React.FC<WizardStepProps> = ({ dataRef, onDataChange }) => {
|
|||||||
|
|
||||||
<div className="pt-2 border-t border-[var(--border-primary)] text-sm">
|
<div className="pt-2 border-t border-[var(--border-primary)] text-sm">
|
||||||
<span className="font-semibold text-[var(--text-primary)]">
|
<span className="font-semibold text-[var(--text-primary)]">
|
||||||
Subtotal: €{item.subtotal.toFixed(2)}
|
{t('purchaseOrder.orderItems.subtotal')}: €{item.subtotal.toFixed(2)}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -404,7 +404,7 @@ const AddItemsStep: React.FC<WizardStepProps> = ({ dataRef, onDataChange }) => {
|
|||||||
{(data.items || []).length > 0 && (
|
{(data.items || []).length > 0 && (
|
||||||
<div className="p-4 bg-gradient-to-r from-[var(--color-primary)]/5 to-[var(--color-primary)]/10 rounded-lg border-2 border-[var(--color-primary)]/20">
|
<div className="p-4 bg-gradient-to-r from-[var(--color-primary)]/5 to-[var(--color-primary)]/10 rounded-lg border-2 border-[var(--color-primary)]/20">
|
||||||
<div className="flex justify-between items-center">
|
<div className="flex justify-between items-center">
|
||||||
<span className="text-lg font-semibold text-[var(--text-primary)]">Total:</span>
|
<span className="text-lg font-semibold text-[var(--text-primary)]">{t('purchaseOrder.orderItems.total')}:</span>
|
||||||
<span className="text-2xl font-bold text-[var(--color-primary)]">€{calculateTotal().toFixed(2)}</span>
|
<span className="text-2xl font-bold text-[var(--color-primary)]">€{calculateTotal().toFixed(2)}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -430,24 +430,24 @@ const OrderDetailsStep: React.FC<WizardStepProps> = ({ dataRef, onDataChange })
|
|||||||
};
|
};
|
||||||
|
|
||||||
const priorityOptions = [
|
const priorityOptions = [
|
||||||
{ value: 'low', label: 'Baja' },
|
{ value: 'low', label: t('purchaseOrder.orderDetails.priorityOptions.low') },
|
||||||
{ value: 'normal', label: 'Normal' },
|
{ value: 'normal', label: t('purchaseOrder.orderDetails.priorityOptions.normal') },
|
||||||
{ value: 'high', label: 'Alta' },
|
{ value: 'high', label: t('purchaseOrder.orderDetails.priorityOptions.high') },
|
||||||
{ value: 'critical', label: 'Crítica' },
|
{ value: 'critical', label: t('purchaseOrder.orderDetails.priorityOptions.critical') },
|
||||||
];
|
];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="space-y-6">
|
<div className="space-y-6">
|
||||||
<div className="text-center pb-4 border-b border-[var(--border-primary)]">
|
<div className="text-center pb-4 border-b border-[var(--border-primary)]">
|
||||||
<Calendar className="w-12 h-12 mx-auto mb-3 text-[var(--color-primary)]" />
|
<Calendar className="w-12 h-12 mx-auto mb-3 text-[var(--color-primary)]" />
|
||||||
<h3 className="text-lg font-semibold text-[var(--text-primary)] mb-2">Detalles de la Orden</h3>
|
<h3 className="text-lg font-semibold text-[var(--text-primary)] mb-2">{t('purchaseOrder.orderDetails.title')}</h3>
|
||||||
<p className="text-sm text-[var(--text-secondary)]">Configura fecha de entrega y prioridad</p>
|
<p className="text-sm text-[var(--text-secondary)]">{t('purchaseOrder.orderDetails.description')}</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||||
<div>
|
<div>
|
||||||
<label className="block text-sm font-medium text-[var(--text-secondary)] mb-2">
|
<label className="block text-sm font-medium text-[var(--text-secondary)] mb-2">
|
||||||
Fecha de Entrega Requerida *
|
{t('purchaseOrder.orderDetails.requiredDeliveryDate')} *
|
||||||
</label>
|
</label>
|
||||||
<input
|
<input
|
||||||
type="date"
|
type="date"
|
||||||
@@ -459,7 +459,7 @@ const OrderDetailsStep: React.FC<WizardStepProps> = ({ dataRef, onDataChange })
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<label className="block text-sm font-medium text-[var(--text-secondary)] mb-2">Prioridad *</label>
|
<label className="block text-sm font-medium text-[var(--text-secondary)] mb-2">{t('purchaseOrder.orderDetails.priority')} *</label>
|
||||||
<select
|
<select
|
||||||
value={getValue('priority', 'normal')}
|
value={getValue('priority', 'normal')}
|
||||||
onChange={(e) => handleFieldChange({ priority: e.target.value })}
|
onChange={(e) => handleFieldChange({ priority: e.target.value })}
|
||||||
@@ -475,12 +475,12 @@ const OrderDetailsStep: React.FC<WizardStepProps> = ({ dataRef, onDataChange })
|
|||||||
|
|
||||||
<div className="md:col-span-2">
|
<div className="md:col-span-2">
|
||||||
<label className="block text-sm font-medium text-[var(--text-secondary)] mb-2">
|
<label className="block text-sm font-medium text-[var(--text-secondary)] mb-2">
|
||||||
Notas (Opcional)
|
{t('purchaseOrder.orderDetails.notes')}
|
||||||
</label>
|
</label>
|
||||||
<textarea
|
<textarea
|
||||||
value={getValue('notes')}
|
value={getValue('notes')}
|
||||||
onChange={(e) => handleFieldChange({ notes: e.target.value })}
|
onChange={(e) => handleFieldChange({ notes: e.target.value })}
|
||||||
placeholder="Instrucciones especiales para el proveedor..."
|
placeholder={t('purchaseOrder.orderDetails.notesPlaceholder')}
|
||||||
rows={4}
|
rows={4}
|
||||||
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)]"
|
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)]"
|
||||||
/>
|
/>
|
||||||
@@ -488,12 +488,12 @@ const OrderDetailsStep: React.FC<WizardStepProps> = ({ dataRef, onDataChange })
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<AdvancedOptionsSection
|
<AdvancedOptionsSection
|
||||||
title="Opciones Avanzadas"
|
title={t('purchaseOrder.advancedOptions.title')}
|
||||||
description="Información financiera adicional"
|
description={t('purchaseOrder.advancedOptions.description')}
|
||||||
>
|
>
|
||||||
<div className="grid grid-cols-1 md:grid-cols-3 gap-4">
|
<div className="grid grid-cols-1 md:grid-cols-3 gap-4">
|
||||||
<div>
|
<div>
|
||||||
<label className="block text-sm font-medium text-[var(--text-secondary)] mb-2">Impuestos (€)</label>
|
<label className="block text-sm font-medium text-[var(--text-secondary)] mb-2">{t('purchaseOrder.advancedOptions.tax')}</label>
|
||||||
<input
|
<input
|
||||||
type="number"
|
type="number"
|
||||||
value={getValue('tax_amount', 0)}
|
value={getValue('tax_amount', 0)}
|
||||||
@@ -505,7 +505,7 @@ const OrderDetailsStep: React.FC<WizardStepProps> = ({ dataRef, onDataChange })
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<label className="block text-sm font-medium text-[var(--text-secondary)] mb-2">Costo de Envío (€)</label>
|
<label className="block text-sm font-medium text-[var(--text-secondary)] mb-2">{t('purchaseOrder.advancedOptions.shippingCost')}</label>
|
||||||
<input
|
<input
|
||||||
type="number"
|
type="number"
|
||||||
value={getValue('shipping_cost', 0)}
|
value={getValue('shipping_cost', 0)}
|
||||||
@@ -517,7 +517,7 @@ const OrderDetailsStep: React.FC<WizardStepProps> = ({ dataRef, onDataChange })
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<label className="block text-sm font-medium text-[var(--text-secondary)] mb-2">Descuento (€)</label>
|
<label className="block text-sm font-medium text-[var(--text-secondary)] mb-2">{t('purchaseOrder.advancedOptions.discount')}</label>
|
||||||
<input
|
<input
|
||||||
type="number"
|
type="number"
|
||||||
value={getValue('discount_amount', 0)}
|
value={getValue('discount_amount', 0)}
|
||||||
@@ -554,28 +554,28 @@ const ReviewSubmitStep: React.FC<WizardStepProps> = ({ dataRef, onDataChange })
|
|||||||
<div className="space-y-6">
|
<div className="space-y-6">
|
||||||
<div className="text-center pb-4 border-b border-[var(--border-primary)]">
|
<div className="text-center pb-4 border-b border-[var(--border-primary)]">
|
||||||
<CheckCircle2 className="w-12 h-12 mx-auto mb-3 text-[var(--color-primary)]" />
|
<CheckCircle2 className="w-12 h-12 mx-auto mb-3 text-[var(--color-primary)]" />
|
||||||
<h3 className="text-lg font-semibold text-[var(--text-primary)] mb-2">Revisar y Confirmar</h3>
|
<h3 className="text-lg font-semibold text-[var(--text-primary)] mb-2">{t('purchaseOrder.review.title')}</h3>
|
||||||
<p className="text-sm text-[var(--text-secondary)]">Verifica los detalles antes de crear la orden</p>
|
<p className="text-sm text-[var(--text-secondary)]">{t('purchaseOrder.review.description')}</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Supplier Info */}
|
{/* Supplier Info */}
|
||||||
<div className="p-4 bg-[var(--bg-secondary)]/50 rounded-lg border border-[var(--border-secondary)]">
|
<div className="p-4 bg-[var(--bg-secondary)]/50 rounded-lg border border-[var(--border-secondary)]">
|
||||||
<h4 className="font-semibold text-[var(--text-primary)] mb-3 flex items-center gap-2">
|
<h4 className="font-semibold text-[var(--text-primary)] mb-3 flex items-center gap-2">
|
||||||
<Building2 className="w-5 h-5" />
|
<Building2 className="w-5 h-5" />
|
||||||
Información del Proveedor
|
{t('purchaseOrder.review.supplierInfo')}
|
||||||
</h4>
|
</h4>
|
||||||
<div className="space-y-2 text-sm">
|
<div className="space-y-2 text-sm">
|
||||||
<div className="flex justify-between">
|
<div className="flex justify-between">
|
||||||
<span className="text-[var(--text-secondary)]">Proveedor:</span>
|
<span className="text-[var(--text-secondary)]">{t('purchaseOrder.review.supplier')}:</span>
|
||||||
<span className="font-medium">{data.supplier?.name || 'N/A'}</span>
|
<span className="font-medium">{data.supplier?.name || 'N/A'}</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex justify-between">
|
<div className="flex justify-between">
|
||||||
<span className="text-[var(--text-secondary)]">Código:</span>
|
<span className="text-[var(--text-secondary)]">{t('purchaseOrder.review.code')}:</span>
|
||||||
<span className="font-medium">{data.supplier?.supplier_code || 'N/A'}</span>
|
<span className="font-medium">{data.supplier?.supplier_code || 'N/A'}</span>
|
||||||
</div>
|
</div>
|
||||||
{data.supplier?.email && (
|
{data.supplier?.email && (
|
||||||
<div className="flex justify-between">
|
<div className="flex justify-between">
|
||||||
<span className="text-[var(--text-secondary)]">Email:</span>
|
<span className="text-[var(--text-secondary)]">{t('purchaseOrder.review.email')}:</span>
|
||||||
<span className="font-medium">{data.supplier.email}</span>
|
<span className="font-medium">{data.supplier.email}</span>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
@@ -586,24 +586,24 @@ const ReviewSubmitStep: React.FC<WizardStepProps> = ({ dataRef, onDataChange })
|
|||||||
<div className="p-4 bg-[var(--bg-secondary)]/50 rounded-lg border border-[var(--border-secondary)]">
|
<div className="p-4 bg-[var(--bg-secondary)]/50 rounded-lg border border-[var(--border-secondary)]">
|
||||||
<h4 className="font-semibold text-[var(--text-primary)] mb-3 flex items-center gap-2">
|
<h4 className="font-semibold text-[var(--text-primary)] mb-3 flex items-center gap-2">
|
||||||
<Calendar className="w-5 h-5" />
|
<Calendar className="w-5 h-5" />
|
||||||
Detalles de la Orden
|
{t('purchaseOrder.review.orderDetails')}
|
||||||
</h4>
|
</h4>
|
||||||
<div className="space-y-2 text-sm">
|
<div className="space-y-2 text-sm">
|
||||||
<div className="flex justify-between">
|
<div className="flex justify-between">
|
||||||
<span className="text-[var(--text-secondary)]">Fecha de Entrega:</span>
|
<span className="text-[var(--text-secondary)]">{t('purchaseOrder.review.deliveryDate')}:</span>
|
||||||
<span className="font-medium">
|
<span className="font-medium">
|
||||||
{data.required_delivery_date
|
{data.required_delivery_date
|
||||||
? new Date(data.required_delivery_date).toLocaleDateString('es-ES')
|
? new Date(data.required_delivery_date).toLocaleDateString('es-ES')
|
||||||
: 'No especificada'}
|
: t('purchaseOrder.review.notSpecified')}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex justify-between">
|
<div className="flex justify-between">
|
||||||
<span className="text-[var(--text-secondary)]">Prioridad:</span>
|
<span className="text-[var(--text-secondary)]">{t('purchaseOrder.review.priority')}:</span>
|
||||||
<span className="font-medium capitalize">{data.priority || 'normal'}</span>
|
<span className="font-medium capitalize">{data.priority || 'normal'}</span>
|
||||||
</div>
|
</div>
|
||||||
{data.notes && (
|
{data.notes && (
|
||||||
<div className="pt-2 border-t border-[var(--border-primary)]">
|
<div className="pt-2 border-t border-[var(--border-primary)]">
|
||||||
<span className="text-[var(--text-secondary)] block mb-1">Notas:</span>
|
<span className="text-[var(--text-secondary)] block mb-1">{t('purchaseOrder.review.notes')}:</span>
|
||||||
<span className="font-medium">{data.notes}</span>
|
<span className="font-medium">{data.notes}</span>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
@@ -614,7 +614,7 @@ const ReviewSubmitStep: React.FC<WizardStepProps> = ({ dataRef, onDataChange })
|
|||||||
<div className="p-4 bg-[var(--bg-secondary)]/50 rounded-lg border border-[var(--border-secondary)]">
|
<div className="p-4 bg-[var(--bg-secondary)]/50 rounded-lg border border-[var(--border-secondary)]">
|
||||||
<h4 className="font-semibold text-[var(--text-primary)] mb-3 flex items-center gap-2">
|
<h4 className="font-semibold text-[var(--text-primary)] mb-3 flex items-center gap-2">
|
||||||
<Package className="w-5 h-5" />
|
<Package className="w-5 h-5" />
|
||||||
Productos ({(data.items || []).length})
|
{t('purchaseOrder.review.products')} ({(data.items || []).length})
|
||||||
</h4>
|
</h4>
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
{(data.items || []).map((item: any, index: number) => (
|
{(data.items || []).map((item: any, index: number) => (
|
||||||
@@ -623,7 +623,7 @@ const ReviewSubmitStep: React.FC<WizardStepProps> = ({ dataRef, onDataChange })
|
|||||||
className="flex justify-between items-start p-3 bg-[var(--bg-primary)] rounded-lg border border-[var(--border-secondary)]"
|
className="flex justify-between items-start p-3 bg-[var(--bg-primary)] rounded-lg border border-[var(--border-secondary)]"
|
||||||
>
|
>
|
||||||
<div className="flex-1">
|
<div className="flex-1">
|
||||||
<p className="font-medium text-[var(--text-primary)]">{item.product_name || 'Producto sin nombre'}</p>
|
<p className="font-medium text-[var(--text-primary)]">{item.product_name || t('purchaseOrder.review.productNoName')}</p>
|
||||||
<p className="text-sm text-[var(--text-secondary)]">
|
<p className="text-sm text-[var(--text-secondary)]">
|
||||||
{item.ordered_quantity} {item.unit_of_measure} × €{item.unit_price.toFixed(2)}
|
{item.ordered_quantity} {item.unit_of_measure} × €{item.unit_price.toFixed(2)}
|
||||||
</p>
|
</p>
|
||||||
@@ -640,33 +640,33 @@ const ReviewSubmitStep: React.FC<WizardStepProps> = ({ dataRef, onDataChange })
|
|||||||
<div className="p-4 bg-gradient-to-r from-[var(--color-primary)]/5 to-[var(--color-primary)]/10 rounded-lg border-2 border-[var(--color-primary)]/20">
|
<div className="p-4 bg-gradient-to-r from-[var(--color-primary)]/5 to-[var(--color-primary)]/10 rounded-lg border-2 border-[var(--color-primary)]/20">
|
||||||
<h4 className="font-semibold text-[var(--text-primary)] mb-3 flex items-center gap-2">
|
<h4 className="font-semibold text-[var(--text-primary)] mb-3 flex items-center gap-2">
|
||||||
<TrendingUp className="w-5 h-5" />
|
<TrendingUp className="w-5 h-5" />
|
||||||
Resumen Financiero
|
{t('purchaseOrder.review.financialSummary')}
|
||||||
</h4>
|
</h4>
|
||||||
<div className="space-y-2 text-sm">
|
<div className="space-y-2 text-sm">
|
||||||
<div className="flex justify-between">
|
<div className="flex justify-between">
|
||||||
<span className="text-[var(--text-secondary)]">Subtotal:</span>
|
<span className="text-[var(--text-secondary)]">{t('purchaseOrder.review.subtotal')}:</span>
|
||||||
<span className="font-medium">€{calculateSubtotal().toFixed(2)}</span>
|
<span className="font-medium">€{calculateSubtotal().toFixed(2)}</span>
|
||||||
</div>
|
</div>
|
||||||
{(data.tax_amount || 0) > 0 && (
|
{(data.tax_amount || 0) > 0 && (
|
||||||
<div className="flex justify-between">
|
<div className="flex justify-between">
|
||||||
<span className="text-[var(--text-secondary)]">Impuestos:</span>
|
<span className="text-[var(--text-secondary)]">{t('purchaseOrder.review.taxes')}:</span>
|
||||||
<span className="font-medium">€{(data.tax_amount || 0).toFixed(2)}</span>
|
<span className="font-medium">€{(data.tax_amount || 0).toFixed(2)}</span>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
{(data.shipping_cost || 0) > 0 && (
|
{(data.shipping_cost || 0) > 0 && (
|
||||||
<div className="flex justify-between">
|
<div className="flex justify-between">
|
||||||
<span className="text-[var(--text-secondary)]">Costo de Envío:</span>
|
<span className="text-[var(--text-secondary)]">{t('purchaseOrder.review.shipping')}:</span>
|
||||||
<span className="font-medium">€{(data.shipping_cost || 0).toFixed(2)}</span>
|
<span className="font-medium">€{(data.shipping_cost || 0).toFixed(2)}</span>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
{(data.discount_amount || 0) > 0 && (
|
{(data.discount_amount || 0) > 0 && (
|
||||||
<div className="flex justify-between">
|
<div className="flex justify-between">
|
||||||
<span className="text-[var(--text-secondary)]">Descuento:</span>
|
<span className="text-[var(--text-secondary)]">{t('purchaseOrder.review.discount')}:</span>
|
||||||
<span className="font-medium text-green-600">-€{(data.discount_amount || 0).toFixed(2)}</span>
|
<span className="font-medium text-green-600">-€{(data.discount_amount || 0).toFixed(2)}</span>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
<div className="pt-2 border-t-2 border-[var(--color-primary)]/30 flex justify-between">
|
<div className="pt-2 border-t-2 border-[var(--color-primary)]/30 flex justify-between">
|
||||||
<span className="text-lg font-semibold text-[var(--text-primary)]">Total:</span>
|
<span className="text-lg font-semibold text-[var(--text-primary)]">{t('purchaseOrder.review.total')}:</span>
|
||||||
<span className="text-2xl font-bold text-[var(--color-primary)]">€{calculateTotal().toFixed(2)}</span>
|
<span className="text-2xl font-bold text-[var(--color-primary)]">€{calculateTotal().toFixed(2)}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -677,17 +677,17 @@ const ReviewSubmitStep: React.FC<WizardStepProps> = ({ dataRef, onDataChange })
|
|||||||
|
|
||||||
export const PurchaseOrderWizardSteps = (
|
export const PurchaseOrderWizardSteps = (
|
||||||
dataRef: React.MutableRefObject<Record<string, any>>,
|
dataRef: React.MutableRefObject<Record<string, any>>,
|
||||||
setData: (data: Record<string, any>) => void
|
_setData: (data: Record<string, any>) => void
|
||||||
): WizardStep[] => {
|
): WizardStep[] => {
|
||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
id: 'supplier-selection',
|
id: 'supplier-selection',
|
||||||
title: 'Seleccionar Proveedor',
|
title: 'Seleccionar Proveedor', // Will be translated in UnifiedAddWizard
|
||||||
component: SupplierSelectionStep,
|
component: SupplierSelectionStep,
|
||||||
validate: () => {
|
validate: () => {
|
||||||
const data = dataRef.current;
|
const data = dataRef.current;
|
||||||
if (!data.supplier_id) {
|
if (!data.supplier_id) {
|
||||||
return 'Debes seleccionar un proveedor';
|
throw new Error('Debes seleccionar un proveedor');
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
},
|
},
|
||||||
@@ -699,13 +699,13 @@ export const PurchaseOrderWizardSteps = (
|
|||||||
validate: () => {
|
validate: () => {
|
||||||
const data = dataRef.current;
|
const data = dataRef.current;
|
||||||
if (!data.items || data.items.length === 0) {
|
if (!data.items || data.items.length === 0) {
|
||||||
return 'Debes agregar al menos un producto';
|
throw new Error('Debes agregar al menos un producto');
|
||||||
}
|
}
|
||||||
const invalidItems = data.items.some(
|
const invalidItems = data.items.some(
|
||||||
(item: any) => !item.inventory_product_id || item.ordered_quantity < 0.01 || item.unit_price < 0.01
|
(item: any) => !item.inventory_product_id || item.ordered_quantity < 0.01 || item.unit_price < 0.01
|
||||||
);
|
);
|
||||||
if (invalidItems) {
|
if (invalidItems) {
|
||||||
return 'Todos los productos deben tener ingrediente, cantidad mayor a 0 y precio mayor a 0';
|
throw new Error('Todos los productos deben tener ingrediente, cantidad mayor a 0 y precio mayor a 0');
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
},
|
},
|
||||||
@@ -717,7 +717,7 @@ export const PurchaseOrderWizardSteps = (
|
|||||||
validate: () => {
|
validate: () => {
|
||||||
const data = dataRef.current;
|
const data = dataRef.current;
|
||||||
if (!data.required_delivery_date) {
|
if (!data.required_delivery_date) {
|
||||||
return 'Debes especificar una fecha de entrega';
|
throw new Error('Debes especificar una fecha de entrega');
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -6,7 +6,13 @@
|
|||||||
"leaveEmptyForAutoGeneration": "Leave empty for auto-generation",
|
"leaveEmptyForAutoGeneration": "Leave empty for auto-generation",
|
||||||
"readOnly": "Read-only - Auto-generated",
|
"readOnly": "Read-only - Auto-generated",
|
||||||
"willBeGeneratedAutomatically": "Will be generated automatically",
|
"willBeGeneratedAutomatically": "Will be generated automatically",
|
||||||
"autoGeneratedOnSave": "Auto-generated on save"
|
"autoGeneratedOnSave": "Auto-generated on save",
|
||||||
|
"show": "Show",
|
||||||
|
"hide": "Hide",
|
||||||
|
"next": "Next",
|
||||||
|
"back": "Back",
|
||||||
|
"complete": "Complete",
|
||||||
|
"stepOf": "Step {current} of {total}"
|
||||||
},
|
},
|
||||||
"keyValueEditor": {
|
"keyValueEditor": {
|
||||||
"showBuilder": "Show Builder",
|
"showBuilder": "Show Builder",
|
||||||
@@ -743,5 +749,313 @@
|
|||||||
"parameters": "Template parameters",
|
"parameters": "Template parameters",
|
||||||
"thresholds": "Threshold values",
|
"thresholds": "Threshold values",
|
||||||
"scoringCriteria": "Custom scoring criteria"
|
"scoringCriteria": "Custom scoring criteria"
|
||||||
|
},
|
||||||
|
"purchaseOrder": {
|
||||||
|
"title": "Add Purchase Order",
|
||||||
|
"steps": {
|
||||||
|
"supplierSelection": "Supplier Selection",
|
||||||
|
"supplierSelectionDescription": "Choose the supplier",
|
||||||
|
"orderItems": "Order Items",
|
||||||
|
"orderItemsDescription": "Products to order",
|
||||||
|
"deliveryDetails": "Delivery Details",
|
||||||
|
"deliveryDetailsDescription": "Delivery date and notes"
|
||||||
|
},
|
||||||
|
"supplierSelection": {
|
||||||
|
"title": "Select Supplier",
|
||||||
|
"subtitle": "Choose the supplier for this purchase order",
|
||||||
|
"searchPlaceholder": "Search supplier by name or code...",
|
||||||
|
"loading": "Loading suppliers...",
|
||||||
|
"error": "Error loading suppliers",
|
||||||
|
"noSuppliersFound": "No suppliers found",
|
||||||
|
"tryDifferentSearch": "Try a different search term"
|
||||||
|
},
|
||||||
|
"orderItems": {
|
||||||
|
"title": "Order Items",
|
||||||
|
"subtitle": "Select products to order",
|
||||||
|
"addItem": "Add Item",
|
||||||
|
"removeItem": "Remove item",
|
||||||
|
"noItemsAdded": "No items added yet",
|
||||||
|
"clickToBegin": "Click 'Add Item' to get started",
|
||||||
|
"supplier": "Supplier",
|
||||||
|
"orderTotal": "Order Total",
|
||||||
|
"fields": {
|
||||||
|
"ingredient": "Ingredient",
|
||||||
|
"ingredientPlaceholder": "Select ingredient...",
|
||||||
|
"quantity": "Quantity",
|
||||||
|
"unitPrice": "Unit Price (€)",
|
||||||
|
"subtotal": "Subtotal",
|
||||||
|
"notes": "Notes",
|
||||||
|
"notesPlaceholder": "Additional notes for this item..."
|
||||||
|
},
|
||||||
|
"loading": "Loading ingredients...",
|
||||||
|
"noIngredients": "No ingredients available",
|
||||||
|
"addToInventory": "Add ingredients to inventory first"
|
||||||
|
},
|
||||||
|
"deliveryDetails": {
|
||||||
|
"title": "Delivery Details",
|
||||||
|
"subtitle": "Configure delivery date and additional notes",
|
||||||
|
"fields": {
|
||||||
|
"expectedDeliveryDate": "Expected Delivery Date",
|
||||||
|
"notes": "Order Notes",
|
||||||
|
"notesPlaceholder": "Special instructions, delivery requirements, etc.",
|
||||||
|
"orderNumber": "Order Number",
|
||||||
|
"orderNumberTooltip": "Will be auto-generated when creating the order",
|
||||||
|
"status": "Status",
|
||||||
|
"priority": "Priority"
|
||||||
|
},
|
||||||
|
"orderSummary": {
|
||||||
|
"title": "Order Summary",
|
||||||
|
"supplier": "Supplier:",
|
||||||
|
"items": "Items:",
|
||||||
|
"total": "Total:",
|
||||||
|
"deliveryDate": "Expected Delivery:"
|
||||||
|
},
|
||||||
|
"statuses": {
|
||||||
|
"draft": "Draft",
|
||||||
|
"pending": "Pending",
|
||||||
|
"approved": "Approved",
|
||||||
|
"ordered": "Ordered",
|
||||||
|
"received": "Received",
|
||||||
|
"cancelled": "Cancelled"
|
||||||
|
},
|
||||||
|
"priorities": {
|
||||||
|
"low": "Low",
|
||||||
|
"normal": "Normal",
|
||||||
|
"high": "High",
|
||||||
|
"urgent": "Urgent"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"messages": {
|
||||||
|
"errorObtainingTenantInfo": "Could not obtain tenant information",
|
||||||
|
"errorLoadingSuppliers": "Error loading suppliers",
|
||||||
|
"errorLoadingIngredients": "Error loading ingredients",
|
||||||
|
"purchaseOrderCreatedSuccessfully": "Purchase order created successfully",
|
||||||
|
"errorCreatingPurchaseOrder": "Error creating purchase order",
|
||||||
|
"selectSupplier": "Please select a supplier",
|
||||||
|
"addAtLeastOneItem": "Add at least one item to the order"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"productionBatch": {
|
||||||
|
"title": "Add Production Batch",
|
||||||
|
"steps": {
|
||||||
|
"productRecipe": "Product & Recipe",
|
||||||
|
"productRecipeDescription": "Select what to produce",
|
||||||
|
"timing": "Scheduling",
|
||||||
|
"timingDescription": "Production dates",
|
||||||
|
"equipmentStaff": "Equipment & Staff",
|
||||||
|
"equipmentStaffDescription": "Assigned resources",
|
||||||
|
"additionalDetails": "Additional Details",
|
||||||
|
"additionalDetailsDescription": "Notes and configuration"
|
||||||
|
},
|
||||||
|
"productRecipe": {
|
||||||
|
"title": "Select Product and Recipe",
|
||||||
|
"subtitle": "Choose the product to produce and optionally a recipe",
|
||||||
|
"loading": "Loading information...",
|
||||||
|
"fields": {
|
||||||
|
"product": "Product to Produce",
|
||||||
|
"productPlaceholder": "Select product...",
|
||||||
|
"recipe": "Recipe to Use (Optional)",
|
||||||
|
"recipePlaceholder": "No specific recipe",
|
||||||
|
"quantityToProduce": "Quantity to Produce",
|
||||||
|
"batchNumber": "Batch Number",
|
||||||
|
"batchNumberPlaceholder": "BATCH-001"
|
||||||
|
},
|
||||||
|
"noProducts": "No products available",
|
||||||
|
"addToInventory": "Add products to inventory first",
|
||||||
|
"recipeDetails": {
|
||||||
|
"title": "Recipe Details",
|
||||||
|
"yield": "Yield:",
|
||||||
|
"prepTime": "Prep Time:",
|
||||||
|
"cookTime": "Cook Time:",
|
||||||
|
"totalTime": "Total Time:",
|
||||||
|
"ingredients": "Ingredients:",
|
||||||
|
"minutes": "min"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"timing": {
|
||||||
|
"title": "Production Scheduling",
|
||||||
|
"subtitle": "Define production start and end dates",
|
||||||
|
"fields": {
|
||||||
|
"scheduledStartTime": "Start Date & Time",
|
||||||
|
"scheduledEndTime": "End Date & Time (Optional)",
|
||||||
|
"estimatedDuration": "Estimated Duration (minutes)",
|
||||||
|
"estimatedDurationPlaceholder": "120"
|
||||||
|
},
|
||||||
|
"summary": {
|
||||||
|
"product": "Product:",
|
||||||
|
"quantity": "Quantity:",
|
||||||
|
"recipe": "Recipe:",
|
||||||
|
"startTime": "Start:",
|
||||||
|
"duration": "Est. Duration:"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"equipmentStaff": {
|
||||||
|
"title": "Equipment & Staff",
|
||||||
|
"subtitle": "Assign resources for this production",
|
||||||
|
"fields": {
|
||||||
|
"assignedEquipment": "Assigned Equipment",
|
||||||
|
"assignedEquipmentPlaceholder": "E.g., Oven #1, Mixer #2",
|
||||||
|
"assignedStaff": "Assigned Staff",
|
||||||
|
"assignedStaffPlaceholder": "E.g., John Doe, Jane Smith",
|
||||||
|
"supervisor": "Supervisor",
|
||||||
|
"supervisorPlaceholder": "Supervisor name"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"additionalDetails": {
|
||||||
|
"title": "Additional Details",
|
||||||
|
"subtitle": "Optional configuration and notes",
|
||||||
|
"fields": {
|
||||||
|
"priority": "Priority",
|
||||||
|
"status": "Status",
|
||||||
|
"productionStage": "Production Stage",
|
||||||
|
"notes": "Production Notes",
|
||||||
|
"notesPlaceholder": "Special instructions, observations, etc.",
|
||||||
|
"qualityCheckRequired": "Quality Check Required",
|
||||||
|
"qualityCheckNotes": "Quality Check Notes",
|
||||||
|
"qualityCheckNotesPlaceholder": "Specific control points..."
|
||||||
|
},
|
||||||
|
"priorities": {
|
||||||
|
"low": "Low",
|
||||||
|
"normal": "Normal",
|
||||||
|
"high": "High",
|
||||||
|
"urgent": "Urgent"
|
||||||
|
},
|
||||||
|
"statuses": {
|
||||||
|
"scheduled": "Scheduled",
|
||||||
|
"in_progress": "In Progress",
|
||||||
|
"completed": "Completed",
|
||||||
|
"on_hold": "On Hold",
|
||||||
|
"cancelled": "Cancelled"
|
||||||
|
},
|
||||||
|
"stages": {
|
||||||
|
"mixing": "Mixing",
|
||||||
|
"proofing": "Proofing",
|
||||||
|
"shaping": "Shaping",
|
||||||
|
"baking": "Baking",
|
||||||
|
"cooling": "Cooling",
|
||||||
|
"packaging": "Packaging",
|
||||||
|
"finishing": "Finishing"
|
||||||
|
},
|
||||||
|
"summary": {
|
||||||
|
"title": "Production Batch Summary",
|
||||||
|
"product": "Product:",
|
||||||
|
"quantity": "Quantity:",
|
||||||
|
"recipe": "Recipe:",
|
||||||
|
"startTime": "Scheduled Start:",
|
||||||
|
"priority": "Priority:",
|
||||||
|
"equipment": "Equipment:",
|
||||||
|
"staff": "Staff:"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"messages": {
|
||||||
|
"errorObtainingTenantInfo": "Could not obtain tenant information",
|
||||||
|
"errorLoadingProducts": "Error loading products",
|
||||||
|
"errorLoadingRecipes": "Error loading recipes",
|
||||||
|
"productionBatchCreatedSuccessfully": "Production batch created successfully",
|
||||||
|
"errorCreatingProductionBatch": "Error creating production batch",
|
||||||
|
"selectProduct": "Please select a product",
|
||||||
|
"enterQuantity": "Enter a valid quantity"
|
||||||
|
},
|
||||||
|
"validation": {
|
||||||
|
"selectProduct": "You must select a product",
|
||||||
|
"startTime": "You must specify the start date and time",
|
||||||
|
"duration": "Duration must be greater than 0 minutes",
|
||||||
|
"quantity": "Quantity must be greater than 0",
|
||||||
|
"endTime": "End date must be after start date"
|
||||||
|
},
|
||||||
|
"stepTitles": {
|
||||||
|
"productRecipe": "Product & Recipe",
|
||||||
|
"planning": "Scheduling",
|
||||||
|
"priorityResources": "Priority & Resources",
|
||||||
|
"review": "Review & Confirm"
|
||||||
|
},
|
||||||
|
"review": {
|
||||||
|
"unspecified": "Not specified",
|
||||||
|
"noRecipe": "No specific recipe"
|
||||||
|
},
|
||||||
|
"productRecipe": {
|
||||||
|
"fields": {
|
||||||
|
"productLabel": "Product to Produce *",
|
||||||
|
"batchNumberAutoPlaceholder": "Will be auto-generated if left empty"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"equipmentStaff": {
|
||||||
|
"fields": {
|
||||||
|
"equipmentExample": "E.g., oven-1, mixer-2",
|
||||||
|
"staffHelp": "Separate names with commas (E.g., John Doe, Jane Smith)",
|
||||||
|
"orderId": "Associated order ID",
|
||||||
|
"forecastId": "Associated forecast ID"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"additionalDetails": {
|
||||||
|
"advancedOptions": {
|
||||||
|
"title": "Advanced Options",
|
||||||
|
"description": "Additional production information"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"purchaseOrder": {
|
||||||
|
"orderItems": {
|
||||||
|
"titleHeader": "Products to Purchase",
|
||||||
|
"addProduct": "Add Product",
|
||||||
|
"clickToBeginProduct": "Click 'Add Product' to get started",
|
||||||
|
"units": {
|
||||||
|
"kg": "Kilograms",
|
||||||
|
"g": "Grams",
|
||||||
|
"l": "Liters",
|
||||||
|
"ml": "Milliliters",
|
||||||
|
"units": "Units",
|
||||||
|
"boxes": "Boxes",
|
||||||
|
"bags": "Bags"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"deliveryDetails": {
|
||||||
|
"titleHeader": "Order Details",
|
||||||
|
"subtitleHeader": "Configure delivery date and priority",
|
||||||
|
"fields": {
|
||||||
|
"instructionsPlaceholder": "Special instructions for the supplier..."
|
||||||
|
},
|
||||||
|
"priorities": {
|
||||||
|
"critical": "Critical"
|
||||||
|
},
|
||||||
|
"advancedOptions": {
|
||||||
|
"title": "Advanced Options",
|
||||||
|
"description": "Additional financial information"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"validation": {
|
||||||
|
"selectSupplier": "You must select a supplier",
|
||||||
|
"addProduct": "You must add at least one product",
|
||||||
|
"invalidItems": "All products must have ingredient, quantity greater than 0 and price greater than 0",
|
||||||
|
"deliveryDate": "You must specify a delivery date"
|
||||||
|
},
|
||||||
|
"stepTitles": {
|
||||||
|
"selectSupplier": "Select Supplier",
|
||||||
|
"addProducts": "Add Products",
|
||||||
|
"orderDetails": "Order Details",
|
||||||
|
"reviewConfirm": "Review & Confirm"
|
||||||
|
},
|
||||||
|
"review": {
|
||||||
|
"unspecified": "Not specified",
|
||||||
|
"noName": "Product without name"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"itemTypeSelector": {
|
||||||
|
"searchPlaceholder": "Search by name or category...",
|
||||||
|
"noResults": "No results found",
|
||||||
|
"resultSingular": "result",
|
||||||
|
"resultPlural": "results",
|
||||||
|
"categories": {
|
||||||
|
"all": "All",
|
||||||
|
"daily": "Daily",
|
||||||
|
"common": "Common",
|
||||||
|
"setup": "Setup"
|
||||||
|
},
|
||||||
|
"badges": {
|
||||||
|
"mostCommon": "⭐ Most Common",
|
||||||
|
"daily": "Daily",
|
||||||
|
"common": "Common",
|
||||||
|
"setup": "Setup"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1154,5 +1154,293 @@
|
|||||||
"fileValidatedSuccessfully": "Archivo validado exitosamente",
|
"fileValidatedSuccessfully": "Archivo validado exitosamente",
|
||||||
"fileImportedSuccessfully": "Archivo importado exitosamente"
|
"fileImportedSuccessfully": "Archivo importado exitosamente"
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"purchaseOrder": {
|
||||||
|
"title": "Agregar Orden de Compra",
|
||||||
|
"steps": {
|
||||||
|
"supplierSelection": "Selección de Proveedor",
|
||||||
|
"supplierSelectionDescription": "Elige el proveedor",
|
||||||
|
"orderItems": "Artículos de la Orden",
|
||||||
|
"orderItemsDescription": "Productos a ordenar",
|
||||||
|
"deliveryDetails": "Detalles de Entrega",
|
||||||
|
"deliveryDetailsDescription": "Fecha y notas de entrega"
|
||||||
|
},
|
||||||
|
"supplierSelection": {
|
||||||
|
"title": "Seleccionar Proveedor",
|
||||||
|
"subtitle": "Elige el proveedor para esta orden de compra",
|
||||||
|
"searchPlaceholder": "Buscar proveedor por nombre o código...",
|
||||||
|
"loading": "Cargando proveedores...",
|
||||||
|
"error": "Error al cargar proveedores",
|
||||||
|
"noSuppliersFound": "No se encontraron proveedores",
|
||||||
|
"tryDifferentSearch": "Intenta con una búsqueda diferente"
|
||||||
|
},
|
||||||
|
"orderItems": {
|
||||||
|
"title": "Artículos de la Orden",
|
||||||
|
"titleHeader": "Productos a Comprar",
|
||||||
|
"subtitle": "Selecciona los productos a ordenar",
|
||||||
|
"addItem": "Agregar Artículo",
|
||||||
|
"addProduct": "Agregar Producto",
|
||||||
|
"removeItem": "Eliminar artículo",
|
||||||
|
"noItemsAdded": "No se han agregado artículos",
|
||||||
|
"clickToBegin": "Haz clic en 'Agregar Artículo' para comenzar",
|
||||||
|
"clickToBeginProduct": "Haz clic en \"Agregar Producto\" para comenzar",
|
||||||
|
"supplier": "Proveedor",
|
||||||
|
"orderTotal": "Total de la Orden",
|
||||||
|
"fields": {
|
||||||
|
"ingredient": "Ingrediente",
|
||||||
|
"ingredientPlaceholder": "Seleccionar ingrediente...",
|
||||||
|
"quantity": "Cantidad",
|
||||||
|
"unitPrice": "Precio Unitario (€)",
|
||||||
|
"subtotal": "Subtotal",
|
||||||
|
"notes": "Notas",
|
||||||
|
"notesPlaceholder": "Notas adicionales para este artículo..."
|
||||||
|
},
|
||||||
|
"loading": "Cargando ingredientes...",
|
||||||
|
"noIngredients": "No hay ingredientes disponibles",
|
||||||
|
"addToInventory": "Agrega ingredientes al inventario primero",
|
||||||
|
"units": {
|
||||||
|
"kg": "Kilogramos",
|
||||||
|
"g": "Gramos",
|
||||||
|
"l": "Litros",
|
||||||
|
"ml": "Mililitros",
|
||||||
|
"units": "Unidades",
|
||||||
|
"boxes": "Cajas",
|
||||||
|
"bags": "Bolsas"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"deliveryDetails": {
|
||||||
|
"title": "Detalles de Entrega",
|
||||||
|
"titleHeader": "Detalles de la Orden",
|
||||||
|
"subtitle": "Configura la fecha de entrega y notas adicionales",
|
||||||
|
"subtitleHeader": "Configura fecha de entrega y prioridad",
|
||||||
|
"fields": {
|
||||||
|
"expectedDeliveryDate": "Fecha de Entrega Esperada",
|
||||||
|
"notes": "Notas de la Orden",
|
||||||
|
"notesPlaceholder": "Instrucciones especiales, requisitos de entrega, etc.",
|
||||||
|
"instructionsPlaceholder": "Instrucciones especiales para el proveedor...",
|
||||||
|
"orderNumber": "Número de Orden",
|
||||||
|
"orderNumberTooltip": "Se generará automáticamente al crear la orden",
|
||||||
|
"status": "Estado",
|
||||||
|
"priority": "Prioridad"
|
||||||
|
},
|
||||||
|
"orderSummary": {
|
||||||
|
"title": "Resumen de la Orden",
|
||||||
|
"supplier": "Proveedor:",
|
||||||
|
"items": "Artículos:",
|
||||||
|
"total": "Total:",
|
||||||
|
"deliveryDate": "Entrega Esperada:"
|
||||||
|
},
|
||||||
|
"statuses": {
|
||||||
|
"draft": "Borrador",
|
||||||
|
"pending": "Pendiente",
|
||||||
|
"approved": "Aprobado",
|
||||||
|
"ordered": "Ordenado",
|
||||||
|
"received": "Recibido",
|
||||||
|
"cancelled": "Cancelado"
|
||||||
|
},
|
||||||
|
"priorities": {
|
||||||
|
"low": "Baja",
|
||||||
|
"normal": "Normal",
|
||||||
|
"high": "Alta",
|
||||||
|
"urgent": "Urgente",
|
||||||
|
"critical": "Crítica"
|
||||||
|
},
|
||||||
|
"advancedOptions": {
|
||||||
|
"title": "Opciones Avanzadas",
|
||||||
|
"description": "Información financiera adicional"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"messages": {
|
||||||
|
"errorObtainingTenantInfo": "No se pudo obtener información del tenant",
|
||||||
|
"errorLoadingSuppliers": "Error al cargar proveedores",
|
||||||
|
"errorLoadingIngredients": "Error al cargar ingredientes",
|
||||||
|
"purchaseOrderCreatedSuccessfully": "Orden de compra creada exitosamente",
|
||||||
|
"errorCreatingPurchaseOrder": "Error al crear la orden de compra",
|
||||||
|
"selectSupplier": "Por favor selecciona un proveedor",
|
||||||
|
"addAtLeastOneItem": "Agrega al menos un artículo a la orden"
|
||||||
|
},
|
||||||
|
"validation": {
|
||||||
|
"selectSupplier": "Debes seleccionar un proveedor",
|
||||||
|
"addProduct": "Debes agregar al menos un producto",
|
||||||
|
"invalidItems": "Todos los productos deben tener ingrediente, cantidad mayor a 0 y precio mayor a 0",
|
||||||
|
"deliveryDate": "Debes especificar una fecha de entrega"
|
||||||
|
},
|
||||||
|
"stepTitles": {
|
||||||
|
"selectSupplier": "Seleccionar Proveedor",
|
||||||
|
"addProducts": "Agregar Productos",
|
||||||
|
"orderDetails": "Detalles de la Orden",
|
||||||
|
"reviewConfirm": "Revisar y Confirmar"
|
||||||
|
},
|
||||||
|
"review": {
|
||||||
|
"unspecified": "No especificada",
|
||||||
|
"noName": "Producto sin nombre"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"productionBatch": {
|
||||||
|
"title": "Agregar Lote de Producción",
|
||||||
|
"steps": {
|
||||||
|
"productRecipe": "Producto y Receta",
|
||||||
|
"productRecipeDescription": "Selecciona qué producir",
|
||||||
|
"timing": "Programación",
|
||||||
|
"timingDescription": "Fechas de producción",
|
||||||
|
"equipmentStaff": "Equipo y Personal",
|
||||||
|
"equipmentStaffDescription": "Recursos asignados",
|
||||||
|
"additionalDetails": "Detalles Adicionales",
|
||||||
|
"additionalDetailsDescription": "Notas y configuración"
|
||||||
|
},
|
||||||
|
"productRecipe": {
|
||||||
|
"title": "Seleccionar Producto y Receta",
|
||||||
|
"subtitle": "Elige el producto a producir y opcionalmente una receta",
|
||||||
|
"loading": "Cargando información...",
|
||||||
|
"fields": {
|
||||||
|
"product": "Producto a Producir",
|
||||||
|
"productLabel": "Producto a Producir *",
|
||||||
|
"productPlaceholder": "Seleccionar producto...",
|
||||||
|
"recipe": "Receta a Utilizar (Opcional)",
|
||||||
|
"recipePlaceholder": "Sin receta específica",
|
||||||
|
"quantityToProduce": "Cantidad a Producir",
|
||||||
|
"batchNumber": "Número de Lote",
|
||||||
|
"batchNumberPlaceholder": "LOTE-001",
|
||||||
|
"batchNumberAutoPlaceholder": "Se generará automáticamente si se deja vacío"
|
||||||
|
},
|
||||||
|
"noProducts": "No hay productos disponibles",
|
||||||
|
"addToInventory": "Agrega productos al inventario primero",
|
||||||
|
"recipeDetails": {
|
||||||
|
"title": "Detalles de la Receta",
|
||||||
|
"yield": "Rendimiento:",
|
||||||
|
"prepTime": "Tiempo de Prep:",
|
||||||
|
"cookTime": "Tiempo de Cocción:",
|
||||||
|
"totalTime": "Tiempo Total:",
|
||||||
|
"ingredients": "Ingredientes:",
|
||||||
|
"minutes": "min"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"timing": {
|
||||||
|
"title": "Programación de Producción",
|
||||||
|
"subtitle": "Define las fechas de inicio y fin de producción",
|
||||||
|
"fields": {
|
||||||
|
"scheduledStartTime": "Fecha y Hora de Inicio",
|
||||||
|
"scheduledEndTime": "Fecha y Hora de Finalización (Opcional)",
|
||||||
|
"estimatedDuration": "Duración Estimada (minutos)",
|
||||||
|
"estimatedDurationPlaceholder": "120"
|
||||||
|
},
|
||||||
|
"summary": {
|
||||||
|
"product": "Producto:",
|
||||||
|
"quantity": "Cantidad:",
|
||||||
|
"recipe": "Receta:",
|
||||||
|
"startTime": "Inicio:",
|
||||||
|
"duration": "Duración Est.:"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"equipmentStaff": {
|
||||||
|
"title": "Equipo y Personal",
|
||||||
|
"subtitle": "Asigna recursos para esta producción",
|
||||||
|
"fields": {
|
||||||
|
"assignedEquipment": "Equipo Asignado",
|
||||||
|
"assignedEquipmentPlaceholder": "Ej: Horno #1, Amasadora #2",
|
||||||
|
"equipmentExample": "Ej: oven-1, mixer-2",
|
||||||
|
"assignedStaff": "Personal Asignado",
|
||||||
|
"assignedStaffPlaceholder": "Ej: Juan García, María López",
|
||||||
|
"staffHelp": "Separar nombres con comas (Ej: Juan Pérez, María García)",
|
||||||
|
"supervisor": "Supervisor",
|
||||||
|
"supervisorPlaceholder": "Nombre del supervisor",
|
||||||
|
"orderId": "ID del pedido asociado",
|
||||||
|
"forecastId": "ID del pronóstico asociado"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"additionalDetails": {
|
||||||
|
"title": "Detalles Adicionales",
|
||||||
|
"subtitle": "Configuración opcional y notas",
|
||||||
|
"advancedOptions": {
|
||||||
|
"title": "Opciones Avanzadas",
|
||||||
|
"description": "Información adicional de producción"
|
||||||
|
},
|
||||||
|
"fields": {
|
||||||
|
"priority": "Prioridad",
|
||||||
|
"status": "Estado",
|
||||||
|
"productionStage": "Etapa de Producción",
|
||||||
|
"notes": "Notas de Producción",
|
||||||
|
"notesPlaceholder": "Instrucciones especiales, observaciones, etc.",
|
||||||
|
"qualityCheckRequired": "Requiere Control de Calidad",
|
||||||
|
"qualityCheckNotes": "Notas de Control de Calidad",
|
||||||
|
"qualityCheckNotesPlaceholder": "Puntos de control específicos..."
|
||||||
|
},
|
||||||
|
"priorities": {
|
||||||
|
"low": "Baja",
|
||||||
|
"normal": "Normal",
|
||||||
|
"high": "Alta",
|
||||||
|
"urgent": "Urgente"
|
||||||
|
},
|
||||||
|
"statuses": {
|
||||||
|
"scheduled": "Programado",
|
||||||
|
"in_progress": "En Progreso",
|
||||||
|
"completed": "Completado",
|
||||||
|
"on_hold": "En Espera",
|
||||||
|
"cancelled": "Cancelado"
|
||||||
|
},
|
||||||
|
"stages": {
|
||||||
|
"mixing": "Mezclado",
|
||||||
|
"proofing": "Fermentación",
|
||||||
|
"shaping": "Formado",
|
||||||
|
"baking": "Horneado",
|
||||||
|
"cooling": "Enfriado",
|
||||||
|
"packaging": "Empaquetado",
|
||||||
|
"finishing": "Acabado"
|
||||||
|
},
|
||||||
|
"summary": {
|
||||||
|
"title": "Resumen del Lote de Producción",
|
||||||
|
"product": "Producto:",
|
||||||
|
"quantity": "Cantidad:",
|
||||||
|
"recipe": "Receta:",
|
||||||
|
"startTime": "Inicio Programado:",
|
||||||
|
"priority": "Prioridad:",
|
||||||
|
"equipment": "Equipo:",
|
||||||
|
"staff": "Personal:"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"messages": {
|
||||||
|
"errorObtainingTenantInfo": "No se pudo obtener información del tenant",
|
||||||
|
"errorLoadingProducts": "Error al cargar productos",
|
||||||
|
"errorLoadingRecipes": "Error al cargar recetas",
|
||||||
|
"productionBatchCreatedSuccessfully": "Lote de producción creado exitosamente",
|
||||||
|
"errorCreatingProductionBatch": "Error al crear el lote de producción",
|
||||||
|
"selectProduct": "Por favor selecciona un producto",
|
||||||
|
"enterQuantity": "Ingresa una cantidad válida"
|
||||||
|
},
|
||||||
|
"validation": {
|
||||||
|
"selectProduct": "Debes seleccionar un producto",
|
||||||
|
"startTime": "Debes especificar la fecha y hora de inicio",
|
||||||
|
"duration": "La duración debe ser mayor a 0 minutos",
|
||||||
|
"quantity": "La cantidad debe ser mayor a 0",
|
||||||
|
"endTime": "La fecha de fin debe ser posterior a la fecha de inicio"
|
||||||
|
},
|
||||||
|
"stepTitles": {
|
||||||
|
"productRecipe": "Producto y Receta",
|
||||||
|
"planning": "Planificación",
|
||||||
|
"priorityResources": "Prioridad y Recursos",
|
||||||
|
"review": "Revisar y Confirmar"
|
||||||
|
},
|
||||||
|
"review": {
|
||||||
|
"unspecified": "No especificado",
|
||||||
|
"noRecipe": "Sin receta específica"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"itemTypeSelector": {
|
||||||
|
"searchPlaceholder": "Buscar por nombre o categoría...",
|
||||||
|
"noResults": "No se encontraron resultados",
|
||||||
|
"resultSingular": "resultado",
|
||||||
|
"resultPlural": "resultados",
|
||||||
|
"categories": {
|
||||||
|
"all": "Todos",
|
||||||
|
"daily": "Diario",
|
||||||
|
"common": "Común",
|
||||||
|
"setup": "Configuración"
|
||||||
|
},
|
||||||
|
"badges": {
|
||||||
|
"mostCommon": "⭐ Más Común",
|
||||||
|
"daily": "Diario",
|
||||||
|
"common": "Común",
|
||||||
|
"setup": "Configuración"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -743,5 +743,313 @@
|
|||||||
"parameters": "Txantiloi parametroak",
|
"parameters": "Txantiloi parametroak",
|
||||||
"thresholds": "Atalase balioak",
|
"thresholds": "Atalase balioak",
|
||||||
"scoringCriteria": "Puntuazio irizpide pertsonalizatuak"
|
"scoringCriteria": "Puntuazio irizpide pertsonalizatuak"
|
||||||
|
},
|
||||||
|
"purchaseOrder": {
|
||||||
|
"title": "Erosketa Eskaera Gehitu",
|
||||||
|
"steps": {
|
||||||
|
"supplierSelection": "Hornitzailearen Hautaketa",
|
||||||
|
"supplierSelectionDescription": "Aukeratu hornitzailea",
|
||||||
|
"orderItems": "Eskaeraren Elementuak",
|
||||||
|
"orderItemsDescription": "Eskatu beharreko produktuak",
|
||||||
|
"deliveryDetails": "Entregaren Xehetasunak",
|
||||||
|
"deliveryDetailsDescription": "Entrega data eta oharrak"
|
||||||
|
},
|
||||||
|
"supplierSelection": {
|
||||||
|
"title": "Hornitzailea Hautatu",
|
||||||
|
"subtitle": "Aukeratu erosketa eskaera honetarako hornitzailea",
|
||||||
|
"searchPlaceholder": "Bilatu hornitzailea izenaren edo kodearen arabera...",
|
||||||
|
"loading": "Hornitzaileak kargatzen...",
|
||||||
|
"error": "Errorea hornitzaileak kargatzean",
|
||||||
|
"noSuppliersFound": "Ez da hornitzailerik aurkitu",
|
||||||
|
"tryDifferentSearch": "Saiatu bilaketa-termino desberdin batekin"
|
||||||
|
},
|
||||||
|
"orderItems": {
|
||||||
|
"title": "Eskaeraren Elementuak",
|
||||||
|
"subtitle": "Hautatu eskatu beharreko produktuak",
|
||||||
|
"addItem": "Gehitu Elementua",
|
||||||
|
"removeItem": "Kendu elementua",
|
||||||
|
"noItemsAdded": "Ez da elementurik gehitu oraindik",
|
||||||
|
"clickToBegin": "Sakatu 'Gehitu Elementua' hasteko",
|
||||||
|
"supplier": "Hornitzailea",
|
||||||
|
"orderTotal": "Eskaeraren Guztira",
|
||||||
|
"fields": {
|
||||||
|
"ingredient": "Osagaia",
|
||||||
|
"ingredientPlaceholder": "Hautatu osagaia...",
|
||||||
|
"quantity": "Kantitatea",
|
||||||
|
"unitPrice": "Unitate Prezioa (€)",
|
||||||
|
"subtotal": "Azpitotala",
|
||||||
|
"notes": "Oharrak",
|
||||||
|
"notesPlaceholder": "Elementu honetarako ohar gehigarriak..."
|
||||||
|
},
|
||||||
|
"loading": "Osagaiak kargatzen...",
|
||||||
|
"noIngredients": "Ez dago osagairik eskuragarri",
|
||||||
|
"addToInventory": "Gehitu osagaiak inventariora lehenik"
|
||||||
|
},
|
||||||
|
"deliveryDetails": {
|
||||||
|
"title": "Entregaren Xehetasunak",
|
||||||
|
"subtitle": "Konfiguratu entrega data eta ohar gehigarriak",
|
||||||
|
"fields": {
|
||||||
|
"expectedDeliveryDate": "Espero den Entrega Data",
|
||||||
|
"notes": "Eskaeraren Oharrak",
|
||||||
|
"notesPlaceholder": "Jarraibide bereziak, entrega eskakizunak, etab.",
|
||||||
|
"orderNumber": "Eskaera Zenbakia",
|
||||||
|
"orderNumberTooltip": "Automatikoki sortuko da eskaera sortzean",
|
||||||
|
"status": "Egoera",
|
||||||
|
"priority": "Lehentasuna"
|
||||||
|
},
|
||||||
|
"orderSummary": {
|
||||||
|
"title": "Eskaeraren Laburpena",
|
||||||
|
"supplier": "Hornitzailea:",
|
||||||
|
"items": "Elementuak:",
|
||||||
|
"total": "Guztira:",
|
||||||
|
"deliveryDate": "Espero den Entrega:"
|
||||||
|
},
|
||||||
|
"statuses": {
|
||||||
|
"draft": "Zirriborroa",
|
||||||
|
"pending": "Zain",
|
||||||
|
"approved": "Onartuta",
|
||||||
|
"ordered": "Eskatuta",
|
||||||
|
"received": "Jasota",
|
||||||
|
"cancelled": "Bertan behera utzita"
|
||||||
|
},
|
||||||
|
"priorities": {
|
||||||
|
"low": "Baxua",
|
||||||
|
"normal": "Normala",
|
||||||
|
"high": "Altua",
|
||||||
|
"urgent": "Urgentea"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"messages": {
|
||||||
|
"errorObtainingTenantInfo": "Ezin izan da tenant informazioa lortu",
|
||||||
|
"errorLoadingSuppliers": "Errorea hornitzaileak kargatzean",
|
||||||
|
"errorLoadingIngredients": "Errorea osagaiak kargatzean",
|
||||||
|
"purchaseOrderCreatedSuccessfully": "Erosketa eskaera ondo sortu da",
|
||||||
|
"errorCreatingPurchaseOrder": "Errorea erosketa eskaera sortzean",
|
||||||
|
"selectSupplier": "Mesedez hautatu hornitzaile bat",
|
||||||
|
"addAtLeastOneItem": "Gehitu gutxienez elementu bat eskaerara"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"productionBatch": {
|
||||||
|
"title": "Ekoizpen Lotea Gehitu",
|
||||||
|
"steps": {
|
||||||
|
"productRecipe": "Produktua eta Errezeta",
|
||||||
|
"productRecipeDescription": "Hautatu zer ekoitzi",
|
||||||
|
"timing": "Programazioa",
|
||||||
|
"timingDescription": "Ekoizpen datak",
|
||||||
|
"equipmentStaff": "Ekipamendua eta Langileak",
|
||||||
|
"equipmentStaffDescription": "Esleitutako baliabideak",
|
||||||
|
"additionalDetails": "Xehetasun Gehigarriak",
|
||||||
|
"additionalDetailsDescription": "Oharrak eta konfigurazioa"
|
||||||
|
},
|
||||||
|
"productRecipe": {
|
||||||
|
"title": "Produktua eta Errezeta Hautatu",
|
||||||
|
"subtitle": "Aukeratu ekoitzi beharreko produktua eta aukeran errezeta bat",
|
||||||
|
"loading": "Informazioa kargatzen...",
|
||||||
|
"fields": {
|
||||||
|
"product": "Ekoitzi Beharreko Produktua",
|
||||||
|
"productPlaceholder": "Hautatu produktua...",
|
||||||
|
"recipe": "Erabili Beharreko Errezeta (Aukerakoa)",
|
||||||
|
"recipePlaceholder": "Errezeta zehatzik gabe",
|
||||||
|
"quantityToProduce": "Ekoitzi Beharreko Kantitatea",
|
||||||
|
"batchNumber": "Lote Zenbakia",
|
||||||
|
"batchNumberPlaceholder": "LOTE-001"
|
||||||
|
},
|
||||||
|
"noProducts": "Ez dago produkturik eskuragarri",
|
||||||
|
"addToInventory": "Gehitu produktuak inventariora lehenik",
|
||||||
|
"recipeDetails": {
|
||||||
|
"title": "Errezeta Xehetasunak",
|
||||||
|
"yield": "Etekin:",
|
||||||
|
"prepTime": "Prestaketa Denbora:",
|
||||||
|
"cookTime": "Sukaldaritza Denbora:",
|
||||||
|
"totalTime": "Denbora Guztira:",
|
||||||
|
"ingredients": "Osagaiak:",
|
||||||
|
"minutes": "min"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"timing": {
|
||||||
|
"title": "Ekoizpen Programazioa",
|
||||||
|
"subtitle": "Zehaztu ekoizpenaren hasiera eta amaiera datak",
|
||||||
|
"fields": {
|
||||||
|
"scheduledStartTime": "Hasiera Data eta Ordua",
|
||||||
|
"scheduledEndTime": "Amaiera Data eta Ordua (Aukerakoa)",
|
||||||
|
"estimatedDuration": "Iraupena Estimatua (minutuak)",
|
||||||
|
"estimatedDurationPlaceholder": "120"
|
||||||
|
},
|
||||||
|
"summary": {
|
||||||
|
"product": "Produktua:",
|
||||||
|
"quantity": "Kantitatea:",
|
||||||
|
"recipe": "Errezeta:",
|
||||||
|
"startTime": "Hasiera:",
|
||||||
|
"duration": "Iraupena Est.:"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"equipmentStaff": {
|
||||||
|
"title": "Ekipamendua eta Langileak",
|
||||||
|
"subtitle": "Esleitu baliabideak ekoizpen honetarako",
|
||||||
|
"fields": {
|
||||||
|
"assignedEquipment": "Esleitutako Ekipamendua",
|
||||||
|
"assignedEquipmentPlaceholder": "Adib: Labea #1, Nahasmaila #2",
|
||||||
|
"assignedStaff": "Esleitutako Langileak",
|
||||||
|
"assignedStaffPlaceholder": "Adib: Jon Gartziak, Maria Lopez",
|
||||||
|
"supervisor": "Gainbegiratzailea",
|
||||||
|
"supervisorPlaceholder": "Gainbegiratzailearen izena"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"additionalDetails": {
|
||||||
|
"title": "Xehetasun Gehigarriak",
|
||||||
|
"subtitle": "Konfigurazio aukerakoa eta oharrak",
|
||||||
|
"fields": {
|
||||||
|
"priority": "Lehentasuna",
|
||||||
|
"status": "Egoera",
|
||||||
|
"productionStage": "Ekoizpen Fasea",
|
||||||
|
"notes": "Ekoizpen Oharrak",
|
||||||
|
"notesPlaceholder": "Jarraibide bereziak, behaketak, etab.",
|
||||||
|
"qualityCheckRequired": "Kalitate Kontrola Beharrezkoa",
|
||||||
|
"qualityCheckNotes": "Kalitate Kontrolaren Oharrak",
|
||||||
|
"qualityCheckNotesPlaceholder": "Kontrol puntu espezifikoak..."
|
||||||
|
},
|
||||||
|
"priorities": {
|
||||||
|
"low": "Baxua",
|
||||||
|
"normal": "Normala",
|
||||||
|
"high": "Altua",
|
||||||
|
"urgent": "Urgentea"
|
||||||
|
},
|
||||||
|
"statuses": {
|
||||||
|
"scheduled": "Programatua",
|
||||||
|
"in_progress": "Abian",
|
||||||
|
"completed": "Osatua",
|
||||||
|
"on_hold": "Zain",
|
||||||
|
"cancelled": "Bertan behera utzita"
|
||||||
|
},
|
||||||
|
"stages": {
|
||||||
|
"mixing": "Nahasketa",
|
||||||
|
"proofing": "Hartzidura",
|
||||||
|
"shaping": "Moldatzea",
|
||||||
|
"baking": "Labea",
|
||||||
|
"cooling": "Hoztea",
|
||||||
|
"packaging": "Ontziratzea",
|
||||||
|
"finishing": "Amaiera"
|
||||||
|
},
|
||||||
|
"summary": {
|
||||||
|
"title": "Ekoizpen Lotearen Laburpena",
|
||||||
|
"product": "Produktua:",
|
||||||
|
"quantity": "Kantitatea:",
|
||||||
|
"recipe": "Errezeta:",
|
||||||
|
"startTime": "Programatutako Hasiera:",
|
||||||
|
"priority": "Lehentasuna:",
|
||||||
|
"equipment": "Ekipamendua:",
|
||||||
|
"staff": "Langileak:"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"messages": {
|
||||||
|
"errorObtainingTenantInfo": "Ezin izan da tenant informazioa lortu",
|
||||||
|
"errorLoadingProducts": "Errorea produktuak kargatzean",
|
||||||
|
"errorLoadingRecipes": "Errorea errezetak kargatzean",
|
||||||
|
"productionBatchCreatedSuccessfully": "Ekoizpen lotea ondo sortu da",
|
||||||
|
"errorCreatingProductionBatch": "Errorea ekoizpen lotea sortzean",
|
||||||
|
"selectProduct": "Mesedez hautatu produktu bat",
|
||||||
|
"enterQuantity": "Sartu kantitate baliozkoa"
|
||||||
|
},
|
||||||
|
"validation": {
|
||||||
|
"selectProduct": "Produktu bat hautatu behar duzu",
|
||||||
|
"startTime": "Hasiera data eta ordua zehaztu behar duzu",
|
||||||
|
"duration": "Iraupena 0 minutu baino handiagoa izan behar da",
|
||||||
|
"quantity": "Kantitatea 0 baino handiagoa izan behar da",
|
||||||
|
"endTime": "Amaiera data hasiera data baino geroagokoa izan behar da"
|
||||||
|
},
|
||||||
|
"stepTitles": {
|
||||||
|
"productRecipe": "Produktua eta Errezeta",
|
||||||
|
"planning": "Programazioa",
|
||||||
|
"priorityResources": "Lehentasuna eta Baliabideak",
|
||||||
|
"review": "Berrikusi eta Berretsi"
|
||||||
|
},
|
||||||
|
"review": {
|
||||||
|
"unspecified": "Zehaztu gabe",
|
||||||
|
"noRecipe": "Errezeta zehatzik gabe"
|
||||||
|
},
|
||||||
|
"productRecipe": {
|
||||||
|
"fields": {
|
||||||
|
"productLabel": "Ekoitzi Beharreko Produktua *",
|
||||||
|
"batchNumberAutoPlaceholder": "Automatikoki sortuko da hutsik uzten bada"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"equipmentStaff": {
|
||||||
|
"fields": {
|
||||||
|
"equipmentExample": "Adib: oven-1, mixer-2",
|
||||||
|
"staffHelp": "Bereizi izenak komaz (Adib: Jon Gartziak, Maria Lopez)",
|
||||||
|
"orderId": "Lotutako eskaeraren IDa",
|
||||||
|
"forecastId": "Lotutako aurreikuspenaren IDa"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"additionalDetails": {
|
||||||
|
"advancedOptions": {
|
||||||
|
"title": "Aukera Aurreratuak",
|
||||||
|
"description": "Ekoizpen informazio gehigarria"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"purchaseOrder": {
|
||||||
|
"orderItems": {
|
||||||
|
"titleHeader": "Erosteko Produktuak",
|
||||||
|
"addProduct": "Gehitu Produktua",
|
||||||
|
"clickToBeginProduct": "Sakatu 'Gehitu Produktua' hasteko",
|
||||||
|
"units": {
|
||||||
|
"kg": "Kilogramoak",
|
||||||
|
"g": "Gramoak",
|
||||||
|
"l": "Litroak",
|
||||||
|
"ml": "Mililitroak",
|
||||||
|
"units": "Unitateak",
|
||||||
|
"boxes": "Kutxak",
|
||||||
|
"bags": "Poltsak"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"deliveryDetails": {
|
||||||
|
"titleHeader": "Eskaeraren Xehetasunak",
|
||||||
|
"subtitleHeader": "Konfiguratu entrega data eta lehentasuna",
|
||||||
|
"fields": {
|
||||||
|
"instructionsPlaceholder": "Hornitzailearentzako jarraibide bereziak..."
|
||||||
|
},
|
||||||
|
"priorities": {
|
||||||
|
"critical": "Kritikoa"
|
||||||
|
},
|
||||||
|
"advancedOptions": {
|
||||||
|
"title": "Aukera Aurreratuak",
|
||||||
|
"description": "Finantza informazio gehigarria"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"validation": {
|
||||||
|
"selectSupplier": "Hornitzaile bat hautatu behar duzu",
|
||||||
|
"addProduct": "Gutxienez produktu bat gehitu behar duzu",
|
||||||
|
"invalidItems": "Produktu guztiek osagaia, 0 baino kantitate handiagoa eta 0 baino prezio handiagoa eduki behar dute",
|
||||||
|
"deliveryDate": "Entrega data bat zehaztu behar duzu"
|
||||||
|
},
|
||||||
|
"stepTitles": {
|
||||||
|
"selectSupplier": "Hornitzailea Hautatu",
|
||||||
|
"addProducts": "Gehitu Produktuak",
|
||||||
|
"orderDetails": "Eskaeraren Xehetasunak",
|
||||||
|
"reviewConfirm": "Berrikusi eta Berretsi"
|
||||||
|
},
|
||||||
|
"review": {
|
||||||
|
"unspecified": "Zehaztu gabe",
|
||||||
|
"noName": "Izenik gabeko produktua"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"itemTypeSelector": {
|
||||||
|
"searchPlaceholder": "Bilatu izenaren edo kategoriaren arabera...",
|
||||||
|
"noResults": "Ez da emaitzarik aurkitu",
|
||||||
|
"resultSingular": "emaitza",
|
||||||
|
"resultPlural": "emaitzak",
|
||||||
|
"categories": {
|
||||||
|
"all": "Guztiak",
|
||||||
|
"daily": "Egunerokoa",
|
||||||
|
"common": "Arrunta",
|
||||||
|
"setup": "Konfigurazioa"
|
||||||
|
},
|
||||||
|
"badges": {
|
||||||
|
"mostCommon": "⭐ Arrunten",
|
||||||
|
"daily": "Egunerokoa",
|
||||||
|
"common": "Arrunta",
|
||||||
|
"setup": "Konfigurazioa"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user