import 'dart:developer'; import 'package:injectable/injectable.dart'; 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'; import 'package:mnemo_cards_common_backend/mnemo_cards_common_backend.dart'; import 'package:mnemo_cards_common/mnemo_cards_common.dart' hide DateTimeExt; import 'package:mnemo_cards_common/src/utils/utils.dart'; @lazySingleton class DiscountsManager { final AppDatabase _db; final DiscountDao _discountDao; final UserDao _userDao; DiscountsManager(this._db) : _discountDao = _db.discountDao, _userDao = _db.userDao; Future applyDiscount({ required Iterable discounts, required UserModel user, }) async { if (user.id == null) return; // Получаем ID скидок final discountIds = discounts .where((d) => d.id != null) .map((d) => d.id as int) .toList(); if (discountIds.isEmpty) return; // Добавляем скидки пользователю через junction таблицу await _discountDao.grantDiscountsToUser(user.id!, discountIds); } Future changeCampaignStatus(DiscountCampaign campaign) async { final now = DateTime.now(); String? targetStatus; if (campaign.status == 'created' && now.isBetween(campaign.start, campaign.finish)) { targetStatus = 'active'; } else if (campaign.status == 'active') { if (now.isBefore(campaign.start)) { targetStatus = 'created'; } else if (now.isAfter(campaign.finish)) { targetStatus = 'expired'; } } if (targetStatus != null && targetStatus != campaign.status) { await _discountDao.updateCampaignStatus(campaign.id, targetStatus); } } Future applyCampaign(DiscountCampaign campaign) async { final now = DateTime.now(); if (!now.isBetween(campaign.start, campaign.finish)) { if (now.isBefore(campaign.start)) { return DiscountError(DiscountErrorType.notStarted); } return DiscountError(DiscountErrorType.expired); } // TODO: Implement applying campaign to users based on tags // This would require querying UserDatas with matching tags return null; } Future removeDiscount({ required DiscountCampaign campaign, required UserModel user, }) async { 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); } Future getProductDiscount( MnemoCardsProductModel model, UserData userData, ) async { double maxDiscount = 0; // Получаем активные скидки пользователя из 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; } } // Получаем активные кампании 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(), ); for (final campaign in activeCampaigns) { 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; } } } return maxDiscount; } Future> discountCampaignsForUser( UserData userData, { MnemoCardsProductModel? product, }) async { final userTags = userData.tags; return await _discountDao.getActiveCampaignsForUser( userTags: userTags, productType: product?.type.name, productId: product?.id.toString(), ); } Future> discountCampaigns() async { final campaigns = await _discountDao.getAllCampaigns(); final dtos = await Future.wait( campaigns.map((c) async => await c.toDto(_discountDao)), ); return dtos; } Future deleteDiscountCampaign(String stringId) async { try { final id = int.parse(stringId); 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); return null; } catch (e, s) { log('Error when deleting discount campaign', error: e, stackTrace: s); return 'Error $e'; } } Future addDiscount(DiscountCampaignDto dto) async { try { 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); } return true; }); } 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, }