Add entity address/contact tables and CRUD for entity resources

This commit is contained in:
seb
2026-08-06 05:43:50 +02:00
parent 0b7e40ac9c
commit 3b77a1c946
32 changed files with 3078 additions and 1363 deletions

View File

@@ -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),

View File

@@ -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,

View File

@@ -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
`;

View File

@@ -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
View 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
View 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
`;

View File

@@ -20,6 +20,8 @@ export const TABLES = [
'articleean',
'articlehistory',
'entityhistory',
'entityaddress',
'entitycontact',
'article',
'brand',
'articlelist',

View 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 };
};
}

View 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;
}
};
}

View 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,
}));
};
}

View 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;
};
}

View 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 };
};
}

View 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;
}
};
}

View 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');
}
};
}

View 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
View 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);
};
}

View 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
View 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;
}
};
}

View 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]);
};
}

View 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');
}
};
}

View 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);
};
}

View 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);
};
}

View 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]);
};
}

View 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');
}
};
}

View 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);
};
}

View 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);
};
}

View File

@@ -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);