diff --git a/TrafagSalesExporter/SageSpainFinalExportPackage/README.txt b/TrafagSalesExporter/SageSpainFinalExportPackage/README.txt index 97d4173..9fa4850 100644 --- a/TrafagSalesExporter/SageSpainFinalExportPackage/README.txt +++ b/TrafagSalesExporter/SageSpainFinalExportPackage/README.txt @@ -94,6 +94,21 @@ Explicit range: .\Run-SpainExportAndUpload.ps1 -ExportMode Range -DateFilter LineRegistrationDate -FromDate "2026-06-02" -ToDate "2026-06-03" +Simple starter script with default test range 2026-06-01 until 2026-06-04: + +.\Start-SpainRangeExportAndUpload.ps1 + +Same starter script with another range: + +.\Start-SpainRangeExportAndUpload.ps1 -FromDate "2026-06-01" -ToDate "2026-06-04" + +Single-file all-in-one range export and upload. +This file does not require Export-SageSpainSalesCsv.ps1 or Run-SpainExportAndUpload.ps1: + +.\Run-SpainRangeExportAndUpload-AllInOne.ps1 -FromDate "2026-06-01" -ToDate "2026-06-04" + +The all-in-one script checks/creates the SharePoint folder before export, uploads the generated CSV and summary, and verifies that the uploaded files are listed in SharePoint. + Full export and upload: .\Run-SpainExportAndUpload.ps1 -ExportMode Full -Year 2025 diff --git a/TrafagSalesExporter/SageSpainFinalExportPackage/Run-SpainExportAndUpload.ps1 b/TrafagSalesExporter/SageSpainFinalExportPackage/Run-SpainExportAndUpload.ps1 index 7b5f715..b6e8b55 100644 --- a/TrafagSalesExporter/SageSpainFinalExportPackage/Run-SpainExportAndUpload.ps1 +++ b/TrafagSalesExporter/SageSpainFinalExportPackage/Run-SpainExportAndUpload.ps1 @@ -26,6 +26,24 @@ $outputDirectory = Join-Path $BaseDirectory "out" $logDirectory = Join-Path $BaseDirectory "logs" New-Item -ItemType Directory -Force -Path $outputDirectory, $logDirectory | Out-Null +if (-not (Get-Command $RcloneExe -ErrorAction SilentlyContinue)) { + throw "rclone executable not found: $RcloneExe" +} + +$target = "${RcloneRemote}:$RcloneTarget" +$rcloneLog = Join-Path $logDirectory ("rclone-spain-" + (Get-Date -Format "yyyyMMdd") + ".log") + +Write-Host "Checking SharePoint target with rclone: $target" +& $RcloneExe mkdir $target --log-file $rcloneLog --log-level INFO +if ($LASTEXITCODE -ne 0) { + throw "Could not create/check SharePoint target '$target'. rclone exit code $LASTEXITCODE. Log: $rcloneLog" +} + +& $RcloneExe lsf $target --max-depth 1 --log-file $rcloneLog --log-level INFO | Out-Null +if ($LASTEXITCODE -ne 0) { + throw "SharePoint target '$target' is not reachable. rclone exit code $LASTEXITCODE. Log: $rcloneLog" +} + $exportArgs = @( "-ServerInstance", $ServerInstance, "-Database", $Database, @@ -54,18 +72,35 @@ if ($null -eq $latestRun) { throw "No export run directory found in $outputDirectory" } -$rcloneLog = Join-Path $logDirectory ("rclone-spain-" + (Get-Date -Format "yyyyMMdd") + ".log") -$target = "${RcloneRemote}:$RcloneTarget" +$filesToUpload = Get-ChildItem -LiteralPath $latestRun.FullName -File | + Where-Object { $_.Name -like "*.csv" -or $_.Name -like "*_summary.txt" } +if ($filesToUpload.Count -eq 0) { + throw "No CSV or summary files found for upload in $($latestRun.FullName)" +} + +Write-Host "Uploading $($filesToUpload.Count) file(s) to SharePoint target: $target" & $RcloneExe copy $latestRun.FullName $target ` --include "*.csv" ` --include "*_summary.txt" ` + --verbose ` --log-file $rcloneLog ` --log-level INFO if ($LASTEXITCODE -ne 0) { throw "rclone upload failed with exit code $LASTEXITCODE" } +foreach ($file in $filesToUpload) { + $uploadedMatch = & $RcloneExe lsf $target --files-only --include $file.Name --log-file $rcloneLog --log-level INFO + if ($LASTEXITCODE -ne 0) { + throw "Could not verify uploaded file '$($file.Name)' in '$target'. rclone exit code $LASTEXITCODE. Log: $rcloneLog" + } + + if (-not ($uploadedMatch | Where-Object { $_ -eq $file.Name })) { + throw "Upload verification failed. File '$($file.Name)' was not listed in '$target'. Log: $rcloneLog" + } +} + Write-Host "Spain export and upload finished." Write-Host "Local export: $($latestRun.FullName)" Write-Host "SharePoint target: $target" diff --git a/TrafagSalesExporter/SageSpainFinalExportPackage/Run-SpainRangeExportAndUpload-AllInOne.ps1 b/TrafagSalesExporter/SageSpainFinalExportPackage/Run-SpainRangeExportAndUpload-AllInOne.ps1 new file mode 100644 index 0000000..432afa9 --- /dev/null +++ b/TrafagSalesExporter/SageSpainFinalExportPackage/Run-SpainRangeExportAndUpload-AllInOne.ps1 @@ -0,0 +1,303 @@ +param( + [string]$ServerInstance = "localhost", + [string]$Database = "Sage", + [ValidateSet("InvoiceDate", "LineRegistrationDate")] + [string]$DateFilter = "LineRegistrationDate", + [datetime]$FromDate = "2026-06-01", + [datetime]$ToDate = "2026-06-04", + [string]$BaseDirectory = "C:\Trafag\SageSpain", + [string]$RcloneExe = "C:\Tools\rclone.exe", + [string]$RcloneRemote = "trafag-bi", + [string]$RcloneTarget = "Import/Finance/Spanien" +) + +$ErrorActionPreference = "Stop" + +function New-Connection { + $builder = New-Object System.Data.SqlClient.SqlConnectionStringBuilder + $builder["Data Source"] = $ServerInstance + $builder["Initial Catalog"] = $Database + $builder["Integrated Security"] = $true + $builder["TrustServerCertificate"] = $true + $builder["Connect Timeout"] = 15 + return New-Object System.Data.SqlClient.SqlConnection($builder.ConnectionString) +} + +function Convert-ToCsvValue { + param($Value) + + if ($null -eq $Value -or $Value -is [System.DBNull]) { + return "" + } + + if ($Value -is [datetime]) { + $text = $Value.ToString("yyyy-MM-dd HH:mm:ss") + } + else { + $text = [string]$Value + } + + $text = $text.Replace('"', '""') + return '"' + $text + '"' +} + +function Export-QueryToCsv { + param( + [string]$Sql, + [string]$Path + ) + + $conn = New-Connection + $cmd = $conn.CreateCommand() + $cmd.CommandText = $Sql + $cmd.CommandTimeout = 0 + + $fromParameter = $cmd.Parameters.Add("@FromDate", [System.Data.SqlDbType]::Date) + $fromParameter.Value = $FromDate.Date + + $toParameter = $cmd.Parameters.Add("@ToDate", [System.Data.SqlDbType]::Date) + $toParameter.Value = $ToDate.Date + + $writer = New-Object System.IO.StreamWriter($Path, $false, [System.Text.Encoding]::UTF8) + $rowCount = 0 + $salesSum = [decimal]0 + + try { + $conn.Open() + $reader = $cmd.ExecuteReader() + + $headers = for ($i = 0; $i -lt $reader.FieldCount; $i++) { + Convert-ToCsvValue $reader.GetName($i) + } + $writer.WriteLine(($headers -join ";")) + + $salesIndex = -1 + for ($i = 0; $i -lt $reader.FieldCount; $i++) { + if ($reader.GetName($i) -eq "SalesPriceValue") { + $salesIndex = $i + break + } + } + + while ($reader.Read()) { + $values = for ($i = 0; $i -lt $reader.FieldCount; $i++) { + Convert-ToCsvValue $reader.GetValue($i) + } + $writer.WriteLine(($values -join ";")) + $rowCount++ + + if ($salesIndex -ge 0 -and -not $reader.IsDBNull($salesIndex)) { + $salesSum += [decimal]$reader.GetValue($salesIndex) + } + } + } + finally { + $writer.Dispose() + $conn.Dispose() + } + + return [pscustomobject]@{ + Rows = $rowCount + SalesPriceValueSum = $salesSum + } +} + +if ($ToDate.Date -le $FromDate.Date) { + throw "ToDate must be later than FromDate. FromDate=$($FromDate.ToString("yyyy-MM-dd")), ToDate=$($ToDate.ToString("yyyy-MM-dd"))" +} + +if (-not (Test-Path -LiteralPath $RcloneExe)) { + throw "rclone executable not found: $RcloneExe" +} + +$outputDirectory = Join-Path $BaseDirectory "out" +$logDirectory = Join-Path $BaseDirectory "logs" +New-Item -ItemType Directory -Force -Path $outputDirectory, $logDirectory | Out-Null + +$target = "${RcloneRemote}:$RcloneTarget" +$rcloneLog = Join-Path $logDirectory ("rclone-spain-" + (Get-Date -Format "yyyyMMdd") + ".log") + +Write-Host "Checking SharePoint target with rclone: $target" +& $RcloneExe mkdir $target --log-file $rcloneLog --log-level INFO +if ($LASTEXITCODE -ne 0) { + throw "Could not create/check SharePoint target '$target'. rclone exit code $LASTEXITCODE. Log: $rcloneLog" +} + +$targetListing = & $RcloneExe lsf $target --max-depth 1 --log-file $rcloneLog --log-level INFO +if ($LASTEXITCODE -ne 0) { + throw "SharePoint target '$target' is not reachable. rclone exit code $LASTEXITCODE. Log: $rcloneLog" +} +Write-Host "SharePoint target reachable. Existing items: $(@($targetListing).Count)" + +$timestamp = Get-Date -Format "yyyyMMdd_HHmmss" +$runDirectory = Join-Path $outputDirectory "Sage_Spain_Sales_Export_$timestamp" +New-Item -ItemType Directory -Path $runDirectory -Force | Out-Null + +$fromToken = $FromDate.ToString("yyyyMMdd") +$toToken = $ToDate.Date.AddDays(-1).ToString("yyyyMMdd") +$outputFileName = "Spain_Sales_range_${fromToken}_to_${toToken}.csv" +$csvPath = Join-Path $runDirectory $outputFileName +$summaryPath = Join-Path $runDirectory ([System.IO.Path]::GetFileNameWithoutExtension($outputFileName) + "_summary.txt") + +$datePredicate = if ($DateFilter -eq "LineRegistrationDate") { + "COALESCE(l.FechaRegistro, c.FechaFactura) >= @FromDate + AND COALESCE(l.FechaRegistro, c.FechaFactura) < @ToDate" +} else { + "c.FechaFactura >= @FromDate + AND c.FechaFactura < @ToDate" +} + +$sql = @" +SELECT + 'TRES' AS TSC, + 'Spanien' AS Land, + 'Sage' AS SourceSystem, + c.CodigoEmpresa AS CompanyCode, + c.EjercicioAlbaran AS DeliveryYear, + c.SerieAlbaran AS DeliverySeries, + c.NumeroAlbaran AS DeliveryNumber, + c.EjercicioFactura AS InvoiceYear, + c.SerieFactura AS InvoiceSeries, + c.NumeroFactura AS InvoiceNumber, + l.Orden AS PositionOnInvoice, + l.LineasPosicion AS SourceLineId, + l.CodigoArticulo AS Material, + l.DescripcionArticulo AS Name, + l.Descripcion2Articulo AS Description2, + l.DescripcionLinea AS DescriptionLine, + l.CodigoFamilia AS ProductGroup, + l.CodigoSubfamilia AS ProductSubGroup, + CAST(l.Unidades AS decimal(19, 6)) AS Quantity, + c.CodigoCliente AS CustomerNumber, + c.Nombre AS CustomerName, + c.CodigoNacion AS CustomerCountryCode, + c.Nacion AS CustomerCountry, + CAST(l.PrecioCoste AS decimal(19, 6)) AS StandardCost, + CAST(l.ImporteCoste AS decimal(19, 6)) AS StandardCostValue, + 'EUR' AS StandardCostCurrency, + CAST(CASE + WHEN c.TipoNuevaFra = 2 OR c.SerieFactura = 'REC' OR c.StatusAbono <> 0 THEN -ABS(l.ImporteNeto) + ELSE l.ImporteNeto + END AS decimal(19, 6)) AS SalesPriceValue, + 'EUR' AS SalesCurrency, + 'EUR' AS DocumentCurrency, + 'EUR' AS CompanyCurrency, + c.CodigoDivisa AS SageCurrencyCode, + CAST(CASE + WHEN c.TipoNuevaFra = 2 OR c.SerieFactura = 'REC' OR c.StatusAbono <> 0 THEN -ABS(c.BaseImponible) + ELSE c.BaseImponible + END AS decimal(19, 6)) AS DocumentNetAmount, + CAST(c.TotalIva AS decimal(19, 6)) AS DocumentVatAmount, + CAST(c.ImporteFactura AS decimal(19, 6)) AS DocumentGrossAmount, + c.FechaFactura AS InvoiceDate, + c.FechaAlbaran AS DeliveryDate, + l.FechaRegistro AS LineRegistrationDate, + c.EjercicioPedido AS OrderYear, + c.SeriePedido AS OrderSeries, + c.NumeroPedido AS OrderNumber, + c.SuPedido AS PurchaseOrderNumber, + c.CodigoExportacion_ AS Incoterms2020, + c.CondicionExportacion_ AS IncotermsText, + c.CodigoComisionista AS SalesResponsibleEmployee, + c.StatusAbono AS CreditStatus, + c.NoFacturable AS NonBillable, + c.TipoNuevaFra AS InvoiceType, + c.StatusFacturado AS BillingStatus, + CASE + WHEN c.TipoNuevaFra = 2 OR c.SerieFactura = 'REC' OR c.StatusAbono <> 0 THEN 'Credit Note' + ELSE 'Invoice' + END AS DocumentType +FROM dbo.CabeceraAlbaranCliente c +JOIN dbo.LineasAlbaranCliente l + ON l.CodigoEmpresa = c.CodigoEmpresa + AND l.EjercicioAlbaran = c.EjercicioAlbaran + AND l.SerieAlbaran = c.SerieAlbaran + AND l.NumeroAlbaran = c.NumeroAlbaran +WHERE $datePredicate +ORDER BY + c.FechaFactura, + c.SerieFactura, + c.NumeroFactura, + l.Orden; +"@ + +Write-Host "Exporting Sage Spain range..." +Write-Host "FromDate: $($FromDate.ToString("yyyy-MM-dd"))" +Write-Host "ToDate: $($ToDate.ToString("yyyy-MM-dd"))" +Write-Host "DateFilter: $DateFilter" + +$result = Export-QueryToCsv -Sql $sql -Path $csvPath + +@" +Sage Spain Sales CSV export +=========================== + +Created: $(Get-Date -Format "yyyy-MM-dd HH:mm:ss") +Server instance: $ServerInstance +Database: $Database +Export mode: Range +Date filter mode: $DateFilter +From date: $($FromDate.ToString("yyyy-MM-dd")) +To date: $($ToDate.ToString("yyyy-MM-dd")) + +Output: +$csvPath + +Rows: +$($result.Rows) + +SalesPriceValue sum: +$($result.SalesPriceValueSum) + +SharePoint target: +$target + +Source: +dbo.CabeceraAlbaranCliente joined with dbo.LineasAlbaranCliente + +Filter: +$datePredicate + +Notes: +- ToDate is exclusive. +- Currency is set to EUR. +- SalesPriceValue uses LineasAlbaranCliente.ImporteNeto; credit notes are forced negative. +- Credit notes are marked when TipoNuevaFra=2, SerieFactura='REC', or StatusAbono is non-zero. +"@ | Set-Content -LiteralPath $summaryPath -Encoding UTF8 + +$filesToUpload = Get-ChildItem -LiteralPath $runDirectory -File | + Where-Object { $_.Name -like "*.csv" -or $_.Name -like "*_summary.txt" } +if ($filesToUpload.Count -eq 0) { + throw "No CSV or summary files found for upload in $runDirectory" +} + +Write-Host "Uploading $($filesToUpload.Count) file(s) to SharePoint target: $target" +& $RcloneExe copy $runDirectory $target ` + --include "*.csv" ` + --include "*_summary.txt" ` + --verbose ` + --log-file $rcloneLog ` + --log-level INFO +if ($LASTEXITCODE -ne 0) { + throw "rclone upload failed with exit code $LASTEXITCODE. Log: $rcloneLog" +} + +foreach ($file in $filesToUpload) { + $uploadedMatch = & $RcloneExe lsf $target --files-only --include $file.Name --log-file $rcloneLog --log-level INFO + if ($LASTEXITCODE -ne 0) { + throw "Could not verify uploaded file '$($file.Name)' in '$target'. rclone exit code $LASTEXITCODE. Log: $rcloneLog" + } + + if (-not ($uploadedMatch | Where-Object { $_ -eq $file.Name })) { + throw "Upload verification failed. File '$($file.Name)' was not listed in '$target'. Log: $rcloneLog" + } +} + +Write-Host "Spain range export and SharePoint upload finished." +Write-Host "Local export: $runDirectory" +Write-Host "CSV: $csvPath" +Write-Host "Summary: $summaryPath" +Write-Host "Rows: $($result.Rows)" +Write-Host "SalesPriceValue sum: $($result.SalesPriceValueSum)" +Write-Host "SharePoint target: $target" +Write-Host "rclone log: $rcloneLog" diff --git a/TrafagSalesExporter/SageSpainFinalExportPackage/SageSpainFinalExportPackage.zip b/TrafagSalesExporter/SageSpainFinalExportPackage/SageSpainFinalExportPackage.zip new file mode 100644 index 0000000..0f4b438 Binary files /dev/null and b/TrafagSalesExporter/SageSpainFinalExportPackage/SageSpainFinalExportPackage.zip differ diff --git a/TrafagSalesExporter/SageSpainFinalExportPackage/Start-SpainRangeExportAndUpload.ps1 b/TrafagSalesExporter/SageSpainFinalExportPackage/Start-SpainRangeExportAndUpload.ps1 new file mode 100644 index 0000000..869f84f --- /dev/null +++ b/TrafagSalesExporter/SageSpainFinalExportPackage/Start-SpainRangeExportAndUpload.ps1 @@ -0,0 +1,46 @@ +param( + [datetime]$FromDate = "2026-06-01", + [datetime]$ToDate = "2026-06-04", + [string]$ServerInstance = "localhost", + [string]$Database = "Sage", + [string]$BaseDirectory = "C:\Trafag\SageSpain", + [string]$RcloneExe = "C:\Tools\rclone.exe", + [string]$RcloneRemote = "trafag-bi", + [string]$RcloneTarget = "Import/Finance/Spanien" +) + +$ErrorActionPreference = "Stop" + +$scriptDirectory = Split-Path -Parent $MyInvocation.MyCommand.Path +$workflowScript = Join-Path $scriptDirectory "Run-SpainExportAndUpload.ps1" + +if (-not (Test-Path -LiteralPath $workflowScript)) { + throw "Workflow script not found: $workflowScript" +} + +if (-not (Test-Path -LiteralPath $RcloneExe)) { + throw "rclone not found: $RcloneExe" +} + +Write-Host "Starting Spain Sage range export and SharePoint upload..." +Write-Host "FromDate: $($FromDate.ToString("yyyy-MM-dd"))" +Write-Host "ToDate: $($ToDate.ToString("yyyy-MM-dd"))" +Write-Host "Target: ${RcloneRemote}:$RcloneTarget" + +& $workflowScript ` + -ServerInstance $ServerInstance ` + -Database $Database ` + -ExportMode Range ` + -DateFilter LineRegistrationDate ` + -FromDate $FromDate ` + -ToDate $ToDate ` + -BaseDirectory $BaseDirectory ` + -RcloneExe $RcloneExe ` + -RcloneRemote $RcloneRemote ` + -RcloneTarget $RcloneTarget + +if ($LASTEXITCODE -ne 0) { + throw "Spain range export and upload failed with exit code $LASTEXITCODE" +} + +Write-Host "Finished Spain Sage range export and SharePoint upload."