Ejemplo n.º 1
0
        public void RunAutomatic()
        {
            var batchState = _batchRepository.Retrieve();

            _executionLogService.Log("Refreshing Inventory from Shopify");

            Run(batchState.ShopifyProductsGetEnd);
        }
Ejemplo n.º 2
0
        private void UpsertOrderAndCustomer(Order order)
        {
            var monsterCustomerRecord = _shopifyCustomerPull.UpsertCustomer(order.customer);

            using (var transaction = _orderRepository.BeginTransaction())
            {
                var existingOrder = _orderRepository.RetrieveOrder(order.id);
                if (existingOrder == null)
                {
                    var newOrder = new ShopifyOrder();

                    newOrder.ShopifyOrderId             = order.id;
                    newOrder.ShopifyOrderNumber         = order.name;
                    newOrder.ShopifyTotalPrice          = order.total_price;
                    newOrder.ShopifyFinancialStatus     = order.financial_status;
                    newOrder.ShopifyFulfillmentStatus   = order.fulfillment_status;
                    newOrder.ShopifyIsCancelled         = order.IsCancelled;
                    newOrder.ShopifyAreAllItemsRefunded = order.AreAllLineItemsRefunded;
                    newOrder.ShopifyTotalQuantity       = order.NetOrderedQuantity;

                    newOrder.NeedsOrderPut       = false;
                    newOrder.NeedsTransactionGet = true;
                    newOrder.ErrorCount          = 0;
                    newOrder.Ignore = false;

                    newOrder.CustomerMonsterId = monsterCustomerRecord.MonsterId;
                    newOrder.DateCreated       = DateTime.UtcNow;
                    newOrder.LastUpdated       = DateTime.UtcNow;

                    _executionLogService.Log(LogBuilder.DetectedNewShopifyOrder(newOrder));
                    _orderRepository.InsertOrder(newOrder);
                }
                else
                {
                    existingOrder.ShopifyTotalPrice          = order.total_price;
                    existingOrder.ShopifyFinancialStatus     = order.financial_status;
                    existingOrder.ShopifyFulfillmentStatus   = order.fulfillment_status;
                    existingOrder.ShopifyIsCancelled         = order.IsCancelled;
                    existingOrder.ShopifyAreAllItemsRefunded = order.AreAllLineItemsRefunded;
                    existingOrder.ShopifyTotalQuantity       = order.NetOrderedQuantity;

                    if (existingOrder.StatusChangeDetected(order))
                    {
                        existingOrder.NeedsOrderPut = true;
                    }

                    existingOrder.NeedsTransactionGet = true;
                    existingOrder.LastUpdated         = DateTime.UtcNow;

                    _executionLogService.Log(LogBuilder.DetectedUpdateShopifyOrder(existingOrder));
                    _orderRepository.SaveChanges();
                }

                _shopifyJsonService.Upsert(ShopifyJsonType.Order, order.id, order.SerializeToJson());
                transaction.Commit();
            }
        }
Ejemplo n.º 3
0
        public void StartEndToEndSync(string cron)
        {
            RecurringJob.AddOrUpdate <JobRunner>(
                RecurringEndToEndSyncJobId,
                x => x.TriggerEndToEndSync(_instanceContext.InstanceId),
                cron,
                TimeZoneInfo.Utc);

            _executionLogService.Log("End-to-End Sync - Recurring Job Started ");
        }
Ejemplo n.º 4
0
        public void RunAutomatic()
        {
            var batchState = _batchStateRepository.Retrieve();

            _executionLogService.Log("Refreshing Stock Items from Acumatica");
            RunStockItems(batchState.AcumaticaStockItemGetEnd);

            _executionLogService.Log("Refreshing Inventory Status from Acumatica");
            RunInventoryStatus(batchState.AcumaticaInventoryStatusGetEnd);
        }
Ejemplo n.º 5
0
 private bool TestIsShopifyPutEnabled()
 {
     if (MonsterConfig.Settings.DisableShopifyPut)
     {
         _executionLogService.Log(LogBuilder.ShopifyPutDisabled());
         return(false);
     }
     else
     {
         return(true);
     }
 }
Ejemplo n.º 6
0
        private AcumaticaSalesOrder FindAndUpsertSalesOrderData(SalesOrder order)
        {
            using (var transaction = _orderRepository.BeginTransaction())
            {
                var orderNbr       = order.OrderNbr.value;
                var shopifyOrderId = order.CustomerOrder.value.ToLongAlt(-1);
                var orderRecord    = _orderRepository.RetrieveSalesOrder(orderNbr);

                bool unknownRef = false;

                if (orderRecord == null)
                {
                    // Skip Sales Orders that were not intentionally loaded into Acumatica
                    //
                    orderRecord = _orderRepository.FindSalesOrderByShopifyId(shopifyOrderId);

                    if (orderRecord == null)
                    {
                        return(null);
                    }

                    unknownRef = true;
                }

                if (unknownRef)
                {
                    _executionLogService.Log(
                        LogBuilder.FillingUnknownAcumaticaSalesOrderRef(orderRecord.ShopifyOrder, orderRecord));
                }

                _executionLogService.Log(LogBuilder.DetectedUpdateAcumaticaOrder(orderRecord));

                _acumaticaJsonService.Upsert(
                    AcumaticaJsonType.SalesOrderShipments,
                    orderNbr,
                    SalesOrderType.SO,
                    order.SerializeToJson());

                // Again, to be tested
                //
                // orderRecord.AcumaticaQtyTotal = (int)order.Details.Sum(x => x.OrderQty.value);

                orderRecord.Ingest(order);
                orderRecord.LastUpdated = DateTime.UtcNow;

                _orderRepository.SaveChanges();
                transaction.Commit();

                return(orderRecord);
            }
        }
Ejemplo n.º 7
0
        public void SyncOrdersToAcumatica()
        {
            var settings = _settingsRepository.RetrieveSettings();
            var msg      = $"Starting Order Sync with {settings.MaxParallelAcumaticaSyncs} worker(s)";

            _executionLogService.Log(msg);

            ServicePointManager.DefaultConnectionLimit = 100;
            var queue = _acumaticaOrderSync.BuildOrderPutQueue();

            // This can be abstracted
            var threads = new List <Thread>();

            for (var i = 0; i < settings.MaxParallelAcumaticaSyncs; i++)
            {
                var t = new Thread(() => OrderSyncInChildScope(queue));
                t.Start();
                threads.Add(t);
            }

            foreach (var t in threads)
            {
                t.Join();
            }
        }
Ejemplo n.º 8
0
        private void PullTransactionsFromShopify(ShopifyOrder orderRecord)
        {
            var transactionsJson = _orderApi.RetrieveTransactions(orderRecord.ShopifyOrderId);
            var transactions     = transactionsJson.DeserializeFromJson <TransactionList>();
            var order            = _shopifyJsonService.RetrieveOrder(orderRecord.ShopifyOrderId);

            foreach (var transaction in transactions.transactions)
            {
                var transactionRecord = _orderRepository.RetrieveTransaction(transaction.id);
                if (transactionRecord != null)
                {
                    transactionRecord.LastUpdated = DateTime.UtcNow;
                    _orderRepository.SaveChanges();
                    continue;
                }

                using (var dbTransaction = _orderRepository.BeginTransaction())
                {
                    var record = new ShopifyTransaction();

                    if (transaction.kind == TransactionKind.Refund)
                    {
                        record.IsSyncableToPayment = true;

                        var refund = order.RefundByTransaction(transaction.id);
                        if (refund != null)
                        {
                            record.ShopifyRefundId = refund.id;
                            record.IsPureCancel    = refund.IsPureCancel;
                        }
                    }

                    if (transaction.kind == TransactionKind.Capture || transaction.kind == TransactionKind.Sale)
                    {
                        record.IsSyncableToPayment = true;
                    }

                    record.ShopifyOrderId        = transaction.order_id;
                    record.ShopifyTransactionId  = transaction.id;
                    record.ShopifyStatus         = transaction.status;
                    record.ShopifyKind           = transaction.kind;
                    record.ShopifyGateway        = transaction.gateway;
                    record.ShopifyAmount         = transaction.amount;
                    record.ShopifyOrderMonsterId = orderRecord.MonsterId;
                    record.DateCreated           = DateTime.UtcNow;
                    record.LastUpdated           = DateTime.UtcNow;

                    _logService.Log(LogBuilder.DetectedNewShopifyTransaction(record));
                    _orderRepository.InsertTransaction(record);

                    _shopifyJsonService.Upsert(
                        ShopifyJsonType.Transaction, transaction.id, transaction.SerializeToJson());
                    dbTransaction.Commit();
                }
            }

            orderRecord.NeedsTransactionGet = false;
            _orderRepository.SaveChanges();
        }
Ejemplo n.º 9
0
        public bool DetectCurrentJobInterrupt()
        {
            if (!_currentScopeMonitorId.HasValue)
            {
                throw new Exception("Must invoke IdentifyCurrentJobType() before calling this method");
            }

            if (IsInterrupted(_currentScopeMonitorId.Value))
            {
                _executionLogService.Log(LogBuilder.JobExecutionIsInterrupted());
                return(true);
            }
            else
            {
                return(false);
            }
        }
Ejemplo n.º 10
0
        public AcumaticaCustomer PushCustomer(ShopifyCustomer shopifyRecord)
        {
            if (shopifyRecord.HasMatch())
            {
                // Already matched - push updates to Acumatica
                //
                UpdateCustomerInAcumatica(shopifyRecord);
                return(shopifyRecord.AcumaticaCustomer);
            }
            else
            {
                // No matching record
                //
                var customerInAcumatica = FindAcumaticaCustomer(shopifyRecord);

                if (customerInAcumatica == null)
                {
                    // Unable to locate Customer in Acumatica? Push brand new Customer to Acumatica API
                    //
                    _logService.Log(LogBuilder.CreateAcumaticaCustomer(shopifyRecord));
                    var newCustomer       = BuildCustomer(shopifyRecord);
                    var newCustomerResult = _customerClient.WriteCustomer(newCustomer);

                    // Then create a SQL record thereof
                    //
                    var acumaticaRecord = CreateAcumaticaCustomerRecord(shopifyRecord, newCustomerResult);

                    return(acumaticaRecord);
                }
                else
                {
                    // Found Customer in Acumatica! Create a SQL record for it...
                    //
                    _logService.Log(LogBuilder.AutomatchingCustomers(shopifyRecord, customerInAcumatica));
                    var acumaticaRecord = CreateAcumaticaCustomerRecord(shopifyRecord, customerInAcumatica);

                    // ... and then now push an update from Shopify into Acumatica
                    // TODO - make this update in Acumatica optional
                    //
                    UpdateCustomerInAcumatica(shopifyRecord);

                    return(acumaticaRecord);
                }
            }
        }
Ejemplo n.º 11
0
        public void ReconcileSettingsWithRefData()
        {
            var referenceData = RetrieveRefData();
            var settings      = _settingsRepository.RetrieveSettings();

            if (referenceData.TimeZones.All(x => x.TimeZoneId != settings.AcumaticaTimeZone))
            {
                settings.AcumaticaTimeZone = null;
            }

            if (!referenceData
                .ItemClasses.Any(
                    x => x.ItemClass == settings.AcumaticaDefaultItemClass &&
                    x.PostingClass == settings.AcumaticaDefaultPostingClass))
            {
                settings.AcumaticaDefaultItemClass    = null;
                settings.AcumaticaDefaultPostingClass = null;
            }

            if (settings.AcumaticaTaxableCategory != null &&
                referenceData.TaxCategories.All(x => x != settings.AcumaticaTaxableCategory))
            {
                _logService.Log($"Tax Category {settings.AcumaticaTaxableCategory} is missing from Acumatica");
                settings.AcumaticaTaxableCategory = null;
            }

            if (settings.AcumaticaTaxExemptCategory != null &&
                referenceData.TaxCategories.All(x => x != settings.AcumaticaTaxExemptCategory))
            {
                _logService.Log($"Tax Category {settings.AcumaticaTaxExemptCategory} is missing from Acumatica");
                settings.AcumaticaTaxExemptCategory = null;
            }

            if (settings.AcumaticaTaxZone != null &&
                referenceData.TaxZones.All(x => x != settings.AcumaticaTaxZone))
            {
                _logService.Log($"Tax Zone {settings.AcumaticaTaxZone} is missing from Acumatica");
                settings.AcumaticaTaxZone = null;
            }

            if (settings.AcumaticaCustomerClass != null &&
                referenceData.CustomerClasses.All(x => x != settings.AcumaticaCustomerClass))
            {
                _logService.Log($"Customer Class {settings.AcumaticaCustomerClass} is missing from Acumatica");
                settings.AcumaticaCustomerClass = null;
            }

            _settingsRepository.SaveChanges();
        }
Ejemplo n.º 12
0
        public ActionResult StartEndToEndRecurring(int scheduleId)
        {
            var option = RecurringSchedule.Options.First(x => x.Id == scheduleId);

            _logRepository.Log($"Scheduling Recurring End-to-End Sync - run {option.Desc}");
            _recurringJobService.StartEndToEndSync(option.Cron);

            var settings = _settingsRepository.RetrieveSettings();

            settings.LastRecurringSchedule = scheduleId;
            _settingsRepository.SaveChanges();
            return(JsonNetResult.Success());
        }
Ejemplo n.º 13
0
        public void Run()
        {
            if (MonsterConfig.Settings.DisableShopifyPut)
            {
                _logService.Log(LogBuilder.ShopifyPutDisabled());
                return;
            }

            var salesOrderRefs = _syncOrderRepository.RetrieveUnsyncedSoShipments();

            RunWorker(salesOrderRefs);
        }
Ejemplo n.º 14
0
 public void TestConnection()
 {
     try
     {
         _executionLogService.Log("Connecting to Shopify...");
         _orderApi.RetrieveCount();
         _stateRepository.UpdateSystemState(x => x.ShopifyConnState, StateCode.Ok);
     }
     catch (Exception ex)
     {
         _logger.Error(ex);
         _stateRepository.UpdateSystemState(x => x.ShopifyConnState, StateCode.SystemFault);
     }
 }
Ejemplo n.º 15
0
        public void Run()
        {
            if (MonsterConfig.Settings.DisableShopifyPut)
            {
                _executionLogService.Log(LogBuilder.ShopifyPutDisabled());
                return;
            }

            RunPriceCostWeightUpdates();
            var settings = _settingsRepository.RetrieveSettings();

            if (settings.InventorySyncAvailableQty)
            {
                RunInventoryUpdate();
            }
        }
Ejemplo n.º 16
0
        private int RunStockItemImport(AcumaticaStockItemImportContext context, ShopifyVariant variant)
        {
            var matchingShopifySkus = _syncRepository.RetrieveNonMissingVariants(variant.StandardizedSku());

            if (matchingShopifySkus.Count > 1)
            {
                _logService.Log($"Stock Item Import: {variant.LogDescriptor()} has duplicates in Shopify - aborting");
                return(StockItemPutResult.NoAction);
            }

            // Attempt to Auto-match
            //
            if (variant.IsMatched())
            {
                _logService.Log($"Stock Item Import: {variant.LogDescriptor()} already matched - aborting");
                return(StockItemPutResult.NoAction);
            }

            var stockItem = _syncRepository.RetrieveStockItem(variant.StandardizedSku());

            if (stockItem != null)
            {
                if (stockItem.IsMatched())
                {
                    var msg = $"Stock Item Import: {variant.LogDescriptor()} SKU already synchronized";
                    _logService.Log(msg);
                    return(StockItemPutResult.NoAction);
                }
                else
                {
                    var msg = $"Stock Item Import: auto-matched {stockItem.LogDescriptor()} to {variant.LogDescriptor()}";
                    _logService.Log(msg);

                    _syncRepository.InsertItemSync(variant, stockItem, context.IsSyncEnabled);
                    return(StockItemPutResult.Synchronized);
                }
            }

            // Abort any further processing
            if (context.SynchronizeOnly == true)
            {
                return(StockItemPutResult.NoAction);
            }


            // With neither duplicates or Auto-matching having succeeded,
            // ... we'll create a new Stock Item in Acumatica
            //
            StockItemPush(context, variant);
            context.VariantsForNextInventoryReceipt.Add(variant);
            return(StockItemPutResult.CreatedStockItem);
        }
Ejemplo n.º 17
0
        public void RunAddToProduct(ShopifyAddVariantImportContext context)
        {
            if (MonsterConfig.Settings.DisableShopifyPut)
            {
                _logService.Log(LogBuilder.ShopifyPutDisabled());
                return;
            }

            // Attempt to auto-match Item Ids SKU's that exists
            //
            AutomatchExistingSkus(context);

            // Creates payload for Shopify API only including valid Variants not in Shopify
            //
            var createVariantList    = CleanAndBuildVariantPayload(context.AcumaticaItemIds);
            var shopifyProductRecord = _syncInventoryRepository.RetrieveProduct(context.ShopifyProductId);

            // Add Variants thru Shopify API
            //
            foreach (var createVariant in createVariantList)
            {
                _logService.Log(LogBuilder.CreatingShopifyVariant(createVariant));

                // Invoke Shopify API and create new Variant
                //
                var json = new { variant = createVariant }.SerializeToJson();
                var resultJson = _productApi.AddVariant(context.ShopifyProductId, json);
                var result     = resultJson.DeserializeFromJson <VariantParent>();

                // Create Variant Record and Sync
                //
                var variantRecord =
                    _shopifyInventoryGet.CreateNewVariantRecord(shopifyProductRecord.MonsterId, result.variant);
                CreateSyncRecord(createVariant.sku.StandardizedSku(), variantRecord);
            }

            // Need to refresh local cache of Product, so Inventory Levels records reflect current state
            //
            _shopifyInventoryGet.Run(context.ShopifyProductId);

            foreach (var stockItemId in context.AcumaticaItemIds)
            {
                // Update the Inventory data
                //
                RunInventoryUpdate(stockItemId);
            }
        }
Ejemplo n.º 18
0
        public void RunOrder(long shopifyOrderId)
        {
            try
            {
                CorrectSalesOrderWithUnknownRef(shopifyOrderId);

                // *** SAVE THIS, JONES! - This little branch of logic increases throughput!!
                //
                var orderPreAction = _pendingActionService.Create(shopifyOrderId).OrderAction;
                if (orderPreAction.ActionCode == ActionCode.UpdateInAcumatica && !orderPreAction.IsValid)
                {
                    _acumaticaOrderPaymentPut.ProcessOrder(shopifyOrderId);
                }

                var orderAction = _pendingActionService.Create(shopifyOrderId).OrderAction;
                if (!orderAction.IsValid)
                {
                    _logService.Log(LogBuilder.SkippingInvalidShopifyOrder(shopifyOrderId));
                    return;
                }

                if (orderAction.ActionCode == ActionCode.CreateInAcumatica)
                {
                    CreateSalesOrder(shopifyOrderId);
                    _acumaticaOrderPaymentPut.ProcessOrder(shopifyOrderId);
                    return;
                }

                if (orderAction.ActionCode == ActionCode.UpdateInAcumatica)
                {
                    _acumaticaOrderPaymentPut.ProcessOrder(shopifyOrderId);
                    UpdateExistingSalesOrder(shopifyOrderId);
                    return;
                }

                if (orderAction.ActionCode == ActionCode.CreateBlankSyncRecord)
                {
                    CreateBlankSalesOrderRecord(shopifyOrderId);
                    return;
                }
            }
            catch (Exception ex)
            {
                _systemLogger.Error(ex);
                _logService.Log($"Encountered error syncing Shopify Order {shopifyOrderId}");
                _syncOrderRepository.IncreaseOrderErrorCount(shopifyOrderId);
            }
        }
Ejemplo n.º 19
0
        // Disconnects Warehouses from Deactivated Locations
        //
        public void Run()
        {
            var locations = _repository.RetrieveDeactivatedMatchedLocations();

            foreach (var location in locations)
            {
                // The ToList() allows us to mutate the collection
                foreach (var sync in location.ShopAcuWarehouseSyncs.ToList())
                {
                    using (var transaction = _repository.BeginTransaction())
                    {
                        var log = $"Removed match from Deactivated Location {location.ShopifyLocationName} " +
                                  $"to Warehouse {sync.AcumaticaWarehouse.AcumaticaWarehouseId}";

                        _repository.DeleteWarehouseSync(sync);
                        _executionLogService.Log(log);

                        transaction.Commit();
                    }
                }
            }
        }
Ejemplo n.º 20
0
        private void ExecuteJob(Guid instanceId, Action action, long jobMonitorId)
        {
            _instanceContext.Initialize(instanceId);
            _jobMonitoringService.RegisterCurrentScopeMonitorId(jobMonitorId);
            var monitor = _jobMonitoringService.RetrieveCurrentScopeMonitor();

            try
            {
                if (!InstanceLock.Acquire(instanceId.ToString()))
                {
                    var msg = $"Failed to acquire lock '{InstanceLock.MethodName}'";
                    _executionLogService.Log(msg, LogLevel.Debug);
                    return;
                }

                if (_jobMonitoringService.IsCorrupted(jobMonitorId))
                {
                    var msg = $"Job is missing or corrupted";
                    _executionLogService.Log(msg);
                    _jobMonitoringService.CleanupPostExecution(jobMonitorId);
                    InstanceLock.Free(instanceId.ToString());
                    return;
                }

                if (_jobMonitoringService.IsInterrupted(jobMonitorId))
                {
                    var msg = $"Job is missing or has received stop signal";
                    _executionLogService.Log(msg);
                    _jobMonitoringService.CleanupPostExecution(jobMonitorId);
                    InstanceLock.Free(instanceId.ToString());
                    return;
                }

                // Phew - we made it! Execute the requested task
                //
                _executionLogService.Log(
                    BackgroundJobType.Name[monitor.BackgroundJobType] + " - starting");

                action();

                _jobMonitoringService.CleanupPostExecution(jobMonitorId);

                // *** IMPORTANT - do not refactor this to use-finally, else it will
                // ... break concurrency locking
                //
                InstanceLock.Free(instanceId.ToString());
            }
            catch (Exception ex)
            {
                InstanceLock.Free(instanceId.ToString());

                // If this is One-Time Job, this will remove the Monitor now that the Job has failed
                //
                _jobMonitoringService.CleanupPostExecution(jobMonitorId);

                _executionLogService.Log(
                    BackgroundJobType.Name[monitor.BackgroundJobType] + " - encountered an error");
                _logger.Error(ex);
            }
            finally
            {
                _executionLogService.Log(
                    BackgroundJobType.Name[monitor.BackgroundJobType] + " - finished");
            }
        }
 private void ProcessOrderAux(long shopifyOrderId)
 {
     try
     {
         ProcessOrder(shopifyOrderId);
     }
     catch (Exception ex)
     {
         _systemLogger.Error(ex);
         _logService.Log($"Encounter error syncing Payments for Shopify Order {shopifyOrderId}");
         _syncOrderRepository.IncreaseOrderErrorCount(shopifyOrderId);
     }
 }
Ejemplo n.º 22
0
 public void TestConnection()
 {
     _executionLogService.Log("Testing Acumatica Connection");
     _acumaticaHttpContext.SessionRun(() => { }, throwException: true);
 }