Document workflow deltas and clean up docs
This commit is contained in:
+258
@@ -0,0 +1,258 @@
|
||||
param(
|
||||
[string]$ServerInstance = "localhost",
|
||||
[string]$Database = "Sage",
|
||||
[ValidateSet("Full", "Range")]
|
||||
[string]$ExportMode = "Full",
|
||||
[ValidateSet("InvoiceDate", "LineRegistrationDate")]
|
||||
[string]$DateFilter = "InvoiceDate",
|
||||
[int]$Year = 2025,
|
||||
[datetime]$FromDate = "2025-01-01",
|
||||
[datetime]$ToDate = "2026-01-01",
|
||||
[string]$OutputDirectory = (Join-Path $env:USERPROFILE "Desktop"),
|
||||
[string]$OutputFileName = ""
|
||||
)
|
||||
|
||||
$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 ($ExportMode -eq "Full") {
|
||||
$FromDate = [datetime]::new($Year, 1, 1)
|
||||
$ToDate = $FromDate.AddYears(1)
|
||||
}
|
||||
else {
|
||||
if (-not $PSBoundParameters.ContainsKey("ToDate")) {
|
||||
throw "Range export requires -ToDate. Example: -ExportMode Range -FromDate '2025-05-01' -ToDate '2025-06-01'"
|
||||
}
|
||||
}
|
||||
|
||||
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"))"
|
||||
}
|
||||
|
||||
$timestamp = Get-Date -Format "yyyyMMdd_HHmmss"
|
||||
$runDirectory = Join-Path $OutputDirectory "Sage_Spain_Sales_Export_$timestamp"
|
||||
New-Item -ItemType Directory -Path $runDirectory -Force | Out-Null
|
||||
|
||||
if ([string]::IsNullOrWhiteSpace($OutputFileName)) {
|
||||
$fromToken = $FromDate.ToString("yyyyMMdd")
|
||||
$toToken = $ToDate.Date.AddDays(-1).ToString("yyyyMMdd")
|
||||
$kindToken = $ExportMode.ToLowerInvariant()
|
||||
$OutputFileName = "Spain_Sales_${kindToken}_${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;
|
||||
"@
|
||||
|
||||
$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: $ExportMode
|
||||
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)
|
||||
|
||||
Source:
|
||||
dbo.CabeceraAlbaranCliente joined with dbo.LineasAlbaranCliente
|
||||
|
||||
Filter:
|
||||
$datePredicate
|
||||
|
||||
Notes:
|
||||
- Currency is set to EUR because Sage exports EnEuros_=-1 and CodigoDivisa is empty in the analysed rows.
|
||||
- SalesPriceValue uses LineasAlbaranCliente.ImporteNeto; credit notes are forced negative.
|
||||
- DocumentNetAmount uses CabeceraAlbaranCliente.BaseImponible; credit notes are forced negative.
|
||||
- Credit notes are marked when TipoNuevaFra=2, SerieFactura='REC', or StatusAbono is non-zero.
|
||||
- Full exports use the complete selected year.
|
||||
- Range exports use the explicit FromDate/ToDate window.
|
||||
"@ | Set-Content -LiteralPath $summaryPath -Encoding UTF8
|
||||
|
||||
Write-Host "Created:"
|
||||
Write-Host " $csvPath"
|
||||
Write-Host " $summaryPath"
|
||||
Write-Host "Rows: $($result.Rows)"
|
||||
Write-Host "SalesPriceValue sum: $($result.SalesPriceValueSum)"
|
||||
@@ -0,0 +1,164 @@
|
||||
Sage Spain final sales export candidate
|
||||
======================================
|
||||
|
||||
Run on the Spain Sage SQL Server machine.
|
||||
|
||||
PowerShell commands:
|
||||
|
||||
Set-ExecutionPolicy -Scope Process Bypass
|
||||
.\Export-SageSpainSalesCsv.ps1
|
||||
|
||||
Full export, default year 2025:
|
||||
|
||||
.\Export-SageSpainSalesCsv.ps1 -ExportMode Full -Year 2025
|
||||
|
||||
Range export, explicit window:
|
||||
|
||||
.\Export-SageSpainSalesCsv.ps1 -ExportMode Range -FromDate "2025-05-01" -ToDate "2025-06-01"
|
||||
|
||||
Range export by registration date, useful for new/changed records registered in a period:
|
||||
|
||||
.\Export-SageSpainSalesCsv.ps1 -ExportMode Range -DateFilter LineRegistrationDate -FromDate "2025-05-01" -ToDate "2025-06-01"
|
||||
|
||||
Output folder on Desktop:
|
||||
|
||||
Sage_Spain_Sales_Export_YYYYMMDD_HHMMSS
|
||||
|
||||
Files created:
|
||||
|
||||
- Spain_Sales_full_YYYY0101_to_YYYY1231.csv for full export
|
||||
- Spain_Sales_range_YYYYMMDD_to_YYYYMMDD.csv for range export
|
||||
- Matching *_summary.txt file
|
||||
|
||||
The script only reads SQL Server data. It does not change Sage or SQL Server.
|
||||
|
||||
Default source:
|
||||
|
||||
- Database: Sage
|
||||
- Header: dbo.CabeceraAlbaranCliente
|
||||
- Lines: dbo.LineasAlbaranCliente
|
||||
- Full export date filter: CabeceraAlbaranCliente.FechaFactura from YYYY-01-01 to next YYYY-01-01
|
||||
- Range export date filter: explicit FromDate/ToDate
|
||||
- DateFilter InvoiceDate uses CabeceraAlbaranCliente.FechaFactura
|
||||
- DateFilter LineRegistrationDate uses LineasAlbaranCliente.FechaRegistro, with fallback to FechaFactura
|
||||
- Sales value: LineasAlbaranCliente.ImporteNeto
|
||||
|
||||
If the SQL instance or database name differs:
|
||||
|
||||
.\Export-SageSpainSalesCsv.ps1 -ServerInstance "localhost" -Database "Sage" -ExportMode Full -Year 2025
|
||||
|
||||
|
||||
Automatic upload to SharePoint with rclone
|
||||
==========================================
|
||||
|
||||
Target SharePoint folder:
|
||||
|
||||
https://trafagag.sharepoint.com/sites/WorldwideBIPlatform/Shared%20Documents/Import/Finance/Spanien
|
||||
|
||||
Decoded folder path:
|
||||
|
||||
Shared Documents/Import/Finance/Spanien
|
||||
|
||||
Recommended rclone setup on the Spain Sage server:
|
||||
|
||||
1. Install rclone.
|
||||
2. Run:
|
||||
|
||||
rclone config
|
||||
|
||||
3. Create a new remote for the SharePoint document library.
|
||||
|
||||
Recommended remote name:
|
||||
|
||||
trafag-bi
|
||||
|
||||
The remote should point to the document library root "Shared Documents" of:
|
||||
|
||||
https://trafagag.sharepoint.com/sites/WorldwideBIPlatform
|
||||
|
||||
Then this target path is used by the wrapper script:
|
||||
|
||||
trafag-bi:Import/Finance/Spanien
|
||||
|
||||
Test rclone:
|
||||
|
||||
rclone lsd trafag-bi:
|
||||
rclone lsd trafag-bi:"Import/Finance"
|
||||
rclone lsd trafag-bi:"Import/Finance/Spanien"
|
||||
|
||||
Run range export and upload with default window last 7 days until today:
|
||||
|
||||
.\Run-SpainExportAndUpload.ps1
|
||||
|
||||
Explicit range:
|
||||
|
||||
.\Run-SpainExportAndUpload.ps1 -ExportMode Range -DateFilter LineRegistrationDate -FromDate "2026-06-02" -ToDate "2026-06-03"
|
||||
|
||||
Simple starter script with default window last 7 days until today:
|
||||
|
||||
.\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
|
||||
|
||||
Default date window:
|
||||
|
||||
- FromDate = today - 7 days
|
||||
- ToDate = today
|
||||
- ToDate is exclusive
|
||||
|
||||
Override the all-in-one default date window:
|
||||
|
||||
.\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.
|
||||
|
||||
rclone.exe lookup for the all-in-one script:
|
||||
|
||||
- explicit -RcloneExe parameter
|
||||
- rclone.exe in the same folder as the script
|
||||
- C:\Tools\rclone.exe
|
||||
- C:\Tools\rclone\rclone.exe
|
||||
- C:\Tools\rclone\rclone\rclone.exe
|
||||
- rclone from PATH
|
||||
|
||||
Known rclone upload issue:
|
||||
|
||||
If the log says:
|
||||
|
||||
CRITICAL: Can't set -v and --log-level
|
||||
|
||||
then the server is running an old script copy that still contains:
|
||||
|
||||
--verbose `
|
||||
|
||||
Remove that line from the rclone copy block or replace the file with the current Run-SpainRangeExportAndUpload-AllInOne.ps1.
|
||||
|
||||
Full export and upload:
|
||||
|
||||
.\Run-SpainExportAndUpload.ps1 -ExportMode Full -Year 2025
|
||||
|
||||
If the rclone remote has another name:
|
||||
|
||||
.\Run-SpainExportAndUpload.ps1 -RcloneRemote "YOUR_REMOTE_NAME"
|
||||
|
||||
If rclone.exe is not in PATH:
|
||||
|
||||
.\Run-SpainExportAndUpload.ps1 -RcloneExe "C:\Tools\rclone\rclone.exe"
|
||||
|
||||
Suggested Windows Task Scheduler command:
|
||||
|
||||
powershell.exe -NoProfile -ExecutionPolicy Bypass -File C:\Trafag\SageSpain\Run-SpainExportAndUpload.ps1
|
||||
|
||||
Important:
|
||||
|
||||
- The export script only reads SQL Server data.
|
||||
- rclone only uploads the generated CSV and matching summary file.
|
||||
- For daily deltas use ExportMode Range with DateFilter LineRegistrationDate.
|
||||
- ToDate is exclusive.
|
||||
+107
@@ -0,0 +1,107 @@
|
||||
param(
|
||||
[string]$ServerInstance = "localhost",
|
||||
[string]$Database = "Sage",
|
||||
[ValidateSet("Full", "Range")]
|
||||
[string]$ExportMode = "Range",
|
||||
[ValidateSet("InvoiceDate", "LineRegistrationDate")]
|
||||
[string]$DateFilter = "LineRegistrationDate",
|
||||
[int]$Year = 2025,
|
||||
[datetime]$FromDate = (Get-Date).Date.AddDays(-7),
|
||||
[datetime]$ToDate = (Get-Date).Date,
|
||||
[string]$BaseDirectory = "C:\Trafag\SageSpain",
|
||||
[string]$RcloneExe = "rclone",
|
||||
[string]$RcloneRemote = "trafag-bi",
|
||||
[string]$RcloneTarget = "Import/Finance/Spanien"
|
||||
)
|
||||
|
||||
$ErrorActionPreference = "Stop"
|
||||
|
||||
$scriptDirectory = Split-Path -Parent $MyInvocation.MyCommand.Path
|
||||
$exportScript = Join-Path $scriptDirectory "Export-SageSpainSalesCsv.ps1"
|
||||
if (-not (Test-Path -LiteralPath $exportScript)) {
|
||||
throw "Export script not found: $exportScript"
|
||||
}
|
||||
|
||||
$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,
|
||||
"-ExportMode", $ExportMode,
|
||||
"-DateFilter", $DateFilter,
|
||||
"-Year", $Year,
|
||||
"-OutputDirectory", $outputDirectory
|
||||
)
|
||||
|
||||
if ($ExportMode -eq "Range") {
|
||||
$exportArgs += @(
|
||||
"-FromDate", $FromDate.ToString("yyyy-MM-dd"),
|
||||
"-ToDate", $ToDate.ToString("yyyy-MM-dd")
|
||||
)
|
||||
}
|
||||
|
||||
& $exportScript @exportArgs
|
||||
if ($LASTEXITCODE -ne 0) {
|
||||
throw "Spain Sage export failed with exit code $LASTEXITCODE"
|
||||
}
|
||||
|
||||
$latestRun = Get-ChildItem -LiteralPath $outputDirectory -Directory |
|
||||
Sort-Object LastWriteTime -Descending |
|
||||
Select-Object -First 1
|
||||
if ($null -eq $latestRun) {
|
||||
throw "No export run directory found in $outputDirectory"
|
||||
}
|
||||
|
||||
$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"
|
||||
Write-Host "rclone log: $rcloneLog"
|
||||
+343
@@ -0,0 +1,343 @@
|
||||
param(
|
||||
[string]$ServerInstance = "localhost",
|
||||
[string]$Database = "Sage",
|
||||
[ValidateSet("InvoiceDate", "LineRegistrationDate")]
|
||||
[string]$DateFilter = "LineRegistrationDate",
|
||||
[datetime]$FromDate = (Get-Date).Date.AddDays(-7),
|
||||
[datetime]$ToDate = (Get-Date).Date,
|
||||
[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
|
||||
}
|
||||
}
|
||||
|
||||
function Resolve-RcloneExecutable {
|
||||
param([string]$ConfiguredPath)
|
||||
|
||||
$scriptDirectory = Split-Path -Parent $MyInvocation.MyCommand.Path
|
||||
$candidates = @(
|
||||
$ConfiguredPath,
|
||||
(Join-Path $scriptDirectory "rclone.exe"),
|
||||
"C:\Tools\rclone.exe",
|
||||
"C:\Tools\rclone\rclone.exe",
|
||||
"C:\Tools\rclone\rclone\rclone.exe",
|
||||
"rclone"
|
||||
) | Where-Object { -not [string]::IsNullOrWhiteSpace($_) }
|
||||
|
||||
foreach ($candidate in $candidates) {
|
||||
if (Test-Path -LiteralPath $candidate) {
|
||||
return (Resolve-Path -LiteralPath $candidate).Path
|
||||
}
|
||||
|
||||
$command = Get-Command $candidate -ErrorAction SilentlyContinue
|
||||
if ($null -ne $command) {
|
||||
return $command.Source
|
||||
}
|
||||
}
|
||||
|
||||
throw "rclone executable not found. Checked: $($candidates -join ', ')"
|
||||
}
|
||||
|
||||
function Throw-RcloneError {
|
||||
param(
|
||||
[string]$Message,
|
||||
[string]$LogPath
|
||||
)
|
||||
|
||||
if (Test-Path -LiteralPath $LogPath) {
|
||||
Write-Host ""
|
||||
Write-Host "Last rclone log lines:"
|
||||
Get-Content -LiteralPath $LogPath -Tail 80 | ForEach-Object { Write-Host $_ }
|
||||
}
|
||||
|
||||
throw "$Message Log: $LogPath"
|
||||
}
|
||||
|
||||
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"))"
|
||||
}
|
||||
|
||||
$RcloneExe = Resolve-RcloneExecutable -ConfiguredPath $RcloneExe
|
||||
Write-Host "Using rclone: $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-RcloneError -Message "Could not create/check SharePoint target '$target'. rclone exit code $LASTEXITCODE." -LogPath $rcloneLog
|
||||
}
|
||||
|
||||
$targetListing = & $RcloneExe lsf $target --max-depth 1 --log-file $rcloneLog --log-level INFO
|
||||
if ($LASTEXITCODE -ne 0) {
|
||||
Throw-RcloneError -Message "SharePoint target '$target' is not reachable. rclone exit code $LASTEXITCODE." -LogPath $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" `
|
||||
--log-file $rcloneLog `
|
||||
--log-level INFO
|
||||
if ($LASTEXITCODE -ne 0) {
|
||||
Throw-RcloneError -Message "rclone upload failed with exit code $LASTEXITCODE." -LogPath $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-RcloneError -Message "Could not verify uploaded file '$($file.Name)' in '$target'. rclone exit code $LASTEXITCODE." -LogPath $rcloneLog
|
||||
}
|
||||
|
||||
if (-not ($uploadedMatch | Where-Object { $_ -eq $file.Name })) {
|
||||
Throw-RcloneError -Message "Upload verification failed. File '$($file.Name)' was not listed in '$target'." -LogPath $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"
|
||||
BIN
Binary file not shown.
+46
@@ -0,0 +1,46 @@
|
||||
param(
|
||||
[datetime]$FromDate = (Get-Date).Date.AddDays(-7),
|
||||
[datetime]$ToDate = (Get-Date).Date,
|
||||
[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."
|
||||
Reference in New Issue
Block a user