added improvements on set proces and minor other improvements
This commit is contained in:
@@ -10,7 +10,7 @@
|
|||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="18.7.0" />
|
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="18.7.0" />
|
||||||
<PackageReference Include="xunit" Version="2.9.3" />
|
<PackageReference Include="xunit.v3" Version="3.2.2" />
|
||||||
<PackageReference Include="xunit.runner.visualstudio" Version="3.1.5">
|
<PackageReference Include="xunit.runner.visualstudio" Version="3.1.5">
|
||||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||||
<PrivateAssets>all</PrivateAssets>
|
<PrivateAssets>all</PrivateAssets>
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using Fuchs.intranet;
|
using Fuchs.intranet;
|
||||||
@@ -31,8 +31,11 @@ public class InvoiceOptionsTests
|
|||||||
=> Assert.Equal("", InvoiceOptionsFor(new { type = "r" }));
|
=> Assert.Equal("", InvoiceOptionsFor(new { type = "r" }));
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void DefaultSetPrice_OmitsToken()
|
public void ExplicitDefaultSetPrice_EmitsToken()
|
||||||
=> Assert.Equal("", InvoiceOptionsFor(new { type = "r", setmode = "setprice" }));
|
// An explicit choice of the default mode must still be persisted — otherwise it is
|
||||||
|
// indistinguishable from an invoice that was never switched to set-pricing at all
|
||||||
|
// (the editor relies on this to hide the "Set-Preisanzeige" menu entry once chosen).
|
||||||
|
=> Assert.Equal("setmode:setprice", InvoiceOptionsFor(new { type = "r", setmode = "setprice" }));
|
||||||
|
|
||||||
[Theory]
|
[Theory]
|
||||||
[InlineData("itemprices", "setmode:itemprices")]
|
[InlineData("itemprices", "setmode:itemprices")]
|
||||||
|
|||||||
@@ -141,7 +141,7 @@ public class ProcessWebComServiceTests
|
|||||||
Assert.True(result);
|
Assert.True(result);
|
||||||
Assert.Equal(1, handler.CallCount);
|
Assert.Equal(1, handler.CallCount);
|
||||||
var json = JObject.Parse(handler.LastRequestBody!);
|
var json = JObject.Parse(handler.LastRequestBody!);
|
||||||
Assert.Equal("dev-inbox@example.test", json["recipient"]!.ToString());
|
Assert.Equal("dev-inbox@example.test", json["communication"]!["to"]!.ToString());
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
@@ -153,7 +153,7 @@ public class ProcessWebComServiceTests
|
|||||||
await svc.SendEmailAsync("inv_ov2", "Rechnung 123", "<p>hi</p>", "realcustomer@example.de", "Kunde");
|
await svc.SendEmailAsync("inv_ov2", "Rechnung 123", "<p>hi</p>", "realcustomer@example.de", "Kunde");
|
||||||
|
|
||||||
var json = JObject.Parse(handler.LastRequestBody!);
|
var json = JObject.Parse(handler.LastRequestBody!);
|
||||||
string subject = json["subject"]!.ToString();
|
string subject = json["communication"]!["subject"]!.ToString();
|
||||||
Assert.Contains("realcustomer@example.de", subject);
|
Assert.Contains("realcustomer@example.de", subject);
|
||||||
Assert.Contains("Rechnung 123", subject);
|
Assert.Contains("Rechnung 123", subject);
|
||||||
}
|
}
|
||||||
@@ -169,7 +169,7 @@ public class ProcessWebComServiceTests
|
|||||||
Assert.True(result);
|
Assert.True(result);
|
||||||
Assert.Equal(1, handler.CallCount);
|
Assert.Equal(1, handler.CallCount);
|
||||||
var json = JObject.Parse(handler.LastRequestBody!);
|
var json = JObject.Parse(handler.LastRequestBody!);
|
||||||
Assert.Equal("dev-inbox@example.test", json["recipient"]!.ToString());
|
Assert.Equal("dev-inbox@example.test", json["communication"]!["to"]!.ToString());
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
@@ -181,7 +181,7 @@ public class ProcessWebComServiceTests
|
|||||||
await svc.SendEmailAsync("inv_ov4", "Subject", "<p>hi</p>", "realcustomer@example.de", "Kunde");
|
await svc.SendEmailAsync("inv_ov4", "Subject", "<p>hi</p>", "realcustomer@example.de", "Kunde");
|
||||||
|
|
||||||
var json = JObject.Parse(handler.LastRequestBody!);
|
var json = JObject.Parse(handler.LastRequestBody!);
|
||||||
Assert.Equal("realcustomer@example.de", json["recipient"]!.ToString());
|
Assert.Equal("realcustomer@example.de", json["communication"]!["to"]!.ToString());
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── Override enforcement across every real appsettings*.json environment ───
|
// ── Override enforcement across every real appsettings*.json environment ───
|
||||||
@@ -194,9 +194,9 @@ public class ProcessWebComServiceTests
|
|||||||
// hand-typed literal - so a future edit that silently breaks the override key
|
// hand-typed literal - so a future edit that silently breaks the override key
|
||||||
// or its value in any appsettings*.json file would fail this test.
|
// or its value in any appsettings*.json file would fail this test.
|
||||||
//
|
//
|
||||||
// Note: this API has a single "recipient" field - there is no distinct
|
// Note: this API has a single "communication.to" field - there is no distinct
|
||||||
// to/cc/bcc concept anywhere in the codebase (see ProcessWebComService.
|
// to/cc/bcc concept anywhere in the codebase (see ProcessWebComService.
|
||||||
// SendEmailAsync / payload.recipient). "to/cc/bcc cleared" is therefore fully
|
// SendEmailAsync / communication.to). "to/cc/bcc cleared" is therefore fully
|
||||||
// satisfied by asserting that field no longer carries the original recipient
|
// satisfied by asserting that field no longer carries the original recipient
|
||||||
// once an override is configured.
|
// once an override is configured.
|
||||||
public static IEnumerable<object[]> AppSettingsEnvironments()
|
public static IEnumerable<object[]> AppSettingsEnvironments()
|
||||||
@@ -236,7 +236,7 @@ public class ProcessWebComServiceTests
|
|||||||
Assert.True(result);
|
Assert.True(result);
|
||||||
Assert.Equal(1, handler.CallCount);
|
Assert.Equal(1, handler.CallCount);
|
||||||
var json = JObject.Parse(handler.LastRequestBody!);
|
var json = JObject.Parse(handler.LastRequestBody!);
|
||||||
string sentRecipient = json["recipient"]!.ToString();
|
string sentRecipient = json["communication"]!["to"]!.ToString();
|
||||||
|
|
||||||
if (!string.IsNullOrWhiteSpace(overrideRecipient))
|
if (!string.IsNullOrWhiteSpace(overrideRecipient))
|
||||||
{
|
{
|
||||||
@@ -266,7 +266,7 @@ public class ProcessWebComServiceTests
|
|||||||
Assert.True(result);
|
Assert.True(result);
|
||||||
Assert.NotNull(handler.LastRequestBody);
|
Assert.NotNull(handler.LastRequestBody);
|
||||||
var json = JObject.Parse(handler.LastRequestBody!);
|
var json = JObject.Parse(handler.LastRequestBody!);
|
||||||
var att = (JArray)json["attachments"]!;
|
var att = (JArray)json["communication"]!["attachments"]!;
|
||||||
Assert.Single(att);
|
Assert.Single(att);
|
||||||
Assert.Equal("Rechnung.pdf", att[0]!["filename"]!.ToString());
|
Assert.Equal("Rechnung.pdf", att[0]!["filename"]!.ToString());
|
||||||
Assert.Equal("application/pdf", att[0]!["mimeType"]!.ToString());
|
Assert.Equal("application/pdf", att[0]!["mimeType"]!.ToString());
|
||||||
|
|||||||
@@ -222,6 +222,52 @@ public partial class IntranetController
|
|||||||
: StatusCode(500, new { error = "not successful" });
|
: StatusCode(500, new { error = "not successful" });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
case "man":
|
||||||
|
{
|
||||||
|
var pl = StdParamlist(
|
||||||
|
SQL_VarChar("@taID", Form("taID"), dbNull_IfEmpty: true),
|
||||||
|
SQL_Date("@ValueDate", Form("ValueDate")),
|
||||||
|
SQL_Decimal("@Amount", Form("Amount"), precision: 9, scale: 2),
|
||||||
|
SQL_NVarChar("@NameOfPayer", Form("NameOfPayer"), dbNull_IfEmpty: true),
|
||||||
|
SQL_VarChar("@SepaRemittanceInformation", Form("SepaRemittanceInformation"), dbNull_IfEmpty: true));
|
||||||
|
await getSQLDatatable_async(
|
||||||
|
"EXECUTE [dbo].[fds__setBankingtransaction_manual] @taID, @ValueDate, @Amount, @NameOfPayer, @SepaRemittanceInformation, @authuser;",
|
||||||
|
_intranet.Intranet__SQLConnectionString, pl,
|
||||||
|
Security: DbSec, options: SqlOpt(fn, id, code));
|
||||||
|
_logger.LogInformation(
|
||||||
|
"Manual banking transaction upserted taID={TaID} user={User}", Form("taID"), UserAccountID);
|
||||||
|
return await JSONAsync(new { ok = true });
|
||||||
|
}
|
||||||
|
|
||||||
|
case "mget":
|
||||||
|
{
|
||||||
|
if (!HasForm("taid")) return BadRequest400();
|
||||||
|
var pl = StdParamlist(SQL_VarChar("@taID", Form("taid"), dbNull_IfEmpty: true));
|
||||||
|
var res = await getSQLDatatable_async(
|
||||||
|
"EXECUTE [dbo].[fds__getBankingtransaction_manual] @taID, @authuser;",
|
||||||
|
_intranet.Intranet__SQLConnectionString, pl,
|
||||||
|
Security: DbSec, options: SqlOpt(fn, id, code));
|
||||||
|
return await JSONAsync(res.FirstRow.toObjectDictionary());
|
||||||
|
}
|
||||||
|
|
||||||
|
case "mdel":
|
||||||
|
{
|
||||||
|
if (!HasForm("taid")) return BadRequest400();
|
||||||
|
var pl = StdParamlist(
|
||||||
|
SQL_VarChar("@taID", Form("taid"), dbNull_IfEmpty: true),
|
||||||
|
SQL_Date("@ValueDate", (string?)null),
|
||||||
|
SQL_Decimal("@Amount", (string?)null, precision: 9, scale: 2),
|
||||||
|
SQL_NVarChar("@NameOfPayer", null, dbNull_IfEmpty: true),
|
||||||
|
SQL_VarChar("@SepaRemittanceInformation", null, dbNull_IfEmpty: true));
|
||||||
|
await getSQLDatatable_async(
|
||||||
|
"EXECUTE [dbo].[fds__setBankingtransaction_manual] @taID, @ValueDate, @Amount, @NameOfPayer, @SepaRemittanceInformation, @authuser;",
|
||||||
|
_intranet.Intranet__SQLConnectionString, pl,
|
||||||
|
Security: DbSec, options: SqlOpt(fn, id, code));
|
||||||
|
_logger.LogInformation(
|
||||||
|
"Manual banking transaction deleted taID={TaID} user={User}", Form("taid"), UserAccountID);
|
||||||
|
return await JSONAsync(new { ok = true });
|
||||||
|
}
|
||||||
|
|
||||||
case "vfi":
|
case "vfi":
|
||||||
{
|
{
|
||||||
if (!HasForm("invid")) return BadRequest400();
|
if (!HasForm("invid")) return BadRequest400();
|
||||||
|
|||||||
@@ -78,9 +78,17 @@ public partial class IntranetController
|
|||||||
var ldic = BuildInvoiceRequestList(sqldset);
|
var ldic = BuildInvoiceRequestList(sqldset);
|
||||||
var adminDic = sqldset.Table("admin").FirstRow.toObjectDictionary();
|
var adminDic = sqldset.Table("admin").FirstRow.toObjectDictionary();
|
||||||
var invDic = sqldset.Table("inv").FirstRow.toObjectDictionary();
|
var invDic = sqldset.Table("inv").FirstRow.toObjectDictionary();
|
||||||
bool has13b = invDic.nz("InvoiceOptions", "").Split(',').Contains("§13b");
|
string invoiceOptions = invDic.nz("InvoiceOptions", "");
|
||||||
|
bool has13b = invoiceOptions.Split(',').Contains("§13b");
|
||||||
if (has13b)
|
if (has13b)
|
||||||
adminDic["p13b"] = true;
|
adminDic["p13b"] = true;
|
||||||
|
// Carry the persisted set-pricing mode forward (see InvoiceSetPricing.cs / FdsInvoiceData.BuildInvoiceOptions):
|
||||||
|
// needed so the editor knows the invoice has already been explicitly switched to a set-display
|
||||||
|
// mode (including the default "setprice") and hides the "Set-Preisanzeige" menu entry accordingly.
|
||||||
|
string? setmodeToken = invoiceOptions.Split(',')
|
||||||
|
.FirstOrDefault(t => t.StartsWith("setmode:", StringComparison.OrdinalIgnoreCase));
|
||||||
|
if (setmodeToken != null)
|
||||||
|
adminDic["setmode"] = setmodeToken["setmode:".Length..].Trim().ToLowerInvariant();
|
||||||
_logger.LogDebug("HandleInvoiceGet invoiceId={InvoiceId} requestCount={ReqCount} has13b={Has13b} user={User}",
|
_logger.LogDebug("HandleInvoiceGet invoiceId={InvoiceId} requestCount={ReqCount} has13b={Has13b} user={User}",
|
||||||
invoiceId, ldic.Count, has13b, UserAccountID);
|
invoiceId, ldic.Count, has13b, UserAccountID);
|
||||||
return await JSONAsync(new { admin = adminDic, inv = invDic, req = ldic });
|
return await JSONAsync(new { admin = adminDic, inv = invDic, req = ldic });
|
||||||
|
|||||||
+1
-1
@@ -38,7 +38,7 @@
|
|||||||
<PackageReference Include="OpenTelemetry.Extensions.Hosting" Version="1.16.0" />
|
<PackageReference Include="OpenTelemetry.Extensions.Hosting" Version="1.16.0" />
|
||||||
<PackageReference Include="OpenTelemetry.Instrumentation.AspNetCore" Version="1.16.0" />
|
<PackageReference Include="OpenTelemetry.Instrumentation.AspNetCore" Version="1.16.0" />
|
||||||
<PackageReference Include="OpenTelemetry.Instrumentation.Http" Version="1.16.0" />
|
<PackageReference Include="OpenTelemetry.Instrumentation.Http" Version="1.16.0" />
|
||||||
<PackageReference Include="OpenTelemetry.Instrumentation.Runtime" Version="1.15.1" />
|
<PackageReference Include="OpenTelemetry.Instrumentation.Runtime" Version="1.16.0" />
|
||||||
<PackageReference Include="OpenTelemetry.Instrumentation.SqlClient" Version="1.16.0" />
|
<PackageReference Include="OpenTelemetry.Instrumentation.SqlClient" Version="1.16.0" />
|
||||||
<PackageReference Include="Portable.BouncyCastle" Version="1.9.0" />
|
<PackageReference Include="Portable.BouncyCastle" Version="1.9.0" />
|
||||||
<PackageReference Include="QRCoder" Version="1.8.0" />
|
<PackageReference Include="QRCoder" Version="1.8.0" />
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Abstraction for outbound communication: email and SMS.
|
/// Abstraction for outbound communication: email and SMS.
|
||||||
/// Backed by the ProcessWeb Mailer API (POST /api/mailer?fn=push_com).
|
/// Backed by the ProcessWeb Mailer API (POST {BaseUrl}/mailer/push_com).
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public interface IComService
|
public interface IComService
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
using System.Text.RegularExpressions;
|
using System.Text.RegularExpressions;
|
||||||
using Fuchs.intranet;
|
using Fuchs.intranet;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
@@ -247,10 +247,53 @@ public sealed class InvoiceDraftEditService : IInvoiceDraftService
|
|||||||
block_net = session.Sums.NetByBlock
|
block_net = session.Sums.NetByBlock
|
||||||
},
|
},
|
||||||
validation = session.ValidationMessages.Select(v => new { field = v.Field, severity = v.Severity, message = v.Message }),
|
validation = session.ValidationMessages.Select(v => new { field = v.Field, severity = v.Severity, message = v.Message }),
|
||||||
historyCount = session.History.Count
|
historyCount = session.History.Count,
|
||||||
|
notes = BuildNotes(session),
|
||||||
|
setDisplay = BuildSetDisplay(session)
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Authoritative per-item set-pricing display flags (ADR 0006/set-pricing): for every block
|
||||||
|
/// that contains a "set" item, runs the same <see cref="InvoiceSetPricing.Build"/> the PDF
|
||||||
|
/// uses (keyed by <c>admin.setmode</c>) and returns a flat map of item id → whether its
|
||||||
|
/// price/total should be shown and whether it is a set header. The online editor applies
|
||||||
|
/// this to the rendered rows so it always matches the PDF without duplicating the pricing
|
||||||
|
/// rules client-side.
|
||||||
|
/// </summary>
|
||||||
|
private static Dictionary<string, object> BuildSetDisplay(InvoiceDraftSession session)
|
||||||
|
{
|
||||||
|
var result = new Dictionary<string, object>();
|
||||||
|
var mode = InvoiceSetPricing.ParseMode(Str(session.Admin["setmode"]));
|
||||||
|
foreach (var blockTok in session.Req)
|
||||||
|
{
|
||||||
|
if (blockTok is not JObject block || block["items"] is not JArray itemsArr) continue;
|
||||||
|
List<Dictionary<string, object?>>? items = itemsArr.ToObject<List<Dictionary<string, object?>>>();
|
||||||
|
if (items == null || !InvoiceSetPricing.ContainsSets(items)) continue;
|
||||||
|
|
||||||
|
foreach (var line in InvoiceSetPricing.Build(items, mode))
|
||||||
|
{
|
||||||
|
if (string.IsNullOrEmpty(line.Id)) continue;
|
||||||
|
result[line.Id] = new { p = line.ShowPrice, h = line.IsSetHeader };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Renders the backend-authoritative notice paragraphs (see <see cref="FuchsPdf.BuildInvoiceNotes"/>)
|
||||||
|
/// for the editor's read-only preview — the same text the PDF will show, generated from the
|
||||||
|
/// same builder, so the two can never drift apart.
|
||||||
|
/// </summary>
|
||||||
|
private IEnumerable<object> BuildNotes(InvoiceDraftSession session)
|
||||||
|
{
|
||||||
|
var fds = BuildFdsData(session);
|
||||||
|
fds.InvoiceRegistration = SynthesizeRegistration(session);
|
||||||
|
bool p13b = AsBool(session.Admin["p13b"]);
|
||||||
|
return FuchsPdf.BuildInvoiceNotes(new FuchsPdf.FdsTextBlocks(), fds, p13b)
|
||||||
|
.Select(n => new { text = n.Text, style = n.Style });
|
||||||
|
}
|
||||||
|
|
||||||
public IReadOnlyList<ChangeHistoryEntry> GetHistory(string token) =>
|
public IReadOnlyList<ChangeHistoryEntry> GetHistory(string token) =>
|
||||||
_cache.Get(token)?.History ?? (IReadOnlyList<ChangeHistoryEntry>)Array.Empty<ChangeHistoryEntry>();
|
_cache.Get(token)?.History ?? (IReadOnlyList<ChangeHistoryEntry>)Array.Empty<ChangeHistoryEntry>();
|
||||||
|
|
||||||
@@ -385,7 +428,9 @@ public sealed class InvoiceDraftEditService : IInvoiceDraftService
|
|||||||
var tokens = new List<string>();
|
var tokens = new List<string>();
|
||||||
if (AsBool(session.Admin["p13b"])) tokens.Add("§13b");
|
if (AsBool(session.Admin["p13b"])) tokens.Add("§13b");
|
||||||
string setmode = Str(session.Admin["setmode"]).Trim().ToLowerInvariant();
|
string setmode = Str(session.Admin["setmode"]).Trim().ToLowerInvariant();
|
||||||
if (setmode is "itemprices" or "setonly") tokens.Add("setmode:" + setmode);
|
// Mirrors FdsInvoiceData.BuildInvoiceOptions: persist any explicitly-chosen mode
|
||||||
|
// (including the default "setprice") so it is distinguishable from "never touched".
|
||||||
|
if (setmode is "setprice" or "itemprices" or "setonly") tokens.Add("setmode:" + setmode);
|
||||||
return string.Join(",", tokens);
|
return string.Join(",", tokens);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ namespace Fuchs.Services;
|
|||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Outbound communication service backed by the ProcessWeb Mailer API
|
/// Outbound communication service backed by the ProcessWeb Mailer API
|
||||||
/// (POST https://api.processweb.de/api/mailer?fn=push_com).
|
/// (POST https://api.processweb.de/mailer/push_com).
|
||||||
/// When <c>ProcessWebComSettings.Enabled</c> is <c>false</c> the service
|
/// When <c>ProcessWebComSettings.Enabled</c> is <c>false</c> the service
|
||||||
/// only logs the intended communication without calling the API.
|
/// only logs the intended communication without calling the API.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -105,10 +105,9 @@ public class ProcessWebComService : IComService
|
|||||||
})
|
})
|
||||||
.ToArray();
|
.ToArray();
|
||||||
|
|
||||||
var payload = new
|
var communication = new
|
||||||
{
|
{
|
||||||
comType = "email",
|
to = email,
|
||||||
recipient = email,
|
|
||||||
subject,
|
subject,
|
||||||
body,
|
body,
|
||||||
attachments = attachmentPayload
|
attachments = attachmentPayload
|
||||||
@@ -117,7 +116,7 @@ public class ProcessWebComService : IComService
|
|||||||
_logger.LogDebug("SendEmailAsync ref={Reference} to={Email} attachments={Count}",
|
_logger.LogDebug("SendEmailAsync ref={Reference} to={Email} attachments={Count}",
|
||||||
reference, email, attachmentPayload.Length);
|
reference, email, attachmentPayload.Length);
|
||||||
|
|
||||||
var (ok, responseBody) = await PostToApiAsync("push_com", payload);
|
var (ok, responseBody) = await PostToApiAsync(reference, "e", communication);
|
||||||
if (ok)
|
if (ok)
|
||||||
{
|
{
|
||||||
success = true;
|
success = true;
|
||||||
@@ -166,14 +165,13 @@ public class ProcessWebComService : IComService
|
|||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var payload = new
|
var communication = new
|
||||||
{
|
{
|
||||||
comType = "sms",
|
to = mobile,
|
||||||
recipient = mobile,
|
body = message
|
||||||
body = message
|
|
||||||
};
|
};
|
||||||
|
|
||||||
var (ok, responseBody) = await PostToApiAsync("push_com", payload);
|
var (ok, responseBody) = await PostToApiAsync(mobile, "s", communication);
|
||||||
if (ok)
|
if (ok)
|
||||||
{
|
{
|
||||||
FuchsTelemetry.SmsSent.Add(1);
|
FuchsTelemetry.SmsSent.Add(1);
|
||||||
@@ -193,10 +191,20 @@ public class ProcessWebComService : IComService
|
|||||||
|
|
||||||
// ── Private helpers ────────────────────────────────────────────────────────
|
// ── Private helpers ────────────────────────────────────────────────────────
|
||||||
|
|
||||||
private async Task<(bool ok, string body)> PostToApiAsync(string fn, object payload)
|
private async Task<(bool ok, string body)> PostToApiAsync(string reference, string comtype, object communication)
|
||||||
{
|
{
|
||||||
var client = _httpClientFactory.CreateClient("ProcessWebMailer");
|
var client = _httpClientFactory.CreateClient("ProcessWebMailer");
|
||||||
var json = JsonConvert.SerializeObject(payload);
|
|
||||||
|
// The Mailer API expects a fixed envelope: { serverid, comtype, communication, reference }.
|
||||||
|
// "communication" carries the actual channel-specific payload (to/subject/body/attachments).
|
||||||
|
var payload = new
|
||||||
|
{
|
||||||
|
serverid = _settings.ServerId,
|
||||||
|
comtype,
|
||||||
|
communication,
|
||||||
|
reference
|
||||||
|
};
|
||||||
|
var json = JsonConvert.SerializeObject(payload);
|
||||||
var content = new StringContent(json, Encoding.UTF8, "application/json");
|
var content = new StringContent(json, Encoding.UTF8, "application/json");
|
||||||
|
|
||||||
string credentials = Convert.ToBase64String(
|
string credentials = Convert.ToBase64String(
|
||||||
@@ -204,7 +212,7 @@ public class ProcessWebComService : IComService
|
|||||||
client.DefaultRequestHeaders.Authorization =
|
client.DefaultRequestHeaders.Authorization =
|
||||||
new AuthenticationHeaderValue("Basic", credentials);
|
new AuthenticationHeaderValue("Basic", credentials);
|
||||||
|
|
||||||
var response = await client.PostAsync($"{_settings.BaseUrl}/api/mailer?fn={fn}", content);
|
var response = await client.PostAsync($"{_settings.BaseUrl}/mailer/push_com", content);
|
||||||
string responseBody = await response.Content.ReadAsStringAsync();
|
string responseBody = await response.Content.ReadAsStringAsync();
|
||||||
return (response.IsSuccessStatusCode, responseBody);
|
return (response.IsSuccessStatusCode, responseBody);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,6 +14,9 @@ public class ProcessWebComSettings
|
|||||||
/// <summary>API token used for HTTP Basic authentication.</summary>
|
/// <summary>API token used for HTTP Basic authentication.</summary>
|
||||||
public string Token { get; set; } = "";
|
public string Token { get; set; } = "";
|
||||||
|
|
||||||
|
/// <summary>Server identifier assigned by ProcessWeb, sent as "serverid" in every push_com call.</summary>
|
||||||
|
public string ServerId { get; set; } = "";
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// When <c>false</c> (default) the service is disabled and only logs the
|
/// When <c>false</c> (default) the service is disabled and only logs the
|
||||||
/// intended communication without actually calling the API.
|
/// intended communication without actually calling the API.
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
using Fuchs.intranet;
|
using Fuchs.intranet;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
using MigraDoc.DocumentObjectModel;
|
using MigraDoc.DocumentObjectModel;
|
||||||
@@ -159,10 +159,24 @@ public sealed class ReminderDraftEditService : IReminderDraftService
|
|||||||
amount_open = session.Sums.AmountOpen
|
amount_open = session.Sums.AmountOpen
|
||||||
},
|
},
|
||||||
validation = session.ValidationMessages.Select(v => new { field = v.Field, severity = v.Severity, message = v.Message }),
|
validation = session.ValidationMessages.Select(v => new { field = v.Field, severity = v.Severity, message = v.Message }),
|
||||||
historyCount = session.History.Count
|
historyCount = session.History.Count,
|
||||||
|
notes = BuildNotes(session)
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Renders the backend-authoritative intro/closing notice paragraphs (see
|
||||||
|
/// <see cref="FuchsPdf.BuildReminderNotes"/>) for the editor's read-only preview — the same
|
||||||
|
/// text the PDF will show, generated from the same builder, so the two can never drift apart.
|
||||||
|
/// </summary>
|
||||||
|
private object BuildNotes(ReminderDraftSession session)
|
||||||
|
{
|
||||||
|
var fds = BuildReminderData(session);
|
||||||
|
fds.ReminderRegistration = SynthesizeRegistration(session);
|
||||||
|
var (intro, closing) = FuchsPdf.BuildReminderNotes(new FuchsPdf.FdsTextBlocks(), fds);
|
||||||
|
return new { intro, closing };
|
||||||
|
}
|
||||||
|
|
||||||
public IReadOnlyList<ChangeHistoryEntry> GetHistory(string token) =>
|
public IReadOnlyList<ChangeHistoryEntry> GetHistory(string token) =>
|
||||||
_cache.Get(token)?.History ?? (IReadOnlyList<ChangeHistoryEntry>)Array.Empty<ChangeHistoryEntry>();
|
_cache.Get(token)?.History ?? (IReadOnlyList<ChangeHistoryEntry>)Array.Empty<ChangeHistoryEntry>();
|
||||||
|
|
||||||
@@ -250,6 +264,8 @@ public sealed class ReminderDraftEditService : IReminderDraftService
|
|||||||
["SendToAddress"] = Str(session.New["invoiceaddress"]),
|
["SendToAddress"] = Str(session.New["invoiceaddress"]),
|
||||||
["SendToEmail"] = Str(session.New["invoiceemail"]),
|
["SendToEmail"] = Str(session.New["invoiceemail"]),
|
||||||
["InvoiceId"] = invoiceId,
|
["InvoiceId"] = invoiceId,
|
||||||
|
["amount"] = session.Sums.AmountTotal,
|
||||||
|
["amount_payed"] = session.Sums.AmountPayed,
|
||||||
["amount_open"] = session.Sums.AmountOpen,
|
["amount_open"] = session.Sums.AmountOpen,
|
||||||
["PaymentTerm"] = Str(session.Rem["paymentterm"]),
|
["PaymentTerm"] = Str(session.Rem["paymentterm"]),
|
||||||
["invoices"] = invoices,
|
["invoices"] = invoices,
|
||||||
|
|||||||
@@ -44,6 +44,7 @@
|
|||||||
"BaseUrl": "https://api.processweb.de",
|
"BaseUrl": "https://api.processweb.de",
|
||||||
"AccountId": "82d87114-c8c3-4d33-95e5-4c781a9229ab",
|
"AccountId": "82d87114-c8c3-4d33-95e5-4c781a9229ab",
|
||||||
"Token": "MANAGED_BY_KEYVAULT",
|
"Token": "MANAGED_BY_KEYVAULT",
|
||||||
|
"ServerId": "87AAB12E-29E0-47AB-8DD1-5F5D49B4AC2E",
|
||||||
"Enabled": false
|
"Enabled": false
|
||||||
},
|
},
|
||||||
"Email": {
|
"Email": {
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
using Microsoft.Data.SqlClient;
|
using Microsoft.Data.SqlClient;
|
||||||
using Newtonsoft.Json.Linq;
|
using Newtonsoft.Json.Linq;
|
||||||
using static OCORE.OCORE_dictionaries;
|
using static OCORE.OCORE_dictionaries;
|
||||||
@@ -171,7 +171,11 @@ public class FdsInvoiceData
|
|||||||
var tokens = new List<string>();
|
var tokens = new List<string>();
|
||||||
if (Admin?.no("p13b", false) is true) tokens.Add("§13b");
|
if (Admin?.no("p13b", false) is true) tokens.Add("§13b");
|
||||||
string setmode = (Admin?.nz("setmode") ?? "").Trim().ToLowerInvariant();
|
string setmode = (Admin?.nz("setmode") ?? "").Trim().ToLowerInvariant();
|
||||||
if (setmode is "itemprices" or "setonly") tokens.Add("setmode:" + setmode);
|
// Persist any explicitly-chosen mode, including the default "setprice" — otherwise an
|
||||||
|
// explicit user choice of the default is indistinguishable from an invoice that was never
|
||||||
|
// switched to set-pricing at all (needed so the editor can hide the "Set-Preisanzeige"
|
||||||
|
// menu entry once a mode has been chosen; see InvoiceDraftEditService.BuildInvoiceOptions).
|
||||||
|
if (setmode is "setprice" or "itemprices" or "setonly") tokens.Add("setmode:" + setmode);
|
||||||
return string.Join(",", tokens);
|
return string.Join(",", tokens);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+247
-169
@@ -112,21 +112,40 @@ public static class FuchsPdf
|
|||||||
"Kreissparkasse D\u00fcsseldorf", "IBAN: DE52 3015 0200 0002 0914 78", "BIC: WELADED1KSD",
|
"Kreissparkasse D\u00fcsseldorf", "IBAN: DE52 3015 0200 0002 0914 78", "BIC: WELADED1KSD",
|
||||||
"Stadtsparkasse D\u00fcsseldorf", "IBAN: DE76 3005 0110 0045 0148 00", "BIC: DUSSDEDDXXX"
|
"Stadtsparkasse D\u00fcsseldorf", "IBAN: DE76 3005 0110 0045 0148 00", "BIC: DUSSDEDDXXX"
|
||||||
};
|
};
|
||||||
|
// Reminder greeting/intro, keyed by the actual reminder type (f = Zahlungserinnerung,
|
||||||
|
// m = Mahnung, l = letzte außergerichtliche Mahnung) — the single source of truth for
|
||||||
|
// both the PDF (ApplyReminder) and the online editor (via BuildReminderNotes).
|
||||||
public Dictionary<string, string[]> ReminderTexts_before { get; } = new()
|
public Dictionary<string, string[]> ReminderTexts_before { get; } = new()
|
||||||
{
|
{
|
||||||
["f"] = new[] {
|
["f"] = new[] {
|
||||||
"Sehr geehrte Damen und Herren,",
|
"Sehr geehrte Damen und Herren,",
|
||||||
"wir erlauben uns, Sie auf die nachfolgende offene Rechnung aufmerksam zu machen."
|
"ein Mahnschreiben sollte kurz, freundlich und erfolgreich sein. Kurz ist es, freundlich sowieso; ob es auch erfolgreich ist, h\u00e4ngt von Ihnen ab."
|
||||||
},
|
},
|
||||||
["s"] = new[] {
|
["m"] = new[] {
|
||||||
"Sehr geehrte Damen und Herren,",
|
"Sehr geehrte Damen und Herren,",
|
||||||
"trotz unserer Ersten Mahnung haben wir bis heute noch keinen Zahlungseingang feststellen k\u00f6nnen. " +
|
"nun m\u00fcssen wir Sie noch einmal anschreiben.",
|
||||||
"Wir bitten Sie, den offenen Betrag inkl. der Mahnzinsen und Mahnkosten umgehend auf unser Konto zu \u00fcberweisen."
|
"Wahrscheinlich haben Sie triftige Gr\u00fcnde daf\u00fcr, warum Sie die Zahlung unserer Forderung nicht vornehmen und auch nicht auf unsere Mahnung reagieren. Sollten wir dar\u00fcber nicht einmal sprechen?",
|
||||||
|
"Bitte nehmen Sie umgehend in dieser Sache mit uns Kontakt auf."
|
||||||
},
|
},
|
||||||
["l"] = Array.Empty<string>()
|
["l"] = new[] {
|
||||||
|
"Sehr geehrte Damen und Herren,",
|
||||||
|
"Eine DRITTE MAHNUNG zu erhalten bereitet Ihnen bestimmt ebenso wenig Freude wie uns, sie zu verschicken. Leider haben wir auf unsere zweite Mahnung noch keine Antwort von Ihnen erhalten.",
|
||||||
|
"Wir bitten Sie, den offenen Betrag innerhalb der n\u00e4chsten 7 Werktage nach Erhalt dieses Schreibens zu begleichen. Nach Ablauf dieser Frist erfolgt keine weitere Mahnung mehr.",
|
||||||
|
"Sollte die Forderung bis dahin nicht beglichen sein, er\u00f6ffnen wir das gerichtliche Mahnverfahren. Sollten Sie die Rechnung inzwischen beglichen haben, so betrachten Sie bitte dieses Schreiben als gegenstandslos."
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/// <summary>Extra closing sentence appended for reminder type <c>m</c> (Mahnung) — <c>f</c>'s closing is generated
|
||||||
|
/// dynamically (amount/IBAN + "gegenstandslos"); <c>l</c> gets no extra closing note.</summary>
|
||||||
|
public Dictionary<string, string> ReminderClosingExtra { get; } = new()
|
||||||
|
{
|
||||||
|
["m"] = "Um Ihnen zus\u00e4tzliche Kosten f\u00fcr weitere Mahnungen zu ersparen, bitten wir Sie nunmehr um die \u00dcberweisung des noch zu zahlenden Gesamtbetrages inklusive der ggf. bereits f\u00e4lligen Mahnzinsen und Mahngeb\u00fchren innerhalb von einer Woche."
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>One notice/explanatory paragraph, as rendered both in the PDF and the online editor.</summary>
|
||||||
|
public sealed record PdfNote(string Text, string Style = "InvoiceNotes");
|
||||||
|
|
||||||
// ── Currency / parse helpers ───────────────────────────────────────────────
|
// ── Currency / parse helpers ───────────────────────────────────────────────
|
||||||
public static string Currency(object? input, string returnobject = "?")
|
public static string Currency(object? input, string returnobject = "?")
|
||||||
{
|
{
|
||||||
@@ -529,7 +548,7 @@ public static class FuchsPdf
|
|||||||
ParagraphFormat ClonePf()
|
ParagraphFormat ClonePf()
|
||||||
{
|
{
|
||||||
var pf = doc.Styles["Normal"]!.ParagraphFormat.Clone();
|
var pf = doc.Styles["Normal"]!.ParagraphFormat.Clone();
|
||||||
pf.Borders.Distance = 3;
|
pf.Borders.Distance = 0;
|
||||||
pf.Alignment = ParagraphAlignment.Left;
|
pf.Alignment = ParagraphAlignment.Left;
|
||||||
return pf;
|
return pf;
|
||||||
}
|
}
|
||||||
@@ -557,145 +576,21 @@ public static class FuchsPdf
|
|||||||
AddStyle(doc, "InvoiceNotes_ucb", "InvoiceNotes", s => s.Font = new Font(tgtFont, 9) { Bold = true });
|
AddStyle(doc, "InvoiceNotes_ucb", "InvoiceNotes", s => s.Font = new Font(tgtFont, 9) { Bold = true });
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── ApplyInvoice ──────────────────────────────────────────────────────────
|
/// <summary>
|
||||||
public static void ApplyInvoice(Document doc, FdsTextBlocks tb, FdsInvoiceData inv, bool draft = false)
|
/// Builds the ordered list of standard invoice notice paragraphs (ported from legacy
|
||||||
|
/// fuchs_fds_pdf.vb) — pure data, no MigraDoc dependency, so it can be reused by both
|
||||||
|
/// <see cref="ApplyInvoice"/> and the online editor's draft view-state (ADR: notice texts
|
||||||
|
/// are backend-authoritative — the editor renders exactly what the PDF will show).
|
||||||
|
/// </summary>
|
||||||
|
public static List<PdfNote> BuildInvoiceNotes(FdsTextBlocks tb, FdsInvoiceData inv, bool p13b)
|
||||||
{
|
{
|
||||||
string invoiceType = inv.InvoiceType;
|
var notes = new List<PdfNote>();
|
||||||
bool p13b = inv.InvoiceRegistration?.getString("InvoiceOptions")
|
|
||||||
.Split(',').Contains("§13b") ?? false;
|
|
||||||
Apply_Invoice_Styles(doc);
|
|
||||||
|
|
||||||
var sec = doc.Sections.Cast<Section>().First();
|
|
||||||
sec.PageSetup.RightMargin = cm(1.5);
|
|
||||||
|
|
||||||
// Title
|
|
||||||
{
|
|
||||||
var p = sec.AddParagraph();
|
|
||||||
p.Style = "SubjectBig";
|
|
||||||
p.Format.SpaceBefore = cm(8.65);
|
|
||||||
p.AddText($"{inv.InvoiceTitle} Nr. {inv.InvoiceId}");
|
|
||||||
p.Format.SpaceAfter = cm(0.5);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Provision location
|
|
||||||
if (inv.ProvisionLocation.Length > 0)
|
|
||||||
{
|
|
||||||
var p = sec.AddParagraph();
|
|
||||||
p.Style = "AddressBox";
|
|
||||||
p.Format.Font.Size = 10;
|
|
||||||
p.Format.LineSpacingRule = LineSpacingRule.Exactly;
|
|
||||||
p.Format.LineSpacing = 12;
|
|
||||||
p.AddFormattedText(tb.ProvisionLoc_Label, TextFormat.Bold);
|
|
||||||
p.AddLineBreak();
|
|
||||||
foreach (string t in inv.ProvisionLocation) { p.AddText(t); p.AddLineBreak(); }
|
|
||||||
}
|
|
||||||
sec.AddParagraph().Format.SpaceBefore = cm(0.7);
|
|
||||||
|
|
||||||
// Invoice items table
|
|
||||||
var tbl = sec.AddTable();
|
|
||||||
tbl.Style = "Table";
|
|
||||||
tbl.Format.SpaceBefore = mm(1); tbl.Format.SpaceAfter = mm(1);
|
|
||||||
tbl.Borders.Color = Colors.White; tbl.Borders.Width = 0.25;
|
|
||||||
tbl.Borders.Left.Width = 0.5; tbl.Borders.Right.Width = 0.5;
|
|
||||||
tbl.Rows.LeftIndent = 0; tbl.Rows.Height = cm(0.6);
|
|
||||||
tbl.Rows.HeightRule = RowHeightRule.AtLeast;
|
|
||||||
|
|
||||||
double pageW = sec.PageSetup.PageWidth.Centimeter
|
|
||||||
- sec.PageSetup.LeftMargin.Centimeter
|
|
||||||
- sec.PageSetup.RightMargin.Centimeter;
|
|
||||||
double[] colWidths = { pageW * 0.06, pageW * 0.42, pageW * 0.12, pageW * 0.18, pageW * 0.22 };
|
|
||||||
foreach (double w in colWidths) tbl.AddColumn(cm(w));
|
|
||||||
|
|
||||||
// Header row
|
|
||||||
string[] headers = { "Pos.", "Bezeichnung", "Menge", "Einzelpreis", "Gesamtpreis" };
|
|
||||||
var hRow = tbl.AddRow();
|
|
||||||
hRow.Shading = GrayShading();
|
|
||||||
for (int i = 0; i < headers.Length; i++)
|
|
||||||
{
|
|
||||||
var p = hRow.Cells[i].AddParagraph(); p.Style = "TblCell_Head";
|
|
||||||
p.AddFormattedText(headers[i], TextFormat.Bold);
|
|
||||||
hRow.Cells[i].Format.Alignment = i >= 2 ? ParagraphAlignment.Right : ParagraphAlignment.Left;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Data rows — grouped by service-request block (see FdsInvoiceData.InvoiceBlocks).
|
|
||||||
// Per the product decision the PDF must mirror the online editor exactly: each section
|
|
||||||
// prints its heading, every position shows its own price, and positions are numbered the
|
|
||||||
// same way the editor numbers them (every line except free-text/heading lines, including a
|
|
||||||
// set header). Set-display collapsing is honoured only for the explicit SetOnly mode; every
|
|
||||||
// other mode renders the items flat, so nothing is silently blanked or renumbered.
|
|
||||||
var setMode = InvoiceSetPricing.ModeFromInvoiceOptions(inv.InvoiceRegistration?.getString("InvoiceOptions"));
|
|
||||||
int pos = 0;
|
|
||||||
foreach (var block in inv.InvoiceBlocks)
|
|
||||||
{
|
|
||||||
if (!string.IsNullOrWhiteSpace(block.Heading))
|
|
||||||
{
|
|
||||||
var hr = tbl.AddRow();
|
|
||||||
hr.HeightRule = RowHeightRule.Auto;
|
|
||||||
hr.Cells[1].MergeRight = 3; // span Bezeichnung … Gesamtpreis
|
|
||||||
hr.Cells[1].AddParagraph().WithStyle("TblCell_RTitle").AddFormattedText(block.Heading, TextFormat.Bold);
|
|
||||||
}
|
|
||||||
|
|
||||||
var lines = setMode == SetDisplayMode.SetOnly && InvoiceSetPricing.ContainsSets(block.Items)
|
|
||||||
? InvoiceSetPricing.Build(block.Items, SetDisplayMode.SetOnly) // only this mode collapses members
|
|
||||||
: block.Items.Select(MapItemToLine).ToList(); // flat: faithful mirror of the editor
|
|
||||||
|
|
||||||
foreach (var line in lines)
|
|
||||||
{
|
|
||||||
bool numbered = line.IsSetHeader || line.ShowPrice; // free-text/heading lines carry no number
|
|
||||||
var row = tbl.AddRow();
|
|
||||||
row.HeightRule = RowHeightRule.Auto;
|
|
||||||
row.Cells[0].AddParagraph(numbered ? (++pos).ToString() : "").Style = "TblCell_Base";
|
|
||||||
if (!string.IsNullOrEmpty(line.Title)) // skip the empty paragraph that added a blank line before free text
|
|
||||||
{
|
|
||||||
var titleCell = row.Cells[1].AddParagraph();
|
|
||||||
titleCell.Style = "TblCell_RTitle";
|
|
||||||
if (line.IsSetHeader) titleCell.AddFormattedText(line.Title, TextFormat.Bold);
|
|
||||||
else titleCell.AddText(line.Title);
|
|
||||||
}
|
|
||||||
if (!string.IsNullOrEmpty(line.Desc)) row.Cells[1].AddHtml($"<div>{line.Desc}</div>");
|
|
||||||
row.Cells[2].AddParagraph(line.Qty).Style = "TblCell_Base";
|
|
||||||
row.Cells[3].AddParagraph(line.ShowPrice ? Currency(line.PriceNet) : "").Style = "TblCell_Base";
|
|
||||||
row.Cells[4].AddParagraph(line.ShowPrice ? Currency(line.TotalNet) : "").Style = "TblCell_RSum";
|
|
||||||
row.Cells[2].Format.Alignment = ParagraphAlignment.Right;
|
|
||||||
row.Cells[3].Format.Alignment = ParagraphAlignment.Right;
|
|
||||||
row.Cells[4].Format.Alignment = ParagraphAlignment.Right;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Totals
|
|
||||||
void TotalRow(string label, string value, string style = "TblCell_TSum", bool topBorder = false)
|
|
||||||
{
|
|
||||||
var r = tbl.AddRow(); r.Shading = LightShading();
|
|
||||||
if (topBorder) r.Borders.Top.Width = 0.5;
|
|
||||||
var lp = r.Cells[3].AddParagraph(); lp.Style = style; lp.AddText(label);
|
|
||||||
r.Cells[3].Format.Alignment = ParagraphAlignment.Left;
|
|
||||||
var vp = r.Cells[4].AddParagraph(); vp.Style = style; vp.AddText(value);
|
|
||||||
r.Cells[4].Format.Alignment = ParagraphAlignment.Right;
|
|
||||||
}
|
|
||||||
|
|
||||||
ParseDec(inv.InvoiceRegistration?.getItem("InvoiceBalance_net"), out decimal netSum);
|
|
||||||
TotalRow("Nettobetrag:", Currency(netSum), "TblCell_TNet", topBorder: true);
|
|
||||||
|
|
||||||
// VAT rows
|
|
||||||
foreach (var vr in inv.VatRows)
|
|
||||||
{
|
|
||||||
string pct = vr.Key;
|
|
||||||
ParseDec(vr.Value.no("vat_amount", 0), out decimal vatAmt);
|
|
||||||
TotalRow($"MwSt. {pct}%:", Currency(vatAmt), "TblCell_TVat");
|
|
||||||
}
|
|
||||||
|
|
||||||
ParseDec(inv.InvoiceRegistration?.getItem("InvoiceBalance"), out decimal gross);
|
|
||||||
TotalRow("Rechnungsbetrag:", Currency(gross), "TblCell_TSum");
|
|
||||||
|
|
||||||
|
|
||||||
// ── Standard invoice notes (ported from legacy fuchs_fds_pdf.vb) ──────────
|
|
||||||
var reg = inv.InvoiceRegistration;
|
|
||||||
void Note(string text, string style = "InvoiceNotes")
|
void Note(string text, string style = "InvoiceNotes")
|
||||||
{
|
{
|
||||||
if (string.IsNullOrEmpty(text)) return;
|
if (!string.IsNullOrEmpty(text)) notes.Add(new PdfNote(text, style));
|
||||||
var np = sec.AddParagraph(); np.Style = style; np.AddText(text);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var reg = inv.InvoiceRegistration;
|
||||||
ParseDec(reg?.getItem("InvoiceService"), out decimal invService);
|
ParseDec(reg?.getItem("InvoiceService"), out decimal invService);
|
||||||
string serviceGross = Currency(reg?.getItem("InvoiceService"));
|
string serviceGross = Currency(reg?.getItem("InvoiceService"));
|
||||||
|
|
||||||
@@ -772,8 +667,179 @@ public static class FuchsPdf
|
|||||||
"die Umwelt zu schützen. Deshalb konzentrieren wir uns auf die Aufforstung von Flächen.");
|
"die Umwelt zu schützen. Deshalb konzentrieren wir uns auf die Aufforstung von Flächen.");
|
||||||
Note("Wir bedanken uns herzlich für Ihren Auftrag.");
|
Note("Wir bedanken uns herzlich für Ihren Auftrag.");
|
||||||
|
|
||||||
|
return notes;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Builds the reminder intro/closing notice paragraphs (greeting + closing bank/amount and
|
||||||
|
/// "gegenstandslos"/Mahnzinsen sentences), keyed by the actual reminder type. Pure data — no
|
||||||
|
/// MigraDoc dependency — reused by <see cref="ApplyReminder"/> and the online editor's draft
|
||||||
|
/// view-state so the editor shows exactly what the PDF will show.
|
||||||
|
/// </summary>
|
||||||
|
public static (string[] Intro, string[] Closing) BuildReminderNotes(FdsTextBlocks tb, FdsReminderData rem)
|
||||||
|
{
|
||||||
|
string rtype = rem.ReminderType;
|
||||||
|
string[] intro = tb.ReminderTexts_before.TryGetValue(rtype, out var i) ? i : Array.Empty<string>();
|
||||||
|
|
||||||
|
ParseDec(rem.ReminderRegistration?.getItem("amount_open"), out decimal openTotal);
|
||||||
|
string terms = TranslatePaymentTerm(rem.ReminderRegistration?.getString("PaymentTerm") ?? "");
|
||||||
|
|
||||||
|
var closing = new List<string>();
|
||||||
|
switch (rtype)
|
||||||
|
{
|
||||||
|
case "f":
|
||||||
|
closing.Add(
|
||||||
|
$"Bitte überweisen Sie den offenen Betrag von {Currency(openTotal)} innerhalb von {terms} auf unser Konto:\n" +
|
||||||
|
"IBAN: DE76\u00a03005\u00a00110\u00a00045\u00a00148\u00a000, BIC DUSSSDEDDXXX (Stadtsparkasse Düsseldorf)");
|
||||||
|
closing.Add("Sollten Sie den Betrag bereits überwiesen haben, so bitten wir Sie, diese Zahlungserinnerung als gegenstandslos zu betrachten.");
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
if (tb.ReminderClosingExtra.TryGetValue(rtype, out var extra)) closing.Add(extra);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
return (intro, closing.ToArray());
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── ApplyInvoice ──────────────────────────────────────────────────────────
|
||||||
|
public static void ApplyInvoice(Document doc, FdsTextBlocks tb, FdsInvoiceData inv, bool draft = false)
|
||||||
|
{
|
||||||
|
string invoiceType = inv.InvoiceType;
|
||||||
|
bool p13b = inv.InvoiceRegistration?.getString("InvoiceOptions")
|
||||||
|
.Split(',').Contains("§13b") ?? false;
|
||||||
|
Apply_Invoice_Styles(doc);
|
||||||
|
|
||||||
|
var sec = doc.Sections.Cast<Section>().First();
|
||||||
|
sec.PageSetup.RightMargin = cm(1.5);
|
||||||
|
|
||||||
|
// Title
|
||||||
|
{
|
||||||
|
var p = sec.AddParagraph();
|
||||||
|
p.Style = "SubjectBig";
|
||||||
|
p.Format.SpaceBefore = cm(8.65);
|
||||||
|
p.AddText($"{inv.InvoiceTitle} Nr. {inv.InvoiceId}");
|
||||||
|
p.Format.SpaceAfter = cm(0.5);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Provision location
|
||||||
|
if (inv.ProvisionLocation.Length > 0)
|
||||||
|
{
|
||||||
|
var p = sec.AddParagraph();
|
||||||
|
p.Style = "AddressBox";
|
||||||
|
p.Format.Font.Size = 10;
|
||||||
|
p.Format.LineSpacingRule = LineSpacingRule.Exactly;
|
||||||
|
p.Format.LineSpacing = 12;
|
||||||
|
p.AddFormattedText(tb.ProvisionLoc_Label, TextFormat.Bold);
|
||||||
|
p.AddLineBreak();
|
||||||
|
foreach (string t in inv.ProvisionLocation) { p.AddText(t); p.AddLineBreak(); }
|
||||||
|
}
|
||||||
|
sec.AddParagraph().Format.SpaceBefore = cm(0.7);
|
||||||
|
|
||||||
|
// Invoice items table
|
||||||
|
var tbl = sec.AddTable();
|
||||||
|
tbl.Style = "Table";
|
||||||
|
tbl.Format.SpaceBefore = mm(1); tbl.Format.SpaceAfter = mm(1);
|
||||||
|
tbl.Borders.Color = Colors.White; tbl.Borders.Width = 0.25;
|
||||||
|
tbl.Borders.Left.Width = 0.5; tbl.Borders.Right.Width = 0.5;
|
||||||
|
tbl.Rows.LeftIndent = 0; tbl.Rows.Height = cm(0.6);
|
||||||
|
tbl.Rows.HeightRule = RowHeightRule.AtLeast;
|
||||||
|
|
||||||
|
double pageW = sec.PageSetup.PageWidth.Centimeter
|
||||||
|
- sec.PageSetup.LeftMargin.Centimeter
|
||||||
|
- sec.PageSetup.RightMargin.Centimeter;
|
||||||
|
double[] colWidths = { pageW * 0.06, pageW * 0.42, pageW * 0.12, pageW * 0.18, pageW * 0.22 };
|
||||||
|
foreach (double w in colWidths) tbl.AddColumn(cm(w));
|
||||||
|
|
||||||
|
// Header row
|
||||||
|
string[] headers = { "Pos.", "Bezeichnung", "Menge", "Einzelpreis", "Gesamtpreis" };
|
||||||
|
var hRow = tbl.AddRow();
|
||||||
|
hRow.Shading = GrayShading();
|
||||||
|
for (int i = 0; i < headers.Length; i++)
|
||||||
|
{
|
||||||
|
var p = hRow.Cells[i].AddParagraph(); p.Style = "TblCell_Head";
|
||||||
|
p.AddFormattedText(headers[i], TextFormat.Bold);
|
||||||
|
hRow.Cells[i].Format.Alignment = i >= 2 ? ParagraphAlignment.Right : ParagraphAlignment.Left;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Data rows — grouped by service-request block (see FdsInvoiceData.InvoiceBlocks).
|
||||||
|
// Each section prints its heading; positions are numbered the same way the editor numbers
|
||||||
|
// them (every line except free-text/heading lines, including a set header). The chosen
|
||||||
|
// set-display mode (see INVOICE_SET_PRICING.md) governs how "set" items and their members
|
||||||
|
// are shown: SetPrice (default) prices the set, blanks members; ItemPrices prices the
|
||||||
|
// members, blanks the set heading; SetOnly prices the set and drops the members entirely.
|
||||||
|
// Blocks without any set items are unaffected and always render flat.
|
||||||
|
var setMode = InvoiceSetPricing.ModeFromInvoiceOptions(inv.InvoiceRegistration?.getString("InvoiceOptions"));
|
||||||
|
int pos = 0;
|
||||||
|
foreach (var block in inv.InvoiceBlocks)
|
||||||
|
{
|
||||||
|
if (!string.IsNullOrWhiteSpace(block.Heading))
|
||||||
|
{
|
||||||
|
var hr = tbl.AddRow();
|
||||||
|
hr.HeightRule = RowHeightRule.Auto;
|
||||||
|
hr.Cells[1].MergeRight = 3; // span Bezeichnung … Gesamtpreis
|
||||||
|
hr.Cells[1].AddParagraph().WithStyle("TblCell_RTitle").AddFormattedText(block.Heading, TextFormat.Bold);
|
||||||
|
}
|
||||||
|
|
||||||
|
var lines = InvoiceSetPricing.ContainsSets(block.Items)
|
||||||
|
? InvoiceSetPricing.Build(block.Items, setMode)
|
||||||
|
: block.Items.Select(MapItemToLine).ToList(); // no sets: flat, faithful mirror of the editor
|
||||||
|
|
||||||
|
foreach (var line in lines)
|
||||||
|
{
|
||||||
|
bool numbered = line.IsSetHeader || line.ShowPrice; // free-text/heading lines carry no number
|
||||||
|
var row = tbl.AddRow();
|
||||||
|
row.HeightRule = RowHeightRule.Auto;
|
||||||
|
row.Cells[0].AddParagraph(numbered ? (++pos).ToString() : "").Style = "TblCell_Base";
|
||||||
|
if (!string.IsNullOrEmpty(line.Title)) // skip the empty paragraph that added a blank line before free text
|
||||||
|
{
|
||||||
|
var titleCell = row.Cells[1].AddParagraph();
|
||||||
|
titleCell.Style = "TblCell_RTitle";
|
||||||
|
if (line.IsSetHeader) titleCell.AddFormattedText(line.Title, TextFormat.Bold);
|
||||||
|
else titleCell.AddText(line.Title);
|
||||||
|
}
|
||||||
|
if (!string.IsNullOrEmpty(line.Desc)) row.Cells[1].AddHtml($"<div>{line.Desc}</div>");
|
||||||
|
row.Cells[2].AddParagraph(line.Qty).Style = "TblCell_Base";
|
||||||
|
row.Cells[3].AddParagraph(line.ShowPrice ? Currency(line.PriceNet) : "").Style = "TblCell_Base";
|
||||||
|
row.Cells[4].AddParagraph(line.ShowPrice ? Currency(line.TotalNet) : "").Style = "TblCell_RSum";
|
||||||
|
row.Cells[2].Format.Alignment = ParagraphAlignment.Right;
|
||||||
|
row.Cells[3].Format.Alignment = ParagraphAlignment.Right;
|
||||||
|
row.Cells[4].Format.Alignment = ParagraphAlignment.Right;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Totals
|
||||||
|
void TotalRow(string label, string value, string style = "TblCell_TSum", bool topBorder = false)
|
||||||
|
{
|
||||||
|
var r = tbl.AddRow(); r.Shading = LightShading();
|
||||||
|
if (topBorder) r.Borders.Top.Width = 0.5;
|
||||||
|
var lp = r.Cells[3].AddParagraph(); lp.Style = style; lp.AddText(label);
|
||||||
|
r.Cells[3].Format.Alignment = ParagraphAlignment.Left;
|
||||||
|
var vp = r.Cells[4].AddParagraph(); vp.Style = style; vp.AddText(value);
|
||||||
|
r.Cells[4].Format.Alignment = ParagraphAlignment.Right;
|
||||||
|
}
|
||||||
|
|
||||||
|
ParseDec(inv.InvoiceRegistration?.getItem("InvoiceBalance_net"), out decimal netSum);
|
||||||
|
TotalRow("Nettobetrag:", Currency(netSum), "TblCell_TNet", topBorder: true);
|
||||||
|
|
||||||
|
// VAT rows
|
||||||
|
foreach (var vr in inv.VatRows)
|
||||||
|
{
|
||||||
|
string pct = vr.Key;
|
||||||
|
ParseDec(vr.Value.no("vat_amount", 0), out decimal vatAmt);
|
||||||
|
TotalRow($"MwSt. {pct}%:", Currency(vatAmt), "TblCell_TVat");
|
||||||
|
}
|
||||||
|
|
||||||
|
ParseDec(inv.InvoiceRegistration?.getItem("InvoiceBalance"), out decimal gross);
|
||||||
|
TotalRow("Rechnungsbetrag:", Currency(gross), "TblCell_TSum");
|
||||||
|
|
||||||
|
|
||||||
|
// ── Standard invoice notes (ported from legacy fuchs_fds_pdf.vb) ──────────
|
||||||
|
foreach (var note in BuildInvoiceNotes(tb, inv, p13b))
|
||||||
|
{
|
||||||
|
var np = sec.AddParagraph(); np.Style = note.Style; np.AddText(note.Text);
|
||||||
|
}
|
||||||
|
|
||||||
// GiroCode payment QR (only on finalized invoices with a positive balance)
|
// GiroCode payment QR (only on finalized invoices with a positive balance)
|
||||||
ParseDec(reg?.getItem("InvoiceBalance"), out decimal payAmount);
|
ParseDec(inv.InvoiceRegistration?.getItem("InvoiceBalance"), out decimal payAmount);
|
||||||
if (!inv.IsDraft && payAmount > 0 && !string.IsNullOrWhiteSpace(inv.InvoiceId))
|
if (!inv.IsDraft && payAmount > 0 && !string.IsNullOrWhiteSpace(inv.InvoiceId))
|
||||||
AddGirocode(sec, payAmount, $"{inv.InvoiceTitle.ne("Rechnung")} {inv.InvoiceId}");
|
AddGirocode(sec, payAmount, $"{inv.InvoiceTitle.ne("Rechnung")} {inv.InvoiceId}");
|
||||||
}
|
}
|
||||||
@@ -795,31 +861,37 @@ public static class FuchsPdf
|
|||||||
p.AddText(rem.ReminderTitle.ne("Zahlungserinnerung"));
|
p.AddText(rem.ReminderTitle.ne("Zahlungserinnerung"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var (introLines, closingLines) = BuildReminderNotes(tb, rem);
|
||||||
|
|
||||||
// Opening text
|
// Opening text
|
||||||
if (tb.ReminderTexts_before.TryGetValue(rtype, out var intro))
|
if (introLines.Length > 0)
|
||||||
{
|
{
|
||||||
var p = sec.AddParagraph(); p.Style = "BodyText";
|
var p = sec.AddParagraph(); p.Style = "BodyText";
|
||||||
p.Format.SpaceBefore = cm(0.5);
|
p.Format.SpaceBefore = cm(0.5);
|
||||||
foreach (string line in intro) { p.AddText(line); p.AddLineBreak(); }
|
foreach (string line in introLines) { p.AddText(line); p.AddLineBreak(); }
|
||||||
}
|
}
|
||||||
|
sec.AddParagraph().Format.SpaceBefore = cm(0.7);
|
||||||
|
|
||||||
// Reminder items table
|
// Reminder items table — mirrors the online editor's remHR columns exactly
|
||||||
|
// (Rechnung, vom, Rechnungsbetrag, bereits bezahlt, noch offen).
|
||||||
var tbl = sec.AddTable();
|
var tbl = sec.AddTable();
|
||||||
tbl.Style = "Table";
|
tbl.Style = "Table";
|
||||||
|
tbl.Format.SpaceBefore = mm(1); tbl.Format.SpaceAfter = mm(1);
|
||||||
tbl.Borders.Color = Colors.White; tbl.Borders.Width = 0.25;
|
tbl.Borders.Color = Colors.White; tbl.Borders.Width = 0.25;
|
||||||
|
tbl.Borders.Left.Width = 0.5; tbl.Borders.Right.Width = 0.5;
|
||||||
tbl.Rows.LeftIndent = 0; tbl.Rows.Height = cm(0.6);
|
tbl.Rows.LeftIndent = 0; tbl.Rows.Height = cm(0.6);
|
||||||
tbl.Rows.HeightRule = RowHeightRule.AtLeast;
|
tbl.Rows.HeightRule = RowHeightRule.AtLeast;
|
||||||
tbl.Format.SpaceBefore = mm(4);
|
|
||||||
|
|
||||||
double pageW = sec.PageSetup.PageWidth.Centimeter
|
double pageW = sec.PageSetup.PageWidth.Centimeter
|
||||||
- sec.PageSetup.LeftMargin.Centimeter
|
- sec.PageSetup.LeftMargin.Centimeter
|
||||||
- sec.PageSetup.RightMargin.Centimeter;
|
- sec.PageSetup.RightMargin.Centimeter;
|
||||||
|
tbl.AddColumn(cm(pageW * 0.20));
|
||||||
tbl.AddColumn(cm(pageW * 0.15));
|
tbl.AddColumn(cm(pageW * 0.15));
|
||||||
tbl.AddColumn(cm(pageW * 0.45));
|
tbl.AddColumn(cm(pageW * 0.22));
|
||||||
tbl.AddColumn(cm(pageW * 0.20));
|
tbl.AddColumn(cm(pageW * 0.21));
|
||||||
tbl.AddColumn(cm(pageW * 0.20));
|
tbl.AddColumn(cm(pageW * 0.22));
|
||||||
|
|
||||||
string[] headers = { "Datum", "Bezeichnung", "Betrag", "Offen" };
|
string[] headers = { "Rechnung", "vom", "Rechnungsbetrag", "bereits bezahlt", "noch offen" };
|
||||||
var hRow = tbl.AddRow(); hRow.Shading = GrayShading();
|
var hRow = tbl.AddRow(); hRow.Shading = GrayShading();
|
||||||
for (int i = 0; i < headers.Length; i++)
|
for (int i = 0; i < headers.Length; i++)
|
||||||
{
|
{
|
||||||
@@ -828,33 +900,39 @@ public static class FuchsPdf
|
|||||||
hRow.Cells[i].Format.Alignment = i >= 2 ? ParagraphAlignment.Right : ParagraphAlignment.Left;
|
hRow.Cells[i].Format.Alignment = i >= 2 ? ParagraphAlignment.Right : ParagraphAlignment.Left;
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach (var itm in rem.ReminderItems)
|
ParseDec(rem.ReminderRegistration?.getItem("amount"), out decimal amountTotal);
|
||||||
{
|
ParseDec(rem.ReminderRegistration?.getItem("amount_payed"), out decimal amountPayed);
|
||||||
var row = tbl.AddRow(); row.HeightRule = RowHeightRule.Auto;
|
ParseDec(rem.ReminderRegistration?.getItem("amount_open"), out decimal openTotal);
|
||||||
row.Cells[0].AddParagraph(itm.nz("InvoiceDate", ""));
|
string invoiceDate = rem.ReminderRegistration?.getString("InvoiceDate") is { Length: > 0 } d
|
||||||
row.Cells[1].AddParagraph(itm.nz("DocumentName", "").ne(itm.nz("InvoiceTitle", "")));
|
&& DateTime.TryParse(d, out var dd) ? dd.ToString("dd.MM.yy") : "";
|
||||||
row.Cells[2].AddParagraph(Currency(itm.no("InvoiceBalance", 0)));
|
|
||||||
row.Cells[3].AddParagraph(Currency(itm.no("amount_open", 0)));
|
var row = tbl.AddRow(); row.HeightRule = RowHeightRule.Auto;
|
||||||
row.Cells[2].Format.Alignment = ParagraphAlignment.Right;
|
row.Cells[0].AddParagraph(rem.InvoiceId);
|
||||||
row.Cells[3].Format.Alignment = ParagraphAlignment.Right;
|
row.Cells[1].AddParagraph(invoiceDate);
|
||||||
}
|
row.Cells[2].AddParagraph(Currency(amountTotal));
|
||||||
|
row.Cells[3].AddParagraph(Currency(amountPayed));
|
||||||
|
row.Cells[4].AddParagraph(Currency(openTotal));
|
||||||
|
for (int i = 2; i <= 4; i++) row.Cells[i].Format.Alignment = ParagraphAlignment.Right;
|
||||||
|
|
||||||
// Open total
|
// Open total
|
||||||
ParseDec(rem.ReminderRegistration?.getItem("amount_open"), out decimal openTotal);
|
|
||||||
var sumRow = tbl.AddRow(); sumRow.Shading = LightShading();
|
var sumRow = tbl.AddRow(); sumRow.Shading = LightShading();
|
||||||
sumRow.Borders.Top.Width = 0.5;
|
sumRow.Borders.Top.Width = 0.5;
|
||||||
sumRow.Cells[2].AddParagraph().AddFormattedText("Offener Betrag:", TextFormat.Bold);
|
sumRow.Cells[3].AddParagraph().AddFormattedText("Offener Betrag:", TextFormat.Bold);
|
||||||
var sv = sumRow.Cells[3].AddParagraph();
|
var sv = sumRow.Cells[4].AddParagraph();
|
||||||
sv.AddFormattedText(Currency(openTotal), TextFormat.Bold);
|
sv.AddFormattedText(Currency(openTotal), TextFormat.Bold);
|
||||||
sumRow.Cells[3].Format.Alignment = ParagraphAlignment.Right;
|
sumRow.Cells[4].Format.Alignment = ParagraphAlignment.Right;
|
||||||
|
|
||||||
// Closing text + QR code
|
// Closing text + QR code
|
||||||
var closing = sec.AddParagraph(); closing.Style = "BodyText";
|
if (closingLines.Length > 0)
|
||||||
closing.Format.SpaceBefore = cm(0.8);
|
{
|
||||||
string terms = TranslatePaymentTerm(rem.ReminderRegistration?.getString("PaymentTerm") ?? "");
|
var closing = sec.AddParagraph(); closing.Style = "BodyText";
|
||||||
closing.AddText(
|
closing.Format.SpaceBefore = cm(0.8);
|
||||||
$"Bitte \u00fcberweisen Sie den offenen Betrag von {Currency(openTotal)} innerhalb von {terms} auf unser Konto:\n" +
|
for (int i = 0; i < closingLines.Length; i++)
|
||||||
"IBAN: DE76\u00a03005\u00a00110\u00a00045\u00a00148\u00a000, BIC DUSSSDEDDXXX (Stadtsparkasse D\u00fcsseldorf)");
|
{
|
||||||
|
if (i > 0) closing.AddLineBreak();
|
||||||
|
closing.AddText(closingLines[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// GiroCode payment QR (only on finalized reminders with a positive open amount)
|
// GiroCode payment QR (only on finalized reminders with a positive open amount)
|
||||||
if (!rem.IsDraft && openTotal > 0 && !string.IsNullOrWhiteSpace(rem.InvoiceId))
|
if (!rem.IsDraft && openTotal > 0 && !string.IsNullOrWhiteSpace(rem.InvoiceId))
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
using static OCORE.commons;
|
using static OCORE.commons;
|
||||||
using static OCORE.OCORE_dictionaries;
|
using static OCORE.OCORE_dictionaries;
|
||||||
|
|
||||||
namespace Fuchs.intranet;
|
namespace Fuchs.intranet;
|
||||||
@@ -22,6 +22,9 @@ public enum SetDisplayMode
|
|||||||
/// <summary>A resolved invoice display line (after applying the set display mode).</summary>
|
/// <summary>A resolved invoice display line (after applying the set display mode).</summary>
|
||||||
public sealed class InvoiceSetLine
|
public sealed class InvoiceSetLine
|
||||||
{
|
{
|
||||||
|
/// <summary>The source item's id — lets callers (e.g. the online editor) map a resolved
|
||||||
|
/// display line back to the DOM row it should update.</summary>
|
||||||
|
public string Id { get; init; } = "";
|
||||||
public string Title { get; init; } = "";
|
public string Title { get; init; } = "";
|
||||||
public string Desc { get; init; } = "";
|
public string Desc { get; init; } = "";
|
||||||
public string Qty { get; init; } = "1";
|
public string Qty { get; init; } = "1";
|
||||||
@@ -156,6 +159,7 @@ public static class InvoiceSetPricing
|
|||||||
|
|
||||||
private static InvoiceSetLine HeaderLine(Dictionary<string, object?> i, decimal setTotal, bool showPrice) => new()
|
private static InvoiceSetLine HeaderLine(Dictionary<string, object?> i, decimal setTotal, bool showPrice) => new()
|
||||||
{
|
{
|
||||||
|
Id = i.nz("id", ""),
|
||||||
Title = i.nz("title", ""),
|
Title = i.nz("title", ""),
|
||||||
Desc = i.nz("desc", ""),
|
Desc = i.nz("desc", ""),
|
||||||
Qty = i.nz("qty", "1"),
|
Qty = i.nz("qty", "1"),
|
||||||
@@ -171,6 +175,7 @@ public static class InvoiceSetPricing
|
|||||||
FuchsPdf.ParseDec(i.no("total_net", 0), out decimal total);
|
FuchsPdf.ParseDec(i.no("total_net", 0), out decimal total);
|
||||||
return new InvoiceSetLine
|
return new InvoiceSetLine
|
||||||
{
|
{
|
||||||
|
Id = i.nz("id", ""),
|
||||||
Title = i.nz("title", ""),
|
Title = i.nz("title", ""),
|
||||||
Desc = i.nz("desc", ""),
|
Desc = i.nz("desc", ""),
|
||||||
Qty = i.nz("qty", "1"),
|
Qty = i.nz("qty", "1"),
|
||||||
|
|||||||
@@ -36,11 +36,16 @@ let $bam = {
|
|||||||
let fds = bool(ix.fds, false), id = ix.taID, m = [];
|
let fds = bool(ix.fds, false), id = ix.taID, m = [];
|
||||||
m.push({ lbl: $bct.smd, fnc: () => { $bam.smd(id); } });
|
m.push({ lbl: $bct.smd, fnc: () => { $bam.smd(id); } });
|
||||||
m.push({ lbl: $bct.ati, fnc: () => { $bam.ati(id); } });
|
m.push({ lbl: $bct.ati, fnc: () => { $bam.ati(id); } });
|
||||||
|
if (bool(ix.IsManual, false) === true) {
|
||||||
|
m.push({ lbl: $bct.mbc, fnc: () => { $bam.mbc(id); } });
|
||||||
|
m.push({ lbl: $bct.mbd, fnc: () => { $bam.mbd(id); } });
|
||||||
|
}
|
||||||
return $('#topbar').ocmsmenu(m);
|
return $('#topbar').ocmsmenu(m);
|
||||||
}, eM : (r, re, opt) => {
|
}, eM : (r, re, opt) => {
|
||||||
let m = [];
|
let m = [];
|
||||||
if ($fis.isAuth('fds_bam', 2) === true) {
|
if ($fis.isAuth('fds_bam', 2) === true) {
|
||||||
m.push({ lbl: $bct.upl, fnc: $bam.ubs});
|
m.push({ lbl: $bct.upl, fnc: $bam.ubs});
|
||||||
|
m.push({ lbl: $bct.mbe, fnc: $bam.mbe});
|
||||||
}
|
}
|
||||||
if (bool(r, false) === true) {
|
if (bool(r, false) === true) {
|
||||||
m.push({ lbl: $bct.rel, fnc: $bam.renderLst });
|
m.push({ lbl: $bct.rel, fnc: $bam.renderLst });
|
||||||
@@ -312,6 +317,32 @@ let $bam = {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
}, mbe: () => {
|
||||||
|
$bam.manForm({});
|
||||||
|
}, mbc: (taid) => {
|
||||||
|
$ocms.postXT({
|
||||||
|
url: $ocms.url('bam/mget'), data: { taid: taid }, success: (dta) => {
|
||||||
|
$bam.manForm(dta || {});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}, mbd: (taid) => {
|
||||||
|
if (confirm($bct.mbdc)) {
|
||||||
|
$ocms.postXT({
|
||||||
|
url: $ocms.url('bam/mdel'), data: { taid: taid }, success: () => {
|
||||||
|
$bam.renderLst();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}, manForm: (dta) => {
|
||||||
|
let flds = $bcol.man.clone().applyValues(dta || {});
|
||||||
|
$ocms.dlgform(flds, {
|
||||||
|
title: (ne(dta.taID, '') !== '') ? $bct.mbc : $bct.mbe,
|
||||||
|
url: $ocms.url('bam/man'),
|
||||||
|
typedvalues: true,
|
||||||
|
success: () => {
|
||||||
|
$bam.renderLst();
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,10 +18,21 @@
|
|||||||
},
|
},
|
||||||
in: 'Rechnungsnummer',
|
in: 'Rechnungsnummer',
|
||||||
bt: 'Buchungstexte',
|
bt: 'Buchungstexte',
|
||||||
bv: 'Buchungswert (volle €)'
|
bv: 'Buchungswert (volle €)',
|
||||||
|
mbe: 'Manuelle Buchung erfassen',
|
||||||
|
mbc: 'Manuelle Buchung ändern',
|
||||||
|
mbd: 'Manuelle Buchung löschen',
|
||||||
|
mbdc: 'Manuelle Buchung wirklich löschen?\nDieser Vorgang kann nicht rückgängig gemacht werden.'
|
||||||
};
|
};
|
||||||
|
|
||||||
let $bcol = {
|
let $bcol = {
|
||||||
|
man: new fields_definition('Manuelle Buchung', 'Manuelle Buchungen', [
|
||||||
|
{ name: 'taID', type: 'hidden' },
|
||||||
|
{ name: 'ValueDate', label: 'Valuta', type: 'date', required: true },
|
||||||
|
{ name: 'Amount', label: 'Betrag', type: 'number', precision: '0.01', required: true },
|
||||||
|
{ name: 'NameOfPayer', label: 'Name', type: 'string' },
|
||||||
|
{ name: 'SepaRemittanceInformation', label: 'Verwendungszweck', type: 'string' }
|
||||||
|
]),
|
||||||
qtl: new fields_definition('Kontobericht', 'Kontoberichte', [
|
qtl: new fields_definition('Kontobericht', 'Kontoberichte', [
|
||||||
{ name: 'InvoiceId', label: 'RechnungsNr', type: 'string' },
|
{ name: 'InvoiceId', label: 'RechnungsNr', type: 'string' },
|
||||||
{ name: 'ValueDate', label: 'Valuta', type: 'date' },
|
{ name: 'ValueDate', label: 'Valuta', type: 'date' },
|
||||||
|
|||||||
@@ -82,6 +82,11 @@ $inv.iMn = (ix) => {
|
|||||||
}
|
}
|
||||||
return $('#topbar').ocmsmenu(m);
|
return $('#topbar').ocmsmenu(m);
|
||||||
}
|
}
|
||||||
|
/* Menu option CSV builder: 'setm' (Set-Preisanzeige) is only offered while the invoice has not
|
||||||
|
yet been explicitly switched to a set-display mode (admin.setmode empty/undefined) — once set,
|
||||||
|
changing it again is not supported (the user edits the line items directly instead); it
|
||||||
|
reappears only if 'Änderungen verwerfen' reverts to a DB state that was never switched. */
|
||||||
|
$inv.emOpt = (admin) => 'iss,p13b,ctp' + (((admin || {}).setmode || '') === '' ? ',setm' : '');
|
||||||
$inv.eM = (r, re, opt) => {
|
$inv.eM = (r, re, opt) => {
|
||||||
let m = [];
|
let m = [];
|
||||||
if (booln(r, false) === true || booln(re, false) === true) {
|
if (booln(r, false) === true || booln(re, false) === true) {
|
||||||
@@ -154,8 +159,24 @@ $inv.d = {
|
|||||||
let tbl = $inv.d.tbl(); if (tbl.length < 1) { return; }
|
let tbl = $inv.d.tbl(); if (tbl.length < 1) { return; }
|
||||||
tbl.data('dver', state.version).data('serverSums', state.sums);
|
tbl.data('dver', state.version).data('serverSums', state.sums);
|
||||||
$inv.d.footer(tbl, state.sums || {}, state.admin || {});
|
$inv.d.footer(tbl, state.sums || {}, state.admin || {});
|
||||||
|
$inv.d.notes(state.notes || []);
|
||||||
$inv.d.validation(state.validation || []);
|
$inv.d.validation(state.validation || []);
|
||||||
$inv.d.applyPositions(tbl, state.req || []);
|
$inv.d.applyPositions(tbl, state.req || []);
|
||||||
|
$inv.d.applySetDisplay(tbl, state.setDisplay || {});
|
||||||
|
/* Keep the menu's "Set-Preisanzeige" entry in sync with the authoritative admin.setmode:
|
||||||
|
hides it right after a mode switch, and restores it if a discard reverted the draft to
|
||||||
|
a DB state that was never switched (ADR 0006 backend-authoritative refresh). */
|
||||||
|
$inv.eM(false, true, $inv.emOpt(state.admin || {}));
|
||||||
|
},
|
||||||
|
/* Renders the backend-authoritative notice paragraphs (ADR: notice texts are
|
||||||
|
backend-authoritative) exactly as FuchsPdf.BuildInvoiceNotes emits them for the PDF,
|
||||||
|
so the online editor always shows precisely what will be printed. */
|
||||||
|
notes: function (notes) {
|
||||||
|
let frm = $inv.d.layout(); if (frm.length < 1) { return; }
|
||||||
|
let box = frm.children('.dnotes');
|
||||||
|
if (box.length < 1) { box = $$.dc('dnotes'); frm.append(box); }
|
||||||
|
box.empty();
|
||||||
|
(notes || []).forEach((n) => $$.dc('dnote').aC(n.style || 'InvoiceNotes').text(n.text));
|
||||||
},
|
},
|
||||||
/* Push the server's authoritative position numbers back onto the rendered rows so the online
|
/* Push the server's authoritative position numbers back onto the rendered rows so the online
|
||||||
editor and the PDF preview always agree (the server numbers priced lines continuously; the
|
editor and the PDF preview always agree (the server numbers priced lines continuously; the
|
||||||
@@ -167,6 +188,23 @@ $inv.d = {
|
|||||||
if (cell.length) { cell.text(co.p != null ? co.p : ''); }
|
if (cell.length) { cell.text(co.p != null ? co.p : ''); }
|
||||||
}));
|
}));
|
||||||
},
|
},
|
||||||
|
/* Applies the backend-authoritative set-pricing display flags (see BuildSetDisplay /
|
||||||
|
InvoiceSetPricing) onto the rendered rows: blanks the price/total cells for lines the
|
||||||
|
server says should show no price (set members in SetPrice mode, the set header in
|
||||||
|
ItemPrices mode), and emphasises the set header line — so the online editor always
|
||||||
|
shows exactly what the PDF will print, without duplicating the pricing rules client-side.
|
||||||
|
Rows not present in the map (no set in that block, or standalone items) are left untouched. */
|
||||||
|
applySetDisplay: function (tbl, setDisplay) {
|
||||||
|
tbl.find('tr.itm').each((i, tr) => {
|
||||||
|
let rw = $(tr), dta = rw.data() || {}, id = (dta.Id || '').toString();
|
||||||
|
if (id === '' || !Object.prototype.hasOwnProperty.call(setDisplay, id)) { return; }
|
||||||
|
let flags = setDisplay[id] || {};
|
||||||
|
rw.tC('sethdr', bool(flags.h, false));
|
||||||
|
if (bool(flags.p, true) === false) {
|
||||||
|
rw.find('td.currency').text('');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
/* Send one change to the server; the draftReady signal and this success both refresh. */
|
/* Send one change to the server; the draftReady signal and this success both refresh. */
|
||||||
sync: function (delta) {
|
sync: function (delta) {
|
||||||
let t = $inv.d.token(); if (t === '') { return; }
|
let t = $inv.d.token(); if (t === '') { return; }
|
||||||
@@ -354,8 +392,23 @@ $inv.rd = {
|
|||||||
let tbl = $inv.rd.tbl(); if (tbl.length < 1) { return; }
|
let tbl = $inv.rd.tbl(); if (tbl.length < 1) { return; }
|
||||||
tbl.data('rdver', state.version).data('serverSums', state.sums).data('remid', state.remid || '');
|
tbl.data('rdver', state.version).data('serverSums', state.sums).data('remid', state.remid || '');
|
||||||
$inv.rd.footer(tbl, state.sums || {});
|
$inv.rd.footer(tbl, state.sums || {});
|
||||||
|
$inv.rd.notes(state.notes || {});
|
||||||
$inv.rd.validation(state.validation || []);
|
$inv.rd.validation(state.validation || []);
|
||||||
},
|
},
|
||||||
|
/* Renders the backend-authoritative intro/closing notice paragraphs (ADR: notice texts
|
||||||
|
are backend-authoritative) exactly as FuchsPdf.BuildReminderNotes emits them for the
|
||||||
|
PDF, so the online editor always shows precisely what will be printed. */
|
||||||
|
notes: function (notes) {
|
||||||
|
let frm = $inv.rd.layout(); if (frm.length < 1) { return; }
|
||||||
|
let rq = frm.find('.srq'); if (rq.length < 1) { return; }
|
||||||
|
rq.children('.dnote').remove();
|
||||||
|
let intro = (notes || {}).intro || [], first = rq.children().first();
|
||||||
|
intro.slice().reverse().forEach((t) => {
|
||||||
|
let p = $$[0]('p').aC('dnote intro').rwText(t);
|
||||||
|
if (first.length) { p.insertBefore(first); } else { p.appendTo(rq); }
|
||||||
|
});
|
||||||
|
((notes || {}).closing || []).forEach((t) => $$[0]('p').aC('dnote closing').rwText(t).appendTo(rq));
|
||||||
|
},
|
||||||
/* Send one change to the server; the draftReady signal and this success both refresh. */
|
/* Send one change to the server; the draftReady signal and this success both refresh. */
|
||||||
sync: function (delta) {
|
sync: function (delta) {
|
||||||
let t = $inv.rd.token(); if (t === '') { return; }
|
let t = $inv.rd.token(); if (t === '') { return; }
|
||||||
@@ -379,10 +432,13 @@ $inv.rd = {
|
|||||||
$inv.rd.sync({ Target: 'amount', Value: (amount != null ? amount : 0).toString() });
|
$inv.rd.sync({ Target: 'amount', Value: (amount != null ? amount : 0).toString() });
|
||||||
$inv.rd.sync({ Target: 'amount_payed', Value: (amount_payed != null ? amount_payed : 0).toString() });
|
$inv.rd.sync({ Target: 'amount_payed', Value: (amount_payed != null ? amount_payed : 0).toString() });
|
||||||
},
|
},
|
||||||
/* Render the open-amount footer from the server sums. */
|
/* Render the open-amount footer from the server sums. Detail rows ($inv.rRemRw) have
|
||||||
|
6 columns (aux, invoiceid, date, amount, amount_payed, open); span the label over the
|
||||||
|
4 columns between aux and the open-amount column so the total lands in the same last
|
||||||
|
column as the per-invoice open amounts. */
|
||||||
footer: function (tbl, sums) {
|
footer: function (tbl, sums) {
|
||||||
let ft = tbl.children('tfoot').empty();
|
let ft = tbl.children('tfoot').empty();
|
||||||
let tr = $$.tr(ft, { class: 'tsum' }).append([$$.tdc('aux'), $$.td({ colspan: 3 }).text('Offener Betrag')]);
|
let tr = $$.tr(ft, { class: 'tsum' }).append([$$.tdc('aux'), $$.td({ colspan: 4 }).text('Offener Betrag')]);
|
||||||
$$.tdc('currency', tr, fnum(sums.amount_open || 0, $rct.cst));
|
$$.tdc('currency', tr, fnum(sums.amount_open || 0, $rct.cst));
|
||||||
},
|
},
|
||||||
validation: function (msgs) {
|
validation: function (msgs) {
|
||||||
@@ -625,7 +681,7 @@ $inv.ccInv = function (ev) { //normale rechnung
|
|||||||
rif.tbl.children('tbody').each($inv.bdysort);
|
rif.tbl.children('tbody').each($inv.bdysort);
|
||||||
rif.tbl.trigger('fds.inv'); /* trigger calculations */
|
rif.tbl.trigger('fds.inv'); /* trigger calculations */
|
||||||
|
|
||||||
$inv.eM(false, true, 'iss,p13b,setm,ctp');
|
$inv.eM(false, true, $inv.emOpt(admin));
|
||||||
}, complete: () => {
|
}, complete: () => {
|
||||||
o.c.trigger('modal_close');
|
o.c.trigger('modal_close');
|
||||||
}
|
}
|
||||||
@@ -770,7 +826,7 @@ $inv.cntInv = function (data) { //invoice continuation
|
|||||||
|
|
||||||
rif.tbl.children('tbody').each($inv.bdysort);
|
rif.tbl.children('tbody').each($inv.bdysort);
|
||||||
rif.tbl.trigger('fds.inv'); /* trigger calculations */
|
rif.tbl.trigger('fds.inv'); /* trigger calculations */
|
||||||
$inv.eM(false, true, 'iss,p13b,setm,ctp');
|
$inv.eM(false, true, $inv.emOpt(response.admin));
|
||||||
}, complete: () => {
|
}, complete: () => {
|
||||||
o.c.trigger('modal_close');
|
o.c.trigger('modal_close');
|
||||||
}
|
}
|
||||||
@@ -970,6 +1026,14 @@ $inv.rrw = function () {
|
|||||||
if (ph === false) {
|
if (ph === false) {
|
||||||
bc.unshift($$.dc('ibtn edit', { title: $rct.cP }).append(gi('pencil')).click(rw, $inv.eRow));
|
bc.unshift($$.dc('ibtn edit', { title: $rct.cP }).append(gi('pencil')).click(rw, $inv.eRow));
|
||||||
bc.push($$.dc('ibtn del', { title: $rct.dR }).append(gi('trash')).click(function (e) { if (confirm($rct.cD)) { rw.remove(); $inv.t_fds_inv(); } }));
|
bc.push($$.dc('ibtn del', { title: $rct.dR }).append(gi('trash')).click(function (e) { if (confirm($rct.cD)) { rw.remove(); $inv.t_fds_inv(); } }));
|
||||||
|
/* "Auf Setpreis umstellen": only offered on a Set header item that is not yet
|
||||||
|
priced from its members (fds__prepInvoice's [SetItmId] is null exactly for a
|
||||||
|
Set header — either already set-priced, or not yet converted). Irreversible:
|
||||||
|
sets this row's price to the sum of its members (rows whose [SetItmId] equals
|
||||||
|
this row's id) and clears each member's price. */
|
||||||
|
if ((dta.Type || '').toLowerCase() === 'set' && (dta.SetItmId || '') === '') {
|
||||||
|
bc.push($$.dc('ibtn tosetp', { title: $rct.toSetP }).append(gi('scale')).click(rw, $inv.toSetPrice));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
let axf = $$.dc('axf').append(bc);
|
let axf = $$.dc('axf').append(bc);
|
||||||
if (ph === true) {
|
if (ph === true) {
|
||||||
@@ -1015,6 +1079,36 @@ $inv.rrw = function () {
|
|||||||
rw.empty().attr('class', ph ? 'placeholder' : 'itm').aC(co.Typ).tC('hidenote', hn).append(tda);
|
rw.empty().attr('class', ph ? 'placeholder' : 'itm').aC(co.Typ).tC('hidenote', hn).append(tda);
|
||||||
dta.co = co;
|
dta.co = co;
|
||||||
};
|
};
|
||||||
|
/* "Auf Setpreis umstellen" (see $inv.rrw): sets this Set-header row's price to the sum of
|
||||||
|
its member rows' totals (rows in the same block whose [SetItmId] equals this row's id,
|
||||||
|
the authoritative grouping from fds__prepInvoice), then clears each member's price so the
|
||||||
|
member items show without a price — matching the SetPrice presentation, but as an actual,
|
||||||
|
irreversible data change instead of a display-mode toggle (INVOICE_SET_PRICING.md's
|
||||||
|
setmode remains display-only and is unaffected by this). */
|
||||||
|
$inv.toSetPrice = function (ev) {
|
||||||
|
let rw = ev.data, dta = rw.data(), id = (dta.Id || '').toString();
|
||||||
|
if (id === '') { return; }
|
||||||
|
if (confirm($rct.toSetPc) === false) { return; }
|
||||||
|
let bdy = rw.closest('tbody'), members = bdy.find('tr.itm').filter(function () {
|
||||||
|
return (($(this).data('SetItmId') || '').toString()) === id;
|
||||||
|
});
|
||||||
|
let sum_net_val = 0, sum_vat_val = 0, sum_svcnet_val = 0, sum_svcvat_val = 0;
|
||||||
|
members.each(function () {
|
||||||
|
let m = $(this).data();
|
||||||
|
sum_net_val += (m.net_val || 0);
|
||||||
|
sum_vat_val += (m.vat_val || 0);
|
||||||
|
sum_svcnet_val += (m.svcnet_val || 0);
|
||||||
|
sum_svcvat_val += (m.svcvat_val || 0);
|
||||||
|
$.extend(m, { net: 0, net_val: 0, vat_val: 0, svcnet_val: 0, svcvat_val: 0, Discount: 0 });
|
||||||
|
$inv.rrw.call($(this));
|
||||||
|
});
|
||||||
|
$.extend(dta, {
|
||||||
|
net: sum_net_val, quantityhours: 1, Discount: 0,
|
||||||
|
net_val: sum_net_val, vat_val: sum_vat_val, svcnet_val: sum_svcnet_val, svcvat_val: sum_svcvat_val
|
||||||
|
});
|
||||||
|
$inv.rrw.call(rw);
|
||||||
|
$inv.t_fds_inv();
|
||||||
|
};
|
||||||
$inv.invSumUpdate = function () {
|
$inv.invSumUpdate = function () {
|
||||||
let tbl = $(this), ft = tbl.children('tfoot').empty(), p13b = bool((tbl.data().admin || {}).p13b || '', false);
|
let tbl = $(this), ft = tbl.children('tfoot').empty(), p13b = bool((tbl.data().admin || {}).p13b || '', false);
|
||||||
tbl.nextAll('.fnote').remove();
|
tbl.nextAll('.fnote').remove();
|
||||||
@@ -1541,7 +1635,10 @@ $inv.ccRem_s2 = function (id, sets) { //reminder creation
|
|||||||
//console.debug(response);
|
//console.debug(response);
|
||||||
let rem = response.rm || {};
|
let rem = response.rm || {};
|
||||||
let rq = $$.dc('srq', frm);
|
let rq = $$.dc('srq', frm);
|
||||||
$ict.remt[rem.type].forEach(r => $$[0]('p').rwText(r).appendTo(rq));
|
/* Intro/closing notice paragraphs are backend-authoritative (see $inv.rd.notes,
|
||||||
|
populated from FuchsPdf.BuildReminderNotes via rem/dstate after seeding below) —
|
||||||
|
they are appended/prepended into this same .srq container once the draft session
|
||||||
|
is open, so no client-side wording is constructed here. */
|
||||||
let rif = $$.tblset({ class: 'invi' }, rq);
|
let rif = $$.tblset({ class: 'invi' }, rq);
|
||||||
rif.ft = $$[0]('tfoot');
|
rif.ft = $$[0]('tfoot');
|
||||||
rif.tbl.data($.extend({ invid: rem.invid, new: {} }, response));
|
rif.tbl.data($.extend({ invid: rem.invid, new: {} }, response));
|
||||||
@@ -1549,7 +1646,6 @@ $inv.ccRem_s2 = function (id, sets) { //reminder creation
|
|||||||
$ict.remHR.forEach(h => $$.th(thr, h));
|
$ict.remHR.forEach(h => $$.th(thr, h));
|
||||||
$inv.rRemRw.call($$.tr(rif.bdy), rif.tbl.data());
|
$inv.rRemRw.call($$.tr(rif.bdy), rif.tbl.data());
|
||||||
rif.ft.appendTo(rif.tbl);
|
rif.ft.appendTo(rif.tbl);
|
||||||
$ict.remt2[rem.type].forEach(r => $$[0]('p').rwText(r).appendTo(rq));
|
|
||||||
|
|
||||||
let fm = (cls, txt, nme, hd, dta) => {
|
let fm = (cls, txt, nme, hd, dta) => {
|
||||||
let fr = $$.dc('inpfrm', frm).aC(cls).append(typeof hd === 'string' ? $$.dc('ahd', hd) : (hd > 0 ? $$.dc('ahd', $rct.frm[nme]) : null)), fc = $$.dc('content', fr).rwText(txt);
|
let fr = $$.dc('inpfrm', frm).aC(cls).append(typeof hd === 'string' ? $$.dc('ahd', hd) : (hd > 0 ? $$.dc('ahd', $rct.frm[nme]) : null)), fc = $$.dc('content', fr).rwText(txt);
|
||||||
|
|||||||
@@ -62,6 +62,18 @@
|
|||||||
height: auto;
|
height: auto;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
|
|
||||||
|
.content {
|
||||||
|
color: #000;
|
||||||
|
}
|
||||||
|
|
||||||
|
.content:empty::before {
|
||||||
|
content: '-';
|
||||||
|
min-width: 5rem;
|
||||||
|
height: 1rem;
|
||||||
|
display: block;
|
||||||
|
position: absolute;
|
||||||
|
}
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
div.axf {
|
div.axf {
|
||||||
display: block;
|
display: block;
|
||||||
@@ -232,6 +244,12 @@
|
|||||||
border-color: #FFF;
|
border-color: #FFF;
|
||||||
border-top: 1.5px solid #000;
|
border-top: 1.5px solid #000;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Set-pricing display (see InvoiceSetPricing / $inv.d.applySetDisplay): the set
|
||||||
|
header line is emphasised the same way the PDF renders it (bold title). */
|
||||||
|
&.sethdr td.txt {
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
> tfoot > tr {
|
> tfoot > tr {
|
||||||
|
|||||||
@@ -63,16 +63,10 @@
|
|||||||
rovlh: 'Übersicht der bisherigen Mahnungen',
|
rovlh: 'Übersicht der bisherigen Mahnungen',
|
||||||
rovl: ['Betreff', 'Betrag', 'Betrag gezahlt', 'fertiggestellt am'],
|
rovl: ['Betreff', 'Betrag', 'Betrag gezahlt', 'fertiggestellt am'],
|
||||||
remHR: ['Rechnung', 'vom', 'Rechnungsbetrag', 'bereits bezahlt', 'noch offen'],
|
remHR: ['Rechnung', 'vom', 'Rechnungsbetrag', 'bereits bezahlt', 'noch offen'],
|
||||||
remt: {
|
// Reminder intro/closing notice texts are backend-authoritative (see FuchsPdf.BuildReminderNotes /
|
||||||
f: ['Sehr geehrte Damen und Herren,', 'ein Mahnschreiben sollte kurz, freundlich und erfolgreich sein. Kurz ist es, freundlich sowieso; ob es auch erfolgreich ist, hängt von Ihnen ab.']
|
// $inv.rd.notes) — no client-side copies here.
|
||||||
, m: ['Sehr geehrte Damen und Herren,', 'nun müssen wir Sie noch einmal anschreiben.', 'Wahrscheinlich haben Sie triftige Gründe dafür, warum Sie die Zahlung unserer Forderung nicht vornehmen und auch nicht auf unsere Mahnung reagieren. Sollten wir darüber nicht einmal sprechen?', 'Bitte nehmen Sie umgehend in dieser Sache mit uns Kontakt auf.']
|
translatePaymentTerm: (pt) => (pt || '').replace('wd', ' Werktagen').replace('d', ' Tagen').replace('wk', ' Wochen') || '10 Tagen'
|
||||||
, l: ['Sehr geehrte Damen und Herren,', 'Eine DRITTE MAHNUNG zu erhalten bereitet Ihnen bestimmt ebenso wenig Freude wie uns, sie zu verschicken. Leider haben wir auf unsere zweite Mahnung noch keine Antwort von Ihnen erhalten.", "Wir bitten Sie, den offenen Betrag innerhalb der nächsten 7 Werktage nach Erhalt dieses Schreibens zu begleichen. Nach Ablauf dieser Frist erfolgt keine weitere Mahnung mehr.', 'Sollte die Forderung bis dahin nicht beglichen sein, eröffnen wir das gerichtliche Mahnverfahren. Sollten Sie die Rechnung inzwischen beglichen haben, so betrachten Sie bitte dieses Schreiben als gegenstandslos.']
|
, payi: { account: 'Konto', name: 'Zahler', text: 'Verw.Zweck', InvoiceID: 'Rechnung', amount: 'Betrag', date: 'Datum', manual: 'Typ'}
|
||||||
},
|
|
||||||
remt2: {
|
|
||||||
f: ['Wir bitten Sie, den noch offenen Rechnungsbetrag innerhalb einer Woche auf unser Konto zu überweisen.', 'Sollten Sie den Betrag bereits überwiesen haben, so bitten wir Sie, diese Zahlungserinnerung als gegenstandslos zu betrachten.']
|
|
||||||
, m: ['Um Ihnen zusätzliche Kosten für weitere Mahnungen zu ersparen, bitten wir Sie nunmehr um die Überweisung des noch zu zahlenden Gesamtbetrages inklusive der ggf. bereits fälligen Mahnzinsen und Mahngebühren innerhalb von einer Woche.']
|
|
||||||
, l: []
|
|
||||||
}, payi: { account: 'Konto', name: 'Zahler', text: 'Verw.Zweck', InvoiceID: 'Rechnung', amount: 'Betrag', date: 'Datum', manual: 'Typ'}
|
|
||||||
}, $invcol = {
|
}, $invcol = {
|
||||||
datev: new fields_definition('Rechnung', 'Rechnungen', [
|
datev: new fields_definition('Rechnung', 'Rechnungen', [
|
||||||
{ name: 'Umsatz (ohne Soll/Haben-Kz)', label: 'Umsatz (ohne Soll/Haben-Kz)', type: 'string' },
|
{ name: 'Umsatz (ohne Soll/Haben-Kz)', label: 'Umsatz (ohne Soll/Haben-Kz)', type: 'string' },
|
||||||
|
|||||||
@@ -34,6 +34,8 @@
|
|||||||
svcPart: 'Service-Anteil',
|
svcPart: 'Service-Anteil',
|
||||||
vat: 'Umsatzsteuer',
|
vat: 'Umsatzsteuer',
|
||||||
combP: 'Positionen zusammenfassen',
|
combP: 'Positionen zusammenfassen',
|
||||||
|
toSetP: 'Auf Setpreis umstellen (Positionen ohne Preis)',
|
||||||
|
toSetPc: 'Der Set-Preis wird auf die Summe seiner Positionen gesetzt und die Positionen selbst verlieren ihren Preis. Das ist nicht rückgängig zu machen (der Betrag kann danach nur noch manuell geändert werden). Fortfahren?',
|
||||||
iSum: 'Zwischensumme',
|
iSum: 'Zwischensumme',
|
||||||
dtRel: 'Freigegeben am: ',
|
dtRel: 'Freigegeben am: ',
|
||||||
dtCr: 'Erstellt am: ',
|
dtCr: 'Erstellt am: ',
|
||||||
|
|||||||
@@ -660,16 +660,16 @@ $ocms.vbl_send = function (ev) {
|
|||||||
} else {
|
} else {
|
||||||
val = null;
|
val = null;
|
||||||
}
|
}
|
||||||
} else if (type === 'number' && typedvalues === true ) {
|
} else if ((type === 'number' || dtf[0] === 'float' || dtf[0] === 'integer') && typedvalues === true) {
|
||||||
let v;
|
let v, raw = String(val).replace(',', '.');
|
||||||
if (dtf[0] === 'integer') {
|
if (dtf[0] === 'integer') {
|
||||||
v = parseInt(val);
|
v = parseInt(raw);
|
||||||
} else {
|
} else {
|
||||||
v = parseFloat(val);
|
v = parseFloat(raw);
|
||||||
}
|
}
|
||||||
val = isNaN(v) ? val : v;
|
val = isNaN(v) ? val : v;
|
||||||
}
|
}
|
||||||
if (req === true && ((val || '') === '' || (val.match(pattern) === null))) {
|
if (req === true && ((val === '' || val === null || typeof val === 'undefined') || (typeof val === 'string' && val.match(pattern) === null))) {
|
||||||
if (cat === false && bool(checkvalidity, false) === true) { e.setCustomValidity(t.nza('ocms-nvnote', $ocms.t.inv || 'Invalid field')); }
|
if (cat === false && bool(checkvalidity, false) === true) { e.setCustomValidity(t.nza('ocms-nvnote', $ocms.t.inv || 'Invalid field')); }
|
||||||
val = null;
|
val = null;
|
||||||
} else if (cat=== false && bool(checkvalidity, false) === true) {
|
} else if (cat=== false && bool(checkvalidity, false) === true) {
|
||||||
|
|||||||
@@ -441,6 +441,8 @@
|
|||||||
break;
|
break;
|
||||||
case 'number':
|
case 'number':
|
||||||
f.pattern = ne(f.pattern, '[-+]?[0-9]*[.,]?[0-9]*');
|
f.pattern = ne(f.pattern, '[-+]?[0-9]*[.,]?[0-9]*');
|
||||||
|
inp.attr('type', 'text');
|
||||||
|
inp.attr('inputmode', 'decimal');
|
||||||
inp.attr('step', f.precision || 'any');
|
inp.attr('step', f.precision || 'any');
|
||||||
inp.attr('data-format', 'float');
|
inp.attr('data-format', 'float');
|
||||||
break;
|
break;
|
||||||
|
|||||||
@@ -18,10 +18,21 @@ let $bct = {
|
|||||||
},
|
},
|
||||||
in: 'Rechnungsnummer',
|
in: 'Rechnungsnummer',
|
||||||
bt: 'Buchungstexte',
|
bt: 'Buchungstexte',
|
||||||
bv: 'Buchungswert (volle €)'
|
bv: 'Buchungswert (volle €)',
|
||||||
|
mbe: 'Manuelle Buchung erfassen',
|
||||||
|
mbc: 'Manuelle Buchung ändern',
|
||||||
|
mbd: 'Manuelle Buchung löschen',
|
||||||
|
mbdc: 'Manuelle Buchung wirklich löschen?\nDieser Vorgang kann nicht rückgängig gemacht werden.'
|
||||||
};
|
};
|
||||||
|
|
||||||
let $bcol = {
|
let $bcol = {
|
||||||
|
man: new fields_definition('Manuelle Buchung', 'Manuelle Buchungen', [
|
||||||
|
{ name: 'taID', type: 'hidden' },
|
||||||
|
{ name: 'ValueDate', label: 'Valuta', type: 'date', required: true },
|
||||||
|
{ name: 'Amount', label: 'Betrag', type: 'number', precision: '0.01', required: true },
|
||||||
|
{ name: 'NameOfPayer', label: 'Name', type: 'string' },
|
||||||
|
{ name: 'SepaRemittanceInformation', label: 'Verwendungszweck', type: 'string' }
|
||||||
|
]),
|
||||||
qtl: new fields_definition('Kontobericht', 'Kontoberichte', [
|
qtl: new fields_definition('Kontobericht', 'Kontoberichte', [
|
||||||
{ name: 'InvoiceId', label: 'RechnungsNr', type: 'string' },
|
{ name: 'InvoiceId', label: 'RechnungsNr', type: 'string' },
|
||||||
{ name: 'ValueDate', label: 'Valuta', type: 'date' },
|
{ name: 'ValueDate', label: 'Valuta', type: 'date' },
|
||||||
@@ -77,11 +88,16 @@ let $bam = {
|
|||||||
let fds = bool(ix.fds, false), id = ix.taID, m = [];
|
let fds = bool(ix.fds, false), id = ix.taID, m = [];
|
||||||
m.push({ lbl: $bct.smd, fnc: () => { $bam.smd(id); } });
|
m.push({ lbl: $bct.smd, fnc: () => { $bam.smd(id); } });
|
||||||
m.push({ lbl: $bct.ati, fnc: () => { $bam.ati(id); } });
|
m.push({ lbl: $bct.ati, fnc: () => { $bam.ati(id); } });
|
||||||
|
if (bool(ix.IsManual, false) === true) {
|
||||||
|
m.push({ lbl: $bct.mbc, fnc: () => { $bam.mbc(id); } });
|
||||||
|
m.push({ lbl: $bct.mbd, fnc: () => { $bam.mbd(id); } });
|
||||||
|
}
|
||||||
return $('#topbar').ocmsmenu(m);
|
return $('#topbar').ocmsmenu(m);
|
||||||
}, eM : (r, re, opt) => {
|
}, eM : (r, re, opt) => {
|
||||||
let m = [];
|
let m = [];
|
||||||
if ($fis.isAuth('fds_bam', 2) === true) {
|
if ($fis.isAuth('fds_bam', 2) === true) {
|
||||||
m.push({ lbl: $bct.upl, fnc: $bam.ubs});
|
m.push({ lbl: $bct.upl, fnc: $bam.ubs});
|
||||||
|
m.push({ lbl: $bct.mbe, fnc: $bam.mbe});
|
||||||
}
|
}
|
||||||
if (bool(r, false) === true) {
|
if (bool(r, false) === true) {
|
||||||
m.push({ lbl: $bct.rel, fnc: $bam.renderLst });
|
m.push({ lbl: $bct.rel, fnc: $bam.renderLst });
|
||||||
@@ -353,6 +369,32 @@ let $bam = {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
}, mbe: () => {
|
||||||
|
$bam.manForm({});
|
||||||
|
}, mbc: (taid) => {
|
||||||
|
$ocms.postXT({
|
||||||
|
url: $ocms.url('bam/mget'), data: { taid: taid }, success: (dta) => {
|
||||||
|
$bam.manForm(dta || {});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}, mbd: (taid) => {
|
||||||
|
if (confirm($bct.mbdc)) {
|
||||||
|
$ocms.postXT({
|
||||||
|
url: $ocms.url('bam/mdel'), data: { taid: taid }, success: () => {
|
||||||
|
$bam.renderLst();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}, manForm: (dta) => {
|
||||||
|
let flds = $bcol.man.clone().applyValues(dta || {});
|
||||||
|
$ocms.dlgform(flds, {
|
||||||
|
title: (ne(dta.taID, '') !== '') ? $bct.mbc : $bct.mbe,
|
||||||
|
url: $ocms.url('bam/man'),
|
||||||
|
typedvalues: true,
|
||||||
|
success: () => {
|
||||||
|
$bam.renderLst();
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
Vendored
+1
-1
File diff suppressed because one or more lines are too long
@@ -156,6 +156,16 @@ table.if td.num {
|
|||||||
height: auto;
|
height: auto;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
.edit_frm .invoice_layout .emailfrm .content {
|
||||||
|
color: #000;
|
||||||
|
}
|
||||||
|
.edit_frm .invoice_layout .emailfrm .content:empty::before {
|
||||||
|
content: "-";
|
||||||
|
min-width: 5rem;
|
||||||
|
height: 1rem;
|
||||||
|
display: block;
|
||||||
|
position: absolute;
|
||||||
|
}
|
||||||
.edit_frm .invoice_layout .emailfrm:hover div.axf {
|
.edit_frm .invoice_layout .emailfrm:hover div.axf {
|
||||||
display: block;
|
display: block;
|
||||||
}
|
}
|
||||||
@@ -285,6 +295,13 @@ table.if td.num {
|
|||||||
border-color: #FFF;
|
border-color: #FFF;
|
||||||
border-top: 1.5px solid #000;
|
border-top: 1.5px solid #000;
|
||||||
}
|
}
|
||||||
|
.edit_frm .invoice_layout table.invi > tbody > tr {
|
||||||
|
/* Set-pricing display (see InvoiceSetPricing / $inv.d.applySetDisplay): the set
|
||||||
|
header line is emphasised the same way the PDF renders it (bold title). */
|
||||||
|
}
|
||||||
|
.edit_frm .invoice_layout table.invi > tbody > tr.sethdr td.txt {
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
.edit_frm .invoice_layout table.invi th.currency, .edit_frm .invoice_layout table.invi th.num, .edit_frm .invoice_layout table.invi td.currency, .edit_frm .invoice_layout table.invi td.num {
|
.edit_frm .invoice_layout table.invi th.currency, .edit_frm .invoice_layout table.invi th.num, .edit_frm .invoice_layout table.invi td.currency, .edit_frm .invoice_layout table.invi td.num {
|
||||||
text-align: right;
|
text-align: right;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
|
|||||||
+108
-16
@@ -34,6 +34,8 @@ let $rct = {
|
|||||||
svcPart: 'Service-Anteil',
|
svcPart: 'Service-Anteil',
|
||||||
vat: 'Umsatzsteuer',
|
vat: 'Umsatzsteuer',
|
||||||
combP: 'Positionen zusammenfassen',
|
combP: 'Positionen zusammenfassen',
|
||||||
|
toSetP: 'Auf Setpreis umstellen (Positionen ohne Preis)',
|
||||||
|
toSetPc: 'Der Set-Preis wird auf die Summe seiner Positionen gesetzt und die Positionen selbst verlieren ihren Preis. Das ist nicht rückgängig zu machen (der Betrag kann danach nur noch manuell geändert werden). Fortfahren?',
|
||||||
iSum: 'Zwischensumme',
|
iSum: 'Zwischensumme',
|
||||||
dtRel: 'Freigegeben am: ',
|
dtRel: 'Freigegeben am: ',
|
||||||
dtCr: 'Erstellt am: ',
|
dtCr: 'Erstellt am: ',
|
||||||
@@ -169,16 +171,10 @@ let $ict = {
|
|||||||
rovlh: 'Übersicht der bisherigen Mahnungen',
|
rovlh: 'Übersicht der bisherigen Mahnungen',
|
||||||
rovl: ['Betreff', 'Betrag', 'Betrag gezahlt', 'fertiggestellt am'],
|
rovl: ['Betreff', 'Betrag', 'Betrag gezahlt', 'fertiggestellt am'],
|
||||||
remHR: ['Rechnung', 'vom', 'Rechnungsbetrag', 'bereits bezahlt', 'noch offen'],
|
remHR: ['Rechnung', 'vom', 'Rechnungsbetrag', 'bereits bezahlt', 'noch offen'],
|
||||||
remt: {
|
// Reminder intro/closing notice texts are backend-authoritative (see FuchsPdf.BuildReminderNotes /
|
||||||
f: ['Sehr geehrte Damen und Herren,', 'ein Mahnschreiben sollte kurz, freundlich und erfolgreich sein. Kurz ist es, freundlich sowieso; ob es auch erfolgreich ist, hängt von Ihnen ab.']
|
// $inv.rd.notes) — no client-side copies here.
|
||||||
, m: ['Sehr geehrte Damen und Herren,', 'nun müssen wir Sie noch einmal anschreiben.', 'Wahrscheinlich haben Sie triftige Gründe dafür, warum Sie die Zahlung unserer Forderung nicht vornehmen und auch nicht auf unsere Mahnung reagieren. Sollten wir darüber nicht einmal sprechen?', 'Bitte nehmen Sie umgehend in dieser Sache mit uns Kontakt auf.']
|
translatePaymentTerm: (pt) => (pt || '').replace('wd', ' Werktagen').replace('d', ' Tagen').replace('wk', ' Wochen') || '10 Tagen'
|
||||||
, l: ['Sehr geehrte Damen und Herren,', 'Eine DRITTE MAHNUNG zu erhalten bereitet Ihnen bestimmt ebenso wenig Freude wie uns, sie zu verschicken. Leider haben wir auf unsere zweite Mahnung noch keine Antwort von Ihnen erhalten.", "Wir bitten Sie, den offenen Betrag innerhalb der nächsten 7 Werktage nach Erhalt dieses Schreibens zu begleichen. Nach Ablauf dieser Frist erfolgt keine weitere Mahnung mehr.', 'Sollte die Forderung bis dahin nicht beglichen sein, eröffnen wir das gerichtliche Mahnverfahren. Sollten Sie die Rechnung inzwischen beglichen haben, so betrachten Sie bitte dieses Schreiben als gegenstandslos.']
|
, payi: { account: 'Konto', name: 'Zahler', text: 'Verw.Zweck', InvoiceID: 'Rechnung', amount: 'Betrag', date: 'Datum', manual: 'Typ'}
|
||||||
},
|
|
||||||
remt2: {
|
|
||||||
f: ['Wir bitten Sie, den noch offenen Rechnungsbetrag innerhalb einer Woche auf unser Konto zu überweisen.', 'Sollten Sie den Betrag bereits überwiesen haben, so bitten wir Sie, diese Zahlungserinnerung als gegenstandslos zu betrachten.']
|
|
||||||
, m: ['Um Ihnen zusätzliche Kosten für weitere Mahnungen zu ersparen, bitten wir Sie nunmehr um die Überweisung des noch zu zahlenden Gesamtbetrages inklusive der ggf. bereits fälligen Mahnzinsen und Mahngebühren innerhalb von einer Woche.']
|
|
||||||
, l: []
|
|
||||||
}, payi: { account: 'Konto', name: 'Zahler', text: 'Verw.Zweck', InvoiceID: 'Rechnung', amount: 'Betrag', date: 'Datum', manual: 'Typ'}
|
|
||||||
}, $invcol = {
|
}, $invcol = {
|
||||||
datev: new fields_definition('Rechnung', 'Rechnungen', [
|
datev: new fields_definition('Rechnung', 'Rechnungen', [
|
||||||
{ name: 'Umsatz (ohne Soll/Haben-Kz)', label: 'Umsatz (ohne Soll/Haben-Kz)', type: 'string' },
|
{ name: 'Umsatz (ohne Soll/Haben-Kz)', label: 'Umsatz (ohne Soll/Haben-Kz)', type: 'string' },
|
||||||
@@ -629,6 +625,11 @@ $inv.iMn = (ix) => {
|
|||||||
}
|
}
|
||||||
return $('#topbar').ocmsmenu(m);
|
return $('#topbar').ocmsmenu(m);
|
||||||
}
|
}
|
||||||
|
/* Menu option CSV builder: 'setm' (Set-Preisanzeige) is only offered while the invoice has not
|
||||||
|
yet been explicitly switched to a set-display mode (admin.setmode empty/undefined) — once set,
|
||||||
|
changing it again is not supported (the user edits the line items directly instead); it
|
||||||
|
reappears only if 'Änderungen verwerfen' reverts to a DB state that was never switched. */
|
||||||
|
$inv.emOpt = (admin) => 'iss,p13b,ctp' + (((admin || {}).setmode || '') === '' ? ',setm' : '');
|
||||||
$inv.eM = (r, re, opt) => {
|
$inv.eM = (r, re, opt) => {
|
||||||
let m = [];
|
let m = [];
|
||||||
if (booln(r, false) === true || booln(re, false) === true) {
|
if (booln(r, false) === true || booln(re, false) === true) {
|
||||||
@@ -701,8 +702,24 @@ $inv.d = {
|
|||||||
let tbl = $inv.d.tbl(); if (tbl.length < 1) { return; }
|
let tbl = $inv.d.tbl(); if (tbl.length < 1) { return; }
|
||||||
tbl.data('dver', state.version).data('serverSums', state.sums);
|
tbl.data('dver', state.version).data('serverSums', state.sums);
|
||||||
$inv.d.footer(tbl, state.sums || {}, state.admin || {});
|
$inv.d.footer(tbl, state.sums || {}, state.admin || {});
|
||||||
|
$inv.d.notes(state.notes || []);
|
||||||
$inv.d.validation(state.validation || []);
|
$inv.d.validation(state.validation || []);
|
||||||
$inv.d.applyPositions(tbl, state.req || []);
|
$inv.d.applyPositions(tbl, state.req || []);
|
||||||
|
$inv.d.applySetDisplay(tbl, state.setDisplay || {});
|
||||||
|
/* Keep the menu's "Set-Preisanzeige" entry in sync with the authoritative admin.setmode:
|
||||||
|
hides it right after a mode switch, and restores it if a discard reverted the draft to
|
||||||
|
a DB state that was never switched (ADR 0006 backend-authoritative refresh). */
|
||||||
|
$inv.eM(false, true, $inv.emOpt(state.admin || {}));
|
||||||
|
},
|
||||||
|
/* Renders the backend-authoritative notice paragraphs (ADR: notice texts are
|
||||||
|
backend-authoritative) exactly as FuchsPdf.BuildInvoiceNotes emits them for the PDF,
|
||||||
|
so the online editor always shows precisely what will be printed. */
|
||||||
|
notes: function (notes) {
|
||||||
|
let frm = $inv.d.layout(); if (frm.length < 1) { return; }
|
||||||
|
let box = frm.children('.dnotes');
|
||||||
|
if (box.length < 1) { box = $$.dc('dnotes'); frm.append(box); }
|
||||||
|
box.empty();
|
||||||
|
(notes || []).forEach((n) => $$.dc('dnote').aC(n.style || 'InvoiceNotes').text(n.text));
|
||||||
},
|
},
|
||||||
/* Push the server's authoritative position numbers back onto the rendered rows so the online
|
/* Push the server's authoritative position numbers back onto the rendered rows so the online
|
||||||
editor and the PDF preview always agree (the server numbers priced lines continuously; the
|
editor and the PDF preview always agree (the server numbers priced lines continuously; the
|
||||||
@@ -714,6 +731,23 @@ $inv.d = {
|
|||||||
if (cell.length) { cell.text(co.p != null ? co.p : ''); }
|
if (cell.length) { cell.text(co.p != null ? co.p : ''); }
|
||||||
}));
|
}));
|
||||||
},
|
},
|
||||||
|
/* Applies the backend-authoritative set-pricing display flags (see BuildSetDisplay /
|
||||||
|
InvoiceSetPricing) onto the rendered rows: blanks the price/total cells for lines the
|
||||||
|
server says should show no price (set members in SetPrice mode, the set header in
|
||||||
|
ItemPrices mode), and emphasises the set header line — so the online editor always
|
||||||
|
shows exactly what the PDF will print, without duplicating the pricing rules client-side.
|
||||||
|
Rows not present in the map (no set in that block, or standalone items) are left untouched. */
|
||||||
|
applySetDisplay: function (tbl, setDisplay) {
|
||||||
|
tbl.find('tr.itm').each((i, tr) => {
|
||||||
|
let rw = $(tr), dta = rw.data() || {}, id = (dta.Id || '').toString();
|
||||||
|
if (id === '' || !Object.prototype.hasOwnProperty.call(setDisplay, id)) { return; }
|
||||||
|
let flags = setDisplay[id] || {};
|
||||||
|
rw.tC('sethdr', bool(flags.h, false));
|
||||||
|
if (bool(flags.p, true) === false) {
|
||||||
|
rw.find('td.currency').text('');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
/* Send one change to the server; the draftReady signal and this success both refresh. */
|
/* Send one change to the server; the draftReady signal and this success both refresh. */
|
||||||
sync: function (delta) {
|
sync: function (delta) {
|
||||||
let t = $inv.d.token(); if (t === '') { return; }
|
let t = $inv.d.token(); if (t === '') { return; }
|
||||||
@@ -901,8 +935,23 @@ $inv.rd = {
|
|||||||
let tbl = $inv.rd.tbl(); if (tbl.length < 1) { return; }
|
let tbl = $inv.rd.tbl(); if (tbl.length < 1) { return; }
|
||||||
tbl.data('rdver', state.version).data('serverSums', state.sums).data('remid', state.remid || '');
|
tbl.data('rdver', state.version).data('serverSums', state.sums).data('remid', state.remid || '');
|
||||||
$inv.rd.footer(tbl, state.sums || {});
|
$inv.rd.footer(tbl, state.sums || {});
|
||||||
|
$inv.rd.notes(state.notes || {});
|
||||||
$inv.rd.validation(state.validation || []);
|
$inv.rd.validation(state.validation || []);
|
||||||
},
|
},
|
||||||
|
/* Renders the backend-authoritative intro/closing notice paragraphs (ADR: notice texts
|
||||||
|
are backend-authoritative) exactly as FuchsPdf.BuildReminderNotes emits them for the
|
||||||
|
PDF, so the online editor always shows precisely what will be printed. */
|
||||||
|
notes: function (notes) {
|
||||||
|
let frm = $inv.rd.layout(); if (frm.length < 1) { return; }
|
||||||
|
let rq = frm.find('.srq'); if (rq.length < 1) { return; }
|
||||||
|
rq.children('.dnote').remove();
|
||||||
|
let intro = (notes || {}).intro || [], first = rq.children().first();
|
||||||
|
intro.slice().reverse().forEach((t) => {
|
||||||
|
let p = $$[0]('p').aC('dnote intro').rwText(t);
|
||||||
|
if (first.length) { p.insertBefore(first); } else { p.appendTo(rq); }
|
||||||
|
});
|
||||||
|
((notes || {}).closing || []).forEach((t) => $$[0]('p').aC('dnote closing').rwText(t).appendTo(rq));
|
||||||
|
},
|
||||||
/* Send one change to the server; the draftReady signal and this success both refresh. */
|
/* Send one change to the server; the draftReady signal and this success both refresh. */
|
||||||
sync: function (delta) {
|
sync: function (delta) {
|
||||||
let t = $inv.rd.token(); if (t === '') { return; }
|
let t = $inv.rd.token(); if (t === '') { return; }
|
||||||
@@ -926,10 +975,13 @@ $inv.rd = {
|
|||||||
$inv.rd.sync({ Target: 'amount', Value: (amount != null ? amount : 0).toString() });
|
$inv.rd.sync({ Target: 'amount', Value: (amount != null ? amount : 0).toString() });
|
||||||
$inv.rd.sync({ Target: 'amount_payed', Value: (amount_payed != null ? amount_payed : 0).toString() });
|
$inv.rd.sync({ Target: 'amount_payed', Value: (amount_payed != null ? amount_payed : 0).toString() });
|
||||||
},
|
},
|
||||||
/* Render the open-amount footer from the server sums. */
|
/* Render the open-amount footer from the server sums. Detail rows ($inv.rRemRw) have
|
||||||
|
6 columns (aux, invoiceid, date, amount, amount_payed, open); span the label over the
|
||||||
|
4 columns between aux and the open-amount column so the total lands in the same last
|
||||||
|
column as the per-invoice open amounts. */
|
||||||
footer: function (tbl, sums) {
|
footer: function (tbl, sums) {
|
||||||
let ft = tbl.children('tfoot').empty();
|
let ft = tbl.children('tfoot').empty();
|
||||||
let tr = $$.tr(ft, { class: 'tsum' }).append([$$.tdc('aux'), $$.td({ colspan: 3 }).text('Offener Betrag')]);
|
let tr = $$.tr(ft, { class: 'tsum' }).append([$$.tdc('aux'), $$.td({ colspan: 4 }).text('Offener Betrag')]);
|
||||||
$$.tdc('currency', tr, fnum(sums.amount_open || 0, $rct.cst));
|
$$.tdc('currency', tr, fnum(sums.amount_open || 0, $rct.cst));
|
||||||
},
|
},
|
||||||
validation: function (msgs) {
|
validation: function (msgs) {
|
||||||
@@ -1172,7 +1224,7 @@ $inv.ccInv = function (ev) { //normale rechnung
|
|||||||
rif.tbl.children('tbody').each($inv.bdysort);
|
rif.tbl.children('tbody').each($inv.bdysort);
|
||||||
rif.tbl.trigger('fds.inv'); /* trigger calculations */
|
rif.tbl.trigger('fds.inv'); /* trigger calculations */
|
||||||
|
|
||||||
$inv.eM(false, true, 'iss,p13b,setm,ctp');
|
$inv.eM(false, true, $inv.emOpt(admin));
|
||||||
}, complete: () => {
|
}, complete: () => {
|
||||||
o.c.trigger('modal_close');
|
o.c.trigger('modal_close');
|
||||||
}
|
}
|
||||||
@@ -1317,7 +1369,7 @@ $inv.cntInv = function (data) { //invoice continuation
|
|||||||
|
|
||||||
rif.tbl.children('tbody').each($inv.bdysort);
|
rif.tbl.children('tbody').each($inv.bdysort);
|
||||||
rif.tbl.trigger('fds.inv'); /* trigger calculations */
|
rif.tbl.trigger('fds.inv'); /* trigger calculations */
|
||||||
$inv.eM(false, true, 'iss,p13b,setm,ctp');
|
$inv.eM(false, true, $inv.emOpt(response.admin));
|
||||||
}, complete: () => {
|
}, complete: () => {
|
||||||
o.c.trigger('modal_close');
|
o.c.trigger('modal_close');
|
||||||
}
|
}
|
||||||
@@ -1517,6 +1569,14 @@ $inv.rrw = function () {
|
|||||||
if (ph === false) {
|
if (ph === false) {
|
||||||
bc.unshift($$.dc('ibtn edit', { title: $rct.cP }).append(gi('pencil')).click(rw, $inv.eRow));
|
bc.unshift($$.dc('ibtn edit', { title: $rct.cP }).append(gi('pencil')).click(rw, $inv.eRow));
|
||||||
bc.push($$.dc('ibtn del', { title: $rct.dR }).append(gi('trash')).click(function (e) { if (confirm($rct.cD)) { rw.remove(); $inv.t_fds_inv(); } }));
|
bc.push($$.dc('ibtn del', { title: $rct.dR }).append(gi('trash')).click(function (e) { if (confirm($rct.cD)) { rw.remove(); $inv.t_fds_inv(); } }));
|
||||||
|
/* "Auf Setpreis umstellen": only offered on a Set header item that is not yet
|
||||||
|
priced from its members (fds__prepInvoice's [SetItmId] is null exactly for a
|
||||||
|
Set header — either already set-priced, or not yet converted). Irreversible:
|
||||||
|
sets this row's price to the sum of its members (rows whose [SetItmId] equals
|
||||||
|
this row's id) and clears each member's price. */
|
||||||
|
if ((dta.Type || '').toLowerCase() === 'set' && (dta.SetItmId || '') === '') {
|
||||||
|
bc.push($$.dc('ibtn tosetp', { title: $rct.toSetP }).append(gi('scale')).click(rw, $inv.toSetPrice));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
let axf = $$.dc('axf').append(bc);
|
let axf = $$.dc('axf').append(bc);
|
||||||
if (ph === true) {
|
if (ph === true) {
|
||||||
@@ -1562,6 +1622,36 @@ $inv.rrw = function () {
|
|||||||
rw.empty().attr('class', ph ? 'placeholder' : 'itm').aC(co.Typ).tC('hidenote', hn).append(tda);
|
rw.empty().attr('class', ph ? 'placeholder' : 'itm').aC(co.Typ).tC('hidenote', hn).append(tda);
|
||||||
dta.co = co;
|
dta.co = co;
|
||||||
};
|
};
|
||||||
|
/* "Auf Setpreis umstellen" (see $inv.rrw): sets this Set-header row's price to the sum of
|
||||||
|
its member rows' totals (rows in the same block whose [SetItmId] equals this row's id,
|
||||||
|
the authoritative grouping from fds__prepInvoice), then clears each member's price so the
|
||||||
|
member items show without a price — matching the SetPrice presentation, but as an actual,
|
||||||
|
irreversible data change instead of a display-mode toggle (INVOICE_SET_PRICING.md's
|
||||||
|
setmode remains display-only and is unaffected by this). */
|
||||||
|
$inv.toSetPrice = function (ev) {
|
||||||
|
let rw = ev.data, dta = rw.data(), id = (dta.Id || '').toString();
|
||||||
|
if (id === '') { return; }
|
||||||
|
if (confirm($rct.toSetPc) === false) { return; }
|
||||||
|
let bdy = rw.closest('tbody'), members = bdy.find('tr.itm').filter(function () {
|
||||||
|
return (($(this).data('SetItmId') || '').toString()) === id;
|
||||||
|
});
|
||||||
|
let sum_net_val = 0, sum_vat_val = 0, sum_svcnet_val = 0, sum_svcvat_val = 0;
|
||||||
|
members.each(function () {
|
||||||
|
let m = $(this).data();
|
||||||
|
sum_net_val += (m.net_val || 0);
|
||||||
|
sum_vat_val += (m.vat_val || 0);
|
||||||
|
sum_svcnet_val += (m.svcnet_val || 0);
|
||||||
|
sum_svcvat_val += (m.svcvat_val || 0);
|
||||||
|
$.extend(m, { net: 0, net_val: 0, vat_val: 0, svcnet_val: 0, svcvat_val: 0, Discount: 0 });
|
||||||
|
$inv.rrw.call($(this));
|
||||||
|
});
|
||||||
|
$.extend(dta, {
|
||||||
|
net: sum_net_val, quantityhours: 1, Discount: 0,
|
||||||
|
net_val: sum_net_val, vat_val: sum_vat_val, svcnet_val: sum_svcnet_val, svcvat_val: sum_svcvat_val
|
||||||
|
});
|
||||||
|
$inv.rrw.call(rw);
|
||||||
|
$inv.t_fds_inv();
|
||||||
|
};
|
||||||
$inv.invSumUpdate = function () {
|
$inv.invSumUpdate = function () {
|
||||||
let tbl = $(this), ft = tbl.children('tfoot').empty(), p13b = bool((tbl.data().admin || {}).p13b || '', false);
|
let tbl = $(this), ft = tbl.children('tfoot').empty(), p13b = bool((tbl.data().admin || {}).p13b || '', false);
|
||||||
tbl.nextAll('.fnote').remove();
|
tbl.nextAll('.fnote').remove();
|
||||||
@@ -2088,7 +2178,10 @@ $inv.ccRem_s2 = function (id, sets) { //reminder creation
|
|||||||
//console.debug(response);
|
//console.debug(response);
|
||||||
let rem = response.rm || {};
|
let rem = response.rm || {};
|
||||||
let rq = $$.dc('srq', frm);
|
let rq = $$.dc('srq', frm);
|
||||||
$ict.remt[rem.type].forEach(r => $$[0]('p').rwText(r).appendTo(rq));
|
/* Intro/closing notice paragraphs are backend-authoritative (see $inv.rd.notes,
|
||||||
|
populated from FuchsPdf.BuildReminderNotes via rem/dstate after seeding below) —
|
||||||
|
they are appended/prepended into this same .srq container once the draft session
|
||||||
|
is open, so no client-side wording is constructed here. */
|
||||||
let rif = $$.tblset({ class: 'invi' }, rq);
|
let rif = $$.tblset({ class: 'invi' }, rq);
|
||||||
rif.ft = $$[0]('tfoot');
|
rif.ft = $$[0]('tfoot');
|
||||||
rif.tbl.data($.extend({ invid: rem.invid, new: {} }, response));
|
rif.tbl.data($.extend({ invid: rem.invid, new: {} }, response));
|
||||||
@@ -2096,7 +2189,6 @@ $inv.ccRem_s2 = function (id, sets) { //reminder creation
|
|||||||
$ict.remHR.forEach(h => $$.th(thr, h));
|
$ict.remHR.forEach(h => $$.th(thr, h));
|
||||||
$inv.rRemRw.call($$.tr(rif.bdy), rif.tbl.data());
|
$inv.rRemRw.call($$.tr(rif.bdy), rif.tbl.data());
|
||||||
rif.ft.appendTo(rif.tbl);
|
rif.ft.appendTo(rif.tbl);
|
||||||
$ict.remt2[rem.type].forEach(r => $$[0]('p').rwText(r).appendTo(rq));
|
|
||||||
|
|
||||||
let fm = (cls, txt, nme, hd, dta) => {
|
let fm = (cls, txt, nme, hd, dta) => {
|
||||||
let fr = $$.dc('inpfrm', frm).aC(cls).append(typeof hd === 'string' ? $$.dc('ahd', hd) : (hd > 0 ? $$.dc('ahd', $rct.frm[nme]) : null)), fc = $$.dc('content', fr).rwText(txt);
|
let fr = $$.dc('inpfrm', frm).aC(cls).append(typeof hd === 'string' ? $$.dc('ahd', hd) : (hd > 0 ? $$.dc('ahd', $rct.frm[nme]) : null)), fc = $$.dc('content', fr).rwText(txt);
|
||||||
|
|||||||
Vendored
+1
-1
File diff suppressed because one or more lines are too long
Vendored
+1
-1
File diff suppressed because one or more lines are too long
@@ -897,16 +897,16 @@ $ocms.vbl_send = function (ev) {
|
|||||||
} else {
|
} else {
|
||||||
val = null;
|
val = null;
|
||||||
}
|
}
|
||||||
} else if (type === 'number' && typedvalues === true ) {
|
} else if ((type === 'number' || dtf[0] === 'float' || dtf[0] === 'integer') && typedvalues === true) {
|
||||||
let v;
|
let v, raw = String(val).replace(',', '.');
|
||||||
if (dtf[0] === 'integer') {
|
if (dtf[0] === 'integer') {
|
||||||
v = parseInt(val);
|
v = parseInt(raw);
|
||||||
} else {
|
} else {
|
||||||
v = parseFloat(val);
|
v = parseFloat(raw);
|
||||||
}
|
}
|
||||||
val = isNaN(v) ? val : v;
|
val = isNaN(v) ? val : v;
|
||||||
}
|
}
|
||||||
if (req === true && ((val || '') === '' || (val.match(pattern) === null))) {
|
if (req === true && ((val === '' || val === null || typeof val === 'undefined') || (typeof val === 'string' && val.match(pattern) === null))) {
|
||||||
if (cat === false && bool(checkvalidity, false) === true) { e.setCustomValidity(t.nza('ocms-nvnote', $ocms.t.inv || 'Invalid field')); }
|
if (cat === false && bool(checkvalidity, false) === true) { e.setCustomValidity(t.nza('ocms-nvnote', $ocms.t.inv || 'Invalid field')); }
|
||||||
val = null;
|
val = null;
|
||||||
} else if (cat=== false && bool(checkvalidity, false) === true) {
|
} else if (cat=== false && bool(checkvalidity, false) === true) {
|
||||||
@@ -2220,6 +2220,8 @@ class NumArray extends Array {
|
|||||||
break;
|
break;
|
||||||
case 'number':
|
case 'number':
|
||||||
f.pattern = ne(f.pattern, '[-+]?[0-9]*[.,]?[0-9]*');
|
f.pattern = ne(f.pattern, '[-+]?[0-9]*[.,]?[0-9]*');
|
||||||
|
inp.attr('type', 'text');
|
||||||
|
inp.attr('inputmode', 'decimal');
|
||||||
inp.attr('step', f.precision || 'any');
|
inp.attr('step', f.precision || 'any');
|
||||||
inp.attr('data-format', 'float');
|
inp.attr('data-format', 'float');
|
||||||
break;
|
break;
|
||||||
|
|||||||
Vendored
+1
-1
File diff suppressed because one or more lines are too long
@@ -293,6 +293,16 @@ table.if th.keep, table.if td.keep {
|
|||||||
height: auto;
|
height: auto;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
.edit_frm .invoice_layout .emailfrm .content {
|
||||||
|
color: #000;
|
||||||
|
}
|
||||||
|
.edit_frm .invoice_layout .emailfrm .content:empty::before {
|
||||||
|
content: "-";
|
||||||
|
min-width: 5rem;
|
||||||
|
height: 1rem;
|
||||||
|
display: block;
|
||||||
|
position: absolute;
|
||||||
|
}
|
||||||
.edit_frm .invoice_layout .emailfrm:hover div.axf {
|
.edit_frm .invoice_layout .emailfrm:hover div.axf {
|
||||||
display: block;
|
display: block;
|
||||||
}
|
}
|
||||||
@@ -422,6 +432,13 @@ table.if th.keep, table.if td.keep {
|
|||||||
border-color: #FFF;
|
border-color: #FFF;
|
||||||
border-top: 1.5px solid #000;
|
border-top: 1.5px solid #000;
|
||||||
}
|
}
|
||||||
|
.edit_frm .invoice_layout table.invi > tbody > tr {
|
||||||
|
/* Set-pricing display (see InvoiceSetPricing / $inv.d.applySetDisplay): the set
|
||||||
|
header line is emphasised the same way the PDF renders it (bold title). */
|
||||||
|
}
|
||||||
|
.edit_frm .invoice_layout table.invi > tbody > tr.sethdr td.txt {
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
.edit_frm .invoice_layout table.invi th.currency, .edit_frm .invoice_layout table.invi th.num, .edit_frm .invoice_layout table.invi td.currency, .edit_frm .invoice_layout table.invi td.num {
|
.edit_frm .invoice_layout table.invi th.currency, .edit_frm .invoice_layout table.invi th.num, .edit_frm .invoice_layout table.invi td.currency, .edit_frm .invoice_layout table.invi td.num {
|
||||||
text-align: right;
|
text-align: right;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
|
|||||||
+108
-16
@@ -34,6 +34,8 @@ let $rct = {
|
|||||||
svcPart: 'Service-Anteil',
|
svcPart: 'Service-Anteil',
|
||||||
vat: 'Umsatzsteuer',
|
vat: 'Umsatzsteuer',
|
||||||
combP: 'Positionen zusammenfassen',
|
combP: 'Positionen zusammenfassen',
|
||||||
|
toSetP: 'Auf Setpreis umstellen (Positionen ohne Preis)',
|
||||||
|
toSetPc: 'Der Set-Preis wird auf die Summe seiner Positionen gesetzt und die Positionen selbst verlieren ihren Preis. Das ist nicht rückgängig zu machen (der Betrag kann danach nur noch manuell geändert werden). Fortfahren?',
|
||||||
iSum: 'Zwischensumme',
|
iSum: 'Zwischensumme',
|
||||||
dtRel: 'Freigegeben am: ',
|
dtRel: 'Freigegeben am: ',
|
||||||
dtCr: 'Erstellt am: ',
|
dtCr: 'Erstellt am: ',
|
||||||
@@ -169,16 +171,10 @@ let $ict = {
|
|||||||
rovlh: 'Übersicht der bisherigen Mahnungen',
|
rovlh: 'Übersicht der bisherigen Mahnungen',
|
||||||
rovl: ['Betreff', 'Betrag', 'Betrag gezahlt', 'fertiggestellt am'],
|
rovl: ['Betreff', 'Betrag', 'Betrag gezahlt', 'fertiggestellt am'],
|
||||||
remHR: ['Rechnung', 'vom', 'Rechnungsbetrag', 'bereits bezahlt', 'noch offen'],
|
remHR: ['Rechnung', 'vom', 'Rechnungsbetrag', 'bereits bezahlt', 'noch offen'],
|
||||||
remt: {
|
// Reminder intro/closing notice texts are backend-authoritative (see FuchsPdf.BuildReminderNotes /
|
||||||
f: ['Sehr geehrte Damen und Herren,', 'ein Mahnschreiben sollte kurz, freundlich und erfolgreich sein. Kurz ist es, freundlich sowieso; ob es auch erfolgreich ist, hängt von Ihnen ab.']
|
// $inv.rd.notes) — no client-side copies here.
|
||||||
, m: ['Sehr geehrte Damen und Herren,', 'nun müssen wir Sie noch einmal anschreiben.', 'Wahrscheinlich haben Sie triftige Gründe dafür, warum Sie die Zahlung unserer Forderung nicht vornehmen und auch nicht auf unsere Mahnung reagieren. Sollten wir darüber nicht einmal sprechen?', 'Bitte nehmen Sie umgehend in dieser Sache mit uns Kontakt auf.']
|
translatePaymentTerm: (pt) => (pt || '').replace('wd', ' Werktagen').replace('d', ' Tagen').replace('wk', ' Wochen') || '10 Tagen'
|
||||||
, l: ['Sehr geehrte Damen und Herren,', 'Eine DRITTE MAHNUNG zu erhalten bereitet Ihnen bestimmt ebenso wenig Freude wie uns, sie zu verschicken. Leider haben wir auf unsere zweite Mahnung noch keine Antwort von Ihnen erhalten.", "Wir bitten Sie, den offenen Betrag innerhalb der nächsten 7 Werktage nach Erhalt dieses Schreibens zu begleichen. Nach Ablauf dieser Frist erfolgt keine weitere Mahnung mehr.', 'Sollte die Forderung bis dahin nicht beglichen sein, eröffnen wir das gerichtliche Mahnverfahren. Sollten Sie die Rechnung inzwischen beglichen haben, so betrachten Sie bitte dieses Schreiben als gegenstandslos.']
|
, payi: { account: 'Konto', name: 'Zahler', text: 'Verw.Zweck', InvoiceID: 'Rechnung', amount: 'Betrag', date: 'Datum', manual: 'Typ'}
|
||||||
},
|
|
||||||
remt2: {
|
|
||||||
f: ['Wir bitten Sie, den noch offenen Rechnungsbetrag innerhalb einer Woche auf unser Konto zu überweisen.', 'Sollten Sie den Betrag bereits überwiesen haben, so bitten wir Sie, diese Zahlungserinnerung als gegenstandslos zu betrachten.']
|
|
||||||
, m: ['Um Ihnen zusätzliche Kosten für weitere Mahnungen zu ersparen, bitten wir Sie nunmehr um die Überweisung des noch zu zahlenden Gesamtbetrages inklusive der ggf. bereits fälligen Mahnzinsen und Mahngebühren innerhalb von einer Woche.']
|
|
||||||
, l: []
|
|
||||||
}, payi: { account: 'Konto', name: 'Zahler', text: 'Verw.Zweck', InvoiceID: 'Rechnung', amount: 'Betrag', date: 'Datum', manual: 'Typ'}
|
|
||||||
}, $invcol = {
|
}, $invcol = {
|
||||||
datev: new fields_definition('Rechnung', 'Rechnungen', [
|
datev: new fields_definition('Rechnung', 'Rechnungen', [
|
||||||
{ name: 'Umsatz (ohne Soll/Haben-Kz)', label: 'Umsatz (ohne Soll/Haben-Kz)', type: 'string' },
|
{ name: 'Umsatz (ohne Soll/Haben-Kz)', label: 'Umsatz (ohne Soll/Haben-Kz)', type: 'string' },
|
||||||
@@ -610,6 +606,11 @@ $inv.iMn = (ix) => {
|
|||||||
}
|
}
|
||||||
return $('#topbar').ocmsmenu(m);
|
return $('#topbar').ocmsmenu(m);
|
||||||
}
|
}
|
||||||
|
/* Menu option CSV builder: 'setm' (Set-Preisanzeige) is only offered while the invoice has not
|
||||||
|
yet been explicitly switched to a set-display mode (admin.setmode empty/undefined) — once set,
|
||||||
|
changing it again is not supported (the user edits the line items directly instead); it
|
||||||
|
reappears only if 'Änderungen verwerfen' reverts to a DB state that was never switched. */
|
||||||
|
$inv.emOpt = (admin) => 'iss,p13b,ctp' + (((admin || {}).setmode || '') === '' ? ',setm' : '');
|
||||||
$inv.eM = (r, re, opt) => {
|
$inv.eM = (r, re, opt) => {
|
||||||
let m = [];
|
let m = [];
|
||||||
if (booln(r, false) === true || booln(re, false) === true) {
|
if (booln(r, false) === true || booln(re, false) === true) {
|
||||||
@@ -682,8 +683,24 @@ $inv.d = {
|
|||||||
let tbl = $inv.d.tbl(); if (tbl.length < 1) { return; }
|
let tbl = $inv.d.tbl(); if (tbl.length < 1) { return; }
|
||||||
tbl.data('dver', state.version).data('serverSums', state.sums);
|
tbl.data('dver', state.version).data('serverSums', state.sums);
|
||||||
$inv.d.footer(tbl, state.sums || {}, state.admin || {});
|
$inv.d.footer(tbl, state.sums || {}, state.admin || {});
|
||||||
|
$inv.d.notes(state.notes || []);
|
||||||
$inv.d.validation(state.validation || []);
|
$inv.d.validation(state.validation || []);
|
||||||
$inv.d.applyPositions(tbl, state.req || []);
|
$inv.d.applyPositions(tbl, state.req || []);
|
||||||
|
$inv.d.applySetDisplay(tbl, state.setDisplay || {});
|
||||||
|
/* Keep the menu's "Set-Preisanzeige" entry in sync with the authoritative admin.setmode:
|
||||||
|
hides it right after a mode switch, and restores it if a discard reverted the draft to
|
||||||
|
a DB state that was never switched (ADR 0006 backend-authoritative refresh). */
|
||||||
|
$inv.eM(false, true, $inv.emOpt(state.admin || {}));
|
||||||
|
},
|
||||||
|
/* Renders the backend-authoritative notice paragraphs (ADR: notice texts are
|
||||||
|
backend-authoritative) exactly as FuchsPdf.BuildInvoiceNotes emits them for the PDF,
|
||||||
|
so the online editor always shows precisely what will be printed. */
|
||||||
|
notes: function (notes) {
|
||||||
|
let frm = $inv.d.layout(); if (frm.length < 1) { return; }
|
||||||
|
let box = frm.children('.dnotes');
|
||||||
|
if (box.length < 1) { box = $$.dc('dnotes'); frm.append(box); }
|
||||||
|
box.empty();
|
||||||
|
(notes || []).forEach((n) => $$.dc('dnote').aC(n.style || 'InvoiceNotes').text(n.text));
|
||||||
},
|
},
|
||||||
/* Push the server's authoritative position numbers back onto the rendered rows so the online
|
/* Push the server's authoritative position numbers back onto the rendered rows so the online
|
||||||
editor and the PDF preview always agree (the server numbers priced lines continuously; the
|
editor and the PDF preview always agree (the server numbers priced lines continuously; the
|
||||||
@@ -695,6 +712,23 @@ $inv.d = {
|
|||||||
if (cell.length) { cell.text(co.p != null ? co.p : ''); }
|
if (cell.length) { cell.text(co.p != null ? co.p : ''); }
|
||||||
}));
|
}));
|
||||||
},
|
},
|
||||||
|
/* Applies the backend-authoritative set-pricing display flags (see BuildSetDisplay /
|
||||||
|
InvoiceSetPricing) onto the rendered rows: blanks the price/total cells for lines the
|
||||||
|
server says should show no price (set members in SetPrice mode, the set header in
|
||||||
|
ItemPrices mode), and emphasises the set header line — so the online editor always
|
||||||
|
shows exactly what the PDF will print, without duplicating the pricing rules client-side.
|
||||||
|
Rows not present in the map (no set in that block, or standalone items) are left untouched. */
|
||||||
|
applySetDisplay: function (tbl, setDisplay) {
|
||||||
|
tbl.find('tr.itm').each((i, tr) => {
|
||||||
|
let rw = $(tr), dta = rw.data() || {}, id = (dta.Id || '').toString();
|
||||||
|
if (id === '' || !Object.prototype.hasOwnProperty.call(setDisplay, id)) { return; }
|
||||||
|
let flags = setDisplay[id] || {};
|
||||||
|
rw.tC('sethdr', bool(flags.h, false));
|
||||||
|
if (bool(flags.p, true) === false) {
|
||||||
|
rw.find('td.currency').text('');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
/* Send one change to the server; the draftReady signal and this success both refresh. */
|
/* Send one change to the server; the draftReady signal and this success both refresh. */
|
||||||
sync: function (delta) {
|
sync: function (delta) {
|
||||||
let t = $inv.d.token(); if (t === '') { return; }
|
let t = $inv.d.token(); if (t === '') { return; }
|
||||||
@@ -882,8 +916,23 @@ $inv.rd = {
|
|||||||
let tbl = $inv.rd.tbl(); if (tbl.length < 1) { return; }
|
let tbl = $inv.rd.tbl(); if (tbl.length < 1) { return; }
|
||||||
tbl.data('rdver', state.version).data('serverSums', state.sums).data('remid', state.remid || '');
|
tbl.data('rdver', state.version).data('serverSums', state.sums).data('remid', state.remid || '');
|
||||||
$inv.rd.footer(tbl, state.sums || {});
|
$inv.rd.footer(tbl, state.sums || {});
|
||||||
|
$inv.rd.notes(state.notes || {});
|
||||||
$inv.rd.validation(state.validation || []);
|
$inv.rd.validation(state.validation || []);
|
||||||
},
|
},
|
||||||
|
/* Renders the backend-authoritative intro/closing notice paragraphs (ADR: notice texts
|
||||||
|
are backend-authoritative) exactly as FuchsPdf.BuildReminderNotes emits them for the
|
||||||
|
PDF, so the online editor always shows precisely what will be printed. */
|
||||||
|
notes: function (notes) {
|
||||||
|
let frm = $inv.rd.layout(); if (frm.length < 1) { return; }
|
||||||
|
let rq = frm.find('.srq'); if (rq.length < 1) { return; }
|
||||||
|
rq.children('.dnote').remove();
|
||||||
|
let intro = (notes || {}).intro || [], first = rq.children().first();
|
||||||
|
intro.slice().reverse().forEach((t) => {
|
||||||
|
let p = $$[0]('p').aC('dnote intro').rwText(t);
|
||||||
|
if (first.length) { p.insertBefore(first); } else { p.appendTo(rq); }
|
||||||
|
});
|
||||||
|
((notes || {}).closing || []).forEach((t) => $$[0]('p').aC('dnote closing').rwText(t).appendTo(rq));
|
||||||
|
},
|
||||||
/* Send one change to the server; the draftReady signal and this success both refresh. */
|
/* Send one change to the server; the draftReady signal and this success both refresh. */
|
||||||
sync: function (delta) {
|
sync: function (delta) {
|
||||||
let t = $inv.rd.token(); if (t === '') { return; }
|
let t = $inv.rd.token(); if (t === '') { return; }
|
||||||
@@ -907,10 +956,13 @@ $inv.rd = {
|
|||||||
$inv.rd.sync({ Target: 'amount', Value: (amount != null ? amount : 0).toString() });
|
$inv.rd.sync({ Target: 'amount', Value: (amount != null ? amount : 0).toString() });
|
||||||
$inv.rd.sync({ Target: 'amount_payed', Value: (amount_payed != null ? amount_payed : 0).toString() });
|
$inv.rd.sync({ Target: 'amount_payed', Value: (amount_payed != null ? amount_payed : 0).toString() });
|
||||||
},
|
},
|
||||||
/* Render the open-amount footer from the server sums. */
|
/* Render the open-amount footer from the server sums. Detail rows ($inv.rRemRw) have
|
||||||
|
6 columns (aux, invoiceid, date, amount, amount_payed, open); span the label over the
|
||||||
|
4 columns between aux and the open-amount column so the total lands in the same last
|
||||||
|
column as the per-invoice open amounts. */
|
||||||
footer: function (tbl, sums) {
|
footer: function (tbl, sums) {
|
||||||
let ft = tbl.children('tfoot').empty();
|
let ft = tbl.children('tfoot').empty();
|
||||||
let tr = $$.tr(ft, { class: 'tsum' }).append([$$.tdc('aux'), $$.td({ colspan: 3 }).text('Offener Betrag')]);
|
let tr = $$.tr(ft, { class: 'tsum' }).append([$$.tdc('aux'), $$.td({ colspan: 4 }).text('Offener Betrag')]);
|
||||||
$$.tdc('currency', tr, fnum(sums.amount_open || 0, $rct.cst));
|
$$.tdc('currency', tr, fnum(sums.amount_open || 0, $rct.cst));
|
||||||
},
|
},
|
||||||
validation: function (msgs) {
|
validation: function (msgs) {
|
||||||
@@ -1153,7 +1205,7 @@ $inv.ccInv = function (ev) { //normale rechnung
|
|||||||
rif.tbl.children('tbody').each($inv.bdysort);
|
rif.tbl.children('tbody').each($inv.bdysort);
|
||||||
rif.tbl.trigger('fds.inv'); /* trigger calculations */
|
rif.tbl.trigger('fds.inv'); /* trigger calculations */
|
||||||
|
|
||||||
$inv.eM(false, true, 'iss,p13b,setm,ctp');
|
$inv.eM(false, true, $inv.emOpt(admin));
|
||||||
}, complete: () => {
|
}, complete: () => {
|
||||||
o.c.trigger('modal_close');
|
o.c.trigger('modal_close');
|
||||||
}
|
}
|
||||||
@@ -1298,7 +1350,7 @@ $inv.cntInv = function (data) { //invoice continuation
|
|||||||
|
|
||||||
rif.tbl.children('tbody').each($inv.bdysort);
|
rif.tbl.children('tbody').each($inv.bdysort);
|
||||||
rif.tbl.trigger('fds.inv'); /* trigger calculations */
|
rif.tbl.trigger('fds.inv'); /* trigger calculations */
|
||||||
$inv.eM(false, true, 'iss,p13b,setm,ctp');
|
$inv.eM(false, true, $inv.emOpt(response.admin));
|
||||||
}, complete: () => {
|
}, complete: () => {
|
||||||
o.c.trigger('modal_close');
|
o.c.trigger('modal_close');
|
||||||
}
|
}
|
||||||
@@ -1498,6 +1550,14 @@ $inv.rrw = function () {
|
|||||||
if (ph === false) {
|
if (ph === false) {
|
||||||
bc.unshift($$.dc('ibtn edit', { title: $rct.cP }).append(gi('pencil')).click(rw, $inv.eRow));
|
bc.unshift($$.dc('ibtn edit', { title: $rct.cP }).append(gi('pencil')).click(rw, $inv.eRow));
|
||||||
bc.push($$.dc('ibtn del', { title: $rct.dR }).append(gi('trash')).click(function (e) { if (confirm($rct.cD)) { rw.remove(); $inv.t_fds_inv(); } }));
|
bc.push($$.dc('ibtn del', { title: $rct.dR }).append(gi('trash')).click(function (e) { if (confirm($rct.cD)) { rw.remove(); $inv.t_fds_inv(); } }));
|
||||||
|
/* "Auf Setpreis umstellen": only offered on a Set header item that is not yet
|
||||||
|
priced from its members (fds__prepInvoice's [SetItmId] is null exactly for a
|
||||||
|
Set header — either already set-priced, or not yet converted). Irreversible:
|
||||||
|
sets this row's price to the sum of its members (rows whose [SetItmId] equals
|
||||||
|
this row's id) and clears each member's price. */
|
||||||
|
if ((dta.Type || '').toLowerCase() === 'set' && (dta.SetItmId || '') === '') {
|
||||||
|
bc.push($$.dc('ibtn tosetp', { title: $rct.toSetP }).append(gi('scale')).click(rw, $inv.toSetPrice));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
let axf = $$.dc('axf').append(bc);
|
let axf = $$.dc('axf').append(bc);
|
||||||
if (ph === true) {
|
if (ph === true) {
|
||||||
@@ -1543,6 +1603,36 @@ $inv.rrw = function () {
|
|||||||
rw.empty().attr('class', ph ? 'placeholder' : 'itm').aC(co.Typ).tC('hidenote', hn).append(tda);
|
rw.empty().attr('class', ph ? 'placeholder' : 'itm').aC(co.Typ).tC('hidenote', hn).append(tda);
|
||||||
dta.co = co;
|
dta.co = co;
|
||||||
};
|
};
|
||||||
|
/* "Auf Setpreis umstellen" (see $inv.rrw): sets this Set-header row's price to the sum of
|
||||||
|
its member rows' totals (rows in the same block whose [SetItmId] equals this row's id,
|
||||||
|
the authoritative grouping from fds__prepInvoice), then clears each member's price so the
|
||||||
|
member items show without a price — matching the SetPrice presentation, but as an actual,
|
||||||
|
irreversible data change instead of a display-mode toggle (INVOICE_SET_PRICING.md's
|
||||||
|
setmode remains display-only and is unaffected by this). */
|
||||||
|
$inv.toSetPrice = function (ev) {
|
||||||
|
let rw = ev.data, dta = rw.data(), id = (dta.Id || '').toString();
|
||||||
|
if (id === '') { return; }
|
||||||
|
if (confirm($rct.toSetPc) === false) { return; }
|
||||||
|
let bdy = rw.closest('tbody'), members = bdy.find('tr.itm').filter(function () {
|
||||||
|
return (($(this).data('SetItmId') || '').toString()) === id;
|
||||||
|
});
|
||||||
|
let sum_net_val = 0, sum_vat_val = 0, sum_svcnet_val = 0, sum_svcvat_val = 0;
|
||||||
|
members.each(function () {
|
||||||
|
let m = $(this).data();
|
||||||
|
sum_net_val += (m.net_val || 0);
|
||||||
|
sum_vat_val += (m.vat_val || 0);
|
||||||
|
sum_svcnet_val += (m.svcnet_val || 0);
|
||||||
|
sum_svcvat_val += (m.svcvat_val || 0);
|
||||||
|
$.extend(m, { net: 0, net_val: 0, vat_val: 0, svcnet_val: 0, svcvat_val: 0, Discount: 0 });
|
||||||
|
$inv.rrw.call($(this));
|
||||||
|
});
|
||||||
|
$.extend(dta, {
|
||||||
|
net: sum_net_val, quantityhours: 1, Discount: 0,
|
||||||
|
net_val: sum_net_val, vat_val: sum_vat_val, svcnet_val: sum_svcnet_val, svcvat_val: sum_svcvat_val
|
||||||
|
});
|
||||||
|
$inv.rrw.call(rw);
|
||||||
|
$inv.t_fds_inv();
|
||||||
|
};
|
||||||
$inv.invSumUpdate = function () {
|
$inv.invSumUpdate = function () {
|
||||||
let tbl = $(this), ft = tbl.children('tfoot').empty(), p13b = bool((tbl.data().admin || {}).p13b || '', false);
|
let tbl = $(this), ft = tbl.children('tfoot').empty(), p13b = bool((tbl.data().admin || {}).p13b || '', false);
|
||||||
tbl.nextAll('.fnote').remove();
|
tbl.nextAll('.fnote').remove();
|
||||||
@@ -2069,7 +2159,10 @@ $inv.ccRem_s2 = function (id, sets) { //reminder creation
|
|||||||
//console.debug(response);
|
//console.debug(response);
|
||||||
let rem = response.rm || {};
|
let rem = response.rm || {};
|
||||||
let rq = $$.dc('srq', frm);
|
let rq = $$.dc('srq', frm);
|
||||||
$ict.remt[rem.type].forEach(r => $$[0]('p').rwText(r).appendTo(rq));
|
/* Intro/closing notice paragraphs are backend-authoritative (see $inv.rd.notes,
|
||||||
|
populated from FuchsPdf.BuildReminderNotes via rem/dstate after seeding below) —
|
||||||
|
they are appended/prepended into this same .srq container once the draft session
|
||||||
|
is open, so no client-side wording is constructed here. */
|
||||||
let rif = $$.tblset({ class: 'invi' }, rq);
|
let rif = $$.tblset({ class: 'invi' }, rq);
|
||||||
rif.ft = $$[0]('tfoot');
|
rif.ft = $$[0]('tfoot');
|
||||||
rif.tbl.data($.extend({ invid: rem.invid, new: {} }, response));
|
rif.tbl.data($.extend({ invid: rem.invid, new: {} }, response));
|
||||||
@@ -2077,7 +2170,6 @@ $inv.ccRem_s2 = function (id, sets) { //reminder creation
|
|||||||
$ict.remHR.forEach(h => $$.th(thr, h));
|
$ict.remHR.forEach(h => $$.th(thr, h));
|
||||||
$inv.rRemRw.call($$.tr(rif.bdy), rif.tbl.data());
|
$inv.rRemRw.call($$.tr(rif.bdy), rif.tbl.data());
|
||||||
rif.ft.appendTo(rif.tbl);
|
rif.ft.appendTo(rif.tbl);
|
||||||
$ict.remt2[rem.type].forEach(r => $$[0]('p').rwText(r).appendTo(rq));
|
|
||||||
|
|
||||||
let fm = (cls, txt, nme, hd, dta) => {
|
let fm = (cls, txt, nme, hd, dta) => {
|
||||||
let fr = $$.dc('inpfrm', frm).aC(cls).append(typeof hd === 'string' ? $$.dc('ahd', hd) : (hd > 0 ? $$.dc('ahd', $rct.frm[nme]) : null)), fc = $$.dc('content', fr).rwText(txt);
|
let fr = $$.dc('inpfrm', frm).aC(cls).append(typeof hd === 'string' ? $$.dc('ahd', hd) : (hd > 0 ? $$.dc('ahd', $rct.frm[nme]) : null)), fc = $$.dc('content', fr).rwText(txt);
|
||||||
|
|||||||
Vendored
+1
-1
File diff suppressed because one or more lines are too long
Vendored
+1
-1
File diff suppressed because one or more lines are too long
@@ -714,16 +714,16 @@ $ocms.vbl_send = function (ev) {
|
|||||||
} else {
|
} else {
|
||||||
val = null;
|
val = null;
|
||||||
}
|
}
|
||||||
} else if (type === 'number' && typedvalues === true ) {
|
} else if ((type === 'number' || dtf[0] === 'float' || dtf[0] === 'integer') && typedvalues === true) {
|
||||||
let v;
|
let v, raw = String(val).replace(',', '.');
|
||||||
if (dtf[0] === 'integer') {
|
if (dtf[0] === 'integer') {
|
||||||
v = parseInt(val);
|
v = parseInt(raw);
|
||||||
} else {
|
} else {
|
||||||
v = parseFloat(val);
|
v = parseFloat(raw);
|
||||||
}
|
}
|
||||||
val = isNaN(v) ? val : v;
|
val = isNaN(v) ? val : v;
|
||||||
}
|
}
|
||||||
if (req === true && ((val || '') === '' || (val.match(pattern) === null))) {
|
if (req === true && ((val === '' || val === null || typeof val === 'undefined') || (typeof val === 'string' && val.match(pattern) === null))) {
|
||||||
if (cat === false && bool(checkvalidity, false) === true) { e.setCustomValidity(t.nza('ocms-nvnote', $ocms.t.inv || 'Invalid field')); }
|
if (cat === false && bool(checkvalidity, false) === true) { e.setCustomValidity(t.nza('ocms-nvnote', $ocms.t.inv || 'Invalid field')); }
|
||||||
val = null;
|
val = null;
|
||||||
} else if (cat=== false && bool(checkvalidity, false) === true) {
|
} else if (cat=== false && bool(checkvalidity, false) === true) {
|
||||||
|
|||||||
Vendored
+1
-1
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -424,5 +424,8 @@
|
|||||||
<Build Include="dbo\User Defined Types\fds__tt__bankingtransactions.sql" />
|
<Build Include="dbo\User Defined Types\fds__tt__bankingtransactions.sql" />
|
||||||
<Build Include="dbo\User Defined Types\fds__tt__admin_ReportAdminTable.sql" />
|
<Build Include="dbo\User Defined Types\fds__tt__admin_ReportAdminTable.sql" />
|
||||||
<Build Include="Security\fuchs_enc_1.sql" />
|
<Build Include="Security\fuchs_enc_1.sql" />
|
||||||
|
<Build Include="dbo\Stored Procedures\fds__setBankingtransaction_manual.sql" />
|
||||||
|
<Build Include="dbo\Stored Procedures\fds__getBankingtransaction_manual_1.sql" />
|
||||||
|
<Build Include="dbo\User Defined Types\fds__tt__mfr_items+.sql" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
</Project>
|
</Project>
|
||||||
@@ -57,7 +57,8 @@ BEGIN
|
|||||||
,[UserCreated]
|
,[UserCreated]
|
||||||
,[DateModified]
|
,[DateModified]
|
||||||
,[UserModified]
|
,[UserModified]
|
||||||
,[file])
|
,[file]
|
||||||
|
,[file_guid])
|
||||||
OUTPUT inserted.*
|
OUTPUT inserted.*
|
||||||
INTO @OUT
|
INTO @OUT
|
||||||
VALUES
|
VALUES
|
||||||
@@ -82,7 +83,8 @@ BEGIN
|
|||||||
,@authuser
|
,@authuser
|
||||||
,@now
|
,@now
|
||||||
,@authuser
|
,@authuser
|
||||||
,NULL);
|
,NULL
|
||||||
|
,NEWID());
|
||||||
|
|
||||||
|
|
||||||
SELECT TOP(1) @Id = [id] FROM @out;
|
SELECT TOP(1) @Id = [id] FROM @out;
|
||||||
|
|||||||
@@ -0,0 +1,24 @@
|
|||||||
|
-- =============================================
|
||||||
|
-- Description: Returns the raw field values of a single manually entered
|
||||||
|
-- banking transaction (TransactionTypeIdCode = 'MAN'), so the
|
||||||
|
-- "manuelle Buchung ändern" dialog can be pre-filled.
|
||||||
|
-- =============================================
|
||||||
|
CREATE PROCEDURE [dbo].[fds__getBankingtransaction_manual]
|
||||||
|
@taID varchar(10),
|
||||||
|
@authuser varchar(100)
|
||||||
|
AS
|
||||||
|
BEGIN
|
||||||
|
SET NOCOUNT ON;
|
||||||
|
IF [dbo].[fis_getModuleAuth]('fds_bam', @authuser) < 1
|
||||||
|
THROW 60000, N'not authorized', 1;
|
||||||
|
|
||||||
|
SELECT
|
||||||
|
[taID]
|
||||||
|
, [ValueDate]
|
||||||
|
, [Amount]
|
||||||
|
, [NameOfPayer]
|
||||||
|
, [SepaRemittanceInformation]
|
||||||
|
FROM [dbo].[fds__bankingtransactions]
|
||||||
|
WHERE [taID] = @taID
|
||||||
|
AND [TransactionTypeIdCode] = 'MAN';
|
||||||
|
END
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
|
||||||
|
-- =============================================
|
||||||
|
-- Description: Returns the raw field values of a single manually entered
|
||||||
|
-- banking transaction (TransactionTypeIdCode = 'MAN'), so the
|
||||||
|
-- "manuelle Buchung ändern" dialog can be pre-filled.
|
||||||
|
-- =============================================
|
||||||
|
CREATE PROCEDURE [dbo].[fds__getBankingtransaction_manual]
|
||||||
|
@taID varchar(10),
|
||||||
|
@authuser varchar(100)
|
||||||
|
AS
|
||||||
|
BEGIN
|
||||||
|
SET NOCOUNT ON;
|
||||||
|
IF [dbo].[fis_getModuleAuth]('fds_bam', @authuser) < 1
|
||||||
|
THROW 60000, N'not authorized', 1;
|
||||||
|
|
||||||
|
SELECT
|
||||||
|
[taID]
|
||||||
|
, [ValueDate]
|
||||||
|
, [Amount]
|
||||||
|
, [NameOfPayer]
|
||||||
|
, [SepaRemittanceInformation]
|
||||||
|
FROM [dbo].[fds__bankingtransactions]
|
||||||
|
WHERE [taID] = @taID
|
||||||
|
AND [TransactionTypeIdCode] = 'MAN';
|
||||||
|
END
|
||||||
@@ -54,6 +54,7 @@ BEGIN
|
|||||||
, bt.NameOfPayer
|
, bt.NameOfPayer
|
||||||
, bt.SepaRemittanceInformation
|
, bt.SepaRemittanceInformation
|
||||||
, bt.EndToEndReference
|
, bt.EndToEndReference
|
||||||
|
, [IsManual] = CAST(CASE WHEN bt.[TransactionTypeIdCode] = 'MAN' THEN 1 ELSE 0 END AS bit)
|
||||||
, [order] = ROW_NUMBER() OVER (ORDER BY bt.[valuedate] DESC)
|
, [order] = ROW_NUMBER() OVER (ORDER BY bt.[valuedate] DESC)
|
||||||
FROM
|
FROM
|
||||||
dbo.[fds__bankingtransactions] as bt
|
dbo.[fds__bankingtransactions] as bt
|
||||||
@@ -67,7 +68,7 @@ BEGIN
|
|||||||
AND NOT (ISNULL(bt.[AccountNumberOfPayer],'') IN ('DE52301502000002091478') AND ISNULL(bt.[SepaRemittanceInformation],'') like ('%umbuchung%'))
|
AND NOT (ISNULL(bt.[AccountNumberOfPayer],'') IN ('DE52301502000002091478') AND ISNULL(bt.[SepaRemittanceInformation],'') like ('%umbuchung%'))
|
||||||
AND NOT (ISNULL(bt.[AccountNumberOfPayer],'') IN ('DE23501108006161606386') AND ISNULL(bt.[SepaRemittanceInformation],'') like ('%ebay%'))
|
AND NOT (ISNULL(bt.[AccountNumberOfPayer],'') IN ('DE23501108006161606386') AND ISNULL(bt.[SepaRemittanceInformation],'') like ('%ebay%'))
|
||||||
AND NOT (ISNULL(bt.[SepaRemittanceInformation],'') like ('%Umsatzsteuer%'))
|
AND NOT (ISNULL(bt.[SepaRemittanceInformation],'') like ('%Umsatzsteuer%'))
|
||||||
AND ISNULL(bt.AccountNumberOfPayer ,'') <> ''
|
AND (ISNULL(bt.AccountNumberOfPayer ,'') <> '' OR bt.[TransactionTypeIdCode] = 'MAN')
|
||||||
) AND
|
) AND
|
||||||
CASE WHEN inv.[InvId] is null THEN 1
|
CASE WHEN inv.[InvId] is null THEN 1
|
||||||
WHEN (ISNULL(inv.ispayed,0) = 1 and fds = 1) THEN 0
|
WHEN (ISNULL(inv.ispayed,0) = 1 and fds = 1) THEN 0
|
||||||
|
|||||||
@@ -44,6 +44,7 @@ BEGIN
|
|||||||
,[DateModified]
|
,[DateModified]
|
||||||
,[UserModified]
|
,[UserModified]
|
||||||
,[file] = CASE WHEN ISNULL(@includefile,0) = 1 THEN [file] else NULL END -- do not return file if not explicitly requested
|
,[file] = CASE WHEN ISNULL(@includefile,0) = 1 THEN [file] else NULL END -- do not return file if not explicitly requested
|
||||||
|
,[file_guid]
|
||||||
FROM [dbo].[fds__reminder]
|
FROM [dbo].[fds__reminder]
|
||||||
WHERE [Id] = @Id AND @Id is not null;
|
WHERE [Id] = @Id AND @Id is not null;
|
||||||
|
|
||||||
|
|||||||
@@ -177,7 +177,7 @@ BEGIN
|
|||||||
|
|
||||||
|
|
||||||
DECLARE @step [dbo].[fds__tt__mfr_steps];
|
DECLARE @step [dbo].[fds__tt__mfr_steps];
|
||||||
DECLARE @itm [dbo].[fds__tt__mfr_items];
|
DECLARE @itm [dbo].[fds__tt__mfr_items+];
|
||||||
DECLARE @company TABLE (
|
DECLARE @company TABLE (
|
||||||
[Id] [bigint] NOT NULL,
|
[Id] [bigint] NOT NULL,
|
||||||
[Location#ID] [bigint] NULL,
|
[Location#ID] [bigint] NULL,
|
||||||
@@ -207,9 +207,19 @@ BEGIN
|
|||||||
INSERT INTO @itm
|
INSERT INTO @itm
|
||||||
SELECT
|
SELECT
|
||||||
itm.*
|
itm.*
|
||||||
, [baseorder] = s.[baseorder], [order] = ROW_NUMBER() OVER (PARTITION BY [ServiceRequestId] ORDER BY [SortOrder], itm.[Id])
|
, [baseorder] = s.[baseorder]
|
||||||
from [dbo].[mfr__items] as itm JOIN @SReq as s ON itm.ServiceRequestId = s.[Id]
|
, [order] = ROW_NUMBER() OVER (PARTITION BY itm.[ServiceRequestId] ORDER BY itm.[SortOrder], itm.[Id])
|
||||||
where itm.[Type] <> 'PartialInvoice';
|
, [SetItmID] = CASE WHEN itm.[Type] = 'Set' AND ISNULL(itm.[Price], 0) > 0 THEN NULL
|
||||||
|
ELSE MAX(CASE WHEN itm.[Type] = 'Set' AND ISNULL(itm.[Price], 0) > 0 THEN itm.[Id] ELSE NULL END)
|
||||||
|
OVER (
|
||||||
|
PARTITION BY itm.[ServiceRequestId]
|
||||||
|
ORDER BY itm.[SortOrder], itm.[Id]
|
||||||
|
ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW
|
||||||
|
)
|
||||||
|
END
|
||||||
|
FROM [dbo].[mfr__items] as itm
|
||||||
|
JOIN @SReq as s ON itm.ServiceRequestId = s.[Id]
|
||||||
|
WHERE itm.[Type] <> 'PartialInvoice';
|
||||||
|
|
||||||
DECLARE @NUM_of_int_Invoices int = 0;
|
DECLARE @NUM_of_int_Invoices int = 0;
|
||||||
|
|
||||||
@@ -264,6 +274,7 @@ BEGIN
|
|||||||
,[UnitString] = 'Pauschal'
|
,[UnitString] = 'Pauschal'
|
||||||
,[baseorder] = ai.[baseorder]
|
,[baseorder] = ai.[baseorder]
|
||||||
, [order] = 10000 + (ROW_NUMBER() OVER (PARTITION BY ai.[mfr__servicerequest] ORDER BY ai.[dateFinalized], ai.DateCreated) * 10)
|
, [order] = 10000 + (ROW_NUMBER() OVER (PARTITION BY ai.[mfr__servicerequest] ORDER BY ai.[dateFinalized], ai.DateCreated) * 10)
|
||||||
|
,[SetItmId] = null
|
||||||
FROM associated_interims as ai
|
FROM associated_interims as ai
|
||||||
UNION
|
UNION
|
||||||
SELECT
|
SELECT
|
||||||
@@ -293,6 +304,7 @@ BEGIN
|
|||||||
,[UnitString] = 'Pauschal'
|
,[UnitString] = 'Pauschal'
|
||||||
,[baseorder] = ai.[baseorder]
|
,[baseorder] = ai.[baseorder]
|
||||||
, [order] = 10001 + (ROW_NUMBER() OVER (PARTITION BY ai.[mfr__servicerequest] ORDER BY ai.[dateFinalized], ai.DateCreated) * 10)
|
, [order] = 10001 + (ROW_NUMBER() OVER (PARTITION BY ai.[mfr__servicerequest] ORDER BY ai.[dateFinalized], ai.DateCreated) * 10)
|
||||||
|
,[SetItmId] = null
|
||||||
FROM associated_interims as ai
|
FROM associated_interims as ai
|
||||||
WHERE ISNULL(ai.open_amount,0.0) > 0.0;
|
WHERE ISNULL(ai.open_amount,0.0) > 0.0;
|
||||||
|
|
||||||
@@ -381,6 +393,7 @@ BEGIN
|
|||||||
, itm.[NameOrNumber]
|
, itm.[NameOrNumber]
|
||||||
, [Note] = IIF(ISNULL([Discount], 0) > 0, '<b>Rabatt '+ FORMAT([discount]*0.01,'#.0%','de-de') + '</b>', '') + ISNULL(itm.[Note],'')
|
, [Note] = IIF(ISNULL([Discount], 0) > 0, '<b>Rabatt '+ FORMAT([discount]*0.01,'#.0%','de-de') + '</b>', '') + ISNULL(itm.[Note],'')
|
||||||
, [order] = (itm.[baseorder] * 100) + itm.[order]
|
, [order] = (itm.[baseorder] * 100) + itm.[order]
|
||||||
|
, [SetItmId]
|
||||||
FROM @itm as itm
|
FROM @itm as itm
|
||||||
LEFT JOIN [dbo].[fds__custom_vat] as vat ON itm.[Id] = vat.[EntityId] and vat.[EntityType] = 'item';
|
LEFT JOIN [dbo].[fds__custom_vat] as vat ON itm.[Id] = vat.[EntityId] and vat.[EntityType] = 'item';
|
||||||
END
|
END
|
||||||
|
|||||||
@@ -176,7 +176,7 @@ BEGIN
|
|||||||
|
|
||||||
|
|
||||||
DECLARE @step [dbo].[fds__tt__mfr_steps];
|
DECLARE @step [dbo].[fds__tt__mfr_steps];
|
||||||
DECLARE @itm [dbo].[fds__tt__mfr_items];
|
DECLARE @itm [dbo].[fds__tt__mfr_items+];
|
||||||
DECLARE @company TABLE (
|
DECLARE @company TABLE (
|
||||||
[Id] [bigint] NOT NULL,
|
[Id] [bigint] NOT NULL,
|
||||||
[Location#ID] [bigint] NULL,
|
[Location#ID] [bigint] NULL,
|
||||||
@@ -204,8 +204,19 @@ BEGIN
|
|||||||
|
|
||||||
|
|
||||||
INSERT INTO @itm
|
INSERT INTO @itm
|
||||||
SELECT itm.*, [baseorder] = s.[baseorder], [order] = ROW_NUMBER() OVER (PARTITION BY [ServiceRequestId] ORDER BY [SortOrder], itm.[Id])
|
SELECT itm.*
|
||||||
from [dbo].[mfr__items] as itm JOIN @SReq as s ON itm.ServiceRequestId = s.[Id];
|
, [baseorder] = s.[baseorder]
|
||||||
|
, [order] = ROW_NUMBER() OVER (PARTITION BY itm.[ServiceRequestId] ORDER BY itm.[SortOrder], itm.[Id])
|
||||||
|
, [SetItmID] = CASE WHEN itm.[Type] = 'Set' AND ISNULL(itm.[Price], 0) > 0 THEN NULL
|
||||||
|
ELSE MAX(CASE WHEN itm.[Type] = 'Set' AND ISNULL(itm.[Price], 0) > 0 THEN itm.[Id] ELSE NULL END)
|
||||||
|
OVER (
|
||||||
|
PARTITION BY itm.[ServiceRequestId]
|
||||||
|
ORDER BY itm.[SortOrder], itm.[Id]
|
||||||
|
ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW
|
||||||
|
)
|
||||||
|
END
|
||||||
|
FROM [dbo].[mfr__items] as itm
|
||||||
|
JOIN @SReq as s ON itm.ServiceRequestId = s.[Id];
|
||||||
|
|
||||||
DECLARE @NUM_of_int_Invoices int = 0;
|
DECLARE @NUM_of_int_Invoices int = 0;
|
||||||
|
|
||||||
@@ -238,7 +249,9 @@ BEGIN
|
|||||||
,[CreatorId] = NULL
|
,[CreatorId] = NULL
|
||||||
,[UnitId] = 15825600513
|
,[UnitId] = 15825600513
|
||||||
,[UnitString] = 'Pauschal'
|
,[UnitString] = 'Pauschal'
|
||||||
,[baseorder] = s.[baseorder], [order] = 10000 + ROW_NUMBER() OVER (PARTITION BY srq.[mfr__servicerequest] ORDER BY inv.[dateFinalized], inv.DateCreated)
|
,[baseorder] = s.[baseorder]
|
||||||
|
, [order] = 10000 + ROW_NUMBER() OVER (PARTITION BY srq.[mfr__servicerequest] ORDER BY inv.[dateFinalized], inv.DateCreated)
|
||||||
|
,[SetItmID] = null
|
||||||
from [dbo].[fds__invoices] as inv
|
from [dbo].[fds__invoices] as inv
|
||||||
JOIN [dbo].[fds__invoice_servicerequests] as srq ON inv.[Id] = srq.[InvId] AND inv.[InvoiceType] = 'i' --important
|
JOIN [dbo].[fds__invoice_servicerequests] as srq ON inv.[Id] = srq.[InvId] AND inv.[InvoiceType] = 'i' --important
|
||||||
JOIN @SReq as s ON srq.[mfr__servicerequest] = s.[Id]
|
JOIN @SReq as s ON srq.[mfr__servicerequest] = s.[Id]
|
||||||
@@ -292,7 +305,9 @@ BEGIN
|
|||||||
,[CreatorId] = NULL
|
,[CreatorId] = NULL
|
||||||
,[UnitId] = 15825600513
|
,[UnitId] = 15825600513
|
||||||
,[UnitString] = 'Pauschal'
|
,[UnitString] = 'Pauschal'
|
||||||
,[baseorder] = 0, [order] = 1
|
,[baseorder] = 0
|
||||||
|
, [order] = 1
|
||||||
|
,[SetItmID] = null
|
||||||
from [dbo].[fds__invoices] as inv
|
from [dbo].[fds__invoices] as inv
|
||||||
WHERE ISNULL(inv.[isFinal],0) = 1 AND ISNULL(inv.[IsCanceled],0) = 0
|
WHERE ISNULL(inv.[isFinal],0) = 1 AND ISNULL(inv.[IsCanceled],0) = 0
|
||||||
AND inv.[Id] = @InvId;
|
AND inv.[Id] = @InvId;
|
||||||
@@ -373,6 +388,7 @@ BEGIN
|
|||||||
, itm.[NameOrNumber]
|
, itm.[NameOrNumber]
|
||||||
, itm.[Note]
|
, itm.[Note]
|
||||||
, [order] = (itm.[baseorder] * 100) + itm.[order]
|
, [order] = (itm.[baseorder] * 100) + itm.[order]
|
||||||
|
, [SetItmId]
|
||||||
FROM @itm as itm
|
FROM @itm as itm
|
||||||
LEFT JOIN [dbo].[fds__custom_vat] as vat ON itm.[Id] = vat.[EntityId] and vat.[EntityType] = 'item';
|
LEFT JOIN [dbo].[fds__custom_vat] as vat ON itm.[Id] = vat.[EntityId] and vat.[EntityType] = 'item';
|
||||||
END
|
END
|
||||||
|
|||||||
@@ -0,0 +1,64 @@
|
|||||||
|
|
||||||
|
CREATE PROCEDURE [dbo].[fds__setBankingtransaction_manual]
|
||||||
|
@taID varchar(10) = NULL, -- NULL=INSERT, value=UPDATE, value+others NULL=DELETE
|
||||||
|
@ValueDate date = NULL,
|
||||||
|
@Amount numeric(9,2) = NULL,
|
||||||
|
@NameOfPayer nvarchar(140) = NULL,
|
||||||
|
@SepaRemittanceInformation varchar(200) = NULL,
|
||||||
|
@authuser varchar(100) = NULL
|
||||||
|
AS
|
||||||
|
BEGIN
|
||||||
|
SET NOCOUNT ON;
|
||||||
|
IF [dbo].[fis_getModuleAuth]('fds_bam', @authuser) < 2
|
||||||
|
THROW 60000, N'not authorized', 1;
|
||||||
|
|
||||||
|
BEGIN TRY
|
||||||
|
|
||||||
|
-- DELETE
|
||||||
|
IF @taID IS NOT NULL AND @Amount IS NULL
|
||||||
|
BEGIN
|
||||||
|
DELETE FROM [dbo].[fds__bankingtransactions]
|
||||||
|
WHERE [taID] = @taID;
|
||||||
|
RETURN;
|
||||||
|
END
|
||||||
|
|
||||||
|
-- INSERT
|
||||||
|
IF @taID IS NULL
|
||||||
|
BEGIN
|
||||||
|
INSERT INTO [dbo].[fds__bankingtransactions]
|
||||||
|
([taID], [AccountIdentification], [ValueDate], [Amount],
|
||||||
|
[FundsCode], [AccountNumberOfPayer], [BankCodeOfPayer],
|
||||||
|
[CompensationAmount], [CreditorReference], [CreditorsReferenceParty],
|
||||||
|
[CustomerReference], [EndToEndReference], [JournalNumber],
|
||||||
|
[MandateReference], [NameOfPayer], [OriginalAmount],
|
||||||
|
[OriginatorsIdentificationCode], [PayersReferenceParty],
|
||||||
|
[PostingText], [SepaRemittanceInformation], [TextKeyAddition],
|
||||||
|
[TransactionCode], [IsUnstructuredData], [UnstructuredData],
|
||||||
|
[UnstructuredRemittanceInformation], [DebitCreditMark], [TransactionTypeIdCode])
|
||||||
|
VALUES
|
||||||
|
([dbo].[fds__fn_bankingtransaction_id](), '999', @ValueDate, @Amount,
|
||||||
|
NULL, NULL, NULL,
|
||||||
|
NULL, NULL, NULL,
|
||||||
|
NULL, NULL, NULL,
|
||||||
|
NULL, @NameOfPayer, NULL,
|
||||||
|
NULL, NULL,
|
||||||
|
'MANUELLE GUTSCHRIFT', @SepaRemittanceInformation, NULL,
|
||||||
|
NULL, 0, NULL,
|
||||||
|
NULL, 'C', 'MAN');
|
||||||
|
RETURN;
|
||||||
|
END
|
||||||
|
|
||||||
|
-- UPDATE
|
||||||
|
UPDATE [dbo].[fds__bankingtransactions]
|
||||||
|
SET
|
||||||
|
[ValueDate] = @ValueDate,
|
||||||
|
[Amount] = @Amount,
|
||||||
|
[NameOfPayer] = @NameOfPayer,
|
||||||
|
[SepaRemittanceInformation] = @SepaRemittanceInformation
|
||||||
|
WHERE [taID] = @taID;
|
||||||
|
|
||||||
|
END TRY
|
||||||
|
BEGIN CATCH
|
||||||
|
THROW;
|
||||||
|
END CATCH
|
||||||
|
END
|
||||||
@@ -20,19 +20,19 @@ BEGIN
|
|||||||
IF @referencetable = 'mfr__companies'
|
IF @referencetable = 'mfr__companies'
|
||||||
BEGIN
|
BEGIN
|
||||||
DELETE p
|
DELETE p
|
||||||
OUTput deleted.*, @now INTO [dbo].[mfr__d_*PartnerSet]
|
--OUTput deleted.*, @now INTO [dbo].[mfr__d_*PartnerSet]
|
||||||
FROM [dbo].[mfr__*PartnerSet] as p
|
FROM [dbo].[mfr__*PartnerSet] as p
|
||||||
WHERE p.Property like 'Company[:]%' AND EXISTS (SELECT * FROM @tmp as tmp where tmp.[id] = p.[EntityId]);
|
WHERE p.Property like 'Company[:]%' AND EXISTS (SELECT * FROM @tmp as tmp where tmp.[id] = p.[EntityId]);
|
||||||
DELETE cv
|
DELETE cv
|
||||||
OUTput deleted.*, @now INTO [dbo].[mfr__d_#customvalues]
|
--OUTput deleted.*, @now INTO [dbo].[mfr__d_#customvalues]
|
||||||
FROM [dbo].[mfr__#customvalues] as cv
|
FROM [dbo].[mfr__#customvalues] as cv
|
||||||
WHERE cv.Property like 'Company[:]%' AND EXISTS (SELECT * FROM @tmp as tmp where tmp.[id] = cv.[EntityId]);
|
WHERE cv.Property like 'Company[:]%' AND EXISTS (SELECT * FROM @tmp as tmp where tmp.[id] = cv.[EntityId]);
|
||||||
DELETE lc
|
DELETE lc
|
||||||
OUTPUT deleted.*, @now INTO [dbo].[mfr__d_#locations]
|
--OUTPUT deleted.*, @now INTO [dbo].[mfr__d_#locations]
|
||||||
FROM [dbo].[mfr__#locations] as lc
|
FROM [dbo].[mfr__#locations] as lc
|
||||||
WHERE lc.Property like 'Company[:]%' AND EXISTS (SELECT * FROM @tmp as tmp where tmp.[id] = lc.[EntityId]);
|
WHERE lc.Property like 'Company[:]%' AND EXISTS (SELECT * FROM @tmp as tmp where tmp.[id] = lc.[EntityId]);
|
||||||
DELETE cm
|
DELETE cm
|
||||||
OUTPUT deleted.*, @now INTO [dbo].[mfr__d_comments]
|
--OUTPUT deleted.*, @now INTO [dbo].[mfr__d_comments]
|
||||||
FROM [dbo].[mfr__comments] as cm
|
FROM [dbo].[mfr__comments] as cm
|
||||||
WHERE EXISTS (SELECT * FROM @tmp as tmp where tmp.[id] = cm.[CompanyId] AND ISNULL(cm.[ServiceRequestId],0) = 0);
|
WHERE EXISTS (SELECT * FROM @tmp as tmp where tmp.[id] = cm.[CompanyId] AND ISNULL(cm.[ServiceRequestId],0) = 0);
|
||||||
END
|
END
|
||||||
|
|||||||
@@ -20,19 +20,19 @@ BEGIN
|
|||||||
IF @referencetable = 'mfr__serviceobjects'
|
IF @referencetable = 'mfr__serviceobjects'
|
||||||
BEGIN
|
BEGIN
|
||||||
DELETE p
|
DELETE p
|
||||||
OUTput deleted.*, @now INTO [dbo].[mfr__d_*PartnerSet]
|
--OUTput deleted.*, @now INTO [dbo].[mfr__d_*PartnerSet]
|
||||||
FROM [dbo].[mfr__*PartnerSet] as p
|
FROM [dbo].[mfr__*PartnerSet] as p
|
||||||
WHERE p.Property like 'ServiceObject[:]%' AND EXISTS (SELECT * FROM @tmp as tmp where tmp.[id] = p.[EntityId]);
|
WHERE p.Property like 'ServiceObject[:]%' AND EXISTS (SELECT * FROM @tmp as tmp where tmp.[id] = p.[EntityId]);
|
||||||
DELETE cv
|
DELETE cv
|
||||||
OUTput deleted.*, @now INTO [dbo].[mfr__d_#customvalues]
|
--OUTput deleted.*, @now INTO [dbo].[mfr__d_#customvalues]
|
||||||
FROM [dbo].[mfr__#customvalues] as cv
|
FROM [dbo].[mfr__#customvalues] as cv
|
||||||
WHERE cv.Property like 'ServiceObject[:]%' AND EXISTS (SELECT * FROM @tmp as tmp where tmp.[id] = cv.[EntityId]);
|
WHERE cv.Property like 'ServiceObject[:]%' AND EXISTS (SELECT * FROM @tmp as tmp where tmp.[id] = cv.[EntityId]);
|
||||||
DELETE lc
|
DELETE lc
|
||||||
OUTPUT deleted.*, @now INTO [dbo].[mfr__d_#locations]
|
--OUTPUT deleted.*, @now INTO [dbo].[mfr__d_#locations]
|
||||||
FROM [dbo].[mfr__#locations] as lc
|
FROM [dbo].[mfr__#locations] as lc
|
||||||
WHERE lc.Property like 'ServiceObject[:]%' AND EXISTS (SELECT * FROM @tmp as tmp where tmp.[id] = lc.[EntityId]);
|
WHERE lc.Property like 'ServiceObject[:]%' AND EXISTS (SELECT * FROM @tmp as tmp where tmp.[id] = lc.[EntityId]);
|
||||||
DELETE itm
|
DELETE itm
|
||||||
OUTPUT deleted.*, @now INTO [dbo].[mfr__d_items]
|
--OUTPUT deleted.*, @now INTO [dbo].[mfr__d_items]
|
||||||
FROM [dbo].[mfr__items] as itm
|
FROM [dbo].[mfr__items] as itm
|
||||||
WHERE EXISTS (SELECT * FROM @tmp as tmp where tmp.[id] = itm.[ServiceObjectId] and ISNULL(itm.[ServiceRequestId],0) = 0);
|
WHERE EXISTS (SELECT * FROM @tmp as tmp where tmp.[id] = itm.[ServiceObjectId] and ISNULL(itm.[ServiceRequestId],0) = 0);
|
||||||
END
|
END
|
||||||
|
|||||||
@@ -24,39 +24,39 @@ BEGIN
|
|||||||
IF @referencetable = 'mfr__servicerequests'
|
IF @referencetable = 'mfr__servicerequests'
|
||||||
BEGIN
|
BEGIN
|
||||||
DELETE p
|
DELETE p
|
||||||
OUTput deleted.*, @now INTO [dbo].[mfr__d_*PartnerSet]
|
--OUTput deleted.*, @now INTO [dbo].[mfr__d_*PartnerSet]
|
||||||
FROM [dbo].[mfr__*PartnerSet] as p
|
FROM [dbo].[mfr__*PartnerSet] as p
|
||||||
WHERE p.Property like 'ServiceRequest[:]%' AND EXISTS (SELECT * FROM @tmp as tmp where tmp.[id] = p.[EntityId]);
|
WHERE p.Property like 'ServiceRequest[:]%' AND EXISTS (SELECT * FROM @tmp as tmp where tmp.[id] = p.[EntityId]);
|
||||||
DELETE cv
|
DELETE cv
|
||||||
OUTput deleted.*, @now INTO [dbo].[mfr__d_#customvalues]
|
--OUTput deleted.*, @now INTO [dbo].[mfr__d_#customvalues]
|
||||||
FROM [dbo].[mfr__#customvalues] as cv
|
FROM [dbo].[mfr__#customvalues] as cv
|
||||||
WHERE cv.Property like 'ServiceRequest[:]%' AND EXISTS (SELECT * FROM @tmp as tmp where tmp.[id] = cv.[EntityId]);
|
WHERE cv.Property like 'ServiceRequest[:]%' AND EXISTS (SELECT * FROM @tmp as tmp where tmp.[id] = cv.[EntityId]);
|
||||||
DELETE lc
|
DELETE lc
|
||||||
OUTPUT deleted.*, @now INTO [dbo].[mfr__d_#locations]
|
--OUTPUT deleted.*, @now INTO [dbo].[mfr__d_#locations]
|
||||||
FROM [dbo].[mfr__#locations] as lc
|
FROM [dbo].[mfr__#locations] as lc
|
||||||
WHERE lc.Property like 'ServiceRequest[:]%' AND EXISTS (SELECT * FROM @tmp as tmp where tmp.[id] = lc.[EntityId]);
|
WHERE lc.Property like 'ServiceRequest[:]%' AND EXISTS (SELECT * FROM @tmp as tmp where tmp.[id] = lc.[EntityId]);
|
||||||
DELETE stp
|
DELETE stp
|
||||||
OUTPUT deleted.*, @now INTO [dbo].[mfr__d_steps]
|
--OUTPUT deleted.*, @now INTO [dbo].[mfr__d_steps]
|
||||||
FROM [dbo].[mfr__steps] as stp
|
FROM [dbo].[mfr__steps] as stp
|
||||||
WHERE EXISTS (SELECT * FROM @tmp as tmp where tmp.[id] = stp.[ServiceRequestId] and ISNULL(stp.[StepListTemplateId],0) = 0);
|
WHERE EXISTS (SELECT * FROM @tmp as tmp where tmp.[id] = stp.[ServiceRequestId] and ISNULL(stp.[StepListTemplateId],0) = 0);
|
||||||
DELETE app
|
DELETE app
|
||||||
OUTPUT deleted.*, @now INTO [dbo].[mfr__d_appointments]
|
--OUTPUT deleted.*, @now INTO [dbo].[mfr__d_appointments]
|
||||||
FROM [dbo].[mfr__appointments] as app
|
FROM [dbo].[mfr__appointments] as app
|
||||||
WHERE EXISTS (SELECT * FROM @tmp as tmp where tmp.[id] = app.[ServiceRequestId]);
|
WHERE EXISTS (SELECT * FROM @tmp as tmp where tmp.[id] = app.[ServiceRequestId]);
|
||||||
DELETE itm
|
DELETE itm
|
||||||
OUTPUT deleted.*, @now INTO [dbo].[mfr__d_items]
|
--OUTPUT deleted.*, @now INTO [dbo].[mfr__d_items]
|
||||||
FROM [dbo].[mfr__items] as itm
|
FROM [dbo].[mfr__items] as itm
|
||||||
WHERE EXISTS (SELECT * FROM @tmp as tmp where tmp.[id] = itm.[ServiceRequestId] and ISNULL(itm.[ServiceobjectId],0) = 0);
|
WHERE EXISTS (SELECT * FROM @tmp as tmp where tmp.[id] = itm.[ServiceRequestId] and ISNULL(itm.[ServiceobjectId],0) = 0);
|
||||||
DELETE te
|
DELETE te
|
||||||
OUTPUT deleted.*, @now INTO [dbo].[mfr__d_timeevents]
|
--OUTPUT deleted.*, @now INTO [dbo].[mfr__d_timeevents]
|
||||||
FROM [dbo].[mfr__timeevents] as te
|
FROM [dbo].[mfr__timeevents] as te
|
||||||
WHERE EXISTS (SELECT * FROM @tmp as tmp where tmp.[id] = te.[ServiceRequestId]);
|
WHERE EXISTS (SELECT * FROM @tmp as tmp where tmp.[id] = te.[ServiceRequestId]);
|
||||||
DELETE sm
|
DELETE sm
|
||||||
OUTPUT deleted.*, @now INTO [dbo].[mfr__d_stockmovements]
|
--OUTPUT deleted.*, @now INTO [dbo].[mfr__d_stockmovements]
|
||||||
FROM [dbo].[mfr__stockmovements] as sm
|
FROM [dbo].[mfr__stockmovements] as sm
|
||||||
WHERE EXISTS (SELECT * FROM @tmp as tmp where tmp.[id] = sm.[ServiceRequestId]);
|
WHERE EXISTS (SELECT * FROM @tmp as tmp where tmp.[id] = sm.[ServiceRequestId]);
|
||||||
DELETE cm
|
DELETE cm
|
||||||
OUTPUT deleted.*, @now INTO [dbo].[mfr__d_comments]
|
--OUTPUT deleted.*, @now INTO [dbo].[mfr__d_comments]
|
||||||
FROM [dbo].[mfr__comments] as cm
|
FROM [dbo].[mfr__comments] as cm
|
||||||
WHERE EXISTS (SELECT * FROM @tmp as tmp where tmp.[id] = cm.[ServiceRequestId] AND ISNULL(cm.[CompanyId],0) = 0);
|
WHERE EXISTS (SELECT * FROM @tmp as tmp where tmp.[id] = cm.[ServiceRequestId] AND ISNULL(cm.[CompanyId],0) = 0);
|
||||||
END
|
END
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ BEGIN
|
|||||||
IF @referencetable = 'mfr__steplisttemplates'
|
IF @referencetable = 'mfr__steplisttemplates'
|
||||||
BEGIN
|
BEGIN
|
||||||
DELETE stp
|
DELETE stp
|
||||||
OUTPUT deleted.*, @now INTO [dbo].[mfr__d_steps]
|
--OUTPUT deleted.*, @now INTO [dbo].[mfr__d_steps]
|
||||||
FROM [dbo].[mfr__steps] as stp
|
FROM [dbo].[mfr__steps] as stp
|
||||||
WHERE EXISTS (SELECT * FROM @tmp as tmp where tmp.[id] = stp.[StepListTemplateId] and ISNULL(stp.[ServiceRequestId],0) = 0);
|
WHERE EXISTS (SELECT * FROM @tmp as tmp where tmp.[id] = stp.[StepListTemplateId] and ISNULL(stp.[ServiceRequestId],0) = 0);
|
||||||
END
|
END
|
||||||
|
|||||||
@@ -124,26 +124,26 @@ BEGIN
|
|||||||
$action, deleted.*
|
$action, deleted.*
|
||||||
INTO @ARCHIVE;
|
INTO @ARCHIVE;
|
||||||
|
|
||||||
INSERT INTO [dbo].[mfr__d_steps]
|
--INSERT INTO [dbo].[mfr__d_steps]
|
||||||
SELECT [Id]
|
-- SELECT [Id]
|
||||||
,[MobileId]
|
-- ,[MobileId]
|
||||||
,[Version]
|
-- ,[Version]
|
||||||
,[Name]
|
-- ,[Name]
|
||||||
,[IsDone]
|
-- ,[IsDone]
|
||||||
,[HasError]
|
-- ,[HasError]
|
||||||
,[TrackingId]
|
-- ,[TrackingId]
|
||||||
,[Type]
|
-- ,[Type]
|
||||||
,[SortOrder]
|
-- ,[SortOrder]
|
||||||
,[Data]
|
-- ,[Data]
|
||||||
,[DateModifiedOffline]
|
-- ,[DateModifiedOffline]
|
||||||
,[ServiceRequestId]
|
-- ,[ServiceRequestId]
|
||||||
,[Description]
|
-- ,[Description]
|
||||||
,[Comment]
|
-- ,[Comment]
|
||||||
,[InternalComment]
|
-- ,[InternalComment]
|
||||||
,[ServiceObjectId]
|
-- ,[ServiceObjectId]
|
||||||
,[StepListTemplateId]
|
-- ,[StepListTemplateId]
|
||||||
,[ParentId]
|
-- ,[ParentId]
|
||||||
,[DateDeleted] = @now
|
-- ,[DateDeleted] = @now
|
||||||
FROM @ARCHIVE
|
-- FROM @ARCHIVE
|
||||||
WHERE [ActionType] IN ( 'DELETE', 'UPDATE' );
|
-- WHERE [ActionType] IN ( 'DELETE', 'UPDATE' );
|
||||||
END
|
END
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
CREATE TYPE [dbo].[fds__tt__mfr_items+] AS TABLE (
|
||||||
|
[Id] BIGINT NULL,
|
||||||
|
[Version] INT NULL,
|
||||||
|
[QuantityHours] NUMERIC (10, 3) NULL,
|
||||||
|
[PlannedQuantityHours] NUMERIC (10, 3) NULL,
|
||||||
|
[ItemTypeId] BIGINT NULL,
|
||||||
|
[ItemNumber] NVARCHAR (255) NULL,
|
||||||
|
[TrackingId] NVARCHAR (255) NULL,
|
||||||
|
[Manufacture] NVARCHAR (255) NULL,
|
||||||
|
[Price] NUMERIC (10, 3) NULL,
|
||||||
|
[Costs] NUMERIC (10, 3) NULL,
|
||||||
|
[NameOrNumber] NVARCHAR (255) NULL,
|
||||||
|
[CustomValues] NVARCHAR (MAX) NULL,
|
||||||
|
[Note] NVARCHAR (MAX) NULL,
|
||||||
|
[ExternalId] NVARCHAR (255) NULL,
|
||||||
|
[Discount] NUMERIC (10, 3) NULL,
|
||||||
|
[VAT] NUMERIC (10, 3) NULL,
|
||||||
|
[IsManual] BIT NULL,
|
||||||
|
[SortOrder] INT NULL,
|
||||||
|
[Type] NVARCHAR (255) NULL,
|
||||||
|
[ServiceRequestId] BIGINT NULL,
|
||||||
|
[ServiceObjectId] BIGINT NULL,
|
||||||
|
[CreatorId] BIGINT NULL,
|
||||||
|
[UnitId] BIGINT NULL,
|
||||||
|
[UnitString] NVARCHAR (255) NULL,
|
||||||
|
[baseorder] INT NULL,
|
||||||
|
[order] INT NULL,
|
||||||
|
[SetItmId] BIGINT NULL);
|
||||||
|
|
||||||
@@ -1,27 +1,28 @@
|
|||||||
CREATE TYPE [dbo].[fds__tt__reminder_core] AS TABLE (
|
CREATE TYPE [dbo].[fds__tt__reminder_core] AS TABLE (
|
||||||
[Id] VARCHAR (10) NULL,
|
[Id] VARCHAR (10) NULL,
|
||||||
[Version] INT NULL,
|
[Version] INT NULL,
|
||||||
[DocumentName] VARCHAR (100) NULL,
|
[DocumentName] VARCHAR (100) NULL,
|
||||||
[InvId] VARCHAR (15) NULL,
|
[InvId] VARCHAR (15) NULL,
|
||||||
[CustomerId] BIGINT NULL,
|
[CustomerId] BIGINT NULL,
|
||||||
[SendToAddress] NVARCHAR (1000) NULL,
|
[SendToAddress] NVARCHAR (1000) NULL,
|
||||||
[SendToEmail] NVARCHAR (255) NULL,
|
[SendToEmail] NVARCHAR (255) NULL,
|
||||||
[type] VARCHAR (3) NULL,
|
[type] VARCHAR (3) NULL,
|
||||||
[amount] NUMERIC (10, 3) NULL,
|
[amount] NUMERIC (10, 3) NULL,
|
||||||
[amount_payed] NUMERIC (10, 3) NULL,
|
[amount_payed] NUMERIC (10, 3) NULL,
|
||||||
[amount_open] NUMERIC (10, 3) NULL,
|
[amount_open] NUMERIC (10, 3) NULL,
|
||||||
[subject] NVARCHAR (255) NULL,
|
[subject] NVARCHAR (255) NULL,
|
||||||
[text] NVARCHAR (2000) NULL,
|
[text] NVARCHAR (2000) NULL,
|
||||||
[IsSent] BIT NULL,
|
[IsSent] BIT NULL,
|
||||||
[IsFinal] BIT NULL,
|
[IsFinal] BIT NULL,
|
||||||
[CustomValues] NVARCHAR (MAX) NULL,
|
[CustomValues] NVARCHAR (MAX) NULL,
|
||||||
[DateSent] DATETIME NULL,
|
[DateSent] DATETIME NULL,
|
||||||
[UserSent] VARCHAR (25) NULL,
|
[UserSent] VARCHAR (25) NULL,
|
||||||
[DateFinalized] DATETIME NULL,
|
[DateFinalized] DATETIME NULL,
|
||||||
[UserFinalized] VARCHAR (25) NULL,
|
[UserFinalized] VARCHAR (25) NULL,
|
||||||
[DateCreated] DATETIME NULL,
|
[DateCreated] DATETIME NULL,
|
||||||
[UserCreated] VARCHAR (25) NULL,
|
[UserCreated] VARCHAR (25) NULL,
|
||||||
[DateModified] DATETIME NULL,
|
[DateModified] DATETIME NULL,
|
||||||
[UserModified] VARCHAR (25) NULL,
|
[UserModified] VARCHAR (25) NULL,
|
||||||
[file] VARBINARY (MAX) NULL);
|
[file] VARBINARY (MAX) NULL,
|
||||||
|
[file_guid] UNIQUEIDENTIFIER NULL);
|
||||||
|
|
||||||
|
|||||||
@@ -32,7 +32,7 @@
|
|||||||
<PackageReference Include="RestSharp" Version="114.0.0" />
|
<PackageReference Include="RestSharp" Version="114.0.0" />
|
||||||
<PackageReference Include="System.IdentityModel.Tokens.Jwt" Version="8.19.1" />
|
<PackageReference Include="System.IdentityModel.Tokens.Jwt" Version="8.19.1" />
|
||||||
<!-- New packages (replacements) -->
|
<!-- New packages (replacements) -->
|
||||||
<PackageReference Include="Azure.Messaging.ServiceBus" Version="7.20.1" />
|
<PackageReference Include="Azure.Messaging.ServiceBus" Version="7.20.2" />
|
||||||
<PackageReference Include="Microsoft.Data.SqlClient" Version="7.0.2" />
|
<PackageReference Include="Microsoft.Data.SqlClient" Version="7.0.2" />
|
||||||
<PackageReference Include="Microsoft.Extensions.Configuration" Version="10.0.9" />
|
<PackageReference Include="Microsoft.Extensions.Configuration" Version="10.0.9" />
|
||||||
<PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="10.0.9" />
|
<PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="10.0.9" />
|
||||||
|
|||||||
Reference in New Issue
Block a user