feat(warehouse): add inventory management features
- Implemented getWarehouseById method in warehouseService to fetch warehouse details by ID. - Added new types for warehouse inventory management in warehouse.d.ts and warehouse.inventory.d.ts. - Created WarehouseAddInventory.vue component for handling inventory entries with serial number management. - Developed inventoryWarehouseServices for adding inventory through API. - Updated router to include the new inventory management component. - Added Docker configuration files for production deployment. - Created Nginx configuration for serving the application. - Added .dockerignore and .env.production for environment-specific settings.
This commit is contained in:
parent
71454dda61
commit
d1c203cd0e
50
.dockerignore
Normal file
50
.dockerignore
Normal file
@ -0,0 +1,50 @@
|
||||
# Dependencies
|
||||
node_modules
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
pnpm-debug.log*
|
||||
|
||||
# Build outputs
|
||||
dist
|
||||
dist-ssr
|
||||
*.local
|
||||
|
||||
# Git
|
||||
.git
|
||||
.gitignore
|
||||
|
||||
# Docker
|
||||
Dockerfile
|
||||
docker-compose.yml
|
||||
.dockerignore
|
||||
|
||||
# IDE
|
||||
.vscode
|
||||
.idea
|
||||
*.sw?
|
||||
*.suo
|
||||
*.ntvs*
|
||||
*.njsproj
|
||||
*.sln
|
||||
|
||||
# OS
|
||||
.DS_Store
|
||||
Thumbs.db
|
||||
|
||||
# Environment
|
||||
.env
|
||||
.env.local
|
||||
.env.*.local
|
||||
|
||||
# Documentation
|
||||
README.md
|
||||
*.md
|
||||
|
||||
# Testing
|
||||
coverage
|
||||
.nyc_output
|
||||
|
||||
# Logs
|
||||
logs
|
||||
*.log
|
||||
@ -3,6 +3,7 @@ VITE_API_URL=http://localhost:3000/api
|
||||
|
||||
# Environment
|
||||
VITE_APP_ENV=development
|
||||
APP_PORT=3000
|
||||
|
||||
# App Configuration
|
||||
VITE_APP_NAME=GOLS Control
|
||||
|
||||
9
.env.production
Normal file
9
.env.production
Normal file
@ -0,0 +1,9 @@
|
||||
# API Configuration (Production)
|
||||
VITE_API_URL=https://api.golscontrol.com/api
|
||||
|
||||
# Environment
|
||||
VITE_APP_ENV=production
|
||||
|
||||
# App Configuration
|
||||
VITE_APP_NAME=GOLS Control
|
||||
VITE_APP_VERSION=1.0.0
|
||||
6
.gitignore
vendored
6
.gitignore
vendored
@ -23,4 +23,10 @@ dist-ssr
|
||||
*.sln
|
||||
*.sw?
|
||||
|
||||
# Environment files
|
||||
.env
|
||||
.env.local
|
||||
.env.*.local
|
||||
|
||||
# Docker
|
||||
docker-compose.override.yml
|
||||
|
||||
33
Dockerfile
Normal file
33
Dockerfile
Normal file
@ -0,0 +1,33 @@
|
||||
# Stage 1: Build the application
|
||||
FROM node:22-alpine AS builder
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
# Copy package files first to leverage Docker cache
|
||||
COPY package*.json ./
|
||||
|
||||
# Install dependencies
|
||||
RUN npm ci
|
||||
|
||||
# Copy the rest of the application code
|
||||
COPY . .
|
||||
|
||||
# Build the application
|
||||
ARG VITE_API_URL
|
||||
ENV VITE_API_URL=$VITE_API_URL
|
||||
RUN npm run build
|
||||
|
||||
# Stage 2: Serve the application with Nginx
|
||||
FROM nginx:alpine AS production
|
||||
|
||||
# Copy the built artifacts from the builder stage
|
||||
COPY --from=builder /app/dist /usr/share/nginx/html
|
||||
|
||||
# Copy custom Nginx configuration
|
||||
COPY nginx.conf /etc/nginx/conf.d/default.conf
|
||||
|
||||
# Expose port 80
|
||||
EXPOSE 80
|
||||
|
||||
# Start Nginx
|
||||
CMD ["nginx", "-g", "daemon off;"]
|
||||
117
README.md
117
README.md
@ -1,115 +1,20 @@
|
||||
# Estructura del Proyecto - GOLS Control Frontend
|
||||
|
||||
## 📁 Estructura de Carpetas
|
||||
## Docker (Producción)
|
||||
|
||||
```
|
||||
src/
|
||||
├── App.vue # Componente raíz (cambia entre demos)
|
||||
├── main.ts # Punto de entrada
|
||||
├── MainLayout.vue # Layout principal con Sidebar + TopBar
|
||||
├── ColorDemo.vue # Demo de personalización de colores
|
||||
│
|
||||
├── assets/ # Recursos estáticos
|
||||
│ └── styles/
|
||||
│ └── main.css # Estilos globales y variables CSS
|
||||
│
|
||||
├── components/ # Componentes globales
|
||||
│ ├── layout/ # Componentes de layout
|
||||
│ │ ├── TopBar.vue # Barra superior
|
||||
│ │ ├── Sidebar.vue # Menú lateral
|
||||
│ │ └── AppConfig.vue # Panel de configuración de colores
|
||||
│ │
|
||||
│ ├── shared/ # Componentes compartidos
|
||||
│ │ └── KpiCard.vue # Tarjeta de KPI
|
||||
│ │
|
||||
│ ├── ui/ # Componentes UI reutilizables
|
||||
│ │ └── (vacío por ahora)
|
||||
│ │
|
||||
│ └── Holos/ # (Legacy - se puede eliminar)
|
||||
│ ├── AppTopbar.vue
|
||||
│ └── AppConfig.vue
|
||||
│
|
||||
├── composables/ # Composables globales
|
||||
│ └── useLayout.ts # Gestión de tema y colores
|
||||
│
|
||||
├── modules/ # Módulos de negocio
|
||||
│ └── warehouse/ # Módulo de almacén
|
||||
│ ├── components/
|
||||
│ │ ├── WarehouseDashboard.vue # Dashboard principal
|
||||
│ │ └── InventoryTable.vue # Tabla de inventario
|
||||
│ │
|
||||
│ ├── composables/
|
||||
│ │ └── useWarehouse.ts # Lógica de negocio
|
||||
│ │
|
||||
│ ├── services/
|
||||
│ │ └── warehouseService.ts # API del módulo
|
||||
│ │
|
||||
│ └── types/
|
||||
│ └── warehouse.d.ts # Tipos TypeScript
|
||||
│
|
||||
├── services/ # Servicios globales
|
||||
│ └── api.ts # Cliente HTTP base
|
||||
│
|
||||
└── types/ # Tipos globales
|
||||
└── global.d.ts # Definiciones TypeScript globales
|
||||
```bash
|
||||
# 1. Configurar
|
||||
cp .env.production .env
|
||||
|
||||
# 2. Levantar
|
||||
docker-compose up -d
|
||||
|
||||
# 3. Verificar en http://localhost
|
||||
```
|
||||
|
||||
## 🎯 Convenciones
|
||||
Ver [DOCKER.md](DOCKER.md) para más detalles.
|
||||
|
||||
### Módulos
|
||||
Cada módulo sigue la estructura:
|
||||
```
|
||||
modules/[nombre-modulo]/
|
||||
├── components/ # Componentes específicos del módulo
|
||||
├── composables/ # Lógica de negocio del módulo
|
||||
├── services/ # API calls del módulo
|
||||
└── types/ # Types específicos del módulo
|
||||
```
|
||||
|
||||
### Componentes
|
||||
- **Layout**: Componentes de estructura (TopBar, Sidebar, etc.)
|
||||
- **Shared**: Componentes reutilizables entre módulos
|
||||
- **UI**: Componentes de interfaz básicos
|
||||
|
||||
### Composables
|
||||
- Prefijo `use` (ej: `useWarehouse`, `useLayout`)
|
||||
- Encapsulan lógica reutilizable
|
||||
- Retornan estado reactivo y métodos
|
||||
|
||||
### Services
|
||||
- Manejan comunicaciones HTTP
|
||||
- Retornan Promises
|
||||
- Usan el cliente `api.ts` base
|
||||
|
||||
## 🚀 Uso
|
||||
|
||||
### Ver Demo de Colores
|
||||
```ts
|
||||
// En App.vue
|
||||
const currentView = ref<'color' | 'warehouse'>('color');
|
||||
```
|
||||
|
||||
### Ver Dashboard de Almacén
|
||||
```ts
|
||||
// En App.vue
|
||||
const currentView = ref<'color' | 'warehouse'>('warehouse');
|
||||
```
|
||||
|
||||
## 📦 Crear un Nuevo Módulo
|
||||
|
||||
1. Crear estructura en `src/modules/[nombre]/`
|
||||
2. Definir tipos en `types/[nombre].d.ts`
|
||||
3. Crear servicio en `services/[nombre]Service.ts`
|
||||
4. Crear composable en `composables/use[Nombre].ts`
|
||||
5. Crear componentes en `components/`
|
||||
|
||||
## 🎨 Sistema de Colores
|
||||
|
||||
- Color primario: **Azul** (fijo)
|
||||
- Colores de superficie: **5 opciones** (slate, gray, zinc, neutral, stone)
|
||||
- Modo oscuro: Activable desde TopBar
|
||||
|
||||
## 📝 Notas
|
||||
## Notas
|
||||
|
||||
- Los componentes de PrimeVue se auto-importan
|
||||
- TypeScript configurado con strict mode
|
||||
|
||||
17
docker-compose.yml
Normal file
17
docker-compose.yml
Normal file
@ -0,0 +1,17 @@
|
||||
services:
|
||||
controls-front:
|
||||
build:
|
||||
context: .
|
||||
dockerfile: Dockerfile
|
||||
args:
|
||||
VITE_API_URL: ${VITE_API_URL}
|
||||
container_name: controls-front-prod
|
||||
ports:
|
||||
- "${APP_PORT}:80"
|
||||
networks:
|
||||
- controls-network
|
||||
restart: unless-stopped
|
||||
|
||||
networks:
|
||||
controls-network:
|
||||
driver: bridge
|
||||
23
nginx.conf
Normal file
23
nginx.conf
Normal file
@ -0,0 +1,23 @@
|
||||
server {
|
||||
listen 80;
|
||||
server_name 127.0.0.1;
|
||||
|
||||
root /usr/share/nginx/html;
|
||||
index index.html;
|
||||
|
||||
|
||||
location / {
|
||||
try_files $uri $uri/ /index.html;
|
||||
}
|
||||
# Optional: Cache static assets for better performance
|
||||
location ~* \.(?:ico|css|js|gif|jpe?g|png|woff2?|eot|ttf|svg|otf)$ {
|
||||
expires 6M;
|
||||
access_log off;
|
||||
add_header Cache-Control "public";
|
||||
}
|
||||
|
||||
error_page 500 502 503 504 /50x.html;
|
||||
location = /50x.html {
|
||||
root /usr/share/nginx/html;
|
||||
}
|
||||
}
|
||||
@ -10,7 +10,7 @@ interface Props {
|
||||
color?: 'primary' | 'success' | 'warning' | 'danger' | 'info';
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
withDefaults(defineProps<Props>(), {
|
||||
color: 'primary'
|
||||
});
|
||||
|
||||
|
||||
@ -19,7 +19,6 @@ import ProgressSpinner from 'primevue/progressspinner';
|
||||
import { useUnitOfMeasureStore } from '../stores/unitOfMeasureStore';
|
||||
import { unitTypesService } from '../services/unitsTypes';
|
||||
import type { UnitOfMeasure, CreateUnitOfMeasureData } from '../types/unitOfMeasure';
|
||||
import type { UnitType } from '../types/unitTypes';
|
||||
|
||||
const router = useRouter();
|
||||
const toast = useToast();
|
||||
|
||||
@ -15,11 +15,11 @@ export const useSupplierStore = defineStore('supplier', () => {
|
||||
const response = await supplierServices.getSuppliers(false);
|
||||
// Si la respuesta es paginada, usar response.data; si es lista, usar response.suppliers o response directamente
|
||||
if (Array.isArray(response)) {
|
||||
suppliers.value = response;
|
||||
suppliers.value = response as Supplier[];
|
||||
} else if ('suppliers' in response) {
|
||||
suppliers.value = response.suppliers;
|
||||
suppliers.value = (response as any).suppliers;
|
||||
} else if ('data' in response) {
|
||||
suppliers.value = response.data;
|
||||
suppliers.value = (response as any).data;
|
||||
} else {
|
||||
suppliers.value = [];
|
||||
}
|
||||
|
||||
@ -1,14 +1,15 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, computed, watch } from 'vue';
|
||||
import { productService } from '../../products/services/productService';
|
||||
import type { Product } from '../../products/types/product';
|
||||
|
||||
const visible = defineModel<boolean>('visible');
|
||||
const emit = defineEmits(['confirm', 'productSelected']);
|
||||
|
||||
const search = ref('');
|
||||
const loading = ref(false);
|
||||
const products = ref([]);
|
||||
const selectedProduct = ref(null);
|
||||
const products = ref<Product[]>([]);
|
||||
const selectedProduct = ref<Product | null>(null);
|
||||
const productAttributes = ref<Record<string, string>>({});
|
||||
const quantity = ref(1);
|
||||
|
||||
@ -42,7 +43,7 @@ const filteredProducts = computed(() => products.value);
|
||||
const totalProducts = computed(() => products.value.length);
|
||||
const totalFiltered = computed(() => products.value.length);
|
||||
|
||||
function selectProduct(product) {
|
||||
function selectProduct(product: Product) {
|
||||
selectedProduct.value = product;
|
||||
// Inicializar atributos dinámicos del producto
|
||||
const attrs: Record<string, string> = {};
|
||||
|
||||
@ -227,52 +227,11 @@ import Column from 'primevue/column';
|
||||
import Breadcrumb from 'primevue/breadcrumb';
|
||||
import { useRouter } from 'vue-router';
|
||||
import { purchaseServices } from '../services/purchaseServices';
|
||||
import type { PurchaseDetailResponse, PurchaseDetailData, PurchaseItem } from '../types/purchases';
|
||||
import type { PurchaseDetailResponse, PurchaseDetailData } from '../types/purchases';
|
||||
import { useRoute } from 'vue-router';
|
||||
import { PURCHASE_STATUS_MAP } from '../types/purchases.d';
|
||||
|
||||
const items = ref([
|
||||
{
|
||||
product: 'Advanced Logic Controller v4',
|
||||
sku: 'ALC-V4-99',
|
||||
qty: 10,
|
||||
unitPrice: 450,
|
||||
tax: 8.5,
|
||||
lineTotal: 4882.5,
|
||||
},
|
||||
{
|
||||
product: 'Industrial Power Supply 12V',
|
||||
sku: 'PWR-IND-12',
|
||||
qty: 25,
|
||||
unitPrice: 85,
|
||||
tax: 8.5,
|
||||
lineTotal: 2305.63,
|
||||
},
|
||||
{
|
||||
product: 'Ethernet Patch Cable Cat6 (2m)',
|
||||
sku: 'CAB-CAT6-2M',
|
||||
qty: 100,
|
||||
unitPrice: 4.5,
|
||||
tax: 0,
|
||||
lineTotal: 450,
|
||||
},
|
||||
{
|
||||
product: 'Sensor Mount Bracket A',
|
||||
sku: 'BRA-SENS-A',
|
||||
qty: 50,
|
||||
unitPrice: 12,
|
||||
tax: 8.5,
|
||||
lineTotal: 651,
|
||||
},
|
||||
]);
|
||||
|
||||
const summary = ref({
|
||||
subtotal: 7625,
|
||||
tax: 664.13,
|
||||
shipping: 0,
|
||||
total: 8289.13,
|
||||
});
|
||||
|
||||
const route = useRoute();
|
||||
const router = useRouter();
|
||||
const breadcrumbItems = [
|
||||
{ label: 'Inicio', route: '/' },
|
||||
@ -281,8 +240,6 @@ const breadcrumbItems = [
|
||||
{ label: 'Detalle' }
|
||||
];
|
||||
const home = { icon: 'pi pi-home', route: '/' };
|
||||
|
||||
const route = useRoute();
|
||||
const purchase = ref<PurchaseDetailData | null>(null);
|
||||
const loading = ref(true);
|
||||
|
||||
|
||||
@ -127,6 +127,10 @@ async function convertToPurchase(id: string | number) {
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function goToAddInventory(id: string | number) {
|
||||
router.push({ name: 'WarehouseAddInventory', query: { purchaseId: id } });
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@ -179,6 +183,8 @@ async function convertToPurchase(id: string | number) {
|
||||
@click="rejectPurchase(data.id)" v-tooltip.top="'Rechazar'" />
|
||||
<Button v-if="data.status === '2'" icon="pi pi-shopping-cart" severity="success" text rounded
|
||||
@click="convertToPurchase(data.id)" v-tooltip.top="'Convertir en Compra'" />
|
||||
<Button v-if="data.status === '3'" icon="pi pi-warehouse" severity="info" text rounded
|
||||
@click="goToAddInventory(data.id)" v-tooltip.top="'Cargar Inventario'" />
|
||||
<Button icon="pi pi-eye" severity="info" text rounded
|
||||
@click="goToDetails(data.id)" v-tooltip.top="'Ver Detalles'" />
|
||||
</div>
|
||||
|
||||
20
src/modules/purchases/types/purchases.d.ts
vendored
20
src/modules/purchases/types/purchases.d.ts
vendored
@ -1,5 +1,22 @@
|
||||
import type { Supplier } from '../../catalog/types/suppliers';
|
||||
|
||||
export interface Product {
|
||||
id: number;
|
||||
code: string;
|
||||
sku: string;
|
||||
name: string;
|
||||
barcode: string;
|
||||
description: string;
|
||||
unit_of_measure_id: number;
|
||||
suggested_sale_price: number;
|
||||
attributes: Record<string, any>;
|
||||
is_active: boolean;
|
||||
is_serial: boolean;
|
||||
created_at: string;
|
||||
updated_at: string;
|
||||
deleted_at: string | null;
|
||||
}
|
||||
|
||||
export interface Purchase {
|
||||
id: number;
|
||||
purchase_number: string;
|
||||
@ -44,11 +61,12 @@ export interface PurchaseItem {
|
||||
quantity: number;
|
||||
unit_price: string;
|
||||
subtotal: string;
|
||||
notes: string;
|
||||
notes: string | null;
|
||||
attributes: Record<string, any>;
|
||||
created_at: string;
|
||||
updated_at: string;
|
||||
deleted_at: string | null;
|
||||
product: Product;
|
||||
}
|
||||
|
||||
export interface PurchaseDetailData extends Purchase {
|
||||
|
||||
@ -261,7 +261,6 @@
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, computed } from 'vue';
|
||||
import { useRouter } from 'vue-router';
|
||||
import { useToast } from 'primevue/usetoast';
|
||||
|
||||
// PrimeVue Components
|
||||
@ -274,11 +273,7 @@ import Column from 'primevue/column';
|
||||
import Dialog from 'primevue/dialog';
|
||||
import InputText from 'primevue/inputtext';
|
||||
import Textarea from 'primevue/textarea';
|
||||
import InputNumber from 'primevue/inputnumber';
|
||||
import Select from 'primevue/select';
|
||||
import Tag from 'primevue/tag';
|
||||
|
||||
const router = useRouter();
|
||||
const toast = useToast();
|
||||
|
||||
// State
|
||||
@ -296,7 +291,7 @@ const breadcrumbItems = ref([
|
||||
]);
|
||||
|
||||
// Form Data
|
||||
const formData = ref({
|
||||
const formData = ref<{id?: number; name: string; description: string; employeeCount: number; color: string; icon: string}>({
|
||||
name: '',
|
||||
description: '',
|
||||
employeeCount: 0,
|
||||
@ -304,29 +299,29 @@ const formData = ref({
|
||||
icon: 'pi pi-building'
|
||||
});
|
||||
|
||||
// Color Options
|
||||
const colorOptions = [
|
||||
{ label: 'Azul', value: '#3b82f6' },
|
||||
{ label: 'Verde', value: '#10b981' },
|
||||
{ label: 'Púrpura', value: '#8b5cf6' },
|
||||
{ label: 'Naranja', value: '#f97316' },
|
||||
{ label: 'Rosa', value: '#ec4899' },
|
||||
{ label: 'Rojo', value: '#ef4444' },
|
||||
{ label: 'Amarillo', value: '#f59e0b' },
|
||||
{ label: 'Índigo', value: '#6366f1' },
|
||||
];
|
||||
// Color Options (for future use)
|
||||
// const colorOptions = [
|
||||
// { label: 'Azul', value: '#3b82f6' },
|
||||
// { label: 'Verde', value: '#10b981' },
|
||||
// { label: 'Púrpura', value: '#8b5cf6' },
|
||||
// { label: 'Naranja', value: '#f97316' },
|
||||
// { label: 'Rosa', value: '#ec4899' },
|
||||
// { label: 'Rojo', value: '#ef4444' },
|
||||
// { label: 'Amarillo', value: '#f59e0b' },
|
||||
// { label: 'Índigo', value: '#6366f1' },
|
||||
// ];
|
||||
|
||||
// Icon Options
|
||||
const iconOptions = [
|
||||
{ label: 'Edificio', value: 'pi pi-building' },
|
||||
{ label: 'Usuarios', value: 'pi pi-users' },
|
||||
{ label: 'Cog', value: 'pi pi-cog' },
|
||||
{ label: 'Herramientas', value: 'pi pi-wrench' },
|
||||
{ label: 'Camión', value: 'pi pi-truck' },
|
||||
{ label: 'Gráfico', value: 'pi pi-chart-line' },
|
||||
{ label: 'Escudo', value: 'pi pi-shield' },
|
||||
{ label: 'Estrella', value: 'pi pi-star' },
|
||||
];
|
||||
// Icon Options (for future use)
|
||||
// const iconOptions = [
|
||||
// { label: 'Edificio', value: 'pi pi-building' },
|
||||
// { label: 'Usuarios', value: 'pi pi-users' },
|
||||
// { label: 'Cog', value: 'pi pi-cog' },
|
||||
// { label: 'Herramientas', value: 'pi pi-wrench' },
|
||||
// { label: 'Camión', value: 'pi pi-truck' },
|
||||
// { label: 'Gráfico', value: 'pi pi-chart-line' },
|
||||
// { label: 'Escudo', value: 'pi pi-shield' },
|
||||
// { label: 'Estrella', value: 'pi pi-star' },
|
||||
// ];
|
||||
|
||||
// Mock Data - Departments
|
||||
const departments = ref([
|
||||
@ -434,7 +429,11 @@ const saveDepartment = () => {
|
||||
// Create new department
|
||||
const newDepartment = {
|
||||
id: Math.max(...departments.value.map(d => d.id)) + 1,
|
||||
...formData.value
|
||||
name: formData.value.name,
|
||||
description: formData.value.description,
|
||||
employeeCount: formData.value.employeeCount,
|
||||
color: formData.value.color,
|
||||
icon: formData.value.icon
|
||||
};
|
||||
departments.value.push(newDepartment);
|
||||
|
||||
@ -448,7 +447,15 @@ const saveDepartment = () => {
|
||||
// Update existing department
|
||||
const index = departments.value.findIndex(d => d.id === formData.value.id);
|
||||
if (index > -1) {
|
||||
departments.value[index] = { ...formData.value };
|
||||
const existingDept = departments.value[index]!;
|
||||
departments.value[index] = {
|
||||
id: existingDept.id,
|
||||
name: formData.value.name,
|
||||
description: formData.value.description,
|
||||
employeeCount: formData.value.employeeCount,
|
||||
color: formData.value.color,
|
||||
icon: formData.value.icon
|
||||
};
|
||||
toast.add({
|
||||
severity: 'success',
|
||||
summary: 'Departamento Actualizado',
|
||||
|
||||
@ -506,7 +506,15 @@ const savePosition = () => {
|
||||
} else {
|
||||
const index = positions.value.findIndex(p => p.id === formData.value.id);
|
||||
if (index > -1) {
|
||||
positions.value[index] = { ...positions.value[index], ...formData.value };
|
||||
const existing = positions.value[index]!;
|
||||
positions.value[index] = {
|
||||
id: existing.id,
|
||||
name: formData.value.name,
|
||||
department: formData.value.department,
|
||||
description: formData.value.description,
|
||||
icon: existing.icon,
|
||||
iconBg: existing.iconBg
|
||||
};
|
||||
toast.add({
|
||||
severity: 'success',
|
||||
summary: 'Puesto Actualizado',
|
||||
|
||||
@ -259,13 +259,13 @@ const toast = useToast();
|
||||
const storeData = ref<Store | null>(null);
|
||||
const loading = ref(false);
|
||||
const isSaving = ref(false);
|
||||
const loadingProducts = ref(false);
|
||||
// const loadingProducts = ref(false);
|
||||
const loadingTerminals = ref(false);
|
||||
|
||||
// Configuration
|
||||
const sourceType = ref('catalog');
|
||||
const selectedWarehouse = ref(null);
|
||||
const productSearchQuery = ref('');
|
||||
// const productSearchQuery = ref('');
|
||||
const terminalSearchQuery = ref('');
|
||||
|
||||
// Breadcrumb
|
||||
@ -286,29 +286,30 @@ const warehouseOptions = ref([
|
||||
{ id: 3, name: 'Almacén Sur' }
|
||||
]);
|
||||
|
||||
const products = ref([
|
||||
{
|
||||
id: 1,
|
||||
sku: 'CAFE-GRN-01',
|
||||
name: 'Café Grano Entero 1kg',
|
||||
category: 'Café',
|
||||
price: 250.00
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
sku: 'ACC-TZA-05',
|
||||
name: 'Taza de Cerámica Blanca',
|
||||
category: 'Accesorios',
|
||||
price: 120.00
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
sku: 'PST-CHC-12',
|
||||
name: 'Pastel de Chocolate',
|
||||
category: 'Repostería',
|
||||
price: 45.00
|
||||
}
|
||||
]);
|
||||
// Mock products data (for future use)
|
||||
// const products = ref([
|
||||
// {
|
||||
// id: 1,
|
||||
// sku: 'CAFE-GRN-01',
|
||||
// name: 'Café Grano Entero 1kg',
|
||||
// category: 'Café',
|
||||
// price: 250.00
|
||||
// },
|
||||
// {
|
||||
// id: 2,
|
||||
// sku: 'ACC-TZA-05',
|
||||
// name: 'Taza de Cerámica Blanca',
|
||||
// category: 'Accesorios',
|
||||
// price: 120.00
|
||||
// },
|
||||
// {
|
||||
// id: 3,
|
||||
// sku: 'PST-CHC-12',
|
||||
// name: 'Pastel de Chocolate',
|
||||
// category: 'Repostería',
|
||||
// price: 45.00
|
||||
// }
|
||||
// ]);
|
||||
|
||||
const terminals = ref([
|
||||
{
|
||||
@ -382,12 +383,13 @@ const formatDate = (dateString: string | undefined) => {
|
||||
});
|
||||
};
|
||||
|
||||
const formatCurrency = (amount: number) => {
|
||||
return new Intl.NumberFormat('es-MX', {
|
||||
style: 'currency',
|
||||
currency: 'MXN'
|
||||
}).format(amount);
|
||||
};
|
||||
// Unused utility (for future use)
|
||||
// const formatCurrency = (amount: number) => {
|
||||
// return new Intl.NumberFormat('es-MX', {
|
||||
// style: 'currency',
|
||||
// currency: 'MXN'
|
||||
// }).format(amount);
|
||||
// };
|
||||
|
||||
const getTerminalStatusLabel = (status: string) => {
|
||||
const labels = {
|
||||
@ -443,23 +445,24 @@ const saveConfiguration = async () => {
|
||||
}
|
||||
};
|
||||
|
||||
const addProduct = () => {
|
||||
toast.add({
|
||||
severity: 'info',
|
||||
summary: 'Añadir Producto',
|
||||
detail: 'Funcionalidad próximamente',
|
||||
life: 3000
|
||||
});
|
||||
};
|
||||
// Unused product methods (for future use)
|
||||
// const addProduct = () => {
|
||||
// toast.add({
|
||||
// severity: 'info',
|
||||
// summary: 'Añadir Producto',
|
||||
// detail: 'Funcionalidad próximamente',
|
||||
// life: 3000
|
||||
// });
|
||||
// };
|
||||
|
||||
const removeProduct = (product: any) => {
|
||||
toast.add({
|
||||
severity: 'warn',
|
||||
summary: 'Quitar Producto',
|
||||
detail: `¿Quitar ${product.name}?`,
|
||||
life: 3000
|
||||
});
|
||||
};
|
||||
// const removeProduct = (product: any) => {
|
||||
// toast.add({
|
||||
// severity: 'warn',
|
||||
// summary: 'Quitar Producto',
|
||||
// detail: `¿Quitar ${product.name}?`,
|
||||
// life: 3000
|
||||
// });
|
||||
// };
|
||||
|
||||
const addTerminal = () => {
|
||||
toast.add({
|
||||
|
||||
@ -83,8 +83,7 @@ import InputSwitch from 'primevue/inputswitch';
|
||||
import Toast from 'primevue/toast';
|
||||
import { useToast } from 'primevue/usetoast';
|
||||
import { permissionsService } from '../services/permissionsService';
|
||||
import { roleService } from '../services/roleService';
|
||||
import type { PermissionType, UpdateRolePermissionsData } from '../types/permissions';
|
||||
import type { PermissionType } from '../types/permissions';
|
||||
import type { Role } from '../types/role';
|
||||
|
||||
const toast = useToast();
|
||||
|
||||
@ -174,7 +174,7 @@ import { useConfirm } from 'primevue/useconfirm';
|
||||
import IconField from 'primevue/iconfield';
|
||||
import InputIcon from 'primevue/inputicon';
|
||||
import { roleService } from '../services/roleService';
|
||||
import type { Role, CreateRoleData, UpdateRoleData } from '../types/role';
|
||||
import type { Role, CreateRoleData } from '../types/role';
|
||||
|
||||
const toast = useToast();
|
||||
const confirm = useConfirm();
|
||||
|
||||
@ -1,12 +1,9 @@
|
||||
import api from "../../../services/api"
|
||||
import type {
|
||||
Role,
|
||||
RolePagination,
|
||||
RoleResponse,
|
||||
CreateRoleData,
|
||||
UpdateRoleData,
|
||||
SingleRoleResponse,
|
||||
ApiError
|
||||
SingleRoleResponse
|
||||
} from '../types/role'
|
||||
|
||||
export const roleService = {
|
||||
|
||||
620
src/modules/warehouse/components/WarehouseAddInventory.vue
Normal file
620
src/modules/warehouse/components/WarehouseAddInventory.vue
Normal file
@ -0,0 +1,620 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, computed, onMounted } from 'vue';
|
||||
import { useRoute, useRouter } from 'vue-router';
|
||||
import Card from 'primevue/card';
|
||||
import Button from 'primevue/button';
|
||||
import InputNumber from 'primevue/inputnumber';
|
||||
import Dropdown from 'primevue/dropdown';
|
||||
import InputText from 'primevue/inputtext';
|
||||
import Badge from 'primevue/badge';
|
||||
import Toast from 'primevue/toast';
|
||||
import { useToast } from 'primevue/usetoast';
|
||||
import { purchaseServices } from '../../purchases/services/purchaseServices';
|
||||
import type { PurchaseDetailResponse } from '../../purchases/types/purchases';
|
||||
import { useWarehouseStore } from '../../../stores/warehouseStore';
|
||||
import { inventoryWarehouseServices } from '../services/inventoryWarehouse.services';
|
||||
import type { InventoryProductItem, CreateInventoryRequest } from '../types/warehouse.inventory';
|
||||
|
||||
interface SerialNumber {
|
||||
serial: string;
|
||||
warehouseId: number;
|
||||
}
|
||||
|
||||
interface Product {
|
||||
id: number;
|
||||
name: string;
|
||||
sku: string;
|
||||
category: string;
|
||||
quantityOrdered: number;
|
||||
quantityReceived: number;
|
||||
warehouseId: number | null;
|
||||
requiresSerial: boolean;
|
||||
serialNumbers: SerialNumber[];
|
||||
purchaseCost: number; // Costo de compra del producto
|
||||
attributes?: Record<string, any>; // Atributos opcionales del producto
|
||||
}
|
||||
|
||||
const toast = useToast();
|
||||
const route = useRoute();
|
||||
const router = useRouter();
|
||||
const warehouseStore = useWarehouseStore();
|
||||
|
||||
// Data from API
|
||||
const purchaseData = ref<PurchaseDetailResponse | null>(null);
|
||||
const loading = ref(false);
|
||||
|
||||
// Data
|
||||
const purchaseOrderNumber = ref('ORD-2023-001');
|
||||
const totalItemsPO = ref(12);
|
||||
|
||||
const products = ref<Product[]>([
|
||||
{
|
||||
id: 1,
|
||||
name: 'Cables de Red Cat6 2m',
|
||||
sku: 'NET-C62M-WH',
|
||||
category: 'Accesorios Networking',
|
||||
quantityOrdered: 50,
|
||||
quantityReceived: 0,
|
||||
warehouseId: 1,
|
||||
requiresSerial: false,
|
||||
serialNumbers: [],
|
||||
purchaseCost: 0,
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
name: 'MacBook Pro M2 14"',
|
||||
sku: 'LAP-MBP14-M2',
|
||||
category: 'Equipos de Cómputo',
|
||||
quantityOrdered: 5,
|
||||
quantityReceived: 1,
|
||||
warehouseId: null,
|
||||
requiresSerial: true,
|
||||
serialNumbers: [
|
||||
{ serial: 'SN-LAP-M2-00192', warehouseId: 1 },
|
||||
],
|
||||
purchaseCost: 0,
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
name: 'Impresora Zebra ZT411',
|
||||
sku: 'PRN-ZB411-IND',
|
||||
category: 'Hardware Almacén',
|
||||
quantityOrdered: 2,
|
||||
quantityReceived: 0,
|
||||
warehouseId: null,
|
||||
requiresSerial: true,
|
||||
serialNumbers: [],
|
||||
purchaseCost: 0,
|
||||
},
|
||||
]);
|
||||
|
||||
const expandedRows = ref<any[]>([]);
|
||||
const newSerialNumber = ref('');
|
||||
const newSerialWarehouse = ref<number>(1);
|
||||
|
||||
const totalReceived = computed(() => {
|
||||
return products.value.reduce((sum, p) => sum + p.quantityReceived, 0);
|
||||
});
|
||||
|
||||
const warehouseSummary = computed(() => {
|
||||
const summary: Record<number, { name: string; count: number }> = {};
|
||||
|
||||
warehouseStore.warehouses.forEach(w => {
|
||||
summary[w.id] = { name: w.name, count: 0 };
|
||||
});
|
||||
|
||||
products.value.forEach(product => {
|
||||
if (product.requiresSerial) {
|
||||
product.serialNumbers.forEach(sn => {
|
||||
const summaryItem = summary[sn.warehouseId];
|
||||
if (summaryItem) {
|
||||
summaryItem.count++;
|
||||
}
|
||||
});
|
||||
} else if (product.warehouseId && product.quantityReceived > 0) {
|
||||
const summaryItem = summary[product.warehouseId];
|
||||
if (summaryItem) {
|
||||
summaryItem.count += product.quantityReceived;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return Object.values(summary);
|
||||
});
|
||||
|
||||
const isFormValid = computed(() => {
|
||||
return products.value.every(product => {
|
||||
if (product.requiresSerial) {
|
||||
return product.serialNumbers.length === product.quantityOrdered;
|
||||
} else {
|
||||
return product.warehouseId !== null;
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
function toggleRow(product: Product) {
|
||||
const index = expandedRows.value.findIndex(p => p.id === product.id);
|
||||
if (index === -1) {
|
||||
expandedRows.value.push(product);
|
||||
} else {
|
||||
expandedRows.value.splice(index, 1);
|
||||
}
|
||||
}
|
||||
|
||||
function isRowExpanded(product: Product) {
|
||||
return expandedRows.value.some(p => p.id === product.id);
|
||||
}
|
||||
|
||||
function addSerialNumber(product: Product) {
|
||||
if (!newSerialNumber.value) {
|
||||
toast.add({ severity: 'warn', summary: 'Campo Requerido', detail: 'Por favor ingrese un número de serie', life: 3000 });
|
||||
return;
|
||||
}
|
||||
|
||||
if (product.serialNumbers.length >= product.quantityOrdered) {
|
||||
toast.add({ severity: 'warn', summary: 'Límite Alcanzado', detail: `Ya se registraron todos los números de serie para este producto`, life: 3000 });
|
||||
return;
|
||||
}
|
||||
|
||||
product.serialNumbers.push({
|
||||
serial: newSerialNumber.value,
|
||||
warehouseId: newSerialWarehouse.value,
|
||||
});
|
||||
|
||||
product.quantityReceived = product.serialNumbers.length;
|
||||
newSerialNumber.value = '';
|
||||
|
||||
toast.add({ severity: 'success', summary: 'Serie Registrada', detail: 'Número de serie agregado exitosamente', life: 2000 });
|
||||
}
|
||||
|
||||
function removeSerialNumber(product: Product, index: number) {
|
||||
product.serialNumbers.splice(index, 1);
|
||||
product.quantityReceived = product.serialNumbers.length;
|
||||
}
|
||||
|
||||
// Unused utility (for future use)
|
||||
// function getWarehouseName(warehouseId: number): string {
|
||||
// return warehouseStore.warehouses.find(w => w.id === warehouseId)?.name || '';
|
||||
// }
|
||||
|
||||
async function confirmReceipt() {
|
||||
if (!isFormValid.value) {
|
||||
toast.add({ severity: 'error', summary: 'Validación', detail: 'Por favor complete todos los campos requeridos', life: 3000 });
|
||||
return;
|
||||
}
|
||||
|
||||
const purchaseId = route.query.purchaseId;
|
||||
if (!purchaseId) {
|
||||
toast.add({ severity: 'error', summary: 'Error', detail: 'ID de compra no válido', life: 3000 });
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
loading.value = true;
|
||||
|
||||
// Transformar productos a formato de API
|
||||
const inventoryItems: InventoryProductItem[] = [];
|
||||
|
||||
products.value.forEach(product => {
|
||||
if (product.requiresSerial) {
|
||||
// Para productos con serial, crear un item por cada número de serie
|
||||
product.serialNumbers.forEach(sn => {
|
||||
inventoryItems.push({
|
||||
product_id: product.id,
|
||||
warehouse_id: sn.warehouseId,
|
||||
purchase_cost: product.purchaseCost,
|
||||
quantity: 1,
|
||||
serial_number: sn.serial,
|
||||
attributes: product.attributes || undefined,
|
||||
});
|
||||
});
|
||||
} else {
|
||||
// Para productos estándar, crear un solo item con la cantidad total
|
||||
if (product.quantityReceived > 0 && product.warehouseId) {
|
||||
inventoryItems.push({
|
||||
product_id: product.id,
|
||||
warehouse_id: product.warehouseId,
|
||||
purchase_cost: product.purchaseCost,
|
||||
quantity: product.quantityReceived,
|
||||
attributes: product.attributes || undefined,
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
const requestData: CreateInventoryRequest = {
|
||||
products: inventoryItems
|
||||
};
|
||||
|
||||
// Enviar al API
|
||||
const response = await inventoryWarehouseServices.addInventory(requestData);
|
||||
|
||||
// Actualizar estado de la compra a "Ingresada a Inventario" (4)
|
||||
await purchaseServices.updatePurchaseStatus(Number(purchaseId), '4');
|
||||
|
||||
toast.add({
|
||||
severity: 'success',
|
||||
summary: 'Inventario Actualizado',
|
||||
detail: response.message,
|
||||
life: 4000
|
||||
});
|
||||
|
||||
// Regresar a la vista de compras
|
||||
setTimeout(() => {
|
||||
router.back();
|
||||
}, 1000);
|
||||
|
||||
} catch (error: any) {
|
||||
console.error('Error al agregar inventario:', error);
|
||||
toast.add({
|
||||
severity: 'error',
|
||||
summary: 'Error',
|
||||
detail: error.response?.data?.message || 'No se pudo agregar el inventario',
|
||||
life: 4000
|
||||
});
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
async function fetchPurchaseDetails() {
|
||||
const purchaseId = route.query.purchaseId;
|
||||
|
||||
if (!purchaseId) {
|
||||
toast.add({
|
||||
severity: 'error',
|
||||
summary: 'Error',
|
||||
detail: 'No se proporcionó un ID de compra válido',
|
||||
life: 3000
|
||||
});
|
||||
router.back();
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
loading.value = true;
|
||||
const response = await purchaseServices.getPurchaseById(Number(purchaseId));
|
||||
purchaseData.value = response;
|
||||
|
||||
// Actualizar datos de la compra
|
||||
purchaseOrderNumber.value = response.data.purchase_number;
|
||||
totalItemsPO.value = response.data.items.length;
|
||||
|
||||
// Mapear items de la compra a productos del componente
|
||||
products.value = response.data.items.map(item => ({
|
||||
id: item.product_id,
|
||||
name: item.product.name,
|
||||
sku: item.product.sku,
|
||||
category: item.product.description || 'Sin categoría',
|
||||
quantityOrdered: item.quantity,
|
||||
quantityReceived: 0,
|
||||
warehouseId: null,
|
||||
requiresSerial: item.product.is_serial, // Determina si requiere serial basado en el producto
|
||||
serialNumbers: [],
|
||||
purchaseCost: parseFloat(item.subtotal),
|
||||
attributes: item.product.attributes || null,
|
||||
}));
|
||||
|
||||
} catch (error) {
|
||||
console.error('Error al cargar los detalles de la compra:', error);
|
||||
toast.add({
|
||||
severity: 'error',
|
||||
summary: 'Error',
|
||||
detail: 'No se pudieron cargar los detalles de la compra',
|
||||
life: 3000
|
||||
});
|
||||
router.back();
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(async () => {
|
||||
await warehouseStore.fetchWarehouses();
|
||||
// Establecer el primer almacén activo como predeterminado
|
||||
if (warehouseStore.activeWarehouses.length > 0) {
|
||||
newSerialWarehouse.value = warehouseStore.activeWarehouses[0]?.id || 1;
|
||||
}
|
||||
await fetchPurchaseDetails();
|
||||
});
|
||||
|
||||
function cancel() {
|
||||
// Lógica para cancelar y regresar
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="space-y-6">
|
||||
<Toast />
|
||||
|
||||
<!-- Loading State -->
|
||||
<Card v-if="loading">
|
||||
<template #content>
|
||||
<div class="flex items-center justify-center py-8">
|
||||
<i class="pi pi-spinner pi-spin text-4xl text-primary"></i>
|
||||
<span class="ml-3 text-lg">Cargando detalles de la compra...</span>
|
||||
</div>
|
||||
</template>
|
||||
</Card>
|
||||
|
||||
<template v-else>
|
||||
<!-- Page Header -->
|
||||
<div class="flex flex-col md:flex-row md:items-end justify-between gap-4">
|
||||
<div>
|
||||
<h2 class="text-3xl font-black text-slate-900 tracking-tight">Registrar Entrada de Mercancía</h2>
|
||||
<p class="text-slate-500 mt-1 flex items-center gap-2">
|
||||
<i class="pi pi-receipt text-base"></i>
|
||||
No. Orden #{{ purchaseOrderNumber }} |
|
||||
<span class="text-primary font-semibold">Distribución Multi-Almacén</span>
|
||||
</p>
|
||||
</div>
|
||||
<div class="flex items-center gap-3">
|
||||
<Card class="shadow-sm">
|
||||
<template #content>
|
||||
<div class="flex gap-4 px-2">
|
||||
<div class="text-center">
|
||||
<p class="text-[10px] text-slate-500 uppercase tracking-wider font-bold">Total Items PO</p>
|
||||
<p class="text-lg font-black text-slate-900 leading-tight">{{ totalItemsPO }}</p>
|
||||
</div>
|
||||
<div class="w-px bg-slate-200 h-8 self-center"></div>
|
||||
<div class="text-center">
|
||||
<p class="text-[10px] text-slate-500 uppercase tracking-wider font-bold">Recibidos</p>
|
||||
<p class="text-lg font-black text-primary leading-tight">{{ totalReceived }}</p>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</Card>
|
||||
<Button icon="pi pi-eye" label="Ver Detalles" severity="secondary" outlined />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Products Table -->
|
||||
<Card>
|
||||
<template #header>
|
||||
<div class="px-6 py-4 border-b border-slate-200 bg-slate-50/50 flex justify-between items-center">
|
||||
<h3 class="font-bold text-slate-900">Productos de la Orden</h3>
|
||||
<span class="text-xs text-slate-500 flex items-center gap-1">
|
||||
<i class="pi pi-info-circle text-sm"></i>
|
||||
Seleccione el almacén de destino por cada producto
|
||||
</span>
|
||||
</div>
|
||||
</template>
|
||||
<template #content>
|
||||
<div class="overflow-x-auto -m-6">
|
||||
<table class="w-full border-collapse">
|
||||
<thead>
|
||||
<tr class="text-left text-xs font-bold text-slate-500 uppercase tracking-wider bg-slate-50">
|
||||
<th class="px-6 py-4" style="width: 35%;">Producto</th>
|
||||
<th class="px-6 py-4" style="width: 12%;">SKU / Ref</th>
|
||||
<th class="px-6 py-4 text-center" style="width: 8%;">Cant. PO</th>
|
||||
<th class="px-6 py-4 text-center" style="width: 10%;">Recibida</th>
|
||||
<th class="px-6 py-4" style="width: 20%;">Almacén de Destino</th>
|
||||
<th class="px-6 py-4 text-center" style="width: 15%;">Acciones</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="divide-y divide-slate-200">
|
||||
<template v-for="product in products" :key="product.id">
|
||||
<tr :class="[
|
||||
'hover:bg-slate-50 transition-colors',
|
||||
isRowExpanded(product) ? 'bg-primary/[0.02]' : ''
|
||||
]">
|
||||
<td class="px-6 py-4" :class="{ 'border-l-4 border-primary': isRowExpanded(product) }">
|
||||
<div class="flex items-center gap-3">
|
||||
<div class="size-10 bg-slate-100 rounded-lg flex items-center justify-center"
|
||||
:class="{ 'bg-primary/10 text-primary': isRowExpanded(product) }">
|
||||
<i :class="[
|
||||
product.requiresSerial ? 'pi-desktop' : 'pi-box',
|
||||
'pi text-xl'
|
||||
]"></i>
|
||||
</div>
|
||||
<div class="flex flex-col">
|
||||
<span class="text-sm font-semibold text-slate-900">{{ product.name }}</span>
|
||||
<span class="text-xs text-slate-500">{{ product.category }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
<td class="px-6 py-4 text-sm text-slate-600">{{ product.sku }}</td>
|
||||
<td class="px-6 py-4 text-center text-sm font-bold text-slate-900">{{ product.quantityOrdered }}</td>
|
||||
<td class="px-6 py-4 text-center">
|
||||
<InputNumber v-if="!product.requiresSerial" v-model="product.quantityReceived"
|
||||
:max="product.quantityOrdered" :min="0"
|
||||
showButtons
|
||||
buttonLayout="horizontal"
|
||||
:step="1"
|
||||
class="w-full max-w-[120px] mx-auto"
|
||||
:inputStyle="{ textAlign: 'center', fontWeight: 'bold', fontSize: '0.875rem', width: '60px' }" />
|
||||
<span v-else class="text-sm font-black text-primary">
|
||||
{{ product.quantityReceived }} / {{ product.quantityOrdered }}
|
||||
</span>
|
||||
</td>
|
||||
<td class="px-6 py-4">
|
||||
<Dropdown v-if="!product.requiresSerial" v-model="product.warehouseId"
|
||||
:options="warehouseStore.activeWarehouses"
|
||||
optionLabel="name"
|
||||
optionValue="id"
|
||||
placeholder="Seleccione Almacén..."
|
||||
class="w-full"
|
||||
style="min-width: 200px;" />
|
||||
<div v-else class="text-xs text-slate-400 italic">
|
||||
Asignación por número de serie
|
||||
</div>
|
||||
</td>
|
||||
<td class="px-6 py-4 text-center">
|
||||
<Button v-if="product.requiresSerial"
|
||||
:label="isRowExpanded(product) ? 'OCULTAR' : 'GESTIONAR SERIES'"
|
||||
:icon="isRowExpanded(product) ? 'pi pi-chevron-up' : 'pi pi-qrcode'"
|
||||
:severity="isRowExpanded(product) ? 'secondary' : 'info'"
|
||||
:outlined="!isRowExpanded(product)"
|
||||
size="small"
|
||||
@click="toggleRow(product)" />
|
||||
<Badge v-else value="Estándar" severity="secondary" />
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<!-- Serial Numbers Expansion -->
|
||||
<tr v-if="isRowExpanded(product)" class="bg-primary/[0.02]">
|
||||
<td colspan="6" class="px-10 pb-6 pt-2">
|
||||
<Card class="border border-primary/20 shadow-sm">
|
||||
<template #header>
|
||||
<div class="px-5 pt-5 pb-3 border-b border-slate-100">
|
||||
<div class="flex items-center justify-between mb-2">
|
||||
<div>
|
||||
<h4 class="text-sm font-bold text-slate-700">
|
||||
Entrada de Números de Serie
|
||||
({{ product.quantityOrdered - product.serialNumbers.length }} Pendientes)
|
||||
</h4>
|
||||
<p class="text-[11px] text-slate-500">
|
||||
Defina el almacén para cada equipo escaneado
|
||||
</p>
|
||||
</div>
|
||||
<Badge severity="info">
|
||||
<i class="pi pi-qrcode mr-1"></i>
|
||||
Listo para escanear
|
||||
</Badge>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<template #content>
|
||||
<div class="grid grid-cols-1 lg:grid-cols-12 gap-6">
|
||||
<!-- Serial Input Form -->
|
||||
<div class="lg:col-span-5 space-y-4">
|
||||
<div>
|
||||
<label class="block text-[10px] font-bold text-slate-500 uppercase mb-1">
|
||||
Número de Serie
|
||||
</label>
|
||||
<div class="relative">
|
||||
<i class="pi pi-qrcode absolute left-3 top-1/2 -translate-y-1/2 text-gray-400"></i>
|
||||
<InputText v-model="newSerialNumber"
|
||||
placeholder="Escanear o escribir serie..."
|
||||
class="w-full pl-10" />
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-[10px] font-bold text-slate-500 uppercase mb-1">
|
||||
Almacén Destino
|
||||
</label>
|
||||
<Dropdown v-model="newSerialWarehouse"
|
||||
:options="warehouseStore.activeWarehouses" optionLabel="name" optionValue="id"
|
||||
class="w-full" />
|
||||
</div>
|
||||
<Button label="Registrar Serie" icon="pi pi-plus"
|
||||
class="w-full" severity="secondary"
|
||||
@click="addSerialNumber(product)" />
|
||||
</div>
|
||||
|
||||
<!-- Serial Numbers List -->
|
||||
<div class="lg:col-span-7">
|
||||
<Card class="bg-slate-50 border border-slate-200">
|
||||
<template #content>
|
||||
<div class="overflow-x-auto -m-6">
|
||||
<table class="w-full text-xs">
|
||||
<thead class="bg-slate-100">
|
||||
<tr>
|
||||
<th class="px-3 py-2 text-left font-bold text-slate-600">
|
||||
Nº Serie
|
||||
</th>
|
||||
<th class="px-3 py-2 text-left font-bold text-slate-600">
|
||||
Almacén Destino
|
||||
</th>
|
||||
<th class="px-3 py-2"></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="divide-y divide-slate-200">
|
||||
<tr v-for="(sn, index) in product.serialNumbers" :key="index">
|
||||
<td class="px-3 py-2 font-mono text-slate-900">
|
||||
{{ sn.serial }}
|
||||
</td>
|
||||
<td class="px-3 py-2">
|
||||
<Dropdown v-model="sn.warehouseId"
|
||||
:options="warehouseStore.activeWarehouses"
|
||||
optionLabel="name"
|
||||
optionValue="id"
|
||||
class="w-full text-xs" />
|
||||
</td>
|
||||
<td class="px-3 py-2 text-right">
|
||||
<Button icon="pi pi-trash"
|
||||
severity="danger"
|
||||
text
|
||||
rounded
|
||||
size="small"
|
||||
@click="removeSerialNumber(product, index)" />
|
||||
</td>
|
||||
</tr>
|
||||
<tr v-if="product.serialNumbers.length === 0">
|
||||
<td colspan="3" class="px-3 py-4 text-center text-slate-400">
|
||||
No hay números de serie registrados
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</template>
|
||||
</Card>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</Card>
|
||||
</td>
|
||||
</tr>
|
||||
</template>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</template>
|
||||
</Card>
|
||||
|
||||
<!-- Summary and Alerts -->
|
||||
<div class="grid grid-cols-1 md:grid-cols-3 gap-6">
|
||||
<div class="md:col-span-2">
|
||||
<Card v-if="!isFormValid" class="bg-amber-50 border border-amber-200">
|
||||
<template #content>
|
||||
<div class="flex items-start gap-4">
|
||||
<i class="pi pi-exclamation-triangle text-amber-600 text-xl"></i>
|
||||
<div>
|
||||
<p class="text-sm font-bold text-amber-800">Pendiente de Validación y Distribución</p>
|
||||
<p class="text-sm text-amber-700">
|
||||
Por favor complete todos los campos requeridos antes de confirmar la recepción.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</Card>
|
||||
</div>
|
||||
|
||||
<Card class="shadow-sm">
|
||||
<template #header>
|
||||
<div class="px-6 pt-4">
|
||||
<h4 class="text-xs font-bold text-slate-500 uppercase tracking-wider">
|
||||
Resumen de Destinos
|
||||
</h4>
|
||||
</div>
|
||||
</template>
|
||||
<template #content>
|
||||
<div class="space-y-2 -mt-2">
|
||||
<div v-for="warehouse in warehouseSummary" :key="warehouse.name"
|
||||
class="flex justify-between text-xs">
|
||||
<span class="text-slate-600">{{ warehouse.name }}</span>
|
||||
<span class="font-bold text-slate-900">
|
||||
{{ warehouse.count }} {{ warehouse.count === 1 ? 'Unidad' : 'Unidades' }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</Card>
|
||||
</div>
|
||||
|
||||
<!-- Footer Actions -->
|
||||
<div class="flex items-center justify-end gap-4 pt-4 border-t border-slate-200">
|
||||
<Button label="Cancelar" severity="secondary" text @click="cancel" />
|
||||
<Button label="Confirmar Recepción Multi-Almacén"
|
||||
icon="pi pi-check"
|
||||
:disabled="!isFormValid"
|
||||
@click="confirmReceipt" />
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
/* Estilos adicionales si son necesarios */
|
||||
</style>
|
||||
@ -9,17 +9,25 @@
|
||||
<!-- Page Header -->
|
||||
<div class="flex flex-wrap items-center justify-between gap-4">
|
||||
<div class="flex flex-col gap-2">
|
||||
<h1 class="text-surface-900 dark:text-white text-3xl md:text-4xl font-black leading-tight tracking-tight">
|
||||
{{ warehouseData?.name || 'North Logistics Center' }}
|
||||
<h1
|
||||
class="text-surface-900 dark:text-white text-3xl md:text-4xl font-black leading-tight tracking-tight">
|
||||
{{ warehouseData?.warehouse.name || 'Cargando...' }}
|
||||
</h1>
|
||||
<div class="flex flex-wrap items-center gap-3">
|
||||
<div class="flex items-center gap-2 text-surface-500 dark:text-surface-400">
|
||||
<i class="pi pi-map-marker text-sm"></i>
|
||||
<span class="text-sm">{{ warehouseData?.location || 'Zone A, Building 4' }}</span>
|
||||
<i class="pi pi-hash text-sm"></i>
|
||||
<span class="text-sm">{{ warehouseData?.warehouse.code || '-' }}</span>
|
||||
</div>
|
||||
<div class="size-1 bg-surface-300 dark:bg-surface-600 rounded-full"></div>
|
||||
<Tag :value="warehouseData?.isActive ? 'Operacional' : 'Inactivo'"
|
||||
:severity="warehouseData?.isActive ? 'success' : 'secondary'" />
|
||||
<div class="flex items-center gap-2 text-surface-500 dark:text-surface-400"
|
||||
v-if="warehouseData?.warehouse.address">
|
||||
<i class="pi pi-map-marker text-sm"></i>
|
||||
<span class="text-sm">{{ warehouseData.warehouse.address }}</span>
|
||||
</div>
|
||||
<div class="size-1 bg-surface-300 dark:bg-surface-600 rounded-full"
|
||||
v-if="warehouseData?.warehouse.address"></div>
|
||||
<Tag :value="warehouseData?.warehouse.is_active ? 'Operacional' : 'Inactivo'"
|
||||
:severity="warehouseData?.warehouse.is_active ? 'success' : 'secondary'" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex gap-3">
|
||||
@ -32,54 +40,65 @@
|
||||
<!-- Stats Overview -->
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6">
|
||||
<!-- Total SKUs -->
|
||||
<Card class="shadow-sm">
|
||||
<!-- <Card class="shadow-sm">
|
||||
<template #content>
|
||||
<div class="space-y-3">
|
||||
<p class="text-sm font-medium text-surface-500 dark:text-surface-400">Total SKUs</p>
|
||||
<p class="text-2xl font-bold text-surface-900 dark:text-white">1,240</p>
|
||||
<div class="flex items-center gap-1 text-xs font-semibold text-green-600 dark:text-green-400">
|
||||
<i class="pi pi-arrow-up text-xs"></i>
|
||||
+2.4% vs mes anterior
|
||||
<p class="text-2xl font-bold text-surface-900 dark:text-white">
|
||||
{{ warehouseData?.stocks.length || 0 }}
|
||||
</p>
|
||||
<div class="flex items-center gap-1 text-xs font-semibold text-blue-600 dark:text-blue-400">
|
||||
<i class="pi pi-box text-xs"></i>
|
||||
Productos en stock
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</Card>
|
||||
|
||||
</Card>
|
||||
-->
|
||||
<!-- Low Stock Items -->
|
||||
<Card class="shadow-sm border-l-4 border-l-red-500">
|
||||
<!-- <Card class="shadow-sm border-l-4 border-l-red-500">
|
||||
<template #content>
|
||||
<div class="space-y-3">
|
||||
<p class="text-sm font-medium text-surface-500 dark:text-surface-400">Items con Stock Bajo</p>
|
||||
<p class="text-2xl font-bold text-surface-900 dark:text-white">18</p>
|
||||
<p class="text-2xl font-bold text-surface-900 dark:text-white">
|
||||
{{ warehouseData?.stocks.filter(s => s.stock_min && s.stock < s.stock_min).length || 0 }}
|
||||
</p>
|
||||
<div class="flex items-center gap-1 text-xs font-semibold text-red-600 dark:text-red-400">
|
||||
<i class="pi pi-exclamation-triangle text-xs"></i>
|
||||
Acción requerida
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</Card>
|
||||
</Card> -->
|
||||
|
||||
<!-- Movements 24h -->
|
||||
<Card class="shadow-sm">
|
||||
<!-- Total Items -->
|
||||
<!-- <Card class="shadow-sm">
|
||||
<template #content>
|
||||
<div class="space-y-3">
|
||||
<p class="text-sm font-medium text-surface-500 dark:text-surface-400">Movimientos (24h)</p>
|
||||
<p class="text-2xl font-bold text-surface-900 dark:text-white">142</p>
|
||||
<p class="text-sm font-medium text-surface-500 dark:text-surface-400">Items Totales</p>
|
||||
<p class="text-2xl font-bold text-surface-900 dark:text-white">
|
||||
{{ warehouseData?.items.length || 0 }}
|
||||
</p>
|
||||
<div class="flex items-center gap-1 text-xs font-semibold text-blue-600 dark:text-blue-400">
|
||||
<i class="pi pi-refresh text-xs"></i>
|
||||
Alta actividad
|
||||
<i class="pi pi-database text-xs"></i>
|
||||
Items registrados
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</Card>
|
||||
</Card> -->
|
||||
|
||||
<!-- Warehouse Utilization -->
|
||||
<!-- Total Stock -->
|
||||
<Card class="shadow-sm">
|
||||
<template #content>
|
||||
<div class="space-y-3">
|
||||
<p class="text-sm font-medium text-surface-500 dark:text-surface-400">Utilización del Almacén</p>
|
||||
<p class="text-2xl font-bold text-surface-900 dark:text-white">84%</p>
|
||||
<ProgressBar :value="84" :showValue="false" class="h-2" />
|
||||
<p class="text-sm font-medium text-surface-500 dark:text-surface-400">Stock Total</p>
|
||||
<p class="text-2xl font-bold text-surface-900 dark:text-white">
|
||||
{{warehouseData?.stocks.reduce((sum, s) => sum + s.stock, 0).toLocaleString() || 0}}
|
||||
</p>
|
||||
<div class="flex items-center gap-1 text-xs font-semibold text-green-600 dark:text-green-400">
|
||||
<i class="pi pi-chart-bar text-xs"></i>
|
||||
Unidades en almacén
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</Card>
|
||||
@ -100,38 +119,19 @@
|
||||
|
||||
<!-- Filters -->
|
||||
<div class="flex flex-wrap items-center justify-end gap-3 mb-4">
|
||||
<Select
|
||||
v-model="selectedCategory"
|
||||
:options="categoryOptions"
|
||||
optionLabel="label"
|
||||
optionValue="value"
|
||||
placeholder="Todas las Categorías"
|
||||
class="w-full md:w-48"
|
||||
/>
|
||||
<Select
|
||||
v-model="selectedStockLevel"
|
||||
:options="stockLevelOptions"
|
||||
optionLabel="label"
|
||||
optionValue="value"
|
||||
placeholder="Todos los Niveles"
|
||||
class="w-full md:w-48"
|
||||
/>
|
||||
<Select v-model="selectedCategory" :options="categoryOptions" optionLabel="label"
|
||||
optionValue="value" placeholder="Todas las Categorías" class="w-full md:w-48" />
|
||||
<Select v-model="selectedStockLevel" :options="stockLevelOptions" optionLabel="label"
|
||||
optionValue="value" placeholder="Todos los Niveles" class="w-full md:w-48" />
|
||||
<Button icon="pi pi-filter" outlined severity="secondary" />
|
||||
</div>
|
||||
|
||||
<!-- Current Stock Table -->
|
||||
<DataTable
|
||||
:value="inventoryData"
|
||||
:paginator="true"
|
||||
:rows="10"
|
||||
<DataTable :value="inventoryData" :paginator="true" :rows="10"
|
||||
:rowsPerPageOptions="[10, 25, 50]"
|
||||
paginatorTemplate="FirstPageLink PrevPageLink PageLinks NextPageLink LastPageLink RowsPerPageDropdown CurrentPageReport"
|
||||
currentPageReportTemplate="Mostrando {first} a {last} de {totalRecords} resultados"
|
||||
:loading="loading"
|
||||
stripedRows
|
||||
responsiveLayout="scroll"
|
||||
class="text-sm"
|
||||
>
|
||||
:loading="loading" stripedRows responsiveLayout="scroll" class="text-sm">
|
||||
<Column field="sku" header="SKU" sortable class="font-mono">
|
||||
<template #body="slotProps">
|
||||
<span class="font-semibold">{{ slotProps.data.sku }}</span>
|
||||
@ -147,7 +147,8 @@
|
||||
</Column>
|
||||
<Column field="quantity" header="Cantidad" sortable>
|
||||
<template #body="slotProps">
|
||||
<span class="font-bold tabular-nums">{{ slotProps.data.quantity.toLocaleString() }}</span>
|
||||
<span class="font-bold tabular-nums">{{ slotProps.data.quantity.toLocaleString()
|
||||
}}</span>
|
||||
</template>
|
||||
</Column>
|
||||
<Column field="unit" header="Unidad" sortable />
|
||||
@ -161,10 +162,8 @@
|
||||
</Column>
|
||||
<Column field="status" header="Estado" sortable>
|
||||
<template #body="slotProps">
|
||||
<Tag
|
||||
:value="slotProps.data.status"
|
||||
:severity="getStatusSeverity(slotProps.data.status)"
|
||||
/>
|
||||
<Tag :value="slotProps.data.status"
|
||||
:severity="getStatusSeverity(slotProps.data.status)" />
|
||||
</template>
|
||||
</Column>
|
||||
<Column field="lastUpdate" header="Última Actualización" sortable>
|
||||
@ -175,8 +174,10 @@
|
||||
<Column header="Acciones" :exportable="false">
|
||||
<template #body="slotProps">
|
||||
<div class="flex gap-2">
|
||||
<Button icon="pi pi-eye" outlined rounded size="small" severity="secondary" @click="viewItem(slotProps.data)" />
|
||||
<Button icon="pi pi-pencil" outlined rounded size="small" @click="editItem(slotProps.data)" />
|
||||
<Button icon="pi pi-eye" outlined rounded size="small" severity="secondary"
|
||||
@click="viewItem(slotProps.data)" />
|
||||
<Button icon="pi pi-pencil" outlined rounded size="small"
|
||||
@click="editItem(slotProps.data)" />
|
||||
</div>
|
||||
</template>
|
||||
</Column>
|
||||
@ -193,25 +194,16 @@
|
||||
</template>
|
||||
|
||||
<!-- Movement History Table -->
|
||||
<DataTable
|
||||
:value="movementHistory"
|
||||
:paginator="true"
|
||||
:rows="10"
|
||||
<DataTable :value="movementHistory" :paginator="true" :rows="10"
|
||||
:rowsPerPageOptions="[10, 25, 50]"
|
||||
paginatorTemplate="FirstPageLink PrevPageLink PageLinks NextPageLink LastPageLink RowsPerPageDropdown CurrentPageReport"
|
||||
currentPageReportTemplate="Mostrando {first} a {last} de {totalRecords} resultados"
|
||||
:loading="loading"
|
||||
stripedRows
|
||||
responsiveLayout="scroll"
|
||||
class="text-sm"
|
||||
>
|
||||
:loading="loading" stripedRows responsiveLayout="scroll" class="text-sm">
|
||||
<Column field="date" header="Fecha" sortable />
|
||||
<Column field="type" header="Tipo" sortable>
|
||||
<template #body="slotProps">
|
||||
<Tag
|
||||
:value="slotProps.data.type"
|
||||
:severity="getMovementTypeSeverity(slotProps.data.type)"
|
||||
/>
|
||||
<Tag :value="slotProps.data.type"
|
||||
:severity="getMovementTypeSeverity(slotProps.data.type)" />
|
||||
</template>
|
||||
</Column>
|
||||
<Column field="product" header="Producto" sortable />
|
||||
@ -231,7 +223,7 @@
|
||||
<!-- Secondary Section: Recent Movements & Insights -->
|
||||
<div class="grid grid-cols-1 lg:grid-cols-3 gap-6">
|
||||
<!-- Quick Activity Log -->
|
||||
<Card class="lg:col-span-2 shadow-sm">
|
||||
<!-- <Card class="lg:col-span-2 shadow-sm">
|
||||
<template #header>
|
||||
<div class="flex items-center justify-between p-4">
|
||||
<h3 class="font-bold text-surface-900 dark:text-white">Registro Rápido de Actividad</h3>
|
||||
@ -262,16 +254,18 @@
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</Card>
|
||||
</Card> -->
|
||||
|
||||
<!-- Warehouse Health Insights -->
|
||||
<Card class="shadow-sm bg-primary-50 dark:bg-primary-900/10 border border-primary-200 dark:border-primary-800">
|
||||
<!-- <Card
|
||||
class="shadow-sm bg-primary-50 dark:bg-primary-900/10 border border-primary-200 dark:border-primary-800">
|
||||
<template #content>
|
||||
<div class="space-y-4">
|
||||
<div>
|
||||
<h3 class="font-bold text-surface-900 dark:text-white mb-2">Salud del Almacén</h3>
|
||||
<p class="text-sm text-surface-600 dark:text-surface-300 leading-relaxed">
|
||||
North Logistics Center está actualmente al 84% de capacidad. Recomendamos auditar la Zona B
|
||||
North Logistics Center está actualmente al 84% de capacidad. Recomendamos auditar la
|
||||
Zona B
|
||||
para optimización de espacio potencial.
|
||||
</p>
|
||||
</div>
|
||||
@ -291,23 +285,20 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Button
|
||||
label="Generar Reporte Completo"
|
||||
class="w-full"
|
||||
outlined
|
||||
@click="generateReport"
|
||||
/>
|
||||
<Button label="Generar Reporte Completo" class="w-full" outlined @click="generateReport" />
|
||||
</div>
|
||||
</template>
|
||||
</Card>
|
||||
</Card> -->
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted } from 'vue';
|
||||
import { ref, onMounted, computed } from 'vue';
|
||||
import { useRouter, useRoute } from 'vue-router';
|
||||
import { useToast } from 'primevue/usetoast';
|
||||
import { warehouseService } from '../services/warehouseService';
|
||||
import type { WarehouseDetailData } from '../types/warehouse';
|
||||
|
||||
// PrimeVue Components
|
||||
import Toast from 'primevue/toast';
|
||||
@ -320,14 +311,13 @@ import TabPanel from 'primevue/tabpanel';
|
||||
import DataTable from 'primevue/datatable';
|
||||
import Column from 'primevue/column';
|
||||
import Select from 'primevue/select';
|
||||
import ProgressBar from 'primevue/progressbar';
|
||||
|
||||
const router = useRouter();
|
||||
const route = useRoute();
|
||||
const toast = useToast();
|
||||
|
||||
// Reactive State
|
||||
const warehouseData = ref<any>(null);
|
||||
const warehouseData = ref<WarehouseDetailData | null>(null);
|
||||
const loading = ref(false);
|
||||
const activeTab = ref(0);
|
||||
const selectedCategory = ref('all');
|
||||
@ -335,9 +325,9 @@ const selectedStockLevel = ref('all');
|
||||
|
||||
// Breadcrumb
|
||||
const breadcrumbHome = ref({ icon: 'pi pi-home', to: '/' });
|
||||
const breadcrumbItems = ref([
|
||||
const breadcrumbItems = computed(() => [
|
||||
{ label: 'Almacenes', to: '/warehouse' },
|
||||
{ label: 'North Logistics Center' }
|
||||
{ label: warehouseData.value?.warehouse.name || 'Cargando...' }
|
||||
]);
|
||||
|
||||
// Filter Options
|
||||
@ -355,49 +345,8 @@ const stockLevelOptions = [
|
||||
{ label: 'Sobrestock', value: 'overstock' },
|
||||
];
|
||||
|
||||
// Mock Data - Current Stock
|
||||
const inventoryData = ref([
|
||||
{
|
||||
sku: 'WH-2024-001',
|
||||
product: 'Laptop Dell XPS 15',
|
||||
category: 'Electrónica',
|
||||
quantity: 450,
|
||||
unit: 'Unidades',
|
||||
location: 'Rack A-12',
|
||||
status: 'En Stock',
|
||||
lastUpdate: 'Hace 2 horas'
|
||||
},
|
||||
{
|
||||
sku: 'WH-2024-002',
|
||||
product: 'Silla Ergonómica Pro',
|
||||
category: 'Mobiliario',
|
||||
quantity: 85,
|
||||
unit: 'Unidades',
|
||||
location: 'Zona B-04',
|
||||
status: 'Stock Bajo',
|
||||
lastUpdate: 'Hace 4 horas'
|
||||
},
|
||||
{
|
||||
sku: 'WH-2024-003',
|
||||
product: 'Monitor LG 27" 4K',
|
||||
category: 'Electrónica',
|
||||
quantity: 320,
|
||||
unit: 'Unidades',
|
||||
location: 'Rack A-15',
|
||||
status: 'En Stock',
|
||||
lastUpdate: 'Hace 1 hora'
|
||||
},
|
||||
{
|
||||
sku: 'WH-2024-004',
|
||||
product: 'Teclado Mecánico RGB',
|
||||
category: 'Periféricos',
|
||||
quantity: 12,
|
||||
unit: 'Unidades',
|
||||
location: 'Rack C-08',
|
||||
status: 'Stock Crítico',
|
||||
lastUpdate: 'Hace 30 min'
|
||||
},
|
||||
]);
|
||||
// Inventory Data from API
|
||||
const inventoryData = ref<any[]>([]);
|
||||
|
||||
// Mock Data - Movement History
|
||||
const movementHistory = ref([
|
||||
@ -427,33 +376,33 @@ const movementHistory = ref([
|
||||
},
|
||||
]);
|
||||
|
||||
// Mock Data - Recent Activities
|
||||
const recentActivities = ref([
|
||||
{
|
||||
id: 1,
|
||||
type: 'in',
|
||||
action: 'Entrada de Stock',
|
||||
product: 'Laptop Dell XPS 15 - 50 unidades',
|
||||
quantity: '+50',
|
||||
time: 'Hace 2 horas'
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
type: 'out',
|
||||
action: 'Salida de Stock',
|
||||
product: 'Monitor LG 27" - 25 unidades',
|
||||
quantity: '-25',
|
||||
time: 'Hace 4 horas'
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
type: 'in',
|
||||
action: 'Entrada de Stock',
|
||||
product: 'Silla Ergonómica - 100 unidades',
|
||||
quantity: '+100',
|
||||
time: 'Hace 6 horas'
|
||||
},
|
||||
]);
|
||||
// Mock Data - Recent Activities (unused - template is commented out)
|
||||
// const recentActivities = ref([
|
||||
// {
|
||||
// id: 1,
|
||||
// type: 'in',
|
||||
// action: 'Entrada de Stock',
|
||||
// product: 'Laptop Dell XPS 15 - 50 unidades',
|
||||
// quantity: '+50',
|
||||
// time: 'Hace 2 horas'
|
||||
// },
|
||||
// {
|
||||
// id: 2,
|
||||
// type: 'out',
|
||||
// action: 'Salida de Stock',
|
||||
// product: 'Monitor LG 27" - 25 unidades',
|
||||
// quantity: '-25',
|
||||
// time: 'Hace 4 horas'
|
||||
// },
|
||||
// {
|
||||
// id: 3,
|
||||
// type: 'in',
|
||||
// action: 'Entrada de Stock',
|
||||
// product: 'Silla Ergonómica - 100 unidades',
|
||||
// quantity: '+100',
|
||||
// time: 'Hace 6 horas'
|
||||
// },
|
||||
// ]);
|
||||
|
||||
// Methods
|
||||
const getStatusSeverity = (status: string) => {
|
||||
@ -498,43 +447,93 @@ const editItem = (item: any) => {
|
||||
});
|
||||
};
|
||||
|
||||
const viewAllMovements = () => {
|
||||
activeTab.value = 1;
|
||||
// Unused methods (template sections are commented out)
|
||||
// const viewAllMovements = () => {
|
||||
// activeTab.value = 1;
|
||||
// };
|
||||
|
||||
// const generateReport = () => {
|
||||
// toast.add({
|
||||
// severity: 'success',
|
||||
// summary: 'Generando Reporte',
|
||||
// detail: 'El reporte se está generando...',
|
||||
// life: 3000
|
||||
// });
|
||||
// };
|
||||
|
||||
// Helper function to get stock status
|
||||
const getStockStatus = (stock: number, stockMin: number | null) => {
|
||||
if (!stockMin) return 'En Stock';
|
||||
if (stock === 0) return 'Sin Stock';
|
||||
if (stock < stockMin) return 'Stock Bajo';
|
||||
if (stock < stockMin * 1.5) return 'Stock Crítico';
|
||||
return 'En Stock';
|
||||
};
|
||||
|
||||
const generateReport = () => {
|
||||
toast.add({
|
||||
severity: 'success',
|
||||
summary: 'Generando Reporte',
|
||||
detail: 'El reporte se está generando...',
|
||||
life: 3000
|
||||
});
|
||||
// Helper function to format date
|
||||
const formatRelativeTime = (dateString: string) => {
|
||||
const date = new Date(dateString);
|
||||
const now = new Date();
|
||||
const diffMs = now.getTime() - date.getTime();
|
||||
const diffHours = Math.floor(diffMs / (1000 * 60 * 60));
|
||||
const diffDays = Math.floor(diffHours / 24);
|
||||
|
||||
if (diffDays > 0) return `Hace ${diffDays} día${diffDays > 1 ? 's' : ''}`;
|
||||
if (diffHours > 0) return `Hace ${diffHours} hora${diffHours > 1 ? 's' : ''}`;
|
||||
return 'Hace menos de 1 hora';
|
||||
};
|
||||
|
||||
// Lifecycle
|
||||
onMounted(async () => {
|
||||
const warehouseId = route.params.id;
|
||||
|
||||
if (!warehouseId) {
|
||||
toast.add({
|
||||
severity: 'error',
|
||||
summary: 'Error',
|
||||
detail: 'ID de almacén no válido',
|
||||
life: 3000
|
||||
});
|
||||
router.push({ name: 'Warehouses' });
|
||||
return;
|
||||
}
|
||||
|
||||
loading.value = true;
|
||||
|
||||
try {
|
||||
// TODO: Cargar datos del almacén desde el API
|
||||
// const response = await warehouseService.getWarehouseById(warehouseId);
|
||||
// warehouseData.value = response.data;
|
||||
const response = await warehouseService.getWarehouseById(Number(warehouseId));
|
||||
warehouseData.value = response.data;
|
||||
|
||||
// Mock data por ahora
|
||||
warehouseData.value = {
|
||||
id: warehouseId,
|
||||
name: 'North Logistics Center',
|
||||
location: 'Zone A, Building 4',
|
||||
isActive: true,
|
||||
};
|
||||
// Mapear stocks a formato de la tabla
|
||||
inventoryData.value = response.data.stocks.map(stock => ({
|
||||
sku: stock.product.sku,
|
||||
product: stock.product.name,
|
||||
category: stock.product.description || 'Sin categoría',
|
||||
quantity: stock.stock,
|
||||
unit: 'Unidades',
|
||||
location: `Almacén ${response.data.warehouse.code}`,
|
||||
status: getStockStatus(stock.stock, stock.stock_min),
|
||||
lastUpdate: formatRelativeTime(stock.updated_at),
|
||||
productId: stock.product_id,
|
||||
stockMin: stock.stock_min,
|
||||
stockMax: stock.stock_max,
|
||||
}));
|
||||
|
||||
toast.add({
|
||||
severity: 'success',
|
||||
summary: 'Datos Cargados',
|
||||
detail: `Almacén ${response.data.warehouse.name} cargado exitosamente`,
|
||||
life: 3000
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Error al cargar los datos del almacén:', error);
|
||||
toast.add({
|
||||
severity: 'error',
|
||||
summary: 'Error',
|
||||
detail: 'Error al cargar los datos del almacén',
|
||||
life: 3000
|
||||
});
|
||||
router.push({ name: 'Warehouses' });
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
|
||||
@ -10,7 +10,6 @@ import InputSwitch from 'primevue/inputswitch';
|
||||
import Textarea from 'primevue/textarea';
|
||||
import Dropdown from 'primevue/dropdown';
|
||||
import Chip from 'primevue/chip';
|
||||
import Avatar from 'primevue/avatar';
|
||||
import Toast from 'primevue/toast';
|
||||
import { warehouseService } from '../services/warehouseService';
|
||||
import { useClassificationStore } from '../stores/classificationStore';
|
||||
@ -44,13 +43,16 @@ const isSubmitting = ref(false);
|
||||
|
||||
// Categories from store
|
||||
const availableClassifications = computed(() => {
|
||||
return classificationStore.activeClassifications.map(cls => ({
|
||||
// Recursive function to ensure all children arrays are defined
|
||||
const mapClassification = (cls: any): any => ({
|
||||
id: cls.id,
|
||||
name: cls.name,
|
||||
code: cls.code,
|
||||
parent_id: cls.parent_id,
|
||||
children: cls.children || []
|
||||
}));
|
||||
children: (cls.children || []).map(mapClassification)
|
||||
});
|
||||
|
||||
return classificationStore.activeClassifications.map(mapClassification);
|
||||
});
|
||||
|
||||
// Get root classifications (categories without parent)
|
||||
@ -87,7 +89,7 @@ const addSelectedCategory = () => {
|
||||
|
||||
// If subcategory is selected, use it instead
|
||||
if (selectedSubCategory.value) {
|
||||
const subcat = parent.children?.find((c: any) => c.id === selectedSubCategory.value);
|
||||
const subcat = parent.children.find((c: any) => c.id === selectedSubCategory.value);
|
||||
if (subcat) {
|
||||
categoryToAdd = subcat;
|
||||
categoryName = `${parent.name} > ${subcat.name}`;
|
||||
@ -95,7 +97,7 @@ const addSelectedCategory = () => {
|
||||
}
|
||||
|
||||
// Check if already added
|
||||
if (assignedCategories.value.some(c => c.id === categoryToAdd.id)) {
|
||||
if (assignedCategories.value.some((c: any) => c.id === categoryToAdd.id)) {
|
||||
toast.add({
|
||||
severity: 'warn',
|
||||
summary: 'Categoría ya agregada',
|
||||
@ -108,7 +110,9 @@ const addSelectedCategory = () => {
|
||||
assignedCategories.value.push({
|
||||
id: categoryToAdd.id,
|
||||
name: categoryName,
|
||||
code: categoryToAdd.code
|
||||
code: categoryToAdd.code,
|
||||
parent_id: categoryToAdd.parent_id,
|
||||
children: categoryToAdd.children || []
|
||||
});
|
||||
|
||||
// Reset selections
|
||||
@ -116,27 +120,27 @@ const addSelectedCategory = () => {
|
||||
selectedSubCategory.value = null;
|
||||
};
|
||||
|
||||
// Staff
|
||||
const assignedStaff = ref([
|
||||
{
|
||||
id: 1,
|
||||
name: 'Jane Cooper',
|
||||
role: 'Gerente de Almacén',
|
||||
avatar: 'https://i.pravatar.cc/150?img=1'
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
name: 'Cody Fisher',
|
||||
role: 'Operador de Montacargas',
|
||||
avatar: 'https://i.pravatar.cc/150?img=2'
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
name: 'Esther Howard',
|
||||
role: 'Auxiliar de Recepción',
|
||||
avatar: 'https://i.pravatar.cc/150?img=3'
|
||||
}
|
||||
]);
|
||||
// Staff (unused, for future use)
|
||||
// const assignedStaff = ref([
|
||||
// {
|
||||
// id: 1,
|
||||
// name: 'Jane Cooper',
|
||||
// role: 'Gerente de Almacén',
|
||||
// avatar: 'https://i.pravatar.cc/150?img=1'
|
||||
// },
|
||||
// {
|
||||
// id: 2,
|
||||
// name: 'Cody Fisher',
|
||||
// role: 'Operador de Montacargas',
|
||||
// avatar: 'https://i.pravatar.cc/150?img=2'
|
||||
// },
|
||||
// {
|
||||
// id: 3,
|
||||
// name: 'Esther Howard',
|
||||
// role: 'Auxiliar de Recepción',
|
||||
// avatar: 'https://i.pravatar.cc/150?img=3'
|
||||
// }
|
||||
// ]);
|
||||
|
||||
const saveDisabled = ref(false);
|
||||
|
||||
@ -144,17 +148,18 @@ const removeCategory = (id: number) => {
|
||||
assignedCategories.value = assignedCategories.value.filter(cat => cat.id !== id);
|
||||
};
|
||||
|
||||
const addStaff = () => {
|
||||
console.log('Add staff');
|
||||
};
|
||||
// Unused staff methods (for future use)
|
||||
// const addStaff = () => {
|
||||
// console.log('Add staff');
|
||||
// };
|
||||
|
||||
const editStaff = (id: number) => {
|
||||
console.log('Edit staff:', id);
|
||||
};
|
||||
// const editStaff = (id: number) => {
|
||||
// console.log('Edit staff:', id);
|
||||
// };
|
||||
|
||||
const removeStaff = (id: number) => {
|
||||
assignedStaff.value = assignedStaff.value.filter(staff => staff.id !== id);
|
||||
};
|
||||
// const removeStaff = (id: number) => {
|
||||
// assignedStaff.value = assignedStaff.value.filter(staff => staff.id !== id);
|
||||
// };
|
||||
|
||||
const cancel = () => {
|
||||
router.push('/warehouse');
|
||||
|
||||
@ -1,12 +1,17 @@
|
||||
<!DOCTYPE html>
|
||||
<html class="light" lang="en"><head>
|
||||
<meta charset="utf-8"/>
|
||||
<meta content="width=device-width, initial-scale=1.0" name="viewport"/>
|
||||
<title>WMS - Batch Add Inventory Items</title>
|
||||
<script src="https://cdn.tailwindcss.com?plugins=forms,container-queries"></script>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800;900&display=swap" rel="stylesheet"/>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:wght,FILL@100..700,0..1&display=swap" rel="stylesheet"/>
|
||||
<script>
|
||||
<html class="light" lang="es">
|
||||
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta content="width=device-width, initial-scale=1.0" name="viewport" />
|
||||
<title>Multi-Warehouse Goods Receipt Entry | Logistics Pro</title>
|
||||
<script src="https://cdn.tailwindcss.com?plugins=forms,container-queries"></script>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;900&display=swap"
|
||||
rel="stylesheet" />
|
||||
<link
|
||||
href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:wght,FILL@100..700,0..1&display=swap"
|
||||
rel="stylesheet" />
|
||||
<script id="tailwind-config">
|
||||
tailwind.config = {
|
||||
darkMode: "class",
|
||||
theme: {
|
||||
@ -17,321 +22,427 @@
|
||||
"background-dark": "#101922",
|
||||
},
|
||||
fontFamily: {
|
||||
"display": ["Inter", "sans-serif"]
|
||||
},
|
||||
borderRadius: {
|
||||
"DEFAULT": "0.25rem",
|
||||
"lg": "0.5rem",
|
||||
"xl": "0.75rem",
|
||||
"full": "9999px"
|
||||
"display": ["Inter"]
|
||||
},
|
||||
borderRadius: { "DEFAULT": "0.25rem", "lg": "0.5rem", "xl": "0.75rem", "full": "9999px" },
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
<style type="text/tailwindcss">
|
||||
@layer utilities {
|
||||
.scrollbar-hide::-webkit-scrollbar {
|
||||
display: none;
|
||||
}
|
||||
<style>
|
||||
body {
|
||||
font-family: 'Inter', sans-serif;
|
||||
}
|
||||
|
||||
.material-symbols-outlined {
|
||||
font-variation-settings: 'FILL' 0, 'wght' 400, 'GRAD' 0, 'opsz' 24;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body class="bg-background-light dark:bg-background-dark font-display text-gray-800 dark:text-gray-200 overflow-hidden">
|
||||
<div class="flex h-screen w-full">
|
||||
<aside class="flex w-64 flex-col border-r border-gray-200 dark:border-gray-800 bg-white dark:bg-background-dark">
|
||||
<div class="flex h-full flex-col justify-between p-4">
|
||||
<div class="flex flex-col gap-4">
|
||||
<div class="flex items-center gap-3 p-2">
|
||||
<div class="bg-center bg-no-repeat aspect-square bg-cover rounded-full size-10" style='background-image: url("https://lh3.googleusercontent.com/aida-public/AB6AXuCztlHhjKvu2qkn2Xi2zFHagNsNToKwcTg3vQr0KtTqBCo13dK1yyz9HzB2uLCiciLyDfnrf7pREvdblPqCcUiN0HqlSbkFwY1dpQLMbJ4hmpVgHVWaLaUCMXju06qyGQSdg2ChGVcbTQIrk-RNI2-hDOFnfrI1PD89RNSsByXGRsdkYWSyEYFOFk7bT4l7aIaasB6cdVxDfNwJdvVx15wb7-qOHZHFTPMbrkkzmjGec-f7iVqTi5U1ykNDclBSezBM97TfXajTwRJE");'></div>
|
||||
<div class="flex flex-col">
|
||||
<h1 class="text-gray-900 dark:text-white text-base font-medium leading-normal">Admin User</h1>
|
||||
<p class="text-gray-500 dark:text-gray-400 text-sm font-normal leading-normal">Warehouse Manager</p>
|
||||
</div>
|
||||
</div>
|
||||
<nav class="flex flex-col gap-2">
|
||||
<a class="flex items-center gap-3 px-3 py-2 text-gray-700 dark:text-gray-300 rounded-lg hover:bg-gray-100 dark:hover:bg-gray-800" href="#">
|
||||
<span class="material-symbols-outlined">dashboard</span>
|
||||
<p class="text-sm font-medium leading-normal">Dashboard</p>
|
||||
</a>
|
||||
<a class="flex items-center gap-3 px-3 py-2 rounded-lg bg-primary/10 text-primary dark:bg-primary/20" href="#">
|
||||
<span class="material-symbols-outlined">warehouse</span>
|
||||
<p class="text-sm font-medium leading-normal">Inventory</p>
|
||||
</a>
|
||||
<a class="flex items-center gap-3 px-3 py-2 text-gray-700 dark:text-gray-300 rounded-lg hover:bg-gray-100 dark:hover:bg-gray-800" href="#">
|
||||
<span class="material-symbols-outlined">receipt_long</span>
|
||||
<p class="text-sm font-medium leading-normal">Orders</p>
|
||||
</a>
|
||||
<a class="flex items-center gap-3 px-3 py-2 text-gray-700 dark:text-gray-300 rounded-lg hover:bg-gray-100 dark:hover:bg-gray-800" href="#">
|
||||
<span class="material-symbols-outlined">local_shipping</span>
|
||||
<p class="text-sm font-medium leading-normal">Suppliers</p>
|
||||
</a>
|
||||
<a class="flex items-center gap-3 px-3 py-2 text-gray-700 dark:text-gray-300 rounded-lg hover:bg-gray-100 dark:hover:bg-gray-800" href="#">
|
||||
<span class="material-symbols-outlined">pie_chart</span>
|
||||
<p class="text-sm font-medium leading-normal">Reports</p>
|
||||
</a>
|
||||
</nav>
|
||||
</div>
|
||||
<div class="flex flex-col gap-2">
|
||||
<nav class="flex flex-col gap-1">
|
||||
<a class="flex items-center gap-3 px-3 py-2 text-gray-700 dark:text-gray-300 rounded-lg hover:bg-gray-100 dark:hover:bg-gray-800" href="#">
|
||||
<span class="material-symbols-outlined">settings</span>
|
||||
<p class="text-sm font-medium leading-normal">Settings</p>
|
||||
</a>
|
||||
<a class="flex items-center gap-3 px-3 py-2 text-gray-700 dark:text-gray-300 rounded-lg hover:bg-gray-100 dark:hover:bg-gray-800" href="#">
|
||||
<span class="material-symbols-outlined">help</span>
|
||||
<p class="text-sm font-medium leading-normal">Help</p>
|
||||
</a>
|
||||
</nav>
|
||||
<button class="flex min-w-[84px] cursor-pointer items-center justify-center overflow-hidden rounded-lg h-10 px-4 bg-gray-200 dark:bg-gray-800 text-gray-800 dark:text-gray-200 text-sm font-bold leading-normal hover:bg-gray-300 dark:hover:bg-gray-700">
|
||||
<span class="truncate">Logout</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</aside>
|
||||
<main class="flex-1 flex flex-col h-screen overflow-hidden">
|
||||
<header class="flex flex-none items-center justify-between whitespace-nowrap border-b border-solid border-gray-200 dark:border-gray-800 px-10 py-3 bg-white dark:bg-background-dark z-10">
|
||||
<div class="flex items-center gap-4 text-gray-900 dark:text-white">
|
||||
<div class="size-6 text-primary">
|
||||
<svg fill="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M21.435 7.182a.75.75 0 0 0-.87-.11L12 10.435 3.435 7.072a.75.75 0 0 0-.87.11.75.75 0 0 0-.11.87l2.122 7.878a.75.75 0 0 0 .869.59l6-1.635a.75.75 0 0 0 .108 0l6 1.635a.75.75 0 0 0 .87-.59l2.12-7.878a.75.75 0 0 0-.11-.87zM12 12.18l-5.693-1.55L12 4.288l5.693 6.342L12 12.18z"></path>
|
||||
</svg>
|
||||
</div>
|
||||
<h2 class="text-lg font-bold leading-tight tracking-[-0.015em]">WMS Dashboard</h2>
|
||||
</div>
|
||||
<div class="flex flex-1 justify-end items-center gap-4">
|
||||
<label class="relative grow max-w-sm">
|
||||
<span class="material-symbols-outlined absolute left-3 top-1/2 -translate-y-1/2 text-gray-500">search</span>
|
||||
<input class="form-input w-full rounded-lg border-none bg-gray-100 dark:bg-gray-800 h-10 pl-10 pr-4 text-sm text-gray-900 dark:text-white placeholder:text-gray-500 focus:ring-primary" placeholder="Search items, orders..." value=""/>
|
||||
</label>
|
||||
<div class="flex gap-2">
|
||||
<button class="flex items-center justify-center rounded-lg h-10 w-10 bg-gray-100 dark:bg-gray-800 text-gray-800 dark:text-gray-200 hover:bg-gray-200 dark:hover:bg-gray-700">
|
||||
<span class="material-symbols-outlined text-xl">notifications</span>
|
||||
</button>
|
||||
<button class="flex items-center justify-center rounded-lg h-10 w-10 bg-gray-100 dark:bg-gray-800 text-gray-800 dark:text-gray-200 hover:bg-gray-200 dark:hover:bg-gray-700">
|
||||
<span class="material-symbols-outlined text-xl">help_outline</span>
|
||||
</button>
|
||||
</div>
|
||||
<div class="bg-center bg-no-repeat aspect-square bg-cover rounded-full size-10" style='background-image: url("https://lh3.googleusercontent.com/aida-public/AB6AXuBNpxncwdjcD3fLZbRbit_NZyuFBZhcpV-Bvdlu6NiZL3kb65hsFRsDkTNHtC0zJxG3HGV1TInl_DCfafj3axNGSwW4-UNj1sZWhiHCYE2aK9hm-FYjrNGiEh0UqKya1EAYMTM5Z4k8qKOWPEPdIaZz9X98tPC5FIn5lbRRusCTuQmgRL-QxK9SdIMA3TflImwA1vyh3zq44j8EkkNTQWf94-e82GDHs5MwHIkK0S-Gg4d950IyRTpoABSa9qXA5yPzoaT9jCGjCodL");'></div>
|
||||
</div>
|
||||
</header>
|
||||
<div class="flex-1 overflow-y-auto p-6 lg:p-10 pb-24 relative">
|
||||
<div class="mx-auto max-w-7xl">
|
||||
<div class="flex flex-wrap gap-2 mb-6">
|
||||
<a class="text-gray-500 dark:text-gray-400 text-sm font-medium leading-normal" href="#">Dashboard</a>
|
||||
<span class="text-gray-500 dark:text-gray-400 text-sm font-medium leading-normal">/</span>
|
||||
<a class="text-gray-500 dark:text-gray-400 text-sm font-medium leading-normal" href="#">Inventory</a>
|
||||
<span class="text-gray-500 dark:text-gray-400 text-sm font-medium leading-normal">/</span>
|
||||
<span class="text-gray-800 dark:text-gray-200 text-sm font-medium leading-normal">Batch Add Items</span>
|
||||
</div>
|
||||
<div class="flex flex-wrap justify-between items-center gap-4 mb-8">
|
||||
<div class="flex flex-col gap-2">
|
||||
<h1 class="text-gray-900 dark:text-white text-3xl font-bold tracking-tight">Add Items to Inventory</h1>
|
||||
<p class="text-gray-500 dark:text-gray-400 text-base font-normal leading-normal">Search and add multiple products to your stock in a single batch.</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="bg-white dark:bg-gray-900/50 p-6 rounded-xl border border-gray-200 dark:border-gray-800 mb-8 shadow-sm">
|
||||
<h2 class="text-lg font-bold text-gray-900 dark:text-white mb-4">Select Products from Catalog</h2>
|
||||
<div class="flex flex-col lg:flex-row gap-4 items-end">
|
||||
<div class="flex-1 w-full relative">
|
||||
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">Search Catalog</label>
|
||||
<div class="relative">
|
||||
<span class="material-symbols-outlined absolute left-3 top-1/2 -translate-y-1/2 text-gray-500">search</span>
|
||||
<input class="form-input w-full rounded-lg border-gray-300 dark:border-gray-700 bg-white dark:bg-gray-800 text-gray-900 dark:text-white pl-10 h-11 focus:ring-primary focus:border-primary" placeholder="Type Product Name or SKU..." type="text"/>
|
||||
</div>
|
||||
<div class="absolute top-full left-0 w-full mt-2 bg-white dark:bg-gray-800 border border-gray-200 dark:border-gray-700 rounded-xl shadow-2xl z-20 p-5 grid grid-cols-1 md:grid-cols-12 gap-6">
|
||||
<div class="md:col-span-4">
|
||||
<p class="text-xs font-semibold uppercase tracking-wider text-gray-400 mb-2">Selected Product</p>
|
||||
<div class="flex items-center gap-3">
|
||||
<div class="w-12 h-12 rounded bg-gray-100 dark:bg-gray-700 flex items-center justify-center">
|
||||
<span class="material-symbols-outlined text-gray-400">image</span>
|
||||
</div>
|
||||
<div>
|
||||
<p class="font-bold text-gray-900 dark:text-white">Smart Display Panel</p>
|
||||
<p class="text-xs text-gray-500">SKU: DISP-PNL-SD-001</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="md:col-span-2">
|
||||
<label class="block text-xs font-semibold uppercase tracking-wider text-gray-400 mb-2">Medida</label>
|
||||
<select class="form-select w-full rounded-lg border-gray-300 dark:border-gray-700 bg-white dark:bg-gray-800 text-sm focus:ring-primary">
|
||||
<option value="24">24</option>
|
||||
<option value="36">36</option>
|
||||
<option value="64">64</option>
|
||||
<option value="85">85</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="md:col-span-2">
|
||||
<label class="block text-xs font-semibold uppercase tracking-wider text-gray-400 mb-2">Resolución</label>
|
||||
<select class="form-select w-full rounded-lg border-gray-300 dark:border-gray-700 bg-white dark:bg-gray-800 text-sm focus:ring-primary">
|
||||
<option value="1080">1080</option>
|
||||
<option value="2K">2K</option>
|
||||
<option value="4K">4K</option>
|
||||
<option value="8K">8K</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="md:col-span-2">
|
||||
<label class="block text-xs font-semibold uppercase tracking-wider text-gray-400 mb-2">Qty</label>
|
||||
<input class="form-input w-full rounded-lg border-gray-300 dark:border-gray-700 bg-white dark:bg-gray-800 text-sm focus:ring-primary" type="number" value="10"/>
|
||||
</div>
|
||||
<div class="md:col-span-2 flex items-end">
|
||||
<button class="w-full bg-primary text-white h-11 px-4 rounded-lg font-bold hover:bg-primary/90 flex items-center justify-center gap-1">
|
||||
<span class="material-symbols-outlined text-lg">add</span> Add
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="bg-white dark:bg-gray-900/50 rounded-xl border border-gray-200 dark:border-gray-800 overflow-hidden shadow-sm">
|
||||
<div class="px-6 py-4 border-b border-gray-200 dark:border-gray-800 flex justify-between items-center">
|
||||
<h2 class="text-lg font-bold text-gray-900 dark:text-white">Queued Items (4)</h2>
|
||||
<button class="text-sm font-medium text-red-500 hover:text-red-600">Clear All</button>
|
||||
</div>
|
||||
<div class="overflow-x-auto">
|
||||
<table class="w-full text-left border-collapse">
|
||||
<thead>
|
||||
<tr class="bg-gray-50 dark:bg-gray-800/50">
|
||||
<th class="px-6 py-4 text-xs font-bold text-gray-500 dark:text-gray-400 uppercase tracking-wider">Product Name</th>
|
||||
<th class="px-6 py-4 text-xs font-bold text-gray-500 dark:text-gray-400 uppercase tracking-wider">SKU</th>
|
||||
<th class="px-6 py-4 text-xs font-bold text-gray-500 dark:text-gray-400 uppercase tracking-wider">Variant</th>
|
||||
<th class="px-6 py-4 text-xs font-bold text-gray-500 dark:text-gray-400 uppercase tracking-wider">Quantity</th>
|
||||
<th class="px-6 py-4 text-xs font-bold text-gray-500 dark:text-gray-400 uppercase tracking-wider">Unit</th>
|
||||
<th class="px-6 py-4 text-xs font-bold text-gray-500 dark:text-gray-400 uppercase tracking-wider text-right">Action</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="divide-y divide-gray-100 dark:divide-gray-800">
|
||||
<tr class="hover:bg-gray-50/50 dark:hover:bg-gray-800/30 transition-colors">
|
||||
<td class="px-6 py-4">
|
||||
<div class="flex items-center gap-3">
|
||||
<div class="w-8 h-8 rounded bg-gray-100 dark:bg-gray-800 overflow-hidden">
|
||||
<img alt="product" class="w-full h-full object-cover" src="https://lh3.googleusercontent.com/aida-public/AB6AXuBMbZhSSPYoHEi3akXSqZXm-T6EghACuHJJ5NYeJ8g4XJIKtqXRhCSQ1kFcvo7Txk01ryR-r6RJ61NzPLENWMywvUVnCkQglsizZG0NKPBwRFQjXvtDULkGbGFm6EPocyHGfuzKJ0EoDr601zMBI34mfPCgn9AAaRkTMj2Ize2nCUcanlGn7QJEp6BdNcmf3JFlk-51jPTXj1-mM4Pw6AGIvarhxnZqtEAji6qRF7evVTR_56c48h5if21S3jD-wVhNVp8AltVn8KEH"/>
|
||||
</div>
|
||||
<span class="font-medium">Smart Display Panel</span>
|
||||
</div>
|
||||
</td>
|
||||
<td class="px-6 py-4 text-sm text-gray-500">DISP-PNL-SD-001</td>
|
||||
<td class="px-6 py-4">
|
||||
<div class="flex flex-wrap gap-1">
|
||||
<span class="px-2.5 py-1 text-[10px] font-semibold bg-blue-50 dark:bg-blue-900/30 text-blue-600 dark:text-blue-400 rounded-full border border-blue-100 dark:border-blue-800/50 uppercase tracking-tight">Medida: 85</span>
|
||||
<span class="px-2.5 py-1 text-[10px] font-semibold bg-purple-50 dark:bg-purple-900/30 text-purple-600 dark:text-purple-400 rounded-full border border-purple-100 dark:border-purple-800/50 uppercase tracking-tight">Resolución: 4K</span>
|
||||
</div>
|
||||
</td>
|
||||
<td class="px-6 py-4">
|
||||
<input class="w-20 form-input h-8 rounded border-gray-300 dark:border-gray-700 bg-transparent text-sm" type="number" value="25"/>
|
||||
</td>
|
||||
<td class="px-6 py-4 text-sm">pcs</td>
|
||||
<td class="px-6 py-4 text-right">
|
||||
<button class="text-gray-400 hover:text-red-500 transition-colors">
|
||||
<span class="material-symbols-outlined text-xl">delete</span>
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
<tr class="hover:bg-gray-50/50 dark:hover:bg-gray-800/30 transition-colors">
|
||||
<td class="px-6 py-4">
|
||||
<div class="flex items-center gap-3">
|
||||
<div class="w-8 h-8 rounded bg-gray-100 dark:bg-gray-800 overflow-hidden">
|
||||
<img alt="product" class="w-full h-full object-cover" src="https://lh3.googleusercontent.com/aida-public/AB6AXuBBMBDEGh1exdBX1lU--TOCsUwfsFkYhrl8DxxvxgH-hKLkcuVtuGP_ZhRS5l_YXFWls08Ecr-Ic748cVHqHexFMMzYTPH8YL2s9OISDgsMIwcDOPxitrPUn3RBCD_krFC8SrBXBeNC8ilcFdg_JCwD5BW5gaYjAAEUMFXyM1vd-YT27KyubBk1IKATsMdDkH-NtwAHcrPgNoXsRaAcubBQiebCG1hmxKVW3fAXoDoSqywrxhwZ7cmIuo7BTThJJ1KcczxQh1jYh_w4"/>
|
||||
</div>
|
||||
<span class="font-medium">Mechanical Keyboard RGB</span>
|
||||
</div>
|
||||
</td>
|
||||
<td class="px-6 py-4 text-sm text-gray-500">KYBD-RGB-US-02</td>
|
||||
<td class="px-6 py-4">
|
||||
<span class="px-2.5 py-1 text-xs font-medium bg-gray-100 dark:bg-gray-800 rounded-full border border-gray-200 dark:border-gray-700">Red Switch</span>
|
||||
</td>
|
||||
<td class="px-6 py-4">
|
||||
<input class="w-20 form-input h-8 rounded border-gray-300 dark:border-gray-700 bg-transparent text-sm" type="number" value="12"/>
|
||||
</td>
|
||||
<td class="px-6 py-4 text-sm">pcs</td>
|
||||
<td class="px-6 py-4 text-right">
|
||||
<button class="text-gray-400 hover:text-red-500 transition-colors">
|
||||
<span class="material-symbols-outlined text-xl">delete</span>
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
<tr class="hover:bg-gray-50/50 dark:hover:bg-gray-800/30 transition-colors">
|
||||
<td class="px-6 py-4">
|
||||
<div class="flex items-center gap-3">
|
||||
<div class="w-8 h-8 rounded bg-gray-100 dark:bg-gray-800 overflow-hidden">
|
||||
<img alt="product" class="w-full h-full object-cover" src="https://lh3.googleusercontent.com/aida-public/AB6AXuAsb9nqDON7PeKGYYAU8hmHn7B2l3waP_ewMQTpsgnpJe2vFiY-k-zncE5VEf186tDzlTwVPA6-1SgAFagugq9Anw2WXloUFTNwOrOHgAi-MNhgs0FuxRKF8XMLk6W_u6YLF5xWh3K2q317JXFSUWtURtxCi8pYM4YtHYEyuT2aIrhbAnI2LQ7ja8DvSdvpO6t1IJ6HE9RqlnAFXuLG22igxiekKT2NjRpuK5Zzu_90rBPEjb0KitG9DwiBjzKa702pccHPRqupgXke"/>
|
||||
</div>
|
||||
<span class="font-medium">USB-C Charging Cable 2m</span>
|
||||
</div>
|
||||
</td>
|
||||
<td class="px-6 py-4 text-sm text-gray-500">CBL-USBC-2M</td>
|
||||
<td class="px-6 py-4">
|
||||
<span class="px-2.5 py-1 text-xs font-medium bg-gray-100 dark:bg-gray-800 rounded-full border border-gray-200 dark:border-gray-700">Braided</span>
|
||||
</td>
|
||||
<td class="px-6 py-4">
|
||||
<input class="w-20 form-input h-8 rounded border-gray-300 dark:border-gray-700 bg-transparent text-sm" type="number" value="100"/>
|
||||
</td>
|
||||
<td class="px-6 py-4 text-sm">pcs</td>
|
||||
<td class="px-6 py-4 text-right">
|
||||
<button class="text-gray-400 hover:text-red-500 transition-colors">
|
||||
<span class="material-symbols-outlined text-xl">delete</span>
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
<tr class="hover:bg-gray-50/50 dark:hover:bg-gray-800/30 transition-colors">
|
||||
<td class="px-6 py-4">
|
||||
<div class="flex items-center gap-3">
|
||||
<div class="w-8 h-8 rounded bg-gray-100 dark:bg-gray-800 overflow-hidden">
|
||||
<img alt="product" class="w-full h-full object-cover" src="https://lh3.googleusercontent.com/aida-public/AB6AXuD1PtrsyrvB9KtL5DHjKotvi92lM1NsfdzyEkoW5DAEfSYoPI8MNsCJZDBMtrz6rXnIWApiymAIlp7Yq5P6JTQqpwaNJVw5I4G3KiefVME-D_jR_0iTDtPBxljmlcohkhIrSfK77wV9Wq1KL0yvjCB2UuDewIxBOs0RUl7_RbISQxYaSzVe9GbqnwI5cpj_N_1faxUA89ATStV6GovMDXFA881MUGKd4_ox_CzEG5zzPZYRe9JF7M33YH-rRHdxlpQWtRH9yw4X8sKp"/>
|
||||
</div>
|
||||
<span class="font-medium">Smart Display Panel</span>
|
||||
</div>
|
||||
</td>
|
||||
<td class="px-6 py-4 text-sm text-gray-500">DISP-PNL-SD-001</td>
|
||||
<td class="px-6 py-4">
|
||||
<div class="flex flex-wrap gap-1">
|
||||
<span class="px-2.5 py-1 text-[10px] font-semibold bg-blue-50 dark:bg-blue-900/30 text-blue-600 dark:text-blue-400 rounded-full border border-blue-100 dark:border-blue-800/50 uppercase tracking-tight">Medida: 36</span>
|
||||
<span class="px-2.5 py-1 text-[10px] font-semibold bg-purple-50 dark:bg-purple-900/30 text-purple-600 dark:text-purple-400 rounded-full border border-purple-100 dark:border-purple-800/50 uppercase tracking-tight">Resolución: 1080</span>
|
||||
</div>
|
||||
</td>
|
||||
<td class="px-6 py-4">
|
||||
<input class="w-20 form-input h-8 rounded border-gray-300 dark:border-gray-700 bg-transparent text-sm" type="number" value="5"/>
|
||||
</td>
|
||||
<td class="px-6 py-4 text-sm">pcs</td>
|
||||
<td class="px-6 py-4 text-right">
|
||||
<button class="text-gray-400 hover:text-red-500 transition-colors">
|
||||
<span class="material-symbols-outlined text-xl">delete</span>
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<div class="p-6 bg-gray-50/50 dark:bg-gray-800/50 border-t border-gray-200 dark:border-gray-800 text-sm text-gray-500 dark:text-gray-400">
|
||||
Total items to be added: <span class="font-bold text-gray-900 dark:text-white">142 units</span> across 4 unique products.
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<footer class="fixed bottom-0 right-0 left-64 bg-white dark:bg-background-dark border-t border-gray-200 dark:border-gray-800 p-4 px-10 flex justify-between items-center z-20 shadow-[0_-4px_6px_-1px_rgba(0,0,0,0.1)]">
|
||||
<div class="flex items-center gap-6">
|
||||
<div class="flex items-center gap-2">
|
||||
<span class="text-xs font-medium text-gray-500 uppercase tracking-widest">Storage Location</span>
|
||||
<select class="form-select text-sm rounded-lg border-gray-300 dark:border-gray-700 bg-gray-50 dark:bg-gray-800 focus:ring-primary h-9">
|
||||
<option>Main Warehouse (A1)</option>
|
||||
<option>South Storage (B4)</option>
|
||||
<option>Cold Storage (C2)</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex items-center gap-4">
|
||||
<button class="flex min-w-[100px] cursor-pointer items-center justify-center rounded-lg h-11 px-6 bg-gray-200 dark:bg-gray-800 text-gray-800 dark:text-gray-200 text-sm font-bold leading-normal hover:bg-gray-300 dark:hover:bg-gray-700 transition-all">
|
||||
Cancel
|
||||
</button>
|
||||
<button class="flex min-w-[200px] cursor-pointer items-center justify-center rounded-lg h-11 px-8 bg-primary text-white text-sm font-bold leading-normal tracking-[0.015em] hover:bg-primary/90 shadow-lg shadow-primary/20 transition-all">
|
||||
Confirm and Save Inventory
|
||||
</button>
|
||||
</div>
|
||||
</footer>
|
||||
</main>
|
||||
</div>
|
||||
|
||||
</body></html>
|
||||
<body class="bg-background-light dark:bg-background-dark font-display min-h-screen">
|
||||
<div class="flex h-screen overflow-hidden">
|
||||
<aside
|
||||
class="w-64 bg-white dark:bg-slate-900 border-r border-slate-200 dark:border-slate-800 flex flex-col shrink-0">
|
||||
<div class="p-6 flex items-center gap-3">
|
||||
<div class="w-10 h-10 bg-primary rounded-lg flex items-center justify-center text-white">
|
||||
<span class="material-symbols-outlined">inventory_2</span>
|
||||
</div>
|
||||
<div>
|
||||
<h1 class="text-slate-900 dark:text-white text-base font-bold leading-none">Logistics Pro</h1>
|
||||
<p class="text-slate-500 dark:text-slate-400 text-xs mt-1">Warehouse Admin</p>
|
||||
</div>
|
||||
</div>
|
||||
<nav class="flex-1 px-4 py-4 space-y-1">
|
||||
<a class="flex items-center gap-3 px-3 py-2 text-slate-600 dark:text-slate-400 hover:bg-slate-50 dark:hover:bg-slate-800 rounded-lg transition-colors"
|
||||
href="#">
|
||||
<span class="material-symbols-outlined">dashboard</span>
|
||||
<span class="text-sm font-medium">Dashboard</span>
|
||||
</a>
|
||||
<a class="flex items-center gap-3 px-3 py-2 text-slate-600 dark:text-slate-400 hover:bg-slate-50 dark:hover:bg-slate-800 rounded-lg transition-colors"
|
||||
href="#">
|
||||
<span class="material-symbols-outlined">package_2</span>
|
||||
<span class="text-sm font-medium">Inventario</span>
|
||||
</a>
|
||||
<a class="flex items-center gap-3 px-3 py-2 bg-primary/10 text-primary rounded-lg transition-colors"
|
||||
href="#">
|
||||
<span class="material-symbols-outlined"
|
||||
style="font-variation-settings: 'FILL' 1">shopping_cart</span>
|
||||
<span class="text-sm font-medium">Órdenes de Compra</span>
|
||||
</a>
|
||||
<a class="flex items-center gap-3 px-3 py-2 text-slate-600 dark:text-slate-400 hover:bg-slate-50 dark:hover:bg-slate-800 rounded-lg transition-colors"
|
||||
href="#">
|
||||
<span class="material-symbols-outlined">warehouse</span>
|
||||
<span class="text-sm font-medium">Almacenes</span>
|
||||
</a>
|
||||
<a class="flex items-center gap-3 px-3 py-2 text-slate-600 dark:text-slate-400 hover:bg-slate-50 dark:hover:bg-slate-800 rounded-lg transition-colors"
|
||||
href="#">
|
||||
<span class="material-symbols-outlined">bar_chart</span>
|
||||
<span class="text-sm font-medium">Reportes</span>
|
||||
</a>
|
||||
</nav>
|
||||
<div class="p-4 border-t border-slate-200 dark:border-slate-800">
|
||||
<div class="flex items-center gap-3 px-3 py-2">
|
||||
<div class="size-8 rounded-full bg-slate-200 dark:bg-slate-700 bg-cover bg-center"
|
||||
style="background-image: url('https://lh3.googleusercontent.com/aida-public/AB6AXuDa90XPiU0x0vWPCwSkY-b0XPWxuaglsKdsGDuVxfyQKkMYEU5M9ZhbSQkCrmRlRjYEiSLJ6gAeZWIORr6MFvWrYq-WoJFzzEUf18zJjkqmJK9oU270B7r6BRVz-ynoNSS6rUNF4_PE2az4uQgysTtym0Akce2JSv5s077kdSfpUvEYPzeMM4Oi9SSMG9kAIjTiQbCrRVUcE81w8a1TwD-JHuzmXutnjRS3BaPKKcT57SiJhYhoSkL1waAxm7SWR_fWDpTzxbnDlHnl')">
|
||||
</div>
|
||||
<div class="flex-1 min-w-0">
|
||||
<p class="text-sm font-medium text-slate-900 dark:text-white truncate">Carlos Ruiz</p>
|
||||
<p class="text-xs text-slate-500 truncate">Supervisor</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</aside>
|
||||
<main class="flex-1 flex flex-col min-w-0 overflow-hidden">
|
||||
<header
|
||||
class="h-16 bg-white dark:bg-slate-900 border-b border-slate-200 dark:border-slate-800 flex items-center justify-between px-8 shrink-0">
|
||||
<div class="flex items-center gap-4">
|
||||
<div class="flex items-center gap-2 text-slate-400 text-sm font-medium">
|
||||
<a class="hover:text-primary" href="#">Inicio</a>
|
||||
<span class="material-symbols-outlined text-xs">chevron_right</span>
|
||||
<a class="hover:text-primary" href="#">Órdenes de Compra</a>
|
||||
<span class="material-symbols-outlined text-xs">chevron_right</span>
|
||||
<span class="text-slate-900 dark:text-white">Registrar Entrada (Multialmacén)</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex items-center gap-4">
|
||||
<div class="relative w-64">
|
||||
<span
|
||||
class="material-symbols-outlined absolute left-3 top-1/2 -translate-y-1/2 text-slate-400 text-lg">search</span>
|
||||
<input
|
||||
class="w-full pl-10 pr-4 py-1.5 bg-slate-100 dark:bg-slate-800 border-none rounded-lg text-sm focus:ring-2 focus:ring-primary/20"
|
||||
placeholder="Buscar PO, SKU..." type="text" />
|
||||
</div>
|
||||
<button class="p-2 text-slate-400 hover:text-slate-600 dark:hover:text-white">
|
||||
<span class="material-symbols-outlined">notifications</span>
|
||||
</button>
|
||||
<button class="p-2 text-slate-400 hover:text-slate-600 dark:hover:text-white">
|
||||
<span class="material-symbols-outlined">settings</span>
|
||||
</button>
|
||||
</div>
|
||||
</header>
|
||||
<div class="flex-1 overflow-y-auto p-8">
|
||||
<div class="max-w-7xl mx-auto space-y-6">
|
||||
<div class="flex flex-col md:flex-row md:items-end justify-between gap-4">
|
||||
<div>
|
||||
<h2 class="text-3xl font-black text-slate-900 dark:text-white tracking-tight">Registrar
|
||||
Entrada de Mercancía</h2>
|
||||
<p class="text-slate-500 dark:text-slate-400 mt-1 flex items-center gap-2">
|
||||
<span class="material-symbols-outlined text-base">receipt_long</span>
|
||||
Purchase Order #ORD-2023-001 | <span class="text-primary font-semibold">Distribución
|
||||
Multi-Almacén</span>
|
||||
</p>
|
||||
</div>
|
||||
<div class="flex items-center gap-3">
|
||||
<div
|
||||
class="flex gap-4 bg-white dark:bg-slate-900 px-4 py-2 rounded-lg border border-slate-200 dark:border-slate-800 shadow-sm">
|
||||
<div class="text-center">
|
||||
<p class="text-[10px] text-slate-500 uppercase tracking-wider font-bold">Total Items
|
||||
PO</p>
|
||||
<p class="text-lg font-black text-slate-900 dark:text-white leading-tight">12</p>
|
||||
</div>
|
||||
<div class="w-px bg-slate-200 dark:bg-slate-800 h-8 self-center"></div>
|
||||
<div class="text-center">
|
||||
<p class="text-[10px] text-slate-500 uppercase tracking-wider font-bold">Recibidos
|
||||
</p>
|
||||
<p class="text-lg font-black text-primary leading-tight">0</p>
|
||||
</div>
|
||||
</div>
|
||||
<button
|
||||
class="inline-flex items-center gap-2 px-4 py-2 bg-slate-200 dark:bg-slate-800 text-slate-700 dark:text-slate-200 font-bold text-sm rounded-lg hover:bg-slate-300 transition-colors">
|
||||
<span class="material-symbols-outlined text-lg">visibility</span>
|
||||
Ver Detalles
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="bg-white dark:bg-slate-900 rounded-xl border border-slate-200 dark:border-slate-800 shadow-sm overflow-hidden">
|
||||
<div
|
||||
class="px-6 py-4 border-b border-slate-200 dark:border-slate-800 bg-slate-50/50 dark:bg-slate-800/50 flex justify-between items-center">
|
||||
<h3 class="font-bold text-slate-900 dark:text-white">Productos de la Orden</h3>
|
||||
<span class="text-xs text-slate-500 flex items-center gap-1">
|
||||
<span class="material-symbols-outlined text-sm">info</span>
|
||||
Seleccione el almacén de destino por cada producto
|
||||
</span>
|
||||
</div>
|
||||
<div class="overflow-x-auto">
|
||||
<table class="w-full border-collapse">
|
||||
<thead>
|
||||
<tr
|
||||
class="text-left text-xs font-bold text-slate-500 dark:text-slate-400 uppercase tracking-wider bg-slate-50 dark:bg-slate-800/30">
|
||||
<th class="px-6 py-4">Producto</th>
|
||||
<th class="px-6 py-4">SKU / Ref</th>
|
||||
<th class="px-6 py-4 text-center">Cant. PO</th>
|
||||
<th class="px-6 py-4 text-center">Recibida</th>
|
||||
<th class="px-6 py-4">Almacén de Destino</th>
|
||||
<th class="px-6 py-4">Acciones</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="divide-y divide-slate-200 dark:divide-slate-800">
|
||||
<tr class="group hover:bg-slate-50 dark:hover:bg-slate-800/50 transition-colors">
|
||||
<td class="px-6 py-4">
|
||||
<div class="flex items-center gap-3">
|
||||
<div
|
||||
class="size-10 bg-slate-100 dark:bg-slate-800 rounded-lg flex items-center justify-center text-slate-400">
|
||||
<span class="material-symbols-outlined">inventory</span>
|
||||
</div>
|
||||
<div class="flex flex-col">
|
||||
<span
|
||||
class="text-sm font-semibold text-slate-900 dark:text-white">Cables
|
||||
de Red Cat6 2m</span>
|
||||
<span class="text-xs text-slate-500">Accesorios Networking</span>
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
<td class="px-6 py-4 text-sm text-slate-600 dark:text-slate-400">NET-C62M-WH
|
||||
</td>
|
||||
<td
|
||||
class="px-6 py-4 text-center text-sm font-bold text-slate-900 dark:text-white">
|
||||
50</td>
|
||||
<td class="px-6 py-4">
|
||||
<div class="flex justify-center">
|
||||
<input
|
||||
class="w-20 text-center px-2 py-1 bg-slate-100 dark:bg-slate-800 border-none rounded-lg text-sm font-bold focus:ring-2 focus:ring-primary"
|
||||
max="50" min="0" type="number" value="0" />
|
||||
</div>
|
||||
</td>
|
||||
<td class="px-6 py-4">
|
||||
<select
|
||||
class="w-full px-3 py-1.5 bg-slate-50 dark:bg-slate-800 border border-slate-200 dark:border-slate-700 rounded-lg text-xs focus:ring-primary focus:border-primary">
|
||||
<option value="">Seleccione Almacén...</option>
|
||||
<option selected="" value="1">Almacén Principal (CDMX)</option>
|
||||
<option value="2">Bodega Regional (MTY)</option>
|
||||
<option value="3">CEDIS (GDL)</option>
|
||||
</select>
|
||||
</td>
|
||||
<td class="px-6 py-4">
|
||||
<span
|
||||
class="inline-flex items-center px-2.5 py-0.5 rounded-full text-[10px] font-bold bg-slate-100 text-slate-600 dark:bg-slate-800 dark:text-slate-400 uppercase">Estándar</span>
|
||||
</td>
|
||||
</tr>
|
||||
<tr class="bg-primary/[0.02] dark:bg-primary/[0.05]">
|
||||
<td class="px-6 py-4 border-l-4 border-primary">
|
||||
<div class="flex items-center gap-3">
|
||||
<div
|
||||
class="size-10 bg-primary/10 rounded-lg flex items-center justify-center text-primary">
|
||||
<span class="material-symbols-outlined">laptop_mac</span>
|
||||
</div>
|
||||
<div class="flex flex-col">
|
||||
<span
|
||||
class="text-sm font-semibold text-slate-900 dark:text-white">MacBook
|
||||
Pro M2 14"</span>
|
||||
<span class="text-xs text-slate-500">Equipos de Cómputo</span>
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
<td class="px-6 py-4 text-sm text-slate-600 dark:text-slate-400">LAP-MBP14-M2
|
||||
</td>
|
||||
<td
|
||||
class="px-6 py-4 text-center text-sm font-bold text-slate-900 dark:text-white">
|
||||
5</td>
|
||||
<td class="px-6 py-4 text-center">
|
||||
<span class="text-sm font-black text-primary">1 / 5</span>
|
||||
</td>
|
||||
<td class="px-6 py-4">
|
||||
<div class="text-xs text-slate-400 italic">Asignación por número de serie
|
||||
</div>
|
||||
</td>
|
||||
<td class="px-6 py-4">
|
||||
<button
|
||||
class="inline-flex items-center gap-2 px-3 py-1.5 bg-primary text-white text-xs font-bold rounded-lg hover:bg-primary/90 transition-all shadow-sm">
|
||||
<span class="material-symbols-outlined text-sm">barcode_scanner</span>
|
||||
GESTIONAR SERIES
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
<tr class="bg-primary/[0.02] dark:bg-primary/[0.05]">
|
||||
<td class="px-10 pb-6 pt-2" colspan="6">
|
||||
<div
|
||||
class="bg-white dark:bg-slate-800 rounded-xl border border-primary/20 p-5 shadow-sm">
|
||||
<div
|
||||
class="flex items-center justify-between mb-4 pb-3 border-b border-slate-100 dark:border-slate-700">
|
||||
<div>
|
||||
<h4
|
||||
class="text-sm font-bold text-slate-700 dark:text-slate-200">
|
||||
Entrada de Números de Serie (4 Pendientes)</h4>
|
||||
<p class="text-[11px] text-slate-500">Defina el almacén para
|
||||
cada equipo escaneado</p>
|
||||
</div>
|
||||
<div
|
||||
class="flex items-center gap-2 text-xs text-primary font-bold bg-primary/10 px-3 py-1.5 rounded-full">
|
||||
<span class="material-symbols-outlined text-sm">barcode</span>
|
||||
Listo para escanear
|
||||
</div>
|
||||
</div>
|
||||
<div class="grid grid-cols-1 lg:grid-cols-12 gap-6">
|
||||
<div class="lg:col-span-5 space-y-4">
|
||||
<div>
|
||||
<label
|
||||
class="block text-[10px] font-bold text-slate-500 uppercase mb-1">Número
|
||||
de Serie</label>
|
||||
<div class="relative">
|
||||
<span
|
||||
class="material-symbols-outlined absolute left-3 top-1/2 -translate-y-1/2 text-slate-400">qr_code_scanner</span>
|
||||
<input
|
||||
class="w-full pl-10 pr-4 py-2 bg-slate-50 dark:bg-slate-700 border border-slate-200 dark:border-slate-600 rounded-lg text-sm focus:ring-primary focus:border-primary"
|
||||
placeholder="Escanear o escribir serie..."
|
||||
type="text" />
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<label
|
||||
class="block text-[10px] font-bold text-slate-500 uppercase mb-1">Almacén
|
||||
Destino</label>
|
||||
<select
|
||||
class="w-full px-3 py-2 bg-slate-50 dark:bg-slate-700 border border-slate-200 dark:border-slate-600 rounded-lg text-sm focus:ring-primary focus:border-primary">
|
||||
<option value="1">Almacén Principal (CDMX)</option>
|
||||
<option value="2">Bodega Regional (MTY)</option>
|
||||
<option value="3">CEDIS (GDL)</option>
|
||||
</select>
|
||||
</div>
|
||||
<button
|
||||
class="w-full py-2 bg-slate-900 dark:bg-slate-700 text-white text-xs font-bold rounded-lg hover:bg-black transition-colors">
|
||||
Registrar Serie
|
||||
</button>
|
||||
</div>
|
||||
<div class="lg:col-span-7">
|
||||
<div
|
||||
class="bg-slate-50 dark:bg-slate-900/50 rounded-lg border border-slate-200 dark:border-slate-700 overflow-hidden">
|
||||
<table class="w-full text-xs">
|
||||
<thead class="bg-slate-100 dark:bg-slate-800">
|
||||
<tr>
|
||||
<th
|
||||
class="px-3 py-2 text-left font-bold text-slate-500 uppercase">
|
||||
Nº Serie</th>
|
||||
<th
|
||||
class="px-3 py-2 text-left font-bold text-slate-500 uppercase">
|
||||
Almacén Destino</th>
|
||||
<th class="px-3 py-2 text-right"></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody
|
||||
class="divide-y divide-slate-200 dark:divide-slate-700">
|
||||
<tr>
|
||||
<td
|
||||
class="px-3 py-2 font-mono text-slate-900 dark:text-white">
|
||||
SN-LAP-M2-00192</td>
|
||||
<td class="px-3 py-2">
|
||||
<span
|
||||
class="inline-flex items-center gap-1.5">
|
||||
<span
|
||||
class="w-2 h-2 rounded-full bg-emerald-500"></span>
|
||||
<span
|
||||
class="text-slate-600 dark:text-slate-300">Almacén
|
||||
Principal (CDMX)</span>
|
||||
</span>
|
||||
</td>
|
||||
<td class="px-3 py-2 text-right">
|
||||
<button
|
||||
class="text-slate-400 hover:text-red-500 transition-colors">
|
||||
<span
|
||||
class="material-symbols-outlined text-lg">delete</span>
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="px-3 py-4 text-center text-slate-400 italic bg-slate-50/50 dark:bg-slate-800/30"
|
||||
colspan="3">
|
||||
Escanee el siguiente equipo para asignar
|
||||
almacén...
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
<tr class="group hover:bg-slate-50 dark:hover:bg-slate-800/50 transition-colors">
|
||||
<td class="px-6 py-4">
|
||||
<div class="flex items-center gap-3">
|
||||
<div
|
||||
class="size-10 bg-slate-100 dark:bg-slate-800 rounded-lg flex items-center justify-center text-slate-400">
|
||||
<span class="material-symbols-outlined">print</span>
|
||||
</div>
|
||||
<div class="flex flex-col">
|
||||
<span
|
||||
class="text-sm font-semibold text-slate-900 dark:text-white">Impresora
|
||||
Zebra ZT411</span>
|
||||
<span class="text-xs text-slate-500">Hardware Almacén</span>
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
<td class="px-6 py-4 text-sm text-slate-600 dark:text-slate-400">PRN-ZB411-IND
|
||||
</td>
|
||||
<td
|
||||
class="px-6 py-4 text-center text-sm font-bold text-slate-900 dark:text-white">
|
||||
2</td>
|
||||
<td class="px-6 py-4 text-center text-sm font-bold text-slate-400">0 / 2</td>
|
||||
<td class="px-6 py-4">
|
||||
<div class="text-xs text-slate-400 italic">Asignación por serie requerida
|
||||
</div>
|
||||
</td>
|
||||
<td class="px-6 py-4">
|
||||
<button
|
||||
class="inline-flex items-center gap-2 px-3 py-1.5 border border-slate-200 dark:border-slate-700 text-slate-600 dark:text-slate-300 text-xs font-bold rounded-lg hover:bg-slate-100 dark:hover:bg-slate-800 transition-colors">
|
||||
<span class="material-symbols-outlined text-sm">barcode_scanner</span>
|
||||
GESTIONAR SERIES
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
<div class="grid grid-cols-1 md:grid-cols-3 gap-6">
|
||||
<div
|
||||
class="md:col-span-2 bg-amber-50 dark:bg-amber-900/20 border border-amber-200 dark:border-amber-900/50 p-4 rounded-xl flex items-start gap-4">
|
||||
<span class="material-symbols-outlined text-amber-600 dark:text-amber-500">warning</span>
|
||||
<div>
|
||||
<p class="text-sm font-bold text-amber-800 dark:text-amber-400">Pendiente de Validación
|
||||
y Distribución</p>
|
||||
<p class="text-sm text-amber-700 dark:text-amber-500/80">Faltan registrar 6 números de
|
||||
serie y asignar almacén de destino a 1 item de tipo estándar antes de confirmar.</p>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="bg-white dark:bg-slate-900 p-4 rounded-xl border border-slate-200 dark:border-slate-800 shadow-sm">
|
||||
<h4 class="text-xs font-bold text-slate-500 uppercase tracking-wider mb-3">Resumen de
|
||||
Destinos</h4>
|
||||
<div class="space-y-2">
|
||||
<div class="flex justify-between text-xs">
|
||||
<span class="text-slate-600 dark:text-slate-400">Almacén Principal (CDMX)</span>
|
||||
<span class="font-bold text-slate-900 dark:text-white">1 Unidad</span>
|
||||
</div>
|
||||
<div class="flex justify-between text-xs">
|
||||
<span class="text-slate-600 dark:text-slate-400">Bodega Regional (MTY)</span>
|
||||
<span class="font-bold text-slate-900 dark:text-white">0 Unidades</span>
|
||||
</div>
|
||||
<div class="flex justify-between text-xs">
|
||||
<span class="text-slate-600 dark:text-slate-400">CEDIS (GDL)</span>
|
||||
<span class="font-bold text-slate-900 dark:text-white">0 Unidades</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<footer
|
||||
class="h-20 bg-white dark:bg-slate-900 border-t border-slate-200 dark:border-slate-800 flex items-center justify-end px-8 gap-4 shrink-0">
|
||||
<button
|
||||
class="px-6 py-2.5 text-sm font-bold text-slate-600 dark:text-slate-400 hover:text-slate-900 dark:hover:text-white transition-colors">
|
||||
Cancelar
|
||||
</button>
|
||||
<button
|
||||
class="px-8 py-2.5 bg-primary text-white text-sm font-bold rounded-lg shadow-lg shadow-primary/20 hover:bg-primary/90 transition-all opacity-50 cursor-not-allowed">
|
||||
Confirmar Recepción Multi-Almacén
|
||||
</button>
|
||||
</footer>
|
||||
</main>
|
||||
</div>
|
||||
|
||||
</body>
|
||||
|
||||
</html>
|
||||
@ -1,364 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html class="light" lang="en"><head>
|
||||
<meta charset="utf-8"/>
|
||||
<meta content="width=device-width, initial-scale=1.0" name="viewport"/>
|
||||
<title>WMS - Batch Add Inventory Items</title>
|
||||
<script src="https://cdn.tailwindcss.com?plugins=forms,container-queries"></script>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800;900&display=swap" rel="stylesheet"/>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:wght,FILL@100..700,0..1&display=swap" rel="stylesheet"/>
|
||||
<script>
|
||||
tailwind.config = {
|
||||
darkMode: "class",
|
||||
theme: {
|
||||
extend: {
|
||||
colors: {
|
||||
"primary": "#137fec",
|
||||
"background-light": "#f6f7f8",
|
||||
"background-dark": "#101922",
|
||||
},
|
||||
fontFamily: {
|
||||
"display": ["Inter", "sans-serif"]
|
||||
},
|
||||
borderRadius: {
|
||||
"DEFAULT": "0.25rem",
|
||||
"lg": "0.5rem",
|
||||
"xl": "0.75rem",
|
||||
"full": "9999px"
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
<style type="text/tailwindcss">
|
||||
@layer utilities {
|
||||
.scrollbar-hide::-webkit-scrollbar {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
.material-symbols-outlined {
|
||||
font-variation-settings: 'FILL' 0, 'wght' 400, 'GRAD' 0, 'opsz' 24;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body class="bg-background-light dark:bg-background-dark font-display text-gray-800 dark:text-gray-200 overflow-hidden">
|
||||
<div class="flex h-screen w-full relative">
|
||||
<aside class="flex w-64 flex-col border-r border-gray-200 dark:border-gray-800 bg-white dark:bg-background-dark">
|
||||
<div class="flex h-full flex-col justify-between p-4">
|
||||
<div class="flex flex-col gap-4">
|
||||
<div class="flex items-center gap-3 p-2">
|
||||
<div class="bg-center bg-no-repeat aspect-square bg-cover rounded-full size-10" style='background-image: url("https://lh3.googleusercontent.com/aida-public/AB6AXuCztlHhjKvu2qkn2Xi2zFHagNsNToKwcTg3vQr0KtTqBCo13dK1yyz9HzB2uLCiciLyDfnrf7pREvdblPqCcUiN0HqlSbkFwY1dpQLMbJ4hmpVgHVWaLaUCMXju06qyGQSdg2ChGVcbTQIrk-RNI2-hDOFnfrI1PD89RNSsByXGRsdkYWSyEYFOFk7bT4l7aIaasB6cdVxDfNwJdvVx15wb7-qOHZHFTPMbrkkzmjGec-f7iVqTi5U1ykNDclBSezBM97TfXajTwRJE");'></div>
|
||||
<div class="flex flex-col">
|
||||
<h1 class="text-gray-900 dark:text-white text-base font-medium leading-normal">Admin User</h1>
|
||||
<p class="text-gray-500 dark:text-gray-400 text-sm font-normal leading-normal">Warehouse Manager</p>
|
||||
</div>
|
||||
</div>
|
||||
<nav class="flex flex-col gap-2">
|
||||
<a class="flex items-center gap-3 px-3 py-2 text-gray-700 dark:text-gray-300 rounded-lg hover:bg-gray-100 dark:hover:bg-gray-800" href="#">
|
||||
<span class="material-symbols-outlined">dashboard</span>
|
||||
<p class="text-sm font-medium leading-normal">Dashboard</p>
|
||||
</a>
|
||||
<a class="flex items-center gap-3 px-3 py-2 rounded-lg bg-primary/10 text-primary dark:bg-primary/20" href="#">
|
||||
<span class="material-symbols-outlined">warehouse</span>
|
||||
<p class="text-sm font-medium leading-normal">Inventory</p>
|
||||
</a>
|
||||
<a class="flex items-center gap-3 px-3 py-2 text-gray-700 dark:text-gray-300 rounded-lg hover:bg-gray-100 dark:hover:bg-gray-800" href="#">
|
||||
<span class="material-symbols-outlined">receipt_long</span>
|
||||
<p class="text-sm font-medium leading-normal">Orders</p>
|
||||
</a>
|
||||
<a class="flex items-center gap-3 px-3 py-2 text-gray-700 dark:text-gray-300 rounded-lg hover:bg-gray-100 dark:hover:bg-gray-800" href="#">
|
||||
<span class="material-symbols-outlined">local_shipping</span>
|
||||
<p class="text-sm font-medium leading-normal">Suppliers</p>
|
||||
</a>
|
||||
<a class="flex items-center gap-3 px-3 py-2 text-gray-700 dark:text-gray-300 rounded-lg hover:bg-gray-100 dark:hover:bg-gray-800" href="#">
|
||||
<span class="material-symbols-outlined">pie_chart</span>
|
||||
<p class="text-sm font-medium leading-normal">Reports</p>
|
||||
</a>
|
||||
</nav>
|
||||
</div>
|
||||
<div class="flex flex-col gap-2">
|
||||
<nav class="flex flex-col gap-1">
|
||||
<a class="flex items-center gap-3 px-3 py-2 text-gray-700 dark:text-gray-300 rounded-lg hover:bg-gray-100 dark:hover:bg-gray-800" href="#">
|
||||
<span class="material-symbols-outlined">settings</span>
|
||||
<p class="text-sm font-medium leading-normal">Settings</p>
|
||||
</a>
|
||||
<a class="flex items-center gap-3 px-3 py-2 text-gray-700 dark:text-gray-300 rounded-lg hover:bg-gray-100 dark:hover:bg-gray-800" href="#">
|
||||
<span class="material-symbols-outlined">help</span>
|
||||
<p class="text-sm font-medium leading-normal">Help</p>
|
||||
</a>
|
||||
</nav>
|
||||
<button class="flex min-w-[84px] cursor-pointer items-center justify-center overflow-hidden rounded-lg h-10 px-4 bg-gray-200 dark:bg-gray-800 text-gray-800 dark:text-gray-200 text-sm font-bold leading-normal hover:bg-gray-300 dark:hover:bg-gray-700">
|
||||
<span class="truncate">Logout</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</aside>
|
||||
<main class="flex-1 flex flex-col h-screen overflow-hidden">
|
||||
<header class="flex flex-none items-center justify-between whitespace-nowrap border-b border-solid border-gray-200 dark:border-gray-800 px-10 py-3 bg-white dark:bg-background-dark z-10">
|
||||
<div class="flex items-center gap-4 text-gray-900 dark:text-white">
|
||||
<div class="size-6 text-primary">
|
||||
<svg fill="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M21.435 7.182a.75.75 0 0 0-.87-.11L12 10.435 3.435 7.072a.75.75 0 0 0-.87.11.75.75 0 0 0-.11.87l2.122 7.878a.75.75 0 0 0 .869.59l6-1.635a.75.75 0 0 0 .108 0l6 1.635a.75.75 0 0 0 .87-.59l2.12-7.878a.75.75 0 0 0-.11-.87zM12 12.18l-5.693-1.55L12 4.288l5.693 6.342L12 12.18z"></path>
|
||||
</svg>
|
||||
</div>
|
||||
<h2 class="text-lg font-bold leading-tight tracking-[-0.015em]">WMS Dashboard</h2>
|
||||
</div>
|
||||
<div class="flex flex-1 justify-end items-center gap-4">
|
||||
<label class="relative grow max-w-sm">
|
||||
<span class="material-symbols-outlined absolute left-3 top-1/2 -translate-y-1/2 text-gray-500">search</span>
|
||||
<input class="form-input w-full rounded-lg border-none bg-gray-100 dark:bg-gray-800 h-10 pl-10 pr-4 text-sm text-gray-900 dark:text-white placeholder:text-gray-500 focus:ring-primary" placeholder="Search items, orders..." value=""/>
|
||||
</label>
|
||||
<div class="flex gap-2">
|
||||
<button class="flex items-center justify-center rounded-lg h-10 w-10 bg-gray-100 dark:bg-gray-800 text-gray-800 dark:text-gray-200 hover:bg-gray-200 dark:hover:bg-gray-700">
|
||||
<span class="material-symbols-outlined text-xl">notifications</span>
|
||||
</button>
|
||||
<button class="flex items-center justify-center rounded-lg h-10 w-10 bg-gray-100 dark:bg-gray-800 text-gray-800 dark:text-gray-200 hover:bg-gray-200 dark:hover:bg-gray-700">
|
||||
<span class="material-symbols-outlined text-xl">help_outline</span>
|
||||
</button>
|
||||
</div>
|
||||
<div class="bg-center bg-no-repeat aspect-square bg-cover rounded-full size-10" style='background-image: url("https://lh3.googleusercontent.com/aida-public/AB6AXuBNpxncwdjcD3fLZbRbit_NZyuFBZhcpV-Bvdlu6NiZL3kb65hsFRsDkTNHtC0zJxG3HGV1TInl_DCfafj3axNGSwW4-UNj1sZWhiHCYE2aK9hm-FYjrNGiEh0UqKya1EAYMTM5Z4k8qKOWPEPdIaZz9X98tPC5FIn5lbRRusCTuQmgRL-QxK9SdIMA3TflImwA1vyh3zq44j8EkkNTQWf94-e82GDHs5MwHIkK0S-Gg4d950IyRTpoABSa9qXA5yPzoaT9jCGjCodL");'></div>
|
||||
</div>
|
||||
</header>
|
||||
<div class="flex-1 overflow-y-auto p-6 lg:p-10 pb-24">
|
||||
<div class="mx-auto max-w-7xl">
|
||||
<div class="flex flex-wrap gap-2 mb-6">
|
||||
<a class="text-gray-500 dark:text-gray-400 text-sm font-medium leading-normal" href="#">Dashboard</a>
|
||||
<span class="text-gray-500 dark:text-gray-400 text-sm font-medium leading-normal">/</span>
|
||||
<a class="text-gray-500 dark:text-gray-400 text-sm font-medium leading-normal" href="#">Inventory</a>
|
||||
<span class="text-gray-500 dark:text-gray-400 text-sm font-medium leading-normal">/</span>
|
||||
<span class="text-gray-800 dark:text-gray-200 text-sm font-medium leading-normal">Batch Add Items</span>
|
||||
</div>
|
||||
<div class="flex flex-wrap justify-between items-center gap-4 mb-8">
|
||||
<div class="flex flex-col gap-2">
|
||||
<h1 class="text-gray-900 dark:text-white text-3xl font-bold tracking-tight">Add Items to Inventory</h1>
|
||||
<p class="text-gray-500 dark:text-gray-400 text-base font-normal leading-normal">Manage your batch inventory addition list here.</p>
|
||||
</div>
|
||||
<button class="flex items-center justify-center gap-2 bg-primary text-white px-6 py-3 rounded-xl font-bold hover:bg-primary/90 transition-all shadow-lg shadow-primary/20">
|
||||
<span class="material-symbols-outlined">add_circle</span>
|
||||
Select Product to Add
|
||||
</button>
|
||||
</div>
|
||||
<div class="bg-white dark:bg-gray-900/50 rounded-xl border border-gray-200 dark:border-gray-800 overflow-hidden shadow-sm">
|
||||
<div class="px-6 py-4 border-b border-gray-200 dark:border-gray-800 flex justify-between items-center">
|
||||
<h2 class="text-lg font-bold text-gray-900 dark:text-white">Queued Items (4)</h2>
|
||||
<button class="text-sm font-medium text-red-500 hover:text-red-600">Clear All</button>
|
||||
</div>
|
||||
<div class="overflow-x-auto">
|
||||
<table class="w-full text-left border-collapse">
|
||||
<thead>
|
||||
<tr class="bg-gray-50 dark:bg-gray-800/50">
|
||||
<th class="px-6 py-4 text-xs font-bold text-gray-500 dark:text-gray-400 uppercase tracking-wider">Product Name</th>
|
||||
<th class="px-6 py-4 text-xs font-bold text-gray-500 dark:text-gray-400 uppercase tracking-wider">SKU</th>
|
||||
<th class="px-6 py-4 text-xs font-bold text-gray-500 dark:text-gray-400 uppercase tracking-wider">Variant</th>
|
||||
<th class="px-6 py-4 text-xs font-bold text-gray-500 dark:text-gray-400 uppercase tracking-wider">Quantity</th>
|
||||
<th class="px-6 py-4 text-xs font-bold text-gray-500 dark:text-gray-400 uppercase tracking-wider">Unit</th>
|
||||
<th class="px-6 py-4 text-xs font-bold text-gray-500 dark:text-gray-400 uppercase tracking-wider text-right">Action</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="divide-y divide-gray-100 dark:divide-gray-800">
|
||||
<tr class="hover:bg-gray-50/50 dark:hover:bg-gray-800/30 transition-colors">
|
||||
<td class="px-6 py-4">
|
||||
<div class="flex items-center gap-3">
|
||||
<div class="w-8 h-8 rounded bg-gray-100 dark:bg-gray-800 overflow-hidden">
|
||||
<img alt="product" class="w-full h-full object-cover" src="https://lh3.googleusercontent.com/aida-public/AB6AXuBMbZhSSPYoHEi3akXSqZXm-T6EghACuHJJ5NYeJ8g4XJIKtqXRhCSQ1kFcvo7Txk01ryR-r6RJ61NzPLENWMywvUVnCkQglsizZG0NKPBwRFQjXvtDULkGbGFm6EPocyHGfuzKJ0EoDr601zMBI34mfPCgn9AAaRkTMj2Ize2nCUcanlGn7QJEp6BdNcmf3JFlk-51jPTXj1-mM4Pw6AGIvarhxnZqtEAji6qRF7evVTR_56c48h5if21S3jD-wVhNVp8AltVn8KEH"/>
|
||||
</div>
|
||||
<span class="font-medium">Smart Display Panel</span>
|
||||
</div>
|
||||
</td>
|
||||
<td class="px-6 py-4 text-sm text-gray-500">DISP-PNL-SD-001</td>
|
||||
<td class="px-6 py-4">
|
||||
<div class="flex flex-wrap gap-1">
|
||||
<span class="px-2.5 py-1 text-[10px] font-semibold bg-blue-50 dark:bg-blue-900/30 text-blue-600 dark:text-blue-400 rounded-full border border-blue-100 dark:border-blue-800/50 uppercase tracking-tight">Medida: 85</span>
|
||||
<span class="px-2.5 py-1 text-[10px] font-semibold bg-purple-50 dark:bg-purple-900/30 text-purple-600 dark:text-purple-400 rounded-full border border-purple-100 dark:border-purple-800/50 uppercase tracking-tight">Resolución: 4K</span>
|
||||
</div>
|
||||
</td>
|
||||
<td class="px-6 py-4">
|
||||
<input class="w-20 form-input h-8 rounded border-gray-300 dark:border-gray-700 bg-transparent text-sm" type="number" value="25"/>
|
||||
</td>
|
||||
<td class="px-6 py-4 text-sm">pcs</td>
|
||||
<td class="px-6 py-4 text-right">
|
||||
<button class="text-gray-400 hover:text-red-500 transition-colors">
|
||||
<span class="material-symbols-outlined text-xl">delete</span>
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
<tr class="hover:bg-gray-50/50 dark:hover:bg-gray-800/30 transition-colors">
|
||||
<td class="px-6 py-4">
|
||||
<div class="flex items-center gap-3">
|
||||
<div class="w-8 h-8 rounded bg-gray-100 dark:bg-gray-800 overflow-hidden">
|
||||
<img alt="product" class="w-full h-full object-cover" src="https://lh3.googleusercontent.com/aida-public/AB6AXuBBMBDEGh1exdBX1lU--TOCsUwfsFkYhrl8DxxvxgH-hKLkcuVtuGP_ZhRS5l_YXFWls08Ecr-Ic748cVHqHexFMMzYTPH8YL2s9OISDgsMIwcDOPxitrPUn3RBCD_krFC8SrBXBeNC8ilcFdg_JCwD5BW5gaYjAAEUMFXyM1vd-YT27KyubBk1IKATsMdDkH-NtwAHcrPgNoXsRaAcubBQiebCG1hmxKVW3fAXoDoSqywrxhwZ7cmIuo7BTThJJ1KcczxQh1jYh_w4"/>
|
||||
</div>
|
||||
<span class="font-medium">Mechanical Keyboard RGB</span>
|
||||
</div>
|
||||
</td>
|
||||
<td class="px-6 py-4 text-sm text-gray-500">KYBD-RGB-US-02</td>
|
||||
<td class="px-6 py-4">
|
||||
<span class="px-2.5 py-1 text-xs font-medium bg-gray-100 dark:bg-gray-800 rounded-full border border-gray-200 dark:border-gray-700">Red Switch</span>
|
||||
</td>
|
||||
<td class="px-6 py-4">
|
||||
<input class="w-20 form-input h-8 rounded border-gray-300 dark:border-gray-700 bg-transparent text-sm" type="number" value="12"/>
|
||||
</td>
|
||||
<td class="px-6 py-4 text-sm">pcs</td>
|
||||
<td class="px-6 py-4 text-right">
|
||||
<button class="text-gray-400 hover:text-red-500 transition-colors">
|
||||
<span class="material-symbols-outlined text-xl">delete</span>
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
<tr class="hover:bg-gray-50/50 dark:hover:bg-gray-800/30 transition-colors">
|
||||
<td class="px-6 py-4">
|
||||
<div class="flex items-center gap-3">
|
||||
<div class="w-8 h-8 rounded bg-gray-100 dark:bg-gray-800 overflow-hidden">
|
||||
<img alt="product" class="w-full h-full object-cover" src="https://lh3.googleusercontent.com/aida-public/AB6AXuAsb9nqDON7PeKGYYAU8hmHn7B2l3waP_ewMQTpsgnpJe2vFiY-k-zncE5VEf186tDzlTwVPA6-1SgAFagugq9Anw2WXloUFTNwOrOHgAi-MNhgs0FuxRKF8XMLk6W_u6YLF5xWh3K2q317JXFSUWtURtxCi8pYM4YtHYEyuT2aIrhbAnI2LQ7ja8DvSdvpO6t1IJ6HE9RqlnAFXuLG22igxiekKT2NjRpuK5Zzu_90rBPEjb0KitG9DwiBjzKa702pccHPRqupgXke"/>
|
||||
</div>
|
||||
<span class="font-medium">USB-C Charging Cable 2m</span>
|
||||
</div>
|
||||
</td>
|
||||
<td class="px-6 py-4 text-sm text-gray-500">CBL-USBC-2M</td>
|
||||
<td class="px-6 py-4">
|
||||
<span class="px-2.5 py-1 text-xs font-medium bg-gray-100 dark:bg-gray-800 rounded-full border border-gray-200 dark:border-gray-700">Braided</span>
|
||||
</td>
|
||||
<td class="px-6 py-4">
|
||||
<input class="w-20 form-input h-8 rounded border-gray-300 dark:border-gray-700 bg-transparent text-sm" type="number" value="100"/>
|
||||
</td>
|
||||
<td class="px-6 py-4 text-sm">pcs</td>
|
||||
<td class="px-6 py-4 text-right">
|
||||
<button class="text-gray-400 hover:text-red-500 transition-colors">
|
||||
<span class="material-symbols-outlined text-xl">delete</span>
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
<tr class="hover:bg-gray-50/50 dark:hover:bg-gray-800/30 transition-colors">
|
||||
<td class="px-6 py-4">
|
||||
<div class="flex items-center gap-3">
|
||||
<div class="w-8 h-8 rounded bg-gray-100 dark:bg-gray-800 overflow-hidden">
|
||||
<img alt="product" class="w-full h-full object-cover" src="https://lh3.googleusercontent.com/aida-public/AB6AXuD1PtrsyrvB9KtL5DHjKotvi92lM1NsfdzyEkoW5DAEfSYoPI8MNsCJZDBMtrz6rXnIWApiymAIlp7Yq5P6JTQqpwaNJVw5I4G3KiefVME-D_jR_0iTDtPBxljmlcohkhIrSfK77wV9Wq1KL0yvjCB2UuDewIxBOs0RUl7_RbISQxYaSzVe9GbqnwI5cpj_N_1faxUA89ATStV6GovMDXFA881MUGKd4_ox_CzEG5zzPZYRe9JF7M33YH-rRHdxlpQWtRH9yw4X8sKp"/>
|
||||
</div>
|
||||
<span class="font-medium">Smart Display Panel</span>
|
||||
</div>
|
||||
</td>
|
||||
<td class="px-6 py-4 text-sm text-gray-500">DISP-PNL-SD-001</td>
|
||||
<td class="px-6 py-4">
|
||||
<div class="flex flex-wrap gap-1">
|
||||
<span class="px-2.5 py-1 text-[10px] font-semibold bg-blue-50 dark:bg-blue-900/30 text-blue-600 dark:text-blue-400 rounded-full border border-blue-100 dark:border-blue-800/50 uppercase tracking-tight">Medida: 36</span>
|
||||
<span class="px-2.5 py-1 text-[10px] font-semibold bg-purple-50 dark:bg-purple-900/30 text-purple-600 dark:text-purple-400 rounded-full border border-purple-100 dark:border-purple-800/50 uppercase tracking-tight">Resolución: 1080</span>
|
||||
</div>
|
||||
</td>
|
||||
<td class="px-6 py-4">
|
||||
<input class="w-20 form-input h-8 rounded border-gray-300 dark:border-gray-700 bg-transparent text-sm" type="number" value="5"/>
|
||||
</td>
|
||||
<td class="px-6 py-4 text-sm">pcs</td>
|
||||
<td class="px-6 py-4 text-right">
|
||||
<button class="text-gray-400 hover:text-red-500 transition-colors">
|
||||
<span class="material-symbols-outlined text-xl">delete</span>
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<div class="p-6 bg-gray-50/50 dark:bg-gray-800/50 border-t border-gray-200 dark:border-gray-800 text-sm text-gray-500 dark:text-gray-400">
|
||||
Total items to be added: <span class="font-bold text-gray-900 dark:text-white">142 units</span> across 4 unique products.
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<footer class="fixed bottom-0 right-0 left-64 bg-white dark:bg-background-dark border-t border-gray-200 dark:border-gray-800 p-4 px-10 flex justify-between items-center z-20 shadow-[0_-4px_6px_-1px_rgba(0,0,0,0.1)]">
|
||||
<div class="flex items-center gap-6">
|
||||
<div class="flex items-center gap-2">
|
||||
<span class="text-xs font-medium text-gray-500 uppercase tracking-widest">Storage Location</span>
|
||||
<select class="form-select text-sm rounded-lg border-gray-300 dark:border-gray-700 bg-gray-50 dark:bg-gray-800 focus:ring-primary h-9">
|
||||
<option>Main Warehouse (A1)</option>
|
||||
<option>South Storage (B4)</option>
|
||||
<option>Cold Storage (C2)</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex items-center gap-4">
|
||||
<button class="flex min-w-[100px] cursor-pointer items-center justify-center rounded-lg h-11 px-6 bg-gray-200 dark:bg-gray-800 text-gray-800 dark:text-gray-200 text-sm font-bold leading-normal hover:bg-gray-300 dark:hover:bg-gray-700 transition-all">
|
||||
Cancel
|
||||
</button>
|
||||
<button class="flex min-w-[200px] cursor-pointer items-center justify-center rounded-lg h-11 px-8 bg-primary text-white text-sm font-bold leading-normal tracking-[0.015em] hover:bg-primary/90 shadow-lg shadow-primary/20 transition-all">
|
||||
Confirm and Save Inventory
|
||||
</button>
|
||||
</div>
|
||||
</footer>
|
||||
</main>
|
||||
<div class="fixed inset-0 z-100 flex items-center justify-center">
|
||||
<div class="absolute inset-0 bg-gray-900/60 backdrop-blur-sm"></div>
|
||||
<div class="relative bg-white dark:bg-background-dark w-full max-w-2xl mx-4 rounded-2xl shadow-2xl border border-gray-200 dark:border-gray-800 overflow-hidden flex flex-col max-h-[90vh]">
|
||||
<div class="p-6 border-b border-gray-100 dark:border-gray-800 flex justify-between items-center">
|
||||
<div>
|
||||
<h3 class="text-xl font-bold text-gray-900 dark:text-white">Add Product to Batch</h3>
|
||||
<p class="text-sm text-gray-500 dark:text-gray-400">Search and configure the product variant</p>
|
||||
</div>
|
||||
<button class="text-gray-400 hover:text-gray-600 dark:hover:text-gray-200 transition-colors">
|
||||
<span class="material-symbols-outlined text-2xl">close</span>
|
||||
</button>
|
||||
</div>
|
||||
<div class="p-6 overflow-y-auto space-y-8">
|
||||
<div class="space-y-3">
|
||||
<label class="block text-sm font-semibold text-gray-700 dark:text-gray-300">Search Catalog</label>
|
||||
<div class="relative">
|
||||
<span class="material-symbols-outlined absolute left-3 top-1/2 -translate-y-1/2 text-gray-500">search</span>
|
||||
<input class="form-input w-full rounded-xl border-gray-300 dark:border-gray-700 bg-gray-50 dark:bg-gray-800/50 text-gray-900 dark:text-white pl-11 h-12 focus:ring-primary focus:border-primary" placeholder="Type Product Name or SKU..." type="text" value="Smart Display Panel"/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="bg-gray-50 dark:bg-gray-800/30 rounded-xl p-5 border border-gray-100 dark:border-gray-700 space-y-6">
|
||||
<div class="flex items-center gap-4">
|
||||
<div class="w-16 h-16 rounded-lg bg-white dark:bg-gray-700 border border-gray-200 dark:border-gray-600 flex items-center justify-center shadow-sm">
|
||||
<span class="material-symbols-outlined text-3xl text-gray-400">image</span>
|
||||
</div>
|
||||
<div>
|
||||
<p class="text-lg font-bold text-gray-900 dark:text-white">Smart Display Panel</p>
|
||||
<p class="text-sm text-gray-500 font-medium tracking-tight">SKU: DISP-PNL-SD-001</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
|
||||
<div class="space-y-2">
|
||||
<label class="block text-xs font-bold uppercase tracking-wider text-gray-500 dark:text-gray-400">Medida</label>
|
||||
<select class="form-select w-full rounded-lg border-gray-300 dark:border-gray-700 bg-white dark:bg-gray-800 text-sm focus:ring-primary focus:border-primary h-11">
|
||||
<option value="24">24</option>
|
||||
<option value="36">36</option>
|
||||
<option value="64">64</option>
|
||||
<option selected="" value="85">85</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="space-y-2">
|
||||
<label class="block text-xs font-bold uppercase tracking-wider text-gray-500 dark:text-gray-400">Resolución</label>
|
||||
<select class="form-select w-full rounded-lg border-gray-300 dark:border-gray-700 bg-white dark:bg-gray-800 text-sm focus:ring-primary focus:border-primary h-11">
|
||||
<option value="1080">1080</option>
|
||||
<option value="2K">2K</option>
|
||||
<option selected="" value="4K">4K</option>
|
||||
<option value="8K">8K</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="space-y-2">
|
||||
<label class="block text-xs font-bold uppercase tracking-wider text-gray-500 dark:text-gray-400">Quantity</label>
|
||||
<div class="flex items-center gap-2">
|
||||
<input class="form-input flex-1 rounded-lg border-gray-300 dark:border-gray-700 bg-white dark:bg-gray-800 text-sm focus:ring-primary focus:border-primary h-11" type="number" value="10"/>
|
||||
<span class="text-sm font-medium text-gray-500">pcs</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex items-end pb-1">
|
||||
<div class="bg-blue-50 dark:bg-blue-900/20 text-blue-600 dark:text-blue-400 px-3 py-2 rounded-lg border border-blue-100 dark:border-blue-800/50 w-full flex items-center gap-2">
|
||||
<span class="material-symbols-outlined text-lg">info</span>
|
||||
<span class="text-xs font-medium">Current Stock: 420 units</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="p-6 bg-gray-50 dark:bg-gray-800/50 border-t border-gray-100 dark:border-gray-800 flex justify-end gap-3">
|
||||
<button class="px-5 py-2.5 rounded-lg text-sm font-bold text-gray-600 dark:text-gray-300 hover:bg-gray-200 dark:hover:bg-gray-700 transition-colors">
|
||||
Cancel
|
||||
</button>
|
||||
<button class="bg-primary text-white px-8 py-2.5 rounded-lg text-sm font-bold hover:bg-primary/90 transition-all shadow-lg shadow-primary/20 flex items-center gap-2">
|
||||
<span class="material-symbols-outlined text-lg">add_task</span>
|
||||
Confirm and Add to List
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</body></html>
|
||||
@ -0,0 +1,16 @@
|
||||
import api from "../../../services/api";
|
||||
import type { CreateInventoryRequest, CreateInventoryResponse } from "../types/warehouse.inventory";
|
||||
|
||||
export const inventoryWarehouseServices = {
|
||||
async addInventory(data: CreateInventoryRequest): Promise<CreateInventoryResponse> {
|
||||
try {
|
||||
const response = await api.post('/api/inventory-warehouses', data);
|
||||
console.log('📦 Add Inventory response:', response.data);
|
||||
return response.data;
|
||||
} catch (error) {
|
||||
console.error('❌ Error adding inventory:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@ -1,5 +1,5 @@
|
||||
import api from '../../../services/api';
|
||||
import type { WarehousesResponse, CreateWarehouseData } from '../types/warehouse';
|
||||
import type { WarehousesResponse, CreateWarehouseData, WarehouseDetailResponse } from '../types/warehouse';
|
||||
|
||||
export const warehouseService = {
|
||||
async getWarehouses() {
|
||||
@ -22,5 +22,16 @@ export const warehouseService = {
|
||||
console.error('Error creating warehouse:', error);
|
||||
throw error;
|
||||
}
|
||||
},
|
||||
|
||||
async getWarehouseById(warehouseId: number) {
|
||||
try {
|
||||
const response = await api.get<WarehouseDetailResponse>(`/api/warehouses/${warehouseId}`);
|
||||
console.log(`Warehouse with ID ${warehouseId} response:`, response.data);
|
||||
return response.data;
|
||||
} catch (error) {
|
||||
console.error(`Error fetching warehouse with ID ${warehouseId}:`, error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
60
src/modules/warehouse/types/warehouse.d.ts
vendored
60
src/modules/warehouse/types/warehouse.d.ts
vendored
@ -46,3 +46,63 @@ export interface WarehousesResponse {
|
||||
warehouses: WarehousePagination;
|
||||
};
|
||||
}
|
||||
|
||||
export interface WarehouseProduct {
|
||||
id: number;
|
||||
code: string;
|
||||
sku: string;
|
||||
name: string;
|
||||
barcode: string;
|
||||
description: string;
|
||||
unit_of_measure_id: number;
|
||||
suggested_sale_price: number;
|
||||
attributes: Record<string, any> | null;
|
||||
is_active: boolean;
|
||||
created_at: string;
|
||||
updated_at: string;
|
||||
deleted_at: string | null;
|
||||
}
|
||||
|
||||
export interface WarehouseStock {
|
||||
id: number;
|
||||
product_id: number;
|
||||
warehouse_id: number;
|
||||
stock: number;
|
||||
stock_min: number | null;
|
||||
stock_max: number | null;
|
||||
reorder_point: number | null;
|
||||
is_active: number;
|
||||
created_at: string;
|
||||
updated_at: string;
|
||||
product: WarehouseProduct;
|
||||
}
|
||||
|
||||
export interface WarehouseInventoryItem {
|
||||
id: number;
|
||||
product_id: number;
|
||||
warehouse_id: number;
|
||||
quantity: string;
|
||||
serial_number: string | null;
|
||||
acquisition_date: string;
|
||||
warehouse_number: string | null;
|
||||
attributes: Record<string, any> | null;
|
||||
purchase_cost: number;
|
||||
warranty_days: number | null;
|
||||
warranty_end_date: string | null;
|
||||
expiration_date: string | null;
|
||||
created_at: string;
|
||||
updated_at: string;
|
||||
deleted_at: string | null;
|
||||
product: WarehouseProduct;
|
||||
}
|
||||
|
||||
export interface WarehouseDetailData {
|
||||
warehouse: Warehouse;
|
||||
stocks: WarehouseStock[];
|
||||
items: WarehouseInventoryItem[];
|
||||
}
|
||||
|
||||
export interface WarehouseDetailResponse {
|
||||
status: string;
|
||||
data: WarehouseDetailData;
|
||||
}
|
||||
|
||||
74
src/modules/warehouse/types/warehouse.inventory.d.ts
vendored
Normal file
74
src/modules/warehouse/types/warehouse.inventory.d.ts
vendored
Normal file
@ -0,0 +1,74 @@
|
||||
export interface InventoryProductItem {
|
||||
product_id: number;
|
||||
warehouse_id: number;
|
||||
purchase_cost: number;
|
||||
quantity?: number;
|
||||
attributes?: Record<string, any>;
|
||||
serial_number?: string;
|
||||
serial_numbers?: string[];
|
||||
}
|
||||
|
||||
export interface CreateInventoryRequest {
|
||||
products: InventoryProductItem[];
|
||||
}
|
||||
|
||||
export interface InventoryProduct {
|
||||
id: number;
|
||||
code: string;
|
||||
sku: string;
|
||||
name: string;
|
||||
barcode: string;
|
||||
description: string;
|
||||
unit_of_measure_id: number;
|
||||
suggested_sale_price: number;
|
||||
attributes: Record<string, any> | null;
|
||||
is_active: boolean;
|
||||
created_at: string;
|
||||
updated_at: string;
|
||||
deleted_at: string | null;
|
||||
}
|
||||
|
||||
export interface InventoryWarehouse {
|
||||
id: number;
|
||||
code: string;
|
||||
name: string;
|
||||
description: string;
|
||||
address: string | null;
|
||||
is_active: boolean;
|
||||
created_at: string;
|
||||
updated_at: string;
|
||||
deleted_at: string | null;
|
||||
}
|
||||
|
||||
export interface InventoryItemDetail {
|
||||
id: number;
|
||||
product_id: number;
|
||||
warehouse_id: number;
|
||||
quantity: string;
|
||||
serial_number: string | null;
|
||||
attributes: Record<string, any> | null;
|
||||
purchase_cost: number;
|
||||
acquisition_date: string;
|
||||
created_at: string;
|
||||
updated_at: string;
|
||||
product: InventoryProduct;
|
||||
warehouse: InventoryWarehouse;
|
||||
}
|
||||
|
||||
export interface InventoryGroupResult {
|
||||
inventory_items: InventoryItemDetail[];
|
||||
previous_stock: number;
|
||||
added_quantity: number;
|
||||
new_stock: number;
|
||||
type: 'stock' | 'serialized';
|
||||
}
|
||||
|
||||
export interface CreateInventoryResponseMeta {
|
||||
total_items: number;
|
||||
}
|
||||
|
||||
export interface CreateInventoryResponse {
|
||||
message: string;
|
||||
data: InventoryGroupResult[];
|
||||
meta: CreateInventoryResponseMeta;
|
||||
}
|
||||
@ -7,8 +7,6 @@ import MainLayout from '../MainLayout.vue';
|
||||
import WarehouseIndex from '../modules/warehouse/components/WarehouseIndex.vue';
|
||||
import WarehouseForm from '../modules/warehouse/components/WarehouseForm.vue';
|
||||
import WarehouseDetails from '../modules/warehouse/components/WarehouseDetails.vue';
|
||||
import BatchAddInventory from '../modules/warehouse/components/BatchAddInventory.vue';
|
||||
|
||||
import WarehouseClassification from '../modules/warehouse/components/WarehouseClassification.vue';
|
||||
import UnitOfMeasure from '../modules/catalog/components/UnitOfMeasure.vue';
|
||||
import ComercialClassification from '../modules/catalog/components/ComercialClassification.vue';
|
||||
@ -28,6 +26,7 @@ import Suppliers from '../modules/catalog/components/suppliers/Suppliers.vue';
|
||||
import Purchases from '../modules/purchases/components/Purchases.vue';
|
||||
import PurchaseDetails from '../modules/purchases/components/PurchaseDetails.vue';
|
||||
import PurchaseForm from '../modules/purchases/components/PurchaseForm.vue';
|
||||
import WarehouseAddInventory from '../modules/warehouse/components/WarehouseAddInventory.vue';
|
||||
const routes: RouteRecordRaw[] = [
|
||||
{
|
||||
path: '/login',
|
||||
@ -100,8 +99,8 @@ const routes: RouteRecordRaw[] = [
|
||||
},
|
||||
{
|
||||
path: 'batch-add',
|
||||
name: 'BatchAddInventory',
|
||||
component: BatchAddInventory,
|
||||
name: 'WarehouseAddInventory',
|
||||
component: WarehouseAddInventory,
|
||||
meta: {
|
||||
title: 'Agregar Items al Inventario',
|
||||
requiresAuth: true
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user