@fluentcommerce/fc-connect-sdk 0.1.54 → 0.1.55
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +12 -0
- package/dist/cjs/clients/fluent-client.js +13 -6
- package/dist/cjs/utils/pagination-helpers.js +38 -2
- package/dist/cjs/versori/fluent-versori-client.js +11 -5
- package/dist/esm/clients/fluent-client.js +13 -6
- package/dist/esm/utils/pagination-helpers.js +38 -2
- package/dist/esm/versori/fluent-versori-client.js +11 -5
- package/dist/tsconfig.esm.tsbuildinfo +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/dist/tsconfig.types.tsbuildinfo +1 -1
- package/docs/00-START-HERE/EXPORT-VALIDATION.md +158 -158
- package/docs/00-START-HERE/cli-analyze-source-structure-guide.md +655 -655
- package/docs/00-START-HERE/cli-documentation-index.md +202 -202
- package/docs/00-START-HERE/cli-quick-reference.md +252 -252
- package/docs/00-START-HERE/decision-tree.md +552 -552
- package/docs/00-START-HERE/getting-started.md +1070 -1070
- package/docs/00-START-HERE/mapper-quick-decision-guide.md +235 -235
- package/docs/00-START-HERE/readme.md +237 -237
- package/docs/00-START-HERE/retailerid-configuration.md +404 -404
- package/docs/00-START-HERE/sdk-philosophy.md +794 -794
- package/docs/00-START-HERE/troubleshooting-quick-reference.md +1086 -1086
- package/docs/01-TEMPLATES/faq.md +686 -686
- package/docs/01-TEMPLATES/patterns/pattern-templates-guide.md +68 -68
- package/docs/01-TEMPLATES/patterns/patterns-csv-schema-validation-and-rejection-report.md +233 -233
- package/docs/01-TEMPLATES/patterns/patterns-custom-resolvers.md +407 -407
- package/docs/01-TEMPLATES/patterns/patterns-error-handling-retry.md +511 -511
- package/docs/01-TEMPLATES/patterns/patterns-field-mapping-universal.md +701 -701
- package/docs/01-TEMPLATES/patterns/patterns-large-file-splitting.md +1430 -1430
- package/docs/01-TEMPLATES/patterns/patterns-master-data-etl.md +2399 -2399
- package/docs/01-TEMPLATES/patterns/patterns-pagination-streaming.md +447 -447
- package/docs/01-TEMPLATES/patterns/patterns-state-duplicate-prevention.md +385 -385
- package/docs/01-TEMPLATES/readme.md +957 -957
- package/docs/01-TEMPLATES/standalone/standalone-asn-inbound-processing.md +1209 -1209
- package/docs/01-TEMPLATES/standalone/standalone-graphql-query-export.md +1140 -1140
- package/docs/01-TEMPLATES/standalone/standalone-graphql-to-parquet-partitioned-s3.md +432 -432
- package/docs/01-TEMPLATES/standalone/standalone-multi-channel-inventory-sync.md +1185 -1185
- package/docs/01-TEMPLATES/standalone/standalone-multi-source-aggregation.md +1462 -1462
- package/docs/01-TEMPLATES/standalone/standalone-s3-csv-batch-api.md +1390 -1390
- package/docs/01-TEMPLATES/standalone/standalone-s3-csv-inventory-to-batch.md +330 -330
- package/docs/01-TEMPLATES/standalone/standalone-scripts-guide.md +87 -87
- package/docs/01-TEMPLATES/standalone/standalone-sftp-xml-graphql.md +1444 -1444
- package/docs/01-TEMPLATES/standalone/standalone-webhook-payload-processing.md +688 -688
- package/docs/01-TEMPLATES/versori/business-examples/business-examples-dropship-order-routing.md +193 -193
- package/docs/01-TEMPLATES/versori/business-examples/business-examples-graphql-parquet-extraction.md +518 -518
- package/docs/01-TEMPLATES/versori/business-examples/business-examples-inter-location-transfers.md +2162 -2162
- package/docs/01-TEMPLATES/versori/business-examples/business-examples-pre-order-allocation.md +2226 -2226
- package/docs/01-TEMPLATES/versori/business-examples/business-scenarios-guide.md +87 -87
- package/docs/01-TEMPLATES/versori/patterns/versori-patterns-connection-validation-pattern.md +656 -656
- package/docs/01-TEMPLATES/versori/patterns/versori-patterns-dual-workflow-connector.md +835 -835
- package/docs/01-TEMPLATES/versori/patterns/versori-patterns-guide.md +108 -108
- package/docs/01-TEMPLATES/versori/patterns/versori-patterns-kv-state-management.md +1533 -1533
- package/docs/01-TEMPLATES/versori/patterns/versori-patterns-xml-response-patterns.md +1160 -1160
- package/docs/01-TEMPLATES/versori/versori-platform-guide.md +201 -201
- package/docs/01-TEMPLATES/versori/webhooks/template-webhook-asn-purchase-order.md +1906 -1906
- package/docs/01-TEMPLATES/versori/webhooks/template-webhook-dropship-routing.md +1074 -1074
- package/docs/01-TEMPLATES/versori/webhooks/template-webhook-flash-sale-reserve.md +1395 -1395
- package/docs/01-TEMPLATES/versori/webhooks/template-webhook-generic-xml-order.md +888 -888
- package/docs/01-TEMPLATES/versori/webhooks/template-webhook-payment-gateway-integration.md +2478 -2478
- package/docs/01-TEMPLATES/versori/webhooks/template-webhook-rma-returns-comprehensive.md +2240 -2240
- package/docs/01-TEMPLATES/versori/webhooks/template-webhook-xml-order-ingestion.md +2029 -2029
- package/docs/01-TEMPLATES/versori/webhooks/webhook-templates-guide.md +140 -140
- package/docs/01-TEMPLATES/versori/workflows/_examples/sample-data/inventory-mapping.json +20 -20
- package/docs/01-TEMPLATES/versori/workflows/_examples/sample-data/products_2025-01-22.csv +11 -11
- package/docs/01-TEMPLATES/versori/workflows/_examples/sample-data/sample-data-guide.md +34 -34
- package/docs/01-TEMPLATES/versori/workflows/_examples/workflow-examples-guide.md +36 -36
- package/docs/01-TEMPLATES/versori/workflows/extraction/extraction-modes-guide.md +1038 -1038
- package/docs/01-TEMPLATES/versori/workflows/extraction/extraction-workflows-guide.md +138 -138
- package/docs/01-TEMPLATES/versori/workflows/extraction/graphql-queries/graphql-extraction-guide.md +63 -63
- package/docs/01-TEMPLATES/versori/workflows/extraction/graphql-queries/template-extraction-fulfillments-to-sftp-csv.md +2062 -2062
- package/docs/01-TEMPLATES/versori/workflows/extraction/graphql-queries/template-extraction-fulfillments-to-sftp-xml.md +2294 -2294
- package/docs/01-TEMPLATES/versori/workflows/extraction/graphql-queries/template-extraction-inventory-positions-to-s3-csv.md +2461 -2461
- package/docs/01-TEMPLATES/versori/workflows/extraction/graphql-queries/template-extraction-inventory-positions-to-sftp-xml.md +2529 -2529
- package/docs/01-TEMPLATES/versori/workflows/extraction/graphql-queries/template-extraction-inventory-quantities-to-s3-csv.md +2464 -2464
- package/docs/01-TEMPLATES/versori/workflows/extraction/graphql-queries/template-extraction-inventory-quantities-to-s3-json.md +1959 -1959
- package/docs/01-TEMPLATES/versori/workflows/extraction/graphql-queries/template-extraction-orders-to-s3-csv.md +1953 -1953
- package/docs/01-TEMPLATES/versori/workflows/extraction/graphql-queries/template-extraction-orders-to-sftp-xml.md +2541 -2541
- package/docs/01-TEMPLATES/versori/workflows/extraction/graphql-queries/template-extraction-products-to-s3-json.md +2384 -2384
- package/docs/01-TEMPLATES/versori/workflows/extraction/graphql-queries/template-extraction-products-to-sftp-xml.md +2445 -2445
- package/docs/01-TEMPLATES/versori/workflows/extraction/graphql-queries/template-extraction-virtual-positions-to-s3-csv.md +2355 -2355
- package/docs/01-TEMPLATES/versori/workflows/extraction/graphql-queries/template-extraction-virtual-positions-to-s3-json.md +2042 -2042
- package/docs/01-TEMPLATES/versori/workflows/extraction/graphql-queries/template-extraction-virtual-positions-to-sftp-xml.md +2726 -2726
- package/docs/01-TEMPLATES/versori/workflows/ingestion/batch-api/batch-api-guide.md +206 -206
- package/docs/01-TEMPLATES/versori/workflows/ingestion/batch-api/template-ingestion-cycle-count-reconciliation.md +2030 -2030
- package/docs/01-TEMPLATES/versori/workflows/ingestion/batch-api/template-ingestion-multi-channel-inventory-sync.md +1882 -1882
- package/docs/01-TEMPLATES/versori/workflows/ingestion/batch-api/template-ingestion-s3-csv-inventory-batch.md +2827 -2827
- package/docs/01-TEMPLATES/versori/workflows/ingestion/batch-api/template-ingestion-s3-json-inventory-batch.md +1952 -1952
- package/docs/01-TEMPLATES/versori/workflows/ingestion/batch-api/template-ingestion-s3-xml-inventory-batch.md +3289 -3289
- package/docs/01-TEMPLATES/versori/workflows/ingestion/batch-api/template-ingestion-sftp-csv-inventory-batch.md +3064 -3064
- package/docs/01-TEMPLATES/versori/workflows/ingestion/batch-api/template-ingestion-sftp-json-inventory-batch.md +3238 -3238
- package/docs/01-TEMPLATES/versori/workflows/ingestion/batch-api/template-ingestion-sftp-xml-inventory-batch.md +2977 -2977
- package/docs/01-TEMPLATES/versori/workflows/ingestion/event-api/event-api-guide.md +321 -321
- package/docs/01-TEMPLATES/versori/workflows/ingestion/event-api/template-ingestion-payload-json-order-cancel-event.md +959 -959
- package/docs/01-TEMPLATES/versori/workflows/ingestion/event-api/template-ingestion-payload-xml-order-cancel-event.md +1170 -1170
- package/docs/01-TEMPLATES/versori/workflows/ingestion/event-api/template-ingestion-s3-csv-product-event.md +2312 -2312
- package/docs/01-TEMPLATES/versori/workflows/ingestion/event-api/template-ingestion-s3-json-product-event.md +2999 -2999
- package/docs/01-TEMPLATES/versori/workflows/ingestion/event-api/template-ingestion-s3-parquet-product-event.md +2836 -2836
- package/docs/01-TEMPLATES/versori/workflows/ingestion/event-api/template-ingestion-s3-xml-product-event.md +2395 -2395
- package/docs/01-TEMPLATES/versori/workflows/ingestion/event-api/template-ingestion-sftp-csv-product-event.md +2295 -2295
- package/docs/01-TEMPLATES/versori/workflows/ingestion/event-api/template-ingestion-sftp-json-product-event.md +2602 -2602
- package/docs/01-TEMPLATES/versori/workflows/ingestion/event-api/template-ingestion-sftp-parquet-product-event.md +2589 -2589
- package/docs/01-TEMPLATES/versori/workflows/ingestion/event-api/template-ingestion-sftp-xml-product-event.md +3578 -3578
- package/docs/01-TEMPLATES/versori/workflows/ingestion/graphql-mutations/graphql-mutations-guide.md +93 -93
- package/docs/01-TEMPLATES/versori/workflows/ingestion/graphql-mutations/template-ingestion-payload-json-order-update-graphql.md +1260 -1260
- package/docs/01-TEMPLATES/versori/workflows/ingestion/graphql-mutations/template-ingestion-payload-xml-order-update-graphql.md +1472 -1472
- package/docs/01-TEMPLATES/versori/workflows/ingestion/graphql-mutations/template-ingestion-s3-csv-control-graphql.md +2417 -2417
- package/docs/01-TEMPLATES/versori/workflows/ingestion/graphql-mutations/template-ingestion-s3-csv-location-graphql.md +2811 -2811
- package/docs/01-TEMPLATES/versori/workflows/ingestion/graphql-mutations/template-ingestion-s3-csv-price-graphql.md +2619 -2619
- package/docs/01-TEMPLATES/versori/workflows/ingestion/graphql-mutations/template-ingestion-s3-json-location-graphql.md +2807 -2807
- package/docs/01-TEMPLATES/versori/workflows/ingestion/graphql-mutations/template-ingestion-s3-xml-location-graphql.md +2373 -2373
- package/docs/01-TEMPLATES/versori/workflows/ingestion/graphql-mutations/template-ingestion-sftp-csv-control-graphql.md +2740 -2740
- package/docs/01-TEMPLATES/versori/workflows/ingestion/graphql-mutations/template-ingestion-sftp-csv-location-graphql.md +2760 -2760
- package/docs/01-TEMPLATES/versori/workflows/ingestion/graphql-mutations/template-ingestion-sftp-json-location-graphql.md +1710 -1710
- package/docs/01-TEMPLATES/versori/workflows/ingestion/ingestion-workflows-guide.md +136 -136
- package/docs/01-TEMPLATES/versori/workflows/rubix-webhooks/rubix-webhooks-guide.md +520 -520
- package/docs/01-TEMPLATES/versori/workflows/rubix-webhooks/template-webhook-rubix-fulfilment-to-sftp-xml-inline.md +1418 -1418
- package/docs/01-TEMPLATES/versori/workflows/rubix-webhooks/template-webhook-rubix-fulfilment-to-sftp-xml-universal-mapper.md +1785 -1785
- package/docs/01-TEMPLATES/versori/workflows/rubix-webhooks/template-webhook-rubix-order-attribute-update.md +824 -824
- package/docs/01-TEMPLATES/versori/workflows/workflows-overview-guide.md +646 -646
- package/docs/02-CORE-GUIDES/advanced-services/advanced-services-batch-archival.md +724 -724
- package/docs/02-CORE-GUIDES/advanced-services/advanced-services-job-tracker.md +627 -627
- package/docs/02-CORE-GUIDES/advanced-services/advanced-services-partial-batch-recovery.md +561 -561
- package/docs/02-CORE-GUIDES/advanced-services/advanced-services-quick-reference.md +367 -367
- package/docs/02-CORE-GUIDES/advanced-services/advanced-services-readme.md +407 -407
- package/docs/02-CORE-GUIDES/advanced-services/readme.md +49 -49
- package/docs/02-CORE-GUIDES/api-reference/api-reference-quick-reference.md +548 -548
- package/docs/02-CORE-GUIDES/api-reference/event-api-input-output-reference.md +702 -1171
- package/docs/02-CORE-GUIDES/api-reference/examples/client-initialization.ts +286 -286
- package/docs/02-CORE-GUIDES/api-reference/graphql-error-classification.md +337 -337
- package/docs/02-CORE-GUIDES/api-reference/modules/api-reference-01-client-api.md +399 -520
- package/docs/02-CORE-GUIDES/api-reference/modules/api-reference-03-authentication.md +199 -199
- package/docs/02-CORE-GUIDES/api-reference/modules/api-reference-04-graphql-mapping.md +925 -925
- package/docs/02-CORE-GUIDES/api-reference/modules/api-reference-05-services.md +1198 -1198
- package/docs/02-CORE-GUIDES/api-reference/modules/api-reference-06-data-sources.md +1083 -1083
- package/docs/02-CORE-GUIDES/api-reference/modules/api-reference-07-parsers.md +1097 -1097
- package/docs/02-CORE-GUIDES/api-reference/modules/api-reference-08-pagination.md +513 -513
- package/docs/02-CORE-GUIDES/api-reference/modules/api-reference-08-types.md +545 -597
- package/docs/02-CORE-GUIDES/api-reference/modules/api-reference-09-error-handling.md +527 -527
- package/docs/02-CORE-GUIDES/api-reference/modules/api-reference-09-webhook-validation.md +514 -514
- package/docs/02-CORE-GUIDES/api-reference/modules/api-reference-10-extraction.md +557 -557
- package/docs/02-CORE-GUIDES/api-reference/modules/api-reference-10-utilities.md +412 -412
- package/docs/02-CORE-GUIDES/api-reference/modules/api-reference-11-cli-tools.md +423 -423
- package/docs/02-CORE-GUIDES/api-reference/modules/api-reference-11-error-handling.md +716 -716
- package/docs/02-CORE-GUIDES/api-reference/modules/api-reference-12-analyze-source-structure.md +518 -518
- package/docs/02-CORE-GUIDES/api-reference/modules/api-reference-12-partial-responses.md +212 -212
- package/docs/02-CORE-GUIDES/api-reference/modules/api-reference-12-testing.md +300 -300
- package/docs/02-CORE-GUIDES/api-reference/modules/api-reference-13-resolver-builder.md +322 -322
- package/docs/02-CORE-GUIDES/api-reference/readme.md +279 -279
- package/docs/02-CORE-GUIDES/auto-pagination/auto-pagination-quick-reference.md +351 -351
- package/docs/02-CORE-GUIDES/auto-pagination/auto-pagination-readme.md +277 -277
- package/docs/02-CORE-GUIDES/auto-pagination/examples/auto-pagination-readme.md +178 -178
- package/docs/02-CORE-GUIDES/auto-pagination/examples/common-patterns.ts +351 -351
- package/docs/02-CORE-GUIDES/auto-pagination/examples/paginate-products.ts +384 -384
- package/docs/02-CORE-GUIDES/auto-pagination/examples/paginate-virtual-positions.ts +308 -308
- package/docs/02-CORE-GUIDES/auto-pagination/modules/auto-pagination-01-foundations.md +470 -470
- package/docs/02-CORE-GUIDES/auto-pagination/modules/auto-pagination-02-quick-start.md +713 -713
- package/docs/02-CORE-GUIDES/auto-pagination/modules/auto-pagination-03-configuration.md +754 -754
- package/docs/02-CORE-GUIDES/auto-pagination/modules/auto-pagination-04-advanced-patterns.md +732 -732
- package/docs/02-CORE-GUIDES/auto-pagination/modules/auto-pagination-05-sdk-integration.md +847 -847
- package/docs/02-CORE-GUIDES/auto-pagination/modules/auto-pagination-06-troubleshooting.md +359 -359
- package/docs/02-CORE-GUIDES/auto-pagination/modules/auto-pagination-07-api-reference.md +462 -462
- package/docs/02-CORE-GUIDES/auto-pagination/readme.md +54 -54
- package/docs/02-CORE-GUIDES/data-sources/data-sources-file-operations-error-handling.md +1487 -1487
- package/docs/02-CORE-GUIDES/data-sources/data-sources-quick-reference.md +836 -836
- package/docs/02-CORE-GUIDES/data-sources/data-sources-readme.md +276 -276
- package/docs/02-CORE-GUIDES/data-sources/data-sources-sftp-credential-access-security.md +553 -553
- package/docs/02-CORE-GUIDES/data-sources/examples/common-patterns.ts +409 -409
- package/docs/02-CORE-GUIDES/data-sources/examples/data-sources-readme.md +178 -178
- package/docs/02-CORE-GUIDES/data-sources/examples/s3-operations.ts +308 -308
- package/docs/02-CORE-GUIDES/data-sources/examples/sftp-operations.ts +371 -371
- package/docs/02-CORE-GUIDES/data-sources/modules/data-sources-01-foundations.md +735 -735
- package/docs/02-CORE-GUIDES/data-sources/modules/data-sources-02-s3-operations.md +1302 -1302
- package/docs/02-CORE-GUIDES/data-sources/modules/data-sources-03-sftp-operations.md +1379 -1379
- package/docs/02-CORE-GUIDES/data-sources/modules/data-sources-04-file-patterns.md +941 -941
- package/docs/02-CORE-GUIDES/data-sources/modules/data-sources-05-advanced-topics.md +813 -813
- package/docs/02-CORE-GUIDES/data-sources/modules/data-sources-06-integration-patterns.md +486 -486
- package/docs/02-CORE-GUIDES/data-sources/modules/data-sources-07-troubleshooting.md +387 -387
- package/docs/02-CORE-GUIDES/data-sources/modules/data-sources-08-api-reference.md +417 -417
- package/docs/02-CORE-GUIDES/data-sources/readme.md +77 -77
- package/docs/02-CORE-GUIDES/error-handling-guide.md +936 -936
- package/docs/02-CORE-GUIDES/extraction/examples/02-core-guides-extraction-readme.md +116 -116
- package/docs/02-CORE-GUIDES/extraction/examples/common-patterns.ts +428 -428
- package/docs/02-CORE-GUIDES/extraction/examples/extract-inventory-basic.ts +187 -187
- package/docs/02-CORE-GUIDES/extraction/extraction-quick-reference.md +596 -596
- package/docs/02-CORE-GUIDES/extraction/modules/02-core-guides-extraction-01-foundations.md +514 -514
- package/docs/02-CORE-GUIDES/extraction/modules/02-core-guides-extraction-02-basic-extraction.md +823 -823
- package/docs/02-CORE-GUIDES/extraction/modules/02-core-guides-extraction-03-parquet-processing.md +507 -507
- package/docs/02-CORE-GUIDES/extraction/modules/02-core-guides-extraction-04-data-enrichment.md +546 -546
- package/docs/02-CORE-GUIDES/extraction/modules/02-core-guides-extraction-05-transformation.md +494 -494
- package/docs/02-CORE-GUIDES/extraction/modules/02-core-guides-extraction-06-export-formats.md +458 -458
- package/docs/02-CORE-GUIDES/extraction/modules/02-core-guides-extraction-06-performance.md +138 -138
- package/docs/02-CORE-GUIDES/extraction/modules/02-core-guides-extraction-07-api-reference.md +148 -148
- package/docs/02-CORE-GUIDES/extraction/modules/02-core-guides-extraction-07-optimization.md +692 -692
- package/docs/02-CORE-GUIDES/extraction/modules/02-core-guides-extraction-08-extraction-orchestrator.md +1008 -1008
- package/docs/02-CORE-GUIDES/extraction/readme.md +151 -151
- package/docs/02-CORE-GUIDES/ingestion/examples/_simple-kv-store.ts +40 -40
- package/docs/02-CORE-GUIDES/ingestion/examples/error-recovery.ts +728 -728
- package/docs/02-CORE-GUIDES/ingestion/examples/event-driven.ts +501 -501
- package/docs/02-CORE-GUIDES/ingestion/examples/local-file-ingestion.ts +88 -88
- package/docs/02-CORE-GUIDES/ingestion/examples/parquet-ingestion.ts +117 -117
- package/docs/02-CORE-GUIDES/ingestion/examples/performance-optimized.ts +647 -647
- package/docs/02-CORE-GUIDES/ingestion/examples/s3-csv-ingestion.ts +169 -169
- package/docs/02-CORE-GUIDES/ingestion/examples/sftp-csv-ingestion.ts +134 -134
- package/docs/02-CORE-GUIDES/ingestion/ingestion-quick-reference.md +546 -546
- package/docs/02-CORE-GUIDES/ingestion/modules/02-core-guides-ingestion-01-introduction.md +626 -626
- package/docs/02-CORE-GUIDES/ingestion/modules/02-core-guides-ingestion-02-quick-start.md +658 -658
- package/docs/02-CORE-GUIDES/ingestion/modules/02-core-guides-ingestion-03-data-sources.md +1052 -1052
- package/docs/02-CORE-GUIDES/ingestion/modules/02-core-guides-ingestion-04-field-mapping.md +763 -763
- package/docs/02-CORE-GUIDES/ingestion/modules/02-core-guides-ingestion-05-advanced-parsers.md +676 -676
- package/docs/02-CORE-GUIDES/ingestion/modules/02-core-guides-ingestion-06-batch-api.md +1295 -1295
- package/docs/02-CORE-GUIDES/ingestion/modules/02-core-guides-ingestion-07-api-reference.md +138 -138
- package/docs/02-CORE-GUIDES/ingestion/modules/02-core-guides-ingestion-07-state-management.md +1037 -1037
- package/docs/02-CORE-GUIDES/ingestion/modules/02-core-guides-ingestion-08-performance-optimization.md +1349 -1349
- package/docs/02-CORE-GUIDES/ingestion/modules/02-core-guides-ingestion-09-best-practices.md +1893 -1893
- package/docs/02-CORE-GUIDES/ingestion/readme.md +160 -160
- package/docs/02-CORE-GUIDES/logging-guide.md +585 -585
- package/docs/02-CORE-GUIDES/mapping/error-handling-patterns.md +401 -401
- package/docs/02-CORE-GUIDES/mapping/examples/02-core-guides-mapping-readme.md +128 -128
- package/docs/02-CORE-GUIDES/mapping/examples/common-patterns.ts +273 -273
- package/docs/02-CORE-GUIDES/mapping/examples/csv-location-ingestion.json +36 -36
- package/docs/02-CORE-GUIDES/mapping/examples/csv-mapping.ts +242 -242
- package/docs/02-CORE-GUIDES/mapping/examples/graphql-to-parquet-extraction.json +36 -36
- package/docs/02-CORE-GUIDES/mapping/examples/json-mapping.ts +213 -213
- package/docs/02-CORE-GUIDES/mapping/examples/json-product-to-mutation.json +48 -48
- package/docs/02-CORE-GUIDES/mapping/examples/xml-mapping.ts +291 -291
- package/docs/02-CORE-GUIDES/mapping/examples/xml-order-to-mutation.json +45 -45
- package/docs/02-CORE-GUIDES/mapping/graphql-mutation-mapping/graphql-mutation-mapping-quick-reference.md +463 -463
- package/docs/02-CORE-GUIDES/mapping/graphql-mutation-mapping/graphql-mutation-mapping-readme.md +227 -227
- package/docs/02-CORE-GUIDES/mapping/graphql-mutation-mapping/modules/graphql-mutation-mapping-01-introduction.md +222 -222
- package/docs/02-CORE-GUIDES/mapping/graphql-mutation-mapping/modules/graphql-mutation-mapping-02-quick-start.md +351 -351
- package/docs/02-CORE-GUIDES/mapping/graphql-mutation-mapping/modules/graphql-mutation-mapping-03-schema-validation.md +569 -569
- package/docs/02-CORE-GUIDES/mapping/graphql-mutation-mapping/modules/graphql-mutation-mapping-04-mapping-patterns.md +471 -471
- package/docs/02-CORE-GUIDES/mapping/graphql-mutation-mapping/modules/graphql-mutation-mapping-05-configuration-reference.md +611 -611
- package/docs/02-CORE-GUIDES/mapping/graphql-mutation-mapping/modules/graphql-mutation-mapping-06-advanced-xpath.md +148 -148
- package/docs/02-CORE-GUIDES/mapping/graphql-mutation-mapping/modules/graphql-mutation-mapping-06-path-syntax.md +464 -464
- package/docs/02-CORE-GUIDES/mapping/graphql-mutation-mapping/modules/graphql-mutation-mapping-07-api-reference.md +94 -94
- package/docs/02-CORE-GUIDES/mapping/graphql-mutation-mapping/modules/graphql-mutation-mapping-07-array-handling.md +307 -307
- package/docs/02-CORE-GUIDES/mapping/graphql-mutation-mapping/modules/graphql-mutation-mapping-08-custom-resolvers.md +544 -544
- package/docs/02-CORE-GUIDES/mapping/graphql-mutation-mapping/modules/graphql-mutation-mapping-09-advanced-patterns.md +427 -427
- package/docs/02-CORE-GUIDES/mapping/graphql-mutation-mapping/modules/graphql-mutation-mapping-10-hooks-and-variables.md +336 -336
- package/docs/02-CORE-GUIDES/mapping/graphql-mutation-mapping/modules/graphql-mutation-mapping-11-error-handling.md +488 -488
- package/docs/02-CORE-GUIDES/mapping/graphql-mutation-mapping/modules/graphql-mutation-mapping-12-arguments-vs-nodes.md +383 -383
- package/docs/02-CORE-GUIDES/mapping/graphql-mutation-mapping/modules/graphql-mutation-mapping-13-best-practices.md +477 -477
- package/docs/02-CORE-GUIDES/mapping/graphql-mutation-mapping/readme.md +62 -62
- package/docs/02-CORE-GUIDES/mapping/mapping-format-decision-tree.md +480 -480
- package/docs/02-CORE-GUIDES/mapping/mapping-graphql-alias-batching-guide.md +820 -820
- package/docs/02-CORE-GUIDES/mapping/mapping-javascript-objects.md +2369 -2369
- package/docs/02-CORE-GUIDES/mapping/mapping-mapper-comparison-guide.md +682 -682
- package/docs/02-CORE-GUIDES/mapping/modules/02-core-guides-mapping-07-api-reference.md +1327 -1327
- package/docs/02-CORE-GUIDES/mapping/modules/02-core-guides-mapping-08-error-handling.md +1142 -1142
- package/docs/02-CORE-GUIDES/mapping/modules/mapping-04-use-cases.md +891 -891
- package/docs/02-CORE-GUIDES/mapping/modules/mapping-06-helpers-resolvers.md +1126 -1126
- package/docs/02-CORE-GUIDES/mapping/modules/mapping-06-sdk-resolvers.md +199 -199
- package/docs/02-CORE-GUIDES/mapping/modules/mapping-07-api-reference.md +1319 -1319
- package/docs/02-CORE-GUIDES/mapping/readme.md +178 -178
- package/docs/02-CORE-GUIDES/mapping/resolver-registration.md +410 -410
- package/docs/02-CORE-GUIDES/mapping/resolvers/examples/common-patterns.ts +226 -226
- package/docs/02-CORE-GUIDES/mapping/resolvers/examples/custom-resolvers.ts +227 -227
- package/docs/02-CORE-GUIDES/mapping/resolvers/examples/sdk-resolvers-usage.ts +203 -203
- package/docs/02-CORE-GUIDES/mapping/resolvers/mapping-resolvers-readme.md +274 -274
- package/docs/02-CORE-GUIDES/mapping/resolvers/mapping-resolvers-resolver-api-reference.md +679 -679
- package/docs/02-CORE-GUIDES/mapping/resolvers/mapping-resolvers-resolver-cookbook.md +826 -826
- package/docs/02-CORE-GUIDES/mapping/resolvers/mapping-resolvers-resolver-guide.md +1330 -1330
- package/docs/02-CORE-GUIDES/mapping/resolvers/mapping-resolvers-resolver-helpers-reference.md +1437 -1437
- package/docs/02-CORE-GUIDES/mapping/resolvers/mapping-resolvers-resolver-parameters-reference.md +553 -553
- package/docs/02-CORE-GUIDES/mapping/resolvers/mapping-resolvers-resolver-troubleshooting.md +854 -854
- package/docs/02-CORE-GUIDES/mapping/resolvers/readme.md +75 -75
- package/docs/02-CORE-GUIDES/parsers/examples/02-core-guides-parsers-readme.md +161 -161
- package/docs/02-CORE-GUIDES/parsers/examples/csv-parser-examples.ts +110 -110
- package/docs/02-CORE-GUIDES/parsers/examples/json-parser-examples.ts +33 -33
- package/docs/02-CORE-GUIDES/parsers/examples/parquet-parser-examples.ts +47 -47
- package/docs/02-CORE-GUIDES/parsers/examples/xml-parser-examples.ts +38 -38
- package/docs/02-CORE-GUIDES/parsers/modules/02-core-guides-parsers-01-foundations.md +355 -355
- package/docs/02-CORE-GUIDES/parsers/modules/02-core-guides-parsers-02-csv-parser.md +772 -772
- package/docs/02-CORE-GUIDES/parsers/modules/02-core-guides-parsers-03-json-parser.md +789 -789
- package/docs/02-CORE-GUIDES/parsers/modules/02-core-guides-parsers-04-xml-parser.md +857 -857
- package/docs/02-CORE-GUIDES/parsers/modules/02-core-guides-parsers-05-parquet-parser.md +603 -603
- package/docs/02-CORE-GUIDES/parsers/modules/02-core-guides-parsers-06-integration-patterns.md +702 -702
- package/docs/02-CORE-GUIDES/parsers/modules/02-core-guides-parsers-06-streaming.md +121 -121
- package/docs/02-CORE-GUIDES/parsers/modules/02-core-guides-parsers-07-api-reference.md +89 -89
- package/docs/02-CORE-GUIDES/parsers/modules/02-core-guides-parsers-07-troubleshooting.md +727 -727
- package/docs/02-CORE-GUIDES/parsers/parsers-quick-reference.md +482 -482
- package/docs/02-CORE-GUIDES/parsers/parsers-readme.md +258 -258
- package/docs/02-CORE-GUIDES/parsers/readme.md +65 -65
- package/docs/02-CORE-GUIDES/readme.md +194 -194
- package/docs/02-CORE-GUIDES/webhook-validation/examples/basic-validation.ts +108 -108
- package/docs/02-CORE-GUIDES/webhook-validation/examples/common-patterns.ts +316 -316
- package/docs/02-CORE-GUIDES/webhook-validation/examples/webhook-validation-readme.md +61 -61
- package/docs/02-CORE-GUIDES/webhook-validation/modules/webhook-validation-01-foundations.md +440 -440
- package/docs/02-CORE-GUIDES/webhook-validation/modules/webhook-validation-02-quick-start.md +525 -525
- package/docs/02-CORE-GUIDES/webhook-validation/modules/webhook-validation-03-versori-integration.md +741 -741
- package/docs/02-CORE-GUIDES/webhook-validation/modules/webhook-validation-04-platform-integration.md +629 -629
- package/docs/02-CORE-GUIDES/webhook-validation/modules/webhook-validation-05-configuration.md +535 -535
- package/docs/02-CORE-GUIDES/webhook-validation/modules/webhook-validation-06-error-handling.md +611 -611
- package/docs/02-CORE-GUIDES/webhook-validation/modules/webhook-validation-06-troubleshooting.md +124 -124
- package/docs/02-CORE-GUIDES/webhook-validation/modules/webhook-validation-07-api-reference.md +511 -511
- package/docs/02-CORE-GUIDES/webhook-validation/modules/webhook-validation-08-rubix-webhooks.md +590 -590
- package/docs/02-CORE-GUIDES/webhook-validation/modules/webhook-validation-09-rubix-event-vs-http-call.md +432 -432
- package/docs/02-CORE-GUIDES/webhook-validation/readme.md +239 -239
- package/docs/02-CORE-GUIDES/webhook-validation/webhook-validation-quick-reference.md +392 -392
- package/docs/03-PATTERN-GUIDES/connector-scenarios/connector-scenarios-quick-reference.md +498 -498
- package/docs/03-PATTERN-GUIDES/connector-scenarios/connector-scenarios-readme.md +313 -313
- package/docs/03-PATTERN-GUIDES/connector-scenarios/examples/common-patterns.ts +612 -612
- package/docs/03-PATTERN-GUIDES/connector-scenarios/examples/connector-scenarios-readme.md +253 -253
- package/docs/03-PATTERN-GUIDES/connector-scenarios/modules/connector-scenarios-01-foundations.md +452 -452
- package/docs/03-PATTERN-GUIDES/connector-scenarios/modules/connector-scenarios-02-simple-scenarios.md +681 -681
- package/docs/03-PATTERN-GUIDES/connector-scenarios/modules/connector-scenarios-03-intermediate-scenarios.md +637 -637
- package/docs/03-PATTERN-GUIDES/connector-scenarios/modules/connector-scenarios-04-advanced-scenarios.md +650 -650
- package/docs/03-PATTERN-GUIDES/connector-scenarios/modules/connector-scenarios-05-bidirectional-sync.md +233 -233
- package/docs/03-PATTERN-GUIDES/connector-scenarios/modules/connector-scenarios-06-production-patterns.md +442 -442
- package/docs/03-PATTERN-GUIDES/connector-scenarios/modules/connector-scenarios-07-reference.md +445 -445
- package/docs/03-PATTERN-GUIDES/connector-scenarios/readme.md +31 -31
- package/docs/03-PATTERN-GUIDES/enterprise-integration-patterns.md +1528 -1528
- package/docs/03-PATTERN-GUIDES/error-handling/comprehensive-error-handling-guide.md +1437 -1437
- package/docs/03-PATTERN-GUIDES/error-handling/error-handling-quick-reference.md +390 -390
- package/docs/03-PATTERN-GUIDES/error-handling/examples/common-patterns.ts +438 -438
- package/docs/03-PATTERN-GUIDES/error-handling/modules/error-handling-01-foundations.md +362 -362
- package/docs/03-PATTERN-GUIDES/error-handling/modules/error-handling-02-error-types.md +850 -850
- package/docs/03-PATTERN-GUIDES/error-handling/modules/error-handling-03-utf8-handling.md +456 -456
- package/docs/03-PATTERN-GUIDES/error-handling/modules/error-handling-04-error-scenarios.md +658 -658
- package/docs/03-PATTERN-GUIDES/error-handling/modules/error-handling-05-calling-patterns.md +671 -671
- package/docs/03-PATTERN-GUIDES/error-handling/modules/error-handling-06-retry-strategies.md +1034 -1034
- package/docs/03-PATTERN-GUIDES/error-handling/modules/error-handling-07-monitoring.md +653 -653
- package/docs/03-PATTERN-GUIDES/error-handling/modules/error-handling-08-api-reference.md +847 -847
- package/docs/03-PATTERN-GUIDES/error-handling/readme.md +36 -36
- package/docs/03-PATTERN-GUIDES/examples/__tests__/readme.md +40 -40
- package/docs/03-PATTERN-GUIDES/examples/__tests__/resolver-examples.test.js +282 -282
- package/docs/03-PATTERN-GUIDES/examples/test-data/03-pattern-guides-readme.md +110 -110
- package/docs/03-PATTERN-GUIDES/examples/test-data/canonical-inventory.json +123 -123
- package/docs/03-PATTERN-GUIDES/examples/test-data/canonical-order.json +171 -171
- package/docs/03-PATTERN-GUIDES/examples/test-data/readme.md +28 -28
- package/docs/03-PATTERN-GUIDES/extraction/extraction-readme.md +15 -15
- package/docs/03-PATTERN-GUIDES/extraction/readme.md +25 -25
- package/docs/03-PATTERN-GUIDES/file-operations/examples/common-patterns.ts +407 -407
- package/docs/03-PATTERN-GUIDES/file-operations/examples/file-operations-readme.md +142 -142
- package/docs/03-PATTERN-GUIDES/file-operations/file-operations-quick-reference.md +462 -462
- package/docs/03-PATTERN-GUIDES/file-operations/file-operations-readme.md +379 -379
- package/docs/03-PATTERN-GUIDES/file-operations/modules/file-operations-01-foundations.md +430 -430
- package/docs/03-PATTERN-GUIDES/file-operations/modules/file-operations-02-quick-start.md +484 -484
- package/docs/03-PATTERN-GUIDES/file-operations/modules/file-operations-03-s3-operations.md +507 -507
- package/docs/03-PATTERN-GUIDES/file-operations/modules/file-operations-04-sftp-operations.md +963 -963
- package/docs/03-PATTERN-GUIDES/file-operations/modules/file-operations-05-streaming-performance.md +503 -503
- package/docs/03-PATTERN-GUIDES/file-operations/modules/file-operations-06-archive-patterns.md +386 -386
- package/docs/03-PATTERN-GUIDES/file-operations/modules/file-operations-06-error-handling.md +117 -117
- package/docs/03-PATTERN-GUIDES/file-operations/modules/file-operations-07-api-reference.md +78 -78
- package/docs/03-PATTERN-GUIDES/file-operations/modules/file-operations-07-testing-troubleshooting.md +567 -567
- package/docs/03-PATTERN-GUIDES/file-operations/modules/file-operations-08-api-reference.md +1055 -1055
- package/docs/03-PATTERN-GUIDES/file-operations/readme.md +32 -32
- package/docs/03-PATTERN-GUIDES/ingestion/ingestion-readme.md +15 -15
- package/docs/03-PATTERN-GUIDES/ingestion/readme.md +25 -25
- package/docs/03-PATTERN-GUIDES/integration-patterns/examples/batch-processing.ts +130 -130
- package/docs/03-PATTERN-GUIDES/integration-patterns/examples/common-patterns.ts +360 -360
- package/docs/03-PATTERN-GUIDES/integration-patterns/examples/delta-sync.ts +130 -130
- package/docs/03-PATTERN-GUIDES/integration-patterns/examples/integration-patterns-readme.md +100 -100
- package/docs/03-PATTERN-GUIDES/integration-patterns/examples/real-time-webhook.ts +398 -398
- package/docs/03-PATTERN-GUIDES/integration-patterns/integration-patterns-quick-reference.md +962 -962
- package/docs/03-PATTERN-GUIDES/integration-patterns/integration-patterns-readme.md +134 -134
- package/docs/03-PATTERN-GUIDES/integration-patterns/modules/integration-patterns-01-real-time-processing.md +991 -991
- package/docs/03-PATTERN-GUIDES/integration-patterns/modules/integration-patterns-02-batch-processing.md +1547 -1547
- package/docs/03-PATTERN-GUIDES/integration-patterns/modules/integration-patterns-03-delta-sync.md +1108 -1108
- package/docs/03-PATTERN-GUIDES/integration-patterns/modules/integration-patterns-04-webhook-patterns.md +1181 -1181
- package/docs/03-PATTERN-GUIDES/integration-patterns/modules/integration-patterns-05-error-handling.md +1061 -1061
- package/docs/03-PATTERN-GUIDES/integration-patterns/modules/integration-patterns-06-advanced-integration-services.md +1547 -1547
- package/docs/03-PATTERN-GUIDES/integration-patterns/modules/integration-patterns-06-performance.md +109 -109
- package/docs/03-PATTERN-GUIDES/integration-patterns/modules/integration-patterns-07-api-reference.md +34 -34
- package/docs/03-PATTERN-GUIDES/integration-patterns/readme.md +30 -30
- package/docs/03-PATTERN-GUIDES/logging-minimal-mode.md +128 -128
- package/docs/03-PATTERN-GUIDES/multiple-connections/examples/common-patterns.ts +380 -380
- package/docs/03-PATTERN-GUIDES/multiple-connections/examples/multiple-connections-readme.md +139 -139
- package/docs/03-PATTERN-GUIDES/multiple-connections/examples/parallel-root-connections.ts +149 -149
- package/docs/03-PATTERN-GUIDES/multiple-connections/examples/real-world-scenarios.ts +405 -405
- package/docs/03-PATTERN-GUIDES/multiple-connections/modules/multiple-connections-01-foundations.md +378 -378
- package/docs/03-PATTERN-GUIDES/multiple-connections/modules/multiple-connections-02-quick-start.md +566 -566
- package/docs/03-PATTERN-GUIDES/multiple-connections/modules/multiple-connections-03-targeting-connections.md +659 -659
- package/docs/03-PATTERN-GUIDES/multiple-connections/modules/multiple-connections-04-parallel-queries.md +656 -656
- package/docs/03-PATTERN-GUIDES/multiple-connections/modules/multiple-connections-05-best-practices.md +624 -624
- package/docs/03-PATTERN-GUIDES/multiple-connections/modules/multiple-connections-06-api-reference.md +824 -824
- package/docs/03-PATTERN-GUIDES/multiple-connections/modules/multiple-connections-06-versori.md +119 -119
- package/docs/03-PATTERN-GUIDES/multiple-connections/modules/multiple-connections-07-api-reference.md +87 -87
- package/docs/03-PATTERN-GUIDES/multiple-connections/multiple-connections-quick-reference.md +353 -353
- package/docs/03-PATTERN-GUIDES/multiple-connections/multiple-connections-readme.md +270 -270
- package/docs/03-PATTERN-GUIDES/multiple-connections/readme.md +30 -30
- package/docs/03-PATTERN-GUIDES/pagination/pagination-readme.md +14 -14
- package/docs/03-PATTERN-GUIDES/pagination/readme.md +24 -24
- package/docs/03-PATTERN-GUIDES/parquet/examples/common-patterns.ts +180 -180
- package/docs/03-PATTERN-GUIDES/parquet/examples/read-parquet.ts +48 -48
- package/docs/03-PATTERN-GUIDES/parquet/examples/write-parquet.ts +65 -65
- package/docs/03-PATTERN-GUIDES/parquet/modules/03-pattern-guides-parquet-01-introduction.md +393 -393
- package/docs/03-PATTERN-GUIDES/parquet/modules/03-pattern-guides-parquet-02-quick-start.md +572 -572
- package/docs/03-PATTERN-GUIDES/parquet/modules/03-pattern-guides-parquet-03-reading-parquet.md +525 -525
- package/docs/03-PATTERN-GUIDES/parquet/modules/03-pattern-guides-parquet-04-writing-parquet.md +554 -554
- package/docs/03-PATTERN-GUIDES/parquet/modules/03-pattern-guides-parquet-05-graphql-extraction.md +405 -405
- package/docs/03-PATTERN-GUIDES/parquet/modules/03-pattern-guides-parquet-06-performance.md +104 -104
- package/docs/03-PATTERN-GUIDES/parquet/modules/03-pattern-guides-parquet-06-s3-integration.md +511 -511
- package/docs/03-PATTERN-GUIDES/parquet/modules/03-pattern-guides-parquet-07-api-reference.md +90 -90
- package/docs/03-PATTERN-GUIDES/parquet/modules/03-pattern-guides-parquet-07-performance-optimization.md +525 -525
- package/docs/03-PATTERN-GUIDES/parquet/modules/03-pattern-guides-parquet-08-best-practices.md +712 -712
- package/docs/03-PATTERN-GUIDES/parquet/parquet-quick-reference.md +683 -683
- package/docs/03-PATTERN-GUIDES/parquet/parquet-readme.md +248 -248
- package/docs/03-PATTERN-GUIDES/parquet/readme.md +32 -32
- package/docs/03-PATTERN-GUIDES/parsers/parsers-readme.md +12 -12
- package/docs/03-PATTERN-GUIDES/parsers/readme.md +24 -24
- package/docs/03-PATTERN-GUIDES/readme.md +159 -159
- package/docs/03-PATTERN-GUIDES/webhooks/readme.md +24 -24
- package/docs/03-PATTERN-GUIDES/webhooks/webhooks-readme.md +8 -8
- package/docs/04-REFERENCE/architecture/architecture-01-overview.md +427 -427
- package/docs/04-REFERENCE/architecture/architecture-02-client-architecture.md +424 -424
- package/docs/04-REFERENCE/architecture/architecture-03-data-flow.md +690 -690
- package/docs/04-REFERENCE/architecture/architecture-04-service-layer.md +834 -834
- package/docs/04-REFERENCE/architecture/architecture-05-integration-architecture.md +655 -655
- package/docs/04-REFERENCE/architecture/architecture-06-state-management.md +653 -653
- package/docs/04-REFERENCE/architecture/architecture-adding-new-data-sources.md +686 -686
- package/docs/04-REFERENCE/architecture/readme.md +279 -279
- package/docs/04-REFERENCE/platforms/deno/readme.md +117 -117
- package/docs/04-REFERENCE/platforms/nodejs/readme.md +146 -146
- package/docs/04-REFERENCE/platforms/readme.md +135 -135
- package/docs/04-REFERENCE/platforms/versori/modules/platforms-versori-01-introduction.md +398 -398
- package/docs/04-REFERENCE/platforms/versori/modules/platforms-versori-02-quick-start.md +560 -560
- package/docs/04-REFERENCE/platforms/versori/modules/platforms-versori-03-authentication.md +757 -757
- package/docs/04-REFERENCE/platforms/versori/modules/platforms-versori-04-workflows.md +2476 -2476
- package/docs/04-REFERENCE/platforms/versori/modules/platforms-versori-05-connections.md +1167 -1167
- package/docs/04-REFERENCE/platforms/versori/modules/platforms-versori-06-kv-storage.md +990 -990
- package/docs/04-REFERENCE/platforms/versori/modules/platforms-versori-06-state-management.md +121 -121
- package/docs/04-REFERENCE/platforms/versori/modules/platforms-versori-07-api-reference.md +68 -68
- package/docs/04-REFERENCE/platforms/versori/modules/platforms-versori-07-deployment.md +731 -731
- package/docs/04-REFERENCE/platforms/versori/modules/platforms-versori-08-best-practices.md +1111 -1111
- package/docs/04-REFERENCE/platforms/versori/modules/platforms-versori-09-signature-reference.md +766 -766
- package/docs/04-REFERENCE/platforms/versori/platforms-versori-readme.md +299 -299
- package/docs/04-REFERENCE/platforms/versori/platforms-versori-s3-sftp-configuration-guide.md +1425 -1425
- package/docs/04-REFERENCE/platforms/versori/platforms-versori-webhook-api-key-security.md +816 -816
- package/docs/04-REFERENCE/platforms/versori/platforms-versori-webhook-connection-security.md +681 -681
- package/docs/04-REFERENCE/platforms/versori/platforms-versori-workflow-task-types.md +708 -708
- package/docs/04-REFERENCE/platforms/versori/readme.md +108 -108
- package/docs/04-REFERENCE/readme.md +148 -148
- package/docs/04-REFERENCE/resolver-signature/examples/advanced-resolvers.ts +482 -482
- package/docs/04-REFERENCE/resolver-signature/examples/async-resolvers.ts +496 -496
- package/docs/04-REFERENCE/resolver-signature/examples/basic-resolvers.ts +343 -343
- package/docs/04-REFERENCE/resolver-signature/examples/resolver-signature-readme.md +188 -188
- package/docs/04-REFERENCE/resolver-signature/examples/testing-resolvers.ts +463 -463
- package/docs/04-REFERENCE/resolver-signature/modules/resolver-signature-01-foundations.md +286 -286
- package/docs/04-REFERENCE/resolver-signature/modules/resolver-signature-02-parameter-reference.md +643 -643
- package/docs/04-REFERENCE/resolver-signature/modules/resolver-signature-03-basic-examples.md +521 -521
- package/docs/04-REFERENCE/resolver-signature/modules/resolver-signature-04-advanced-patterns.md +739 -739
- package/docs/04-REFERENCE/resolver-signature/modules/resolver-signature-05-sdk-resolvers.md +531 -531
- package/docs/04-REFERENCE/resolver-signature/modules/resolver-signature-06-migration-guide.md +650 -650
- package/docs/04-REFERENCE/resolver-signature/modules/resolver-signature-06-testing.md +125 -125
- package/docs/04-REFERENCE/resolver-signature/modules/resolver-signature-07-api-reference.md +794 -794
- package/docs/04-REFERENCE/resolver-signature/readme.md +64 -64
- package/docs/04-REFERENCE/resolver-signature/resolver-signature-quick-reference.md +270 -270
- package/docs/04-REFERENCE/resolver-signature/resolver-signature-readme.md +351 -351
- package/docs/04-REFERENCE/schema/fluent-commerce-schema.json +764 -764
- package/docs/04-REFERENCE/schema/readme.md +141 -141
- package/docs/04-REFERENCE/testing/examples/04-reference-testing-readme.md +158 -158
- package/docs/04-REFERENCE/testing/examples/fluent-testing.ts +62 -62
- package/docs/04-REFERENCE/testing/examples/health-check.ts +155 -155
- package/docs/04-REFERENCE/testing/examples/integration-test.ts +119 -119
- package/docs/04-REFERENCE/testing/examples/performance-test.ts +183 -183
- package/docs/04-REFERENCE/testing/examples/s3-testing.ts +127 -127
- package/docs/04-REFERENCE/testing/modules/04-reference-testing-01-foundations.md +267 -267
- package/docs/04-REFERENCE/testing/modules/04-reference-testing-02-s3-testing.md +599 -599
- package/docs/04-REFERENCE/testing/modules/04-reference-testing-03-fluent-testing.md +589 -589
- package/docs/04-REFERENCE/testing/modules/04-reference-testing-04-integration-testing.md +699 -699
- package/docs/04-REFERENCE/testing/modules/04-reference-testing-05-debugging.md +478 -478
- package/docs/04-REFERENCE/testing/modules/04-reference-testing-06-cicd-integration.md +463 -463
- package/docs/04-REFERENCE/testing/modules/04-reference-testing-06-preflight-validation.md +131 -131
- package/docs/04-REFERENCE/testing/modules/04-reference-testing-07-best-practices.md +499 -499
- package/docs/04-REFERENCE/testing/modules/04-reference-testing-07-coverage-ci.md +165 -165
- package/docs/04-REFERENCE/testing/modules/04-reference-testing-08-api-reference.md +634 -634
- package/docs/04-REFERENCE/testing/readme.md +86 -86
- package/docs/04-REFERENCE/testing/testing-quick-reference.md +667 -667
- package/docs/04-REFERENCE/testing/testing-readme.md +286 -286
- package/docs/04-REFERENCE/troubleshooting/readme.md +144 -144
- package/docs/04-REFERENCE/troubleshooting/troubleshooting-deno-sftp-compatibility.md +392 -392
- package/docs/template-loading-matrix.md +242 -242
- package/package.json +5 -3
- package/docs/02-CORE-GUIDES/api-reference/cli-profile-integration.md +0 -377
|
@@ -1,1209 +1,1209 @@
|
|
|
1
|
-
# Standalone: ASN Inbound Processing (WMS/3PL Integration)
|
|
2
|
-
|
|
3
|
-
**FC Connect SDK Use Case Guide**
|
|
4
|
-
|
|
5
|
-
> **SDK**: [@fluentcommerce/fc-connect-sdk](https://www.npmjs.com/package/@fluentcommerce/fc-connect-sdk)
|
|
6
|
-
> **Version**: Use latest - `npm install @fluentcommerce/fc-connect-sdk@latest`
|
|
7
|
-
|
|
8
|
-
**Context**: Node.js script that processes Advanced Ship Notices (ASNs) from WMS/3PL systems via SFTP, creating expected receipts in Fluent Commerce and confirming actual receipts
|
|
9
|
-
|
|
10
|
-
**Complexity**: Medium
|
|
11
|
-
|
|
12
|
-
**Runtime**: Node.js ≥18 / Deno
|
|
13
|
-
|
|
14
|
-
**Estimated Lines**: ~900 lines
|
|
15
|
-
|
|
16
|
-
**Volume**: 10-100 ASNs/day, 100-5000 units per ASN
|
|
17
|
-
|
|
18
|
-
**Latency**: Near real-time (< 5 min)
|
|
19
|
-
|
|
20
|
-
**Pattern**: SFTP polling → EDI/XML parsing → GraphQL mutation
|
|
21
|
-
|
|
22
|
-
## What You'll Build
|
|
23
|
-
|
|
24
|
-
- Standalone Node.js/Deno script (no Versori)
|
|
25
|
-
- SFTP integration for ASN file polling
|
|
26
|
-
- EDI 856 (X12) and XML ASN format parsing
|
|
27
|
-
- Container hierarchy mapping (shipment → pallet → case → item)
|
|
28
|
-
- Expected receipt creation in Fluent Commerce
|
|
29
|
-
- Actual receipt confirmation with variance handling
|
|
30
|
-
- Cross-dock scenario support (direct to customer)
|
|
31
|
-
- Email alerts for significant variances (> 5%)
|
|
32
|
-
- State management to prevent duplicate processing
|
|
33
|
-
- Comprehensive error handling and logging
|
|
34
|
-
|
|
35
|
-
## SDK Methods Used
|
|
36
|
-
|
|
37
|
-
- `createClient({ config: { baseUrl, clientId, clientSecret, retailerId } })` - OAuth2 client
|
|
38
|
-
- `SftpDataSource(config, logger)` - SFTP operations
|
|
39
|
-
- `XMLParserService` - XML ASN parsing
|
|
40
|
-
- `GraphQLMutationMapper` - ASN → Fluent GraphQL mutations
|
|
41
|
-
- `VersoriFileTracker` (adapted for standalone) - Deduplication
|
|
42
|
-
- `client.graphql(payload)` - Execute mutations
|
|
43
|
-
- Custom EDI 856 parser for X12 format
|
|
44
|
-
|
|
45
|
-
---
|
|
46
|
-
|
|
47
|
-
## Complete Working Implementation
|
|
48
|
-
|
|
49
|
-
### 1. Project Setup
|
|
50
|
-
|
|
51
|
-
```bash
|
|
52
|
-
# Create project directory
|
|
53
|
-
mkdir asn-inbound-processor
|
|
54
|
-
cd asn-inbound-processor
|
|
55
|
-
|
|
56
|
-
# Initialize Node.js project
|
|
57
|
-
npm init -y
|
|
58
|
-
|
|
59
|
-
# Install dependencies
|
|
60
|
-
npm install @fluentcommerce/fc-connect-sdk dotenv nodemailer
|
|
61
|
-
```
|
|
62
|
-
|
|
63
|
-
### 2. Environment Configuration
|
|
64
|
-
|
|
65
|
-
Create `.env` file:
|
|
66
|
-
|
|
67
|
-
```env
|
|
68
|
-
# Fluent Commerce Configuration
|
|
69
|
-
FLUENT_BASE_URL=https://api.fluentcommerce.com
|
|
70
|
-
FLUENT_CLIENT_ID=your-oauth2-client-id
|
|
71
|
-
FLUENT_CLIENT_SECRET=your-oauth2-client-secret
|
|
72
|
-
FLUENT_RETAILER_ID=your-retailer-id
|
|
73
|
-
|
|
74
|
-
# SFTP Configuration
|
|
75
|
-
SFTP_HOST=sftp.3pl-provider.com
|
|
76
|
-
SFTP_PORT=22
|
|
77
|
-
SFTP_USERNAME=your-username
|
|
78
|
-
SFTP_PASSWORD=your-password
|
|
79
|
-
# OR use private key authentication
|
|
80
|
-
# SFTP_PRIVATE_KEY=/path/to/private_key
|
|
81
|
-
# SFTP_PASSPHRASE=key-passphrase
|
|
82
|
-
|
|
83
|
-
# SFTP Paths
|
|
84
|
-
SFTP_INBOUND_PATH=/inbound/asn
|
|
85
|
-
SFTP_PROCESSED_PATH=/processed/asn
|
|
86
|
-
SFTP_ERROR_PATH=/errors/asn
|
|
87
|
-
SFTP_FILE_PATTERN=*.{xml,edi,856}
|
|
88
|
-
|
|
89
|
-
# Processing Configuration
|
|
90
|
-
ASN_FORMAT=xml# Options: xml, edi856
|
|
91
|
-
ENABLE_ACTUAL_RECEIPT_CONFIRMATION=true
|
|
92
|
-
VARIANCE_ALERT_THRESHOLD=0.05# 5% threshold for email alerts
|
|
93
|
-
|
|
94
|
-
# Email Alerts (for variances)
|
|
95
|
-
SMTP_HOST=smtp.gmail.com
|
|
96
|
-
SMTP_PORT=587
|
|
97
|
-
SMTP_USER=alerts@yourcompany.com
|
|
98
|
-
SMTP_PASSWORD=your-smtp-password
|
|
99
|
-
ALERT_EMAIL_TO=warehouse-mgr@yourcompany.com
|
|
100
|
-
|
|
101
|
-
# State Management
|
|
102
|
-
STATE_STORAGE_PATH=./data/asn-state.json
|
|
103
|
-
|
|
104
|
-
# Optional: Cross-dock support
|
|
105
|
-
ENABLE_CROSS_DOCK=true
|
|
106
|
-
CROSS_DOCK_LOCATION_PREFIX=CUSTOMER_
|
|
107
|
-
|
|
108
|
-
# Logging
|
|
109
|
-
LOG_LEVEL=info# Options: debug, info, warn, error
|
|
110
|
-
```
|
|
111
|
-
|
|
112
|
-
### 3. Main Script Implementation (src/asn-processor.ts)
|
|
113
|
-
|
|
114
|
-
```typescript
|
|
115
|
-
import 'dotenv/config';
|
|
116
|
-
// FC Connect SDK+
|
|
117
|
-
// Install: npm install @fluentcommerce/fc-connect-sdk@latest
|
|
118
|
-
// Docs: https://www.npmjs.com/package/@fluentcommerce/fc-connect-sdk
|
|
119
|
-
// GitHub: https://github.com/fluentcommerce/fc-connect-sdk
|
|
120
|
-
|
|
121
|
-
import {
|
|
122
|
-
createClient,
|
|
123
|
-
SftpDataSource,
|
|
124
|
-
XMLParserService,
|
|
125
|
-
GraphQLMutationMapper,
|
|
126
|
-
type FluentClient,
|
|
127
|
-
type StructuredLogger,
|
|
128
|
-
createConsoleLogger,
|
|
129
|
-
toStructuredLogger
|
|
130
|
-
} from '@fluentcommerce/fc-connect-sdk';
|
|
131
|
-
|
|
132
|
-
import { readFileSync, writeFileSync, existsSync, mkdirSync } from 'fs';
|
|
133
|
-
import { join, dirname } from 'path';
|
|
134
|
-
import nodemailer from 'nodemailer';
|
|
135
|
-
|
|
136
|
-
// ============================================================================
|
|
137
|
-
// TYPES & INTERFACES
|
|
138
|
-
// ============================================================================
|
|
139
|
-
|
|
140
|
-
interface AsnDocument {
|
|
141
|
-
asnNumber: string;
|
|
142
|
-
shipmentDate: string;
|
|
143
|
-
expectedDeliveryDate: string;
|
|
144
|
-
carrier: string;
|
|
145
|
-
trackingNumber?: string;
|
|
146
|
-
origin: {
|
|
147
|
-
locationRef: string;
|
|
148
|
-
name: string;
|
|
149
|
-
address?: string;
|
|
150
|
-
};
|
|
151
|
-
destination: {
|
|
152
|
-
locationRef: string;
|
|
153
|
-
name: string;
|
|
154
|
-
address?: string;
|
|
155
|
-
};
|
|
156
|
-
containers: Container[];
|
|
157
|
-
totalQuantity: number;
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
interface Container {
|
|
161
|
-
id: string;
|
|
162
|
-
type: 'PALLET' | 'CASE' | 'CARTON';
|
|
163
|
-
sscc?: string;
|
|
164
|
-
items: AsnItem[];
|
|
165
|
-
children?: Container[];
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
interface AsnItem {
|
|
169
|
-
sku: string;
|
|
170
|
-
productName?: string;
|
|
171
|
-
expectedQuantity: number;
|
|
172
|
-
actualQuantity?: number;
|
|
173
|
-
uom: string;
|
|
174
|
-
lotNumber?: string;
|
|
175
|
-
serialNumbers?: string[];
|
|
176
|
-
expiryDate?: string;
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
interface ProcessingState {
|
|
180
|
-
processedFiles: Record<string, {
|
|
181
|
-
timestamp: string;
|
|
182
|
-
asnNumber: string;
|
|
183
|
-
status: 'success' | 'failed';
|
|
184
|
-
receiptId?: string;
|
|
185
|
-
}>;
|
|
186
|
-
}
|
|
187
|
-
|
|
188
|
-
// ============================================================================
|
|
189
|
-
// CONFIGURATION
|
|
190
|
-
// ============================================================================
|
|
191
|
-
|
|
192
|
-
const config = {
|
|
193
|
-
fluent: {
|
|
194
|
-
baseUrl: process.env.FLUENT_BASE_URL!,
|
|
195
|
-
clientId: process.env.FLUENT_CLIENT_ID!,
|
|
196
|
-
clientSecret: process.env.FLUENT_CLIENT_SECRET!,
|
|
197
|
-
retailerId: process.env.FLUENT_RETAILER_ID!,
|
|
198
|
-
},
|
|
199
|
-
sftp: {
|
|
200
|
-
host: process.env.SFTP_HOST!,
|
|
201
|
-
port: parseInt(process.env.SFTP_PORT || '22'),
|
|
202
|
-
username: process.env.SFTP_USERNAME!,
|
|
203
|
-
password: process.env.SFTP_PASSWORD,
|
|
204
|
-
privateKey: process.env.SFTP_PRIVATE_KEY
|
|
205
|
-
? readFileSync(process.env.SFTP_PRIVATE_KEY, 'utf8')
|
|
206
|
-
: undefined,
|
|
207
|
-
passphrase: process.env.SFTP_PASSPHRASE,
|
|
208
|
-
inboundPath: process.env.SFTP_INBOUND_PATH || '/inbound/asn',
|
|
209
|
-
processedPath: process.env.SFTP_PROCESSED_PATH || '/processed/asn',
|
|
210
|
-
errorPath: process.env.SFTP_ERROR_PATH || '/errors/asn',
|
|
211
|
-
filePattern: process.env.SFTP_FILE_PATTERN || '*.{xml,edi,856}',
|
|
212
|
-
},
|
|
213
|
-
processing: {
|
|
214
|
-
asnFormat: process.env.ASN_FORMAT || 'xml',
|
|
215
|
-
enableActualReceipt: process.env.ENABLE_ACTUAL_RECEIPT_CONFIRMATION === 'true',
|
|
216
|
-
varianceThreshold: parseFloat(process.env.VARIANCE_ALERT_THRESHOLD || '0.05'),
|
|
217
|
-
enableCrossDock: process.env.ENABLE_CROSS_DOCK === 'true',
|
|
218
|
-
crossDockLocationPrefix: process.env.CROSS_DOCK_LOCATION_PREFIX || 'CUSTOMER_',
|
|
219
|
-
},
|
|
220
|
-
email: {
|
|
221
|
-
smtpHost: process.env.SMTP_HOST,
|
|
222
|
-
smtpPort: parseInt(process.env.SMTP_PORT || '587'),
|
|
223
|
-
smtpUser: process.env.SMTP_USER,
|
|
224
|
-
smtpPassword: process.env.SMTP_PASSWORD,
|
|
225
|
-
alertEmailTo: process.env.ALERT_EMAIL_TO,
|
|
226
|
-
},
|
|
227
|
-
state: {
|
|
228
|
-
storagePath: process.env.STATE_STORAGE_PATH || './data/asn-state.json',
|
|
229
|
-
},
|
|
230
|
-
};
|
|
231
|
-
|
|
232
|
-
// ============================================================================
|
|
233
|
-
// STATE MANAGEMENT
|
|
234
|
-
// ============================================================================
|
|
235
|
-
|
|
236
|
-
class StateManager {
|
|
237
|
-
private state: ProcessingState;
|
|
238
|
-
private storagePath: string;
|
|
239
|
-
|
|
240
|
-
constructor(storagePath: string) {
|
|
241
|
-
this.storagePath = storagePath;
|
|
242
|
-
this.state = this.loadState();
|
|
243
|
-
}
|
|
244
|
-
|
|
245
|
-
private loadState(): ProcessingState {
|
|
246
|
-
try {
|
|
247
|
-
if (existsSync(this.storagePath)) {
|
|
248
|
-
const data = readFileSync(this.storagePath, 'utf8');
|
|
249
|
-
return JSON.parse(data);
|
|
250
|
-
}
|
|
251
|
-
} catch (error) {
|
|
252
|
-
console.warn('Failed to load state, starting fresh', error);
|
|
253
|
-
}
|
|
254
|
-
return { processedFiles: {} };
|
|
255
|
-
}
|
|
256
|
-
|
|
257
|
-
private saveState(): void {
|
|
258
|
-
try {
|
|
259
|
-
const dir = dirname(this.storagePath);
|
|
260
|
-
if (!existsSync(dir)) {
|
|
261
|
-
mkdirSync(dir, { recursive: true });
|
|
262
|
-
}
|
|
263
|
-
writeFileSync(this.storagePath, JSON.stringify(this.state, null, 2));
|
|
264
|
-
} catch (error) {
|
|
265
|
-
console.error('Failed to save state', error);
|
|
266
|
-
}
|
|
267
|
-
}
|
|
268
|
-
|
|
269
|
-
isFileProcessed(fileName: string): boolean {
|
|
270
|
-
return !!this.state.processedFiles[fileName];
|
|
271
|
-
}
|
|
272
|
-
|
|
273
|
-
markFileProcessed(fileName: string, asnNumber: string, receiptId?: string): void {
|
|
274
|
-
this.state.processedFiles[fileName] = {
|
|
275
|
-
timestamp: new Date().toISOString(),
|
|
276
|
-
asnNumber,
|
|
277
|
-
status: 'success',
|
|
278
|
-
receiptId,
|
|
279
|
-
};
|
|
280
|
-
this.saveState();
|
|
281
|
-
}
|
|
282
|
-
|
|
283
|
-
markFileFailed(fileName: string, asnNumber: string): void {
|
|
284
|
-
this.state.processedFiles[fileName] = {
|
|
285
|
-
timestamp: new Date().toISOString(),
|
|
286
|
-
asnNumber,
|
|
287
|
-
status: 'failed',
|
|
288
|
-
};
|
|
289
|
-
this.saveState();
|
|
290
|
-
}
|
|
291
|
-
}
|
|
292
|
-
|
|
293
|
-
// ============================================================================
|
|
294
|
-
// EMAIL ALERTS
|
|
295
|
-
// ============================================================================
|
|
296
|
-
|
|
297
|
-
class EmailAlerter {
|
|
298
|
-
private transporter: any;
|
|
299
|
-
private enabled: boolean;
|
|
300
|
-
|
|
301
|
-
constructor(config: typeof config.email) {
|
|
302
|
-
this.enabled = !!(config.smtpHost && config.smtpUser && config.alertEmailTo);
|
|
303
|
-
|
|
304
|
-
if (this.enabled) {
|
|
305
|
-
this.transporter = nodemailer.createTransport({
|
|
306
|
-
host: config.smtpHost,
|
|
307
|
-
port: config.smtpPort,
|
|
308
|
-
secure: config.smtpPort === 465,
|
|
309
|
-
auth: {
|
|
310
|
-
user: config.smtpUser,
|
|
311
|
-
pass: config.smtpPassword,
|
|
312
|
-
},
|
|
313
|
-
});
|
|
314
|
-
}
|
|
315
|
-
}
|
|
316
|
-
|
|
317
|
-
async sendVarianceAlert(asnNumber: string, variances: any[]): Promise<void> {
|
|
318
|
-
if (!this.enabled) return;
|
|
319
|
-
|
|
320
|
-
const html = `
|
|
321
|
-
<h2>ASN Variance Alert: ${asnNumber}</h2>
|
|
322
|
-
<p>Significant variances detected during receipt confirmation:</p>
|
|
323
|
-
<table border="1" cellpadding="5">
|
|
324
|
-
<tr>
|
|
325
|
-
<th>SKU</th>
|
|
326
|
-
<th>Expected</th>
|
|
327
|
-
<th>Actual</th>
|
|
328
|
-
<th>Variance</th>
|
|
329
|
-
<th>%</th>
|
|
330
|
-
</tr>
|
|
331
|
-
${variances.map(v => `
|
|
332
|
-
<tr>
|
|
333
|
-
<td>${v.sku}</td>
|
|
334
|
-
<td>${v.expected}</td>
|
|
335
|
-
<td>${v.actual}</td>
|
|
336
|
-
<td style="color: ${v.variance < 0 ? 'red' : 'orange'}">${v.variance > 0 ? '+' : ''}${v.variance}</td>
|
|
337
|
-
<td>${(v.variancePercent * 100).toFixed(1)}%</td>
|
|
338
|
-
</tr>
|
|
339
|
-
`).join('')}
|
|
340
|
-
</table>
|
|
341
|
-
<p><strong>Action Required:</strong> Review and investigate variances.</p>
|
|
342
|
-
`;
|
|
343
|
-
|
|
344
|
-
try {
|
|
345
|
-
await this.transporter.sendMail({
|
|
346
|
-
from: config.email.smtpUser,
|
|
347
|
-
to: config.email.alertEmailTo,
|
|
348
|
-
subject: `⚠️ ASN Variance Alert: ${asnNumber}`,
|
|
349
|
-
html,
|
|
350
|
-
});
|
|
351
|
-
} catch (error) {
|
|
352
|
-
console.error('Failed to send email alert', error);
|
|
353
|
-
}
|
|
354
|
-
}
|
|
355
|
-
}
|
|
356
|
-
|
|
357
|
-
// ============================================================================
|
|
358
|
-
// ASN PROCESSOR
|
|
359
|
-
// ============================================================================
|
|
360
|
-
|
|
361
|
-
class AsnProcessor {
|
|
362
|
-
private client: FluentClient;
|
|
363
|
-
private sftpSource: SftpDataSource;
|
|
364
|
-
private xmlParser: XMLParserService;
|
|
365
|
-
private logger: StructuredLogger;
|
|
366
|
-
private stateManager: StateManager;
|
|
367
|
-
private emailAlerter: EmailAlerter;
|
|
368
|
-
|
|
369
|
-
constructor() {
|
|
370
|
-
this.logger = toStructuredLogger(createConsoleLogger(), { logLevel: 'info' });
|
|
371
|
-
}
|
|
372
|
-
|
|
373
|
-
async initialize(): Promise<void> {
|
|
374
|
-
this.logger.info('Initializing ASN processor...');
|
|
375
|
-
|
|
376
|
-
// Initialize Fluent client
|
|
377
|
-
this.client = await createClient({ config: config.fluent });
|
|
378
|
-
|
|
379
|
-
// Initialize SFTP data source
|
|
380
|
-
this.sftpSource = new SftpDataSource({
|
|
381
|
-
type: 'SFTP_XML',
|
|
382
|
-
connectionId: 'asn-sftp',
|
|
383
|
-
name: 'ASN SFTP',
|
|
384
|
-
settings: {
|
|
385
|
-
host: config.sftp.host,
|
|
386
|
-
port: config.sftp.port,
|
|
387
|
-
username: config.sftp.username,
|
|
388
|
-
password: config.sftp.password,
|
|
389
|
-
privateKey: config.sftp.privateKey,
|
|
390
|
-
passphrase: config.sftp.passphrase,
|
|
391
|
-
},
|
|
392
|
-
}, this.logger);
|
|
393
|
-
|
|
394
|
-
// Initialize XML parser
|
|
395
|
-
this.xmlParser = new XMLParserService();
|
|
396
|
-
|
|
397
|
-
// Initialize state manager
|
|
398
|
-
this.stateManager = new StateManager(config.state.storagePath);
|
|
399
|
-
|
|
400
|
-
// Initialize email alerter
|
|
401
|
-
this.emailAlerter = new EmailAlerter(config.email);
|
|
402
|
-
|
|
403
|
-
// Validate SFTP connection
|
|
404
|
-
const isConnected = await this.sftpSource.validateConnection();
|
|
405
|
-
if (!isConnected) {
|
|
406
|
-
throw new Error('SFTP connection validation failed');
|
|
407
|
-
}
|
|
408
|
-
|
|
409
|
-
this.logger.info('ASN processor initialized successfully');
|
|
410
|
-
}
|
|
411
|
-
|
|
412
|
-
async processFiles(): Promise<void> {
|
|
413
|
-
this.logger.info('Starting ASN file processing cycle');
|
|
414
|
-
|
|
415
|
-
try {
|
|
416
|
-
// List files from SFTP
|
|
417
|
-
const files = await this.sftpSource.listFiles({
|
|
418
|
-
remotePath: config.sftp.inboundPath,
|
|
419
|
-
filePattern: config.sftp.filePattern,
|
|
420
|
-
});
|
|
421
|
-
|
|
422
|
-
this.logger.info(`Found ${files.length} ASN files`);
|
|
423
|
-
|
|
424
|
-
for (const file of files) {
|
|
425
|
-
// Skip already processed files
|
|
426
|
-
if (this.stateManager.isFileProcessed(file.name)) {
|
|
427
|
-
this.logger.debug(`Skipping already processed file: ${file.name}`);
|
|
428
|
-
continue;
|
|
429
|
-
}
|
|
430
|
-
|
|
431
|
-
try {
|
|
432
|
-
await this.processAsnFile(file.name);
|
|
433
|
-
} catch (error: any) {
|
|
434
|
-
this.logger.error(`Failed to process ASN file: ${file.name}`, error);
|
|
435
|
-
|
|
436
|
-
// Move to error folder
|
|
437
|
-
const errorPath = `${config.sftp.errorPath}/${file.name}`;
|
|
438
|
-
await this.sftpSource.moveFile(file.path, errorPath, true); // Use file.path for source (full path)
|
|
439
|
-
}
|
|
440
|
-
}
|
|
441
|
-
|
|
442
|
-
this.logger.info('ASN processing cycle completed');
|
|
443
|
-
} catch (error: any) {
|
|
444
|
-
this.logger.error('ASN processing cycle failed', error);
|
|
445
|
-
throw error;
|
|
446
|
-
}
|
|
447
|
-
}
|
|
448
|
-
|
|
449
|
-
private async processAsnFile(fileName: string): Promise<void> {
|
|
450
|
-
this.logger.info(`Processing ASN file: ${fileName}`);
|
|
451
|
-
|
|
452
|
-
// Download file
|
|
453
|
-
const content = await this.sftpSource.downloadFile(fileName, {
|
|
454
|
-
encoding: 'utf8'
|
|
455
|
-
}) as string;
|
|
456
|
-
|
|
457
|
-
// Parse based on format
|
|
458
|
-
let asnDoc: AsnDocument;
|
|
459
|
-
if (config.processing.asnFormat === 'edi856') {
|
|
460
|
-
asnDoc = this.parseEdi856(content);
|
|
461
|
-
} else {
|
|
462
|
-
asnDoc = await this.parseXmlAsn(content);
|
|
463
|
-
}
|
|
464
|
-
|
|
465
|
-
this.logger.info(`Parsed ASN: ${asnDoc.asnNumber}`, {
|
|
466
|
-
containers: asnDoc.containers.length,
|
|
467
|
-
totalQuantity: asnDoc.totalQuantity,
|
|
468
|
-
});
|
|
469
|
-
|
|
470
|
-
// Create expected receipt in Fluent
|
|
471
|
-
const receiptId = await this.createExpectedReceipt(asnDoc);
|
|
472
|
-
this.logger.info(`Created expected receipt: ${receiptId}`);
|
|
473
|
-
|
|
474
|
-
// If actual receipt confirmation is enabled, process it
|
|
475
|
-
if (config.processing.enableActualReceipt) {
|
|
476
|
-
await this.confirmActualReceipt(asnDoc, receiptId);
|
|
477
|
-
}
|
|
478
|
-
|
|
479
|
-
// Mark as processed
|
|
480
|
-
this.stateManager.markFileProcessed(fileName, asnDoc.asnNumber, receiptId);
|
|
481
|
-
|
|
482
|
-
// Archive file
|
|
483
|
-
const archivePath = `${config.sftp.processedPath}/${new Date().toISOString().split('T')[0]}/${fileName}`;
|
|
484
|
-
await this.sftpSource.createDirectory(dirname(archivePath), true);
|
|
485
|
-
await this.sftpSource.moveFile(fileName, archivePath, false);
|
|
486
|
-
|
|
487
|
-
this.logger.info(`ASN file processed successfully: ${fileName}`);
|
|
488
|
-
}
|
|
489
|
-
|
|
490
|
-
private parseEdi856(ediContent: string): AsnDocument {
|
|
491
|
-
// Simplified EDI 856 parser - implement full parser based on your needs
|
|
492
|
-
const segments = ediContent.split('~').map(s => s.trim()).filter(Boolean);
|
|
493
|
-
|
|
494
|
-
const asnDoc: AsnDocument = {
|
|
495
|
-
asnNumber: '',
|
|
496
|
-
shipmentDate: '',
|
|
497
|
-
expectedDeliveryDate: '',
|
|
498
|
-
carrier: '',
|
|
499
|
-
origin: { locationRef: '', name: '' },
|
|
500
|
-
destination: { locationRef: '', name: '' },
|
|
501
|
-
containers: [],
|
|
502
|
-
totalQuantity: 0,
|
|
503
|
-
};
|
|
504
|
-
|
|
505
|
-
for (const segment of segments) {
|
|
506
|
-
const elements = segment.split('*');
|
|
507
|
-
const segmentId = elements[0];
|
|
508
|
-
|
|
509
|
-
if (segmentId === 'BSN') {
|
|
510
|
-
// Beginning Segment for Ship Notice
|
|
511
|
-
asnDoc.asnNumber = elements[2];
|
|
512
|
-
asnDoc.shipmentDate = elements[3];
|
|
513
|
-
} else if (segmentId === 'LIN') {
|
|
514
|
-
// Item Identification
|
|
515
|
-
// Parse item details...
|
|
516
|
-
}
|
|
517
|
-
// Add more segment parsing as needed
|
|
518
|
-
}
|
|
519
|
-
|
|
520
|
-
return asnDoc;
|
|
521
|
-
}
|
|
522
|
-
|
|
523
|
-
private async parseXmlAsn(xmlContent: string): Promise<AsnDocument> {
|
|
524
|
-
const parsed = await this.xmlParser.parse(xmlContent, {
|
|
525
|
-
includeAttributes: true,
|
|
526
|
-
parseNumbers: true,
|
|
527
|
-
});
|
|
528
|
-
|
|
529
|
-
const asn = parsed.asn || parsed.shipmentNotice;
|
|
530
|
-
|
|
531
|
-
return {
|
|
532
|
-
asnNumber: asn['@number'] || asn.asnNumber,
|
|
533
|
-
shipmentDate: asn.shipmentDate || asn['@shipmentDate'],
|
|
534
|
-
expectedDeliveryDate: asn.expectedDeliveryDate,
|
|
535
|
-
carrier: asn.carrier?.name || asn['@carrier'],
|
|
536
|
-
trackingNumber: asn.trackingNumber,
|
|
537
|
-
origin: {
|
|
538
|
-
locationRef: asn.origin?.locationRef || asn.origin?.['@ref'],
|
|
539
|
-
name: asn.origin?.name || '',
|
|
540
|
-
address: asn.origin?.address,
|
|
541
|
-
},
|
|
542
|
-
destination: {
|
|
543
|
-
locationRef: asn.destination?.locationRef || asn.destination?.['@ref'],
|
|
544
|
-
name: asn.destination?.name || '',
|
|
545
|
-
address: asn.destination?.address,
|
|
546
|
-
},
|
|
547
|
-
containers: this.parseContainers(asn.containers?.container || []),
|
|
548
|
-
totalQuantity: parseInt(asn.totalQuantity || '0'),
|
|
549
|
-
};
|
|
550
|
-
}
|
|
551
|
-
|
|
552
|
-
private parseContainers(containers: any[]): Container[] {
|
|
553
|
-
if (!Array.isArray(containers)) {
|
|
554
|
-
containers = [containers];
|
|
555
|
-
}
|
|
556
|
-
|
|
557
|
-
return containers.map(c => ({
|
|
558
|
-
id: c['@id'] || c.containerId,
|
|
559
|
-
type: (c['@type'] || c.type || 'CARTON').toUpperCase() as any,
|
|
560
|
-
sscc: c.sscc || c['@sscc'],
|
|
561
|
-
items: this.parseItems(c.items?.item || []),
|
|
562
|
-
children: c.children ? this.parseContainers(c.children.container) : undefined,
|
|
563
|
-
}));
|
|
564
|
-
}
|
|
565
|
-
|
|
566
|
-
private parseItems(items: any[]): AsnItem[] {
|
|
567
|
-
if (!Array.isArray(items)) {
|
|
568
|
-
items = [items];
|
|
569
|
-
}
|
|
570
|
-
|
|
571
|
-
return items.map(item => ({
|
|
572
|
-
sku: item.sku || item['@sku'],
|
|
573
|
-
productName: item.productName || item.description,
|
|
574
|
-
expectedQuantity: parseInt(item.expectedQuantity || item.quantity || '0'),
|
|
575
|
-
uom: item.uom || 'EA',
|
|
576
|
-
lotNumber: item.lotNumber,
|
|
577
|
-
serialNumbers: item.serialNumbers ?
|
|
578
|
-
(Array.isArray(item.serialNumbers.serial) ?
|
|
579
|
-
item.serialNumbers.serial : [item.serialNumbers.serial]) :
|
|
580
|
-
undefined,
|
|
581
|
-
expiryDate: item.expiryDate,
|
|
582
|
-
}));
|
|
583
|
-
}
|
|
584
|
-
|
|
585
|
-
private async createExpectedReceipt(asnDoc: AsnDocument): Promise<string> {
|
|
586
|
-
const mutation = `
|
|
587
|
-
mutation CreateExpectedReceipt($input: CreateReceiptInput!) {
|
|
588
|
-
createReceipt(input: $input) {
|
|
589
|
-
id
|
|
590
|
-
ref
|
|
591
|
-
status
|
|
592
|
-
type
|
|
593
|
-
}
|
|
594
|
-
}
|
|
595
|
-
`;
|
|
596
|
-
|
|
597
|
-
const variables = {
|
|
598
|
-
input: {
|
|
599
|
-
ref: asnDoc.asnNumber,
|
|
600
|
-
type: 'EXPECTED',
|
|
601
|
-
retailerId: config.fluent.retailerId,
|
|
602
|
-
locationRef: asnDoc.destination.locationRef,
|
|
603
|
-
expectedOn: asnDoc.expectedDeliveryDate,
|
|
604
|
-
carrier: asnDoc.carrier,
|
|
605
|
-
trackingNumber: asnDoc.trackingNumber,
|
|
606
|
-
items: this.flattenItems(asnDoc.containers),
|
|
607
|
-
attributes: {
|
|
608
|
-
asnNumber: asnDoc.asnNumber,
|
|
609
|
-
originLocation: asnDoc.origin.locationRef,
|
|
610
|
-
containerCount: asnDoc.containers.length,
|
|
611
|
-
totalQuantity: asnDoc.totalQuantity.toString(),
|
|
612
|
-
},
|
|
613
|
-
},
|
|
614
|
-
};
|
|
615
|
-
|
|
616
|
-
const result = await this.client.graphql({ query: mutation, variables });
|
|
617
|
-
|
|
618
|
-
if (result.errors) {
|
|
619
|
-
throw new Error(`GraphQL errors: ${JSON.stringify(result.errors)}`);
|
|
620
|
-
}
|
|
621
|
-
|
|
622
|
-
return (result.data as any).createReceipt.id;
|
|
623
|
-
}
|
|
624
|
-
|
|
625
|
-
private flattenItems(containers: Container[]): any[] {
|
|
626
|
-
const items: any[] = [];
|
|
627
|
-
|
|
628
|
-
for (const container of containers) {
|
|
629
|
-
for (const item of container.items) {
|
|
630
|
-
items.push({
|
|
631
|
-
productRef: item.sku,
|
|
632
|
-
expectedQuantity: item.expectedQuantity,
|
|
633
|
-
uom: item.uom,
|
|
634
|
-
lotNumber: item.lotNumber,
|
|
635
|
-
expiryDate: item.expiryDate,
|
|
636
|
-
serialNumbers: item.serialNumbers,
|
|
637
|
-
});
|
|
638
|
-
}
|
|
639
|
-
|
|
640
|
-
if (container.children) {
|
|
641
|
-
items.push(...this.flattenItems(container.children));
|
|
642
|
-
}
|
|
643
|
-
}
|
|
644
|
-
|
|
645
|
-
return items;
|
|
646
|
-
}
|
|
647
|
-
|
|
648
|
-
private async confirmActualReceipt(asnDoc: AsnDocument, receiptId: string): Promise<void> {
|
|
649
|
-
// In a real scenario, actual quantities would come from a separate file
|
|
650
|
-
// or WMS system. For demo, we assume expected = actual with potential variances
|
|
651
|
-
|
|
652
|
-
const mutation = `
|
|
653
|
-
mutation ConfirmReceipt($input: ConfirmReceiptInput!) {
|
|
654
|
-
confirmReceipt(input: $input) {
|
|
655
|
-
id
|
|
656
|
-
status
|
|
657
|
-
variances {
|
|
658
|
-
productRef
|
|
659
|
-
expectedQuantity
|
|
660
|
-
actualQuantity
|
|
661
|
-
variance
|
|
662
|
-
}
|
|
663
|
-
}
|
|
664
|
-
}
|
|
665
|
-
`;
|
|
666
|
-
|
|
667
|
-
const items = this.flattenItems(asnDoc.containers);
|
|
668
|
-
const variances: any[] = [];
|
|
669
|
-
|
|
670
|
-
// Simulate actual quantities (in production, these come from WMS)
|
|
671
|
-
const actualItems = items.map(item => {
|
|
672
|
-
const actualQty = item.expectedQuantity; // Replace with actual scanned quantity
|
|
673
|
-
const variance = actualQty - item.expectedQuantity;
|
|
674
|
-
const variancePercent = Math.abs(variance / item.expectedQuantity);
|
|
675
|
-
|
|
676
|
-
if (variancePercent > config.processing.varianceThreshold) {
|
|
677
|
-
variances.push({
|
|
678
|
-
sku: item.productRef,
|
|
679
|
-
expected: item.expectedQuantity,
|
|
680
|
-
actual: actualQty,
|
|
681
|
-
variance,
|
|
682
|
-
variancePercent,
|
|
683
|
-
});
|
|
684
|
-
}
|
|
685
|
-
|
|
686
|
-
return {
|
|
687
|
-
...item,
|
|
688
|
-
actualQuantity: actualQty,
|
|
689
|
-
};
|
|
690
|
-
});
|
|
691
|
-
|
|
692
|
-
const variables = {
|
|
693
|
-
input: {
|
|
694
|
-
receiptId,
|
|
695
|
-
items: actualItems,
|
|
696
|
-
confirmedBy: 'ASN_PROCESSOR',
|
|
697
|
-
},
|
|
698
|
-
};
|
|
699
|
-
|
|
700
|
-
const result = await this.client.graphql({ query: mutation, variables });
|
|
701
|
-
|
|
702
|
-
if (result.errors) {
|
|
703
|
-
throw new Error(`GraphQL errors: ${JSON.stringify(result.errors)}`);
|
|
704
|
-
}
|
|
705
|
-
|
|
706
|
-
// Send variance alerts if needed
|
|
707
|
-
if (variances.length > 0) {
|
|
708
|
-
this.logger.warn(`Variances detected for ASN ${asnDoc.asnNumber}`, {
|
|
709
|
-
count: variances.length,
|
|
710
|
-
});
|
|
711
|
-
await this.emailAlerter.sendVarianceAlert(asnDoc.asnNumber, variances);
|
|
712
|
-
}
|
|
713
|
-
}
|
|
714
|
-
}
|
|
715
|
-
|
|
716
|
-
// ============================================================================
|
|
717
|
-
// MAIN ENTRY POINT
|
|
718
|
-
// ============================================================================
|
|
719
|
-
|
|
720
|
-
async function main() {
|
|
721
|
-
const processor = new AsnProcessor();
|
|
722
|
-
|
|
723
|
-
try {
|
|
724
|
-
await processor.initialize();
|
|
725
|
-
await processor.processFiles();
|
|
726
|
-
|
|
727
|
-
console.log('ASN processing completed successfully');
|
|
728
|
-
process.exit(0);
|
|
729
|
-
} catch (error: any) {
|
|
730
|
-
console.error('Fatal error:', error);
|
|
731
|
-
process.exit(1);
|
|
732
|
-
}
|
|
733
|
-
}
|
|
734
|
-
|
|
735
|
-
// Run if executed directly
|
|
736
|
-
if (require.main === module) {
|
|
737
|
-
main();
|
|
738
|
-
}
|
|
739
|
-
|
|
740
|
-
export { AsnProcessor };
|
|
741
|
-
```
|
|
742
|
-
|
|
743
|
-
### 4. Key Patterns Explained
|
|
744
|
-
|
|
745
|
-
## Pattern 1: EDI 856 Parsing
|
|
746
|
-
|
|
747
|
-
**Purpose**: Parse X12 EDI 856 format into structured data
|
|
748
|
-
|
|
749
|
-
**Implementation**:
|
|
750
|
-
|
|
751
|
-
```typescript
|
|
752
|
-
class Edi856Parser {
|
|
753
|
-
parse(ediContent: string): AsnDocument {
|
|
754
|
-
// Split into segments (~ delimiter)
|
|
755
|
-
const segments = ediContent.split('~');
|
|
756
|
-
|
|
757
|
-
// Extract header info
|
|
758
|
-
const asnNumber = this.findSegmentValue(segments, 'BSN', 2);
|
|
759
|
-
const poNumber = this.findSegmentValue(segments, 'REF', 2, 'PO');
|
|
760
|
-
|
|
761
|
-
// Parse hierarchical levels (HL segments)
|
|
762
|
-
const containers = this.parseHierarchy(segments);
|
|
763
|
-
|
|
764
|
-
return { asnNumber, poNumber, containers, ... };
|
|
765
|
-
}
|
|
766
|
-
}
|
|
767
|
-
```
|
|
768
|
-
|
|
769
|
-
**Key Points**:
|
|
770
|
-
- EDI segments end with `~` delimiter
|
|
771
|
-
- Fields within segments separated by `*`
|
|
772
|
-
- Hierarchical levels (HL) define container structure
|
|
773
|
-
- Qualifiers identify reference types (PO, BOL, etc.)
|
|
774
|
-
|
|
775
|
-
**EDI 856 Hierarchy**:
|
|
776
|
-
- S = Shipment (top level)
|
|
777
|
-
- O = Order (PO reference)
|
|
778
|
-
- P = Pack (pallet/case)
|
|
779
|
-
- I = Item (SKU)
|
|
780
|
-
|
|
781
|
-
## Pattern 2: Container Hierarchy Mapping
|
|
782
|
-
|
|
783
|
-
**Purpose**: Map nested container structures (shipment → pallet → case → item)
|
|
784
|
-
|
|
785
|
-
**Implementation**:
|
|
786
|
-
|
|
787
|
-
```typescript
|
|
788
|
-
interface AsnContainer {
|
|
789
|
-
containerId: string;
|
|
790
|
-
containerType: 'PALLET' | 'CASE' | 'TOTE';
|
|
791
|
-
parentContainerId?: string; // For nested containers
|
|
792
|
-
items: AsnItem[];
|
|
793
|
-
}
|
|
794
|
-
|
|
795
|
-
// Flatten containers into Fluent format
|
|
796
|
-
const consignmentArticles = asn.containers.flatMap(container =>
|
|
797
|
-
container.items.map(item => ({
|
|
798
|
-
ref: `${asn.asnNumber}-${item.sku}`,
|
|
799
|
-
skuRef: item.sku,
|
|
800
|
-
requestedQuantity: item.quantity,
|
|
801
|
-
containerId: container.containerId,
|
|
802
|
-
containerType: container.containerType,
|
|
803
|
-
}))
|
|
804
|
-
);
|
|
805
|
-
```
|
|
806
|
-
|
|
807
|
-
**Key Points**:
|
|
808
|
-
- Containers can be nested (pallet contains cases)
|
|
809
|
-
- Each item references its container
|
|
810
|
-
- flatMap creates flat array for Fluent API
|
|
811
|
-
- Container IDs track physical units
|
|
812
|
-
|
|
813
|
-
## Pattern 3: Expected vs Actual Receipt Reconciliation
|
|
814
|
-
|
|
815
|
-
**Purpose**: Compare expected quantities with actual received quantities
|
|
816
|
-
|
|
817
|
-
**Implementation**:
|
|
818
|
-
|
|
819
|
-
```typescript
|
|
820
|
-
interface ActualReceipt {
|
|
821
|
-
items: {
|
|
822
|
-
sku: string;
|
|
823
|
-
expectedQty: number;
|
|
824
|
-
receivedQty: number;
|
|
825
|
-
variance: number;
|
|
826
|
-
variancePercent: number;
|
|
827
|
-
damageQty?: number;
|
|
828
|
-
shortQty?: number;
|
|
829
|
-
overQty?: number;
|
|
830
|
-
}[];
|
|
831
|
-
totalVariance: number;
|
|
832
|
-
}
|
|
833
|
-
|
|
834
|
-
// Calculate variance
|
|
835
|
-
const variance = receivedQty - expectedQty;
|
|
836
|
-
const variancePercent = (variance / expectedQty) * 100;
|
|
837
|
-
|
|
838
|
-
// Alert if above threshold
|
|
839
|
-
if (Math.abs(variancePercent) > VARIANCE_THRESHOLD) {
|
|
840
|
-
await sendVarianceAlert(asn, actualReceipt);
|
|
841
|
-
}
|
|
842
|
-
```
|
|
843
|
-
|
|
844
|
-
**Variance Categories**:
|
|
845
|
-
- **Shortage**: receivedQty < expectedQty (negative variance)
|
|
846
|
-
- **Overage**: receivedQty > expectedQty (positive variance)
|
|
847
|
-
- **Damage**: Items received damaged (separate count)
|
|
848
|
-
- **Perfect**: receivedQty === expectedQty (no variance)
|
|
849
|
-
|
|
850
|
-
**Best Practices**:
|
|
851
|
-
- Set reasonable variance threshold (3-5%)
|
|
852
|
-
- Separate damage from shortage
|
|
853
|
-
- Track variance reasons (code required by some WMS)
|
|
854
|
-
- Escalate variances > 10% to management
|
|
855
|
-
|
|
856
|
-
## Pattern 4: Cross-Dock Support
|
|
857
|
-
|
|
858
|
-
**Purpose**: Route shipments directly to customers (bypass warehouse)
|
|
859
|
-
|
|
860
|
-
**Implementation**:
|
|
861
|
-
|
|
862
|
-
```typescript
|
|
863
|
-
// Detect cross-dock from ASN
|
|
864
|
-
const isCrossDock = asn.header.isCrossDock === 'true';
|
|
865
|
-
const customerOrderRef = asn.header.customerOrderRef;
|
|
866
|
-
|
|
867
|
-
// Set destination to customer location
|
|
868
|
-
const destinationLocation = isCrossDock
|
|
869
|
-
? `${CROSS_DOCK_PREFIX}${customerOrderRef}`
|
|
870
|
-
: asn.destinationLocation;
|
|
871
|
-
|
|
872
|
-
// Create consignment with cross-dock flag
|
|
873
|
-
const input = {
|
|
874
|
-
ref: asn.asnNumber,
|
|
875
|
-
destinationLocation: { ref: destinationLocation },
|
|
876
|
-
isCrossDock,
|
|
877
|
-
customerOrderRef,
|
|
878
|
-
...
|
|
879
|
-
};
|
|
880
|
-
```
|
|
881
|
-
|
|
882
|
-
**Cross-Dock Scenarios**:
|
|
883
|
-
1. **Direct-to-Customer**: Bypass warehouse entirely
|
|
884
|
-
2. **Fast-Track**: Minimal warehouse touch (receive → pack → ship)
|
|
885
|
-
3. **Bulk-Break**: Receive pallet, ship cases to multiple customers
|
|
886
|
-
|
|
887
|
-
**Key Points**:
|
|
888
|
-
- Destination location is customer address
|
|
889
|
-
- Skip put-away workflow
|
|
890
|
-
- Immediate allocation to customer order
|
|
891
|
-
- Faster delivery times
|
|
892
|
-
|
|
893
|
-
## Pattern 5: State Management & Deduplication
|
|
894
|
-
|
|
895
|
-
**Purpose**: Prevent duplicate processing of ASN files
|
|
896
|
-
|
|
897
|
-
**Implementation**:
|
|
898
|
-
|
|
899
|
-
```typescript
|
|
900
|
-
class StateManager {
|
|
901
|
-
private state: { processedFiles: Record<string, { hash: string, ... }> };
|
|
902
|
-
|
|
903
|
-
isFileProcessed(fileName: string, fileHash: string): boolean {
|
|
904
|
-
const record = this.state.processedFiles[fileName];
|
|
905
|
-
if (!record) return false;
|
|
906
|
-
|
|
907
|
-
// Reprocess if file content changed
|
|
908
|
-
if (record.hash !== fileHash) return false;
|
|
909
|
-
|
|
910
|
-
return record.status === 'SUCCESS';
|
|
911
|
-
}
|
|
912
|
-
|
|
913
|
-
markFileProcessed(fileName: string, fileHash: string, ...): void {
|
|
914
|
-
this.state.processedFiles[fileName] = {
|
|
915
|
-
hash: fileHash,
|
|
916
|
-
processedAt: new Date().toISOString(),
|
|
917
|
-
status: 'SUCCESS',
|
|
918
|
-
};
|
|
919
|
-
this.saveState();
|
|
920
|
-
}
|
|
921
|
-
}
|
|
922
|
-
```
|
|
923
|
-
|
|
924
|
-
**Key Points**:
|
|
925
|
-
- File name + content hash for deduplication
|
|
926
|
-
- Reprocess if file content changed (hash mismatch)
|
|
927
|
-
- Persist state to JSON file (or Redis/DB)
|
|
928
|
-
- Track success/error status
|
|
929
|
-
|
|
930
|
-
**State Storage Options**:
|
|
931
|
-
- **JSON File**: Simple, local filesystem (this implementation)
|
|
932
|
-
- **Redis**: Distributed, faster lookup
|
|
933
|
-
- **PostgreSQL**: Relational, queryable history
|
|
934
|
-
- **DynamoDB**: AWS-native, serverless
|
|
935
|
-
|
|
936
|
-
---
|
|
937
|
-
|
|
938
|
-
## Testing
|
|
939
|
-
|
|
940
|
-
### Local Testing
|
|
941
|
-
|
|
942
|
-
```bash
|
|
943
|
-
# 1. Upload test EDI file to SFTP
|
|
944
|
-
sftp user@sftp.example.com
|
|
945
|
-
put test-data/sample-asn.edi /inbound/asn/
|
|
946
|
-
|
|
947
|
-
# 2. Run processor
|
|
948
|
-
npm run process
|
|
949
|
-
|
|
950
|
-
# 3. Check logs
|
|
951
|
-
tail -f logs/asn-processor.log
|
|
952
|
-
|
|
953
|
-
# 4. Verify in Fluent Commerce
|
|
954
|
-
# - Check consignment created
|
|
955
|
-
# - Verify expected quantities
|
|
956
|
-
# - Check container references
|
|
957
|
-
```
|
|
958
|
-
|
|
959
|
-
### Integration Testing
|
|
960
|
-
|
|
961
|
-
```bash
|
|
962
|
-
# Test EDI parsing
|
|
963
|
-
npm test -- edi856-parser.test.ts
|
|
964
|
-
|
|
965
|
-
# Test XML parsing
|
|
966
|
-
npm test -- xml-asn-parser.test.ts
|
|
967
|
-
|
|
968
|
-
# Test variance calculation
|
|
969
|
-
npm test -- variance-calculator.test.ts
|
|
970
|
-
|
|
971
|
-
# Test state management
|
|
972
|
-
npm test -- state-manager.test.ts
|
|
973
|
-
```
|
|
974
|
-
|
|
975
|
-
---
|
|
976
|
-
|
|
977
|
-
## Common Issues
|
|
978
|
-
|
|
979
|
-
### Issue 1: EDI Segment Parsing Errors
|
|
980
|
-
|
|
981
|
-
**Symptom**: `Failed to parse EDI 856: Invalid segment`
|
|
982
|
-
|
|
983
|
-
**Cause**: Non-standard EDI format or line ending issues
|
|
984
|
-
|
|
985
|
-
**Solution**:
|
|
986
|
-
|
|
987
|
-
```typescript
|
|
988
|
-
// Normalize line endings before parsing
|
|
989
|
-
const normalizedContent = ediContent
|
|
990
|
-
.replace(/\r\n/g, '\n')
|
|
991
|
-
.replace(/\r/g, '\n')
|
|
992
|
-
.replace(/\n+/g, '~'); // Ensure ~ delimiter
|
|
993
|
-
```
|
|
994
|
-
|
|
995
|
-
### Issue 2: Container Hierarchy Missing
|
|
996
|
-
|
|
997
|
-
**Symptom**: Items not associated with containers
|
|
998
|
-
|
|
999
|
-
**Cause**: XML parser not handling nested structures correctly
|
|
1000
|
-
|
|
1001
|
-
**Solution**:
|
|
1002
|
-
|
|
1003
|
-
```typescript
|
|
1004
|
-
// Force array elements for consistent parsing
|
|
1005
|
-
const parsed = await xmlParser.parse(xmlContent, {
|
|
1006
|
-
includeAttributes: true,
|
|
1007
|
-
arrayElements: ['container', 'item', 'serialNumber'],
|
|
1008
|
-
});
|
|
1009
|
-
```
|
|
1010
|
-
|
|
1011
|
-
### Issue 3: Variance Alerts Not Sending
|
|
1012
|
-
|
|
1013
|
-
**Symptom**: No email alerts despite variances > threshold
|
|
1014
|
-
|
|
1015
|
-
**Cause**: SMTP authentication failure or missing configuration
|
|
1016
|
-
|
|
1017
|
-
**Solution**:
|
|
1018
|
-
|
|
1019
|
-
```bash
|
|
1020
|
-
# Test SMTP connection
|
|
1021
|
-
npm install -g nodemailer-smtp-transport
|
|
1022
|
-
node -e "require('nodemailer').createTransport({host:'smtp.gmail.com',port:587,auth:{user:'test',pass:'test'}}).verify((e,s)=>console.log(e||'OK'))"
|
|
1023
|
-
|
|
1024
|
-
# Enable app-specific password for Gmail
|
|
1025
|
-
# https://myaccount.google.com/apppasswords
|
|
1026
|
-
```
|
|
1027
|
-
|
|
1028
|
-
### Issue 4: Duplicate ASN Processing
|
|
1029
|
-
|
|
1030
|
-
**Symptom**: Same ASN processed multiple times
|
|
1031
|
-
|
|
1032
|
-
**Cause**: State file not persisting or file hash not matching
|
|
1033
|
-
|
|
1034
|
-
**Solution**:
|
|
1035
|
-
|
|
1036
|
-
```typescript
|
|
1037
|
-
// Ensure state directory exists
|
|
1038
|
-
const stateDir = path.dirname(config.state.storagePath);
|
|
1039
|
-
if (!fs.existsSync(stateDir)) {
|
|
1040
|
-
fs.mkdirSync(stateDir, { recursive: true });
|
|
1041
|
-
}
|
|
1042
|
-
|
|
1043
|
-
// Use consistent hash algorithm
|
|
1044
|
-
const fileHash = crypto.createHash('sha256').update(content).digest('hex');
|
|
1045
|
-
```
|
|
1046
|
-
|
|
1047
|
-
### Issue 5: SFTP Connection Timeout
|
|
1048
|
-
|
|
1049
|
-
**Symptom**: `SFTP connection timeout after 30 seconds`
|
|
1050
|
-
|
|
1051
|
-
**Cause**: Firewall blocking port 22 or network latency
|
|
1052
|
-
|
|
1053
|
-
**Solution**:
|
|
1054
|
-
|
|
1055
|
-
```typescript
|
|
1056
|
-
// Increase connection timeout
|
|
1057
|
-
const sftpSource = new SftpDataSource({
|
|
1058
|
-
settings: {
|
|
1059
|
-
...config.sftp,
|
|
1060
|
-
connectionTimeout: 60000, // 60 seconds
|
|
1061
|
-
keepAliveInterval: 10000, // 10 seconds
|
|
1062
|
-
},
|
|
1063
|
-
}, logger);
|
|
1064
|
-
```
|
|
1065
|
-
|
|
1066
|
-
---
|
|
1067
|
-
|
|
1068
|
-
## Sample EDI 856 File
|
|
1069
|
-
|
|
1070
|
-
Create `test-data/sample-asn.edi`:
|
|
1071
|
-
|
|
1072
|
-
```
|
|
1073
|
-
ISA*00* *00* *ZZ*SENDER *ZZ*RECEIVER *240115*1200*U*00401*000000001*0*P*>~
|
|
1074
|
-
GS*SH*SENDER*RECEIVER*20240115*1200*1*X*004010~
|
|
1075
|
-
ST*856*0001~
|
|
1076
|
-
BSN*00*ASN-12345*20240115*120000~
|
|
1077
|
-
HL*1**S~
|
|
1078
|
-
TD5****UPS*GROUND~
|
|
1079
|
-
REF*PO*PO-67890~
|
|
1080
|
-
REF*BM*SHIP-001~
|
|
1081
|
-
DTM*011*20240116~
|
|
1082
|
-
N1*SF*SUPPLIER WAREHOUSE~
|
|
1083
|
-
N1*ST*DESTINATION WAREHOUSE~
|
|
1084
|
-
HL*2*1*O~
|
|
1085
|
-
PRF*PO-67890~
|
|
1086
|
-
HL*3*2*P~
|
|
1087
|
-
MAN*GM*PALLET-001~
|
|
1088
|
-
HL*4*3*I~
|
|
1089
|
-
LIN**SK*SKU-001~
|
|
1090
|
-
SN1**100*EA~
|
|
1091
|
-
HL*5*3*I~
|
|
1092
|
-
LIN**SK*SKU-002~
|
|
1093
|
-
SN1**50*EA~
|
|
1094
|
-
HL*6*2*P~
|
|
1095
|
-
MAN*GM*PALLET-002~
|
|
1096
|
-
HL*7*6*I~
|
|
1097
|
-
LIN**SK*SKU-003~
|
|
1098
|
-
SN1**25*EA~
|
|
1099
|
-
SE*20*0001~
|
|
1100
|
-
GE*1*1~
|
|
1101
|
-
IEA*1*000000001~
|
|
1102
|
-
```
|
|
1103
|
-
|
|
1104
|
-
## Sample XML ASN File
|
|
1105
|
-
|
|
1106
|
-
Create `test-data/sample-asn.xml`:
|
|
1107
|
-
|
|
1108
|
-
```xml
|
|
1109
|
-
<?xml version="1.0" encoding="UTF-8"?>
|
|
1110
|
-
<asn>
|
|
1111
|
-
<header>
|
|
1112
|
-
<asnNumber>ASN-12345</asnNumber>
|
|
1113
|
-
<poNumber>PO-67890</poNumber>
|
|
1114
|
-
<shipmentId>SHIP-001</shipmentId>
|
|
1115
|
-
<carrier>UPS Ground</carrier>
|
|
1116
|
-
<trackingNumber>1Z999AA10123456784</trackingNumber>
|
|
1117
|
-
<scheduledDeliveryDate>2024-01-16T10:00:00Z</scheduledDeliveryDate>
|
|
1118
|
-
<originLocation>SUPPLIER-WAREHOUSE</originLocation>
|
|
1119
|
-
<destinationLocation>DC-EAST</destinationLocation>
|
|
1120
|
-
<isCrossDock>false</isCrossDock>
|
|
1121
|
-
<notes>Handle with care - fragile items</notes>
|
|
1122
|
-
</header>
|
|
1123
|
-
<containers>
|
|
1124
|
-
<container>
|
|
1125
|
-
<containerId>PALLET-001</containerId>
|
|
1126
|
-
<containerType>PALLET</containerType>
|
|
1127
|
-
<items>
|
|
1128
|
-
<item>
|
|
1129
|
-
<sku>SKU-001</sku>
|
|
1130
|
-
<description>Widget A</description>
|
|
1131
|
-
<quantity>100</quantity>
|
|
1132
|
-
<uom>EA</uom>
|
|
1133
|
-
<lotNumber>LOT-2024-001</lotNumber>
|
|
1134
|
-
<expirationDate>2024-12-31</expirationDate>
|
|
1135
|
-
</item>
|
|
1136
|
-
<item>
|
|
1137
|
-
<sku>SKU-002</sku>
|
|
1138
|
-
<description>Widget B</description>
|
|
1139
|
-
<quantity>50</quantity>
|
|
1140
|
-
<uom>EA</uom>
|
|
1141
|
-
<lotNumber>LOT-2024-002</lotNumber>
|
|
1142
|
-
</item>
|
|
1143
|
-
</items>
|
|
1144
|
-
</container>
|
|
1145
|
-
<container>
|
|
1146
|
-
<containerId>PALLET-002</containerId>
|
|
1147
|
-
<containerType>PALLET</containerType>
|
|
1148
|
-
<items>
|
|
1149
|
-
<item>
|
|
1150
|
-
<sku>SKU-003</sku>
|
|
1151
|
-
<description>Gadget X</description>
|
|
1152
|
-
<quantity>25</quantity>
|
|
1153
|
-
<uom>EA</uom>
|
|
1154
|
-
</item>
|
|
1155
|
-
</items>
|
|
1156
|
-
</container>
|
|
1157
|
-
</containers>
|
|
1158
|
-
</asn>
|
|
1159
|
-
```
|
|
1160
|
-
|
|
1161
|
-
---
|
|
1162
|
-
|
|
1163
|
-
## Related Guides
|
|
1164
|
-
|
|
1165
|
-
- **SDK Reference**: `../../readme.md` - Core SDK documentation
|
|
1166
|
-
- **SFTP Data Source**: `../../02-CORE-GUIDES/data-sources/readme.md` - SFTP operations
|
|
1167
|
-
- **XML Parser**: `../../02-CORE-GUIDES/parsers/readme.md` - XML parsing options
|
|
1168
|
-
- **GraphQL Mutation Mapper**: `../../02-CORE-GUIDES/mapping/graphql-mutation-mapping/` - ASN → Fluent mutations
|
|
1169
|
-
- **State Management**: `../../02-CORE-GUIDES/ingestion/modules/07-state-management.md` - Deduplication patterns
|
|
1170
|
-
- **Error Handling**: `../../03-PATTERN-GUIDES/error-handling/readme.md` - Error patterns
|
|
1171
|
-
|
|
1172
|
-
---
|
|
1173
|
-
|
|
1174
|
-
## Production Checklist
|
|
1175
|
-
|
|
1176
|
-
Before deploying to production:
|
|
1177
|
-
|
|
1178
|
-
- [ ] EDI 856 format validated with 3PL provider
|
|
1179
|
-
- [ ] XML schema aligned with WMS export format
|
|
1180
|
-
- [ ] SFTP credentials secured (vault, secrets manager)
|
|
1181
|
-
- [ ] Variance threshold tuned based on historical data
|
|
1182
|
-
- [ ] Email alerts tested with actual SMTP server
|
|
1183
|
-
- [ ] State storage configured for persistence (Redis/DB)
|
|
1184
|
-
- [ ] Monitoring dashboard setup (ASN success rate, avg variance)
|
|
1185
|
-
- [ ] Cross-dock routing logic tested with real orders
|
|
1186
|
-
- [ ] Container hierarchy validated (pallet → case → item)
|
|
1187
|
-
- [ ] Error alerting configured (PagerDuty, Slack)
|
|
1188
|
-
- [ ] Retry logic tested (network failures, API errors)
|
|
1189
|
-
- [ ] SFTP connection pooling configured
|
|
1190
|
-
- [ ] Health check endpoint added
|
|
1191
|
-
- [ ] Disaster recovery tested (reprocess from archive)
|
|
1192
|
-
|
|
1193
|
-
---
|
|
1194
|
-
|
|
1195
|
-
**Next Steps**:
|
|
1196
|
-
|
|
1197
|
-
1. Configure SFTP connection with your 3PL provider
|
|
1198
|
-
2. Obtain sample EDI 856 or XML ASN files
|
|
1199
|
-
3. Customize field mappings for your ASN format
|
|
1200
|
-
4. Test variance calculation with historical data
|
|
1201
|
-
5. Set up monitoring and alerting
|
|
1202
|
-
6. Deploy to cron or Lambda for automated processing
|
|
1203
|
-
|
|
1204
|
-
**Support**:
|
|
1205
|
-
|
|
1206
|
-
- SDK Issues: https://github.com/fluentcommerce/fc-connect-sdk/issues
|
|
1207
|
-
- Fluent Commerce API: https://docs.fluentcommerce.com
|
|
1208
|
-
- EDI 856 Spec: https://www.x12.org/examples/004010/856/
|
|
1209
|
-
- Community: https://community.fluentcommerce.com
|
|
1
|
+
# Standalone: ASN Inbound Processing (WMS/3PL Integration)
|
|
2
|
+
|
|
3
|
+
**FC Connect SDK Use Case Guide**
|
|
4
|
+
|
|
5
|
+
> **SDK**: [@fluentcommerce/fc-connect-sdk](https://www.npmjs.com/package/@fluentcommerce/fc-connect-sdk)
|
|
6
|
+
> **Version**: Use latest - `npm install @fluentcommerce/fc-connect-sdk@latest`
|
|
7
|
+
|
|
8
|
+
**Context**: Node.js script that processes Advanced Ship Notices (ASNs) from WMS/3PL systems via SFTP, creating expected receipts in Fluent Commerce and confirming actual receipts
|
|
9
|
+
|
|
10
|
+
**Complexity**: Medium
|
|
11
|
+
|
|
12
|
+
**Runtime**: Node.js ≥18 / Deno
|
|
13
|
+
|
|
14
|
+
**Estimated Lines**: ~900 lines
|
|
15
|
+
|
|
16
|
+
**Volume**: 10-100 ASNs/day, 100-5000 units per ASN
|
|
17
|
+
|
|
18
|
+
**Latency**: Near real-time (< 5 min)
|
|
19
|
+
|
|
20
|
+
**Pattern**: SFTP polling → EDI/XML parsing → GraphQL mutation
|
|
21
|
+
|
|
22
|
+
## What You'll Build
|
|
23
|
+
|
|
24
|
+
- Standalone Node.js/Deno script (no Versori)
|
|
25
|
+
- SFTP integration for ASN file polling
|
|
26
|
+
- EDI 856 (X12) and XML ASN format parsing
|
|
27
|
+
- Container hierarchy mapping (shipment → pallet → case → item)
|
|
28
|
+
- Expected receipt creation in Fluent Commerce
|
|
29
|
+
- Actual receipt confirmation with variance handling
|
|
30
|
+
- Cross-dock scenario support (direct to customer)
|
|
31
|
+
- Email alerts for significant variances (> 5%)
|
|
32
|
+
- State management to prevent duplicate processing
|
|
33
|
+
- Comprehensive error handling and logging
|
|
34
|
+
|
|
35
|
+
## SDK Methods Used
|
|
36
|
+
|
|
37
|
+
- `createClient({ config: { baseUrl, clientId, clientSecret, retailerId } })` - OAuth2 client
|
|
38
|
+
- `SftpDataSource(config, logger)` - SFTP operations
|
|
39
|
+
- `XMLParserService` - XML ASN parsing
|
|
40
|
+
- `GraphQLMutationMapper` - ASN → Fluent GraphQL mutations
|
|
41
|
+
- `VersoriFileTracker` (adapted for standalone) - Deduplication
|
|
42
|
+
- `client.graphql(payload)` - Execute mutations
|
|
43
|
+
- Custom EDI 856 parser for X12 format
|
|
44
|
+
|
|
45
|
+
---
|
|
46
|
+
|
|
47
|
+
## Complete Working Implementation
|
|
48
|
+
|
|
49
|
+
### 1. Project Setup
|
|
50
|
+
|
|
51
|
+
```bash
|
|
52
|
+
# Create project directory
|
|
53
|
+
mkdir asn-inbound-processor
|
|
54
|
+
cd asn-inbound-processor
|
|
55
|
+
|
|
56
|
+
# Initialize Node.js project
|
|
57
|
+
npm init -y
|
|
58
|
+
|
|
59
|
+
# Install dependencies
|
|
60
|
+
npm install @fluentcommerce/fc-connect-sdk dotenv nodemailer
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
### 2. Environment Configuration
|
|
64
|
+
|
|
65
|
+
Create `.env` file:
|
|
66
|
+
|
|
67
|
+
```env
|
|
68
|
+
# Fluent Commerce Configuration
|
|
69
|
+
FLUENT_BASE_URL=https://api.fluentcommerce.com
|
|
70
|
+
FLUENT_CLIENT_ID=your-oauth2-client-id
|
|
71
|
+
FLUENT_CLIENT_SECRET=your-oauth2-client-secret
|
|
72
|
+
FLUENT_RETAILER_ID=your-retailer-id
|
|
73
|
+
|
|
74
|
+
# SFTP Configuration
|
|
75
|
+
SFTP_HOST=sftp.3pl-provider.com
|
|
76
|
+
SFTP_PORT=22
|
|
77
|
+
SFTP_USERNAME=your-username
|
|
78
|
+
SFTP_PASSWORD=your-password
|
|
79
|
+
# OR use private key authentication
|
|
80
|
+
# SFTP_PRIVATE_KEY=/path/to/private_key
|
|
81
|
+
# SFTP_PASSPHRASE=key-passphrase
|
|
82
|
+
|
|
83
|
+
# SFTP Paths
|
|
84
|
+
SFTP_INBOUND_PATH=/inbound/asn
|
|
85
|
+
SFTP_PROCESSED_PATH=/processed/asn
|
|
86
|
+
SFTP_ERROR_PATH=/errors/asn
|
|
87
|
+
SFTP_FILE_PATTERN=*.{xml,edi,856}
|
|
88
|
+
|
|
89
|
+
# Processing Configuration
|
|
90
|
+
ASN_FORMAT=xml# Options: xml, edi856
|
|
91
|
+
ENABLE_ACTUAL_RECEIPT_CONFIRMATION=true
|
|
92
|
+
VARIANCE_ALERT_THRESHOLD=0.05# 5% threshold for email alerts
|
|
93
|
+
|
|
94
|
+
# Email Alerts (for variances)
|
|
95
|
+
SMTP_HOST=smtp.gmail.com
|
|
96
|
+
SMTP_PORT=587
|
|
97
|
+
SMTP_USER=alerts@yourcompany.com
|
|
98
|
+
SMTP_PASSWORD=your-smtp-password
|
|
99
|
+
ALERT_EMAIL_TO=warehouse-mgr@yourcompany.com
|
|
100
|
+
|
|
101
|
+
# State Management
|
|
102
|
+
STATE_STORAGE_PATH=./data/asn-state.json
|
|
103
|
+
|
|
104
|
+
# Optional: Cross-dock support
|
|
105
|
+
ENABLE_CROSS_DOCK=true
|
|
106
|
+
CROSS_DOCK_LOCATION_PREFIX=CUSTOMER_
|
|
107
|
+
|
|
108
|
+
# Logging
|
|
109
|
+
LOG_LEVEL=info# Options: debug, info, warn, error
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
### 3. Main Script Implementation (src/asn-processor.ts)
|
|
113
|
+
|
|
114
|
+
```typescript
|
|
115
|
+
import 'dotenv/config';
|
|
116
|
+
// FC Connect SDK+
|
|
117
|
+
// Install: npm install @fluentcommerce/fc-connect-sdk@latest
|
|
118
|
+
// Docs: https://www.npmjs.com/package/@fluentcommerce/fc-connect-sdk
|
|
119
|
+
// GitHub: https://github.com/fluentcommerce/fc-connect-sdk
|
|
120
|
+
|
|
121
|
+
import {
|
|
122
|
+
createClient,
|
|
123
|
+
SftpDataSource,
|
|
124
|
+
XMLParserService,
|
|
125
|
+
GraphQLMutationMapper,
|
|
126
|
+
type FluentClient,
|
|
127
|
+
type StructuredLogger,
|
|
128
|
+
createConsoleLogger,
|
|
129
|
+
toStructuredLogger
|
|
130
|
+
} from '@fluentcommerce/fc-connect-sdk';
|
|
131
|
+
|
|
132
|
+
import { readFileSync, writeFileSync, existsSync, mkdirSync } from 'fs';
|
|
133
|
+
import { join, dirname } from 'path';
|
|
134
|
+
import nodemailer from 'nodemailer';
|
|
135
|
+
|
|
136
|
+
// ============================================================================
|
|
137
|
+
// TYPES & INTERFACES
|
|
138
|
+
// ============================================================================
|
|
139
|
+
|
|
140
|
+
interface AsnDocument {
|
|
141
|
+
asnNumber: string;
|
|
142
|
+
shipmentDate: string;
|
|
143
|
+
expectedDeliveryDate: string;
|
|
144
|
+
carrier: string;
|
|
145
|
+
trackingNumber?: string;
|
|
146
|
+
origin: {
|
|
147
|
+
locationRef: string;
|
|
148
|
+
name: string;
|
|
149
|
+
address?: string;
|
|
150
|
+
};
|
|
151
|
+
destination: {
|
|
152
|
+
locationRef: string;
|
|
153
|
+
name: string;
|
|
154
|
+
address?: string;
|
|
155
|
+
};
|
|
156
|
+
containers: Container[];
|
|
157
|
+
totalQuantity: number;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
interface Container {
|
|
161
|
+
id: string;
|
|
162
|
+
type: 'PALLET' | 'CASE' | 'CARTON';
|
|
163
|
+
sscc?: string;
|
|
164
|
+
items: AsnItem[];
|
|
165
|
+
children?: Container[];
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
interface AsnItem {
|
|
169
|
+
sku: string;
|
|
170
|
+
productName?: string;
|
|
171
|
+
expectedQuantity: number;
|
|
172
|
+
actualQuantity?: number;
|
|
173
|
+
uom: string;
|
|
174
|
+
lotNumber?: string;
|
|
175
|
+
serialNumbers?: string[];
|
|
176
|
+
expiryDate?: string;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
interface ProcessingState {
|
|
180
|
+
processedFiles: Record<string, {
|
|
181
|
+
timestamp: string;
|
|
182
|
+
asnNumber: string;
|
|
183
|
+
status: 'success' | 'failed';
|
|
184
|
+
receiptId?: string;
|
|
185
|
+
}>;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
// ============================================================================
|
|
189
|
+
// CONFIGURATION
|
|
190
|
+
// ============================================================================
|
|
191
|
+
|
|
192
|
+
const config = {
|
|
193
|
+
fluent: {
|
|
194
|
+
baseUrl: process.env.FLUENT_BASE_URL!,
|
|
195
|
+
clientId: process.env.FLUENT_CLIENT_ID!,
|
|
196
|
+
clientSecret: process.env.FLUENT_CLIENT_SECRET!,
|
|
197
|
+
retailerId: process.env.FLUENT_RETAILER_ID!,
|
|
198
|
+
},
|
|
199
|
+
sftp: {
|
|
200
|
+
host: process.env.SFTP_HOST!,
|
|
201
|
+
port: parseInt(process.env.SFTP_PORT || '22'),
|
|
202
|
+
username: process.env.SFTP_USERNAME!,
|
|
203
|
+
password: process.env.SFTP_PASSWORD,
|
|
204
|
+
privateKey: process.env.SFTP_PRIVATE_KEY
|
|
205
|
+
? readFileSync(process.env.SFTP_PRIVATE_KEY, 'utf8')
|
|
206
|
+
: undefined,
|
|
207
|
+
passphrase: process.env.SFTP_PASSPHRASE,
|
|
208
|
+
inboundPath: process.env.SFTP_INBOUND_PATH || '/inbound/asn',
|
|
209
|
+
processedPath: process.env.SFTP_PROCESSED_PATH || '/processed/asn',
|
|
210
|
+
errorPath: process.env.SFTP_ERROR_PATH || '/errors/asn',
|
|
211
|
+
filePattern: process.env.SFTP_FILE_PATTERN || '*.{xml,edi,856}',
|
|
212
|
+
},
|
|
213
|
+
processing: {
|
|
214
|
+
asnFormat: process.env.ASN_FORMAT || 'xml',
|
|
215
|
+
enableActualReceipt: process.env.ENABLE_ACTUAL_RECEIPT_CONFIRMATION === 'true',
|
|
216
|
+
varianceThreshold: parseFloat(process.env.VARIANCE_ALERT_THRESHOLD || '0.05'),
|
|
217
|
+
enableCrossDock: process.env.ENABLE_CROSS_DOCK === 'true',
|
|
218
|
+
crossDockLocationPrefix: process.env.CROSS_DOCK_LOCATION_PREFIX || 'CUSTOMER_',
|
|
219
|
+
},
|
|
220
|
+
email: {
|
|
221
|
+
smtpHost: process.env.SMTP_HOST,
|
|
222
|
+
smtpPort: parseInt(process.env.SMTP_PORT || '587'),
|
|
223
|
+
smtpUser: process.env.SMTP_USER,
|
|
224
|
+
smtpPassword: process.env.SMTP_PASSWORD,
|
|
225
|
+
alertEmailTo: process.env.ALERT_EMAIL_TO,
|
|
226
|
+
},
|
|
227
|
+
state: {
|
|
228
|
+
storagePath: process.env.STATE_STORAGE_PATH || './data/asn-state.json',
|
|
229
|
+
},
|
|
230
|
+
};
|
|
231
|
+
|
|
232
|
+
// ============================================================================
|
|
233
|
+
// STATE MANAGEMENT
|
|
234
|
+
// ============================================================================
|
|
235
|
+
|
|
236
|
+
class StateManager {
|
|
237
|
+
private state: ProcessingState;
|
|
238
|
+
private storagePath: string;
|
|
239
|
+
|
|
240
|
+
constructor(storagePath: string) {
|
|
241
|
+
this.storagePath = storagePath;
|
|
242
|
+
this.state = this.loadState();
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
private loadState(): ProcessingState {
|
|
246
|
+
try {
|
|
247
|
+
if (existsSync(this.storagePath)) {
|
|
248
|
+
const data = readFileSync(this.storagePath, 'utf8');
|
|
249
|
+
return JSON.parse(data);
|
|
250
|
+
}
|
|
251
|
+
} catch (error) {
|
|
252
|
+
console.warn('Failed to load state, starting fresh', error);
|
|
253
|
+
}
|
|
254
|
+
return { processedFiles: {} };
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
private saveState(): void {
|
|
258
|
+
try {
|
|
259
|
+
const dir = dirname(this.storagePath);
|
|
260
|
+
if (!existsSync(dir)) {
|
|
261
|
+
mkdirSync(dir, { recursive: true });
|
|
262
|
+
}
|
|
263
|
+
writeFileSync(this.storagePath, JSON.stringify(this.state, null, 2));
|
|
264
|
+
} catch (error) {
|
|
265
|
+
console.error('Failed to save state', error);
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
isFileProcessed(fileName: string): boolean {
|
|
270
|
+
return !!this.state.processedFiles[fileName];
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
markFileProcessed(fileName: string, asnNumber: string, receiptId?: string): void {
|
|
274
|
+
this.state.processedFiles[fileName] = {
|
|
275
|
+
timestamp: new Date().toISOString(),
|
|
276
|
+
asnNumber,
|
|
277
|
+
status: 'success',
|
|
278
|
+
receiptId,
|
|
279
|
+
};
|
|
280
|
+
this.saveState();
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
markFileFailed(fileName: string, asnNumber: string): void {
|
|
284
|
+
this.state.processedFiles[fileName] = {
|
|
285
|
+
timestamp: new Date().toISOString(),
|
|
286
|
+
asnNumber,
|
|
287
|
+
status: 'failed',
|
|
288
|
+
};
|
|
289
|
+
this.saveState();
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
// ============================================================================
|
|
294
|
+
// EMAIL ALERTS
|
|
295
|
+
// ============================================================================
|
|
296
|
+
|
|
297
|
+
class EmailAlerter {
|
|
298
|
+
private transporter: any;
|
|
299
|
+
private enabled: boolean;
|
|
300
|
+
|
|
301
|
+
constructor(config: typeof config.email) {
|
|
302
|
+
this.enabled = !!(config.smtpHost && config.smtpUser && config.alertEmailTo);
|
|
303
|
+
|
|
304
|
+
if (this.enabled) {
|
|
305
|
+
this.transporter = nodemailer.createTransport({
|
|
306
|
+
host: config.smtpHost,
|
|
307
|
+
port: config.smtpPort,
|
|
308
|
+
secure: config.smtpPort === 465,
|
|
309
|
+
auth: {
|
|
310
|
+
user: config.smtpUser,
|
|
311
|
+
pass: config.smtpPassword,
|
|
312
|
+
},
|
|
313
|
+
});
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
async sendVarianceAlert(asnNumber: string, variances: any[]): Promise<void> {
|
|
318
|
+
if (!this.enabled) return;
|
|
319
|
+
|
|
320
|
+
const html = `
|
|
321
|
+
<h2>ASN Variance Alert: ${asnNumber}</h2>
|
|
322
|
+
<p>Significant variances detected during receipt confirmation:</p>
|
|
323
|
+
<table border="1" cellpadding="5">
|
|
324
|
+
<tr>
|
|
325
|
+
<th>SKU</th>
|
|
326
|
+
<th>Expected</th>
|
|
327
|
+
<th>Actual</th>
|
|
328
|
+
<th>Variance</th>
|
|
329
|
+
<th>%</th>
|
|
330
|
+
</tr>
|
|
331
|
+
${variances.map(v => `
|
|
332
|
+
<tr>
|
|
333
|
+
<td>${v.sku}</td>
|
|
334
|
+
<td>${v.expected}</td>
|
|
335
|
+
<td>${v.actual}</td>
|
|
336
|
+
<td style="color: ${v.variance < 0 ? 'red' : 'orange'}">${v.variance > 0 ? '+' : ''}${v.variance}</td>
|
|
337
|
+
<td>${(v.variancePercent * 100).toFixed(1)}%</td>
|
|
338
|
+
</tr>
|
|
339
|
+
`).join('')}
|
|
340
|
+
</table>
|
|
341
|
+
<p><strong>Action Required:</strong> Review and investigate variances.</p>
|
|
342
|
+
`;
|
|
343
|
+
|
|
344
|
+
try {
|
|
345
|
+
await this.transporter.sendMail({
|
|
346
|
+
from: config.email.smtpUser,
|
|
347
|
+
to: config.email.alertEmailTo,
|
|
348
|
+
subject: `⚠️ ASN Variance Alert: ${asnNumber}`,
|
|
349
|
+
html,
|
|
350
|
+
});
|
|
351
|
+
} catch (error) {
|
|
352
|
+
console.error('Failed to send email alert', error);
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
// ============================================================================
|
|
358
|
+
// ASN PROCESSOR
|
|
359
|
+
// ============================================================================
|
|
360
|
+
|
|
361
|
+
class AsnProcessor {
|
|
362
|
+
private client: FluentClient;
|
|
363
|
+
private sftpSource: SftpDataSource;
|
|
364
|
+
private xmlParser: XMLParserService;
|
|
365
|
+
private logger: StructuredLogger;
|
|
366
|
+
private stateManager: StateManager;
|
|
367
|
+
private emailAlerter: EmailAlerter;
|
|
368
|
+
|
|
369
|
+
constructor() {
|
|
370
|
+
this.logger = toStructuredLogger(createConsoleLogger(), { logLevel: 'info' });
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
async initialize(): Promise<void> {
|
|
374
|
+
this.logger.info('Initializing ASN processor...');
|
|
375
|
+
|
|
376
|
+
// Initialize Fluent client
|
|
377
|
+
this.client = await createClient({ config: config.fluent });
|
|
378
|
+
|
|
379
|
+
// Initialize SFTP data source
|
|
380
|
+
this.sftpSource = new SftpDataSource({
|
|
381
|
+
type: 'SFTP_XML',
|
|
382
|
+
connectionId: 'asn-sftp',
|
|
383
|
+
name: 'ASN SFTP',
|
|
384
|
+
settings: {
|
|
385
|
+
host: config.sftp.host,
|
|
386
|
+
port: config.sftp.port,
|
|
387
|
+
username: config.sftp.username,
|
|
388
|
+
password: config.sftp.password,
|
|
389
|
+
privateKey: config.sftp.privateKey,
|
|
390
|
+
passphrase: config.sftp.passphrase,
|
|
391
|
+
},
|
|
392
|
+
}, this.logger);
|
|
393
|
+
|
|
394
|
+
// Initialize XML parser
|
|
395
|
+
this.xmlParser = new XMLParserService();
|
|
396
|
+
|
|
397
|
+
// Initialize state manager
|
|
398
|
+
this.stateManager = new StateManager(config.state.storagePath);
|
|
399
|
+
|
|
400
|
+
// Initialize email alerter
|
|
401
|
+
this.emailAlerter = new EmailAlerter(config.email);
|
|
402
|
+
|
|
403
|
+
// Validate SFTP connection
|
|
404
|
+
const isConnected = await this.sftpSource.validateConnection();
|
|
405
|
+
if (!isConnected) {
|
|
406
|
+
throw new Error('SFTP connection validation failed');
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
this.logger.info('ASN processor initialized successfully');
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
async processFiles(): Promise<void> {
|
|
413
|
+
this.logger.info('Starting ASN file processing cycle');
|
|
414
|
+
|
|
415
|
+
try {
|
|
416
|
+
// List files from SFTP
|
|
417
|
+
const files = await this.sftpSource.listFiles({
|
|
418
|
+
remotePath: config.sftp.inboundPath,
|
|
419
|
+
filePattern: config.sftp.filePattern,
|
|
420
|
+
});
|
|
421
|
+
|
|
422
|
+
this.logger.info(`Found ${files.length} ASN files`);
|
|
423
|
+
|
|
424
|
+
for (const file of files) {
|
|
425
|
+
// Skip already processed files
|
|
426
|
+
if (this.stateManager.isFileProcessed(file.name)) {
|
|
427
|
+
this.logger.debug(`Skipping already processed file: ${file.name}`);
|
|
428
|
+
continue;
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
try {
|
|
432
|
+
await this.processAsnFile(file.name);
|
|
433
|
+
} catch (error: any) {
|
|
434
|
+
this.logger.error(`Failed to process ASN file: ${file.name}`, error);
|
|
435
|
+
|
|
436
|
+
// Move to error folder
|
|
437
|
+
const errorPath = `${config.sftp.errorPath}/${file.name}`;
|
|
438
|
+
await this.sftpSource.moveFile(file.path, errorPath, true); // Use file.path for source (full path)
|
|
439
|
+
}
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
this.logger.info('ASN processing cycle completed');
|
|
443
|
+
} catch (error: any) {
|
|
444
|
+
this.logger.error('ASN processing cycle failed', error);
|
|
445
|
+
throw error;
|
|
446
|
+
}
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
private async processAsnFile(fileName: string): Promise<void> {
|
|
450
|
+
this.logger.info(`Processing ASN file: ${fileName}`);
|
|
451
|
+
|
|
452
|
+
// Download file
|
|
453
|
+
const content = await this.sftpSource.downloadFile(fileName, {
|
|
454
|
+
encoding: 'utf8'
|
|
455
|
+
}) as string;
|
|
456
|
+
|
|
457
|
+
// Parse based on format
|
|
458
|
+
let asnDoc: AsnDocument;
|
|
459
|
+
if (config.processing.asnFormat === 'edi856') {
|
|
460
|
+
asnDoc = this.parseEdi856(content);
|
|
461
|
+
} else {
|
|
462
|
+
asnDoc = await this.parseXmlAsn(content);
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
this.logger.info(`Parsed ASN: ${asnDoc.asnNumber}`, {
|
|
466
|
+
containers: asnDoc.containers.length,
|
|
467
|
+
totalQuantity: asnDoc.totalQuantity,
|
|
468
|
+
});
|
|
469
|
+
|
|
470
|
+
// Create expected receipt in Fluent
|
|
471
|
+
const receiptId = await this.createExpectedReceipt(asnDoc);
|
|
472
|
+
this.logger.info(`Created expected receipt: ${receiptId}`);
|
|
473
|
+
|
|
474
|
+
// If actual receipt confirmation is enabled, process it
|
|
475
|
+
if (config.processing.enableActualReceipt) {
|
|
476
|
+
await this.confirmActualReceipt(asnDoc, receiptId);
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
// Mark as processed
|
|
480
|
+
this.stateManager.markFileProcessed(fileName, asnDoc.asnNumber, receiptId);
|
|
481
|
+
|
|
482
|
+
// Archive file
|
|
483
|
+
const archivePath = `${config.sftp.processedPath}/${new Date().toISOString().split('T')[0]}/${fileName}`;
|
|
484
|
+
await this.sftpSource.createDirectory(dirname(archivePath), true);
|
|
485
|
+
await this.sftpSource.moveFile(fileName, archivePath, false);
|
|
486
|
+
|
|
487
|
+
this.logger.info(`ASN file processed successfully: ${fileName}`);
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
private parseEdi856(ediContent: string): AsnDocument {
|
|
491
|
+
// Simplified EDI 856 parser - implement full parser based on your needs
|
|
492
|
+
const segments = ediContent.split('~').map(s => s.trim()).filter(Boolean);
|
|
493
|
+
|
|
494
|
+
const asnDoc: AsnDocument = {
|
|
495
|
+
asnNumber: '',
|
|
496
|
+
shipmentDate: '',
|
|
497
|
+
expectedDeliveryDate: '',
|
|
498
|
+
carrier: '',
|
|
499
|
+
origin: { locationRef: '', name: '' },
|
|
500
|
+
destination: { locationRef: '', name: '' },
|
|
501
|
+
containers: [],
|
|
502
|
+
totalQuantity: 0,
|
|
503
|
+
};
|
|
504
|
+
|
|
505
|
+
for (const segment of segments) {
|
|
506
|
+
const elements = segment.split('*');
|
|
507
|
+
const segmentId = elements[0];
|
|
508
|
+
|
|
509
|
+
if (segmentId === 'BSN') {
|
|
510
|
+
// Beginning Segment for Ship Notice
|
|
511
|
+
asnDoc.asnNumber = elements[2];
|
|
512
|
+
asnDoc.shipmentDate = elements[3];
|
|
513
|
+
} else if (segmentId === 'LIN') {
|
|
514
|
+
// Item Identification
|
|
515
|
+
// Parse item details...
|
|
516
|
+
}
|
|
517
|
+
// Add more segment parsing as needed
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
return asnDoc;
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
private async parseXmlAsn(xmlContent: string): Promise<AsnDocument> {
|
|
524
|
+
const parsed = await this.xmlParser.parse(xmlContent, {
|
|
525
|
+
includeAttributes: true,
|
|
526
|
+
parseNumbers: true,
|
|
527
|
+
});
|
|
528
|
+
|
|
529
|
+
const asn = parsed.asn || parsed.shipmentNotice;
|
|
530
|
+
|
|
531
|
+
return {
|
|
532
|
+
asnNumber: asn['@number'] || asn.asnNumber,
|
|
533
|
+
shipmentDate: asn.shipmentDate || asn['@shipmentDate'],
|
|
534
|
+
expectedDeliveryDate: asn.expectedDeliveryDate,
|
|
535
|
+
carrier: asn.carrier?.name || asn['@carrier'],
|
|
536
|
+
trackingNumber: asn.trackingNumber,
|
|
537
|
+
origin: {
|
|
538
|
+
locationRef: asn.origin?.locationRef || asn.origin?.['@ref'],
|
|
539
|
+
name: asn.origin?.name || '',
|
|
540
|
+
address: asn.origin?.address,
|
|
541
|
+
},
|
|
542
|
+
destination: {
|
|
543
|
+
locationRef: asn.destination?.locationRef || asn.destination?.['@ref'],
|
|
544
|
+
name: asn.destination?.name || '',
|
|
545
|
+
address: asn.destination?.address,
|
|
546
|
+
},
|
|
547
|
+
containers: this.parseContainers(asn.containers?.container || []),
|
|
548
|
+
totalQuantity: parseInt(asn.totalQuantity || '0'),
|
|
549
|
+
};
|
|
550
|
+
}
|
|
551
|
+
|
|
552
|
+
private parseContainers(containers: any[]): Container[] {
|
|
553
|
+
if (!Array.isArray(containers)) {
|
|
554
|
+
containers = [containers];
|
|
555
|
+
}
|
|
556
|
+
|
|
557
|
+
return containers.map(c => ({
|
|
558
|
+
id: c['@id'] || c.containerId,
|
|
559
|
+
type: (c['@type'] || c.type || 'CARTON').toUpperCase() as any,
|
|
560
|
+
sscc: c.sscc || c['@sscc'],
|
|
561
|
+
items: this.parseItems(c.items?.item || []),
|
|
562
|
+
children: c.children ? this.parseContainers(c.children.container) : undefined,
|
|
563
|
+
}));
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
private parseItems(items: any[]): AsnItem[] {
|
|
567
|
+
if (!Array.isArray(items)) {
|
|
568
|
+
items = [items];
|
|
569
|
+
}
|
|
570
|
+
|
|
571
|
+
return items.map(item => ({
|
|
572
|
+
sku: item.sku || item['@sku'],
|
|
573
|
+
productName: item.productName || item.description,
|
|
574
|
+
expectedQuantity: parseInt(item.expectedQuantity || item.quantity || '0'),
|
|
575
|
+
uom: item.uom || 'EA',
|
|
576
|
+
lotNumber: item.lotNumber,
|
|
577
|
+
serialNumbers: item.serialNumbers ?
|
|
578
|
+
(Array.isArray(item.serialNumbers.serial) ?
|
|
579
|
+
item.serialNumbers.serial : [item.serialNumbers.serial]) :
|
|
580
|
+
undefined,
|
|
581
|
+
expiryDate: item.expiryDate,
|
|
582
|
+
}));
|
|
583
|
+
}
|
|
584
|
+
|
|
585
|
+
private async createExpectedReceipt(asnDoc: AsnDocument): Promise<string> {
|
|
586
|
+
const mutation = `
|
|
587
|
+
mutation CreateExpectedReceipt($input: CreateReceiptInput!) {
|
|
588
|
+
createReceipt(input: $input) {
|
|
589
|
+
id
|
|
590
|
+
ref
|
|
591
|
+
status
|
|
592
|
+
type
|
|
593
|
+
}
|
|
594
|
+
}
|
|
595
|
+
`;
|
|
596
|
+
|
|
597
|
+
const variables = {
|
|
598
|
+
input: {
|
|
599
|
+
ref: asnDoc.asnNumber,
|
|
600
|
+
type: 'EXPECTED',
|
|
601
|
+
retailerId: config.fluent.retailerId,
|
|
602
|
+
locationRef: asnDoc.destination.locationRef,
|
|
603
|
+
expectedOn: asnDoc.expectedDeliveryDate,
|
|
604
|
+
carrier: asnDoc.carrier,
|
|
605
|
+
trackingNumber: asnDoc.trackingNumber,
|
|
606
|
+
items: this.flattenItems(asnDoc.containers),
|
|
607
|
+
attributes: {
|
|
608
|
+
asnNumber: asnDoc.asnNumber,
|
|
609
|
+
originLocation: asnDoc.origin.locationRef,
|
|
610
|
+
containerCount: asnDoc.containers.length,
|
|
611
|
+
totalQuantity: asnDoc.totalQuantity.toString(),
|
|
612
|
+
},
|
|
613
|
+
},
|
|
614
|
+
};
|
|
615
|
+
|
|
616
|
+
const result = await this.client.graphql({ query: mutation, variables });
|
|
617
|
+
|
|
618
|
+
if (result.errors) {
|
|
619
|
+
throw new Error(`GraphQL errors: ${JSON.stringify(result.errors)}`);
|
|
620
|
+
}
|
|
621
|
+
|
|
622
|
+
return (result.data as any).createReceipt.id;
|
|
623
|
+
}
|
|
624
|
+
|
|
625
|
+
private flattenItems(containers: Container[]): any[] {
|
|
626
|
+
const items: any[] = [];
|
|
627
|
+
|
|
628
|
+
for (const container of containers) {
|
|
629
|
+
for (const item of container.items) {
|
|
630
|
+
items.push({
|
|
631
|
+
productRef: item.sku,
|
|
632
|
+
expectedQuantity: item.expectedQuantity,
|
|
633
|
+
uom: item.uom,
|
|
634
|
+
lotNumber: item.lotNumber,
|
|
635
|
+
expiryDate: item.expiryDate,
|
|
636
|
+
serialNumbers: item.serialNumbers,
|
|
637
|
+
});
|
|
638
|
+
}
|
|
639
|
+
|
|
640
|
+
if (container.children) {
|
|
641
|
+
items.push(...this.flattenItems(container.children));
|
|
642
|
+
}
|
|
643
|
+
}
|
|
644
|
+
|
|
645
|
+
return items;
|
|
646
|
+
}
|
|
647
|
+
|
|
648
|
+
private async confirmActualReceipt(asnDoc: AsnDocument, receiptId: string): Promise<void> {
|
|
649
|
+
// In a real scenario, actual quantities would come from a separate file
|
|
650
|
+
// or WMS system. For demo, we assume expected = actual with potential variances
|
|
651
|
+
|
|
652
|
+
const mutation = `
|
|
653
|
+
mutation ConfirmReceipt($input: ConfirmReceiptInput!) {
|
|
654
|
+
confirmReceipt(input: $input) {
|
|
655
|
+
id
|
|
656
|
+
status
|
|
657
|
+
variances {
|
|
658
|
+
productRef
|
|
659
|
+
expectedQuantity
|
|
660
|
+
actualQuantity
|
|
661
|
+
variance
|
|
662
|
+
}
|
|
663
|
+
}
|
|
664
|
+
}
|
|
665
|
+
`;
|
|
666
|
+
|
|
667
|
+
const items = this.flattenItems(asnDoc.containers);
|
|
668
|
+
const variances: any[] = [];
|
|
669
|
+
|
|
670
|
+
// Simulate actual quantities (in production, these come from WMS)
|
|
671
|
+
const actualItems = items.map(item => {
|
|
672
|
+
const actualQty = item.expectedQuantity; // Replace with actual scanned quantity
|
|
673
|
+
const variance = actualQty - item.expectedQuantity;
|
|
674
|
+
const variancePercent = Math.abs(variance / item.expectedQuantity);
|
|
675
|
+
|
|
676
|
+
if (variancePercent > config.processing.varianceThreshold) {
|
|
677
|
+
variances.push({
|
|
678
|
+
sku: item.productRef,
|
|
679
|
+
expected: item.expectedQuantity,
|
|
680
|
+
actual: actualQty,
|
|
681
|
+
variance,
|
|
682
|
+
variancePercent,
|
|
683
|
+
});
|
|
684
|
+
}
|
|
685
|
+
|
|
686
|
+
return {
|
|
687
|
+
...item,
|
|
688
|
+
actualQuantity: actualQty,
|
|
689
|
+
};
|
|
690
|
+
});
|
|
691
|
+
|
|
692
|
+
const variables = {
|
|
693
|
+
input: {
|
|
694
|
+
receiptId,
|
|
695
|
+
items: actualItems,
|
|
696
|
+
confirmedBy: 'ASN_PROCESSOR',
|
|
697
|
+
},
|
|
698
|
+
};
|
|
699
|
+
|
|
700
|
+
const result = await this.client.graphql({ query: mutation, variables });
|
|
701
|
+
|
|
702
|
+
if (result.errors) {
|
|
703
|
+
throw new Error(`GraphQL errors: ${JSON.stringify(result.errors)}`);
|
|
704
|
+
}
|
|
705
|
+
|
|
706
|
+
// Send variance alerts if needed
|
|
707
|
+
if (variances.length > 0) {
|
|
708
|
+
this.logger.warn(`Variances detected for ASN ${asnDoc.asnNumber}`, {
|
|
709
|
+
count: variances.length,
|
|
710
|
+
});
|
|
711
|
+
await this.emailAlerter.sendVarianceAlert(asnDoc.asnNumber, variances);
|
|
712
|
+
}
|
|
713
|
+
}
|
|
714
|
+
}
|
|
715
|
+
|
|
716
|
+
// ============================================================================
|
|
717
|
+
// MAIN ENTRY POINT
|
|
718
|
+
// ============================================================================
|
|
719
|
+
|
|
720
|
+
async function main() {
|
|
721
|
+
const processor = new AsnProcessor();
|
|
722
|
+
|
|
723
|
+
try {
|
|
724
|
+
await processor.initialize();
|
|
725
|
+
await processor.processFiles();
|
|
726
|
+
|
|
727
|
+
console.log('ASN processing completed successfully');
|
|
728
|
+
process.exit(0);
|
|
729
|
+
} catch (error: any) {
|
|
730
|
+
console.error('Fatal error:', error);
|
|
731
|
+
process.exit(1);
|
|
732
|
+
}
|
|
733
|
+
}
|
|
734
|
+
|
|
735
|
+
// Run if executed directly
|
|
736
|
+
if (require.main === module) {
|
|
737
|
+
main();
|
|
738
|
+
}
|
|
739
|
+
|
|
740
|
+
export { AsnProcessor };
|
|
741
|
+
```
|
|
742
|
+
|
|
743
|
+
### 4. Key Patterns Explained
|
|
744
|
+
|
|
745
|
+
## Pattern 1: EDI 856 Parsing
|
|
746
|
+
|
|
747
|
+
**Purpose**: Parse X12 EDI 856 format into structured data
|
|
748
|
+
|
|
749
|
+
**Implementation**:
|
|
750
|
+
|
|
751
|
+
```typescript
|
|
752
|
+
class Edi856Parser {
|
|
753
|
+
parse(ediContent: string): AsnDocument {
|
|
754
|
+
// Split into segments (~ delimiter)
|
|
755
|
+
const segments = ediContent.split('~');
|
|
756
|
+
|
|
757
|
+
// Extract header info
|
|
758
|
+
const asnNumber = this.findSegmentValue(segments, 'BSN', 2);
|
|
759
|
+
const poNumber = this.findSegmentValue(segments, 'REF', 2, 'PO');
|
|
760
|
+
|
|
761
|
+
// Parse hierarchical levels (HL segments)
|
|
762
|
+
const containers = this.parseHierarchy(segments);
|
|
763
|
+
|
|
764
|
+
return { asnNumber, poNumber, containers, ... };
|
|
765
|
+
}
|
|
766
|
+
}
|
|
767
|
+
```
|
|
768
|
+
|
|
769
|
+
**Key Points**:
|
|
770
|
+
- EDI segments end with `~` delimiter
|
|
771
|
+
- Fields within segments separated by `*`
|
|
772
|
+
- Hierarchical levels (HL) define container structure
|
|
773
|
+
- Qualifiers identify reference types (PO, BOL, etc.)
|
|
774
|
+
|
|
775
|
+
**EDI 856 Hierarchy**:
|
|
776
|
+
- S = Shipment (top level)
|
|
777
|
+
- O = Order (PO reference)
|
|
778
|
+
- P = Pack (pallet/case)
|
|
779
|
+
- I = Item (SKU)
|
|
780
|
+
|
|
781
|
+
## Pattern 2: Container Hierarchy Mapping
|
|
782
|
+
|
|
783
|
+
**Purpose**: Map nested container structures (shipment → pallet → case → item)
|
|
784
|
+
|
|
785
|
+
**Implementation**:
|
|
786
|
+
|
|
787
|
+
```typescript
|
|
788
|
+
interface AsnContainer {
|
|
789
|
+
containerId: string;
|
|
790
|
+
containerType: 'PALLET' | 'CASE' | 'TOTE';
|
|
791
|
+
parentContainerId?: string; // For nested containers
|
|
792
|
+
items: AsnItem[];
|
|
793
|
+
}
|
|
794
|
+
|
|
795
|
+
// Flatten containers into Fluent format
|
|
796
|
+
const consignmentArticles = asn.containers.flatMap(container =>
|
|
797
|
+
container.items.map(item => ({
|
|
798
|
+
ref: `${asn.asnNumber}-${item.sku}`,
|
|
799
|
+
skuRef: item.sku,
|
|
800
|
+
requestedQuantity: item.quantity,
|
|
801
|
+
containerId: container.containerId,
|
|
802
|
+
containerType: container.containerType,
|
|
803
|
+
}))
|
|
804
|
+
);
|
|
805
|
+
```
|
|
806
|
+
|
|
807
|
+
**Key Points**:
|
|
808
|
+
- Containers can be nested (pallet contains cases)
|
|
809
|
+
- Each item references its container
|
|
810
|
+
- flatMap creates flat array for Fluent API
|
|
811
|
+
- Container IDs track physical units
|
|
812
|
+
|
|
813
|
+
## Pattern 3: Expected vs Actual Receipt Reconciliation
|
|
814
|
+
|
|
815
|
+
**Purpose**: Compare expected quantities with actual received quantities
|
|
816
|
+
|
|
817
|
+
**Implementation**:
|
|
818
|
+
|
|
819
|
+
```typescript
|
|
820
|
+
interface ActualReceipt {
|
|
821
|
+
items: {
|
|
822
|
+
sku: string;
|
|
823
|
+
expectedQty: number;
|
|
824
|
+
receivedQty: number;
|
|
825
|
+
variance: number;
|
|
826
|
+
variancePercent: number;
|
|
827
|
+
damageQty?: number;
|
|
828
|
+
shortQty?: number;
|
|
829
|
+
overQty?: number;
|
|
830
|
+
}[];
|
|
831
|
+
totalVariance: number;
|
|
832
|
+
}
|
|
833
|
+
|
|
834
|
+
// Calculate variance
|
|
835
|
+
const variance = receivedQty - expectedQty;
|
|
836
|
+
const variancePercent = (variance / expectedQty) * 100;
|
|
837
|
+
|
|
838
|
+
// Alert if above threshold
|
|
839
|
+
if (Math.abs(variancePercent) > VARIANCE_THRESHOLD) {
|
|
840
|
+
await sendVarianceAlert(asn, actualReceipt);
|
|
841
|
+
}
|
|
842
|
+
```
|
|
843
|
+
|
|
844
|
+
**Variance Categories**:
|
|
845
|
+
- **Shortage**: receivedQty < expectedQty (negative variance)
|
|
846
|
+
- **Overage**: receivedQty > expectedQty (positive variance)
|
|
847
|
+
- **Damage**: Items received damaged (separate count)
|
|
848
|
+
- **Perfect**: receivedQty === expectedQty (no variance)
|
|
849
|
+
|
|
850
|
+
**Best Practices**:
|
|
851
|
+
- Set reasonable variance threshold (3-5%)
|
|
852
|
+
- Separate damage from shortage
|
|
853
|
+
- Track variance reasons (code required by some WMS)
|
|
854
|
+
- Escalate variances > 10% to management
|
|
855
|
+
|
|
856
|
+
## Pattern 4: Cross-Dock Support
|
|
857
|
+
|
|
858
|
+
**Purpose**: Route shipments directly to customers (bypass warehouse)
|
|
859
|
+
|
|
860
|
+
**Implementation**:
|
|
861
|
+
|
|
862
|
+
```typescript
|
|
863
|
+
// Detect cross-dock from ASN
|
|
864
|
+
const isCrossDock = asn.header.isCrossDock === 'true';
|
|
865
|
+
const customerOrderRef = asn.header.customerOrderRef;
|
|
866
|
+
|
|
867
|
+
// Set destination to customer location
|
|
868
|
+
const destinationLocation = isCrossDock
|
|
869
|
+
? `${CROSS_DOCK_PREFIX}${customerOrderRef}`
|
|
870
|
+
: asn.destinationLocation;
|
|
871
|
+
|
|
872
|
+
// Create consignment with cross-dock flag
|
|
873
|
+
const input = {
|
|
874
|
+
ref: asn.asnNumber,
|
|
875
|
+
destinationLocation: { ref: destinationLocation },
|
|
876
|
+
isCrossDock,
|
|
877
|
+
customerOrderRef,
|
|
878
|
+
...
|
|
879
|
+
};
|
|
880
|
+
```
|
|
881
|
+
|
|
882
|
+
**Cross-Dock Scenarios**:
|
|
883
|
+
1. **Direct-to-Customer**: Bypass warehouse entirely
|
|
884
|
+
2. **Fast-Track**: Minimal warehouse touch (receive → pack → ship)
|
|
885
|
+
3. **Bulk-Break**: Receive pallet, ship cases to multiple customers
|
|
886
|
+
|
|
887
|
+
**Key Points**:
|
|
888
|
+
- Destination location is customer address
|
|
889
|
+
- Skip put-away workflow
|
|
890
|
+
- Immediate allocation to customer order
|
|
891
|
+
- Faster delivery times
|
|
892
|
+
|
|
893
|
+
## Pattern 5: State Management & Deduplication
|
|
894
|
+
|
|
895
|
+
**Purpose**: Prevent duplicate processing of ASN files
|
|
896
|
+
|
|
897
|
+
**Implementation**:
|
|
898
|
+
|
|
899
|
+
```typescript
|
|
900
|
+
class StateManager {
|
|
901
|
+
private state: { processedFiles: Record<string, { hash: string, ... }> };
|
|
902
|
+
|
|
903
|
+
isFileProcessed(fileName: string, fileHash: string): boolean {
|
|
904
|
+
const record = this.state.processedFiles[fileName];
|
|
905
|
+
if (!record) return false;
|
|
906
|
+
|
|
907
|
+
// Reprocess if file content changed
|
|
908
|
+
if (record.hash !== fileHash) return false;
|
|
909
|
+
|
|
910
|
+
return record.status === 'SUCCESS';
|
|
911
|
+
}
|
|
912
|
+
|
|
913
|
+
markFileProcessed(fileName: string, fileHash: string, ...): void {
|
|
914
|
+
this.state.processedFiles[fileName] = {
|
|
915
|
+
hash: fileHash,
|
|
916
|
+
processedAt: new Date().toISOString(),
|
|
917
|
+
status: 'SUCCESS',
|
|
918
|
+
};
|
|
919
|
+
this.saveState();
|
|
920
|
+
}
|
|
921
|
+
}
|
|
922
|
+
```
|
|
923
|
+
|
|
924
|
+
**Key Points**:
|
|
925
|
+
- File name + content hash for deduplication
|
|
926
|
+
- Reprocess if file content changed (hash mismatch)
|
|
927
|
+
- Persist state to JSON file (or Redis/DB)
|
|
928
|
+
- Track success/error status
|
|
929
|
+
|
|
930
|
+
**State Storage Options**:
|
|
931
|
+
- **JSON File**: Simple, local filesystem (this implementation)
|
|
932
|
+
- **Redis**: Distributed, faster lookup
|
|
933
|
+
- **PostgreSQL**: Relational, queryable history
|
|
934
|
+
- **DynamoDB**: AWS-native, serverless
|
|
935
|
+
|
|
936
|
+
---
|
|
937
|
+
|
|
938
|
+
## Testing
|
|
939
|
+
|
|
940
|
+
### Local Testing
|
|
941
|
+
|
|
942
|
+
```bash
|
|
943
|
+
# 1. Upload test EDI file to SFTP
|
|
944
|
+
sftp user@sftp.example.com
|
|
945
|
+
put test-data/sample-asn.edi /inbound/asn/
|
|
946
|
+
|
|
947
|
+
# 2. Run processor
|
|
948
|
+
npm run process
|
|
949
|
+
|
|
950
|
+
# 3. Check logs
|
|
951
|
+
tail -f logs/asn-processor.log
|
|
952
|
+
|
|
953
|
+
# 4. Verify in Fluent Commerce
|
|
954
|
+
# - Check consignment created
|
|
955
|
+
# - Verify expected quantities
|
|
956
|
+
# - Check container references
|
|
957
|
+
```
|
|
958
|
+
|
|
959
|
+
### Integration Testing
|
|
960
|
+
|
|
961
|
+
```bash
|
|
962
|
+
# Test EDI parsing
|
|
963
|
+
npm test -- edi856-parser.test.ts
|
|
964
|
+
|
|
965
|
+
# Test XML parsing
|
|
966
|
+
npm test -- xml-asn-parser.test.ts
|
|
967
|
+
|
|
968
|
+
# Test variance calculation
|
|
969
|
+
npm test -- variance-calculator.test.ts
|
|
970
|
+
|
|
971
|
+
# Test state management
|
|
972
|
+
npm test -- state-manager.test.ts
|
|
973
|
+
```
|
|
974
|
+
|
|
975
|
+
---
|
|
976
|
+
|
|
977
|
+
## Common Issues
|
|
978
|
+
|
|
979
|
+
### Issue 1: EDI Segment Parsing Errors
|
|
980
|
+
|
|
981
|
+
**Symptom**: `Failed to parse EDI 856: Invalid segment`
|
|
982
|
+
|
|
983
|
+
**Cause**: Non-standard EDI format or line ending issues
|
|
984
|
+
|
|
985
|
+
**Solution**:
|
|
986
|
+
|
|
987
|
+
```typescript
|
|
988
|
+
// Normalize line endings before parsing
|
|
989
|
+
const normalizedContent = ediContent
|
|
990
|
+
.replace(/\r\n/g, '\n')
|
|
991
|
+
.replace(/\r/g, '\n')
|
|
992
|
+
.replace(/\n+/g, '~'); // Ensure ~ delimiter
|
|
993
|
+
```
|
|
994
|
+
|
|
995
|
+
### Issue 2: Container Hierarchy Missing
|
|
996
|
+
|
|
997
|
+
**Symptom**: Items not associated with containers
|
|
998
|
+
|
|
999
|
+
**Cause**: XML parser not handling nested structures correctly
|
|
1000
|
+
|
|
1001
|
+
**Solution**:
|
|
1002
|
+
|
|
1003
|
+
```typescript
|
|
1004
|
+
// Force array elements for consistent parsing
|
|
1005
|
+
const parsed = await xmlParser.parse(xmlContent, {
|
|
1006
|
+
includeAttributes: true,
|
|
1007
|
+
arrayElements: ['container', 'item', 'serialNumber'],
|
|
1008
|
+
});
|
|
1009
|
+
```
|
|
1010
|
+
|
|
1011
|
+
### Issue 3: Variance Alerts Not Sending
|
|
1012
|
+
|
|
1013
|
+
**Symptom**: No email alerts despite variances > threshold
|
|
1014
|
+
|
|
1015
|
+
**Cause**: SMTP authentication failure or missing configuration
|
|
1016
|
+
|
|
1017
|
+
**Solution**:
|
|
1018
|
+
|
|
1019
|
+
```bash
|
|
1020
|
+
# Test SMTP connection
|
|
1021
|
+
npm install -g nodemailer-smtp-transport
|
|
1022
|
+
node -e "require('nodemailer').createTransport({host:'smtp.gmail.com',port:587,auth:{user:'test',pass:'test'}}).verify((e,s)=>console.log(e||'OK'))"
|
|
1023
|
+
|
|
1024
|
+
# Enable app-specific password for Gmail
|
|
1025
|
+
# https://myaccount.google.com/apppasswords
|
|
1026
|
+
```
|
|
1027
|
+
|
|
1028
|
+
### Issue 4: Duplicate ASN Processing
|
|
1029
|
+
|
|
1030
|
+
**Symptom**: Same ASN processed multiple times
|
|
1031
|
+
|
|
1032
|
+
**Cause**: State file not persisting or file hash not matching
|
|
1033
|
+
|
|
1034
|
+
**Solution**:
|
|
1035
|
+
|
|
1036
|
+
```typescript
|
|
1037
|
+
// Ensure state directory exists
|
|
1038
|
+
const stateDir = path.dirname(config.state.storagePath);
|
|
1039
|
+
if (!fs.existsSync(stateDir)) {
|
|
1040
|
+
fs.mkdirSync(stateDir, { recursive: true });
|
|
1041
|
+
}
|
|
1042
|
+
|
|
1043
|
+
// Use consistent hash algorithm
|
|
1044
|
+
const fileHash = crypto.createHash('sha256').update(content).digest('hex');
|
|
1045
|
+
```
|
|
1046
|
+
|
|
1047
|
+
### Issue 5: SFTP Connection Timeout
|
|
1048
|
+
|
|
1049
|
+
**Symptom**: `SFTP connection timeout after 30 seconds`
|
|
1050
|
+
|
|
1051
|
+
**Cause**: Firewall blocking port 22 or network latency
|
|
1052
|
+
|
|
1053
|
+
**Solution**:
|
|
1054
|
+
|
|
1055
|
+
```typescript
|
|
1056
|
+
// Increase connection timeout
|
|
1057
|
+
const sftpSource = new SftpDataSource({
|
|
1058
|
+
settings: {
|
|
1059
|
+
...config.sftp,
|
|
1060
|
+
connectionTimeout: 60000, // 60 seconds
|
|
1061
|
+
keepAliveInterval: 10000, // 10 seconds
|
|
1062
|
+
},
|
|
1063
|
+
}, logger);
|
|
1064
|
+
```
|
|
1065
|
+
|
|
1066
|
+
---
|
|
1067
|
+
|
|
1068
|
+
## Sample EDI 856 File
|
|
1069
|
+
|
|
1070
|
+
Create `test-data/sample-asn.edi`:
|
|
1071
|
+
|
|
1072
|
+
```
|
|
1073
|
+
ISA*00* *00* *ZZ*SENDER *ZZ*RECEIVER *240115*1200*U*00401*000000001*0*P*>~
|
|
1074
|
+
GS*SH*SENDER*RECEIVER*20240115*1200*1*X*004010~
|
|
1075
|
+
ST*856*0001~
|
|
1076
|
+
BSN*00*ASN-12345*20240115*120000~
|
|
1077
|
+
HL*1**S~
|
|
1078
|
+
TD5****UPS*GROUND~
|
|
1079
|
+
REF*PO*PO-67890~
|
|
1080
|
+
REF*BM*SHIP-001~
|
|
1081
|
+
DTM*011*20240116~
|
|
1082
|
+
N1*SF*SUPPLIER WAREHOUSE~
|
|
1083
|
+
N1*ST*DESTINATION WAREHOUSE~
|
|
1084
|
+
HL*2*1*O~
|
|
1085
|
+
PRF*PO-67890~
|
|
1086
|
+
HL*3*2*P~
|
|
1087
|
+
MAN*GM*PALLET-001~
|
|
1088
|
+
HL*4*3*I~
|
|
1089
|
+
LIN**SK*SKU-001~
|
|
1090
|
+
SN1**100*EA~
|
|
1091
|
+
HL*5*3*I~
|
|
1092
|
+
LIN**SK*SKU-002~
|
|
1093
|
+
SN1**50*EA~
|
|
1094
|
+
HL*6*2*P~
|
|
1095
|
+
MAN*GM*PALLET-002~
|
|
1096
|
+
HL*7*6*I~
|
|
1097
|
+
LIN**SK*SKU-003~
|
|
1098
|
+
SN1**25*EA~
|
|
1099
|
+
SE*20*0001~
|
|
1100
|
+
GE*1*1~
|
|
1101
|
+
IEA*1*000000001~
|
|
1102
|
+
```
|
|
1103
|
+
|
|
1104
|
+
## Sample XML ASN File
|
|
1105
|
+
|
|
1106
|
+
Create `test-data/sample-asn.xml`:
|
|
1107
|
+
|
|
1108
|
+
```xml
|
|
1109
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
|
1110
|
+
<asn>
|
|
1111
|
+
<header>
|
|
1112
|
+
<asnNumber>ASN-12345</asnNumber>
|
|
1113
|
+
<poNumber>PO-67890</poNumber>
|
|
1114
|
+
<shipmentId>SHIP-001</shipmentId>
|
|
1115
|
+
<carrier>UPS Ground</carrier>
|
|
1116
|
+
<trackingNumber>1Z999AA10123456784</trackingNumber>
|
|
1117
|
+
<scheduledDeliveryDate>2024-01-16T10:00:00Z</scheduledDeliveryDate>
|
|
1118
|
+
<originLocation>SUPPLIER-WAREHOUSE</originLocation>
|
|
1119
|
+
<destinationLocation>DC-EAST</destinationLocation>
|
|
1120
|
+
<isCrossDock>false</isCrossDock>
|
|
1121
|
+
<notes>Handle with care - fragile items</notes>
|
|
1122
|
+
</header>
|
|
1123
|
+
<containers>
|
|
1124
|
+
<container>
|
|
1125
|
+
<containerId>PALLET-001</containerId>
|
|
1126
|
+
<containerType>PALLET</containerType>
|
|
1127
|
+
<items>
|
|
1128
|
+
<item>
|
|
1129
|
+
<sku>SKU-001</sku>
|
|
1130
|
+
<description>Widget A</description>
|
|
1131
|
+
<quantity>100</quantity>
|
|
1132
|
+
<uom>EA</uom>
|
|
1133
|
+
<lotNumber>LOT-2024-001</lotNumber>
|
|
1134
|
+
<expirationDate>2024-12-31</expirationDate>
|
|
1135
|
+
</item>
|
|
1136
|
+
<item>
|
|
1137
|
+
<sku>SKU-002</sku>
|
|
1138
|
+
<description>Widget B</description>
|
|
1139
|
+
<quantity>50</quantity>
|
|
1140
|
+
<uom>EA</uom>
|
|
1141
|
+
<lotNumber>LOT-2024-002</lotNumber>
|
|
1142
|
+
</item>
|
|
1143
|
+
</items>
|
|
1144
|
+
</container>
|
|
1145
|
+
<container>
|
|
1146
|
+
<containerId>PALLET-002</containerId>
|
|
1147
|
+
<containerType>PALLET</containerType>
|
|
1148
|
+
<items>
|
|
1149
|
+
<item>
|
|
1150
|
+
<sku>SKU-003</sku>
|
|
1151
|
+
<description>Gadget X</description>
|
|
1152
|
+
<quantity>25</quantity>
|
|
1153
|
+
<uom>EA</uom>
|
|
1154
|
+
</item>
|
|
1155
|
+
</items>
|
|
1156
|
+
</container>
|
|
1157
|
+
</containers>
|
|
1158
|
+
</asn>
|
|
1159
|
+
```
|
|
1160
|
+
|
|
1161
|
+
---
|
|
1162
|
+
|
|
1163
|
+
## Related Guides
|
|
1164
|
+
|
|
1165
|
+
- **SDK Reference**: `../../readme.md` - Core SDK documentation
|
|
1166
|
+
- **SFTP Data Source**: `../../02-CORE-GUIDES/data-sources/readme.md` - SFTP operations
|
|
1167
|
+
- **XML Parser**: `../../02-CORE-GUIDES/parsers/readme.md` - XML parsing options
|
|
1168
|
+
- **GraphQL Mutation Mapper**: `../../02-CORE-GUIDES/mapping/graphql-mutation-mapping/` - ASN → Fluent mutations
|
|
1169
|
+
- **State Management**: `../../02-CORE-GUIDES/ingestion/modules/07-state-management.md` - Deduplication patterns
|
|
1170
|
+
- **Error Handling**: `../../03-PATTERN-GUIDES/error-handling/readme.md` - Error patterns
|
|
1171
|
+
|
|
1172
|
+
---
|
|
1173
|
+
|
|
1174
|
+
## Production Checklist
|
|
1175
|
+
|
|
1176
|
+
Before deploying to production:
|
|
1177
|
+
|
|
1178
|
+
- [ ] EDI 856 format validated with 3PL provider
|
|
1179
|
+
- [ ] XML schema aligned with WMS export format
|
|
1180
|
+
- [ ] SFTP credentials secured (vault, secrets manager)
|
|
1181
|
+
- [ ] Variance threshold tuned based on historical data
|
|
1182
|
+
- [ ] Email alerts tested with actual SMTP server
|
|
1183
|
+
- [ ] State storage configured for persistence (Redis/DB)
|
|
1184
|
+
- [ ] Monitoring dashboard setup (ASN success rate, avg variance)
|
|
1185
|
+
- [ ] Cross-dock routing logic tested with real orders
|
|
1186
|
+
- [ ] Container hierarchy validated (pallet → case → item)
|
|
1187
|
+
- [ ] Error alerting configured (PagerDuty, Slack)
|
|
1188
|
+
- [ ] Retry logic tested (network failures, API errors)
|
|
1189
|
+
- [ ] SFTP connection pooling configured
|
|
1190
|
+
- [ ] Health check endpoint added
|
|
1191
|
+
- [ ] Disaster recovery tested (reprocess from archive)
|
|
1192
|
+
|
|
1193
|
+
---
|
|
1194
|
+
|
|
1195
|
+
**Next Steps**:
|
|
1196
|
+
|
|
1197
|
+
1. Configure SFTP connection with your 3PL provider
|
|
1198
|
+
2. Obtain sample EDI 856 or XML ASN files
|
|
1199
|
+
3. Customize field mappings for your ASN format
|
|
1200
|
+
4. Test variance calculation with historical data
|
|
1201
|
+
5. Set up monitoring and alerting
|
|
1202
|
+
6. Deploy to cron or Lambda for automated processing
|
|
1203
|
+
|
|
1204
|
+
**Support**:
|
|
1205
|
+
|
|
1206
|
+
- SDK Issues: https://github.com/fluentcommerce/fc-connect-sdk/issues
|
|
1207
|
+
- Fluent Commerce API: https://docs.fluentcommerce.com
|
|
1208
|
+
- EDI 856 Spec: https://www.x12.org/examples/004010/856/
|
|
1209
|
+
- Community: https://community.fluentcommerce.com
|