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.
@@ -668,7 +668,7 @@ const csvController = ({ strapi }) => ({
668
668
  },
669
669
  async importCsv(ctx) {
670
670
  try {
671
- const { uid, csvText, mode, publish } = ctx.request.body;
671
+ const { uid, csvText, mode, publish, publishStrategy, unreviewedConsent } = ctx.request.body;
672
672
  if (!uid) {
673
673
  ctx.status = 400;
674
674
  ctx.body = { success: false, error: "Target collection UID is required." };
@@ -683,6 +683,8 @@ const csvController = ({ strapi }) => ({
683
683
  const result = await csvService2.importCsv(uid, csvText, {
684
684
  mode: mode === "update" ? "update" : mode === "create_or_override" ? "create_or_override" : "auto",
685
685
  publish: publish !== false,
686
+ publishStrategy: publishStrategy === "all" ? "all" : "safe",
687
+ unreviewedConsent: Boolean(unreviewedConsent),
686
688
  user: ctx.state.user
687
689
  });
688
690
  ctx.body = result;
@@ -693,7 +695,7 @@ const csvController = ({ strapi }) => ({
693
695
  },
694
696
  async importChunk(ctx) {
695
697
  try {
696
- const { uid, rows, batchId, isFirstChunk, isLastChunk, startRowNumber, mode, publish } = ctx.request.body;
698
+ const { uid, rows, batchId, isFirstChunk, isLastChunk, startRowNumber, mode, publish, publishStrategy, unreviewedConsent } = ctx.request.body;
697
699
  if (!uid) {
698
700
  ctx.status = 400;
699
701
  ctx.body = { success: false, error: "Target collection UID is required." };
@@ -712,6 +714,8 @@ const csvController = ({ strapi }) => ({
712
714
  startRowNumber: typeof startRowNumber === "number" ? startRowNumber : void 0,
713
715
  mode: mode === "update" ? "update" : mode === "create_or_override" ? "create_or_override" : "auto",
714
716
  publish: publish !== false,
717
+ publishStrategy: publishStrategy === "all" ? "all" : "safe",
718
+ unreviewedConsent: Boolean(unreviewedConsent),
715
719
  user: ctx.state.user
716
720
  });
717
721
  ctx.body = result;
@@ -745,7 +749,7 @@ const csvController = ({ strapi }) => ({
745
749
  },
746
750
  async submitBatch(ctx) {
747
751
  try {
748
- const { uid, rows, executionMode, makerNotes, forceImmediate, mode, publish } = ctx.request.body;
752
+ const { uid, rows, executionMode, makerNotes, forceImmediate, mode, publish, publishStrategy, unreviewedConsent, unreviewedCount } = ctx.request.body;
749
753
  if (!uid) {
750
754
  ctx.status = 400;
751
755
  ctx.body = { success: false, error: "Target collection UID is required." };
@@ -763,6 +767,9 @@ const csvController = ({ strapi }) => ({
763
767
  forceImmediate: Boolean(forceImmediate),
764
768
  mode: mode === "update" ? "update" : mode === "create_or_override" ? "create_or_override" : "auto",
765
769
  publish: publish !== false,
770
+ publishStrategy: publishStrategy === "all" ? "all" : "safe",
771
+ unreviewedConsent: Boolean(unreviewedConsent),
772
+ unreviewedCount: typeof unreviewedCount === "number" ? unreviewedCount : 0,
766
773
  user: ctx.state.user
767
774
  });
768
775
  ctx.body = result;
@@ -920,6 +927,60 @@ const modelsCache = /* @__PURE__ */ new Map();
920
927
  const MAX_QUERY_CACHE_SIZE = 500;
921
928
  const CACHE_TTL_MS = 60 * 1e3;
922
929
  let staticSchemaCache = null;
930
+ function formatEntryRecord(entry) {
931
+ if (!entry || typeof entry !== "object") return entry;
932
+ const formatted = {
933
+ id: entry.id,
934
+ documentId: entry.documentId,
935
+ ...entry
936
+ };
937
+ if (entry.updatedAt !== void 0 && formatted.updated_at === void 0) {
938
+ formatted.updated_at = entry.updatedAt;
939
+ }
940
+ if (entry.updated_at !== void 0 && formatted.updatedAt === void 0) {
941
+ formatted.updatedAt = entry.updated_at;
942
+ }
943
+ if (entry.createdAt !== void 0 && formatted.created_at === void 0) {
944
+ formatted.created_at = entry.createdAt;
945
+ }
946
+ if (entry.created_at !== void 0 && formatted.createdAt === void 0) {
947
+ formatted.createdAt = entry.created_at;
948
+ }
949
+ if (entry.publishedAt !== void 0 && formatted.published_at === void 0) {
950
+ formatted.published_at = entry.publishedAt;
951
+ }
952
+ if (entry.published_at !== void 0 && formatted.publishedAt === void 0) {
953
+ formatted.publishedAt = entry.published_at;
954
+ }
955
+ return formatted;
956
+ }
957
+ function resolveContentTypeField(contentType, field) {
958
+ if (!field) return null;
959
+ const attrs = contentType?.attributes || {};
960
+ if (attrs[field]) return field;
961
+ if (field === "updated_at" || field === "updatedAt") {
962
+ return attrs["updated_at"] ? "updated_at" : "updatedAt";
963
+ }
964
+ if (field === "created_at" || field === "createdAt") {
965
+ return attrs["created_at"] ? "created_at" : "createdAt";
966
+ }
967
+ if (field === "published_at" || field === "publishedAt") {
968
+ return attrs["published_at"] ? "published_at" : "publishedAt";
969
+ }
970
+ if (field === "id" || field === "documentId") {
971
+ return field;
972
+ }
973
+ const camel = field.replace(/_([a-z0-9])/g, (_, l) => l.toUpperCase());
974
+ if (attrs[camel]) return camel;
975
+ const snake = field.replace(/([A-Z])/g, "_$1").toLowerCase();
976
+ if (attrs[snake]) return snake;
977
+ const lower = field.toLowerCase().replace(/_/g, "");
978
+ const matched = Object.keys(attrs).find(
979
+ (k) => k.toLowerCase().replace(/_/g, "") === lower
980
+ );
981
+ if (matched) return matched;
982
+ return null;
983
+ }
923
984
  function buildDeepPopulate$1(attributes, allComponents, depth = 0) {
924
985
  if (depth > 5) return void 0;
925
986
  const populate = {};
@@ -1352,7 +1413,9 @@ async function executeBatchInternal(strapi, {
1352
1413
  selectedCollections = [],
1353
1414
  isRegex = false,
1354
1415
  regexPattern = "",
1355
- publishOnApproval = true
1416
+ publishOnApproval = true,
1417
+ publishStrategy = "safe",
1418
+ unreviewedConsent = false
1356
1419
  }, currentUser, existingBatchId) {
1357
1420
  if (!attribute) {
1358
1421
  throw new Error("Attribute name is required.");
@@ -1405,6 +1468,8 @@ async function executeBatchInternal(strapi, {
1405
1468
  failedCount: 0,
1406
1469
  isLive: false,
1407
1470
  publishOnApproval: Boolean(publishOnApproval),
1471
+ publishStrategy,
1472
+ unreviewedConsent,
1408
1473
  results: []
1409
1474
  };
1410
1475
  batchJobsMap.set(batchId, jobState);
@@ -1478,7 +1543,9 @@ async function executeBatchInternal(strapi, {
1478
1543
  }
1479
1544
  });
1480
1545
  const isDraftAndPublishComp = Boolean(contentType?.options?.draftAndPublish);
1481
- if (publishOnApproval && isDraftAndPublishComp) {
1546
+ const isApprovedComp = isDraftAndPublishComp && Boolean(currentEntry?.publishedAt) && currentEntry?.status !== "modified";
1547
+ const shouldPublishComp = isDraftAndPublishComp && Boolean(publishOnApproval) && (publishStrategy === "all" ? true : isApprovedComp);
1548
+ if (shouldPublishComp) {
1482
1549
  try {
1483
1550
  await strapi.documents(target.uid).publish({
1484
1551
  documentId: target.documentId
@@ -1492,7 +1559,7 @@ async function executeBatchInternal(strapi, {
1492
1559
  uid: target.uid,
1493
1560
  documentId: target.documentId,
1494
1561
  userId: currentUser?.id,
1495
- statusOverride: publishOnApproval && isDraftAndPublishComp ? "published" : void 0
1562
+ statusOverride: shouldPublishComp ? "published" : isDraftAndPublishComp ? "draft" : void 0
1496
1563
  });
1497
1564
  const dzDiffs = isRootDz && affectedMatches.length > 0 ? affectedMatches.map((m) => ({
1498
1565
  index: m.index,
@@ -1575,7 +1642,9 @@ async function executeBatchInternal(strapi, {
1575
1642
  }
1576
1643
  });
1577
1644
  const isDraftAndPublishRel = Boolean(contentType?.options?.draftAndPublish);
1578
- if (publishOnApproval && isDraftAndPublishRel) {
1645
+ const isApprovedRel = isDraftAndPublishRel && Boolean(currentEntry?.publishedAt) && currentEntry?.status !== "modified";
1646
+ const shouldPublishRel = isDraftAndPublishRel && Boolean(publishOnApproval) && (publishStrategy === "all" ? true : isApprovedRel);
1647
+ if (shouldPublishRel) {
1579
1648
  try {
1580
1649
  await strapi.documents(target.uid).publish({
1581
1650
  documentId: target.documentId
@@ -1592,7 +1661,7 @@ async function executeBatchInternal(strapi, {
1592
1661
  uid: target.uid,
1593
1662
  documentId: target.documentId,
1594
1663
  userId: currentUser?.id,
1595
- statusOverride: publishOnApproval && isDraftAndPublishRel ? "published" : void 0
1664
+ statusOverride: shouldPublishRel ? "published" : isDraftAndPublishRel ? "draft" : void 0
1596
1665
  });
1597
1666
  return {
1598
1667
  uid: target.uid,
@@ -1618,7 +1687,9 @@ async function executeBatchInternal(strapi, {
1618
1687
  }
1619
1688
  });
1620
1689
  const isDraftAndPublishScalar = Boolean(contentType?.options?.draftAndPublish);
1621
- if (publishOnApproval && isDraftAndPublishScalar) {
1690
+ const isApprovedScalar = isDraftAndPublishScalar && Boolean(currentEntry?.publishedAt) && currentEntry?.status !== "modified";
1691
+ const shouldPublishScalar = isDraftAndPublishScalar && Boolean(publishOnApproval) && (publishStrategy === "all" ? true : isApprovedScalar);
1692
+ if (shouldPublishScalar) {
1622
1693
  try {
1623
1694
  await strapi.documents(target.uid).publish({
1624
1695
  documentId: target.documentId
@@ -1631,7 +1702,7 @@ async function executeBatchInternal(strapi, {
1631
1702
  uid: target.uid,
1632
1703
  documentId: target.documentId,
1633
1704
  userId: currentUser?.id,
1634
- statusOverride: publishOnApproval && isDraftAndPublishScalar ? "published" : void 0
1705
+ statusOverride: shouldPublishScalar ? "published" : isDraftAndPublishScalar ? "draft" : void 0
1635
1706
  });
1636
1707
  return {
1637
1708
  uid: target.uid,
@@ -1718,7 +1789,9 @@ async function executeBatchInternal(strapi, {
1718
1789
  updatedCount,
1719
1790
  skippedCount,
1720
1791
  totalTargets: targetsMap.size,
1721
- publishOnApproval
1792
+ publishOnApproval,
1793
+ publishStrategy,
1794
+ unreviewedConsent
1722
1795
  };
1723
1796
  await strapi.plugin("bulk-updater").service("audit").recordBatch(
1724
1797
  batchSummary,
@@ -1741,7 +1814,8 @@ async function executeBatchInternal(strapi, {
1741
1814
  }
1742
1815
  async function executePublishBatchInternal(strapi, {
1743
1816
  selectedEntries = [],
1744
- selectedCollections = []
1817
+ selectedCollections = [],
1818
+ unreviewedConsent = false
1745
1819
  }, currentUser, existingBatchId) {
1746
1820
  const targetsMap = /* @__PURE__ */ new Map();
1747
1821
  for (const colUid of selectedCollections) {
@@ -1786,6 +1860,7 @@ async function executePublishBatchInternal(strapi, {
1786
1860
  failedCount: 0,
1787
1861
  isLive: false,
1788
1862
  publishOnApproval: true,
1863
+ unreviewedConsent,
1789
1864
  results: []
1790
1865
  };
1791
1866
  batchJobsMap.set(batchId, jobState);
@@ -1883,7 +1958,8 @@ async function executePublishBatchInternal(strapi, {
1883
1958
  updatedCount: publishedCount,
1884
1959
  skippedCount,
1885
1960
  totalTargets: targetsMap.size,
1886
- publishOnApproval: true
1961
+ publishOnApproval: true,
1962
+ unreviewedConsent
1887
1963
  };
1888
1964
  await strapi.plugin("bulk-updater").service("audit").recordBatch(
1889
1965
  batchSummary,
@@ -2142,11 +2218,7 @@ const bulkUpdaterService = ({ strapi }) => {
2142
2218
  entries = [];
2143
2219
  totalCount = 0;
2144
2220
  }
2145
- const formattedEntries = (entries || []).map((entry) => ({
2146
- id: entry.id,
2147
- documentId: entry.documentId,
2148
- ...entry
2149
- }));
2221
+ const formattedEntries = (entries || []).map(formatEntryRecord);
2150
2222
  modelsCache.set(uid, {
2151
2223
  signature,
2152
2224
  totalCount,
@@ -2235,21 +2307,26 @@ const bulkUpdaterService = ({ strapi }) => {
2235
2307
  let isNestedAttr = false;
2236
2308
  let isRootDz = false;
2237
2309
  let parts = [];
2310
+ let resolvedFilterAttr = targetFilterAttr;
2238
2311
  if (targetFilterAttr) {
2239
- const parsed = parseDynamicZonePath(targetFilterAttr);
2240
- if (parsed.rootKey && contentType.attributes?.[parsed.rootKey]?.type === "dynamiczone") {
2241
- isRootDz = true;
2312
+ const resolvedField = resolveContentTypeField(contentType, targetFilterAttr);
2313
+ if (resolvedField) {
2314
+ resolvedFilterAttr = resolvedField;
2242
2315
  colHasAttribute = true;
2243
- isNestedAttr = true;
2244
- parts = parsed.cleanPath.split(".");
2245
- } else if (targetFilterAttr.includes(".") || targetFilterAttr.includes("[")) {
2246
- isNestedAttr = isDeepComponentField(contentType, strapi.components, targetFilterAttr);
2247
- if (isNestedAttr) {
2316
+ } else {
2317
+ const parsed = parseDynamicZonePath(targetFilterAttr);
2318
+ if (parsed.rootKey && contentType.attributes?.[parsed.rootKey]?.type === "dynamiczone") {
2319
+ isRootDz = true;
2248
2320
  colHasAttribute = true;
2321
+ isNestedAttr = true;
2249
2322
  parts = parsed.cleanPath.split(".");
2323
+ } else if (targetFilterAttr.includes(".") || targetFilterAttr.includes("[")) {
2324
+ isNestedAttr = isDeepComponentField(contentType, strapi.components, targetFilterAttr);
2325
+ if (isNestedAttr) {
2326
+ colHasAttribute = true;
2327
+ parts = parsed.cleanPath.split(".");
2328
+ }
2250
2329
  }
2251
- } else if (attrs[targetFilterAttr]) {
2252
- colHasAttribute = true;
2253
2330
  }
2254
2331
  }
2255
2332
  const normOp = (filterOperator || "").toLowerCase().replace(/_/g, "");
@@ -2273,9 +2350,44 @@ const bulkUpdaterService = ({ strapi }) => {
2273
2350
  }
2274
2351
  let condition = void 0;
2275
2352
  const q = (filterQuery || "").trim();
2276
- const targetAttrSchema = isNestedAttr ? getLeafAttributeSchema(contentType, strapi.components, targetFilterAttr) : attrs[targetFilterAttr];
2353
+ const targetAttrSchema = isNestedAttr ? getLeafAttributeSchema(contentType, strapi.components, targetFilterAttr) : attrs[resolvedFilterAttr];
2354
+ const isRelation = targetAttrSchema?.type === "relation";
2277
2355
  const isStringLike = ["string", "text", "email", "richtext", "uid"].includes(targetAttrSchema?.type);
2278
- if (normOp === "empty") {
2356
+ if (isRelation) {
2357
+ const targetUid = targetAttrSchema?.target;
2358
+ const targetContentType = targetUid ? strapi.contentTypes[targetUid] : void 0;
2359
+ const targetAttrs = targetContentType?.attributes || {};
2360
+ const targetStringAttrs = Object.keys(targetAttrs).filter(
2361
+ (k) => ["string", "text", "email", "uid"].includes(targetAttrs[k]?.type)
2362
+ );
2363
+ if (normOp === "empty") {
2364
+ condition = { documentId: { $null: true } };
2365
+ } else if (normOp === "notempty") {
2366
+ condition = { documentId: { $notNull: true } };
2367
+ } else if (q !== "") {
2368
+ const orConditions = [];
2369
+ targetStringAttrs.slice(0, 5).forEach((k) => {
2370
+ if (normOp === "equals") {
2371
+ orConditions.push({ [k]: { $eqi: q } });
2372
+ } else if (normOp === "startswith") {
2373
+ orConditions.push({ [k]: { $startsWithi: q } });
2374
+ } else if (normOp === "endswith") {
2375
+ orConditions.push({ [k]: { $endsWithi: q } });
2376
+ } else if (normOp === "notequals") {
2377
+ orConditions.push({ [k]: { $ne: q } });
2378
+ } else if (normOp === "notcontains") {
2379
+ orConditions.push({ [k]: { $notContainsi: q } });
2380
+ } else {
2381
+ orConditions.push({ [k]: { $containsi: q } });
2382
+ }
2383
+ });
2384
+ orConditions.push({ documentId: { $eq: q } });
2385
+ if (/^\d+$/.test(q)) {
2386
+ orConditions.push({ id: { $eq: Number(q) } });
2387
+ }
2388
+ condition = orConditions.length === 1 ? orConditions[0] : { $or: orConditions };
2389
+ }
2390
+ } else if (normOp === "empty") {
2279
2391
  condition = isStringLike ? { $or: [{ $null: true }, { $eq: "" }] } : { $null: true };
2280
2392
  } else if (normOp === "notempty") {
2281
2393
  condition = isStringLike ? { $and: [{ $notNull: true }, { $ne: "" }] } : { $notNull: true };
@@ -2311,13 +2423,13 @@ const bulkUpdaterService = ({ strapi }) => {
2311
2423
  } else {
2312
2424
  if (normOp === "empty" && isStringLike) {
2313
2425
  filters.$or = [
2314
- { [targetFilterAttr]: { $null: true } },
2315
- { [targetFilterAttr]: { $eq: "" } }
2426
+ { [resolvedFilterAttr]: { $null: true } },
2427
+ { [resolvedFilterAttr]: { $eq: "" } }
2316
2428
  ];
2317
2429
  } else if (normOp === "notempty" && isStringLike) {
2318
- filters[targetFilterAttr] = { $notNull: true, $ne: "" };
2430
+ filters[resolvedFilterAttr] = { $notNull: true, $ne: "" };
2319
2431
  } else {
2320
- filters[targetFilterAttr] = condition;
2432
+ filters[resolvedFilterAttr] = condition;
2321
2433
  }
2322
2434
  }
2323
2435
  }
@@ -2359,7 +2471,7 @@ const bulkUpdaterService = ({ strapi }) => {
2359
2471
  displayName: contentType.info?.displayName || uid,
2360
2472
  isSingleType: true,
2361
2473
  draftAndPublish: Boolean(contentType.options?.draftAndPublish),
2362
- entries: singleDoc ? [singleDoc] : [],
2474
+ entries: singleDoc ? [formatEntryRecord(singleDoc)] : [],
2363
2475
  pagination: {
2364
2476
  page: 1,
2365
2477
  pageSize: 1,
@@ -2376,8 +2488,13 @@ const bulkUpdaterService = ({ strapi }) => {
2376
2488
  } else {
2377
2489
  sortClause["createdAt"] = "desc";
2378
2490
  }
2379
- } else if (sortField && contentType.attributes?.[sortField]) {
2380
- sortClause[sortField] = sortOrder;
2491
+ } else if (sortField) {
2492
+ const resolvedField = resolveContentTypeField(contentType, sortField);
2493
+ if (resolvedField) {
2494
+ sortClause[resolvedField] = sortOrder;
2495
+ } else {
2496
+ sortClause["createdAt"] = "desc";
2497
+ }
2381
2498
  } else {
2382
2499
  sortClause["createdAt"] = "desc";
2383
2500
  }
@@ -2395,14 +2512,30 @@ const bulkUpdaterService = ({ strapi }) => {
2395
2512
  const passesDzFilter = (entry) => {
2396
2513
  const matches = getAllNestedValues(entry, targetFilterAttr);
2397
2514
  if (normOp === "empty") {
2398
- return matches.length === 0 || matches.every((m) => m.value === void 0 || m.value === null || String(m.value).trim() === "");
2515
+ return matches.length === 0 || matches.every((m) => {
2516
+ const v = m.value;
2517
+ if (v === void 0 || v === null) return true;
2518
+ if (Array.isArray(v)) return v.length === 0;
2519
+ if (typeof v === "object") return Object.keys(v).length === 0;
2520
+ return String(v).trim() === "";
2521
+ });
2399
2522
  }
2400
2523
  if (normOp === "notempty") {
2401
- return matches.some((m) => m.value !== void 0 && m.value !== null && String(m.value).trim() !== "");
2524
+ return matches.some((m) => {
2525
+ const v = m.value;
2526
+ if (v === void 0 || v === null) return false;
2527
+ if (Array.isArray(v)) return v.length > 0;
2528
+ if (typeof v === "object") return Object.keys(v).length > 0;
2529
+ return String(v).trim() !== "";
2530
+ });
2402
2531
  }
2403
- return matches.some((m) => {
2404
- if (m.value === void 0 || m.value === null) return false;
2405
- const valStr = String(m.value).toLowerCase();
2532
+ const testSingleValue = (v) => {
2533
+ if (v === void 0 || v === null) return false;
2534
+ if (Array.isArray(v)) return v.some(testSingleValue);
2535
+ if (typeof v === "object") {
2536
+ return testSingleValue(v.name) || testSingleValue(v.title) || testSingleValue(v.email) || testSingleValue(v.username) || testSingleValue(v.documentId) || testSingleValue(v.id) || testSingleValue(v.slug);
2537
+ }
2538
+ const valStr = String(v).toLowerCase();
2406
2539
  switch (normOp) {
2407
2540
  case "equals":
2408
2541
  return valStr === qVal;
@@ -2419,7 +2552,8 @@ const bulkUpdaterService = ({ strapi }) => {
2419
2552
  default:
2420
2553
  return true;
2421
2554
  }
2422
- });
2555
+ };
2556
+ return matches.some((m) => testSingleValue(m.value));
2423
2557
  };
2424
2558
  const matchedEntries = (rawEntries || []).filter(passesDzFilter);
2425
2559
  const totalEntries = matchedEntries.length;
@@ -2429,11 +2563,7 @@ const bulkUpdaterService = ({ strapi }) => {
2429
2563
  displayName: contentType.info?.displayName || uid,
2430
2564
  isSingleType: false,
2431
2565
  draftAndPublish: Boolean(contentType.options?.draftAndPublish),
2432
- entries: paginatedEntries.map((entry) => ({
2433
- id: entry.id,
2434
- documentId: entry.documentId,
2435
- ...entry
2436
- })),
2566
+ entries: paginatedEntries.map(formatEntryRecord),
2437
2567
  pagination: {
2438
2568
  page: currentPage,
2439
2569
  pageSize: limit,
@@ -2462,11 +2592,7 @@ const bulkUpdaterService = ({ strapi }) => {
2462
2592
  displayName: contentType.info?.displayName || uid,
2463
2593
  isSingleType: false,
2464
2594
  draftAndPublish: Boolean(contentType.options?.draftAndPublish),
2465
- entries: (entries || []).map((entry) => ({
2466
- id: entry.id,
2467
- documentId: entry.documentId,
2468
- ...entry
2469
- })),
2595
+ entries: (entries || []).map(formatEntryRecord),
2470
2596
  pagination: {
2471
2597
  page: currentPage,
2472
2598
  pageSize: limit,
@@ -2810,7 +2936,10 @@ const bulkUpdaterService = ({ strapi }) => {
2810
2936
  executionMode = "immediate",
2811
2937
  makerNotes = "",
2812
2938
  forceImmediate = false,
2813
- publishOnApproval = true
2939
+ publishOnApproval = true,
2940
+ publishStrategy = "safe",
2941
+ unreviewedConsent = false,
2942
+ unreviewedCount = 0
2814
2943
  }, currentUser) {
2815
2944
  if (!attribute) {
2816
2945
  throw new Error("Attribute name is required.");
@@ -2833,13 +2962,16 @@ const bulkUpdaterService = ({ strapi }) => {
2833
2962
  lastname: currentUser.lastname,
2834
2963
  username: currentUser.username
2835
2964
  } : { email: "admin@system.local" },
2836
- payload: { attribute, value, selectedEntries, selectedCollections, isRegex, regexPattern, publishOnApproval },
2965
+ payload: { attribute, value, selectedEntries, selectedCollections, isRegex, regexPattern, publishOnApproval, publishStrategy, unreviewedConsent, unreviewedCount },
2837
2966
  summary: {
2838
2967
  attribute,
2839
2968
  value,
2840
2969
  totalTargets: summary.totalTargets,
2841
2970
  collectionBreakdown: summary.collectionBreakdown,
2842
- publishOnApproval
2971
+ publishOnApproval,
2972
+ publishStrategy,
2973
+ unreviewedConsent,
2974
+ unreviewedCount
2843
2975
  },
2844
2976
  status: "pending_approval",
2845
2977
  executionMode: "maker_checker",
@@ -2861,6 +2993,9 @@ const bulkUpdaterService = ({ strapi }) => {
2861
2993
  failedCount: 0,
2862
2994
  isLive: false,
2863
2995
  publishOnApproval: Boolean(publishOnApproval),
2996
+ publishStrategy,
2997
+ unreviewedConsent,
2998
+ unreviewedCount,
2864
2999
  makerNotes,
2865
3000
  results: []
2866
3001
  });
@@ -2889,13 +3024,16 @@ const bulkUpdaterService = ({ strapi }) => {
2889
3024
  lastname: currentUser.lastname,
2890
3025
  username: currentUser.username
2891
3026
  } : { email: "admin@system.local" },
2892
- payload: { attribute, value, selectedEntries, selectedCollections, isRegex, regexPattern, publishOnApproval },
3027
+ payload: { attribute, value, selectedEntries, selectedCollections, isRegex, regexPattern, publishOnApproval, publishStrategy, unreviewedConsent, unreviewedCount },
2893
3028
  summary: {
2894
3029
  attribute,
2895
3030
  value,
2896
3031
  totalTargets: summary.totalTargets,
2897
3032
  collectionBreakdown: summary.collectionBreakdown,
2898
- publishOnApproval
3033
+ publishOnApproval,
3034
+ publishStrategy,
3035
+ unreviewedConsent,
3036
+ unreviewedCount
2899
3037
  },
2900
3038
  status: "queued_offpeak",
2901
3039
  executionMode: "queue_offpeak",
@@ -2919,6 +3057,9 @@ const bulkUpdaterService = ({ strapi }) => {
2919
3057
  failedCount: 0,
2920
3058
  isLive: false,
2921
3059
  publishOnApproval: Boolean(publishOnApproval),
3060
+ publishStrategy,
3061
+ unreviewedConsent,
3062
+ unreviewedCount,
2922
3063
  makerNotes,
2923
3064
  results: []
2924
3065
  });
@@ -2948,13 +3089,16 @@ const bulkUpdaterService = ({ strapi }) => {
2948
3089
  lastname: currentUser.lastname,
2949
3090
  username: currentUser.username
2950
3091
  } : { email: "admin@system.local" },
2951
- payload: { attribute, value, selectedEntries, selectedCollections, isRegex, regexPattern, publishOnApproval },
3092
+ payload: { attribute, value, selectedEntries, selectedCollections, isRegex, regexPattern, publishOnApproval, publishStrategy, unreviewedConsent, unreviewedCount },
2952
3093
  summary: {
2953
3094
  attribute,
2954
3095
  value,
2955
3096
  totalTargets: summary.totalTargets,
2956
3097
  collectionBreakdown: summary.collectionBreakdown,
2957
- publishOnApproval
3098
+ publishOnApproval,
3099
+ publishStrategy,
3100
+ unreviewedConsent,
3101
+ unreviewedCount
2958
3102
  },
2959
3103
  status: "queued_offpeak",
2960
3104
  executionMode: "queue_offpeak",
@@ -2978,6 +3122,9 @@ const bulkUpdaterService = ({ strapi }) => {
2978
3122
  failedCount: 0,
2979
3123
  isLive: false,
2980
3124
  publishOnApproval: Boolean(publishOnApproval),
3125
+ publishStrategy,
3126
+ unreviewedConsent,
3127
+ unreviewedCount,
2981
3128
  policyMessage,
2982
3129
  makerNotes: "Automatically queued due to peak business hours restriction.",
2983
3130
  results: []
@@ -2994,7 +3141,7 @@ const bulkUpdaterService = ({ strapi }) => {
2994
3141
  }
2995
3142
  return await executeBatchInternal(
2996
3143
  strapi,
2997
- { attribute, value, selectedEntries, selectedCollections, isRegex, regexPattern, publishOnApproval },
3144
+ { attribute, value, selectedEntries, selectedCollections, isRegex, regexPattern, publishOnApproval, publishStrategy, unreviewedConsent },
2998
3145
  currentUser
2999
3146
  );
3000
3147
  },
@@ -3006,7 +3153,9 @@ const bulkUpdaterService = ({ strapi }) => {
3006
3153
  selectedCollections = [],
3007
3154
  executionMode = "immediate",
3008
3155
  makerNotes = "",
3009
- forceImmediate = false
3156
+ forceImmediate = false,
3157
+ unreviewedConsent = false,
3158
+ unreviewedCount = 0
3010
3159
  }, currentUser) {
3011
3160
  const executionConfig = getExecutionWindowConfig(strapi);
3012
3161
  const { isBusinessHours, policyMessage } = checkBusinessHours(executionConfig);
@@ -3026,13 +3175,15 @@ const bulkUpdaterService = ({ strapi }) => {
3026
3175
  lastname: currentUser.lastname,
3027
3176
  username: currentUser.username
3028
3177
  } : { email: "admin@system.local" },
3029
- payload: { action: "publish", attribute: "(publish)", value: "published", selectedEntries, selectedCollections, publishOnApproval: true },
3178
+ payload: { action: "publish", attribute: "(publish)", value: "published", selectedEntries, selectedCollections, publishOnApproval: true, unreviewedConsent, unreviewedCount },
3030
3179
  summary: {
3031
3180
  attribute: "(publish)",
3032
3181
  value: "published",
3033
3182
  totalTargets: summary.totalTargets,
3034
3183
  collectionBreakdown: summary.collectionBreakdown,
3035
- publishOnApproval: true
3184
+ publishOnApproval: true,
3185
+ unreviewedConsent,
3186
+ unreviewedCount
3036
3187
  },
3037
3188
  status: "pending_approval",
3038
3189
  executionMode: "maker_checker",
@@ -3054,6 +3205,8 @@ const bulkUpdaterService = ({ strapi }) => {
3054
3205
  failedCount: 0,
3055
3206
  isLive: false,
3056
3207
  publishOnApproval: true,
3208
+ unreviewedConsent,
3209
+ unreviewedCount,
3057
3210
  makerNotes,
3058
3211
  results: []
3059
3212
  });
@@ -3082,13 +3235,15 @@ const bulkUpdaterService = ({ strapi }) => {
3082
3235
  lastname: currentUser.lastname,
3083
3236
  username: currentUser.username
3084
3237
  } : { email: "admin@system.local" },
3085
- payload: { action: "publish", attribute: "(publish)", value: "published", selectedEntries, selectedCollections, publishOnApproval: true },
3238
+ payload: { action: "publish", attribute: "(publish)", value: "published", selectedEntries, selectedCollections, publishOnApproval: true, unreviewedConsent, unreviewedCount },
3086
3239
  summary: {
3087
3240
  attribute: "(publish)",
3088
3241
  value: "published",
3089
3242
  totalTargets: summary.totalTargets,
3090
3243
  collectionBreakdown: summary.collectionBreakdown,
3091
- publishOnApproval: true
3244
+ publishOnApproval: true,
3245
+ unreviewedConsent,
3246
+ unreviewedCount
3092
3247
  },
3093
3248
  status: "queued_offpeak",
3094
3249
  executionMode: "queue_offpeak",
@@ -3112,6 +3267,8 @@ const bulkUpdaterService = ({ strapi }) => {
3112
3267
  failedCount: 0,
3113
3268
  isLive: false,
3114
3269
  publishOnApproval: true,
3270
+ unreviewedConsent,
3271
+ unreviewedCount,
3115
3272
  makerNotes,
3116
3273
  results: []
3117
3274
  });
@@ -3141,13 +3298,15 @@ const bulkUpdaterService = ({ strapi }) => {
3141
3298
  lastname: currentUser.lastname,
3142
3299
  username: currentUser.username
3143
3300
  } : { email: "admin@system.local" },
3144
- payload: { action: "publish", attribute: "(publish)", value: "published", selectedEntries, selectedCollections, publishOnApproval: true },
3301
+ payload: { action: "publish", attribute: "(publish)", value: "published", selectedEntries, selectedCollections, publishOnApproval: true, unreviewedConsent, unreviewedCount },
3145
3302
  summary: {
3146
3303
  attribute: "(publish)",
3147
3304
  value: "published",
3148
3305
  totalTargets: summary.totalTargets,
3149
3306
  collectionBreakdown: summary.collectionBreakdown,
3150
- publishOnApproval: true
3307
+ publishOnApproval: true,
3308
+ unreviewedConsent,
3309
+ unreviewedCount
3151
3310
  },
3152
3311
  status: "queued_offpeak",
3153
3312
  executionMode: "queue_offpeak",
@@ -3171,6 +3330,8 @@ const bulkUpdaterService = ({ strapi }) => {
3171
3330
  failedCount: 0,
3172
3331
  isLive: false,
3173
3332
  publishOnApproval: true,
3333
+ unreviewedConsent,
3334
+ unreviewedCount,
3174
3335
  policyMessage,
3175
3336
  makerNotes: "Automatically queued due to peak business hours restriction.",
3176
3337
  results: []
@@ -3187,7 +3348,7 @@ const bulkUpdaterService = ({ strapi }) => {
3187
3348
  }
3188
3349
  return await executePublishBatchInternal(
3189
3350
  strapi,
3190
- { selectedEntries, selectedCollections },
3351
+ { selectedEntries, selectedCollections, unreviewedConsent },
3191
3352
  currentUser
3192
3353
  );
3193
3354
  },
@@ -3721,7 +3882,9 @@ const auditService = ({ strapi }) => ({
3721
3882
  value: batchSummary.value,
3722
3883
  updatedCount: batchSummary.updatedCount,
3723
3884
  skippedCount: batchSummary.skippedCount,
3724
- totalTargets: batchSummary.totalTargets
3885
+ totalTargets: batchSummary.totalTargets,
3886
+ publishStrategy: batchSummary.publishStrategy,
3887
+ unreviewedConsent: batchSummary.unreviewedConsent
3725
3888
  },
3726
3889
  user
3727
3890
  );
@@ -3744,6 +3907,9 @@ const auditService = ({ strapi }) => ({
3744
3907
  updatedCount: batchSummary.updatedCount,
3745
3908
  skippedCount: batchSummary.skippedCount,
3746
3909
  totalTargets: batchSummary.totalTargets,
3910
+ publishOnApproval: batchSummary.publishOnApproval,
3911
+ publishStrategy: batchSummary.publishStrategy,
3912
+ unreviewedConsent: batchSummary.unreviewedConsent,
3747
3913
  diffs: results,
3748
3914
  isRolledBack: false,
3749
3915
  rolledBackAt: null,
@@ -4740,7 +4906,23 @@ function getNestedExportValue(entry, path) {
4740
4906
  return String(item);
4741
4907
  }).filter(Boolean).join("; ");
4742
4908
  }
4743
- current = current[part];
4909
+ if (current && typeof current === "object") {
4910
+ if (current[part] !== void 0) {
4911
+ current = current[part];
4912
+ } else {
4913
+ const camel = part.replace(/_([a-z0-9])/g, (_, l) => l.toUpperCase());
4914
+ const snake = part.replace(/([A-Z])/g, "_$1").toLowerCase();
4915
+ if (current[camel] !== void 0) {
4916
+ current = current[camel];
4917
+ } else if (current[snake] !== void 0) {
4918
+ current = current[snake];
4919
+ } else {
4920
+ current = void 0;
4921
+ }
4922
+ }
4923
+ } else {
4924
+ current = void 0;
4925
+ }
4744
4926
  }
4745
4927
  if (current === null || current === void 0) return "";
4746
4928
  if (typeof current === "object" && current.url) {
@@ -4808,9 +4990,27 @@ async function findExistingDocument(strapi, uid, targetDocId, docLocale) {
4808
4990
  }
4809
4991
  return null;
4810
4992
  }
4993
+ function deepMergeComponent(existingVal, newVal) {
4994
+ if (!existingVal || typeof existingVal !== "object" || Array.isArray(existingVal)) {
4995
+ return newVal;
4996
+ }
4997
+ if (!newVal || typeof newVal !== "object" || Array.isArray(newVal)) {
4998
+ return newVal;
4999
+ }
5000
+ const merged = { ...existingVal };
5001
+ for (const k of Object.keys(newVal)) {
5002
+ if (newVal[k] && typeof newVal[k] === "object" && !Array.isArray(newVal[k])) {
5003
+ merged[k] = deepMergeComponent(existingVal[k], newVal[k]);
5004
+ } else if (newVal[k] !== void 0) {
5005
+ merged[k] = newVal[k];
5006
+ }
5007
+ }
5008
+ return merged;
5009
+ }
4811
5010
  async function processCsvRows(strapi, ct, uid, rows, startRowNumber, options) {
4812
5011
  const attrs = ct.attributes || {};
4813
- const { mode = "auto", publish = true, user } = options;
5012
+ const { mode = "auto", publish = true, publishStrategy = "safe", user } = options;
5013
+ const isDraftAndPublish = Boolean(ct.options?.draftAndPublish);
4814
5014
  const stats = {
4815
5015
  total: rows.length,
4816
5016
  created: 0,
@@ -4841,32 +5041,86 @@ async function processCsvRows(strapi, ct, uid, rows, startRowNumber, options) {
4841
5041
  const normalizedKey = key.toLowerCase().replace(/[\s_-]/g, "");
4842
5042
  if (SYSTEM_FIELD_KEYS.has(normalizedKey)) continue;
4843
5043
  if (key.includes(".")) {
4844
- const [parent, child] = key.split(".");
4845
- const parentDef = attrs[parent];
4846
- if (parentDef?.type === "component" && !parentDef.repeatable && parentDef.component) {
4847
- const comp = strapi.components[parentDef.component];
4848
- const childDef = comp?.attributes?.[child];
4849
- if (childDef) {
4850
- if (!payload[parent]) payload[parent] = {};
4851
- let val = rawVal;
5044
+ const parts = key.split(".");
5045
+ let currentAttrs = attrs;
5046
+ let currentTarget = payload;
5047
+ for (let pIdx = 0; pIdx < parts.length; pIdx++) {
5048
+ const part = parts[pIdx];
5049
+ const isLeaf = pIdx === parts.length - 1;
5050
+ if (isLeaf) {
5051
+ let leafDef = currentAttrs[part];
5052
+ if (!leafDef) {
5053
+ const match = Object.keys(currentAttrs).find(
5054
+ (k) => k.toLowerCase() === part.toLowerCase() || k.toLowerCase() === part.toLowerCase().replace(/\s+/g, "")
5055
+ );
5056
+ if (match) leafDef = currentAttrs[match];
5057
+ }
5058
+ if (!leafDef) {
5059
+ break;
5060
+ }
4852
5061
  if (rawVal === "" || rawVal === null || rawVal === void 0) {
4853
- val = null;
4854
- } else if (childDef.type === "integer" || childDef.type === "biginteger") {
5062
+ if (leafDef.type !== "dynamiczone" && !(leafDef.type === "component" && leafDef.repeatable) && leafDef.type !== "relation") {
5063
+ currentTarget[part] = null;
5064
+ }
5065
+ } else if (leafDef.type === "integer" || leafDef.type === "biginteger") {
4855
5066
  const parsedInt = parseInt(rawVal, 10);
4856
- val = isNaN(parsedInt) ? null : parsedInt;
4857
- } else if (childDef.type === "float" || childDef.type === "decimal") {
5067
+ currentTarget[part] = isNaN(parsedInt) ? null : parsedInt;
5068
+ } else if (leafDef.type === "float" || leafDef.type === "decimal") {
4858
5069
  const parsedFloat = parseFloat(rawVal);
4859
- val = isNaN(parsedFloat) ? null : parsedFloat;
4860
- } else if (childDef.type === "boolean") {
4861
- val = rawVal.toLowerCase() === "true" || rawVal === "1";
4862
- } else if (childDef.type === "json") {
5070
+ currentTarget[part] = isNaN(parsedFloat) ? null : parsedFloat;
5071
+ } else if (leafDef.type === "boolean") {
5072
+ currentTarget[part] = rawVal.toLowerCase() === "true" || rawVal === "1";
5073
+ } else if (leafDef.type === "json") {
5074
+ try {
5075
+ currentTarget[part] = JSON.parse(rawVal);
5076
+ } catch {
5077
+ currentTarget[part] = rawVal;
5078
+ }
5079
+ } else if (leafDef.type === "component" && leafDef.repeatable) {
4863
5080
  try {
4864
- val = JSON.parse(rawVal);
5081
+ const parsed = JSON.parse(rawVal);
5082
+ if (Array.isArray(parsed)) {
5083
+ currentTarget[part] = parsed;
5084
+ }
4865
5085
  } catch {
4866
- val = rawVal;
4867
5086
  }
5087
+ } else if (leafDef.type === "relation") {
5088
+ try {
5089
+ const parsed = JSON.parse(rawVal);
5090
+ if (Array.isArray(parsed) || typeof parsed === "object" && parsed !== null) {
5091
+ currentTarget[part] = parsed;
5092
+ } else if (isValidDocId(rawVal)) {
5093
+ currentTarget[part] = rawVal;
5094
+ }
5095
+ } catch {
5096
+ if (isValidDocId(rawVal)) {
5097
+ currentTarget[part] = rawVal;
5098
+ }
5099
+ }
5100
+ } else {
5101
+ currentTarget[part] = rawVal;
5102
+ }
5103
+ } else {
5104
+ let compDef = currentAttrs[part];
5105
+ if (!compDef) {
5106
+ const match = Object.keys(currentAttrs).find(
5107
+ (k) => k.toLowerCase() === part.toLowerCase() || k.toLowerCase() === part.toLowerCase().replace(/\s+/g, "")
5108
+ );
5109
+ if (match) compDef = currentAttrs[match];
5110
+ }
5111
+ if (compDef?.type === "component" && !compDef.repeatable && compDef.component) {
5112
+ const comp = strapi.components[compDef.component];
5113
+ if (!comp?.attributes) {
5114
+ break;
5115
+ }
5116
+ if (!currentTarget[part] || typeof currentTarget[part] !== "object" || Array.isArray(currentTarget[part])) {
5117
+ currentTarget[part] = {};
5118
+ }
5119
+ currentTarget = currentTarget[part];
5120
+ currentAttrs = comp.attributes;
5121
+ } else {
5122
+ break;
4868
5123
  }
4869
- payload[parent][child] = val;
4870
5124
  }
4871
5125
  }
4872
5126
  continue;
@@ -5001,7 +5255,7 @@ async function processCsvRows(strapi, ct, uid, rows, startRowNumber, options) {
5001
5255
  const mergedPayload = { ...payload };
5002
5256
  for (const key of Object.keys(payload)) {
5003
5257
  if (attrs[key]?.type === "component" && typeof payload[key] === "object" && payload[key] !== null) {
5004
- mergedPayload[key] = { ...existing[key] || {}, ...payload[key] };
5258
+ mergedPayload[key] = deepMergeComponent(existing[key], payload[key]);
5005
5259
  }
5006
5260
  }
5007
5261
  if (Object.keys(mergedPayload).length === 0) {
@@ -5025,7 +5279,9 @@ async function processCsvRows(strapi, ct, uid, rows, startRowNumber, options) {
5025
5279
  };
5026
5280
  if (rowLocale) updateArgs.locale = rowLocale;
5027
5281
  await strapi.documents(uid).update(updateArgs);
5028
- if (publish && ct.options?.draftAndPublish) {
5282
+ const isApproved = isDraftAndPublish && Boolean(existing?.publishedAt) && existing?.status !== "modified";
5283
+ const shouldPublish = isDraftAndPublish && Boolean(publish) && (publishStrategy === "all" ? true : isApproved);
5284
+ if (shouldPublish) {
5029
5285
  try {
5030
5286
  const pubArgs = { documentId: resolvedDocId };
5031
5287
  if (rowLocale) pubArgs.locale = rowLocale;
@@ -5039,7 +5295,7 @@ async function processCsvRows(strapi, ct, uid, rows, startRowNumber, options) {
5039
5295
  uid,
5040
5296
  documentId: resolvedDocId,
5041
5297
  userId: user?.id,
5042
- statusOverride: publish && ct.options?.draftAndPublish ? "published" : void 0
5298
+ statusOverride: shouldPublish ? "published" : isDraftAndPublish ? "draft" : void 0
5043
5299
  });
5044
5300
  } catch {
5045
5301
  }
@@ -5069,7 +5325,7 @@ async function processCsvRows(strapi, ct, uid, rows, startRowNumber, options) {
5069
5325
  const mergedPayload = { ...payload };
5070
5326
  for (const key of Object.keys(payload)) {
5071
5327
  if (attrs[key]?.type === "component" && typeof payload[key] === "object" && payload[key] !== null) {
5072
- mergedPayload[key] = { ...existing[key] || {}, ...payload[key] };
5328
+ mergedPayload[key] = deepMergeComponent(existing[key], payload[key]);
5073
5329
  }
5074
5330
  }
5075
5331
  const updateArgs = {
@@ -5078,7 +5334,9 @@ async function processCsvRows(strapi, ct, uid, rows, startRowNumber, options) {
5078
5334
  };
5079
5335
  if (rowLocale) updateArgs.locale = rowLocale;
5080
5336
  await strapi.documents(uid).update(updateArgs);
5081
- if (publish && ct.options?.draftAndPublish) {
5337
+ const isApproved = isDraftAndPublish && Boolean(existing?.publishedAt) && existing?.status !== "modified";
5338
+ const shouldPublish = isDraftAndPublish && Boolean(publish) && (publishStrategy === "all" ? true : isApproved);
5339
+ if (shouldPublish) {
5082
5340
  try {
5083
5341
  const pubArgs = { documentId: resolvedDocId };
5084
5342
  if (rowLocale) pubArgs.locale = rowLocale;
@@ -5092,7 +5350,7 @@ async function processCsvRows(strapi, ct, uid, rows, startRowNumber, options) {
5092
5350
  uid,
5093
5351
  documentId: resolvedDocId,
5094
5352
  userId: user?.id,
5095
- statusOverride: publish && ct.options?.draftAndPublish ? "published" : void 0
5353
+ statusOverride: shouldPublish ? "published" : isDraftAndPublish ? "draft" : void 0
5096
5354
  });
5097
5355
  } catch {
5098
5356
  }
@@ -5118,7 +5376,8 @@ async function processCsvRows(strapi, ct, uid, rows, startRowNumber, options) {
5118
5376
  if (rowLocale) createArgs.locale = rowLocale;
5119
5377
  const created = await strapi.documents(uid).create(createArgs);
5120
5378
  const newDocId = created?.documentId || docId;
5121
- if (publish && ct.options?.draftAndPublish && newDocId) {
5379
+ const shouldPublishNew = isDraftAndPublish && Boolean(publish) && (publishStrategy === "all" ? true : false);
5380
+ if (shouldPublishNew && newDocId) {
5122
5381
  try {
5123
5382
  const pubArgs = { documentId: newDocId };
5124
5383
  if (rowLocale) pubArgs.locale = rowLocale;
@@ -5132,7 +5391,7 @@ async function processCsvRows(strapi, ct, uid, rows, startRowNumber, options) {
5132
5391
  uid,
5133
5392
  documentId: newDocId,
5134
5393
  userId: user?.id,
5135
- statusOverride: publish && ct.options?.draftAndPublish ? "published" : void 0
5394
+ statusOverride: shouldPublishNew ? "published" : isDraftAndPublish ? "draft" : void 0
5136
5395
  });
5137
5396
  } catch {
5138
5397
  }
@@ -5163,7 +5422,7 @@ async function processCsvRows(strapi, ct, uid, rows, startRowNumber, options) {
5163
5422
  const mergedPayload = { ...payload };
5164
5423
  for (const key of Object.keys(payload)) {
5165
5424
  if (attrs[key]?.type === "component" && typeof payload[key] === "object" && payload[key] !== null) {
5166
- mergedPayload[key] = { ...existing[key] || {}, ...payload[key] };
5425
+ mergedPayload[key] = deepMergeComponent(existing[key], payload[key]);
5167
5426
  }
5168
5427
  }
5169
5428
  const updateArgs = {
@@ -5172,7 +5431,9 @@ async function processCsvRows(strapi, ct, uid, rows, startRowNumber, options) {
5172
5431
  };
5173
5432
  if (rowLocale) updateArgs.locale = rowLocale;
5174
5433
  await strapi.documents(uid).update(updateArgs);
5175
- if (publish && ct.options?.draftAndPublish) {
5434
+ const isApproved = isDraftAndPublish && Boolean(existing?.publishedAt) && existing?.status !== "modified";
5435
+ const shouldPublish = isDraftAndPublish && Boolean(publish) && (publishStrategy === "all" ? true : isApproved);
5436
+ if (shouldPublish) {
5176
5437
  try {
5177
5438
  const pubArgs = { documentId: resolvedDocId };
5178
5439
  if (rowLocale) pubArgs.locale = rowLocale;
@@ -5186,7 +5447,7 @@ async function processCsvRows(strapi, ct, uid, rows, startRowNumber, options) {
5186
5447
  uid,
5187
5448
  documentId: resolvedDocId,
5188
5449
  userId: user?.id,
5189
- statusOverride: publish && ct.options?.draftAndPublish ? "published" : void 0
5450
+ statusOverride: shouldPublish ? "published" : isDraftAndPublish ? "draft" : void 0
5190
5451
  });
5191
5452
  } catch {
5192
5453
  }
@@ -5212,7 +5473,8 @@ async function processCsvRows(strapi, ct, uid, rows, startRowNumber, options) {
5212
5473
  if (rowLocale) createArgs.locale = rowLocale;
5213
5474
  const created = await strapi.documents(uid).create(createArgs);
5214
5475
  const newDocId = created?.documentId || docId;
5215
- if (publish && ct.options?.draftAndPublish && newDocId) {
5476
+ const shouldPublishNew = isDraftAndPublish && Boolean(publish) && (publishStrategy === "all" ? true : false);
5477
+ if (shouldPublishNew && newDocId) {
5216
5478
  try {
5217
5479
  const pubArgs = { documentId: newDocId };
5218
5480
  if (rowLocale) pubArgs.locale = rowLocale;
@@ -5226,7 +5488,7 @@ async function processCsvRows(strapi, ct, uid, rows, startRowNumber, options) {
5226
5488
  uid,
5227
5489
  documentId: newDocId,
5228
5490
  userId: user?.id,
5229
- statusOverride: publish && ct.options?.draftAndPublish ? "published" : void 0
5491
+ statusOverride: shouldPublishNew ? "published" : isDraftAndPublish ? "draft" : void 0
5230
5492
  });
5231
5493
  } catch {
5232
5494
  }
@@ -5258,7 +5520,7 @@ async function processCsvRows(strapi, ct, uid, rows, startRowNumber, options) {
5258
5520
  }
5259
5521
  return stats;
5260
5522
  }
5261
- async function recordCsvAuditLog(strapi, ct, uid, batchId, stats, user) {
5523
+ async function recordCsvAuditLog(strapi, ct, uid, batchId, stats, user, publishOnApproval, publishStrategy, unreviewedConsent) {
5262
5524
  if (stats.updated === 0 && stats.created === 0) return;
5263
5525
  try {
5264
5526
  const auditService2 = strapi.plugin("bulk-updater").service("audit");
@@ -5302,7 +5564,14 @@ async function recordCsvAuditLog(strapi, ct, uid, batchId, stats, user) {
5302
5564
  firstname: userName || "Admin"
5303
5565
  };
5304
5566
  if (typeof auditService2.recordBatch === "function") {
5305
- await auditService2.recordBatch(batchSummary, stats.diffs, userObj);
5567
+ await auditService2.recordBatch(
5568
+ batchSummary,
5569
+ stats.diffs,
5570
+ userObj,
5571
+ publishOnApproval,
5572
+ publishStrategy,
5573
+ unreviewedConsent
5574
+ );
5306
5575
  } else if (typeof auditService2.logOperation === "function") {
5307
5576
  await auditService2.logOperation({
5308
5577
  ...batchSummary,
@@ -5629,7 +5898,9 @@ const csvService = ({ strapi }) => ({
5629
5898
  skipped: 0,
5630
5899
  newWithoutDocId: 0,
5631
5900
  errors: [],
5632
- diffs: []
5901
+ diffs: [],
5902
+ publishStrategy: options.publishStrategy,
5903
+ unreviewedConsent: options.unreviewedConsent
5633
5904
  };
5634
5905
  csvImportSessions.set(batchId, session);
5635
5906
  }
@@ -5643,7 +5914,17 @@ const csvService = ({ strapi }) => ({
5643
5914
  session.errors.push(...chunkStats.errors);
5644
5915
  session.diffs.push(...chunkStats.diffs);
5645
5916
  if (options.isLastChunk) {
5646
- await recordCsvAuditLog(strapi, ct, uid, batchId, session, options.user);
5917
+ await recordCsvAuditLog(
5918
+ strapi,
5919
+ ct,
5920
+ uid,
5921
+ batchId,
5922
+ session,
5923
+ options.user,
5924
+ options.publish !== false,
5925
+ session.publishStrategy || options.publishStrategy,
5926
+ session.unreviewedConsent ?? options.unreviewedConsent
5927
+ );
5647
5928
  try {
5648
5929
  const bulkService = strapi.plugin("bulk-updater").service("bulkUpdater");
5649
5930
  if (bulkService && bulkService.invalidateCollectionCache) {
@@ -5697,7 +5978,17 @@ const csvService = ({ strapi }) => ({
5697
5978
  }
5698
5979
  } catch {
5699
5980
  }
5700
- await recordCsvAuditLog(strapi, ct, uid, batchId, stats, options.user);
5981
+ await recordCsvAuditLog(
5982
+ strapi,
5983
+ ct,
5984
+ uid,
5985
+ batchId,
5986
+ stats,
5987
+ options.user,
5988
+ options.publish !== false,
5989
+ options.publishStrategy,
5990
+ options.unreviewedConsent
5991
+ );
5701
5992
  return {
5702
5993
  success: true,
5703
5994
  batchId,
@@ -5717,7 +6008,17 @@ const csvService = ({ strapi }) => ({
5717
6008
  ...options,
5718
6009
  user: effectiveUser
5719
6010
  });
5720
- await recordCsvAuditLog(strapi, ct, uid, batchId, stats, effectiveUser);
6011
+ await recordCsvAuditLog(
6012
+ strapi,
6013
+ ct,
6014
+ uid,
6015
+ batchId,
6016
+ stats,
6017
+ effectiveUser,
6018
+ options.publish !== false,
6019
+ options.publishStrategy,
6020
+ options.unreviewedConsent
6021
+ );
5721
6022
  try {
5722
6023
  const bulkService = strapi.plugin("bulk-updater").service("bulkUpdater");
5723
6024
  if (bulkService && bulkService.invalidateCollectionCache) {
@@ -5755,6 +6056,15 @@ const csvService = ({ strapi }) => ({
5755
6056
  let validRows = 0;
5756
6057
  let estimatedUpdates = 0;
5757
6058
  let estimatedCreates = 0;
6059
+ const isDraftAndPublish = Boolean(ct.options?.draftAndPublish);
6060
+ const lifecycleStats = {
6061
+ isDraftAndPublish,
6062
+ totalChecked: 0,
6063
+ published: 0,
6064
+ modified: 0,
6065
+ draft: 0,
6066
+ unreviewedCount: 0
6067
+ };
5758
6068
  const errors = [];
5759
6069
  const warnings = [];
5760
6070
  for (let i = 0; i < rows.length; i++) {
@@ -5767,6 +6077,19 @@ const csvService = ({ strapi }) => ({
5767
6077
  if (docId) {
5768
6078
  existing = await findExistingDocument(strapi, uid, docId, rowLocale);
5769
6079
  }
6080
+ if (existing && isDraftAndPublish) {
6081
+ lifecycleStats.totalChecked++;
6082
+ const isApproved = Boolean(existing.publishedAt) && existing.status !== "modified";
6083
+ if (isApproved) {
6084
+ lifecycleStats.published++;
6085
+ } else if (existing.status === "modified") {
6086
+ lifecycleStats.modified++;
6087
+ lifecycleStats.unreviewedCount++;
6088
+ } else {
6089
+ lifecycleStats.draft++;
6090
+ lifecycleStats.unreviewedCount++;
6091
+ }
6092
+ }
5770
6093
  if (mode === "update") {
5771
6094
  if (!docId) {
5772
6095
  errors.push({
@@ -5885,6 +6208,7 @@ const csvService = ({ strapi }) => ({
5885
6208
  validRows,
5886
6209
  estimatedUpdates,
5887
6210
  estimatedCreates,
6211
+ lifecycleStats,
5888
6212
  hasErrors: errors.length > 0,
5889
6213
  hasWarnings: warnings.length > 0,
5890
6214
  errors: errors.slice(0, 100),
@@ -5910,6 +6234,9 @@ const csvService = ({ strapi }) => ({
5910
6234
  forceImmediate = false,
5911
6235
  mode = "auto",
5912
6236
  publish = true,
6237
+ publishStrategy = "safe",
6238
+ unreviewedConsent = false,
6239
+ unreviewedCount = 0,
5913
6240
  user
5914
6241
  } = options;
5915
6242
  const executionConfig = getExecutionWindowConfig(strapi);
@@ -5934,22 +6261,33 @@ const csvService = ({ strapi }) => ({
5934
6261
  options: {
5935
6262
  mode,
5936
6263
  publish,
6264
+ publishStrategy,
6265
+ unreviewedConsent,
5937
6266
  user
5938
6267
  },
5939
6268
  attribute: `CSV Import: ${collectionDisplayName}`,
5940
6269
  value: `${rows.length} rows (${mode} mode)`,
5941
- publishOnApproval: publish
6270
+ publishOnApproval: publish,
6271
+ publishStrategy,
6272
+ unreviewedConsent,
6273
+ unreviewedCount
5942
6274
  },
5943
6275
  summary: {
5944
6276
  attribute: `CSV Import: ${collectionDisplayName}`,
5945
6277
  value: `${rows.length} rows (${mode} mode)`,
5946
6278
  totalTargets: rows.length,
5947
6279
  collectionBreakdown: { [collectionDisplayName]: rows.length },
5948
- publishOnApproval: publish
6280
+ publishOnApproval: publish,
6281
+ publishStrategy,
6282
+ unreviewedConsent,
6283
+ unreviewedCount
5949
6284
  },
5950
6285
  status: "pending_approval",
5951
6286
  executionMode: "maker_checker",
5952
- makerNotes
6287
+ makerNotes,
6288
+ publishStrategy,
6289
+ unreviewedConsent,
6290
+ unreviewedCount
5953
6291
  };
5954
6292
  registerPendingBatch(batch, {
5955
6293
  id: batchId,
@@ -5966,6 +6304,9 @@ const csvService = ({ strapi }) => ({
5966
6304
  failedCount: 0,
5967
6305
  isLive: false,
5968
6306
  publishOnApproval: publish,
6307
+ publishStrategy,
6308
+ unreviewedConsent,
6309
+ unreviewedCount,
5969
6310
  makerNotes,
5970
6311
  results: []
5971
6312
  });
@@ -5997,23 +6338,34 @@ const csvService = ({ strapi }) => ({
5997
6338
  options: {
5998
6339
  mode,
5999
6340
  publish,
6341
+ publishStrategy,
6342
+ unreviewedConsent,
6000
6343
  user
6001
6344
  },
6002
6345
  attribute: `CSV Import: ${collectionDisplayName}`,
6003
6346
  value: `${rows.length} rows (${mode} mode)`,
6004
- publishOnApproval: publish
6347
+ publishOnApproval: publish,
6348
+ publishStrategy,
6349
+ unreviewedConsent,
6350
+ unreviewedCount
6005
6351
  },
6006
6352
  summary: {
6007
6353
  attribute: `CSV Import: ${collectionDisplayName}`,
6008
6354
  value: `${rows.length} rows (${mode} mode)`,
6009
6355
  totalTargets: rows.length,
6010
6356
  collectionBreakdown: { [collectionDisplayName]: rows.length },
6011
- publishOnApproval: publish
6357
+ publishOnApproval: publish,
6358
+ publishStrategy,
6359
+ unreviewedConsent,
6360
+ unreviewedCount
6012
6361
  },
6013
6362
  status: "queued_offpeak",
6014
6363
  executionMode: "queue_offpeak",
6015
6364
  scheduledFor: executionConfig.offPeakStartTime,
6016
- makerNotes
6365
+ makerNotes,
6366
+ publishStrategy,
6367
+ unreviewedConsent,
6368
+ unreviewedCount
6017
6369
  };
6018
6370
  registerPendingBatch(batch, {
6019
6371
  id: batchId,
@@ -6031,6 +6383,9 @@ const csvService = ({ strapi }) => ({
6031
6383
  failedCount: 0,
6032
6384
  isLive: false,
6033
6385
  publishOnApproval: publish,
6386
+ publishStrategy,
6387
+ unreviewedConsent,
6388
+ unreviewedCount,
6034
6389
  makerNotes,
6035
6390
  results: []
6036
6391
  });
@@ -6063,23 +6418,34 @@ const csvService = ({ strapi }) => ({
6063
6418
  options: {
6064
6419
  mode,
6065
6420
  publish,
6421
+ publishStrategy,
6422
+ unreviewedConsent,
6066
6423
  user
6067
6424
  },
6068
6425
  attribute: `CSV Import: ${collectionDisplayName}`,
6069
6426
  value: `${rows.length} rows (${mode} mode)`,
6070
- publishOnApproval: publish
6427
+ publishOnApproval: publish,
6428
+ publishStrategy,
6429
+ unreviewedConsent,
6430
+ unreviewedCount
6071
6431
  },
6072
6432
  summary: {
6073
6433
  attribute: `CSV Import: ${collectionDisplayName}`,
6074
6434
  value: `${rows.length} rows (${mode} mode)`,
6075
6435
  totalTargets: rows.length,
6076
6436
  collectionBreakdown: { [collectionDisplayName]: rows.length },
6077
- publishOnApproval: publish
6437
+ publishOnApproval: publish,
6438
+ publishStrategy,
6439
+ unreviewedConsent,
6440
+ unreviewedCount
6078
6441
  },
6079
6442
  status: "queued_offpeak",
6080
6443
  executionMode: "queue_offpeak",
6081
6444
  scheduledFor: executionConfig.offPeakStartTime,
6082
- makerNotes: "Automatically queued due to peak business hours restriction."
6445
+ makerNotes: "Automatically queued due to peak business hours restriction.",
6446
+ publishStrategy,
6447
+ unreviewedConsent,
6448
+ unreviewedCount
6083
6449
  };
6084
6450
  registerPendingBatch(batch, {
6085
6451
  id: batchId,
@@ -6097,6 +6463,9 @@ const csvService = ({ strapi }) => ({
6097
6463
  failedCount: 0,
6098
6464
  isLive: false,
6099
6465
  publishOnApproval: publish,
6466
+ publishStrategy,
6467
+ unreviewedConsent,
6468
+ unreviewedCount,
6100
6469
  policyMessage,
6101
6470
  makerNotes: "Automatically queued due to peak business hours restriction.",
6102
6471
  results: []