@autobusal/admin-api-consumers 1.0.0 → 1.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/Browse.tsx CHANGED
@@ -1,6 +1,6 @@
1
1
  import { TFunction } from 'i18next';
2
- import { useSearchParams } from 'react-router-dom';
3
- import { Meta, Table, Row } from '@autobusal/common';
2
+ import { Link, useSearchParams } from 'react-router-dom';
3
+ import { Meta, Table, Row, Button } from '@autobusal/common';
4
4
  import { usePage, useBreadcrumbs } from '@autobusal/hooks';
5
5
  import { DisplayStatus, getParams, success } from '@autobusal/utilities';
6
6
  import { useGetApiConsumers, useDeleteApiConsumer } from './services';
@@ -80,8 +80,13 @@ const Browse = ({ url, t }: Props): JSX.Element => {
80
80
  fetching={ isFetching }
81
81
  t={ t }
82
82
  actions={ [
83
- 'create', 'search', 'view', 'delete'
83
+ 'create', 'search', 'extra', 'view', 'delete'
84
84
  ] }
85
+ extra={
86
+ <Link to={ `${ url }/receivables` }>
87
+ <Button type="button" size="medium" subtype="secondary" noMargin text={ t('admin_api_consumers.receivables.title', { ns: 'common' }) } />
88
+ </Link>
89
+ }
85
90
  handlers={ {
86
91
  delete: onDelete
87
92
  } }
package/CHANGELOG.md CHANGED
@@ -1,9 +1,37 @@
1
1
  # Changelog
2
2
 
3
+ ## 1.2.0
4
+
5
+ ### Added
6
+
7
+ - **Credit limit on the partner form, running balance on receivables.** A
8
+ Mode 1 partner now sells against a running account (obtapi Payments\
9
+ External\Charge): every sale debits the net they owe, cancellations
10
+ credit it back, and recorded settlements raise it. `credit_limit` is the
11
+ floor - 0 means prepaid (deposit before selling), a positive number means
12
+ postpaid down to -that-much. The receivables table shows the live
13
+ `balance` and `headroom` alongside the statement figures, deliberately
14
+ from two books: when they disagree, something needs investigating.
15
+
3
16
  All notable changes to `@autobusal/admin-api-consumers` are documented here.
4
17
  This project adheres to [Keep a Changelog](https://keepachangelog.com/en/1.1.0/)
5
18
  and [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
6
19
 
20
+ ## [1.1.0] - 2026-07-27
21
+
22
+ ### Added
23
+
24
+ - Package 3 of the external-routes plan: **receivables reports + settlements**.
25
+ `Receivables.tsx` is a per-partner dashboard for a selectable period
26
+ (orders/tickets/gross amount/commission/net/settled/outstanding, the
27
+ last two accumulated all-time regardless of the period filter).
28
+ `ReceivablesManage.tsx` is the per-partner drill-down (ticket-level order
29
+ rows, doubling as the CSV/dispute document via a plain browser-download
30
+ link to the backend export endpoint - no frontend CSV generation, same
31
+ convention as `@autobusal/account-reports`), plus a settlements section
32
+ to record/list/delete actual payments received. Backed by obtapi's
33
+ `App\Http\Controllers\ApiConsumers\ReportsController`.
34
+
7
35
  ## [1.0.0] - 2026-07-27
8
36
 
9
37
  ### Added
package/Manage.tsx CHANGED
@@ -131,6 +131,16 @@ const Manage = ({ url, t }: Props): JSX.Element => {
131
131
  type: 'select',
132
132
  value: data?.user.comissions?.unique ?? 0,
133
133
  values: getAllowed(t)
134
+ }, {
135
+ // Claude - 2026-08-22: the floor of the partner's running
136
+ // account. 0 = prepaid (they deposit before they can sell);
137
+ // a positive number = postpaid down to -that-much. "Unlimited"
138
+ // is a big number typed on purpose, never a switch.
139
+ label: t('admin_api_consumers.manage.credit_limit', { ns: 'common' }),
140
+ name: 'credit_limit',
141
+ type: 'text',
142
+ value: data?.credit_limit ?? 0,
143
+ rules: 'required|numeric'
134
144
  }, {
135
145
  label: t('admin_api_consumers.manage.status', { ns: 'common' }),
136
146
  name: 'status',
@@ -0,0 +1,96 @@
1
+ import { useState } from 'react';
2
+ import { TFunction } from 'i18next';
3
+ import { Link } from 'react-router-dom';
4
+ import { Meta, Box } from '@autobusal/common';
5
+ import { usePage, useBreadcrumbs } from '@autobusal/hooks';
6
+ import { Table, Filters } from './styles';
7
+ import { useGetReceivables } from './services';
8
+
9
+ interface Props {
10
+ url: string
11
+ t: TFunction<'common'>
12
+ }
13
+
14
+ const Receivables = ({ url, t }: Props): JSX.Element => {
15
+ usePage('api_consumers');
16
+
17
+ useBreadcrumbs(t('admin_api_consumers.receivables.title', { ns: 'common' }), [{ name: t('admin_api_consumers.browse.title', { ns: 'common' }), url }]);
18
+
19
+ const [ from, setFrom ] = useState<string>('');
20
+ const [ to, setTo ] = useState<string>('');
21
+
22
+ const { data, isLoading } = useGetReceivables({ from: from || undefined, to: to || undefined });
23
+
24
+ return (
25
+ <Meta title={ t('admin_api_consumers.receivables.title', { ns: 'common' }) }>
26
+ <h1>{ t('admin_api_consumers.receivables.title', { ns: 'common' }) }</h1>
27
+
28
+ <Filters>
29
+ <div>
30
+ <label>{ t('admin_api_consumers.receivables.from', { ns: 'common' }) }</label>
31
+ <input type="date" value={ from } onChange={ (e) => setFrom(e.target.value) } />
32
+ </div>
33
+
34
+ <div>
35
+ <label>{ t('admin_api_consumers.receivables.to', { ns: 'common' }) }</label>
36
+ <input type="date" value={ to } onChange={ (e) => setTo(e.target.value) } />
37
+ </div>
38
+ </Filters>
39
+
40
+ { isLoading ? <Box /> : (
41
+ <div className="box">
42
+ <Table>
43
+ <thead>
44
+ <tr>
45
+ <th>{ t('admin_api_consumers.receivables.company', { ns: 'common' }) }</th>
46
+ <th>{ t('admin_api_consumers.receivables.orders', { ns: 'common' }) }</th>
47
+ <th>{ t('admin_api_consumers.receivables.tickets', { ns: 'common' }) }</th>
48
+ <th>{ t('admin_api_consumers.receivables.amount', { ns: 'common' }) }</th>
49
+ <th>{ t('admin_api_consumers.receivables.commission', { ns: 'common' }) }</th>
50
+ <th>{ t('admin_api_consumers.receivables.net', { ns: 'common' }) }</th>
51
+ <th>{ t('admin_api_consumers.receivables.settled', { ns: 'common' }) }</th>
52
+ <th>{ t('admin_api_consumers.receivables.outstanding', { ns: 'common' }) }</th>
53
+ <th>{ t('admin_api_consumers.receivables.balance', { ns: 'common' }) }</th>
54
+ <th>{ t('admin_api_consumers.receivables.headroom', { ns: 'common' }) }</th>
55
+ <th />
56
+ </tr>
57
+ </thead>
58
+
59
+ <tbody>
60
+ { data?.map(row => (
61
+ <tr key={ row.agent_id }>
62
+ <td>{ row.company }</td>
63
+ <td>{ row.orders }</td>
64
+ <td>{ row.tickets }</td>
65
+ <td>{ row.amount.toFixed(2) }</td>
66
+ <td>{ row.commission.toFixed(2) }</td>
67
+ <td>{ row.net.toFixed(2) }</td>
68
+ <td>{ row.settled.toFixed(2) }</td>
69
+ <td>{ row.outstanding.toFixed(2) }</td>
70
+ { /* the running account: negative is what they owe right
71
+ now; headroom is what they can still sell before
72
+ their credit floor refuses them */ }
73
+ <td>{ row.balance.toFixed(2) }</td>
74
+ <td>{ row.headroom.toFixed(2) }</td>
75
+ <td>
76
+ <Link to={ `${ url }/receivables/${ row.agent_id }?from=${ from }&to=${ to }` }>
77
+ { t('admin_api_consumers.receivables.view', { ns: 'common' }) }
78
+ </Link>
79
+ </td>
80
+ </tr>
81
+ )) }
82
+
83
+ { data?.length === 0 && (
84
+ <tr>
85
+ <td colSpan={ 9 }>{ t('admin_api_consumers.receivables.empty', { ns: 'common' }) }</td>
86
+ </tr>
87
+ ) }
88
+ </tbody>
89
+ </Table>
90
+ </div>
91
+ ) }
92
+ </Meta>
93
+ );
94
+ };
95
+
96
+ export default Receivables;
@@ -0,0 +1,207 @@
1
+ import { useState } from 'react';
2
+ import { TFunction } from 'i18next';
3
+ import { useParams, useSearchParams } from 'react-router-dom';
4
+ import { Meta, BackWithTitle, Button, Box } from '@autobusal/common';
5
+ import { usePage, useBreadcrumbs } from '@autobusal/hooks';
6
+ import { success } from '@autobusal/utilities';
7
+ import { Table, SectionTitle, Filters } from './styles';
8
+ import { useGetReceivableDrilldown, downloadReceivablesExport, useGetSettlements, useStoreSettlement, useDeleteSettlement } from './services';
9
+
10
+ interface Props {
11
+ url: string
12
+ t: TFunction<'common'>
13
+ }
14
+
15
+ const ReceivablesManage = ({ url, t }: Props): JSX.Element => {
16
+ const params = useParams();
17
+ const [ searchParams ] = useSearchParams();
18
+
19
+ const agentId = Number(params.agent_id);
20
+ const from = searchParams.get('from') || undefined;
21
+ const to = searchParams.get('to') || undefined;
22
+
23
+ usePage('api_consumers');
24
+
25
+ useBreadcrumbs(t('admin_api_consumers.receivables.drilldown_title', { ns: 'common' }), [
26
+ { name: t('admin_api_consumers.browse.title', { ns: 'common' }), url },
27
+ { name: t('admin_api_consumers.receivables.title', { ns: 'common' }), url: `${ url }/receivables` }
28
+ ]);
29
+
30
+ const { data, isLoading } = useGetReceivableDrilldown({ agent_id: agentId, from, to });
31
+ const { data: settlements, isLoading: isLoadingSettlements, refetch: refetchSettlements } = useGetSettlements(agentId);
32
+
33
+ const { mutate: StoreSettlement, isPending } = useStoreSettlement();
34
+ const { mutate: DeleteSettlement } = useDeleteSettlement();
35
+
36
+ const [ isExporting, setIsExporting ] = useState<boolean>(false);
37
+
38
+ const onExport = (): void => {
39
+ setIsExporting(true);
40
+
41
+ downloadReceivablesExport({ agent_id: agentId, from, to })
42
+ .finally(() => setIsExporting(false));
43
+ };
44
+
45
+ const [ period, setPeriod ] = useState<string>('');
46
+ const [ amountReceived, setAmountReceived ] = useState<string>('');
47
+ const [ receivedAt, setReceivedAt ] = useState<string>('');
48
+ const [ note, setNote ] = useState<string>('');
49
+
50
+ const onRecordSettlement = (): void => {
51
+ StoreSettlement({
52
+ agent_id: agentId,
53
+ period: period ? `${ period }-01` : '',
54
+ amount_received: amountReceived,
55
+ received_at: receivedAt,
56
+ note
57
+ }, {
58
+ onSuccess: () => {
59
+ success(t('admin_api_consumers.receivables.settlement_saved', { ns: 'common' }));
60
+
61
+ setPeriod('');
62
+ setAmountReceived('');
63
+ setReceivedAt('');
64
+ setNote('');
65
+
66
+ refetchSettlements();
67
+ }
68
+ });
69
+ };
70
+
71
+ const onDeleteSettlement = (id: number): void => {
72
+ if (!window.confirm(t('table.confirm', { ns: 'common' }))) {
73
+ return;
74
+ }
75
+
76
+ DeleteSettlement(id, {
77
+ onSuccess: () => refetchSettlements()
78
+ });
79
+ };
80
+
81
+ return (
82
+ <Meta title={ t('admin_api_consumers.receivables.drilldown_title', { ns: 'common' }) }>
83
+ <BackWithTitle title={ t('admin_api_consumers.receivables.drilldown_title', { ns: 'common' }) } to={ `${ url }/receivables` } t={ t } />
84
+
85
+ <Filters>
86
+ <Button
87
+ type="button"
88
+ size="medium"
89
+ subtype="secondary"
90
+ loading={ isExporting }
91
+ text={ t('admin_api_consumers.receivables.export_csv', { ns: 'common' }) }
92
+ onClick={ onExport }
93
+ />
94
+ </Filters>
95
+
96
+ { isLoading ? <Box /> : (
97
+ <div className="box">
98
+ <Table>
99
+ <thead>
100
+ <tr>
101
+ <th>{ t('admin_api_consumers.receivables.order', { ns: 'common' }) }</th>
102
+ <th>{ t('admin_api_consumers.receivables.sale_date', { ns: 'common' }) }</th>
103
+ <th>{ t('admin_api_consumers.receivables.route', { ns: 'common' }) }</th>
104
+ <th>{ t('admin_api_consumers.receivables.travel_date', { ns: 'common' }) }</th>
105
+ <th>{ t('admin_api_consumers.receivables.amount', { ns: 'common' }) }</th>
106
+ <th>{ t('admin_api_consumers.receivables.commission', { ns: 'common' }) }</th>
107
+ <th>{ t('admin_api_consumers.receivables.net', { ns: 'common' }) }</th>
108
+ </tr>
109
+ </thead>
110
+
111
+ <tbody>
112
+ { data?.data.map(order => (
113
+ <tr key={ order.id }>
114
+ <td>{ order.hash }</td>
115
+ <td>{ order.created_at }</td>
116
+ <td>{ order.route?.name ?? '-' }</td>
117
+ <td>{ order.date_travel ?? '-' }</td>
118
+ <td>{ order.amount }</td>
119
+ <td>{ order.comission_agent }</td>
120
+ <td>{ order.net }</td>
121
+ </tr>
122
+ )) }
123
+
124
+ { data?.data.length === 0 && (
125
+ <tr>
126
+ <td colSpan={ 7 }>{ t('admin_api_consumers.receivables.empty', { ns: 'common' }) }</td>
127
+ </tr>
128
+ ) }
129
+ </tbody>
130
+ </Table>
131
+ </div>
132
+ ) }
133
+
134
+ <SectionTitle>{ t('admin_api_consumers.receivables.settlements', { ns: 'common' }) }</SectionTitle>
135
+
136
+ { isLoadingSettlements ? <Box /> : (
137
+ <div className="box">
138
+ <Table>
139
+ <thead>
140
+ <tr>
141
+ <th>{ t('admin_api_consumers.receivables.period', { ns: 'common' }) }</th>
142
+ <th>{ t('admin_api_consumers.receivables.amount_received', { ns: 'common' }) }</th>
143
+ <th>{ t('admin_api_consumers.receivables.received_at', { ns: 'common' }) }</th>
144
+ <th>{ t('admin_api_consumers.receivables.note', { ns: 'common' }) }</th>
145
+ <th />
146
+ </tr>
147
+ </thead>
148
+
149
+ <tbody>
150
+ { settlements?.map(item => (
151
+ <tr key={ item.id }>
152
+ <td>{ item.period.slice(0, 7) }</td>
153
+ <td>{ item.amount_received }</td>
154
+ <td>{ item.received_at.slice(0, 10) }</td>
155
+ <td>{ item.note ?? '-' }</td>
156
+ <td>
157
+ <button type="button" onClick={ () => onDeleteSettlement(item.id) }>{ t('table.actions.delete', { ns: 'common' }) }</button>
158
+ </td>
159
+ </tr>
160
+ )) }
161
+
162
+ { settlements?.length === 0 && (
163
+ <tr>
164
+ <td colSpan={ 5 }>{ t('admin_api_consumers.receivables.no_settlements', { ns: 'common' }) }</td>
165
+ </tr>
166
+ ) }
167
+ </tbody>
168
+ </Table>
169
+ </div>
170
+ ) }
171
+
172
+ <SectionTitle>{ t('admin_api_consumers.receivables.record_settlement', { ns: 'common' }) }</SectionTitle>
173
+
174
+ <Filters>
175
+ <div>
176
+ <label>{ t('admin_api_consumers.receivables.period', { ns: 'common' }) }</label>
177
+ <input type="month" value={ period } onChange={ (e) => setPeriod(e.target.value) } />
178
+ </div>
179
+
180
+ <div>
181
+ <label>{ t('admin_api_consumers.receivables.amount_received', { ns: 'common' }) }</label>
182
+ <input type="number" step="0.01" value={ amountReceived } onChange={ (e) => setAmountReceived(e.target.value) } />
183
+ </div>
184
+
185
+ <div>
186
+ <label>{ t('admin_api_consumers.receivables.received_at', { ns: 'common' }) }</label>
187
+ <input type="date" value={ receivedAt } onChange={ (e) => setReceivedAt(e.target.value) } />
188
+ </div>
189
+
190
+ <div>
191
+ <label>{ t('admin_api_consumers.receivables.note', { ns: 'common' }) }</label>
192
+ <input type="text" value={ note } onChange={ (e) => setNote(e.target.value) } />
193
+ </div>
194
+
195
+ <Button
196
+ type="button"
197
+ size="medium"
198
+ loading={ isPending }
199
+ text={ t('admin_api_consumers.receivables.save_settlement', { ns: 'common' }) }
200
+ onClick={ onRecordSettlement }
201
+ />
202
+ </Filters>
203
+ </Meta>
204
+ );
205
+ };
206
+
207
+ export default ReceivablesManage;
package/index.ts CHANGED
@@ -1,7 +1,11 @@
1
1
  import Browse from './Browse';
2
2
  import Manage from './Manage';
3
+ import Receivables from './Receivables';
4
+ import ReceivablesManage from './ReceivablesManage';
3
5
 
4
6
  export {
5
7
  Browse,
6
- Manage
8
+ Manage,
9
+ Receivables,
10
+ ReceivablesManage
7
11
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@autobusal/admin-api-consumers",
3
- "version": "1.0.0",
3
+ "version": "1.2.0",
4
4
  "author": "Ferjolt Ozuni",
5
5
  "type": "module",
6
6
  "main": "index.ts"
package/services.ts CHANGED
@@ -3,7 +3,7 @@ import { apiClient } from '@autobusal/providers';
3
3
  import { PaginationData } from '@autobusal/providers/types/pagination';
4
4
  import { TableParamsData } from '@autobusal/providers/types/other';
5
5
  import { DropdownData } from '@autobusal/providers/types/other';
6
- import { ApiConsumerData } from './types';
6
+ import { ApiConsumerData, ReceivableData, ReceivableOrderData, SettlementData } from './types';
7
7
 
8
8
  export const useGetApiConsumers = (params: TableParamsData): UseQueryResult<PaginationData<ApiConsumerData>> => (
9
9
  useQuery({
@@ -144,3 +144,101 @@ export const useRegenerateApiConsumerToken = (): UseMutationResult<{ api_token:
144
144
  )
145
145
  })
146
146
  );
147
+
148
+ // Edited: Ferjolt Ozuni - Date: 2026-07-27
149
+ // Package 3 - API partner receivables reports + settlements
150
+
151
+ export const useGetReceivables = (params: { from?: string, to?: string }): UseQueryResult<ReceivableData[]> => (
152
+ useQuery({
153
+ queryKey: ['admin-api-consumers-receivables', params],
154
+ queryFn: async () => (
155
+ await apiClient
156
+ .get('/api/api_consumers/admin/reports/receivables', { params })
157
+ .then(response => (
158
+ response.data
159
+ ))
160
+ )
161
+ })
162
+ );
163
+
164
+ export const useGetReceivableDrilldown = (params: { agent_id: number, from?: string, to?: string, page?: number }): UseQueryResult<PaginationData<ReceivableOrderData>> => (
165
+ useQuery({
166
+ queryKey: ['admin-api-consumers-drilldown', params],
167
+ queryFn: async () => (
168
+ await apiClient
169
+ .get('/api/api_consumers/admin/reports/drilldown', { params })
170
+ .then(response => (
171
+ response.data
172
+ ))
173
+ )
174
+ })
175
+ );
176
+
177
+ // Edited: Ferjolt Ozuni - Date: 2026-07-27
178
+ // A plain cross-origin `<a href>` straight to obtapi (the
179
+ // @autobusal/account-reports' Downloads.tsx convention this was first
180
+ // modeled on) does NOT authenticate here: a top-level browser navigation
181
+ // never carries the bearer-mode Authorization header (it lives in
182
+ // localStorage, JS-only), and confirmed live there's no separate session
183
+ // cookie backing it either - the export 401'd/500'd depending on whether
184
+ // the browser even sent a Referer. Downloading through `apiClient`
185
+ // instead reuses the SAME authenticated request path as every other call
186
+ // in the app (bearer header or BFF cookie, whichever the brand uses) and
187
+ // works regardless of auth mode.
188
+ export const downloadReceivablesExport = async (params: { agent_id: number, from?: string, to?: string }): Promise<void> => {
189
+ const response = await apiClient.get('/api/api_consumers/admin/reports/export', {
190
+ params,
191
+ responseType: 'blob'
192
+ });
193
+
194
+ const url = window.URL.createObjectURL(new Blob([response.data], { type: 'text/csv' }));
195
+ const link = document.createElement('a');
196
+
197
+ link.href = url;
198
+ link.download = `receivables-${ params.agent_id }.csv`;
199
+
200
+ document.body.appendChild(link);
201
+ link.click();
202
+ document.body.removeChild(link);
203
+
204
+ window.URL.revokeObjectURL(url);
205
+ };
206
+
207
+ export const useGetSettlements = (agentId: number): UseQueryResult<SettlementData[]> => (
208
+ useQuery({
209
+ queryKey: ['admin-api-consumers-settlements', { agentId }],
210
+ queryFn: async () => (
211
+ await apiClient
212
+ .get('/api/api_consumers/admin/reports/settlements', { params: { agent_id: agentId } })
213
+ .then(response => (
214
+ response.data
215
+ ))
216
+ )
217
+ })
218
+ );
219
+
220
+ export const useStoreSettlement = (): UseMutationResult<void, Error, Record<string, any>, unknown> => (
221
+ useMutation({
222
+ mutationKey: ['admin-api-consumers-settlements-store'],
223
+ mutationFn: async (data: Record<string, any>) => (
224
+ await apiClient
225
+ .post('/api/api_consumers/admin/reports/settlements', data)
226
+ .then(response => (
227
+ response.data
228
+ ))
229
+ )
230
+ })
231
+ );
232
+
233
+ export const useDeleteSettlement = (): UseMutationResult<void, Error, number, unknown> => (
234
+ useMutation({
235
+ mutationKey: ['admin-api-consumers-settlements-delete'],
236
+ mutationFn: async (id: number) => (
237
+ await apiClient
238
+ .delete('/api/api_consumers/admin/reports/settlements', { params: { id } })
239
+ .then(response => (
240
+ response.data
241
+ ))
242
+ )
243
+ })
244
+ );
package/styles.ts CHANGED
@@ -29,3 +29,45 @@ export const TokenActions = styled.div`
29
29
  display: flex;
30
30
  gap: 10px;
31
31
  `;
32
+
33
+ // Edited: Ferjolt Ozuni - Date: 2026-07-27
34
+ // Package 3 - receivables/settlements tables + filters, matching
35
+ // @autobusal/admin-external's plain-table approach (this data isn't
36
+ // paginated the same way as the generic <Table>/<Row> components assume)
37
+ export const Table = styled.table`
38
+ width: 100%;
39
+ border-collapse: collapse;
40
+
41
+ th, td {
42
+ text-align: left;
43
+ padding: 10px 12px;
44
+ border-bottom: 1px solid ${ props => props.theme.primary.neutral };
45
+ }
46
+
47
+ th {
48
+ font-size: ${ props => props.theme.size.sm };
49
+ color: ${ props => props.theme.font.faded };
50
+ }
51
+ `;
52
+
53
+ export const Actions = styled.div`
54
+ display: flex;
55
+ gap: 10px;
56
+ `;
57
+
58
+ export const SectionTitle = styled.h2`
59
+ margin: 25px 0 10px;
60
+ `;
61
+
62
+ export const Filters = styled.div`
63
+ display: flex;
64
+ gap: 15px;
65
+ align-items: flex-end;
66
+ flex-wrap: wrap;
67
+ margin-bottom: 20px;
68
+
69
+ input {
70
+ padding: 8px 10px;
71
+ border-radius: 6px;
72
+ }
73
+ `;
package/types.ts CHANGED
@@ -6,6 +6,10 @@ export interface ApiConsumerData {
6
6
  operators: (number | string)[]
7
7
  external_providers: (number | string)[]
8
8
  is_api_consumer: boolean
9
+ // Claude - 2026-08-22: the running account and its floor - see
10
+ // ReceivableData below for what they mean
11
+ funds?: number
12
+ credit_limit?: number
9
13
  user: {
10
14
  id: number
11
15
  email: string
@@ -17,3 +21,48 @@ export interface ApiConsumerData {
17
21
  }
18
22
  }
19
23
  }
24
+
25
+ // Edited: Ferjolt Ozuni - Date: 2026-07-27
26
+ // Package 3 - API partner receivables reports + settlements
27
+ export interface ReceivableData {
28
+ agent_id: number
29
+ company: string
30
+ email: string
31
+ orders: number
32
+ tickets: number
33
+ amount: number
34
+ commission: number
35
+ net: number
36
+ settled: number
37
+ outstanding: number
38
+ // Claude - 2026-08-22: the LIVE running account (debited per sale,
39
+ // credited by settlements) and its floor - alongside the statement
40
+ // figures, which describe the same debt from the frozen columns. When
41
+ // balance and -outstanding disagree, something needs investigating.
42
+ balance: number
43
+ credit_limit: number
44
+ headroom: number
45
+ }
46
+
47
+ export interface ReceivableOrderData {
48
+ id: number
49
+ hash: string
50
+ tickets: number
51
+ date_travel: string
52
+ amount: number
53
+ comission_agent: number
54
+ net: number
55
+ created_at: string
56
+ route?: { id: number, name: string }
57
+ passengers?: { id: number, first_name: string, last_name: string, bus_seat?: number }[]
58
+ }
59
+
60
+ export interface SettlementData {
61
+ id: number
62
+ api_user_id: number
63
+ period: string
64
+ amount_received: string
65
+ received_at: string
66
+ note?: string
67
+ created_at: string
68
+ }