mirror of
https://github.com/Kingsrook/qqq.git
synced 2025-07-19 21:50:45 +00:00
Initial support for associated records (implemented insert, delete).
Include "api" on audit.
This commit is contained in:
@ -54,6 +54,7 @@ import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldType;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.processes.QProcessMetaData;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.security.RecordSecurityLock;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData;
|
||||
import com.kingsrook.qqq.backend.core.model.session.QSession;
|
||||
import com.kingsrook.qqq.backend.core.utils.CollectionUtils;
|
||||
import com.kingsrook.qqq.backend.core.utils.ValueUtils;
|
||||
import static com.kingsrook.qqq.backend.core.logging.LogUtils.logPair;
|
||||
@ -106,6 +107,13 @@ public class DMLAuditAction extends AbstractQActionFunction<DMLAuditInput, DMLAu
|
||||
}
|
||||
}
|
||||
|
||||
QSession qSession = QContext.getQSession();
|
||||
String apiVersion = qSession.getValue("apiVersion");
|
||||
if(apiVersion != null)
|
||||
{
|
||||
contextSuffix += (" via API Version: " + apiVersion);
|
||||
}
|
||||
|
||||
AuditInput auditInput = new AuditInput();
|
||||
if(auditLevel.equals(AuditLevel.RECORD) || (auditLevel.equals(AuditLevel.FIELD) && !dmlType.supportsFields))
|
||||
{
|
||||
@ -125,7 +133,7 @@ public class DMLAuditAction extends AbstractQActionFunction<DMLAuditInput, DMLAu
|
||||
///////////////////////////////////////////////////////////////////
|
||||
// do many audits, all with field level details, for FIELD level //
|
||||
///////////////////////////////////////////////////////////////////
|
||||
QPossibleValueTranslator qPossibleValueTranslator = new QPossibleValueTranslator(QContext.getQInstance(), QContext.getQSession());
|
||||
QPossibleValueTranslator qPossibleValueTranslator = new QPossibleValueTranslator(QContext.getQInstance(), qSession);
|
||||
qPossibleValueTranslator.translatePossibleValuesInRecords(table, CollectionUtils.mergeLists(recordList, oldRecordList));
|
||||
|
||||
//////////////////////////////////////////
|
||||
|
@ -28,6 +28,7 @@ import java.util.List;
|
||||
import com.kingsrook.qqq.backend.core.actions.ActionHelper;
|
||||
import com.kingsrook.qqq.backend.core.actions.audits.DMLAuditAction;
|
||||
import com.kingsrook.qqq.backend.core.actions.interfaces.DeleteInterface;
|
||||
import com.kingsrook.qqq.backend.core.context.QContext;
|
||||
import com.kingsrook.qqq.backend.core.exceptions.QException;
|
||||
import com.kingsrook.qqq.backend.core.logging.QLogger;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.audits.DMLAuditInput;
|
||||
@ -40,6 +41,9 @@ import com.kingsrook.qqq.backend.core.model.actions.tables.query.QueryInput;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QueryOutput;
|
||||
import com.kingsrook.qqq.backend.core.model.data.QRecord;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.audits.AuditLevel;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.joins.QJoinMetaData;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.tables.Association;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData;
|
||||
import com.kingsrook.qqq.backend.core.modules.backend.QBackendModuleDispatcher;
|
||||
import com.kingsrook.qqq.backend.core.modules.backend.QBackendModuleInterface;
|
||||
import com.kingsrook.qqq.backend.core.utils.CollectionUtils;
|
||||
@ -91,6 +95,9 @@ public class DeleteAction
|
||||
List<QRecord> recordListForAudit = getRecordListForAuditIfNeeded(deleteInput);
|
||||
|
||||
DeleteOutput deleteOutput = deleteInterface.execute(deleteInput);
|
||||
|
||||
manageAssociations(deleteInput);
|
||||
|
||||
// todo post-customization - can do whatever w/ the result if you want
|
||||
|
||||
new DMLAuditAction().execute(new DMLAuditInput().withTableActionInput(deleteInput).withRecordList(recordListForAudit));
|
||||
@ -100,6 +107,46 @@ public class DeleteAction
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
private void manageAssociations(DeleteInput deleteInput) throws QException
|
||||
{
|
||||
QTableMetaData table = deleteInput.getTable();
|
||||
for(Association association : CollectionUtils.nonNullList(table.getAssociations()))
|
||||
{
|
||||
// e.g., order -> orderLine
|
||||
QJoinMetaData join = QContext.getQInstance().getJoin(association.getJoinName()); // todo ... ever need to flip?
|
||||
// just assume this, at least for now... if(BooleanUtils.isTrue(association.getDoInserts()))
|
||||
|
||||
QQueryFilter filter = new QQueryFilter();
|
||||
|
||||
if(join.getJoinOns().size() == 1 && join.getJoinOns().get(0).getLeftField().equals(table.getPrimaryKeyField()))
|
||||
{
|
||||
filter.addCriteria(new QFilterCriteria(join.getJoinOns().get(0).getRightField(), QCriteriaOperator.IN, deleteInput.getPrimaryKeys()));
|
||||
}
|
||||
else
|
||||
{
|
||||
throw (new QException("Join of this type is not supported for an associated delete at this time..."));
|
||||
}
|
||||
|
||||
QTableMetaData associatedTable = QContext.getQInstance().getTable(association.getAssociatedTableName());
|
||||
|
||||
QueryInput queryInput = new QueryInput();
|
||||
queryInput.setTableName(association.getAssociatedTableName());
|
||||
queryInput.setFilter(filter);
|
||||
QueryOutput queryOutput = new QueryAction().execute(queryInput);
|
||||
List<Serializable> associatedKeys = queryOutput.getRecords().stream().map(r -> r.getValue(associatedTable.getPrimaryKeyField())).toList();
|
||||
|
||||
DeleteInput nextLevelDeleteInput = new DeleteInput();
|
||||
nextLevelDeleteInput.setTableName(association.getAssociatedTableName());
|
||||
nextLevelDeleteInput.setPrimaryKeys(associatedKeys);
|
||||
DeleteOutput nextLevelDeleteOutput = new DeleteAction().execute(nextLevelDeleteInput);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
|
@ -23,6 +23,7 @@ package com.kingsrook.qqq.backend.core.actions.tables;
|
||||
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
@ -40,12 +41,16 @@ import com.kingsrook.qqq.backend.core.actions.customizers.QCodeLoader;
|
||||
import com.kingsrook.qqq.backend.core.actions.customizers.TableCustomizers;
|
||||
import com.kingsrook.qqq.backend.core.actions.tables.helpers.UniqueKeyHelper;
|
||||
import com.kingsrook.qqq.backend.core.actions.values.ValueBehaviorApplier;
|
||||
import com.kingsrook.qqq.backend.core.context.QContext;
|
||||
import com.kingsrook.qqq.backend.core.exceptions.QException;
|
||||
import com.kingsrook.qqq.backend.core.logging.QLogger;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.audits.DMLAuditInput;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.insert.InsertInput;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.insert.InsertOutput;
|
||||
import com.kingsrook.qqq.backend.core.model.data.QRecord;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.joins.JoinOn;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.joins.QJoinMetaData;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.tables.Association;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.tables.UniqueKey;
|
||||
import com.kingsrook.qqq.backend.core.modules.backend.QBackendModuleDispatcher;
|
||||
@ -75,15 +80,16 @@ public class InsertAction extends AbstractQActionFunction<InsertInput, InsertOut
|
||||
Optional<AbstractPostInsertCustomizer> postInsertCustomizer = QCodeLoader.getTableCustomizer(AbstractPostInsertCustomizer.class, table, TableCustomizers.POST_INSERT_RECORD.getRole());
|
||||
setAutomationStatusField(insertInput);
|
||||
|
||||
ValueBehaviorApplier.applyFieldBehaviors(insertInput.getInstance(), table, insertInput.getRecords());
|
||||
// todo - need to handle records with errors coming out of here...
|
||||
|
||||
QBackendModuleInterface qModule = getBackendModuleInterface(insertInput);
|
||||
// todo pre-customization - just get to modify the request?
|
||||
|
||||
ValueBehaviorApplier.applyFieldBehaviors(insertInput.getInstance(), table, insertInput.getRecords());
|
||||
setErrorsIfUniqueKeyErrors(insertInput, table);
|
||||
|
||||
InsertOutput insertOutput = qModule.getInsertInterface().execute(insertInput);
|
||||
|
||||
manageAssociations(table, insertOutput.getRecords());
|
||||
|
||||
// todo post-customization - can do whatever w/ the result if you want
|
||||
|
||||
new DMLAuditAction().execute(new DMLAuditInput().withTableActionInput(insertInput).withRecordList(insertOutput.getRecords()));
|
||||
@ -98,6 +104,43 @@ public class InsertAction extends AbstractQActionFunction<InsertInput, InsertOut
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
private void manageAssociations(QTableMetaData table, List<QRecord> insertedRecords) throws QException
|
||||
{
|
||||
for(Association association : CollectionUtils.nonNullList(table.getAssociations()))
|
||||
{
|
||||
// e.g., order -> orderLine
|
||||
QJoinMetaData join = QContext.getQInstance().getJoin(association.getJoinName()); // todo ... ever need to flip?
|
||||
// just assume this, at least for now... if(BooleanUtils.isTrue(association.getDoInserts()))
|
||||
|
||||
List<QRecord> nextLevelInserts = new ArrayList<>();
|
||||
for(QRecord record : insertedRecords)
|
||||
{
|
||||
if(record.getAssociatedRecords() != null && record.getAssociatedRecords().containsKey(association.getName()))
|
||||
{
|
||||
for(QRecord associatedRecord : CollectionUtils.nonNullList(record.getAssociatedRecords().get(association.getName())))
|
||||
{
|
||||
for(JoinOn joinOn : join.getJoinOns())
|
||||
{
|
||||
associatedRecord.setValue(joinOn.getRightField(), record.getValue(joinOn.getLeftField()));
|
||||
}
|
||||
nextLevelInserts.add(associatedRecord);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
InsertInput nextLevelInsertInput = new InsertInput();
|
||||
nextLevelInsertInput.setTableName(association.getAssociatedTableName());
|
||||
nextLevelInsertInput.setRecords(nextLevelInserts);
|
||||
InsertOutput nextLevelInsertOutput = new InsertAction().execute(nextLevelInsertInput);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
|
@ -28,6 +28,7 @@ import java.time.Instant;
|
||||
import java.time.LocalDate;
|
||||
import java.time.LocalTime;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
@ -65,6 +66,8 @@ public class QRecord implements Serializable
|
||||
private Map<String, Serializable> backendDetails = new LinkedHashMap<>();
|
||||
private List<String> errors = new ArrayList<>();
|
||||
|
||||
private Map<String, List<QRecord>> associatedRecords = new HashMap<>();
|
||||
|
||||
public static final String BACKEND_DETAILS_TYPE_JSON_SOURCE_OBJECT = "jsonSourceObject";
|
||||
|
||||
|
||||
@ -102,6 +105,7 @@ public class QRecord implements Serializable
|
||||
this.displayValues = doDeepCopy(record.displayValues);
|
||||
this.backendDetails = doDeepCopy(record.backendDetails);
|
||||
this.errors = doDeepCopy(record.errors);
|
||||
this.associatedRecords = doDeepCopy(record.associatedRecords);
|
||||
}
|
||||
|
||||
|
||||
@ -575,4 +579,66 @@ public class QRecord implements Serializable
|
||||
return (QRecordEntity.fromQRecord(c, this));
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for associatedRecords
|
||||
*******************************************************************************/
|
||||
public Map<String, List<QRecord>> getAssociatedRecords()
|
||||
{
|
||||
return (this.associatedRecords);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for associatedRecords
|
||||
*******************************************************************************/
|
||||
public void setAssociatedRecords(Map<String, List<QRecord>> associatedRecords)
|
||||
{
|
||||
this.associatedRecords = associatedRecords;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Fluent setter for associatedRecords
|
||||
*******************************************************************************/
|
||||
public QRecord withAssociatedRecords(Map<String, List<QRecord>> associatedRecords)
|
||||
{
|
||||
this.associatedRecords = associatedRecords;
|
||||
return (this);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Fluent setter for associatedRecords
|
||||
*******************************************************************************/
|
||||
public QRecord withAssociatedRecords(String name, List<QRecord> associatedRecords)
|
||||
{
|
||||
if(this.associatedRecords == null)
|
||||
{
|
||||
this.associatedRecords = new HashMap<>();
|
||||
}
|
||||
this.associatedRecords.put(name, associatedRecords);
|
||||
return (this);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Fluent setter for associatedRecord
|
||||
*******************************************************************************/
|
||||
public QRecord withAssociatedRecord(String name, QRecord associatedRecord)
|
||||
{
|
||||
if(this.associatedRecords == null)
|
||||
{
|
||||
this.associatedRecords = new HashMap<>();
|
||||
}
|
||||
this.associatedRecords.putIfAbsent(name, new ArrayList<>());
|
||||
this.associatedRecords.get(name).add(associatedRecord);
|
||||
return (this);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -0,0 +1,128 @@
|
||||
/*
|
||||
* QQQ - Low-code Application Framework for Engineers.
|
||||
* Copyright (C) 2021-2023. 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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.kingsrook.qqq.backend.core.model.metadata.tables;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** definition of a qqq table that is "associated" with another table, e.g.,
|
||||
** managed along with it - such as child-records under a parent record.
|
||||
*******************************************************************************/
|
||||
public class Association
|
||||
{
|
||||
private String name;
|
||||
private String associatedTableName;
|
||||
private String joinName;
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for name
|
||||
*******************************************************************************/
|
||||
public String getName()
|
||||
{
|
||||
return (this.name);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for name
|
||||
*******************************************************************************/
|
||||
public void setName(String name)
|
||||
{
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Fluent setter for name
|
||||
*******************************************************************************/
|
||||
public Association withName(String name)
|
||||
{
|
||||
this.name = name;
|
||||
return (this);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for associatedTableName
|
||||
*******************************************************************************/
|
||||
public String getAssociatedTableName()
|
||||
{
|
||||
return (this.associatedTableName);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for associatedTableName
|
||||
*******************************************************************************/
|
||||
public void setAssociatedTableName(String associatedTableName)
|
||||
{
|
||||
this.associatedTableName = associatedTableName;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Fluent setter for associatedTableName
|
||||
*******************************************************************************/
|
||||
public Association withAssociatedTableName(String associatedTableName)
|
||||
{
|
||||
this.associatedTableName = associatedTableName;
|
||||
return (this);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for joinName
|
||||
*******************************************************************************/
|
||||
public String getJoinName()
|
||||
{
|
||||
return (this.joinName);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for joinName
|
||||
*******************************************************************************/
|
||||
public void setJoinName(String joinName)
|
||||
{
|
||||
this.joinName = joinName;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Fluent setter for joinName
|
||||
*******************************************************************************/
|
||||
public Association withJoinName(String joinName)
|
||||
{
|
||||
this.joinName = joinName;
|
||||
return (this);
|
||||
}
|
||||
|
||||
}
|
@ -72,6 +72,7 @@ public class QTableMetaData implements QAppChildMetaData, Serializable, MetaData
|
||||
|
||||
private Map<String, QFieldMetaData> fields;
|
||||
private List<UniqueKey> uniqueKeys;
|
||||
private List<Association> associations;
|
||||
|
||||
private List<RecordSecurityLock> recordSecurityLocks;
|
||||
private QPermissionRules permissionRules;
|
||||
@ -1237,4 +1238,50 @@ public class QTableMetaData implements QAppChildMetaData, Serializable, MetaData
|
||||
return (this);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for associations
|
||||
*******************************************************************************/
|
||||
public List<Association> getAssociations()
|
||||
{
|
||||
return (this.associations);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for associations
|
||||
*******************************************************************************/
|
||||
public void setAssociations(List<Association> associations)
|
||||
{
|
||||
this.associations = associations;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Fluent setter for associations
|
||||
*******************************************************************************/
|
||||
public QTableMetaData withAssociations(List<Association> associations)
|
||||
{
|
||||
this.associations = associations;
|
||||
return (this);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Fluent setter for associations
|
||||
*******************************************************************************/
|
||||
public QTableMetaData withAssociation(Association association)
|
||||
{
|
||||
if(this.associations == null)
|
||||
{
|
||||
this.associations = new ArrayList<>();
|
||||
}
|
||||
this.associations.add(association);
|
||||
return (this);
|
||||
}
|
||||
|
||||
}
|
||||
|
Reference in New Issue
Block a user