@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.
Files changed (40) hide show
  1. package/README.md +289 -0
  2. package/dist/module.d.mts +24 -0
  3. package/dist/module.json +9 -0
  4. package/dist/module.mjs +46 -0
  5. package/dist/runtime/composables/useDriverOrders.d.ts +1 -0
  6. package/dist/runtime/composables/useDriverOrders.js +10 -0
  7. package/dist/runtime/composables/useFleet.d.ts +1 -0
  8. package/dist/runtime/composables/useFleet.js +16 -0
  9. package/dist/runtime/composables/useInvoice.d.ts +1 -0
  10. package/dist/runtime/composables/useInvoice.js +10 -0
  11. package/dist/runtime/composables/useInvoiceActions.d.ts +5 -0
  12. package/dist/runtime/composables/useInvoiceActions.js +39 -0
  13. package/dist/runtime/composables/useOrder.d.ts +1 -0
  14. package/dist/runtime/composables/useOrder.js +18 -0
  15. package/dist/runtime/composables/useOrderActions.d.ts +7 -0
  16. package/dist/runtime/composables/useOrderActions.js +55 -0
  17. package/dist/runtime/composables/useOrderConfig.d.ts +2 -0
  18. package/dist/runtime/composables/useOrderConfig.js +4 -0
  19. package/dist/runtime/composables/useOrderKpis.d.ts +2 -0
  20. package/dist/runtime/composables/useOrderKpis.js +30 -0
  21. package/dist/runtime/composables/useOrderLocale.d.ts +7 -0
  22. package/dist/runtime/composables/useOrderLocale.js +32 -0
  23. package/dist/runtime/composables/useOrders.d.ts +2 -0
  24. package/dist/runtime/composables/useOrders.js +11 -0
  25. package/dist/runtime/composables/useQuote.d.ts +1 -0
  26. package/dist/runtime/composables/useQuote.js +10 -0
  27. package/dist/runtime/composables/useQuoteActions.d.ts +6 -0
  28. package/dist/runtime/composables/useQuoteActions.js +33 -0
  29. package/dist/runtime/composables/useStorageActions.d.ts +5 -0
  30. package/dist/runtime/composables/useStorageActions.js +31 -0
  31. package/dist/runtime/composables/useStoragePeriod.d.ts +1 -0
  32. package/dist/runtime/composables/useStoragePeriod.js +10 -0
  33. package/dist/runtime/locales/en.d.ts +84 -0
  34. package/dist/runtime/locales/en.js +80 -0
  35. package/dist/runtime/locales/sv.d.ts +3 -0
  36. package/dist/runtime/locales/sv.js +80 -0
  37. package/dist/runtime/server/migrations/001_transport_schema.sql +241 -0
  38. package/dist/runtime/server/tsconfig.json +3 -0
  39. package/dist/types.d.mts +9 -0
  40. 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 };
@@ -0,0 +1,9 @@
1
+ {
2
+ "name": "mbl-order",
3
+ "configKey": "mblOrder",
4
+ "version": "0.1.0",
5
+ "builder": {
6
+ "@nuxt/module-builder": "1.0.2",
7
+ "unbuild": "3.6.1"
8
+ }
9
+ }
@@ -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,2 @@
1
+ import type { OrderConfig } from '../../types.js';
2
+ export declare function useOrderConfig(): OrderConfig;
@@ -0,0 +1,4 @@
1
+ export function useOrderConfig() {
2
+ const config = useRuntimeConfig();
3
+ return config.public.mblOrder;
4
+ }
@@ -0,0 +1,2 @@
1
+ import type { KpiFilters } from '../../types.js';
2
+ export declare function useOrderKpis(filters?: KpiFilters): any;
@@ -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,7 @@
1
+ import type { Locale } from '../../types.js';
2
+ export declare function useOrderLocale(): {
3
+ t: (key: string) => string;
4
+ formatCurrency: (amount: number) => string;
5
+ formatDate: (dateStr: string) => string;
6
+ locale: Locale;
7
+ };
@@ -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,2 @@
1
+ import type { OrderFilters } from '../../types.js';
2
+ export declare function useOrders(filters?: OrderFilters): any;
@@ -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
+ }