feat: update profit shareing
Build & Deploy iOS to TestFlight / build-and-deploy (push) Canceled after 0s
Build & Deploy iOS to TestFlight / build-and-deploy (push) Canceled after 0s
This commit is contained in:
@@ -0,0 +1,289 @@
|
||||
# Migrasi profit sharing
|
||||
|
||||
4 Oktober 2026
|
||||
|
||||
## Ringkasan
|
||||
|
||||
Ada tiga perubahan di backend:
|
||||
|
||||
1. **Parent category bisa ditandai bukan team.** Kategori punya field baru `is_team`. Parent category dengan `is_team: false` tidak bisa dipilih sebagai team di purchase order dan cash advance, dan tidak ikut laporan profit sharing.
|
||||
2. **Endpoint laporan pindah path.** `/api/v1/analytics/parent-categories` menjadi `/api/v1/analytics/profit-sharing`.
|
||||
3. **Pembagian revenue tinggal dua porsi.** Porsi purchase (60%) dihapus. Revenue sekarang dibagi ke owner (SDL) dan team, dengan porsi team = 100% − fee owner.
|
||||
|
||||
Nomor 2 dan 3 adalah breaking change. Setelah backend baru dirilis, client yang masih memanggil path lama mendapat 404, dan `limit_purchase` serta `percentages.purchase` tidak ada lagi di response. Karena itu client harus diupdate lebih dulu, lihat [Urutan rilis](#database-dan-urutan-rilis).
|
||||
|
||||
Yang perlu bertindak, di dashboard maupun app mobile, mana pun yang punya layarnya:
|
||||
|
||||
- **Form kategori**: tambah toggle team untuk parent category.
|
||||
- **Laporan profit sharing**: ganti path, hapus porsi purchase, tampilkan dua porsi.
|
||||
- **Form purchase order dan cash advance**: picker team tidak perlu diubah, tapi form edit perlu menyesuaikan, lihat [Purchase order dan cash advance](#purchase-order-dan-cash-advance).
|
||||
|
||||
## Kategori: field `is_team`
|
||||
|
||||
### Response
|
||||
|
||||
Semua response kategori membawa `is_team` (boolean, tidak pernah `null`): `POST /api/v1/categories`, `PUT /api/v1/categories/:id`, `GET /api/v1/categories`, dan `GET /api/v1/categories/:id`.
|
||||
|
||||
```json
|
||||
{
|
||||
"id": "<uuid>",
|
||||
"name": "Merchandise",
|
||||
"parent_id": null,
|
||||
"owner_fee_percent": null,
|
||||
"is_team": false
|
||||
}
|
||||
```
|
||||
|
||||
### Request
|
||||
|
||||
`POST /api/v1/categories` dan `PUT /api/v1/categories/:id` menerima `is_team`.
|
||||
|
||||
```json
|
||||
{
|
||||
"name": "Merchandise",
|
||||
"is_team": false
|
||||
}
|
||||
```
|
||||
|
||||
| Request | `is_team` yang dikirim | Hasil |
|
||||
| --- | --- | --- |
|
||||
| Create | tidak dikirim | `true` |
|
||||
| Create | `false` | `false` |
|
||||
| Update | tidak dikirim atau `null` | tidak berubah |
|
||||
| Update | `true` atau `false` | diganti |
|
||||
|
||||
Update yang hanya berisi `is_team` diterima.
|
||||
|
||||
Aturan nilainya:
|
||||
|
||||
- `is_team` hanya berpengaruh di parent category, yaitu kategori dengan `parent_id: null`. Di sub-category nilainya disimpan tapi tidak dipakai, jadi tampilkan toggle hanya untuk parent category.
|
||||
- Semua kategori yang sudah ada sebelum rilis bernilai `true`. Tidak ada yang berubah sampai admin mematikannya.
|
||||
- Flag dibaca saat request, bukan saat transaksi. Kalau parent category dimatikan, penjualannya di periode lampau juga hilang dari laporan profit sharing. Kalau dinyalakan lagi, semuanya muncul kembali.
|
||||
|
||||
### Efek `is_team: false`
|
||||
|
||||
| Endpoint | Efek |
|
||||
| --- | --- |
|
||||
| `GET /api/v1/purchase-orders/teams`, `GET /api/v1/cash-advances/teams` | Kategori tidak muncul. Pusat tetap ada. |
|
||||
| Create dan update purchase order dan cash advance | `team_scope: "category"` dengan `team_category_id` kategori ini ditolak. |
|
||||
| `GET /api/v1/analytics/profit-sharing` | Kategori tidak muncul di `data`, dan revenue-nya tidak dihitung di `budget`. |
|
||||
| `GET /api/v1/analytics/profit-sharing/:parent_category_id` | Ditolak. |
|
||||
|
||||
Data yang sudah ada tidak diubah. Purchase order dan cash advance yang sudah tercatat ke kategori itu tetap menyimpan team-nya. Laporan purchasing (`GET /api/v1/analytics/purchasing`) masih menampilkannya di `team_data`, dan filter `team=<category_id>` tetap bisa dipakai.
|
||||
|
||||
Laporan lain yang tidak menyaring `is_team`, misalnya `GET /api/v1/analytics/categories`, tetap menampilkan penjualan kategori itu.
|
||||
|
||||
## Purchase order dan cash advance
|
||||
|
||||
Picker team sudah mengambil dari `GET /api/v1/purchase-orders/teams` dan `GET /api/v1/cash-advances/teams`, jadi kategori non-team otomatis tidak muncul tanpa perubahan di client.
|
||||
|
||||
Yang perlu diubah ada di form edit. Purchase order atau cash advance lama bisa tercatat ke kategori yang sekarang sudah non-team. Kalau form edit mengirim ulang `team_scope` dan `team_category_id` yang sama, request ditolak, walaupun user tidak mengubah team-nya.
|
||||
|
||||
1. Kirim `team_scope` dan `team_category_id` hanya kalau user mengganti team. Kalau `team_scope` tidak dikirim, team yang tersimpan tidak berubah.
|
||||
2. Kalau team yang tersimpan tidak ada di daftar `/teams`, tampilkan namanya dari field `team` di response apa adanya, dan jangan memilihkan team lain secara otomatis.
|
||||
|
||||
Contoh error saat team ditolak, dengan HTTP status 500:
|
||||
|
||||
```json
|
||||
{
|
||||
"success": false,
|
||||
"data": null,
|
||||
"errors": [
|
||||
{
|
||||
"code": "900",
|
||||
"entity": "purchase_order_service",
|
||||
"cause": "category Merchandise is not a team"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
Untuk cash advance, `entity` bernilai `cash_advance_service`. Cocokkan dengan teks `is not a team` di `cause` kalau perlu menampilkan pesan khusus.
|
||||
|
||||
## Laporan profit sharing
|
||||
|
||||
### Path baru
|
||||
|
||||
| Lama | Baru |
|
||||
| --- | --- |
|
||||
| `GET /api/v1/analytics/parent-categories` | `GET /api/v1/analytics/profit-sharing` |
|
||||
| `GET /api/v1/analytics/parent-categories/:parent_category_id` | `GET /api/v1/analytics/profit-sharing/:parent_category_id` |
|
||||
|
||||
Query parameter dan role tidak berubah:
|
||||
|
||||
- `date_from` dan `date_to` wajib, dengan format `DD-MM-YYYY`, misalnya `28-09-2026`.
|
||||
- `outlet_id` opsional.
|
||||
- Hanya bisa diakses superadmin, admin, manager, owner, dan purchasing.
|
||||
|
||||
Path lama sudah tidak ada dan mengembalikan 404.
|
||||
|
||||
### Pembagian revenue
|
||||
|
||||
Revenue tiap parent category yang team dibagi dua:
|
||||
|
||||
- **SDL (fee owner)** = revenue × `owner_fee_percent` / 100. Default-nya 20%, dan bisa diganti per parent category lewat `owner_fee_percent` di kategori.
|
||||
- **Team** = revenue − SDL.
|
||||
|
||||
Contoh dengan tiga parent category dalam satu minggu:
|
||||
|
||||
| Parent category | `is_team` | Fee owner | Revenue | SDL | Team |
|
||||
| --- | --- | --- | --- | --- | --- |
|
||||
| Food | `true` | 20% (default) | 1.000.000 | 200.000 | 800.000 |
|
||||
| Drink | `true` | 35% | 2.000.000 | 700.000 | 1.300.000 |
|
||||
| Merchandise | `false` | - | 500.000 | tidak dihitung | tidak dihitung |
|
||||
| **Budget minggu ini** | | | **3.000.000** | **900.000** | **2.100.000** |
|
||||
|
||||
### Perubahan field
|
||||
|
||||
| Field | Sebelum | Sesudah |
|
||||
| --- | --- | --- |
|
||||
| `data[]` | semua parent category | hanya parent category team |
|
||||
| `budget.percentages.purchase` | `60` | dihapus |
|
||||
| `budget.percentages.owner` | `20`, atau fee parent itu di endpoint detail | tidak berubah |
|
||||
| `budget.percentages.team` | `20` | `100 − owner`: `80` di list, `100 − fee parent` di detail |
|
||||
| `limit_purchase` di `budget.total`, `budget.weekly[]`, `budget.monthly[]` | 60% revenue | dihapus |
|
||||
| `limit_team` di tempat yang sama | 20% revenue | `revenue − sdl` |
|
||||
| `revenue` dan `sdl` di `budget` | semua parent category | hanya parent category team |
|
||||
|
||||
Di endpoint list, `budget.percentages` selalu berisi default `20` dan `80`, walaupun ada parent dengan fee berbeda. Angka `sdl` dan `limit_team` dihitung per parent dengan fee masing-masing, jadi `limit_team / revenue` bisa tidak persis 80%. Tampilkan angka rupiah dari response, jangan dihitung ulang dari persentase.
|
||||
|
||||
Baris di `data[]` membawa `sdl` tapi tidak membawa porsi team. Kalau porsi team per parent perlu ditampilkan, hitung dari `total_revenue − sdl`.
|
||||
|
||||
Contoh response list, dipotong:
|
||||
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"data": {
|
||||
"date_from": "2026-09-28T00:00:00+07:00",
|
||||
"date_to": "2026-10-04T23:59:59.999999999+07:00",
|
||||
"data": [
|
||||
{
|
||||
"parent_category_id": "<uuid>",
|
||||
"parent_category_name": "Drink",
|
||||
"owner_fee_percent": 35,
|
||||
"sdl": 700000,
|
||||
"total_revenue": 2000000
|
||||
},
|
||||
{
|
||||
"parent_category_id": "<uuid>",
|
||||
"parent_category_name": "Food",
|
||||
"owner_fee_percent": 20,
|
||||
"sdl": 200000,
|
||||
"total_revenue": 1000000
|
||||
}
|
||||
],
|
||||
"budget": {
|
||||
"percentages": { "owner": 20, "team": 80 },
|
||||
"cut_off_from": "2026-09-28T00:00:00+07:00",
|
||||
"cut_off_to": "2026-10-04T23:59:59.999999999+07:00",
|
||||
"total": {
|
||||
"period_start": "2026-09-28T00:00:00+07:00",
|
||||
"period_end": "2026-10-04T23:59:59.999999999+07:00",
|
||||
"revenue": 3000000,
|
||||
"order_count": 4,
|
||||
"sdl": 900000,
|
||||
"limit_team": 2100000
|
||||
},
|
||||
"weekly": [
|
||||
{
|
||||
"period_start": "2026-09-28T00:00:00+07:00",
|
||||
"period_end": "2026-10-04T23:59:59.999999999+07:00",
|
||||
"revenue": 3000000,
|
||||
"order_count": 4,
|
||||
"sdl": 900000,
|
||||
"limit_team": 2100000
|
||||
}
|
||||
],
|
||||
"monthly": [
|
||||
{
|
||||
"month": "2026-09",
|
||||
"week_count": 1,
|
||||
"period_start": "2026-09-28T00:00:00+07:00",
|
||||
"period_end": "2026-10-04T23:59:59.999999999+07:00",
|
||||
"revenue": 3000000,
|
||||
"order_count": 4,
|
||||
"sdl": 900000,
|
||||
"limit_team": 2100000
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"errors": null
|
||||
}
|
||||
```
|
||||
|
||||
Di endpoint detail, `budget` bentuknya sama, tapi `percentages` memakai fee parent itu, misalnya `{ "owner": 35, "team": 65 }` untuk Drink.
|
||||
|
||||
### Detail kategori non-team
|
||||
|
||||
Detail untuk parent category non-team ditolak dengan HTTP status 500:
|
||||
|
||||
```json
|
||||
{
|
||||
"success": false,
|
||||
"data": null,
|
||||
"errors": [
|
||||
{
|
||||
"code": "internal_error",
|
||||
"entity": "AnalyticsHandler::GetParentCategoryAnalyticsDetail",
|
||||
"cause": "failed to get parent category analytics detail: failed to get parent category analytics detail: category Merchandise is not a team"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
Ini bisa terjadi kalau user membuka link lama atau bookmark ke parent yang baru dimatikan. Cocokkan dengan teks `is not a team` di `cause`, lalu arahkan user kembali ke list.
|
||||
|
||||
## Migrasi client
|
||||
|
||||
### Laporan profit sharing
|
||||
|
||||
1. Ganti path ke `/api/v1/analytics/profit-sharing`. Selama backend lama masih jalan, path baru mengembalikan 404. Kalau dapat 404, panggil path lama `/api/v1/analytics/parent-categories`, supaya client baru bisa dirilis sebelum backend.
|
||||
2. Hapus tampilan limit purchase dan persentase purchase. Jangan menganggap `limit_purchase` atau `percentages.purchase` selalu ada.
|
||||
3. Tampilkan dua porsi dengan label "SDL / Fee owner" dan "Team", ambil angkanya dari `sdl` dan `limit_team`.
|
||||
4. Kalau detail ditolak dengan `is not a team`, arahkan kembali ke list.
|
||||
|
||||
Selama fallback ke backend lama, `limit_team` masih berisi 20% revenue. Angkanya baru jadi `revenue − sdl` setelah backend baru dirilis.
|
||||
|
||||
### Form kategori
|
||||
|
||||
1. Tambah toggle `is_team` untuk parent category, misalnya berlabel "Ikut profit sharing (team)". Untuk kategori baru, toggle menyala secara default.
|
||||
2. Saat membuka form edit, isi toggle dari `is_team`. Selama backend lama masih jalan, field ini tidak ada di response, jadi anggap `true`.
|
||||
3. Saat admin mematikan toggle, tampilkan konfirmasi bahwa kategori itu tidak akan muncul di pilihan team dan di laporan profit sharing, termasuk untuk periode lampau.
|
||||
4. Di daftar kategori, beri penanda untuk parent category dengan `is_team: false`.
|
||||
|
||||
Backend lama mengabaikan `is_team` di request, jadi toggle bisa dirilis lebih dulu, tapi belum berpengaruh sampai backend baru jalan. Pengecualiannya update yang hanya berisi `is_team`: backend lama menolaknya dengan error code `303` dan pesan `at least one field must be provided for update`. Selama masa transisi, kirim `is_team` bersama field form lainnya.
|
||||
|
||||
## Database dan urutan rilis
|
||||
|
||||
Migration `000101_add_is_team_to_categories` menambah kolom `categories.is_team` (`BOOLEAN NOT NULL DEFAULT TRUE`). Semua kategori yang ada otomatis bernilai `true`. Migration ini hanya menambah kolom, jadi backend lama tetap jalan normal setelahnya.
|
||||
|
||||
Urutan rilis:
|
||||
|
||||
1. Rilis client baru: path profit sharing dengan fallback ke path lama, tanpa porsi purchase, dan dengan toggle `is_team`. Untuk app mobile, pastikan versi baru sudah dipakai sebagian besar user sebelum langkah 3, karena versi lama yang memanggil `/parent-categories` mendapat 404 setelah itu.
|
||||
2. Jalankan migration `000101`.
|
||||
3. Deploy backend baru.
|
||||
4. Hapus fallback path lama di client.
|
||||
5. Admin mematikan `is_team` di parent category yang bukan team.
|
||||
|
||||
Rollback: deploy backend lama, lalu jalankan down migration yang menghapus kolom `is_team`. Client dengan fallback tetap jalan di backend lama. Nilai `is_team` yang sudah diatur admin hilang saat kolom dihapus.
|
||||
|
||||
## Checklist
|
||||
|
||||
- [ ] Client baru (fallback path, tanpa porsi purchase, toggle `is_team`) dirilis
|
||||
- [ ] Migration `000101` dan backend baru dirilis di staging
|
||||
- [ ] Uji: parent category yang dimatikan hilang dari `/purchase-orders/teams` dan `/cash-advances/teams`
|
||||
- [ ] Uji: purchase order dan cash advance baru dengan kategori itu ditolak, dan edit purchase order lama tanpa mengganti team tetap berhasil
|
||||
- [ ] Uji: kategori itu hilang dari `/analytics/profit-sharing`, dan detailnya ditolak
|
||||
- [ ] Uji: `limit_team = revenue − sdl`, dan `sdl` mengikuti fee masing-masing parent
|
||||
- [ ] Migration `000101` dan backend baru dirilis di production
|
||||
- [ ] Fallback path lama di client dihapus
|
||||
|
||||
## FAQ
|
||||
|
||||
**Kenapa porsi team jadi 100% − fee owner, bukan tetap 20%?** Porsi purchase sudah tidak ada, jadi seluruh revenue dibagi dua. Owner mengambil fee-nya, dan sisanya untuk team. Kalau fee owner sebuah parent dinaikkan, porsi team parent itu turun sebesar yang sama.
|
||||
|
||||
**Apakah sub-category bisa dijadikan non-team sendiri?** Tidak. Team dan profit sharing dihitung per parent category, jadi semua sub-category ikut status parent-nya.
|
||||
|
||||
**Bagaimana dengan kategori top-level yang tidak punya sub-category?** Kategori itu tetap parent category, jadi `is_team` berlaku untuknya.
|
||||
@@ -14,7 +14,12 @@ class ApiPath {
|
||||
static const String purchasingAnalytic = '/api/v1/analytics/purchasing';
|
||||
static const String exclusiveSummaryAnalytic =
|
||||
'/api/v1/analytics/exclusive-summary/period';
|
||||
static const String parentCategoryAnalytic =
|
||||
static const String profitSharingAnalytic =
|
||||
'/api/v1/analytics/profit-sharing';
|
||||
|
||||
/// Path lama profit sharing, hanya untuk fallback selama backend lama masih
|
||||
/// jalan. Hapus setelah backend baru rilis (docs/migrasi-profit-sharing.md).
|
||||
static const String profitSharingAnalyticLegacy =
|
||||
'/api/v1/analytics/parent-categories';
|
||||
|
||||
// Inventory
|
||||
|
||||
@@ -13342,7 +13342,6 @@ abstract class _ProfitSharingBudget implements ProfitSharingBudget {
|
||||
|
||||
/// @nodoc
|
||||
mixin _$ProfitSharingPercentage {
|
||||
double get purchase => throw _privateConstructorUsedError;
|
||||
double get owner => throw _privateConstructorUsedError;
|
||||
double get team => throw _privateConstructorUsedError;
|
||||
|
||||
@@ -13360,7 +13359,7 @@ abstract class $ProfitSharingPercentageCopyWith<$Res> {
|
||||
$Res Function(ProfitSharingPercentage) then,
|
||||
) = _$ProfitSharingPercentageCopyWithImpl<$Res, ProfitSharingPercentage>;
|
||||
@useResult
|
||||
$Res call({double purchase, double owner, double team});
|
||||
$Res call({double owner, double team});
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
@@ -13380,17 +13379,9 @@ class _$ProfitSharingPercentageCopyWithImpl<
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@pragma('vm:prefer-inline')
|
||||
@override
|
||||
$Res call({
|
||||
Object? purchase = null,
|
||||
Object? owner = null,
|
||||
Object? team = null,
|
||||
}) {
|
||||
$Res call({Object? owner = null, Object? team = null}) {
|
||||
return _then(
|
||||
_value.copyWith(
|
||||
purchase: null == purchase
|
||||
? _value.purchase
|
||||
: purchase // ignore: cast_nullable_to_non_nullable
|
||||
as double,
|
||||
owner: null == owner
|
||||
? _value.owner
|
||||
: owner // ignore: cast_nullable_to_non_nullable
|
||||
@@ -13414,7 +13405,7 @@ abstract class _$$ProfitSharingPercentageImplCopyWith<$Res>
|
||||
) = __$$ProfitSharingPercentageImplCopyWithImpl<$Res>;
|
||||
@override
|
||||
@useResult
|
||||
$Res call({double purchase, double owner, double team});
|
||||
$Res call({double owner, double team});
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
@@ -13434,17 +13425,9 @@ class __$$ProfitSharingPercentageImplCopyWithImpl<$Res>
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@pragma('vm:prefer-inline')
|
||||
@override
|
||||
$Res call({
|
||||
Object? purchase = null,
|
||||
Object? owner = null,
|
||||
Object? team = null,
|
||||
}) {
|
||||
$Res call({Object? owner = null, Object? team = null}) {
|
||||
return _then(
|
||||
_$ProfitSharingPercentageImpl(
|
||||
purchase: null == purchase
|
||||
? _value.purchase
|
||||
: purchase // ignore: cast_nullable_to_non_nullable
|
||||
as double,
|
||||
owner: null == owner
|
||||
? _value.owner
|
||||
: owner // ignore: cast_nullable_to_non_nullable
|
||||
@@ -13462,13 +13445,10 @@ class __$$ProfitSharingPercentageImplCopyWithImpl<$Res>
|
||||
|
||||
class _$ProfitSharingPercentageImpl implements _ProfitSharingPercentage {
|
||||
const _$ProfitSharingPercentageImpl({
|
||||
required this.purchase,
|
||||
required this.owner,
|
||||
required this.team,
|
||||
});
|
||||
|
||||
@override
|
||||
final double purchase;
|
||||
@override
|
||||
final double owner;
|
||||
@override
|
||||
@@ -13476,7 +13456,7 @@ class _$ProfitSharingPercentageImpl implements _ProfitSharingPercentage {
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'ProfitSharingPercentage(purchase: $purchase, owner: $owner, team: $team)';
|
||||
return 'ProfitSharingPercentage(owner: $owner, team: $team)';
|
||||
}
|
||||
|
||||
@override
|
||||
@@ -13484,14 +13464,12 @@ class _$ProfitSharingPercentageImpl implements _ProfitSharingPercentage {
|
||||
return identical(this, other) ||
|
||||
(other.runtimeType == runtimeType &&
|
||||
other is _$ProfitSharingPercentageImpl &&
|
||||
(identical(other.purchase, purchase) ||
|
||||
other.purchase == purchase) &&
|
||||
(identical(other.owner, owner) || other.owner == owner) &&
|
||||
(identical(other.team, team) || other.team == team));
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode => Object.hash(runtimeType, purchase, owner, team);
|
||||
int get hashCode => Object.hash(runtimeType, owner, team);
|
||||
|
||||
/// Create a copy of ProfitSharingPercentage
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@@ -13507,13 +13485,10 @@ class _$ProfitSharingPercentageImpl implements _ProfitSharingPercentage {
|
||||
|
||||
abstract class _ProfitSharingPercentage implements ProfitSharingPercentage {
|
||||
const factory _ProfitSharingPercentage({
|
||||
required final double purchase,
|
||||
required final double owner,
|
||||
required final double team,
|
||||
}) = _$ProfitSharingPercentageImpl;
|
||||
|
||||
@override
|
||||
double get purchase;
|
||||
@override
|
||||
double get owner;
|
||||
@override
|
||||
@@ -13533,7 +13508,6 @@ mixin _$ProfitSharingPeriod {
|
||||
DateTime get periodEnd => throw _privateConstructorUsedError;
|
||||
int get revenue => throw _privateConstructorUsedError;
|
||||
int get orderCount => throw _privateConstructorUsedError;
|
||||
int get limitPurchase => throw _privateConstructorUsedError;
|
||||
int get sdl => throw _privateConstructorUsedError;
|
||||
int get limitTeam => throw _privateConstructorUsedError;
|
||||
String get month => throw _privateConstructorUsedError;
|
||||
@@ -13558,7 +13532,6 @@ abstract class $ProfitSharingPeriodCopyWith<$Res> {
|
||||
DateTime periodEnd,
|
||||
int revenue,
|
||||
int orderCount,
|
||||
int limitPurchase,
|
||||
int sdl,
|
||||
int limitTeam,
|
||||
String month,
|
||||
@@ -13585,7 +13558,6 @@ class _$ProfitSharingPeriodCopyWithImpl<$Res, $Val extends ProfitSharingPeriod>
|
||||
Object? periodEnd = null,
|
||||
Object? revenue = null,
|
||||
Object? orderCount = null,
|
||||
Object? limitPurchase = null,
|
||||
Object? sdl = null,
|
||||
Object? limitTeam = null,
|
||||
Object? month = null,
|
||||
@@ -13609,10 +13581,6 @@ class _$ProfitSharingPeriodCopyWithImpl<$Res, $Val extends ProfitSharingPeriod>
|
||||
? _value.orderCount
|
||||
: orderCount // ignore: cast_nullable_to_non_nullable
|
||||
as int,
|
||||
limitPurchase: null == limitPurchase
|
||||
? _value.limitPurchase
|
||||
: limitPurchase // ignore: cast_nullable_to_non_nullable
|
||||
as int,
|
||||
sdl: null == sdl
|
||||
? _value.sdl
|
||||
: sdl // ignore: cast_nullable_to_non_nullable
|
||||
@@ -13649,7 +13617,6 @@ abstract class _$$ProfitSharingPeriodImplCopyWith<$Res>
|
||||
DateTime periodEnd,
|
||||
int revenue,
|
||||
int orderCount,
|
||||
int limitPurchase,
|
||||
int sdl,
|
||||
int limitTeam,
|
||||
String month,
|
||||
@@ -13675,7 +13642,6 @@ class __$$ProfitSharingPeriodImplCopyWithImpl<$Res>
|
||||
Object? periodEnd = null,
|
||||
Object? revenue = null,
|
||||
Object? orderCount = null,
|
||||
Object? limitPurchase = null,
|
||||
Object? sdl = null,
|
||||
Object? limitTeam = null,
|
||||
Object? month = null,
|
||||
@@ -13699,10 +13665,6 @@ class __$$ProfitSharingPeriodImplCopyWithImpl<$Res>
|
||||
? _value.orderCount
|
||||
: orderCount // ignore: cast_nullable_to_non_nullable
|
||||
as int,
|
||||
limitPurchase: null == limitPurchase
|
||||
? _value.limitPurchase
|
||||
: limitPurchase // ignore: cast_nullable_to_non_nullable
|
||||
as int,
|
||||
sdl: null == sdl
|
||||
? _value.sdl
|
||||
: sdl // ignore: cast_nullable_to_non_nullable
|
||||
@@ -13732,7 +13694,6 @@ class _$ProfitSharingPeriodImpl extends _ProfitSharingPeriod {
|
||||
required this.periodEnd,
|
||||
required this.revenue,
|
||||
required this.orderCount,
|
||||
required this.limitPurchase,
|
||||
required this.sdl,
|
||||
required this.limitTeam,
|
||||
this.month = '',
|
||||
@@ -13748,8 +13709,6 @@ class _$ProfitSharingPeriodImpl extends _ProfitSharingPeriod {
|
||||
@override
|
||||
final int orderCount;
|
||||
@override
|
||||
final int limitPurchase;
|
||||
@override
|
||||
final int sdl;
|
||||
@override
|
||||
final int limitTeam;
|
||||
@@ -13762,7 +13721,7 @@ class _$ProfitSharingPeriodImpl extends _ProfitSharingPeriod {
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'ProfitSharingPeriod(periodStart: $periodStart, periodEnd: $periodEnd, revenue: $revenue, orderCount: $orderCount, limitPurchase: $limitPurchase, sdl: $sdl, limitTeam: $limitTeam, month: $month, weekCount: $weekCount)';
|
||||
return 'ProfitSharingPeriod(periodStart: $periodStart, periodEnd: $periodEnd, revenue: $revenue, orderCount: $orderCount, sdl: $sdl, limitTeam: $limitTeam, month: $month, weekCount: $weekCount)';
|
||||
}
|
||||
|
||||
@override
|
||||
@@ -13777,8 +13736,6 @@ class _$ProfitSharingPeriodImpl extends _ProfitSharingPeriod {
|
||||
(identical(other.revenue, revenue) || other.revenue == revenue) &&
|
||||
(identical(other.orderCount, orderCount) ||
|
||||
other.orderCount == orderCount) &&
|
||||
(identical(other.limitPurchase, limitPurchase) ||
|
||||
other.limitPurchase == limitPurchase) &&
|
||||
(identical(other.sdl, sdl) || other.sdl == sdl) &&
|
||||
(identical(other.limitTeam, limitTeam) ||
|
||||
other.limitTeam == limitTeam) &&
|
||||
@@ -13794,7 +13751,6 @@ class _$ProfitSharingPeriodImpl extends _ProfitSharingPeriod {
|
||||
periodEnd,
|
||||
revenue,
|
||||
orderCount,
|
||||
limitPurchase,
|
||||
sdl,
|
||||
limitTeam,
|
||||
month,
|
||||
@@ -13819,7 +13775,6 @@ abstract class _ProfitSharingPeriod extends ProfitSharingPeriod {
|
||||
required final DateTime periodEnd,
|
||||
required final int revenue,
|
||||
required final int orderCount,
|
||||
required final int limitPurchase,
|
||||
required final int sdl,
|
||||
required final int limitTeam,
|
||||
final String month,
|
||||
@@ -13836,8 +13791,6 @@ abstract class _ProfitSharingPeriod extends ProfitSharingPeriod {
|
||||
@override
|
||||
int get orderCount;
|
||||
@override
|
||||
int get limitPurchase;
|
||||
@override
|
||||
int get sdl;
|
||||
@override
|
||||
int get limitTeam;
|
||||
@@ -15050,6 +15003,7 @@ mixin _$AnalyticFailure {
|
||||
required TResult Function(ApiFailure failure) serverError,
|
||||
required TResult Function() unexpectedError,
|
||||
required TResult Function() empty,
|
||||
required TResult Function() notTeam,
|
||||
required TResult Function(String erroMessage) dynamicErrorMessage,
|
||||
}) => throw _privateConstructorUsedError;
|
||||
@optionalTypeArgs
|
||||
@@ -15057,6 +15011,7 @@ mixin _$AnalyticFailure {
|
||||
TResult? Function(ApiFailure failure)? serverError,
|
||||
TResult? Function()? unexpectedError,
|
||||
TResult? Function()? empty,
|
||||
TResult? Function()? notTeam,
|
||||
TResult? Function(String erroMessage)? dynamicErrorMessage,
|
||||
}) => throw _privateConstructorUsedError;
|
||||
@optionalTypeArgs
|
||||
@@ -15064,6 +15019,7 @@ mixin _$AnalyticFailure {
|
||||
TResult Function(ApiFailure failure)? serverError,
|
||||
TResult Function()? unexpectedError,
|
||||
TResult Function()? empty,
|
||||
TResult Function()? notTeam,
|
||||
TResult Function(String erroMessage)? dynamicErrorMessage,
|
||||
required TResult orElse(),
|
||||
}) => throw _privateConstructorUsedError;
|
||||
@@ -15072,6 +15028,7 @@ mixin _$AnalyticFailure {
|
||||
required TResult Function(_ServerError value) serverError,
|
||||
required TResult Function(_UnexpectedError value) unexpectedError,
|
||||
required TResult Function(_Empty value) empty,
|
||||
required TResult Function(_NotTeam value) notTeam,
|
||||
required TResult Function(_DynamicErrorMessage value) dynamicErrorMessage,
|
||||
}) => throw _privateConstructorUsedError;
|
||||
@optionalTypeArgs
|
||||
@@ -15079,6 +15036,7 @@ mixin _$AnalyticFailure {
|
||||
TResult? Function(_ServerError value)? serverError,
|
||||
TResult? Function(_UnexpectedError value)? unexpectedError,
|
||||
TResult? Function(_Empty value)? empty,
|
||||
TResult? Function(_NotTeam value)? notTeam,
|
||||
TResult? Function(_DynamicErrorMessage value)? dynamicErrorMessage,
|
||||
}) => throw _privateConstructorUsedError;
|
||||
@optionalTypeArgs
|
||||
@@ -15086,6 +15044,7 @@ mixin _$AnalyticFailure {
|
||||
TResult Function(_ServerError value)? serverError,
|
||||
TResult Function(_UnexpectedError value)? unexpectedError,
|
||||
TResult Function(_Empty value)? empty,
|
||||
TResult Function(_NotTeam value)? notTeam,
|
||||
TResult Function(_DynamicErrorMessage value)? dynamicErrorMessage,
|
||||
required TResult orElse(),
|
||||
}) => throw _privateConstructorUsedError;
|
||||
@@ -15198,6 +15157,7 @@ class _$ServerErrorImpl implements _ServerError {
|
||||
required TResult Function(ApiFailure failure) serverError,
|
||||
required TResult Function() unexpectedError,
|
||||
required TResult Function() empty,
|
||||
required TResult Function() notTeam,
|
||||
required TResult Function(String erroMessage) dynamicErrorMessage,
|
||||
}) {
|
||||
return serverError(failure);
|
||||
@@ -15209,6 +15169,7 @@ class _$ServerErrorImpl implements _ServerError {
|
||||
TResult? Function(ApiFailure failure)? serverError,
|
||||
TResult? Function()? unexpectedError,
|
||||
TResult? Function()? empty,
|
||||
TResult? Function()? notTeam,
|
||||
TResult? Function(String erroMessage)? dynamicErrorMessage,
|
||||
}) {
|
||||
return serverError?.call(failure);
|
||||
@@ -15220,6 +15181,7 @@ class _$ServerErrorImpl implements _ServerError {
|
||||
TResult Function(ApiFailure failure)? serverError,
|
||||
TResult Function()? unexpectedError,
|
||||
TResult Function()? empty,
|
||||
TResult Function()? notTeam,
|
||||
TResult Function(String erroMessage)? dynamicErrorMessage,
|
||||
required TResult orElse(),
|
||||
}) {
|
||||
@@ -15235,6 +15197,7 @@ class _$ServerErrorImpl implements _ServerError {
|
||||
required TResult Function(_ServerError value) serverError,
|
||||
required TResult Function(_UnexpectedError value) unexpectedError,
|
||||
required TResult Function(_Empty value) empty,
|
||||
required TResult Function(_NotTeam value) notTeam,
|
||||
required TResult Function(_DynamicErrorMessage value) dynamicErrorMessage,
|
||||
}) {
|
||||
return serverError(this);
|
||||
@@ -15246,6 +15209,7 @@ class _$ServerErrorImpl implements _ServerError {
|
||||
TResult? Function(_ServerError value)? serverError,
|
||||
TResult? Function(_UnexpectedError value)? unexpectedError,
|
||||
TResult? Function(_Empty value)? empty,
|
||||
TResult? Function(_NotTeam value)? notTeam,
|
||||
TResult? Function(_DynamicErrorMessage value)? dynamicErrorMessage,
|
||||
}) {
|
||||
return serverError?.call(this);
|
||||
@@ -15257,6 +15221,7 @@ class _$ServerErrorImpl implements _ServerError {
|
||||
TResult Function(_ServerError value)? serverError,
|
||||
TResult Function(_UnexpectedError value)? unexpectedError,
|
||||
TResult Function(_Empty value)? empty,
|
||||
TResult Function(_NotTeam value)? notTeam,
|
||||
TResult Function(_DynamicErrorMessage value)? dynamicErrorMessage,
|
||||
required TResult orElse(),
|
||||
}) {
|
||||
@@ -15325,6 +15290,7 @@ class _$UnexpectedErrorImpl implements _UnexpectedError {
|
||||
required TResult Function(ApiFailure failure) serverError,
|
||||
required TResult Function() unexpectedError,
|
||||
required TResult Function() empty,
|
||||
required TResult Function() notTeam,
|
||||
required TResult Function(String erroMessage) dynamicErrorMessage,
|
||||
}) {
|
||||
return unexpectedError();
|
||||
@@ -15336,6 +15302,7 @@ class _$UnexpectedErrorImpl implements _UnexpectedError {
|
||||
TResult? Function(ApiFailure failure)? serverError,
|
||||
TResult? Function()? unexpectedError,
|
||||
TResult? Function()? empty,
|
||||
TResult? Function()? notTeam,
|
||||
TResult? Function(String erroMessage)? dynamicErrorMessage,
|
||||
}) {
|
||||
return unexpectedError?.call();
|
||||
@@ -15347,6 +15314,7 @@ class _$UnexpectedErrorImpl implements _UnexpectedError {
|
||||
TResult Function(ApiFailure failure)? serverError,
|
||||
TResult Function()? unexpectedError,
|
||||
TResult Function()? empty,
|
||||
TResult Function()? notTeam,
|
||||
TResult Function(String erroMessage)? dynamicErrorMessage,
|
||||
required TResult orElse(),
|
||||
}) {
|
||||
@@ -15362,6 +15330,7 @@ class _$UnexpectedErrorImpl implements _UnexpectedError {
|
||||
required TResult Function(_ServerError value) serverError,
|
||||
required TResult Function(_UnexpectedError value) unexpectedError,
|
||||
required TResult Function(_Empty value) empty,
|
||||
required TResult Function(_NotTeam value) notTeam,
|
||||
required TResult Function(_DynamicErrorMessage value) dynamicErrorMessage,
|
||||
}) {
|
||||
return unexpectedError(this);
|
||||
@@ -15373,6 +15342,7 @@ class _$UnexpectedErrorImpl implements _UnexpectedError {
|
||||
TResult? Function(_ServerError value)? serverError,
|
||||
TResult? Function(_UnexpectedError value)? unexpectedError,
|
||||
TResult? Function(_Empty value)? empty,
|
||||
TResult? Function(_NotTeam value)? notTeam,
|
||||
TResult? Function(_DynamicErrorMessage value)? dynamicErrorMessage,
|
||||
}) {
|
||||
return unexpectedError?.call(this);
|
||||
@@ -15384,6 +15354,7 @@ class _$UnexpectedErrorImpl implements _UnexpectedError {
|
||||
TResult Function(_ServerError value)? serverError,
|
||||
TResult Function(_UnexpectedError value)? unexpectedError,
|
||||
TResult Function(_Empty value)? empty,
|
||||
TResult Function(_NotTeam value)? notTeam,
|
||||
TResult Function(_DynamicErrorMessage value)? dynamicErrorMessage,
|
||||
required TResult orElse(),
|
||||
}) {
|
||||
@@ -15444,6 +15415,7 @@ class _$EmptyImpl implements _Empty {
|
||||
required TResult Function(ApiFailure failure) serverError,
|
||||
required TResult Function() unexpectedError,
|
||||
required TResult Function() empty,
|
||||
required TResult Function() notTeam,
|
||||
required TResult Function(String erroMessage) dynamicErrorMessage,
|
||||
}) {
|
||||
return empty();
|
||||
@@ -15455,6 +15427,7 @@ class _$EmptyImpl implements _Empty {
|
||||
TResult? Function(ApiFailure failure)? serverError,
|
||||
TResult? Function()? unexpectedError,
|
||||
TResult? Function()? empty,
|
||||
TResult? Function()? notTeam,
|
||||
TResult? Function(String erroMessage)? dynamicErrorMessage,
|
||||
}) {
|
||||
return empty?.call();
|
||||
@@ -15466,6 +15439,7 @@ class _$EmptyImpl implements _Empty {
|
||||
TResult Function(ApiFailure failure)? serverError,
|
||||
TResult Function()? unexpectedError,
|
||||
TResult Function()? empty,
|
||||
TResult Function()? notTeam,
|
||||
TResult Function(String erroMessage)? dynamicErrorMessage,
|
||||
required TResult orElse(),
|
||||
}) {
|
||||
@@ -15481,6 +15455,7 @@ class _$EmptyImpl implements _Empty {
|
||||
required TResult Function(_ServerError value) serverError,
|
||||
required TResult Function(_UnexpectedError value) unexpectedError,
|
||||
required TResult Function(_Empty value) empty,
|
||||
required TResult Function(_NotTeam value) notTeam,
|
||||
required TResult Function(_DynamicErrorMessage value) dynamicErrorMessage,
|
||||
}) {
|
||||
return empty(this);
|
||||
@@ -15492,6 +15467,7 @@ class _$EmptyImpl implements _Empty {
|
||||
TResult? Function(_ServerError value)? serverError,
|
||||
TResult? Function(_UnexpectedError value)? unexpectedError,
|
||||
TResult? Function(_Empty value)? empty,
|
||||
TResult? Function(_NotTeam value)? notTeam,
|
||||
TResult? Function(_DynamicErrorMessage value)? dynamicErrorMessage,
|
||||
}) {
|
||||
return empty?.call(this);
|
||||
@@ -15503,6 +15479,7 @@ class _$EmptyImpl implements _Empty {
|
||||
TResult Function(_ServerError value)? serverError,
|
||||
TResult Function(_UnexpectedError value)? unexpectedError,
|
||||
TResult Function(_Empty value)? empty,
|
||||
TResult Function(_NotTeam value)? notTeam,
|
||||
TResult Function(_DynamicErrorMessage value)? dynamicErrorMessage,
|
||||
required TResult orElse(),
|
||||
}) {
|
||||
@@ -15517,6 +15494,131 @@ abstract class _Empty implements AnalyticFailure {
|
||||
const factory _Empty() = _$EmptyImpl;
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
abstract class _$$NotTeamImplCopyWith<$Res> {
|
||||
factory _$$NotTeamImplCopyWith(
|
||||
_$NotTeamImpl value,
|
||||
$Res Function(_$NotTeamImpl) then,
|
||||
) = __$$NotTeamImplCopyWithImpl<$Res>;
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
class __$$NotTeamImplCopyWithImpl<$Res>
|
||||
extends _$AnalyticFailureCopyWithImpl<$Res, _$NotTeamImpl>
|
||||
implements _$$NotTeamImplCopyWith<$Res> {
|
||||
__$$NotTeamImplCopyWithImpl(
|
||||
_$NotTeamImpl _value,
|
||||
$Res Function(_$NotTeamImpl) _then,
|
||||
) : super(_value, _then);
|
||||
|
||||
/// Create a copy of AnalyticFailure
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
|
||||
class _$NotTeamImpl implements _NotTeam {
|
||||
const _$NotTeamImpl();
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'AnalyticFailure.notTeam()';
|
||||
}
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
return identical(this, other) ||
|
||||
(other.runtimeType == runtimeType && other is _$NotTeamImpl);
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode => runtimeType.hashCode;
|
||||
|
||||
@override
|
||||
@optionalTypeArgs
|
||||
TResult when<TResult extends Object?>({
|
||||
required TResult Function(ApiFailure failure) serverError,
|
||||
required TResult Function() unexpectedError,
|
||||
required TResult Function() empty,
|
||||
required TResult Function() notTeam,
|
||||
required TResult Function(String erroMessage) dynamicErrorMessage,
|
||||
}) {
|
||||
return notTeam();
|
||||
}
|
||||
|
||||
@override
|
||||
@optionalTypeArgs
|
||||
TResult? whenOrNull<TResult extends Object?>({
|
||||
TResult? Function(ApiFailure failure)? serverError,
|
||||
TResult? Function()? unexpectedError,
|
||||
TResult? Function()? empty,
|
||||
TResult? Function()? notTeam,
|
||||
TResult? Function(String erroMessage)? dynamicErrorMessage,
|
||||
}) {
|
||||
return notTeam?.call();
|
||||
}
|
||||
|
||||
@override
|
||||
@optionalTypeArgs
|
||||
TResult maybeWhen<TResult extends Object?>({
|
||||
TResult Function(ApiFailure failure)? serverError,
|
||||
TResult Function()? unexpectedError,
|
||||
TResult Function()? empty,
|
||||
TResult Function()? notTeam,
|
||||
TResult Function(String erroMessage)? dynamicErrorMessage,
|
||||
required TResult orElse(),
|
||||
}) {
|
||||
if (notTeam != null) {
|
||||
return notTeam();
|
||||
}
|
||||
return orElse();
|
||||
}
|
||||
|
||||
@override
|
||||
@optionalTypeArgs
|
||||
TResult map<TResult extends Object?>({
|
||||
required TResult Function(_ServerError value) serverError,
|
||||
required TResult Function(_UnexpectedError value) unexpectedError,
|
||||
required TResult Function(_Empty value) empty,
|
||||
required TResult Function(_NotTeam value) notTeam,
|
||||
required TResult Function(_DynamicErrorMessage value) dynamicErrorMessage,
|
||||
}) {
|
||||
return notTeam(this);
|
||||
}
|
||||
|
||||
@override
|
||||
@optionalTypeArgs
|
||||
TResult? mapOrNull<TResult extends Object?>({
|
||||
TResult? Function(_ServerError value)? serverError,
|
||||
TResult? Function(_UnexpectedError value)? unexpectedError,
|
||||
TResult? Function(_Empty value)? empty,
|
||||
TResult? Function(_NotTeam value)? notTeam,
|
||||
TResult? Function(_DynamicErrorMessage value)? dynamicErrorMessage,
|
||||
}) {
|
||||
return notTeam?.call(this);
|
||||
}
|
||||
|
||||
@override
|
||||
@optionalTypeArgs
|
||||
TResult maybeMap<TResult extends Object?>({
|
||||
TResult Function(_ServerError value)? serverError,
|
||||
TResult Function(_UnexpectedError value)? unexpectedError,
|
||||
TResult Function(_Empty value)? empty,
|
||||
TResult Function(_NotTeam value)? notTeam,
|
||||
TResult Function(_DynamicErrorMessage value)? dynamicErrorMessage,
|
||||
required TResult orElse(),
|
||||
}) {
|
||||
if (notTeam != null) {
|
||||
return notTeam(this);
|
||||
}
|
||||
return orElse();
|
||||
}
|
||||
}
|
||||
|
||||
abstract class _NotTeam implements AnalyticFailure {
|
||||
const factory _NotTeam() = _$NotTeamImpl;
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
abstract class _$$DynamicErrorMessageImplCopyWith<$Res> {
|
||||
factory _$$DynamicErrorMessageImplCopyWith(
|
||||
@@ -15594,6 +15696,7 @@ class _$DynamicErrorMessageImpl implements _DynamicErrorMessage {
|
||||
required TResult Function(ApiFailure failure) serverError,
|
||||
required TResult Function() unexpectedError,
|
||||
required TResult Function() empty,
|
||||
required TResult Function() notTeam,
|
||||
required TResult Function(String erroMessage) dynamicErrorMessage,
|
||||
}) {
|
||||
return dynamicErrorMessage(erroMessage);
|
||||
@@ -15605,6 +15708,7 @@ class _$DynamicErrorMessageImpl implements _DynamicErrorMessage {
|
||||
TResult? Function(ApiFailure failure)? serverError,
|
||||
TResult? Function()? unexpectedError,
|
||||
TResult? Function()? empty,
|
||||
TResult? Function()? notTeam,
|
||||
TResult? Function(String erroMessage)? dynamicErrorMessage,
|
||||
}) {
|
||||
return dynamicErrorMessage?.call(erroMessage);
|
||||
@@ -15616,6 +15720,7 @@ class _$DynamicErrorMessageImpl implements _DynamicErrorMessage {
|
||||
TResult Function(ApiFailure failure)? serverError,
|
||||
TResult Function()? unexpectedError,
|
||||
TResult Function()? empty,
|
||||
TResult Function()? notTeam,
|
||||
TResult Function(String erroMessage)? dynamicErrorMessage,
|
||||
required TResult orElse(),
|
||||
}) {
|
||||
@@ -15631,6 +15736,7 @@ class _$DynamicErrorMessageImpl implements _DynamicErrorMessage {
|
||||
required TResult Function(_ServerError value) serverError,
|
||||
required TResult Function(_UnexpectedError value) unexpectedError,
|
||||
required TResult Function(_Empty value) empty,
|
||||
required TResult Function(_NotTeam value) notTeam,
|
||||
required TResult Function(_DynamicErrorMessage value) dynamicErrorMessage,
|
||||
}) {
|
||||
return dynamicErrorMessage(this);
|
||||
@@ -15642,6 +15748,7 @@ class _$DynamicErrorMessageImpl implements _DynamicErrorMessage {
|
||||
TResult? Function(_ServerError value)? serverError,
|
||||
TResult? Function(_UnexpectedError value)? unexpectedError,
|
||||
TResult? Function(_Empty value)? empty,
|
||||
TResult? Function(_NotTeam value)? notTeam,
|
||||
TResult? Function(_DynamicErrorMessage value)? dynamicErrorMessage,
|
||||
}) {
|
||||
return dynamicErrorMessage?.call(this);
|
||||
@@ -15653,6 +15760,7 @@ class _$DynamicErrorMessageImpl implements _DynamicErrorMessage {
|
||||
TResult Function(_ServerError value)? serverError,
|
||||
TResult Function(_UnexpectedError value)? unexpectedError,
|
||||
TResult Function(_Empty value)? empty,
|
||||
TResult Function(_NotTeam value)? notTeam,
|
||||
TResult Function(_DynamicErrorMessage value)? dynamicErrorMessage,
|
||||
required TResult orElse(),
|
||||
}) {
|
||||
|
||||
@@ -59,6 +59,13 @@ class ProfitSharingCategory with _$ProfitSharingCategory {
|
||||
|
||||
bool get hasTopProduct => topProduct.productId.isNotEmpty;
|
||||
|
||||
/// Porsi tim = omzet − SDL. Response tidak membawa porsi tim per kategori,
|
||||
/// jadi dihitung dari nominalnya (docs/migrasi-profit-sharing.md).
|
||||
int get teamShare => totalRevenue - sdl;
|
||||
|
||||
/// % porsi tim terhadap omzet, yaitu 100 − fee owner.
|
||||
double get teamPercent => 100 - ownerFeePercent;
|
||||
|
||||
/// Laba kotor memakai HPP standar (acuan yang dipakai di laporan laba rugi).
|
||||
int get grossProfit => totalRevenue - totalStandardHpp;
|
||||
|
||||
@@ -106,16 +113,16 @@ class ProfitSharingBudget with _$ProfitSharingBudget {
|
||||
);
|
||||
}
|
||||
|
||||
/// Porsi bawaan bagi hasil: owner (SDL) dan tim, dengan tim = 100 − owner.
|
||||
@freezed
|
||||
class ProfitSharingPercentage with _$ProfitSharingPercentage {
|
||||
const factory ProfitSharingPercentage({
|
||||
required double purchase,
|
||||
required double owner,
|
||||
required double team,
|
||||
}) = _ProfitSharingPercentage;
|
||||
|
||||
factory ProfitSharingPercentage.empty() =>
|
||||
const ProfitSharingPercentage(purchase: 0, owner: 0, team: 0);
|
||||
const ProfitSharingPercentage(owner: 0, team: 0);
|
||||
}
|
||||
|
||||
/// Satu baris periode bagi hasil. Dipakai untuk total, mingguan, dan bulanan.
|
||||
@@ -129,7 +136,6 @@ class ProfitSharingPeriod with _$ProfitSharingPeriod {
|
||||
required DateTime periodEnd,
|
||||
required int revenue,
|
||||
required int orderCount,
|
||||
required int limitPurchase,
|
||||
required int sdl,
|
||||
required int limitTeam,
|
||||
@Default('') String month,
|
||||
@@ -141,7 +147,6 @@ class ProfitSharingPeriod with _$ProfitSharingPeriod {
|
||||
periodEnd: DateTime.fromMillisecondsSinceEpoch(0),
|
||||
revenue: 0,
|
||||
orderCount: 0,
|
||||
limitPurchase: 0,
|
||||
sdl: 0,
|
||||
limitTeam: 0,
|
||||
);
|
||||
@@ -152,6 +157,9 @@ class ProfitSharingPeriod with _$ProfitSharingPeriod {
|
||||
/// induk, jadi porsinya dihitung dari nominal, bukan dari persentase tetap.
|
||||
double get sdlPercentage => revenue == 0 ? 0 : (sdl / revenue) * 100;
|
||||
|
||||
/// % porsi tim terhadap omzet, dihitung dari nominal dengan alasan yang sama.
|
||||
double get teamPercentage => revenue == 0 ? 0 : (limitTeam / revenue) * 100;
|
||||
|
||||
int get averageOrderValue => orderCount == 0 ? 0 : revenue ~/ orderCount;
|
||||
}
|
||||
|
||||
|
||||
@@ -5,6 +5,9 @@ sealed class AnalyticFailure with _$AnalyticFailure {
|
||||
const factory AnalyticFailure.serverError(ApiFailure failure) = _ServerError;
|
||||
const factory AnalyticFailure.unexpectedError() = _UnexpectedError;
|
||||
const factory AnalyticFailure.empty() = _Empty;
|
||||
|
||||
/// Detail profit sharing ditolak karena kategori induknya sudah non-team.
|
||||
const factory AnalyticFailure.notTeam() = _NotTeam;
|
||||
const factory AnalyticFailure.dynamicErrorMessage(String erroMessage) =
|
||||
_DynamicErrorMessage;
|
||||
}
|
||||
|
||||
@@ -15656,8 +15656,6 @@ ProfitSharingPercentageDto _$ProfitSharingPercentageDtoFromJson(
|
||||
|
||||
/// @nodoc
|
||||
mixin _$ProfitSharingPercentageDto {
|
||||
@JsonKey(name: 'purchase')
|
||||
num? get purchase => throw _privateConstructorUsedError;
|
||||
@JsonKey(name: 'owner')
|
||||
num? get owner => throw _privateConstructorUsedError;
|
||||
@JsonKey(name: 'team')
|
||||
@@ -15685,7 +15683,6 @@ abstract class $ProfitSharingPercentageDtoCopyWith<$Res> {
|
||||
>;
|
||||
@useResult
|
||||
$Res call({
|
||||
@JsonKey(name: 'purchase') num? purchase,
|
||||
@JsonKey(name: 'owner') num? owner,
|
||||
@JsonKey(name: 'team') num? team,
|
||||
});
|
||||
@@ -15708,17 +15705,9 @@ class _$ProfitSharingPercentageDtoCopyWithImpl<
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@pragma('vm:prefer-inline')
|
||||
@override
|
||||
$Res call({
|
||||
Object? purchase = freezed,
|
||||
Object? owner = freezed,
|
||||
Object? team = freezed,
|
||||
}) {
|
||||
$Res call({Object? owner = freezed, Object? team = freezed}) {
|
||||
return _then(
|
||||
_value.copyWith(
|
||||
purchase: freezed == purchase
|
||||
? _value.purchase
|
||||
: purchase // ignore: cast_nullable_to_non_nullable
|
||||
as num?,
|
||||
owner: freezed == owner
|
||||
? _value.owner
|
||||
: owner // ignore: cast_nullable_to_non_nullable
|
||||
@@ -15743,7 +15732,6 @@ abstract class _$$ProfitSharingPercentageDtoImplCopyWith<$Res>
|
||||
@override
|
||||
@useResult
|
||||
$Res call({
|
||||
@JsonKey(name: 'purchase') num? purchase,
|
||||
@JsonKey(name: 'owner') num? owner,
|
||||
@JsonKey(name: 'team') num? team,
|
||||
});
|
||||
@@ -15766,17 +15754,9 @@ class __$$ProfitSharingPercentageDtoImplCopyWithImpl<$Res>
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@pragma('vm:prefer-inline')
|
||||
@override
|
||||
$Res call({
|
||||
Object? purchase = freezed,
|
||||
Object? owner = freezed,
|
||||
Object? team = freezed,
|
||||
}) {
|
||||
$Res call({Object? owner = freezed, Object? team = freezed}) {
|
||||
return _then(
|
||||
_$ProfitSharingPercentageDtoImpl(
|
||||
purchase: freezed == purchase
|
||||
? _value.purchase
|
||||
: purchase // ignore: cast_nullable_to_non_nullable
|
||||
as num?,
|
||||
owner: freezed == owner
|
||||
? _value.owner
|
||||
: owner // ignore: cast_nullable_to_non_nullable
|
||||
@@ -15794,7 +15774,6 @@ class __$$ProfitSharingPercentageDtoImplCopyWithImpl<$Res>
|
||||
@JsonSerializable()
|
||||
class _$ProfitSharingPercentageDtoImpl extends _ProfitSharingPercentageDto {
|
||||
const _$ProfitSharingPercentageDtoImpl({
|
||||
@JsonKey(name: 'purchase') this.purchase,
|
||||
@JsonKey(name: 'owner') this.owner,
|
||||
@JsonKey(name: 'team') this.team,
|
||||
}) : super._();
|
||||
@@ -15803,9 +15782,6 @@ class _$ProfitSharingPercentageDtoImpl extends _ProfitSharingPercentageDto {
|
||||
Map<String, dynamic> json,
|
||||
) => _$$ProfitSharingPercentageDtoImplFromJson(json);
|
||||
|
||||
@override
|
||||
@JsonKey(name: 'purchase')
|
||||
final num? purchase;
|
||||
@override
|
||||
@JsonKey(name: 'owner')
|
||||
final num? owner;
|
||||
@@ -15815,7 +15791,7 @@ class _$ProfitSharingPercentageDtoImpl extends _ProfitSharingPercentageDto {
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'ProfitSharingPercentageDto(purchase: $purchase, owner: $owner, team: $team)';
|
||||
return 'ProfitSharingPercentageDto(owner: $owner, team: $team)';
|
||||
}
|
||||
|
||||
@override
|
||||
@@ -15823,15 +15799,13 @@ class _$ProfitSharingPercentageDtoImpl extends _ProfitSharingPercentageDto {
|
||||
return identical(this, other) ||
|
||||
(other.runtimeType == runtimeType &&
|
||||
other is _$ProfitSharingPercentageDtoImpl &&
|
||||
(identical(other.purchase, purchase) ||
|
||||
other.purchase == purchase) &&
|
||||
(identical(other.owner, owner) || other.owner == owner) &&
|
||||
(identical(other.team, team) || other.team == team));
|
||||
}
|
||||
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
@override
|
||||
int get hashCode => Object.hash(runtimeType, purchase, owner, team);
|
||||
int get hashCode => Object.hash(runtimeType, owner, team);
|
||||
|
||||
/// Create a copy of ProfitSharingPercentageDto
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@@ -15852,7 +15826,6 @@ class _$ProfitSharingPercentageDtoImpl extends _ProfitSharingPercentageDto {
|
||||
|
||||
abstract class _ProfitSharingPercentageDto extends ProfitSharingPercentageDto {
|
||||
const factory _ProfitSharingPercentageDto({
|
||||
@JsonKey(name: 'purchase') final num? purchase,
|
||||
@JsonKey(name: 'owner') final num? owner,
|
||||
@JsonKey(name: 'team') final num? team,
|
||||
}) = _$ProfitSharingPercentageDtoImpl;
|
||||
@@ -15861,9 +15834,6 @@ abstract class _ProfitSharingPercentageDto extends ProfitSharingPercentageDto {
|
||||
factory _ProfitSharingPercentageDto.fromJson(Map<String, dynamic> json) =
|
||||
_$ProfitSharingPercentageDtoImpl.fromJson;
|
||||
|
||||
@override
|
||||
@JsonKey(name: 'purchase')
|
||||
num? get purchase;
|
||||
@override
|
||||
@JsonKey(name: 'owner')
|
||||
num? get owner;
|
||||
@@ -15895,8 +15865,6 @@ mixin _$ProfitSharingPeriodDto {
|
||||
num? get revenue => throw _privateConstructorUsedError;
|
||||
@JsonKey(name: 'order_count')
|
||||
num? get orderCount => throw _privateConstructorUsedError;
|
||||
@JsonKey(name: 'limit_purchase')
|
||||
num? get limitPurchase => throw _privateConstructorUsedError;
|
||||
@JsonKey(name: 'sdl')
|
||||
num? get sdl => throw _privateConstructorUsedError;
|
||||
@JsonKey(name: 'limit_team')
|
||||
@@ -15928,7 +15896,6 @@ abstract class $ProfitSharingPeriodDtoCopyWith<$Res> {
|
||||
@JsonKey(name: 'period_end') DateTime? periodEnd,
|
||||
@JsonKey(name: 'revenue') num? revenue,
|
||||
@JsonKey(name: 'order_count') num? orderCount,
|
||||
@JsonKey(name: 'limit_purchase') num? limitPurchase,
|
||||
@JsonKey(name: 'sdl') num? sdl,
|
||||
@JsonKey(name: 'limit_team') num? limitTeam,
|
||||
@JsonKey(name: 'month') String? month,
|
||||
@@ -15958,7 +15925,6 @@ class _$ProfitSharingPeriodDtoCopyWithImpl<
|
||||
Object? periodEnd = freezed,
|
||||
Object? revenue = freezed,
|
||||
Object? orderCount = freezed,
|
||||
Object? limitPurchase = freezed,
|
||||
Object? sdl = freezed,
|
||||
Object? limitTeam = freezed,
|
||||
Object? month = freezed,
|
||||
@@ -15982,10 +15948,6 @@ class _$ProfitSharingPeriodDtoCopyWithImpl<
|
||||
? _value.orderCount
|
||||
: orderCount // ignore: cast_nullable_to_non_nullable
|
||||
as num?,
|
||||
limitPurchase: freezed == limitPurchase
|
||||
? _value.limitPurchase
|
||||
: limitPurchase // ignore: cast_nullable_to_non_nullable
|
||||
as num?,
|
||||
sdl: freezed == sdl
|
||||
? _value.sdl
|
||||
: sdl // ignore: cast_nullable_to_non_nullable
|
||||
@@ -16022,7 +15984,6 @@ abstract class _$$ProfitSharingPeriodDtoImplCopyWith<$Res>
|
||||
@JsonKey(name: 'period_end') DateTime? periodEnd,
|
||||
@JsonKey(name: 'revenue') num? revenue,
|
||||
@JsonKey(name: 'order_count') num? orderCount,
|
||||
@JsonKey(name: 'limit_purchase') num? limitPurchase,
|
||||
@JsonKey(name: 'sdl') num? sdl,
|
||||
@JsonKey(name: 'limit_team') num? limitTeam,
|
||||
@JsonKey(name: 'month') String? month,
|
||||
@@ -16049,7 +16010,6 @@ class __$$ProfitSharingPeriodDtoImplCopyWithImpl<$Res>
|
||||
Object? periodEnd = freezed,
|
||||
Object? revenue = freezed,
|
||||
Object? orderCount = freezed,
|
||||
Object? limitPurchase = freezed,
|
||||
Object? sdl = freezed,
|
||||
Object? limitTeam = freezed,
|
||||
Object? month = freezed,
|
||||
@@ -16073,10 +16033,6 @@ class __$$ProfitSharingPeriodDtoImplCopyWithImpl<$Res>
|
||||
? _value.orderCount
|
||||
: orderCount // ignore: cast_nullable_to_non_nullable
|
||||
as num?,
|
||||
limitPurchase: freezed == limitPurchase
|
||||
? _value.limitPurchase
|
||||
: limitPurchase // ignore: cast_nullable_to_non_nullable
|
||||
as num?,
|
||||
sdl: freezed == sdl
|
||||
? _value.sdl
|
||||
: sdl // ignore: cast_nullable_to_non_nullable
|
||||
@@ -16106,7 +16062,6 @@ class _$ProfitSharingPeriodDtoImpl extends _ProfitSharingPeriodDto {
|
||||
@JsonKey(name: 'period_end') this.periodEnd,
|
||||
@JsonKey(name: 'revenue') this.revenue,
|
||||
@JsonKey(name: 'order_count') this.orderCount,
|
||||
@JsonKey(name: 'limit_purchase') this.limitPurchase,
|
||||
@JsonKey(name: 'sdl') this.sdl,
|
||||
@JsonKey(name: 'limit_team') this.limitTeam,
|
||||
@JsonKey(name: 'month') this.month,
|
||||
@@ -16129,9 +16084,6 @@ class _$ProfitSharingPeriodDtoImpl extends _ProfitSharingPeriodDto {
|
||||
@JsonKey(name: 'order_count')
|
||||
final num? orderCount;
|
||||
@override
|
||||
@JsonKey(name: 'limit_purchase')
|
||||
final num? limitPurchase;
|
||||
@override
|
||||
@JsonKey(name: 'sdl')
|
||||
final num? sdl;
|
||||
@override
|
||||
@@ -16146,7 +16098,7 @@ class _$ProfitSharingPeriodDtoImpl extends _ProfitSharingPeriodDto {
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'ProfitSharingPeriodDto(periodStart: $periodStart, periodEnd: $periodEnd, revenue: $revenue, orderCount: $orderCount, limitPurchase: $limitPurchase, sdl: $sdl, limitTeam: $limitTeam, month: $month, weekCount: $weekCount)';
|
||||
return 'ProfitSharingPeriodDto(periodStart: $periodStart, periodEnd: $periodEnd, revenue: $revenue, orderCount: $orderCount, sdl: $sdl, limitTeam: $limitTeam, month: $month, weekCount: $weekCount)';
|
||||
}
|
||||
|
||||
@override
|
||||
@@ -16161,8 +16113,6 @@ class _$ProfitSharingPeriodDtoImpl extends _ProfitSharingPeriodDto {
|
||||
(identical(other.revenue, revenue) || other.revenue == revenue) &&
|
||||
(identical(other.orderCount, orderCount) ||
|
||||
other.orderCount == orderCount) &&
|
||||
(identical(other.limitPurchase, limitPurchase) ||
|
||||
other.limitPurchase == limitPurchase) &&
|
||||
(identical(other.sdl, sdl) || other.sdl == sdl) &&
|
||||
(identical(other.limitTeam, limitTeam) ||
|
||||
other.limitTeam == limitTeam) &&
|
||||
@@ -16179,7 +16129,6 @@ class _$ProfitSharingPeriodDtoImpl extends _ProfitSharingPeriodDto {
|
||||
periodEnd,
|
||||
revenue,
|
||||
orderCount,
|
||||
limitPurchase,
|
||||
sdl,
|
||||
limitTeam,
|
||||
month,
|
||||
@@ -16210,7 +16159,6 @@ abstract class _ProfitSharingPeriodDto extends ProfitSharingPeriodDto {
|
||||
@JsonKey(name: 'period_end') final DateTime? periodEnd,
|
||||
@JsonKey(name: 'revenue') final num? revenue,
|
||||
@JsonKey(name: 'order_count') final num? orderCount,
|
||||
@JsonKey(name: 'limit_purchase') final num? limitPurchase,
|
||||
@JsonKey(name: 'sdl') final num? sdl,
|
||||
@JsonKey(name: 'limit_team') final num? limitTeam,
|
||||
@JsonKey(name: 'month') final String? month,
|
||||
@@ -16234,9 +16182,6 @@ abstract class _ProfitSharingPeriodDto extends ProfitSharingPeriodDto {
|
||||
@JsonKey(name: 'order_count')
|
||||
num? get orderCount;
|
||||
@override
|
||||
@JsonKey(name: 'limit_purchase')
|
||||
num? get limitPurchase;
|
||||
@override
|
||||
@JsonKey(name: 'sdl')
|
||||
num? get sdl;
|
||||
@override
|
||||
|
||||
@@ -1251,18 +1251,13 @@ Map<String, dynamic> _$$ProfitSharingBudgetDtoImplToJson(
|
||||
_$ProfitSharingPercentageDtoImpl _$$ProfitSharingPercentageDtoImplFromJson(
|
||||
Map<String, dynamic> json,
|
||||
) => _$ProfitSharingPercentageDtoImpl(
|
||||
purchase: json['purchase'] as num?,
|
||||
owner: json['owner'] as num?,
|
||||
team: json['team'] as num?,
|
||||
);
|
||||
|
||||
Map<String, dynamic> _$$ProfitSharingPercentageDtoImplToJson(
|
||||
_$ProfitSharingPercentageDtoImpl instance,
|
||||
) => <String, dynamic>{
|
||||
'purchase': instance.purchase,
|
||||
'owner': instance.owner,
|
||||
'team': instance.team,
|
||||
};
|
||||
) => <String, dynamic>{'owner': instance.owner, 'team': instance.team};
|
||||
|
||||
_$ProfitSharingPeriodDtoImpl _$$ProfitSharingPeriodDtoImplFromJson(
|
||||
Map<String, dynamic> json,
|
||||
@@ -1275,7 +1270,6 @@ _$ProfitSharingPeriodDtoImpl _$$ProfitSharingPeriodDtoImplFromJson(
|
||||
: DateTime.parse(json['period_end'] as String),
|
||||
revenue: json['revenue'] as num?,
|
||||
orderCount: json['order_count'] as num?,
|
||||
limitPurchase: json['limit_purchase'] as num?,
|
||||
sdl: json['sdl'] as num?,
|
||||
limitTeam: json['limit_team'] as num?,
|
||||
month: json['month'] as String?,
|
||||
@@ -1289,7 +1283,6 @@ Map<String, dynamic> _$$ProfitSharingPeriodDtoImplToJson(
|
||||
'period_end': instance.periodEnd?.toIso8601String(),
|
||||
'revenue': instance.revenue,
|
||||
'order_count': instance.orderCount,
|
||||
'limit_purchase': instance.limitPurchase,
|
||||
'sdl': instance.sdl,
|
||||
'limit_team': instance.limitTeam,
|
||||
'month': instance.month,
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import 'dart:developer';
|
||||
|
||||
import 'package:data_channel/data_channel.dart';
|
||||
import 'package:dio/dio.dart' show Response;
|
||||
import 'package:injectable/injectable.dart';
|
||||
|
||||
import '../../../common/api/api_client.dart';
|
||||
@@ -291,7 +292,12 @@ class AnalyticRemoteDataProvider {
|
||||
|
||||
return DC.data(dto);
|
||||
} on ApiFailure catch (e, s) {
|
||||
log('fetchExclusiveSummaryError', name: _logName, error: e, stackTrace: s);
|
||||
log(
|
||||
'fetchExclusiveSummaryError',
|
||||
name: _logName,
|
||||
error: e,
|
||||
stackTrace: s,
|
||||
);
|
||||
return DC.error(AnalyticFailure.serverError(e));
|
||||
}
|
||||
}
|
||||
@@ -310,17 +316,18 @@ class AnalyticRemoteDataProvider {
|
||||
};
|
||||
if (outletId != null) params['outlet_id'] = outletId;
|
||||
|
||||
final response = await _apiClient.get(
|
||||
ApiPath.parentCategoryAnalytic,
|
||||
params: params,
|
||||
headers: getAuthorizationHeader(),
|
||||
);
|
||||
final response = await _getProfitSharing('', params);
|
||||
|
||||
if (response.data['data'] == null) {
|
||||
final failure = _profitSharingFailure(response);
|
||||
if (failure != null) return DC.error(failure);
|
||||
|
||||
final body = response.data;
|
||||
final data = body is Map ? body['data'] : null;
|
||||
if (data == null) {
|
||||
return DC.error(AnalyticFailure.empty());
|
||||
}
|
||||
|
||||
final dto = ProfitSharingDto.fromJson(response.data['data']);
|
||||
final dto = ProfitSharingDto.fromJson(data);
|
||||
|
||||
return DC.data(dto);
|
||||
} on ApiFailure catch (e, s) {
|
||||
@@ -342,17 +349,18 @@ class AnalyticRemoteDataProvider {
|
||||
};
|
||||
if (outletId != null) params['outlet_id'] = outletId;
|
||||
|
||||
final response = await _apiClient.get(
|
||||
'${ApiPath.parentCategoryAnalytic}/$parentCategoryId',
|
||||
params: params,
|
||||
headers: getAuthorizationHeader(),
|
||||
);
|
||||
final response = await _getProfitSharing('/$parentCategoryId', params);
|
||||
|
||||
if (response.data['data'] == null) {
|
||||
final failure = _profitSharingFailure(response);
|
||||
if (failure != null) return DC.error(failure);
|
||||
|
||||
final body = response.data;
|
||||
final data = body is Map ? body['data'] : null;
|
||||
if (data == null) {
|
||||
return DC.error(AnalyticFailure.empty());
|
||||
}
|
||||
|
||||
final dto = ProfitSharingDetailDto.fromJson(response.data['data']);
|
||||
final dto = ProfitSharingDetailDto.fromJson(data);
|
||||
|
||||
return DC.data(dto);
|
||||
} on ApiFailure catch (e, s) {
|
||||
@@ -365,4 +373,43 @@ class AnalyticRemoteDataProvider {
|
||||
return DC.error(AnalyticFailure.serverError(e));
|
||||
}
|
||||
}
|
||||
|
||||
/// GET ke endpoint profit sharing. Selama backend lama masih jalan path baru
|
||||
/// mengembalikan 404, jadi dicoba sekali lagi ke path lama.
|
||||
Future<Response> _getProfitSharing(
|
||||
String subPath,
|
||||
Map<String, dynamic> params,
|
||||
) async {
|
||||
Future<Response> request(String basePath) => _apiClient.get(
|
||||
'$basePath$subPath',
|
||||
params: params,
|
||||
headers: getAuthorizationHeader(),
|
||||
// Status 5xx tidak dilempar supaya `cause` di body masih bisa dibaca.
|
||||
validateStatus: (status) => status != null && status < 600,
|
||||
);
|
||||
|
||||
final response = await request(ApiPath.profitSharingAnalytic);
|
||||
if (response.statusCode != 404) return response;
|
||||
return request(ApiPath.profitSharingAnalyticLegacy);
|
||||
}
|
||||
|
||||
/// Failure dari status error endpoint profit sharing, null kalau berhasil.
|
||||
/// Detail kategori non-team ditolak 500 dengan `cause` "... is not a team".
|
||||
AnalyticFailure? _profitSharingFailure(Response response) {
|
||||
final status = response.statusCode ?? 0;
|
||||
if (status == 404) {
|
||||
return const AnalyticFailure.serverError(ApiFailure.notFound(null));
|
||||
}
|
||||
if (status < 500) return null;
|
||||
|
||||
final body = response.data;
|
||||
final errors = body is Map ? body['errors'] : null;
|
||||
final isNotTeam =
|
||||
errors is List &&
|
||||
errors.any(
|
||||
(e) => e is Map && '${e['cause']}'.contains('is not a team'),
|
||||
);
|
||||
if (isNotTeam) return const AnalyticFailure.notTeam();
|
||||
return const AnalyticFailure.serverError(ApiFailure.internalServerError());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -121,7 +121,6 @@ class ProfitSharingPercentageDto with _$ProfitSharingPercentageDto {
|
||||
const ProfitSharingPercentageDto._();
|
||||
|
||||
const factory ProfitSharingPercentageDto({
|
||||
@JsonKey(name: 'purchase') num? purchase,
|
||||
@JsonKey(name: 'owner') num? owner,
|
||||
@JsonKey(name: 'team') num? team,
|
||||
}) = _ProfitSharingPercentageDto;
|
||||
@@ -130,7 +129,6 @@ class ProfitSharingPercentageDto with _$ProfitSharingPercentageDto {
|
||||
_$ProfitSharingPercentageDtoFromJson(json);
|
||||
|
||||
ProfitSharingPercentage toDomain() => ProfitSharingPercentage(
|
||||
purchase: purchase?.toDouble() ?? 0,
|
||||
owner: owner?.toDouble() ?? 0,
|
||||
team: team?.toDouble() ?? 0,
|
||||
);
|
||||
@@ -145,7 +143,6 @@ class ProfitSharingPeriodDto with _$ProfitSharingPeriodDto {
|
||||
@JsonKey(name: 'period_end') DateTime? periodEnd,
|
||||
@JsonKey(name: 'revenue') num? revenue,
|
||||
@JsonKey(name: 'order_count') num? orderCount,
|
||||
@JsonKey(name: 'limit_purchase') num? limitPurchase,
|
||||
@JsonKey(name: 'sdl') num? sdl,
|
||||
@JsonKey(name: 'limit_team') num? limitTeam,
|
||||
@JsonKey(name: 'month') String? month,
|
||||
@@ -161,7 +158,6 @@ class ProfitSharingPeriodDto with _$ProfitSharingPeriodDto {
|
||||
periodEnd: periodEnd?.toLocal() ?? DateTime.fromMillisecondsSinceEpoch(0),
|
||||
revenue: revenue?.toInt() ?? 0,
|
||||
orderCount: orderCount?.toInt() ?? 0,
|
||||
limitPurchase: limitPurchase?.toInt() ?? 0,
|
||||
sdl: sdl?.toInt() ?? 0,
|
||||
limitTeam: limitTeam?.toInt() ?? 0,
|
||||
month: month ?? '',
|
||||
|
||||
+20
-3
@@ -541,18 +541,35 @@
|
||||
"@total_inventory_value": {},
|
||||
"profit_sharing": "Profit Sharing",
|
||||
"@profit_sharing": {},
|
||||
"profit_sharing_desc": "Revenue allocation for purchasing, owner, and team",
|
||||
"profit_sharing_desc": "Revenue allocation for owner (SDL) and team",
|
||||
"@profit_sharing_desc": {},
|
||||
"profit_sharing_allocation": "Profit Sharing Allocation",
|
||||
"@profit_sharing_allocation": {},
|
||||
"share_purchase": "Purchasing",
|
||||
"@share_purchase": {},
|
||||
"profit_sharing_not_team": "This category is no longer part of profit sharing",
|
||||
"@profit_sharing_not_team": {},
|
||||
"share_owner": "Owner",
|
||||
"@share_owner": {},
|
||||
"share_team": "Team",
|
||||
"@share_team": {},
|
||||
"sdl": "SDL",
|
||||
"@sdl": {},
|
||||
"sdl_summary": "SDL Summary",
|
||||
"@sdl_summary": {},
|
||||
"sdl_desc": "Owner's share of revenue",
|
||||
"@sdl_desc": {},
|
||||
"sdl_per_parent_category": "SDL per Parent Category",
|
||||
"@sdl_per_parent_category": {},
|
||||
"sdl_per_week": "SDL per Week",
|
||||
"@sdl_per_week": {},
|
||||
"from_revenue": "from {revenue} revenue",
|
||||
"@from_revenue": {
|
||||
"placeholders": {
|
||||
"revenue": {
|
||||
"type": "String",
|
||||
"example": "Rp 52,500,000"
|
||||
}
|
||||
}
|
||||
},
|
||||
"top_product": "Top Product",
|
||||
"@top_product": {},
|
||||
"weekly": "Weekly",
|
||||
|
||||
+20
-3
@@ -541,18 +541,35 @@
|
||||
"@total_inventory_value": {},
|
||||
"profit_sharing": "Bagi Hasil",
|
||||
"@profit_sharing": {},
|
||||
"profit_sharing_desc": "Alokasi omzet untuk belanja, owner, dan tim",
|
||||
"profit_sharing_desc": "Alokasi omzet untuk owner (SDL) dan tim",
|
||||
"@profit_sharing_desc": {},
|
||||
"profit_sharing_allocation": "Alokasi Bagi Hasil",
|
||||
"@profit_sharing_allocation": {},
|
||||
"share_purchase": "Belanja",
|
||||
"@share_purchase": {},
|
||||
"profit_sharing_not_team": "Kategori ini sudah tidak ikut bagi hasil",
|
||||
"@profit_sharing_not_team": {},
|
||||
"share_owner": "Owner",
|
||||
"@share_owner": {},
|
||||
"share_team": "Tim",
|
||||
"@share_team": {},
|
||||
"sdl": "SDL",
|
||||
"@sdl": {},
|
||||
"sdl_summary": "Ringkasan SDL",
|
||||
"@sdl_summary": {},
|
||||
"sdl_desc": "Bagian owner dari omzet",
|
||||
"@sdl_desc": {},
|
||||
"sdl_per_parent_category": "SDL per Kategori Induk",
|
||||
"@sdl_per_parent_category": {},
|
||||
"sdl_per_week": "SDL per Minggu",
|
||||
"@sdl_per_week": {},
|
||||
"from_revenue": "dari omzet {revenue}",
|
||||
"@from_revenue": {
|
||||
"placeholders": {
|
||||
"revenue": {
|
||||
"type": "String",
|
||||
"example": "Rp 52.500.000"
|
||||
}
|
||||
}
|
||||
},
|
||||
"top_product": "Produk Terlaris",
|
||||
"@top_product": {},
|
||||
"weekly": "Mingguan",
|
||||
|
||||
@@ -1574,7 +1574,7 @@ abstract class AppLocalizations {
|
||||
/// No description provided for @profit_sharing_desc.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Revenue allocation for purchasing, owner, and team'**
|
||||
/// **'Revenue allocation for owner (SDL) and team'**
|
||||
String get profit_sharing_desc;
|
||||
|
||||
/// No description provided for @profit_sharing_allocation.
|
||||
@@ -1583,11 +1583,11 @@ abstract class AppLocalizations {
|
||||
/// **'Profit Sharing Allocation'**
|
||||
String get profit_sharing_allocation;
|
||||
|
||||
/// No description provided for @share_purchase.
|
||||
/// No description provided for @profit_sharing_not_team.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Purchasing'**
|
||||
String get share_purchase;
|
||||
/// **'This category is no longer part of profit sharing'**
|
||||
String get profit_sharing_not_team;
|
||||
|
||||
/// No description provided for @share_owner.
|
||||
///
|
||||
@@ -1607,6 +1607,36 @@ abstract class AppLocalizations {
|
||||
/// **'SDL'**
|
||||
String get sdl;
|
||||
|
||||
/// No description provided for @sdl_summary.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'SDL Summary'**
|
||||
String get sdl_summary;
|
||||
|
||||
/// No description provided for @sdl_desc.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Owner\'s share of revenue'**
|
||||
String get sdl_desc;
|
||||
|
||||
/// No description provided for @sdl_per_parent_category.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'SDL per Parent Category'**
|
||||
String get sdl_per_parent_category;
|
||||
|
||||
/// No description provided for @sdl_per_week.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'SDL per Week'**
|
||||
String get sdl_per_week;
|
||||
|
||||
/// No description provided for @from_revenue.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'from {revenue} revenue'**
|
||||
String from_revenue(String revenue);
|
||||
|
||||
/// No description provided for @top_product.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
|
||||
@@ -761,13 +761,13 @@ class AppLocalizationsEn extends AppLocalizations {
|
||||
String get profit_sharing => 'Profit Sharing';
|
||||
|
||||
@override
|
||||
String get profit_sharing_desc => 'Revenue allocation for purchasing, owner, and team';
|
||||
String get profit_sharing_desc => 'Revenue allocation for owner (SDL) and team';
|
||||
|
||||
@override
|
||||
String get profit_sharing_allocation => 'Profit Sharing Allocation';
|
||||
|
||||
@override
|
||||
String get share_purchase => 'Purchasing';
|
||||
String get profit_sharing_not_team => 'This category is no longer part of profit sharing';
|
||||
|
||||
@override
|
||||
String get share_owner => 'Owner';
|
||||
@@ -778,6 +778,23 @@ class AppLocalizationsEn extends AppLocalizations {
|
||||
@override
|
||||
String get sdl => 'SDL';
|
||||
|
||||
@override
|
||||
String get sdl_summary => 'SDL Summary';
|
||||
|
||||
@override
|
||||
String get sdl_desc => 'Owner\'s share of revenue';
|
||||
|
||||
@override
|
||||
String get sdl_per_parent_category => 'SDL per Parent Category';
|
||||
|
||||
@override
|
||||
String get sdl_per_week => 'SDL per Week';
|
||||
|
||||
@override
|
||||
String from_revenue(String revenue) {
|
||||
return 'from $revenue revenue';
|
||||
}
|
||||
|
||||
@override
|
||||
String get top_product => 'Top Product';
|
||||
|
||||
|
||||
@@ -761,13 +761,13 @@ class AppLocalizationsId extends AppLocalizations {
|
||||
String get profit_sharing => 'Bagi Hasil';
|
||||
|
||||
@override
|
||||
String get profit_sharing_desc => 'Alokasi omzet untuk belanja, owner, dan tim';
|
||||
String get profit_sharing_desc => 'Alokasi omzet untuk owner (SDL) dan tim';
|
||||
|
||||
@override
|
||||
String get profit_sharing_allocation => 'Alokasi Bagi Hasil';
|
||||
|
||||
@override
|
||||
String get share_purchase => 'Belanja';
|
||||
String get profit_sharing_not_team => 'Kategori ini sudah tidak ikut bagi hasil';
|
||||
|
||||
@override
|
||||
String get share_owner => 'Owner';
|
||||
@@ -778,6 +778,23 @@ class AppLocalizationsId extends AppLocalizations {
|
||||
@override
|
||||
String get sdl => 'SDL';
|
||||
|
||||
@override
|
||||
String get sdl_summary => 'Ringkasan SDL';
|
||||
|
||||
@override
|
||||
String get sdl_desc => 'Bagian owner dari omzet';
|
||||
|
||||
@override
|
||||
String get sdl_per_parent_category => 'SDL per Kategori Induk';
|
||||
|
||||
@override
|
||||
String get sdl_per_week => 'SDL per Minggu';
|
||||
|
||||
@override
|
||||
String from_revenue(String revenue) {
|
||||
return 'dari omzet $revenue';
|
||||
}
|
||||
|
||||
@override
|
||||
String get top_product => 'Produk Terlaris';
|
||||
|
||||
|
||||
@@ -9,8 +9,10 @@ import '../../../common/theme/theme.dart';
|
||||
import '../../../domain/analytic/analytic.dart';
|
||||
import '../../../injection.dart';
|
||||
import '../../components/spacer/spacer.dart';
|
||||
import 'widgets/period_label.dart';
|
||||
import 'widgets/profit_sharing_allocation.dart';
|
||||
import 'widgets/profit_sharing_detail_header.dart';
|
||||
import 'widgets/profit_sharing_sdl.dart';
|
||||
import 'widgets/profit_sharing_status.dart';
|
||||
import 'widgets/profit_sharing_subcategories.dart';
|
||||
|
||||
@@ -48,75 +50,116 @@ class ProfitSharingDetailPage extends StatelessWidget
|
||||
return Scaffold(
|
||||
backgroundColor: AppColor.background,
|
||||
body:
|
||||
BlocBuilder<
|
||||
BlocListener<
|
||||
ProfitSharingDetailLoaderBloc,
|
||||
ProfitSharingDetailLoaderState
|
||||
>(
|
||||
builder: (context, state) {
|
||||
return RefreshIndicator(
|
||||
backgroundColor: AppColor.white,
|
||||
color: AppColor.primary,
|
||||
onRefresh: () async {
|
||||
context.read<ProfitSharingDetailLoaderBloc>().add(
|
||||
_fetchEvent(),
|
||||
);
|
||||
await context
|
||||
.read<ProfitSharingDetailLoaderBloc>()
|
||||
.stream
|
||||
.firstWhere((s) => !s.isFetching);
|
||||
},
|
||||
child: CustomScrollView(
|
||||
physics: const AlwaysScrollableScrollPhysics(),
|
||||
slivers: [
|
||||
SliverToBoxAdapter(
|
||||
child: ProfitSharingDetailHeader(
|
||||
state: state,
|
||||
fallbackTitle: parentCategoryName,
|
||||
),
|
||||
),
|
||||
|
||||
const SliverToBoxAdapter(child: SpaceHeight(16)),
|
||||
|
||||
if (state.isFetching)
|
||||
SliverToBoxAdapter(child: _buildShimmer())
|
||||
else ...[
|
||||
SliverToBoxAdapter(
|
||||
child: _SummaryCard(summary: state.detail.summary),
|
||||
),
|
||||
|
||||
const SliverToBoxAdapter(child: SpaceHeight(16)),
|
||||
|
||||
SliverToBoxAdapter(
|
||||
child: ProfitSharingAllocation(
|
||||
budget: state.detail.budget,
|
||||
),
|
||||
),
|
||||
|
||||
const SliverToBoxAdapter(child: SpaceHeight(16)),
|
||||
|
||||
SliverToBoxAdapter(
|
||||
child: ProfitSharingSubCategories(
|
||||
categories: state.detail.categories,
|
||||
expandedCategoryId: state.expandedCategoryId,
|
||||
onToggle: (categoryId) =>
|
||||
context.read<ProfitSharingDetailLoaderBloc>().add(
|
||||
ProfitSharingDetailLoaderEvent.expandedCategoryChanged(
|
||||
categoryId,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
|
||||
const SliverToBoxAdapter(child: SizedBox(height: 100)),
|
||||
],
|
||||
),
|
||||
listenWhen: (previous, current) =>
|
||||
previous.failureOption != current.failureOption,
|
||||
listener: (context, state) {
|
||||
final isNotTeam = state.failureOption.fold(
|
||||
() => false,
|
||||
(f) => f.maybeMap(notTeam: (_) => true, orElse: () => false),
|
||||
);
|
||||
// Kategori induk dimatikan dari team setelah list dimuat:
|
||||
// kembali ke list dengan hasil `true` supaya list memuat ulang.
|
||||
if (isNotTeam) context.router.maybePop(true);
|
||||
},
|
||||
child: _buildContent(),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildContent() {
|
||||
return BlocBuilder<
|
||||
ProfitSharingDetailLoaderBloc,
|
||||
ProfitSharingDetailLoaderState
|
||||
>(
|
||||
builder: (context, state) {
|
||||
return RefreshIndicator(
|
||||
backgroundColor: AppColor.white,
|
||||
color: AppColor.primary,
|
||||
onRefresh: () async {
|
||||
context.read<ProfitSharingDetailLoaderBloc>().add(_fetchEvent());
|
||||
await context
|
||||
.read<ProfitSharingDetailLoaderBloc>()
|
||||
.stream
|
||||
.firstWhere((s) => !s.isFetching);
|
||||
},
|
||||
child: CustomScrollView(
|
||||
physics: const AlwaysScrollableScrollPhysics(),
|
||||
slivers: [
|
||||
SliverToBoxAdapter(
|
||||
child: ProfitSharingDetailHeader(
|
||||
state: state,
|
||||
fallbackTitle: parentCategoryName,
|
||||
),
|
||||
),
|
||||
|
||||
const SliverToBoxAdapter(child: SpaceHeight(16)),
|
||||
|
||||
if (state.isFetching)
|
||||
SliverToBoxAdapter(child: _buildShimmer())
|
||||
else ...[
|
||||
SliverToBoxAdapter(
|
||||
child: _SummaryCard(summary: state.detail.summary),
|
||||
),
|
||||
|
||||
const SliverToBoxAdapter(child: SpaceHeight(16)),
|
||||
|
||||
SliverToBoxAdapter(
|
||||
child: _buildSdlSection(context, state.detail),
|
||||
),
|
||||
|
||||
const SliverToBoxAdapter(child: SpaceHeight(16)),
|
||||
|
||||
SliverToBoxAdapter(
|
||||
child: ProfitSharingAllocation(budget: state.detail.budget),
|
||||
),
|
||||
|
||||
const SliverToBoxAdapter(child: SpaceHeight(16)),
|
||||
|
||||
SliverToBoxAdapter(
|
||||
child: ProfitSharingSubCategories(
|
||||
categories: state.detail.categories,
|
||||
expandedCategoryId: state.expandedCategoryId,
|
||||
onToggle: (categoryId) =>
|
||||
context.read<ProfitSharingDetailLoaderBloc>().add(
|
||||
ProfitSharingDetailLoaderEvent.expandedCategoryChanged(
|
||||
categoryId,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
|
||||
const SliverToBoxAdapter(child: SizedBox(height: 100)),
|
||||
],
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildSdlSection(BuildContext context, ProfitSharingDetail detail) {
|
||||
final summary = detail.summary;
|
||||
|
||||
return ProfitSharingSdlSection(
|
||||
sdl: summary.sdl,
|
||||
revenue: summary.totalRevenue,
|
||||
percent: summary.ownerFeePercent,
|
||||
breakdownTitle: context.lang.sdl_per_week,
|
||||
items: [
|
||||
for (final period in detail.budget.weekly.where((e) => e.hasRevenue))
|
||||
ProfitSharingSdlItem(
|
||||
label: formatPeriodLabel(period.periodStart, period.periodEnd),
|
||||
sdl: period.sdl,
|
||||
revenue: period.revenue,
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildShimmer() {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16),
|
||||
@@ -187,6 +230,7 @@ class _SummaryCard extends StatelessWidget {
|
||||
icon: Icons.category_rounded,
|
||||
label: context.lang.sub_category,
|
||||
value: '${summary.categoryCount}',
|
||||
color: ProfitSharingColor.subCategory,
|
||||
),
|
||||
),
|
||||
const SpaceWidth(10),
|
||||
@@ -195,7 +239,7 @@ class _SummaryCard extends StatelessWidget {
|
||||
icon: Icons.inventory_2_rounded,
|
||||
label: context.lang.products,
|
||||
value: '${summary.productCount}',
|
||||
color: AppColor.info,
|
||||
color: ProfitSharingColor.product,
|
||||
),
|
||||
),
|
||||
const SpaceWidth(10),
|
||||
@@ -204,20 +248,13 @@ class _SummaryCard extends StatelessWidget {
|
||||
icon: Icons.receipt_long_rounded,
|
||||
label: context.lang.orders,
|
||||
value: summary.orderCount.thousandFormat,
|
||||
color: AppColor.warning,
|
||||
color: ProfitSharingColor.order,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
|
||||
const SpaceHeight(12),
|
||||
|
||||
ProfitSharingSdlStrip(
|
||||
amount: summary.sdl,
|
||||
feePercent: summary.ownerFeePercent,
|
||||
),
|
||||
|
||||
const SpaceHeight(12),
|
||||
const SpaceHeight(16),
|
||||
|
||||
Row(
|
||||
children: [
|
||||
|
||||
@@ -4,15 +4,19 @@ import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:shimmer/shimmer.dart';
|
||||
|
||||
import '../../../application/analytic/profit_sharing_loader/profit_sharing_loader_bloc.dart';
|
||||
import '../../../common/extension/extension.dart';
|
||||
import '../../../common/theme/theme.dart';
|
||||
import '../../../domain/analytic/analytic.dart';
|
||||
import '../../../injection.dart';
|
||||
import '../../components/field/date_range_picker_field.dart';
|
||||
import '../../components/spacer/spacer.dart';
|
||||
import '../../components/toast/flushbar.dart';
|
||||
import '../../router/app_router.gr.dart';
|
||||
import 'widgets/profit_sharing_allocation.dart';
|
||||
import 'widgets/profit_sharing_categories.dart';
|
||||
import 'widgets/profit_sharing_header.dart';
|
||||
import 'widgets/profit_sharing_periods.dart';
|
||||
import 'widgets/profit_sharing_sdl.dart';
|
||||
|
||||
@RoutePage()
|
||||
class ProfitSharingPage extends StatefulWidget implements AutoRouteWrapper {
|
||||
@@ -124,20 +128,23 @@ class _ProfitSharingPageState extends State<ProfitSharingPage>
|
||||
opacity: _fadeAnimation,
|
||||
child: ProfitSharingCategories(
|
||||
categories: state.profitSharing.categories,
|
||||
onCategoryTap: (category) => context.router.push(
|
||||
ProfitSharingDetailRoute(
|
||||
parentCategoryId: category.parentCategoryId,
|
||||
parentCategoryName: category.parentCategoryName,
|
||||
dateFrom: state.dateFrom,
|
||||
dateTo: state.dateTo,
|
||||
),
|
||||
),
|
||||
onCategoryTap: (category) =>
|
||||
_openDetail(context, state, category),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
const SliverToBoxAdapter(child: SpaceHeight(16)),
|
||||
|
||||
SliverToBoxAdapter(
|
||||
child: FadeTransition(
|
||||
opacity: _fadeAnimation,
|
||||
child: _buildSdlSection(context, state),
|
||||
),
|
||||
),
|
||||
|
||||
const SliverToBoxAdapter(child: SpaceHeight(16)),
|
||||
|
||||
SliverToBoxAdapter(
|
||||
child: FadeTransition(
|
||||
opacity: _fadeAnimation,
|
||||
@@ -174,6 +181,58 @@ class _ProfitSharingPageState extends State<ProfitSharingPage>
|
||||
);
|
||||
}
|
||||
|
||||
/// Detail kembali dengan `true` kalau kategori induknya sudah non-team;
|
||||
/// list dimuat ulang supaya kategori itu hilang.
|
||||
Future<void> _openDetail(
|
||||
BuildContext context,
|
||||
ProfitSharingLoaderState state,
|
||||
ProfitSharingCategory category,
|
||||
) async {
|
||||
final isNotTeam = await context.router.push<bool>(
|
||||
ProfitSharingDetailRoute(
|
||||
parentCategoryId: category.parentCategoryId,
|
||||
parentCategoryName: category.parentCategoryName,
|
||||
dateFrom: state.dateFrom,
|
||||
dateTo: state.dateTo,
|
||||
),
|
||||
);
|
||||
if (isNotTeam != true || !context.mounted) return;
|
||||
|
||||
AppFlushbar.showError(context, context.lang.profit_sharing_not_team);
|
||||
context.read<ProfitSharingLoaderBloc>().add(
|
||||
const ProfitSharingLoaderEvent.fetched(),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildSdlSection(
|
||||
BuildContext context,
|
||||
ProfitSharingLoaderState state,
|
||||
) {
|
||||
final total = state.profitSharing.budget.total;
|
||||
final categories = [...state.profitSharing.categories]
|
||||
..sort((a, b) => b.sdl.compareTo(a.sdl));
|
||||
|
||||
return ProfitSharingSdlSection(
|
||||
sdl: total.sdl,
|
||||
revenue: total.revenue,
|
||||
// Fee owner beda tiap kategori induk, jadi yang ditampilkan persen
|
||||
// efektifnya; persentase bawaan hanya dipakai saat belum ada omzet.
|
||||
percent: total.hasRevenue
|
||||
? total.sdlPercentage
|
||||
: state.profitSharing.budget.percentages.owner,
|
||||
breakdownTitle: context.lang.sdl_per_parent_category,
|
||||
items: [
|
||||
for (final category in categories)
|
||||
ProfitSharingSdlItem(
|
||||
label: category.parentCategoryName,
|
||||
sdl: category.sdl,
|
||||
revenue: category.totalRevenue,
|
||||
feePercent: category.ownerFeePercent,
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildShimmer() {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16),
|
||||
|
||||
@@ -6,10 +6,20 @@ import '../../../../domain/analytic/analytic.dart';
|
||||
import '../../../components/spacer/spacer.dart';
|
||||
|
||||
/// Warna tetap untuk tiap pos bagi hasil supaya konsisten di semua bagian.
|
||||
/// Sengaja pakai warna jenuh, bukan default Material yang pucat.
|
||||
class ProfitSharingColor {
|
||||
static const Color purchase = Color(0xFF2196F3);
|
||||
static const Color owner = Color(0xFF4CAF50);
|
||||
static const Color team = Color(0xFFFF9800);
|
||||
static const Color owner = Color(0xFF00B341);
|
||||
static const Color team = Color(0xFFFF6A00);
|
||||
|
||||
// Status margin (Sehat / Waspada / Tidak Sehat).
|
||||
static const Color healthy = Color(0xFF00B341);
|
||||
static const Color watch = Color(0xFFFF9500);
|
||||
static const Color critical = Color(0xFFFF1E3C);
|
||||
|
||||
// Tile ringkasan.
|
||||
static const Color subCategory = AppColor.primary;
|
||||
static const Color product = Color(0xFF7B2FF7);
|
||||
static const Color order = Color(0xFF00A3D9);
|
||||
}
|
||||
|
||||
class ProfitSharingAllocation extends StatelessWidget {
|
||||
@@ -21,11 +31,14 @@ class ProfitSharingAllocation extends StatelessWidget {
|
||||
Widget build(BuildContext context) {
|
||||
final total = budget.total;
|
||||
final percentages = budget.percentages;
|
||||
// Fee owner berbeda tiap kategori induk, jadi porsi SDL dihitung dari
|
||||
// Fee owner berbeda tiap kategori induk, jadi porsi dihitung dari
|
||||
// nominalnya; persentase bawaan hanya dipakai saat belum ada omzet.
|
||||
final sdlPercentage = total.hasRevenue
|
||||
? total.sdlPercentage
|
||||
: percentages.owner;
|
||||
final teamPercentage = total.hasRevenue
|
||||
? total.teamPercentage
|
||||
: percentages.team;
|
||||
|
||||
return Container(
|
||||
margin: const EdgeInsets.symmetric(horizontal: 16),
|
||||
@@ -55,22 +68,10 @@ class ProfitSharingAllocation extends StatelessWidget {
|
||||
|
||||
const SpaceHeight(16),
|
||||
|
||||
ProfitSharingBar(
|
||||
purchase: percentages.purchase,
|
||||
owner: sdlPercentage,
|
||||
team: percentages.team,
|
||||
),
|
||||
ProfitSharingBar(owner: sdlPercentage, team: teamPercentage),
|
||||
|
||||
const SpaceHeight(20),
|
||||
|
||||
_row(
|
||||
context: context,
|
||||
color: ProfitSharingColor.purchase,
|
||||
label: context.lang.share_purchase,
|
||||
percentage: percentages.purchase,
|
||||
amount: total.limitPurchase,
|
||||
),
|
||||
const Divider(height: 24, color: AppColor.borderLight),
|
||||
_row(
|
||||
context: context,
|
||||
color: ProfitSharingColor.owner,
|
||||
@@ -83,7 +84,7 @@ class ProfitSharingAllocation extends StatelessWidget {
|
||||
context: context,
|
||||
color: ProfitSharingColor.team,
|
||||
label: context.lang.share_team,
|
||||
percentage: percentages.team,
|
||||
percentage: teamPercentage,
|
||||
amount: total.limitTeam,
|
||||
),
|
||||
],
|
||||
@@ -117,14 +118,14 @@ class ProfitSharingAllocation extends StatelessWidget {
|
||||
Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 2),
|
||||
decoration: BoxDecoration(
|
||||
color: color.withOpacity(0.12),
|
||||
color: color,
|
||||
borderRadius: BorderRadius.circular(6),
|
||||
),
|
||||
child: Text(
|
||||
'${percentage.toStringAsFixed(percentage % 1 == 0 ? 0 : 1)}%',
|
||||
style: AppStyle.xs.copyWith(
|
||||
color: color,
|
||||
fontWeight: FontWeight.w700,
|
||||
color: AppColor.textWhite,
|
||||
fontWeight: FontWeight.w800,
|
||||
),
|
||||
),
|
||||
),
|
||||
@@ -145,16 +146,15 @@ class ProfitSharingAllocation extends StatelessWidget {
|
||||
}
|
||||
}
|
||||
|
||||
/// Bar proporsi belanja / SDL (owner) / tim.
|
||||
/// Bar proporsi SDL (owner) / tim dalam persen omzet. Sisa yang belum terbagi
|
||||
/// (backend lama masih menyisihkan porsi belanja) tampil sebagai track kosong.
|
||||
class ProfitSharingBar extends StatelessWidget {
|
||||
final double purchase;
|
||||
final double owner;
|
||||
final double team;
|
||||
final double height;
|
||||
|
||||
const ProfitSharingBar({
|
||||
super.key,
|
||||
required this.purchase,
|
||||
required this.owner,
|
||||
required this.team,
|
||||
this.height = 12,
|
||||
@@ -162,7 +162,7 @@ class ProfitSharingBar extends StatelessWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final total = purchase + owner + team;
|
||||
final total = owner + team;
|
||||
if (total <= 0) {
|
||||
return Container(
|
||||
height: height,
|
||||
@@ -179,9 +179,9 @@ class ProfitSharingBar extends StatelessWidget {
|
||||
height: height,
|
||||
child: Row(
|
||||
children: [
|
||||
_segment(purchase, ProfitSharingColor.purchase),
|
||||
_segment(owner, ProfitSharingColor.owner),
|
||||
_segment(team, ProfitSharingColor.team),
|
||||
_segment(100 - total, AppColor.borderLight),
|
||||
].whereType<Widget>().toList(),
|
||||
),
|
||||
),
|
||||
|
||||
@@ -4,9 +4,10 @@ import '../../../../common/extension/extension.dart';
|
||||
import '../../../../common/theme/theme.dart';
|
||||
import '../../../../domain/analytic/analytic.dart';
|
||||
import '../../../components/spacer/spacer.dart';
|
||||
import 'profit_sharing_allocation.dart';
|
||||
import 'profit_sharing_status.dart';
|
||||
|
||||
/// Ringkasan omzet & SDL per kategori induk (parent category).
|
||||
/// Ringkasan porsi tim (omzet − SDL) per kategori induk (parent category).
|
||||
/// Tiap kartu bisa diketuk untuk melihat rincian sub kategori & produknya.
|
||||
class ProfitSharingCategories extends StatelessWidget {
|
||||
final List<ProfitSharingCategory> categories;
|
||||
@@ -21,9 +22,9 @@ class ProfitSharingCategories extends StatelessWidget {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final sorted = [...categories]
|
||||
..sort((a, b) => b.totalRevenue.compareTo(a.totalRevenue));
|
||||
..sort((a, b) => b.teamShare.compareTo(a.teamShare));
|
||||
|
||||
final totalRevenue = sorted.fold<int>(0, (sum, e) => sum + e.totalRevenue);
|
||||
final totalTeam = sorted.fold<int>(0, (sum, e) => sum + e.teamShare);
|
||||
|
||||
return Container(
|
||||
margin: const EdgeInsets.symmetric(horizontal: 16),
|
||||
@@ -75,9 +76,7 @@ class ProfitSharingCategories extends StatelessWidget {
|
||||
child: _CategoryCard(
|
||||
rank: i + 1,
|
||||
category: sorted[i],
|
||||
share: totalRevenue == 0
|
||||
? 0
|
||||
: sorted[i].totalRevenue / totalRevenue,
|
||||
share: totalTeam == 0 ? 0 : sorted[i].teamShare / totalTeam,
|
||||
onTap: () => onCategoryTap(sorted[i]),
|
||||
),
|
||||
),
|
||||
@@ -106,18 +105,11 @@ class _CategoryCard extends StatelessWidget {
|
||||
Widget build(BuildContext context) {
|
||||
final isTop = rank == 1;
|
||||
|
||||
return Material(
|
||||
color: AppColor.background,
|
||||
borderRadius: BorderRadius.circular(14),
|
||||
return ProfitSharingItemCard(
|
||||
child: InkWell(
|
||||
onTap: onTap,
|
||||
borderRadius: BorderRadius.circular(14),
|
||||
child: Container(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(14),
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(14),
|
||||
border: Border.all(color: AppColor.borderLight),
|
||||
),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
@@ -188,7 +180,7 @@ class _CategoryCard extends StatelessWidget {
|
||||
children: [
|
||||
Expanded(
|
||||
child: Text(
|
||||
category.totalRevenue.currencyFormatRp,
|
||||
category.teamShare.currencyFormatRp,
|
||||
style: AppStyle.xl.copyWith(
|
||||
color: AppColor.textPrimary,
|
||||
fontWeight: FontWeight.w800,
|
||||
@@ -204,21 +196,53 @@ class _CategoryCard extends StatelessWidget {
|
||||
vertical: 3,
|
||||
),
|
||||
decoration: BoxDecoration(
|
||||
color: AppColor.primary.withOpacity(0.08),
|
||||
color: AppColor.primary,
|
||||
borderRadius: BorderRadius.circular(20),
|
||||
),
|
||||
child: Text(
|
||||
formatPercent(share * 100),
|
||||
style: AppStyle.xs.copyWith(
|
||||
color: AppColor.primary,
|
||||
fontWeight: FontWeight.w700,
|
||||
color: AppColor.textWhite,
|
||||
fontWeight: FontWeight.w800,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
|
||||
const SpaceHeight(8),
|
||||
const SpaceHeight(4),
|
||||
|
||||
Row(
|
||||
children: [
|
||||
Text(
|
||||
context.lang.share_team,
|
||||
style: AppStyle.xs.copyWith(
|
||||
color: ProfitSharingColor.team,
|
||||
fontWeight: FontWeight.w700,
|
||||
),
|
||||
),
|
||||
const SpaceWidth(4),
|
||||
ProfitSharingFeeBadge(
|
||||
feePercent: category.teamPercent,
|
||||
color: ProfitSharingColor.team,
|
||||
),
|
||||
const SpaceWidth(6),
|
||||
Expanded(
|
||||
child: Text(
|
||||
context.lang.from_revenue(
|
||||
category.totalRevenue.currencyFormatRp,
|
||||
),
|
||||
style: AppStyle.xs.copyWith(
|
||||
color: AppColor.textSecondary,
|
||||
),
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
|
||||
const SpaceHeight(10),
|
||||
|
||||
ClipRRect(
|
||||
borderRadius: BorderRadius.circular(6),
|
||||
@@ -232,15 +256,8 @@ class _CategoryCard extends StatelessWidget {
|
||||
),
|
||||
),
|
||||
|
||||
const SpaceHeight(12),
|
||||
|
||||
ProfitSharingSdlStrip(
|
||||
amount: category.sdl,
|
||||
feePercent: category.ownerFeePercent,
|
||||
),
|
||||
|
||||
if (category.hasTopProduct) ...[
|
||||
const SpaceHeight(10),
|
||||
const SpaceHeight(12),
|
||||
_TopProduct(product: category.topProduct),
|
||||
],
|
||||
],
|
||||
@@ -333,7 +350,7 @@ class _TotalCard extends StatelessWidget {
|
||||
0,
|
||||
(sum, e) => sum + e.totalRevenue,
|
||||
);
|
||||
final totalSdl = categories.fold<int>(0, (sum, e) => sum + e.sdl);
|
||||
final totalTeam = categories.fold<int>(0, (sum, e) => sum + e.teamShare);
|
||||
|
||||
return Container(
|
||||
padding: const EdgeInsets.all(16),
|
||||
@@ -349,7 +366,8 @@ class _TotalCard extends StatelessWidget {
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
context.lang.grand_total,
|
||||
'${context.lang.grand_total} '
|
||||
'${context.lang.share_team.toUpperCase()}',
|
||||
style: AppStyle.xs.copyWith(
|
||||
color: AppColor.textWhite.withOpacity(0.8),
|
||||
fontWeight: FontWeight.w700,
|
||||
@@ -358,7 +376,7 @@ class _TotalCard extends StatelessWidget {
|
||||
),
|
||||
const SizedBox(height: 4),
|
||||
Text(
|
||||
totalRevenue.currencyFormatRp,
|
||||
totalTeam.currencyFormatRp,
|
||||
style: AppStyle.xl.copyWith(
|
||||
color: AppColor.textWhite,
|
||||
fontWeight: FontWeight.w900,
|
||||
@@ -369,8 +387,8 @@ class _TotalCard extends StatelessWidget {
|
||||
|
||||
ProfitSharingHeaderStats(
|
||||
stats: [
|
||||
(context.lang.revenue, totalRevenue.currencyFormatRp),
|
||||
(context.lang.qty, totalQuantity.thousandFormat),
|
||||
(context.lang.sdl, totalSdl.currencyFormatRp),
|
||||
],
|
||||
),
|
||||
],
|
||||
|
||||
@@ -7,6 +7,7 @@ import '../../../../domain/analytic/analytic.dart';
|
||||
import '../../../components/spacer/spacer.dart';
|
||||
import 'period_label.dart';
|
||||
import 'profit_sharing_allocation.dart';
|
||||
import 'profit_sharing_status.dart';
|
||||
|
||||
class ProfitSharingPeriods extends StatelessWidget {
|
||||
final ProfitSharingLoaderState state;
|
||||
@@ -151,78 +152,73 @@ class _PeriodCard extends StatelessWidget {
|
||||
? '${formatPeriodLabel(period.periodStart, period.periodEnd)} - ${context.lang.week_count(period.weekCount)}'
|
||||
: context.lang.order_count_label(period.orderCount);
|
||||
|
||||
return Container(
|
||||
padding: const EdgeInsets.all(16),
|
||||
decoration: BoxDecoration(
|
||||
color: AppColor.background,
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
border: Border.all(color: AppColor.borderLight),
|
||||
),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
title,
|
||||
style: AppStyle.md.copyWith(
|
||||
fontWeight: FontWeight.w700,
|
||||
color: AppColor.textPrimary,
|
||||
return ProfitSharingItemCard(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(16),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
title,
|
||||
style: AppStyle.md.copyWith(
|
||||
fontWeight: FontWeight.w700,
|
||||
color: AppColor.textPrimary,
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 2),
|
||||
Text(
|
||||
subtitle,
|
||||
style: AppStyle.xs.copyWith(
|
||||
color: AppColor.textSecondary,
|
||||
const SizedBox(height: 2),
|
||||
Text(
|
||||
subtitle,
|
||||
style: AppStyle.xs.copyWith(
|
||||
color: AppColor.textSecondary,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
const SpaceWidth(8),
|
||||
Text(
|
||||
period.revenue.currencyFormatRp,
|
||||
style: AppStyle.md.copyWith(
|
||||
fontWeight: FontWeight.w800,
|
||||
color: AppColor.primary,
|
||||
const SpaceWidth(8),
|
||||
Text(
|
||||
period.revenue.currencyFormatRp,
|
||||
style: AppStyle.md.copyWith(
|
||||
fontWeight: FontWeight.w800,
|
||||
color: AppColor.primary,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
|
||||
const SpaceHeight(12),
|
||||
const SpaceHeight(12),
|
||||
|
||||
ProfitSharingBar(
|
||||
purchase: percentages.purchase,
|
||||
owner: period.hasRevenue ? period.sdlPercentage : percentages.owner,
|
||||
team: percentages.team,
|
||||
height: 6,
|
||||
),
|
||||
ProfitSharingBar(
|
||||
owner: period.hasRevenue
|
||||
? period.sdlPercentage
|
||||
: percentages.owner,
|
||||
team: period.hasRevenue
|
||||
? period.teamPercentage
|
||||
: percentages.team,
|
||||
height: 6,
|
||||
),
|
||||
|
||||
const SpaceHeight(12),
|
||||
const SpaceHeight(12),
|
||||
|
||||
Row(
|
||||
children: [
|
||||
_share(
|
||||
context.lang.share_purchase,
|
||||
period.limitPurchase,
|
||||
ProfitSharingColor.purchase,
|
||||
),
|
||||
_share(context.lang.sdl, period.sdl, ProfitSharingColor.owner),
|
||||
_share(
|
||||
context.lang.share_team,
|
||||
period.limitTeam,
|
||||
ProfitSharingColor.team,
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
Row(
|
||||
children: [
|
||||
_share(context.lang.sdl, period.sdl, ProfitSharingColor.owner),
|
||||
_share(
|
||||
context.lang.share_team,
|
||||
period.limitTeam,
|
||||
ProfitSharingColor.team,
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,283 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import '../../../../common/extension/extension.dart';
|
||||
import '../../../../common/theme/theme.dart';
|
||||
import '../../../components/spacer/spacer.dart';
|
||||
import 'profit_sharing_allocation.dart';
|
||||
import 'profit_sharing_status.dart';
|
||||
|
||||
/// Satu baris rincian SDL, bisa per kategori induk atau per periode.
|
||||
class ProfitSharingSdlItem {
|
||||
final String label;
|
||||
final int sdl;
|
||||
final int revenue;
|
||||
final double? feePercent;
|
||||
|
||||
const ProfitSharingSdlItem({
|
||||
required this.label,
|
||||
required this.sdl,
|
||||
required this.revenue,
|
||||
this.feePercent,
|
||||
});
|
||||
}
|
||||
|
||||
/// Section khusus SDL (bagian owner): total di blok hijau, lalu rinciannya.
|
||||
/// Urutan [items] ditampilkan apa adanya.
|
||||
class ProfitSharingSdlSection extends StatelessWidget {
|
||||
final int sdl;
|
||||
final int revenue;
|
||||
|
||||
/// Persen di blok total: fee owner kalau satu kategori, atau persen
|
||||
/// efektif terhadap omzet kalau gabungan beberapa kategori.
|
||||
final double percent;
|
||||
final String breakdownTitle;
|
||||
final List<ProfitSharingSdlItem> items;
|
||||
|
||||
const ProfitSharingSdlSection({
|
||||
super.key,
|
||||
required this.sdl,
|
||||
required this.revenue,
|
||||
required this.percent,
|
||||
required this.breakdownTitle,
|
||||
this.items = const [],
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final totalSdl = items.fold<int>(0, (sum, e) => sum + e.sdl);
|
||||
|
||||
return Container(
|
||||
margin: const EdgeInsets.symmetric(horizontal: 16),
|
||||
padding: const EdgeInsets.all(20),
|
||||
decoration: BoxDecoration(
|
||||
color: AppColor.white,
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: AppColor.textLight.withOpacity(0.08),
|
||||
spreadRadius: 1,
|
||||
blurRadius: 10,
|
||||
offset: const Offset(0, 2),
|
||||
),
|
||||
],
|
||||
),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
context.lang.sdl_summary,
|
||||
style: AppStyle.lg.copyWith(
|
||||
fontWeight: FontWeight.w700,
|
||||
color: AppColor.textPrimary,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 2),
|
||||
Text(
|
||||
context.lang.sdl_desc,
|
||||
style: AppStyle.xs.copyWith(color: AppColor.textSecondary),
|
||||
),
|
||||
|
||||
const SpaceHeight(16),
|
||||
|
||||
_TotalBlock(sdl: sdl, revenue: revenue, percent: percent),
|
||||
|
||||
if (items.isNotEmpty) ...[
|
||||
const SpaceHeight(20),
|
||||
Text(
|
||||
breakdownTitle,
|
||||
style: AppStyle.md.copyWith(
|
||||
fontWeight: FontWeight.w700,
|
||||
color: AppColor.textPrimary,
|
||||
),
|
||||
),
|
||||
const SpaceHeight(12),
|
||||
for (var i = 0; i < items.length; i++) ...[
|
||||
if (i > 0) const Divider(height: 24, color: AppColor.borderLight),
|
||||
_SdlRow(
|
||||
item: items[i],
|
||||
share: totalSdl == 0 ? 0 : items[i].sdl / totalSdl,
|
||||
),
|
||||
],
|
||||
],
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _TotalBlock extends StatelessWidget {
|
||||
final int sdl;
|
||||
final int revenue;
|
||||
final double percent;
|
||||
|
||||
const _TotalBlock({
|
||||
required this.sdl,
|
||||
required this.revenue,
|
||||
required this.percent,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
const color = ProfitSharingColor.owner;
|
||||
|
||||
return Container(
|
||||
width: double.infinity,
|
||||
padding: const EdgeInsets.all(16),
|
||||
decoration: BoxDecoration(
|
||||
gradient: LinearGradient(
|
||||
colors: [color, darken(color)],
|
||||
begin: Alignment.topLeft,
|
||||
end: Alignment.bottomRight,
|
||||
),
|
||||
borderRadius: BorderRadius.circular(14),
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: color.withOpacity(0.3),
|
||||
blurRadius: 10,
|
||||
offset: const Offset(0, 4),
|
||||
),
|
||||
],
|
||||
),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
Container(
|
||||
width: 36,
|
||||
height: 36,
|
||||
decoration: BoxDecoration(
|
||||
color: AppColor.textWhite.withOpacity(0.2),
|
||||
borderRadius: BorderRadius.circular(10),
|
||||
),
|
||||
child: const Icon(
|
||||
Icons.account_balance_wallet_rounded,
|
||||
color: AppColor.textWhite,
|
||||
size: 20,
|
||||
),
|
||||
),
|
||||
const SpaceWidth(10),
|
||||
Expanded(
|
||||
child: Text(
|
||||
context.lang.sdl,
|
||||
style: AppStyle.md.copyWith(
|
||||
color: AppColor.textWhite,
|
||||
fontWeight: FontWeight.w700,
|
||||
),
|
||||
),
|
||||
),
|
||||
ProfitSharingFeeBadge(
|
||||
feePercent: percent,
|
||||
onDarkBackground: true,
|
||||
),
|
||||
],
|
||||
),
|
||||
const SpaceHeight(14),
|
||||
FittedBox(
|
||||
fit: BoxFit.scaleDown,
|
||||
alignment: Alignment.centerLeft,
|
||||
child: Text(
|
||||
sdl.currencyFormatRp,
|
||||
style: AppStyle.h3.copyWith(
|
||||
color: AppColor.textWhite,
|
||||
fontWeight: FontWeight.w900,
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 4),
|
||||
Text(
|
||||
context.lang.from_revenue(revenue.currencyFormatRp),
|
||||
style: AppStyle.sm.copyWith(
|
||||
color: AppColor.textWhite.withOpacity(0.85),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _SdlRow extends StatelessWidget {
|
||||
final ProfitSharingSdlItem item;
|
||||
|
||||
/// Porsi baris ini terhadap total SDL di daftar (0..1).
|
||||
final double share;
|
||||
|
||||
const _SdlRow({required this.item, required this.share});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final amountColor = darken(ProfitSharingColor.owner);
|
||||
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: Row(
|
||||
children: [
|
||||
Flexible(
|
||||
child: Text(
|
||||
item.label,
|
||||
style: AppStyle.md.copyWith(
|
||||
color: AppColor.textPrimary,
|
||||
fontWeight: FontWeight.w700,
|
||||
),
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
),
|
||||
if (item.feePercent != null) ...[
|
||||
const SpaceWidth(6),
|
||||
ProfitSharingFeeBadge(feePercent: item.feePercent!),
|
||||
],
|
||||
],
|
||||
),
|
||||
),
|
||||
const SpaceWidth(8),
|
||||
Text(
|
||||
item.sdl.currencyFormatRp,
|
||||
style: AppStyle.md.copyWith(
|
||||
color: amountColor,
|
||||
fontWeight: FontWeight.w800,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SpaceHeight(8),
|
||||
ClipRRect(
|
||||
borderRadius: BorderRadius.circular(6),
|
||||
child: LinearProgressIndicator(
|
||||
value: share.clamp(0.0, 1.0),
|
||||
minHeight: 6,
|
||||
backgroundColor: AppColor.border,
|
||||
valueColor: const AlwaysStoppedAnimation<Color>(
|
||||
ProfitSharingColor.owner,
|
||||
),
|
||||
),
|
||||
),
|
||||
const SpaceHeight(6),
|
||||
Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: Text(
|
||||
context.lang.from_revenue(item.revenue.currencyFormatRp),
|
||||
style: AppStyle.xs.copyWith(color: AppColor.textSecondary),
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
),
|
||||
Text(
|
||||
formatPercent(share * 100),
|
||||
style: AppStyle.xs.copyWith(
|
||||
color: amountColor,
|
||||
fontWeight: FontWeight.w700,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -18,10 +18,16 @@ String formatPercent(double value) => '${value.toStringAsFixed(1)}%';
|
||||
String formatFeePercent(double value) =>
|
||||
'${value.toStringAsFixed(value % 1 == 0 ? 0 : 1)}%';
|
||||
|
||||
/// Versi lebih gelap dari [color], dipakai sebagai ujung gradien.
|
||||
Color darken(Color color, [double amount = 0.2]) =>
|
||||
Color.lerp(color, AppColor.black, amount)!;
|
||||
|
||||
Color statusColor(double realHppPercentage) {
|
||||
if (realHppPercentage <= HppThreshold.healthy) return AppColor.success;
|
||||
if (realHppPercentage <= HppThreshold.watch) return AppColor.warning;
|
||||
return AppColor.error;
|
||||
if (realHppPercentage <= HppThreshold.healthy) {
|
||||
return ProfitSharingColor.healthy;
|
||||
}
|
||||
if (realHppPercentage <= HppThreshold.watch) return ProfitSharingColor.watch;
|
||||
return ProfitSharingColor.critical;
|
||||
}
|
||||
|
||||
String statusLabel(BuildContext context, double realHppPercentage) {
|
||||
@@ -47,97 +53,95 @@ class ProfitSharingStatusBadge extends StatelessWidget {
|
||||
Widget build(BuildContext context) {
|
||||
final color = statusColor(realHppPercentage);
|
||||
|
||||
// Di atas header merah, pill putih supaya status merah tidak tenggelam.
|
||||
return Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 4),
|
||||
decoration: BoxDecoration(
|
||||
color: onDarkBackground
|
||||
? AppColor.textWhite.withOpacity(0.18)
|
||||
: color.withOpacity(0.12),
|
||||
color: onDarkBackground ? AppColor.white : color,
|
||||
borderRadius: BorderRadius.circular(20),
|
||||
border: onDarkBackground
|
||||
? Border.all(color: AppColor.textWhite.withOpacity(0.3))
|
||||
: null,
|
||||
),
|
||||
child: Text(
|
||||
statusLabel(context, realHppPercentage),
|
||||
style: AppStyle.xs.copyWith(
|
||||
color: onDarkBackground ? AppColor.textWhite : color,
|
||||
fontWeight: FontWeight.w700,
|
||||
color: onDarkBackground ? color : AppColor.textWhite,
|
||||
fontWeight: FontWeight.w800,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// Strip sorotan SDL: label + persen fee owner di kiri, nominal di kanan.
|
||||
class ProfitSharingSdlStrip extends StatelessWidget {
|
||||
final int amount;
|
||||
final double? feePercent;
|
||||
/// Kartu item di dalam section: latar putih, garis aksen berwarna di kiri.
|
||||
/// Child boleh berisi InkWell; ripple-nya ikut terpotong sudut kartu.
|
||||
class ProfitSharingItemCard extends StatelessWidget {
|
||||
final Widget child;
|
||||
final Color accentColor;
|
||||
|
||||
const ProfitSharingSdlStrip({
|
||||
const ProfitSharingItemCard({
|
||||
super.key,
|
||||
required this.amount,
|
||||
this.feePercent,
|
||||
required this.child,
|
||||
this.accentColor = AppColor.primary,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
const color = ProfitSharingColor.owner;
|
||||
|
||||
return Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 10),
|
||||
clipBehavior: Clip.antiAlias,
|
||||
decoration: BoxDecoration(
|
||||
color: color.withOpacity(0.08),
|
||||
borderRadius: BorderRadius.circular(10),
|
||||
border: Border.all(color: color.withOpacity(0.2)),
|
||||
),
|
||||
child: Row(
|
||||
children: [
|
||||
const Icon(
|
||||
Icons.account_balance_wallet_rounded,
|
||||
color: color,
|
||||
size: 18,
|
||||
),
|
||||
const SpaceWidth(8),
|
||||
Text(
|
||||
context.lang.sdl,
|
||||
style: AppStyle.sm.copyWith(
|
||||
color: AppColor.textPrimary,
|
||||
fontWeight: FontWeight.w600,
|
||||
),
|
||||
),
|
||||
if (feePercent != null) ...[
|
||||
const SpaceWidth(6),
|
||||
Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 6, vertical: 2),
|
||||
decoration: BoxDecoration(
|
||||
color: color,
|
||||
borderRadius: BorderRadius.circular(6),
|
||||
),
|
||||
child: Text(
|
||||
formatFeePercent(feePercent!),
|
||||
style: AppStyle.xs.copyWith(
|
||||
color: AppColor.textWhite,
|
||||
fontWeight: FontWeight.w700,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
const SpaceWidth(8),
|
||||
Expanded(
|
||||
child: Text(
|
||||
amount.currencyFormatRp,
|
||||
textAlign: TextAlign.right,
|
||||
style: AppStyle.md.copyWith(
|
||||
color: AppColor.secondaryDark,
|
||||
fontWeight: FontWeight.w800,
|
||||
),
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
color: AppColor.white,
|
||||
borderRadius: BorderRadius.circular(14),
|
||||
border: Border.all(color: AppColor.border),
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: AppColor.black.withOpacity(0.05),
|
||||
blurRadius: 8,
|
||||
offset: const Offset(0, 2),
|
||||
),
|
||||
],
|
||||
),
|
||||
child: Stack(
|
||||
children: [
|
||||
Material(type: MaterialType.transparency, child: child),
|
||||
Positioned(
|
||||
left: 0,
|
||||
top: 0,
|
||||
bottom: 0,
|
||||
child: Container(width: 4, color: accentColor),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// Pill persen fee owner. Di atas latar berwarna jadi putih supaya terbaca.
|
||||
class ProfitSharingFeeBadge extends StatelessWidget {
|
||||
final double feePercent;
|
||||
final Color color;
|
||||
final bool onDarkBackground;
|
||||
|
||||
const ProfitSharingFeeBadge({
|
||||
super.key,
|
||||
required this.feePercent,
|
||||
this.color = ProfitSharingColor.owner,
|
||||
this.onDarkBackground = false,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 6, vertical: 2),
|
||||
decoration: BoxDecoration(
|
||||
color: onDarkBackground ? AppColor.white : color,
|
||||
borderRadius: BorderRadius.circular(6),
|
||||
),
|
||||
child: Text(
|
||||
formatFeePercent(feePercent),
|
||||
style: AppStyle.xs.copyWith(
|
||||
color: onDarkBackground ? color : AppColor.textWhite,
|
||||
fontWeight: FontWeight.w800,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -205,7 +209,7 @@ class ProfitSharingHeaderStats extends StatelessWidget {
|
||||
}
|
||||
}
|
||||
|
||||
/// Kotak statistik kecil: ikon, nilai, lalu label.
|
||||
/// Kartu statistik kecil berwarna solid: ikon, nilai, lalu label.
|
||||
class ProfitSharingStatTile extends StatelessWidget {
|
||||
final IconData icon;
|
||||
final String label;
|
||||
@@ -225,9 +229,19 @@ class ProfitSharingStatTile extends StatelessWidget {
|
||||
return Container(
|
||||
padding: const EdgeInsets.all(12),
|
||||
decoration: BoxDecoration(
|
||||
color: AppColor.background,
|
||||
gradient: LinearGradient(
|
||||
colors: [color, darken(color)],
|
||||
begin: Alignment.topLeft,
|
||||
end: Alignment.bottomRight,
|
||||
),
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
border: Border.all(color: AppColor.borderLight),
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: color.withOpacity(0.3),
|
||||
blurRadius: 8,
|
||||
offset: const Offset(0, 3),
|
||||
),
|
||||
],
|
||||
),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
@@ -236,25 +250,30 @@ class ProfitSharingStatTile extends StatelessWidget {
|
||||
width: 30,
|
||||
height: 30,
|
||||
decoration: BoxDecoration(
|
||||
color: color.withOpacity(0.1),
|
||||
color: AppColor.textWhite.withOpacity(0.2),
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
),
|
||||
child: Icon(icon, color: color, size: 16),
|
||||
child: Icon(icon, color: AppColor.textWhite, size: 16),
|
||||
),
|
||||
const SpaceHeight(10),
|
||||
Text(
|
||||
value,
|
||||
style: AppStyle.lg.copyWith(
|
||||
color: AppColor.textPrimary,
|
||||
fontWeight: FontWeight.w800,
|
||||
FittedBox(
|
||||
fit: BoxFit.scaleDown,
|
||||
alignment: Alignment.centerLeft,
|
||||
child: Text(
|
||||
value,
|
||||
style: AppStyle.lg.copyWith(
|
||||
color: AppColor.textWhite,
|
||||
fontWeight: FontWeight.w800,
|
||||
),
|
||||
),
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
const SizedBox(height: 2),
|
||||
Text(
|
||||
label,
|
||||
style: AppStyle.xs.copyWith(color: AppColor.textSecondary),
|
||||
style: AppStyle.xs.copyWith(
|
||||
color: AppColor.textWhite.withOpacity(0.85),
|
||||
fontWeight: FontWeight.w600,
|
||||
),
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
|
||||
@@ -99,17 +99,11 @@ class _SubCategoryCard extends StatelessWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Container(
|
||||
decoration: BoxDecoration(
|
||||
color: AppColor.background,
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
border: Border.all(color: AppColor.borderLight),
|
||||
),
|
||||
return ProfitSharingItemCard(
|
||||
child: Column(
|
||||
children: [
|
||||
InkWell(
|
||||
onTap: onToggle,
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(14),
|
||||
child: Column(
|
||||
@@ -155,7 +149,7 @@ class _SubCategoryCard extends StatelessWidget {
|
||||
child: LinearProgressIndicator(
|
||||
value: share.clamp(0.0, 1.0),
|
||||
minHeight: 5,
|
||||
backgroundColor: AppColor.borderLight,
|
||||
backgroundColor: AppColor.border,
|
||||
valueColor: const AlwaysStoppedAnimation<Color>(
|
||||
AppColor.primary,
|
||||
),
|
||||
|
||||
+1
-1
@@ -3,7 +3,7 @@ description: "A new Flutter project."
|
||||
|
||||
publish_to: "none"
|
||||
|
||||
version: 1.0.4+13
|
||||
version: 1.0.4+14
|
||||
|
||||
environment:
|
||||
sdk: ^3.8.1
|
||||
|
||||
Reference in New Issue
Block a user