Add entity address/contact tables and CRUD for entity resources
This commit is contained in:
46
src/index.js
46
src/index.js
@@ -39,6 +39,23 @@ import companysettingDelete from './store/companysetting/delete.js';
|
||||
import brandList from './store/brand/list.js';
|
||||
import brandUpsert from './store/brand/upsert.js';
|
||||
import brandDelete from './store/brand/delete.js';
|
||||
import customergroupList from './store/customergroup/list.js';
|
||||
import customergroupCreate from './store/customergroup/create.js';
|
||||
import customergroupUpdate from './store/customergroup/update.js';
|
||||
import customergroupDelete from './store/customergroup/delete.js';
|
||||
import entityList from './store/entity/list.js';
|
||||
import entityGetById from './store/entity/getById.js';
|
||||
import entityCreate from './store/entity/create.js';
|
||||
import entityUpdate from './store/entity/update.js';
|
||||
import entityDelete from './store/entity/delete.js';
|
||||
import entityaddressList from './store/entityaddress/list.js';
|
||||
import entityaddressCreate from './store/entityaddress/create.js';
|
||||
import entityaddressUpdate from './store/entityaddress/update.js';
|
||||
import entityaddressDelete from './store/entityaddress/delete.js';
|
||||
import entitycontactList from './store/entitycontact/list.js';
|
||||
import entitycontactCreate from './store/entitycontact/create.js';
|
||||
import entitycontactUpdate from './store/entitycontact/update.js';
|
||||
import entitycontactDelete from './store/entitycontact/delete.js';
|
||||
import supplierList from './store/supplier/list.js';
|
||||
import supplierCreate from './store/supplier/create.js';
|
||||
import supplierUpdate from './store/supplier/update.js';
|
||||
@@ -158,6 +175,35 @@ export default class LibDashStore {
|
||||
delete: brandDelete(pool),
|
||||
};
|
||||
|
||||
this.customergroup = {
|
||||
list: customergroupList(pool),
|
||||
create: customergroupCreate(pool),
|
||||
update: customergroupUpdate(pool),
|
||||
delete: customergroupDelete(pool),
|
||||
};
|
||||
|
||||
this.entity = {
|
||||
list: entityList(pool),
|
||||
getById: entityGetById(pool),
|
||||
create: entityCreate(pool),
|
||||
update: entityUpdate(pool),
|
||||
delete: entityDelete(pool),
|
||||
};
|
||||
|
||||
this.entityaddress = {
|
||||
list: entityaddressList(pool),
|
||||
create: entityaddressCreate(pool),
|
||||
update: entityaddressUpdate(pool),
|
||||
delete: entityaddressDelete(pool),
|
||||
};
|
||||
|
||||
this.entitycontact = {
|
||||
list: entitycontactList(pool),
|
||||
create: entitycontactCreate(pool),
|
||||
update: entitycontactUpdate(pool),
|
||||
delete: entitycontactDelete(pool),
|
||||
};
|
||||
|
||||
this.supplier = {
|
||||
list: supplierList(pool),
|
||||
create: supplierCreate(pool),
|
||||
|
||||
@@ -164,6 +164,38 @@ BEGIN
|
||||
END
|
||||
`;
|
||||
|
||||
export const INIT_ENTITYADDRESS = `
|
||||
IF NOT EXISTS (SELECT * FROM sys.tables WHERE name = 'entityaddress')
|
||||
BEGIN
|
||||
CREATE TABLE entityaddress (
|
||||
id INT IDENTITY(1,1) PRIMARY KEY,
|
||||
entity_id INT NOT NULL,
|
||||
first_name NVARCHAR(255) NULL,
|
||||
name NVARCHAR(255) NULL,
|
||||
street NVARCHAR(255) NULL,
|
||||
streetnumber NVARCHAR(50) NULL,
|
||||
zip NVARCHAR(50) NULL,
|
||||
city NVARCHAR(255) NULL,
|
||||
county NVARCHAR(255) NULL,
|
||||
addresstype NVARCHAR(50) NOT NULL,
|
||||
CONSTRAINT FK_entityaddress_entity FOREIGN KEY (entity_id) REFERENCES entity(id) ON DELETE CASCADE
|
||||
);
|
||||
END
|
||||
`;
|
||||
|
||||
export const INIT_ENTITYCONTACT = `
|
||||
IF NOT EXISTS (SELECT * FROM sys.tables WHERE name = 'entitycontact')
|
||||
BEGIN
|
||||
CREATE TABLE entitycontact (
|
||||
id INT IDENTITY(1,1) PRIMARY KEY,
|
||||
entity_id INT NOT NULL,
|
||||
contacttype NVARCHAR(50) NOT NULL,
|
||||
contact NVARCHAR(255) NOT NULL,
|
||||
CONSTRAINT FK_entitycontact_entity FOREIGN KEY (entity_id) REFERENCES entity(id) ON DELETE CASCADE
|
||||
);
|
||||
END
|
||||
`;
|
||||
|
||||
export const INIT_ENTITYHISTORY = `
|
||||
IF NOT EXISTS (SELECT * FROM sys.tables WHERE name = 'entityhistory')
|
||||
BEGIN
|
||||
@@ -636,6 +668,8 @@ export const SCHEMA_SCRIPTS = [
|
||||
INIT_CUSTOMERGROUP,
|
||||
INIT_COMPANY_POS_CUSTOMERGROUP_FK,
|
||||
INIT_ENTITY,
|
||||
INIT_ENTITYADDRESS,
|
||||
INIT_ENTITYCONTACT,
|
||||
INIT_UNIT,
|
||||
INIT_UNITTEXT,
|
||||
INIT_OPTIONSLIST,
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
export const CREATE = `
|
||||
INSERT INTO customergroup (name, company_id)
|
||||
OUTPUT INSERTED.id
|
||||
OUTPUT INSERTED.id, INSERTED.name
|
||||
VALUES (@name, @companyId)
|
||||
`;
|
||||
|
||||
@@ -17,7 +17,32 @@ WHERE id = @id
|
||||
`;
|
||||
|
||||
export const GET_BY_COMPANY_AND_NAME = `
|
||||
SELECT id
|
||||
SELECT id, name
|
||||
FROM customergroup
|
||||
WHERE company_id = @companyId AND name = @name
|
||||
`;
|
||||
|
||||
export const LIST_BY_COMPANY = `
|
||||
SELECT id, name
|
||||
FROM customergroup
|
||||
WHERE company_id = @companyId
|
||||
ORDER BY name, id
|
||||
`;
|
||||
|
||||
export const GET_BY_COMPANY_AND_ID = `
|
||||
SELECT id, name
|
||||
FROM customergroup
|
||||
WHERE id = @id AND company_id = @companyId
|
||||
`;
|
||||
|
||||
export const UPDATE_NAME = `
|
||||
UPDATE customergroup
|
||||
SET name = @name
|
||||
OUTPUT INSERTED.id, INSERTED.name
|
||||
WHERE id = @id AND company_id = @companyId
|
||||
`;
|
||||
|
||||
export const DELETE_BY_COMPANY_AND_ID = `
|
||||
DELETE FROM customergroup
|
||||
WHERE id = @id AND company_id = @companyId
|
||||
`;
|
||||
|
||||
@@ -1,5 +1,42 @@
|
||||
export const SUPPLIER_GROUP_NAME = 'Lieferanten';
|
||||
|
||||
export const ENTITY_COLUMNS = `
|
||||
e.id,
|
||||
e.customergroup_id AS customergroupId,
|
||||
e.first_name AS firstName,
|
||||
e.name,
|
||||
e.created_at AS createdAt,
|
||||
e.updated_at AS updatedAt
|
||||
`;
|
||||
|
||||
export const LIST_BY_COMPANY = `
|
||||
SELECT
|
||||
${ENTITY_COLUMNS}
|
||||
FROM entity e
|
||||
INNER JOIN customergroup cg ON cg.id = e.customergroup_id
|
||||
WHERE cg.company_id = @companyId
|
||||
ORDER BY e.name, e.id
|
||||
`;
|
||||
|
||||
export const LIST_BY_COMPANY_AND_GROUP = `
|
||||
SELECT
|
||||
${ENTITY_COLUMNS}
|
||||
FROM entity e
|
||||
INNER JOIN customergroup cg ON cg.id = e.customergroup_id
|
||||
WHERE cg.company_id = @companyId
|
||||
AND e.customergroup_id = @customergroupId
|
||||
ORDER BY e.name, e.id
|
||||
`;
|
||||
|
||||
export const GET_BY_COMPANY_AND_ID = `
|
||||
SELECT
|
||||
${ENTITY_COLUMNS}
|
||||
FROM entity e
|
||||
INNER JOIN customergroup cg ON cg.id = e.customergroup_id
|
||||
WHERE e.id = @id
|
||||
AND cg.company_id = @companyId
|
||||
`;
|
||||
|
||||
export const LIST_SUPPLIERS_BY_COMPANY = `
|
||||
SELECT
|
||||
e.id,
|
||||
@@ -27,9 +64,31 @@ WHERE e.id = @id
|
||||
`;
|
||||
|
||||
export const CREATE = `
|
||||
INSERT INTO entity (customergroup_id, name)
|
||||
OUTPUT INSERTED.id, INSERTED.name, INSERTED.created_at AS createdAt, INSERTED.updated_at AS updatedAt
|
||||
VALUES (@customergroupId, @name)
|
||||
INSERT INTO entity (customergroup_id, first_name, name)
|
||||
OUTPUT
|
||||
INSERTED.id,
|
||||
INSERTED.customergroup_id AS customergroupId,
|
||||
INSERTED.first_name AS firstName,
|
||||
INSERTED.name,
|
||||
INSERTED.created_at AS createdAt,
|
||||
INSERTED.updated_at AS updatedAt
|
||||
VALUES (@customergroupId, @firstName, @name)
|
||||
`;
|
||||
|
||||
export const UPDATE = `
|
||||
UPDATE entity
|
||||
SET
|
||||
first_name = @firstName,
|
||||
name = @name,
|
||||
updated_at = SYSUTCDATETIME()
|
||||
OUTPUT
|
||||
INSERTED.id,
|
||||
INSERTED.customergroup_id AS customergroupId,
|
||||
INSERTED.first_name AS firstName,
|
||||
INSERTED.name,
|
||||
INSERTED.created_at AS createdAt,
|
||||
INSERTED.updated_at AS updatedAt
|
||||
WHERE id = @id
|
||||
`;
|
||||
|
||||
export const UPDATE_NAME = `
|
||||
@@ -40,6 +99,14 @@ WHERE id = @id
|
||||
AND customergroup_id = @customergroupId
|
||||
`;
|
||||
|
||||
export const DELETE_BY_COMPANY_AND_ID = `
|
||||
DELETE e
|
||||
FROM entity e
|
||||
INNER JOIN customergroup cg ON cg.id = e.customergroup_id
|
||||
WHERE e.id = @id
|
||||
AND cg.company_id = @companyId
|
||||
`;
|
||||
|
||||
export const DELETE_BY_ID = `
|
||||
DELETE e
|
||||
FROM entity e
|
||||
|
||||
93
src/sql/entityaddress.js
Normal file
93
src/sql/entityaddress.js
Normal file
@@ -0,0 +1,93 @@
|
||||
export const ADDRESS_COLUMNS = `
|
||||
id,
|
||||
entity_id AS entityId,
|
||||
first_name AS firstName,
|
||||
name,
|
||||
street,
|
||||
streetnumber AS streetNumber,
|
||||
zip,
|
||||
city,
|
||||
county,
|
||||
addresstype AS addressType
|
||||
`;
|
||||
|
||||
export const LIST_BY_ENTITY = `
|
||||
SELECT
|
||||
${ADDRESS_COLUMNS}
|
||||
FROM entityaddress
|
||||
WHERE entity_id = @entityId
|
||||
ORDER BY id
|
||||
`;
|
||||
|
||||
export const GET_BY_ENTITY_AND_ID = `
|
||||
SELECT
|
||||
${ADDRESS_COLUMNS}
|
||||
FROM entityaddress
|
||||
WHERE id = @id AND entity_id = @entityId
|
||||
`;
|
||||
|
||||
export const CREATE = `
|
||||
INSERT INTO entityaddress (
|
||||
entity_id,
|
||||
first_name,
|
||||
name,
|
||||
street,
|
||||
streetnumber,
|
||||
zip,
|
||||
city,
|
||||
county,
|
||||
addresstype
|
||||
)
|
||||
OUTPUT
|
||||
INSERTED.id,
|
||||
INSERTED.entity_id AS entityId,
|
||||
INSERTED.first_name AS firstName,
|
||||
INSERTED.name,
|
||||
INSERTED.street,
|
||||
INSERTED.streetnumber AS streetNumber,
|
||||
INSERTED.zip,
|
||||
INSERTED.city,
|
||||
INSERTED.county,
|
||||
INSERTED.addresstype AS addressType
|
||||
VALUES (
|
||||
@entityId,
|
||||
@firstName,
|
||||
@name,
|
||||
@street,
|
||||
@streetNumber,
|
||||
@zip,
|
||||
@city,
|
||||
@county,
|
||||
@addressType
|
||||
)
|
||||
`;
|
||||
|
||||
export const UPDATE = `
|
||||
UPDATE entityaddress
|
||||
SET
|
||||
first_name = @firstName,
|
||||
name = @name,
|
||||
street = @street,
|
||||
streetnumber = @streetNumber,
|
||||
zip = @zip,
|
||||
city = @city,
|
||||
county = @county,
|
||||
addresstype = @addressType
|
||||
OUTPUT
|
||||
INSERTED.id,
|
||||
INSERTED.entity_id AS entityId,
|
||||
INSERTED.first_name AS firstName,
|
||||
INSERTED.name,
|
||||
INSERTED.street,
|
||||
INSERTED.streetnumber AS streetNumber,
|
||||
INSERTED.zip,
|
||||
INSERTED.city,
|
||||
INSERTED.county,
|
||||
INSERTED.addresstype AS addressType
|
||||
WHERE id = @id AND entity_id = @entityId
|
||||
`;
|
||||
|
||||
export const DELETE_BY_ENTITY_AND_ID = `
|
||||
DELETE FROM entityaddress
|
||||
WHERE id = @id AND entity_id = @entityId
|
||||
`;
|
||||
47
src/sql/entitycontact.js
Normal file
47
src/sql/entitycontact.js
Normal file
@@ -0,0 +1,47 @@
|
||||
export const CONTACT_COLUMNS = `
|
||||
id,
|
||||
entity_id AS entityId,
|
||||
contacttype AS contactType,
|
||||
contact
|
||||
`;
|
||||
|
||||
export const LIST_BY_ENTITY = `
|
||||
SELECT
|
||||
${CONTACT_COLUMNS}
|
||||
FROM entitycontact
|
||||
WHERE entity_id = @entityId
|
||||
ORDER BY id
|
||||
`;
|
||||
|
||||
export const GET_BY_ENTITY_AND_ID = `
|
||||
SELECT
|
||||
${CONTACT_COLUMNS}
|
||||
FROM entitycontact
|
||||
WHERE id = @id AND entity_id = @entityId
|
||||
`;
|
||||
|
||||
export const CREATE = `
|
||||
INSERT INTO entitycontact (entity_id, contacttype, contact)
|
||||
OUTPUT
|
||||
INSERTED.id,
|
||||
INSERTED.entity_id AS entityId,
|
||||
INSERTED.contacttype AS contactType,
|
||||
INSERTED.contact
|
||||
VALUES (@entityId, @contactType, @contact)
|
||||
`;
|
||||
|
||||
export const UPDATE = `
|
||||
UPDATE entitycontact
|
||||
SET contacttype = @contactType, contact = @contact
|
||||
OUTPUT
|
||||
INSERTED.id,
|
||||
INSERTED.entity_id AS entityId,
|
||||
INSERTED.contacttype AS contactType,
|
||||
INSERTED.contact
|
||||
WHERE id = @id AND entity_id = @entityId
|
||||
`;
|
||||
|
||||
export const DELETE_BY_ENTITY_AND_ID = `
|
||||
DELETE FROM entitycontact
|
||||
WHERE id = @id AND entity_id = @entityId
|
||||
`;
|
||||
@@ -20,6 +20,8 @@ export const TABLES = [
|
||||
'articleean',
|
||||
'articlehistory',
|
||||
'entityhistory',
|
||||
'entityaddress',
|
||||
'entitycontact',
|
||||
'article',
|
||||
'brand',
|
||||
'articlelist',
|
||||
|
||||
24
src/store/customergroup/create.js
Normal file
24
src/store/customergroup/create.js
Normal file
@@ -0,0 +1,24 @@
|
||||
import { CREATE } from '../../sql/customergroup.js';
|
||||
import resolveCompanyAccess from '../companyuser/resolveCompanyAccess.js';
|
||||
|
||||
export default function create(pool) {
|
||||
const access = resolveCompanyAccess(pool);
|
||||
|
||||
return async (googleAuthId, companyId, { name } = {}) => {
|
||||
const cleaned = name?.trim();
|
||||
if (!cleaned) {
|
||||
throw new Error('Name required');
|
||||
}
|
||||
|
||||
await access(googleAuthId, companyId);
|
||||
|
||||
const result = await pool
|
||||
.request()
|
||||
.input('companyId', companyId)
|
||||
.input('name', cleaned)
|
||||
.query(CREATE);
|
||||
|
||||
const row = result.recordset[0];
|
||||
return { id: row.id, name: row.name };
|
||||
};
|
||||
}
|
||||
31
src/store/customergroup/delete.js
Normal file
31
src/store/customergroup/delete.js
Normal file
@@ -0,0 +1,31 @@
|
||||
import { DELETE_BY_COMPANY_AND_ID } from '../../sql/customergroup.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('Customer group not found');
|
||||
}
|
||||
|
||||
await access(googleAuthId, companyId);
|
||||
|
||||
try {
|
||||
const result = await pool
|
||||
.request()
|
||||
.input('id', id)
|
||||
.input('companyId', companyId)
|
||||
.query(DELETE_BY_COMPANY_AND_ID);
|
||||
|
||||
if (!result.rowsAffected[0]) {
|
||||
throw new Error('Customer group not found');
|
||||
}
|
||||
} catch (err) {
|
||||
if (err.number === 547) {
|
||||
throw new Error('Customer group in use');
|
||||
}
|
||||
throw err;
|
||||
}
|
||||
};
|
||||
}
|
||||
20
src/store/customergroup/list.js
Normal file
20
src/store/customergroup/list.js
Normal file
@@ -0,0 +1,20 @@
|
||||
import { LIST_BY_COMPANY } from '../../sql/customergroup.js';
|
||||
import resolveCompanyAccess from '../companyuser/resolveCompanyAccess.js';
|
||||
|
||||
export default function list(pool) {
|
||||
const access = resolveCompanyAccess(pool);
|
||||
|
||||
return async (googleAuthId, companyId) => {
|
||||
await access(googleAuthId, companyId);
|
||||
|
||||
const result = await pool
|
||||
.request()
|
||||
.input('companyId', companyId)
|
||||
.query(LIST_BY_COMPANY);
|
||||
|
||||
return result.recordset.map((row) => ({
|
||||
id: row.id,
|
||||
name: row.name,
|
||||
}));
|
||||
};
|
||||
}
|
||||
22
src/store/customergroup/resolveCustomerGroupAccess.js
Normal file
22
src/store/customergroup/resolveCustomerGroupAccess.js
Normal file
@@ -0,0 +1,22 @@
|
||||
import { GET_BY_COMPANY_AND_ID } from '../../sql/customergroup.js';
|
||||
import request from '../request.js';
|
||||
|
||||
/** Ensure customergroup belongs to company. Throws Customer group not found. */
|
||||
export default function resolveCustomerGroupAccess(pool) {
|
||||
return async (companyId, customergroupId, tx) => {
|
||||
if (!Number.isFinite(customergroupId)) {
|
||||
throw new Error('Customer group not found');
|
||||
}
|
||||
|
||||
const result = await request(pool, tx)
|
||||
.input('id', customergroupId)
|
||||
.input('companyId', companyId)
|
||||
.query(GET_BY_COMPANY_AND_ID);
|
||||
|
||||
const row = result.recordset[0];
|
||||
if (!row) {
|
||||
throw new Error('Customer group not found');
|
||||
}
|
||||
return row;
|
||||
};
|
||||
}
|
||||
32
src/store/customergroup/update.js
Normal file
32
src/store/customergroup/update.js
Normal file
@@ -0,0 +1,32 @@
|
||||
import { UPDATE_NAME } from '../../sql/customergroup.js';
|
||||
import resolveCompanyAccess from '../companyuser/resolveCompanyAccess.js';
|
||||
|
||||
export default function update(pool) {
|
||||
const access = resolveCompanyAccess(pool);
|
||||
|
||||
return async (googleAuthId, companyId, id, { name } = {}) => {
|
||||
if (!Number.isFinite(id)) {
|
||||
throw new Error('Customer group not found');
|
||||
}
|
||||
|
||||
const cleaned = name?.trim();
|
||||
if (!cleaned) {
|
||||
throw new Error('Name required');
|
||||
}
|
||||
|
||||
await access(googleAuthId, companyId);
|
||||
|
||||
const result = await pool
|
||||
.request()
|
||||
.input('id', id)
|
||||
.input('companyId', companyId)
|
||||
.input('name', cleaned)
|
||||
.query(UPDATE_NAME);
|
||||
|
||||
const row = result.recordset[0];
|
||||
if (!row) {
|
||||
throw new Error('Customer group not found');
|
||||
}
|
||||
return { id: row.id, name: row.name };
|
||||
};
|
||||
}
|
||||
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;
|
||||
}
|
||||
};
|
||||
}
|
||||
56
src/store/entityaddress/create.js
Normal file
56
src/store/entityaddress/create.js
Normal file
@@ -0,0 +1,56 @@
|
||||
import { CREATE } from '../../sql/entityaddress.js';
|
||||
import resolveCompanyAccess from '../companyuser/resolveCompanyAccess.js';
|
||||
import resolveEntityAccess from '../entity/resolveEntityAccess.js';
|
||||
|
||||
function shape(row) {
|
||||
return {
|
||||
id: row.id,
|
||||
entityId: row.entityId,
|
||||
firstName: row.firstName ?? null,
|
||||
name: row.name ?? null,
|
||||
street: row.street ?? null,
|
||||
streetNumber: row.streetNumber ?? null,
|
||||
zip: row.zip ?? null,
|
||||
city: row.city ?? null,
|
||||
county: row.county ?? null,
|
||||
addressType: row.addressType,
|
||||
};
|
||||
}
|
||||
|
||||
function optionalString(value) {
|
||||
if (value == null || value === '') return null;
|
||||
const s = String(value).trim();
|
||||
return s || null;
|
||||
}
|
||||
|
||||
export default function create(pool) {
|
||||
const access = resolveCompanyAccess(pool);
|
||||
const entityAccess = resolveEntityAccess(pool);
|
||||
|
||||
return async (googleAuthId, companyId, entityId, input = {}) => {
|
||||
const addressType = input.addressType?.trim?.()
|
||||
? input.addressType.trim()
|
||||
: null;
|
||||
if (!addressType) {
|
||||
throw new Error('Address type required');
|
||||
}
|
||||
|
||||
await access(googleAuthId, companyId);
|
||||
await entityAccess(companyId, entityId);
|
||||
|
||||
const result = await pool
|
||||
.request()
|
||||
.input('entityId', entityId)
|
||||
.input('firstName', optionalString(input.firstName))
|
||||
.input('name', optionalString(input.name))
|
||||
.input('street', optionalString(input.street))
|
||||
.input('streetNumber', optionalString(input.streetNumber))
|
||||
.input('zip', optionalString(input.zip))
|
||||
.input('city', optionalString(input.city))
|
||||
.input('county', optionalString(input.county))
|
||||
.input('addressType', addressType)
|
||||
.query(CREATE);
|
||||
|
||||
return shape(result.recordset[0]);
|
||||
};
|
||||
}
|
||||
27
src/store/entityaddress/delete.js
Normal file
27
src/store/entityaddress/delete.js
Normal file
@@ -0,0 +1,27 @@
|
||||
import { DELETE_BY_ENTITY_AND_ID } from '../../sql/entityaddress.js';
|
||||
import resolveCompanyAccess from '../companyuser/resolveCompanyAccess.js';
|
||||
import resolveEntityAccess from '../entity/resolveEntityAccess.js';
|
||||
|
||||
export default function del(pool) {
|
||||
const access = resolveCompanyAccess(pool);
|
||||
const entityAccess = resolveEntityAccess(pool);
|
||||
|
||||
return async (googleAuthId, companyId, entityId, id) => {
|
||||
if (!Number.isFinite(id)) {
|
||||
throw new Error('Address not found');
|
||||
}
|
||||
|
||||
await access(googleAuthId, companyId);
|
||||
await entityAccess(companyId, entityId);
|
||||
|
||||
const result = await pool
|
||||
.request()
|
||||
.input('id', id)
|
||||
.input('entityId', entityId)
|
||||
.query(DELETE_BY_ENTITY_AND_ID);
|
||||
|
||||
if (!result.rowsAffected[0]) {
|
||||
throw new Error('Address not found');
|
||||
}
|
||||
};
|
||||
}
|
||||
35
src/store/entityaddress/list.js
Normal file
35
src/store/entityaddress/list.js
Normal file
@@ -0,0 +1,35 @@
|
||||
import { LIST_BY_ENTITY } from '../../sql/entityaddress.js';
|
||||
import resolveCompanyAccess from '../companyuser/resolveCompanyAccess.js';
|
||||
import resolveEntityAccess from '../entity/resolveEntityAccess.js';
|
||||
|
||||
function shape(row) {
|
||||
return {
|
||||
id: row.id,
|
||||
entityId: row.entityId,
|
||||
firstName: row.firstName ?? null,
|
||||
name: row.name ?? null,
|
||||
street: row.street ?? null,
|
||||
streetNumber: row.streetNumber ?? null,
|
||||
zip: row.zip ?? null,
|
||||
city: row.city ?? null,
|
||||
county: row.county ?? null,
|
||||
addressType: row.addressType,
|
||||
};
|
||||
}
|
||||
|
||||
export default function list(pool) {
|
||||
const access = resolveCompanyAccess(pool);
|
||||
const entityAccess = resolveEntityAccess(pool);
|
||||
|
||||
return async (googleAuthId, companyId, entityId) => {
|
||||
await access(googleAuthId, companyId);
|
||||
await entityAccess(companyId, entityId);
|
||||
|
||||
const result = await pool
|
||||
.request()
|
||||
.input('entityId', entityId)
|
||||
.query(LIST_BY_ENTITY);
|
||||
|
||||
return result.recordset.map(shape);
|
||||
};
|
||||
}
|
||||
104
src/store/entityaddress/update.js
Normal file
104
src/store/entityaddress/update.js
Normal file
@@ -0,0 +1,104 @@
|
||||
import { GET_BY_ENTITY_AND_ID, UPDATE } from '../../sql/entityaddress.js';
|
||||
import resolveCompanyAccess from '../companyuser/resolveCompanyAccess.js';
|
||||
import resolveEntityAccess from '../entity/resolveEntityAccess.js';
|
||||
|
||||
function shape(row) {
|
||||
return {
|
||||
id: row.id,
|
||||
entityId: row.entityId,
|
||||
firstName: row.firstName ?? null,
|
||||
name: row.name ?? null,
|
||||
street: row.street ?? null,
|
||||
streetNumber: row.streetNumber ?? null,
|
||||
zip: row.zip ?? null,
|
||||
city: row.city ?? null,
|
||||
county: row.county ?? null,
|
||||
addressType: row.addressType,
|
||||
};
|
||||
}
|
||||
|
||||
function optionalString(value) {
|
||||
if (value == null || value === '') return null;
|
||||
const s = String(value).trim();
|
||||
return s || null;
|
||||
}
|
||||
|
||||
export default function update(pool) {
|
||||
const access = resolveCompanyAccess(pool);
|
||||
const entityAccess = resolveEntityAccess(pool);
|
||||
|
||||
return async (googleAuthId, companyId, entityId, id, patch = {}) => {
|
||||
if (!Number.isFinite(id)) {
|
||||
throw new Error('Address not found');
|
||||
}
|
||||
|
||||
await access(googleAuthId, companyId);
|
||||
await entityAccess(companyId, entityId);
|
||||
|
||||
const existing = await pool
|
||||
.request()
|
||||
.input('id', id)
|
||||
.input('entityId', entityId)
|
||||
.query(GET_BY_ENTITY_AND_ID);
|
||||
|
||||
const current = existing.recordset[0];
|
||||
if (!current) {
|
||||
throw new Error('Address not found');
|
||||
}
|
||||
|
||||
const firstName = Object.prototype.hasOwnProperty.call(patch, 'firstName')
|
||||
? optionalString(patch.firstName)
|
||||
: current.firstName ?? null;
|
||||
const name = Object.prototype.hasOwnProperty.call(patch, 'name')
|
||||
? optionalString(patch.name)
|
||||
: current.name ?? null;
|
||||
const street = Object.prototype.hasOwnProperty.call(patch, 'street')
|
||||
? optionalString(patch.street)
|
||||
: current.street ?? null;
|
||||
const streetNumber = Object.prototype.hasOwnProperty.call(
|
||||
patch,
|
||||
'streetNumber',
|
||||
)
|
||||
? optionalString(patch.streetNumber)
|
||||
: current.streetNumber ?? null;
|
||||
const zip = Object.prototype.hasOwnProperty.call(patch, 'zip')
|
||||
? optionalString(patch.zip)
|
||||
: current.zip ?? null;
|
||||
const city = Object.prototype.hasOwnProperty.call(patch, 'city')
|
||||
? optionalString(patch.city)
|
||||
: current.city ?? null;
|
||||
const county = Object.prototype.hasOwnProperty.call(patch, 'county')
|
||||
? optionalString(patch.county)
|
||||
: current.county ?? null;
|
||||
|
||||
let addressType = current.addressType;
|
||||
if (Object.prototype.hasOwnProperty.call(patch, 'addressType')) {
|
||||
addressType = patch.addressType?.trim?.()
|
||||
? patch.addressType.trim()
|
||||
: null;
|
||||
if (!addressType) {
|
||||
throw new Error('Address type required');
|
||||
}
|
||||
}
|
||||
|
||||
const result = await pool
|
||||
.request()
|
||||
.input('id', id)
|
||||
.input('entityId', entityId)
|
||||
.input('firstName', firstName)
|
||||
.input('name', name)
|
||||
.input('street', street)
|
||||
.input('streetNumber', streetNumber)
|
||||
.input('zip', zip)
|
||||
.input('city', city)
|
||||
.input('county', county)
|
||||
.input('addressType', addressType)
|
||||
.query(UPDATE);
|
||||
|
||||
const row = result.recordset[0];
|
||||
if (!row) {
|
||||
throw new Error('Address not found');
|
||||
}
|
||||
return shape(row);
|
||||
};
|
||||
}
|
||||
39
src/store/entitycontact/create.js
Normal file
39
src/store/entitycontact/create.js
Normal file
@@ -0,0 +1,39 @@
|
||||
import { CREATE } from '../../sql/entitycontact.js';
|
||||
import resolveCompanyAccess from '../companyuser/resolveCompanyAccess.js';
|
||||
import resolveEntityAccess from '../entity/resolveEntityAccess.js';
|
||||
|
||||
function shape(row) {
|
||||
return {
|
||||
id: row.id,
|
||||
entityId: row.entityId,
|
||||
contactType: row.contactType,
|
||||
contact: row.contact,
|
||||
};
|
||||
}
|
||||
|
||||
export default function create(pool) {
|
||||
const access = resolveCompanyAccess(pool);
|
||||
const entityAccess = resolveEntityAccess(pool);
|
||||
|
||||
return async (googleAuthId, companyId, entityId, input = {}) => {
|
||||
const contactType = input.contactType?.trim?.()
|
||||
? input.contactType.trim()
|
||||
: null;
|
||||
const contact = input.contact?.trim?.() ? input.contact.trim() : null;
|
||||
if (!contactType || !contact) {
|
||||
throw new Error('Contact required');
|
||||
}
|
||||
|
||||
await access(googleAuthId, companyId);
|
||||
await entityAccess(companyId, entityId);
|
||||
|
||||
const result = await pool
|
||||
.request()
|
||||
.input('entityId', entityId)
|
||||
.input('contactType', contactType)
|
||||
.input('contact', contact)
|
||||
.query(CREATE);
|
||||
|
||||
return shape(result.recordset[0]);
|
||||
};
|
||||
}
|
||||
27
src/store/entitycontact/delete.js
Normal file
27
src/store/entitycontact/delete.js
Normal file
@@ -0,0 +1,27 @@
|
||||
import { DELETE_BY_ENTITY_AND_ID } from '../../sql/entitycontact.js';
|
||||
import resolveCompanyAccess from '../companyuser/resolveCompanyAccess.js';
|
||||
import resolveEntityAccess from '../entity/resolveEntityAccess.js';
|
||||
|
||||
export default function del(pool) {
|
||||
const access = resolveCompanyAccess(pool);
|
||||
const entityAccess = resolveEntityAccess(pool);
|
||||
|
||||
return async (googleAuthId, companyId, entityId, id) => {
|
||||
if (!Number.isFinite(id)) {
|
||||
throw new Error('Contact not found');
|
||||
}
|
||||
|
||||
await access(googleAuthId, companyId);
|
||||
await entityAccess(companyId, entityId);
|
||||
|
||||
const result = await pool
|
||||
.request()
|
||||
.input('id', id)
|
||||
.input('entityId', entityId)
|
||||
.query(DELETE_BY_ENTITY_AND_ID);
|
||||
|
||||
if (!result.rowsAffected[0]) {
|
||||
throw new Error('Contact not found');
|
||||
}
|
||||
};
|
||||
}
|
||||
29
src/store/entitycontact/list.js
Normal file
29
src/store/entitycontact/list.js
Normal file
@@ -0,0 +1,29 @@
|
||||
import { LIST_BY_ENTITY } from '../../sql/entitycontact.js';
|
||||
import resolveCompanyAccess from '../companyuser/resolveCompanyAccess.js';
|
||||
import resolveEntityAccess from '../entity/resolveEntityAccess.js';
|
||||
|
||||
function shape(row) {
|
||||
return {
|
||||
id: row.id,
|
||||
entityId: row.entityId,
|
||||
contactType: row.contactType,
|
||||
contact: row.contact,
|
||||
};
|
||||
}
|
||||
|
||||
export default function list(pool) {
|
||||
const access = resolveCompanyAccess(pool);
|
||||
const entityAccess = resolveEntityAccess(pool);
|
||||
|
||||
return async (googleAuthId, companyId, entityId) => {
|
||||
await access(googleAuthId, companyId);
|
||||
await entityAccess(companyId, entityId);
|
||||
|
||||
const result = await pool
|
||||
.request()
|
||||
.input('entityId', entityId)
|
||||
.query(LIST_BY_ENTITY);
|
||||
|
||||
return result.recordset.map(shape);
|
||||
};
|
||||
}
|
||||
67
src/store/entitycontact/update.js
Normal file
67
src/store/entitycontact/update.js
Normal file
@@ -0,0 +1,67 @@
|
||||
import { GET_BY_ENTITY_AND_ID, UPDATE } from '../../sql/entitycontact.js';
|
||||
import resolveCompanyAccess from '../companyuser/resolveCompanyAccess.js';
|
||||
import resolveEntityAccess from '../entity/resolveEntityAccess.js';
|
||||
|
||||
function shape(row) {
|
||||
return {
|
||||
id: row.id,
|
||||
entityId: row.entityId,
|
||||
contactType: row.contactType,
|
||||
contact: row.contact,
|
||||
};
|
||||
}
|
||||
|
||||
export default function update(pool) {
|
||||
const access = resolveCompanyAccess(pool);
|
||||
const entityAccess = resolveEntityAccess(pool);
|
||||
|
||||
return async (googleAuthId, companyId, entityId, id, patch = {}) => {
|
||||
if (!Number.isFinite(id)) {
|
||||
throw new Error('Contact not found');
|
||||
}
|
||||
|
||||
await access(googleAuthId, companyId);
|
||||
await entityAccess(companyId, entityId);
|
||||
|
||||
const existing = await pool
|
||||
.request()
|
||||
.input('id', id)
|
||||
.input('entityId', entityId)
|
||||
.query(GET_BY_ENTITY_AND_ID);
|
||||
|
||||
const current = existing.recordset[0];
|
||||
if (!current) {
|
||||
throw new Error('Contact not found');
|
||||
}
|
||||
|
||||
let contactType = current.contactType;
|
||||
if (Object.prototype.hasOwnProperty.call(patch, 'contactType')) {
|
||||
contactType = patch.contactType?.trim?.()
|
||||
? patch.contactType.trim()
|
||||
: null;
|
||||
}
|
||||
|
||||
let contact = current.contact;
|
||||
if (Object.prototype.hasOwnProperty.call(patch, 'contact')) {
|
||||
contact = patch.contact?.trim?.() ? patch.contact.trim() : null;
|
||||
}
|
||||
|
||||
if (!contactType || !contact) {
|
||||
throw new Error('Contact required');
|
||||
}
|
||||
|
||||
const result = await pool
|
||||
.request()
|
||||
.input('id', id)
|
||||
.input('entityId', entityId)
|
||||
.input('contactType', contactType)
|
||||
.input('contact', contact)
|
||||
.query(UPDATE);
|
||||
|
||||
const row = result.recordset[0];
|
||||
if (!row) {
|
||||
throw new Error('Contact not found');
|
||||
}
|
||||
return shape(row);
|
||||
};
|
||||
}
|
||||
@@ -23,6 +23,7 @@ export default function create(pool) {
|
||||
|
||||
const created = await request(pool, tx)
|
||||
.input('customergroupId', customergroupId)
|
||||
.input('firstName', null)
|
||||
.input('name', cleaned)
|
||||
.query(CREATE);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user