Add POS terminal category visibility system
Add posterminal and posterminalcategory tables to control which categories are visible on each POS terminal. The existence of a row in posterminalcategory means the category is visible on that terminal. Store methods: - posterminal.list/upsert/delete - CRUD for terminals - posterminal.setOn/setOff - toggle single category on a terminal - posterminal.setOnAll/setOffAll - toggle all categories on a terminal - posterminal.getCategoryPosterminalIds - internal, used by category tree The category tree (getTreeForCompany) now includes a posterminalIds array on each node listing which terminals have that category enabled.
This commit is contained in:
38
README.md
38
README.md
@@ -182,6 +182,37 @@ Company-scoped brands. Auth via `resolveCompanyAccess`.
|
||||
|
||||
---
|
||||
|
||||
## `posterminal`
|
||||
|
||||
Company-scoped POS terminals. Auth via `resolveCompanyAccess`.
|
||||
|
||||
| Method | Signature | Returns |
|
||||
| --- | --- | --- |
|
||||
| `list` | `(googleAuthId, companyId)` | `{ id, token, pairingpin }[]` |
|
||||
| `upsert` | `(googleAuthId, companyId, { id?, token?, pairingpin })` | `{ id, token, pairingpin }` |
|
||||
| `delete` | `(googleAuthId, companyId, id)` | `void` |
|
||||
| `setOn` | `(googleAuthId, companyId, posterminalId, categoryId)` | `{ id, categoryIds[] }` |
|
||||
| `setOff` | `(googleAuthId, companyId, posterminalId, categoryId)` | `{ id, categoryIds[] }` |
|
||||
| `setOnAll` | `(googleAuthId, companyId, posterminalId)` | `{ id, categoryIds[] }` |
|
||||
| `setOffAll` | `(googleAuthId, companyId, posterminalId)` | `{ id, categoryIds[] }` |
|
||||
| `getCategoryPosterminalIds` | `(companyId)` | `{ [categoryId]: posterminalId[] }` (internal, used by category tree) |
|
||||
|
||||
`posterminal.company_id` **NOT NULL**. `token` is nullable and unique (pairing code assigned during setup). `pairingpin` is required.
|
||||
|
||||
`upsert`: if `id` is provided, updates the existing posterminal (must belong to company); otherwise creates a new one. Errors: `Company not found`, `Not authorized`, `Invalid posterminal`, `Posterminal not found`.
|
||||
|
||||
`setOn` / `setOff`: toggle a single category's visibility on a single terminal. `setOn` inserts the row (idempotent — no error if already present); `setOff` deletes it (idempotent). Both return the updated `categoryIds` for that terminal. Errors: `Company not found`, `Not authorized`, `Posterminal not found`, `Invalid posterminal or category`.
|
||||
|
||||
`setOnAll` / `setOffAll`: toggle **all** company categories on a single terminal. `setOnAll` walks the company's category tree and inserts any missing links; `setOffAll` removes all category links for the terminal. Both return the updated `categoryIds` for that terminal. Errors: `Company not found`, `Not authorized`, `Posterminal not found`, `Invalid posterminal`.
|
||||
|
||||
`delete` cascades to `posterminalcategory`. Errors: `Company not found`, `Not authorized`, `Posterminal not found`.
|
||||
|
||||
### Category tree integration
|
||||
|
||||
`getTreeForCompany` returns a `posterminalIds` array on each tree node — the list of posterminal ids (of the current company) for which that category is visible. A category with an empty `posterminalIds` array is visible on all terminals (or has no terminal-specific filtering).
|
||||
|
||||
---
|
||||
|
||||
## `category`
|
||||
|
||||
| Method | Signature | Returns |
|
||||
@@ -335,6 +366,13 @@ res.set('Content-Type', thumb.contentType).send(thumb.buffer);
|
||||
| `GET /api/brand?companyId=` | `brand.list(googleAuthId, companyId)` |
|
||||
| `PUT /api/brand` | `brand.upsert(googleAuthId, companyId, { name, gsprpinfo? })` |
|
||||
| `DELETE /api/brand/:id` | `brand.delete(googleAuthId, companyId, id)` |
|
||||
| `GET /api/posterminal?companyId=` | `posterminal.list(googleAuthId, companyId)` |
|
||||
| `PUT /api/posterminal` | `posterminal.upsert(googleAuthId, companyId, { id?, token?, pairingpin })` |
|
||||
| `DELETE /api/posterminal/:id` | `posterminal.delete(googleAuthId, companyId, id)` |
|
||||
| `PUT /api/posterminal/:id/category/:categoryId` | `posterminal.setOn(googleAuthId, companyId, id, categoryId)` |
|
||||
| `DELETE /api/posterminal/:id/category/:categoryId` | `posterminal.setOff(googleAuthId, companyId, id, categoryId)` |
|
||||
| `PUT /api/posterminal/:id/categories` | `posterminal.setOnAll(googleAuthId, companyId, id)` |
|
||||
| `DELETE /api/posterminal/:id/categories` | `posterminal.setOffAll(googleAuthId, companyId, id)` |
|
||||
| `POST /api/article` | `article.createUnderParent({ googleAuthId, parentId, name })` |
|
||||
| `GET /api/article/:sku` | `article.getBySku(googleAuthId, sku)` |
|
||||
| `PATCH /api/article/:sku` | `article.updateBySku(googleAuthId, sku, { name?, unitamount?, unitSi? })` |
|
||||
|
||||
File diff suppressed because one or more lines are too long
2123
schema-graph.svg
2123
schema-graph.svg
File diff suppressed because it is too large
Load Diff
|
Before Width: | Height: | Size: 148 KiB After Width: | Height: | Size: 158 KiB |
19
src/index.js
19
src/index.js
@@ -34,6 +34,14 @@ 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 posterminalList from './store/posterminal/list.js';
|
||||
import posterminalUpsert from './store/posterminal/upsert.js';
|
||||
import posterminalDelete from './store/posterminal/delete.js';
|
||||
import posterminalSetOn from './store/posterminal/setOn.js';
|
||||
import posterminalSetOff from './store/posterminal/setOff.js';
|
||||
import posterminalSetOnAll from './store/posterminal/setOnAll.js';
|
||||
import posterminalSetOffAll from './store/posterminal/setOffAll.js';
|
||||
import posterminalGetCategoryPosterminalIds from './store/posterminal/getCategoryPosterminalIds.js';
|
||||
import langCreate from './store/lang/create.js';
|
||||
import langDelete from './store/lang/delete.js';
|
||||
import langList from './store/lang/list.js';
|
||||
@@ -129,6 +137,17 @@ export default class LibDashStore {
|
||||
delete: brandDelete(pool),
|
||||
};
|
||||
|
||||
this.posterminal = {
|
||||
list: posterminalList(pool),
|
||||
upsert: posterminalUpsert(pool),
|
||||
delete: posterminalDelete(pool),
|
||||
setOn: posterminalSetOn(pool),
|
||||
setOff: posterminalSetOff(pool),
|
||||
setOnAll: posterminalSetOnAll(pool),
|
||||
setOffAll: posterminalSetOffAll(pool),
|
||||
getCategoryPosterminalIds: posterminalGetCategoryPosterminalIds(pool),
|
||||
};
|
||||
|
||||
this.google_auth = {
|
||||
add: googleAuthAdd(pool),
|
||||
getByEmail: googleAuthGetByEmail(pool),
|
||||
|
||||
@@ -568,6 +568,34 @@ BEGIN
|
||||
END
|
||||
`;
|
||||
|
||||
export const INIT_POSTERMINAL = `
|
||||
IF NOT EXISTS (SELECT * FROM sys.tables WHERE name = 'posterminal')
|
||||
BEGIN
|
||||
CREATE TABLE posterminal (
|
||||
id INT IDENTITY(1,1) PRIMARY KEY,
|
||||
company_id INT NOT NULL,
|
||||
token NVARCHAR(255) NULL,
|
||||
pairingpin NVARCHAR(50) NOT NULL,
|
||||
CONSTRAINT FK_posterminal_company FOREIGN KEY (company_id) REFERENCES company(id) ON DELETE CASCADE,
|
||||
CONSTRAINT UQ_posterminal_token UNIQUE (token)
|
||||
);
|
||||
END
|
||||
`;
|
||||
|
||||
export const INIT_POSTERMINALCATEGORY = `
|
||||
IF NOT EXISTS (SELECT * FROM sys.tables WHERE name = 'posterminalcategory')
|
||||
BEGIN
|
||||
CREATE TABLE posterminalcategory (
|
||||
id INT IDENTITY(1,1) PRIMARY KEY,
|
||||
posterminal_id INT NOT NULL,
|
||||
category_id INT NOT NULL,
|
||||
CONSTRAINT FK_posterminalcategory_posterminal FOREIGN KEY (posterminal_id) REFERENCES posterminal(id) ON DELETE CASCADE,
|
||||
CONSTRAINT FK_posterminalcategory_category FOREIGN KEY (category_id) REFERENCES category(id) ON DELETE NO ACTION,
|
||||
CONSTRAINT UQ_posterminalcategory_posterminal_category UNIQUE (posterminal_id, category_id)
|
||||
);
|
||||
END
|
||||
`;
|
||||
|
||||
/** DDL scripts in dependency order for schema init. */
|
||||
export const SCHEMA_SCRIPTS = [
|
||||
INIT_META,
|
||||
@@ -604,4 +632,6 @@ export const SCHEMA_SCRIPTS = [
|
||||
INIT_COMPANYUSER,
|
||||
INIT_SYNCPOS,
|
||||
INIT_COMPANYSETTING,
|
||||
INIT_POSTERMINAL,
|
||||
INIT_POSTERMINALCATEGORY,
|
||||
];
|
||||
|
||||
@@ -10,6 +10,10 @@ FROM company
|
||||
WHERE id = @id
|
||||
`;
|
||||
|
||||
export const GET_COMPANY_ROOT_CATEGORY = `
|
||||
SELECT root_category_id AS rootCategoryId FROM company WHERE id = @companyId
|
||||
`;
|
||||
|
||||
export const CREATE = `
|
||||
INSERT INTO company (
|
||||
name,
|
||||
|
||||
89
src/sql/posterminal.js
Normal file
89
src/sql/posterminal.js
Normal file
@@ -0,0 +1,89 @@
|
||||
export const LIST_BY_COMPANY = `
|
||||
SELECT
|
||||
id,
|
||||
token,
|
||||
pairingpin
|
||||
FROM posterminal
|
||||
WHERE company_id = @companyId
|
||||
ORDER BY id
|
||||
`;
|
||||
|
||||
export const GET_BY_ID = `
|
||||
SELECT
|
||||
id,
|
||||
company_id AS companyId,
|
||||
token,
|
||||
pairingpin
|
||||
FROM posterminal
|
||||
WHERE id = @id
|
||||
`;
|
||||
|
||||
export const CREATE = `
|
||||
INSERT INTO posterminal (company_id, token, pairingpin)
|
||||
OUTPUT INSERTED.id, INSERTED.token, INSERTED.pairingpin
|
||||
VALUES (@companyId, @token, @pairingpin)
|
||||
`;
|
||||
|
||||
export const UPDATE = `
|
||||
UPDATE posterminal
|
||||
SET token = @token, pairingpin = @pairingpin
|
||||
WHERE id = @id AND company_id = @companyId
|
||||
`;
|
||||
|
||||
export const DELETE_BY_COMPANY_AND_ID = `
|
||||
DELETE FROM posterminal
|
||||
WHERE id = @id AND company_id = @companyId
|
||||
`;
|
||||
|
||||
export const LIST_CATEGORY_IDS = `
|
||||
SELECT category_id AS categoryId
|
||||
FROM posterminalcategory
|
||||
WHERE posterminal_id = @posterminalId
|
||||
`;
|
||||
|
||||
/** setOn: insert if not exists (single terminal + single category). */
|
||||
export const SET_ON = `
|
||||
IF NOT EXISTS (
|
||||
SELECT 1 FROM posterminalcategory
|
||||
WHERE posterminal_id = @posterminalId AND category_id = @categoryId
|
||||
)
|
||||
INSERT INTO posterminalcategory (posterminal_id, category_id)
|
||||
VALUES (@posterminalId, @categoryId)
|
||||
`;
|
||||
|
||||
/** setOff: remove a single category from a single terminal. */
|
||||
export const SET_OFF = `
|
||||
DELETE FROM posterminalcategory
|
||||
WHERE posterminal_id = @posterminalId AND category_id = @categoryId
|
||||
`;
|
||||
|
||||
/** setOnAll: enable ALL company categories on one terminal. */
|
||||
export const SET_ON_ALL = `
|
||||
;WITH cats AS (
|
||||
SELECT id FROM category WHERE id = @rootCategoryId
|
||||
UNION ALL
|
||||
SELECT c.id FROM category c INNER JOIN cats ON c.parent_id = cats.id
|
||||
)
|
||||
INSERT INTO posterminalcategory (posterminal_id, category_id)
|
||||
SELECT @posterminalId, cats.id
|
||||
FROM cats
|
||||
WHERE NOT EXISTS (
|
||||
SELECT 1 FROM posterminalcategory pc
|
||||
WHERE pc.posterminal_id = @posterminalId AND pc.category_id = cats.id
|
||||
)
|
||||
`;
|
||||
|
||||
/** setOffAll: remove ALL category links from one terminal. */
|
||||
export const SET_OFF_ALL = `
|
||||
DELETE FROM posterminalcategory WHERE posterminal_id = @posterminalId
|
||||
`;
|
||||
|
||||
/** For category tree: map category -> posterminal ids (company-scoped). */
|
||||
export const POSTERMINAL_IDS_BY_COMPANY = `
|
||||
SELECT
|
||||
pc.category_id AS categoryId,
|
||||
pc.posterminal_id AS posterminalId
|
||||
FROM posterminalcategory pc
|
||||
INNER JOIN posterminal pt ON pt.id = pc.posterminal_id
|
||||
WHERE pt.company_id = @companyId
|
||||
`;
|
||||
@@ -3,6 +3,8 @@ export const TABLES = [
|
||||
'companyuser',
|
||||
'syncpos',
|
||||
'companysetting',
|
||||
'posterminalcategory',
|
||||
'posterminal',
|
||||
'numbers',
|
||||
'manifestarticle',
|
||||
'manifest',
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
import { GET_TREE_FOR_COMPANY } from '../../sql/category.js';
|
||||
import resolveCompanyAccess from '../companyuser/resolveCompanyAccess.js';
|
||||
import queryCategoryArticles from './queryCategoryArticles.js';
|
||||
import getCategoryPosterminalIds from '../posterminal/getCategoryPosterminalIds.js';
|
||||
|
||||
export default function getTreeForCompany(pool) {
|
||||
const access = resolveCompanyAccess(pool);
|
||||
const articles = queryCategoryArticles(pool);
|
||||
const posterminalIds = getCategoryPosterminalIds(pool);
|
||||
|
||||
return async (googleAuthId, companyId) => {
|
||||
const { rootCategoryId, languageId, posCustomerGroupId } = await access(
|
||||
@@ -12,18 +14,24 @@ export default function getTreeForCompany(pool) {
|
||||
companyId,
|
||||
);
|
||||
|
||||
const [treeResult, articleRows] = await Promise.all([
|
||||
const [treeResult, articleRows, ptMap] = await Promise.all([
|
||||
pool
|
||||
.request()
|
||||
.input('rootId', rootCategoryId)
|
||||
.input('languageId', languageId)
|
||||
.query(GET_TREE_FOR_COMPANY),
|
||||
articles({ categoryId: rootCategoryId, languageId, posCustomerGroupId }),
|
||||
posterminalIds(companyId),
|
||||
]);
|
||||
|
||||
const tree = treeResult.recordset.map((row) => ({
|
||||
...row,
|
||||
posterminalIds: ptMap[row.id] ?? [],
|
||||
}));
|
||||
|
||||
return {
|
||||
languageId,
|
||||
tree: treeResult.recordset,
|
||||
tree,
|
||||
articles: articleRows,
|
||||
};
|
||||
};
|
||||
|
||||
24
src/store/posterminal/delete.js
Normal file
24
src/store/posterminal/delete.js
Normal file
@@ -0,0 +1,24 @@
|
||||
import { DELETE_BY_COMPANY_AND_ID } from '../../sql/posterminal.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('Posterminal 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('Posterminal not found');
|
||||
}
|
||||
};
|
||||
}
|
||||
19
src/store/posterminal/getCategoryPosterminalIds.js
Normal file
19
src/store/posterminal/getCategoryPosterminalIds.js
Normal file
@@ -0,0 +1,19 @@
|
||||
import { POSTERMINAL_IDS_BY_COMPANY } from '../../sql/posterminal.js';
|
||||
|
||||
export default function getCategoryPosterminalIds(pool) {
|
||||
return async (companyId) => {
|
||||
const result = await pool
|
||||
.request()
|
||||
.input('companyId', companyId)
|
||||
.query(POSTERMINAL_IDS_BY_COMPANY);
|
||||
|
||||
const map = {};
|
||||
for (const row of result.recordset) {
|
||||
if (!map[row.categoryId]) {
|
||||
map[row.categoryId] = [];
|
||||
}
|
||||
map[row.categoryId].push(row.posterminalId);
|
||||
}
|
||||
return map;
|
||||
};
|
||||
}
|
||||
17
src/store/posterminal/list.js
Normal file
17
src/store/posterminal/list.js
Normal file
@@ -0,0 +1,17 @@
|
||||
import { LIST_BY_COMPANY } from '../../sql/posterminal.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;
|
||||
};
|
||||
}
|
||||
39
src/store/posterminal/setOff.js
Normal file
39
src/store/posterminal/setOff.js
Normal file
@@ -0,0 +1,39 @@
|
||||
import { GET_BY_ID, SET_OFF, LIST_CATEGORY_IDS } from '../../sql/posterminal.js';
|
||||
import resolveCompanyAccess from '../companyuser/resolveCompanyAccess.js';
|
||||
|
||||
export default function setOff(pool) {
|
||||
const access = resolveCompanyAccess(pool);
|
||||
|
||||
return async (googleAuthId, companyId, posterminalId, categoryId) => {
|
||||
if (!Number.isFinite(posterminalId) || !Number.isFinite(categoryId)) {
|
||||
throw new Error('Invalid posterminal or category');
|
||||
}
|
||||
|
||||
await access(googleAuthId, companyId);
|
||||
|
||||
const existing = await pool
|
||||
.request()
|
||||
.input('id', posterminalId)
|
||||
.query(GET_BY_ID);
|
||||
|
||||
if (!existing.recordset[0] || existing.recordset[0].companyId !== companyId) {
|
||||
throw new Error('Posterminal not found');
|
||||
}
|
||||
|
||||
await pool
|
||||
.request()
|
||||
.input('posterminalId', posterminalId)
|
||||
.input('categoryId', categoryId)
|
||||
.query(SET_OFF);
|
||||
|
||||
const result = await pool
|
||||
.request()
|
||||
.input('posterminalId', posterminalId)
|
||||
.query(LIST_CATEGORY_IDS);
|
||||
|
||||
return {
|
||||
id: posterminalId,
|
||||
categoryIds: result.recordset.map((r) => r.categoryId),
|
||||
};
|
||||
};
|
||||
}
|
||||
39
src/store/posterminal/setOffAll.js
Normal file
39
src/store/posterminal/setOffAll.js
Normal file
@@ -0,0 +1,39 @@
|
||||
import { GET_BY_ID, SET_OFF_ALL, LIST_CATEGORY_IDS } from '../../sql/posterminal.js';
|
||||
import resolveCompanyAccess from '../companyuser/resolveCompanyAccess.js';
|
||||
|
||||
export default function setOffAll(pool) {
|
||||
const access = resolveCompanyAccess(pool);
|
||||
|
||||
return async (googleAuthId, companyId, posterminalId) => {
|
||||
if (!Number.isFinite(posterminalId)) {
|
||||
throw new Error('Invalid posterminal');
|
||||
}
|
||||
|
||||
await access(googleAuthId, companyId);
|
||||
|
||||
// Verify posterminal belongs to company
|
||||
const pt = await pool
|
||||
.request()
|
||||
.input('id', posterminalId)
|
||||
.query(GET_BY_ID);
|
||||
|
||||
if (!pt.recordset[0] || pt.recordset[0].companyId !== companyId) {
|
||||
throw new Error('Posterminal not found');
|
||||
}
|
||||
|
||||
await pool
|
||||
.request()
|
||||
.input('posterminalId', posterminalId)
|
||||
.query(SET_OFF_ALL);
|
||||
|
||||
const result = await pool
|
||||
.request()
|
||||
.input('posterminalId', posterminalId)
|
||||
.query(LIST_CATEGORY_IDS);
|
||||
|
||||
return {
|
||||
id: posterminalId,
|
||||
categoryIds: result.recordset.map((r) => r.categoryId),
|
||||
};
|
||||
};
|
||||
}
|
||||
39
src/store/posterminal/setOn.js
Normal file
39
src/store/posterminal/setOn.js
Normal file
@@ -0,0 +1,39 @@
|
||||
import { GET_BY_ID, SET_ON, LIST_CATEGORY_IDS } from '../../sql/posterminal.js';
|
||||
import resolveCompanyAccess from '../companyuser/resolveCompanyAccess.js';
|
||||
|
||||
export default function setOn(pool) {
|
||||
const access = resolveCompanyAccess(pool);
|
||||
|
||||
return async (googleAuthId, companyId, posterminalId, categoryId) => {
|
||||
if (!Number.isFinite(posterminalId) || !Number.isFinite(categoryId)) {
|
||||
throw new Error('Invalid posterminal or category');
|
||||
}
|
||||
|
||||
await access(googleAuthId, companyId);
|
||||
|
||||
const existing = await pool
|
||||
.request()
|
||||
.input('id', posterminalId)
|
||||
.query(GET_BY_ID);
|
||||
|
||||
if (!existing.recordset[0] || existing.recordset[0].companyId !== companyId) {
|
||||
throw new Error('Posterminal not found');
|
||||
}
|
||||
|
||||
await pool
|
||||
.request()
|
||||
.input('posterminalId', posterminalId)
|
||||
.input('categoryId', categoryId)
|
||||
.query(SET_ON);
|
||||
|
||||
const result = await pool
|
||||
.request()
|
||||
.input('posterminalId', posterminalId)
|
||||
.query(LIST_CATEGORY_IDS);
|
||||
|
||||
return {
|
||||
id: posterminalId,
|
||||
categoryIds: result.recordset.map((r) => r.categoryId),
|
||||
};
|
||||
};
|
||||
}
|
||||
52
src/store/posterminal/setOnAll.js
Normal file
52
src/store/posterminal/setOnAll.js
Normal file
@@ -0,0 +1,52 @@
|
||||
import { GET_BY_ID, SET_ON_ALL, LIST_CATEGORY_IDS } from '../../sql/posterminal.js';
|
||||
import resolveCompanyAccess from '../companyuser/resolveCompanyAccess.js';
|
||||
import { GET_COMPANY_ROOT_CATEGORY } from '../../sql/company.js';
|
||||
|
||||
export default function setOnAll(pool) {
|
||||
const access = resolveCompanyAccess(pool);
|
||||
|
||||
return async (googleAuthId, companyId, posterminalId) => {
|
||||
if (!Number.isFinite(posterminalId)) {
|
||||
throw new Error('Invalid posterminal');
|
||||
}
|
||||
|
||||
await access(googleAuthId, companyId);
|
||||
|
||||
// Verify posterminal belongs to company
|
||||
const pt = await pool
|
||||
.request()
|
||||
.input('id', posterminalId)
|
||||
.query(GET_BY_ID);
|
||||
|
||||
if (!pt.recordset[0] || pt.recordset[0].companyId !== companyId) {
|
||||
throw new Error('Posterminal not found');
|
||||
}
|
||||
|
||||
// Get the company's root category to walk the tree
|
||||
const root = await pool
|
||||
.request()
|
||||
.input('companyId', companyId)
|
||||
.query(GET_COMPANY_ROOT_CATEGORY);
|
||||
|
||||
const rootCategoryId = root.recordset[0]?.rootCategoryId;
|
||||
if (!rootCategoryId) {
|
||||
throw new Error('Company not found');
|
||||
}
|
||||
|
||||
await pool
|
||||
.request()
|
||||
.input('posterminalId', posterminalId)
|
||||
.input('rootCategoryId', rootCategoryId)
|
||||
.query(SET_ON_ALL);
|
||||
|
||||
const result = await pool
|
||||
.request()
|
||||
.input('posterminalId', posterminalId)
|
||||
.query(LIST_CATEGORY_IDS);
|
||||
|
||||
return {
|
||||
id: posterminalId,
|
||||
categoryIds: result.recordset.map((r) => r.categoryId),
|
||||
};
|
||||
};
|
||||
}
|
||||
51
src/store/posterminal/upsert.js
Normal file
51
src/store/posterminal/upsert.js
Normal file
@@ -0,0 +1,51 @@
|
||||
import { GET_BY_ID, CREATE, UPDATE } from '../../sql/posterminal.js';
|
||||
import resolveCompanyAccess from '../companyuser/resolveCompanyAccess.js';
|
||||
|
||||
export default function upsert(pool) {
|
||||
const access = resolveCompanyAccess(pool);
|
||||
|
||||
return async (googleAuthId, companyId, input = {}) => {
|
||||
const pairingpin = input.pairingpin?.trim();
|
||||
if (!pairingpin) {
|
||||
throw new Error('Invalid posterminal');
|
||||
}
|
||||
const token = input.token ?? null;
|
||||
const id = input.id ?? null;
|
||||
|
||||
await access(googleAuthId, companyId);
|
||||
|
||||
if (id) {
|
||||
const existing = await pool
|
||||
.request()
|
||||
.input('id', id)
|
||||
.query(GET_BY_ID);
|
||||
|
||||
if (!existing.recordset[0] || existing.recordset[0].companyId !== companyId) {
|
||||
throw new Error('Posterminal not found');
|
||||
}
|
||||
|
||||
await pool
|
||||
.request()
|
||||
.input('id', id)
|
||||
.input('companyId', companyId)
|
||||
.input('token', token)
|
||||
.input('pairingpin', pairingpin)
|
||||
.query(UPDATE);
|
||||
|
||||
return { id, token, pairingpin };
|
||||
}
|
||||
|
||||
const created = await pool
|
||||
.request()
|
||||
.input('companyId', companyId)
|
||||
.input('token', token)
|
||||
.input('pairingpin', pairingpin)
|
||||
.query(CREATE);
|
||||
|
||||
return {
|
||||
id: created.recordset[0].id,
|
||||
token: created.recordset[0].token,
|
||||
pairingpin: created.recordset[0].pairingpin,
|
||||
};
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user