@infuro/cms-core 1.0.24 → 1.0.25
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/admin.cjs +4239 -3299
- package/dist/admin.cjs.map +1 -1
- package/dist/admin.d.cts +23 -4
- package/dist/admin.d.ts +23 -4
- package/dist/admin.js +4195 -3255
- package/dist/admin.js.map +1 -1
- package/dist/api.cjs +422 -52
- package/dist/api.cjs.map +1 -1
- package/dist/api.d.cts +1 -1
- package/dist/api.d.ts +1 -1
- package/dist/api.js +430 -60
- package/dist/api.js.map +1 -1
- package/dist/auth.cjs +9 -1
- package/dist/auth.cjs.map +1 -1
- package/dist/auth.js +9 -1
- package/dist/auth.js.map +1 -1
- package/dist/cli.cjs +7 -0
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +7 -0
- package/dist/cli.js.map +1 -1
- package/dist/{index-BGAh4fPQ.d.cts → index--vbixpxE.d.cts} +17 -2
- package/dist/{index-Cnwh7B3r.d.ts → index-DMJgi-fy.d.ts} +17 -2
- package/dist/index.cjs +508 -67
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +3 -2
- package/dist/index.d.ts +3 -2
- package/dist/index.js +509 -68
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/api.cjs
CHANGED
|
@@ -38,16 +38,51 @@ var __decorateClass = (decorators, target, key, kind) => {
|
|
|
38
38
|
return result;
|
|
39
39
|
};
|
|
40
40
|
|
|
41
|
+
// src/plugins/erp/erp-log.ts
|
|
42
|
+
function logErp(event, detail) {
|
|
43
|
+
if (detail && Object.keys(detail).length) console.info(ERP_LOG, event, detail);
|
|
44
|
+
else console.info(ERP_LOG, event);
|
|
45
|
+
}
|
|
46
|
+
function warnErp(event, detail) {
|
|
47
|
+
console.warn(ERP_LOG, event, detail);
|
|
48
|
+
}
|
|
49
|
+
function errorErp(event, detail) {
|
|
50
|
+
console.error(ERP_LOG, event, detail);
|
|
51
|
+
}
|
|
52
|
+
var ERP_LOG;
|
|
53
|
+
var init_erp_log = __esm({
|
|
54
|
+
"src/plugins/erp/erp-log.ts"() {
|
|
55
|
+
"use strict";
|
|
56
|
+
ERP_LOG = "[webcore:erp]";
|
|
57
|
+
}
|
|
58
|
+
});
|
|
59
|
+
|
|
41
60
|
// src/plugins/erp/erp-queue.ts
|
|
61
|
+
function queuePayloadSummary(payload) {
|
|
62
|
+
if (payload.kind === "order") {
|
|
63
|
+
const o = payload.order;
|
|
64
|
+
return {
|
|
65
|
+
kind: payload.kind,
|
|
66
|
+
platformOrderId: o.platformOrderId ?? o.platformOrderNumber,
|
|
67
|
+
itemCount: Array.isArray(o.items) ? o.items.length : 0
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
return { kind: payload.kind };
|
|
71
|
+
}
|
|
42
72
|
async function queueErp(cms, payload) {
|
|
43
73
|
const queue = cms.getPlugin("queue");
|
|
44
|
-
if (!queue)
|
|
74
|
+
if (!queue) {
|
|
75
|
+
warnErp("queue:add_skipped", { reason: "queue_plugin_missing", ...queuePayloadSummary(payload) });
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
logErp("queue:add", { job: ERP_QUEUE_NAME, ...queuePayloadSummary(payload) });
|
|
45
79
|
await queue.add(ERP_QUEUE_NAME, payload);
|
|
46
80
|
}
|
|
47
81
|
var ERP_QUEUE_NAME;
|
|
48
82
|
var init_erp_queue = __esm({
|
|
49
83
|
"src/plugins/erp/erp-queue.ts"() {
|
|
50
84
|
"use strict";
|
|
85
|
+
init_erp_log();
|
|
51
86
|
ERP_QUEUE_NAME = "erp";
|
|
52
87
|
}
|
|
53
88
|
});
|
|
@@ -360,21 +395,36 @@ async function queueErpPaidOrderForOrderId(cms, dataSource, entityMap, orderId)
|
|
|
360
395
|
const cfgRows = await configRepo.find({ where: { settings: "erp", deleted: false } });
|
|
361
396
|
for (const row of cfgRows) {
|
|
362
397
|
const r = row;
|
|
363
|
-
if (r.key === "enabled" && r.value === "false")
|
|
398
|
+
if (r.key === "enabled" && r.value === "false") {
|
|
399
|
+
logErp("paid-order:skip", { orderId, reason: "erp_config_disabled" });
|
|
400
|
+
return;
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
if (!cms.getPlugin("erp")) {
|
|
404
|
+
logErp("paid-order:skip", { orderId, reason: "erp_plugin_missing" });
|
|
405
|
+
return;
|
|
364
406
|
}
|
|
365
|
-
if (!cms.getPlugin("erp")) return;
|
|
366
407
|
const orderRepo = dataSource.getRepository(entityMap.orders);
|
|
367
408
|
const ord = await orderRepo.findOne({
|
|
368
409
|
where: { id: orderId },
|
|
369
410
|
relations: ["items", "items.product", "contact", "billingAddress", "shippingAddress", "payments"]
|
|
370
411
|
});
|
|
371
|
-
if (!ord)
|
|
412
|
+
if (!ord) {
|
|
413
|
+
logErp("paid-order:skip", { orderId, reason: "order_not_found" });
|
|
414
|
+
return;
|
|
415
|
+
}
|
|
372
416
|
const o = ord;
|
|
373
417
|
const okKind = o.orderKind === void 0 || o.orderKind === null || o.orderKind === "sale";
|
|
374
|
-
if (!okKind)
|
|
418
|
+
if (!okKind) {
|
|
419
|
+
logErp("paid-order:skip", { orderId, reason: "order_kind_not_sale", orderKind: o.orderKind });
|
|
420
|
+
return;
|
|
421
|
+
}
|
|
375
422
|
const rawPayments = o.payments ?? [];
|
|
376
423
|
const completedPayments = rawPayments.filter((pay) => pay.status === "completed" && pay.deleted !== true);
|
|
377
|
-
if (!completedPayments.length)
|
|
424
|
+
if (!completedPayments.length) {
|
|
425
|
+
logErp("paid-order:skip", { orderId, reason: "no_completed_payments" });
|
|
426
|
+
return;
|
|
427
|
+
}
|
|
378
428
|
const rawItems = o.items ?? [];
|
|
379
429
|
const lines = rawItems.filter((it) => it.product).map((it) => {
|
|
380
430
|
const p = it.product;
|
|
@@ -393,7 +443,10 @@ async function queueErpPaidOrderForOrderId(cms, dataSource, entityMap, orderId)
|
|
|
393
443
|
type: itemType
|
|
394
444
|
};
|
|
395
445
|
});
|
|
396
|
-
if (!lines.length)
|
|
446
|
+
if (!lines.length) {
|
|
447
|
+
logErp("paid-order:skip", { orderId, reason: "no_line_items_with_product" });
|
|
448
|
+
return;
|
|
449
|
+
}
|
|
397
450
|
const contact = o.contact;
|
|
398
451
|
const orderTotalMajor = Number(o.total);
|
|
399
452
|
const paymentDtos = completedPayments.length === 1 && Number.isFinite(orderTotalMajor) ? [paymentRowToWebhookDto(completedPayments[0], orderTotalMajor)] : completedPayments.map((pay) => paymentRowToWebhookDto(pay));
|
|
@@ -415,13 +468,28 @@ async function queueErpPaidOrderForOrderId(cms, dataSource, entityMap, orderId)
|
|
|
415
468
|
payments: paymentDtos,
|
|
416
469
|
metadata: { ...baseMeta, source: "storefront" }
|
|
417
470
|
};
|
|
471
|
+
logErp("paid-order:payload_built", {
|
|
472
|
+
orderId,
|
|
473
|
+
platformOrderId: orderDto.platformOrderId,
|
|
474
|
+
status: orderDto.status,
|
|
475
|
+
itemCount: lines.length,
|
|
476
|
+
skus: lines.map((l) => l.sku),
|
|
477
|
+
paymentCount: paymentDtos.length,
|
|
478
|
+
paymentIds: paymentDtos.map((p) => p.id),
|
|
479
|
+
total: orderTotalMajor
|
|
480
|
+
});
|
|
418
481
|
await queueErp(cms, { kind: "order", order: orderDto });
|
|
419
|
-
} catch {
|
|
482
|
+
} catch (e) {
|
|
483
|
+
errorErp("paid-order:enqueue_failed", {
|
|
484
|
+
orderId,
|
|
485
|
+
message: e instanceof Error ? e.message : String(e)
|
|
486
|
+
});
|
|
420
487
|
}
|
|
421
488
|
}
|
|
422
489
|
var init_paid_order_erp = __esm({
|
|
423
490
|
"src/plugins/erp/paid-order-erp.ts"() {
|
|
424
491
|
"use strict";
|
|
492
|
+
init_erp_log();
|
|
425
493
|
init_erp_queue();
|
|
426
494
|
}
|
|
427
495
|
});
|
|
@@ -684,6 +752,156 @@ function buildSearchWhereClause(repo, search) {
|
|
|
684
752
|
function entityHasSoftDelete(repo) {
|
|
685
753
|
return repo.metadata.columns.some((c) => c.propertyName === "deleted");
|
|
686
754
|
}
|
|
755
|
+
var LIST_QUERY_RESERVED = /* @__PURE__ */ new Set(["page", "limit", "sortField", "sortOrder", "search"]);
|
|
756
|
+
function dayStartUtc(isoDay) {
|
|
757
|
+
return /* @__PURE__ */ new Date(isoDay + "T00:00:00.000Z");
|
|
758
|
+
}
|
|
759
|
+
function dayEndUtc(isoDay) {
|
|
760
|
+
return /* @__PURE__ */ new Date(isoDay + "T23:59:59.999Z");
|
|
761
|
+
}
|
|
762
|
+
function columnTypeLabel(col) {
|
|
763
|
+
const t = col.type;
|
|
764
|
+
if (typeof t === "string") return t.toLowerCase();
|
|
765
|
+
if (typeof t === "function") return t.name?.toLowerCase?.() ?? "";
|
|
766
|
+
if (t && typeof t === "object" && "name" in t && typeof t.name === "string") {
|
|
767
|
+
return String(t.name).toLowerCase();
|
|
768
|
+
}
|
|
769
|
+
return "";
|
|
770
|
+
}
|
|
771
|
+
function isListDateColumn(col) {
|
|
772
|
+
const tl = columnTypeLabel(col);
|
|
773
|
+
return DATE_COLUMN_TYPES.has(tl) || col.type === Date || TIMESTAMP_PROP_NAMES.has(col.propertyName);
|
|
774
|
+
}
|
|
775
|
+
function isListNumericColumn(col) {
|
|
776
|
+
const tl = columnTypeLabel(col);
|
|
777
|
+
if (col.type === Number) return true;
|
|
778
|
+
const patterns = [
|
|
779
|
+
"int",
|
|
780
|
+
"integer",
|
|
781
|
+
"int2",
|
|
782
|
+
"int4",
|
|
783
|
+
"int8",
|
|
784
|
+
"smallint",
|
|
785
|
+
"bigint",
|
|
786
|
+
"float",
|
|
787
|
+
"double",
|
|
788
|
+
"decimal",
|
|
789
|
+
"numeric",
|
|
790
|
+
"real",
|
|
791
|
+
"tinyint",
|
|
792
|
+
"mediumint"
|
|
793
|
+
];
|
|
794
|
+
if (patterns.some((x) => tl.includes(x))) return true;
|
|
795
|
+
if (tl.includes("unsigned") && (tl.includes("int") || tl.includes("bigint") || tl.includes("small"))) return true;
|
|
796
|
+
if (!tl && /Id$/i.test(col.propertyName) && !TIMESTAMP_PROP_NAMES.has(col.propertyName)) return true;
|
|
797
|
+
return false;
|
|
798
|
+
}
|
|
799
|
+
function isListBooleanColumn(col) {
|
|
800
|
+
const tl = columnTypeLabel(col);
|
|
801
|
+
return tl === "boolean" || tl === "bool" || col.type === Boolean;
|
|
802
|
+
}
|
|
803
|
+
function isListStringColumn(col) {
|
|
804
|
+
const tl = columnTypeLabel(col);
|
|
805
|
+
if (isListDateColumn(col) || isListNumericColumn(col) || isListBooleanColumn(col)) return false;
|
|
806
|
+
if (["varchar", "character varying", "text", "citext", "uuid", "char", "character", "enum"].some(
|
|
807
|
+
(x) => tl.includes(x)
|
|
808
|
+
)) {
|
|
809
|
+
return true;
|
|
810
|
+
}
|
|
811
|
+
if (col.type === String) return true;
|
|
812
|
+
return false;
|
|
813
|
+
}
|
|
814
|
+
function mergeListWhereAnd(where, patch) {
|
|
815
|
+
if (Object.keys(patch).length === 0) return where;
|
|
816
|
+
if (Array.isArray(where)) {
|
|
817
|
+
if (where.length === 0) return [patch];
|
|
818
|
+
return where.map((w) => ({ ...w, ...patch }));
|
|
819
|
+
}
|
|
820
|
+
if (where && typeof where === "object" && Object.keys(where).length > 0) {
|
|
821
|
+
return { ...where, ...patch };
|
|
822
|
+
}
|
|
823
|
+
return patch;
|
|
824
|
+
}
|
|
825
|
+
function buildListFilterAndFromSearchParams(repo, searchParams) {
|
|
826
|
+
const and = {};
|
|
827
|
+
const columnNames = new Set(repo.metadata.columns.map((c) => c.propertyName));
|
|
828
|
+
for (const col of repo.metadata.columns) {
|
|
829
|
+
const name = col.propertyName;
|
|
830
|
+
if (!columnNames.has(name)) continue;
|
|
831
|
+
if (name === "deleted" || name === "deletedAt" || name === "deletedBy") continue;
|
|
832
|
+
if (!isListDateColumn(col)) continue;
|
|
833
|
+
const from = searchParams.get(`${name}From`)?.trim();
|
|
834
|
+
const to = searchParams.get(`${name}To`)?.trim();
|
|
835
|
+
if (!from && !to) continue;
|
|
836
|
+
if (from && to) {
|
|
837
|
+
and[name] = (0, import_typeorm.Between)(dayStartUtc(from), dayEndUtc(to));
|
|
838
|
+
} else if (from) {
|
|
839
|
+
and[name] = (0, import_typeorm.MoreThanOrEqual)(dayStartUtc(from));
|
|
840
|
+
} else if (to) {
|
|
841
|
+
and[name] = (0, import_typeorm.LessThanOrEqual)(dayEndUtc(to));
|
|
842
|
+
}
|
|
843
|
+
}
|
|
844
|
+
for (const col of repo.metadata.columns) {
|
|
845
|
+
const name = col.propertyName;
|
|
846
|
+
if (!columnNames.has(name)) continue;
|
|
847
|
+
if (name === "deleted" || name === "deletedAt" || name === "deletedBy") continue;
|
|
848
|
+
if (!isListNumericColumn(col)) continue;
|
|
849
|
+
if (Object.prototype.hasOwnProperty.call(and, name)) continue;
|
|
850
|
+
const minRaw = searchParams.get(`${name}Min`)?.trim();
|
|
851
|
+
const maxRaw = searchParams.get(`${name}Max`)?.trim();
|
|
852
|
+
if (!minRaw && !maxRaw) continue;
|
|
853
|
+
const parseNum = (s) => {
|
|
854
|
+
const n = Number(s);
|
|
855
|
+
return Number.isFinite(n) ? n : null;
|
|
856
|
+
};
|
|
857
|
+
const nMin = minRaw ? parseNum(minRaw) : null;
|
|
858
|
+
const nMax = maxRaw ? parseNum(maxRaw) : null;
|
|
859
|
+
if (nMin != null && nMax != null) {
|
|
860
|
+
and[name] = (0, import_typeorm.Between)(nMin, nMax);
|
|
861
|
+
} else if (nMin != null) {
|
|
862
|
+
and[name] = (0, import_typeorm.MoreThanOrEqual)(nMin);
|
|
863
|
+
} else if (nMax != null) {
|
|
864
|
+
and[name] = (0, import_typeorm.LessThanOrEqual)(nMax);
|
|
865
|
+
}
|
|
866
|
+
}
|
|
867
|
+
for (const col of repo.metadata.columns) {
|
|
868
|
+
const name = col.propertyName;
|
|
869
|
+
if (!columnNames.has(name)) continue;
|
|
870
|
+
if (LIST_QUERY_RESERVED.has(name)) continue;
|
|
871
|
+
if (name === "deleted" || name === "deletedAt" || name === "deletedBy") continue;
|
|
872
|
+
if (!isListStringColumn(col)) continue;
|
|
873
|
+
if (Object.prototype.hasOwnProperty.call(and, name)) continue;
|
|
874
|
+
const raw = searchParams.get(name)?.trim();
|
|
875
|
+
if (!raw) continue;
|
|
876
|
+
and[name] = (0, import_typeorm.ILike)(`%${raw}%`);
|
|
877
|
+
}
|
|
878
|
+
return and;
|
|
879
|
+
}
|
|
880
|
+
function buildExactListParamWhere(repo, searchParams) {
|
|
881
|
+
const extraWhere = {};
|
|
882
|
+
const columnNames = new Set(repo.metadata.columns.map((c) => c.propertyName));
|
|
883
|
+
for (const col of repo.metadata.columns) {
|
|
884
|
+
const name = col.propertyName;
|
|
885
|
+
if (!columnNames.has(name)) continue;
|
|
886
|
+
if (name === "deleted" || name === "deletedAt" || name === "deletedBy") continue;
|
|
887
|
+
if (!isListNumericColumn(col)) continue;
|
|
888
|
+
const v = searchParams.get(name)?.trim();
|
|
889
|
+
if (v == null || v === "") continue;
|
|
890
|
+
const n = Number(v);
|
|
891
|
+
if (!Number.isFinite(n)) continue;
|
|
892
|
+
extraWhere[name] = n;
|
|
893
|
+
}
|
|
894
|
+
for (const col of repo.metadata.columns) {
|
|
895
|
+
if (String(col.type) !== "boolean") continue;
|
|
896
|
+
const name = col.propertyName;
|
|
897
|
+
if (!columnNames.has(name)) continue;
|
|
898
|
+
const raw = searchParams.get(name)?.trim();
|
|
899
|
+
if (raw === "true" || raw === "false") {
|
|
900
|
+
extraWhere[name] = raw === "true";
|
|
901
|
+
}
|
|
902
|
+
}
|
|
903
|
+
return extraWhere;
|
|
904
|
+
}
|
|
687
905
|
function mergeDeletedFalseWhere(repo, where) {
|
|
688
906
|
if (!entityHasSoftDelete(repo)) return where;
|
|
689
907
|
const d = { deleted: false };
|
|
@@ -693,6 +911,12 @@ function mergeDeletedFalseWhere(repo, where) {
|
|
|
693
911
|
}
|
|
694
912
|
return Object.keys(where).length > 0 ? { ...where, ...d } : d;
|
|
695
913
|
}
|
|
914
|
+
function pickDefaultListSortField(columnNames, columns) {
|
|
915
|
+
for (const candidate of ["createdAt", "updatedAt", "id", "name", "sortOrder", "title"]) {
|
|
916
|
+
if (columnNames.has(candidate)) return candidate;
|
|
917
|
+
}
|
|
918
|
+
return columns[0]?.propertyName ?? "id";
|
|
919
|
+
}
|
|
696
920
|
function normalizeProductSku(value) {
|
|
697
921
|
if (value == null) return null;
|
|
698
922
|
const s = String(value).trim();
|
|
@@ -795,6 +1019,18 @@ function createCrudHandler(dataSource, entityMap, options) {
|
|
|
795
1019
|
if (statusFilter) qb.andWhere("order.status = :status", { status: statusFilter });
|
|
796
1020
|
if (dateFrom) qb.andWhere("order.createdAt >= :dateFrom", { dateFrom: /* @__PURE__ */ new Date(dateFrom + "T00:00:00.000Z") });
|
|
797
1021
|
if (dateTo) qb.andWhere("order.createdAt <= :dateTo", { dateTo: /* @__PURE__ */ new Date(dateTo + "T23:59:59.999Z") });
|
|
1022
|
+
const totalMin = searchParams.get("totalMin")?.trim();
|
|
1023
|
+
const totalMax = searchParams.get("totalMax")?.trim();
|
|
1024
|
+
if (totalMin) {
|
|
1025
|
+
const n = Number(totalMin);
|
|
1026
|
+
if (Number.isFinite(n)) qb.andWhere("order.total >= :totalMin", { totalMin: n });
|
|
1027
|
+
}
|
|
1028
|
+
if (totalMax) {
|
|
1029
|
+
const n = Number(totalMax);
|
|
1030
|
+
if (Number.isFinite(n)) qb.andWhere("order.total <= :totalMax", { totalMax: n });
|
|
1031
|
+
}
|
|
1032
|
+
const currency = searchParams.get("currency")?.trim();
|
|
1033
|
+
if (currency) qb.andWhere("order.currency ILIKE :orderCurrency", { orderCurrency: `%${currency}%` });
|
|
798
1034
|
if (orderIdsFromPayment && orderIdsFromPayment.length) qb.andWhere("order.id IN (:...orderIds)", { orderIds: orderIdsFromPayment });
|
|
799
1035
|
const [rows, total2] = await qb.getManyAndCount();
|
|
800
1036
|
const data2 = rows.map((order) => {
|
|
@@ -833,8 +1069,30 @@ function createCrudHandler(dataSource, entityMap, options) {
|
|
|
833
1069
|
if (statusFilter) qb.andWhere("payment.status = :status", { status: statusFilter });
|
|
834
1070
|
if (dateFrom) qb.andWhere("payment.createdAt >= :dateFrom", { dateFrom: /* @__PURE__ */ new Date(dateFrom + "T00:00:00.000Z") });
|
|
835
1071
|
if (dateTo) qb.andWhere("payment.createdAt <= :dateTo", { dateTo: /* @__PURE__ */ new Date(dateTo + "T23:59:59.999Z") });
|
|
1072
|
+
const paidAtFrom = searchParams.get("paidAtFrom")?.trim();
|
|
1073
|
+
const paidAtTo = searchParams.get("paidAtTo")?.trim();
|
|
1074
|
+
if (paidAtFrom) {
|
|
1075
|
+
qb.andWhere("payment.paidAt >= :paidAtFrom", { paidAtFrom: /* @__PURE__ */ new Date(paidAtFrom + "T00:00:00.000Z") });
|
|
1076
|
+
}
|
|
1077
|
+
if (paidAtTo) {
|
|
1078
|
+
qb.andWhere("payment.paidAt <= :paidAtTo", { paidAtTo: /* @__PURE__ */ new Date(paidAtTo + "T23:59:59.999Z") });
|
|
1079
|
+
}
|
|
836
1080
|
if (methodFilter) qb.andWhere("payment.method = :method", { method: methodFilter });
|
|
837
1081
|
if (orderNumberParam) qb.andWhere("ord.orderNumber ILIKE :orderNumber", { orderNumber: `%${orderNumberParam}%` });
|
|
1082
|
+
const amountMin = searchParams.get("amountMin")?.trim();
|
|
1083
|
+
const amountMax = searchParams.get("amountMax")?.trim();
|
|
1084
|
+
if (amountMin) {
|
|
1085
|
+
const n = Number(amountMin);
|
|
1086
|
+
if (Number.isFinite(n)) qb.andWhere("payment.amount >= :amountMin", { amountMin: n });
|
|
1087
|
+
}
|
|
1088
|
+
if (amountMax) {
|
|
1089
|
+
const n = Number(amountMax);
|
|
1090
|
+
if (Number.isFinite(n)) qb.andWhere("payment.amount <= :amountMax", { amountMax: n });
|
|
1091
|
+
}
|
|
1092
|
+
const extRef = searchParams.get("externalReference")?.trim();
|
|
1093
|
+
if (extRef) {
|
|
1094
|
+
qb.andWhere("payment.externalReference ILIKE :extRef", { extRef: `%${extRef}%` });
|
|
1095
|
+
}
|
|
838
1096
|
const [rows, total2] = await qb.getManyAndCount();
|
|
839
1097
|
const data2 = rows.map((payment) => {
|
|
840
1098
|
const order = payment.order;
|
|
@@ -853,7 +1111,10 @@ function createCrudHandler(dataSource, entityMap, options) {
|
|
|
853
1111
|
const repo2 = dataSource.getRepository(entity);
|
|
854
1112
|
const statusFilter = searchParams.get("status")?.trim();
|
|
855
1113
|
const inventory = searchParams.get("inventory")?.trim();
|
|
856
|
-
const productWhere = {
|
|
1114
|
+
const productWhere = {
|
|
1115
|
+
deleted: false,
|
|
1116
|
+
...buildListFilterAndFromSearchParams(repo2, searchParams)
|
|
1117
|
+
};
|
|
857
1118
|
if (statusFilter) productWhere.status = statusFilter;
|
|
858
1119
|
if (inventory === "in_stock") productWhere.quantity = (0, import_typeorm.MoreThan)(0);
|
|
859
1120
|
if (inventory === "out_of_stock") productWhere.quantity = 0;
|
|
@@ -871,11 +1132,15 @@ function createCrudHandler(dataSource, entityMap, options) {
|
|
|
871
1132
|
if (search && typeof search === "string" && search.trim()) {
|
|
872
1133
|
productWhere.name = (0, import_typeorm.ILike)(`%${search.trim()}%`);
|
|
873
1134
|
}
|
|
1135
|
+
const productColumnNames = new Set(repo2.metadata.columns.map((c) => c.propertyName));
|
|
1136
|
+
const defaultProductSort = pickDefaultListSortField(productColumnNames, repo2.metadata.columns);
|
|
1137
|
+
const sortParam2 = (searchParams.get("sortField") ?? "").trim();
|
|
1138
|
+
const productSortField = sortParam2 && productColumnNames.has(sortParam2) ? sortParam2 : defaultProductSort;
|
|
874
1139
|
const [data2, total2] = await repo2.findAndCount({
|
|
875
1140
|
where: Object.keys(productWhere).length ? productWhere : void 0,
|
|
876
1141
|
skip,
|
|
877
1142
|
take: limit,
|
|
878
|
-
order: { [
|
|
1143
|
+
order: { [productSortField]: sortOrder }
|
|
879
1144
|
});
|
|
880
1145
|
return json({ total: total2, page, limit, totalPages: Math.ceil(total2 / limit), data: data2 });
|
|
881
1146
|
}
|
|
@@ -968,36 +1233,22 @@ function createCrudHandler(dataSource, entityMap, options) {
|
|
|
968
1233
|
}
|
|
969
1234
|
return json({ total: total2, page, limit, totalPages: Math.ceil(total2 / limit), data: data2 });
|
|
970
1235
|
}
|
|
971
|
-
const
|
|
1236
|
+
const defaultSortField = pickDefaultListSortField(columnNames, repo.metadata.columns);
|
|
1237
|
+
const sortParam = (searchParams.get("sortField") ?? "").trim();
|
|
1238
|
+
const sortField = sortParam && columnNames.has(sortParam) ? sortParam : defaultSortField;
|
|
972
1239
|
let where = {};
|
|
973
1240
|
if (search) {
|
|
974
1241
|
where = buildSearchWhereClause(repo, search);
|
|
975
1242
|
}
|
|
976
|
-
|
|
977
|
-
const
|
|
978
|
-
|
|
979
|
-
const v = searchParams.get(key);
|
|
980
|
-
if (v != null && v !== "" && columnNames.has(key)) {
|
|
981
|
-
const n = Number(v);
|
|
982
|
-
if (Number.isFinite(n)) extraWhere[key] = n;
|
|
983
|
-
}
|
|
984
|
-
}
|
|
985
|
-
for (const col of repo.metadata.columns) {
|
|
986
|
-
if (String(col.type) !== "boolean") continue;
|
|
987
|
-
const name = col.propertyName;
|
|
988
|
-
if (!columnNames.has(name)) continue;
|
|
989
|
-
const raw = searchParams.get(name)?.trim();
|
|
990
|
-
if (raw === "true" || raw === "false") {
|
|
991
|
-
extraWhere[name] = raw === "true";
|
|
992
|
-
}
|
|
993
|
-
}
|
|
994
|
-
if (Object.keys(extraWhere).length > 0) {
|
|
1243
|
+
where = mergeListWhereAnd(where, buildListFilterAndFromSearchParams(repo, searchParams));
|
|
1244
|
+
const exactParamWhere = buildExactListParamWhere(repo, searchParams);
|
|
1245
|
+
if (Object.keys(exactParamWhere).length > 0) {
|
|
995
1246
|
if (Array.isArray(where)) {
|
|
996
|
-
where = where.map((w) => ({ ...w, ...
|
|
1247
|
+
where = where.map((w) => ({ ...w, ...exactParamWhere }));
|
|
997
1248
|
} else if (where && typeof where === "object" && Object.keys(where).length > 0) {
|
|
998
|
-
where = { ...where, ...
|
|
1249
|
+
where = { ...where, ...exactParamWhere };
|
|
999
1250
|
} else {
|
|
1000
|
-
where =
|
|
1251
|
+
where = exactParamWhere;
|
|
1001
1252
|
}
|
|
1002
1253
|
}
|
|
1003
1254
|
where = mergeDeletedFalseWhere(repo, where);
|
|
@@ -1077,6 +1328,10 @@ function createCrudHandler(dataSource, entityMap, options) {
|
|
|
1077
1328
|
}
|
|
1078
1329
|
const repo = dataSource.getRepository(entity);
|
|
1079
1330
|
const persistBody = resource === "media" ? body : pickColumnUpdates(repo, body);
|
|
1331
|
+
if (resource === "contacts" && "type" in persistBody) {
|
|
1332
|
+
const t = persistBody.type;
|
|
1333
|
+
if (t === "" || t === "none" || t == null) persistBody.type = null;
|
|
1334
|
+
}
|
|
1080
1335
|
if (resource !== "media" && Object.keys(persistBody).length === 0) {
|
|
1081
1336
|
logCrudClientError("POST create", {
|
|
1082
1337
|
reason: "no_scalar_columns_after_pick",
|
|
@@ -1427,6 +1682,10 @@ function createCrudByIdHandler(dataSource, entityMap, options) {
|
|
|
1427
1682
|
if (!cur) return json({ message: "Not found" }, { status: 404 });
|
|
1428
1683
|
}
|
|
1429
1684
|
const updatePayload = rawBody && typeof rawBody === "object" ? pickColumnUpdates(repo, rawBody) : {};
|
|
1685
|
+
if (resource === "contacts" && "type" in updatePayload) {
|
|
1686
|
+
const t = updatePayload.type;
|
|
1687
|
+
if (t === "" || t === "none" || t == null) updatePayload.type = null;
|
|
1688
|
+
}
|
|
1430
1689
|
if (resource === "media") {
|
|
1431
1690
|
const u = updatePayload;
|
|
1432
1691
|
delete u.kind;
|
|
@@ -2892,18 +3151,37 @@ function createUsersApiHandlers(config) {
|
|
|
2892
3151
|
try {
|
|
2893
3152
|
const body = await req.json();
|
|
2894
3153
|
if (!body?.name || !body?.email) return json({ error: "Name and email are required" }, { status: 400 });
|
|
2895
|
-
const
|
|
2896
|
-
|
|
3154
|
+
const email = body.email;
|
|
3155
|
+
const existing = await userRepo().findOne({ where: { email } });
|
|
3156
|
+
if (existing && !existing.deleted) {
|
|
3157
|
+
return json({ error: "User with this email already exists" }, { status: 400 });
|
|
3158
|
+
}
|
|
2897
3159
|
const groupRepo = dataSource.getRepository(entityMap.user_groups);
|
|
2898
3160
|
const customerG = await groupRepo.findOne({ where: { name: "Customer", deleted: false } });
|
|
2899
3161
|
const gid = body.groupId ?? null;
|
|
2900
3162
|
const isCustomer = !!(customerG && gid === customerG.id);
|
|
2901
3163
|
const adminAccess = isCustomer ? false : body.adminAccess === false ? false : true;
|
|
2902
3164
|
const blocked = body.blocked === true || body.blocked === "true" || body.blocked === 1 || body.blocked === "1";
|
|
2903
|
-
const newUser = await
|
|
3165
|
+
const newUser = existing?.deleted ? await (async () => {
|
|
3166
|
+
await userRepo().update(existing.id, {
|
|
3167
|
+
deleted: false,
|
|
3168
|
+
deletedAt: null,
|
|
3169
|
+
deletedBy: null,
|
|
3170
|
+
name: body.name,
|
|
3171
|
+
email,
|
|
3172
|
+
password: null,
|
|
3173
|
+
blocked,
|
|
3174
|
+
groupId: gid,
|
|
3175
|
+
adminAccess,
|
|
3176
|
+
updatedAt: /* @__PURE__ */ new Date()
|
|
3177
|
+
});
|
|
3178
|
+
const row = await userRepo().findOne({ where: { id: existing.id } });
|
|
3179
|
+
if (!row) throw new Error("user missing after restore");
|
|
3180
|
+
return row;
|
|
3181
|
+
})() : await userRepo().save(
|
|
2904
3182
|
userRepo().create({
|
|
2905
3183
|
name: body.name,
|
|
2906
|
-
email
|
|
3184
|
+
email,
|
|
2907
3185
|
password: null,
|
|
2908
3186
|
blocked,
|
|
2909
3187
|
groupId: gid,
|
|
@@ -3056,21 +3334,110 @@ function createUserAvatarHandler(config) {
|
|
|
3056
3334
|
}
|
|
3057
3335
|
};
|
|
3058
3336
|
}
|
|
3337
|
+
var PROFILE_EMAIL_RE = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
|
3059
3338
|
function createUserProfileHandler(config) {
|
|
3060
|
-
const { dataSource, entityMap, json, getSession } = config;
|
|
3061
|
-
|
|
3339
|
+
const { dataSource, entityMap, json, getSession, onProfileUpdated } = config;
|
|
3340
|
+
async function loadCurrentUser() {
|
|
3062
3341
|
const session = await getSession();
|
|
3063
|
-
|
|
3064
|
-
|
|
3065
|
-
|
|
3066
|
-
|
|
3067
|
-
|
|
3068
|
-
|
|
3069
|
-
|
|
3070
|
-
|
|
3071
|
-
|
|
3072
|
-
|
|
3073
|
-
|
|
3342
|
+
const su = session?.user;
|
|
3343
|
+
if (!su?.email && su?.id == null) {
|
|
3344
|
+
return { ok: false, response: json({ error: "Unauthorized" }, { status: 401 }) };
|
|
3345
|
+
}
|
|
3346
|
+
const userRepo = dataSource.getRepository(entityMap.users);
|
|
3347
|
+
let user = null;
|
|
3348
|
+
const uidRaw = su.id != null ? String(su.id).trim() : "";
|
|
3349
|
+
const uid = uidRaw && /^\d+$/.test(uidRaw) ? parseInt(uidRaw, 10) : NaN;
|
|
3350
|
+
if (Number.isFinite(uid) && uid > 0) {
|
|
3351
|
+
user = await userRepo.findOne({
|
|
3352
|
+
where: { id: uid, deleted: false },
|
|
3353
|
+
select: ["id", "name", "email", "phone", "createdAt"]
|
|
3354
|
+
});
|
|
3355
|
+
}
|
|
3356
|
+
if (!user && su.email) {
|
|
3357
|
+
const em = String(su.email).trim().toLowerCase();
|
|
3358
|
+
if (em) {
|
|
3359
|
+
user = await userRepo.findOne({
|
|
3360
|
+
where: { email: em, deleted: false },
|
|
3361
|
+
select: ["id", "name", "email", "phone", "createdAt"]
|
|
3362
|
+
});
|
|
3363
|
+
}
|
|
3364
|
+
}
|
|
3365
|
+
if (!user) return { ok: false, response: json({ error: "Not found" }, { status: 404 }) };
|
|
3366
|
+
return { ok: true, user };
|
|
3367
|
+
}
|
|
3368
|
+
return {
|
|
3369
|
+
async GET(_req) {
|
|
3370
|
+
try {
|
|
3371
|
+
const r = await loadCurrentUser();
|
|
3372
|
+
if (!r.ok) return r.response;
|
|
3373
|
+
const u = r.user;
|
|
3374
|
+
return json({
|
|
3375
|
+
id: u.id,
|
|
3376
|
+
name: u.name ?? "",
|
|
3377
|
+
email: u.email ?? "",
|
|
3378
|
+
phone: u.phone ?? null,
|
|
3379
|
+
createdAt: u.createdAt instanceof Date ? u.createdAt.toISOString() : u.createdAt ?? void 0
|
|
3380
|
+
});
|
|
3381
|
+
} catch {
|
|
3382
|
+
return json({ error: "Internal server error" }, { status: 500 });
|
|
3383
|
+
}
|
|
3384
|
+
},
|
|
3385
|
+
async PUT(req) {
|
|
3386
|
+
try {
|
|
3387
|
+
const r = await loadCurrentUser();
|
|
3388
|
+
if (!r.ok) return r.response;
|
|
3389
|
+
const current = r.user;
|
|
3390
|
+
let body;
|
|
3391
|
+
try {
|
|
3392
|
+
body = await req.json();
|
|
3393
|
+
} catch {
|
|
3394
|
+
return json({ error: "Invalid JSON" }, { status: 400 });
|
|
3395
|
+
}
|
|
3396
|
+
const name = typeof body.name === "string" ? body.name.trim() : "";
|
|
3397
|
+
if (!name) return json({ error: "Name is required" }, { status: 400 });
|
|
3398
|
+
const emailRaw = typeof body.email === "string" ? body.email.trim().toLowerCase() : "";
|
|
3399
|
+
if (!emailRaw || !PROFILE_EMAIL_RE.test(emailRaw)) {
|
|
3400
|
+
return json({ error: "Valid email is required" }, { status: 400 });
|
|
3401
|
+
}
|
|
3402
|
+
const phone = body.phone === null || body.phone === void 0 ? null : typeof body.phone === "string" ? body.phone.trim() || null : null;
|
|
3403
|
+
const userRepo = dataSource.getRepository(entityMap.users);
|
|
3404
|
+
if (emailRaw !== String(current.email ?? "").toLowerCase()) {
|
|
3405
|
+
const taken = await userRepo.findOne({
|
|
3406
|
+
where: { email: emailRaw, deleted: false },
|
|
3407
|
+
select: ["id"]
|
|
3408
|
+
});
|
|
3409
|
+
if (taken && taken.id !== current.id) {
|
|
3410
|
+
return json({ error: "Email is already in use" }, { status: 409 });
|
|
3411
|
+
}
|
|
3412
|
+
}
|
|
3413
|
+
await userRepo.update(
|
|
3414
|
+
{ id: current.id },
|
|
3415
|
+
{
|
|
3416
|
+
name,
|
|
3417
|
+
email: emailRaw,
|
|
3418
|
+
phone,
|
|
3419
|
+
updatedAt: /* @__PURE__ */ new Date()
|
|
3420
|
+
}
|
|
3421
|
+
);
|
|
3422
|
+
const updated = await userRepo.findOne({
|
|
3423
|
+
where: { id: current.id },
|
|
3424
|
+
select: ["id", "name", "email", "phone"]
|
|
3425
|
+
});
|
|
3426
|
+
if (!updated) return json({ error: "Not found" }, { status: 404 });
|
|
3427
|
+
const row = updated;
|
|
3428
|
+
if (onProfileUpdated) {
|
|
3429
|
+
try {
|
|
3430
|
+
await onProfileUpdated(req, row);
|
|
3431
|
+
} catch {
|
|
3432
|
+
}
|
|
3433
|
+
}
|
|
3434
|
+
return json({
|
|
3435
|
+
message: "Profile updated successfully",
|
|
3436
|
+
user: { id: row.id, name: row.name, email: row.email, phone: row.phone }
|
|
3437
|
+
});
|
|
3438
|
+
} catch {
|
|
3439
|
+
return json({ error: "Internal server error" }, { status: 500 });
|
|
3440
|
+
}
|
|
3074
3441
|
}
|
|
3075
3442
|
};
|
|
3076
3443
|
}
|
|
@@ -7083,7 +7450,7 @@ function createCmsApiHandler(config) {
|
|
|
7083
7450
|
} : usersApi;
|
|
7084
7451
|
const usersHandlers = usersApiMerged ? createUsersApiHandlers(mergePerm(usersApiMerged) ?? usersApiMerged) : null;
|
|
7085
7452
|
const avatarPost = userAvatar ? createUserAvatarHandler(userAvatar) : null;
|
|
7086
|
-
const
|
|
7453
|
+
const profileHandlers = userProfile ? createUserProfileHandler(userProfile) : null;
|
|
7087
7454
|
const settingsHandlers = settingsConfig ? createSettingsApiHandlers(settingsConfig) : null;
|
|
7088
7455
|
const smsMessageTemplateHandlers = createSmsMessageTemplateHandlers({
|
|
7089
7456
|
dataSource,
|
|
@@ -7182,7 +7549,10 @@ function createCmsApiHandler(config) {
|
|
|
7182
7549
|
}
|
|
7183
7550
|
if (path.length === 2) {
|
|
7184
7551
|
if (path[1] === "avatar" && m === "POST" && avatarPost) return avatarPost(req);
|
|
7185
|
-
if (path[1] === "profile" &&
|
|
7552
|
+
if (path[1] === "profile" && profileHandlers) {
|
|
7553
|
+
if (m === "GET") return profileHandlers.GET(req);
|
|
7554
|
+
if (m === "PUT") return profileHandlers.PUT(req);
|
|
7555
|
+
}
|
|
7186
7556
|
const id = path[1];
|
|
7187
7557
|
if (m === "GET") return usersHandlers.getById(req, id);
|
|
7188
7558
|
if (m === "PUT" || m === "PATCH") return usersHandlers.update(req, id);
|