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