feat: manual payment package-based + StockMovement constraint fix
Build and Deploy to Kubernetes / build-and-deploy (push) Failing after 7m23s
Build and Deploy to Kubernetes / build-and-deploy (push) Failing after 7m23s
- ManualPayment: add package_id to proto, handler selects package by ID - ManualPayment: remove manual amount input, use package price - ManualPayment: validator now checks PackageId instead of Amount - StockMovement: fix CHECK constraint (store negative qty for decreases) - StockMovement: Reserved/Released don't change physical Quantity - Proto NuGet bumped to 0.0.192
This commit is contained in:
@@ -19,28 +19,89 @@ type: Opaque
|
||||
stringData:
|
||||
appsettings.Production.json: |
|
||||
{
|
||||
"PaymentProvider": "zarinpal",
|
||||
"ZarinPal": {
|
||||
"MerchantId": "4225d555-5fa9-4df0-9b61-1ce152cbbba8",
|
||||
"UseSandbox": false
|
||||
},
|
||||
"CmsBaseUrl": "https://cms.kbs2.ir",
|
||||
"FrontOfficeBaseUrl": "https://kbs2.ir",
|
||||
"JwtSecurityKey": "TvlZVx5TJaHs8e9HgUdGzhGP2CIidoI444nAj+8+g7c=",
|
||||
"JwtIssuer": "https://localhost",
|
||||
"JwtAudience": "https://localhost",
|
||||
"JwtExpiryInDays": 5,
|
||||
"ConnectionStrings": {
|
||||
"DefaultConnection": "Server=mssql-svc;Database=KBS;User Id=sa;Password=YourStrong@Passw0rd;TrustServerCertificate=True;"
|
||||
"DefaultConnection": "Server=mssql-svc;Database=KBS;User Id=sa;Password=YourStrong@Passw0rd;TrustServerCertificate=True;",
|
||||
"providerName": "System.Data.SqlClient"
|
||||
},
|
||||
"Otp": {
|
||||
"Secret": "K2w8k1h1mH2Qz1kqWk0c8kQ2Pq8q9H1eE2nqN1qQ8x7M="
|
||||
},
|
||||
"Monitoring": {
|
||||
"SentryEnabled": false,
|
||||
"SentryDsn": "",
|
||||
"SlackEnabled": false,
|
||||
"SlackWebhookUrl": "",
|
||||
"EmailAlertsEnabled": false,
|
||||
"AdminEmails": [
|
||||
"admin@example.com"
|
||||
],
|
||||
"SmsNotificationsEnabled": false,
|
||||
"SmsApiKey": "",
|
||||
"SmsGatewayUrl": ""
|
||||
},
|
||||
"Email": {
|
||||
"Enabled": true,
|
||||
"SmtpHost": "smtp.gmail.com",
|
||||
"SmtpPort": 587,
|
||||
"SmtpUsername": "your-email@gmail.com",
|
||||
"SmtpPassword": "your-app-password",
|
||||
"FromEmail": "noreply@foursat.com",
|
||||
"FromName": "FourSat CMS",
|
||||
"EnableSsl": true
|
||||
},
|
||||
"Sms": {
|
||||
"Enabled": true,
|
||||
"Provider": "Kavenegar",
|
||||
"KavenegarApiKey": "43676A4E786A6C50452F2F6252507939346A5145764B33566B456454374E657A614468706658656D6534593D",
|
||||
"Sender": "1000001110100"
|
||||
},
|
||||
"DayaPayment": {
|
||||
"BaseUrl": "https://api.daya.ir",
|
||||
"ApiKey": "YOUR_DAYA_API_KEY"
|
||||
},
|
||||
"DayaApi": {
|
||||
"UseMock": false,
|
||||
"BaseAddress": "https://Dayadiamond.ir",
|
||||
"MerchantPermissionKey": "56146364$04sXjethI5WxhItR1Q9xnmFdJzl2BB8Bclsq8dAy7YVSZp3vtt-wP7ivrcCvmKLq",
|
||||
"CacheDurationMinutes": 20
|
||||
},
|
||||
"Chatika": {
|
||||
"Enabled": true,
|
||||
"BaseUrl": "https://api.chatika.ir",
|
||||
"ApiKey": "tIukvL8dnV4cB3yVWcCD9Xyfbj8rBxm5wPt2mLyJCgTsBBoMTWjt6mFEqQwpw-er"
|
||||
},
|
||||
"BackgroundJobs": {
|
||||
"WeeklyCommissionCalculation": {
|
||||
"Enabled": false,
|
||||
"CronExpression": "5 0 * * 0"
|
||||
}
|
||||
},
|
||||
"SeedWorkers": {
|
||||
"MagicWalletCycleSeed": {
|
||||
"Enabled": true
|
||||
}
|
||||
},
|
||||
"FileStorage": {
|
||||
"UploadPath": "/app/Uploads"
|
||||
},
|
||||
"AllowedHosts": "*",
|
||||
"Kestrel": {
|
||||
"EndpointDefaults": {
|
||||
"Protocols": "Http2"
|
||||
}
|
||||
},
|
||||
"Authentication": {
|
||||
"Authority": "https://ids.domain.com/",
|
||||
"Audience": "domain_api"
|
||||
},
|
||||
"Seq": {
|
||||
"ServerUrl": "http://seq-svc:5341",
|
||||
"ApiKey": "oxpvpUzU1pZxMS4s3Fqq"
|
||||
@@ -52,3 +113,4 @@ stringData:
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+6
-1
@@ -14,10 +14,15 @@ public class CreateManualPaymentCommand : IRequest<long>
|
||||
public long UserId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// مبلغ تراکنش (تومان)
|
||||
/// مبلغ تراکنش (تومان) — [DEPRECATED] مبلغ از پکیج خوانده میشود
|
||||
/// </summary>
|
||||
public long Amount { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// شناسه پکیج — اگر 0 باشد، پکیج پایه استفاده میشود
|
||||
/// </summary>
|
||||
public long PackageId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// نوع تراکنش دستی
|
||||
/// </summary>
|
||||
|
||||
+12
-2
@@ -70,10 +70,20 @@ public class CreateManualPaymentCommandHandler : IRequestHandler<CreateManualPay
|
||||
throw new NotFoundException($"کیف پول کاربر {request.UserId} یافت نشد");
|
||||
}
|
||||
|
||||
// 4. بارگذاری پکیج پایه و محاسبه مبالغ
|
||||
var package = await _context.Packages
|
||||
// 4. بارگذاری پکیج انتخابشده (یا پکیج پایه اگر PackageId=0)
|
||||
Domain.Entities.Package package;
|
||||
if (request.PackageId > 0)
|
||||
{
|
||||
package = await _context.Packages
|
||||
.FirstOrDefaultAsync(p => p.Id == request.PackageId && !p.IsDeleted, cancellationToken)
|
||||
?? throw new NotFoundException($"پکیج با شناسه {request.PackageId} یافت نشد");
|
||||
}
|
||||
else
|
||||
{
|
||||
package = await _context.Packages
|
||||
.FirstOrDefaultAsync(p => p.IsBasePackage && !p.IsDeleted, cancellationToken)
|
||||
?? throw new NotFoundException("پکیج پایه یافت نشد");
|
||||
}
|
||||
|
||||
var balanceAmount = package.Price;
|
||||
var discountBalanceAmount = (long)(package.Price * package.DiscountMultiplier);
|
||||
|
||||
+4
-6
@@ -11,12 +11,10 @@ public class CreateManualPaymentCommandValidator : AbstractValidator<CreateManua
|
||||
.GreaterThan(0)
|
||||
.WithMessage("شناسه کاربر باید بزرگتر از صفر باشد");
|
||||
|
||||
// حصار ایمنی — مبلغ واقعی از پکیج کاربر خوانده میشود
|
||||
RuleFor(x => x.Amount)
|
||||
.GreaterThan(0)
|
||||
.WithMessage("مبلغ باید بزرگتر از صفر باشد")
|
||||
.LessThanOrEqualTo(SystemConstants.WalletMaxSafeAmount)
|
||||
.WithMessage("مبلغ وارد شده بیش از حد مجاز است");
|
||||
// مبلغ از پکیج خوانده میشود — فیلد Amount دیگر اعتبارسنجی نمیشود
|
||||
RuleFor(x => x.PackageId)
|
||||
.GreaterThanOrEqualTo(0)
|
||||
.WithMessage("شناسه پکیج نامعتبر است");
|
||||
|
||||
RuleFor(x => x.Type)
|
||||
.IsInEnum()
|
||||
|
||||
+20
-1
@@ -61,7 +61,9 @@ public class CreateStockMovementCommandHandler : IRequestHandler<CreateStockMove
|
||||
{
|
||||
throw new InvalidOperationException($"Insufficient available stock. Available: {inventoryItem.AvailableQuantity}, Requested: {request.Quantity}");
|
||||
}
|
||||
// رزرو فقط ReservedQuantity رو زیاد میکنه، Quantity فیزیکی تغییر نمیکنه
|
||||
inventoryItem.ReservedQuantity += request.Quantity;
|
||||
// quantityAfter = quantityBefore (بدون تغییر)
|
||||
break;
|
||||
|
||||
case StockMovementType.Released:
|
||||
@@ -69,15 +71,32 @@ public class CreateStockMovementCommandHandler : IRequestHandler<CreateStockMove
|
||||
{
|
||||
throw new InvalidOperationException($"Cannot release more than reserved. Reserved: {inventoryItem.ReservedQuantity}, Requested: {request.Quantity}");
|
||||
}
|
||||
// آزادسازی فقط ReservedQuantity رو کم میکنه، Quantity فیزیکی تغییر نمیکنه
|
||||
inventoryItem.ReservedQuantity -= request.Quantity;
|
||||
// quantityAfter = quantityBefore (بدون تغییر)
|
||||
break;
|
||||
}
|
||||
|
||||
// محاسبه مقدار Quantity برای ذخیره (منفی برای عملیات کاهشی)
|
||||
// تا constraint دیتابیس (QuantityAfter = QuantityBefore + Quantity) برقرار بماند
|
||||
var storedQuantity = request.MovementType switch
|
||||
{
|
||||
StockMovementType.Sale or
|
||||
StockMovementType.AdjustmentMinus or
|
||||
StockMovementType.Lost or
|
||||
StockMovementType.Damaged or
|
||||
StockMovementType.TransferOut => -request.Quantity,
|
||||
// رزرو و آزادسازی تأثیری روی Quantity فیزیکی ندارن
|
||||
StockMovementType.Reserved or
|
||||
StockMovementType.Released => 0,
|
||||
_ => request.Quantity
|
||||
};
|
||||
|
||||
var entity = new StockMovement
|
||||
{
|
||||
InventoryItemId = request.InventoryItemId,
|
||||
MovementType = request.MovementType,
|
||||
Quantity = request.Quantity,
|
||||
Quantity = storedQuantity,
|
||||
QuantityBefore = quantityBefore,
|
||||
QuantityAfter = quantityAfter,
|
||||
Note = request.Note,
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
<TargetFramework>net9.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
<Version>0.0.190</Version>
|
||||
<Version>0.0.192</Version>
|
||||
<DebugType>None</DebugType>
|
||||
<DebugSymbols>False</DebugSymbols>
|
||||
<GeneratePackageOnBuild>False</GeneratePackageOnBuild>
|
||||
|
||||
@@ -72,13 +72,14 @@ enum ManualPaymentStatus
|
||||
message CreateManualPaymentRequest
|
||||
{
|
||||
int64 user_id = 1;
|
||||
int64 amount = 2;
|
||||
int64 amount = 2; // [DEPRECATED] مبلغ از پکیج خوانده میشود — نادیده گرفته میشود
|
||||
ManualPaymentType type = 3;
|
||||
string description = 4;
|
||||
google.protobuf.StringValue reference_number = 5;
|
||||
google.protobuf.StringValue image_path = 6;
|
||||
google.protobuf.Int64Value image_document_id = 7;
|
||||
FileUploadModel image_file = 8;
|
||||
int64 package_id = 9; // شناسه پکیج — اگر 0 باشد، پکیج پایه استفاده میشود
|
||||
}
|
||||
|
||||
message CreateManualPaymentResponse
|
||||
|
||||
Reference in New Issue
Block a user