mnemo_cards/mnemo_cards_backend/lib/discounts/discounts_manager.dart
2025-11-16 14:25:27 +03:00

204 lines
6.2 KiB
Dart

import 'dart:developer';
import 'package:injectable/injectable.dart';
import 'package:isar/isar.dart';
import 'package:mnemo_cards_backend/discounts/discount_campaign_ext.dart';
import 'package:mnemo_cards_common_backend/mnemo_cards_common_backend.dart';
import 'package:mnemo_cards_common/mnemo_cards_common.dart';
import '../main.dart';
@lazySingleton
class DiscountsManager {
Future<void> applyDiscount({
required Iterable<DiscountModel> discounts,
required UserModel user,
}) async {
await isar.writeTxn(() async {
await user.userData.load();
final userData = user.userData.value!;
userData.activeDiscounts.addAll(discounts);
await user.userData.save();
});
}
Future<void> changeCampaignStatus(DiscountCampaignModel campaign) async {
final now = DateTime.now();
var target = campaign.status;
if (campaign.status == DiscountCampaignModelStatus.created &&
now.isBetween(campaign.start, campaign.finish)) {
target = DiscountCampaignModelStatus.active;
} else if (campaign.status == DiscountCampaignModelStatus.active) {
if (now.isBefore(campaign.start)) {
target = DiscountCampaignModelStatus.created;
} else if (now.isAfter(campaign.finish)) {
target = DiscountCampaignModelStatus.expired;
}
}
if (target != campaign.status) {
await isar.writeTxn(() => isar.discountCampaignModels.put(
campaign.copyWith(status: target),
));
}
}
Future<DiscountError?> applyCampaign(DiscountCampaignModel 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);
}
// final usersData = await isar.txn(
// () => isar.userDataModels
// .filter()
// .optional(
// campaign.tags.isNotEmpty,
// (q) => q.anyOf(
// campaign.tags,
// (q, tag) => q.tagsElementContains(tag),
// ),
// )
// .findAll(),
// );
return null;
}
Future<void> removeDiscount({
required DiscountCampaignModel campaign,
required UserModel user,
}) async {
await isar.writeTxn(() async {
await user.userData.load();
final userData = user.userData.value!;
userData.activeDiscounts.removeAll(campaign.discounts);
await isar.userDataModels.put(userData);
});
}
Future<double> getProductDiscount(
MnemoCardsProductModel model,
UserDataModel userData,
) async {
double maxDiscount = 0;
for (final discount in userData.activeDiscounts) {
if (discount.discountPercent > maxDiscount &&
discount.products.any((p) => p.equalsProduct(model))) {
maxDiscount = discount.discountPercent;
}
}
final activeCampaigns =
await discountCampaignsForUser(userData, product: model);
for (final campaign in activeCampaigns) {
for (final discount in campaign.discounts) {
if (discount.discountPercent > maxDiscount &&
discount.products.any((p) => p.equalsProduct(model))) {
maxDiscount = discount.discountPercent;
}
}
}
return maxDiscount;
}
Future<List<DiscountCampaignModel>> discountCampaignsForUser(
UserDataModel userModel, {
MnemoCardsProductModel? product,
}) async {
final activeCampaigns =
await isar.txn(() async => isar.discountCampaignModels
.filter()
.statusEqualTo(DiscountCampaignModelStatus.active)
.optional(userModel.tags.isEmpty, (q) => q.tagsIsEmpty())
.optional(
product != null,
(q) => q.discounts(
(d) => d.productsElement(
(p) => p.typeEqualTo(product!.type).productIdEqualTo(
product.id,
),
),
),
)
.findAll());
if (userModel.tags.isEmpty) {
return activeCampaigns;
} else {
// todo check tags in query
final userTags = userModel.tags.toSet();
return activeCampaigns
.where((campaign) => campaign.tags.hasIntersection(userTags))
.toList();
}
}
Future<List<DiscountCampaignDto>> discountCampaigns() async {
final models = await isar
.txn(() async => isar.discountCampaignModels.where().findAll());
final dtos = await Future.wait(models.map((m) async => await m.toDto()));
return dtos;
}
Future<String?> deleteDiscountCampaign(String stringId) async {
try {
final id = int.parse(stringId);
isar.writeTxn(() async {
final model = await isar.discountCampaignModels.get(id);
if (model == null) {
return 'Campaign $id not found';
}
if (model.status != DiscountCampaignModelStatus.disabled) {
return 'Disable campaign before deleting';
}
isar.discountCampaignModels.delete(id);
});
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 {
final model = DiscountCampaignModel(
id: dto.id,
start: dto.start,
finish: dto.finish,
status: dto.status.toModel(),
name: dto.name,
tags: dto.tags,
);
final discounts = dto.discounts.map((d) => d.toModel()).toList();
await isar.writeTxn(() async {
await isar.discountModels.putAll(discounts);
await isar.discountCampaignModels.put(model);
await model.discounts.reset();
model.discounts.addAll(discounts);
await model.discounts.save();
});
return true;
} catch (e, s) {
print('error when adding campaign');
log('error when adding campaign', error: e, stackTrace: s);
return false;
}
}
const DiscountsManager();
}
class DiscountError {
final String? message;
final DiscountErrorType type;
DiscountError(this.type, {this.message});
}
enum DiscountErrorType {
notAvailable,
notStarted,
expired,
}