- Updated privilege key structure to use a 3- or 4-part dotted hierarchy (e.g., `GROUP.PARENT.MODULE`). - Modified the `RequirePrivilege` decorator to accept multiple keys, allowing for OR logic in privilege checks. - Enhanced `PrivilegesGuard` to validate against multiple privilege keys, improving access control logic. - Created migration scripts to update existing privilege keys in the database to the new format. - Updated related services, controllers, and tests to accommodate the new privilege key structure and validation logic.
571 lines
15 KiB
TypeScript
571 lines
15 KiB
TypeScript
import {
|
|
DATA_FORMAT,
|
|
DATA_TYPE,
|
|
FILTER_FIELD_TYPE,
|
|
FILTER_TYPE,
|
|
REPORT_GROUP,
|
|
} from '../constants';
|
|
import type { ReportConfigEntity } from '../entities';
|
|
|
|
const SALES_PREFIX = `${REPORT_GROUP.SALES_REPORT}__`;
|
|
const LOGISTICS_PREFIX = `${REPORT_GROUP.LOGISTICS_REPORT}__`;
|
|
|
|
export const salesOrderReport: ReportConfigEntity = {
|
|
groupName: REPORT_GROUP.SALES_REPORT,
|
|
uniqueName: `${SALES_PREFIX}sales_order`,
|
|
privilegeKey: 'ADMIN.SALES.REPORT',
|
|
label: 'Report Sales Order',
|
|
tableSchema: `sales_orders main
|
|
JOIN customers cust ON cust.id = main.customer_id
|
|
JOIN branches br ON br.id = main.branch_id
|
|
JOIN divisions dv ON dv.id = main.division_id
|
|
JOIN employees emp ON emp.id = main.sales_person_id
|
|
LEFT JOIN (
|
|
SELECT sales_order_id, SUM(quantity * price) AS amount
|
|
FROM sales_order_products
|
|
GROUP BY sales_order_id
|
|
) sop ON sop.sales_order_id = main.id`,
|
|
mainTableAlias: 'main',
|
|
whereDefaultConditions: [],
|
|
defaultOrderBy: ['main.created_at DESC'],
|
|
lowLevelOrderBy: ['main.id DESC'],
|
|
filterPeriodConfig: { hidden: true },
|
|
columnConfigs: [
|
|
{
|
|
column: 'main__date',
|
|
query: 'main.date',
|
|
label: 'Date',
|
|
type: DATA_TYPE.DIMENSION,
|
|
format: DATA_FORMAT.DATE_EPOCH,
|
|
},
|
|
{
|
|
column: 'br__name',
|
|
query: 'br.name',
|
|
label: 'Branch',
|
|
type: DATA_TYPE.DIMENSION,
|
|
format: DATA_FORMAT.TEXT,
|
|
},
|
|
{
|
|
column: 'dv__name',
|
|
query: 'dv.name',
|
|
label: 'Division',
|
|
type: DATA_TYPE.DIMENSION,
|
|
format: DATA_FORMAT.TEXT,
|
|
},
|
|
{
|
|
column: 'main__code',
|
|
query: 'main.code',
|
|
label: 'No. Sales Order',
|
|
type: DATA_TYPE.DIMENSION,
|
|
format: DATA_FORMAT.TEXT,
|
|
},
|
|
{
|
|
column: 'cust__name',
|
|
query: 'cust.name',
|
|
label: 'Customer',
|
|
type: DATA_TYPE.DIMENSION,
|
|
format: DATA_FORMAT.TEXT,
|
|
},
|
|
{
|
|
column: 'main__amount',
|
|
query: 'COALESCE(sop.amount, 0)',
|
|
label: 'Invoice Amount',
|
|
type: DATA_TYPE.MEASURE,
|
|
format: DATA_FORMAT.CURRENCY,
|
|
},
|
|
{
|
|
column: 'emp__name',
|
|
query: 'emp.name',
|
|
label: 'Sales Rep.',
|
|
type: DATA_TYPE.DIMENSION,
|
|
format: DATA_FORMAT.TEXT,
|
|
},
|
|
{
|
|
column: 'main__status',
|
|
query: 'main.status',
|
|
label: 'Last Status Order',
|
|
type: DATA_TYPE.DIMENSION,
|
|
format: DATA_FORMAT.STATUS,
|
|
},
|
|
],
|
|
filterConfigs: [
|
|
{
|
|
fieldLabel: 'Date',
|
|
filterColumn: 'main__date',
|
|
fieldType: FILTER_FIELD_TYPE.DATE_RANGE_PICKER,
|
|
filterType: FILTER_TYPE.TEXT_IN_DATE_RANGE_EPOCH,
|
|
},
|
|
{
|
|
fieldLabel: 'Branch',
|
|
filterColumn: 'br__name',
|
|
fieldType: FILTER_FIELD_TYPE.INPUT_TAG,
|
|
filterType: FILTER_TYPE.TEXT_MULTIPLE_CONTAINS,
|
|
},
|
|
{
|
|
fieldLabel: 'Division',
|
|
filterColumn: 'dv__name',
|
|
fieldType: FILTER_FIELD_TYPE.INPUT_TAG,
|
|
filterType: FILTER_TYPE.TEXT_MULTIPLE_CONTAINS,
|
|
},
|
|
{
|
|
fieldLabel: 'Customer',
|
|
filterColumn: 'cust__name',
|
|
fieldType: FILTER_FIELD_TYPE.INPUT_TAG,
|
|
filterType: FILTER_TYPE.TEXT_MULTIPLE_CONTAINS,
|
|
},
|
|
{
|
|
fieldLabel: 'Sales Rep.',
|
|
filterColumn: 'emp__name',
|
|
fieldType: FILTER_FIELD_TYPE.INPUT_TAG,
|
|
filterType: FILTER_TYPE.TEXT_MULTIPLE_CONTAINS,
|
|
},
|
|
{
|
|
fieldLabel: 'Status',
|
|
filterColumn: 'main__status',
|
|
fieldType: FILTER_FIELD_TYPE.SELECT,
|
|
filterType: FILTER_TYPE.TEXT_IN_MEMBER_TEXT,
|
|
selectCustomOptions: ['draft', 'active', 'archived'],
|
|
},
|
|
],
|
|
customQueryColumn: (column) => {
|
|
if (column === 'main__date') {
|
|
return 'main.date';
|
|
}
|
|
return undefined;
|
|
},
|
|
};
|
|
|
|
export const salesRequestReport: ReportConfigEntity = {
|
|
groupName: REPORT_GROUP.SALES_REPORT,
|
|
uniqueName: `${SALES_PREFIX}sales_request`,
|
|
privilegeKey: 'ADMIN.SALES.REPORT',
|
|
label: 'Report Request Order',
|
|
tableSchema: `sales_requests main
|
|
JOIN customers cust ON cust.id = main.customer_id
|
|
JOIN branches br ON br.id = main.branch_id
|
|
JOIN divisions dv ON dv.id = main.division_id
|
|
JOIN employees emp ON emp.id = main.sales_person_id`,
|
|
mainTableAlias: 'main',
|
|
defaultOrderBy: ['main.created_at DESC'],
|
|
lowLevelOrderBy: ['main.id DESC'],
|
|
filterPeriodConfig: { hidden: true },
|
|
columnConfigs: [
|
|
{
|
|
column: 'main__date',
|
|
query: 'main.date',
|
|
label: 'Date',
|
|
type: DATA_TYPE.DIMENSION,
|
|
format: DATA_FORMAT.DATE_EPOCH,
|
|
},
|
|
{
|
|
column: 'br__name',
|
|
query: 'br.name',
|
|
label: 'Branch',
|
|
type: DATA_TYPE.DIMENSION,
|
|
format: DATA_FORMAT.TEXT,
|
|
},
|
|
{
|
|
column: 'dv__name',
|
|
query: 'dv.name',
|
|
label: 'Division',
|
|
type: DATA_TYPE.DIMENSION,
|
|
format: DATA_FORMAT.TEXT,
|
|
},
|
|
{
|
|
column: 'main__code',
|
|
query: 'main.code',
|
|
label: 'No. Request Order',
|
|
type: DATA_TYPE.DIMENSION,
|
|
format: DATA_FORMAT.TEXT,
|
|
},
|
|
{
|
|
column: 'cust__name',
|
|
query: 'cust.name',
|
|
label: 'Customer',
|
|
type: DATA_TYPE.DIMENSION,
|
|
format: DATA_FORMAT.TEXT,
|
|
},
|
|
{
|
|
column: 'emp__name',
|
|
query: 'emp.name',
|
|
label: 'Sales Rep.',
|
|
type: DATA_TYPE.DIMENSION,
|
|
format: DATA_FORMAT.TEXT,
|
|
},
|
|
{
|
|
column: 'main__status',
|
|
query: 'main.status',
|
|
label: 'Status',
|
|
type: DATA_TYPE.DIMENSION,
|
|
format: DATA_FORMAT.STATUS,
|
|
},
|
|
],
|
|
filterConfigs: [
|
|
{
|
|
fieldLabel: 'Date',
|
|
filterColumn: 'main__date',
|
|
fieldType: FILTER_FIELD_TYPE.DATE_RANGE_PICKER,
|
|
filterType: FILTER_TYPE.TEXT_IN_DATE_RANGE_EPOCH,
|
|
},
|
|
{
|
|
fieldLabel: 'Branch',
|
|
filterColumn: 'br__name',
|
|
fieldType: FILTER_FIELD_TYPE.INPUT_TAG,
|
|
filterType: FILTER_TYPE.TEXT_MULTIPLE_CONTAINS,
|
|
},
|
|
{
|
|
fieldLabel: 'Division',
|
|
filterColumn: 'dv__name',
|
|
fieldType: FILTER_FIELD_TYPE.INPUT_TAG,
|
|
filterType: FILTER_TYPE.TEXT_MULTIPLE_CONTAINS,
|
|
},
|
|
{
|
|
fieldLabel: 'Customer',
|
|
filterColumn: 'cust__name',
|
|
fieldType: FILTER_FIELD_TYPE.INPUT_TAG,
|
|
filterType: FILTER_TYPE.TEXT_MULTIPLE_CONTAINS,
|
|
},
|
|
{
|
|
fieldLabel: 'Sales Rep.',
|
|
filterColumn: 'emp__name',
|
|
fieldType: FILTER_FIELD_TYPE.INPUT_TAG,
|
|
filterType: FILTER_TYPE.TEXT_MULTIPLE_CONTAINS,
|
|
},
|
|
{
|
|
fieldLabel: 'Status',
|
|
filterColumn: 'main__status',
|
|
fieldType: FILTER_FIELD_TYPE.SELECT,
|
|
filterType: FILTER_TYPE.TEXT_IN_MEMBER_TEXT,
|
|
selectCustomOptions: ['draft', 'active', 'archived'],
|
|
},
|
|
],
|
|
customQueryColumn: (column) => {
|
|
if (column === 'main__date') {
|
|
return 'main.date';
|
|
}
|
|
return undefined;
|
|
},
|
|
};
|
|
|
|
export const salesInvoiceReport: ReportConfigEntity = {
|
|
groupName: REPORT_GROUP.SALES_REPORT,
|
|
uniqueName: `${SALES_PREFIX}sales_invoice`,
|
|
privilegeKey: 'ADMIN.SALES.REPORT',
|
|
label: 'Report Invoice',
|
|
tableSchema: `sales_invoices main
|
|
JOIN customers cust ON cust.id = main.customer_id
|
|
JOIN branches br ON br.id = main.branch_id
|
|
JOIN divisions dv ON dv.id = main.division_id
|
|
JOIN employees emp ON emp.id = main.sales_person_id`,
|
|
mainTableAlias: 'main',
|
|
defaultOrderBy: ['main.created_at DESC'],
|
|
lowLevelOrderBy: ['main.id DESC'],
|
|
filterPeriodConfig: { hidden: true },
|
|
columnConfigs: [
|
|
{
|
|
column: 'main__date',
|
|
query: 'main.date',
|
|
label: 'Date',
|
|
type: DATA_TYPE.DIMENSION,
|
|
format: DATA_FORMAT.DATE_EPOCH,
|
|
},
|
|
{
|
|
column: 'br__name',
|
|
query: 'br.name',
|
|
label: 'Branch',
|
|
type: DATA_TYPE.DIMENSION,
|
|
format: DATA_FORMAT.TEXT,
|
|
},
|
|
{
|
|
column: 'dv__name',
|
|
query: 'dv.name',
|
|
label: 'Division',
|
|
type: DATA_TYPE.DIMENSION,
|
|
format: DATA_FORMAT.TEXT,
|
|
},
|
|
{
|
|
column: 'cust__name',
|
|
query: 'cust.name',
|
|
label: 'Customer',
|
|
type: DATA_TYPE.DIMENSION,
|
|
format: DATA_FORMAT.TEXT,
|
|
},
|
|
{
|
|
column: 'cust__code',
|
|
query: 'cust.code',
|
|
label: 'Customer code',
|
|
type: DATA_TYPE.DIMENSION,
|
|
format: DATA_FORMAT.TEXT,
|
|
},
|
|
{
|
|
column: 'main__sales_order_code',
|
|
query: 'main.sales_order_code',
|
|
label: 'Sales Order No.',
|
|
type: DATA_TYPE.DIMENSION,
|
|
format: DATA_FORMAT.TEXT,
|
|
},
|
|
{
|
|
column: 'main__code',
|
|
query: 'main.code',
|
|
label: 'Invoice No.',
|
|
type: DATA_TYPE.DIMENSION,
|
|
format: DATA_FORMAT.TEXT,
|
|
},
|
|
{
|
|
column: 'main__status',
|
|
query: 'main.status',
|
|
label: 'Status',
|
|
type: DATA_TYPE.DIMENSION,
|
|
format: DATA_FORMAT.STATUS,
|
|
},
|
|
{
|
|
column: 'emp__name',
|
|
query: 'emp.name',
|
|
label: 'Sales Rep.',
|
|
type: DATA_TYPE.DIMENSION,
|
|
format: DATA_FORMAT.TEXT,
|
|
},
|
|
{
|
|
column: 'main__balance',
|
|
query: 'main.balance',
|
|
label: 'Balance',
|
|
type: DATA_TYPE.MEASURE,
|
|
format: DATA_FORMAT.CURRENCY,
|
|
},
|
|
],
|
|
filterConfigs: [
|
|
{
|
|
fieldLabel: 'Date',
|
|
filterColumn: 'main__date',
|
|
fieldType: FILTER_FIELD_TYPE.DATE_RANGE_PICKER,
|
|
filterType: FILTER_TYPE.TEXT_IN_DATE_RANGE_EPOCH,
|
|
},
|
|
{
|
|
fieldLabel: 'Branch',
|
|
filterColumn: 'br__name',
|
|
fieldType: FILTER_FIELD_TYPE.INPUT_TAG,
|
|
filterType: FILTER_TYPE.TEXT_MULTIPLE_CONTAINS,
|
|
},
|
|
{
|
|
fieldLabel: 'Status',
|
|
filterColumn: 'main__status',
|
|
fieldType: FILTER_FIELD_TYPE.SELECT,
|
|
filterType: FILTER_TYPE.TEXT_IN_MEMBER_TEXT,
|
|
selectCustomOptions: ['draft', 'active', 'archived'],
|
|
},
|
|
],
|
|
customQueryColumn: (column) => {
|
|
if (column === 'main__date') {
|
|
return 'main.date';
|
|
}
|
|
return undefined;
|
|
},
|
|
};
|
|
|
|
export const salesPaymentReport: ReportConfigEntity = {
|
|
groupName: REPORT_GROUP.SALES_REPORT,
|
|
uniqueName: `${SALES_PREFIX}sales_payment`,
|
|
privilegeKey: 'ADMIN.SALES.REPORT',
|
|
label: 'Report Payment',
|
|
tableSchema: `sales_payments main
|
|
JOIN sales_payment_invoices spi ON spi.sales_payment_id = main.id
|
|
JOIN sales_invoices inv ON inv.id = spi.sales_invoice_id
|
|
JOIN customers cust ON cust.id = inv.customer_id
|
|
JOIN branches br ON br.id = inv.branch_id
|
|
JOIN divisions dv ON dv.id = inv.division_id
|
|
JOIN employees emp ON emp.id = inv.sales_person_id`,
|
|
mainTableAlias: 'main',
|
|
defaultOrderBy: ['main.created_at DESC'],
|
|
lowLevelOrderBy: ['main.id DESC'],
|
|
filterPeriodConfig: { hidden: true },
|
|
columnConfigs: [
|
|
{
|
|
column: 'main__date',
|
|
query: 'main.date',
|
|
label: 'Date',
|
|
type: DATA_TYPE.DIMENSION,
|
|
format: DATA_FORMAT.DATE_EPOCH,
|
|
},
|
|
{
|
|
column: 'main__code',
|
|
query: 'main.code',
|
|
label: 'Payment No.',
|
|
type: DATA_TYPE.DIMENSION,
|
|
format: DATA_FORMAT.TEXT,
|
|
},
|
|
{
|
|
column: 'cust__code',
|
|
query: 'cust.code',
|
|
label: 'Customer code',
|
|
type: DATA_TYPE.DIMENSION,
|
|
format: DATA_FORMAT.TEXT,
|
|
},
|
|
{
|
|
column: 'br__name',
|
|
query: 'br.name',
|
|
label: 'Branch',
|
|
type: DATA_TYPE.DIMENSION,
|
|
format: DATA_FORMAT.TEXT,
|
|
},
|
|
{
|
|
column: 'dv__name',
|
|
query: 'dv.name',
|
|
label: 'Division',
|
|
type: DATA_TYPE.DIMENSION,
|
|
format: DATA_FORMAT.TEXT,
|
|
},
|
|
{
|
|
column: 'emp__name',
|
|
query: 'emp.name',
|
|
label: 'Sales Rep',
|
|
type: DATA_TYPE.DIMENSION,
|
|
format: DATA_FORMAT.TEXT,
|
|
},
|
|
{
|
|
column: 'inv__code',
|
|
query: 'inv.code',
|
|
label: 'Invoice ID',
|
|
type: DATA_TYPE.DIMENSION,
|
|
format: DATA_FORMAT.TEXT,
|
|
},
|
|
{
|
|
column: 'inv__balance',
|
|
query: 'inv.balance',
|
|
label: 'Invoice Amount',
|
|
type: DATA_TYPE.MEASURE,
|
|
format: DATA_FORMAT.CURRENCY,
|
|
},
|
|
{
|
|
column: 'spi__amount',
|
|
query: 'spi.amount',
|
|
label: 'Payment Amount',
|
|
type: DATA_TYPE.MEASURE,
|
|
format: DATA_FORMAT.CURRENCY,
|
|
},
|
|
{
|
|
column: 'main__status',
|
|
query: 'main.status',
|
|
label: 'Status',
|
|
type: DATA_TYPE.DIMENSION,
|
|
format: DATA_FORMAT.STATUS,
|
|
},
|
|
],
|
|
filterConfigs: [
|
|
{
|
|
fieldLabel: 'Date',
|
|
filterColumn: 'main__date',
|
|
fieldType: FILTER_FIELD_TYPE.DATE_RANGE_PICKER,
|
|
filterType: FILTER_TYPE.TEXT_IN_DATE_RANGE_EPOCH,
|
|
},
|
|
{
|
|
fieldLabel: 'Status',
|
|
filterColumn: 'main__status',
|
|
fieldType: FILTER_FIELD_TYPE.SELECT,
|
|
filterType: FILTER_TYPE.TEXT_IN_MEMBER_TEXT,
|
|
selectCustomOptions: ['draft', 'active', 'archived'],
|
|
},
|
|
],
|
|
customQueryColumn: (column) => {
|
|
if (column === 'main__date') {
|
|
return 'main.date';
|
|
}
|
|
return undefined;
|
|
},
|
|
};
|
|
|
|
export const visitPlanReport: ReportConfigEntity = {
|
|
groupName: REPORT_GROUP.SALES_REPORT,
|
|
uniqueName: `${SALES_PREFIX}visit_plan`,
|
|
privilegeKey: 'ADMIN.SALES.REPORT',
|
|
label: 'Report Visit Plan',
|
|
tableSchema: `plans main
|
|
JOIN employees emp ON emp.id = main.employee_id
|
|
JOIN branches start_br ON start_br.id = main.start_branch_id
|
|
LEFT JOIN (
|
|
SELECT plan_id, COUNT(*) AS destination_count
|
|
FROM plan_destinations
|
|
GROUP BY plan_id
|
|
) pd ON pd.plan_id = main.id
|
|
LEFT JOIN (
|
|
SELECT plan_id, COUNT(*) AS invoice_count
|
|
FROM plan_invoices
|
|
GROUP BY plan_id
|
|
) pi ON pi.plan_id = main.id`,
|
|
mainTableAlias: 'main',
|
|
whereDefaultConditions: ["main.purpose = 'sales'"],
|
|
defaultOrderBy: ['main.date DESC'],
|
|
lowLevelOrderBy: ['main.id DESC'],
|
|
filterPeriodConfig: { hidden: true },
|
|
columnConfigs: [
|
|
{
|
|
column: 'main__date',
|
|
query: 'main.date',
|
|
label: 'Date',
|
|
type: DATA_TYPE.DIMENSION,
|
|
format: DATA_FORMAT.DATE_EPOCH,
|
|
},
|
|
{
|
|
column: 'emp__name',
|
|
query: 'emp.name',
|
|
label: 'Sales Rep',
|
|
type: DATA_TYPE.DIMENSION,
|
|
format: DATA_FORMAT.TEXT,
|
|
},
|
|
{
|
|
column: 'start_br__name',
|
|
query: 'start_br.name',
|
|
label: 'Branch',
|
|
type: DATA_TYPE.DIMENSION,
|
|
format: DATA_FORMAT.TEXT,
|
|
},
|
|
{
|
|
column: 'pd__destination_count',
|
|
query: 'COALESCE(pd.destination_count, 0)',
|
|
label: 'Plan',
|
|
type: DATA_TYPE.MEASURE,
|
|
format: DATA_FORMAT.NUMBER,
|
|
},
|
|
{
|
|
column: 'pi__invoice_count',
|
|
query: 'COALESCE(pi.invoice_count, 0)',
|
|
label: 'Invoice',
|
|
type: DATA_TYPE.MEASURE,
|
|
format: DATA_FORMAT.NUMBER,
|
|
},
|
|
{
|
|
column: 'main__status',
|
|
query: 'main.status',
|
|
label: 'Status',
|
|
type: DATA_TYPE.DIMENSION,
|
|
format: DATA_FORMAT.STATUS,
|
|
},
|
|
],
|
|
filterConfigs: [
|
|
{
|
|
fieldLabel: 'Date',
|
|
filterColumn: 'main__date',
|
|
fieldType: FILTER_FIELD_TYPE.DATE_RANGE_PICKER,
|
|
filterType: FILTER_TYPE.TEXT_IN_DATE_RANGE_EPOCH,
|
|
},
|
|
{
|
|
fieldLabel: 'Sales Rep',
|
|
filterColumn: 'emp__name',
|
|
fieldType: FILTER_FIELD_TYPE.INPUT_TAG,
|
|
filterType: FILTER_TYPE.TEXT_MULTIPLE_CONTAINS,
|
|
},
|
|
{
|
|
fieldLabel: 'Status',
|
|
filterColumn: 'main__status',
|
|
fieldType: FILTER_FIELD_TYPE.SELECT,
|
|
filterType: FILTER_TYPE.TEXT_IN_MEMBER_TEXT,
|
|
selectCustomOptions: ['draft', 'active', 'archived'],
|
|
},
|
|
],
|
|
customQueryColumn: (column) => {
|
|
if (column === 'main__date') {
|
|
return 'main.date';
|
|
}
|
|
return undefined;
|
|
},
|
|
};
|