mnemo_cards/mnemo_cards_backend/lib/discounts/discounts_manager.dart

207 lines
6.5 KiB
Dart
Raw Normal View History

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/daos/user_dao.dart';
import 'package:mnemo_cards_backend/database/database.dart';
import 'package:mnemo_cards_backend/discounts/discount_drift_extension.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;
final UserDao _userDao;
DiscountsManager(this._db)
: _discountDao = _db.discountDao,
_userDao = _db.userDao;
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;
// Получаем 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();
if (discountIds.isEmpty) return;
// Добавляем скидки пользователю через 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;
if (campaign.status == 'created' &&
2025-11-16 11:25:27 +00:00
now.isBetween(campaign.start, campaign.finish)) {
2025-12-13 14:48:00 +00:00
targetStatus = 'active';
} else if (campaign.status == 'active') {
2025-11-16 11:25:27 +00:00
if (now.isBefore(campaign.start)) {
2025-12-13 14:48:00 +00:00
targetStatus = 'created';
2025-11-16 11:25:27 +00:00
} else if (now.isAfter(campaign.finish)) {
2025-12-13 14:48:00 +00:00
targetStatus = 'expired';
2025-11-16 11:25:27 +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();
if (!now.isBetween(campaign.start, campaign.finish)) {
if (now.isBefore(campaign.start)) {
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;
// Получаем скидки кампании
final discounts = await _discountDao.getDiscountsByCampaignId(campaign.id);
final discountIds = discounts.map((d) => d.id).toList();
if (discountIds.isEmpty) return;
// Удаляем скидки пользователя
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-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
// Получаем активные кампании
final user = await _userDao.getUserById(userData.userId);
if (user == null) return maxDiscount;
final userDataModel = await _userDao.getUserData(userData.userId);
final userTags = userDataModel?.tags ?? [];
final activeCampaigns = await _discountDao.getActiveCampaignsForUser(
userTags: userTags,
productType: model.type.name,
productId: model.id.toString(),
);
2025-11-16 11:25:27 +00:00
for (final campaign in activeCampaigns) {
2025-12-13 14:48:00 +00:00
final discounts = await _discountDao.getDiscountsByCampaignId(campaign.id);
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-13 14:48:00 +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);
// Создаем скидки
final discountCompanions = dto.discounts
.map((discountDto) => discountDto.toCompanion(campaignId))
.toList();
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});
}
enum DiscountErrorType {
notAvailable,
notStarted,
expired,
}