feat: agregar gestión de proveedores y unidad de medida en inventarios
This commit is contained in:
parent
562397402c
commit
aff2448356
@ -187,6 +187,7 @@ public function downloadTemplate()
|
|||||||
'sku',
|
'sku',
|
||||||
'codigo_barras',
|
'codigo_barras',
|
||||||
'categoria',
|
'categoria',
|
||||||
|
'unidad_medida',
|
||||||
'precio_venta',
|
'precio_venta',
|
||||||
'impuesto'
|
'impuesto'
|
||||||
];
|
];
|
||||||
@ -197,22 +198,25 @@ public function downloadTemplate()
|
|||||||
'sku' => 'SAM-A55-BLK',
|
'sku' => 'SAM-A55-BLK',
|
||||||
'codigo_barras' => '7502276853456',
|
'codigo_barras' => '7502276853456',
|
||||||
'categoria' => 'Electrónica',
|
'categoria' => 'Electrónica',
|
||||||
|
'unidad_medida' => 'Pieza',
|
||||||
'precio_venta' => 7500.00,
|
'precio_venta' => 7500.00,
|
||||||
'impuesto' => 16
|
'impuesto' => 16
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
'nombre' => 'Coca Cola 600ml',
|
'nombre' => 'Cable UTP CAT6 (Metro)',
|
||||||
'sku' => 'COCA-600',
|
'sku' => 'UTP6-MTR',
|
||||||
'codigo_barras' => '750227686666',
|
'codigo_barras' => '750227686666',
|
||||||
'categoria' => 'Bebidas',
|
'categoria' => 'Cables',
|
||||||
'precio_venta' => 18.00,
|
'unidad_medida' => 'Metro',
|
||||||
'impuesto' => 8
|
'precio_venta' => 50.00,
|
||||||
|
'impuesto' => 16
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
'nombre' => 'Laptop HP Pavilion 15',
|
'nombre' => 'Laptop HP Pavilion 15',
|
||||||
'sku' => 'HP-LAP-15',
|
'sku' => 'HP-LAP-15',
|
||||||
'codigo_barras' => '7502276854443',
|
'codigo_barras' => '7502276854443',
|
||||||
'categoria' => 'Computadoras',
|
'categoria' => 'Computadoras',
|
||||||
|
'unidad_medida' => 'Pieza',
|
||||||
'precio_venta' => 12000.00,
|
'precio_venta' => 12000.00,
|
||||||
'impuesto' => 16
|
'impuesto' => 16
|
||||||
],
|
],
|
||||||
|
|||||||
@ -25,7 +25,7 @@ public function __construct(
|
|||||||
*/
|
*/
|
||||||
public function index(Request $request)
|
public function index(Request $request)
|
||||||
{
|
{
|
||||||
$query = InventoryMovement::with(['inventory', 'warehouseFrom', 'warehouseTo', 'user'])
|
$query = InventoryMovement::with(['inventory', 'warehouseFrom', 'warehouseTo', 'user', 'supplier'])
|
||||||
->orderBy('created_at', 'desc');
|
->orderBy('created_at', 'desc');
|
||||||
|
|
||||||
if ($request->has('q') && $request->q){
|
if ($request->has('q') && $request->q){
|
||||||
@ -69,7 +69,7 @@ public function index(Request $request)
|
|||||||
*/
|
*/
|
||||||
public function show(int $id)
|
public function show(int $id)
|
||||||
{
|
{
|
||||||
$movement = InventoryMovement::with(['inventory', 'warehouseFrom', 'warehouseTo', 'user'])
|
$movement = InventoryMovement::with(['inventory', 'warehouseFrom', 'warehouseTo', 'user', 'supplier'])
|
||||||
->find($id);
|
->find($id);
|
||||||
|
|
||||||
if (!$movement) {
|
if (!$movement) {
|
||||||
|
|||||||
124
app/Http/Controllers/App/SupplierController.php
Normal file
124
app/Http/Controllers/App/SupplierController.php
Normal file
@ -0,0 +1,124 @@
|
|||||||
|
<?php namespace App\Http\Controllers\App;
|
||||||
|
|
||||||
|
use App\Http\Controllers\Controller;
|
||||||
|
use App\Models\Supplier;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
use Notsoweb\ApiResponse\Enums\ApiResponse;
|
||||||
|
|
||||||
|
class SupplierController extends Controller
|
||||||
|
{
|
||||||
|
public function index(Request $request)
|
||||||
|
{
|
||||||
|
$query = Supplier::query();
|
||||||
|
|
||||||
|
// Filtro por búsqueda
|
||||||
|
if ($request->has('q')) {
|
||||||
|
$query->where(function($q) use ($request) {
|
||||||
|
$q->where('business_name', 'like', "%{$request->q}%")
|
||||||
|
->orWhere('rfc', 'like', "%{$request->q}%");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Filtro por estado
|
||||||
|
if ($request->has('is_active')) {
|
||||||
|
$query->where('is_active', $request->is_active);
|
||||||
|
}
|
||||||
|
|
||||||
|
$suppliers = $query->orderBy('business_name')
|
||||||
|
->paginate(config('app.pagination'));
|
||||||
|
|
||||||
|
return ApiResponse::OK->response([
|
||||||
|
'suppliers' => $suppliers
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function store(Request $request)
|
||||||
|
{
|
||||||
|
$validated = $request->validate([
|
||||||
|
'business_name' => 'required|string|max:255',
|
||||||
|
'email' => 'nullable|email',
|
||||||
|
'phone' => 'nullable|string|max:10',
|
||||||
|
'rfc' => 'nullable|string|unique:suppliers,rfc',
|
||||||
|
'address' => 'nullable|string',
|
||||||
|
'postal_code' => 'nullable|string',
|
||||||
|
'notes' => 'nullable|string',
|
||||||
|
]);
|
||||||
|
|
||||||
|
$supplier = Supplier::create($validated);
|
||||||
|
|
||||||
|
return ApiResponse::CREATED->response([
|
||||||
|
'supplier' => $supplier
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function show(Supplier $supplier)
|
||||||
|
{
|
||||||
|
return ApiResponse::OK->response([
|
||||||
|
'supplier' => $supplier->load('inventoryMovements')
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function update(Request $request, Supplier $supplier)
|
||||||
|
{
|
||||||
|
$validated = $request->validate([
|
||||||
|
'business_name' => 'nullable|string|max:255',
|
||||||
|
'email' => 'nullable|email',
|
||||||
|
'phone' => 'nullable|string|max:10',
|
||||||
|
'rfc' => 'nullable|string|unique:suppliers,rfc,' . $supplier->id,
|
||||||
|
'address' => 'nullable|string',
|
||||||
|
'postal_code' => 'nullable|string',
|
||||||
|
'notes' => 'nullable|string',
|
||||||
|
]);
|
||||||
|
|
||||||
|
$supplier->update($validated);
|
||||||
|
|
||||||
|
return ApiResponse::OK->response([
|
||||||
|
'supplier' => $supplier->fresh()
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function destroy(Supplier $supplier)
|
||||||
|
{
|
||||||
|
$supplier->delete();
|
||||||
|
return ApiResponse::OK->response();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Productos suministrados por el proveedor
|
||||||
|
*/
|
||||||
|
public function products(Supplier $supplier)
|
||||||
|
{
|
||||||
|
$products = $supplier->suppliedProducts()
|
||||||
|
->with(['category', 'price'])
|
||||||
|
->paginate(config('app.pagination'));
|
||||||
|
|
||||||
|
return ApiResponse::OK->response([
|
||||||
|
'products' => $products
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Historial de compras al proveedor
|
||||||
|
*/
|
||||||
|
public function purchases(Supplier $supplier, Request $request)
|
||||||
|
{
|
||||||
|
$query = $supplier->inventoryMovements()
|
||||||
|
->with(['inventory', 'warehouseTo', 'user'])
|
||||||
|
->orderBy('created_at', 'desc');
|
||||||
|
|
||||||
|
if ($request->has('from_date')) {
|
||||||
|
$query->whereDate('created_at', '>=', $request->from_date);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($request->has('to_date')) {
|
||||||
|
$query->whereDate('created_at', '<=', $request->to_date);
|
||||||
|
}
|
||||||
|
|
||||||
|
$purchases = $query->paginate(config('app.pagination'));
|
||||||
|
|
||||||
|
return ApiResponse::OK->response([
|
||||||
|
'purchases' => $purchases,
|
||||||
|
'total_amount' => $supplier->total_purchases
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -17,6 +17,7 @@ public function rules(): array
|
|||||||
if ($this->has('products')) {
|
if ($this->has('products')) {
|
||||||
return [
|
return [
|
||||||
'warehouse_id' => 'required|exists:warehouses,id',
|
'warehouse_id' => 'required|exists:warehouses,id',
|
||||||
|
'supplier_id' => 'nullable|exists:suppliers,id',
|
||||||
'invoice_reference' => 'required|string|max:255',
|
'invoice_reference' => 'required|string|max:255',
|
||||||
'notes' => 'nullable|string|max:1000',
|
'notes' => 'nullable|string|max:1000',
|
||||||
|
|
||||||
@ -33,6 +34,7 @@ public function rules(): array
|
|||||||
return [
|
return [
|
||||||
'inventory_id' => 'required|exists:inventories,id',
|
'inventory_id' => 'required|exists:inventories,id',
|
||||||
'warehouse_id' => 'required|exists:warehouses,id',
|
'warehouse_id' => 'required|exists:warehouses,id',
|
||||||
|
'supplier_id' => 'nullable|exists:suppliers,id',
|
||||||
'quantity' => 'required|numeric|min:0.001',
|
'quantity' => 'required|numeric|min:0.001',
|
||||||
'unit_cost' => 'required|numeric|min:0',
|
'unit_cost' => 'required|numeric|min:0',
|
||||||
'invoice_reference' => 'required|string|max:255',
|
'invoice_reference' => 'required|string|max:255',
|
||||||
|
|||||||
@ -6,6 +6,7 @@
|
|||||||
use App\Models\Price;
|
use App\Models\Price;
|
||||||
use App\Models\Category;
|
use App\Models\Category;
|
||||||
use App\Http\Requests\App\InventoryImportRequest;
|
use App\Http\Requests\App\InventoryImportRequest;
|
||||||
|
use App\Models\UnitOfMeasurement;
|
||||||
use Maatwebsite\Excel\Concerns\ToModel;
|
use Maatwebsite\Excel\Concerns\ToModel;
|
||||||
use Maatwebsite\Excel\Concerns\WithHeadingRow;
|
use Maatwebsite\Excel\Concerns\WithHeadingRow;
|
||||||
use Maatwebsite\Excel\Concerns\WithValidation;
|
use Maatwebsite\Excel\Concerns\WithValidation;
|
||||||
@ -47,6 +48,7 @@ public function map($row): array
|
|||||||
'sku' => isset($row['sku']) ? (string) $row['sku'] : null,
|
'sku' => isset($row['sku']) ? (string) $row['sku'] : null,
|
||||||
'codigo_barras' => isset($row['codigo_barras']) ? (string) $row['codigo_barras'] : null,
|
'codigo_barras' => isset($row['codigo_barras']) ? (string) $row['codigo_barras'] : null,
|
||||||
'categoria' => $row['categoria'] ?? null,
|
'categoria' => $row['categoria'] ?? null,
|
||||||
|
'unidad_medida' => $row['unidad_medida'] ?? null,
|
||||||
'precio_venta' => $row['precio_venta'] ?? null,
|
'precio_venta' => $row['precio_venta'] ?? null,
|
||||||
'impuesto' => $row['impuesto'] ?? null,
|
'impuesto' => $row['impuesto'] ?? null,
|
||||||
];
|
];
|
||||||
@ -63,12 +65,12 @@ public function model(array $row)
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Buscar producto existente por SKU o código de barras
|
|
||||||
$existingInventory = null;
|
$existingInventory = null;
|
||||||
if (!empty($row['sku'])) {
|
if(!empty($row['sku'])) {
|
||||||
$existingInventory = Inventory::where('sku', trim($row['sku']))->first();
|
$existingInventory = Inventory::where('sku', trim($row['sku']))->first();
|
||||||
}
|
}
|
||||||
if (!$existingInventory && !empty($row['codigo_barras'])) {
|
if(!$existingInventory && !empty($row['codigo_barras'])) {
|
||||||
$existingInventory = Inventory::where('barcode', trim($row['codigo_barras']))->first();
|
$existingInventory = Inventory::where('barcode', trim($row['codigo_barras']))->first();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -78,8 +80,27 @@ public function model(array $row)
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Producto nuevo
|
// Producto nuevo
|
||||||
$precioVenta = (float) $row['precio_venta'];
|
return $this->createNewProduct($row);
|
||||||
|
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
$this->skipped++;
|
||||||
|
$this->errors[] = "Error en fila: " . $e->getMessage();
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private function createNewProduct(array $row)
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
// Validar nombre del producto
|
||||||
|
if (!isset($row['nombre']) || empty(trim($row['nombre']))) {
|
||||||
|
$this->skipped++;
|
||||||
|
$this->errors[] = "Fila sin nombre de producto";
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validar precio de venta
|
||||||
|
$precioVenta = (float) $row['precio_venta'];
|
||||||
if ($precioVenta <= 0) {
|
if ($precioVenta <= 0) {
|
||||||
$this->skipped++;
|
$this->skipped++;
|
||||||
$this->errors[] = "Fila con producto '{$row['nombre']}': El precio de venta debe ser mayor a 0";
|
$this->errors[] = "Fila con producto '{$row['nombre']}': El precio de venta debe ser mayor a 0";
|
||||||
@ -96,20 +117,47 @@ public function model(array $row)
|
|||||||
$categoryId = $category->id;
|
$categoryId = $category->id;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Crear el producto en inventario (sin stock inicial)
|
// Buscar unidad de medida (requerida)
|
||||||
|
$unitId = null;
|
||||||
|
if (!empty($row['unidad_medida'])) {
|
||||||
|
$unit = \App\Models\UnitOfMeasurement::where('name', trim($row['unidad_medida']))
|
||||||
|
->orWhere('abbreviation', trim($row['unidad_medida']))
|
||||||
|
->first();
|
||||||
|
|
||||||
|
if ($unit) {
|
||||||
|
$unitId = $unit->id;
|
||||||
|
} else {
|
||||||
|
$this->skipped++;
|
||||||
|
$this->errors[] = "Fila con producto '{$row['nombre']}': Unidad de medida '{$row['unidad_medida']}' no encontrada";
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Si no se proporciona, usar 'Pieza' por defecto
|
||||||
|
$unit = \App\Models\UnitOfMeasurement::where('name', 'Pieza')->first();
|
||||||
|
if ($unit) {
|
||||||
|
$unitId = $unit->id;
|
||||||
|
} else {
|
||||||
|
$this->skipped++;
|
||||||
|
$this->errors[] = "Fila con producto '{$row['nombre']}': No se proporcionó unidad de medida y no existe 'Pieza' por defecto";
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Crear el producto en inventario
|
||||||
$inventory = new Inventory();
|
$inventory = new Inventory();
|
||||||
$inventory->name = trim($row['nombre']);
|
$inventory->name = trim($row['nombre']);
|
||||||
$inventory->sku = !empty($row['sku']) ? trim($row['sku']) : null;
|
$inventory->sku = !empty($row['sku']) ? trim($row['sku']) : null;
|
||||||
$inventory->barcode = !empty($row['codigo_barras']) ? trim($row['codigo_barras']) : null;
|
$inventory->barcode = !empty($row['codigo_barras']) ? trim($row['codigo_barras']) : null;
|
||||||
$inventory->category_id = $categoryId;
|
$inventory->category_id = $categoryId;
|
||||||
|
$inventory->unit_of_measure_id = $unitId;
|
||||||
$inventory->is_active = true;
|
$inventory->is_active = true;
|
||||||
$inventory->track_serials = false; // Por defecto no rastrea seriales
|
$inventory->track_serials = false;
|
||||||
$inventory->save();
|
$inventory->save();
|
||||||
|
|
||||||
// Crear el precio del producto (sin costo inicial)
|
// Crear el precio del producto
|
||||||
Price::create([
|
Price::create([
|
||||||
'inventory_id' => $inventory->id,
|
'inventory_id' => $inventory->id,
|
||||||
'cost' => 0, // El costo se actualiza con movimientos de entrada
|
'cost' => 0,
|
||||||
'retail_price' => $precioVenta,
|
'retail_price' => $precioVenta,
|
||||||
'tax' => !empty($row['impuesto']) ? (float) $row['impuesto'] : 0,
|
'tax' => !empty($row['impuesto']) ? (float) $row['impuesto'] : 0,
|
||||||
]);
|
]);
|
||||||
@ -119,7 +167,7 @@ public function model(array $row)
|
|||||||
return $inventory;
|
return $inventory;
|
||||||
} catch (\Exception $e) {
|
} catch (\Exception $e) {
|
||||||
$this->skipped++;
|
$this->skipped++;
|
||||||
$this->errors[] = "Error en fila: " . $e->getMessage();
|
$this->errors[] = "Error creando producto '{$row['nombre']}': " . $e->getMessage();
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -148,6 +196,17 @@ private function updateExistingProduct(Inventory $inventory, array $row)
|
|||||||
$inventory->category_id = $category->id;
|
$inventory->category_id = $category->id;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Actualizar unidad de medida si se proporciona
|
||||||
|
if (!empty($row['unidad_medida'])) {
|
||||||
|
$unit = UnitOfMeasurement::where('name', trim($row['unidad_medida']))
|
||||||
|
->orWhere('abbreviation', trim($row['unidad_medida']))
|
||||||
|
->first();
|
||||||
|
|
||||||
|
if ($unit) {
|
||||||
|
$inventory->unit_of_measure_id = $unit->id;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
$inventory->save();
|
$inventory->save();
|
||||||
|
|
||||||
// Actualizar precio de venta e impuesto (NO el costo)
|
// Actualizar precio de venta e impuesto (NO el costo)
|
||||||
|
|||||||
@ -13,6 +13,7 @@ class InventoryMovement extends Model
|
|||||||
'movement_type',
|
'movement_type',
|
||||||
'quantity',
|
'quantity',
|
||||||
'unit_cost',
|
'unit_cost',
|
||||||
|
'supplier_id',
|
||||||
'reference_type',
|
'reference_type',
|
||||||
'reference_id',
|
'reference_id',
|
||||||
'user_id',
|
'user_id',
|
||||||
@ -31,6 +32,10 @@ public function inventory() {
|
|||||||
return $this->belongsTo(Inventory::class);
|
return $this->belongsTo(Inventory::class);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function supplier() {
|
||||||
|
return $this->belongsTo(Supplier::class);
|
||||||
|
}
|
||||||
|
|
||||||
public function warehouseFrom() {
|
public function warehouseFrom() {
|
||||||
return $this->belongsTo(Warehouse::class, 'warehouse_from_id');
|
return $this->belongsTo(Warehouse::class, 'warehouse_from_id');
|
||||||
}
|
}
|
||||||
|
|||||||
31
app/Models/Supplier.php
Normal file
31
app/Models/Supplier.php
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
<?php namespace App\Models;
|
||||||
|
|
||||||
|
use Illuminate\Database\Eloquent\Model;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Descripción
|
||||||
|
*/
|
||||||
|
class Supplier extends Model
|
||||||
|
{
|
||||||
|
protected $fillable = [
|
||||||
|
'business_name',
|
||||||
|
'rfc',
|
||||||
|
'email',
|
||||||
|
'phone',
|
||||||
|
'address',
|
||||||
|
'postal_code',
|
||||||
|
'notes'
|
||||||
|
];
|
||||||
|
|
||||||
|
public function inventoryMovements()
|
||||||
|
{
|
||||||
|
return $this->hasMany(InventoryMovement::class)->where('movement_type', 'entry');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function suppliedProducts()
|
||||||
|
{
|
||||||
|
return $this->hasManyThrough(Inventory::class, InventoryMovement::class, 'supplier_id', 'id', 'id', 'product_id')
|
||||||
|
->where('inventory_movements.movement_type', 'entry')
|
||||||
|
->distinct();
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -99,6 +99,7 @@ public function entry(array $data): InventoryMovement
|
|||||||
'movement_type' => 'entry',
|
'movement_type' => 'entry',
|
||||||
'quantity' => $quantity,
|
'quantity' => $quantity,
|
||||||
'unit_cost' => $unitCost,
|
'unit_cost' => $unitCost,
|
||||||
|
'supplier_id' => $data['supplier_id'] ?? null,
|
||||||
'user_id' => auth()->id(),
|
'user_id' => auth()->id(),
|
||||||
'notes' => $data['notes'] ?? null,
|
'notes' => $data['notes'] ?? null,
|
||||||
'invoice_reference' => $data['invoice_reference'] ?? null,
|
'invoice_reference' => $data['invoice_reference'] ?? null,
|
||||||
@ -182,7 +183,6 @@ public function bulkEntry(array $data): array
|
|||||||
$this->updateWarehouseStock($inventory->id, $warehouse->id, $quantity);
|
$this->updateWarehouseStock($inventory->id, $warehouse->id, $quantity);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Registrar movimiento
|
|
||||||
$movement = InventoryMovement::create([
|
$movement = InventoryMovement::create([
|
||||||
'inventory_id' => $inventory->id,
|
'inventory_id' => $inventory->id,
|
||||||
'warehouse_from_id' => null,
|
'warehouse_from_id' => null,
|
||||||
@ -190,6 +190,7 @@ public function bulkEntry(array $data): array
|
|||||||
'movement_type' => 'entry',
|
'movement_type' => 'entry',
|
||||||
'quantity' => $quantity,
|
'quantity' => $quantity,
|
||||||
'unit_cost' => $unitCost,
|
'unit_cost' => $unitCost,
|
||||||
|
'supplier_id' => $data['supplier_id'] ?? null,
|
||||||
'user_id' => auth()->id(),
|
'user_id' => auth()->id(),
|
||||||
'notes' => $data['notes'] ?? null,
|
'notes' => $data['notes'] ?? null,
|
||||||
'invoice_reference' => $data['invoice_reference'],
|
'invoice_reference' => $data['invoice_reference'],
|
||||||
|
|||||||
@ -0,0 +1,50 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
use Illuminate\Database\Migrations\Migration;
|
||||||
|
use Illuminate\Database\Schema\Blueprint;
|
||||||
|
use Illuminate\Support\Facades\Schema;
|
||||||
|
|
||||||
|
return new class extends Migration
|
||||||
|
{
|
||||||
|
public function up(): void
|
||||||
|
{
|
||||||
|
// 1. Preguntamos si la tabla NO existe antes de crearla
|
||||||
|
if (!Schema::hasTable('suppliers')) {
|
||||||
|
Schema::create('suppliers', function (Blueprint $table) {
|
||||||
|
$table->id();
|
||||||
|
$table->string('business_name');
|
||||||
|
$table->string('rfc')->nullable();
|
||||||
|
$table->string('email')->nullable();
|
||||||
|
$table->string('phone')->nullable();
|
||||||
|
$table->string('address')->nullable();
|
||||||
|
$table->string('postal_code')->nullable();
|
||||||
|
$table->text('notes')->nullable();
|
||||||
|
$table->timestamps();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!Schema::hasColumn('inventory_movements', 'supplier_id')) {
|
||||||
|
Schema::table('inventory_movements', function (Blueprint $table) {
|
||||||
|
$table->foreignId('supplier_id')
|
||||||
|
->nullable()
|
||||||
|
->after('warehouse_to_id')
|
||||||
|
->constrained('suppliers')
|
||||||
|
->onDelete('restrict');
|
||||||
|
$table->index(['supplier_id', 'movement_type']);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function down(): void
|
||||||
|
{
|
||||||
|
Schema::table('inventory_movements', function (Blueprint $table) {
|
||||||
|
if (Schema::hasColumn('inventory_movements', 'supplier_id')) {
|
||||||
|
$table->dropForeign(['supplier_id']);
|
||||||
|
$table->dropIndex(['supplier_id', 'movement_type']);
|
||||||
|
$table->dropColumn('supplier_id');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
Schema::dropIfExists('suppliers');
|
||||||
|
}
|
||||||
|
};
|
||||||
@ -9,7 +9,6 @@
|
|||||||
use App\Models\PermissionType;
|
use App\Models\PermissionType;
|
||||||
use App\Models\Role;
|
use App\Models\Role;
|
||||||
use Illuminate\Database\Seeder;
|
use Illuminate\Database\Seeder;
|
||||||
use Illuminate\Support\Facades\DB;
|
|
||||||
use Notsoweb\LaravelCore\Traits\MySql\RolePermission;
|
use Notsoweb\LaravelCore\Traits\MySql\RolePermission;
|
||||||
use Spatie\Permission\Models\Permission;
|
use Spatie\Permission\Models\Permission;
|
||||||
|
|
||||||
@ -179,6 +178,15 @@ public function run(): void
|
|||||||
$movementsEdit = $this->onEdit('movements', 'Actualizar registro', $movementsType, 'api');
|
$movementsEdit = $this->onEdit('movements', 'Actualizar registro', $movementsType, 'api');
|
||||||
$movementsDestroy = $this->onDestroy('movements', 'Eliminar registro', $movementsType, 'api');
|
$movementsDestroy = $this->onDestroy('movements', 'Eliminar registro', $movementsType, 'api');
|
||||||
|
|
||||||
|
$suppliers = PermissionType::firstOrCreate([
|
||||||
|
'name' => 'Proveedores'
|
||||||
|
]);
|
||||||
|
|
||||||
|
$supplierIndex = $this->onIndex('suppliers', 'Mostrar datos', $suppliers, 'api');
|
||||||
|
$supplierCreate = $this->onCreate('suppliers', 'Crear registros', $suppliers, 'api');
|
||||||
|
$supplierEdit = $this->onEdit('suppliers', 'Actualizar registro', $suppliers, 'api');
|
||||||
|
$supplierDestroy = $this->onDestroy('suppliers', 'Eliminar registro', $suppliers, 'api');
|
||||||
|
|
||||||
// ==================== ROLES ====================
|
// ==================== ROLES ====================
|
||||||
|
|
||||||
// Desarrollador
|
// Desarrollador
|
||||||
@ -248,7 +256,11 @@ public function run(): void
|
|||||||
$unitsIndex,
|
$unitsIndex,
|
||||||
$unitsCreate,
|
$unitsCreate,
|
||||||
$unitsEdit,
|
$unitsEdit,
|
||||||
$unitsDestroy
|
$unitsDestroy,
|
||||||
|
$supplierIndex,
|
||||||
|
$supplierCreate,
|
||||||
|
$supplierEdit,
|
||||||
|
$supplierDestroy
|
||||||
);
|
);
|
||||||
|
|
||||||
//Operador PDV (solo permisos de operación de caja y ventas)
|
//Operador PDV (solo permisos de operación de caja y ventas)
|
||||||
|
|||||||
@ -11,7 +11,8 @@ class UnitsSeeder extends Seeder
|
|||||||
public function run(): void
|
public function run(): void
|
||||||
{
|
{
|
||||||
$units = [
|
$units = [
|
||||||
['name' => 'Pieza', 'abbreviation' => 'u', 'allows_decimals' => false],
|
['name' => 'Serials', 'abbreviation' => 'ser', 'allows_decimals' => false],
|
||||||
|
['name' => 'Pieza', 'abbreviation' => 'u', 'allows_decimals' => true],
|
||||||
['name' => 'Kilogramo', 'abbreviation' => 'kg', 'allows_decimals' => true],
|
['name' => 'Kilogramo', 'abbreviation' => 'kg', 'allows_decimals' => true],
|
||||||
['name' => 'Litro', 'abbreviation' => 'L', 'allows_decimals' => true],
|
['name' => 'Litro', 'abbreviation' => 'L', 'allows_decimals' => true],
|
||||||
['name' => 'Metro', 'abbreviation' => 'm', 'allows_decimals' => true],
|
['name' => 'Metro', 'abbreviation' => 'm', 'allows_decimals' => true],
|
||||||
|
|||||||
@ -15,6 +15,7 @@
|
|||||||
use App\Http\Controllers\App\InvoiceRequestController;
|
use App\Http\Controllers\App\InvoiceRequestController;
|
||||||
use App\Http\Controllers\App\InventoryMovementController;
|
use App\Http\Controllers\App\InventoryMovementController;
|
||||||
use App\Http\Controllers\App\KardexController;
|
use App\Http\Controllers\App\KardexController;
|
||||||
|
use App\Http\Controllers\App\SupplierController;
|
||||||
use App\Http\Controllers\App\UnitOfMeasurementController;
|
use App\Http\Controllers\App\UnitOfMeasurementController;
|
||||||
use App\Http\Controllers\App\WarehouseController;
|
use App\Http\Controllers\App\WarehouseController;
|
||||||
use App\Http\Controllers\App\WhatsappController;
|
use App\Http\Controllers\App\WhatsappController;
|
||||||
@ -142,6 +143,16 @@
|
|||||||
Route::post('/{id}/upload', [InvoiceRequestController::class, 'uploadInvoiceFile']);
|
Route::post('/{id}/upload', [InvoiceRequestController::class, 'uploadInvoiceFile']);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
Route::prefix('proveedores')->group(function () {
|
||||||
|
Route::get('/', [SupplierController::class, 'index']);
|
||||||
|
Route::post('/', [SupplierController::class, 'store']);
|
||||||
|
Route::get('/{supplier}', [SupplierController::class, 'show']);
|
||||||
|
Route::put('/{supplier}', [SupplierController::class, 'update']);
|
||||||
|
Route::delete('/{supplier}', [SupplierController::class, 'destroy']);
|
||||||
|
Route::get('/{supplier}/productos', [SupplierController::class, 'products']);
|
||||||
|
Route::get('/{supplier}/compras', [SupplierController::class, 'purchases']);
|
||||||
|
});
|
||||||
|
|
||||||
// WHATSAPP
|
// WHATSAPP
|
||||||
Route::prefix('whatsapp')->group(function () {
|
Route::prefix('whatsapp')->group(function () {
|
||||||
Route::post('/send-document', [WhatsappController::class, 'sendDocument']);
|
Route::post('/send-document', [WhatsappController::class, 'sendDocument']);
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user