feat: expand admin and data sync workflows
This commit is contained in:
@@ -112,6 +112,26 @@ function dec(v: Prisma.Decimal | null | undefined): string {
|
||||
*/
|
||||
const NOT_VOIDED: Prisma.TransactionWhereInput = { voidedAt: null };
|
||||
|
||||
/**
|
||||
* Source tables excluded from the customer-facing statement.
|
||||
*
|
||||
* The legacy portal's `datosfreak` table was materialized from DATOS2 only
|
||||
* (`objects.json:1358`), so the customer's "current balance" never saw
|
||||
* EFECTIVO / EFECTIVO FM3 / CHEQUE FM3 / EFECTIVO_BACKUP cash receipts, nor
|
||||
* the IVA 2015 snapshot. The unified `transactions` table has all of them, so
|
||||
* the statement must drop them to match the legacy number the customer has
|
||||
* been quoted for years. The staff-facing balances worklist and movement
|
||||
* browser keep them — they're real money, just tracked separately
|
||||
* (FM3 = visa fee stream, EFECTIVO = cash receipt stream).
|
||||
*/
|
||||
const STATEMENT_EXCLUDED_SOURCE_TABLES: readonly string[] = [
|
||||
"EFECTIVO",
|
||||
"EFECTIVO_BACKUP",
|
||||
"EFECTIVO FM3",
|
||||
"CHEQUE FM3",
|
||||
"IVA 2015",
|
||||
];
|
||||
|
||||
@Injectable()
|
||||
export class BillingService {
|
||||
constructor(private readonly prisma: PrismaService) {}
|
||||
@@ -579,7 +599,10 @@ export class BillingService {
|
||||
}
|
||||
|
||||
const rows = await this.prisma.transaction.findMany({
|
||||
where: { customerId },
|
||||
where: {
|
||||
customerId,
|
||||
legacySourceTable: { notIn: STATEMENT_EXCLUDED_SOURCE_TABLES as string[] },
|
||||
},
|
||||
orderBy: [{ transactionDate: "asc" }, { id: "asc" }],
|
||||
select: {
|
||||
id: true,
|
||||
|
||||
@@ -44,7 +44,7 @@ export class OpsController {
|
||||
|
||||
@Post("ingest/:name")
|
||||
@UseInterceptors(
|
||||
FileInterceptor("file", { limits: { fileSize: 500 * 1024 * 1024 } }),
|
||||
FileInterceptor("file", { limits: { fileSize: 2 * 1024 * 1024 * 1024 } }),
|
||||
)
|
||||
async uploadIngest(
|
||||
@Param("name") name: string,
|
||||
|
||||
@@ -43,8 +43,11 @@ interface MysqlConn {
|
||||
export class OpsService implements OnModuleInit {
|
||||
private readonly logger = new Logger(OpsService.name);
|
||||
|
||||
// Resolve from this source file so it works regardless of process.cwd()
|
||||
// (the API runs from apps/api/, but the Python ETL lives at repo-root migration/).
|
||||
private readonly migrationDir =
|
||||
process.env.MIGRATION_DIR ?? path.resolve(process.cwd(), "migration");
|
||||
process.env.MIGRATION_DIR ??
|
||||
path.resolve(__dirname, "..", "..", "..", "..", "migration");
|
||||
private readonly ingestDir =
|
||||
process.env.INGEST_DIR ?? path.join(this.migrationDir, "ingest");
|
||||
private readonly backupDir =
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
import {
|
||||
Body,
|
||||
Controller,
|
||||
Delete,
|
||||
Get,
|
||||
HttpCode,
|
||||
Param,
|
||||
Patch,
|
||||
Post,
|
||||
@@ -69,4 +71,12 @@ export class UsersController {
|
||||
void this.audit.log(this.actingId(req), "user.reset_password", { userId: id });
|
||||
return user;
|
||||
}
|
||||
|
||||
@Delete(":id")
|
||||
@HttpCode(204)
|
||||
async remove(@Param("id") id: string, @Req() req: Request) {
|
||||
const actingId = this.actingId(req);
|
||||
await this.users.remove(id, actingId);
|
||||
void this.audit.log(actingId, "user.delete", { userId: id });
|
||||
}
|
||||
}
|
||||
|
||||
@@ -111,6 +111,30 @@ export class UsersService {
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Hard-delete a user. The schema's ActivityLog.userId FK would otherwise
|
||||
* block the row (default `Restrict`), so null it out in the same
|
||||
* transaction. Rows + the actor id captured in the `message` JSON stay
|
||||
* intact for the audit trail.
|
||||
*/
|
||||
async remove(id: string, actingUserId: string): Promise<void> {
|
||||
if (id === actingUserId) {
|
||||
throw new BadRequestException("No puede eliminar su propia cuenta");
|
||||
}
|
||||
await this.ensureExists(id);
|
||||
try {
|
||||
await this.prisma.$transaction([
|
||||
this.prisma.activityLog.updateMany({
|
||||
where: { userId: id },
|
||||
data: { userId: null },
|
||||
}),
|
||||
this.prisma.user.delete({ where: { id } }),
|
||||
]);
|
||||
} catch (e) {
|
||||
throw this.mapError(e);
|
||||
}
|
||||
}
|
||||
|
||||
private async ensureExists(id: string): Promise<void> {
|
||||
const found = await this.prisma.user.findUnique({ where: { id }, select: { id: true } });
|
||||
if (!found) throw new NotFoundException(`Usuario ${id} no encontrado`);
|
||||
|
||||
Reference in New Issue
Block a user