Error in console "cannot read property 'widget' of null"

Hello,
on my staging environment, when I display an object of my ‘Leads’ collection, I get an error in my console and the other collections related to this object are not displayed correctly.

This does not happen when I run my staging code on my local environment, so I guess it’s not a code problem.

  • Express Version: 4.5.10
  • Sequelize Version: 6.3.5
  • Project Name: Homeloop

Hello @Benjamin_Crespo,

Sorry about this.

Is this the only collection with this problem?
Also, did this happen overnight or did you make some change in your admin panel recently?

Could you try to create a new development environment, and copy the staging layout to it? To see if the problem also appears on this new environment?

I’ll try to dig more on my side.

Hello Guillaume,

Yes it seems to be the only one. I don’t think anybody did some change on the layout recently.
I’ll try to create a new dev environment as soon as I can, thanks

How can I copy the staging layout on my local env ?

@Benjamin_Crespo you can find the corresponding documentation here
https://docs.forestadmin.com/documentation/reference-guide/project-settings/environments-tab#copying-a-layout-configuration

You will find this in the environment tab of your project settings, when displaying the staging details.

As stated this action cannot be undone, please be sure to copy your staging to your dev and not the inverse.

The only COPY button that I have is the one that copies the env_secret

@Benjamin_Crespo this should appear here indeed.
Is this also NOT visible on other environments detail pages or only on this one?

I also refreshed some cache on our end, can you confirm that the initial issue is still there?
I continue on my end.

Yes the problem is still there and no, no COPY button on the other environments :frowning:

Sorry I missed that, but your project has our Dev Workflow activated.
Copying layout this way is not available in this case, and your development environment will reflect your production layout.

One more question, I see you have three teams.
Does the error occur for all teams or only a specific one?

@Benjamin_Crespo, would you mind sharing a screenshot of your project when the error occurs, or event better a screen recording of it (using Loom or any tool you like).

This will help us narrow the issue and reproduce it.

If possible, could you also share the code of your leads model?

Thank you.

For the other teams I don’t know.

Here’s my collection

Once I click to see one of the items, I see the Tasks associated to it being fetched indefinitely, and the error in the console occurs.

Here’s the code

(Attachment leads.model.txt is missing)

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
}

Hi @Benjamin_Crespo,

Wow, that’s a long message :slight_smile:

Can you please confirm the issue is still present now ?

If yes, in order to have some more information:

  • Was the collection working in the past, or is it a fresh new collection?
  • Does this collection receives new customization just before you find the problem?
  • You are a member of the team Central, so the issue is happening there, right?
  • Can you please confirm only Central team faces the issue?
  • (Also, is only Central team who is using the Leads collection?)

Regards

Hi,

The collection the main one in our infrastructure, so it’s one of the oldest and it’s been working until now.
One of its relations (with the collection Inspections) has been passed from hasOne to hasMany, but I don’t have the problem on my local environment.
I don’t think there is anyone in the other teams, I think they are not used.

Yes the problem is still present.

Thank you for your help

Your liana version is in 6.6.3
Can you try to update on 7.9.1?
The forest-express-sequelize npm package contain fixes about hasMany.

You will find upgrade note here

Regards

Hi,

I’ll try to do this when I have some time, because it will force me to update some other libraries.
Is it possible for you to copy our production layout to the staging environment ?

Hey @Benjamin_Crespo,

Yes, it’s possible. You need to go to the Project Settings > Environments. Then click on your Production environment. Finally you will be able to Copy layout configuration to… your remote environment.

I hope it will help you.

Best regards

I don’t have the “Copy layout configuration” field