@autobusal/admin-api-consumers 1.0.0 → 1.1.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 +8 -3
- package/CHANGELOG.md +15 -0
- package/Receivables.tsx +89 -0
- package/ReceivablesManage.tsx +207 -0
- package/index.ts +5 -1
- package/package.json +1 -1
- package/services.ts +99 -1
- package/styles.ts +42 -0
- package/types.ts +38 -0
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
|
@@ -4,6 +4,21 @@ All notable changes to `@autobusal/admin-api-consumers` are documented here.
|
|
|
4
4
|
This project adheres to [Keep a Changelog](https://keepachangelog.com/en/1.1.0/)
|
|
5
5
|
and [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
6
6
|
|
|
7
|
+
## [1.1.0] - 2026-07-27
|
|
8
|
+
|
|
9
|
+
### Added
|
|
10
|
+
|
|
11
|
+
- Package 3 of the external-routes plan: **receivables reports + settlements**.
|
|
12
|
+
`Receivables.tsx` is a per-partner dashboard for a selectable period
|
|
13
|
+
(orders/tickets/gross amount/commission/net/settled/outstanding, the
|
|
14
|
+
last two accumulated all-time regardless of the period filter).
|
|
15
|
+
`ReceivablesManage.tsx` is the per-partner drill-down (ticket-level order
|
|
16
|
+
rows, doubling as the CSV/dispute document via a plain browser-download
|
|
17
|
+
link to the backend export endpoint - no frontend CSV generation, same
|
|
18
|
+
convention as `@autobusal/account-reports`), plus a settlements section
|
|
19
|
+
to record/list/delete actual payments received. Backed by obtapi's
|
|
20
|
+
`App\Http\Controllers\ApiConsumers\ReportsController`.
|
|
21
|
+
|
|
7
22
|
## [1.0.0] - 2026-07-27
|
|
8
23
|
|
|
9
24
|
### Added
|
package/Receivables.tsx
ADDED
|
@@ -0,0 +1,89 @@
|
|
|
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 />
|
|
54
|
+
</tr>
|
|
55
|
+
</thead>
|
|
56
|
+
|
|
57
|
+
<tbody>
|
|
58
|
+
{ data?.map(row => (
|
|
59
|
+
<tr key={ row.agent_id }>
|
|
60
|
+
<td>{ row.company }</td>
|
|
61
|
+
<td>{ row.orders }</td>
|
|
62
|
+
<td>{ row.tickets }</td>
|
|
63
|
+
<td>{ row.amount.toFixed(2) }</td>
|
|
64
|
+
<td>{ row.commission.toFixed(2) }</td>
|
|
65
|
+
<td>{ row.net.toFixed(2) }</td>
|
|
66
|
+
<td>{ row.settled.toFixed(2) }</td>
|
|
67
|
+
<td>{ row.outstanding.toFixed(2) }</td>
|
|
68
|
+
<td>
|
|
69
|
+
<Link to={ `${ url }/receivables/${ row.agent_id }?from=${ from }&to=${ to }` }>
|
|
70
|
+
{ t('admin_api_consumers.receivables.view', { ns: 'common' }) }
|
|
71
|
+
</Link>
|
|
72
|
+
</td>
|
|
73
|
+
</tr>
|
|
74
|
+
)) }
|
|
75
|
+
|
|
76
|
+
{ data?.length === 0 && (
|
|
77
|
+
<tr>
|
|
78
|
+
<td colSpan={ 9 }>{ t('admin_api_consumers.receivables.empty', { ns: 'common' }) }</td>
|
|
79
|
+
</tr>
|
|
80
|
+
) }
|
|
81
|
+
</tbody>
|
|
82
|
+
</Table>
|
|
83
|
+
</div>
|
|
84
|
+
) }
|
|
85
|
+
</Meta>
|
|
86
|
+
);
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
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
package/package.json
CHANGED
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
|
@@ -17,3 +17,41 @@ export interface ApiConsumerData {
|
|
|
17
17
|
}
|
|
18
18
|
}
|
|
19
19
|
}
|
|
20
|
+
|
|
21
|
+
// Edited: Ferjolt Ozuni - Date: 2026-07-27
|
|
22
|
+
// Package 3 - API partner receivables reports + settlements
|
|
23
|
+
export interface ReceivableData {
|
|
24
|
+
agent_id: number
|
|
25
|
+
company: string
|
|
26
|
+
email: string
|
|
27
|
+
orders: number
|
|
28
|
+
tickets: number
|
|
29
|
+
amount: number
|
|
30
|
+
commission: number
|
|
31
|
+
net: number
|
|
32
|
+
settled: number
|
|
33
|
+
outstanding: number
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export interface ReceivableOrderData {
|
|
37
|
+
id: number
|
|
38
|
+
hash: string
|
|
39
|
+
tickets: number
|
|
40
|
+
date_travel: string
|
|
41
|
+
amount: number
|
|
42
|
+
comission_agent: number
|
|
43
|
+
net: number
|
|
44
|
+
created_at: string
|
|
45
|
+
route?: { id: number, name: string }
|
|
46
|
+
passengers?: { id: number, first_name: string, last_name: string, bus_seat?: number }[]
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export interface SettlementData {
|
|
50
|
+
id: number
|
|
51
|
+
api_user_id: number
|
|
52
|
+
period: string
|
|
53
|
+
amount_received: string
|
|
54
|
+
received_at: string
|
|
55
|
+
note?: string
|
|
56
|
+
created_at: string
|
|
57
|
+
}
|