@infuro/cms-core 1.0.24 → 1.0.26
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 +5725 -4475
- 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 +5672 -4420
- 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.js
CHANGED
|
@@ -17,16 +17,51 @@ var __decorateClass = (decorators, target, key, kind) => {
|
|
|
17
17
|
return result;
|
|
18
18
|
};
|
|
19
19
|
|
|
20
|
+
// src/plugins/erp/erp-log.ts
|
|
21
|
+
function logErp(event, detail) {
|
|
22
|
+
if (detail && Object.keys(detail).length) console.info(ERP_LOG, event, detail);
|
|
23
|
+
else console.info(ERP_LOG, event);
|
|
24
|
+
}
|
|
25
|
+
function warnErp(event, detail) {
|
|
26
|
+
console.warn(ERP_LOG, event, detail);
|
|
27
|
+
}
|
|
28
|
+
function errorErp(event, detail) {
|
|
29
|
+
console.error(ERP_LOG, event, detail);
|
|
30
|
+
}
|
|
31
|
+
var ERP_LOG;
|
|
32
|
+
var init_erp_log = __esm({
|
|
33
|
+
"src/plugins/erp/erp-log.ts"() {
|
|
34
|
+
"use strict";
|
|
35
|
+
ERP_LOG = "[webcore:erp]";
|
|
36
|
+
}
|
|
37
|
+
});
|
|
38
|
+
|
|
20
39
|
// src/plugins/erp/erp-queue.ts
|
|
40
|
+
function queuePayloadSummary(payload) {
|
|
41
|
+
if (payload.kind === "order") {
|
|
42
|
+
const o = payload.order;
|
|
43
|
+
return {
|
|
44
|
+
kind: payload.kind,
|
|
45
|
+
platformOrderId: o.platformOrderId ?? o.platformOrderNumber,
|
|
46
|
+
itemCount: Array.isArray(o.items) ? o.items.length : 0
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
return { kind: payload.kind };
|
|
50
|
+
}
|
|
21
51
|
async function queueErp(cms, payload) {
|
|
22
52
|
const queue = cms.getPlugin("queue");
|
|
23
|
-
if (!queue)
|
|
53
|
+
if (!queue) {
|
|
54
|
+
warnErp("queue:add_skipped", { reason: "queue_plugin_missing", ...queuePayloadSummary(payload) });
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
logErp("queue:add", { job: ERP_QUEUE_NAME, ...queuePayloadSummary(payload) });
|
|
24
58
|
await queue.add(ERP_QUEUE_NAME, payload);
|
|
25
59
|
}
|
|
26
60
|
var ERP_QUEUE_NAME;
|
|
27
61
|
var init_erp_queue = __esm({
|
|
28
62
|
"src/plugins/erp/erp-queue.ts"() {
|
|
29
63
|
"use strict";
|
|
64
|
+
init_erp_log();
|
|
30
65
|
ERP_QUEUE_NAME = "erp";
|
|
31
66
|
}
|
|
32
67
|
});
|
|
@@ -339,21 +374,36 @@ async function queueErpPaidOrderForOrderId(cms, dataSource, entityMap, orderId)
|
|
|
339
374
|
const cfgRows = await configRepo.find({ where: { settings: "erp", deleted: false } });
|
|
340
375
|
for (const row of cfgRows) {
|
|
341
376
|
const r = row;
|
|
342
|
-
if (r.key === "enabled" && r.value === "false")
|
|
377
|
+
if (r.key === "enabled" && r.value === "false") {
|
|
378
|
+
logErp("paid-order:skip", { orderId, reason: "erp_config_disabled" });
|
|
379
|
+
return;
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
if (!cms.getPlugin("erp")) {
|
|
383
|
+
logErp("paid-order:skip", { orderId, reason: "erp_plugin_missing" });
|
|
384
|
+
return;
|
|
343
385
|
}
|
|
344
|
-
if (!cms.getPlugin("erp")) return;
|
|
345
386
|
const orderRepo = dataSource.getRepository(entityMap.orders);
|
|
346
387
|
const ord = await orderRepo.findOne({
|
|
347
388
|
where: { id: orderId },
|
|
348
389
|
relations: ["items", "items.product", "contact", "billingAddress", "shippingAddress", "payments"]
|
|
349
390
|
});
|
|
350
|
-
if (!ord)
|
|
391
|
+
if (!ord) {
|
|
392
|
+
logErp("paid-order:skip", { orderId, reason: "order_not_found" });
|
|
393
|
+
return;
|
|
394
|
+
}
|
|
351
395
|
const o = ord;
|
|
352
396
|
const okKind = o.orderKind === void 0 || o.orderKind === null || o.orderKind === "sale";
|
|
353
|
-
if (!okKind)
|
|
397
|
+
if (!okKind) {
|
|
398
|
+
logErp("paid-order:skip", { orderId, reason: "order_kind_not_sale", orderKind: o.orderKind });
|
|
399
|
+
return;
|
|
400
|
+
}
|
|
354
401
|
const rawPayments = o.payments ?? [];
|
|
355
402
|
const completedPayments = rawPayments.filter((pay) => pay.status === "completed" && pay.deleted !== true);
|
|
356
|
-
if (!completedPayments.length)
|
|
403
|
+
if (!completedPayments.length) {
|
|
404
|
+
logErp("paid-order:skip", { orderId, reason: "no_completed_payments" });
|
|
405
|
+
return;
|
|
406
|
+
}
|
|
357
407
|
const rawItems = o.items ?? [];
|
|
358
408
|
const lines = rawItems.filter((it) => it.product).map((it) => {
|
|
359
409
|
const p = it.product;
|
|
@@ -372,7 +422,10 @@ async function queueErpPaidOrderForOrderId(cms, dataSource, entityMap, orderId)
|
|
|
372
422
|
type: itemType
|
|
373
423
|
};
|
|
374
424
|
});
|
|
375
|
-
if (!lines.length)
|
|
425
|
+
if (!lines.length) {
|
|
426
|
+
logErp("paid-order:skip", { orderId, reason: "no_line_items_with_product" });
|
|
427
|
+
return;
|
|
428
|
+
}
|
|
376
429
|
const contact = o.contact;
|
|
377
430
|
const orderTotalMajor = Number(o.total);
|
|
378
431
|
const paymentDtos = completedPayments.length === 1 && Number.isFinite(orderTotalMajor) ? [paymentRowToWebhookDto(completedPayments[0], orderTotalMajor)] : completedPayments.map((pay) => paymentRowToWebhookDto(pay));
|
|
@@ -394,19 +447,34 @@ async function queueErpPaidOrderForOrderId(cms, dataSource, entityMap, orderId)
|
|
|
394
447
|
payments: paymentDtos,
|
|
395
448
|
metadata: { ...baseMeta, source: "storefront" }
|
|
396
449
|
};
|
|
450
|
+
logErp("paid-order:payload_built", {
|
|
451
|
+
orderId,
|
|
452
|
+
platformOrderId: orderDto.platformOrderId,
|
|
453
|
+
status: orderDto.status,
|
|
454
|
+
itemCount: lines.length,
|
|
455
|
+
skus: lines.map((l) => l.sku),
|
|
456
|
+
paymentCount: paymentDtos.length,
|
|
457
|
+
paymentIds: paymentDtos.map((p) => p.id),
|
|
458
|
+
total: orderTotalMajor
|
|
459
|
+
});
|
|
397
460
|
await queueErp(cms, { kind: "order", order: orderDto });
|
|
398
|
-
} catch {
|
|
461
|
+
} catch (e) {
|
|
462
|
+
errorErp("paid-order:enqueue_failed", {
|
|
463
|
+
orderId,
|
|
464
|
+
message: e instanceof Error ? e.message : String(e)
|
|
465
|
+
});
|
|
399
466
|
}
|
|
400
467
|
}
|
|
401
468
|
var init_paid_order_erp = __esm({
|
|
402
469
|
"src/plugins/erp/paid-order-erp.ts"() {
|
|
403
470
|
"use strict";
|
|
471
|
+
init_erp_log();
|
|
404
472
|
init_erp_queue();
|
|
405
473
|
}
|
|
406
474
|
});
|
|
407
475
|
|
|
408
476
|
// src/api/crud.ts
|
|
409
|
-
import { ILike, MoreThan, Not } from "typeorm";
|
|
477
|
+
import { Between, ILike, LessThanOrEqual, MoreThan, MoreThanOrEqual, Not } from "typeorm";
|
|
410
478
|
|
|
411
479
|
// src/plugins/erp/erp-contact-sync.ts
|
|
412
480
|
init_erp_queue();
|
|
@@ -631,6 +699,156 @@ function buildSearchWhereClause(repo, search) {
|
|
|
631
699
|
function entityHasSoftDelete(repo) {
|
|
632
700
|
return repo.metadata.columns.some((c) => c.propertyName === "deleted");
|
|
633
701
|
}
|
|
702
|
+
var LIST_QUERY_RESERVED = /* @__PURE__ */ new Set(["page", "limit", "sortField", "sortOrder", "search"]);
|
|
703
|
+
function dayStartUtc(isoDay) {
|
|
704
|
+
return /* @__PURE__ */ new Date(isoDay + "T00:00:00.000Z");
|
|
705
|
+
}
|
|
706
|
+
function dayEndUtc(isoDay) {
|
|
707
|
+
return /* @__PURE__ */ new Date(isoDay + "T23:59:59.999Z");
|
|
708
|
+
}
|
|
709
|
+
function columnTypeLabel(col) {
|
|
710
|
+
const t = col.type;
|
|
711
|
+
if (typeof t === "string") return t.toLowerCase();
|
|
712
|
+
if (typeof t === "function") return t.name?.toLowerCase?.() ?? "";
|
|
713
|
+
if (t && typeof t === "object" && "name" in t && typeof t.name === "string") {
|
|
714
|
+
return String(t.name).toLowerCase();
|
|
715
|
+
}
|
|
716
|
+
return "";
|
|
717
|
+
}
|
|
718
|
+
function isListDateColumn(col) {
|
|
719
|
+
const tl = columnTypeLabel(col);
|
|
720
|
+
return DATE_COLUMN_TYPES.has(tl) || col.type === Date || TIMESTAMP_PROP_NAMES.has(col.propertyName);
|
|
721
|
+
}
|
|
722
|
+
function isListNumericColumn(col) {
|
|
723
|
+
const tl = columnTypeLabel(col);
|
|
724
|
+
if (col.type === Number) return true;
|
|
725
|
+
const patterns = [
|
|
726
|
+
"int",
|
|
727
|
+
"integer",
|
|
728
|
+
"int2",
|
|
729
|
+
"int4",
|
|
730
|
+
"int8",
|
|
731
|
+
"smallint",
|
|
732
|
+
"bigint",
|
|
733
|
+
"float",
|
|
734
|
+
"double",
|
|
735
|
+
"decimal",
|
|
736
|
+
"numeric",
|
|
737
|
+
"real",
|
|
738
|
+
"tinyint",
|
|
739
|
+
"mediumint"
|
|
740
|
+
];
|
|
741
|
+
if (patterns.some((x) => tl.includes(x))) return true;
|
|
742
|
+
if (tl.includes("unsigned") && (tl.includes("int") || tl.includes("bigint") || tl.includes("small"))) return true;
|
|
743
|
+
if (!tl && /Id$/i.test(col.propertyName) && !TIMESTAMP_PROP_NAMES.has(col.propertyName)) return true;
|
|
744
|
+
return false;
|
|
745
|
+
}
|
|
746
|
+
function isListBooleanColumn(col) {
|
|
747
|
+
const tl = columnTypeLabel(col);
|
|
748
|
+
return tl === "boolean" || tl === "bool" || col.type === Boolean;
|
|
749
|
+
}
|
|
750
|
+
function isListStringColumn(col) {
|
|
751
|
+
const tl = columnTypeLabel(col);
|
|
752
|
+
if (isListDateColumn(col) || isListNumericColumn(col) || isListBooleanColumn(col)) return false;
|
|
753
|
+
if (["varchar", "character varying", "text", "citext", "uuid", "char", "character", "enum"].some(
|
|
754
|
+
(x) => tl.includes(x)
|
|
755
|
+
)) {
|
|
756
|
+
return true;
|
|
757
|
+
}
|
|
758
|
+
if (col.type === String) return true;
|
|
759
|
+
return false;
|
|
760
|
+
}
|
|
761
|
+
function mergeListWhereAnd(where, patch) {
|
|
762
|
+
if (Object.keys(patch).length === 0) return where;
|
|
763
|
+
if (Array.isArray(where)) {
|
|
764
|
+
if (where.length === 0) return [patch];
|
|
765
|
+
return where.map((w) => ({ ...w, ...patch }));
|
|
766
|
+
}
|
|
767
|
+
if (where && typeof where === "object" && Object.keys(where).length > 0) {
|
|
768
|
+
return { ...where, ...patch };
|
|
769
|
+
}
|
|
770
|
+
return patch;
|
|
771
|
+
}
|
|
772
|
+
function buildListFilterAndFromSearchParams(repo, searchParams) {
|
|
773
|
+
const and = {};
|
|
774
|
+
const columnNames = new Set(repo.metadata.columns.map((c) => c.propertyName));
|
|
775
|
+
for (const col of repo.metadata.columns) {
|
|
776
|
+
const name = col.propertyName;
|
|
777
|
+
if (!columnNames.has(name)) continue;
|
|
778
|
+
if (name === "deleted" || name === "deletedAt" || name === "deletedBy") continue;
|
|
779
|
+
if (!isListDateColumn(col)) continue;
|
|
780
|
+
const from = searchParams.get(`${name}From`)?.trim();
|
|
781
|
+
const to = searchParams.get(`${name}To`)?.trim();
|
|
782
|
+
if (!from && !to) continue;
|
|
783
|
+
if (from && to) {
|
|
784
|
+
and[name] = Between(dayStartUtc(from), dayEndUtc(to));
|
|
785
|
+
} else if (from) {
|
|
786
|
+
and[name] = MoreThanOrEqual(dayStartUtc(from));
|
|
787
|
+
} else if (to) {
|
|
788
|
+
and[name] = LessThanOrEqual(dayEndUtc(to));
|
|
789
|
+
}
|
|
790
|
+
}
|
|
791
|
+
for (const col of repo.metadata.columns) {
|
|
792
|
+
const name = col.propertyName;
|
|
793
|
+
if (!columnNames.has(name)) continue;
|
|
794
|
+
if (name === "deleted" || name === "deletedAt" || name === "deletedBy") continue;
|
|
795
|
+
if (!isListNumericColumn(col)) continue;
|
|
796
|
+
if (Object.prototype.hasOwnProperty.call(and, name)) continue;
|
|
797
|
+
const minRaw = searchParams.get(`${name}Min`)?.trim();
|
|
798
|
+
const maxRaw = searchParams.get(`${name}Max`)?.trim();
|
|
799
|
+
if (!minRaw && !maxRaw) continue;
|
|
800
|
+
const parseNum = (s) => {
|
|
801
|
+
const n = Number(s);
|
|
802
|
+
return Number.isFinite(n) ? n : null;
|
|
803
|
+
};
|
|
804
|
+
const nMin = minRaw ? parseNum(minRaw) : null;
|
|
805
|
+
const nMax = maxRaw ? parseNum(maxRaw) : null;
|
|
806
|
+
if (nMin != null && nMax != null) {
|
|
807
|
+
and[name] = Between(nMin, nMax);
|
|
808
|
+
} else if (nMin != null) {
|
|
809
|
+
and[name] = MoreThanOrEqual(nMin);
|
|
810
|
+
} else if (nMax != null) {
|
|
811
|
+
and[name] = LessThanOrEqual(nMax);
|
|
812
|
+
}
|
|
813
|
+
}
|
|
814
|
+
for (const col of repo.metadata.columns) {
|
|
815
|
+
const name = col.propertyName;
|
|
816
|
+
if (!columnNames.has(name)) continue;
|
|
817
|
+
if (LIST_QUERY_RESERVED.has(name)) continue;
|
|
818
|
+
if (name === "deleted" || name === "deletedAt" || name === "deletedBy") continue;
|
|
819
|
+
if (!isListStringColumn(col)) continue;
|
|
820
|
+
if (Object.prototype.hasOwnProperty.call(and, name)) continue;
|
|
821
|
+
const raw = searchParams.get(name)?.trim();
|
|
822
|
+
if (!raw) continue;
|
|
823
|
+
and[name] = ILike(`%${raw}%`);
|
|
824
|
+
}
|
|
825
|
+
return and;
|
|
826
|
+
}
|
|
827
|
+
function buildExactListParamWhere(repo, searchParams) {
|
|
828
|
+
const extraWhere = {};
|
|
829
|
+
const columnNames = new Set(repo.metadata.columns.map((c) => c.propertyName));
|
|
830
|
+
for (const col of repo.metadata.columns) {
|
|
831
|
+
const name = col.propertyName;
|
|
832
|
+
if (!columnNames.has(name)) continue;
|
|
833
|
+
if (name === "deleted" || name === "deletedAt" || name === "deletedBy") continue;
|
|
834
|
+
if (!isListNumericColumn(col)) continue;
|
|
835
|
+
const v = searchParams.get(name)?.trim();
|
|
836
|
+
if (v == null || v === "") continue;
|
|
837
|
+
const n = Number(v);
|
|
838
|
+
if (!Number.isFinite(n)) continue;
|
|
839
|
+
extraWhere[name] = n;
|
|
840
|
+
}
|
|
841
|
+
for (const col of repo.metadata.columns) {
|
|
842
|
+
if (String(col.type) !== "boolean") continue;
|
|
843
|
+
const name = col.propertyName;
|
|
844
|
+
if (!columnNames.has(name)) continue;
|
|
845
|
+
const raw = searchParams.get(name)?.trim();
|
|
846
|
+
if (raw === "true" || raw === "false") {
|
|
847
|
+
extraWhere[name] = raw === "true";
|
|
848
|
+
}
|
|
849
|
+
}
|
|
850
|
+
return extraWhere;
|
|
851
|
+
}
|
|
634
852
|
function mergeDeletedFalseWhere(repo, where) {
|
|
635
853
|
if (!entityHasSoftDelete(repo)) return where;
|
|
636
854
|
const d = { deleted: false };
|
|
@@ -640,6 +858,12 @@ function mergeDeletedFalseWhere(repo, where) {
|
|
|
640
858
|
}
|
|
641
859
|
return Object.keys(where).length > 0 ? { ...where, ...d } : d;
|
|
642
860
|
}
|
|
861
|
+
function pickDefaultListSortField(columnNames, columns) {
|
|
862
|
+
for (const candidate of ["createdAt", "updatedAt", "id", "name", "sortOrder", "title"]) {
|
|
863
|
+
if (columnNames.has(candidate)) return candidate;
|
|
864
|
+
}
|
|
865
|
+
return columns[0]?.propertyName ?? "id";
|
|
866
|
+
}
|
|
643
867
|
function normalizeProductSku(value) {
|
|
644
868
|
if (value == null) return null;
|
|
645
869
|
const s = String(value).trim();
|
|
@@ -742,6 +966,18 @@ function createCrudHandler(dataSource, entityMap, options) {
|
|
|
742
966
|
if (statusFilter) qb.andWhere("order.status = :status", { status: statusFilter });
|
|
743
967
|
if (dateFrom) qb.andWhere("order.createdAt >= :dateFrom", { dateFrom: /* @__PURE__ */ new Date(dateFrom + "T00:00:00.000Z") });
|
|
744
968
|
if (dateTo) qb.andWhere("order.createdAt <= :dateTo", { dateTo: /* @__PURE__ */ new Date(dateTo + "T23:59:59.999Z") });
|
|
969
|
+
const totalMin = searchParams.get("totalMin")?.trim();
|
|
970
|
+
const totalMax = searchParams.get("totalMax")?.trim();
|
|
971
|
+
if (totalMin) {
|
|
972
|
+
const n = Number(totalMin);
|
|
973
|
+
if (Number.isFinite(n)) qb.andWhere("order.total >= :totalMin", { totalMin: n });
|
|
974
|
+
}
|
|
975
|
+
if (totalMax) {
|
|
976
|
+
const n = Number(totalMax);
|
|
977
|
+
if (Number.isFinite(n)) qb.andWhere("order.total <= :totalMax", { totalMax: n });
|
|
978
|
+
}
|
|
979
|
+
const currency = searchParams.get("currency")?.trim();
|
|
980
|
+
if (currency) qb.andWhere("order.currency ILIKE :orderCurrency", { orderCurrency: `%${currency}%` });
|
|
745
981
|
if (orderIdsFromPayment && orderIdsFromPayment.length) qb.andWhere("order.id IN (:...orderIds)", { orderIds: orderIdsFromPayment });
|
|
746
982
|
const [rows, total2] = await qb.getManyAndCount();
|
|
747
983
|
const data2 = rows.map((order) => {
|
|
@@ -780,8 +1016,30 @@ function createCrudHandler(dataSource, entityMap, options) {
|
|
|
780
1016
|
if (statusFilter) qb.andWhere("payment.status = :status", { status: statusFilter });
|
|
781
1017
|
if (dateFrom) qb.andWhere("payment.createdAt >= :dateFrom", { dateFrom: /* @__PURE__ */ new Date(dateFrom + "T00:00:00.000Z") });
|
|
782
1018
|
if (dateTo) qb.andWhere("payment.createdAt <= :dateTo", { dateTo: /* @__PURE__ */ new Date(dateTo + "T23:59:59.999Z") });
|
|
1019
|
+
const paidAtFrom = searchParams.get("paidAtFrom")?.trim();
|
|
1020
|
+
const paidAtTo = searchParams.get("paidAtTo")?.trim();
|
|
1021
|
+
if (paidAtFrom) {
|
|
1022
|
+
qb.andWhere("payment.paidAt >= :paidAtFrom", { paidAtFrom: /* @__PURE__ */ new Date(paidAtFrom + "T00:00:00.000Z") });
|
|
1023
|
+
}
|
|
1024
|
+
if (paidAtTo) {
|
|
1025
|
+
qb.andWhere("payment.paidAt <= :paidAtTo", { paidAtTo: /* @__PURE__ */ new Date(paidAtTo + "T23:59:59.999Z") });
|
|
1026
|
+
}
|
|
783
1027
|
if (methodFilter) qb.andWhere("payment.method = :method", { method: methodFilter });
|
|
784
1028
|
if (orderNumberParam) qb.andWhere("ord.orderNumber ILIKE :orderNumber", { orderNumber: `%${orderNumberParam}%` });
|
|
1029
|
+
const amountMin = searchParams.get("amountMin")?.trim();
|
|
1030
|
+
const amountMax = searchParams.get("amountMax")?.trim();
|
|
1031
|
+
if (amountMin) {
|
|
1032
|
+
const n = Number(amountMin);
|
|
1033
|
+
if (Number.isFinite(n)) qb.andWhere("payment.amount >= :amountMin", { amountMin: n });
|
|
1034
|
+
}
|
|
1035
|
+
if (amountMax) {
|
|
1036
|
+
const n = Number(amountMax);
|
|
1037
|
+
if (Number.isFinite(n)) qb.andWhere("payment.amount <= :amountMax", { amountMax: n });
|
|
1038
|
+
}
|
|
1039
|
+
const extRef = searchParams.get("externalReference")?.trim();
|
|
1040
|
+
if (extRef) {
|
|
1041
|
+
qb.andWhere("payment.externalReference ILIKE :extRef", { extRef: `%${extRef}%` });
|
|
1042
|
+
}
|
|
785
1043
|
const [rows, total2] = await qb.getManyAndCount();
|
|
786
1044
|
const data2 = rows.map((payment) => {
|
|
787
1045
|
const order = payment.order;
|
|
@@ -800,7 +1058,10 @@ function createCrudHandler(dataSource, entityMap, options) {
|
|
|
800
1058
|
const repo2 = dataSource.getRepository(entity);
|
|
801
1059
|
const statusFilter = searchParams.get("status")?.trim();
|
|
802
1060
|
const inventory = searchParams.get("inventory")?.trim();
|
|
803
|
-
const productWhere = {
|
|
1061
|
+
const productWhere = {
|
|
1062
|
+
deleted: false,
|
|
1063
|
+
...buildListFilterAndFromSearchParams(repo2, searchParams)
|
|
1064
|
+
};
|
|
804
1065
|
if (statusFilter) productWhere.status = statusFilter;
|
|
805
1066
|
if (inventory === "in_stock") productWhere.quantity = MoreThan(0);
|
|
806
1067
|
if (inventory === "out_of_stock") productWhere.quantity = 0;
|
|
@@ -818,11 +1079,15 @@ function createCrudHandler(dataSource, entityMap, options) {
|
|
|
818
1079
|
if (search && typeof search === "string" && search.trim()) {
|
|
819
1080
|
productWhere.name = ILike(`%${search.trim()}%`);
|
|
820
1081
|
}
|
|
1082
|
+
const productColumnNames = new Set(repo2.metadata.columns.map((c) => c.propertyName));
|
|
1083
|
+
const defaultProductSort = pickDefaultListSortField(productColumnNames, repo2.metadata.columns);
|
|
1084
|
+
const sortParam2 = (searchParams.get("sortField") ?? "").trim();
|
|
1085
|
+
const productSortField = sortParam2 && productColumnNames.has(sortParam2) ? sortParam2 : defaultProductSort;
|
|
821
1086
|
const [data2, total2] = await repo2.findAndCount({
|
|
822
1087
|
where: Object.keys(productWhere).length ? productWhere : void 0,
|
|
823
1088
|
skip,
|
|
824
1089
|
take: limit,
|
|
825
|
-
order: { [
|
|
1090
|
+
order: { [productSortField]: sortOrder }
|
|
826
1091
|
});
|
|
827
1092
|
return json({ total: total2, page, limit, totalPages: Math.ceil(total2 / limit), data: data2 });
|
|
828
1093
|
}
|
|
@@ -915,36 +1180,22 @@ function createCrudHandler(dataSource, entityMap, options) {
|
|
|
915
1180
|
}
|
|
916
1181
|
return json({ total: total2, page, limit, totalPages: Math.ceil(total2 / limit), data: data2 });
|
|
917
1182
|
}
|
|
918
|
-
const
|
|
1183
|
+
const defaultSortField = pickDefaultListSortField(columnNames, repo.metadata.columns);
|
|
1184
|
+
const sortParam = (searchParams.get("sortField") ?? "").trim();
|
|
1185
|
+
const sortField = sortParam && columnNames.has(sortParam) ? sortParam : defaultSortField;
|
|
919
1186
|
let where = {};
|
|
920
1187
|
if (search) {
|
|
921
1188
|
where = buildSearchWhereClause(repo, search);
|
|
922
1189
|
}
|
|
923
|
-
|
|
924
|
-
const
|
|
925
|
-
|
|
926
|
-
const v = searchParams.get(key);
|
|
927
|
-
if (v != null && v !== "" && columnNames.has(key)) {
|
|
928
|
-
const n = Number(v);
|
|
929
|
-
if (Number.isFinite(n)) extraWhere[key] = n;
|
|
930
|
-
}
|
|
931
|
-
}
|
|
932
|
-
for (const col of repo.metadata.columns) {
|
|
933
|
-
if (String(col.type) !== "boolean") continue;
|
|
934
|
-
const name = col.propertyName;
|
|
935
|
-
if (!columnNames.has(name)) continue;
|
|
936
|
-
const raw = searchParams.get(name)?.trim();
|
|
937
|
-
if (raw === "true" || raw === "false") {
|
|
938
|
-
extraWhere[name] = raw === "true";
|
|
939
|
-
}
|
|
940
|
-
}
|
|
941
|
-
if (Object.keys(extraWhere).length > 0) {
|
|
1190
|
+
where = mergeListWhereAnd(where, buildListFilterAndFromSearchParams(repo, searchParams));
|
|
1191
|
+
const exactParamWhere = buildExactListParamWhere(repo, searchParams);
|
|
1192
|
+
if (Object.keys(exactParamWhere).length > 0) {
|
|
942
1193
|
if (Array.isArray(where)) {
|
|
943
|
-
where = where.map((w) => ({ ...w, ...
|
|
1194
|
+
where = where.map((w) => ({ ...w, ...exactParamWhere }));
|
|
944
1195
|
} else if (where && typeof where === "object" && Object.keys(where).length > 0) {
|
|
945
|
-
where = { ...where, ...
|
|
1196
|
+
where = { ...where, ...exactParamWhere };
|
|
946
1197
|
} else {
|
|
947
|
-
where =
|
|
1198
|
+
where = exactParamWhere;
|
|
948
1199
|
}
|
|
949
1200
|
}
|
|
950
1201
|
where = mergeDeletedFalseWhere(repo, where);
|
|
@@ -1024,6 +1275,10 @@ function createCrudHandler(dataSource, entityMap, options) {
|
|
|
1024
1275
|
}
|
|
1025
1276
|
const repo = dataSource.getRepository(entity);
|
|
1026
1277
|
const persistBody = resource === "media" ? body : pickColumnUpdates(repo, body);
|
|
1278
|
+
if (resource === "contacts" && "type" in persistBody) {
|
|
1279
|
+
const t = persistBody.type;
|
|
1280
|
+
if (t === "" || t === "none" || t == null) persistBody.type = null;
|
|
1281
|
+
}
|
|
1027
1282
|
if (resource !== "media" && Object.keys(persistBody).length === 0) {
|
|
1028
1283
|
logCrudClientError("POST create", {
|
|
1029
1284
|
reason: "no_scalar_columns_after_pick",
|
|
@@ -1374,6 +1629,10 @@ function createCrudByIdHandler(dataSource, entityMap, options) {
|
|
|
1374
1629
|
if (!cur) return json({ message: "Not found" }, { status: 404 });
|
|
1375
1630
|
}
|
|
1376
1631
|
const updatePayload = rawBody && typeof rawBody === "object" ? pickColumnUpdates(repo, rawBody) : {};
|
|
1632
|
+
if (resource === "contacts" && "type" in updatePayload) {
|
|
1633
|
+
const t = updatePayload.type;
|
|
1634
|
+
if (t === "" || t === "none" || t == null) updatePayload.type = null;
|
|
1635
|
+
}
|
|
1377
1636
|
if (resource === "media") {
|
|
1378
1637
|
const u = updatePayload;
|
|
1379
1638
|
delete u.kind;
|
|
@@ -1663,7 +1922,7 @@ function createUserAuthApiRouter(config) {
|
|
|
1663
1922
|
// src/api/cms-handlers.ts
|
|
1664
1923
|
init_email_queue();
|
|
1665
1924
|
init_erp_queue();
|
|
1666
|
-
import { MoreThanOrEqual, ILike as ILike2, In } from "typeorm";
|
|
1925
|
+
import { MoreThanOrEqual as MoreThanOrEqual2, ILike as ILike2, In } from "typeorm";
|
|
1667
1926
|
|
|
1668
1927
|
// src/plugins/captcha/assert.ts
|
|
1669
1928
|
async function assertCaptchaOk(getCms, body, req, json) {
|
|
@@ -2049,9 +2308,9 @@ function createDashboardStatsHandler(config) {
|
|
|
2049
2308
|
repo("users")?.count({ where: { deleted: false } }) ?? 0,
|
|
2050
2309
|
repo("blogs")?.count({ where: { deleted: false } }) ?? 0,
|
|
2051
2310
|
repo("contacts")?.count({
|
|
2052
|
-
where: { deleted: false, createdAt:
|
|
2311
|
+
where: { deleted: false, createdAt: MoreThanOrEqual2(sevenDaysAgo) }
|
|
2053
2312
|
}) ?? 0,
|
|
2054
|
-
repo("form_submissions")?.count({ where: { createdAt:
|
|
2313
|
+
repo("form_submissions")?.count({ where: { createdAt: MoreThanOrEqual2(sevenDaysAgo) } }) ?? 0,
|
|
2055
2314
|
repo("contacts")?.createQueryBuilder("c").select("COALESCE(NULLIF(TRIM(c.type), ''), 'unknown')", "type").addSelect("COUNT(*)", "count").where("c.deleted = :deleted", { deleted: false }).groupBy("COALESCE(NULLIF(TRIM(c.type), ''), 'unknown')").getRawMany() ?? []
|
|
2056
2315
|
]);
|
|
2057
2316
|
return json({
|
|
@@ -2100,19 +2359,19 @@ function createEcommerceAnalyticsHandler(config) {
|
|
|
2100
2359
|
const productRepo = dataSource.getRepository(entityMap.products);
|
|
2101
2360
|
const [salesOrders, returnOrders, replacementOrders, payments, products] = await Promise.all([
|
|
2102
2361
|
orderRepo.find({
|
|
2103
|
-
where: { deleted: false, createdAt:
|
|
2362
|
+
where: { deleted: false, createdAt: MoreThanOrEqual2(start), orderKind: "sale", status: In(["confirmed", "processing", "completed"]) },
|
|
2104
2363
|
select: ["id", "contactId", "createdAt", "subtotal", "discount", "tax", "total", "status"]
|
|
2105
2364
|
}),
|
|
2106
2365
|
orderRepo.find({
|
|
2107
|
-
where: { deleted: false, createdAt:
|
|
2366
|
+
where: { deleted: false, createdAt: MoreThanOrEqual2(start), orderKind: "return" },
|
|
2108
2367
|
select: ["id", "createdAt", "total"]
|
|
2109
2368
|
}),
|
|
2110
2369
|
orderRepo.find({
|
|
2111
|
-
where: { deleted: false, createdAt:
|
|
2370
|
+
where: { deleted: false, createdAt: MoreThanOrEqual2(start), orderKind: "replacement" },
|
|
2112
2371
|
select: ["id", "createdAt", "total"]
|
|
2113
2372
|
}),
|
|
2114
2373
|
paymentRepo.find({
|
|
2115
|
-
where: { deleted: false, createdAt:
|
|
2374
|
+
where: { deleted: false, createdAt: MoreThanOrEqual2(start) },
|
|
2116
2375
|
select: ["id", "status", "method", "amount", "createdAt"]
|
|
2117
2376
|
}),
|
|
2118
2377
|
productRepo.find({
|
|
@@ -2839,18 +3098,37 @@ function createUsersApiHandlers(config) {
|
|
|
2839
3098
|
try {
|
|
2840
3099
|
const body = await req.json();
|
|
2841
3100
|
if (!body?.name || !body?.email) return json({ error: "Name and email are required" }, { status: 400 });
|
|
2842
|
-
const
|
|
2843
|
-
|
|
3101
|
+
const email = body.email;
|
|
3102
|
+
const existing = await userRepo().findOne({ where: { email } });
|
|
3103
|
+
if (existing && !existing.deleted) {
|
|
3104
|
+
return json({ error: "User with this email already exists" }, { status: 400 });
|
|
3105
|
+
}
|
|
2844
3106
|
const groupRepo = dataSource.getRepository(entityMap.user_groups);
|
|
2845
3107
|
const customerG = await groupRepo.findOne({ where: { name: "Customer", deleted: false } });
|
|
2846
3108
|
const gid = body.groupId ?? null;
|
|
2847
3109
|
const isCustomer = !!(customerG && gid === customerG.id);
|
|
2848
3110
|
const adminAccess = isCustomer ? false : body.adminAccess === false ? false : true;
|
|
2849
3111
|
const blocked = body.blocked === true || body.blocked === "true" || body.blocked === 1 || body.blocked === "1";
|
|
2850
|
-
const newUser = await
|
|
3112
|
+
const newUser = existing?.deleted ? await (async () => {
|
|
3113
|
+
await userRepo().update(existing.id, {
|
|
3114
|
+
deleted: false,
|
|
3115
|
+
deletedAt: null,
|
|
3116
|
+
deletedBy: null,
|
|
3117
|
+
name: body.name,
|
|
3118
|
+
email,
|
|
3119
|
+
password: null,
|
|
3120
|
+
blocked,
|
|
3121
|
+
groupId: gid,
|
|
3122
|
+
adminAccess,
|
|
3123
|
+
updatedAt: /* @__PURE__ */ new Date()
|
|
3124
|
+
});
|
|
3125
|
+
const row = await userRepo().findOne({ where: { id: existing.id } });
|
|
3126
|
+
if (!row) throw new Error("user missing after restore");
|
|
3127
|
+
return row;
|
|
3128
|
+
})() : await userRepo().save(
|
|
2851
3129
|
userRepo().create({
|
|
2852
3130
|
name: body.name,
|
|
2853
|
-
email
|
|
3131
|
+
email,
|
|
2854
3132
|
password: null,
|
|
2855
3133
|
blocked,
|
|
2856
3134
|
groupId: gid,
|
|
@@ -3003,21 +3281,110 @@ function createUserAvatarHandler(config) {
|
|
|
3003
3281
|
}
|
|
3004
3282
|
};
|
|
3005
3283
|
}
|
|
3284
|
+
var PROFILE_EMAIL_RE = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
|
3006
3285
|
function createUserProfileHandler(config) {
|
|
3007
|
-
const { dataSource, entityMap, json, getSession } = config;
|
|
3008
|
-
|
|
3286
|
+
const { dataSource, entityMap, json, getSession, onProfileUpdated } = config;
|
|
3287
|
+
async function loadCurrentUser() {
|
|
3009
3288
|
const session = await getSession();
|
|
3010
|
-
|
|
3011
|
-
|
|
3012
|
-
|
|
3013
|
-
|
|
3014
|
-
|
|
3015
|
-
|
|
3016
|
-
|
|
3017
|
-
|
|
3018
|
-
|
|
3019
|
-
|
|
3020
|
-
|
|
3289
|
+
const su = session?.user;
|
|
3290
|
+
if (!su?.email && su?.id == null) {
|
|
3291
|
+
return { ok: false, response: json({ error: "Unauthorized" }, { status: 401 }) };
|
|
3292
|
+
}
|
|
3293
|
+
const userRepo = dataSource.getRepository(entityMap.users);
|
|
3294
|
+
let user = null;
|
|
3295
|
+
const uidRaw = su.id != null ? String(su.id).trim() : "";
|
|
3296
|
+
const uid = uidRaw && /^\d+$/.test(uidRaw) ? parseInt(uidRaw, 10) : NaN;
|
|
3297
|
+
if (Number.isFinite(uid) && uid > 0) {
|
|
3298
|
+
user = await userRepo.findOne({
|
|
3299
|
+
where: { id: uid, deleted: false },
|
|
3300
|
+
select: ["id", "name", "email", "phone", "createdAt"]
|
|
3301
|
+
});
|
|
3302
|
+
}
|
|
3303
|
+
if (!user && su.email) {
|
|
3304
|
+
const em = String(su.email).trim().toLowerCase();
|
|
3305
|
+
if (em) {
|
|
3306
|
+
user = await userRepo.findOne({
|
|
3307
|
+
where: { email: em, deleted: false },
|
|
3308
|
+
select: ["id", "name", "email", "phone", "createdAt"]
|
|
3309
|
+
});
|
|
3310
|
+
}
|
|
3311
|
+
}
|
|
3312
|
+
if (!user) return { ok: false, response: json({ error: "Not found" }, { status: 404 }) };
|
|
3313
|
+
return { ok: true, user };
|
|
3314
|
+
}
|
|
3315
|
+
return {
|
|
3316
|
+
async GET(_req) {
|
|
3317
|
+
try {
|
|
3318
|
+
const r = await loadCurrentUser();
|
|
3319
|
+
if (!r.ok) return r.response;
|
|
3320
|
+
const u = r.user;
|
|
3321
|
+
return json({
|
|
3322
|
+
id: u.id,
|
|
3323
|
+
name: u.name ?? "",
|
|
3324
|
+
email: u.email ?? "",
|
|
3325
|
+
phone: u.phone ?? null,
|
|
3326
|
+
createdAt: u.createdAt instanceof Date ? u.createdAt.toISOString() : u.createdAt ?? void 0
|
|
3327
|
+
});
|
|
3328
|
+
} catch {
|
|
3329
|
+
return json({ error: "Internal server error" }, { status: 500 });
|
|
3330
|
+
}
|
|
3331
|
+
},
|
|
3332
|
+
async PUT(req) {
|
|
3333
|
+
try {
|
|
3334
|
+
const r = await loadCurrentUser();
|
|
3335
|
+
if (!r.ok) return r.response;
|
|
3336
|
+
const current = r.user;
|
|
3337
|
+
let body;
|
|
3338
|
+
try {
|
|
3339
|
+
body = await req.json();
|
|
3340
|
+
} catch {
|
|
3341
|
+
return json({ error: "Invalid JSON" }, { status: 400 });
|
|
3342
|
+
}
|
|
3343
|
+
const name = typeof body.name === "string" ? body.name.trim() : "";
|
|
3344
|
+
if (!name) return json({ error: "Name is required" }, { status: 400 });
|
|
3345
|
+
const emailRaw = typeof body.email === "string" ? body.email.trim().toLowerCase() : "";
|
|
3346
|
+
if (!emailRaw || !PROFILE_EMAIL_RE.test(emailRaw)) {
|
|
3347
|
+
return json({ error: "Valid email is required" }, { status: 400 });
|
|
3348
|
+
}
|
|
3349
|
+
const phone = body.phone === null || body.phone === void 0 ? null : typeof body.phone === "string" ? body.phone.trim() || null : null;
|
|
3350
|
+
const userRepo = dataSource.getRepository(entityMap.users);
|
|
3351
|
+
if (emailRaw !== String(current.email ?? "").toLowerCase()) {
|
|
3352
|
+
const taken = await userRepo.findOne({
|
|
3353
|
+
where: { email: emailRaw, deleted: false },
|
|
3354
|
+
select: ["id"]
|
|
3355
|
+
});
|
|
3356
|
+
if (taken && taken.id !== current.id) {
|
|
3357
|
+
return json({ error: "Email is already in use" }, { status: 409 });
|
|
3358
|
+
}
|
|
3359
|
+
}
|
|
3360
|
+
await userRepo.update(
|
|
3361
|
+
{ id: current.id },
|
|
3362
|
+
{
|
|
3363
|
+
name,
|
|
3364
|
+
email: emailRaw,
|
|
3365
|
+
phone,
|
|
3366
|
+
updatedAt: /* @__PURE__ */ new Date()
|
|
3367
|
+
}
|
|
3368
|
+
);
|
|
3369
|
+
const updated = await userRepo.findOne({
|
|
3370
|
+
where: { id: current.id },
|
|
3371
|
+
select: ["id", "name", "email", "phone"]
|
|
3372
|
+
});
|
|
3373
|
+
if (!updated) return json({ error: "Not found" }, { status: 404 });
|
|
3374
|
+
const row = updated;
|
|
3375
|
+
if (onProfileUpdated) {
|
|
3376
|
+
try {
|
|
3377
|
+
await onProfileUpdated(req, row);
|
|
3378
|
+
} catch {
|
|
3379
|
+
}
|
|
3380
|
+
}
|
|
3381
|
+
return json({
|
|
3382
|
+
message: "Profile updated successfully",
|
|
3383
|
+
user: { id: row.id, name: row.name, email: row.email, phone: row.phone }
|
|
3384
|
+
});
|
|
3385
|
+
} catch {
|
|
3386
|
+
return json({ error: "Internal server error" }, { status: 500 });
|
|
3387
|
+
}
|
|
3021
3388
|
}
|
|
3022
3389
|
};
|
|
3023
3390
|
}
|
|
@@ -7039,7 +7406,7 @@ function createCmsApiHandler(config) {
|
|
|
7039
7406
|
} : usersApi;
|
|
7040
7407
|
const usersHandlers = usersApiMerged ? createUsersApiHandlers(mergePerm(usersApiMerged) ?? usersApiMerged) : null;
|
|
7041
7408
|
const avatarPost = userAvatar ? createUserAvatarHandler(userAvatar) : null;
|
|
7042
|
-
const
|
|
7409
|
+
const profileHandlers = userProfile ? createUserProfileHandler(userProfile) : null;
|
|
7043
7410
|
const settingsHandlers = settingsConfig ? createSettingsApiHandlers(settingsConfig) : null;
|
|
7044
7411
|
const smsMessageTemplateHandlers = createSmsMessageTemplateHandlers({
|
|
7045
7412
|
dataSource,
|
|
@@ -7138,7 +7505,10 @@ function createCmsApiHandler(config) {
|
|
|
7138
7505
|
}
|
|
7139
7506
|
if (path.length === 2) {
|
|
7140
7507
|
if (path[1] === "avatar" && m === "POST" && avatarPost) return avatarPost(req);
|
|
7141
|
-
if (path[1] === "profile" &&
|
|
7508
|
+
if (path[1] === "profile" && profileHandlers) {
|
|
7509
|
+
if (m === "GET") return profileHandlers.GET(req);
|
|
7510
|
+
if (m === "PUT") return profileHandlers.PUT(req);
|
|
7511
|
+
}
|
|
7142
7512
|
const id = path[1];
|
|
7143
7513
|
if (m === "GET") return usersHandlers.getById(req, id);
|
|
7144
7514
|
if (m === "PUT" || m === "PATCH") return usersHandlers.update(req, id);
|