agmission/Development/server/model/customer.js

189 lines
6.3 KiB
JavaScript

const mongoose = require('mongoose'),
Schema = mongoose.Schema,
mongoUtil = require('../helpers/mongo'),
User = require('./user'),
{ Fields, UserTypes, APTypes, TrialTypes } = require('../helpers/constants'),
Client = require('./client'),
Product = require('./product'),
Vehicle = require('./vehicle'),
Crop = require('./crop'),
AddressSchema = require('./common').addressSchema,
SubscriptionSchema = require('./subscription').subscriptionSchema,
uniqid = require('uniqid'),
validator = require('validator');
const schema = new Schema({
contact: { type: String, required: false },
fax: { type: String, required: false },
premium: { type: Number, default: 0 },
billable: { type: Boolean, default: false },
country: {
type: String,
validate: [validator.isISO31661Alpha2, 'invalid_country_code'],
trim: true,
uppercase: true,
required: [true, 'Country is required']
},
membership: {
custId: String, // The Stripe Customer ID of this customer
subscriptions: [SubscriptionSchema],
trials: {
type: {
type: String,
enum: {
values: Object.values(TrialTypes),
message: '{VALUE} for \'trials.type\' is not supported'
}
},
trialDays: { type: Number, default: 0 },
byDate: { type: Date, default: null },
startDate: { type: Date }, // The date the customer is offered a trial
lastStartDate: { type: Date }, // Last trial start date
lastEndDate: { type: Date }, // Last trial end date
}
},
billAddress: { type: AddressSchema, required: false },
/** Dealer that managge the account */
// dealerCode: { type: String, required: false }
}, { strictQuery: false });
async function prepareDefault(cust, ses) {
let defProd = new Product({ name: 'Water', type: APTypes.CARRIER, desc: 'Default carrier product', byPuid: cust._id });
await defProd.save({ session: ses });
}
/**
* Create a new customer (applicator) with all default data in atomic db session's transactions
* @param {*} ses the db session. If ses is null, create a session and end when done
* @param {boolean} endSesDone whether to end the session
* @returns the new customer instance otherwise thrown exceptions
*/
schema.methods.createOne = async function (ses = null, endSesDone = false) {
let _ses = ses;
if (!ses) {
_ses = await this.db.startSession();
endSesDone = true;
}
try {
if (_ses.transaction && _ses.transaction.isActive) {
await prepareDefault(this, _ses);
await this.save({ session: _ses });
} else {
await _ses.withTransaction(async () => {
await prepareDefault(this, _ses);
return this.save({ session: _ses });
});
}
} finally {
(endSesDone && _ses) && (await _ses.endSession());
}
return this;
}
async function freeRelatedUserNames(session) {
const users = await User.find({ parent: this._id }, '_id username', { lean: true });
if (!users.length) return;
session && (session.startTransaction(mongoUtil.getTranOps()));
try {
// De-activate all related user in one batch
const bulkDelOps = []
let updateDoc;
for (let user of users) {
updateDoc = { active: false };
if (user.username)
updateDoc.username = user.username + '#' + uniqid();
bulkDelOps.push({
'updateOne': {
'filter': { _id: user._id },
'update': updateDoc
}
});
}
await User.bulkWrite(bulkDelOps, { session });
} catch (error) {
session && (session.abortTransaction());
throw error;
}
await mongoUtil.commitWithRetry(session);
}
async function deleteRelatedData(session) {
const custUid = this._id;
session && (session.startTransaction(mongoUtil.getTranOps()));
try {
// Entities
await Product.deleteMany({ byPuid: custUid }).session(session);
await Crop.deleteMany({ byPuid: custUid }).session(session);
// Other accounts, related configs
const users = await User.find({ parent: custUid, kind: { $nin: [UserTypes.CLIENT, UserTypes.DEVICE] } });
for (let j = 0; j < users.length; j++) {
await users[j].remove({ session });
}
} catch (error) {
session && (session.abortTransaction());
throw error;
}
await mongoUtil.commitWithRetry(session);
}
async function deleteCustomerClients(session, markDeletedOnly) {
const clients = await Client.find({ parent: this._id });
for (let i = 0; i < clients.length; i++) {
await clients[i].removeFull(session, markDeletedOnly);
}
}
async function deleteCustomerVehicles(session, markDeletedOnly) {
const vehicles = await Vehicle.find({ parent: this._id });
for (let i = 0; i < vehicles.length; i++) {
await vehicles[i].removeFull(session, markDeletedOnly);
}
}
/**
* Remove the Customer (applicator) with all related data in one atomic db session's transaction
* @param {ClientSession} ses the db session. If sess is null, create a session and end when done
* @param {boolean} markDeletedOnly Determine whether to just mark the item as deleted only. Default is true.
* @param {boolean} endSesDone whether to end the session
*/
schema.methods.removeFull = async function (ses = null, markDeletedOnly = true, endSesDone = false) {
let session = ses;
if (!ses) {
session = await this.db.startSession({ readPreference: { mode: "primary" } });
endSesDone = true;
}
try {
// 1. Mark the customer as deleted
if (!this[Fields.MARKED_DELETE])
await mongoUtil.runTransactionWithRetry(this.markAsDeleted.bind(this), session);
if (markDeletedOnly) {
// 1.1 Free related users
await mongoUtil.runTransactionWithRetry(freeRelatedUserNames.bind(this), session);
} else {
// 2. Delete related common data: i.e.: entities
await mongoUtil.runTransactionWithRetry(deleteRelatedData.bind(this), session);
// 3. Delete related clients and jobs and job data.
await deleteCustomerClients.call(this, session, markDeletedOnly);
// 4. Delete related vehicles and data
await deleteCustomerVehicles.call(this, session, markDeletedOnly);
// 5. Delete the customer
await mongoUtil.runTransactionWithRetry(this.deleteMarked.bind(this), session);
}
} catch (error) {
throw error;
} finally {
if (endSesDone && session) await session.endSession();
}
}
module.exports = User.discriminator(UserTypes.APP, schema, { clone: false });