feat: add Warehouses + Integrations modules, /admin/configuracion page
- Schema v0.3: Warehouse + Integration models (db push applied) - WarehousesModule: CRUD, set-default, multi-warehouse support - IntegrationsModule: 26 keys across 7 groups (payment, notifications, customs, courier, marketplace, compliance, warehouse) - AuthService: suite address pulled from default Warehouse in DB (env fallback) - AuthModule: imports WarehousesModule - Seed: creates default warehouse from WAREHOUSE_ADDRESS_* env vars - Web: /admin/configuracion page (3 tabs: Bodegas, Integraciones, Estado APIs) - Web: admin layout adds Configuracion nav link - api.ts: warehouses + integrations client methods
This commit is contained in:
@@ -14,6 +14,8 @@ import { AuditLogModule } from "./audit-log/audit-log.module";
|
||||
import { NotificationsModule } from "./notifications/notifications.module";
|
||||
import { TariffsModule } from "./tariffs/tariffs.module";
|
||||
import { ProductsModule } from "./products/products.module";
|
||||
import { WarehousesModule } from "./warehouses/warehouses.module";
|
||||
import { IntegrationsModule } from "./integrations/integrations.module";
|
||||
|
||||
@Module({
|
||||
imports: [
|
||||
@@ -35,6 +37,8 @@ import { ProductsModule } from "./products/products.module";
|
||||
NotificationsModule,
|
||||
TariffsModule,
|
||||
ProductsModule,
|
||||
WarehousesModule,
|
||||
IntegrationsModule,
|
||||
],
|
||||
})
|
||||
export class AppModule {}
|
||||
|
||||
@@ -5,6 +5,7 @@ import { ConfigModule, ConfigService } from "@nestjs/config";
|
||||
import { AuthController } from "./auth.controller";
|
||||
import { AuthService } from "./auth.service";
|
||||
import { JwtStrategy } from "./jwt.strategy";
|
||||
import { WarehousesModule } from "../warehouses/warehouses.module";
|
||||
|
||||
@Module({
|
||||
imports: [
|
||||
@@ -17,6 +18,7 @@ import { JwtStrategy } from "./jwt.strategy";
|
||||
signOptions: { expiresIn: config.get("JWT_EXPIRES_IN", "15m") },
|
||||
}),
|
||||
}),
|
||||
WarehousesModule,
|
||||
],
|
||||
controllers: [AuthController],
|
||||
providers: [AuthService, JwtStrategy],
|
||||
|
||||
@@ -4,6 +4,7 @@ import {
|
||||
import { JwtService } from "@nestjs/jwt";
|
||||
import { ConfigService } from "@nestjs/config";
|
||||
import { PrismaService } from "../prisma/prisma.service";
|
||||
import { WarehousesService } from "../warehouses/warehouses.service";
|
||||
import { generateSuiteCode } from "../common/utils/suite-code.util";
|
||||
import { RegisterDto, LoginDto } from "./dto/auth.dto";
|
||||
import * as bcrypt from "bcrypt";
|
||||
@@ -13,22 +14,29 @@ import { TOTP, generateSecret, generateURI, verify as totpVerify } from "otplib"
|
||||
const TENANT_SLUG = "moraworld";
|
||||
const BCRYPT_ROUNDS = 10;
|
||||
|
||||
function buildSuiteAddress(suiteCode: string, config: import("@nestjs/config").ConfigService): string {
|
||||
const street = config.get("WAREHOUSE_ADDRESS_STREET", "150 N Day St");
|
||||
const city = config.get("WAREHOUSE_ADDRESS_CITY", "City of Orange");
|
||||
const state = config.get("WAREHOUSE_ADDRESS_STATE", "NJ");
|
||||
const zip = config.get("WAREHOUSE_ADDRESS_ZIP", "07050");
|
||||
return `${street}, Suite ${suiteCode}, ${city}, ${state} ${zip}, EE.UU.`;
|
||||
}
|
||||
|
||||
@Injectable()
|
||||
export class AuthService {
|
||||
constructor(
|
||||
private prisma: PrismaService,
|
||||
private jwt: JwtService,
|
||||
private config: ConfigService,
|
||||
private warehouses: WarehousesService,
|
||||
) {}
|
||||
|
||||
/** Builds the suite address from the default warehouse in DB, falls back to env vars */
|
||||
private async buildSuiteAddress(suiteCode: string, tenantId: string): Promise<string> {
|
||||
const wh = await this.warehouses.findDefault(tenantId);
|
||||
if (wh) {
|
||||
return `${wh.street}, Suite ${suiteCode}, ${wh.city}, ${wh.state} ${wh.zip}, EE.UU.`;
|
||||
}
|
||||
// Fallback to env vars (backwards compat during migration)
|
||||
const street = this.config.get("WAREHOUSE_ADDRESS_STREET", "150 N Day St");
|
||||
const city = this.config.get("WAREHOUSE_ADDRESS_CITY", "City of Orange");
|
||||
const state = this.config.get("WAREHOUSE_ADDRESS_STATE", "NJ");
|
||||
const zip = this.config.get("WAREHOUSE_ADDRESS_ZIP", "07050");
|
||||
return `${street}, Suite ${suiteCode}, ${city}, ${state} ${zip}, EE.UU.`;
|
||||
}
|
||||
|
||||
// ─── Register ────────────────────────────────────────────────
|
||||
async register(dto: RegisterDto): Promise<any> {
|
||||
const tenant = await this.prisma.client.tenant.findUnique({ where: { slug: TENANT_SLUG } });
|
||||
@@ -66,7 +74,7 @@ export class AuthService {
|
||||
return {
|
||||
user: this.sanitizeUser(user),
|
||||
suiteCode,
|
||||
suiteAddress: buildSuiteAddress(suiteCode, this.config),
|
||||
suiteAddress: await this.buildSuiteAddress(suiteCode, tenant.id),
|
||||
...tokens,
|
||||
};
|
||||
}
|
||||
@@ -114,7 +122,7 @@ export class AuthService {
|
||||
user: this.sanitizeUser(user),
|
||||
suite: suite ? {
|
||||
code: suite.code,
|
||||
address: buildSuiteAddress(suite.code, this.config),
|
||||
address: await this.buildSuiteAddress(suite.code, tenant.id),
|
||||
} : null,
|
||||
...tokens,
|
||||
};
|
||||
@@ -186,7 +194,7 @@ export class AuthService {
|
||||
...this.sanitizeUser(user),
|
||||
suite: suite ? {
|
||||
code: suite.code,
|
||||
address: buildSuiteAddress(suite.code, this.config),
|
||||
address: await this.buildSuiteAddress(suite.code, user.tenantId),
|
||||
} : null,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
import { Controller, Get, Put, Body, UseGuards } from "@nestjs/common";
|
||||
import { IntegrationsService } from "./integrations.service";
|
||||
import { JwtAuthGuard, RolesGuard, Roles } from "../auth/guards/auth.guard";
|
||||
import { CurrentUser } from "../auth/decorators/current-user.decorator";
|
||||
|
||||
@Controller("integrations")
|
||||
@UseGuards(JwtAuthGuard, RolesGuard)
|
||||
@Roles("ADMIN_EMPRESA", "SUPER_ADMIN")
|
||||
export class IntegrationsController {
|
||||
constructor(private svc: IntegrationsService) {}
|
||||
|
||||
@Get()
|
||||
findAll(@CurrentUser() user: any) {
|
||||
return this.svc.findAll(user.tenantId);
|
||||
}
|
||||
|
||||
@Get("status")
|
||||
getStatus(@CurrentUser() user: any) {
|
||||
return this.svc.getStatus(user.tenantId);
|
||||
}
|
||||
|
||||
@Put("batch")
|
||||
batchUpsert(
|
||||
@CurrentUser() user: any,
|
||||
@Body() body: { items: Array<{ key: string; value: string | null; isActive: boolean }> },
|
||||
) {
|
||||
return this.svc.batchUpsert(user.tenantId, body.items, user.id);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
import { Module } from "@nestjs/common";
|
||||
import { IntegrationsService } from "./integrations.service";
|
||||
import { IntegrationsController } from "./integrations.controller";
|
||||
import { PrismaModule } from "../prisma/prisma.module";
|
||||
|
||||
@Module({
|
||||
imports: [PrismaModule],
|
||||
providers: [IntegrationsService],
|
||||
controllers: [IntegrationsController],
|
||||
exports: [IntegrationsService],
|
||||
})
|
||||
export class IntegrationsModule {}
|
||||
@@ -0,0 +1,122 @@
|
||||
import { Injectable } from "@nestjs/common";
|
||||
import { PrismaService } from "../prisma/prisma.service";
|
||||
|
||||
/** All integration keys defined in the system, grouped by category */
|
||||
export const INTEGRATION_CATALOG = [
|
||||
// ── Pasarela de Pagos ──────────────────────────────────────
|
||||
{ key: "stripe_public_key", label: "Stripe — Clave pública", group: "payment", required: false },
|
||||
{ key: "stripe_secret_key", label: "Stripe — Clave secreta", group: "payment", required: false },
|
||||
{ key: "payphone_token", label: "PayPhone — Token de API", group: "payment", required: false },
|
||||
{ key: "paypal_client_id", label: "PayPal — Client ID", group: "payment", required: false },
|
||||
// ── Notificaciones ────────────────────────────────────────
|
||||
{ key: "whatsapp_token", label: "WhatsApp Business — Token", group: "notifications", required: true },
|
||||
{ key: "whatsapp_phone_id", label: "WhatsApp Business — Phone ID", group: "notifications", required: true },
|
||||
{ key: "whatsapp_nj_number", label: "WhatsApp NJ (operaciones)", group: "notifications", required: false },
|
||||
{ key: "whatsapp_ec_number", label: "WhatsApp Ecuador (aduanas)", group: "notifications", required: false },
|
||||
{ key: "sendgrid_api_key", label: "SendGrid — API Key", group: "notifications", required: false },
|
||||
{ key: "email_from", label: "Email remitente (from)", group: "notifications", required: false },
|
||||
{ key: "sms_provider", label: "SMS — Proveedor (ej: Twilio)", group: "notifications", required: false },
|
||||
{ key: "sms_api_key", label: "SMS — API Key", group: "notifications", required: false },
|
||||
// ── Aduana / SENAE ────────────────────────────────────────
|
||||
{ key: "senae_endpoint", label: "SENAE — URL WebService", group: "customs", required: true },
|
||||
{ key: "senae_api_key", label: "SENAE — API Key / Token", group: "customs", required: true },
|
||||
{ key: "senae_ruc", label: "RUC declarante SENAE", group: "customs", required: true },
|
||||
{ key: "senae_agent_code", label: "Código Agente Aduanero SENAE", group: "customs", required: false },
|
||||
// ── Couriers ──────────────────────────────────────────────
|
||||
{ key: "fedex_api_key", label: "FedEx — API Key", group: "courier", required: false },
|
||||
{ key: "fedex_account", label: "FedEx — Account Number", group: "courier", required: false },
|
||||
{ key: "dhl_api_key", label: "DHL — API Key", group: "courier", required: false },
|
||||
{ key: "ups_client_id", label: "UPS — Client ID", group: "courier", required: false },
|
||||
{ key: "ups_client_secret", label: "UPS — Client Secret", group: "courier", required: false },
|
||||
// ── Amazon SP-API ─────────────────────────────────────────
|
||||
{ key: "amazon_client_id", label: "Amazon SP-API — Client ID", group: "marketplace", required: false },
|
||||
{ key: "amazon_client_secret", label: "Amazon SP-API — Client Secret", group: "marketplace", required: false },
|
||||
{ key: "amazon_refresh_token", label: "Amazon SP-API — Refresh Token", group: "marketplace", required: false },
|
||||
// ── INEN ──────────────────────────────────────────────────
|
||||
{ key: "inen_endpoint", label: "INEN — URL de consulta", group: "compliance", required: false },
|
||||
{ key: "inen_api_key", label: "INEN — API Key", group: "compliance", required: false },
|
||||
// ── Bodega propia ─────────────────────────────────────────
|
||||
{ key: "warehouse_api_url", label: "Software Bodega — URL base", group: "warehouse", required: false },
|
||||
{ key: "warehouse_api_key", label: "Software Bodega — API Key", group: "warehouse", required: false },
|
||||
{ key: "warehouse_webhook_secret", label: "Software Bodega — Webhook Secret", group: "warehouse", required: false },
|
||||
];
|
||||
|
||||
@Injectable()
|
||||
export class IntegrationsService {
|
||||
constructor(private prisma: PrismaService) {}
|
||||
|
||||
/** Return all integration keys for the tenant, merged with the catalog */
|
||||
async findAll(tenantId: string): Promise<any[]> {
|
||||
const stored = await this.prisma.client.integration.findMany({
|
||||
where: { tenantId },
|
||||
});
|
||||
const storedMap = new Map(stored.map(s => [s.key, s]));
|
||||
|
||||
return INTEGRATION_CATALOG.map(cat => {
|
||||
const stored = storedMap.get(cat.key);
|
||||
return {
|
||||
...cat,
|
||||
id: stored?.id ?? null,
|
||||
value: stored?.value ? "***" : null, // mask for security
|
||||
hasValue: !!stored?.value,
|
||||
isActive: stored?.isActive ?? false,
|
||||
updatedAt: stored?.updatedAt ?? null,
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
/** Upsert a single integration key */
|
||||
async upsert(tenantId: string, key: string, value: string | null, isActive: boolean, updatedBy: string): Promise<any> {
|
||||
const cat = INTEGRATION_CATALOG.find(c => c.key === key);
|
||||
const result = await this.prisma.client.integration.upsert({
|
||||
where: { tenantId_key: { tenantId, key } },
|
||||
create: {
|
||||
tenantId,
|
||||
key,
|
||||
value: value ?? null,
|
||||
label: cat?.label,
|
||||
group: cat?.group,
|
||||
isActive,
|
||||
updatedBy,
|
||||
},
|
||||
update: {
|
||||
value: value !== undefined ? value : undefined,
|
||||
isActive,
|
||||
updatedBy,
|
||||
updatedAt: new Date(),
|
||||
},
|
||||
});
|
||||
return { ...result, value: result.value ? "***" : null, hasValue: !!result.value };
|
||||
}
|
||||
|
||||
/** Batch upsert multiple keys at once */
|
||||
async batchUpsert(tenantId: string, items: Array<{ key: string; value: string | null; isActive: boolean }>, updatedBy: string): Promise<void> {
|
||||
await Promise.all(
|
||||
items.map(item => this.upsert(tenantId, item.key, item.value, item.isActive, updatedBy))
|
||||
);
|
||||
}
|
||||
|
||||
/** Get a single integration value (unmasked — internal use only) */
|
||||
async getValue(tenantId: string, key: string): Promise<string | null> {
|
||||
const row = await this.prisma.client.integration.findUnique({
|
||||
where: { tenantId_key: { tenantId, key } },
|
||||
});
|
||||
return row?.isActive ? (row.value ?? null) : null;
|
||||
}
|
||||
|
||||
/** Return connection status summary by group */
|
||||
async getStatus(tenantId: string): Promise<Record<string, { connected: boolean; missing: string[] }>> {
|
||||
const stored = await this.prisma.client.integration.findMany({ where: { tenantId, isActive: true } });
|
||||
const activeKeys = new Set(stored.filter(s => !!s.value).map(s => s.key));
|
||||
|
||||
const groups = [...new Set(INTEGRATION_CATALOG.map(c => c.group ?? "other"))];
|
||||
const status: Record<string, { connected: boolean; missing: string[] }> = {};
|
||||
|
||||
for (const group of groups) {
|
||||
const required = INTEGRATION_CATALOG.filter(c => c.group === group && c.required);
|
||||
const missing = required.filter(c => !activeKeys.has(c.key)).map(c => c.label);
|
||||
status[group] = { connected: missing.length === 0 && required.length > 0, missing };
|
||||
}
|
||||
return status;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
import { IsString, IsOptional, IsBoolean, IsNumber, Min } from "class-validator";
|
||||
import { Type } from "class-transformer";
|
||||
|
||||
export class CreateWarehouseDto {
|
||||
@IsString() name!: string;
|
||||
@IsString() street!: string;
|
||||
@IsString() city!: string;
|
||||
@IsString() state!: string;
|
||||
@IsString() zip!: string;
|
||||
@IsOptional() @IsString() country?: string;
|
||||
@IsOptional() @IsString() phone?: string;
|
||||
@IsOptional() @IsString() email?: string;
|
||||
@IsOptional() @IsString() contactName?: string;
|
||||
@IsOptional() @IsString() schedule?: string;
|
||||
@IsOptional() @Type(() => Number) @IsNumber() @Min(0) capacityM2?: number;
|
||||
@IsOptional() @IsBoolean() isDefault?: boolean;
|
||||
}
|
||||
|
||||
export class UpdateWarehouseDto {
|
||||
@IsOptional() @IsString() name?: string;
|
||||
@IsOptional() @IsString() street?: string;
|
||||
@IsOptional() @IsString() city?: string;
|
||||
@IsOptional() @IsString() state?: string;
|
||||
@IsOptional() @IsString() zip?: string;
|
||||
@IsOptional() @IsString() country?: string;
|
||||
@IsOptional() @IsString() phone?: string;
|
||||
@IsOptional() @IsString() email?: string;
|
||||
@IsOptional() @IsString() contactName?: string;
|
||||
@IsOptional() @IsString() schedule?: string;
|
||||
@IsOptional() @Type(() => Number) @IsNumber() @Min(0) capacityM2?: number;
|
||||
@IsOptional() @IsBoolean() isActive?: boolean;
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
import { Controller, Get, Post, Patch, Delete, Body, Param, UseGuards } from "@nestjs/common";
|
||||
import { WarehousesService } from "./warehouses.service";
|
||||
import { CreateWarehouseDto, UpdateWarehouseDto } from "./dto/warehouse.dto";
|
||||
import { JwtAuthGuard, RolesGuard, Roles } from "../auth/guards/auth.guard";
|
||||
import { CurrentUser } from "../auth/decorators/current-user.decorator";
|
||||
|
||||
@Controller("warehouses")
|
||||
@UseGuards(JwtAuthGuard, RolesGuard)
|
||||
export class WarehousesController {
|
||||
constructor(private svc: WarehousesService) {}
|
||||
|
||||
@Get()
|
||||
@Roles("ADMIN_EMPRESA", "SUPER_ADMIN", "OPERADOR_BODEGA", "AGENTE_ADUANERO")
|
||||
findAll(@CurrentUser() user: any) {
|
||||
return this.svc.findAll(user.tenantId);
|
||||
}
|
||||
|
||||
@Get("default")
|
||||
@Roles("ADMIN_EMPRESA", "SUPER_ADMIN", "OPERADOR_BODEGA", "AGENTE_ADUANERO", "CLIENTE")
|
||||
findDefault(@CurrentUser() user: any) {
|
||||
return this.svc.findDefault(user.tenantId);
|
||||
}
|
||||
|
||||
@Get(":id")
|
||||
@Roles("ADMIN_EMPRESA", "SUPER_ADMIN", "OPERADOR_BODEGA")
|
||||
findOne(@Param("id") id: string, @CurrentUser() user: any) {
|
||||
return this.svc.findOne(id, user.tenantId);
|
||||
}
|
||||
|
||||
@Post()
|
||||
@Roles("ADMIN_EMPRESA", "SUPER_ADMIN")
|
||||
create(@Body() dto: CreateWarehouseDto, @CurrentUser() user: any) {
|
||||
return this.svc.create(dto, user.tenantId);
|
||||
}
|
||||
|
||||
@Patch(":id")
|
||||
@Roles("ADMIN_EMPRESA", "SUPER_ADMIN")
|
||||
update(@Param("id") id: string, @Body() dto: UpdateWarehouseDto, @CurrentUser() user: any) {
|
||||
return this.svc.update(id, dto, user.tenantId);
|
||||
}
|
||||
|
||||
@Patch(":id/set-default")
|
||||
@Roles("ADMIN_EMPRESA", "SUPER_ADMIN")
|
||||
setDefault(@Param("id") id: string, @CurrentUser() user: any) {
|
||||
return this.svc.setDefault(id, user.tenantId);
|
||||
}
|
||||
|
||||
@Delete(":id")
|
||||
@Roles("ADMIN_EMPRESA", "SUPER_ADMIN")
|
||||
remove(@Param("id") id: string, @CurrentUser() user: any) {
|
||||
return this.svc.remove(id, user.tenantId);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
import { Module } from "@nestjs/common";
|
||||
import { WarehousesService } from "./warehouses.service";
|
||||
import { WarehousesController } from "./warehouses.controller";
|
||||
import { PrismaModule } from "../prisma/prisma.module";
|
||||
|
||||
@Module({
|
||||
imports: [PrismaModule],
|
||||
providers: [WarehousesService],
|
||||
controllers: [WarehousesController],
|
||||
exports: [WarehousesService],
|
||||
})
|
||||
export class WarehousesModule {}
|
||||
@@ -0,0 +1,90 @@
|
||||
import { Injectable, NotFoundException, BadRequestException } from "@nestjs/common";
|
||||
import { PrismaService } from "../prisma/prisma.service";
|
||||
import { CreateWarehouseDto, UpdateWarehouseDto } from "./dto/warehouse.dto";
|
||||
|
||||
@Injectable()
|
||||
export class WarehousesService {
|
||||
constructor(private prisma: PrismaService) {}
|
||||
|
||||
async findAll(tenantId: string): Promise<any[]> {
|
||||
return this.prisma.client.warehouse.findMany({
|
||||
where: { tenantId },
|
||||
orderBy: [{ isDefault: "desc" }, { createdAt: "asc" }],
|
||||
});
|
||||
}
|
||||
|
||||
async findDefault(tenantId: string): Promise<any | null> {
|
||||
return this.prisma.client.warehouse.findFirst({
|
||||
where: { tenantId, isDefault: true, isActive: true },
|
||||
});
|
||||
}
|
||||
|
||||
async findOne(id: string, tenantId: string): Promise<any> {
|
||||
const wh = await this.prisma.client.warehouse.findFirst({ where: { id, tenantId } });
|
||||
if (!wh) throw new NotFoundException("Bodega no encontrada.");
|
||||
return wh;
|
||||
}
|
||||
|
||||
async create(dto: CreateWarehouseDto, tenantId: string): Promise<any> {
|
||||
// If this is the first warehouse or explicitly set as default, handle default logic
|
||||
const existing = await this.prisma.client.warehouse.count({ where: { tenantId } });
|
||||
const setAsDefault = dto.isDefault ?? existing === 0;
|
||||
|
||||
if (setAsDefault) {
|
||||
await this.prisma.client.warehouse.updateMany({
|
||||
where: { tenantId, isDefault: true },
|
||||
data: { isDefault: false },
|
||||
});
|
||||
}
|
||||
|
||||
return this.prisma.client.warehouse.create({
|
||||
data: {
|
||||
tenantId,
|
||||
name: dto.name,
|
||||
street: dto.street,
|
||||
city: dto.city,
|
||||
state: dto.state,
|
||||
zip: dto.zip,
|
||||
country: dto.country ?? "US",
|
||||
phone: dto.phone,
|
||||
email: dto.email,
|
||||
contactName: dto.contactName,
|
||||
schedule: dto.schedule,
|
||||
capacityM2: dto.capacityM2,
|
||||
isDefault: setAsDefault,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
async update(id: string, dto: UpdateWarehouseDto, tenantId: string): Promise<any> {
|
||||
await this.findOne(id, tenantId);
|
||||
return this.prisma.client.warehouse.update({
|
||||
where: { id },
|
||||
data: dto,
|
||||
});
|
||||
}
|
||||
|
||||
async setDefault(id: string, tenantId: string): Promise<any> {
|
||||
await this.findOne(id, tenantId);
|
||||
// Remove default from all, set on this one
|
||||
await this.prisma.client.warehouse.updateMany({
|
||||
where: { tenantId, isDefault: true },
|
||||
data: { isDefault: false },
|
||||
});
|
||||
return this.prisma.client.warehouse.update({
|
||||
where: { id },
|
||||
data: { isDefault: true, isActive: true },
|
||||
});
|
||||
}
|
||||
|
||||
async remove(id: string, tenantId: string): Promise<void> {
|
||||
const wh = await this.findOne(id, tenantId);
|
||||
if (wh.isDefault) throw new BadRequestException("No puedes eliminar la bodega predeterminada.");
|
||||
await this.prisma.client.warehouse.delete({ where: { id } });
|
||||
}
|
||||
|
||||
/** Build the suite address string from the default warehouse */
|
||||
buildSuiteAddress(warehouse: any, suiteCode: string): string {
|
||||
return `${warehouse.street}, Suite ${suiteCode}, ${warehouse.city}, ${warehouse.state} ${warehouse.zip}, EE.UU.`;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user