feat: 客退看板新增「最晚退厂时间」列(工作平台数据,先占位待接入)
- 新增 supplierExitDateProvider.js:按供应商名匹配「最晚退厂时间」, 数据源为工作平台库 WorkbenchDataShared(同实例,另有 WorkbenchIdentity / DataPlatformControl)。当前副本账号 cangku 无该库访问权限、系统设置里没有它的 连接配置、.env 的 JIAN_SUN_DB_* 与 WORK_ORDER_* 也为空,因此先返回不可用。 接入方式:IT 开通后配 SUPPLIER_EXIT_DATE_ENABLED=true + SUPPLIER_EXIT_DATE_SQL (查询需返回 supplierName / exitDate 两列),或直接写死 CODE_* 常量,无需改其他文件。 - returnStockWarningService.js:composeRows 增加 latestExitDate / latestExitDateLabel / latestExitDateAvailable;列表响应新增 exitDateMeta; 导出列在「供应商是否合作」后新增「最晚退厂时间」(未接入时导出显示「待接入」); 口径说明补一条(含完整取证:工作平台库不可达、副本无任何退厂字段、 fys_supplier.NoCoopTime 是不可用的停更快照)。 - StockWarning.jsx:表格新增「最晚退厂时间」列,有值显示日期、未接入显示「待接入」 (Tooltip 说明数据来源与开通方式);monitor.css 补占位符样式。
This commit is contained in:
parent
901212a32c
commit
30554720b2
@ -64,6 +64,10 @@
|
||||
|
||||
const cloudDb = require('../config/cloudDatabase');
|
||||
const database = require('../config/database');
|
||||
const {
|
||||
loadSupplierExitDates,
|
||||
EMPTY_EXIT_DATES,
|
||||
} = require('./supplierExitDateProvider');
|
||||
const {
|
||||
WAREHOUSE_LIST,
|
||||
RETURN_SLOT_COLUMNS,
|
||||
@ -242,6 +246,7 @@ const buildKnownGaps = (warehouse = DEFAULT_WAREHOUSE) => {
|
||||
'单价 / 总金额:单价取商品资料 jst_sku 的成本价(cost_price),成本价缺失或为 0 时回退采购价(purchase_price);总金额 = 单价 × 总库存(即「客退临时上架仓」的库存金额)。商品资料里两个价格都没有的编码(多为虚拟编码,如 HBZBF)单价与总金额显示「--」,且不计入顶部「库存总金额」,页面会单独提示这类编码的数量。',
|
||||
'单价预警:按商品单价单独分级,默认「单价 > 300 元 → 重点关注」「200 ~ 300 元 → 关注」「< 200 元 → 正常」,阈值可在页面上直接调。它与库龄预警是两个互补维度——库龄看「货压了多久」,单价看「每件压了多少钱」,两者叠加才能看出真正占资金的那批编码。',
|
||||
'退厂时限:单价 > 重点关注线(默认 300 元)的编码,要求入库后 7 天内完成库存退厂(时限可在页面调)。页面按「在库天数」对比时限,给出「已超期 N 天 / 剩余 N 天」,并在顶部单独提示这批编码的处置要求。「已退厂」的判定是「该编码已从当天库存中消失」(不再依赖出库表),一旦退厂完成,这条编码自然就不会再出现在报表里。',
|
||||
'最晚退厂时间(当前「待接入」):业务上由工作平台对「不再合作」的供应商给出最晚退厂日期,看板按供应商名匹配后与「供应商是否合作」列并排展示。它不在报表副本里——数据属于工作平台库 WorkbenchDataShared(同实例另有 WorkbenchIdentity、DataPlatformControl),而 2026-09-28 取证时副本账号 cangku 访问该库被拒(The server principal "cangku" is not able to access the database "WorkbenchDataShared" under the current security context),系统设置里也没有它的连接配置,.env 的 JIAN_SUN_DB_* / WORK_ORDER_* 全为空;副本库内 5,496 个字段中没有任何「退厂」字段,最接近的 fys_supplier.NoCoopTime 是不可用的停更快照(崔桥仓 8 个不合作供应商里只有 1 个有值,且那 1 条自身还标着「合作」)。所以该列先以「待接入」占位,不影响其他列。要出数只需二选一:① 让 IT 给副本账号开通 WorkbenchDataShared 只读;② 让 IT 把工作平台「供应商不合作」表同步进 JSReportReplica。之后在 backend/src/services/supplierExitDateProvider.js 里填上查询语句即可自动出数,页面与导出都无需再改。',
|
||||
];
|
||||
if (!warehouse.hasReturnStock) {
|
||||
gaps.unshift(
|
||||
@ -825,6 +830,8 @@ function composeRows({
|
||||
replicaRows, overrides, qtyBasis, createdAtBasis, today, scope, focusDays, alertDays, priceMap,
|
||||
priceFocusMin = DEFAULT_PRICE_FOCUS_MIN, priceHighMin = DEFAULT_PRICE_HIGH_MIN,
|
||||
returnLimitDays = DEFAULT_RETURN_LIMIT_DAYS, warehouse = DEFAULT_WAREHOUSE,
|
||||
// 供应商「最晚退厂时间」(工作平台数据,见 supplierExitDateProvider;未接入时是空 Map)
|
||||
exitDates = EMPTY_EXIT_DATES,
|
||||
}) {
|
||||
const hasImport = overrides.size > 0;
|
||||
const rows = [];
|
||||
@ -853,6 +860,9 @@ function composeRows({
|
||||
});
|
||||
// 供应商是否合作(js_supplier.coopersgn)
|
||||
const supplierCoop = resolveSupplierCoop(row.supplierCoopSign);
|
||||
// 最晚退厂时间(工作平台「供应商不合作」,按供应商名匹配;未接入时为空)
|
||||
const supplierName = String(row.supplier || '').trim();
|
||||
const latestExitDate = exitDates.bySupplier.get(supplierName) || '';
|
||||
|
||||
if (scope === 'imported' && !override) return;
|
||||
if (qty <= 0) return;
|
||||
@ -885,6 +895,9 @@ function composeRows({
|
||||
isOutbound,
|
||||
supplierCoop,
|
||||
supplierCoopLabel: SUPPLIER_COOP_LABELS[supplierCoop] || '',
|
||||
latestExitDate,
|
||||
latestExitDateLabel: latestExitDate,
|
||||
latestExitDateAvailable: exitDates.available,
|
||||
source: override ? 'jst' : 'replica',
|
||||
noInboundRecord: !createdAtRaw,
|
||||
});
|
||||
@ -906,6 +919,7 @@ function composeRows({
|
||||
});
|
||||
// 这条是「导入里有、副本里没有」补进来的编码,没走副本 join,拿不到 coopersgn → 标「未知」
|
||||
const supplierCoop = 'unknown';
|
||||
const supplierExitDate = exitDates.bySupplier.get(String(override.supplier || '').trim()) || '';
|
||||
rows.push({
|
||||
productCode: code,
|
||||
createdAt: formatDateTime(override.createdAt),
|
||||
@ -933,6 +947,9 @@ function composeRows({
|
||||
isOutbound,
|
||||
supplierCoop,
|
||||
supplierCoopLabel: SUPPLIER_COOP_LABELS[supplierCoop] || '',
|
||||
latestExitDate: supplierExitDate,
|
||||
latestExitDateLabel: supplierExitDate,
|
||||
latestExitDateAvailable: exitDates.available,
|
||||
source: 'jst',
|
||||
noInboundRecord: !override.createdAt,
|
||||
});
|
||||
@ -1095,10 +1112,11 @@ async function getReturnStockWarningList(options = {}) {
|
||||
const priceLevel = resolvePriceLevelFilter(options.priceLevel);
|
||||
const today = formatLocalDate();
|
||||
|
||||
const [replicaRows, overrides, latestImport] = await Promise.all([
|
||||
const [replicaRows, overrides, latestImport, exitDates] = await Promise.all([
|
||||
loadReplicaRows({ refresh: String(options.refresh) === 'true', createdAtBasis, warehouse }),
|
||||
loadImportOverrides(warehouse),
|
||||
loadLatestImportBatch(warehouse),
|
||||
loadSupplierExitDates(),
|
||||
]);
|
||||
|
||||
const priceMap = await loadPriceMapForOverrides(replicaRows, overrides);
|
||||
@ -1117,6 +1135,7 @@ async function getReturnStockWarningList(options = {}) {
|
||||
priceHighMin,
|
||||
returnLimitDays,
|
||||
warehouse,
|
||||
exitDates,
|
||||
});
|
||||
|
||||
const filtered = composed.rows.filter((row) => matchesKeyword(row, keyword) && matchesSupplier(row, supplier));
|
||||
@ -1167,6 +1186,12 @@ async function getReturnStockWarningList(options = {}) {
|
||||
summary,
|
||||
import: latestImport,
|
||||
hasImport: composed.hasImport,
|
||||
// 「最晚退厂时间」数据源状态(页面用它渲染「待接入」提示与列 Tooltip)
|
||||
exitDateMeta: {
|
||||
available: exitDates.available,
|
||||
sourceLabel: exitDates.sourceLabel,
|
||||
message: exitDates.message,
|
||||
},
|
||||
gaps: buildKnownGaps(warehouse),
|
||||
};
|
||||
}
|
||||
@ -1187,6 +1212,7 @@ const buildExportColumns = (warehouse = DEFAULT_WAREHOUSE) => [
|
||||
{ key: 'unitPriceLevelLabel', title: '单价预警' },
|
||||
{ key: 'returnDeadlineLabel', title: '退厂时限' },
|
||||
{ key: 'supplierCoopLabel', title: '供应商是否合作' },
|
||||
{ key: 'latestExitDateLabel', title: '最晚退厂时间' },
|
||||
{ key: 'returnQty', title: '退货仓库存(return_qty)' },
|
||||
{ key: 'returnSlotQty', title: `客退临时上架库存(${warehouse.returnSlot})` },
|
||||
{ key: 'sourceLabel', title: '数据来源' },
|
||||
@ -1212,9 +1238,10 @@ async function getReturnStockWarningExportRows(options = {}) {
|
||||
const priceLevel = resolvePriceLevelFilter(options.priceLevel);
|
||||
const today = formatLocalDate();
|
||||
|
||||
const [replicaRows, overrides] = await Promise.all([
|
||||
const [replicaRows, overrides, exitDates] = await Promise.all([
|
||||
loadReplicaRows({ refresh: String(options.refresh) === 'true', createdAtBasis, warehouse }),
|
||||
loadImportOverrides(warehouse),
|
||||
loadSupplierExitDates(),
|
||||
]);
|
||||
|
||||
const priceMap = await loadPriceMapForOverrides(replicaRows, overrides);
|
||||
@ -1233,6 +1260,7 @@ async function getReturnStockWarningExportRows(options = {}) {
|
||||
priceHighMin,
|
||||
returnLimitDays,
|
||||
warehouse,
|
||||
exitDates,
|
||||
});
|
||||
|
||||
const filtered = composed.rows.filter((row) => matchesKeyword(row, keyword) && matchesSupplier(row, supplier));
|
||||
@ -1247,6 +1275,9 @@ async function getReturnStockWarningExportRows(options = {}) {
|
||||
unitPriceLevelLabel: PRICE_WARNING_LABELS[row.unitPriceLevel] || '',
|
||||
returnDeadlineLabel: formatReturnDeadlineLabel(row.returnDeadlineStatus, row.returnRemainDays),
|
||||
supplierCoopLabel: row.supplierCoopLabel || SUPPLIER_COOP_LABELS.unknown,
|
||||
// 工作平台未接入时导出显示「待接入」,避免空列被误读成「没有退厂时间」
|
||||
latestExitDateLabel: row.latestExitDateLabel
|
||||
|| (row.latestExitDateAvailable ? '—' : '待接入'),
|
||||
sourceLabel: row.createdAtSourceLabel || '',
|
||||
createdAtRaw: undefined,
|
||||
}));
|
||||
|
||||
140
backend/src/services/supplierExitDateProvider.js
Normal file
140
backend/src/services/supplierExitDateProvider.js
Normal file
@ -0,0 +1,140 @@
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* 「最晚退厂时间」取数(2026-09-28 新增)
|
||||
*
|
||||
* 业务口径:供应商不再合作时,工作平台会给该供应商一个「最晚退厂时间」,
|
||||
* 即它放在仓里的货最后必须拉走的日期,看板上跟「供应商是否合作」列并排展示。
|
||||
*
|
||||
* ── 数据在哪 ──────────────────────────────────────────────────────────────
|
||||
* 工作平台库。副本实例(218.93.9.21:1433)上共有 9 个库:
|
||||
* master / model / msdb / tempdb / SSISDB / JSReportReplica(副本,我们只读它)/
|
||||
* DataPlatformControl / WorkbenchDataShared / WorkbenchIdentity。
|
||||
* 「工作平台」= WorkbenchDataShared(配套身份库 WorkbenchIdentity,控制库 DataPlatformControl)。
|
||||
*
|
||||
* ── 当前为什么是空的(2026-09-28 已逐条取证,别再重复排查)───────────────
|
||||
* ① 副本账号 cangku 访问 WorkbenchDataShared / WorkbenchIdentity / DataPlatformControl
|
||||
* **一律被拒**:The server principal "cangku" is not able to access the database
|
||||
* "WorkbenchDataShared" under the current security context.
|
||||
* ② 系统设置(database_configs)里只有 cloud_database_public(JSReportReplica)与
|
||||
* product_information_database(PostgreSQL),没有工作平台库。
|
||||
* ③ 本机 .env 与生产 /opt/cangku/shared/backend.env 里的 JIAN_SUN_DB_HOST/USER/PASSWORD、
|
||||
* WORK_ORDER_* 全部为空(只留了端口/库名占位)。
|
||||
* ④ 副本库内部没有任何「退厂」字段:5,496 个字段全量搜过,COLUMN_NAME 含「退厂」的为 0;
|
||||
* 最接近的 fys_supplier.NoCoopTime(不合作时间)**不能拿来顶替** —— 它是停更快照
|
||||
* (DownloadTime 多在 2023~2024),崔桥仓 8 个不合作供应商里只有 1 个有值,
|
||||
* 而且那 1 条自身还标着 IsCoop='合作',自相矛盾。
|
||||
*
|
||||
* ── IT 开通后怎么接(只需改本文件)────────────────────────────────────────
|
||||
* 二选一,改完即生效,页面与导出都会自动出数:
|
||||
* A. 给副本账号授只读:让 IT 执行 `USE WorkbenchDataShared; GRANT SELECT TO [cangku];`
|
||||
* (或建同义词/视图),然后把下面 EXIT_DATE_QUERY 填成跨库查询,例如:
|
||||
* SELECT SupplierName AS supplierName, CONVERT(varchar(10), LatestExitDate, 120) AS exitDate
|
||||
* FROM WorkbenchDataShared.dbo.<供应商不合作表>
|
||||
* B. 让 IT 把「供应商不合作」表同步进 JSReportReplica(像 js_supplier / fys_supplier 那样),
|
||||
* 再把 EXIT_DATE_QUERY 指向副本里的表名。
|
||||
* 查询只需要返回两列:supplierName(供应商名,口径同 js_supplier.scmname / jst_sku.supplier_name,
|
||||
* 即简称)与 exitDate(最晚退厂日期,字符串 'YYYY-MM-DD' 或 datetime 均可)。
|
||||
*
|
||||
* 未接入时本模块恒返回 available=false,页面该列显示「待接入」,不会报错、不影响其他列。
|
||||
*/
|
||||
|
||||
const cloudDb = require('../config/cloudDatabase');
|
||||
|
||||
const SOURCE_LABEL = '工作平台(WorkbenchDataShared)· 供应商不合作';
|
||||
const UNAVAILABLE_MESSAGE = '工作平台库尚未授权,待 IT 开通后自动出数';
|
||||
|
||||
// ---- 接入点(IT 开通后配这两个环境变量即可,不必改代码)--------------------
|
||||
// SUPPLIER_EXIT_DATE_ENABLED=true
|
||||
// SUPPLIER_EXIT_DATE_SQL=<查询语句,需返回 supplierName / exitDate 两列>
|
||||
// 也可以在下面 CODE_* 常量里直接写死(env 优先)。
|
||||
const CODE_EXIT_DATE_ENABLED = false;
|
||||
const CODE_EXIT_DATE_QUERY = '';
|
||||
const ENABLED = process.env.SUPPLIER_EXIT_DATE_ENABLED === 'true' || CODE_EXIT_DATE_ENABLED;
|
||||
const EXIT_DATE_QUERY = process.env.SUPPLIER_EXIT_DATE_SQL || CODE_EXIT_DATE_QUERY;
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
const CACHE_TTL_MS = Number.parseInt(process.env.SUPPLIER_EXIT_DATE_CACHE_TTL_MS, 10) >= 0
|
||||
? Number.parseInt(process.env.SUPPLIER_EXIT_DATE_CACHE_TTL_MS, 10)
|
||||
: 30 * 60 * 1000;
|
||||
|
||||
let cache = null; // { at, payload }
|
||||
|
||||
const formatExitDate = (value) => {
|
||||
if (value === null || value === undefined || value === '') return '';
|
||||
if (value instanceof Date) {
|
||||
if (Number.isNaN(value.getTime())) return '';
|
||||
const y = value.getFullYear();
|
||||
const m = String(value.getMonth() + 1).padStart(2, '0');
|
||||
const d = String(value.getDate()).padStart(2, '0');
|
||||
return `${y}-${m}-${d}`;
|
||||
}
|
||||
const text = String(value).trim();
|
||||
if (!text) return '';
|
||||
const match = text.match(/^(\d{4})[-/.](\d{1,2})[-/.](\d{1,2})/);
|
||||
if (!match) return text;
|
||||
return `${match[1]}-${match[2].padStart(2, '0')}-${match[3].padStart(2, '0')}`;
|
||||
};
|
||||
|
||||
/**
|
||||
* 取「供应商 → 最晚退厂时间」映射。
|
||||
* 返回 { available, sourceLabel, message, bySupplier: Map<string, string> }
|
||||
*/
|
||||
async function loadSupplierExitDates() {
|
||||
const describe = () => ({
|
||||
available: false,
|
||||
sourceLabel: SOURCE_LABEL,
|
||||
message: UNAVAILABLE_MESSAGE,
|
||||
bySupplier: new Map(),
|
||||
});
|
||||
|
||||
if (!ENABLED || !EXIT_DATE_QUERY) return describe();
|
||||
if (cache && Date.now() - cache.at < CACHE_TTL_MS) return cache.payload;
|
||||
|
||||
try {
|
||||
const rows = await cloudDb.query(EXIT_DATE_QUERY, {}, { requestTimeout: 120000 });
|
||||
const bySupplier = new Map();
|
||||
(rows || []).forEach((row) => {
|
||||
const name = String(row.supplierName || '').trim();
|
||||
const date = formatExitDate(row.exitDate);
|
||||
if (!name || !date) return;
|
||||
const prev = bySupplier.get(name);
|
||||
// 同一供应商多条时取最晚的日期
|
||||
if (!prev || date > prev) bySupplier.set(name, date);
|
||||
});
|
||||
const payload = {
|
||||
available: true,
|
||||
sourceLabel: SOURCE_LABEL,
|
||||
message: '',
|
||||
bySupplier,
|
||||
};
|
||||
cache = { at: Date.now(), payload };
|
||||
return payload;
|
||||
} catch (error) {
|
||||
// 取不到不算致命:该列留空、页面提示待接入,其余列照常
|
||||
console.warn('[SupplierExitDate] 最晚退厂时间取数失败:', error.message);
|
||||
const payload = {
|
||||
available: false,
|
||||
sourceLabel: SOURCE_LABEL,
|
||||
message: `工作平台库读取失败(${error.message}),该列暂空`,
|
||||
bySupplier: new Map(),
|
||||
};
|
||||
cache = { at: Date.now(), payload };
|
||||
return payload;
|
||||
}
|
||||
}
|
||||
|
||||
const EMPTY_EXIT_DATES = Object.freeze({
|
||||
available: false,
|
||||
sourceLabel: SOURCE_LABEL,
|
||||
message: UNAVAILABLE_MESSAGE,
|
||||
bySupplier: new Map(),
|
||||
});
|
||||
|
||||
module.exports = {
|
||||
loadSupplierExitDates,
|
||||
formatExitDate,
|
||||
EMPTY_EXIT_DATES,
|
||||
EXIT_DATE_SOURCE_LABEL: SOURCE_LABEL,
|
||||
EXIT_DATE_UNAVAILABLE_MESSAGE: UNAVAILABLE_MESSAGE,
|
||||
};
|
||||
@ -84,6 +84,12 @@ const SUPPLIER_COOP_TIPS = {
|
||||
unknown: '供应商名在简尚供应商主数据(js_supplier)里没有匹配记录,或该编码没有供应商,无法判断合作状态',
|
||||
};
|
||||
|
||||
// 最晚退厂时间(工作平台「供应商不合作」)—— 2026-09-28 先上线占位:
|
||||
// 数据属于工作平台库 WorkbenchDataShared,副本账号目前没有该库访问权限(后端
|
||||
// supplierExitDateProvider.js 有完整取证),IT 开通后自动出数,页面无需再改。
|
||||
const EXIT_DATE_PENDING_TIP = '数据来源:工作平台(WorkbenchDataShared)· 供应商不合作。该库当前未授权给副本账号,待 IT 开通只读权限(或把该表同步进报表副本)后自动显示最晚退厂日期。';
|
||||
const EXIT_DATE_VALUE_TIP = '工作平台「供应商不合作」给出的最晚退厂日期:该供应商放在仓里的货最后必须拉走的日期';
|
||||
|
||||
// 预警等级过滤:主视图改成一排状态页签(全部 / 红色预警 / 关注 / 正常 / 未知),
|
||||
// 因此不再需要下拉选项;导出时仍把 warningLevel 原样传给后端(服务端 filter 同口径)。
|
||||
|
||||
@ -375,6 +381,29 @@ const COLUMNS = [
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '最晚退厂时间',
|
||||
dataIndex: 'latestExitDateLabel',
|
||||
width: 132,
|
||||
align: 'center',
|
||||
sorter: (a, b) => String(a.latestExitDateLabel || '').localeCompare(String(b.latestExitDateLabel || '')),
|
||||
render: (value, record) => {
|
||||
if (value) {
|
||||
return (
|
||||
<Tooltip title={EXIT_DATE_VALUE_TIP}>
|
||||
<Tag color="orange">{value}</Tag>
|
||||
</Tooltip>
|
||||
);
|
||||
}
|
||||
// 已接入但该供应商没有退厂日期 → 显示破折号;整列未接入 → 显示「待接入」
|
||||
if (record?.latestExitDateAvailable) return <span className="rsw-exit-date-empty">—</span>;
|
||||
return (
|
||||
<Tooltip title={EXIT_DATE_PENDING_TIP}>
|
||||
<Tag color="default">待接入</Tag>
|
||||
</Tooltip>
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '创建时间来源',
|
||||
dataIndex: 'createdAtSourceLabel',
|
||||
@ -426,6 +455,8 @@ const StockWarning = ({ warehouse }) => {
|
||||
rows: [], total: 0, summary: {}, warehouse: '', warehouseName: '', returnSlot: '',
|
||||
warehouseHasReturnStock: true, statDate: '', stockSyncedAt: '', gaps: [], import: null,
|
||||
hasImport: false,
|
||||
// 最晚退厂时间数据源状态(工作平台库未接入时为 available:false + message)
|
||||
exitDateMeta: { available: false, sourceLabel: '', message: '' },
|
||||
});
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [exporting, setExporting] = useState(false);
|
||||
@ -464,6 +495,7 @@ const StockWarning = ({ warehouse }) => {
|
||||
gaps: payload.gaps || [],
|
||||
import: payload.import || null,
|
||||
hasImport: Boolean(payload.hasImport),
|
||||
exitDateMeta: payload.exitDateMeta || { available: false, sourceLabel: '', message: '' },
|
||||
});
|
||||
} catch (requestError) {
|
||||
setError(requestError?.response?.data?.error || requestError?.message || '加载客退数据监控失败');
|
||||
|
||||
@ -648,6 +648,11 @@
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
/* 表格「最晚退厂时间」列:数据源已接入但该供应商无日期时的占位符 */
|
||||
.rsw-exit-date-empty {
|
||||
color: var(--sycm-text-quaternary, #bfbfbf);
|
||||
}
|
||||
|
||||
/* ── 退货库存预警:图表区 ─────────────────────────────────── */
|
||||
.rsw-charts {
|
||||
display: grid;
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user