Smart field values disappear after Edit

Feature(s) impacted

Smart fields no longer computed after editing

Observed behavior

I have this collection :

// This model was generated by Lumber. However, you remain in control of your models.
// Learn how here: https://docs.forestadmin.com/documentation/v/v6/reference-guide/models/enrich-your-models
module.exports = (sequelize, DataTypes) => {
  const { Sequelize } = sequelize;
  // This section contains the fields of your model, mapped to your table's columns.
  // Learn more here: https://docs.forestadmin.com/documentation/v/v6/reference-guide/models/enrich-your-models#declaring-a-new-field-in-a-model
  const Contracts = sequelize.define('contracts', {
    displayName: {
      type: DataTypes.STRING,
      defaultValue: "",
      allowNull: false,
    },
    amount: {
      type: DataTypes.BIGINT,
      defaultValue: 0,
      allowNull: false,
    },
    createdAt: {
      type: DataTypes.DATE,
    },
    updatedAt: {
      type: DataTypes.DATE,
    },
    parcelSergicId: {
      type: DataTypes.INTEGER,
    },
    sergicUpdateTime: {
      type: DataTypes.DATE,
    },
    customerReferenceNumber: {
      type: DataTypes.STRING,
    },
    customerKey: {
      type: DataTypes.STRING,
    },
    invitedCount: {
      type: DataTypes.INTEGER,
      defaultValue: 0,
      allowNull: false,
    },
    reminderGroup: {
      type: DataTypes.INTEGER,
      defaultValue: 8,
    },
    reportAccountEntryAmount: {
      type: DataTypes.BIGINT,
      defaultValue: 0,
    },
    reportAccountEntryOperationDate: {
      type: DataTypes.DATE,
    },
    deletedAt: {
      type: DataTypes.DATE,
    },
    coownerAmount: {
      type: DataTypes.BIGINT,
      defaultValue: Sequelize.literal('0.0'),
    },
    pendingAmount: {
      type: DataTypes.INTEGER,
      defaultValue: 0,
      allowNull: false,
    },
    dueDate: {
      type: DataTypes.DATE,
    },
    occupant: {
      type: DataTypes.BOOLEAN,
    },
    startDate: {
      type: DataTypes.DATEONLY,
    },
    endDate: {
      type: DataTypes.DATEONLY,
    },
    lastReminderGroupUpdate: {
      type: DataTypes.DATEONLY,
    },
  }, {
    tableName: 'contracts',
    underscored: true,
    schema: process.env.DATABASE_SCHEMA,
  });

  // This section contains the relationships for this model. See: https://docs.forestadmin.com/documentation/v/v6/reference-guide/relationships#adding-relationships.
  Contracts.associate = (models) => {
    Contracts.belongsTo(models.identities, {
      foreignKey: {
        name: 'identityIdKey',
        field: 'identity_id',
      },
      as: 'identity',
    });
    Contracts.belongsTo(models.places, {
      foreignKey: {
        name: 'placeIdKey',
        field: 'place_id',
      },
      as: 'place',
    });
    Contracts.belongsTo(models.integrations, {
      foreignKey: {
        name: 'placeIdKey',
        field: 'place_id',
      },
      as: 'integration',
    });
    Contracts.belongsToMany(models.parcels, {
      through: 'parcelContracts',
      foreignKey: 'contract_id',
      otherKey: 'parcel_id',
      as: 'parcelsThroughParcelContracts',
    });

    // decomposition de la relation ci-dessus en 2
    Contracts.hasMany(models.parcelContracts, {
      foreignKey: {
        name: 'contractIdKey',
        field: 'contract_id',
      },
      as: 'contractParcel',
    });

    Contracts.hasMany(models.managementFees, {
      foreignKey: {
        name: 'contractIdKey',
        field: 'contract_id',
      },
      as: 'managementFees',
    });
    Contracts.hasMany(models.contractDocuments, {
      foreignKey: {
        name: 'contractIdKey',
        field: 'contract_id',
      },
      as: 'contractDocuments',
    });
    Contracts.hasMany(models.accountEntries, {
      foreignKey: {
        name: 'contractIdKey',
        field: 'contract_id',
      },
      as: 'accountEntries',
    });
    Contracts.hasMany(models.alerts, {
      foreignKey: {
        name: 'contractIdKey',
        field: 'contract_id',
      },
      as: 'alerts',
    });
    Contracts.hasMany(models.publicWorkDetails, {
      foreignKey: {
        name: 'contractIdKey',
        field: 'contract_id',
      },
      as: 'publicWorkDetails',
    });
    Contracts.hasMany(models.convocationSettings, {
      foreignKey: {
        name: 'contractIdKey',
        field: 'contract_id',
      },
      as: 'convocationSettings',
    });
    Contracts.hasMany(models.preEtatDateRequests, {
      foreignKey: {
        name: 'contractIdKey',
        field: 'contract_id',
      },
      as: 'preEtatDateRequests',
    });
    Contracts.hasMany(models.contractRepartitionKeys, {
      foreignKey: {
        name: 'contractIdKey',
        field: 'contract_id',
      },
      as: 'contractRepartitionKeys',
    });
    Contracts.hasMany(models.waterReadings, {
      foreignKey: {
        name: 'contractIdKey',
        field: 'contract_id',
      },
      as: 'waterReadings',
    });
    // relation creee manuellement pour les mandataires de compte client
    Contracts.hasMany(models.roles, {
      foreignKey: {
        name: 'contractIdKey',
        field: 'resource_id',
      },
      as: 'roles',
    });
    Contracts.hasOne(models.moneyOrders, {
      foreignKey: {
        name: 'contractIdKey',
        field: 'contract_id',
      },
      as: 'moneyOrder',
    });
    Contracts.hasOne(models.contractAddresses, {
      foreignKey: {
        name: 'contractIdKey',
        field: 'contract_id',
      },
      as: 'contractAddress',
    });
  };

  return Contracts;
};

with a bunch of smart fields :

const { collection } = require('forest-express-sequelize');
const models = require('../models');

const { Sequelize } = require('sequelize');

const Op = require('sequelize').Op;

const axios = require('axios').default;
const API_URL = process.env.API_URL;

// This file allows you to add to your Forest UI:
// - Smart actions: https://docs.forestadmin.com/documentation/reference-guide/actions/create-and-manage-smart-actions
// - Smart fields: https://docs.forestadmin.com/documentation/reference-guide/fields/create-and-manage-smart-fields
// - Smart relationships: https://docs.forestadmin.com/documentation/reference-guide/relationships/create-a-smart-relationship
// - Smart segments: https://docs.forestadmin.com/documentation/reference-guide/segments/smart-segments
collection('contracts', {
  actions: [],
  fields: [{
    field: 'reference immeuble',
    type: 'String',
    get: contract => models.places.findByPk(contract.placeIdKey)
      .then(place => place ? place.sergicIdFull : ''),
    search: (query, search) => {
      const searchCondition = {
        [Op.or]: [
          { '$place.sergic_id_full$': { [Op.iLike]: `%${search}%` } }
        ],
      };
      query.where[Op.and][0][Op.or].push(searchCondition);
      if (!query.include.find((include) => include.as === 'place')) {
        query.include.push({
          model: models.places,
          as: 'place',
        });
      }
      return query;
    }
  }, {
    field: 'nom complet',
    type: 'String',
    get: contract => models.identities.findByPk(contract.identityIdKey)
      .then(identity => identity ? `${identity.lastName ? identity.lastName.toUpperCase() : ''} ${identity.firstName ? identity.firstName : ''}` : ''),
    search: (query, search) => {
      const split = search.split(' ');
      const searchCondition = {
        [Op.or]: [
          { '$identity.last_name$': { [Op.iLike]: `%${split[0]}%` } },
          { '$identity.last_name$': { [Op.iLike]: `%${split[1]}%` } },
          { '$identity.first_name$': { [Op.iLike]: `%${split[0]}%` } },
          { '$identity.first_name$': { [Op.iLike]: `%${split[1]}%` } },
        ],
      };
      query.where[Op.and][0][Op.or].push(searchCondition);
      if (!query.include.find((include) => include.as === 'identity')) {
        query.include.push({
          model: models.identities,
          as: 'identity',
        });
      }
      return query;
    }
  }, {
    field: 'ID client',
    type: 'String',
    get: contract => models.identities.findByPk(contract.identityIdKey)
      .then(identity => identity && identity.sergicId ? identity.sergicId : '')
  }, {
    field: 'civilite',
    type: 'Enum',
    enums: [
      'Madame et Monsieur',
      'Madame ou Monsieur',
      'Madame',
      'Mesdames',
      'Madame, Mademoiselle ou Monsieur',
      'Monsieur',
      'Messieurs',
      'Mademoiselle',
      'Succession',
      'Indivision',
      'Société civile immobilière',
      'Compagnie',
      'Société',
      'Groupe',
      'Administrations',
      'Société civile promotion immobilière',
      'Société civile construction vente',
      'Maître',
      'Maîtres',
      'Cabinet',
      'Docteur',
      'Notaire',
      'Etablissements',
      'Entreprise unipersonnelle à responsabilité limitée',
      'Groupement d\'intéret économique',
      'Société anonyme',
      'Société à responsabilité limitée',
      'Société anonyme simplifiée',
      'Société civile',
      'Société civile de moyens',
      'Société d\'économie mixte',
      'Société en nom collectif',
      'Société privée européenne',
      'Entreprise individuelle à responsabilité limitée',
      'Syndicat',
      'Association syndicale libre',
      'Association',
      'Banque',
      'Club',
    ],
    get: contract => models.identities.findByPk(contract.identityIdKey)
      .then(identity => identity && identity.title ? identity.title : '')
  }, {
    field: 'nom',
    type: 'String',
    get: contract => {
      console.log(contract);
      return models.identities.findByPk(contract.identityIdKey)
        .then(identity => identity && identity.lastName ? identity.lastName.toUpperCase() : '');
    }
  }, {
    field: 'prenom',
    type: 'String',
    get: contract => models.identities.findByPk(contract.identityIdKey)
      .then(identity => identity ? identity.firstName : '')
  }, {
    field: 'Invite a la connexion',
    type: 'Boolean',
  }, {
    field: 'telephone',
    type: 'String',
    get: contract => models.identities.findByPk(contract.identityIdKey)
      .then(identity => identity && identity.phoneNumber ? identity.phoneNumber : ''),
    search: (query, search) => {
      const searchCondition = {
        [Op.or]: [
          { '$identity.phone_number$': { [Op.iLike]: `%${search}%` } }
        ],
      };
      query.where[Op.and][0][Op.or].push(searchCondition);
      if (!query.include.find((include) => include.as === 'identity')) {
        query.include.push({
          model: models.identities,
          as: 'identity',
        });
      }
      return query;
    }
  }, {
    field: 'telephone domicile',
    type: 'String',
    get: contract => models.identities.findByPk(contract.identityIdKey)
      .then(identity => identity && identity.homePhone ? identity.homePhone : '')
  }, {
    field: 'email',
    type: 'String',
    get: contract => models.identities.findByPk(contract.identityIdKey)
      .then(identity => identity && identity.email ? identity.email : ''),
    search: (query, search) => {
      const searchCondition = {
        [Op.or]: [
          { '$identity.email$': { [Op.iLike]: `%${search}%` } }
        ],
      };
      query.where[Op.and][0][Op.or].push(searchCondition);
      if (!query.include.find((include) => include.as === 'identity')) {
        query.include.push({
          model: models.identities,
          as: 'identity',
        });
      }
      return query;
    }
  }, {
    field: 'lots',
    type: 'String',
    get: contract => models.contracts.findAll({
      where: { id: contract.id },
      include: [{
        model: models.parcels,
        as: 'parcelsThroughParcelContracts'
      }]
    }).then(results => {
      if (results[0].parcelsThroughParcelContracts) {
        return results[0].parcelsThroughParcelContracts.map(lot =>
          lot.displayName + (lot.doorNumber ? ' ' + lot.doorNumber : '')).join(', ');
      }
    })
  }, {
    field: 'Date derniere connexion ',
    type: 'Date',
    get: contract => models.identities.findOne({
      where: { id: contract.identityIdKey },
      include: [{
        model: models.users,
        as: 'user'
      }],
    }).then(identity => identity && identity.user ? identity.user.lastConnectionSyndicone : null)
  }, {
    field: 'Mandataire',
    type: 'String',
    get: contract => models.roles.findAll({
      where: {
        name: 'representative',
        resource_type: 'Contract',
        resource_id: contract.id,
      },
      include: [{
        model: models.identities,
        as: 'identitiesThroughIdentitiesRoles',
        required: true,
      }]
    }).then(roles => {
      if (roles[0]) {
        const identity = roles[0].identitiesThroughIdentitiesRoles[0];
        return `${identity.title} ${identity.lastName.toUpperCase()} ${identity.firstName ? identity.firstName : ''}`;
      }
    })
  }, {
    field: 'Telephone mandataire',
    type: 'String',
    get: contract => models.roles.findAll({
      where: {
        name: 'representative',
        resource_type: 'Contract',
        resource_id: contract.id,
      },
      include: [{
        model: models.identities,
        as: 'identitiesThroughIdentitiesRoles',
        required: true,
      }]
    }).then(roles => {
      if (roles[0]) {
        const identity = roles[0].identitiesThroughIdentitiesRoles[0];
        return `${identity.phoneNumber}`;
      }
    })
  }, {
    field: 'Email mandataire',
    type: 'String',
    get: contract => models.roles.findAll({
      where: {
        name: 'representative',
        resource_type: 'Contract',
        resource_id: contract.id,
      },
      include: [{
        model: models.identities,
        as: 'identitiesThroughIdentitiesRoles',
        required: true,
      }]
    }).then(roles => {
      if (roles[0]) {
        const identity = roles[0].identitiesThroughIdentitiesRoles[0];
        return `${identity.email}`;
      }
    })
  }, {
    field: 'Adresse mandataire',
    type: 'String',
    get: contract => models.roles.findAll({
      where: {
        name: 'representative',
        resource_type: 'Contract',
        resource_id: contract.id,
      },
      include: [{
        model: models.identities,
        as: 'identitiesThroughIdentitiesRoles',
        required: true,
        include: [{
          model: models.addresses,
          as: 'addresses',
          required: true,
          where: { addressable_type: 'Identity' }
        }]
      }]
    }).then(roles => {
      if (roles[0]) {
        const address = roles[0].identitiesThroughIdentitiesRoles[0].addresses[0];
        return `${address.streetNumber} ${address.street} ${address.complementary ? address.complementary : ''} ${address.zipCode} ${address.city.toUpperCase()} ${address.country}`;
      }
    })
  }, {
    field: 'Adresse postale',
    type: 'String',
    get: contract => models.addresses.findOne({
      where: {
        addressable_id: contract.identityIdKey,
        addressable_type: 'Identity'
      }
    }).then(address => address && address.street ? `${address.streetNumber} ${address.street} ${address.complementary ? address.complementary : ''} ${address.zipCode} ${address.city.toUpperCase()} ${address.country ? address.country.toUpperCase() : ''}` : '')
  }, {
    field: 'nature juridique',
    type: 'Enum',
    enums: ['natural_person', 'legal_entity'],
    get: contract => models.roles.findAll({
      where: {
        name: 'representative',
        resource_type: 'Contract',
        resource_id: contract.id,
      },
      include: [{
        model: models.identities,
        as: 'identitiesThroughIdentitiesRoles',
        required: true,
      }]
    }).then(roles => roles[0] && roles[0].identitiesThroughIdentitiesRoles[0] ? roles[0].identitiesThroughIdentitiesRoles[0].legalKind : null)
  }, {
    field: 'comptes',
    type: ['String'],
    reference: 'contracts.id'
  }, {
    field: 'appels de charges dematerialisees',
    type: 'Boolean',
    get: contract => models.convocationSettings.findOne({
      where: { contract_id: contract.id },
      order: [['created_at', 'DESC']],
    }).then(convocationSetting => convocationSetting ? convocationSetting.acceptDematerializedMail : null)
  }, {
    field: 'convocation ag',
    type: 'Enum',
    enums: ['dematerialized_only', 'paper_except_annexes', 'paper_only'],
    get: contract => models.convocationSettings.findOne({
      where: { contract_id: contract.id },
      order: [['created_at', 'DESC']],
    }).then(convocationSetting => convocationSetting && convocationSetting.auxilliaryDocumentsShipment ? convocationSetting.auxilliaryDocumentsShipment : null)
  }, {
    field: 'statut du mandat Yousign',
    type: 'Enum',
    enums: ['unsigned', 'waiting', 'init', 'signed_complete'],
    get: contract => models.convocationSettings.findOne({
      where: { contract_id: contract.id },
      order: [['created_at', 'DESC']],
    }).then(convocationSetting => convocationSetting && convocationSetting.state ? convocationSetting.state : null)
  }, {
    field: 'derniere modification',
    type: 'Date',
    get: contract => models.convocationSettings.findOne({
      where: { contract_id: contract.id },
      order: [['created_at', 'DESC']],
    }).then(convocationSetting => convocationSetting ? convocationSetting.updatedAt : null)
  }, {
    field: 'Envoi de la convoc AG au mandataire',
    type: 'Boolean',
    get: contract => models.convocationSettings.findOne({
      where: { contract_id: contract.id },
      order: [['created_at', 'DESC']],
    }).then(convocationSetting => convocationSetting ? convocationSetting.sendConvocationToRepresentative : null)
  }, {
    field: 'Envoi des charges au mandataire',
    type: 'Boolean',
    get: contract => models.convocationSettings.findOne({
      where: { contract_id: contract.id },
      order: [['created_at', 'DESC']],
    }).then(convocationSetting => convocationSetting ? convocationSetting.sendExpenseReportToRepresentative : null)
  }, {
    field: 'Mode de paiement',
    type: 'Enum',
    enums: ['auto_usual', 'auto', 'manual'],
    get: contract => models.moneyOrders.findOne({
      where: { contract_id: contract.id }
    }).then(moneyOrder => moneyOrder ? moneyOrder.paymentType : null)
  }, {
    field: 'Mandat de prelevement recu',
    type: 'Boolean',
    get: contract => models.moneyOrders.findOne({
      where: { contract_id: contract.id }
    }).then(moneyOrder => moneyOrder ? moneyOrder.yousignState : null)
  }, {
    field: 'role',
    type: 'String',
    get: contract => axios.get(`${API_URL}/forest_admin/places/${contract.placeIdKey}/top_place_role/${contract.identityIdKey}`, {
      headers: {
        'Authorization': `Bearer ${process.env.ACCESS_TOKEN}`,
        'X-CURRENT-USER-EMAIL': process.env.SERVICE_EMAIL
      },
    }).then(res => res.data.display_place_role)
  }, {
    field: 'solde',
    type: 'Number',
    get: contract => axios.get(`${API_URL}/forest_admin/places/${contract.placeIdKey}/sage_contract_balance`, {
      headers: {
        'Authorization': `Bearer ${process.env.ACCESS_TOKEN}`,
        'X-CURRENT-USER-EMAIL': process.env.SERVICE_EMAIL
      },
      params: {
        contract_id: contract.id
      }
    }).then(res => res.data.solde_actuel
    ).catch(() => null)
  }, {
    field: 'ecritures-comptables',
    type: ['String'],
    reference: 'contract-accounting-entries.id'
  }, {
    field: 'emails',
    type: ['String'],
    reference: 'contractEmails.id'
  }, {
    field: 'tickets',
    type: ['String'],
    reference: 'contractTickets.id'
  }, {
    field: 'niveau de relance',
    type: 'String',
    get: contract => models.alerts.findOne({
      where: {
        contract_id: contract.id,
        treated: false
      },
      order: [['created_at', 'DESC']],
    }).then(alert => alert ? alert.reminderLevel : '')
  }, {
    field: 'date de derniere relance',
    type: 'Dateonly',
    get: contract => models.alerts.findOne({
      where: {
        contract_id: contract.id,
        treated: false
      },
      order: [['created_at', 'DESC']],
    }).then(alert => alert ? alert.reminderDate : null)
  }, {
    field: 'alerte traitee',
    type: 'Boolean',
    get: contract => models.alerts.findOne({
      where: {
        contract_id: contract.id,
        treated: false
      },
      order: [['created_at', 'DESC']],
    }).then(alert => alert ? alert.treated : '')
  }, {
    field: 'commentaire',
    type: 'String',
    get: contract => models.alerts.findOne({
      where: {
        contract_id: contract.id,
        treated: false
      },
      order: [['created_at', 'DESC']],
    }).then(alert => alert ? alert.comment : '')
  }, {
    field: 'gestionnaire',
    type: 'String',
    get: contract => models.roles.findOne({
      where: {
        name: 'coowner_manager',
        resource_type: 'Place',
        resource_id: contract.placeIdKey
      },
      include: [{
        model: models.identities,
        as: 'identitiesThroughIdentitiesRoles'
      }]
    }).then(role => `${role ? role.identitiesThroughIdentitiesRoles[0].firstName : ''} ${role ? role.identitiesThroughIdentitiesRoles[0].lastName.toUpperCase() : ''}`),
    search: async (query, search) => {
      const { QueryTypes, Op } = models.objectMapping;
      const split = search.split(' ');

      // NOTICE: complexe raw query to gather all ids from the places collection that match your business needs
      const contractsIds = await models.connections.default.query(`
      SELECT contracts.id FROM contracts
      INNER JOIN places ON places.id = contracts.place_id
      INNER JOIN roles ON places.id = roles.resource_id
      INNER JOIN identities_roles ON identities_roles.role_id = roles.id
      INNER JOIN identities ON identities.id = identities_roles.identity_id
      WHERE roles.name = 'coowner_manager'
      AND roles.resource_type = 'Place'
      AND (identities.first_name ILIKE '%${split[0]}%'
      OR identities.last_name ILIKE '%${split[0]}%'
      OR identities.last_name ILIKE '%${split[1]}%'
      OR identities.last_name ILIKE '%${split[1]}%')`,
        { type: QueryTypes.SELECT });

      // NOTICE: fill the where condition with a simple `id in (ArrayOfIds)` to match ids returned by the complexe query making the polymorphic joins
      const searchCondition = { id: { [Op.in]: contractsIds.map(a => a.id) } };
      query.where[Op.and][0][Op.or].push(searchCondition);
    }
  }, {
    field: 'assistant copro',
    type: 'String',
    get: contract => models.roles.findOne({
      where: {
        name: 'coowner_assistant',
        resource_type: 'Place',
        resource_id: contract.placeIdKey
      },
      include: [{
        model: models.identities,
        as: 'identitiesThroughIdentitiesRoles'
      }]
    }).then(role => `${role ? role.identitiesThroughIdentitiesRoles[0].firstName : ''} ${role ? role.identitiesThroughIdentitiesRoles[0].lastName.toUpperCase() : ''}`),
    search: async (query, search) => {
      const { QueryTypes, Op } = models.objectMapping;
      const split = search.split(' ');

      // NOTICE: complexe raw query to gather all ids from the places collection that match your business needs
      const contractsIds = await models.connections.default.query(`
      SELECT contracts.id FROM contracts
      INNER JOIN places ON places.id = contracts.place_id
      INNER JOIN roles ON places.id = roles.resource_id
      INNER JOIN identities_roles ON identities_roles.role_id = roles.id
      INNER JOIN identities ON identities.id = identities_roles.identity_id
      WHERE roles.name = 'coowner_assistant'
      AND roles.resource_type = 'Place'
      AND (identities.first_name ILIKE '%${split[0]}%'
      OR identities.last_name ILIKE '%${split[0]}%'
      OR identities.last_name ILIKE '%${split[1]}%'
      OR identities.last_name ILIKE '%${split[1]}%')`,
        { type: QueryTypes.SELECT });

      // NOTICE: fill the where condition with a simple `id in (ArrayOfIds)` to match ids returned by the complexe query making the polymorphic joins
      const searchCondition = { id: { [Op.in]: contractsIds.map(a => a.id) } };
      query.where[Op.and][0][Op.or].push(searchCondition);
    }
  }, {
    field: 'cgu acceptees',
    type: 'String',
    get: contract => models.identities.findOne({
      where: { id: contract.identityIdKey },
      include: [{
        model: models.users,
        as: 'user'
      }]
    }).then(identity => identity && identity.user && identity.user.termVersionSyndicone ? identity.user.termVersionSyndicone : '')
  }],
  segments: [],
});

I edit a record as so :

router.put('/contracts/:id', permissionMiddlewareCreator.update(), (request, response, next) => {
  // Learn what this route does here: https://docs.forestadmin.com/documentation/v/v6/reference-guide/routes/default-routes#update-a-record
  const recordSerializer = new RecordSerializer({ name: 'contracts' });
  const contract_id = parseInt(request.params.id, 10);
  const data = request.body.data.attributes;
  let body = { contract: {} };

  if (data.occupant != null || typeof data.occupant !== 'undefined') { body.contract.occupant = data.occupant; }
  if (data.reminderGroup) { body.contract.reminder_group = data.reminderGroup; }

  axios.put(`${API_URL}/forest_admin/contracts/${contract_id}`, body, {
    headers: {
      'Authorization': `Bearer ${process.env.ACCESS_TOKEN}`,
      'X-CURRENT-USER-EMAIL': request.user.email
    }
  }).then(async res => {
    console.log(res.data);
    console.log(await recordSerializer.serialize(res.data));
    response.send(await recordSerializer.serialize(res.data));
  }).catch(err => {
    response.status(400).send(err.response.data.error.message);
  });
});

The strangest thing happens :

Here is some data returned by the API :

{
  id: 812,
  reminder_group: 2,
  place_id: 185,
  customer_reference_number: '9213853',
  last_reminder_group_update: '2022-01-05',
  display_name: '9213853 ()',
  amount: 0,
  created_at: '2022-01-03T18:03:51.552+01:00',
  updated_at: '2022-01-05T16:29:33.064+01:00',
  parcel_sergic_id: null,
  sergic_update_time: null,
  customer_key: null,
  invited_count: 0,
  report_account_entry_amount: 0,
  report_account_entry_operation_date: null,
  deleted_at: null,
  identity_id: 870,
  coowner_amount: 0,
  pending_amount: 0,
  due_date: null,
  occupant: null,
  start_date: null,
  end_date: null,
  integration_completed: null
}

and once this record is serialized, it looks as so:

{
  data: {
    type: 'contracts',
    id: '812',
    attributes: {
      'reference immeuble': '',
      'nom complet': '',
      'ID client': '',
      civilite: '',
      nom: '',
      prenom: '',
      telephone: '',
      'telephone domicile': '',
      email: '',
      lots: '',
      Mandataire: undefined,
      'Telephone mandataire': undefined,
      'Email mandataire': undefined,
      'Adresse mandataire': undefined,
      'nature juridique': null,
      'appels de charges dematerialisees': false,
      'convocation ag': 'paper_only',
      'statut du mandat Yousign': 'unsigned',
      'derniere modification': 2022-01-03T16:03:51.605Z,
      'Envoi de la convoc AG au mandataire': false,
      'Envoi des charges au mandataire': false,
      'Mode de paiement': null,
      'Mandat de prelevement recu': null,
      solde: null,
      'niveau de relance': '',
      'date de derniere relance': null,
      'alerte traitee': '',
      commentaire: '',
      id: 812,
      amount: 0,
      occupant: null
    },
    relationships: {
      comptes: [Object],
      'ecritures-comptables': [Object],
      emails: [Object],
      tickets: [Object],
      parcelsThroughParcelContracts: [Object],
      contractParcel: [Object],
      managementFees: [Object],
      contractDocuments: [Object],
      accountEntries: [Object],
      alerts: [Object],
      publicWorkDetails: [Object],
      convocationSettings: [Object],
      preEtatDateRequests: [Object],
      contractRepartitionKeys: [Object],
      waterReadings: [Object],
      roles: [Object]
    }
  }
}

As you can see, the fields ‘nom’, ‘prenom’, ‘telephone’… aren’t part of the record but smart fields.

These smart fields are not re-computed and disappear from the screen after Edit.

Before:

After:

I have to manually refresh my page to have my smart fields re-computed.

Expected behavior

My smart fields should still be visible after Edit

Context

Please provide in this mandatory section, the relevant information about your configuration:

  • Project name: Sergic
  • Team name: Gestion
  • Environment name: Dev
  • Agent type & version:
    “forest-express”: “^7.9.4”,
    “forest-express-sequelize”: “^7.12.3”,
  • Recent changes made on your end if any: none

I did find a workaround :

router.put('/contracts/:id', permissionMiddlewareCreator.update(), (request, response, next) => {
  // Learn what this route does here: https://docs.forestadmin.com/documentation/v/v6/reference-guide/routes/default-routes#update-a-record
  const recordSerializer = new RecordSerializer({ name: 'contracts' });
  const contract_id = parseInt(request.params.id, 10);
  const data = request.body.data.attributes;
  let body = { contract: {} };

  if (data.occupant != null || typeof data.occupant !== 'undefined') { body.contract.occupant = data.occupant; }
  if (data.reminderGroup) { body.contract.reminder_group = data.reminderGroup; }

  axios.put(`${API_URL}/forest_admin/contracts/${contract_id}`, body, {
    headers: {
      'Authorization': `Bearer ${process.env.ACCESS_TOKEN}`,
      'X-CURRENT-USER-EMAIL': request.user.email
    }
  }).then(() => models.contracts.findByPk(contract_id)
  ).then(async record => {
    response.send(await recordSerializer.serialize(record));
  }).catch(err => {
    response.status(400).send(err.response.data.error.message);
  });
});

but i’m curious about why it did happen in the first place.

I did not check the implementation details yet, but as you can see in the smart field definition, it relies a lot on the fact that the input object is coming from sequelize.
So it appears normal that these values are not computed if you are returning a response from your api instead of a sequelize object.