Add supplier CRUD on entity with Lieferanten group and entityhistory
This commit is contained in:
19
README.md
19
README.md
@@ -187,6 +187,21 @@ Company-scoped brands. Auth via `resolveCompanyAccess`.
|
||||
|
||||
---
|
||||
|
||||
## `supplier`
|
||||
|
||||
Suppliers are `entity` rows in the company `customergroup` named `Lieferanten` (created implicitly on first supplier). Auth via `resolveCompanyAccess`.
|
||||
|
||||
| Method | Signature | Returns |
|
||||
| --- | --- | --- |
|
||||
| `list` | `(googleAuthId, companyId)` | `{ id, name, createdAt, updatedAt }[]` |
|
||||
| `create` | `(googleAuthId, companyId, { name })` | `{ id, name, createdAt, updatedAt }` |
|
||||
| `update` | `(googleAuthId, companyId, id, { name })` | `{ id, name, createdAt, updatedAt }` |
|
||||
| `delete` | `(googleAuthId, companyId, id)` | `void` |
|
||||
|
||||
`create` ensures `customergroup` `Lieferanten` for the company, inserts `entity` (`name` only; `first_name` null), writes `entityhistory` (`Erstellt`) with the auth `companyuser_id`. `update` renames and writes `Name geändert von "…" auf "…"` when the name actually changes; bumps `updated_at`. Empty name → `Name required`. `delete` removes the entity (`entityhistory` cascades; `manifest.entity_id` set null). Errors: `Company not found`, `Not authorized`, `Name required`, `Supplier not found`.
|
||||
|
||||
---
|
||||
|
||||
## `posterminal`
|
||||
|
||||
Company-scoped POS terminals. Auth via `resolveCompanyAccess`.
|
||||
@@ -460,6 +475,10 @@ res.set('Content-Type', thumb.contentType).send(thumb.buffer);
|
||||
| `GET /api/brand` | `brand.list(googleAuthId, companyId)` |
|
||||
| `PUT /api/brand` | `brand.upsert(googleAuthId, companyId, { name, gsprpinfo? })` |
|
||||
| `DELETE /api/brand/:id` | `brand.delete(googleAuthId, companyId, id)` |
|
||||
| `GET /api/supplier` | `supplier.list(googleAuthId, companyId)` |
|
||||
| `POST /api/supplier` | `supplier.create(googleAuthId, companyId, { name })` |
|
||||
| `PATCH /api/supplier/:id` | `supplier.update(googleAuthId, companyId, id, { name })` |
|
||||
| `DELETE /api/supplier/:id` | `supplier.delete(googleAuthId, companyId, id)` |
|
||||
| `GET /api/posterminal` | `posterminal.list(googleAuthId, companyId)` |
|
||||
| `PUT /api/posterminal` | `posterminal.upsert(googleAuthId, companyId, { id?, name, token?, pairingpin })` |
|
||||
| `DELETE /api/posterminal/:id` | `posterminal.delete(googleAuthId, companyId, id)` |
|
||||
|
||||
@@ -274,6 +274,7 @@ const CLUSTERS = [
|
||||
'companysetting',
|
||||
'customergroup',
|
||||
'entity',
|
||||
'entityhistory',
|
||||
'numbers',
|
||||
],
|
||||
},
|
||||
|
||||
144
schema-graph.dot
144
schema-graph.dot
File diff suppressed because one or more lines are too long
2605
schema-graph.svg
2605
schema-graph.svg
File diff suppressed because it is too large
Load Diff
|
Before Width: | Height: | Size: 161 KiB After Width: | Height: | Size: 169 KiB |
@@ -158,6 +158,31 @@ const MENU = {
|
||||
},
|
||||
],
|
||||
},
|
||||
supplier: {
|
||||
label: 'supplier',
|
||||
items: [
|
||||
{
|
||||
value: 'supplier.list',
|
||||
label: 'list',
|
||||
hint: 'googleAuthId, companyId → rows',
|
||||
},
|
||||
{
|
||||
value: 'supplier.create',
|
||||
label: 'create',
|
||||
hint: 'googleAuthId, companyId, name → row',
|
||||
},
|
||||
{
|
||||
value: 'supplier.update',
|
||||
label: 'update',
|
||||
hint: 'googleAuthId, companyId, id, name → row',
|
||||
},
|
||||
{
|
||||
value: 'supplier.delete',
|
||||
label: 'delete',
|
||||
hint: 'googleAuthId, companyId, id',
|
||||
},
|
||||
],
|
||||
},
|
||||
category: {
|
||||
label: 'category',
|
||||
items: [
|
||||
@@ -626,6 +651,67 @@ async function runMethod(client, action) {
|
||||
await client.brand.delete(Number(googleAuthId), Number(companyId), id);
|
||||
return ['ok', true];
|
||||
}
|
||||
case 'supplier.list': {
|
||||
const googleAuthId = await askInt('google_auth id');
|
||||
if (googleAuthId == null) return;
|
||||
const companyId = await askInt('company id');
|
||||
if (companyId == null) return;
|
||||
return [
|
||||
'result[]',
|
||||
await client.supplier.list(Number(googleAuthId), Number(companyId)),
|
||||
];
|
||||
}
|
||||
case 'supplier.create': {
|
||||
const googleAuthId = await askInt('google_auth id');
|
||||
if (googleAuthId == null) return;
|
||||
const companyId = await askInt('company id');
|
||||
if (companyId == null) return;
|
||||
const name = await askText('name', {
|
||||
validate: (v) => (!v?.trim() ? 'Required' : undefined),
|
||||
});
|
||||
if (name == null) return;
|
||||
return [
|
||||
'result',
|
||||
await client.supplier.create(Number(googleAuthId), Number(companyId), {
|
||||
name,
|
||||
}),
|
||||
];
|
||||
}
|
||||
case 'supplier.update': {
|
||||
const googleAuthId = await askInt('google_auth id');
|
||||
if (googleAuthId == null) return;
|
||||
const companyId = await askInt('company id');
|
||||
if (companyId == null) return;
|
||||
const id = await askInt('supplier id');
|
||||
if (id == null) return;
|
||||
const name = await askText('name', {
|
||||
validate: (v) => (!v?.trim() ? 'Required' : undefined),
|
||||
});
|
||||
if (name == null) return;
|
||||
return [
|
||||
'result',
|
||||
await client.supplier.update(
|
||||
Number(googleAuthId),
|
||||
Number(companyId),
|
||||
Number(id),
|
||||
{ name },
|
||||
),
|
||||
];
|
||||
}
|
||||
case 'supplier.delete': {
|
||||
const googleAuthId = await askInt('google_auth id');
|
||||
if (googleAuthId == null) return;
|
||||
const companyId = await askInt('company id');
|
||||
if (companyId == null) return;
|
||||
const id = await askInt('supplier id');
|
||||
if (id == null) return;
|
||||
await client.supplier.delete(
|
||||
Number(googleAuthId),
|
||||
Number(companyId),
|
||||
Number(id),
|
||||
);
|
||||
return ['ok', true];
|
||||
}
|
||||
case 'lang.create': {
|
||||
const iso639 = await askText('ISO 639', {
|
||||
placeholder: 'de',
|
||||
|
||||
11
src/index.js
11
src/index.js
@@ -39,6 +39,10 @@ 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 supplierList from './store/supplier/list.js';
|
||||
import supplierCreate from './store/supplier/create.js';
|
||||
import supplierUpdate from './store/supplier/update.js';
|
||||
import supplierDelete from './store/supplier/delete.js';
|
||||
import posterminalList from './store/posterminal/list.js';
|
||||
import posterminalUpsert from './store/posterminal/upsert.js';
|
||||
import posterminalDelete from './store/posterminal/delete.js';
|
||||
@@ -149,6 +153,13 @@ export default class LibDashStore {
|
||||
delete: brandDelete(pool),
|
||||
};
|
||||
|
||||
this.supplier = {
|
||||
list: supplierList(pool),
|
||||
create: supplierCreate(pool),
|
||||
update: supplierUpdate(pool),
|
||||
delete: supplierDelete(pool),
|
||||
};
|
||||
|
||||
this.posterminal = {
|
||||
list: posterminalList(pool),
|
||||
upsert: posterminalUpsert(pool),
|
||||
|
||||
@@ -155,13 +155,30 @@ BEGIN
|
||||
CREATE TABLE entity (
|
||||
id INT IDENTITY(1,1) PRIMARY KEY,
|
||||
customergroup_id INT NOT NULL,
|
||||
firstname NVARCHAR(255) NOT NULL,
|
||||
lastname NVARCHAR(255) NOT NULL,
|
||||
first_name NVARCHAR(255) NULL,
|
||||
name NVARCHAR(255) NOT NULL,
|
||||
created_at DATETIME2 NOT NULL CONSTRAINT DF_entity_created_at DEFAULT SYSUTCDATETIME(),
|
||||
updated_at DATETIME2 NOT NULL CONSTRAINT DF_entity_updated_at DEFAULT SYSUTCDATETIME(),
|
||||
CONSTRAINT FK_entity_customergroup FOREIGN KEY (customergroup_id) REFERENCES customergroup(id) ON DELETE CASCADE
|
||||
);
|
||||
END
|
||||
`;
|
||||
|
||||
export const INIT_ENTITYHISTORY = `
|
||||
IF NOT EXISTS (SELECT * FROM sys.tables WHERE name = 'entityhistory')
|
||||
BEGIN
|
||||
CREATE TABLE entityhistory (
|
||||
id INT IDENTITY(1,1) PRIMARY KEY,
|
||||
entity_id INT NOT NULL,
|
||||
companyuser_id INT NULL,
|
||||
[date] DATETIME2 NOT NULL CONSTRAINT DF_entityhistory_date DEFAULT SYSUTCDATETIME(),
|
||||
[text] NVARCHAR(MAX) NOT NULL,
|
||||
CONSTRAINT FK_entityhistory_entity FOREIGN KEY (entity_id) REFERENCES entity(id) ON DELETE CASCADE,
|
||||
CONSTRAINT FK_entityhistory_companyuser FOREIGN KEY (companyuser_id) REFERENCES companyuser(id) ON DELETE NO ACTION
|
||||
);
|
||||
END
|
||||
`;
|
||||
|
||||
export const INIT_UNIT = `
|
||||
IF NOT EXISTS (SELECT * FROM sys.tables WHERE name = 'unit')
|
||||
BEGIN
|
||||
@@ -453,7 +470,9 @@ BEGIN
|
||||
id INT IDENTITY(1,1) PRIMARY KEY,
|
||||
[number] NVARCHAR(100) NOT NULL,
|
||||
[type] NVARCHAR(100) NOT NULL,
|
||||
[date] DATETIME2 NOT NULL CONSTRAINT DF_manifest_date DEFAULT SYSUTCDATETIME()
|
||||
entity_id INT NULL,
|
||||
[date] DATETIME2 NOT NULL CONSTRAINT DF_manifest_date DEFAULT SYSUTCDATETIME(),
|
||||
CONSTRAINT FK_manifest_entity FOREIGN KEY (entity_id) REFERENCES entity(id) ON DELETE SET NULL
|
||||
);
|
||||
END
|
||||
`;
|
||||
@@ -637,6 +656,7 @@ export const SCHEMA_SCRIPTS = [
|
||||
INIT_GET_NEXT_NUMBER_WITH_PREFIX_AND_PADDING,
|
||||
INIT_COMPANYUSER,
|
||||
INIT_ARTICLEHISTORY,
|
||||
INIT_ENTITYHISTORY,
|
||||
INIT_SYNCPOS,
|
||||
INIT_COMPANYSETTING,
|
||||
INIT_POSTERMINAL,
|
||||
|
||||
@@ -15,3 +15,9 @@ UPDATE customergroup
|
||||
SET company_id = @companyId
|
||||
WHERE id = @id
|
||||
`;
|
||||
|
||||
export const GET_BY_COMPANY_AND_NAME = `
|
||||
SELECT id
|
||||
FROM customergroup
|
||||
WHERE company_id = @companyId AND name = @name
|
||||
`;
|
||||
|
||||
59
src/sql/entity.js
Normal file
59
src/sql/entity.js
Normal file
@@ -0,0 +1,59 @@
|
||||
export const SUPPLIER_GROUP_NAME = 'Lieferanten';
|
||||
|
||||
export const LIST_SUPPLIERS_BY_COMPANY = `
|
||||
SELECT
|
||||
e.id,
|
||||
e.name,
|
||||
e.created_at AS createdAt,
|
||||
e.updated_at AS updatedAt
|
||||
FROM entity e
|
||||
INNER JOIN customergroup cg ON cg.id = e.customergroup_id
|
||||
WHERE cg.company_id = @companyId
|
||||
AND cg.name = @groupName
|
||||
ORDER BY e.name, e.id
|
||||
`;
|
||||
|
||||
export const GET_SUPPLIER_BY_ID = `
|
||||
SELECT
|
||||
e.id,
|
||||
e.name,
|
||||
e.created_at AS createdAt,
|
||||
e.updated_at AS updatedAt
|
||||
FROM entity e
|
||||
INNER JOIN customergroup cg ON cg.id = e.customergroup_id
|
||||
WHERE e.id = @id
|
||||
AND cg.company_id = @companyId
|
||||
AND cg.name = @groupName
|
||||
`;
|
||||
|
||||
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)
|
||||
`;
|
||||
|
||||
export const UPDATE_NAME = `
|
||||
UPDATE entity
|
||||
SET name = @name, updated_at = SYSUTCDATETIME()
|
||||
OUTPUT INSERTED.id, INSERTED.name, INSERTED.created_at AS createdAt, INSERTED.updated_at AS updatedAt
|
||||
WHERE id = @id
|
||||
AND customergroup_id = @customergroupId
|
||||
`;
|
||||
|
||||
export const DELETE_BY_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
|
||||
AND cg.name = @groupName
|
||||
`;
|
||||
|
||||
export const GET_GROUP_ID_FOR_SUPPLIER = `
|
||||
SELECT e.customergroup_id AS customergroupId, e.name
|
||||
FROM entity e
|
||||
INNER JOIN customergroup cg ON cg.id = e.customergroup_id
|
||||
WHERE e.id = @id
|
||||
AND cg.company_id = @companyId
|
||||
AND cg.name = @groupName
|
||||
`;
|
||||
11
src/sql/entityhistory.js
Normal file
11
src/sql/entityhistory.js
Normal file
@@ -0,0 +1,11 @@
|
||||
export const INSERT = `
|
||||
INSERT INTO entityhistory (entity_id, companyuser_id, [text])
|
||||
VALUES (@entityId, @companyuserId, @text)
|
||||
`;
|
||||
|
||||
export const LIST_BY_ENTITY_ID = `
|
||||
SELECT id, companyuser_id AS companyuserId, [date], [text]
|
||||
FROM entityhistory
|
||||
WHERE entity_id = @entityId
|
||||
ORDER BY [date] DESC, id DESC
|
||||
`;
|
||||
@@ -19,6 +19,7 @@ export const TABLES = [
|
||||
'articlepic',
|
||||
'articleean',
|
||||
'articlehistory',
|
||||
'entityhistory',
|
||||
'article',
|
||||
'brand',
|
||||
'articlelist',
|
||||
|
||||
@@ -23,6 +23,7 @@ export default function resolveCompanyAccess(pool) {
|
||||
rootCategoryId: row.rootCategoryId,
|
||||
languageId: row.languageId,
|
||||
posCustomerGroupId: row.posCustomerGroupId ?? null,
|
||||
companyuserId: row.companyuserId,
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
54
src/store/supplier/create.js
Normal file
54
src/store/supplier/create.js
Normal file
@@ -0,0 +1,54 @@
|
||||
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 ensureLieferantenGroup from './ensureLieferantenGroup.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');
|
||||
}
|
||||
|
||||
const { companyuserId } = await access(googleAuthId, companyId);
|
||||
|
||||
const tx = pool.transaction();
|
||||
await tx.begin();
|
||||
|
||||
try {
|
||||
const customergroupId = await ensureLieferantenGroup(pool, companyId, tx);
|
||||
|
||||
const created = await request(pool, tx)
|
||||
.input('customergroupId', customergroupId)
|
||||
.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 {
|
||||
id: row.id,
|
||||
name: row.name,
|
||||
createdAt: row.createdAt,
|
||||
updatedAt: row.updatedAt,
|
||||
};
|
||||
} catch (err) {
|
||||
try {
|
||||
await tx.rollback();
|
||||
} catch {
|
||||
// ignore
|
||||
}
|
||||
throw err;
|
||||
}
|
||||
};
|
||||
}
|
||||
25
src/store/supplier/delete.js
Normal file
25
src/store/supplier/delete.js
Normal file
@@ -0,0 +1,25 @@
|
||||
import { DELETE_BY_ID, SUPPLIER_GROUP_NAME } 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('Supplier not found');
|
||||
}
|
||||
|
||||
await access(googleAuthId, companyId);
|
||||
|
||||
const result = await pool
|
||||
.request()
|
||||
.input('id', id)
|
||||
.input('companyId', companyId)
|
||||
.input('groupName', SUPPLIER_GROUP_NAME)
|
||||
.query(DELETE_BY_ID);
|
||||
|
||||
if (!result.rowsAffected[0]) {
|
||||
throw new Error('Supplier not found');
|
||||
}
|
||||
};
|
||||
}
|
||||
26
src/store/supplier/ensureLieferantenGroup.js
Normal file
26
src/store/supplier/ensureLieferantenGroup.js
Normal file
@@ -0,0 +1,26 @@
|
||||
import {
|
||||
CREATE,
|
||||
GET_BY_COMPANY_AND_NAME,
|
||||
} from '../../sql/customergroup.js';
|
||||
import { SUPPLIER_GROUP_NAME } from '../../sql/entity.js';
|
||||
|
||||
/** Ensure company has customergroup "Lieferanten"; return its id. */
|
||||
export default async function ensureLieferantenGroup(pool, companyId, tx) {
|
||||
const req = () => (tx ?? pool).request();
|
||||
|
||||
const existing = await req()
|
||||
.input('companyId', companyId)
|
||||
.input('name', SUPPLIER_GROUP_NAME)
|
||||
.query(GET_BY_COMPANY_AND_NAME);
|
||||
|
||||
if (existing.recordset[0]) {
|
||||
return existing.recordset[0].id;
|
||||
}
|
||||
|
||||
const created = await req()
|
||||
.input('companyId', companyId)
|
||||
.input('name', SUPPLIER_GROUP_NAME)
|
||||
.query(CREATE);
|
||||
|
||||
return created.recordset[0].id;
|
||||
}
|
||||
23
src/store/supplier/list.js
Normal file
23
src/store/supplier/list.js
Normal file
@@ -0,0 +1,23 @@
|
||||
import { LIST_SUPPLIERS_BY_COMPANY, SUPPLIER_GROUP_NAME } from '../../sql/entity.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)
|
||||
.input('groupName', SUPPLIER_GROUP_NAME)
|
||||
.query(LIST_SUPPLIERS_BY_COMPANY);
|
||||
|
||||
return result.recordset.map((row) => ({
|
||||
id: row.id,
|
||||
name: row.name,
|
||||
createdAt: row.createdAt,
|
||||
updatedAt: row.updatedAt,
|
||||
}));
|
||||
};
|
||||
}
|
||||
93
src/store/supplier/update.js
Normal file
93
src/store/supplier/update.js
Normal file
@@ -0,0 +1,93 @@
|
||||
import {
|
||||
GET_GROUP_ID_FOR_SUPPLIER,
|
||||
GET_SUPPLIER_BY_ID,
|
||||
SUPPLIER_GROUP_NAME,
|
||||
UPDATE_NAME,
|
||||
} 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,
|
||||
name: row.name,
|
||||
createdAt: row.createdAt,
|
||||
updatedAt: row.updatedAt,
|
||||
};
|
||||
}
|
||||
|
||||
export default function update(pool) {
|
||||
const access = resolveCompanyAccess(pool);
|
||||
|
||||
return async (googleAuthId, companyId, id, { name } = {}) => {
|
||||
if (!Number.isFinite(id)) {
|
||||
throw new Error('Supplier not found');
|
||||
}
|
||||
|
||||
const cleaned = name?.trim();
|
||||
if (!cleaned) {
|
||||
throw new Error('Name required');
|
||||
}
|
||||
|
||||
const { companyuserId } = await access(googleAuthId, companyId);
|
||||
|
||||
const existing = await pool
|
||||
.request()
|
||||
.input('id', id)
|
||||
.input('companyId', companyId)
|
||||
.input('groupName', SUPPLIER_GROUP_NAME)
|
||||
.query(GET_GROUP_ID_FOR_SUPPLIER);
|
||||
|
||||
const current = existing.recordset[0];
|
||||
if (!current) {
|
||||
throw new Error('Supplier not found');
|
||||
}
|
||||
|
||||
if (current.name === cleaned) {
|
||||
const same = await pool
|
||||
.request()
|
||||
.input('id', id)
|
||||
.input('companyId', companyId)
|
||||
.input('groupName', SUPPLIER_GROUP_NAME)
|
||||
.query(GET_SUPPLIER_BY_ID);
|
||||
return shape(same.recordset[0]);
|
||||
}
|
||||
|
||||
const tx = pool.transaction();
|
||||
await tx.begin();
|
||||
|
||||
try {
|
||||
const updated = await request(pool, tx)
|
||||
.input('id', id)
|
||||
.input('customergroupId', current.customergroupId)
|
||||
.input('name', cleaned)
|
||||
.query(UPDATE_NAME);
|
||||
|
||||
const row = updated.recordset[0];
|
||||
if (!row) {
|
||||
throw new Error('Supplier not found');
|
||||
}
|
||||
|
||||
await request(pool, tx)
|
||||
.input('entityId', id)
|
||||
.input('companyuserId', companyuserId)
|
||||
.input(
|
||||
'text',
|
||||
`Name geändert von "${current.name}" auf "${cleaned}"`,
|
||||
)
|
||||
.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