@shushed/helpers 0.0.205-fix-erp-729-20251209155111 → 0.0.207

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.
@@ -62,6 +62,7 @@ class AirtableHelper extends runtime_1.default {
62
62
  fieldsToMergeOn: (options.fieldsToMergeOn ?? [this.primaryKeyFieldName]).map(x => this.dictionary[x] || x),
63
63
  },
64
64
  returnFieldsByFieldId: true,
65
+ typecast: options.typecast || false,
65
66
  records: currentBatch.map(x => {
66
67
  const recordId = x.$recordId;
67
68
  const fieldsWithoutRecordId = { ...x };
@@ -621,5 +622,14 @@ class AirtableHelper extends runtime_1.default {
621
622
  missingFields,
622
623
  };
623
624
  }
625
+ static diff(x, y) {
626
+ if (!y || !y.fields) {
627
+ return x;
628
+ }
629
+ const notUndefinedAndNotClearsUndefined = ([key, value]) => value !== undefined && !((value === null || value === '') && (y.fields[key] === undefined));
630
+ const notEqual = ([key, value]) => !(0, lodash_isequal_1.default)(y.fields[key], value);
631
+ const notSameImage = ([key, value]) => !(value && Array.isArray(value) && value.every(x => x && typeof x === 'object' && Object.keys(x).length === 1 && x.url && y.fields[key]?.some((y) => y.url === x.url)));
632
+ return Object.fromEntries(Object.entries(x).filter(([key, value]) => notUndefinedAndNotClearsUndefined([key, value]) && notEqual([key, value]) && notSameImage([key, value])));
633
+ }
624
634
  }
625
635
  exports.default = AirtableHelper;
@@ -400,15 +400,18 @@ function transformMasterOrder(payload) {
400
400
  const desc = String(line?.description || '').trim().toLowerCase();
401
401
  return desc === 'delivery';
402
402
  };
403
+ let invoicesNetMinor = 0;
403
404
  let invoicesGrossMinor = 0;
404
405
  (payload.invoices || []).forEach((inv) => {
405
406
  (inv.invoice_lines || []).forEach((line) => {
406
407
  if (!isDeliveryItem(line)) {
408
+ invoicesNetMinor += toMinorUnits(Number(line?.amount || line?.amount_excl_vat || 0));
407
409
  invoicesGrossMinor += toMinorUnits(Number(line?.amount_including_vat || line?.amount_incl_vat || 0));
408
410
  }
409
411
  });
410
412
  });
411
413
  if (invoicesGrossMinor === 0 && (payload.invoices || []).length > 0) {
414
+ invoicesNetMinor = toMinorUnits((payload.invoices || []).reduce((acc, inv) => acc + (inv.amount_excl_vat || 0), 0));
412
415
  invoicesGrossMinor = toMinorUnits((payload.invoices || []).reduce((acc, inv) => acc + (inv.amount_incl_vat || 0), 0));
413
416
  }
414
417
  if (invoicesGrossMinor === 0 && invoicedLineNos.size > 0 && sale0?.sale_lines) {
@@ -420,8 +423,10 @@ function transformMasterOrder(payload) {
420
423
  const lineNo = Number(l?.line_no || -1);
421
424
  return invoicedLineNos.has(lineNo);
422
425
  });
426
+ invoicesNetMinor = toMinorUnits(invoicedSaleLines.reduce((acc, l) => acc + (Number(l?.amount || 0)), 0));
423
427
  invoicesGrossMinor = toMinorUnits(invoicedSaleLines.reduce((acc, l) => acc + (Number(l?.amount_including_vat || 0)), 0));
424
428
  }
429
+ let nonInvSalesNetMinor = 0;
425
430
  let nonInvSalesGrossMinor = 0;
426
431
  if (sale0?.sale_lines && sale0.sale_lines.length > 0) {
427
432
  const allSaleLines = (sale0.sale_lines || []).filter((l) => {
@@ -431,20 +436,26 @@ function transformMasterOrder(payload) {
431
436
  return false;
432
437
  return true;
433
438
  });
439
+ const totalSaleNetMinor = toMinorUnits(allSaleLines.reduce((acc, l) => acc + (Number(l?.amount || 0)), 0));
434
440
  const totalSaleGrossMinor = toMinorUnits(allSaleLines.reduce((acc, l) => acc + (Number(l?.amount_including_vat || 0)), 0));
435
441
  if (invoicesGrossMinor > 0) {
442
+ nonInvSalesNetMinor = Math.max(0, totalSaleNetMinor - invoicesNetMinor);
436
443
  nonInvSalesGrossMinor = Math.max(0, totalSaleGrossMinor - invoicesGrossMinor);
437
444
  }
438
445
  else {
446
+ nonInvSalesNetMinor = totalSaleNetMinor;
439
447
  nonInvSalesGrossMinor = totalSaleGrossMinor;
440
448
  }
441
449
  }
442
450
  else if (sale0 && (sale0.amount_excl_vat || sale0.amount_incl_vat)) {
451
+ const saleNetMinor = toMinorUnits(sale0.amount_excl_vat || 0);
443
452
  const saleGrossMinor = toMinorUnits(sale0.amount_incl_vat || 0);
444
453
  if (invoicesGrossMinor > 0) {
454
+ nonInvSalesNetMinor = Math.max(0, saleNetMinor - invoicesNetMinor);
445
455
  nonInvSalesGrossMinor = Math.max(0, saleGrossMinor - invoicesGrossMinor);
446
456
  }
447
457
  else {
458
+ nonInvSalesNetMinor = saleNetMinor;
448
459
  nonInvSalesGrossMinor = saleGrossMinor;
449
460
  }
450
461
  }
@@ -457,57 +468,32 @@ function transformMasterOrder(payload) {
457
468
  const lineNo = Number(l?.line_no || -1);
458
469
  return !invoicedLineNos.has(lineNo);
459
470
  });
471
+ nonInvSalesNetMinor = toMinorUnits(nonInvoicedSales.reduce((acc, l) => acc + (Number(l?.amount || 0)), 0));
460
472
  nonInvSalesGrossMinor = toMinorUnits(nonInvoicedSales.reduce((acc, l) => acc + (Number(l?.amount_including_vat || 0)), 0));
461
473
  }
474
+ const returnsNetMinor = toMinorUnits((payload.credit_memos || []).reduce((acc, cm) => acc + (cm.amount_excl_vat || 0), 0));
475
+ const returnsGrossMinor = toMinorUnits((payload.credit_memos || []).reduce((acc, cm) => acc + (cm.amount_incl_vat || 0), 0));
476
+ const totalNetMinor = invoicesNetMinor + nonInvSalesNetMinor;
462
477
  const totalGrossMinor = invoicesGrossMinor + nonInvSalesGrossMinor;
463
- const invoicesNetMinorWithDelivery = toMinorUnits((payload.invoices || []).reduce((acc, inv) => acc + (inv.amount_excl_vat || 0), 0));
464
- const invoicesGrossMinorWithDelivery = toMinorUnits((payload.invoices || []).reduce((acc, inv) => acc + (inv.amount_incl_vat || 0), 0));
465
- let nonInvSalesNetMinorWithDelivery = 0;
466
- let nonInvSalesGrossMinorWithDelivery = 0;
467
- if (sale0?.sale_lines && sale0.sale_lines.length > 0) {
468
- const allSaleLinesWithDelivery = (sale0.sale_lines || []).filter((l) => {
469
- if (String(l?.type || '').toLowerCase() !== 'item')
470
- return false;
471
- return true;
472
- });
473
- const totalSaleNetMinorWithDelivery = toMinorUnits(allSaleLinesWithDelivery.reduce((acc, l) => acc + (Number(l?.amount || 0)), 0));
474
- const totalSaleGrossMinorWithDelivery = toMinorUnits(allSaleLinesWithDelivery.reduce((acc, l) => acc + (Number(l?.amount_including_vat || 0)), 0));
475
- if (invoicesGrossMinorWithDelivery > 0) {
476
- nonInvSalesNetMinorWithDelivery = Math.max(0, totalSaleNetMinorWithDelivery - invoicesNetMinorWithDelivery);
477
- nonInvSalesGrossMinorWithDelivery = Math.max(0, totalSaleGrossMinorWithDelivery - invoicesGrossMinorWithDelivery);
478
- }
479
- else {
480
- nonInvSalesNetMinorWithDelivery = totalSaleNetMinorWithDelivery;
481
- nonInvSalesGrossMinorWithDelivery = totalSaleGrossMinorWithDelivery;
482
- }
483
- }
484
- else if (sale0 && (sale0.amount_excl_vat || sale0.amount_incl_vat)) {
485
- const saleNetMinor = toMinorUnits(sale0.amount_excl_vat || 0);
486
- const saleGrossMinor = toMinorUnits(sale0.amount_incl_vat || 0);
487
- if (invoicesGrossMinorWithDelivery > 0) {
488
- nonInvSalesNetMinorWithDelivery = Math.max(0, saleNetMinor - invoicesNetMinorWithDelivery);
489
- nonInvSalesGrossMinorWithDelivery = Math.max(0, saleGrossMinor - invoicesGrossMinorWithDelivery);
490
- }
491
- else {
492
- nonInvSalesNetMinorWithDelivery = saleNetMinor;
493
- nonInvSalesGrossMinorWithDelivery = saleGrossMinor;
494
- }
495
- }
496
- else {
497
- const nonInvoicedSalesWithDelivery = (sale0?.sale_lines || []).filter((l) => {
498
- if (String(l?.type || '').toLowerCase() !== 'item')
499
- return false;
500
- const lineNo = Number(l?.line_no || -1);
501
- return !invoicedLineNos.has(lineNo);
502
- });
503
- nonInvSalesNetMinorWithDelivery = toMinorUnits(nonInvoicedSalesWithDelivery.reduce((acc, l) => acc + (Number(l?.amount || 0)), 0));
504
- nonInvSalesGrossMinorWithDelivery = toMinorUnits(nonInvoicedSalesWithDelivery.reduce((acc, l) => acc + (Number(l?.amount_including_vat || 0)), 0));
505
- }
506
- const totalNetMinorWithDelivery = invoicesNetMinorWithDelivery + nonInvSalesNetMinorWithDelivery;
507
- const totalGrossMinorWithDelivery = invoicesGrossMinorWithDelivery + nonInvSalesGrossMinorWithDelivery;
508
478
  const total = {
509
- amount_net: moneyFromMinor(totalNetMinorWithDelivery),
510
- amount_gross: moneyFromMinor(totalGrossMinorWithDelivery),
479
+ amount_net: moneyFromMinor(totalNetMinor),
480
+ amount_gross: moneyFromMinor(totalGrossMinor),
481
+ discount_amount_net: money(0),
482
+ discount_amount_gross: money(0),
483
+ discount_amount_percent: 0,
484
+ };
485
+ const return_total = (payload.credit_memos && payload.credit_memos.length > 0)
486
+ ? {
487
+ amount_net: moneyFromMinor(returnsNetMinor),
488
+ amount_gross: moneyFromMinor(returnsGrossMinor),
489
+ discount_amount_net: money(0),
490
+ discount_amount_gross: money(0),
491
+ discount_amount_percent: 0,
492
+ }
493
+ : undefined;
494
+ const net_total = {
495
+ amount_net: moneyFromMinor(Math.max(0, totalNetMinor - returnsNetMinor)),
496
+ amount_gross: moneyFromMinor(Math.max(0, totalGrossMinor - returnsGrossMinor)),
511
497
  discount_amount_net: money(0),
512
498
  discount_amount_gross: money(0),
513
499
  discount_amount_percent: 0,
@@ -617,7 +603,7 @@ function transformMasterOrder(payload) {
617
603
  const merchReturnGrossFromCreditMemos = (payload.credit_memos || []).reduce((acc, cm) => {
618
604
  const lines = cm?.credit_memo_lines || [];
619
605
  let hasQualifying = false;
620
- const sumGross = lines.reduce((s, l) => {
606
+ const sum = lines.reduce((s, l) => {
621
607
  const itemNo = String(l?.item_no || l?.no || "");
622
608
  const desc = String(l?.description || "");
623
609
  const looksProduct = itemNo.includes('-') || desc.includes(' - ');
@@ -630,36 +616,24 @@ function transformMasterOrder(payload) {
630
616
  }
631
617
  return s;
632
618
  }, 0);
633
- const sumNet = lines.reduce((s, l) => {
634
- const itemNo = String(l?.item_no || l?.no || "");
635
- const desc = String(l?.description || "");
636
- const looksProduct = itemNo.includes('-') || desc.includes(' - ');
637
- const typeStr = String(l?.type || "").toLowerCase();
638
- const isItemish = typeStr.includes('item') || looksProduct;
639
- const isRefund = itemNo.toUpperCase() === 'REFUND';
640
- if ((l?.quantity || 0) > 0 && isItemish && !isRefund && !isDeliveryItem(l)) {
641
- return s + (l?.amount || l?.amount_excl_vat || 0);
642
- }
643
- return s;
644
- }, 0);
645
619
  if (!hasQualifying)
646
620
  return acc;
647
621
  if (cm?.amount_incl_vat != null && cm.amount_incl_vat > 0) {
648
- return { gross: acc.gross + cm.amount_incl_vat, net: acc.net + (cm.amount_excl_vat || 0) };
622
+ return acc + cm.amount_incl_vat;
649
623
  }
650
- return { gross: acc.gross + sumGross, net: acc.net + sumNet };
651
- }, { gross: 0, net: 0 });
624
+ return acc + sum;
625
+ }, 0);
652
626
  const merchReturnGrossFromReturnSales = (payload.sales || []).reduce((acc, sale) => {
653
627
  const docTypeRaw = String(sale?.document_type || sale?.documentType || "");
654
628
  const isReturnOrder = docTypeRaw.toLowerCase().includes('return');
655
629
  if (!isReturnOrder)
656
630
  return acc;
657
631
  if (sale?.amount_incl_vat != null && sale.amount_incl_vat > 0) {
658
- return { gross: acc.gross + sale.amount_incl_vat, net: acc.net + (sale.amount_excl_vat || sale.amount || 0) };
632
+ return acc + sale.amount_incl_vat;
659
633
  }
660
634
  const lines = sale?.sale_lines || [];
661
635
  let hasQualifying = false;
662
- const sumGross = lines.reduce((s, l) => {
636
+ const sum = lines.reduce((s, l) => {
663
637
  const itemNo = String(l?.item_no || l?.no || "");
664
638
  const desc = String(l?.description || "");
665
639
  const looksProduct = itemNo.includes('-') || desc.includes(' - ');
@@ -672,46 +646,10 @@ function transformMasterOrder(payload) {
672
646
  }
673
647
  return s;
674
648
  }, 0);
675
- const sumNet = lines.reduce((s, l) => {
676
- const itemNo = String(l?.item_no || l?.no || "");
677
- const desc = String(l?.description || "");
678
- const looksProduct = itemNo.includes('-') || desc.includes(' - ');
679
- const typeStr = String(l?.type || "").toLowerCase();
680
- const isItemish = typeStr.includes('item') || looksProduct;
681
- const isRefund = itemNo.toUpperCase() === 'REFUND';
682
- if ((l?.quantity || 0) > 0 && isItemish && !isRefund && !isDeliveryItem(l)) {
683
- return s + (l?.amount || l?.amount_excl_vat || 0);
684
- }
685
- return s;
686
- }, 0);
687
- if (sumGross > 0) {
688
- return { gross: acc.gross + sumGross, net: acc.net + sumNet };
689
- }
690
- if (hasQualifying) {
691
- return { gross: acc.gross + (sale?.amount_incl_vat || sale?.amount_including_vat || 0), net: acc.net + (sale?.amount_excl_vat || sale?.amount || 0) };
692
- }
693
- return acc;
694
- }, { gross: 0, net: 0 });
695
- const merchReturnGross = merchReturnGrossFromCreditMemos.gross + merchReturnGrossFromReturnSales.gross;
649
+ return acc + (sum > 0 ? sum : (hasQualifying ? (sale?.amount_incl_vat || sale?.amount_including_vat || 0) : 0));
650
+ }, 0);
651
+ const merchReturnGross = merchReturnGrossFromCreditMemos + merchReturnGrossFromReturnSales;
696
652
  const hasMerchReturn = merchReturnGross > 0;
697
- const returnsNetMinor = toMinorUnits((payload.credit_memos || []).reduce((acc, cm) => acc + (cm.amount_excl_vat || 0), 0));
698
- const returnsGrossMinor = toMinorUnits((payload.credit_memos || []).reduce((acc, cm) => acc + (cm.amount_incl_vat || 0), 0));
699
- const return_total = (returnsGrossMinor > 0)
700
- ? {
701
- amount_net: moneyFromMinor(returnsNetMinor),
702
- amount_gross: moneyFromMinor(returnsGrossMinor),
703
- discount_amount_net: money(0),
704
- discount_amount_gross: money(0),
705
- discount_amount_percent: 0,
706
- }
707
- : undefined;
708
- const net_total = {
709
- amount_net: moneyFromMinor(Math.max(0, totalNetMinorWithDelivery - returnsNetMinor)),
710
- amount_gross: moneyFromMinor(Math.max(0, totalGrossMinorWithDelivery - returnsGrossMinor)),
711
- discount_amount_net: money(0),
712
- discount_amount_gross: money(0),
713
- discount_amount_percent: 0,
714
- };
715
653
  const totalOrderGrossMinor = totalGrossMinor;
716
654
  const sourceLineMap = new Map();
717
655
  sourceLines.forEach((line) => {
@@ -73,6 +73,14 @@ class CentraHelper extends env_1.default {
73
73
  errors.push(...userErrors.map((x) => ({ message: x.message, path: x.path })));
74
74
  return { errors, limitExceeded: response.status === 429 };
75
75
  }
76
+ if (resp.data && typeof resp.data === 'object') {
77
+ for (const key in resp.data) {
78
+ if (resp.data[key] && typeof resp.data[key] === 'object' && 'userErrors' in resp.data[key] && Array.isArray(resp.data[key].userErrors) && resp.data[key].userErrors.length > 0) {
79
+ errors.push(...resp.data[key].userErrors.map((x) => ({ message: x.message, path: x.path })));
80
+ return { errors, limitExceeded: response.status === 429 };
81
+ }
82
+ }
83
+ }
76
84
  }
77
85
  if (response.status === 429) {
78
86
  resp.limitExceeded = true;
@@ -323,7 +331,7 @@ class CentraHelper extends env_1.default {
323
331
  } while (hasNextPage);
324
332
  return result;
325
333
  }
326
- async fetchCentraMarkets(externalIds) {
334
+ async fetchCentraMarkets() {
327
335
  const result = [];
328
336
  const limit = 200;
329
337
  let page = 0;
@@ -333,15 +341,15 @@ class CentraHelper extends env_1.default {
333
341
  method: 'POST',
334
342
  body: JSON.stringify({
335
343
  query: `
336
- query GetMarkets($limit: Int, $page: Int${externalIds ? `, $externalIds: MarketsFilter` : ''}) {
337
- markets(${externalIds ? `where: $externalIds, ` : ''}limit: $limit, page: $page) {
344
+ query GetMarkets($limit: Int, $page: Int) {
345
+ markets(limit: $limit, page: $page) {
338
346
  id
339
347
  externalId
340
348
  name
341
349
  }
342
350
  }
343
351
  `,
344
- variables: { page: page, limit: limit, externalIds: { externalId: externalIds } }
352
+ variables: { page: page, limit: limit }
345
353
  })
346
354
  });
347
355
  if (CentraHelper.isCentraErrors(data)) {
@@ -471,7 +479,7 @@ class CentraHelper extends env_1.default {
471
479
  nextCursor = null;
472
480
  }
473
481
  if (data && data.data?.productConnection?.edges?.length) {
474
- for (let i = data.data.productConnection.edges.length; i < styleIds.length; i++) {
482
+ for (let i = 0; i < data.data.productConnection.edges.length; i++) {
475
483
  const { node } = data.data.productConnection.edges[i];
476
484
  result[node.externalId] = node;
477
485
  }
@@ -497,16 +505,15 @@ class CentraHelper extends env_1.default {
497
505
  edges {
498
506
  node {
499
507
  id
500
- externalId
501
508
  status
502
- url
509
+ uri
503
510
  metaTitle
504
511
  metaDescription
505
512
  name
506
513
  categories {
507
514
  id
508
515
  name
509
- url
516
+ uri
510
517
  isTopCategory
511
518
  }
512
519
  markets {
@@ -518,7 +525,7 @@ class CentraHelper extends env_1.default {
518
525
  externalId
519
526
  status
520
527
  }
521
- productVariant {
528
+ productVariants {
522
529
  id
523
530
  externalId
524
531
  status
@@ -565,8 +572,8 @@ class CentraHelper extends env_1.default {
565
572
  for (let i = 0; i < edges.length; i++) {
566
573
  const { node } = edges[i];
567
574
  if (inputIdType === 'variant') {
568
- result[+node.productVariant[0].id] = [];
569
- result[+node.productVariant[0].id].push(node);
575
+ result[+node.productVariants[0].id] = [];
576
+ result[+node.productVariants[0].id].push(node);
570
577
  }
571
578
  else {
572
579
  result[+node.product.id] = [];
@@ -790,59 +797,45 @@ class CentraHelper extends env_1.default {
790
797
  }
791
798
  return Object.assign({}, pricelistInCache, pricelistToSet);
792
799
  }
793
- async getCentraMarkets(externalIds, alwaysFetch = false) {
794
- if (externalIds && !externalIds.length) {
795
- return {};
796
- }
800
+ async getCentraMarkets(alwaysFetch = false) {
797
801
  let marketInCache = {};
798
- let dedupedExternalIds = [];
802
+ let dedupedMarketNamesInCache = [];
799
803
  let marketsToFetch = null;
800
804
  if (!alwaysFetch) {
801
- if (!externalIds) {
802
- externalIds = (await this.get(this.getCacheKeyForMarkets(), 'env', {
803
- isEphemeral: true,
804
- encrypted: false,
805
- }).then(x => x ? JSON.parse(x) : null));
806
- }
807
- if (externalIds) {
808
- dedupedExternalIds = externalIds.filter((x, index, self) => self.indexOf(x) === index);
809
- marketInCache = Object.fromEntries(Object.entries(await this.get(dedupedExternalIds.map(x => this.getCacheKeyForMarket(x)), 'env', {
805
+ const marketNamesInCache = (await this.get(this.getCacheKeyForMarkets(), 'env', {
806
+ isEphemeral: true,
807
+ encrypted: false,
808
+ }).then(x => x ? JSON.parse(x) : null));
809
+ if (marketNamesInCache) {
810
+ dedupedMarketNamesInCache = marketNamesInCache.filter((x, index, self) => self.indexOf(x) === index);
811
+ marketInCache = Object.fromEntries(Object.entries(await this.get(dedupedMarketNamesInCache.map(x => this.getCacheKeyForMarket(x)), 'env', {
810
812
  isEphemeral: true,
811
813
  encrypted: false,
812
814
  })).map(([key, value]) => [key, value ? JSON.parse(value || 'null') : undefined]).filter(([_, value]) => value));
813
- marketsToFetch = dedupedExternalIds.filter(x => !marketInCache[x]);
815
+ marketsToFetch = dedupedMarketNamesInCache.filter(x => !marketInCache[x]);
814
816
  }
815
817
  }
816
818
  const marketToSet = {};
817
819
  if (!marketsToFetch || marketsToFetch.length) {
818
- const markets = await this.fetchCentraMarkets(marketsToFetch);
820
+ const markets = await this.fetchCentraMarkets();
819
821
  if (CentraHelper.isCentraErrors(markets)) {
820
- if (marketsToFetch) {
821
- for (const marketExternalId of marketsToFetch) {
822
- marketToSet[marketExternalId] = new Error(`Failed to fetch market ${marketExternalId}: ${markets.errors.map((x) => x.message).join(', ')}`);
823
- }
824
- }
825
- else {
826
- return new Error(`Failed to fetch markets: ${markets.errors.map((x) => x.message).join(', ')}`);
827
- }
822
+ return new Error(`Failed to fetch markets: ${markets.errors.map((x) => x.message).join(', ')}`);
828
823
  }
829
824
  else {
830
825
  for (const market of markets) {
831
- marketToSet[market.externalId] = market;
826
+ marketToSet[market.name] = market;
832
827
  }
833
828
  await this.set(Object.entries(marketToSet).filter(([_, value]) => !(value instanceof Error)).map(([key, value]) => ({ name: this.getCacheKeyForMarket(key), value: JSON.stringify(value) })), 'env', {
834
829
  ephemeralMs: CACHE_EXPIRATION_MS,
835
830
  encrypted: false,
836
831
  });
837
- if (!marketsToFetch) {
838
- await this.set([{
839
- name: this.getCacheKeyForMarkets(),
840
- value: JSON.stringify(Object.keys(marketToSet)),
841
- }], 'env', {
842
- ephemeralMs: CACHE_EXPIRATION_MS,
843
- encrypted: false,
844
- });
845
- }
832
+ await this.set([{
833
+ name: this.getCacheKeyForMarkets(),
834
+ value: JSON.stringify(Object.keys(marketToSet)),
835
+ }], 'env', {
836
+ ephemeralMs: CACHE_EXPIRATION_MS,
837
+ encrypted: false,
838
+ });
846
839
  }
847
840
  }
848
841
  return Object.assign({}, marketInCache, marketToSet);
@@ -72,11 +72,6 @@ class DatoHelper extends runtime_1.default {
72
72
  return response;
73
73
  }
74
74
  async createAssetFromUpload(uploadId, opts, uploadCollectionId) {
75
- const normalizedTags = Array.isArray(opts.tags) && opts.tags.length > 0
76
- ? opts.tags
77
- : ["integrations"];
78
- const safeTitle = opts.title && opts.title.trim().length > 0 ? opts.title : "untitled";
79
- const safeAlt = opts.alt && opts.alt.trim().length > 0 ? opts.alt : safeTitle;
80
75
  const body = {
81
76
  data: {
82
77
  type: 'upload',
@@ -85,35 +80,13 @@ class DatoHelper extends runtime_1.default {
85
80
  author: opts.author,
86
81
  copyright: opts.copyright,
87
82
  notes: opts.notes || null,
88
- title: safeTitle,
89
- alt: safeAlt,
90
83
  default_field_metadata: {
91
- "en-GB": {
92
- title: safeTitle,
93
- alt: safeAlt,
94
- custom_data: {
95
- source: normalizedTags[0] || "uploaded-by-integrations",
96
- tags: normalizedTags,
97
- }
98
- },
99
84
  en: {
100
- title: safeTitle,
101
- alt: safeAlt,
102
- custom_data: {
103
- source: normalizedTags[0] || "uploaded-by-integrations",
104
- tags: normalizedTags,
105
- }
106
- },
107
- "en-US": {
108
- title: safeTitle,
109
- alt: safeAlt,
110
- custom_data: {
111
- source: normalizedTags[0] || "uploaded-by-integrations",
112
- tags: normalizedTags,
113
- }
85
+ title: opts.title,
86
+ alt: opts.alt,
114
87
  }
115
88
  },
116
- tags: normalizedTags || [],
89
+ tags: opts.tags || [],
117
90
  },
118
91
  },
119
92
  };
@@ -182,119 +155,8 @@ class DatoHelper extends runtime_1.default {
182
155
  this.logging.error(timeoutMsg);
183
156
  throw new Error(timeoutMsg);
184
157
  }
185
- updateUpload = async (options) => {
186
- try {
187
- this.logging.log(`Starting update upload - ${JSON.stringify(options)}`);
188
- if (!options.id)
189
- throw new Error("updateUpload requires 'id' in options");
190
- const safeTitle = options.title && options.title.trim().length > 0
191
- ? options.title.trim()
192
- : options.filename?.trim().split(".")[0] || "untitled";
193
- const safeAlt = options.alt && options.alt.trim().length > 0
194
- ? options.alt.trim()
195
- : safeTitle;
196
- const normalizedTags = Array.isArray(options.tags) && options.tags.length > 0
197
- ? options.tags
198
- : ["integrations"];
199
- const body = {
200
- data: {
201
- type: "upload",
202
- id: options.id,
203
- attributes: {
204
- author: options.author,
205
- copyright: options.copyright,
206
- tags: normalizedTags,
207
- notes: options.notes || null,
208
- default_field_metadata: {
209
- "en-GB": {
210
- title: safeTitle,
211
- alt: safeAlt,
212
- custom_data: {
213
- source: normalizedTags[0] || "uploaded-by-integrations",
214
- tags: normalizedTags,
215
- },
216
- },
217
- en: {
218
- title: safeTitle,
219
- alt: safeAlt,
220
- custom_data: {
221
- source: normalizedTags[0] || "uploaded-by-integrations",
222
- tags: normalizedTags,
223
- },
224
- },
225
- "en-US": {
226
- title: safeTitle,
227
- alt: safeAlt,
228
- custom_data: {
229
- source: normalizedTags[0] || "uploaded-by-integrations",
230
- tags: normalizedTags,
231
- },
232
- },
233
- },
234
- },
235
- },
236
- };
237
- if (options.uploadCollectionId) {
238
- body.data.relationships = {
239
- upload_collection: {
240
- data: {
241
- type: "upload_collection",
242
- id: options.uploadCollectionId,
243
- },
244
- },
245
- };
246
- }
247
- const response = await fetch(`${this.baseUrl}/uploads/${options.id}`, {
248
- method: "PATCH",
249
- headers: {
250
- Authorization: `Bearer ${this.apiToken}`,
251
- "X-Api-Version": "3",
252
- "X-Environment": this.environment,
253
- "Content-Type": "application/vnd.api+json",
254
- Accept: "application/json",
255
- },
256
- body: JSON.stringify(body),
257
- });
258
- if (!response.ok) {
259
- const errorText = await response.text();
260
- this.logging.error(`Failed to update upload: ${response.statusText} - ${errorText}`);
261
- throw new Error(`Failed to update upload: ${response.statusText} - ${errorText}`);
262
- }
263
- const updated = await response.json();
264
- if (Array.isArray(updated.data) && updated.data[0]?.type === "api_error") {
265
- const errorCode = updated.data[0].attributes?.code || "UNKNOWN_ERROR";
266
- const errorMsg = `DatoCMS API error: ${errorCode}`;
267
- this.logging.error(errorMsg);
268
- throw new Error(errorMsg);
269
- }
270
- if (updated.data?.type === "job") {
271
- this.logging.log(`Waiting for job completion: ${updated.data.id}`);
272
- const jobPayload = await this.waitForJobCompletion(updated.data.id);
273
- const assetId = jobPayload?.data?.id;
274
- const assetUrl = jobPayload?.data?.attributes?.url;
275
- if (!assetId || !assetUrl) {
276
- throw new Error(`Job completed but payload missing expected fields: ${JSON.stringify(jobPayload)}`);
277
- }
278
- this.logging.log(`Job completed for upload: ${assetId}`);
279
- return { success: true, assetId, upload: assetUrl };
280
- }
281
- if (updated.data?.type === "upload") {
282
- const { id, attributes } = updated.data;
283
- this.logging.log(`Upload updated successfully: ${id}`);
284
- return { success: true, assetId: id, upload: attributes.url };
285
- }
286
- const errMsg = `Unexpected update response: ${JSON.stringify(updated)}`;
287
- this.logging.error(errMsg);
288
- throw new Error(errMsg);
289
- }
290
- catch (err) {
291
- const msg = err?.message ?? JSON.stringify(err);
292
- this.logging.error(`[updateUpload] failed: ${msg}`);
293
- throw new Error(msg);
294
- }
295
- };
296
158
  async uploadFromUrl(options) {
297
- const { url, filename, uploadCollectionId, skipCreationIfAlreadyExists } = options;
159
+ const { url, filename, uploadCollectionId } = options;
298
160
  this.logging.log(`Starting upload from URL: ${url}`);
299
161
  const fileResponse = await fetch(url);
300
162
  if (!fileResponse.ok) {
@@ -302,43 +164,9 @@ class DatoHelper extends runtime_1.default {
302
164
  this.logging.error(errorMsg);
303
165
  throw new Error(errorMsg);
304
166
  }
305
- let finalFilename = filename || path_1.default.basename(new URL(url).pathname) ||
306
- `bs-${this.triggerId}-${Date.now()}`;
307
- if (filename && !path_1.default.extname(finalFilename)) {
308
- const urlObj = new URL(url);
309
- const pathname = urlObj.pathname;
310
- if (pathname.includes(".")) {
311
- const urlExtension = path_1.default.extname(pathname);
312
- if (urlExtension) {
313
- finalFilename += urlExtension;
314
- }
315
- }
316
- if (!path_1.default.extname(finalFilename) && fileResponse.headers.has('content-type')) {
317
- const ext = (0, mime_types_1.extension)(fileResponse.headers.get('content-type'));
318
- if (typeof ext === "string" && ext.length > 0) {
319
- finalFilename += `.${ext}`;
320
- }
321
- }
322
- }
323
- else if (!filename) {
324
- if (!path_1.default.extname(finalFilename) && fileResponse.headers.has('content-type')) {
325
- const ext = (0, mime_types_1.extension)(fileResponse.headers.get('content-type'));
326
- if (typeof ext === "string" && ext.length > 0) {
327
- finalFilename += `.${ext}`;
328
- }
329
- }
330
- }
331
- if (skipCreationIfAlreadyExists) {
332
- const existing = await this.findUploadByFilename(finalFilename);
333
- if (existing) {
334
- this.logging.log(`Skipping upload: existing asset found for "${finalFilename}"`);
335
- return {
336
- success: true,
337
- assetId: existing.id,
338
- upload: existing.attributes.url,
339
- skipped: true,
340
- };
341
- }
167
+ let finalFilename = filename || path_1.default.basename(new URL(url).pathname) || 'bs-' + this.triggerId + '-' + new Date().getTime();
168
+ if (!path_1.default.extname(finalFilename) && fileResponse.headers.has('content-type')) {
169
+ finalFilename += `.${(0, mime_types_1.extension)(fileResponse.headers.get('content-type'))}`;
342
170
  }
343
171
  const uploadRequest = await this.requestUploadParameters(finalFilename);
344
172
  const uploadId = uploadRequest.data.id;
@@ -355,7 +183,6 @@ class DatoHelper extends runtime_1.default {
355
183
  success: true,
356
184
  upload: assetPayload.data.attributes.url,
357
185
  assetId: assetPayload.data.id,
358
- skipped: false,
359
186
  };
360
187
  }
361
188
  async uploadFromFile(options) {
@@ -437,14 +264,7 @@ class DatoHelper extends runtime_1.default {
437
264
  }
438
265
  async findUploadByFilename(filename) {
439
266
  try {
440
- if (!filename || typeof filename !== 'string') {
441
- this.logging.log(`findUploadByFilename called with invalid filename: ${String(filename)}`);
442
- return null;
443
- }
444
- const normalizedTarget = filename.trim().toLowerCase().replace(/\.[^.]+$/, '');
445
- const query = encodeURIComponent(normalizedTarget);
446
- this.logging.log(`Searching uploads with query="${normalizedTarget}" for filename="${filename}"`);
447
- const response = await fetch(`${this.baseUrl}/uploads?filter[query]=${query}&page[limit]=100`, {
267
+ const response = await fetch(`${this.baseUrl}/uploads?filter[filename]=${encodeURIComponent(filename)}`, {
448
268
  headers: {
449
269
  'Authorization': `Bearer ${this.apiToken}`,
450
270
  'X-Api-Version': '3',
@@ -461,32 +281,10 @@ class DatoHelper extends runtime_1.default {
461
281
  this.logging.error(`Failed to parse JSON in findUploadByFilename: ${err.message}`);
462
282
  return null;
463
283
  });
464
- const uploads = data?.data || [];
465
- if (!uploads.length) {
466
- this.logging.log(`No uploads returned for query="${normalizedTarget}"`);
467
- return null;
468
- }
469
- const targetFull = filename.trim().toLowerCase();
470
- const match = uploads.find((u) => {
471
- const storedFull = u?.attributes?.filename?.toLowerCase();
472
- if (!storedFull)
473
- return false;
474
- if (storedFull === targetFull)
475
- return true;
476
- const storedNormalized = storedFull.replace(/\.[^.]+$/, '');
477
- if (storedNormalized === normalizedTarget)
478
- return true;
479
- return false;
480
- });
481
- if (match) {
482
- this.logging.log(`Found existing upload for "${filename}" -> asset_id=${match.id}, storedFilename=${match.attributes?.filename}`);
483
- return match;
484
- }
485
- this.logging.log(`No exact filename match found among ${uploads.length} uploads for "${filename}" (query="${normalizedTarget}")`);
486
- return null;
284
+ return data?.data?.[0] || null;
487
285
  }
488
286
  catch (err) {
489
- this.logging.error(`Error in findUploadByFilename: ${err?.message ?? String(err)}`);
287
+ this.logging.error(`Error in findUploadByFilename: ${err.message}`);
490
288
  return null;
491
289
  }
492
290
  }
@@ -601,37 +399,5 @@ class DatoHelper extends runtime_1.default {
601
399
  throw err;
602
400
  }
603
401
  }
604
- async updateProductSwatch(productId, uploadId) {
605
- try {
606
- const res = await fetch(`${this.baseUrl}/items/${productId}`, {
607
- method: "PATCH",
608
- headers: {
609
- Authorization: `Bearer ${this.apiToken}`,
610
- "X-Api-Version": "3",
611
- "X-Environment": this.environment,
612
- "Content-Type": "application/vnd.api+json",
613
- Accept: "application/json",
614
- },
615
- body: JSON.stringify({
616
- data: {
617
- type: "item",
618
- id: productId,
619
- attributes: {
620
- swatch: { upload_id: uploadId },
621
- },
622
- },
623
- }),
624
- });
625
- if (!res.ok) {
626
- const errorText = await res.text();
627
- throw new Error(`Failed to update product swatch: ${res.status} ${res.statusText} - ${errorText}`);
628
- }
629
- return await res.json();
630
- }
631
- catch (err) {
632
- this.logging.error(`updateProductSwatch failed: ${err.message}`);
633
- throw err;
634
- }
635
- }
636
402
  }
637
403
  exports.default = DatoHelper;
@@ -44,6 +44,7 @@ declare class AirtableHelper<T extends Record<string, string>, K extends keyof T
44
44
  }>, options?: {
45
45
  fieldsToMergeOn?: Array<keyof T>;
46
46
  primaryKeyWritable?: boolean;
47
+ typecast?: boolean;
47
48
  }, callIdx?: number, collectedResult?: {
48
49
  updatedRecords: Array<string>;
49
50
  createdRecords: Array<string>;
@@ -183,6 +184,11 @@ declare class AirtableHelper<T extends Record<string, string>, K extends keyof T
183
184
  expirationTime: string;
184
185
  }>;
185
186
  replaceAirtableFields(formula: string): ReplaceFormulaResult;
187
+ static diff<T1 extends Record<string, any>, T2 extends {
188
+ fields: any;
189
+ }>(x: T1, y: T2 | null | undefined): {
190
+ [k: string]: any;
191
+ };
186
192
  }
187
193
  export default AirtableHelper;
188
194
  type AirtableWebhook = {
@@ -19,19 +19,18 @@ export type BasicCentraProduct = {
19
19
  export type BasicCentraDisplay = {
20
20
  id: number;
21
21
  name: string;
22
- externalId: string;
23
22
  status: string;
24
- url: string;
23
+ uri: string;
25
24
  metaTitle: string;
26
25
  metaDescription: string;
27
26
  categories: {
28
27
  id: number;
29
28
  name: string;
30
- url: string;
29
+ uri: string;
31
30
  isTopCategory: boolean;
32
31
  }[];
33
32
  markets: BasicCentraMarket[];
34
- productVariant: Array<{
33
+ productVariants: Array<{
35
34
  id: number;
36
35
  externalId: string;
37
36
  status: string;
@@ -77,7 +76,6 @@ export type BasicCentraCountry = {
77
76
  export type BasicCentraMarket = {
78
77
  id: number;
79
78
  name: string;
80
- externalId: string;
81
79
  };
82
80
  export type BasicCentraVariant = {
83
81
  id: number;
@@ -91,12 +89,12 @@ export type BasicCentraVariant = {
91
89
  id: string;
92
90
  };
93
91
  productSizes: {
94
- id: string;
92
+ id: number;
95
93
  EAN: string;
96
94
  SKU: string;
97
95
  sizeNumber: string;
98
96
  size: {
99
- id: string;
97
+ id: number;
100
98
  name: string;
101
99
  };
102
100
  }[];
@@ -158,14 +156,14 @@ export default class CentraHelper extends EnvEngine {
158
156
  fetchCentraWarehouses(externalIds?: string[] | undefined | null): Promise<CentraErrors | Array<BasicCentraWarehouse>>;
159
157
  fetchCentraCampaigns(names?: string[] | undefined | null): Promise<CentraErrors | Record<string, BasicCentraCampaign | Error>>;
160
158
  fetchPricelists(names?: string[] | undefined | null): Promise<CentraErrors | Array<BasicPricelist>>;
161
- fetchCentraMarkets(externalIds?: string[] | undefined | null): Promise<CentraErrors | Array<BasicCentraMarket>>;
159
+ fetchCentraMarkets(): Promise<CentraErrors | Array<BasicCentraMarket>>;
162
160
  fetchCentraSizeCharts(externalIds?: string[] | undefined | null): Promise<CentraErrors | Array<BasicCentraSizeChart>>;
163
161
  private fetchCentraProducts;
164
162
  private fetchCentraDisplays;
165
163
  private fetchCentraVariants;
166
164
  getCentraWarehouses(externalIds?: string[] | null | undefined, alwaysFetch?: boolean): Promise<Error | Record<string, Error | BasicCentraWarehouse>>;
167
165
  getCentraPricelists(names?: string[] | null | undefined, alwaysFetch?: boolean): Promise<Error | Record<string, Error | BasicPricelist>>;
168
- getCentraMarkets(externalIds?: string[] | null | undefined, alwaysFetch?: boolean): Promise<Error | Record<string, Error | BasicCentraMarket>>;
166
+ getCentraMarkets(alwaysFetch?: boolean): Promise<Error | Record<string, Error | BasicCentraMarket>>;
169
167
  getCentraCountries(iso2Codes?: string[] | null | undefined, alwaysFetch?: boolean): Promise<Error | Record<string, Error | BasicCentraCountry>>;
170
168
  getCentraSizeCharts(externalIds?: string[] | null | undefined, alwaysFetch?: boolean): Promise<Error | Record<string, Error | BasicCentraSizeChart>>;
171
169
  getCentraProducts(styleIds: string[]): Promise<Error | Record<string, Error | BasicCentraProduct>>;
@@ -8,16 +8,6 @@ interface UploadResult {
8
8
  success: boolean;
9
9
  upload: string;
10
10
  assetId?: string;
11
- skipped?: boolean;
12
- }
13
- interface UploadReturn {
14
- id: string;
15
- type: string;
16
- attributes: {
17
- url: string;
18
- filename: string;
19
- [key: string]: any;
20
- };
21
11
  }
22
12
  export default class DatoHelper extends Runtime {
23
13
  private apiToken;
@@ -29,21 +19,6 @@ export default class DatoHelper extends Runtime {
29
19
  private createAssetFromUpload;
30
20
  private checkJobResult;
31
21
  private waitForJobCompletion;
32
- updateUpload: (options: {
33
- id: string;
34
- filename?: string;
35
- tags: string[];
36
- notes?: string;
37
- title: string;
38
- alt: string;
39
- author: string;
40
- copyright: string;
41
- uploadCollectionId?: string;
42
- }) => Promise<{
43
- success: boolean;
44
- assetId: string;
45
- upload: string;
46
- }>;
47
22
  uploadFromUrl(options: {
48
23
  url: string;
49
24
  copyright: string;
@@ -78,11 +53,10 @@ export default class DatoHelper extends Runtime {
78
53
  quality?: number;
79
54
  format?: 'jpg' | 'pjpg' | 'png' | 'webp' | 'avif' | 'gif' | 'jxl' | 'jp2' | 'jxr' | 'json' | 'blurhash' | (string & {});
80
55
  }): string;
81
- findUploadByFilename(filename: string): Promise<UploadReturn | null>;
56
+ findUploadByFilename(filename: string): Promise<any>;
82
57
  getItemTypeId(apiKey: string): Promise<string>;
83
58
  getProductBySku(sku: string): Promise<any>;
84
59
  createProduct(sku: string, productTypeId: string): Promise<any>;
85
60
  updateProductGallery(productId: string, gallery: any[]): Promise<any>;
86
- updateProductSwatch(productId: string, uploadId: string): Promise<any>;
87
61
  }
88
62
  export {};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@shushed/helpers",
3
- "version": "0.0.205-fix-erp-729-20251209155111",
3
+ "version": "0.0.207",
4
4
  "author": "",
5
5
  "license": "UNLICENSED",
6
6
  "description": "",