@stacksjs/orm 0.72.16 → 0.72.18
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/dist/index.d.ts +97 -97
- package/dist/index.js +7 -7
- package/dist/models/Activity.ts +95 -0
- package/dist/models/AnalyticsEvent.ts +70 -0
- package/dist/models/Automation.ts +23 -0
- package/dist/models/AutomationRun.ts +26 -0
- package/dist/models/Board.ts +104 -0
- package/dist/models/BoardColumn.ts +88 -0
- package/dist/models/Campaign.ts +321 -0
- package/dist/models/CampaignSend.ts +191 -0
- package/dist/models/CampaignVariant.ts +25 -0
- package/dist/models/Card.ts +118 -0
- package/dist/models/CardComment.ts +68 -0
- package/dist/models/Comment.ts +109 -0
- package/dist/models/CommunicationSuppression.ts +22 -0
- package/dist/models/ConsentEvent.ts +30 -0
- package/dist/models/Content/Author.ts +109 -0
- package/dist/models/Content/Menu.ts +56 -0
- package/dist/models/Content/MenuItem.ts +91 -0
- package/dist/models/Content/Page.ts +205 -0
- package/dist/models/Content/PageRevision.ts +86 -0
- package/dist/models/Content/Post.ts +230 -0
- package/dist/models/Content/Redirect.ts +80 -0
- package/dist/models/Deployment.ts +117 -0
- package/dist/models/EmailIdempotency.ts +64 -0
- package/dist/models/EmailList.ts +134 -0
- package/dist/models/EmailListSubscriber.ts +91 -0
- package/dist/models/EmailSuppression.ts +52 -0
- package/dist/models/EmailWebhookEvent.ts +51 -0
- package/dist/models/Error.ts +77 -0
- package/dist/models/FailedJob.ts +84 -0
- package/dist/models/Forms/Form.ts +91 -0
- package/dist/models/Forms/FormField.ts +127 -0
- package/dist/models/Forms/FormSubmission.ts +114 -0
- package/dist/models/Job.ts +69 -0
- package/dist/models/Label.ts +67 -0
- package/dist/models/Log.ts +134 -0
- package/dist/models/MagicLinkToken.ts +97 -0
- package/dist/models/MailPreference.ts +213 -0
- package/dist/models/Notification.ts +68 -0
- package/dist/models/NotificationDelivery.ts +102 -0
- package/dist/models/PaymentMethod.ts +98 -0
- package/dist/models/PaymentProduct.ts +91 -0
- package/dist/models/PaymentTransaction.ts +78 -0
- package/dist/models/QueryLog.ts +182 -0
- package/dist/models/README.md +31 -0
- package/dist/models/Release.ts +90 -0
- package/dist/models/Request.ts +133 -0
- package/dist/models/SenderDomain.ts +22 -0
- package/dist/models/Site.ts +112 -0
- package/dist/models/SiteDomain.ts +74 -0
- package/dist/models/SmsOptOut.ts +65 -0
- package/dist/models/SocialAccount.ts +70 -0
- package/dist/models/SocialPost.ts +144 -0
- package/dist/models/Subscriber.ts +89 -0
- package/dist/models/SubscriberEmail.ts +55 -0
- package/dist/models/Subscription.ts +153 -0
- package/dist/models/Tag.ts +105 -0
- package/dist/models/Team.ts +75 -0
- package/dist/models/TeamInvitation.ts +159 -0
- package/dist/models/TeamMember.ts +88 -0
- package/dist/models/UsageEvent.ts +24 -0
- package/dist/models/User.ts +142 -0
- package/dist/models/commerce/Auction.ts +173 -0
- package/dist/models/commerce/AuctionItem.ts +204 -0
- package/dist/models/commerce/Bid.ts +129 -0
- package/dist/models/commerce/Cart.ts +140 -0
- package/dist/models/commerce/CartItem.ts +138 -0
- package/dist/models/commerce/Category.ts +125 -0
- package/dist/models/commerce/Coupon.ts +186 -0
- package/dist/models/commerce/Customer.ts +132 -0
- package/dist/models/commerce/DeliveryRoute.ts +127 -0
- package/dist/models/commerce/DeliveryStop.ts +166 -0
- package/dist/models/commerce/DigitalDelivery.ts +106 -0
- package/dist/models/commerce/Driver.ts +168 -0
- package/dist/models/commerce/DriverPing.ts +103 -0
- package/dist/models/commerce/GiftCard.ts +195 -0
- package/dist/models/commerce/LicenseKey.ts +89 -0
- package/dist/models/commerce/LoyaltyPoint.ts +108 -0
- package/dist/models/commerce/LoyaltyReward.ts +129 -0
- package/dist/models/commerce/Manufacturer.ts +90 -0
- package/dist/models/commerce/Order.ts +198 -0
- package/dist/models/commerce/OrderIdempotency.ts +34 -0
- package/dist/models/commerce/OrderItem.ts +51 -0
- package/dist/models/commerce/Payment.ts +182 -0
- package/dist/models/commerce/Pledge.ts +112 -0
- package/dist/models/commerce/PrintDevice.ts +120 -0
- package/dist/models/commerce/Product.ts +153 -0
- package/dist/models/commerce/ProductUnit.ts +113 -0
- package/dist/models/commerce/ProductVariant.ts +111 -0
- package/dist/models/commerce/Receipt.ts +144 -0
- package/dist/models/commerce/Review.ts +171 -0
- package/dist/models/commerce/ShippingMethod.ts +102 -0
- package/dist/models/commerce/ShippingRate.ts +79 -0
- package/dist/models/commerce/ShippingZone.ts +116 -0
- package/dist/models/commerce/TaxRate.ts +167 -0
- package/dist/models/commerce/Transaction.ts +127 -0
- package/dist/models/commerce/WaitlistProduct.ts +151 -0
- package/dist/models/commerce/WaitlistRestaurant.ts +189 -0
- package/dist/models/realtime/Websocket.ts +78 -0
- package/dist/password-policy.ts +46 -0
- package/package.json +4 -4
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
import type { Attributes } from '@stacksjs/types'
|
|
2
|
+
import { defineModel } from '@stacksjs/orm'
|
|
3
|
+
import { schema } from '@stacksjs/validation'
|
|
4
|
+
|
|
5
|
+
export default defineModel({
|
|
6
|
+
name: 'Customer',
|
|
7
|
+
table: 'customers',
|
|
8
|
+
primaryKey: 'id',
|
|
9
|
+
autoIncrement: true,
|
|
10
|
+
|
|
11
|
+
traits: {
|
|
12
|
+
useUuid: true,
|
|
13
|
+
useTimestamps: true,
|
|
14
|
+
useSearch: {
|
|
15
|
+
displayable: ['id', 'name', 'email', 'phone', 'status'],
|
|
16
|
+
searchable: ['name', 'email', 'phone'],
|
|
17
|
+
sortable: ['name', 'totalSpent', 'lastOrder', 'created_at', 'updated_at'],
|
|
18
|
+
filterable: ['status'],
|
|
19
|
+
},
|
|
20
|
+
|
|
21
|
+
useSeeder: {
|
|
22
|
+
count: 20,
|
|
23
|
+
},
|
|
24
|
+
|
|
25
|
+
useApi: {
|
|
26
|
+
uri: 'customers',
|
|
27
|
+
middleware: ['auth'],
|
|
28
|
+
},
|
|
29
|
+
|
|
30
|
+
observe: true,
|
|
31
|
+
},
|
|
32
|
+
|
|
33
|
+
hasMany: ['Order', 'GiftCard', 'Review', 'Payment', 'LicenseKey', 'WaitlistProduct', 'WaitlistRestaurant'],
|
|
34
|
+
belongsTo: ['User'],
|
|
35
|
+
|
|
36
|
+
attributes: {
|
|
37
|
+
name: {
|
|
38
|
+
order: 1,
|
|
39
|
+
fillable: true,
|
|
40
|
+
validation: {
|
|
41
|
+
rule: schema.string().required().min(2).max(255),
|
|
42
|
+
message: {
|
|
43
|
+
min: 'Name must have a minimum of 2 characters',
|
|
44
|
+
max: 'Name must have a maximum of 255 characters',
|
|
45
|
+
},
|
|
46
|
+
},
|
|
47
|
+
factory: faker => faker.person.fullName(),
|
|
48
|
+
},
|
|
49
|
+
|
|
50
|
+
email: {
|
|
51
|
+
unique: true,
|
|
52
|
+
order: 2,
|
|
53
|
+
fillable: true,
|
|
54
|
+
validation: {
|
|
55
|
+
rule: schema.string().required().email(),
|
|
56
|
+
message: {
|
|
57
|
+
email: 'Email must be a valid email address',
|
|
58
|
+
},
|
|
59
|
+
},
|
|
60
|
+
factory: faker => faker.internet.email(),
|
|
61
|
+
},
|
|
62
|
+
|
|
63
|
+
phone: {
|
|
64
|
+
order: 3,
|
|
65
|
+
fillable: true,
|
|
66
|
+
validation: {
|
|
67
|
+
rule: schema.string().max(50),
|
|
68
|
+
message: {
|
|
69
|
+
max: 'Phone number must have a maximum of 50 characters',
|
|
70
|
+
},
|
|
71
|
+
},
|
|
72
|
+
factory: faker => faker.phone.number(),
|
|
73
|
+
},
|
|
74
|
+
|
|
75
|
+
totalSpent: {
|
|
76
|
+
default: 0,
|
|
77
|
+
order: 5,
|
|
78
|
+
fillable: true,
|
|
79
|
+
validation: {
|
|
80
|
+
rule: schema.number().min(0),
|
|
81
|
+
message: {
|
|
82
|
+
min: 'Total spent cannot be negative',
|
|
83
|
+
},
|
|
84
|
+
},
|
|
85
|
+
factory: faker => faker.number.int({ min: 0, max: 2000 }),
|
|
86
|
+
},
|
|
87
|
+
|
|
88
|
+
lastOrder: {
|
|
89
|
+
order: 6,
|
|
90
|
+
fillable: true,
|
|
91
|
+
validation: {
|
|
92
|
+
rule: schema.string(),
|
|
93
|
+
},
|
|
94
|
+
factory: faker => faker.date.recent({ days: 60 }).toISOString().split('T')[0],
|
|
95
|
+
},
|
|
96
|
+
|
|
97
|
+
status: {
|
|
98
|
+
default: 'Active',
|
|
99
|
+
order: 7,
|
|
100
|
+
fillable: true,
|
|
101
|
+
validation: {
|
|
102
|
+
rule: schema.enum(['Active', 'Inactive']).required(),
|
|
103
|
+
message: {
|
|
104
|
+
enum: 'Status must be either Active or Inactive',
|
|
105
|
+
},
|
|
106
|
+
},
|
|
107
|
+
factory: faker => faker.helpers.arrayElement(['Active', 'Inactive']),
|
|
108
|
+
},
|
|
109
|
+
|
|
110
|
+
avatar: {
|
|
111
|
+
order: 8,
|
|
112
|
+
fillable: true,
|
|
113
|
+
validation: {
|
|
114
|
+
rule: schema.string().required().url(),
|
|
115
|
+
message: {
|
|
116
|
+
url: 'Avatar must be a valid URL',
|
|
117
|
+
},
|
|
118
|
+
},
|
|
119
|
+
factory: faker => faker.image.avatar(),
|
|
120
|
+
},
|
|
121
|
+
},
|
|
122
|
+
|
|
123
|
+
get: {
|
|
124
|
+
fullContactInfo: (attributes: Attributes) => {
|
|
125
|
+
return `${attributes.name} (${attributes.email}, ${attributes.phone})`
|
|
126
|
+
},
|
|
127
|
+
},
|
|
128
|
+
|
|
129
|
+
dashboard: {
|
|
130
|
+
highlight: true,
|
|
131
|
+
},
|
|
132
|
+
} as const)
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
import { defineModel } from '@stacksjs/orm'
|
|
2
|
+
import { schema } from '@stacksjs/validation'
|
|
3
|
+
|
|
4
|
+
export default defineModel({
|
|
5
|
+
name: 'DeliveryRoute',
|
|
6
|
+
table: 'delivery_routes',
|
|
7
|
+
primaryKey: 'id',
|
|
8
|
+
autoIncrement: true,
|
|
9
|
+
|
|
10
|
+
traits: {
|
|
11
|
+
useUuid: true,
|
|
12
|
+
useTimestamps: true,
|
|
13
|
+
useSearch: {
|
|
14
|
+
displayable: ['id', 'driver', 'vehicle', 'stops', 'deliveryTime', 'totalDistance', 'lastActive'],
|
|
15
|
+
searchable: ['driver', 'vehicle'],
|
|
16
|
+
sortable: ['stops', 'deliveryTime', 'totalDistance', 'lastActive', 'createdAt', 'updatedAt'],
|
|
17
|
+
filterable: ['driver', 'vehicle'],
|
|
18
|
+
},
|
|
19
|
+
|
|
20
|
+
useSeeder: {
|
|
21
|
+
count: 5,
|
|
22
|
+
},
|
|
23
|
+
|
|
24
|
+
useApi: {
|
|
25
|
+
uri: 'delivery-routes',
|
|
26
|
+
middleware: ['auth'],
|
|
27
|
+
},
|
|
28
|
+
|
|
29
|
+
observe: true,
|
|
30
|
+
},
|
|
31
|
+
|
|
32
|
+
belongsTo: ['Driver'],
|
|
33
|
+
hasMany: ['DeliveryStop', 'DriverPing'],
|
|
34
|
+
|
|
35
|
+
attributes: {
|
|
36
|
+
driver: {
|
|
37
|
+
order: 1,
|
|
38
|
+
fillable: true,
|
|
39
|
+
validation: {
|
|
40
|
+
rule: schema.string().required().max(255),
|
|
41
|
+
},
|
|
42
|
+
factory: faker => faker.person.fullName(),
|
|
43
|
+
},
|
|
44
|
+
|
|
45
|
+
vehicle: {
|
|
46
|
+
order: 2,
|
|
47
|
+
fillable: true,
|
|
48
|
+
validation: {
|
|
49
|
+
rule: schema.string().required().max(255),
|
|
50
|
+
},
|
|
51
|
+
factory: faker => faker.vehicle.vehicle(),
|
|
52
|
+
},
|
|
53
|
+
|
|
54
|
+
stops: {
|
|
55
|
+
order: 3,
|
|
56
|
+
fillable: true,
|
|
57
|
+
validation: {
|
|
58
|
+
rule: schema.number().required().min(0),
|
|
59
|
+
},
|
|
60
|
+
factory: faker => faker.number.int({ min: 1, max: 20 }),
|
|
61
|
+
},
|
|
62
|
+
|
|
63
|
+
deliveryTime: {
|
|
64
|
+
order: 4,
|
|
65
|
+
fillable: true,
|
|
66
|
+
validation: {
|
|
67
|
+
rule: schema.number().required().min(0),
|
|
68
|
+
},
|
|
69
|
+
factory: faker => faker.number.int({ min: 30, max: 480 }), // 30 minutes to 8 hours
|
|
70
|
+
},
|
|
71
|
+
|
|
72
|
+
totalDistance: {
|
|
73
|
+
order: 5,
|
|
74
|
+
fillable: true,
|
|
75
|
+
validation: {
|
|
76
|
+
rule: schema.number().required().min(0),
|
|
77
|
+
},
|
|
78
|
+
factory: faker => faker.number.int({ min: 5, max: 200 }), // 5 to 200 miles
|
|
79
|
+
},
|
|
80
|
+
|
|
81
|
+
lastActive: {
|
|
82
|
+
order: 6,
|
|
83
|
+
fillable: true,
|
|
84
|
+
validation: {
|
|
85
|
+
rule: schema.unix().required(),
|
|
86
|
+
},
|
|
87
|
+
factory: faker => faker.date.recent().getTime(),
|
|
88
|
+
},
|
|
89
|
+
|
|
90
|
+
/*
|
|
91
|
+
* Route lifecycle. `stops` and `totalDistance` describe a route that has
|
|
92
|
+
* already run; a route being followed right now needs to say so, because
|
|
93
|
+
* that is the difference between a tracking map that draws a moving
|
|
94
|
+
* vehicle and one that draws yesterday's.
|
|
95
|
+
*/
|
|
96
|
+
status: {
|
|
97
|
+
order: 7,
|
|
98
|
+
fillable: true,
|
|
99
|
+
default: 'planned',
|
|
100
|
+
validation: {
|
|
101
|
+
rule: schema.enum(['planned', 'active', 'completed', 'cancelled']),
|
|
102
|
+
message: {
|
|
103
|
+
enum: 'Status must be one of: planned, active, completed, cancelled',
|
|
104
|
+
},
|
|
105
|
+
},
|
|
106
|
+
factory: faker => faker.helpers.arrayElement(['planned', 'active', 'completed']),
|
|
107
|
+
},
|
|
108
|
+
|
|
109
|
+
startedAt: {
|
|
110
|
+
order: 8,
|
|
111
|
+
fillable: true,
|
|
112
|
+
validation: { rule: schema.timestamp() },
|
|
113
|
+
factory: () => null,
|
|
114
|
+
},
|
|
115
|
+
|
|
116
|
+
completedAt: {
|
|
117
|
+
order: 9,
|
|
118
|
+
fillable: true,
|
|
119
|
+
validation: { rule: schema.timestamp() },
|
|
120
|
+
factory: () => null,
|
|
121
|
+
},
|
|
122
|
+
},
|
|
123
|
+
|
|
124
|
+
dashboard: {
|
|
125
|
+
highlight: true,
|
|
126
|
+
},
|
|
127
|
+
} as const)
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
import { defineModel } from '@stacksjs/orm'
|
|
2
|
+
import { schema } from '@stacksjs/validation'
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* One address on a delivery route.
|
|
6
|
+
*
|
|
7
|
+
* `DeliveryRoute` records that a route happened and how far it went; this is
|
|
8
|
+
* the route itself. Without it `stops` is an integer, which is enough to bill
|
|
9
|
+
* a driver and not nearly enough to tell a customer where their order is: the
|
|
10
|
+
* link from an order to the vehicle carrying it runs through here.
|
|
11
|
+
*
|
|
12
|
+
* Sequence is the planned order. `status` is what actually happened, which
|
|
13
|
+
* diverges the moment a driver skips a building and comes back to it.
|
|
14
|
+
*/
|
|
15
|
+
export default defineModel({
|
|
16
|
+
name: 'DeliveryStop',
|
|
17
|
+
table: 'delivery_stops',
|
|
18
|
+
primaryKey: 'id',
|
|
19
|
+
autoIncrement: true,
|
|
20
|
+
|
|
21
|
+
traits: {
|
|
22
|
+
useUuid: true,
|
|
23
|
+
useTimestamps: true,
|
|
24
|
+
|
|
25
|
+
useSearch: {
|
|
26
|
+
displayable: ['id', 'sequence', 'status', 'address', 'etaAt'],
|
|
27
|
+
searchable: ['address', 'recipientName'],
|
|
28
|
+
sortable: ['sequence', 'etaAt', 'createdAt'],
|
|
29
|
+
filterable: ['status', 'deliveryRouteId'],
|
|
30
|
+
},
|
|
31
|
+
|
|
32
|
+
useSeeder: { count: 0 },
|
|
33
|
+
|
|
34
|
+
useApi: {
|
|
35
|
+
// A stop names a customer and their address. Staff only, both ways.
|
|
36
|
+
// Customers reach their own stop through the order tracking route,
|
|
37
|
+
// which authorises on the order's tracking token instead.
|
|
38
|
+
middleware: ['auth'],
|
|
39
|
+
uri: 'delivery-stops',
|
|
40
|
+
},
|
|
41
|
+
|
|
42
|
+
observe: true,
|
|
43
|
+
},
|
|
44
|
+
|
|
45
|
+
belongsTo: ['DeliveryRoute', 'Order'],
|
|
46
|
+
|
|
47
|
+
attributes: {
|
|
48
|
+
/** Position in the planned run, 1-based. */
|
|
49
|
+
sequence: {
|
|
50
|
+
order: 1,
|
|
51
|
+
required: true,
|
|
52
|
+
fillable: true,
|
|
53
|
+
default: 1,
|
|
54
|
+
validation: {
|
|
55
|
+
rule: schema.number().required().min(1),
|
|
56
|
+
message: { min: 'Sequence starts at 1' },
|
|
57
|
+
},
|
|
58
|
+
factory: faker => faker.number.int({ min: 1, max: 20 }),
|
|
59
|
+
},
|
|
60
|
+
|
|
61
|
+
status: {
|
|
62
|
+
order: 2,
|
|
63
|
+
required: true,
|
|
64
|
+
fillable: true,
|
|
65
|
+
default: 'pending',
|
|
66
|
+
validation: {
|
|
67
|
+
rule: schema.enum(['pending', 'en_route', 'arrived', 'completed', 'failed', 'skipped']),
|
|
68
|
+
message: {
|
|
69
|
+
enum: 'Status must be one of: pending, en_route, arrived, completed, failed, skipped',
|
|
70
|
+
},
|
|
71
|
+
},
|
|
72
|
+
factory: faker => faker.helpers.arrayElement(['pending', 'en_route', 'completed']),
|
|
73
|
+
},
|
|
74
|
+
|
|
75
|
+
address: {
|
|
76
|
+
order: 3,
|
|
77
|
+
required: true,
|
|
78
|
+
fillable: true,
|
|
79
|
+
validation: { rule: schema.string().required().max(255) },
|
|
80
|
+
factory: faker => faker.location.streetAddress(true),
|
|
81
|
+
},
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Geocoded destination. Nullable because an address can arrive before it
|
|
85
|
+
* resolves, and a stop with no coordinates is still a stop the driver can
|
|
86
|
+
* complete; it just cannot be drawn.
|
|
87
|
+
*/
|
|
88
|
+
latitude: {
|
|
89
|
+
order: 4,
|
|
90
|
+
fillable: true,
|
|
91
|
+
validation: { rule: schema.number().min(-90).max(90) },
|
|
92
|
+
factory: faker => faker.location.latitude(),
|
|
93
|
+
},
|
|
94
|
+
|
|
95
|
+
longitude: {
|
|
96
|
+
order: 5,
|
|
97
|
+
fillable: true,
|
|
98
|
+
validation: { rule: schema.number().min(-180).max(180) },
|
|
99
|
+
factory: faker => faker.location.longitude(),
|
|
100
|
+
},
|
|
101
|
+
|
|
102
|
+
recipientName: {
|
|
103
|
+
order: 6,
|
|
104
|
+
fillable: true,
|
|
105
|
+
validation: { rule: schema.string().max(255) },
|
|
106
|
+
factory: faker => faker.person.fullName(),
|
|
107
|
+
},
|
|
108
|
+
|
|
109
|
+
recipientPhone: {
|
|
110
|
+
order: 7,
|
|
111
|
+
fillable: true,
|
|
112
|
+
validation: { rule: schema.string().max(40) },
|
|
113
|
+
factory: faker => faker.phone.number(),
|
|
114
|
+
},
|
|
115
|
+
|
|
116
|
+
/** Current estimate, rewritten as the driver moves. */
|
|
117
|
+
etaAt: {
|
|
118
|
+
order: 8,
|
|
119
|
+
fillable: true,
|
|
120
|
+
validation: { rule: schema.timestamp() },
|
|
121
|
+
factory: () => new Date(Date.now() + 25 * 60_000).toISOString(),
|
|
122
|
+
},
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* When the customer was told the driver was close.
|
|
126
|
+
*
|
|
127
|
+
* The latch that makes "nearly there" fire once. Without it every ping
|
|
128
|
+
* inside the radius sends another text, which is roughly one text every
|
|
129
|
+
* four seconds for the last four hundred metres.
|
|
130
|
+
*/
|
|
131
|
+
notifiedNearbyAt: {
|
|
132
|
+
order: 9,
|
|
133
|
+
fillable: true,
|
|
134
|
+
validation: { rule: schema.timestamp() },
|
|
135
|
+
factory: () => null,
|
|
136
|
+
},
|
|
137
|
+
|
|
138
|
+
/** When the driver crossed the arrival radius. */
|
|
139
|
+
arrivedAt: {
|
|
140
|
+
order: 10,
|
|
141
|
+
fillable: true,
|
|
142
|
+
validation: { rule: schema.timestamp() },
|
|
143
|
+
factory: () => null,
|
|
144
|
+
},
|
|
145
|
+
|
|
146
|
+
/** When the handover finished. Together with `arrivedAt`, dwell time. */
|
|
147
|
+
completedAt: {
|
|
148
|
+
order: 11,
|
|
149
|
+
fillable: true,
|
|
150
|
+
validation: { rule: schema.timestamp() },
|
|
151
|
+
factory: () => null,
|
|
152
|
+
},
|
|
153
|
+
|
|
154
|
+
/** Why a stop failed, or anything the driver needs to record. */
|
|
155
|
+
notes: {
|
|
156
|
+
order: 12,
|
|
157
|
+
fillable: true,
|
|
158
|
+
validation: { rule: schema.string().max(1000) },
|
|
159
|
+
factory: () => '',
|
|
160
|
+
},
|
|
161
|
+
},
|
|
162
|
+
|
|
163
|
+
dashboard: {
|
|
164
|
+
highlight: true,
|
|
165
|
+
},
|
|
166
|
+
} as const)
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import { defineModel } from '@stacksjs/orm'
|
|
2
|
+
import { schema } from '@stacksjs/validation'
|
|
3
|
+
|
|
4
|
+
export default defineModel({
|
|
5
|
+
name: 'DigitalDelivery',
|
|
6
|
+
table: 'digital_deliveries',
|
|
7
|
+
primaryKey: 'id',
|
|
8
|
+
autoIncrement: true,
|
|
9
|
+
|
|
10
|
+
traits: {
|
|
11
|
+
useUuid: true,
|
|
12
|
+
useTimestamps: true,
|
|
13
|
+
useSearch: {
|
|
14
|
+
displayable: ['id', 'name', 'description', 'downloadLimit', 'expiryDays', 'status'],
|
|
15
|
+
searchable: ['name', 'description'],
|
|
16
|
+
sortable: ['name', 'downloadLimit', 'expiryDays', 'status', 'createdAt', 'updatedAt'],
|
|
17
|
+
filterable: ['status'],
|
|
18
|
+
},
|
|
19
|
+
|
|
20
|
+
useSeeder: {
|
|
21
|
+
count: 10,
|
|
22
|
+
},
|
|
23
|
+
|
|
24
|
+
useApi: {
|
|
25
|
+
uri: 'digital-deliveries',
|
|
26
|
+
middleware: ['auth'],
|
|
27
|
+
},
|
|
28
|
+
|
|
29
|
+
observe: true,
|
|
30
|
+
},
|
|
31
|
+
|
|
32
|
+
attributes: {
|
|
33
|
+
name: {
|
|
34
|
+
order: 2,
|
|
35
|
+
fillable: true,
|
|
36
|
+
validation: {
|
|
37
|
+
rule: schema.string().required().max(255),
|
|
38
|
+
message: {
|
|
39
|
+
max: 'Name must have a maximum of 255 characters',
|
|
40
|
+
},
|
|
41
|
+
},
|
|
42
|
+
factory: faker => faker.commerce.productName(),
|
|
43
|
+
},
|
|
44
|
+
|
|
45
|
+
description: {
|
|
46
|
+
order: 3,
|
|
47
|
+
fillable: true,
|
|
48
|
+
validation: {
|
|
49
|
+
rule: schema.string().required(),
|
|
50
|
+
},
|
|
51
|
+
factory: faker => faker.commerce.productDescription(),
|
|
52
|
+
},
|
|
53
|
+
|
|
54
|
+
downloadLimit: {
|
|
55
|
+
order: 4,
|
|
56
|
+
fillable: true,
|
|
57
|
+
validation: {
|
|
58
|
+
rule: schema.number().min(0),
|
|
59
|
+
},
|
|
60
|
+
factory: faker => faker.number.int({ min: 1, max: 100 }),
|
|
61
|
+
},
|
|
62
|
+
|
|
63
|
+
expiryDays: {
|
|
64
|
+
order: 5,
|
|
65
|
+
fillable: true,
|
|
66
|
+
validation: {
|
|
67
|
+
rule: schema.number().required().min(0),
|
|
68
|
+
},
|
|
69
|
+
factory: faker => faker.number.int({ min: 1, max: 365 }),
|
|
70
|
+
},
|
|
71
|
+
|
|
72
|
+
requiresLogin: {
|
|
73
|
+
default: false,
|
|
74
|
+
order: 6,
|
|
75
|
+
fillable: true,
|
|
76
|
+
validation: {
|
|
77
|
+
rule: schema.boolean(),
|
|
78
|
+
},
|
|
79
|
+
factory: faker => faker.datatype.boolean(),
|
|
80
|
+
},
|
|
81
|
+
|
|
82
|
+
automaticDelivery: {
|
|
83
|
+
default: false,
|
|
84
|
+
order: 7,
|
|
85
|
+
fillable: true,
|
|
86
|
+
validation: {
|
|
87
|
+
rule: schema.boolean(),
|
|
88
|
+
},
|
|
89
|
+
factory: faker => faker.datatype.boolean(),
|
|
90
|
+
},
|
|
91
|
+
|
|
92
|
+
status: {
|
|
93
|
+
default: 'active',
|
|
94
|
+
order: 8,
|
|
95
|
+
fillable: true,
|
|
96
|
+
validation: {
|
|
97
|
+
rule: schema.enum(['active', 'inactive']),
|
|
98
|
+
},
|
|
99
|
+
factory: faker => faker.helpers.arrayElement(['active', 'inactive']),
|
|
100
|
+
},
|
|
101
|
+
},
|
|
102
|
+
|
|
103
|
+
dashboard: {
|
|
104
|
+
highlight: true,
|
|
105
|
+
},
|
|
106
|
+
} as const)
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
import { defineModel } from '@stacksjs/orm'
|
|
2
|
+
import { schema } from '@stacksjs/validation'
|
|
3
|
+
|
|
4
|
+
export default defineModel({
|
|
5
|
+
name: 'Driver',
|
|
6
|
+
table: 'drivers',
|
|
7
|
+
primaryKey: 'id',
|
|
8
|
+
autoIncrement: true,
|
|
9
|
+
|
|
10
|
+
belongsTo: ['User'],
|
|
11
|
+
hasMany: ['DeliveryRoute', 'DriverPing'],
|
|
12
|
+
|
|
13
|
+
traits: {
|
|
14
|
+
useUuid: true,
|
|
15
|
+
useTimestamps: true,
|
|
16
|
+
useSearch: {
|
|
17
|
+
displayable: ['id', 'name', 'phone', 'vehicleNumber', 'license', 'status'],
|
|
18
|
+
searchable: ['name', 'phone', 'vehicleNumber', 'license'],
|
|
19
|
+
sortable: ['name', 'status', 'createdAt', 'updatedAt'],
|
|
20
|
+
filterable: ['status'],
|
|
21
|
+
},
|
|
22
|
+
|
|
23
|
+
useSeeder: {
|
|
24
|
+
count: 10,
|
|
25
|
+
},
|
|
26
|
+
|
|
27
|
+
useApi: {
|
|
28
|
+
uri: 'drivers',
|
|
29
|
+
middleware: ['auth'],
|
|
30
|
+
},
|
|
31
|
+
|
|
32
|
+
observe: true,
|
|
33
|
+
},
|
|
34
|
+
|
|
35
|
+
attributes: {
|
|
36
|
+
name: {
|
|
37
|
+
order: 1,
|
|
38
|
+
fillable: true,
|
|
39
|
+
validation: {
|
|
40
|
+
rule: schema.string().required().max(255),
|
|
41
|
+
message: {
|
|
42
|
+
max: 'Name must have a maximum of 255 characters',
|
|
43
|
+
},
|
|
44
|
+
},
|
|
45
|
+
factory: faker => faker.person.fullName(),
|
|
46
|
+
},
|
|
47
|
+
|
|
48
|
+
phone: {
|
|
49
|
+
order: 2,
|
|
50
|
+
fillable: true,
|
|
51
|
+
validation: {
|
|
52
|
+
rule: schema.string().required().max(255),
|
|
53
|
+
message: {
|
|
54
|
+
max: 'Phone number must have a maximum of 255 characters',
|
|
55
|
+
},
|
|
56
|
+
},
|
|
57
|
+
factory: faker => faker.phone.number(),
|
|
58
|
+
},
|
|
59
|
+
|
|
60
|
+
vehicleNumber: {
|
|
61
|
+
order: 3,
|
|
62
|
+
fillable: true,
|
|
63
|
+
validation: {
|
|
64
|
+
rule: schema.string().required().max(255),
|
|
65
|
+
message: {
|
|
66
|
+
max: 'Vehicle number must have a maximum of 255 characters',
|
|
67
|
+
},
|
|
68
|
+
},
|
|
69
|
+
factory: faker => faker.vehicle.vrm(),
|
|
70
|
+
},
|
|
71
|
+
|
|
72
|
+
license: {
|
|
73
|
+
order: 4,
|
|
74
|
+
fillable: true,
|
|
75
|
+
validation: {
|
|
76
|
+
rule: schema.string().required().max(255),
|
|
77
|
+
message: {
|
|
78
|
+
max: 'License number must have a maximum of 255 characters',
|
|
79
|
+
},
|
|
80
|
+
},
|
|
81
|
+
factory: faker => faker.string.alphanumeric({ length: 10, casing: 'upper' }),
|
|
82
|
+
},
|
|
83
|
+
|
|
84
|
+
status: {
|
|
85
|
+
default: 'active',
|
|
86
|
+
order: 5,
|
|
87
|
+
fillable: true,
|
|
88
|
+
validation: {
|
|
89
|
+
rule: schema.enum(['active', 'on_delivery', 'on_break', 'offline']),
|
|
90
|
+
},
|
|
91
|
+
factory: faker => faker.helpers.arrayElement(['active', 'on_delivery', 'on_break']),
|
|
92
|
+
},
|
|
93
|
+
|
|
94
|
+
/*
|
|
95
|
+
* Last known position.
|
|
96
|
+
*
|
|
97
|
+
* Denormalised from the `driver_pings` series on purpose: "where is this
|
|
98
|
+
* driver right now" is asked on every map frame and by every tracking
|
|
99
|
+
* page, and answering it with a MAX(recorded_at) subquery over a table
|
|
100
|
+
* that grows by a row every few seconds is the wrong shape. The series is
|
|
101
|
+
* the history; these three columns are the present.
|
|
102
|
+
*/
|
|
103
|
+
latitude: {
|
|
104
|
+
order: 6,
|
|
105
|
+
fillable: true,
|
|
106
|
+
validation: {
|
|
107
|
+
rule: schema.number().min(-90).max(90),
|
|
108
|
+
message: {
|
|
109
|
+
min: 'Latitude must be between -90 and 90',
|
|
110
|
+
max: 'Latitude must be between -90 and 90',
|
|
111
|
+
},
|
|
112
|
+
},
|
|
113
|
+
factory: faker => faker.location.latitude(),
|
|
114
|
+
},
|
|
115
|
+
|
|
116
|
+
longitude: {
|
|
117
|
+
order: 7,
|
|
118
|
+
fillable: true,
|
|
119
|
+
validation: {
|
|
120
|
+
rule: schema.number().min(-180).max(180),
|
|
121
|
+
message: {
|
|
122
|
+
min: 'Longitude must be between -180 and 180',
|
|
123
|
+
max: 'Longitude must be between -180 and 180',
|
|
124
|
+
},
|
|
125
|
+
},
|
|
126
|
+
factory: faker => faker.location.longitude(),
|
|
127
|
+
},
|
|
128
|
+
|
|
129
|
+
/** Degrees clockwise from true north, so a map can rotate the marker. */
|
|
130
|
+
heading: {
|
|
131
|
+
order: 8,
|
|
132
|
+
fillable: true,
|
|
133
|
+
validation: {
|
|
134
|
+
rule: schema.number().min(0).max(360),
|
|
135
|
+
},
|
|
136
|
+
factory: faker => faker.number.int({ min: 0, max: 359 }),
|
|
137
|
+
},
|
|
138
|
+
|
|
139
|
+
/** Metres per second, as reported by the device. */
|
|
140
|
+
speed: {
|
|
141
|
+
order: 9,
|
|
142
|
+
fillable: true,
|
|
143
|
+
default: 0,
|
|
144
|
+
validation: {
|
|
145
|
+
rule: schema.number().min(0),
|
|
146
|
+
},
|
|
147
|
+
factory: () => 0,
|
|
148
|
+
},
|
|
149
|
+
|
|
150
|
+
/*
|
|
151
|
+
* When the last fix landed. A tracking map needs this to say "updated 4
|
|
152
|
+
* seconds ago" and, more importantly, to stop claiming a driver is at a
|
|
153
|
+
* position that is ten minutes stale.
|
|
154
|
+
*/
|
|
155
|
+
lastPingAt: {
|
|
156
|
+
order: 10,
|
|
157
|
+
fillable: true,
|
|
158
|
+
validation: {
|
|
159
|
+
rule: schema.timestamp(),
|
|
160
|
+
},
|
|
161
|
+
factory: () => new Date().toISOString(),
|
|
162
|
+
},
|
|
163
|
+
},
|
|
164
|
+
|
|
165
|
+
dashboard: {
|
|
166
|
+
highlight: true,
|
|
167
|
+
},
|
|
168
|
+
} as const)
|