srs/lib/core/models.dart
Aswin Biju 45fcdcebfb Initial commit
Add SRS app: results, shops, orders and users, wired to srs-api
Flutter web/PWA for the lottery agency, built on Riverpod and go_router,
talking to srs-api over HTTP (HttpApi, token kept in SharedPreferences).

- Results, Shops, Orders and Users tabs (Users for admins only), with
  phone, tablet and desktop layouts and shared widgets and theme tokens
- Users: list with search and Active/Inactive filters; user detail with
  shops, sign-in details, and an Access card
- WhatsApp results switch on each user (sendResults on
  PATCH /api/users/:id). Shows the WhatsApp logo (Simple Icons, CC0,
  drawn with flutter_svg), green only when results will really go out.
  Can't be turned on without a phone number ("Add a phone number first"
  opens the phone sheet), and is paused while the user is inactive. The
  users list marks people who get results with the logo.
- Each Access switch has a one-line description; the explanation
  paragraphs are gone
- Remove demo mode: the app always uses the real AP
  test/support and is used only by tests.
2026-11-04 23:21:50 +05:30

174 lines
3.8 KiB
Dart

import 'format.dart';
class AppUser {
const AppUser({
required this.id,
required this.username,
required this.isAdmin,
required this.active,
required this.shopIds,
this.phone,
this.sendResults = false,
});
final int id;
final String username;
final String? phone;
final bool isAdmin;
final bool active;
final List<int> shopIds;
final bool sendResults;
bool get getsResults => sendResults && active && phone != null;
}
class Shop {
const Shop({
required this.id,
required this.code,
required this.name,
this.active = true,
});
final int id;
final String code;
final String name;
final bool active;
Shop copyWith({String? name, bool? active}) => Shop(
id: id,
code: code,
name: name ?? this.name,
active: active ?? this.active,
);
}
class DrawInfo {
const DrawInfo({
required this.date,
required this.published,
this.scheme,
this.code,
this.hasWin = false,
});
final DateTime date;
final String? scheme;
final String? code;
final bool published;
final bool hasWin;
String get label => published ? '$scheme · $code' : 'No result yet';
}
class PrizeHit {
const PrizeHit({required this.tier, required this.amount});
final int tier;
final int amount;
}
class EndingQty {
const EndingQty({
required this.ending,
required this.qty,
this.hits = const [],
});
final String ending;
final int qty;
final List<PrizeHit> hits;
bool get wins => hits.isNotEmpty;
int get perTicket => hits.fold(0, (a, h) => a + h.amount);
int get prize => qty * perTicket;
String get tierText => tiersLabel([for (final h in hits) h.tier]);
}
class Order {
const Order({
required this.id,
required this.shop,
required this.drawDate,
required this.time,
required this.isPhoto,
required this.edited,
required this.lines,
this.rawText,
});
final int id;
final Shop shop;
final DateTime drawDate;
final DateTime time;
final bool isPhoto;
final bool edited;
final String? rawText;
final List<EndingQty> lines;
int get tickets => lines.fold(0, (a, l) => a + l.qty);
int get prize => lines.fold(0, (a, l) => a + l.prize);
int get winTickets => lines.where((l) => l.wins).fold(0, (a, l) => a + l.qty);
}
class ShopResult {
const ShopResult({
required this.shop,
required this.tickets,
required this.orders,
required this.winTickets,
required this.prize,
});
final Shop shop;
final int tickets;
final int orders;
final int winTickets;
final int prize;
}
class DrawResults {
const DrawResults({required this.draw, required this.shops});
final DrawInfo draw;
final List<ShopResult> shops;
int get totalPrize => shops.fold(0, (a, s) => a + s.prize);
int get winningTickets => shops.fold(0, (a, s) => a + s.winTickets);
int get totalTickets => shops.fold(0, (a, s) => a + s.tickets);
int get orders => shops.fold(0, (a, s) => a + s.orders);
int get winningShops => shops.where((s) => s.prize > 0).length;
ShopResult? get topShop =>
shops.isNotEmpty && shops.first.prize > 0 ? shops.first : null;
}
class ShopDay {
const ShopDay({
required this.shop,
required this.tickets,
required this.orders,
required this.endings,
});
final Shop shop;
final int tickets;
final int orders;
final int endings;
}
class ShopDetail {
const ShopDetail({
required this.shop,
required this.draw,
required this.endings,
required this.orders,
});
final Shop shop;
final DrawInfo draw;
final List<EndingQty> endings;
final List<Order> orders;
int get tickets => endings.fold(0, (a, e) => a + e.qty);
List<EndingQty> get wins => endings.where((e) => e.wins).toList();
int get winTickets => wins.fold(0, (a, e) => a + e.qty);
int get prize => wins.fold(0, (a, e) => a + e.prize);
}