const Sequelize = require(‘sequelize’)
const DataTypes = Sequelize.DataTypes
const leadStages = require(’…/…/utils/leadStages’)
const photosNotify = require(’…/common/photosNotify’)
const enums = {
abandoned_reason: [
‘fake’,
‘fake_number’,
‘estimate_only’,
‘eligible_to_nothing’,
‘cannot_contact_client’,
‘offers_definitely_refused’,
‘not_selling_anymore’,
‘definitely_sold’,
‘no_go’,
‘other’
],
acquisition_source: [‘manual’, ‘haussmann’, ‘BtB’],
haussmann_funnel: [
‘pri’,
‘dir_incomplete’,
‘pri_incomplete’,
‘dir_io’,
‘dir_nio’,
‘ipa_accepted’,
‘ipa_refused’,
‘eligible_to_nothing’,
‘agent_dir_incomplete’,
‘agent_dir_io’,
‘agent_dir_nio’
],
project_homeloop_motives: [
‘sell_fast’,
‘avoid_pret_relais’,
‘avoid_withdrawal’,
‘simplicity’,
‘need_cash’,
‘simply_interested’,
‘test’,
‘finance_new’,
‘no_offer’
],
project_selling_motives: [
‘divorce’,
‘inheritance’,
‘moving_out’,
‘investing’,
‘purchased_another’,
‘sell_before_buy’
],
qualification_status: [‘short_term’, ‘long_term’],
dir_non_eligible_reasons: [‘geography’, ‘estate_type’, ‘floor’, ‘price’, ‘construction_year’, ‘rented’, ‘legal’, ‘recycled’],
pri_non_eligible_reasons: [‘geography’, ‘estate_type’, ‘floor’, ‘price’, ‘construction_year’, ‘rented’, ‘legal’],
ipa_non_eligible_reasons: [‘geography’, ‘estate_type’, ‘floor’, ‘price’, ‘construction_year’, ‘rented’, ‘legal’]
}
// Load associations
async function loadAssociations(lead) {
lead.contact = await lead.getContact()
lead.inspection = await lead.getInspection()
lead.dir_offers = await lead.getDir_offers()
lead.pri_offers = await lead.getPri_deals()
lead.dir_deals = await lead.getDir_deals()
lead.pri_deals = await lead.getPri_deals()
lead.ipa_deals = await lead.getIpa_deals()
lead.resellings = await lead.getResellings()
lead.agent = await lead.getAgent()
}
// Abandon associated offers
function abandonAssociatedOffers(sequelizeClient, lead) {
let abandonDirOffers = sequelizeClient.models.dir_offers.findAll({ where: { lead_id: lead.id } }).then((offers) => {
for (const offer of offers) {
offer.update({ status: ‘rather_refused’ })
}
})
let abandonPriOffers = sequelizeClient.models.pri_offers.findAll({ where: { lead_id: lead.id } }).then((offers) => {
for (const offer of offers) {
offer.update({ status: ‘rather_refused’ })
}
})
return Promise.all([abandonDirOffers, abandonPriOffers])
}
async function createCustomerDocuments(sequelizeClient,lead){
const types = await sequelizeClient.models.document_types.findAll({ })
const promises = await types.map(async function(type){
await sequelizeClient.models.customer_documents.create({lead_id:lead.id,document_type_id:type.id})
})
await Promise.all(promises)
}
module.exports = function (app) {
const sequelizeClient = app.get(‘sequelizeClient’)
// fix Sequelize cli because app.get(’***’) always return Sequelize
const delayLeadStageStuckDays = !(app.get(‘delayLeadStageStuckDays’) || {}).models
? JSON.parse(app.get(‘delayLeadStageStuckDays’) || ‘’)
:
const leads = sequelizeClient.define(
‘leads’,
{
reference: {
type: DataTypes.STRING,
allowNull: false,
unique: true,
validate: {
is: /^[-a-z0-9_]+$/
}
},
stage: DataTypes.STRING,
score: DataTypes.INTEGER,
abandoned: {
type: DataTypes.BOOLEAN,
allowNull: false,
defaultValue: false
},
abandoned_date: {
type: DataTypes.DATE,
validate: {
isInPastOrPresent(val) {
if (val > new Date(Date.now() + 60000)) {
throw new sequelizeClient.ValidationError(‘Validation isInPastOrPresent on abandoned_date failed’, [
{ path: ‘abandoned_date’ }
])
}
}
},
meta: {
visibility: ‘abandoned’
}
},
abandoned_reason: {
type: DataTypes.ENUM(…enums.abandoned_reason),
validate: {
isIn: [enums.abandoned_reason]
},
meta: {
visibility: ‘abandoned’
}
},
abandoned_comments: {
type: DataTypes.TEXT,
meta: {
visibility: ‘abandoned’
}
},
rival_cannot_get_info: {
type: DataTypes.BOOLEAN,
allowNull: false,
defaultValue: false
},
acquisition_source: {
type: DataTypes.ENUM(…enums.acquisition_source),
allowNull: false,
validate: {
isIn: [enums.acquisition_source]
}
},
acquisition_source_internal_id: DataTypes.STRING,
acquisition_date: {
type: DataTypes.DATE,
allowNull: false,
defaultValue: sequelizeClient.fn(‘now’)
},
marketing_ft_channel_other: DataTypes.STRING,
marketing_does_not_remember: {
type: DataTypes.BOOLEAN,
allowNull: false,
defaultValue: false
},
haussmann_funnel: {
type: DataTypes.ENUM(…enums.haussmann_funnel),
validate: {
isIn: [enums.haussmann_funnel]
}
},
buying_price_euro: {
type: DataTypes.DECIMAL(12, 2),
validate: {
min: 0
},
meta: {
measure: ‘price’
}
},
buying_contract_signed_date: {
type: DataTypes.DATEONLY,
validate: {
isInPastOrPresent(val) {
if (new Date(val) > new Date(Date.now() + 60000)) {
throw new sequelizeClient.ValidationError(
‘Validation isInPastOrPresent on buying_contract_signed_date failed’,
[{ path: ‘buying_contract_signed_date’ }]
)
}
}
}
},
project_homeloop_motives: {
type: DataTypes.ARRAY(DataTypes.ENUM(…enums.project_homeloop_motives)),
validate: {
isArrayOfProjectHomeloopMotives(value) {
for (let item of value) {
if (!enums.project_homeloop_motives.includes(item)) {
throw new sequelizeClient.ValidationError(
‘Validation isArrayOfProjectHomeloopMotives on project_homeloop_motives failed’,
[{ path: ‘project_homeloop_motives’ }]
)
}
}
}
}
},
project_selling_motives: {
type: DataTypes.ARRAY(DataTypes.ENUM(…enums.project_selling_motives)),
validate: {
isArrayOfProjectSellingMotives(value) {
for (let item of value) {
if (!enums.project_selling_motives.includes(item)) {
throw new sequelizeClient.ValidationError(
‘Validation isArrayOfProjectSellingMotives on project_selling_motives failed’,
[{ path: ‘project_selling_motives’ }]
)
}
}
}
}
},
project_is_estate_rented: DataTypes.BOOLEAN,
project_rented_until: {
type: DataTypes.DATEONLY,
allowNull: true,
defaultValue: null
},
project_is_judiciary_procedure_ongoing: DataTypes.BOOLEAN,
project_expected_selling_start_date: DataTypes.DATEONLY,
project_expected_negociated_net_price_euro: {
type: DataTypes.DECIMAL(12, 2),
meta: {
measure: ‘price’
}
},
project_expected_precontract_signed_date: DataTypes.DATEONLY,
project_expected_contract_signed_date: DataTypes.DATEONLY,
qualification_call_completed: {
type: DataTypes.BOOLEAN,
allowNull: false,
defaultValue: false
},
qualification_call_completed_date: DataTypes.DATE,
qualification_status: {
type: DataTypes.ENUM(…enums.qualification_status),
validate: {
isIn: [enums.qualification_status]
}
},
qualification_nps: {
type: DataTypes.INTEGER,
validate: {
min: 0,
max: 10
},
meta: {
measure: ‘rating’
}
},
dir_eligibility: DataTypes.BOOLEAN,
dir_non_eligible_reasons: {
type: DataTypes.ARRAY(DataTypes.ENUM(…enums.dir_non_eligible_reasons)),
validate: {
isArrayOfDirNonEligibleReasons(value) {
for (let item of value) {
if (!enums.dir_non_eligible_reasons.includes(item)) {
throw new sequelizeClient.ValidationError(
‘Validation isArrayOfDirNonEligibleReasons on dir_non_eligible_reasons failed’,
[{ path: ‘dir_non_eligible_reasons’ }]
)
}
}
}
},
meta: {
visibility: ‘dir_eligibility === false’
}
},
dir_consent: DataTypes.BOOLEAN,
dir_consent_date: {
type: DataTypes.DATE,
meta: {
visibility: ‘dir_consent’
}
},
pri_eligibility: DataTypes.BOOLEAN,
pri_non_eligible_reasons: {
type: DataTypes.ARRAY(DataTypes.ENUM(…enums.pri_non_eligible_reasons)),
validate: {
isArrayOfPriNonEligibleReasons(value) {
for (let item of value) {
if (!enums.pri_non_eligible_reasons.includes(item)) {
throw new sequelizeClient.ValidationError(
‘Validation isArrayOfPriNonEligibleReasons on pri_non_eligible_reasons failed’,
[{ path: ‘pri_non_eligible_reasons’ }]
)
}
}
}
},
meta: {
visibility: ‘pri_eligibility === false’
}
},
pri_consent: DataTypes.BOOLEAN,
pri_consent_date: {
type: DataTypes.DATE,
meta: {
visibility: ‘pri_consent’
}
},
ipa_eligibility: DataTypes.BOOLEAN,
ipa_non_eligible_reasons: {
type: DataTypes.ARRAY(DataTypes.ENUM(…enums.ipa_non_eligible_reasons)),
validate: {
isArrayOfIpaNonEligibleReasons(value) {
for (let item of value) {
if (!enums.ipa_non_eligible_reasons.includes(item)) {
throw new sequelizeClient.ValidationError(
‘Validation isArrayOfIpaNonEligibleReasons on ipa_non_eligible_reasons failed’,
[{ path: ‘ipa_non_eligible_reasons’ }]
)
}
}
}
},
meta: {
visibility: ‘ipa_eligibility === false’
}
},
ipa_consent: DataTypes.BOOLEAN,
ipa_consent_date: {
type: DataTypes.DATE,
meta: {
visibility: ‘ipa_consent’
}
},
ic_completed: {
type: DataTypes.BOOLEAN,
allowNull: false,
defaultValue: false
},
ic_completed_date: DataTypes.DATE,
ic_approval: {
type: DataTypes.BOOLEAN,
meta: {
visibility: ‘ic_completed’
}
},
ic_comments: DataTypes.TEXT,
photos_number: {
type: DataTypes.INTEGER,
validate: {
min: 0,
max: 8
},
allowNull: true
},
clust_created: {
type: DataTypes.BOOLEAN,
defaultValue: false
},
utm_campaign: {
type: DataTypes.STRING,
allowNull: true
},
utm_source: {
type: DataTypes.STRING,
allowNull: true
},
utm_medium: {
type: DataTypes.STRING,
allowNull: true
},
utm_term: {
type: DataTypes.STRING,
allowNull: true
},
drive_folder: {
type: DataTypes.STRING,
allowNull: true
},
call_booked: {
type: DataTypes.BOOLEAN,
defaultValue: false
},
ipa_selling_project: {
type: DataTypes.TEXT,
},
preselling_agreed:{
type: DataTypes.BOOLEAN,
defaultValue: false
},
video_inspection_booked:{
type: DataTypes.BOOLEAN,
defaultValue: false
}
},
{
hooks: {
beforeCount(options) {
options.raw = true
},
async beforeUpdate(lead) {
await loadAssociations(lead)
lead.stage = leadStages.computeStage(lead)
},
async beforeCreate(lead) {
await loadAssociations(lead)
lead.stage = leadStages.computeStage(lead)
},
async afterCreate(lead) {
await createCustomerDocuments(sequelizeClient,lead)
},
async beforeSave(lead) {
const cio2 = app.get(‘cio2’)
if (lead.id != null) {
cio2.trackLead(
lead.id,
‘lead_changed’,
{
changed: lead.changed ? JSON.stringify(lead.changed()) : false,
sendToBuyer: lead.changed(‘stage’) && lead.stage == ‘dir_reclosed’
},
lead
)
}
if (!lead.changed(‘id’) && lead.changed(‘qualification_call_completed_date’)) {
cio2.trackLeadWithQualification(lead.id, ‘qualification_completed’)
}
if (!lead.changed(‘reference’) && lead.changed(‘marketing_ft_channel_id’)) {
await photosNotify(lead.id, app)
}
if (lead.changed(‘abandoned’) && lead.abandoned === true) {
// lead was just abandoned, should abandon associated offers
await abandonAssociatedOffers(sequelizeClient, lead)
}
if (lead.changed(‘owner_id’)) {
// snapshot branch
const owner = await lead.getOwner()
if (owner) {
lead.branch_id = owner.branch_id
}
}
if (
lead.changed(‘haussmann_funnel’) &&
[‘dir_incomplete’, ‘agent_dir_incomplete’].includes(lead.haussmann_funnel)
) {
// Track incomplete leads
cio2.trackLead(lead.id, ‘vf_lead_acquired’)
}
},
beforeBulkCreate(options) {
options.individualHooks = true
},
beforeBulkUpdate(options) {
options.individualHooks = true
}
},
indexes: [
{
name: ‘leads_estate_id_index’,
fields: [‘estate_id’]
}
],
validate: {
async icApprovalNeeded() {
if (this.id && this.ic_approval === false) {
const dirOffers = await sequelizeClient.models.dir_offers.findAll({
where: { lead_id: this.id }
})
if (dirOffers.filter((dir_offer) => dir_offer.is_preoffer !== true).length > 0) {
throw new Error(‘There cannot exist any DIR offer that is not a preoffer when ic_approval is false.’)
}
const dirDeals = await sequelizeClient.models.dir_deals.findAll({
where: { lead_id: this.id }
})
if (dirDeals.length > 0) {
throw new Error(‘There cannot exist any DIR deal when ic_approval is false.’)
}
}
},
abandonedDetails() {
if (
this.abandoned === true &&
(!this.abandoned_date || this.abandoned_initiator_id == null || !this.abandoned_reason)
) {
throw new Error(
‘Validation abandoned_details: If abandoned is true, then abandoned_date, abandoned_initiator_id and abandoned_reason must not be null.’
)
}
},
abandonedReasonOtherComments() {
if (this.abandoned_reason === ‘other’ && !this.abandoned_comments) {
throw new Error(
‘Validation abandoned_reason_other_comments: If abandoned_reason is “other”, then abandoned_comments must not be null.’
)
}
},
abandonedNoDetails() {
if (
this.abandoned !== true &&
(this.abandoned_date || this.abandoned_initiator_id != null || this.abandoned_reason)
) {
throw new Error(
‘Validation abandoned_no_details: If abandoned is not true, then abandoned_date, abandoned_initiator_id, abandoned_reason and abandoned_comments must be null.’
)
}
},
acquisitionManualCreator() {
if (this.acquisition_source === ‘manual’ && this.acquisition_creator_id == null) {
throw new Error(
‘Validation acquisition_manual_creator: If acquisition_source is “manual”, then acquisition_creator_id must not be null.’
)
}
},
qualificationCallDetails() {
if (
this.qualification_call_completed === true &&
(!this.qualification_call_completed_date ||
this.qualification_call_caller_id == null ||
!this.qualification_status)
) {
throw new Error(
‘Validation qualification_call_details: If qualification_call_completed is true, then qualification_call_completed_date, qualification_call_caller_id and qualification_status must not be null.’
)
}
},
qualificationCallCompletedInPast() {
if (
this.qualification_call_completed === true &&
this.qualification_call_completed_date != null &&
this.qualification_call_completed_date > new Date()
) {
throw new Error(
Validation qualification_call_completed_in_past: If qualification_call_completed is true and qualification_call_completed_date is not null, then qualification_call_completed_date must be <= now.
)
}
},
dirNonEligibilityDetails() {
if (
this.dir_eligibility === false &&
(this.dir_non_eligible_reasons == null || this.dir_non_eligible_reasons.length === 0)
) {
throw new Error(
‘Validation dir_non_eligibility_details: If dir_eligibility is false, then dir_non_eligibile_reasons must not be null.’
)
}
},
dirNonEligibilityNoDetails() {
if (
this.dir_eligibility !== false &&
this.dir_non_eligible_reasons != null &&
this.dir_non_eligible_reasons.length > 0
) {
throw new Error(
‘Validation dir_non_eligibility_no_details: If dir_eligibility is not false, then dir_non_eligibile_reasons must be null.’
)
}
},
dirConsentDetails() {
if (this.dir_consent === true && !this.dir_consent_date) {
throw new Error(
‘Validation dir_consent_details: If dir_consent is true, then dir_consent_date must not be null.’
)
}
},
dirConsentNoDetails() {
if (this.dir_consent !== true && this.dir_consent_date) {
throw new Error(
‘Validation dir_consent_no_details: If dir_consent is not true, then dir_consent_date must be null.’
)
}
},
priNonEligibilityDetails() {
if (
this.pri_eligibility === false &&
(this.pri_non_eligible_reasons == null || this.pri_non_eligible_reasons.length === 0)
) {
throw new Error(
‘Validation pri_non_eligibility_details: If pri_eligibility is false, then pri_non_eligibile_reasons must not be null.’
)
}
},
priNonEligibilityNoDetails() {
if (
this.pri_eligibility !== false &&
this.pri_non_eligible_reasons != null &&
this.pri_non_eligible_reasons.length > 0
) {
throw new Error(
‘Validation pri_non_eligibility_no_details: If pri_eligibility is not false, then pri_non_eligibile_reasons must be null.’
)
}
},
priConsentDetails() {
if (this.pri_consent === true && !this.pri_consent_date) {
throw new Error(
‘Validation pri_consent_details: If pri_consent is true, then pri_consent_date must not be null.’
)
}
},
priConsentNoDetails() {
if (this.pri_consent !== true && this.pri_consent_date) {
throw new Error(
‘Validation pri_consent_no_details: If pri_consent is not true, then pri_consent_date must be null.’
)
}
},
ipaNonEligibilityDetails() {
if (
this.ipa_eligibility === false &&
(this.ipa_non_eligible_reasons == null || this.ipa_non_eligible_reasons.length === 0)
) {
throw new Error(
‘Validation ipa_non_eligibility_details: If ipa_eligibility is false, then ipa_non_eligibile_reasons must not be null.’
)
}
},
ipaNonEligibilityNoDetails() {
if (
this.ipa_eligibility !== false &&
this.ipa_non_eligible_reasons != null &&
this.ipa_non_eligible_reasons.length > 0
) {
throw new Error(
‘Validation ipa_non_eligibility_no_details: If ipa_eligibility is not false, then ipa_non_eligibile_reasons must be null.’
)
}
},
ipaConsentDetails() {
if (this.ipa_consent === true && !this.ipa_consent_date) {
throw new Error(
‘Validation ipa_consent_details: If ipa_consent is true, then ipa_consent_date must not be null.’
)
}
},
ipaConsentNoDetails() {
if (this.ipa_consent !== true && this.ipa_consent_date) {
throw new Error(
‘Validation ipa_consent_no_details: If ipa_consent is not true, then ipa_consent_date must be null.’
)
}
},
icDetails() {
if (this.ic_completed === true && (!this.ic_completed_date || this.ic_approval == null)) {
throw new Error(
‘Validation ic_details: If ic_completed is true, then ic_completed_date must not be null and ic_approval must not be null.’
)
}
},
icNoDetails() {
if (this.ic_completed !== true && this.ic_approval != null) {
throw new Error(‘Validation ic_no_details: If ic_completed is not true, then ic_approval must be null.’)
}
},
icCompletedInPast() {
if (this.ic_completed === true && this.ic_completed_date != null && this.ic_completed_date > new Date()) {
throw new Error(
Validation ic_completed_in_past: If ic_completed is true and ic_completed_date is not null, then ic_completed_date must be <= now.
)
}
},
contactOrAgentRequired() {
if (this.contact_id == null && this.agent_id == null) {
throw new Error(On doit préciser au moins un agent ou un contact.
)
}
}
},
underscored: true
}
)
leads.associate = function (models) {
leads.belongsTo(models.contacts, {
as: ‘contact’,
foreignKey: {
name: ‘contact_id’,
allowNull: true,
unique: ‘leads_unique_contact_estate_combination’
},
onDelete: ‘RESTRICT’
})
leads.belongsTo(models.contacts, {
as: ‘agent’,
foreignKey: {
name: ‘agent_id’,
allowNull: true,
unique: ‘leads_unique_agent_estate_combination’
},
onDelete: ‘RESTRICT’
})
leads.belongsTo(models.estates, {
as: ‘estate’,
foreignKey: {
name: ‘estate_id’,
allowNull: false,
unique: ‘leads_unique_contact_estate_combination’
},
onDelete: ‘RESTRICT’
})
leads.belongsTo(models.employees, {
as: ‘owner’,
foreignKey: {
name: ‘owner_id’,
allowNull: false
},
onDelete: ‘RESTRICT’
})
leads.belongsTo(models.employees, {
as: ‘abandoned_initiator’,
foreignKey: {
name: ‘abandoned_initiator_id’,
allowNull: true,
meta: {
visibility: ‘abandoned’
}
},
onDelete: ‘RESTRICT’
})
leads.belongsTo(models.employees, {
as: ‘acquisition_creator’,
foreignKey: {
name: ‘acquisition_creator_id’,
allowNull: true,
meta: {
visibility: acquisition_source === 'manual'
}
},
onDelete: ‘RESTRICT’
})
leads.belongsTo(models.config_marketing_channels, {
as: ‘marketing_ft_channel’,
foreignKey: {
name: ‘marketing_ft_channel_id’,
allowNull: true
},
onDelete: ‘RESTRICT’
})
leads.belongsTo(models.employees, {
as: ‘qualification_call_caller’,
foreignKey: {
name: ‘qualification_call_caller_id’,
allowNull: true
},
onDelete: ‘RESTRICT’
})
leads.hasOne(models.buyer_satisfactions, {
as: ‘buyer_satisfactions’,
foreignKey: {
name: ‘lead_id’,
allowNull: true
},
onDelete: ‘CASCADE’
})
leads.hasOne(models.seller_satisfactions, {
as: ‘seller_satisfactions’,
foreignKey: {
name: ‘lead_id’,
allowNull: true
},
onDelete: ‘CASCADE’
})
leads.hasMany(models.rivals, {
as: ‘rivals’,
foreignKey: {
name: ‘lead_id’,
allowNull: false
},
onDelete: ‘CASCADE’
})
leads.hasMany(models.tasks, {
as: ‘tasks’,
foreignKey: {
name: ‘lead_id’,
allowNull: false
},
onDelete: ‘CASCADE’
})
leads.hasMany(models.valuations, {
as: ‘valuations’,
foreignKey: {
name: ‘lead_id’,
allowNull: false
},
onDelete: ‘CASCADE’
})
leads.hasMany(models.inspections, {
as: ‘inspection’,
foreignKey: {
name: ‘lead_id’,
allowNull: false
},
onDelete: ‘CASCADE’
})
leads.hasMany(models.dir_offers, {
as: ‘dir_offers’,
foreignKey: {
name: ‘lead_id’,
allowNull: false
},
onDelete: ‘CASCADE’
})
leads.hasMany(models.pri_offers, {
as: ‘pri_offers’,
foreignKey: {
name: ‘lead_id’,
allowNull: false
},
onDelete: ‘CASCADE’
})
leads.hasMany(models.dir_deals, {
as: ‘dir_deals’,
foreignKey: {
name: ‘lead_id’,
allowNull: false
},
onDelete: ‘CASCADE’
})
leads.hasMany(models.pri_deals, {
as: ‘pri_deals’,
foreignKey: {
name: ‘lead_id’,
allowNull: false
},
onDelete: ‘CASCADE’
})
leads.hasMany(models.ipa_deals, {
as: ‘ipa_deals’,
foreignKey: {
name: ‘lead_id’,
allowNull: false
},
onDelete: ‘CASCADE’
})
leads.hasMany(models.resellings, {
as: ‘resellings’,
foreignKey: {
name: ‘lead_id’,
allowNull: false
},
onDelete: ‘CASCADE’
})
leads.hasMany(models.matches, {
as: ‘matches’,
foreignKey: {
name: ‘lead_id’,
allowNull: false,
unique: ‘matches_unique_lead_buying-project_combination’
},
onDelete: ‘CASCADE’
})
leads.hasMany(models.visits, {
as: ‘visits’,
foreignKey: {
name: ‘lead_id’,
allowNull: false
},
onDelete: ‘CASCADE’
})
leads.hasMany(models.buying_offers, {
as: ‘buying_offers’,
foreignKey: {
name: ‘lead_id’,
allowNull: false
},
onDelete: ‘CASCADE’
})
leads.hasMany(models.buying_options, {
as: ‘buying_options’,
foreignKey: {
name: ‘lead_id’,
allowNull: false
},
onDelete: ‘CASCADE’
})
leads.belongsTo(models.branches, {
as: ‘branch’,
foreignKey: {
name: ‘branch_id’,
allowNull: true
},
onDelete: ‘RESTRICT’
})
leads.hasMany(models.buying_projects, {
as: ‘buying_projects’,
foreignKey: {
name: ‘lead_id’,
allowNull: true
},
onDelete: ‘RESTRICT’
})
leads.hasMany(models.customer_documents, {
as: ‘customer_documents’,
foreignKey: {
name: ‘lead_id’,
allowNull: false
},
onDelete: ‘CASCADE’
})
}
return leads
}