public PowerofficeClientTester(ITestOutputHelper output) : base(output)
 {
     // It's safe to use .Result in the tests.
     Client = Task.Run(() => PowerofficeClient.Create(
                           TestTypedEnvironment.PowerofficeApiSettings,
                           TestConfigurations.PowerofficeConfiguration.PowerofficeClientKey)).Result;
 }
Esempio n. 2
0
        /// <summary>Returns the PowerOffice customer code of the organisation that this delivery is associated with. Returns `null` if the code couldn't be found. This can happen if the organisation doesn't exist in PowerOffice.</summary>
        private async Task <long?> GetPowerofficeCustomerCode(
            DeliveryDto webcrmDelivery)
        {
            if (!webcrmDelivery.DeliveryOrganisationId.HasValue)
            {
                throw new ApplicationException("Cannot get PowerOffice organisation ID since DeliveryOrganisationId is null.");
            }

            var webcrmOrganisation = await WebcrmClient.GetOrganisationById(webcrmDelivery.DeliveryOrganisationId.Value);

            long?powerofficeOrganisationId = webcrmOrganisation.GetPowerofficeOrganisationId(Configuration.OrganisationIdFieldName);

            if (!powerofficeOrganisationId.HasValue)
            {
                return(null);
            }

            var powerofficeOrganisation = await PowerofficeClient.GetCustomer(powerofficeOrganisationId.Value);

            if (powerofficeOrganisation == null)
            {
                throw new ApplicationException($"Could not find a PowerOffice organisation with ID {powerofficeOrganisationId.Value}.");
            }

            if (!powerofficeOrganisation.Code.HasValue)
            {
                throw new ApplicationException("PowerOffice organisation code is null.");
            }

            return(powerofficeOrganisation.Code.Value);
        }
Esempio n. 3
0
        // The internal methods are only used by the tests.

        internal async Task CopyOrganisationFromPoweroffice(
            long powerofficeOrganisationId)
        {
            var powerofficeOrganisation = await PowerofficeClient.GetCustomer(powerofficeOrganisationId);

            await CopyOrganisationFromPoweroffice(powerofficeOrganisation);
        }
Esempio n. 4
0
        public async Task CopyOrganisationToPoweroffice(
            OrganisationDto webcrmOrganisation)
        {
            long?powerofficeOrganisationId = webcrmOrganisation.GetPowerofficeOrganisationId(Configuration.OrganisationIdFieldName);

            if (powerofficeOrganisationId == null)
            {
                Logger.LogTrace("Copying organisation to PowerOffice, creating a new one.");
                var newPowerofficeOrganisation     = new NewCustomer(webcrmOrganisation);
                var createdPowerofficeOrganisation = await PowerofficeClient.CreateCustomer(newPowerofficeOrganisation);

                webcrmOrganisation.SetPowerofficeOrganisationId(Configuration.OrganisationIdFieldName, createdPowerofficeOrganisation.Id);
                await WebcrmClient.UpdateOrganisation(webcrmOrganisation);
            }
            else
            {
                var matchingPowerofficeOrganisation = await PowerofficeClient.GetCustomer(powerofficeOrganisationId.Value);

                if (matchingPowerofficeOrganisation == null)
                {
                    Logger.LogWarning($"Could not find PowerOffice organisation (customer) with id {powerofficeOrganisationId.Value}.");
                    return;
                }

                if (!webcrmOrganisation.HasChangesRelevantToPoweroffice(matchingPowerofficeOrganisation, Configuration))
                {
                    Logger.LogTrace("Not copying organisation to PowerOffice because it does not have any relevant changes.");
                    return;
                }

                Logger.LogTrace("Copying organisation to PowerOffice, updating an existing one.");
                matchingPowerofficeOrganisation.Update(webcrmOrganisation);
                await PowerofficeClient.UpdateCustomer(matchingPowerofficeOrganisation);
            }
        }
        private async Task EnqueueUpsertedPersons(
            DateTime upsertedAfterUtc,
            PowerofficeClient powerofficeClient,
            string webcrmSystemId)
        {
            var payloads = new List <UpsertPersonFromPowerofficePayload>();

            var activeOrganisations = await powerofficeClient.GetActiveOrganisations();

            foreach (var organisation in activeOrganisations)
            {
                if (organisation.ContactPersonId == null)
                {
                    continue;
                }

                var upsertedPerson = await powerofficeClient.GetMainContactPersonIfUpserted(organisation.Id, organisation.ContactPersonId.Value, upsertedAfterUtc);

                if (upsertedPerson == null)
                {
                    continue;
                }

                payloads.Add(new UpsertPersonFromPowerofficePayload(organisation.Id, upsertedPerson, webcrmSystemId));
            }

            Logger.LogInformation($"Found {payloads.Count} upserted persons in PowerOffice.");
            await EnqueueActions(PowerofficeQueueAction.UpsertWebcrmPerson, payloads);
        }
Esempio n. 6
0
        internal async Task CopyPersonFromPoweroffice(
            long powerofficeOrganisationId,
            long powerofficePersonId)
        {
            var contactPerson = await PowerofficeClient.GetContactPerson(powerofficeOrganisationId, powerofficePersonId);

            await CopyPersonFromPoweroffice(contactPerson, powerofficeOrganisationId);
        }
Esempio n. 7
0
        private async Task <double> GetVatPercentage(Product powerofficeProduct)
        {
            // If we start getting errors because we are hitting the PowerOffice API too much we could save the VAT percentage in custom field on the webCRM product.
            long salesAccount = await GetSalesAccount(powerofficeProduct);

            var vatCode = (await PowerofficeClient.GetVatCodesBySalesAccount(salesAccount)).FirstOrDefault();

            return(Convert.ToDouble(vatCode?.Rate));
        }
Esempio n. 8
0
 /// <remarks>All the copy methods log a single entry containing the word "copying" and "PowerOffice" so that it is possible to filter them out.</remarks>
 private PowerofficeDataCopier(
     ILogger logger,
     PowerofficeConfiguration configuration,
     PowerofficeClient powerofficeClient,
     WebcrmClient webcrmClient)
 {
     Logger            = logger;
     Configuration     = configuration;
     PowerofficeClient = powerofficeClient;
     WebcrmClient      = webcrmClient;
 }
Esempio n. 9
0
        public async Task CopyDeliveryToPoweroffice(
            DeliveryDto webcrmDelivery,
            List <QuotationLineDto> webcrmDeliveryLines)
        {
            Guid?powerofficeDeliveryId = webcrmDelivery.GetPowerofficeDeliveryId(Configuration.DeliveryIdFieldName);

            if (powerofficeDeliveryId == null)
            {
                long?powerofficeCustomerCode = await GetPowerofficeCustomerCode(webcrmDelivery);

                if (!powerofficeCustomerCode.HasValue)
                {
                    Logger.LogInformation($"Not copying webCRM delivery with ID {webcrmDelivery.DeliveryId} to PowerOffice because the corresponding PowerOffice organisation could not be found.");
                    return;
                }

                Logger.LogTrace("Copying delivery to PowerOffice, creating a new one.");
                var newPowerofficeDelivery     = new NewOutgoingInvoice(webcrmDelivery, webcrmDeliveryLines, powerofficeCustomerCode.Value, Configuration.ProductIdFieldName);
                var createdPowerofficeDelivery = await PowerofficeClient.CreateInvoice(newPowerofficeDelivery);

                webcrmDelivery.SetPowerofficeDeliveryId(Configuration.DeliveryIdFieldName, createdPowerofficeDelivery.Id);
                await WebcrmClient.UpdateDelivery(webcrmDelivery);

                if (createdPowerofficeDelivery.OutgoingInvoiceLines.Count != webcrmDeliveryLines.Count)
                {
                    throw new ApplicationException($"Sanity check failed: Expected the same number of lines in newly created PowerOffice delivery. Lines in webCRM delivery: {webcrmDeliveryLines.Count}. Lines in PowerOffice delivery: {createdPowerofficeDelivery.OutgoingInvoiceLines.Count}.");
                }
            }
            else
            {
                var matchingPowerofficeDelivery = await PowerofficeClient.GetInvoice(powerofficeDeliveryId.Value);

                if (matchingPowerofficeDelivery == null)
                {
                    Logger.LogWarning($"Could not find PowerOffice delivery (invoice) with id {powerofficeDeliveryId.Value}. Not copying the delivery to PowerOffice.");
                    return;
                }

                // Not comparing the two deliveries, since we're only synchronising deliveries one way.

                long?powerofficeCustomerCode = await GetPowerofficeCustomerCode(webcrmDelivery);

                if (!powerofficeCustomerCode.HasValue)
                {
                    Logger.LogInformation($"Not copying webCRM delivery with ID {webcrmDelivery.DeliveryId} to PowerOffice because the corresponding PowerOffice organisation could not be found.");
                    return;
                }

                Logger.LogTrace("Copying delivery to PowerOffice, updating an existing one.");
                matchingPowerofficeDelivery.UpdateIncludingLines(webcrmDelivery, webcrmDeliveryLines, powerofficeCustomerCode.Value, Configuration.ProductCodeFieldName);
                await PowerofficeClient.UpdateInvoice(matchingPowerofficeDelivery);
            }
        }
        private async Task EnqueueUpsertedOrganisations(
            DateTime upsertedAfterUtc,
            PowerofficeClient powerofficeClient,
            string webcrmSystemId)
        {
            var upsertedOrganisations = await powerofficeClient.GetUpsertedOrganisations(upsertedAfterUtc);

            Logger.LogInformation($"Found {upsertedOrganisations.Count} upserted organisations in PowerOffice.");

            var organisationPayloads = upsertedOrganisations
                                       .Select(upsertedOrganisation => new UpsertOrganisationFromPowerofficePayload(upsertedOrganisation, webcrmSystemId));

            await EnqueueActions(PowerofficeQueueAction.UpsertWebcrmOrganisation, organisationPayloads);
        }
        private async Task EnqueueUpsertedDeliveries(
            DateTime upsertedAfterUtc,
            PowerofficeClient powerofficeClient,
            string webcrmSystemId)
        {
            var upsertedDeliveries = await powerofficeClient.GetUpsertedInvoices(upsertedAfterUtc);

            Logger.LogInformation($"Found {upsertedDeliveries.Count} upserted deliveries in PowerOffice.");

            var payloads = upsertedDeliveries
                           .Select(upsertedDelivery => new UpsertDeliveryFromPowerofficePayload(upsertedDelivery, webcrmSystemId));

            await EnqueueActions(PowerofficeQueueAction.UpsertWebcrmDelivery, payloads);
        }
        private async Task EnqueueUpsertedProducts(
            DateTime upsertedAfterUtc,
            PowerofficeClient powerofficeClient,
            string webcrmSystemId)
        {
            var upsertedProducts = await powerofficeClient.GetUpsertedProducts(upsertedAfterUtc);

            Logger.LogInformation($"Found {upsertedProducts.Count} upserted products in PowerOffice.");

            var payloads = upsertedProducts
                           .Select(upsertedProduct => new UpsertProductFromPowerofficePayload(upsertedProduct, webcrmSystemId));

            await EnqueueActions(PowerofficeQueueAction.UpsertWebcrmProduct, payloads);
        }
Esempio n. 13
0
        private async Task <string> GetProductGroupName(long?powerofficeProductGroupId)
        {
            if (!powerofficeProductGroupId.HasValue)
            {
                return(string.Empty);
            }

            var powerofficeProductGroup = await PowerofficeClient.GetProductGroup(powerofficeProductGroupId.Value);

            if (powerofficeProductGroup == null)
            {
                return(string.Empty);
            }

            return(powerofficeProductGroup.Name);
        }
Esempio n. 14
0
        public async Task CopyPersonToPoweroffice(
            PersonDto webcrmPerson)
        {
            var personKey = webcrmPerson.GetPowerofficePersonKey(Logger, Configuration.PersonIdFieldName);

            if (personKey == null)
            {
                long?powerofficeOrganisationId = await GetPowerofficeOrganisationId(webcrmPerson);

                if (powerofficeOrganisationId == null)
                {
                    throw new ApplicationException("Cannot create person in PowerOffice without an associated organisation.");
                }

                Logger.LogTrace("Copying person to PowerOffice, creating a new one.");
                var newPowerofficePerson = new NewContactPerson();
                newPowerofficePerson.Update(webcrmPerson);
                var createdPowerofficePerson = await PowerofficeClient.CreateContactPerson(powerofficeOrganisationId.Value, newPowerofficePerson);

                personKey = new PowerofficePersonKey(powerofficeOrganisationId.Value, createdPowerofficePerson.Id);
                webcrmPerson.SetPowerofficePersonKey(Configuration.PersonIdFieldName, personKey);
                await WebcrmClient.UpdatePerson(webcrmPerson);
            }
            else
            {
                var matchingPowerofficePerson = await PowerofficeClient.GetContactPerson(personKey.PowerofficeOrganisationId, personKey.PowerofficePersonId);

                if (matchingPowerofficePerson == null)
                {
                    Logger.LogWarning($"Could not find PowerOffice person (contact person) with key {personKey}.");
                    return;
                }

                if (!matchingPowerofficePerson.HasRelevantChanges(webcrmPerson))
                {
                    Logger.LogTrace("Not copying person to PowerOffice because it does not have any relevant changes.");
                    return;
                }

                Logger.LogTrace("Copying person to PowerOffice, updating an existing one.");
                matchingPowerofficePerson.Update(webcrmPerson);
                await PowerofficeClient.UpdateContactPerson(personKey.PowerofficeOrganisationId, matchingPowerofficePerson);
            }
        }
Esempio n. 15
0
        private async Task <long> GetSalesAccount(Product powerofficeProduct)
        {
            if (powerofficeProduct.SalesAccount.HasValue)
            {
                return(powerofficeProduct.SalesAccount.Value);
            }

            if (powerofficeProduct.ProductGroupId.HasValue)
            {
                var group = await PowerofficeClient.GetProductGroup(powerofficeProduct.ProductGroupId.Value);

                if (group?.SalesAccount != null)
                {
                    return(group.SalesAccount.Value);
                }
            }

            return(Constants.DefaultPowerofficeSalesAccount);
        }
Esempio n. 16
0
        public async Task CopyDeliveryFromPoweroffice(OutgoingInvoiceWithLines powerofficeDelivery)
        {
            var matchingWebcrmDelivery = await WebcrmClient.GetDeliveryByField(Configuration.DeliveryIdFieldName, powerofficeDelivery.Id.ToString());

            // Creating or updating a delivery in webCRM requires multiple calls to the API. If one of the call fails, the queue message will be retried, and this should make the incorrect state very temporary.
            int webcrmDeliveryId;
            int webcrmOrganisationId;

            if (matchingWebcrmDelivery == null)
            {
                if (!powerofficeDelivery.CustomerCode.HasValue)
                {
                    Logger.LogInformation("The PowerOffice delivery is not associated with a customer, so we cannot create it in webCRM.");
                    return;
                }

                // PowerOffice deliveries are associated with the Code of the organisation and not the Id.
                var webcrmOrganisation = await WebcrmClient.GetOrganisationByField(Configuration.OrganisationCodeFieldName, powerofficeDelivery.CustomerCode.ToString());

                if (webcrmOrganisation == null)
                {
                    var powerofficeOrganisation = await PowerofficeClient.GetCustomerByCode(powerofficeDelivery.CustomerCode.Value);

                    if (powerofficeOrganisation.IsArchived)
                    {
                        Logger.LogInformation("Not copying the delivery to webCRM because the organisation is archived and therefore not synchronised to webCRM.");
                        return;
                    }

                    if (powerofficeOrganisation.IsPerson)
                    {
                        Logger.LogInformation("Not copying the delivery to webCRM because the organisation is a person and therefore not synchronised to webCRM.");
                        return;
                    }

                    throw new ApplicationException($"Not copying the delivery to webCRM because no organisation was found with the PowerOffice code {powerofficeDelivery.CustomerCode}.");
                }

                Logger.LogTrace("Copying delivery from PowerOffice, creating a new one.");

                webcrmOrganisationId = webcrmOrganisation.OrganisationId;

                var newWebcrmDelivery = new DeliveryDto(powerofficeDelivery, webcrmOrganisation.OrganisationId, Configuration);
                webcrmDeliveryId = await WebcrmClient.CreateDelivery(newWebcrmDelivery);
            }
            else
            {
                if (!matchingWebcrmDelivery.DeliveryOrganisationId.HasValue)
                {
                    throw new ApplicationException("Not copying the delivery to webCRM because the matching webCRM delivery does not have a DeliveryOrganisationId.");
                }

                // Not comparing the two deliveries, since we're only synchronising deliveries one way.

                Logger.LogTrace("Copying delivery from PowerOffice, updating an existing one.");

                webcrmOrganisationId = matchingWebcrmDelivery.DeliveryOrganisationId.Value;

                // We cannot delete a delivery through the API, so we delete the associated delivery lines, but modify the delivery.
                var lines = await WebcrmClient.GetQuotationLines(matchingWebcrmDelivery.DeliveryId);

                Logger.LogTrace($"Deleting {lines.Count} associated quotation lines.");
                foreach (var line in lines)
                {
                    await WebcrmClient.DeleteQuotationLine(line.QuotationLineId);
                }

                matchingWebcrmDelivery.Update(powerofficeDelivery, matchingWebcrmDelivery.DeliveryOrganisationId.Value, Configuration);
                await WebcrmClient.UpdateDelivery(matchingWebcrmDelivery);

                webcrmDeliveryId = matchingWebcrmDelivery.DeliveryId;
            }

            // PowerOffice allow adding lines of text on the invoices. We do not synchronise these lines.
            var powerofficeLinesWithProductCode = powerofficeDelivery.OutgoingInvoiceLines
                                                  .Where(line => !string.IsNullOrWhiteSpace(line.ProductCode));

            Logger.LogTrace($"Copying {powerofficeLinesWithProductCode.Count()} quotation lines from PowerOffice.");
            const int millisecondsDelayBetweenCalls = 20;

            foreach (var powerofficeLine in powerofficeLinesWithProductCode)
            {
                var powerofficeProduct = await PowerofficeClient.GetProductByCode(powerofficeLine.ProductCode);

                double vatPercentage = await GetVatPercentage(powerofficeProduct);

                var webcrmLine = new QuotationLineDto(
                    powerofficeLine,
                    powerofficeProduct,
                    webcrmDeliveryId,
                    webcrmOrganisationId,
                    vatPercentage,
                    Configuration);

                await WebcrmClient.CreateQuotationLine(webcrmLine);

                await Task.Delay(millisecondsDelayBetweenCalls);
            }
        }