Add entity address/contact tables and CRUD for entity resources
This commit is contained in:
69
src/store/entity/create.js
Normal file
69
src/store/entity/create.js
Normal file
@@ -0,0 +1,69 @@
|
||||
import { CREATE } from '../../sql/entity.js';
|
||||
import { INSERT as INSERT_HISTORY } from '../../sql/entityhistory.js';
|
||||
import request from '../request.js';
|
||||
import resolveCompanyAccess from '../companyuser/resolveCompanyAccess.js';
|
||||
import resolveCustomerGroupAccess from '../customergroup/resolveCustomerGroupAccess.js';
|
||||
|
||||
function shape(row) {
|
||||
return {
|
||||
id: row.id,
|
||||
customergroupId: row.customergroupId,
|
||||
firstName: row.firstName ?? null,
|
||||
name: row.name,
|
||||
createdAt: row.createdAt,
|
||||
updatedAt: row.updatedAt,
|
||||
};
|
||||
}
|
||||
|
||||
export default function create(pool) {
|
||||
const access = resolveCompanyAccess(pool);
|
||||
const groupAccess = resolveCustomerGroupAccess(pool);
|
||||
|
||||
return async (
|
||||
googleAuthId,
|
||||
companyId,
|
||||
{ customergroupId, name, firstName = null } = {},
|
||||
) => {
|
||||
const cleaned = name?.trim();
|
||||
if (!cleaned) {
|
||||
throw new Error('Name required');
|
||||
}
|
||||
|
||||
const first =
|
||||
firstName == null || firstName === ''
|
||||
? null
|
||||
: String(firstName).trim() || null;
|
||||
|
||||
const { companyuserId } = await access(googleAuthId, companyId);
|
||||
await groupAccess(companyId, customergroupId);
|
||||
|
||||
const tx = pool.transaction();
|
||||
await tx.begin();
|
||||
|
||||
try {
|
||||
const created = await request(pool, tx)
|
||||
.input('customergroupId', customergroupId)
|
||||
.input('firstName', first)
|
||||
.input('name', cleaned)
|
||||
.query(CREATE);
|
||||
|
||||
const row = created.recordset[0];
|
||||
|
||||
await request(pool, tx)
|
||||
.input('entityId', row.id)
|
||||
.input('companyuserId', companyuserId)
|
||||
.input('text', 'Erstellt')
|
||||
.query(INSERT_HISTORY);
|
||||
|
||||
await tx.commit();
|
||||
return shape(row);
|
||||
} catch (err) {
|
||||
try {
|
||||
await tx.rollback();
|
||||
} catch {
|
||||
// ignore
|
||||
}
|
||||
throw err;
|
||||
}
|
||||
};
|
||||
}
|
||||
24
src/store/entity/delete.js
Normal file
24
src/store/entity/delete.js
Normal file
@@ -0,0 +1,24 @@
|
||||
import { DELETE_BY_COMPANY_AND_ID } from '../../sql/entity.js';
|
||||
import resolveCompanyAccess from '../companyuser/resolveCompanyAccess.js';
|
||||
|
||||
export default function del(pool) {
|
||||
const access = resolveCompanyAccess(pool);
|
||||
|
||||
return async (googleAuthId, companyId, id) => {
|
||||
if (!Number.isFinite(id)) {
|
||||
throw new Error('Entity not found');
|
||||
}
|
||||
|
||||
await access(googleAuthId, companyId);
|
||||
|
||||
const result = await pool
|
||||
.request()
|
||||
.input('id', id)
|
||||
.input('companyId', companyId)
|
||||
.query(DELETE_BY_COMPANY_AND_ID);
|
||||
|
||||
if (!result.rowsAffected[0]) {
|
||||
throw new Error('Entity not found');
|
||||
}
|
||||
};
|
||||
}
|
||||
37
src/store/entity/getById.js
Normal file
37
src/store/entity/getById.js
Normal file
@@ -0,0 +1,37 @@
|
||||
import { GET_BY_COMPANY_AND_ID } from '../../sql/entity.js';
|
||||
import resolveCompanyAccess from '../companyuser/resolveCompanyAccess.js';
|
||||
|
||||
function shape(row) {
|
||||
return {
|
||||
id: row.id,
|
||||
customergroupId: row.customergroupId,
|
||||
firstName: row.firstName ?? null,
|
||||
name: row.name,
|
||||
createdAt: row.createdAt,
|
||||
updatedAt: row.updatedAt,
|
||||
};
|
||||
}
|
||||
|
||||
export default function getById(pool) {
|
||||
const access = resolveCompanyAccess(pool);
|
||||
|
||||
return async (googleAuthId, companyId, id) => {
|
||||
if (!Number.isFinite(id)) {
|
||||
throw new Error('Entity not found');
|
||||
}
|
||||
|
||||
await access(googleAuthId, companyId);
|
||||
|
||||
const result = await pool
|
||||
.request()
|
||||
.input('id', id)
|
||||
.input('companyId', companyId)
|
||||
.query(GET_BY_COMPANY_AND_ID);
|
||||
|
||||
const row = result.recordset[0];
|
||||
if (!row) {
|
||||
throw new Error('Entity not found');
|
||||
}
|
||||
return shape(row);
|
||||
};
|
||||
}
|
||||
43
src/store/entity/list.js
Normal file
43
src/store/entity/list.js
Normal file
@@ -0,0 +1,43 @@
|
||||
import {
|
||||
LIST_BY_COMPANY,
|
||||
LIST_BY_COMPANY_AND_GROUP,
|
||||
} from '../../sql/entity.js';
|
||||
import resolveCompanyAccess from '../companyuser/resolveCompanyAccess.js';
|
||||
import resolveCustomerGroupAccess from '../customergroup/resolveCustomerGroupAccess.js';
|
||||
|
||||
function shape(row) {
|
||||
return {
|
||||
id: row.id,
|
||||
customergroupId: row.customergroupId,
|
||||
firstName: row.firstName ?? null,
|
||||
name: row.name,
|
||||
createdAt: row.createdAt,
|
||||
updatedAt: row.updatedAt,
|
||||
};
|
||||
}
|
||||
|
||||
export default function list(pool) {
|
||||
const access = resolveCompanyAccess(pool);
|
||||
const groupAccess = resolveCustomerGroupAccess(pool);
|
||||
|
||||
return async (googleAuthId, companyId, { customergroupId } = {}) => {
|
||||
await access(googleAuthId, companyId);
|
||||
|
||||
if (customergroupId != null) {
|
||||
await groupAccess(companyId, customergroupId);
|
||||
const result = await pool
|
||||
.request()
|
||||
.input('companyId', companyId)
|
||||
.input('customergroupId', customergroupId)
|
||||
.query(LIST_BY_COMPANY_AND_GROUP);
|
||||
return result.recordset.map(shape);
|
||||
}
|
||||
|
||||
const result = await pool
|
||||
.request()
|
||||
.input('companyId', companyId)
|
||||
.query(LIST_BY_COMPANY);
|
||||
|
||||
return result.recordset.map(shape);
|
||||
};
|
||||
}
|
||||
22
src/store/entity/resolveEntityAccess.js
Normal file
22
src/store/entity/resolveEntityAccess.js
Normal file
@@ -0,0 +1,22 @@
|
||||
import { GET_BY_COMPANY_AND_ID } from '../../sql/entity.js';
|
||||
import request from '../request.js';
|
||||
|
||||
/** Ensure entity belongs to company. Throws Entity not found. */
|
||||
export default function resolveEntityAccess(pool) {
|
||||
return async (companyId, entityId, tx) => {
|
||||
if (!Number.isFinite(entityId)) {
|
||||
throw new Error('Entity not found');
|
||||
}
|
||||
|
||||
const result = await request(pool, tx)
|
||||
.input('id', entityId)
|
||||
.input('companyId', companyId)
|
||||
.query(GET_BY_COMPANY_AND_ID);
|
||||
|
||||
const row = result.recordset[0];
|
||||
if (!row) {
|
||||
throw new Error('Entity not found');
|
||||
}
|
||||
return row;
|
||||
};
|
||||
}
|
||||
110
src/store/entity/update.js
Normal file
110
src/store/entity/update.js
Normal file
@@ -0,0 +1,110 @@
|
||||
import { GET_BY_COMPANY_AND_ID, UPDATE } from '../../sql/entity.js';
|
||||
import { INSERT as INSERT_HISTORY } from '../../sql/entityhistory.js';
|
||||
import request from '../request.js';
|
||||
import resolveCompanyAccess from '../companyuser/resolveCompanyAccess.js';
|
||||
|
||||
function shape(row) {
|
||||
return {
|
||||
id: row.id,
|
||||
customergroupId: row.customergroupId,
|
||||
firstName: row.firstName ?? null,
|
||||
name: row.name,
|
||||
createdAt: row.createdAt,
|
||||
updatedAt: row.updatedAt,
|
||||
};
|
||||
}
|
||||
|
||||
export default function update(pool) {
|
||||
const access = resolveCompanyAccess(pool);
|
||||
|
||||
return async (googleAuthId, companyId, id, patch = {}) => {
|
||||
if (!Number.isFinite(id)) {
|
||||
throw new Error('Entity not found');
|
||||
}
|
||||
|
||||
const hasName = Object.prototype.hasOwnProperty.call(patch, 'name');
|
||||
const hasFirstName = Object.prototype.hasOwnProperty.call(patch, 'firstName');
|
||||
if (!hasName && !hasFirstName) {
|
||||
throw new Error('Name required');
|
||||
}
|
||||
|
||||
const { companyuserId } = await access(googleAuthId, companyId);
|
||||
|
||||
const existing = await pool
|
||||
.request()
|
||||
.input('id', id)
|
||||
.input('companyId', companyId)
|
||||
.query(GET_BY_COMPANY_AND_ID);
|
||||
|
||||
const current = existing.recordset[0];
|
||||
if (!current) {
|
||||
throw new Error('Entity not found');
|
||||
}
|
||||
|
||||
let name = current.name;
|
||||
if (hasName) {
|
||||
name = patch.name?.trim();
|
||||
if (!name) {
|
||||
throw new Error('Name required');
|
||||
}
|
||||
}
|
||||
|
||||
let firstName = current.firstName ?? null;
|
||||
if (hasFirstName) {
|
||||
firstName =
|
||||
patch.firstName == null || patch.firstName === ''
|
||||
? null
|
||||
: String(patch.firstName).trim() || null;
|
||||
}
|
||||
|
||||
const historyLines = [];
|
||||
const oldFirst = current.firstName ?? '';
|
||||
const newFirst = firstName ?? '';
|
||||
if (oldFirst !== newFirst) {
|
||||
historyLines.push(
|
||||
`Vorname geändert von "${oldFirst}" auf "${newFirst}"`,
|
||||
);
|
||||
}
|
||||
if (current.name !== name) {
|
||||
historyLines.push(
|
||||
`Name geändert von "${current.name}" auf "${name}"`,
|
||||
);
|
||||
}
|
||||
|
||||
if (!historyLines.length) {
|
||||
return shape(current);
|
||||
}
|
||||
|
||||
const tx = pool.transaction();
|
||||
await tx.begin();
|
||||
|
||||
try {
|
||||
const updated = await request(pool, tx)
|
||||
.input('id', id)
|
||||
.input('firstName', firstName)
|
||||
.input('name', name)
|
||||
.query(UPDATE);
|
||||
|
||||
const row = updated.recordset[0];
|
||||
if (!row) {
|
||||
throw new Error('Entity not found');
|
||||
}
|
||||
|
||||
await request(pool, tx)
|
||||
.input('entityId', id)
|
||||
.input('companyuserId', companyuserId)
|
||||
.input('text', historyLines.join('\n'))
|
||||
.query(INSERT_HISTORY);
|
||||
|
||||
await tx.commit();
|
||||
return shape(row);
|
||||
} catch (err) {
|
||||
try {
|
||||
await tx.rollback();
|
||||
} catch {
|
||||
// ignore
|
||||
}
|
||||
throw err;
|
||||
}
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user