@madebylars.com/mbl-order 0.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/README.md +289 -0
- package/dist/module.d.mts +24 -0
- package/dist/module.json +9 -0
- package/dist/module.mjs +46 -0
- package/dist/runtime/composables/useDriverOrders.d.ts +1 -0
- package/dist/runtime/composables/useDriverOrders.js +10 -0
- package/dist/runtime/composables/useFleet.d.ts +1 -0
- package/dist/runtime/composables/useFleet.js +16 -0
- package/dist/runtime/composables/useInvoice.d.ts +1 -0
- package/dist/runtime/composables/useInvoice.js +10 -0
- package/dist/runtime/composables/useInvoiceActions.d.ts +5 -0
- package/dist/runtime/composables/useInvoiceActions.js +39 -0
- package/dist/runtime/composables/useOrder.d.ts +1 -0
- package/dist/runtime/composables/useOrder.js +18 -0
- package/dist/runtime/composables/useOrderActions.d.ts +7 -0
- package/dist/runtime/composables/useOrderActions.js +55 -0
- package/dist/runtime/composables/useOrderConfig.d.ts +2 -0
- package/dist/runtime/composables/useOrderConfig.js +4 -0
- package/dist/runtime/composables/useOrderKpis.d.ts +2 -0
- package/dist/runtime/composables/useOrderKpis.js +30 -0
- package/dist/runtime/composables/useOrderLocale.d.ts +7 -0
- package/dist/runtime/composables/useOrderLocale.js +32 -0
- package/dist/runtime/composables/useOrders.d.ts +2 -0
- package/dist/runtime/composables/useOrders.js +11 -0
- package/dist/runtime/composables/useQuote.d.ts +1 -0
- package/dist/runtime/composables/useQuote.js +10 -0
- package/dist/runtime/composables/useQuoteActions.d.ts +6 -0
- package/dist/runtime/composables/useQuoteActions.js +33 -0
- package/dist/runtime/composables/useStorageActions.d.ts +5 -0
- package/dist/runtime/composables/useStorageActions.js +31 -0
- package/dist/runtime/composables/useStoragePeriod.d.ts +1 -0
- package/dist/runtime/composables/useStoragePeriod.js +10 -0
- package/dist/runtime/locales/en.d.ts +84 -0
- package/dist/runtime/locales/en.js +80 -0
- package/dist/runtime/locales/sv.d.ts +3 -0
- package/dist/runtime/locales/sv.js +80 -0
- package/dist/runtime/server/migrations/001_transport_schema.sql +241 -0
- package/dist/runtime/server/tsconfig.json +3 -0
- package/dist/types.d.mts +9 -0
- package/package.json +82 -0
package/README.md
ADDED
|
@@ -0,0 +1,289 @@
|
|
|
1
|
+
# mbl-order
|
|
2
|
+
|
|
3
|
+
[![npm version][npm-version-src]][npm-version-href]
|
|
4
|
+
[![npm downloads][npm-downloads-src]][npm-downloads-href]
|
|
5
|
+
[![License][license-src]][license-href]
|
|
6
|
+
[![Nuxt][nuxt-src]][nuxt-href]
|
|
7
|
+
|
|
8
|
+
Transport-vertical order management module for the [MadeByLars](https://madebylars.com) Nuxt 4 ecosystem.
|
|
9
|
+
|
|
10
|
+
Handles the full lifecycle: **Quote → Order → Dispatch → Delivery → Invoice**
|
|
11
|
+
|
|
12
|
+
- [✨ Release Notes](/CHANGELOG.md)
|
|
13
|
+
|
|
14
|
+
---
|
|
15
|
+
|
|
16
|
+
## Features
|
|
17
|
+
|
|
18
|
+
- **Two order types** — transport (A→B jobs) and storage (recurring warehouse rental)
|
|
19
|
+
- **Full status lifecycle** — per order type, enforced via Supabase RLS
|
|
20
|
+
- **Role-based access** — customer / driver / dispatcher / admin extending `mbl-auth` RBAC
|
|
21
|
+
- **Invoice generation** — draft invoices with VAT line items; accounting export is a future phase
|
|
22
|
+
- **Fleet view** — active drivers with last-known GPS position (integrates with `mbl-whereabout`)
|
|
23
|
+
- **i18n built-in** — English (en-GB) and Swedish (sv-SE); integrates with `@nuxtjs/i18n` if present
|
|
24
|
+
- **Composables-only API** — no UI components; you build the interface
|
|
25
|
+
|
|
26
|
+
---
|
|
27
|
+
|
|
28
|
+
## Requirements
|
|
29
|
+
|
|
30
|
+
| Peer dependency | Required |
|
|
31
|
+
|---|---|
|
|
32
|
+
| `nuxt` | ≥ 4.0.0 |
|
|
33
|
+
| `@nuxtjs/supabase` | ≥ 1.0.0 |
|
|
34
|
+
| `@nuxtjs/i18n` | optional |
|
|
35
|
+
| `@madebylars.com/mbl-auth` | optional, recommended |
|
|
36
|
+
| `@madebylars.com/mbl-whereabout` | optional |
|
|
37
|
+
| `@madebylars.com/mbl-graph` | optional |
|
|
38
|
+
| `@madebylars.com/mbl-mapman` | optional |
|
|
39
|
+
|
|
40
|
+
---
|
|
41
|
+
|
|
42
|
+
## Setup
|
|
43
|
+
|
|
44
|
+
### 1. Install
|
|
45
|
+
|
|
46
|
+
```bash
|
|
47
|
+
npm install @madebylars.com/mbl-order
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
### 2. Register in `nuxt.config.ts`
|
|
51
|
+
|
|
52
|
+
```ts
|
|
53
|
+
export default defineNuxtConfig({
|
|
54
|
+
modules: [
|
|
55
|
+
'@nuxtjs/supabase',
|
|
56
|
+
'@madebylars.com/mbl-order',
|
|
57
|
+
],
|
|
58
|
+
|
|
59
|
+
mblOrder: {
|
|
60
|
+
supabaseSchema: 'transport', // default
|
|
61
|
+
locale: 'en', // 'en' | 'sv'
|
|
62
|
+
currency: 'GBP', // 'GBP' | 'SEK'
|
|
63
|
+
vatRates: {
|
|
64
|
+
standard: 0.20, // UK: 0.20 Sweden: 0.25
|
|
65
|
+
reduced: 0.05, // UK: 0.05 Sweden: 0.12
|
|
66
|
+
zero: 0,
|
|
67
|
+
},
|
|
68
|
+
invoiceTrigger: 'on_delivery', // 'on_delivery' | 'manual' | 'periodic'
|
|
69
|
+
periodicBillingDay: 1,
|
|
70
|
+
},
|
|
71
|
+
})
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
### 3. Run the database migration
|
|
75
|
+
|
|
76
|
+
Apply `src/runtime/server/migrations/001_transport_schema.sql` to your Supabase project.
|
|
77
|
+
|
|
78
|
+
**Via Supabase CLI:**
|
|
79
|
+
```bash
|
|
80
|
+
supabase db push
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
**Or paste directly** into the Supabase SQL editor.
|
|
84
|
+
|
|
85
|
+
The migration creates a dedicated `transport` schema with seven tables, indexes, RLS policies, and a role helper function. It is safe to run on a fresh project; it will fail if the schema already exists.
|
|
86
|
+
|
|
87
|
+
> **Important:** The RLS policies rely on `mbl-auth` storing the user role in `auth.users.app_metadata` as `{ "role": "customer" | "driver" | "dispatcher" | "admin" }`. Confirm this matches your `mbl-auth` setup before running.
|
|
88
|
+
|
|
89
|
+
---
|
|
90
|
+
|
|
91
|
+
## Module options
|
|
92
|
+
|
|
93
|
+
| Option | Type | Default | Description |
|
|
94
|
+
|---|---|---|---|
|
|
95
|
+
| `supabaseSchema` | `string` | `'transport'` | Supabase schema name |
|
|
96
|
+
| `locale` | `'en' \| 'sv'` | `'en'` | Default locale |
|
|
97
|
+
| `currency` | `'GBP' \| 'SEK'` | `'GBP'` | Currency for invoices and formatting |
|
|
98
|
+
| `vatRates.standard` | `number` | `0.20` | Standard VAT rate (UK: 0.20, Sweden: 0.25) |
|
|
99
|
+
| `vatRates.reduced` | `number` | `0.05` | Reduced VAT rate (UK: 0.05, Sweden: 0.12) |
|
|
100
|
+
| `vatRates.zero` | `number` | `0` | Zero-rate VAT |
|
|
101
|
+
| `invoiceTrigger` | `'on_delivery' \| 'manual' \| 'periodic'` | `'on_delivery'` | When to auto-generate invoices |
|
|
102
|
+
| `periodicBillingDay` | `number` | `1` | Day of month for storage billing |
|
|
103
|
+
|
|
104
|
+
---
|
|
105
|
+
|
|
106
|
+
## Composable API
|
|
107
|
+
|
|
108
|
+
All data composables return the standard Nuxt `AsyncData` shape — `{ data, pending, error, refresh }`.
|
|
109
|
+
|
|
110
|
+
### Orders
|
|
111
|
+
|
|
112
|
+
```ts
|
|
113
|
+
const { data: orders } = await useOrders(filters?)
|
|
114
|
+
const { data: order } = await useOrder(id)
|
|
115
|
+
const { createOrder, updateStatus, assignDriver, cancelOrder } = useOrderActions()
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
### Quotes
|
|
119
|
+
|
|
120
|
+
```ts
|
|
121
|
+
const { data: quote } = await useQuote(orderId)
|
|
122
|
+
const { createQuote, acceptQuote, expireQuote } = useQuoteActions()
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
### Invoices
|
|
126
|
+
|
|
127
|
+
```ts
|
|
128
|
+
const { data: invoice } = await useInvoice(orderId)
|
|
129
|
+
const { generateInvoice, markPaid } = useInvoiceActions()
|
|
130
|
+
// Note: syncToAccounting() is not available in the POC — accounting export is a future phase
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
### Storage
|
|
134
|
+
|
|
135
|
+
```ts
|
|
136
|
+
const { data: period } = await useStoragePeriod(orderId)
|
|
137
|
+
const { extendPeriod, endPeriod } = useStorageActions()
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
### Fleet (dispatcher / admin)
|
|
141
|
+
|
|
142
|
+
```ts
|
|
143
|
+
const { data: fleet } = await useFleet()
|
|
144
|
+
const { data: orders } = await useDriverOrders(driverId)
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
### Locale
|
|
148
|
+
|
|
149
|
+
```ts
|
|
150
|
+
const { t, formatCurrency, formatDate, locale } = useOrderLocale()
|
|
151
|
+
|
|
152
|
+
t('mblOrder.status.in_transit') // → 'In transit' or 'Under transport'
|
|
153
|
+
formatCurrency(1250) // → '£1,250.00' or '1 250,00 kr'
|
|
154
|
+
formatDate('2026-06-10') // → '10 June 2026' or '10 juni 2026'
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
### KPIs (for `mbl-graph`)
|
|
158
|
+
|
|
159
|
+
```ts
|
|
160
|
+
const { data: kpis } = await useOrderKpis(filters?)
|
|
161
|
+
// Returns: totalOrders, activeOrders, totalRevenue, fleetUtilisationPercent, …
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
### Config
|
|
165
|
+
|
|
166
|
+
```ts
|
|
167
|
+
const config = useOrderConfig()
|
|
168
|
+
// Returns: supabaseSchema, locale, currency, vatRates, invoiceTrigger, periodicBillingDay
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
---
|
|
172
|
+
|
|
173
|
+
## Order types and statuses
|
|
174
|
+
|
|
175
|
+
### Transport order
|
|
176
|
+
|
|
177
|
+
Single A→B job. Billing: per km / per hour / per weight / fixed.
|
|
178
|
+
|
|
179
|
+
```
|
|
180
|
+
draft → quoted → confirmed → assigned → collected → in_transit → delivered → invoiced
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
Integrates with `mbl-whereabout` for real-time driver GPS during active jobs.
|
|
184
|
+
|
|
185
|
+
### Storage order
|
|
186
|
+
|
|
187
|
+
Recurring warehouse rental. Billing: per m² / per pallet / per period / fixed subscription.
|
|
188
|
+
|
|
189
|
+
```
|
|
190
|
+
draft → quoted → active → extended → ended → invoiced
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
Supports periodic invoice generation (monthly or custom interval via `periodicBillingDay`).
|
|
194
|
+
|
|
195
|
+
---
|
|
196
|
+
|
|
197
|
+
## Roles
|
|
198
|
+
|
|
199
|
+
| Role | Capabilities |
|
|
200
|
+
|---|---|
|
|
201
|
+
| `customer` | Create orders, accept quotes, track delivery, view invoices |
|
|
202
|
+
| `driver` | View assigned jobs, update job status |
|
|
203
|
+
| `dispatcher` | Assign jobs, manage queue, live fleet view |
|
|
204
|
+
| `admin` | Full access, invoice generation, KPIs |
|
|
205
|
+
|
|
206
|
+
Roles extend the `mbl-auth` RBAC system. The JWT `app_metadata.role` field is used by Supabase RLS to enforce access at the database level.
|
|
207
|
+
|
|
208
|
+
---
|
|
209
|
+
|
|
210
|
+
## i18n
|
|
211
|
+
|
|
212
|
+
The module ships English and Swedish translations namespaced under `mblOrder.*`.
|
|
213
|
+
|
|
214
|
+
If `@nuxtjs/i18n` is installed, translations are registered automatically via the `i18n:registerModule` hook and become available through `useI18n().t()`.
|
|
215
|
+
|
|
216
|
+
If `@nuxtjs/i18n` is absent, use `useOrderLocale()` directly — it works standalone with no extra dependencies.
|
|
217
|
+
|
|
218
|
+
---
|
|
219
|
+
|
|
220
|
+
## Ecosystem integration
|
|
221
|
+
|
|
222
|
+
| Module | Integration |
|
|
223
|
+
|---|---|
|
|
224
|
+
| `mbl-auth` | Session + RBAC — role checks in RLS and composables |
|
|
225
|
+
| `mbl-whereabout` | Driver GPS stream merged into `useFleet()` results |
|
|
226
|
+
| `mbl-graph` | `useOrderKpis()` feeds directly into graph datasets |
|
|
227
|
+
| `mbl-mapman` | Driver markers via `useFleet()` — marker API compatible |
|
|
228
|
+
| `mbl-payment` | Integration point reserved; not implemented in POC |
|
|
229
|
+
|
|
230
|
+
---
|
|
231
|
+
|
|
232
|
+
## Database schema
|
|
233
|
+
|
|
234
|
+
Seven tables in the `transport` Supabase schema:
|
|
235
|
+
|
|
236
|
+
| Table | Purpose |
|
|
237
|
+
|---|---|
|
|
238
|
+
| `transport.orders` | Core order record |
|
|
239
|
+
| `transport.order_lines` | Line items with VAT |
|
|
240
|
+
| `transport.quotes` | Quote linked to an order |
|
|
241
|
+
| `transport.storage_periods` | Active/historical storage periods |
|
|
242
|
+
| `transport.invoices` | Generated invoices |
|
|
243
|
+
| `transport.drivers` | Driver profiles linked to auth users |
|
|
244
|
+
| `transport.vehicles` | Vehicle registry |
|
|
245
|
+
|
|
246
|
+
All tables have RLS enabled. See the migration file for full column definitions, indexes, and policies.
|
|
247
|
+
|
|
248
|
+
---
|
|
249
|
+
|
|
250
|
+
## Contributing
|
|
251
|
+
|
|
252
|
+
```bash
|
|
253
|
+
# Install dependencies
|
|
254
|
+
npm install
|
|
255
|
+
|
|
256
|
+
# Generate type stubs (required once before development)
|
|
257
|
+
npm run dev:prepare
|
|
258
|
+
|
|
259
|
+
# Develop with the playground
|
|
260
|
+
npm run dev
|
|
261
|
+
|
|
262
|
+
# Build the playground
|
|
263
|
+
npm run dev:build
|
|
264
|
+
|
|
265
|
+
# Lint
|
|
266
|
+
npm run lint
|
|
267
|
+
|
|
268
|
+
# Test
|
|
269
|
+
npm run test
|
|
270
|
+
npm run test:watch
|
|
271
|
+
|
|
272
|
+
# Release
|
|
273
|
+
npm run release
|
|
274
|
+
```
|
|
275
|
+
|
|
276
|
+
---
|
|
277
|
+
|
|
278
|
+
<!-- Badges -->
|
|
279
|
+
[npm-version-src]: https://img.shields.io/npm/v/@madebylars.com/mbl-order/latest.svg?style=flat&colorA=020420&colorB=00DC82
|
|
280
|
+
[npm-version-href]: https://npmjs.com/package/@madebylars.com/mbl-order
|
|
281
|
+
|
|
282
|
+
[npm-downloads-src]: https://img.shields.io/npm/dm/@madebylars.com/mbl-order.svg?style=flat&colorA=020420&colorB=00DC82
|
|
283
|
+
[npm-downloads-href]: https://npm.chart.dev/@madebylars.com/mbl-order
|
|
284
|
+
|
|
285
|
+
[license-src]: https://img.shields.io/npm/l/@madebylars.com/mbl-order.svg?style=flat&colorA=020420&colorB=00DC82
|
|
286
|
+
[license-href]: https://npmjs.com/package/@madebylars.com/mbl-order
|
|
287
|
+
|
|
288
|
+
[nuxt-src]: https://img.shields.io/badge/Nuxt-020420?logo=nuxt
|
|
289
|
+
[nuxt-href]: https://nuxt.com
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import * as _nuxt_schema from '@nuxt/schema';
|
|
2
|
+
|
|
3
|
+
interface MblOrderOptions {
|
|
4
|
+
/** Supabase schema name. Default: 'transport' */
|
|
5
|
+
supabaseSchema: string;
|
|
6
|
+
/** Default locale. Default: 'en' */
|
|
7
|
+
locale?: 'en' | 'sv';
|
|
8
|
+
/** ISO 4217 currency code. Default: 'GBP' */
|
|
9
|
+
currency?: 'GBP' | 'SEK';
|
|
10
|
+
vatRates?: {
|
|
11
|
+
/** UK default: 0.20 / Sweden: 0.25 */
|
|
12
|
+
standard: number;
|
|
13
|
+
/** UK default: 0.05 / Sweden: 0.12 */
|
|
14
|
+
reduced: number;
|
|
15
|
+
zero: number;
|
|
16
|
+
};
|
|
17
|
+
invoiceTrigger?: 'on_delivery' | 'manual' | 'periodic';
|
|
18
|
+
/** Day of month for periodic storage billing. Default: 1 */
|
|
19
|
+
periodicBillingDay?: number;
|
|
20
|
+
}
|
|
21
|
+
declare const _default: _nuxt_schema.NuxtModule<MblOrderOptions, MblOrderOptions, false>;
|
|
22
|
+
|
|
23
|
+
export { _default as default };
|
|
24
|
+
export type { MblOrderOptions };
|
package/dist/module.json
ADDED
package/dist/module.mjs
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { defineNuxtModule, createResolver, addImportsDir } from '@nuxt/kit';
|
|
2
|
+
|
|
3
|
+
const module$1 = defineNuxtModule({
|
|
4
|
+
meta: {
|
|
5
|
+
name: "mbl-order",
|
|
6
|
+
configKey: "mblOrder"
|
|
7
|
+
},
|
|
8
|
+
defaults: {
|
|
9
|
+
supabaseSchema: "transport",
|
|
10
|
+
locale: "en",
|
|
11
|
+
currency: "GBP",
|
|
12
|
+
vatRates: {
|
|
13
|
+
standard: 0.2,
|
|
14
|
+
reduced: 0.05,
|
|
15
|
+
zero: 0
|
|
16
|
+
},
|
|
17
|
+
invoiceTrigger: "on_delivery",
|
|
18
|
+
periodicBillingDay: 1
|
|
19
|
+
},
|
|
20
|
+
setup(options, nuxt) {
|
|
21
|
+
const resolver = createResolver(import.meta.url);
|
|
22
|
+
nuxt.options.runtimeConfig.public.mblOrder = {
|
|
23
|
+
supabaseSchema: options.supabaseSchema,
|
|
24
|
+
locale: options.locale ?? "en",
|
|
25
|
+
currency: options.currency ?? "GBP",
|
|
26
|
+
vatRates: options.vatRates ?? { standard: 0.2, reduced: 0.05, zero: 0 },
|
|
27
|
+
invoiceTrigger: options.invoiceTrigger ?? "on_delivery",
|
|
28
|
+
periodicBillingDay: options.periodicBillingDay ?? 1
|
|
29
|
+
};
|
|
30
|
+
nuxt.hook(
|
|
31
|
+
"i18n:registerModule",
|
|
32
|
+
(register) => {
|
|
33
|
+
register({
|
|
34
|
+
langDir: resolver.resolve("./runtime/locales"),
|
|
35
|
+
locales: [
|
|
36
|
+
{ code: "en", file: "en.ts" },
|
|
37
|
+
{ code: "sv", file: "sv.ts" }
|
|
38
|
+
]
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
);
|
|
42
|
+
addImportsDir(resolver.resolve("./runtime/composables"));
|
|
43
|
+
}
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
export { module$1 as default };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function useDriverOrders(driverId: string): any;
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export function useDriverOrders(driverId) {
|
|
2
|
+
return useAsyncData(`mbl-order:driver-orders:${driverId}`, async () => {
|
|
3
|
+
const client = useSupabaseClient();
|
|
4
|
+
const config = useRuntimeConfig();
|
|
5
|
+
const schema = config.public.mblOrder.supabaseSchema;
|
|
6
|
+
const { data, error } = await client.schema(schema).from("orders").select("*").eq("assigned_driver_id", driverId).in("status", ["assigned", "collected", "in_transit"]).order("scheduled_pickup_at", { ascending: true });
|
|
7
|
+
if (error) throw error;
|
|
8
|
+
return data ?? [];
|
|
9
|
+
});
|
|
10
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function useFleet(): any;
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
export function useFleet() {
|
|
2
|
+
return useAsyncData("mbl-order:fleet", async () => {
|
|
3
|
+
const client = useSupabaseClient();
|
|
4
|
+
const config = useRuntimeConfig();
|
|
5
|
+
const schema = config.public.mblOrder.supabaseSchema;
|
|
6
|
+
const { data, error } = await client.schema(schema).from("drivers").select(`
|
|
7
|
+
*,
|
|
8
|
+
vehicles (*),
|
|
9
|
+
orders!assigned_driver_id (
|
|
10
|
+
id, order_number, status, pickup_address, delivery_address
|
|
11
|
+
)
|
|
12
|
+
`);
|
|
13
|
+
if (error) throw error;
|
|
14
|
+
return data ?? [];
|
|
15
|
+
});
|
|
16
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function useInvoice(orderId: string): any;
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export function useInvoice(orderId) {
|
|
2
|
+
return useAsyncData(`mbl-order:invoice:${orderId}`, async () => {
|
|
3
|
+
const client = useSupabaseClient();
|
|
4
|
+
const config = useRuntimeConfig();
|
|
5
|
+
const schema = config.public.mblOrder.supabaseSchema;
|
|
6
|
+
const { data, error } = await client.schema(schema).from("invoices").select("*").eq("order_id", orderId).order("created_at", { ascending: false }).limit(1).maybeSingle();
|
|
7
|
+
if (error) throw error;
|
|
8
|
+
return data;
|
|
9
|
+
});
|
|
10
|
+
}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { Invoice, GenerateInvoicePayload, ActionResult } from '../../types.js';
|
|
2
|
+
export declare function useInvoiceActions(): {
|
|
3
|
+
generateInvoice: (payload: GenerateInvoicePayload) => Promise<ActionResult<Invoice>>;
|
|
4
|
+
markPaid: (invoiceId: string, paidAt?: string) => Promise<ActionResult<Invoice>>;
|
|
5
|
+
};
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
export function useInvoiceActions() {
|
|
2
|
+
const client = useSupabaseClient();
|
|
3
|
+
const config = useRuntimeConfig();
|
|
4
|
+
const schema = config.public.mblOrder.supabaseSchema;
|
|
5
|
+
async function generateInvoice(payload) {
|
|
6
|
+
try {
|
|
7
|
+
const { data, error } = await client.schema(schema).from("invoices").insert({
|
|
8
|
+
order_id: payload.orderId,
|
|
9
|
+
due_at: payload.dueAt ?? null,
|
|
10
|
+
status: "draft",
|
|
11
|
+
currency: config.public.mblOrder.currency,
|
|
12
|
+
accounting_provider: null,
|
|
13
|
+
external_id: null,
|
|
14
|
+
// TODO: compute from order lines
|
|
15
|
+
total_excl_vat: 0,
|
|
16
|
+
total_vat: 0,
|
|
17
|
+
total_incl_vat: 0
|
|
18
|
+
}).select().single();
|
|
19
|
+
if (error) throw error;
|
|
20
|
+
return { data, error: null };
|
|
21
|
+
} catch (err) {
|
|
22
|
+
return { data: null, error: err };
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
async function markPaid(invoiceId, paidAt) {
|
|
26
|
+
try {
|
|
27
|
+
const { data, error } = await client.schema(schema).from("invoices").update({
|
|
28
|
+
status: "paid",
|
|
29
|
+
paid_at: paidAt ?? (/* @__PURE__ */ new Date()).toISOString(),
|
|
30
|
+
updated_at: (/* @__PURE__ */ new Date()).toISOString()
|
|
31
|
+
}).eq("id", invoiceId).select().single();
|
|
32
|
+
if (error) throw error;
|
|
33
|
+
return { data, error: null };
|
|
34
|
+
} catch (err) {
|
|
35
|
+
return { data: null, error: err };
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
return { generateInvoice, markPaid };
|
|
39
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function useOrder(id: string): any;
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
export function useOrder(id) {
|
|
2
|
+
return useAsyncData(`mbl-order:order:${id}`, async () => {
|
|
3
|
+
const client = useSupabaseClient();
|
|
4
|
+
const config = useRuntimeConfig();
|
|
5
|
+
const schema = config.public.mblOrder.supabaseSchema;
|
|
6
|
+
const { data, error } = await client.schema(schema).from("orders").select(`
|
|
7
|
+
*,
|
|
8
|
+
order_lines (*),
|
|
9
|
+
quotes (*),
|
|
10
|
+
invoices (*),
|
|
11
|
+
storage_periods (*),
|
|
12
|
+
drivers (*),
|
|
13
|
+
vehicles (*)
|
|
14
|
+
`).eq("id", id).single();
|
|
15
|
+
if (error) throw error;
|
|
16
|
+
return data;
|
|
17
|
+
});
|
|
18
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { Order, OrderStatus, CreateOrderPayload, ActionResult } from '../../types.js';
|
|
2
|
+
export declare function useOrderActions(): {
|
|
3
|
+
createOrder: (payload: CreateOrderPayload) => Promise<ActionResult<Order>>;
|
|
4
|
+
updateStatus: (orderId: string, status: OrderStatus) => Promise<ActionResult<Order>>;
|
|
5
|
+
assignDriver: (orderId: string, driverId: string, vehicleId?: string) => Promise<ActionResult<Order>>;
|
|
6
|
+
cancelOrder: (orderId: string, reason?: string) => Promise<ActionResult<Order>>;
|
|
7
|
+
};
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
export function useOrderActions() {
|
|
2
|
+
const client = useSupabaseClient();
|
|
3
|
+
const config = useRuntimeConfig();
|
|
4
|
+
const schema = config.public.mblOrder.supabaseSchema;
|
|
5
|
+
async function createOrder(payload) {
|
|
6
|
+
try {
|
|
7
|
+
const { data, error } = await client.schema(schema).from("orders").insert({
|
|
8
|
+
/* TODO: map payload to snake_case */
|
|
9
|
+
...payload
|
|
10
|
+
}).select().single();
|
|
11
|
+
if (error) throw error;
|
|
12
|
+
return { data, error: null };
|
|
13
|
+
} catch (err) {
|
|
14
|
+
return { data: null, error: err };
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
async function updateStatus(orderId, status) {
|
|
18
|
+
try {
|
|
19
|
+
const { data, error } = await client.schema(schema).from("orders").update({ status, updated_at: (/* @__PURE__ */ new Date()).toISOString() }).eq("id", orderId).select().single();
|
|
20
|
+
if (error) throw error;
|
|
21
|
+
return { data, error: null };
|
|
22
|
+
} catch (err) {
|
|
23
|
+
return { data: null, error: err };
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
async function assignDriver(orderId, driverId, vehicleId) {
|
|
27
|
+
try {
|
|
28
|
+
const { data, error } = await client.schema(schema).from("orders").update({
|
|
29
|
+
assigned_driver_id: driverId,
|
|
30
|
+
assigned_vehicle_id: vehicleId ?? null,
|
|
31
|
+
status: "assigned",
|
|
32
|
+
updated_at: (/* @__PURE__ */ new Date()).toISOString()
|
|
33
|
+
}).eq("id", orderId).select().single();
|
|
34
|
+
if (error) throw error;
|
|
35
|
+
return { data, error: null };
|
|
36
|
+
} catch (err) {
|
|
37
|
+
return { data: null, error: err };
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
async function cancelOrder(orderId, reason) {
|
|
41
|
+
try {
|
|
42
|
+
const { data, error } = await client.schema(schema).from("orders").update({
|
|
43
|
+
status: "draft",
|
|
44
|
+
// placeholder — actual cancel status TBD
|
|
45
|
+
notes: reason ?? null,
|
|
46
|
+
updated_at: (/* @__PURE__ */ new Date()).toISOString()
|
|
47
|
+
}).eq("id", orderId).select().single();
|
|
48
|
+
if (error) throw error;
|
|
49
|
+
return { data, error: null };
|
|
50
|
+
} catch (err) {
|
|
51
|
+
return { data: null, error: err };
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
return { createOrder, updateStatus, assignDriver, cancelOrder };
|
|
55
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
export function useOrderKpis(filters) {
|
|
2
|
+
const key = `mbl-order:kpis:${JSON.stringify(filters ?? {})}`;
|
|
3
|
+
return useAsyncData(key, async () => {
|
|
4
|
+
const client = useSupabaseClient();
|
|
5
|
+
const config = useRuntimeConfig();
|
|
6
|
+
const schema = config.public.mblOrder.supabaseSchema;
|
|
7
|
+
const { data, error } = await client.schema(schema).from("orders").select("status, type, created_at");
|
|
8
|
+
if (error) throw error;
|
|
9
|
+
const rows = data ?? [];
|
|
10
|
+
const kpis = {
|
|
11
|
+
totalOrders: rows.length,
|
|
12
|
+
activeOrders: rows.filter((r) => !["invoiced", "ended", "delivered"].includes(r.status)).length,
|
|
13
|
+
deliveredOrders: rows.filter((r) => r.status === "delivered").length,
|
|
14
|
+
cancelledOrders: 0,
|
|
15
|
+
// TODO: add cancelled status
|
|
16
|
+
averageDeliveryTimeHours: null,
|
|
17
|
+
// TODO: compute from actual_pickup_at / actual_delivery_at
|
|
18
|
+
totalRevenue: 0,
|
|
19
|
+
// TODO: sum from invoices
|
|
20
|
+
pendingInvoices: 0,
|
|
21
|
+
// TODO: query invoices with status draft/sent
|
|
22
|
+
overdueInvoices: 0,
|
|
23
|
+
// TODO: query invoices with status overdue
|
|
24
|
+
activeStorageContracts: rows.filter((r) => r.type === "storage" && r.status === "active").length,
|
|
25
|
+
fleetUtilisationPercent: null
|
|
26
|
+
// TODO: compute from fleet + active orders
|
|
27
|
+
};
|
|
28
|
+
return kpis;
|
|
29
|
+
});
|
|
30
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import en from "../locales/en.js";
|
|
2
|
+
import sv from "../locales/sv.js";
|
|
3
|
+
const LOCALES = { en, sv };
|
|
4
|
+
function resolve(obj, path) {
|
|
5
|
+
const value = path.split(".").reduce((node, key) => {
|
|
6
|
+
if (node && typeof node === "object") return node[key];
|
|
7
|
+
return void 0;
|
|
8
|
+
}, obj);
|
|
9
|
+
return typeof value === "string" ? value : path;
|
|
10
|
+
}
|
|
11
|
+
export function useOrderLocale() {
|
|
12
|
+
const config = useRuntimeConfig();
|
|
13
|
+
const locale = config.public.mblOrder?.locale ?? "en";
|
|
14
|
+
const messages = LOCALES[locale] ?? LOCALES.en;
|
|
15
|
+
function t(key) {
|
|
16
|
+
return resolve(messages, key);
|
|
17
|
+
}
|
|
18
|
+
function formatCurrency(amount) {
|
|
19
|
+
const currency = config.public.mblOrder?.currency ?? "GBP";
|
|
20
|
+
const intlLocale = locale === "sv" ? "sv-SE" : "en-GB";
|
|
21
|
+
return new Intl.NumberFormat(intlLocale, { style: "currency", currency }).format(amount);
|
|
22
|
+
}
|
|
23
|
+
function formatDate(dateStr) {
|
|
24
|
+
const intlLocale = locale === "sv" ? "sv-SE" : "en-GB";
|
|
25
|
+
return new Intl.DateTimeFormat(intlLocale, {
|
|
26
|
+
day: "numeric",
|
|
27
|
+
month: "long",
|
|
28
|
+
year: "numeric"
|
|
29
|
+
}).format(new Date(dateStr));
|
|
30
|
+
}
|
|
31
|
+
return { t, formatCurrency, formatDate, locale };
|
|
32
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export function useOrders(filters) {
|
|
2
|
+
const key = `mbl-order:orders:${JSON.stringify(filters ?? {})}`;
|
|
3
|
+
return useAsyncData(key, async () => {
|
|
4
|
+
const client = useSupabaseClient();
|
|
5
|
+
const config = useRuntimeConfig();
|
|
6
|
+
const schema = config.public.mblOrder.supabaseSchema;
|
|
7
|
+
const { data, error } = await client.schema(schema).from("orders").select("*");
|
|
8
|
+
if (error) throw error;
|
|
9
|
+
return data ?? [];
|
|
10
|
+
});
|
|
11
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function useQuote(orderId: string): any;
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export function useQuote(orderId) {
|
|
2
|
+
return useAsyncData(`mbl-order:quote:${orderId}`, async () => {
|
|
3
|
+
const client = useSupabaseClient();
|
|
4
|
+
const config = useRuntimeConfig();
|
|
5
|
+
const schema = config.public.mblOrder.supabaseSchema;
|
|
6
|
+
const { data, error } = await client.schema(schema).from("quotes").select("*").eq("order_id", orderId).order("created_at", { ascending: false }).limit(1).maybeSingle();
|
|
7
|
+
if (error) throw error;
|
|
8
|
+
return data;
|
|
9
|
+
});
|
|
10
|
+
}
|