2025-11-16 11:25:27 +00:00
|
|
|
import 'dart:developer';
|
|
|
|
|
|
|
|
|
|
import 'package:injectable/injectable.dart';
|
2025-12-13 14:48:00 +00:00
|
|
|
import 'package:mnemo_cards_backend/database/daos/discount_dao.dart';
|
|
|
|
|
import 'package:mnemo_cards_backend/database/database.dart';
|
|
|
|
|
import 'package:mnemo_cards_backend/discounts/discount_drift_extension.dart';
|
2026-01-08 16:07:56 +00:00
|
|
|
import 'package:mnemo_cards_backend/user/user_repository.dart';
|
2025-11-16 11:25:27 +00:00
|
|
|
import 'package:mnemo_cards_common_backend/mnemo_cards_common_backend.dart';
|
2025-12-13 14:48:00 +00:00
|
|
|
import 'package:mnemo_cards_common/mnemo_cards_common.dart' hide DateTimeExt;
|
|
|
|
|
import 'package:mnemo_cards_common/src/utils/utils.dart';
|
2025-11-16 11:25:27 +00:00
|
|
|
|
|
|
|
|
@lazySingleton
|
|
|
|
|
class DiscountsManager {
|
2025-12-13 14:48:00 +00:00
|
|
|
final AppDatabase _db;
|
|
|
|
|
final DiscountDao _discountDao;
|
2026-01-08 16:07:56 +00:00
|
|
|
final UserRepository _userRepository;
|
2025-12-13 14:48:00 +00:00
|
|
|
|
2026-01-08 16:07:56 +00:00
|
|
|
DiscountsManager(this._db, this._userRepository)
|
|
|
|
|
: _discountDao = _db.discountDao;
|
2025-12-13 14:48:00 +00:00
|
|
|
|
2025-11-16 11:25:27 +00:00
|
|
|
Future<void> applyDiscount({
|
|
|
|
|
required Iterable<DiscountModel> discounts,
|
|
|
|
|
required UserModel user,
|
|
|
|
|
}) async {
|
2025-12-13 14:48:00 +00:00
|
|
|
if (user.id == null) return;
|
2025-12-20 18:26:15 +00:00
|
|
|
|
2025-12-13 14:48:00 +00:00
|
|
|
// Получаем ID скидок
|
|
|
|
|
final discountIds = discounts
|
|
|
|
|
.where((d) => d.id != null)
|
2025-12-13 20:55:50 +00:00
|
|
|
.map((d) => d.id!)
|
2025-12-13 14:48:00 +00:00
|
|
|
.toList();
|
2025-12-20 18:26:15 +00:00
|
|
|
|
2025-12-13 14:48:00 +00:00
|
|
|
if (discountIds.isEmpty) return;
|
2025-12-20 18:26:15 +00:00
|
|
|
|
2025-12-13 14:48:00 +00:00
|
|
|
// Добавляем скидки пользователю через junction таблицу
|
|
|
|
|
await _discountDao.grantDiscountsToUser(user.id!, discountIds);
|
2025-11-16 11:25:27 +00:00
|
|
|
}
|
|
|
|
|
|
2025-12-13 14:48:00 +00:00
|
|
|
Future<void> changeCampaignStatus(DiscountCampaign campaign) async {
|
2025-11-16 11:25:27 +00:00
|
|
|
final now = DateTime.now();
|
2025-12-13 14:48:00 +00:00
|
|
|
String? targetStatus;
|
2025-12-20 18:26:15 +00:00
|
|
|
|
2025-12-13 14:48:00 +00:00
|
|
|
if (campaign.status == 'created' &&
|
2025-12-13 23:35:14 +00:00
|
|
|
now.isBetween(campaign.start.dateTime, campaign.finish.dateTime)) {
|
2025-12-13 14:48:00 +00:00
|
|
|
targetStatus = 'active';
|
|
|
|
|
} else if (campaign.status == 'active') {
|
2025-12-13 23:35:14 +00:00
|
|
|
if (now.isBefore(campaign.start.dateTime)) {
|
2025-12-13 14:48:00 +00:00
|
|
|
targetStatus = 'created';
|
2025-12-13 23:35:14 +00:00
|
|
|
} else if (now.isAfter(campaign.finish.dateTime)) {
|
2025-12-13 14:48:00 +00:00
|
|
|
targetStatus = 'expired';
|
2025-11-16 11:25:27 +00:00
|
|
|
}
|
|
|
|
|
}
|
2025-12-20 18:26:15 +00:00
|
|
|
|
2025-12-13 14:48:00 +00:00
|
|
|
if (targetStatus != null && targetStatus != campaign.status) {
|
|
|
|
|
await _discountDao.updateCampaignStatus(campaign.id, targetStatus);
|
2025-11-16 11:25:27 +00:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-12-13 14:48:00 +00:00
|
|
|
Future<DiscountError?> applyCampaign(DiscountCampaign campaign) async {
|
2025-11-16 11:25:27 +00:00
|
|
|
final now = DateTime.now();
|
2025-12-13 23:35:14 +00:00
|
|
|
if (!now.isBetween(campaign.start.dateTime, campaign.finish.dateTime)) {
|
|
|
|
|
if (now.isBefore(campaign.start.dateTime)) {
|
2025-11-16 11:25:27 +00:00
|
|
|
return DiscountError(DiscountErrorType.notStarted);
|
|
|
|
|
}
|
|
|
|
|
return DiscountError(DiscountErrorType.expired);
|
|
|
|
|
}
|
2025-12-13 14:48:00 +00:00
|
|
|
// TODO: Implement applying campaign to users based on tags
|
|
|
|
|
// This would require querying UserDatas with matching tags
|
2025-11-16 11:25:27 +00:00
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Future<void> removeDiscount({
|
2025-12-13 14:48:00 +00:00
|
|
|
required DiscountCampaign campaign,
|
2025-11-16 11:25:27 +00:00
|
|
|
required UserModel user,
|
|
|
|
|
}) async {
|
2025-12-13 14:48:00 +00:00
|
|
|
if (user.id == null) return;
|
2025-12-20 18:26:15 +00:00
|
|
|
|
2025-12-13 14:48:00 +00:00
|
|
|
// Получаем скидки кампании
|
|
|
|
|
final discounts = await _discountDao.getDiscountsByCampaignId(campaign.id);
|
|
|
|
|
final discountIds = discounts.map((d) => d.id).toList();
|
2025-12-20 18:26:15 +00:00
|
|
|
|
2025-12-13 14:48:00 +00:00
|
|
|
if (discountIds.isEmpty) return;
|
2025-12-20 18:26:15 +00:00
|
|
|
|
2025-12-13 14:48:00 +00:00
|
|
|
// Удаляем скидки пользователя
|
|
|
|
|
await _discountDao.revokeDiscountsFromUser(user.id!, discountIds);
|
2025-11-16 11:25:27 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Future<double> getProductDiscount(
|
|
|
|
|
MnemoCardsProductModel model,
|
2025-12-13 14:48:00 +00:00
|
|
|
UserData userData,
|
2025-11-16 11:25:27 +00:00
|
|
|
) async {
|
|
|
|
|
double maxDiscount = 0;
|
2025-12-20 18:26:15 +00:00
|
|
|
|
2025-12-13 14:48:00 +00:00
|
|
|
// Получаем активные скидки пользователя из junction таблицы
|
|
|
|
|
final userDiscounts = await _discountDao.getUserDiscounts(userData.userId);
|
|
|
|
|
for (final discount in userDiscounts) {
|
|
|
|
|
final discountDto = discount.toDto();
|
|
|
|
|
if (discountDto.discountPercent > maxDiscount &&
|
|
|
|
|
discountDto.products.any((p) => model.equalsDto(p))) {
|
|
|
|
|
maxDiscount = discountDto.discountPercent;
|
2025-11-16 11:25:27 +00:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-12-13 14:48:00 +00:00
|
|
|
// Получаем активные кампании
|
2026-01-08 16:07:56 +00:00
|
|
|
final user = await _userRepository.getUserById(userData.userId);
|
2025-12-13 14:48:00 +00:00
|
|
|
if (user == null) return maxDiscount;
|
2025-12-20 18:26:15 +00:00
|
|
|
|
2026-01-08 16:07:56 +00:00
|
|
|
final userDataModel = await _userRepository.getUserData(userData.userId);
|
2025-12-13 14:48:00 +00:00
|
|
|
final userTags = userDataModel?.tags ?? [];
|
2025-12-20 18:26:15 +00:00
|
|
|
|
2025-12-13 14:48:00 +00:00
|
|
|
final activeCampaigns = await _discountDao.getActiveCampaignsForUser(
|
|
|
|
|
userTags: userTags,
|
|
|
|
|
productType: model.type.name,
|
|
|
|
|
productId: model.id.toString(),
|
|
|
|
|
);
|
2025-12-20 18:26:15 +00:00
|
|
|
|
2025-11-16 11:25:27 +00:00
|
|
|
for (final campaign in activeCampaigns) {
|
2025-12-20 18:26:15 +00:00
|
|
|
final discounts = await _discountDao.getDiscountsByCampaignId(
|
|
|
|
|
campaign.id,
|
|
|
|
|
);
|
2025-12-13 14:48:00 +00:00
|
|
|
for (final discount in discounts) {
|
|
|
|
|
final discountDto = discount.toDto();
|
|
|
|
|
if (discountDto.discountPercent > maxDiscount &&
|
|
|
|
|
discountDto.products.any((p) => model.equalsDto(p))) {
|
|
|
|
|
maxDiscount = discountDto.discountPercent;
|
2025-11-16 11:25:27 +00:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2025-12-20 18:26:15 +00:00
|
|
|
|
2025-11-16 11:25:27 +00:00
|
|
|
return maxDiscount;
|
|
|
|
|
}
|
|
|
|
|
|
2025-12-13 14:48:00 +00:00
|
|
|
Future<List<DiscountCampaign>> discountCampaignsForUser(
|
|
|
|
|
UserData userData, {
|
2025-11-16 11:25:27 +00:00
|
|
|
MnemoCardsProductModel? product,
|
|
|
|
|
}) async {
|
2025-12-13 14:48:00 +00:00
|
|
|
final userTags = userData.tags;
|
|
|
|
|
return await _discountDao.getActiveCampaignsForUser(
|
|
|
|
|
userTags: userTags,
|
|
|
|
|
productType: product?.type.name,
|
|
|
|
|
productId: product?.id.toString(),
|
|
|
|
|
);
|
2025-11-16 11:25:27 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Future<List<DiscountCampaignDto>> discountCampaigns() async {
|
2025-12-13 14:48:00 +00:00
|
|
|
final campaigns = await _discountDao.getAllCampaigns();
|
|
|
|
|
final dtos = await Future.wait(
|
|
|
|
|
campaigns.map((c) async => await c.toDto(_discountDao)),
|
|
|
|
|
);
|
2025-11-16 11:25:27 +00:00
|
|
|
return dtos;
|
|
|
|
|
}
|
|
|
|
|
|
2025-12-13 20:55:50 +00:00
|
|
|
Future<String?> deleteDiscountCampaign(String id) async {
|
2025-11-16 11:25:27 +00:00
|
|
|
try {
|
2025-12-13 14:48:00 +00:00
|
|
|
final campaign = await _discountDao.getCampaignById(id);
|
|
|
|
|
if (campaign == null) {
|
|
|
|
|
return 'Campaign $id not found';
|
|
|
|
|
}
|
|
|
|
|
if (campaign.status != 'disabled') {
|
|
|
|
|
return 'Disable campaign before deleting';
|
|
|
|
|
}
|
|
|
|
|
await _discountDao.deleteCampaign(id);
|
2025-11-16 11:25:27 +00:00
|
|
|
return null;
|
|
|
|
|
} catch (e, s) {
|
|
|
|
|
log('Error when deleting discount campaign', error: e, stackTrace: s);
|
|
|
|
|
return 'Error $e';
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Future<bool> addDiscount(DiscountCampaignDto dto) async {
|
|
|
|
|
try {
|
2025-12-13 14:48:00 +00:00
|
|
|
return await _db.transaction(() async {
|
|
|
|
|
// Создаем кампанию
|
|
|
|
|
final campaignCompanion = dto.toCompanion();
|
|
|
|
|
final campaignId = await _discountDao.createCampaign(campaignCompanion);
|
2025-12-20 18:26:15 +00:00
|
|
|
|
2025-12-13 14:48:00 +00:00
|
|
|
// Создаем скидки
|
|
|
|
|
final discountCompanions = dto.discounts
|
|
|
|
|
.map((discountDto) => discountDto.toCompanion(campaignId))
|
|
|
|
|
.toList();
|
2025-12-20 18:26:15 +00:00
|
|
|
|
2025-12-13 14:48:00 +00:00
|
|
|
for (final discountCompanion in discountCompanions) {
|
|
|
|
|
await _discountDao.createDiscount(discountCompanion);
|
|
|
|
|
}
|
2025-11-16 11:25:27 +00:00
|
|
|
|
2025-12-13 14:48:00 +00:00
|
|
|
return true;
|
|
|
|
|
});
|
2025-11-16 11:25:27 +00:00
|
|
|
} catch (e, s) {
|
|
|
|
|
print('error when adding campaign');
|
|
|
|
|
log('error when adding campaign', error: e, stackTrace: s);
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
class DiscountError {
|
|
|
|
|
final String? message;
|
|
|
|
|
final DiscountErrorType type;
|
|
|
|
|
|
|
|
|
|
DiscountError(this.type, {this.message});
|
|
|
|
|
}
|
|
|
|
|
|
2025-12-20 18:26:15 +00:00
|
|
|
enum DiscountErrorType { notAvailable, notStarted, expired }
|