strapi-plugin-bulk-updater 1.4.6 → 1.4.10

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.
@@ -665,7 +665,7 @@ const csvController = ({ strapi }) => ({
665
665
  },
666
666
  async importCsv(ctx) {
667
667
  try {
668
- const { uid, csvText, mode, publish } = ctx.request.body;
668
+ const { uid, csvText, mode, publish, publishStrategy, unreviewedConsent } = ctx.request.body;
669
669
  if (!uid) {
670
670
  ctx.status = 400;
671
671
  ctx.body = { success: false, error: "Target collection UID is required." };
@@ -680,6 +680,8 @@ const csvController = ({ strapi }) => ({
680
680
  const result = await csvService2.importCsv(uid, csvText, {
681
681
  mode: mode === "update" ? "update" : mode === "create_or_override" ? "create_or_override" : "auto",
682
682
  publish: publish !== false,
683
+ publishStrategy: publishStrategy === "all" ? "all" : "safe",
684
+ unreviewedConsent: Boolean(unreviewedConsent),
683
685
  user: ctx.state.user
684
686
  });
685
687
  ctx.body = result;
@@ -690,7 +692,7 @@ const csvController = ({ strapi }) => ({
690
692
  },
691
693
  async importChunk(ctx) {
692
694
  try {
693
- const { uid, rows, batchId, isFirstChunk, isLastChunk, startRowNumber, mode, publish } = ctx.request.body;
695
+ const { uid, rows, batchId, isFirstChunk, isLastChunk, startRowNumber, mode, publish, publishStrategy, unreviewedConsent } = ctx.request.body;
694
696
  if (!uid) {
695
697
  ctx.status = 400;
696
698
  ctx.body = { success: false, error: "Target collection UID is required." };
@@ -709,6 +711,8 @@ const csvController = ({ strapi }) => ({
709
711
  startRowNumber: typeof startRowNumber === "number" ? startRowNumber : void 0,
710
712
  mode: mode === "update" ? "update" : mode === "create_or_override" ? "create_or_override" : "auto",
711
713
  publish: publish !== false,
714
+ publishStrategy: publishStrategy === "all" ? "all" : "safe",
715
+ unreviewedConsent: Boolean(unreviewedConsent),
712
716
  user: ctx.state.user
713
717
  });
714
718
  ctx.body = result;
@@ -742,7 +746,7 @@ const csvController = ({ strapi }) => ({
742
746
  },
743
747
  async submitBatch(ctx) {
744
748
  try {
745
- const { uid, rows, executionMode, makerNotes, forceImmediate, mode, publish } = ctx.request.body;
749
+ const { uid, rows, executionMode, makerNotes, forceImmediate, mode, publish, publishStrategy, unreviewedConsent, unreviewedCount } = ctx.request.body;
746
750
  if (!uid) {
747
751
  ctx.status = 400;
748
752
  ctx.body = { success: false, error: "Target collection UID is required." };
@@ -760,6 +764,9 @@ const csvController = ({ strapi }) => ({
760
764
  forceImmediate: Boolean(forceImmediate),
761
765
  mode: mode === "update" ? "update" : mode === "create_or_override" ? "create_or_override" : "auto",
762
766
  publish: publish !== false,
767
+ publishStrategy: publishStrategy === "all" ? "all" : "safe",
768
+ unreviewedConsent: Boolean(unreviewedConsent),
769
+ unreviewedCount: typeof unreviewedCount === "number" ? unreviewedCount : 0,
763
770
  user: ctx.state.user
764
771
  });
765
772
  ctx.body = result;
@@ -917,6 +924,60 @@ const modelsCache = /* @__PURE__ */ new Map();
917
924
  const MAX_QUERY_CACHE_SIZE = 500;
918
925
  const CACHE_TTL_MS = 60 * 1e3;
919
926
  let staticSchemaCache = null;
927
+ function formatEntryRecord(entry) {
928
+ if (!entry || typeof entry !== "object") return entry;
929
+ const formatted = {
930
+ id: entry.id,
931
+ documentId: entry.documentId,
932
+ ...entry
933
+ };
934
+ if (entry.updatedAt !== void 0 && formatted.updated_at === void 0) {
935
+ formatted.updated_at = entry.updatedAt;
936
+ }
937
+ if (entry.updated_at !== void 0 && formatted.updatedAt === void 0) {
938
+ formatted.updatedAt = entry.updated_at;
939
+ }
940
+ if (entry.createdAt !== void 0 && formatted.created_at === void 0) {
941
+ formatted.created_at = entry.createdAt;
942
+ }
943
+ if (entry.created_at !== void 0 && formatted.createdAt === void 0) {
944
+ formatted.createdAt = entry.created_at;
945
+ }
946
+ if (entry.publishedAt !== void 0 && formatted.published_at === void 0) {
947
+ formatted.published_at = entry.publishedAt;
948
+ }
949
+ if (entry.published_at !== void 0 && formatted.publishedAt === void 0) {
950
+ formatted.publishedAt = entry.published_at;
951
+ }
952
+ return formatted;
953
+ }
954
+ function resolveContentTypeField(contentType, field) {
955
+ if (!field) return null;
956
+ const attrs = contentType?.attributes || {};
957
+ if (attrs[field]) return field;
958
+ if (field === "updated_at" || field === "updatedAt") {
959
+ return attrs["updated_at"] ? "updated_at" : "updatedAt";
960
+ }
961
+ if (field === "created_at" || field === "createdAt") {
962
+ return attrs["created_at"] ? "created_at" : "createdAt";
963
+ }
964
+ if (field === "published_at" || field === "publishedAt") {
965
+ return attrs["published_at"] ? "published_at" : "publishedAt";
966
+ }
967
+ if (field === "id" || field === "documentId") {
968
+ return field;
969
+ }
970
+ const camel = field.replace(/_([a-z0-9])/g, (_, l) => l.toUpperCase());
971
+ if (attrs[camel]) return camel;
972
+ const snake = field.replace(/([A-Z])/g, "_$1").toLowerCase();
973
+ if (attrs[snake]) return snake;
974
+ const lower = field.toLowerCase().replace(/_/g, "");
975
+ const matched = Object.keys(attrs).find(
976
+ (k) => k.toLowerCase().replace(/_/g, "") === lower
977
+ );
978
+ if (matched) return matched;
979
+ return null;
980
+ }
920
981
  function buildDeepPopulate$1(attributes, allComponents, depth = 0) {
921
982
  if (depth > 5) return void 0;
922
983
  const populate = {};
@@ -1349,7 +1410,9 @@ async function executeBatchInternal(strapi, {
1349
1410
  selectedCollections = [],
1350
1411
  isRegex = false,
1351
1412
  regexPattern = "",
1352
- publishOnApproval = true
1413
+ publishOnApproval = true,
1414
+ publishStrategy = "safe",
1415
+ unreviewedConsent = false
1353
1416
  }, currentUser, existingBatchId) {
1354
1417
  if (!attribute) {
1355
1418
  throw new Error("Attribute name is required.");
@@ -1402,6 +1465,8 @@ async function executeBatchInternal(strapi, {
1402
1465
  failedCount: 0,
1403
1466
  isLive: false,
1404
1467
  publishOnApproval: Boolean(publishOnApproval),
1468
+ publishStrategy,
1469
+ unreviewedConsent,
1405
1470
  results: []
1406
1471
  };
1407
1472
  batchJobsMap.set(batchId, jobState);
@@ -1475,7 +1540,9 @@ async function executeBatchInternal(strapi, {
1475
1540
  }
1476
1541
  });
1477
1542
  const isDraftAndPublishComp = Boolean(contentType?.options?.draftAndPublish);
1478
- if (publishOnApproval && isDraftAndPublishComp) {
1543
+ const isApprovedComp = isDraftAndPublishComp && Boolean(currentEntry?.publishedAt) && currentEntry?.status !== "modified";
1544
+ const shouldPublishComp = isDraftAndPublishComp && Boolean(publishOnApproval) && (publishStrategy === "all" ? true : isApprovedComp);
1545
+ if (shouldPublishComp) {
1479
1546
  try {
1480
1547
  await strapi.documents(target.uid).publish({
1481
1548
  documentId: target.documentId
@@ -1489,7 +1556,7 @@ async function executeBatchInternal(strapi, {
1489
1556
  uid: target.uid,
1490
1557
  documentId: target.documentId,
1491
1558
  userId: currentUser?.id,
1492
- statusOverride: publishOnApproval && isDraftAndPublishComp ? "published" : void 0
1559
+ statusOverride: shouldPublishComp ? "published" : isDraftAndPublishComp ? "draft" : void 0
1493
1560
  });
1494
1561
  const dzDiffs = isRootDz && affectedMatches.length > 0 ? affectedMatches.map((m) => ({
1495
1562
  index: m.index,
@@ -1572,7 +1639,9 @@ async function executeBatchInternal(strapi, {
1572
1639
  }
1573
1640
  });
1574
1641
  const isDraftAndPublishRel = Boolean(contentType?.options?.draftAndPublish);
1575
- if (publishOnApproval && isDraftAndPublishRel) {
1642
+ const isApprovedRel = isDraftAndPublishRel && Boolean(currentEntry?.publishedAt) && currentEntry?.status !== "modified";
1643
+ const shouldPublishRel = isDraftAndPublishRel && Boolean(publishOnApproval) && (publishStrategy === "all" ? true : isApprovedRel);
1644
+ if (shouldPublishRel) {
1576
1645
  try {
1577
1646
  await strapi.documents(target.uid).publish({
1578
1647
  documentId: target.documentId
@@ -1589,7 +1658,7 @@ async function executeBatchInternal(strapi, {
1589
1658
  uid: target.uid,
1590
1659
  documentId: target.documentId,
1591
1660
  userId: currentUser?.id,
1592
- statusOverride: publishOnApproval && isDraftAndPublishRel ? "published" : void 0
1661
+ statusOverride: shouldPublishRel ? "published" : isDraftAndPublishRel ? "draft" : void 0
1593
1662
  });
1594
1663
  return {
1595
1664
  uid: target.uid,
@@ -1615,7 +1684,9 @@ async function executeBatchInternal(strapi, {
1615
1684
  }
1616
1685
  });
1617
1686
  const isDraftAndPublishScalar = Boolean(contentType?.options?.draftAndPublish);
1618
- if (publishOnApproval && isDraftAndPublishScalar) {
1687
+ const isApprovedScalar = isDraftAndPublishScalar && Boolean(currentEntry?.publishedAt) && currentEntry?.status !== "modified";
1688
+ const shouldPublishScalar = isDraftAndPublishScalar && Boolean(publishOnApproval) && (publishStrategy === "all" ? true : isApprovedScalar);
1689
+ if (shouldPublishScalar) {
1619
1690
  try {
1620
1691
  await strapi.documents(target.uid).publish({
1621
1692
  documentId: target.documentId
@@ -1628,7 +1699,7 @@ async function executeBatchInternal(strapi, {
1628
1699
  uid: target.uid,
1629
1700
  documentId: target.documentId,
1630
1701
  userId: currentUser?.id,
1631
- statusOverride: publishOnApproval && isDraftAndPublishScalar ? "published" : void 0
1702
+ statusOverride: shouldPublishScalar ? "published" : isDraftAndPublishScalar ? "draft" : void 0
1632
1703
  });
1633
1704
  return {
1634
1705
  uid: target.uid,
@@ -1715,7 +1786,9 @@ async function executeBatchInternal(strapi, {
1715
1786
  updatedCount,
1716
1787
  skippedCount,
1717
1788
  totalTargets: targetsMap.size,
1718
- publishOnApproval
1789
+ publishOnApproval,
1790
+ publishStrategy,
1791
+ unreviewedConsent
1719
1792
  };
1720
1793
  await strapi.plugin("bulk-updater").service("audit").recordBatch(
1721
1794
  batchSummary,
@@ -1738,7 +1811,8 @@ async function executeBatchInternal(strapi, {
1738
1811
  }
1739
1812
  async function executePublishBatchInternal(strapi, {
1740
1813
  selectedEntries = [],
1741
- selectedCollections = []
1814
+ selectedCollections = [],
1815
+ unreviewedConsent = false
1742
1816
  }, currentUser, existingBatchId) {
1743
1817
  const targetsMap = /* @__PURE__ */ new Map();
1744
1818
  for (const colUid of selectedCollections) {
@@ -1783,6 +1857,7 @@ async function executePublishBatchInternal(strapi, {
1783
1857
  failedCount: 0,
1784
1858
  isLive: false,
1785
1859
  publishOnApproval: true,
1860
+ unreviewedConsent,
1786
1861
  results: []
1787
1862
  };
1788
1863
  batchJobsMap.set(batchId, jobState);
@@ -1880,7 +1955,8 @@ async function executePublishBatchInternal(strapi, {
1880
1955
  updatedCount: publishedCount,
1881
1956
  skippedCount,
1882
1957
  totalTargets: targetsMap.size,
1883
- publishOnApproval: true
1958
+ publishOnApproval: true,
1959
+ unreviewedConsent
1884
1960
  };
1885
1961
  await strapi.plugin("bulk-updater").service("audit").recordBatch(
1886
1962
  batchSummary,
@@ -2139,11 +2215,7 @@ const bulkUpdaterService = ({ strapi }) => {
2139
2215
  entries = [];
2140
2216
  totalCount = 0;
2141
2217
  }
2142
- const formattedEntries = (entries || []).map((entry) => ({
2143
- id: entry.id,
2144
- documentId: entry.documentId,
2145
- ...entry
2146
- }));
2218
+ const formattedEntries = (entries || []).map(formatEntryRecord);
2147
2219
  modelsCache.set(uid, {
2148
2220
  signature,
2149
2221
  totalCount,
@@ -2232,21 +2304,26 @@ const bulkUpdaterService = ({ strapi }) => {
2232
2304
  let isNestedAttr = false;
2233
2305
  let isRootDz = false;
2234
2306
  let parts = [];
2307
+ let resolvedFilterAttr = targetFilterAttr;
2235
2308
  if (targetFilterAttr) {
2236
- const parsed = parseDynamicZonePath(targetFilterAttr);
2237
- if (parsed.rootKey && contentType.attributes?.[parsed.rootKey]?.type === "dynamiczone") {
2238
- isRootDz = true;
2309
+ const resolvedField = resolveContentTypeField(contentType, targetFilterAttr);
2310
+ if (resolvedField) {
2311
+ resolvedFilterAttr = resolvedField;
2239
2312
  colHasAttribute = true;
2240
- isNestedAttr = true;
2241
- parts = parsed.cleanPath.split(".");
2242
- } else if (targetFilterAttr.includes(".") || targetFilterAttr.includes("[")) {
2243
- isNestedAttr = isDeepComponentField(contentType, strapi.components, targetFilterAttr);
2244
- if (isNestedAttr) {
2313
+ } else {
2314
+ const parsed = parseDynamicZonePath(targetFilterAttr);
2315
+ if (parsed.rootKey && contentType.attributes?.[parsed.rootKey]?.type === "dynamiczone") {
2316
+ isRootDz = true;
2245
2317
  colHasAttribute = true;
2318
+ isNestedAttr = true;
2246
2319
  parts = parsed.cleanPath.split(".");
2320
+ } else if (targetFilterAttr.includes(".") || targetFilterAttr.includes("[")) {
2321
+ isNestedAttr = isDeepComponentField(contentType, strapi.components, targetFilterAttr);
2322
+ if (isNestedAttr) {
2323
+ colHasAttribute = true;
2324
+ parts = parsed.cleanPath.split(".");
2325
+ }
2247
2326
  }
2248
- } else if (attrs[targetFilterAttr]) {
2249
- colHasAttribute = true;
2250
2327
  }
2251
2328
  }
2252
2329
  const normOp = (filterOperator || "").toLowerCase().replace(/_/g, "");
@@ -2270,9 +2347,44 @@ const bulkUpdaterService = ({ strapi }) => {
2270
2347
  }
2271
2348
  let condition = void 0;
2272
2349
  const q = (filterQuery || "").trim();
2273
- const targetAttrSchema = isNestedAttr ? getLeafAttributeSchema(contentType, strapi.components, targetFilterAttr) : attrs[targetFilterAttr];
2350
+ const targetAttrSchema = isNestedAttr ? getLeafAttributeSchema(contentType, strapi.components, targetFilterAttr) : attrs[resolvedFilterAttr];
2351
+ const isRelation = targetAttrSchema?.type === "relation";
2274
2352
  const isStringLike = ["string", "text", "email", "richtext", "uid"].includes(targetAttrSchema?.type);
2275
- if (normOp === "empty") {
2353
+ if (isRelation) {
2354
+ const targetUid = targetAttrSchema?.target;
2355
+ const targetContentType = targetUid ? strapi.contentTypes[targetUid] : void 0;
2356
+ const targetAttrs = targetContentType?.attributes || {};
2357
+ const targetStringAttrs = Object.keys(targetAttrs).filter(
2358
+ (k) => ["string", "text", "email", "uid"].includes(targetAttrs[k]?.type)
2359
+ );
2360
+ if (normOp === "empty") {
2361
+ condition = { documentId: { $null: true } };
2362
+ } else if (normOp === "notempty") {
2363
+ condition = { documentId: { $notNull: true } };
2364
+ } else if (q !== "") {
2365
+ const orConditions = [];
2366
+ targetStringAttrs.slice(0, 5).forEach((k) => {
2367
+ if (normOp === "equals") {
2368
+ orConditions.push({ [k]: { $eqi: q } });
2369
+ } else if (normOp === "startswith") {
2370
+ orConditions.push({ [k]: { $startsWithi: q } });
2371
+ } else if (normOp === "endswith") {
2372
+ orConditions.push({ [k]: { $endsWithi: q } });
2373
+ } else if (normOp === "notequals") {
2374
+ orConditions.push({ [k]: { $ne: q } });
2375
+ } else if (normOp === "notcontains") {
2376
+ orConditions.push({ [k]: { $notContainsi: q } });
2377
+ } else {
2378
+ orConditions.push({ [k]: { $containsi: q } });
2379
+ }
2380
+ });
2381
+ orConditions.push({ documentId: { $eq: q } });
2382
+ if (/^\d+$/.test(q)) {
2383
+ orConditions.push({ id: { $eq: Number(q) } });
2384
+ }
2385
+ condition = orConditions.length === 1 ? orConditions[0] : { $or: orConditions };
2386
+ }
2387
+ } else if (normOp === "empty") {
2276
2388
  condition = isStringLike ? { $or: [{ $null: true }, { $eq: "" }] } : { $null: true };
2277
2389
  } else if (normOp === "notempty") {
2278
2390
  condition = isStringLike ? { $and: [{ $notNull: true }, { $ne: "" }] } : { $notNull: true };
@@ -2308,13 +2420,13 @@ const bulkUpdaterService = ({ strapi }) => {
2308
2420
  } else {
2309
2421
  if (normOp === "empty" && isStringLike) {
2310
2422
  filters.$or = [
2311
- { [targetFilterAttr]: { $null: true } },
2312
- { [targetFilterAttr]: { $eq: "" } }
2423
+ { [resolvedFilterAttr]: { $null: true } },
2424
+ { [resolvedFilterAttr]: { $eq: "" } }
2313
2425
  ];
2314
2426
  } else if (normOp === "notempty" && isStringLike) {
2315
- filters[targetFilterAttr] = { $notNull: true, $ne: "" };
2427
+ filters[resolvedFilterAttr] = { $notNull: true, $ne: "" };
2316
2428
  } else {
2317
- filters[targetFilterAttr] = condition;
2429
+ filters[resolvedFilterAttr] = condition;
2318
2430
  }
2319
2431
  }
2320
2432
  }
@@ -2356,7 +2468,7 @@ const bulkUpdaterService = ({ strapi }) => {
2356
2468
  displayName: contentType.info?.displayName || uid,
2357
2469
  isSingleType: true,
2358
2470
  draftAndPublish: Boolean(contentType.options?.draftAndPublish),
2359
- entries: singleDoc ? [singleDoc] : [],
2471
+ entries: singleDoc ? [formatEntryRecord(singleDoc)] : [],
2360
2472
  pagination: {
2361
2473
  page: 1,
2362
2474
  pageSize: 1,
@@ -2373,8 +2485,13 @@ const bulkUpdaterService = ({ strapi }) => {
2373
2485
  } else {
2374
2486
  sortClause["createdAt"] = "desc";
2375
2487
  }
2376
- } else if (sortField && contentType.attributes?.[sortField]) {
2377
- sortClause[sortField] = sortOrder;
2488
+ } else if (sortField) {
2489
+ const resolvedField = resolveContentTypeField(contentType, sortField);
2490
+ if (resolvedField) {
2491
+ sortClause[resolvedField] = sortOrder;
2492
+ } else {
2493
+ sortClause["createdAt"] = "desc";
2494
+ }
2378
2495
  } else {
2379
2496
  sortClause["createdAt"] = "desc";
2380
2497
  }
@@ -2392,14 +2509,30 @@ const bulkUpdaterService = ({ strapi }) => {
2392
2509
  const passesDzFilter = (entry) => {
2393
2510
  const matches = getAllNestedValues(entry, targetFilterAttr);
2394
2511
  if (normOp === "empty") {
2395
- return matches.length === 0 || matches.every((m) => m.value === void 0 || m.value === null || String(m.value).trim() === "");
2512
+ return matches.length === 0 || matches.every((m) => {
2513
+ const v = m.value;
2514
+ if (v === void 0 || v === null) return true;
2515
+ if (Array.isArray(v)) return v.length === 0;
2516
+ if (typeof v === "object") return Object.keys(v).length === 0;
2517
+ return String(v).trim() === "";
2518
+ });
2396
2519
  }
2397
2520
  if (normOp === "notempty") {
2398
- return matches.some((m) => m.value !== void 0 && m.value !== null && String(m.value).trim() !== "");
2521
+ return matches.some((m) => {
2522
+ const v = m.value;
2523
+ if (v === void 0 || v === null) return false;
2524
+ if (Array.isArray(v)) return v.length > 0;
2525
+ if (typeof v === "object") return Object.keys(v).length > 0;
2526
+ return String(v).trim() !== "";
2527
+ });
2399
2528
  }
2400
- return matches.some((m) => {
2401
- if (m.value === void 0 || m.value === null) return false;
2402
- const valStr = String(m.value).toLowerCase();
2529
+ const testSingleValue = (v) => {
2530
+ if (v === void 0 || v === null) return false;
2531
+ if (Array.isArray(v)) return v.some(testSingleValue);
2532
+ if (typeof v === "object") {
2533
+ return testSingleValue(v.name) || testSingleValue(v.title) || testSingleValue(v.email) || testSingleValue(v.username) || testSingleValue(v.documentId) || testSingleValue(v.id) || testSingleValue(v.slug);
2534
+ }
2535
+ const valStr = String(v).toLowerCase();
2403
2536
  switch (normOp) {
2404
2537
  case "equals":
2405
2538
  return valStr === qVal;
@@ -2416,7 +2549,8 @@ const bulkUpdaterService = ({ strapi }) => {
2416
2549
  default:
2417
2550
  return true;
2418
2551
  }
2419
- });
2552
+ };
2553
+ return matches.some((m) => testSingleValue(m.value));
2420
2554
  };
2421
2555
  const matchedEntries = (rawEntries || []).filter(passesDzFilter);
2422
2556
  const totalEntries = matchedEntries.length;
@@ -2426,11 +2560,7 @@ const bulkUpdaterService = ({ strapi }) => {
2426
2560
  displayName: contentType.info?.displayName || uid,
2427
2561
  isSingleType: false,
2428
2562
  draftAndPublish: Boolean(contentType.options?.draftAndPublish),
2429
- entries: paginatedEntries.map((entry) => ({
2430
- id: entry.id,
2431
- documentId: entry.documentId,
2432
- ...entry
2433
- })),
2563
+ entries: paginatedEntries.map(formatEntryRecord),
2434
2564
  pagination: {
2435
2565
  page: currentPage,
2436
2566
  pageSize: limit,
@@ -2459,11 +2589,7 @@ const bulkUpdaterService = ({ strapi }) => {
2459
2589
  displayName: contentType.info?.displayName || uid,
2460
2590
  isSingleType: false,
2461
2591
  draftAndPublish: Boolean(contentType.options?.draftAndPublish),
2462
- entries: (entries || []).map((entry) => ({
2463
- id: entry.id,
2464
- documentId: entry.documentId,
2465
- ...entry
2466
- })),
2592
+ entries: (entries || []).map(formatEntryRecord),
2467
2593
  pagination: {
2468
2594
  page: currentPage,
2469
2595
  pageSize: limit,
@@ -2807,7 +2933,10 @@ const bulkUpdaterService = ({ strapi }) => {
2807
2933
  executionMode = "immediate",
2808
2934
  makerNotes = "",
2809
2935
  forceImmediate = false,
2810
- publishOnApproval = true
2936
+ publishOnApproval = true,
2937
+ publishStrategy = "safe",
2938
+ unreviewedConsent = false,
2939
+ unreviewedCount = 0
2811
2940
  }, currentUser) {
2812
2941
  if (!attribute) {
2813
2942
  throw new Error("Attribute name is required.");
@@ -2830,13 +2959,16 @@ const bulkUpdaterService = ({ strapi }) => {
2830
2959
  lastname: currentUser.lastname,
2831
2960
  username: currentUser.username
2832
2961
  } : { email: "admin@system.local" },
2833
- payload: { attribute, value, selectedEntries, selectedCollections, isRegex, regexPattern, publishOnApproval },
2962
+ payload: { attribute, value, selectedEntries, selectedCollections, isRegex, regexPattern, publishOnApproval, publishStrategy, unreviewedConsent, unreviewedCount },
2834
2963
  summary: {
2835
2964
  attribute,
2836
2965
  value,
2837
2966
  totalTargets: summary.totalTargets,
2838
2967
  collectionBreakdown: summary.collectionBreakdown,
2839
- publishOnApproval
2968
+ publishOnApproval,
2969
+ publishStrategy,
2970
+ unreviewedConsent,
2971
+ unreviewedCount
2840
2972
  },
2841
2973
  status: "pending_approval",
2842
2974
  executionMode: "maker_checker",
@@ -2858,6 +2990,9 @@ const bulkUpdaterService = ({ strapi }) => {
2858
2990
  failedCount: 0,
2859
2991
  isLive: false,
2860
2992
  publishOnApproval: Boolean(publishOnApproval),
2993
+ publishStrategy,
2994
+ unreviewedConsent,
2995
+ unreviewedCount,
2861
2996
  makerNotes,
2862
2997
  results: []
2863
2998
  });
@@ -2886,13 +3021,16 @@ const bulkUpdaterService = ({ strapi }) => {
2886
3021
  lastname: currentUser.lastname,
2887
3022
  username: currentUser.username
2888
3023
  } : { email: "admin@system.local" },
2889
- payload: { attribute, value, selectedEntries, selectedCollections, isRegex, regexPattern, publishOnApproval },
3024
+ payload: { attribute, value, selectedEntries, selectedCollections, isRegex, regexPattern, publishOnApproval, publishStrategy, unreviewedConsent, unreviewedCount },
2890
3025
  summary: {
2891
3026
  attribute,
2892
3027
  value,
2893
3028
  totalTargets: summary.totalTargets,
2894
3029
  collectionBreakdown: summary.collectionBreakdown,
2895
- publishOnApproval
3030
+ publishOnApproval,
3031
+ publishStrategy,
3032
+ unreviewedConsent,
3033
+ unreviewedCount
2896
3034
  },
2897
3035
  status: "queued_offpeak",
2898
3036
  executionMode: "queue_offpeak",
@@ -2916,6 +3054,9 @@ const bulkUpdaterService = ({ strapi }) => {
2916
3054
  failedCount: 0,
2917
3055
  isLive: false,
2918
3056
  publishOnApproval: Boolean(publishOnApproval),
3057
+ publishStrategy,
3058
+ unreviewedConsent,
3059
+ unreviewedCount,
2919
3060
  makerNotes,
2920
3061
  results: []
2921
3062
  });
@@ -2945,13 +3086,16 @@ const bulkUpdaterService = ({ strapi }) => {
2945
3086
  lastname: currentUser.lastname,
2946
3087
  username: currentUser.username
2947
3088
  } : { email: "admin@system.local" },
2948
- payload: { attribute, value, selectedEntries, selectedCollections, isRegex, regexPattern, publishOnApproval },
3089
+ payload: { attribute, value, selectedEntries, selectedCollections, isRegex, regexPattern, publishOnApproval, publishStrategy, unreviewedConsent, unreviewedCount },
2949
3090
  summary: {
2950
3091
  attribute,
2951
3092
  value,
2952
3093
  totalTargets: summary.totalTargets,
2953
3094
  collectionBreakdown: summary.collectionBreakdown,
2954
- publishOnApproval
3095
+ publishOnApproval,
3096
+ publishStrategy,
3097
+ unreviewedConsent,
3098
+ unreviewedCount
2955
3099
  },
2956
3100
  status: "queued_offpeak",
2957
3101
  executionMode: "queue_offpeak",
@@ -2975,6 +3119,9 @@ const bulkUpdaterService = ({ strapi }) => {
2975
3119
  failedCount: 0,
2976
3120
  isLive: false,
2977
3121
  publishOnApproval: Boolean(publishOnApproval),
3122
+ publishStrategy,
3123
+ unreviewedConsent,
3124
+ unreviewedCount,
2978
3125
  policyMessage,
2979
3126
  makerNotes: "Automatically queued due to peak business hours restriction.",
2980
3127
  results: []
@@ -2991,7 +3138,7 @@ const bulkUpdaterService = ({ strapi }) => {
2991
3138
  }
2992
3139
  return await executeBatchInternal(
2993
3140
  strapi,
2994
- { attribute, value, selectedEntries, selectedCollections, isRegex, regexPattern, publishOnApproval },
3141
+ { attribute, value, selectedEntries, selectedCollections, isRegex, regexPattern, publishOnApproval, publishStrategy, unreviewedConsent },
2995
3142
  currentUser
2996
3143
  );
2997
3144
  },
@@ -3003,7 +3150,9 @@ const bulkUpdaterService = ({ strapi }) => {
3003
3150
  selectedCollections = [],
3004
3151
  executionMode = "immediate",
3005
3152
  makerNotes = "",
3006
- forceImmediate = false
3153
+ forceImmediate = false,
3154
+ unreviewedConsent = false,
3155
+ unreviewedCount = 0
3007
3156
  }, currentUser) {
3008
3157
  const executionConfig = getExecutionWindowConfig(strapi);
3009
3158
  const { isBusinessHours, policyMessage } = checkBusinessHours(executionConfig);
@@ -3023,13 +3172,15 @@ const bulkUpdaterService = ({ strapi }) => {
3023
3172
  lastname: currentUser.lastname,
3024
3173
  username: currentUser.username
3025
3174
  } : { email: "admin@system.local" },
3026
- payload: { action: "publish", attribute: "(publish)", value: "published", selectedEntries, selectedCollections, publishOnApproval: true },
3175
+ payload: { action: "publish", attribute: "(publish)", value: "published", selectedEntries, selectedCollections, publishOnApproval: true, unreviewedConsent, unreviewedCount },
3027
3176
  summary: {
3028
3177
  attribute: "(publish)",
3029
3178
  value: "published",
3030
3179
  totalTargets: summary.totalTargets,
3031
3180
  collectionBreakdown: summary.collectionBreakdown,
3032
- publishOnApproval: true
3181
+ publishOnApproval: true,
3182
+ unreviewedConsent,
3183
+ unreviewedCount
3033
3184
  },
3034
3185
  status: "pending_approval",
3035
3186
  executionMode: "maker_checker",
@@ -3051,6 +3202,8 @@ const bulkUpdaterService = ({ strapi }) => {
3051
3202
  failedCount: 0,
3052
3203
  isLive: false,
3053
3204
  publishOnApproval: true,
3205
+ unreviewedConsent,
3206
+ unreviewedCount,
3054
3207
  makerNotes,
3055
3208
  results: []
3056
3209
  });
@@ -3079,13 +3232,15 @@ const bulkUpdaterService = ({ strapi }) => {
3079
3232
  lastname: currentUser.lastname,
3080
3233
  username: currentUser.username
3081
3234
  } : { email: "admin@system.local" },
3082
- payload: { action: "publish", attribute: "(publish)", value: "published", selectedEntries, selectedCollections, publishOnApproval: true },
3235
+ payload: { action: "publish", attribute: "(publish)", value: "published", selectedEntries, selectedCollections, publishOnApproval: true, unreviewedConsent, unreviewedCount },
3083
3236
  summary: {
3084
3237
  attribute: "(publish)",
3085
3238
  value: "published",
3086
3239
  totalTargets: summary.totalTargets,
3087
3240
  collectionBreakdown: summary.collectionBreakdown,
3088
- publishOnApproval: true
3241
+ publishOnApproval: true,
3242
+ unreviewedConsent,
3243
+ unreviewedCount
3089
3244
  },
3090
3245
  status: "queued_offpeak",
3091
3246
  executionMode: "queue_offpeak",
@@ -3109,6 +3264,8 @@ const bulkUpdaterService = ({ strapi }) => {
3109
3264
  failedCount: 0,
3110
3265
  isLive: false,
3111
3266
  publishOnApproval: true,
3267
+ unreviewedConsent,
3268
+ unreviewedCount,
3112
3269
  makerNotes,
3113
3270
  results: []
3114
3271
  });
@@ -3138,13 +3295,15 @@ const bulkUpdaterService = ({ strapi }) => {
3138
3295
  lastname: currentUser.lastname,
3139
3296
  username: currentUser.username
3140
3297
  } : { email: "admin@system.local" },
3141
- payload: { action: "publish", attribute: "(publish)", value: "published", selectedEntries, selectedCollections, publishOnApproval: true },
3298
+ payload: { action: "publish", attribute: "(publish)", value: "published", selectedEntries, selectedCollections, publishOnApproval: true, unreviewedConsent, unreviewedCount },
3142
3299
  summary: {
3143
3300
  attribute: "(publish)",
3144
3301
  value: "published",
3145
3302
  totalTargets: summary.totalTargets,
3146
3303
  collectionBreakdown: summary.collectionBreakdown,
3147
- publishOnApproval: true
3304
+ publishOnApproval: true,
3305
+ unreviewedConsent,
3306
+ unreviewedCount
3148
3307
  },
3149
3308
  status: "queued_offpeak",
3150
3309
  executionMode: "queue_offpeak",
@@ -3168,6 +3327,8 @@ const bulkUpdaterService = ({ strapi }) => {
3168
3327
  failedCount: 0,
3169
3328
  isLive: false,
3170
3329
  publishOnApproval: true,
3330
+ unreviewedConsent,
3331
+ unreviewedCount,
3171
3332
  policyMessage,
3172
3333
  makerNotes: "Automatically queued due to peak business hours restriction.",
3173
3334
  results: []
@@ -3184,7 +3345,7 @@ const bulkUpdaterService = ({ strapi }) => {
3184
3345
  }
3185
3346
  return await executePublishBatchInternal(
3186
3347
  strapi,
3187
- { selectedEntries, selectedCollections },
3348
+ { selectedEntries, selectedCollections, unreviewedConsent },
3188
3349
  currentUser
3189
3350
  );
3190
3351
  },
@@ -3718,7 +3879,9 @@ const auditService = ({ strapi }) => ({
3718
3879
  value: batchSummary.value,
3719
3880
  updatedCount: batchSummary.updatedCount,
3720
3881
  skippedCount: batchSummary.skippedCount,
3721
- totalTargets: batchSummary.totalTargets
3882
+ totalTargets: batchSummary.totalTargets,
3883
+ publishStrategy: batchSummary.publishStrategy,
3884
+ unreviewedConsent: batchSummary.unreviewedConsent
3722
3885
  },
3723
3886
  user
3724
3887
  );
@@ -3741,6 +3904,9 @@ const auditService = ({ strapi }) => ({
3741
3904
  updatedCount: batchSummary.updatedCount,
3742
3905
  skippedCount: batchSummary.skippedCount,
3743
3906
  totalTargets: batchSummary.totalTargets,
3907
+ publishOnApproval: batchSummary.publishOnApproval,
3908
+ publishStrategy: batchSummary.publishStrategy,
3909
+ unreviewedConsent: batchSummary.unreviewedConsent,
3744
3910
  diffs: results,
3745
3911
  isRolledBack: false,
3746
3912
  rolledBackAt: null,
@@ -4737,7 +4903,23 @@ function getNestedExportValue(entry, path) {
4737
4903
  return String(item);
4738
4904
  }).filter(Boolean).join("; ");
4739
4905
  }
4740
- current = current[part];
4906
+ if (current && typeof current === "object") {
4907
+ if (current[part] !== void 0) {
4908
+ current = current[part];
4909
+ } else {
4910
+ const camel = part.replace(/_([a-z0-9])/g, (_, l) => l.toUpperCase());
4911
+ const snake = part.replace(/([A-Z])/g, "_$1").toLowerCase();
4912
+ if (current[camel] !== void 0) {
4913
+ current = current[camel];
4914
+ } else if (current[snake] !== void 0) {
4915
+ current = current[snake];
4916
+ } else {
4917
+ current = void 0;
4918
+ }
4919
+ }
4920
+ } else {
4921
+ current = void 0;
4922
+ }
4741
4923
  }
4742
4924
  if (current === null || current === void 0) return "";
4743
4925
  if (typeof current === "object" && current.url) {
@@ -4805,9 +4987,27 @@ async function findExistingDocument(strapi, uid, targetDocId, docLocale) {
4805
4987
  }
4806
4988
  return null;
4807
4989
  }
4990
+ function deepMergeComponent(existingVal, newVal) {
4991
+ if (!existingVal || typeof existingVal !== "object" || Array.isArray(existingVal)) {
4992
+ return newVal;
4993
+ }
4994
+ if (!newVal || typeof newVal !== "object" || Array.isArray(newVal)) {
4995
+ return newVal;
4996
+ }
4997
+ const merged = { ...existingVal };
4998
+ for (const k of Object.keys(newVal)) {
4999
+ if (newVal[k] && typeof newVal[k] === "object" && !Array.isArray(newVal[k])) {
5000
+ merged[k] = deepMergeComponent(existingVal[k], newVal[k]);
5001
+ } else if (newVal[k] !== void 0) {
5002
+ merged[k] = newVal[k];
5003
+ }
5004
+ }
5005
+ return merged;
5006
+ }
4808
5007
  async function processCsvRows(strapi, ct, uid, rows, startRowNumber, options) {
4809
5008
  const attrs = ct.attributes || {};
4810
- const { mode = "auto", publish = true, user } = options;
5009
+ const { mode = "auto", publish = true, publishStrategy = "safe", user } = options;
5010
+ const isDraftAndPublish = Boolean(ct.options?.draftAndPublish);
4811
5011
  const stats = {
4812
5012
  total: rows.length,
4813
5013
  created: 0,
@@ -4838,32 +5038,86 @@ async function processCsvRows(strapi, ct, uid, rows, startRowNumber, options) {
4838
5038
  const normalizedKey = key.toLowerCase().replace(/[\s_-]/g, "");
4839
5039
  if (SYSTEM_FIELD_KEYS.has(normalizedKey)) continue;
4840
5040
  if (key.includes(".")) {
4841
- const [parent, child] = key.split(".");
4842
- const parentDef = attrs[parent];
4843
- if (parentDef?.type === "component" && !parentDef.repeatable && parentDef.component) {
4844
- const comp = strapi.components[parentDef.component];
4845
- const childDef = comp?.attributes?.[child];
4846
- if (childDef) {
4847
- if (!payload[parent]) payload[parent] = {};
4848
- let val = rawVal;
5041
+ const parts = key.split(".");
5042
+ let currentAttrs = attrs;
5043
+ let currentTarget = payload;
5044
+ for (let pIdx = 0; pIdx < parts.length; pIdx++) {
5045
+ const part = parts[pIdx];
5046
+ const isLeaf = pIdx === parts.length - 1;
5047
+ if (isLeaf) {
5048
+ let leafDef = currentAttrs[part];
5049
+ if (!leafDef) {
5050
+ const match = Object.keys(currentAttrs).find(
5051
+ (k) => k.toLowerCase() === part.toLowerCase() || k.toLowerCase() === part.toLowerCase().replace(/\s+/g, "")
5052
+ );
5053
+ if (match) leafDef = currentAttrs[match];
5054
+ }
5055
+ if (!leafDef) {
5056
+ break;
5057
+ }
4849
5058
  if (rawVal === "" || rawVal === null || rawVal === void 0) {
4850
- val = null;
4851
- } else if (childDef.type === "integer" || childDef.type === "biginteger") {
5059
+ if (leafDef.type !== "dynamiczone" && !(leafDef.type === "component" && leafDef.repeatable) && leafDef.type !== "relation") {
5060
+ currentTarget[part] = null;
5061
+ }
5062
+ } else if (leafDef.type === "integer" || leafDef.type === "biginteger") {
4852
5063
  const parsedInt = parseInt(rawVal, 10);
4853
- val = isNaN(parsedInt) ? null : parsedInt;
4854
- } else if (childDef.type === "float" || childDef.type === "decimal") {
5064
+ currentTarget[part] = isNaN(parsedInt) ? null : parsedInt;
5065
+ } else if (leafDef.type === "float" || leafDef.type === "decimal") {
4855
5066
  const parsedFloat = parseFloat(rawVal);
4856
- val = isNaN(parsedFloat) ? null : parsedFloat;
4857
- } else if (childDef.type === "boolean") {
4858
- val = rawVal.toLowerCase() === "true" || rawVal === "1";
4859
- } else if (childDef.type === "json") {
5067
+ currentTarget[part] = isNaN(parsedFloat) ? null : parsedFloat;
5068
+ } else if (leafDef.type === "boolean") {
5069
+ currentTarget[part] = rawVal.toLowerCase() === "true" || rawVal === "1";
5070
+ } else if (leafDef.type === "json") {
5071
+ try {
5072
+ currentTarget[part] = JSON.parse(rawVal);
5073
+ } catch {
5074
+ currentTarget[part] = rawVal;
5075
+ }
5076
+ } else if (leafDef.type === "component" && leafDef.repeatable) {
4860
5077
  try {
4861
- val = JSON.parse(rawVal);
5078
+ const parsed = JSON.parse(rawVal);
5079
+ if (Array.isArray(parsed)) {
5080
+ currentTarget[part] = parsed;
5081
+ }
4862
5082
  } catch {
4863
- val = rawVal;
4864
5083
  }
5084
+ } else if (leafDef.type === "relation") {
5085
+ try {
5086
+ const parsed = JSON.parse(rawVal);
5087
+ if (Array.isArray(parsed) || typeof parsed === "object" && parsed !== null) {
5088
+ currentTarget[part] = parsed;
5089
+ } else if (isValidDocId(rawVal)) {
5090
+ currentTarget[part] = rawVal;
5091
+ }
5092
+ } catch {
5093
+ if (isValidDocId(rawVal)) {
5094
+ currentTarget[part] = rawVal;
5095
+ }
5096
+ }
5097
+ } else {
5098
+ currentTarget[part] = rawVal;
5099
+ }
5100
+ } else {
5101
+ let compDef = currentAttrs[part];
5102
+ if (!compDef) {
5103
+ const match = Object.keys(currentAttrs).find(
5104
+ (k) => k.toLowerCase() === part.toLowerCase() || k.toLowerCase() === part.toLowerCase().replace(/\s+/g, "")
5105
+ );
5106
+ if (match) compDef = currentAttrs[match];
5107
+ }
5108
+ if (compDef?.type === "component" && !compDef.repeatable && compDef.component) {
5109
+ const comp = strapi.components[compDef.component];
5110
+ if (!comp?.attributes) {
5111
+ break;
5112
+ }
5113
+ if (!currentTarget[part] || typeof currentTarget[part] !== "object" || Array.isArray(currentTarget[part])) {
5114
+ currentTarget[part] = {};
5115
+ }
5116
+ currentTarget = currentTarget[part];
5117
+ currentAttrs = comp.attributes;
5118
+ } else {
5119
+ break;
4865
5120
  }
4866
- payload[parent][child] = val;
4867
5121
  }
4868
5122
  }
4869
5123
  continue;
@@ -4998,7 +5252,7 @@ async function processCsvRows(strapi, ct, uid, rows, startRowNumber, options) {
4998
5252
  const mergedPayload = { ...payload };
4999
5253
  for (const key of Object.keys(payload)) {
5000
5254
  if (attrs[key]?.type === "component" && typeof payload[key] === "object" && payload[key] !== null) {
5001
- mergedPayload[key] = { ...existing[key] || {}, ...payload[key] };
5255
+ mergedPayload[key] = deepMergeComponent(existing[key], payload[key]);
5002
5256
  }
5003
5257
  }
5004
5258
  if (Object.keys(mergedPayload).length === 0) {
@@ -5022,7 +5276,9 @@ async function processCsvRows(strapi, ct, uid, rows, startRowNumber, options) {
5022
5276
  };
5023
5277
  if (rowLocale) updateArgs.locale = rowLocale;
5024
5278
  await strapi.documents(uid).update(updateArgs);
5025
- if (publish && ct.options?.draftAndPublish) {
5279
+ const isApproved = isDraftAndPublish && Boolean(existing?.publishedAt) && existing?.status !== "modified";
5280
+ const shouldPublish = isDraftAndPublish && Boolean(publish) && (publishStrategy === "all" ? true : isApproved);
5281
+ if (shouldPublish) {
5026
5282
  try {
5027
5283
  const pubArgs = { documentId: resolvedDocId };
5028
5284
  if (rowLocale) pubArgs.locale = rowLocale;
@@ -5036,7 +5292,7 @@ async function processCsvRows(strapi, ct, uid, rows, startRowNumber, options) {
5036
5292
  uid,
5037
5293
  documentId: resolvedDocId,
5038
5294
  userId: user?.id,
5039
- statusOverride: publish && ct.options?.draftAndPublish ? "published" : void 0
5295
+ statusOverride: shouldPublish ? "published" : isDraftAndPublish ? "draft" : void 0
5040
5296
  });
5041
5297
  } catch {
5042
5298
  }
@@ -5066,7 +5322,7 @@ async function processCsvRows(strapi, ct, uid, rows, startRowNumber, options) {
5066
5322
  const mergedPayload = { ...payload };
5067
5323
  for (const key of Object.keys(payload)) {
5068
5324
  if (attrs[key]?.type === "component" && typeof payload[key] === "object" && payload[key] !== null) {
5069
- mergedPayload[key] = { ...existing[key] || {}, ...payload[key] };
5325
+ mergedPayload[key] = deepMergeComponent(existing[key], payload[key]);
5070
5326
  }
5071
5327
  }
5072
5328
  const updateArgs = {
@@ -5075,7 +5331,9 @@ async function processCsvRows(strapi, ct, uid, rows, startRowNumber, options) {
5075
5331
  };
5076
5332
  if (rowLocale) updateArgs.locale = rowLocale;
5077
5333
  await strapi.documents(uid).update(updateArgs);
5078
- if (publish && ct.options?.draftAndPublish) {
5334
+ const isApproved = isDraftAndPublish && Boolean(existing?.publishedAt) && existing?.status !== "modified";
5335
+ const shouldPublish = isDraftAndPublish && Boolean(publish) && (publishStrategy === "all" ? true : isApproved);
5336
+ if (shouldPublish) {
5079
5337
  try {
5080
5338
  const pubArgs = { documentId: resolvedDocId };
5081
5339
  if (rowLocale) pubArgs.locale = rowLocale;
@@ -5089,7 +5347,7 @@ async function processCsvRows(strapi, ct, uid, rows, startRowNumber, options) {
5089
5347
  uid,
5090
5348
  documentId: resolvedDocId,
5091
5349
  userId: user?.id,
5092
- statusOverride: publish && ct.options?.draftAndPublish ? "published" : void 0
5350
+ statusOverride: shouldPublish ? "published" : isDraftAndPublish ? "draft" : void 0
5093
5351
  });
5094
5352
  } catch {
5095
5353
  }
@@ -5115,7 +5373,8 @@ async function processCsvRows(strapi, ct, uid, rows, startRowNumber, options) {
5115
5373
  if (rowLocale) createArgs.locale = rowLocale;
5116
5374
  const created = await strapi.documents(uid).create(createArgs);
5117
5375
  const newDocId = created?.documentId || docId;
5118
- if (publish && ct.options?.draftAndPublish && newDocId) {
5376
+ const shouldPublishNew = isDraftAndPublish && Boolean(publish) && (publishStrategy === "all" ? true : false);
5377
+ if (shouldPublishNew && newDocId) {
5119
5378
  try {
5120
5379
  const pubArgs = { documentId: newDocId };
5121
5380
  if (rowLocale) pubArgs.locale = rowLocale;
@@ -5129,7 +5388,7 @@ async function processCsvRows(strapi, ct, uid, rows, startRowNumber, options) {
5129
5388
  uid,
5130
5389
  documentId: newDocId,
5131
5390
  userId: user?.id,
5132
- statusOverride: publish && ct.options?.draftAndPublish ? "published" : void 0
5391
+ statusOverride: shouldPublishNew ? "published" : isDraftAndPublish ? "draft" : void 0
5133
5392
  });
5134
5393
  } catch {
5135
5394
  }
@@ -5160,7 +5419,7 @@ async function processCsvRows(strapi, ct, uid, rows, startRowNumber, options) {
5160
5419
  const mergedPayload = { ...payload };
5161
5420
  for (const key of Object.keys(payload)) {
5162
5421
  if (attrs[key]?.type === "component" && typeof payload[key] === "object" && payload[key] !== null) {
5163
- mergedPayload[key] = { ...existing[key] || {}, ...payload[key] };
5422
+ mergedPayload[key] = deepMergeComponent(existing[key], payload[key]);
5164
5423
  }
5165
5424
  }
5166
5425
  const updateArgs = {
@@ -5169,7 +5428,9 @@ async function processCsvRows(strapi, ct, uid, rows, startRowNumber, options) {
5169
5428
  };
5170
5429
  if (rowLocale) updateArgs.locale = rowLocale;
5171
5430
  await strapi.documents(uid).update(updateArgs);
5172
- if (publish && ct.options?.draftAndPublish) {
5431
+ const isApproved = isDraftAndPublish && Boolean(existing?.publishedAt) && existing?.status !== "modified";
5432
+ const shouldPublish = isDraftAndPublish && Boolean(publish) && (publishStrategy === "all" ? true : isApproved);
5433
+ if (shouldPublish) {
5173
5434
  try {
5174
5435
  const pubArgs = { documentId: resolvedDocId };
5175
5436
  if (rowLocale) pubArgs.locale = rowLocale;
@@ -5183,7 +5444,7 @@ async function processCsvRows(strapi, ct, uid, rows, startRowNumber, options) {
5183
5444
  uid,
5184
5445
  documentId: resolvedDocId,
5185
5446
  userId: user?.id,
5186
- statusOverride: publish && ct.options?.draftAndPublish ? "published" : void 0
5447
+ statusOverride: shouldPublish ? "published" : isDraftAndPublish ? "draft" : void 0
5187
5448
  });
5188
5449
  } catch {
5189
5450
  }
@@ -5209,7 +5470,8 @@ async function processCsvRows(strapi, ct, uid, rows, startRowNumber, options) {
5209
5470
  if (rowLocale) createArgs.locale = rowLocale;
5210
5471
  const created = await strapi.documents(uid).create(createArgs);
5211
5472
  const newDocId = created?.documentId || docId;
5212
- if (publish && ct.options?.draftAndPublish && newDocId) {
5473
+ const shouldPublishNew = isDraftAndPublish && Boolean(publish) && (publishStrategy === "all" ? true : false);
5474
+ if (shouldPublishNew && newDocId) {
5213
5475
  try {
5214
5476
  const pubArgs = { documentId: newDocId };
5215
5477
  if (rowLocale) pubArgs.locale = rowLocale;
@@ -5223,7 +5485,7 @@ async function processCsvRows(strapi, ct, uid, rows, startRowNumber, options) {
5223
5485
  uid,
5224
5486
  documentId: newDocId,
5225
5487
  userId: user?.id,
5226
- statusOverride: publish && ct.options?.draftAndPublish ? "published" : void 0
5488
+ statusOverride: shouldPublishNew ? "published" : isDraftAndPublish ? "draft" : void 0
5227
5489
  });
5228
5490
  } catch {
5229
5491
  }
@@ -5255,7 +5517,7 @@ async function processCsvRows(strapi, ct, uid, rows, startRowNumber, options) {
5255
5517
  }
5256
5518
  return stats;
5257
5519
  }
5258
- async function recordCsvAuditLog(strapi, ct, uid, batchId, stats, user) {
5520
+ async function recordCsvAuditLog(strapi, ct, uid, batchId, stats, user, publishOnApproval, publishStrategy, unreviewedConsent) {
5259
5521
  if (stats.updated === 0 && stats.created === 0) return;
5260
5522
  try {
5261
5523
  const auditService2 = strapi.plugin("bulk-updater").service("audit");
@@ -5299,7 +5561,14 @@ async function recordCsvAuditLog(strapi, ct, uid, batchId, stats, user) {
5299
5561
  firstname: userName || "Admin"
5300
5562
  };
5301
5563
  if (typeof auditService2.recordBatch === "function") {
5302
- await auditService2.recordBatch(batchSummary, stats.diffs, userObj);
5564
+ await auditService2.recordBatch(
5565
+ batchSummary,
5566
+ stats.diffs,
5567
+ userObj,
5568
+ publishOnApproval,
5569
+ publishStrategy,
5570
+ unreviewedConsent
5571
+ );
5303
5572
  } else if (typeof auditService2.logOperation === "function") {
5304
5573
  await auditService2.logOperation({
5305
5574
  ...batchSummary,
@@ -5626,7 +5895,9 @@ const csvService = ({ strapi }) => ({
5626
5895
  skipped: 0,
5627
5896
  newWithoutDocId: 0,
5628
5897
  errors: [],
5629
- diffs: []
5898
+ diffs: [],
5899
+ publishStrategy: options.publishStrategy,
5900
+ unreviewedConsent: options.unreviewedConsent
5630
5901
  };
5631
5902
  csvImportSessions.set(batchId, session);
5632
5903
  }
@@ -5640,7 +5911,17 @@ const csvService = ({ strapi }) => ({
5640
5911
  session.errors.push(...chunkStats.errors);
5641
5912
  session.diffs.push(...chunkStats.diffs);
5642
5913
  if (options.isLastChunk) {
5643
- await recordCsvAuditLog(strapi, ct, uid, batchId, session, options.user);
5914
+ await recordCsvAuditLog(
5915
+ strapi,
5916
+ ct,
5917
+ uid,
5918
+ batchId,
5919
+ session,
5920
+ options.user,
5921
+ options.publish !== false,
5922
+ session.publishStrategy || options.publishStrategy,
5923
+ session.unreviewedConsent ?? options.unreviewedConsent
5924
+ );
5644
5925
  try {
5645
5926
  const bulkService = strapi.plugin("bulk-updater").service("bulkUpdater");
5646
5927
  if (bulkService && bulkService.invalidateCollectionCache) {
@@ -5694,7 +5975,17 @@ const csvService = ({ strapi }) => ({
5694
5975
  }
5695
5976
  } catch {
5696
5977
  }
5697
- await recordCsvAuditLog(strapi, ct, uid, batchId, stats, options.user);
5978
+ await recordCsvAuditLog(
5979
+ strapi,
5980
+ ct,
5981
+ uid,
5982
+ batchId,
5983
+ stats,
5984
+ options.user,
5985
+ options.publish !== false,
5986
+ options.publishStrategy,
5987
+ options.unreviewedConsent
5988
+ );
5698
5989
  return {
5699
5990
  success: true,
5700
5991
  batchId,
@@ -5714,7 +6005,17 @@ const csvService = ({ strapi }) => ({
5714
6005
  ...options,
5715
6006
  user: effectiveUser
5716
6007
  });
5717
- await recordCsvAuditLog(strapi, ct, uid, batchId, stats, effectiveUser);
6008
+ await recordCsvAuditLog(
6009
+ strapi,
6010
+ ct,
6011
+ uid,
6012
+ batchId,
6013
+ stats,
6014
+ effectiveUser,
6015
+ options.publish !== false,
6016
+ options.publishStrategy,
6017
+ options.unreviewedConsent
6018
+ );
5718
6019
  try {
5719
6020
  const bulkService = strapi.plugin("bulk-updater").service("bulkUpdater");
5720
6021
  if (bulkService && bulkService.invalidateCollectionCache) {
@@ -5752,6 +6053,15 @@ const csvService = ({ strapi }) => ({
5752
6053
  let validRows = 0;
5753
6054
  let estimatedUpdates = 0;
5754
6055
  let estimatedCreates = 0;
6056
+ const isDraftAndPublish = Boolean(ct.options?.draftAndPublish);
6057
+ const lifecycleStats = {
6058
+ isDraftAndPublish,
6059
+ totalChecked: 0,
6060
+ published: 0,
6061
+ modified: 0,
6062
+ draft: 0,
6063
+ unreviewedCount: 0
6064
+ };
5755
6065
  const errors2 = [];
5756
6066
  const warnings = [];
5757
6067
  for (let i = 0; i < rows.length; i++) {
@@ -5764,6 +6074,19 @@ const csvService = ({ strapi }) => ({
5764
6074
  if (docId) {
5765
6075
  existing = await findExistingDocument(strapi, uid, docId, rowLocale);
5766
6076
  }
6077
+ if (existing && isDraftAndPublish) {
6078
+ lifecycleStats.totalChecked++;
6079
+ const isApproved = Boolean(existing.publishedAt) && existing.status !== "modified";
6080
+ if (isApproved) {
6081
+ lifecycleStats.published++;
6082
+ } else if (existing.status === "modified") {
6083
+ lifecycleStats.modified++;
6084
+ lifecycleStats.unreviewedCount++;
6085
+ } else {
6086
+ lifecycleStats.draft++;
6087
+ lifecycleStats.unreviewedCount++;
6088
+ }
6089
+ }
5767
6090
  if (mode === "update") {
5768
6091
  if (!docId) {
5769
6092
  errors2.push({
@@ -5882,6 +6205,7 @@ const csvService = ({ strapi }) => ({
5882
6205
  validRows,
5883
6206
  estimatedUpdates,
5884
6207
  estimatedCreates,
6208
+ lifecycleStats,
5885
6209
  hasErrors: errors2.length > 0,
5886
6210
  hasWarnings: warnings.length > 0,
5887
6211
  errors: errors2.slice(0, 100),
@@ -5907,6 +6231,9 @@ const csvService = ({ strapi }) => ({
5907
6231
  forceImmediate = false,
5908
6232
  mode = "auto",
5909
6233
  publish = true,
6234
+ publishStrategy = "safe",
6235
+ unreviewedConsent = false,
6236
+ unreviewedCount = 0,
5910
6237
  user
5911
6238
  } = options;
5912
6239
  const executionConfig = getExecutionWindowConfig(strapi);
@@ -5931,22 +6258,33 @@ const csvService = ({ strapi }) => ({
5931
6258
  options: {
5932
6259
  mode,
5933
6260
  publish,
6261
+ publishStrategy,
6262
+ unreviewedConsent,
5934
6263
  user
5935
6264
  },
5936
6265
  attribute: `CSV Import: ${collectionDisplayName}`,
5937
6266
  value: `${rows.length} rows (${mode} mode)`,
5938
- publishOnApproval: publish
6267
+ publishOnApproval: publish,
6268
+ publishStrategy,
6269
+ unreviewedConsent,
6270
+ unreviewedCount
5939
6271
  },
5940
6272
  summary: {
5941
6273
  attribute: `CSV Import: ${collectionDisplayName}`,
5942
6274
  value: `${rows.length} rows (${mode} mode)`,
5943
6275
  totalTargets: rows.length,
5944
6276
  collectionBreakdown: { [collectionDisplayName]: rows.length },
5945
- publishOnApproval: publish
6277
+ publishOnApproval: publish,
6278
+ publishStrategy,
6279
+ unreviewedConsent,
6280
+ unreviewedCount
5946
6281
  },
5947
6282
  status: "pending_approval",
5948
6283
  executionMode: "maker_checker",
5949
- makerNotes
6284
+ makerNotes,
6285
+ publishStrategy,
6286
+ unreviewedConsent,
6287
+ unreviewedCount
5950
6288
  };
5951
6289
  registerPendingBatch(batch, {
5952
6290
  id: batchId,
@@ -5963,6 +6301,9 @@ const csvService = ({ strapi }) => ({
5963
6301
  failedCount: 0,
5964
6302
  isLive: false,
5965
6303
  publishOnApproval: publish,
6304
+ publishStrategy,
6305
+ unreviewedConsent,
6306
+ unreviewedCount,
5966
6307
  makerNotes,
5967
6308
  results: []
5968
6309
  });
@@ -5994,23 +6335,34 @@ const csvService = ({ strapi }) => ({
5994
6335
  options: {
5995
6336
  mode,
5996
6337
  publish,
6338
+ publishStrategy,
6339
+ unreviewedConsent,
5997
6340
  user
5998
6341
  },
5999
6342
  attribute: `CSV Import: ${collectionDisplayName}`,
6000
6343
  value: `${rows.length} rows (${mode} mode)`,
6001
- publishOnApproval: publish
6344
+ publishOnApproval: publish,
6345
+ publishStrategy,
6346
+ unreviewedConsent,
6347
+ unreviewedCount
6002
6348
  },
6003
6349
  summary: {
6004
6350
  attribute: `CSV Import: ${collectionDisplayName}`,
6005
6351
  value: `${rows.length} rows (${mode} mode)`,
6006
6352
  totalTargets: rows.length,
6007
6353
  collectionBreakdown: { [collectionDisplayName]: rows.length },
6008
- publishOnApproval: publish
6354
+ publishOnApproval: publish,
6355
+ publishStrategy,
6356
+ unreviewedConsent,
6357
+ unreviewedCount
6009
6358
  },
6010
6359
  status: "queued_offpeak",
6011
6360
  executionMode: "queue_offpeak",
6012
6361
  scheduledFor: executionConfig.offPeakStartTime,
6013
- makerNotes
6362
+ makerNotes,
6363
+ publishStrategy,
6364
+ unreviewedConsent,
6365
+ unreviewedCount
6014
6366
  };
6015
6367
  registerPendingBatch(batch, {
6016
6368
  id: batchId,
@@ -6028,6 +6380,9 @@ const csvService = ({ strapi }) => ({
6028
6380
  failedCount: 0,
6029
6381
  isLive: false,
6030
6382
  publishOnApproval: publish,
6383
+ publishStrategy,
6384
+ unreviewedConsent,
6385
+ unreviewedCount,
6031
6386
  makerNotes,
6032
6387
  results: []
6033
6388
  });
@@ -6060,23 +6415,34 @@ const csvService = ({ strapi }) => ({
6060
6415
  options: {
6061
6416
  mode,
6062
6417
  publish,
6418
+ publishStrategy,
6419
+ unreviewedConsent,
6063
6420
  user
6064
6421
  },
6065
6422
  attribute: `CSV Import: ${collectionDisplayName}`,
6066
6423
  value: `${rows.length} rows (${mode} mode)`,
6067
- publishOnApproval: publish
6424
+ publishOnApproval: publish,
6425
+ publishStrategy,
6426
+ unreviewedConsent,
6427
+ unreviewedCount
6068
6428
  },
6069
6429
  summary: {
6070
6430
  attribute: `CSV Import: ${collectionDisplayName}`,
6071
6431
  value: `${rows.length} rows (${mode} mode)`,
6072
6432
  totalTargets: rows.length,
6073
6433
  collectionBreakdown: { [collectionDisplayName]: rows.length },
6074
- publishOnApproval: publish
6434
+ publishOnApproval: publish,
6435
+ publishStrategy,
6436
+ unreviewedConsent,
6437
+ unreviewedCount
6075
6438
  },
6076
6439
  status: "queued_offpeak",
6077
6440
  executionMode: "queue_offpeak",
6078
6441
  scheduledFor: executionConfig.offPeakStartTime,
6079
- makerNotes: "Automatically queued due to peak business hours restriction."
6442
+ makerNotes: "Automatically queued due to peak business hours restriction.",
6443
+ publishStrategy,
6444
+ unreviewedConsent,
6445
+ unreviewedCount
6080
6446
  };
6081
6447
  registerPendingBatch(batch, {
6082
6448
  id: batchId,
@@ -6094,6 +6460,9 @@ const csvService = ({ strapi }) => ({
6094
6460
  failedCount: 0,
6095
6461
  isLive: false,
6096
6462
  publishOnApproval: publish,
6463
+ publishStrategy,
6464
+ unreviewedConsent,
6465
+ unreviewedCount,
6097
6466
  policyMessage,
6098
6467
  makerNotes: "Automatically queued due to peak business hours restriction.",
6099
6468
  results: []