feat: implement ARCA integration architecture including credential, fiscal profile, and invoice models, services, and onboarding flows
This commit is contained in:
+100
@@ -0,0 +1,100 @@
|
||||
import { Document, FilterQuery, Model, Schema, model } from "mongoose";
|
||||
import {
|
||||
CreateOrganizationFiscalProfileParams,
|
||||
FindOrganizationFiscalProfilesParams,
|
||||
IOrganizationFiscalProfile,
|
||||
IOrganizationFiscalProfilesAdapter,
|
||||
ORGANIZATION_FISCAL_PROFILE_STATUS,
|
||||
ORGANIZATION_FISCAL_PROFILE_TAX_CONDITION,
|
||||
UpdateOrganizationFiscalProfileParams,
|
||||
} from "./OrganizationFiscalProfiles.Interface";
|
||||
|
||||
export interface IOrganizationFiscalProfileDocument
|
||||
extends Omit<IOrganizationFiscalProfile, "id">,
|
||||
Document {}
|
||||
|
||||
export class OrganizationFiscalProfilesAdapterMongoose implements IOrganizationFiscalProfilesAdapter {
|
||||
schema: Schema;
|
||||
organizationFiscalProfileList: Model<IOrganizationFiscalProfileDocument>;
|
||||
|
||||
constructor() {
|
||||
this.schema = new Schema({
|
||||
companyId: {
|
||||
type: Schema.Types.ObjectId,
|
||||
required: true,
|
||||
ref: "Company",
|
||||
unique: true,
|
||||
index: true,
|
||||
},
|
||||
cuit: { type: String, required: true },
|
||||
legalName: { type: String, required: true },
|
||||
taxCondition: {
|
||||
type: String,
|
||||
required: true,
|
||||
enum: Object.values(ORGANIZATION_FISCAL_PROFILE_TAX_CONDITION),
|
||||
},
|
||||
pointOfSale: { type: Number, required: true },
|
||||
status: {
|
||||
type: String,
|
||||
required: true,
|
||||
enum: Object.values(ORGANIZATION_FISCAL_PROFILE_STATUS),
|
||||
index: true,
|
||||
},
|
||||
createdAt: { type: Date, required: true, default: Date.now },
|
||||
updatedAt: { type: Date, required: true, default: Date.now },
|
||||
});
|
||||
|
||||
this.organizationFiscalProfileList = model<IOrganizationFiscalProfileDocument>(
|
||||
"OrganizationFiscalProfile",
|
||||
this.schema
|
||||
);
|
||||
}
|
||||
|
||||
public async create(
|
||||
data: CreateOrganizationFiscalProfileParams
|
||||
): Promise<IOrganizationFiscalProfile> {
|
||||
return await this.organizationFiscalProfileList.create(data);
|
||||
}
|
||||
|
||||
private buildSearchCriteria(
|
||||
filters: FindOrganizationFiscalProfilesParams
|
||||
): FilterQuery<IOrganizationFiscalProfile> {
|
||||
const searchCriteria: FilterQuery<IOrganizationFiscalProfile> = {};
|
||||
|
||||
if (filters.id) searchCriteria._id = filters.id;
|
||||
if (filters.companyId) searchCriteria.companyId = filters.companyId;
|
||||
if (filters.status) searchCriteria.status = filters.status;
|
||||
|
||||
return searchCriteria;
|
||||
}
|
||||
|
||||
public async find(
|
||||
filters: FindOrganizationFiscalProfilesParams
|
||||
): Promise<IOrganizationFiscalProfile[]> {
|
||||
const searchCriteria = this.buildSearchCriteria(filters);
|
||||
return this.organizationFiscalProfileList.find(searchCriteria).sort({ createdAt: -1 }).exec();
|
||||
}
|
||||
|
||||
public async findOne(
|
||||
filters: FindOrganizationFiscalProfilesParams
|
||||
): Promise<IOrganizationFiscalProfileDocument | null> {
|
||||
const searchCriteria = this.buildSearchCriteria(filters);
|
||||
return this.organizationFiscalProfileList.findOne(searchCriteria).exec();
|
||||
}
|
||||
|
||||
public async update(
|
||||
data: UpdateOrganizationFiscalProfileParams
|
||||
): Promise<IOrganizationFiscalProfileDocument | null> {
|
||||
const { id, companyId, sessionUser, ...updateData } = data;
|
||||
|
||||
if (!id && !companyId) {
|
||||
throw new Error("Se requiere un identificador para actualizar el perfil fiscal");
|
||||
}
|
||||
|
||||
return this.organizationFiscalProfileList
|
||||
.findOneAndUpdate(this.buildSearchCriteria({ id, companyId, sessionUser }), updateData, {
|
||||
new: true,
|
||||
})
|
||||
.exec();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
import { IOrganizationFiscalProfileDocument } from "./OrganizationFiscalProfiles.Adapter.Mongoose";
|
||||
|
||||
export enum ORGANIZATION_FISCAL_PROFILE_TAX_CONDITION {
|
||||
MONOTRIBUTO = "MONOTRIBUTO",
|
||||
EXENTO = "EXENTO",
|
||||
}
|
||||
|
||||
export enum ORGANIZATION_FISCAL_PROFILE_STATUS {
|
||||
PENDING = "PENDING",
|
||||
ACTIVE = "ACTIVE",
|
||||
DISABLED = "DISABLED",
|
||||
}
|
||||
|
||||
export type UpsertOrganizationFiscalProfileParams = {
|
||||
companyId: string;
|
||||
cuit: string;
|
||||
legalName: string;
|
||||
taxCondition: ORGANIZATION_FISCAL_PROFILE_TAX_CONDITION;
|
||||
pointOfSale: number;
|
||||
sessionUser: string;
|
||||
};
|
||||
|
||||
export type FindOrganizationFiscalProfilesParams = {
|
||||
id?: string;
|
||||
companyId?: string;
|
||||
status?: ORGANIZATION_FISCAL_PROFILE_STATUS;
|
||||
sessionUser?: string;
|
||||
};
|
||||
|
||||
export type CreateOrganizationFiscalProfileParams = {
|
||||
companyId: string;
|
||||
cuit: string;
|
||||
legalName: string;
|
||||
taxCondition: ORGANIZATION_FISCAL_PROFILE_TAX_CONDITION;
|
||||
pointOfSale: number;
|
||||
status: ORGANIZATION_FISCAL_PROFILE_STATUS;
|
||||
createdAt: Date;
|
||||
updatedAt: Date;
|
||||
};
|
||||
|
||||
export type UpdateOrganizationFiscalProfileParams = FindOrganizationFiscalProfilesParams & {
|
||||
cuit?: string;
|
||||
legalName?: string;
|
||||
taxCondition?: ORGANIZATION_FISCAL_PROFILE_TAX_CONDITION;
|
||||
pointOfSale?: number;
|
||||
status?: ORGANIZATION_FISCAL_PROFILE_STATUS;
|
||||
updatedAt: Date;
|
||||
};
|
||||
|
||||
export interface IOrganizationFiscalProfile {
|
||||
id?: string;
|
||||
companyId: string;
|
||||
cuit: string;
|
||||
legalName: string;
|
||||
taxCondition: ORGANIZATION_FISCAL_PROFILE_TAX_CONDITION;
|
||||
pointOfSale: number;
|
||||
status: ORGANIZATION_FISCAL_PROFILE_STATUS;
|
||||
createdAt: Date;
|
||||
updatedAt: Date;
|
||||
}
|
||||
|
||||
export interface IOrganizationFiscalProfilesAdapter {
|
||||
create(data: CreateOrganizationFiscalProfileParams): Promise<IOrganizationFiscalProfile>;
|
||||
find(filters: FindOrganizationFiscalProfilesParams): Promise<IOrganizationFiscalProfile[]>;
|
||||
findOne(
|
||||
filters: FindOrganizationFiscalProfilesParams
|
||||
): Promise<IOrganizationFiscalProfileDocument | null>;
|
||||
update(data: UpdateOrganizationFiscalProfileParams): Promise<IOrganizationFiscalProfileDocument | null>;
|
||||
}
|
||||
|
||||
export interface IOrganizationFiscalProfilesManager {
|
||||
organizationFiscalProfiles: IOrganizationFiscalProfilesAdapter;
|
||||
upsert(data: UpsertOrganizationFiscalProfileParams): Promise<IOrganizationFiscalProfile>;
|
||||
find(data: FindOrganizationFiscalProfilesParams): Promise<IOrganizationFiscalProfile[]>;
|
||||
}
|
||||
@@ -0,0 +1,149 @@
|
||||
import { validatePermissionsByCompany, validateSessionUser } from "../../helpers/check";
|
||||
import { OrganizationFiscalProfilesAdapterMongoose } from "./OrganizationFiscalProfiles.Adapter.Mongoose";
|
||||
import {
|
||||
FindOrganizationFiscalProfilesParams,
|
||||
IOrganizationFiscalProfile,
|
||||
IOrganizationFiscalProfilesManager,
|
||||
ORGANIZATION_FISCAL_PROFILE_STATUS,
|
||||
UpsertOrganizationFiscalProfileParams,
|
||||
} from "./OrganizationFiscalProfiles.Interface";
|
||||
|
||||
class OrganizationFiscalProfilesManager implements IOrganizationFiscalProfilesManager {
|
||||
organizationFiscalProfiles: OrganizationFiscalProfilesAdapterMongoose;
|
||||
|
||||
constructor() {
|
||||
this.organizationFiscalProfiles = new OrganizationFiscalProfilesAdapterMongoose();
|
||||
}
|
||||
|
||||
private isDuplicateKeyError(error: unknown): boolean {
|
||||
return typeof error === "object" && error !== null && "code" in error && error.code === 11000;
|
||||
}
|
||||
|
||||
private validateProfileFields(data: UpsertOrganizationFiscalProfileParams): void {
|
||||
if (!/^\d{11}$/.test(data.cuit)) {
|
||||
throw new Error("El CUIT debe contener 11 dígitos");
|
||||
}
|
||||
|
||||
if (!data.legalName || !data.legalName.trim()) {
|
||||
throw new Error("La razón social es requerida");
|
||||
}
|
||||
|
||||
if (!data.pointOfSale || data.pointOfSale <= 0) {
|
||||
throw new Error("El punto de venta debe ser mayor a 0");
|
||||
}
|
||||
}
|
||||
|
||||
public async upsert(
|
||||
data: UpsertOrganizationFiscalProfileParams
|
||||
): Promise<IOrganizationFiscalProfile> {
|
||||
await validateSessionUser({ sessionUser: data.sessionUser });
|
||||
|
||||
if (!data.companyId || !data.companyId.trim()) {
|
||||
throw new Error("Se requiere una organización para guardar el perfil fiscal");
|
||||
}
|
||||
|
||||
await validatePermissionsByCompany({ companyId: data.companyId, sessionUser: data.sessionUser });
|
||||
|
||||
this.validateProfileFields(data);
|
||||
|
||||
const existingProfile = await this.organizationFiscalProfiles.findOne({
|
||||
companyId: data.companyId,
|
||||
});
|
||||
|
||||
const now = new Date();
|
||||
|
||||
if (existingProfile) {
|
||||
const updatedProfile = await this.organizationFiscalProfiles.update({
|
||||
companyId: data.companyId,
|
||||
cuit: data.cuit,
|
||||
legalName: data.legalName.trim(),
|
||||
taxCondition: data.taxCondition,
|
||||
pointOfSale: data.pointOfSale,
|
||||
status: ORGANIZATION_FISCAL_PROFILE_STATUS.ACTIVE,
|
||||
updatedAt: now,
|
||||
});
|
||||
|
||||
if (!updatedProfile) {
|
||||
throw new Error("No se pudo actualizar el perfil fiscal");
|
||||
}
|
||||
|
||||
return updatedProfile;
|
||||
}
|
||||
|
||||
try {
|
||||
return await this.organizationFiscalProfiles.create({
|
||||
companyId: data.companyId,
|
||||
cuit: data.cuit,
|
||||
legalName: data.legalName.trim(),
|
||||
taxCondition: data.taxCondition,
|
||||
pointOfSale: data.pointOfSale,
|
||||
status: ORGANIZATION_FISCAL_PROFILE_STATUS.ACTIVE,
|
||||
createdAt: now,
|
||||
updatedAt: now,
|
||||
});
|
||||
} catch (error) {
|
||||
if (this.isDuplicateKeyError(error)) {
|
||||
const updatedProfile = await this.organizationFiscalProfiles.update({
|
||||
companyId: data.companyId,
|
||||
cuit: data.cuit,
|
||||
legalName: data.legalName.trim(),
|
||||
taxCondition: data.taxCondition,
|
||||
pointOfSale: data.pointOfSale,
|
||||
status: ORGANIZATION_FISCAL_PROFILE_STATUS.ACTIVE,
|
||||
updatedAt: new Date(),
|
||||
});
|
||||
|
||||
if (updatedProfile) {
|
||||
return updatedProfile;
|
||||
}
|
||||
|
||||
const existingProfile = await this.organizationFiscalProfiles.findOne({
|
||||
companyId: data.companyId,
|
||||
});
|
||||
|
||||
if (existingProfile) {
|
||||
return existingProfile;
|
||||
}
|
||||
}
|
||||
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
public async find(
|
||||
data: FindOrganizationFiscalProfilesParams
|
||||
): Promise<IOrganizationFiscalProfile[]> {
|
||||
if (!data.sessionUser) {
|
||||
throw new Error("Se requiere un usuario autenticado");
|
||||
}
|
||||
|
||||
const sessionUser = data.sessionUser;
|
||||
await validateSessionUser({ sessionUser });
|
||||
|
||||
if (data.companyId) {
|
||||
await validatePermissionsByCompany({
|
||||
companyId: data.companyId,
|
||||
sessionUser,
|
||||
});
|
||||
} else if (data.id) {
|
||||
const profile = await this.organizationFiscalProfiles.findOne(data);
|
||||
|
||||
if (!profile) {
|
||||
return [];
|
||||
}
|
||||
|
||||
await validatePermissionsByCompany({
|
||||
companyId: String(profile.companyId),
|
||||
sessionUser,
|
||||
});
|
||||
|
||||
return [profile];
|
||||
} else {
|
||||
throw new Error("Se requiere una organización para buscar perfiles fiscales");
|
||||
}
|
||||
|
||||
return await this.organizationFiscalProfiles.find(data);
|
||||
}
|
||||
}
|
||||
|
||||
export default new OrganizationFiscalProfilesManager();
|
||||
Reference in New Issue
Block a user