shopware-admin-api-client
Version:
Shopware 6 admin API client
264 lines (226 loc) • 7.87 kB
JavaScript
import types from '../utils/types.utils.js';
function castValueToNullIfNecessary(value) {
if (value === '' || typeof value === 'undefined') {
return null;
}
return value;
}
// eslint-disable-next-line sw-deprecation-rules/private-feature-declarations
export default class ChangesetGenerator {
constructor(definition) {
this.definition = definition;
}
/**
* returns the primary key data of an entity
* @param entity
*/
getPrimaryKeyData(entity) {
const definition = this.definition.get(entity.getEntityName());
const pkFields = definition.getPrimaryKeyFields();
const pkData = {};
Object.keys(pkFields).forEach((fieldName) => {
pkData[fieldName] = entity[fieldName];
});
return pkData;
}
/**
* Creates the change set for the provided entity.
* @param entity
* @returns {{changes: *, deletionQueue: Array}}
*/
generate(entity) {
const deletionQueue = [];
const changes = this.recursion(entity, deletionQueue);
return { changes, deletionQueue };
}
/**
* @private
* @param {Entity} entity
* @param deletionQueue
* @returns {null}
*/
recursion(entity, deletionQueue) {
const definition = this.definition.get(entity.getEntityName());
const changes = {};
const origin = entity.getOrigin();
const draft = entity.getDraft();
definition.forEachField((field, fieldName) => {
if (field.readOnly) {
return;
}
if (field.flags.write_protected) {
return;
}
let draftValue = castValueToNullIfNecessary(draft[fieldName]);
let originValue = castValueToNullIfNecessary(origin[fieldName]);
if (definition.isScalarField(field)) {
if (draftValue !== originValue) {
changes[fieldName] = draftValue;
}
return;
}
if (field.flags.extension) {
// extensions cloud be undefined
if (origin.extensions || draft.extensions) {
draftValue = castValueToNullIfNecessary(draft.extensions[fieldName]);
originValue = castValueToNullIfNecessary(origin.extensions[fieldName]);
}
}
if (definition.isJsonField(field)) {
if (!types.isEqual(originValue, draftValue)) {
if (Array.isArray(draftValue) && draftValue.length <= 0) {
changes[fieldName] = [];
return;
}
changes[fieldName] = draftValue;
}
return;
}
if (field.type !== 'association') {
// if we don't know what kind of field we write send complete draft
if (draftValue !== originValue) {
changes[fieldName] = draftValue;
}
return;
}
switch (field.relation) {
case 'one_to_many': {
const associationChanges = this.handleOneToMany(field, draftValue, originValue, deletionQueue);
if (associationChanges.length > 0) {
changes[fieldName] = associationChanges;
}
break;
}
case 'many_to_many': {
const associationChanges = this.handleManyToMany(draftValue, originValue, deletionQueue, field, entity);
if (associationChanges.length > 0) {
changes[fieldName] = associationChanges;
}
break;
}
case 'one_to_one': {
if (!draftValue) {
return;
}
const change = this.recursion(draftValue, deletionQueue);
if (change !== null) {
// if a change is detected, add id as identifier for updates
change.id = draftValue.id;
changes[fieldName] = change;
}
break;
}
case 'many_to_one':
default: {
break;
}
}
});
if (Object.keys(changes).length > 0) {
return { ...this.getPrimaryKeyData(entity), ...changes };
}
return null;
}
/**
* @private
* @param {EntityCollection} draft
* @param {EntityCollection} origin
* @param {Object} field
* @param {Entity} entity
* @param deletionQueue
* @returns {Array}
*/
handleManyToMany(draft, origin, deletionQueue, field, entity) {
const changes = [];
// return early if origin is null
if (!origin) {
return changes;
}
const originIds = origin.getIds();
draft.forEach((nested) => {
if (!originIds.includes(nested.id)) {
changes.push({ id: nested.id });
}
});
originIds.forEach((id) => {
if (!draft.has(id)) {
const primary = {
[field.local]: entity.id,
[field.reference]: id,
};
deletionQueue.push({
route: draft.source,
key: id,
entity: field.mapping,
primary: primary,
});
}
});
return changes;
}
/**
* @private
* @param {Object} field
* @param {EntityCollection} draft
* @param {EntityCollection} origin
* @param {Array} deletionQueue
* @returns {Array}
*/
handleOneToMany(field, draft, origin, deletionQueue) {
const changes = [];
// return early if origin is null
if (!origin) {
return changes;
}
const originIds = origin.getIds();
// check for new and updated items
draft.forEach((entity) => {
// new record?
if (!originIds.includes(entity.id)) {
let change = this.recursion(entity, []);
if (change === null) {
change = { id: entity.id };
} else {
change.id = entity.id;
}
changes.push(change);
return;
}
// check if some properties changed
const change = this.recursion(entity, deletionQueue);
if (change !== null) {
// if a change is detected, add id as identifier for updates
change.id = entity.id;
changes.push(change);
}
});
if (field.flags?.cascade_delete) {
originIds.forEach((id) => {
if (!draft.has(id)) {
const primary = {
[field.primary]: id,
};
// still existing?
deletionQueue.push({
route: draft.source,
key: id,
entity: field.entity,
primary,
});
}
});
return changes;
}
if (!field.referenceField) {
return changes;
}
originIds.forEach((id) => {
if (!draft.has(id)) {
const data = { id };
data[field.referenceField] = null;
changes.push(data);
}
});
return changes;
}
}