payservedb 9.1.8 → 9.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/index.js CHANGED
@@ -94,11 +94,10 @@ const models = {
94
94
  Levy: require("./src/models/levy"),
95
95
  LevyType: require("./src/models/levytype"),
96
96
  LevyContract: require("./src/models/levycontract"),
97
- DepositSchema: require("./src/models/levy_deposits"),
98
97
  Invoice: require("./src/models/invoice"),
99
- InvoiceEditLog: require("./src/models/invoice_edit_log"),
100
98
  CombinedInvoice: require("./src/models/combined_invoice"),
101
99
  InvoiceGeneration: require("./src/models/invoice_generation_approval"),
100
+ InvoiceEditLog: require("./src/models/invoice_edit_log"),
102
101
  ShortUrl: require("./src/models/short_urls"),
103
102
  InvoicingSchedule: require("./src/models/invoicing_schedule"),
104
103
  Reminder: require("./src/models/reminder"),
@@ -111,8 +110,8 @@ const models = {
111
110
  Report: require("./src/models/report"),
112
111
  Ticket: require("./src/models/tickets"),
113
112
  Rating: require("./src/models/facility_rating"),
114
- FacilityEtimsConfig: require("./src/models/facility_etims_config"),
115
113
  Stocksandspare: require("./src/models/stocksandspare"),
114
+ FacilityEtimsConfig: require("./src/models/facility_etims_config"),
116
115
  ServiceVendor: require("./src/models/maintenance_service_vendor"),
117
116
  MaintenanceServices: require("./src/models/maintenance_services"),
118
117
  StockRequisition: require("./src/models/maintenancerequisition"),
@@ -199,7 +198,6 @@ const models = {
199
198
  FacilityDepartment: require("./src/models/facility_departements"),
200
199
  EmailSmsQueue: require("./src/models/email_sms_queue"),
201
200
  PurchaseOrderInvoice: require("./src/models/purchaseOrderInvoice"),
202
- PowerInvoice: require("./src/models/power_invoice"),
203
201
  PowerMeterCustomerBand: require("./src/models/powerMeterCustomerBand"),
204
202
  PowerMeterCommunicationProtocol: require("./src/models/powerMeterCommunicationProtocol"),
205
203
  PowerMeterDailyReading: require("./src/models/powerMeterDailyReading"),
@@ -211,6 +209,7 @@ const models = {
211
209
  PowerMeterLog: require("./src/models/power_meter_command_logs"),
212
210
  PowerMeterManufacturer: require("./src/models/powerMetersManufacturer"),
213
211
  PowerMeterMonthlyReading: require("./src/models/powerMeterMonthlyReading"),
212
+ PowerInvoice: require("./src/models/power_invoice"),
214
213
  PowerMeterSettings: require("./src/models/powerMeterSettings"),
215
214
  PowerPrepaidDebit: require("./src/models/power_prepaid_debits"),
216
215
  PowerPrepaidCredit: require("./src/models/power_prepaid_credits"),
@@ -253,85 +252,21 @@ const models = {
253
252
  PrivacyPolicy: require("./src/models/privacy_policy"),
254
253
  TermsAndConditions: require("./src/models/terms_and_conditions"),
255
254
  CommunityGuidelines: require("./src/models/community_guidelines"),
256
- WhatsappConversation: require("./src/models/whatsapp_conversation"),
255
+ WhatsappConversation : require("./src/models/whatsapp_conversation"),
257
256
  CustomerPreference: require("./src/models/customer_preference"),
258
257
  MoveinApplication: require("./src/models/movein_application"),
259
258
  EmailThread: require("./src/models/email_thread"),
260
259
  MoveinLandlord: require("./src/models/movein_landlord"),
261
260
  MoveinUser: require("./src/models/movein_user"),
262
- InvoiceWithholdingTax: require("./src/models/InvoiceWithholdingTax"),
263
- InvoiceCreditAdjustment: require("./src/models/invoiceCreditAdjustment"),
264
- // Customer Obsession admin-managed config
261
+ // Customer Obsession admin-managed config (PR3/4/5)
265
262
  EmailCcConfig: require("./src/models/email_cc_config"),
266
263
  AutoReplyRule: require("./src/models/auto_reply_rule"),
267
264
  RecipientGroup: require("./src/models/recipient_group"),
268
265
  RecipientGroupMember: require("./src/models/recipient_group_member"),
266
+ InvoiceWithholdingTax: require("./src/models/InvoiceWithholdingTax"),
267
+ InvoiceCreditAdjustment: require("./src/models/invoiceCreditAdjustment"),
269
268
  };
270
269
 
271
- // ── Move-In platform model schema sources (keyed by model name) ─────────────
272
- const moveInModelDefs = {
273
- MoveInUser: { file: "./src/models/movein_user", name: "MoveInUser" },
274
- MoveInUnit: { file: "./src/models/movein_unit", name: "MoveInUnit" },
275
- MoveInLandlord: { file: "./src/models/movein_landlord", name: "MoveInLandlord" },
276
- MoveInApplication: { file: "./src/models/movein_application", name: "MoveInApplication" },
277
- CustomerPreference: { file: "./src/models/customer_preference", name: "CustomerPreference" },
278
- MoveInViewingSlot: { file: "./src/models/movein_viewing_slot", name: "MoveInViewingSlot" },
279
- MoveInBooking: { file: "./src/models/movein_booking", name: "MoveInBooking" },
280
- MoveInReservation: { file: "./src/models/movein_reservation", name: "MoveInReservation" },
281
- MoveInPayment: { file: "./src/models/movein_payment", name: "MoveInPayment" },
282
- MoveInDeal: { file: "./src/models/movein_deal", name: "MoveInDeal" },
283
- MoveInCommission: { file: "./src/models/movein_commission", name: "MoveInCommission" },
284
- MoveInNotification: { file: "./src/models/movein_notification", name: "MoveInNotification" },
285
- MoveInAuditLog: { file: "./src/models/movein_audit_log", name: "MoveInAuditLog" },
286
- MoveInOtp: { file: "./src/models/movein_otp", name: "MoveInOtp" },
287
- MoveInConversation: { file: "./src/models/movein_conversation", name: "MoveInConversation" },
288
- MoveInMessage: { file: "./src/models/movein_message", name: "MoveInMessage" },
289
- MoveInLandlordUser: { file: "./src/models/movein_landlord_user", name: "MoveInLandlordUser" },
290
- MoveInHandoffToken: { file: "./src/models/movein_handoff_token", name: "MoveInHandoffToken" },
291
- MoveInReminder: { file: "./src/models/movein_reminder", name: "MoveInReminder" },
292
- };
293
-
294
- // Initial moveIn namespace — models on default connection (payserve_property).
295
- // connectToMoveInDB replaces these with models bound to payserve_movein.
296
- const moveInModels = {};
297
- for (const [key, def] of Object.entries(moveInModelDefs)) {
298
- moveInModels[key] = require(def.file);
299
- }
300
-
301
- // Opens a dedicated connection to payserve_movein and re-registers all
302
- // Move-In models on that connection so queries hit the correct database.
303
- async function connectToMoveInDB(dbName, secured, username, password, url, port) {
304
- try {
305
- let connectionString;
306
- if (secured === false) {
307
- connectionString = `mongodb://${url}:${port}/${dbName}`;
308
- } else {
309
- const source = "?authSource=admin";
310
- connectionString = `mongodb://${username}:${password}@${url}:${port}/${dbName}${source}`;
311
- }
312
-
313
- const conn = await mongoose.createConnection(connectionString, {
314
- useNewUrlParser: true,
315
- });
316
-
317
- console.log(`Connected to Move-In DB: ${dbName}`);
318
-
319
- for (const [key, def] of Object.entries(moveInModelDefs)) {
320
- const baseModel = require(def.file);
321
- const schema = baseModel.schema;
322
- moveInModels[key] = conn.models[def.name]
323
- ? conn.models[def.name]
324
- : conn.model(def.name, schema);
325
- }
326
-
327
- module.exports.moveInConnection = conn;
328
- return conn;
329
- } catch (err) {
330
- console.error("Error connecting to Move-In DB:", err);
331
- throw err;
332
- }
333
- }
334
-
335
270
  // Function to get models dynamically from a specific database connection
336
271
  async function getModelFromDB(dbConnection, modelName, schema) {
337
272
  if (!dbConnection.models[modelName]) {
@@ -392,6 +327,82 @@ function initializeService(modelNames = []) {
392
327
  };
393
328
  }
394
329
 
330
+ // Move-In platform model schema sources (keyed by model name)
331
+ const moveInModelDefs = {
332
+ MoveInUser: { file: "./src/models/movein_user", name: "MoveInUser" },
333
+ MoveInUnit: { file: "./src/models/movein_unit", name: "MoveInUnit" },
334
+ MoveInLandlord: { file: "./src/models/movein_landlord", name: "MoveInLandlord" },
335
+ MoveInApplication: { file: "./src/models/movein_application", name: "MoveInApplication" },
336
+ CustomerPreference: { file: "./src/models/customer_preference", name: "CustomerPreference" },
337
+ MoveInViewingSlot: { file: "./src/models/movein_viewing_slot", name: "MoveInViewingSlot" },
338
+ MoveInBooking: { file: "./src/models/movein_booking", name: "MoveInBooking" },
339
+ MoveInReservation: { file: "./src/models/movein_reservation", name: "MoveInReservation" },
340
+ MoveInPayment: { file: "./src/models/movein_payment", name: "MoveInPayment" },
341
+ MoveInDeal: { file: "./src/models/movein_deal", name: "MoveInDeal" },
342
+ MoveInCommission: { file: "./src/models/movein_commission", name: "MoveInCommission" },
343
+ MoveInNotification: { file: "./src/models/movein_notification", name: "MoveInNotification" },
344
+ MoveInReminder: { file: "./src/models/movein_reminder", name: "MoveInReminder" },
345
+ MoveInAuditLog: { file: "./src/models/movein_audit_log", name: "MoveInAuditLog" },
346
+ MoveInOtp: { file: "./src/models/movein_otp", name: "MoveInOtp" },
347
+ MoveInConversation: { file: "./src/models/movein_conversation", name: "MoveInConversation" },
348
+ MoveInMessage: { file: "./src/models/movein_message", name: "MoveInMessage" },
349
+ MoveInLandlordUser: { file: "./src/models/movein_landlord_user", name: "MoveInLandlordUser" },
350
+ MoveInHandoffToken: { file: "./src/models/movein_handoff_token", name: "MoveInHandoffToken" },
351
+ MoveInTenancy: { file: "./src/models/movein_tenancy", name: "MoveInTenancy" },
352
+ MoveInDisbursement: { file: "./src/models/movein_disbursement", name: "MoveInDisbursement" },
353
+ MoveInLease: { file: "./src/models/movein_lease", name: "MoveInLease" },
354
+ MoveInAmenity: { file: "./src/models/movein_amenity", name: "MoveInAmenity" },
355
+ MoveInEnquiry: { file: "./src/models/movein_enquiry", name: "MoveInEnquiry" },
356
+ MoveInInvoice: { file: "./src/models/movein_invoice", name: "MoveInInvoice" },
357
+ MoveInSettings: { file: "./src/models/movein_settings", name: "MoveInSettings" },
358
+ MoveInWantUnit: { file: "./src/models/movein_want_unit", name: "MoveInWantUnit" },
359
+ MoveInFeaturedPackage: { file: "./src/models/movein_featured_package", name: "MoveInFeaturedPackage" },
360
+ MoveInEmailThread: { file: "./src/models/movein_email_thread", name: "MoveInEmailThread" },
361
+ };
362
+
363
+ // Initial moveIn namespace — models on default connection (payserve_property).
364
+ // connectToMoveInDB replaces these with models bound to payserve_movein.
365
+ const moveInModels = {};
366
+ for (const [key, def] of Object.entries(moveInModelDefs)) {
367
+ moveInModels[key] = require(def.file);
368
+ }
369
+
370
+ // Opens a dedicated connection to payserve_movein and re-registers all
371
+ // moveIn models on that connection so queries hit the correct database.
372
+ async function connectToMoveInDB(dbName, secured, username, password, url, port) {
373
+ try {
374
+ let connectionString;
375
+ if (secured === false) {
376
+ connectionString = `mongodb://${url}:${port}/${dbName}`;
377
+ } else {
378
+ const source = "?authSource=admin";
379
+ connectionString = `mongodb://${username}:${password}@${url}:${port}/${dbName}${source}`;
380
+ }
381
+
382
+ const conn = await mongoose.createConnection(connectionString, {
383
+ useNewUrlParser: true,
384
+ });
385
+
386
+ console.log(`Connected to Move-In DB: ${dbName}`);
387
+
388
+ // Re-register each moveIn model on the new connection
389
+ for (const [key, def] of Object.entries(moveInModelDefs)) {
390
+ const baseModel = require(def.file);
391
+ const schema = baseModel.schema;
392
+ moveInModels[key] = conn.models[def.name]
393
+ ? conn.models[def.name]
394
+ : conn.model(def.name, schema);
395
+ }
396
+
397
+ // Expose the raw connection for advanced use
398
+ module.exports.moveInConnection = conn;
399
+ return conn;
400
+ } catch (err) {
401
+ console.error("Error connecting to Move-In DB:", err);
402
+ throw err;
403
+ }
404
+ }
405
+
395
406
  module.exports = {
396
407
  connectToMongoDB,
397
408
  connectToMoveInDB,
@@ -399,4 +410,5 @@ module.exports = {
399
410
  getModelFromDB,
400
411
  initializeService,
401
412
  ...models,
402
- };
413
+ moveIn: moveInModels,
414
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "payservedb",
3
- "version": "9.1.8",
3
+ "version": "9.2.0",
4
4
  "main": "index.js",
5
5
  "scripts": {
6
6
  "test": "echo \"Error: no test specified\" && exit 1"
@@ -118,6 +118,19 @@ const LevyContractSchema = new Schema(
118
118
  default: 0,
119
119
  min: [0, 'Upfront amount cannot be negative']
120
120
  },
121
+ numberOfMonths: {
122
+ type: Number,
123
+ min: [1, 'Number of months must be at least 1']
124
+ },
125
+ amountPerMonth: {
126
+ type: Number,
127
+ min: [0, 'Amount per month cannot be negative']
128
+ },
129
+ calculationMode: {
130
+ type: String,
131
+ enum: ['auto', 'custom'],
132
+ default: 'auto'
133
+ },
121
134
  billingPeriods: {
122
135
  type: [String],
123
136
  default: []
@@ -0,0 +1,14 @@
1
+ const mongoose = require('mongoose');
2
+
3
+ const moveInAmenitySchema = new mongoose.Schema(
4
+ {
5
+ name: { type: String, required: true, trim: true },
6
+ icon: { type: String, default: null },
7
+ category: { type: String, default: 'general', trim: true },
8
+ isActive: { type: Boolean, default: true },
9
+ },
10
+ { timestamps: true }
11
+ );
12
+
13
+ const MoveInAmenity = mongoose.model('MoveInAmenity', moveInAmenitySchema);
14
+ module.exports = MoveInAmenity;
@@ -3,7 +3,7 @@ const mongoose = require('mongoose');
3
3
  const moveInApplicationSchema = new mongoose.Schema(
4
4
  {
5
5
  unitId: { type: mongoose.Schema.Types.ObjectId, required: true, index: true },
6
- facilityId: { type: mongoose.Schema.Types.ObjectId, ref: 'Facility', required: true, index: true },
6
+ facilityId: { type: mongoose.Schema.Types.ObjectId, ref: 'Facility', default: null, index: true },
7
7
  unitName: { type: String, default: null },
8
8
  facilityName: { type: String, default: null },
9
9
  tenantId: { type: mongoose.Schema.Types.ObjectId, ref: 'User', required: true, index: true },
@@ -26,4 +26,4 @@ const moveInApplicationSchema = new mongoose.Schema(
26
26
  );
27
27
 
28
28
  const MoveInApplication = mongoose.model('MoveInApplication', moveInApplicationSchema);
29
- module.exports = MoveInApplication;
29
+ module.exports = MoveInApplication;
@@ -0,0 +1,37 @@
1
+ const mongoose = require('mongoose');
2
+
3
+ const moveInDisbursementSchema = new mongoose.Schema(
4
+ {
5
+ tenancyId: { type: mongoose.Schema.Types.ObjectId, required: true, index: true },
6
+ unitId: { type: mongoose.Schema.Types.ObjectId, required: true, index: true },
7
+ landlordId: { type: mongoose.Schema.Types.ObjectId, required: true, index: true },
8
+ tenantId: { type: mongoose.Schema.Types.ObjectId, required: true, index: true },
9
+
10
+ // Amounts
11
+ totalCollected: { type: Number, required: true },
12
+ commissionAmount:{ type: Number, required: true },
13
+ netAmount: { type: Number, required: true }, // totalCollected - commissionAmount
14
+ currency: { type: String, default: 'KES' },
15
+
16
+ // Disbursement details
17
+ status: { type: String, enum: ['pending', 'processing', 'settled', 'failed'], default: 'pending', index: true },
18
+ method: { type: String, enum: ['mpesa', 'bank', null], default: null },
19
+ reference: { type: String, default: null }, // M-Pesa code or bank ref
20
+ disbursedAt: { type: Date, default: null },
21
+ disbursedBy: { type: mongoose.Schema.Types.ObjectId, default: null },
22
+ note: { type: String, default: null },
23
+
24
+ // Bank details snapshot at time of disbursement
25
+ bankName: { type: String, default: null },
26
+ bankAccount: { type: String, default: null },
27
+ bankBranch: { type: String, default: null },
28
+
29
+ // Invoice references
30
+ landlordInvoiceId: { type: mongoose.Schema.Types.ObjectId, default: null },
31
+ landlordReceiptId: { type: mongoose.Schema.Types.ObjectId, default: null },
32
+ },
33
+ { timestamps: true }
34
+ );
35
+
36
+ const MoveInDisbursement = mongoose.model('MoveInDisbursement', moveInDisbursementSchema);
37
+ module.exports = MoveInDisbursement;
@@ -0,0 +1,20 @@
1
+ const mongoose = require('mongoose');
2
+
3
+ const moveInEmailThreadSchema = new mongoose.Schema(
4
+ {
5
+ messageId: { type: String, required: true, unique: true, index: true },
6
+ from: { type: String, default: '' },
7
+ fromName: { type: String, default: '' },
8
+ subject: { type: String, default: '(no subject)' },
9
+ bodyText: { type: String, default: '' },
10
+ bodyHtml: { type: String, default: '' },
11
+ date: { type: Date, default: Date.now },
12
+ uid: { type: Number, default: null },
13
+ isRead: { type: Boolean, default: false },
14
+ isReplied: { type: Boolean, default: false },
15
+ },
16
+ { timestamps: true }
17
+ );
18
+
19
+ const MoveInEmailThread = mongoose.model('MoveInEmailThread', moveInEmailThreadSchema);
20
+ module.exports = MoveInEmailThread;
@@ -0,0 +1,17 @@
1
+ const mongoose = require('mongoose');
2
+
3
+ const moveInEnquirySchema = new mongoose.Schema(
4
+ {
5
+ unitId: { type: mongoose.Schema.Types.ObjectId, required: true, index: true },
6
+ landlordId: { type: mongoose.Schema.Types.ObjectId, default: null, index: true },
7
+ fullName: { type: String, required: true, trim: true },
8
+ phone: { type: String, default: null },
9
+ email: { type: String, required: true, trim: true, lowercase: true },
10
+ message: { type: String, default: null },
11
+ isRead: { type: Boolean, default: false },
12
+ },
13
+ { timestamps: true }
14
+ );
15
+
16
+ const MoveInEnquiry = mongoose.model('MoveInEnquiry', moveInEnquirySchema);
17
+ module.exports = MoveInEnquiry;
@@ -0,0 +1,17 @@
1
+ const mongoose = require('mongoose');
2
+
3
+ const moveInFeaturedPackageSchema = new mongoose.Schema(
4
+ {
5
+ name: { type: String, required: true, trim: true, maxlength: 80 },
6
+ description: { type: String, default: '', maxlength: 500 },
7
+ durationDays: { type: Number, required: true, min: 1, max: 365 },
8
+ price: { type: Number, required: true, min: 0 },
9
+ sortOrder: { type: Number, default: 0 },
10
+ isActive: { type: Boolean, default: true },
11
+ createdBy: { type: mongoose.Schema.Types.ObjectId, default: null },
12
+ },
13
+ { timestamps: true }
14
+ );
15
+
16
+ const MoveInFeaturedPackage = mongoose.model('MoveInFeaturedPackage', moveInFeaturedPackageSchema);
17
+ module.exports = MoveInFeaturedPackage;
@@ -0,0 +1,25 @@
1
+ const mongoose = require('mongoose');
2
+
3
+ const moveInInvoiceSchema = new mongoose.Schema(
4
+ {
5
+ invoiceNumber: { type: String, required: true, unique: true, index: true },
6
+ tenantName: { type: String, required: true, trim: true },
7
+ tenantEmail: { type: String, required: true, trim: true, lowercase: true },
8
+ tenantPhone: { type: String, default: null },
9
+ unitId: { type: mongoose.Schema.Types.ObjectId, required: true, index: true },
10
+ landlordId: { type: mongoose.Schema.Types.ObjectId, default: null, index: true },
11
+ wantUnitApplicationId: { type: mongoose.Schema.Types.ObjectId, default: null },
12
+ type: { type: String, default: 'deposit' },
13
+ amount: { type: Number, required: true },
14
+ currency: { type: String, default: 'KES' },
15
+ status: { type: String, enum: ['pending', 'paid', 'cancelled', 'expired'], default: 'pending', index: true },
16
+ deadlineAt: { type: Date, default: null },
17
+ paidAt: { type: Date, default: null },
18
+ mpesaTransactionId: { type: String, default: null },
19
+ notes: { type: String, default: null },
20
+ },
21
+ { timestamps: true }
22
+ );
23
+
24
+ const MoveInInvoice = mongoose.model('MoveInInvoice', moveInInvoiceSchema);
25
+ module.exports = MoveInInvoice;
@@ -0,0 +1,38 @@
1
+ const mongoose = require('mongoose');
2
+
3
+ const moveInLeaseSchema = new mongoose.Schema(
4
+ {
5
+ // 'default' = platform-wide template; 'unit' = unit-specific override
6
+ type: { type: String, enum: ['default', 'unit'], required: true, index: true },
7
+ unitId: { type: mongoose.Schema.Types.ObjectId, default: null, index: true },
8
+ uploadedBy: { type: mongoose.Schema.Types.ObjectId, required: true },
9
+
10
+ // File storage
11
+ originalName: { type: String, required: true },
12
+ filePath: { type: String, required: true },
13
+ mimeType: { type: String, default: 'application/pdf' },
14
+ fileSizeBytes: { type: Number, default: null },
15
+
16
+ status: { type: String, enum: ['active', 'superseded'], default: 'active', index: true },
17
+ supersededAt: { type: Date, default: null },
18
+
19
+ // Signed copy (per tenancy — embedded subdoc array)
20
+ signedCopies: [
21
+ {
22
+ tenancyId: { type: mongoose.Schema.Types.ObjectId, required: true },
23
+ tenantId: { type: mongoose.Schema.Types.ObjectId, required: true },
24
+ filePath: { type: String, required: true },
25
+ originalName: { type: String, default: null },
26
+ uploadedAt: { type: Date, default: Date.now },
27
+ status: { type: String, enum: ['pending_review', 'approved', 'rejected'], default: 'pending_review' },
28
+ reviewedAt: { type: Date, default: null },
29
+ reviewedBy: { type: mongoose.Schema.Types.ObjectId, default: null },
30
+ rejectionNote: { type: String, default: null },
31
+ }
32
+ ],
33
+ },
34
+ { timestamps: true }
35
+ );
36
+
37
+ const MoveInLease = mongoose.model('MoveInLease', moveInLeaseSchema);
38
+ module.exports = MoveInLease;
@@ -0,0 +1,26 @@
1
+ const mongoose = require('mongoose');
2
+
3
+ const feeRuleSchema = new mongoose.Schema(
4
+ {
5
+ rule: { type: String, default: 'same_as_rent' },
6
+ value: { type: Number, default: 0 },
7
+ },
8
+ { _id: false }
9
+ );
10
+
11
+ const moveInSettingsSchema = new mongoose.Schema(
12
+ {
13
+ singletonKey: { type: String, default: 'global', unique: true },
14
+ reservation: { type: feeRuleSchema, default: () => ({ rule: 'same_as_rent', value: 0 }) },
15
+ viewing: { type: feeRuleSchema, default: () => ({ rule: 'fixed_amount', value: 500 }) },
16
+ commission: { type: feeRuleSchema, default: () => ({ rule: 'fixed_amount', value: 0 }) },
17
+ paymentDeadlineHours: { type: Number, default: 24 },
18
+ featuredEnabled: { type: Boolean, default: false },
19
+ updatedBy: { type: mongoose.Schema.Types.ObjectId, default: null },
20
+ updatedAt: { type: Date, default: null },
21
+ },
22
+ { timestamps: false }
23
+ );
24
+
25
+ const MoveInSettings = mongoose.model('MoveInSettings', moveInSettingsSchema);
26
+ module.exports = MoveInSettings;
@@ -0,0 +1,56 @@
1
+ const mongoose = require('mongoose');
2
+
3
+ const moveInTenancySchema = new mongoose.Schema(
4
+ {
5
+ unitId: { type: mongoose.Schema.Types.ObjectId, required: true, index: true },
6
+ unitName: { type: String, default: null },
7
+ applicationId: { type: mongoose.Schema.Types.ObjectId, required: true, index: true },
8
+ tenantId: { type: mongoose.Schema.Types.ObjectId, required: true, index: true },
9
+ tenantName: { type: String, default: null },
10
+ tenantEmail: { type: String, default: null },
11
+ tenantPhone: { type: String, default: null },
12
+ landlordId: { type: mongoose.Schema.Types.ObjectId, default: null, index: true },
13
+
14
+ // Payment leg
15
+ paymentId: { type: mongoose.Schema.Types.ObjectId, default: null },
16
+ paymentStatus: { type: String, enum: ['pending', 'paid'], default: 'pending' },
17
+ amountPaid: { type: Number, default: null },
18
+ accountNumber: { type: String, default: null }, // MVR...
19
+ commissionAccountNumber: { type: String, default: null }, // MVC...
20
+ commissionType: { type: String, enum: ['percent', 'flat'], default: 'percent' },
21
+ commissionValue: { type: Number, default: 15 },
22
+ commissionAmount:{ type: Number, default: null },
23
+ netToLandlord: { type: Number, default: null },
24
+ paidAt: { type: Date, default: null },
25
+
26
+ // Lease leg
27
+ leaseId: { type: mongoose.Schema.Types.ObjectId, default: null },
28
+ leaseStatus: { type: String, enum: ['pending', 'downloaded', 'uploaded', 'approved', 'rejected'], default: 'pending' },
29
+ leaseSignedAt: { type: Date, default: null },
30
+ leaseApprovedAt: { type: Date, default: null },
31
+ leaseApprovedBy: { type: mongoose.Schema.Types.ObjectId, default: null },
32
+
33
+ // Overall tenancy status
34
+ status: {
35
+ type: String,
36
+ enum: ['awaiting_payment', 'awaiting_lease', 'awaiting_both', 'active', 'cancelled', 'ended'],
37
+ default: 'awaiting_both',
38
+ index: true,
39
+ },
40
+ activatedAt: { type: Date, default: null },
41
+
42
+ // Disbursement leg
43
+ disbursementId: { type: mongoose.Schema.Types.ObjectId, default: null },
44
+ disbursedAt: { type: Date, default: null },
45
+
46
+ // Invoice references
47
+ tenantInvoiceId: { type: mongoose.Schema.Types.ObjectId, default: null },
48
+ tenantReceiptId: { type: mongoose.Schema.Types.ObjectId, default: null },
49
+
50
+ notes: { type: String, default: null },
51
+ },
52
+ { timestamps: true }
53
+ );
54
+
55
+ const MoveInTenancy = mongoose.model('MoveInTenancy', moveInTenancySchema);
56
+ module.exports = MoveInTenancy;
@@ -0,0 +1,21 @@
1
+ const mongoose = require('mongoose');
2
+
3
+ const moveInWantUnitSchema = new mongoose.Schema(
4
+ {
5
+ unitId: { type: mongoose.Schema.Types.ObjectId, required: true, index: true },
6
+ landlordId: { type: mongoose.Schema.Types.ObjectId, default: null, index: true },
7
+ fullName: { type: String, required: true, trim: true },
8
+ phone: { type: String, required: true, trim: true },
9
+ email: { type: String, required: true, trim: true, lowercase: true },
10
+ occupation: { type: String, default: null },
11
+ evidencePath: { type: String, default: null },
12
+ status: { type: String, enum: ['pending', 'approved', 'rejected'], default: 'pending', index: true },
13
+ reason: { type: String, default: null },
14
+ respondedAt: { type: Date, default: null },
15
+ invoiceId: { type: mongoose.Schema.Types.ObjectId, default: null },
16
+ },
17
+ { timestamps: true }
18
+ );
19
+
20
+ const MoveInWantUnit = mongoose.model('MoveInWantUnit', moveInWantUnitSchema);
21
+ module.exports = MoveInWantUnit;
@@ -46,16 +46,11 @@ const powerInvoiceSchema = new mongoose.Schema(
46
46
  // Positive = arrears carried forward; Negative = credit from overpayment
47
47
  },
48
48
 
49
- withholdingTaxes: [
50
- {
51
- name: { type: String, required: true }, // e.g. "withholding"
52
- percentage: { type: Number, required: true }, // e.g. 5
53
- amount: { type: Number, required: true }, // e.g. 50 (subTotal * percentage / 100)
54
- },
55
- ],
56
- totalWithholdingTax: {
49
+ withholdingTaxPercentage: {
57
50
  type: Number,
58
- default: 0,
51
+ min: 0,
52
+ max: 100,
53
+ default: 0
59
54
  },
60
55
 
61
56
  // ── Biller Address ─────────────────────────────────────────────────────────