diff --git a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/savedbulkloadprofiles/SavedBulkLoadProfile.java b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/savedbulkloadprofiles/SavedBulkLoadProfile.java
new file mode 100644
index 00000000..65f07c77
--- /dev/null
+++ b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/savedbulkloadprofiles/SavedBulkLoadProfile.java
@@ -0,0 +1,285 @@
+/*
+ * QQQ - Low-code Application Framework for Engineers.
+ * Copyright (C) 2021-2024. Kingsrook, LLC
+ * 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
+ * contact@kingsrook.com
+ * https://github.com/Kingsrook/
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+
+package com.kingsrook.qqq.backend.core.model.savedbulkloadprofiles;
+
+
+import java.time.Instant;
+import com.kingsrook.qqq.backend.core.exceptions.QException;
+import com.kingsrook.qqq.backend.core.model.data.QField;
+import com.kingsrook.qqq.backend.core.model.data.QRecord;
+import com.kingsrook.qqq.backend.core.model.data.QRecordEntity;
+import com.kingsrook.qqq.backend.core.model.metadata.fields.DynamicDefaultValueBehavior;
+import com.kingsrook.qqq.backend.core.model.metadata.fields.ValueTooLongBehavior;
+import com.kingsrook.qqq.backend.core.model.metadata.tables.TablesPossibleValueSourceMetaDataProvider;
+
+
+/*******************************************************************************
+ ** Entity bean for the savedBulkLoadProfile table
+ *******************************************************************************/
+public class SavedBulkLoadProfile extends QRecordEntity
+{
+ public static final String TABLE_NAME = "savedBulkLoadProfile";
+
+ @QField(isEditable = false)
+ private Integer id;
+
+ @QField(isEditable = false)
+ private Instant createDate;
+
+ @QField(isEditable = false)
+ private Instant modifyDate;
+
+ @QField(isRequired = true, maxLength = 250, valueTooLongBehavior = ValueTooLongBehavior.TRUNCATE_ELLIPSIS, label = "Profile Name")
+ private String label;
+
+ @QField(possibleValueSourceName = TablesPossibleValueSourceMetaDataProvider.NAME, maxLength = 250, valueTooLongBehavior = ValueTooLongBehavior.ERROR, label = "Table", isRequired = true)
+ private String tableName;
+
+ @QField(maxLength = 250, valueTooLongBehavior = ValueTooLongBehavior.ERROR, dynamicDefaultValueBehavior = DynamicDefaultValueBehavior.USER_ID, label = "Owner")
+ private String userId;
+
+ @QField(label = "Mapping JSON")
+ private String mappingJson;
+
+
+
+ /*******************************************************************************
+ ** Constructor
+ **
+ *******************************************************************************/
+ public SavedBulkLoadProfile()
+ {
+ }
+
+
+
+ /*******************************************************************************
+ ** Constructor
+ **
+ *******************************************************************************/
+ public SavedBulkLoadProfile(QRecord qRecord) throws QException
+ {
+ populateFromQRecord(qRecord);
+ }
+
+
+
+ /*******************************************************************************
+ ** Getter for id
+ **
+ *******************************************************************************/
+ public Integer getId()
+ {
+ return id;
+ }
+
+
+
+ /*******************************************************************************
+ ** Setter for id
+ **
+ *******************************************************************************/
+ public void setId(Integer id)
+ {
+ this.id = id;
+ }
+
+
+
+ /*******************************************************************************
+ ** Getter for createDate
+ **
+ *******************************************************************************/
+ public Instant getCreateDate()
+ {
+ return createDate;
+ }
+
+
+
+ /*******************************************************************************
+ ** Setter for createDate
+ **
+ *******************************************************************************/
+ public void setCreateDate(Instant createDate)
+ {
+ this.createDate = createDate;
+ }
+
+
+
+ /*******************************************************************************
+ ** Getter for modifyDate
+ **
+ *******************************************************************************/
+ public Instant getModifyDate()
+ {
+ return modifyDate;
+ }
+
+
+
+ /*******************************************************************************
+ ** Setter for modifyDate
+ **
+ *******************************************************************************/
+ public void setModifyDate(Instant modifyDate)
+ {
+ this.modifyDate = modifyDate;
+ }
+
+
+
+ /*******************************************************************************
+ ** Getter for label
+ **
+ *******************************************************************************/
+ public String getLabel()
+ {
+ return label;
+ }
+
+
+
+ /*******************************************************************************
+ ** Setter for label
+ **
+ *******************************************************************************/
+ public void setLabel(String label)
+ {
+ this.label = label;
+ }
+
+
+
+ /*******************************************************************************
+ ** Fluent setter for label
+ **
+ *******************************************************************************/
+ public SavedBulkLoadProfile withLabel(String label)
+ {
+ this.label = label;
+ return (this);
+ }
+
+
+
+ /*******************************************************************************
+ ** Getter for tableName
+ **
+ *******************************************************************************/
+ public String getTableName()
+ {
+ return tableName;
+ }
+
+
+
+ /*******************************************************************************
+ ** Setter for tableName
+ **
+ *******************************************************************************/
+ public void setTableName(String tableName)
+ {
+ this.tableName = tableName;
+ }
+
+
+
+ /*******************************************************************************
+ ** Fluent setter for tableName
+ **
+ *******************************************************************************/
+ public SavedBulkLoadProfile withTableName(String tableName)
+ {
+ this.tableName = tableName;
+ return (this);
+ }
+
+
+
+ /*******************************************************************************
+ ** Getter for userId
+ **
+ *******************************************************************************/
+ public String getUserId()
+ {
+ return userId;
+ }
+
+
+
+ /*******************************************************************************
+ ** Setter for userId
+ **
+ *******************************************************************************/
+ public void setUserId(String userId)
+ {
+ this.userId = userId;
+ }
+
+
+
+ /*******************************************************************************
+ ** Fluent setter for userId
+ **
+ *******************************************************************************/
+ public SavedBulkLoadProfile withUserId(String userId)
+ {
+ this.userId = userId;
+ return (this);
+ }
+
+
+
+
+ /*******************************************************************************
+ ** Getter for mappingJson
+ *******************************************************************************/
+ public String getMappingJson()
+ {
+ return (this.mappingJson);
+ }
+
+
+
+ /*******************************************************************************
+ ** Setter for mappingJson
+ *******************************************************************************/
+ public void setMappingJson(String mappingJson)
+ {
+ this.mappingJson = mappingJson;
+ }
+
+
+
+ /*******************************************************************************
+ ** Fluent setter for mappingJson
+ *******************************************************************************/
+ public SavedBulkLoadProfile withMappingJson(String mappingJson)
+ {
+ this.mappingJson = mappingJson;
+ return (this);
+ }
+
+
+}
diff --git a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/savedbulkloadprofiles/SavedBulkLoadProfileMetaDataProvider.java b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/savedbulkloadprofiles/SavedBulkLoadProfileMetaDataProvider.java
new file mode 100644
index 00000000..d8d376e1
--- /dev/null
+++ b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/savedbulkloadprofiles/SavedBulkLoadProfileMetaDataProvider.java
@@ -0,0 +1,159 @@
+/*
+ * QQQ - Low-code Application Framework for Engineers.
+ * Copyright (C) 2021-2024. Kingsrook, LLC
+ * 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
+ * contact@kingsrook.com
+ * https://github.com/Kingsrook/
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+
+package com.kingsrook.qqq.backend.core.model.savedbulkloadprofiles;
+
+
+import java.util.List;
+import java.util.function.Consumer;
+import com.kingsrook.qqq.backend.core.exceptions.QException;
+import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
+import com.kingsrook.qqq.backend.core.model.metadata.audits.AuditLevel;
+import com.kingsrook.qqq.backend.core.model.metadata.audits.QAuditRules;
+import com.kingsrook.qqq.backend.core.model.metadata.joins.JoinOn;
+import com.kingsrook.qqq.backend.core.model.metadata.joins.JoinType;
+import com.kingsrook.qqq.backend.core.model.metadata.joins.QJoinMetaData;
+import com.kingsrook.qqq.backend.core.model.metadata.layout.QIcon;
+import com.kingsrook.qqq.backend.core.model.metadata.possiblevalues.QPossibleValueSource;
+import com.kingsrook.qqq.backend.core.model.metadata.sharing.ShareScopePossibleValueMetaDataProducer;
+import com.kingsrook.qqq.backend.core.model.metadata.sharing.ShareableAudienceType;
+import com.kingsrook.qqq.backend.core.model.metadata.sharing.ShareableTableMetaData;
+import com.kingsrook.qqq.backend.core.model.metadata.tables.QFieldSection;
+import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData;
+import com.kingsrook.qqq.backend.core.model.metadata.tables.Tier;
+import com.kingsrook.qqq.backend.core.model.metadata.tables.UniqueKey;
+
+
+/*******************************************************************************
+ **
+ *******************************************************************************/
+public class SavedBulkLoadProfileMetaDataProvider
+{
+ public static final String SHARED_SAVED_BULK_LOAD_PROFILE_JOIN_SAVED_BULK_LOAD_PROFILE = "sharedSavedBulkLoadProfileJoinSavedBulkLoadProfile";
+
+
+
+ /*******************************************************************************
+ **
+ *******************************************************************************/
+ public void defineAll(QInstance instance, String recordTablesBackendName, Consumer backendDetailEnricher) throws QException
+ {
+ instance.addTable(defineSavedBulkLoadProfileTable(recordTablesBackendName, backendDetailEnricher));
+ instance.addPossibleValueSource(QPossibleValueSource.newForTable(SavedBulkLoadProfile.TABLE_NAME));
+
+ /////////////////////////////////////
+ // todo - param to enable sharing? //
+ /////////////////////////////////////
+ instance.addTable(defineSharedSavedBulkLoadProfileTable(recordTablesBackendName, backendDetailEnricher));
+ instance.addJoin(defineSharedSavedBulkLoadProfileJoinSavedBulkLoadProfile());
+ if(instance.getPossibleValueSource(ShareScopePossibleValueMetaDataProducer.NAME) == null)
+ {
+ instance.addPossibleValueSource(new ShareScopePossibleValueMetaDataProducer().produce(new QInstance()));
+ }
+ }
+
+
+
+ /*******************************************************************************
+ **
+ *******************************************************************************/
+ private QJoinMetaData defineSharedSavedBulkLoadProfileJoinSavedBulkLoadProfile()
+ {
+ return (new QJoinMetaData()
+ .withName(SHARED_SAVED_BULK_LOAD_PROFILE_JOIN_SAVED_BULK_LOAD_PROFILE)
+ .withLeftTable(SharedSavedBulkLoadProfile.TABLE_NAME)
+ .withRightTable(SavedBulkLoadProfile.TABLE_NAME)
+ .withType(JoinType.MANY_TO_ONE)
+ .withJoinOn(new JoinOn("savedBulkLoadProfileId", "id")));
+ }
+
+
+
+ /*******************************************************************************
+ **
+ *******************************************************************************/
+ public QTableMetaData defineSavedBulkLoadProfileTable(String backendName, Consumer backendDetailEnricher) throws QException
+ {
+ QTableMetaData table = new QTableMetaData()
+ .withName(SavedBulkLoadProfile.TABLE_NAME)
+ .withLabel("Bulk Load Profile")
+ .withIcon(new QIcon().withName("drive_folder_upload"))
+ .withRecordLabelFormat("%s")
+ .withRecordLabelFields("label")
+ .withBackendName(backendName)
+ .withPrimaryKeyField("id")
+ .withFieldsFromEntity(SavedBulkLoadProfile.class)
+ .withAuditRules(new QAuditRules().withAuditLevel(AuditLevel.FIELD))
+ .withSection(new QFieldSection("identity", new QIcon().withName("badge"), Tier.T1, List.of("id", "label", "tableName")))
+ .withSection(new QFieldSection("data", new QIcon().withName("text_snippet"), Tier.T2, List.of("mappingJson")).withIsHidden(true))
+ .withSection(new QFieldSection("hidden", new QIcon().withName("text_snippet"), Tier.T2, List.of("userId")).withIsHidden(true))
+ .withSection(new QFieldSection("dates", new QIcon().withName("calendar_month"), Tier.T3, List.of("createDate", "modifyDate")));
+
+ // todo - want one of these?
+ // table.getField("queryFilterJson").withBehavior(SavedReportJsonFieldDisplayValueFormatter.getInstance());
+
+ table.withShareableTableMetaData(new ShareableTableMetaData()
+ .withSharedRecordTableName(SharedSavedBulkLoadProfile.TABLE_NAME)
+ .withAssetIdFieldName("savedBulkLoadProfileId")
+ .withScopeFieldName("scope")
+ .withThisTableOwnerIdFieldName("userId")
+ .withAudienceType(new ShareableAudienceType().withName("user").withFieldName("userId")));
+
+ if(backendDetailEnricher != null)
+ {
+ backendDetailEnricher.accept(table);
+ }
+
+ return (table);
+ }
+
+
+
+ /*******************************************************************************
+ **
+ *******************************************************************************/
+ public QTableMetaData defineSharedSavedBulkLoadProfileTable(String backendName, Consumer backendDetailEnricher) throws QException
+ {
+ QTableMetaData table = new QTableMetaData()
+ .withName(SharedSavedBulkLoadProfile.TABLE_NAME)
+ .withLabel("Shared Bulk Load Profile")
+ .withIcon(new QIcon().withName("share"))
+ .withRecordLabelFormat("%s")
+ .withRecordLabelFields("savedBulkLoadProfileId")
+ .withBackendName(backendName)
+ .withUniqueKey(new UniqueKey("savedBulkLoadProfileId", "userId"))
+ .withPrimaryKeyField("id")
+ .withFieldsFromEntity(SharedSavedBulkLoadProfile.class)
+ // todo - security key
+ .withAuditRules(new QAuditRules().withAuditLevel(AuditLevel.FIELD))
+ .withSection(new QFieldSection("identity", new QIcon().withName("badge"), Tier.T1, List.of("id", "savedBulkLoadProfileId", "userId")))
+ .withSection(new QFieldSection("data", new QIcon().withName("text_snippet"), Tier.T2, List.of("scope")))
+ .withSection(new QFieldSection("dates", new QIcon().withName("calendar_month"), Tier.T3, List.of("createDate", "modifyDate")));
+
+ if(backendDetailEnricher != null)
+ {
+ backendDetailEnricher.accept(table);
+ }
+
+ return (table);
+ }
+
+}
diff --git a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/savedbulkloadprofiles/SharedSavedBulkLoadProfile.java b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/savedbulkloadprofiles/SharedSavedBulkLoadProfile.java
new file mode 100644
index 00000000..a02e5c3b
--- /dev/null
+++ b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/savedbulkloadprofiles/SharedSavedBulkLoadProfile.java
@@ -0,0 +1,268 @@
+/*
+ * QQQ - Low-code Application Framework for Engineers.
+ * Copyright (C) 2021-2024. Kingsrook, LLC
+ * 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
+ * contact@kingsrook.com
+ * https://github.com/Kingsrook/
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+
+package com.kingsrook.qqq.backend.core.model.savedbulkloadprofiles;
+
+
+import java.time.Instant;
+import com.kingsrook.qqq.backend.core.exceptions.QException;
+import com.kingsrook.qqq.backend.core.model.data.QField;
+import com.kingsrook.qqq.backend.core.model.data.QRecord;
+import com.kingsrook.qqq.backend.core.model.data.QRecordEntity;
+import com.kingsrook.qqq.backend.core.model.metadata.sharing.ShareScopePossibleValueMetaDataProducer;
+
+
+/*******************************************************************************
+ ** Entity bean for the shared saved bulk load profile table
+ *******************************************************************************/
+public class SharedSavedBulkLoadProfile extends QRecordEntity
+{
+ public static final String TABLE_NAME = "sharedSavedBulkLoadProfile";
+
+ @QField(isEditable = false)
+ private Integer id;
+
+ @QField(isEditable = false)
+ private Instant createDate;
+
+ @QField(isEditable = false)
+ private Instant modifyDate;
+
+ @QField(possibleValueSourceName = SavedBulkLoadProfile.TABLE_NAME, label = "Bulk Load Profile")
+ private Integer savedBulkLoadProfileId;
+
+ @QField(label = "User")
+ private String userId;
+
+ @QField(possibleValueSourceName = ShareScopePossibleValueMetaDataProducer.NAME)
+ private String scope;
+
+
+
+ /*******************************************************************************
+ ** Constructor
+ **
+ *******************************************************************************/
+ public SharedSavedBulkLoadProfile()
+ {
+ }
+
+
+
+ /*******************************************************************************
+ ** Constructor
+ **
+ *******************************************************************************/
+ public SharedSavedBulkLoadProfile(QRecord qRecord) throws QException
+ {
+ populateFromQRecord(qRecord);
+ }
+
+
+
+
+ /*******************************************************************************
+ ** Getter for id
+ *******************************************************************************/
+ public Integer getId()
+ {
+ return (this.id);
+ }
+
+
+
+ /*******************************************************************************
+ ** Setter for id
+ *******************************************************************************/
+ public void setId(Integer id)
+ {
+ this.id = id;
+ }
+
+
+
+ /*******************************************************************************
+ ** Fluent setter for id
+ *******************************************************************************/
+ public SharedSavedBulkLoadProfile withId(Integer id)
+ {
+ this.id = id;
+ return (this);
+ }
+
+
+
+ /*******************************************************************************
+ ** Getter for createDate
+ *******************************************************************************/
+ public Instant getCreateDate()
+ {
+ return (this.createDate);
+ }
+
+
+
+ /*******************************************************************************
+ ** Setter for createDate
+ *******************************************************************************/
+ public void setCreateDate(Instant createDate)
+ {
+ this.createDate = createDate;
+ }
+
+
+
+ /*******************************************************************************
+ ** Fluent setter for createDate
+ *******************************************************************************/
+ public SharedSavedBulkLoadProfile withCreateDate(Instant createDate)
+ {
+ this.createDate = createDate;
+ return (this);
+ }
+
+
+
+ /*******************************************************************************
+ ** Getter for modifyDate
+ *******************************************************************************/
+ public Instant getModifyDate()
+ {
+ return (this.modifyDate);
+ }
+
+
+
+ /*******************************************************************************
+ ** Setter for modifyDate
+ *******************************************************************************/
+ public void setModifyDate(Instant modifyDate)
+ {
+ this.modifyDate = modifyDate;
+ }
+
+
+
+ /*******************************************************************************
+ ** Fluent setter for modifyDate
+ *******************************************************************************/
+ public SharedSavedBulkLoadProfile withModifyDate(Instant modifyDate)
+ {
+ this.modifyDate = modifyDate;
+ return (this);
+ }
+
+
+
+
+ /*******************************************************************************
+ ** Getter for userId
+ *******************************************************************************/
+ public String getUserId()
+ {
+ return (this.userId);
+ }
+
+
+
+ /*******************************************************************************
+ ** Setter for userId
+ *******************************************************************************/
+ public void setUserId(String userId)
+ {
+ this.userId = userId;
+ }
+
+
+
+ /*******************************************************************************
+ ** Fluent setter for userId
+ *******************************************************************************/
+ public SharedSavedBulkLoadProfile withUserId(String userId)
+ {
+ this.userId = userId;
+ return (this);
+ }
+
+
+
+ /*******************************************************************************
+ ** Getter for scope
+ *******************************************************************************/
+ public String getScope()
+ {
+ return (this.scope);
+ }
+
+
+
+ /*******************************************************************************
+ ** Setter for scope
+ *******************************************************************************/
+ public void setScope(String scope)
+ {
+ this.scope = scope;
+ }
+
+
+
+ /*******************************************************************************
+ ** Fluent setter for scope
+ *******************************************************************************/
+ public SharedSavedBulkLoadProfile withScope(String scope)
+ {
+ this.scope = scope;
+ return (this);
+ }
+
+
+
+ /*******************************************************************************
+ ** Getter for savedBulkLoadProfileId
+ *******************************************************************************/
+ public Integer getSavedBulkLoadProfileId()
+ {
+ return (this.savedBulkLoadProfileId);
+ }
+
+
+
+ /*******************************************************************************
+ ** Setter for savedBulkLoadProfileId
+ *******************************************************************************/
+ public void setSavedBulkLoadProfileId(Integer savedBulkLoadProfileId)
+ {
+ this.savedBulkLoadProfileId = savedBulkLoadProfileId;
+ }
+
+
+
+ /*******************************************************************************
+ ** Fluent setter for savedBulkLoadProfileId
+ *******************************************************************************/
+ public SharedSavedBulkLoadProfile withSavedBulkLoadProfileId(Integer savedBulkLoadProfileId)
+ {
+ this.savedBulkLoadProfileId = savedBulkLoadProfileId;
+ return (this);
+ }
+
+
+}