Press n or j to go to the next uncovered block, b, p or k for the previous block.
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 | import { Prop, Schema, SchemaFactory } from '@nestjs/mongoose'; import { Document, Schema as MongooseSchema } from 'mongoose'; // ════════════════════════════════════════════════════════════════ // NUMBER SERIES DEFINITION // Template for generating sequential codes (EMP-0001, INV-2025-0001) // ════════════════════════════════════════════════════════════════ @Schema({ collection: 'mdm_number_series_definitions', timestamps: true }) export class NumberSeriesDefinition extends Document { @Prop({ required: true }) seriesCode!: string; // EMP_ID, INVOICE_NO, LEAD_NO @Prop({ required: true }) seriesName!: string; @Prop() description?: string; @Prop({ required: true }) entityTypeKey!: string; // employee, invoice, lead @Prop({ required: true }) prefix!: string; // EMP, INV, LEAD (tokens supported) @Prop() suffix?: string; @Prop({ default: false }) includeDateToken!: boolean; @Prop({ enum: ['YYYY', 'YY', 'YYYY-MM', 'MM', 'DD', 'FY', 'FY-SHORT'] }) dateTokenFormat?: string; // e.g. YYYY → 2025 @Prop({ required: true, default: 4 }) sequenceDigits!: number; // zero-padded length @Prop({ default: '-' }) separator!: string; // between segments @Prop({ required: true, enum: ['global', 'annual', 'monthly', 'fiscal_year', 'tenant', 'branch'], }) resetCycle!: string; @Prop({ default: false }) branchSpecific!: boolean; @Prop({ default: false }) tenantSpecific!: boolean; @Prop({ type: MongooseSchema.Types.ObjectId }) tenantId?: MongooseSchema.Types.ObjectId; @Prop({ required: true, enum: ['active', 'draft', 'deprecated'], default: 'active', }) status!: string; @Prop({ default: false }) requiresApproval!: boolean; @Prop({ default: 0 }) sortOrder!: number; @Prop({ default: false }) systemManaged!: boolean; } export const NumberSeriesDefinitionSchema = SchemaFactory.createForClass( NumberSeriesDefinition, ); NumberSeriesDefinitionSchema.index( { seriesCode: 1, tenantId: 1 }, { unique: true, sparse: true }, ); NumberSeriesDefinitionSchema.index({ entityTypeKey: 1, status: 1 }); // ════════════════════════════════════════════════════════════════ // NUMBER SERIES VERSION // Active version of a series definition (captures prefix, format snapshot) // ════════════════════════════════════════════════════════════════ @Schema({ collection: 'mdm_number_series_versions', timestamps: true }) export class NumberSeriesVersion extends Document { @Prop({ required: true, type: MongooseSchema.Types.ObjectId }) definitionId!: MongooseSchema.Types.ObjectId; @Prop({ required: true }) seriesCode!: string; @Prop({ required: true }) versionNumber!: number; @Prop({ required: true }) compiledPrefix!: string; // evaluated prefix (after date token substitution) @Prop() compiledSuffix?: string; @Prop({ required: true }) effectiveFrom!: Date; @Prop() effectiveTo?: Date; @Prop({ required: true, enum: ['active', 'superseded'], default: 'active' }) status!: string; @Prop({ type: MongooseSchema.Types.ObjectId }) tenantId?: MongooseSchema.Types.ObjectId; } export const NumberSeriesVersionSchema = SchemaFactory.createForClass(NumberSeriesVersion); NumberSeriesVersionSchema.index({ definitionId: 1, status: 1 }); NumberSeriesVersionSchema.index({ seriesCode: 1, effectiveFrom: -1, tenantId: 1, }); // ════════════════════════════════════════════════════════════════ // NUMBER SERIES SEQUENCE // The atomic counter per series+cycle+scope. // Updated with findOneAndUpdate + $inc for concurrency safety. // ════════════════════════════════════════════════════════════════ @Schema({ collection: 'mdm_number_series_sequences', timestamps: true }) export class NumberSeriesSequence extends Document { @Prop({ required: true }) seriesCode!: string; @Prop({ required: true }) cycleKey!: string; // '2025' | '2025-04' | 'FY2025-26' | 'GLOBAL' @Prop({ required: true }) scopeKey!: string; // tenantId|branchId or 'GLOBAL' @Prop({ required: true, default: 0 }) lastNumber!: number; @Prop({ required: true, default: 1 }) nextNumber!: number; @Prop() lastGeneratedAt?: Date; @Prop() lastGeneratedValue?: string; // for quick verification } export const NumberSeriesSequenceSchema = SchemaFactory.createForClass(NumberSeriesSequence); // Unique constraint prevents duplicate counter documents NumberSeriesSequenceSchema.index( { seriesCode: 1, cycleKey: 1, scopeKey: 1 }, { unique: true }, ); // ════════════════════════════════════════════════════════════════ // NUMBER SERIES RESERVATION // Pre-reserved blocks (bulk import, migration) // ════════════════════════════════════════════════════════════════ @Schema({ collection: 'mdm_number_series_reservations', timestamps: true }) export class NumberSeriesReservation extends Document { @Prop({ required: true }) seriesCode!: string; @Prop({ required: true }) cycleKey!: string; @Prop({ required: true }) scopeKey!: string; @Prop({ required: true }) fromNumber!: number; @Prop({ required: true }) toNumber!: number; @Prop({ required: true }) blockSize!: number; @Prop({ required: true, enum: ['reserved', 'consumed', 'released', 'expired'], default: 'reserved', }) status!: string; @Prop() purpose?: string; // 'bulk_import', 'migration' @Prop() expiresAt?: Date; @Prop({ type: MongooseSchema.Types.ObjectId }) reservedBy?: MongooseSchema.Types.ObjectId; } export const NumberSeriesReservationSchema = SchemaFactory.createForClass( NumberSeriesReservation, ); NumberSeriesReservationSchema.index({ seriesCode: 1, cycleKey: 1, scopeKey: 1, status: 1, }); NumberSeriesReservationSchema.index({ expiresAt: 1, status: 1 }); |