diff --git a/addons/hr/static/src/models/employee/employee.js b/addons/hr/static/src/models/employee/employee.js index 6d3c12cb14b..ef749203364 100644 --- a/addons/hr/static/src/models/employee/employee.js +++ b/addons/hr/static/src/models/employee/employee.js @@ -178,7 +178,9 @@ function factory(dependencies) { /** * Unique identifier for this employee. */ - id: attr(), + id: attr({ + required: true, + }), /** * Partner related to this employee. */ diff --git a/addons/mail/static/src/model/model_field.js b/addons/mail/static/src/model/model_field.js index 55617d5eaa1..940e9efd6cd 100644 --- a/addons/mail/static/src/model/model_field.js +++ b/addons/mail/static/src/model/model_field.js @@ -27,6 +27,7 @@ class ModelField { isCausal = false, related, relationType, + required = false, to, } = {}) { const id = _.uniqueId('field_'); @@ -151,6 +152,13 @@ class ModelField { * and 'many2many'. */ this.relationType = relationType; + /** + * Determine whether the field is required or not. + * + * Empty value is systematically undefined. + * null or empty string are NOT considered empty value, meaning these values meet the requirement. + */ + this.required = required; /** * This prop only makes sense in a relational field. Determine which * model name this relation refers to. diff --git a/addons/mail/static/src/model/model_manager.js b/addons/mail/static/src/model/model_manager.js index 73c3c313ed4..fe53840d85c 100644 --- a/addons/mail/static/src/model/model_manager.js +++ b/addons/mail/static/src/model/model_manager.js @@ -307,6 +307,7 @@ class ModelManager { 'dependencies', 'fieldType', 'related', + 'required', ].includes(key) ); if (invalidKeys.length > 0) { @@ -324,6 +325,7 @@ class ModelManager { 'isCausal', 'related', 'relationType', + 'required', 'to', ].includes(key) ); @@ -336,6 +338,9 @@ class ModelManager { if (field.isCausal && !(['one2many', 'one2one'].includes(field.relationType))) { throw new Error(`Relational field "${Model.modelName}/${fieldName}" has "isCausal" true with a relation of type "${field.relationType}" but "isCausal" is only supported for "one2many" and "one2one".`); } + if (field.required && !(['one2one', 'many2one'].includes(field.relationType))) { + throw new Error(`Relational field "${Model.modelName}/${fieldName}" has "required" true with a relation of type "${field.relationType}" but "required" is only supported for "one2one" and "many2one".`); + } } // 3. Computed field. if (field.compute && !(typeof field.compute === 'string')) { @@ -708,6 +713,16 @@ class ModelManager { for (const record of this._updatedRecords) { record.__state++; } + + // handle required field. + for (const record of this._updatedRecords) { + for (const required of record.constructor.__requiredFieldsList) { + if (record[required.fieldName] === undefined) { + throw Error(`Field ${required.fieldName} of ${record.localId} is required.`); + } + } + } + this._updatedRecords.clear(); // Trigger at most one useStore call per update cycle @@ -977,6 +992,9 @@ class ModelManager { Model.__fieldMap = Model.__combinedFields; // List of all fields, for iterating. Model.__fieldList = Object.values(Model.__fieldMap); + Model.__requiredFieldsList = Model.__fieldList.filter( + field => field.required + ); // Add field accessors. for (const field of Model.__fieldList) { Object.defineProperty(Model.prototype, field.fieldName, { diff --git a/addons/mail/static/src/models/activity/activity.js b/addons/mail/static/src/models/activity/activity.js index b2d8ef3cb13..3508636db16 100644 --- a/addons/mail/static/src/models/activity/activity.js +++ b/addons/mail/static/src/models/activity/activity.js @@ -283,7 +283,9 @@ function factory(dependencies) { default: false, }), icon: attr(), - id: attr(), + id: attr({ + required: true, + }), isCurrentPartnerAssignee: attr({ compute: '_computeIsCurrentPartnerAssignee', default: false, diff --git a/addons/mail/static/src/models/activity_type/activity_type.js b/addons/mail/static/src/models/activity_type/activity_type.js index f8a621a8569..6c12c691221 100644 --- a/addons/mail/static/src/models/activity_type/activity_type.js +++ b/addons/mail/static/src/models/activity_type/activity_type.js @@ -26,7 +26,9 @@ function factory(dependencies) { inverse: 'type', }), displayName: attr(), - id: attr(), + id: attr({ + required: true, + }), }; ActivityType.modelName = 'mail.activity_type'; diff --git a/addons/mail/static/src/models/attachment/attachment.js b/addons/mail/static/src/models/attachment/attachment.js index 7399dee7b33..03edf5abcab 100644 --- a/addons/mail/static/src/models/attachment/attachment.js +++ b/addons/mail/static/src/models/attachment/attachment.js @@ -321,7 +321,9 @@ function factory(dependencies) { 'url', ], }), - id: attr(), + id: attr({ + required: true, + }), isLinkedToComposer: attr({ compute: '_computeIsLinkedToComposer', dependencies: ['composers'], diff --git a/addons/mail/static/src/models/country/country.js b/addons/mail/static/src/models/country/country.js index fb3617cf783..8dd48aac442 100644 --- a/addons/mail/static/src/models/country/country.js +++ b/addons/mail/static/src/models/country/country.js @@ -41,7 +41,9 @@ function factory(dependencies) { 'code', ], }), - id: attr(), + id: attr({ + required: true, + }), name: attr(), }; diff --git a/addons/mail/static/src/models/follower/follower.js b/addons/mail/static/src/models/follower/follower.js index 183c20e4684..591e6a2ee57 100644 --- a/addons/mail/static/src/models/follower/follower.js +++ b/addons/mail/static/src/models/follower/follower.js @@ -239,7 +239,9 @@ function factory(dependencies) { followedThread: many2one('mail.thread', { inverse: 'followers', }), - id: attr(), + id: attr({ + required: true, + }), isActive: attr({ default: true, }), diff --git a/addons/mail/static/src/models/follower_subtype/follower_subtype.js b/addons/mail/static/src/models/follower_subtype/follower_subtype.js index 68c16fce189..2b2a571d7e4 100644 --- a/addons/mail/static/src/models/follower_subtype/follower_subtype.js +++ b/addons/mail/static/src/models/follower_subtype/follower_subtype.js @@ -57,7 +57,9 @@ function factory(dependencies) { } FollowerSubtype.fields = { - id: attr(), + id: attr({ + required: true, + }), isDefault: attr({ default: false, }), diff --git a/addons/mail/static/src/models/mail_template/mail_template.js b/addons/mail/static/src/models/mail_template/mail_template.js index 3144c3147c3..ba75bbdcc0b 100644 --- a/addons/mail/static/src/models/mail_template/mail_template.js +++ b/addons/mail/static/src/models/mail_template/mail_template.js @@ -69,7 +69,9 @@ function factory(dependencies) { activities: many2many('mail.activity', { inverse: 'mailTemplates', }), - id: attr(), + id: attr({ + required: true, + }), name: attr(), }; diff --git a/addons/mail/static/src/models/message/message.js b/addons/mail/static/src/models/message/message.js index c3318aa8906..fe598f168e9 100644 --- a/addons/mail/static/src/models/message/message.js +++ b/addons/mail/static/src/models/message/message.js @@ -553,7 +553,9 @@ function factory(dependencies) { default: false, dependencies: ['isModeratedByCurrentPartner'], }), - id: attr(), + id: attr({ + required: true, + }), isCurrentPartnerAuthor: attr({ compute: '_computeIsCurrentPartnerAuthor', default: false, diff --git a/addons/mail/static/src/models/message_seen_indicator/message_seen_indicator.js b/addons/mail/static/src/models/message_seen_indicator/message_seen_indicator.js index dd1848aaf7b..e44bd05b8b5 100644 --- a/addons/mail/static/src/models/message_seen_indicator/message_seen_indicator.js +++ b/addons/mail/static/src/models/message_seen_indicator/message_seen_indicator.js @@ -262,7 +262,9 @@ function factory(dependencies) { * (required fields) should improve and let us just use the relational * fields. */ - channelId: attr(), + channelId: attr({ + required: true, + }), hasEveryoneFetched: attr({ compute: '_computeHasEveryoneFetched', default: false, @@ -321,7 +323,9 @@ function factory(dependencies) { * (required fields) should improve and let us just use the relational * fields. */ - messageId: attr(), + messageId: attr({ + required: true, + }), partnersThatHaveFetched: many2many('mail.partner', { compute: '_computePartnersThatHaveFetched', dependencies: ['messageAuthor', 'messageId', 'threadPartnerSeenInfos'], diff --git a/addons/mail/static/src/models/notification/notification.js b/addons/mail/static/src/models/notification/notification.js index 047faee95b8..f74dd64339a 100644 --- a/addons/mail/static/src/models/notification/notification.js +++ b/addons/mail/static/src/models/notification/notification.js @@ -61,7 +61,9 @@ function factory(dependencies) { Notification.fields = { failure_type: attr(), - id: attr(), + id: attr({ + required: true, + }), message: many2one('mail.message', { inverse: 'notifications', }), diff --git a/addons/mail/static/src/models/notification_group/notification_group.js b/addons/mail/static/src/models/notification_group/notification_group.js index 89111a4ed74..4da0dccbe8f 100644 --- a/addons/mail/static/src/models/notification_group/notification_group.js +++ b/addons/mail/static/src/models/notification_group/notification_group.js @@ -98,7 +98,9 @@ function factory(dependencies) { NotificationGroup.fields = { date: attr(), - id: attr(), + id: attr({ + required: true, + }), notification_type: attr(), notifications: one2many('mail.notification'), res_id: attr(), diff --git a/addons/mail/static/src/models/partner/partner.js b/addons/mail/static/src/models/partner/partner.js index e9a2135f606..80859d74244 100644 --- a/addons/mail/static/src/models/partner/partner.js +++ b/addons/mail/static/src/models/partner/partner.js @@ -311,7 +311,9 @@ function factory(dependencies) { hasCheckedUser: attr({ default: false, }), - id: attr(), + id: attr({ + required: true, + }), im_status: attr(), memberThreads: many2many('mail.thread', { inverse: 'members', diff --git a/addons/mail/static/src/models/thread/thread.js b/addons/mail/static/src/models/thread/thread.js index 176a8afb501..36d50a7c4cf 100644 --- a/addons/mail/static/src/models/thread/thread.js +++ b/addons/mail/static/src/models/thread/thread.js @@ -1653,7 +1653,9 @@ function factory(dependencies) { 'model', ], }), - id: attr(), + id: attr({ + required: true, + }), /** * States whether this thread is a `mail.channel` qualified as chat. * @@ -1834,7 +1836,9 @@ function factory(dependencies) { messagingCurrentPartner: many2one('mail.partner', { related: 'messaging.currentPartner', }), - model: attr(), + model: attr({ + required: true, + }), model_name: attr(), moderation: attr({ default: false, diff --git a/addons/mail/static/src/models/thread_partner_seen_info/thread_partner_seen_info.js b/addons/mail/static/src/models/thread_partner_seen_info/thread_partner_seen_info.js index 8fd3b95af45..c4134d6737a 100644 --- a/addons/mail/static/src/models/thread_partner_seen_info/thread_partner_seen_info.js +++ b/addons/mail/static/src/models/thread_partner_seen_info/thread_partner_seen_info.js @@ -59,7 +59,9 @@ function factory(dependencies) { * (required fields) should improve and let us just use the relational * fields. */ - channelId: attr(), + channelId: attr({ + required: true, + }), lastFetchedMessage: many2one('mail.message'), lastSeenMessage: many2one('mail.message'), /** @@ -87,7 +89,9 @@ function factory(dependencies) { * (required fields) should improve and let us just use the relational * fields. */ - partnerId: attr(), + partnerId: attr({ + required: true, + }), /** * Thread (channel) that this seen info is related to. * diff --git a/addons/mail/static/src/models/user/user.js b/addons/mail/static/src/models/user/user.js index 172b8c5b780..2bf958bdbd8 100644 --- a/addons/mail/static/src/models/user/user.js +++ b/addons/mail/static/src/models/user/user.js @@ -203,7 +203,9 @@ function factory(dependencies) { } User.fields = { - id: attr(), + id: attr({ + required: true, + }), display_name: attr({ compute: '_computeDisplayName', dependencies: [