Initial LisgloSIPS V2 implementation
This commit is contained in:
+135
@@ -0,0 +1,135 @@
|
||||
import { BadRequestException, Inject, Injectable } from '@nestjs/common';
|
||||
import {
|
||||
VENDORS_REPOSITORY,
|
||||
type CreateVendorInput,
|
||||
type UpdateVendorInput,
|
||||
type VendorStatus,
|
||||
type VendorSummary,
|
||||
type VendorsRepository
|
||||
} from './vendors.repository.js';
|
||||
|
||||
interface CreateVendorDto {
|
||||
name?: unknown;
|
||||
contactName?: unknown;
|
||||
phone?: unknown;
|
||||
email?: unknown;
|
||||
status?: unknown;
|
||||
creditLimit?: unknown;
|
||||
settlement?: unknown;
|
||||
notes?: unknown;
|
||||
}
|
||||
|
||||
interface UpdateVendorDto {
|
||||
name?: unknown;
|
||||
contactName?: unknown;
|
||||
phone?: unknown;
|
||||
email?: unknown;
|
||||
status?: unknown;
|
||||
creditLimit?: unknown;
|
||||
settlement?: unknown;
|
||||
notes?: unknown;
|
||||
}
|
||||
|
||||
@Injectable()
|
||||
export class VendorsService {
|
||||
constructor(@Inject(VENDORS_REPOSITORY) private readonly vendors: VendorsRepository) {}
|
||||
|
||||
list(): Promise<VendorSummary[]> {
|
||||
return this.vendors.list();
|
||||
}
|
||||
|
||||
get(vendorId: string): Promise<VendorSummary> {
|
||||
return this.vendors.get(vendorId);
|
||||
}
|
||||
|
||||
create(body: CreateVendorDto, actorId?: string): Promise<VendorSummary> {
|
||||
const input: CreateVendorInput = {
|
||||
name: this.limitedString(body.name, 'name', 120),
|
||||
contactName: this.optionalString(body.contactName, 'contactName', 80),
|
||||
phone: this.optionalString(body.phone, 'phone', 32),
|
||||
email: this.optionalString(body.email, 'email', 160),
|
||||
status: body.status === undefined ? 'ENABLED' : this.status(body.status),
|
||||
creditLimit: body.creditLimit === undefined ? '0.000000' : this.money(body.creditLimit, 'creditLimit'),
|
||||
settlement: this.optionalString(body.settlement, 'settlement', 80),
|
||||
notes: this.optionalString(body.notes, 'notes', 500),
|
||||
actorId
|
||||
};
|
||||
|
||||
return this.vendors.create(input);
|
||||
}
|
||||
|
||||
update(vendorId: string, body: UpdateVendorDto, actorId?: string): Promise<VendorSummary> {
|
||||
const input: UpdateVendorInput = {
|
||||
name: body.name === undefined ? undefined : this.limitedString(body.name, 'name', 120),
|
||||
contactName: body.contactName === undefined ? undefined : this.nullableString(body.contactName, 'contactName', 80),
|
||||
phone: body.phone === undefined ? undefined : this.nullableString(body.phone, 'phone', 32),
|
||||
email: body.email === undefined ? undefined : this.nullableString(body.email, 'email', 160),
|
||||
status: body.status === undefined ? undefined : this.status(body.status),
|
||||
creditLimit: body.creditLimit === undefined ? undefined : this.money(body.creditLimit, 'creditLimit'),
|
||||
settlement: body.settlement === undefined ? undefined : this.nullableString(body.settlement, 'settlement', 80),
|
||||
notes: body.notes === undefined ? undefined : this.nullableString(body.notes, 'notes', 500),
|
||||
actorId
|
||||
};
|
||||
|
||||
return this.vendors.update(vendorId, input);
|
||||
}
|
||||
|
||||
enable(vendorId: string, actorId?: string): Promise<VendorSummary> {
|
||||
return this.vendors.setStatus(vendorId, 'ENABLED', actorId);
|
||||
}
|
||||
|
||||
disable(vendorId: string, actorId?: string): Promise<VendorSummary> {
|
||||
return this.vendors.setStatus(vendorId, 'DISABLED', actorId);
|
||||
}
|
||||
|
||||
remove(vendorId: string, actorId?: string): Promise<VendorSummary> {
|
||||
return this.vendors.softDelete(vendorId, actorId);
|
||||
}
|
||||
|
||||
private limitedString(value: unknown, field: string, maxLength: number): string {
|
||||
if (typeof value !== 'string' || value.trim().length === 0) {
|
||||
throw new BadRequestException({ code: 'VALIDATION_ERROR', message: `${field} is required.` });
|
||||
}
|
||||
|
||||
const trimmed = value.trim();
|
||||
if (trimmed.length > maxLength) {
|
||||
throw new BadRequestException({ code: 'VALIDATION_ERROR', message: `${field} is too long.` });
|
||||
}
|
||||
|
||||
return trimmed;
|
||||
}
|
||||
|
||||
private optionalString(value: unknown, field: string, maxLength: number): string | undefined {
|
||||
if (value === undefined) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return this.limitedString(value, field, maxLength);
|
||||
}
|
||||
|
||||
private nullableString(value: unknown, field: string, maxLength: number): string | null {
|
||||
if (value === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return this.limitedString(value, field, maxLength);
|
||||
}
|
||||
|
||||
private status(value: unknown): VendorStatus {
|
||||
if (value !== 'ENABLED' && value !== 'DISABLED') {
|
||||
throw new BadRequestException({ code: 'STATUS_INVALID', message: 'Status is invalid.' });
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
private money(value: unknown, field: string): string {
|
||||
const raw = typeof value === 'number' ? value.toString() : typeof value === 'string' ? value.trim() : '';
|
||||
if (!/^\d{1,14}(?:\.\d{1,6})?$/.test(raw)) {
|
||||
throw new BadRequestException({ code: 'MONEY_INVALID', message: `${field} must be a non-negative decimal with up to 6 places.` });
|
||||
}
|
||||
|
||||
const [integerPart, fractionPart = ''] = raw.split('.');
|
||||
return `${integerPart}.${fractionPart.padEnd(6, '0')}`;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user