7 Commits

Author SHA1 Message Date
CPATRD f1ccb7375b Bump Version to 9.6.1 2026-04-15 18:40:48 +02:00
CPAMAP f5ef2aac0f Match ZeitConsens user case-insensitively
The saved tapi-zc-user and the server's MA_USER_NAME are compared
to decide whether to auto-switch the 3CX presence. A mismatch in
capitalization between the two sources silently disabled the sync,
so both are lowercased before comparison.
2026-04-15 17:21:51 +02:00
CPATRD fae65a637c Fixed docker build order for StaticFiles 2026-04-13 14:46:33 +02:00
CPATRD fd5ec49569 TapiDirectoryRepository Tests 2026-04-13 14:19:46 +02:00
CPAMAP 8daa8bf0ff Mark TapiContact properties as required
Silences nullable-reference warnings (CS8618) using the same
pattern already applied to Availability.MA_USER_NAME.
2026-04-13 14:14:14 +02:00
CPAMAP 788ec55bde Filter availability by US_ACTIVE
Restricts the availability listing to projectmanagement.dbo.CP_USER
rows where US_ACTIVE = 1, so deactivated users are not returned.
2026-04-13 14:14:14 +02:00
CPATRD 21683f5e8b Client aktualisiert 2026-04-13 13:23:50 +02:00
11 changed files with 55 additions and 26 deletions
+1 -1
View File
@@ -1,7 +1,7 @@
{ {
"name": "3cx-tapi", "name": "3cx-tapi",
"description": "3CX CP Tapi and Projectmanager integration", "description": "3CX CP Tapi and Projectmanager integration",
"version": "9.6.0", "version": "9.6.1",
"author": { "author": {
"name": "Daniel Triendl", "name": "Daniel Triendl",
"email": "d.triendl@cp-solutions.at" "email": "d.triendl@cp-solutions.at"
+1 -1
View File
@@ -42,7 +42,7 @@ export class Status {
if (!this._enabled || !this._user) { if (!this._enabled || !this._user) {
return; return;
} }
var entry = avs.find(a => a.user === this._user); var entry = avs.find(a => a.user.toLowerCase() === this._user.toLowerCase());
if (!entry) { if (!entry) {
return; return;
} }
@@ -14,6 +14,8 @@ namespace CPATapi.Client.Models
{ {
/// <summary>Stores additional data not described in the OpenAPI description found when deserializing. Can be used for serialization as well.</summary> /// <summary>Stores additional data not described in the OpenAPI description found when deserializing. Can be used for serialization as well.</summary>
public IDictionary<string, object> AdditionalData { get; set; } public IDictionary<string, object> AdditionalData { get; set; }
/// <summary>The atCompany property</summary>
public bool? AtCompany { get; set; }
/// <summary>The extension property</summary> /// <summary>The extension property</summary>
#if NETSTANDARD2_1_OR_GREATER || NETCOREAPP3_1_OR_GREATER #if NETSTANDARD2_1_OR_GREATER || NETCOREAPP3_1_OR_GREATER
#nullable enable #nullable enable
@@ -22,6 +24,8 @@ namespace CPATapi.Client.Models
#else #else
public string Extension { get; set; } public string Extension { get; set; }
#endif #endif
/// <summary>The lastStamp property</summary>
public DateTimeOffset? LastStamp { get; set; }
/// <summary>The loggedIn property</summary> /// <summary>The loggedIn property</summary>
public bool? LoggedIn { get; set; } public bool? LoggedIn { get; set; }
/// <summary>The user property</summary> /// <summary>The user property</summary>
@@ -57,7 +61,9 @@ namespace CPATapi.Client.Models
{ {
return new Dictionary<string, Action<IParseNode>> return new Dictionary<string, Action<IParseNode>>
{ {
{ "atCompany", n => { AtCompany = n.GetBoolValue(); } },
{ "extension", n => { Extension = n.GetStringValue(); } }, { "extension", n => { Extension = n.GetStringValue(); } },
{ "lastStamp", n => { LastStamp = n.GetDateTimeOffsetValue(); } },
{ "loggedIn", n => { LoggedIn = n.GetBoolValue(); } }, { "loggedIn", n => { LoggedIn = n.GetBoolValue(); } },
{ "user", n => { User = n.GetStringValue(); } }, { "user", n => { User = n.GetStringValue(); } },
}; };
@@ -69,7 +75,9 @@ namespace CPATapi.Client.Models
public virtual void Serialize(ISerializationWriter writer) public virtual void Serialize(ISerializationWriter writer)
{ {
if(ReferenceEquals(writer, null)) throw new ArgumentNullException(nameof(writer)); if(ReferenceEquals(writer, null)) throw new ArgumentNullException(nameof(writer));
writer.WriteBoolValue("atCompany", AtCompany);
writer.WriteStringValue("extension", Extension); writer.WriteStringValue("extension", Extension);
writer.WriteDateTimeOffsetValue("lastStamp", LastStamp);
writer.WriteBoolValue("loggedIn", LoggedIn); writer.WriteBoolValue("loggedIn", LoggedIn);
writer.WriteStringValue("user", User); writer.WriteStringValue("user", User);
writer.WriteAdditionalData(AdditionalData); writer.WriteAdditionalData(AdditionalData);
+1 -1
View File
@@ -1,5 +1,5 @@
{ {
"descriptionHash": "3ADB4B190A2637B9EC01981B2508C539F2A582D95310D01FF97D2F2C068B9024CDC66F4D14F486265ED22314E9EEB2EA7CD3BF0F3D1ECC061BA7B9734B520A9D", "descriptionHash": "DEDF4FD053830F062E3DE69918EF38C90DCD76DC47BB854DDA337BB592E2A34DFAAAC5F0C23D40481BD2883833366E7AAC243D825DBCCE2897E4E6A78B890BE9",
"descriptionLocation": "../CPATapi.Server/CPATapi.Server.json", "descriptionLocation": "../CPATapi.Server/CPATapi.Server.json",
"lockFileVersion": "1.0.0", "lockFileVersion": "1.0.0",
"kiotaVersion": "1.30.0", "kiotaVersion": "1.30.0",
@@ -8,7 +8,7 @@
<DockerDefaultTargetOS>Linux</DockerDefaultTargetOS> <DockerDefaultTargetOS>Linux</DockerDefaultTargetOS>
<DockerfileContext>..\..</DockerfileContext> <DockerfileContext>..\..</DockerfileContext>
<OpenApiDocumentsDirectory>.</OpenApiDocumentsDirectory> <OpenApiDocumentsDirectory>.</OpenApiDocumentsDirectory>
<Version>9.6.0</Version> <Version>9.6.1</Version>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
+1 -1
View File
@@ -22,6 +22,7 @@ COPY ["server/src/CPATapi.Server/CPATapi.Server.csproj", "server/src/CPATapi.Ser
RUN dotnet restore "server/src/CPATapi.Server/CPATapi.Server.csproj" RUN dotnet restore "server/src/CPATapi.Server/CPATapi.Server.csproj"
COPY . . COPY . .
WORKDIR "/src/server/src/CPATapi.Server" WORKDIR "/src/server/src/CPATapi.Server"
COPY --from=build-userscript ["/src/dist/3CX TAPI.prod.user.js", "./wwwroot/3CX_TAPI.user.js"]
RUN dotnet build "./CPATapi.Server.csproj" -c $BUILD_CONFIGURATION -o /app/build RUN dotnet build "./CPATapi.Server.csproj" -c $BUILD_CONFIGURATION -o /app/build
# This stage is used to publish the service project to be copied to the final stage # This stage is used to publish the service project to be copied to the final stage
@@ -33,5 +34,4 @@ RUN dotnet publish "./CPATapi.Server.csproj" -c $BUILD_CONFIGURATION -o /app/pub
FROM base AS final FROM base AS final
WORKDIR /app WORKDIR /app
COPY --from=publish /app/publish . COPY --from=publish /app/publish .
COPY --from=build-userscript ["/src/dist/3CX TAPI.prod.user.js", "./wwwroot/3CX_TAPI.user.js"]
ENTRYPOINT ["dotnet", "CPATapi.Server.dll"] ENTRYPOINT ["dotnet", "CPATapi.Server.dll"]
@@ -2,9 +2,9 @@ namespace CPATapi.Server.Models;
public class TapiContact public class TapiContact
{ {
public string TD_ID { get; set; } public required string TD_ID { get; set; }
public string TD_NAME { get; set; } public required string TD_NAME { get; set; }
public string TD_NUMBER { get; set; } public required string TD_NUMBER { get; set; }
public string TD_NUMBER_TAPI { get; set; } public required string TD_NUMBER_TAPI { get; set; }
public string TD_MEDIUM { get; set; } public required string TD_MEDIUM { get; set; }
} }
@@ -31,6 +31,7 @@ internal class ZeitConsensRepository(IConfiguration config) : Repository(config)
) buLast ) buLast
WHERE WHERE
ma.MA_USER_AKTIV = 1 ma.MA_USER_AKTIV = 1
AND us.US_ACTIVE = 1
"""; """;
public async Task<IEnumerable<Availability>> GetUsersAvailabilityAsync(DateTime from, DateTime to) public async Task<IEnumerable<Availability>> GetUsersAvailabilityAsync(DateTime from, DateTime to)
@@ -0,0 +1,18 @@
using Microsoft.Extensions.Configuration;
namespace CPATapi.Server.Tests;
public class RepositoryTestsBase
{
[OneTimeSetUp]
public void Setup()
{
// the type specified here is just so the secrets library can
// find the UserSecretId we added in the csproj file
var builder = new ConfigurationBuilder().AddUserSecrets<RepositoryTestsBase>();
Configuration = builder.Build();
}
protected IConfigurationRoot Configuration { get; private set; }
}
@@ -0,0 +1,15 @@
using CPATapi.Server.Repository;
namespace CPATapi.Server.Tests;
public class TapiDirectoryRepositoryTests : RepositoryTestsBase
{
[Test]
public async Task TestGetAllAsync()
{
var tdRepo = new TapiDirectoryRepository(Configuration);
var contacts = (await tdRepo.GetAllAsync()).ToList();
Assert.That(contacts, Is.Not.Empty);
Assert.That(contacts, Has.Count.GreaterThan(1));
}
}
@@ -1,26 +1,13 @@
using Microsoft.Extensions.Configuration;
using CPATapi.Server.Repository; using CPATapi.Server.Repository;
namespace CPATapi.Server.Tests; namespace CPATapi.Server.Tests;
public class ZeitConsensRepositoryTest public class ZeitConsensRepositoryTest : RepositoryTestsBase
{ {
[OneTimeSetUp]
public void Setup()
{
// the type specified here is just so the secrets library can
// find the UserSecretId we added in the csproj file
var builder = new ConfigurationBuilder().AddUserSecrets<ZeitConsensRepositoryTest>();
_configuration = builder.Build();
}
private IConfigurationRoot _configuration { get; set; }
[Test] [Test]
public async Task TestGetUsersAvailabilityAsync() public async Task TestGetUsersAvailabilityAsync()
{ {
var zcRepo = new ZeitConsensRepository(_configuration); var zcRepo = new ZeitConsensRepository(Configuration);
var availability= (await zcRepo.GetUsersAvailabilityAsync(DateTime.Now.Date, DateTime.Now.AddDays(1).Date)).ToList(); var availability= (await zcRepo.GetUsersAvailabilityAsync(DateTime.Now.Date, DateTime.Now.AddDays(1).Date)).ToList();
Assert.That(availability, Is.Not.Empty); Assert.That(availability, Is.Not.Empty);
Assert.That(availability, Has.Count.GreaterThan(1)); Assert.That(availability, Has.Count.GreaterThan(1));
@@ -29,7 +16,7 @@ public class ZeitConsensRepositoryTest
[Test] [Test]
public async Task TestGetUserAvailabilityAsync() public async Task TestGetUserAvailabilityAsync()
{ {
var zcRepo = new ZeitConsensRepository(_configuration); var zcRepo = new ZeitConsensRepository(Configuration);
var availability = await zcRepo.GetUserAvailabilityAsync("CPATRD", DateTime.Now.Date, DateTime.Now.AddDays(1).Date); var availability = await zcRepo.GetUserAvailabilityAsync("CPATRD", DateTime.Now.Date, DateTime.Now.AddDays(1).Date);
Assert.That(availability, Is.Not.Null); Assert.That(availability, Is.Not.Null);
Assert.That(availability.MA_USER_NAME, Is.EqualTo("CPATRD")); Assert.That(availability.MA_USER_NAME, Is.EqualTo("CPATRD"));