tt-entities 0.0.56 → 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/dist/libs/tatayab-entities-library/entities/order-edit-item.entity.d.ts +24 -0
- package/dist/libs/tatayab-entities-library/entities/order-edit-item.entity.js +98 -0
- package/dist/libs/tatayab-entities-library/entities/order-edit-item.entity.js.map +1 -0
- package/dist/libs/tatayab-entities-library/entities/order-edit-session.entity.d.ts +10 -0
- package/dist/libs/tatayab-entities-library/entities/order-edit-session.entity.js +43 -0
- package/dist/libs/tatayab-entities-library/entities/order-edit-session.entity.js.map +1 -0
- package/dist/libs/tatayab-entities-library/entities/order-item-change-log.entity.d.ts +22 -0
- package/dist/libs/tatayab-entities-library/entities/order-item-change-log.entity.js +77 -0
- package/dist/libs/tatayab-entities-library/entities/order-item-change-log.entity.js.map +1 -0
- package/dist/libs/tatayab-entities-library/entities/order-payment.entity.d.ts +28 -0
- package/dist/libs/tatayab-entities-library/entities/order-payment.entity.js +95 -0
- package/dist/libs/tatayab-entities-library/entities/order-payment.entity.js.map +1 -0
- package/dist/libs/tatayab-entities-library/entities/order.entity.d.ts +7 -0
- package/dist/libs/tatayab-entities-library/entities/order.entity.js +19 -0
- package/dist/libs/tatayab-entities-library/entities/order.entity.js.map +1 -1
- package/dist/libs/tatayab-entities-library/entities/refund.entity.d.ts +8 -0
- package/dist/libs/tatayab-entities-library/entities/refund.entity.js +24 -1
- package/dist/libs/tatayab-entities-library/entities/refund.entity.js.map +1 -1
- package/libs/tatayab-entities-library/src/entities/order-edit-item.entity.ts +101 -0
- package/libs/tatayab-entities-library/src/entities/order-edit-session.entity.ts +47 -0
- package/libs/tatayab-entities-library/src/entities/order-item-change-log.entity.ts +84 -0
- package/libs/tatayab-entities-library/src/entities/order-payment.entity.ts +107 -0
- package/libs/tatayab-entities-library/src/entities/order.entity.ts +15 -0
- package/libs/tatayab-entities-library/src/entities/refund.entity.ts +25 -0
- package/package.json +1 -1
- package/src/app.module.ts +0 -2
- package/src/database/config.js +4 -4
- package/src/database/migrations/20260615103000-add-edit-order-feature.js +283 -0
- package/libs/tatayab-entities-library/src/entities.zip +0 -0
|
@@ -0,0 +1,283 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Edit Order — schema migration.
|
|
5
|
+
*
|
|
6
|
+
* FK columns and new table PKs use Sequelize.INTEGER to match the existing
|
|
7
|
+
* Orders.id / OrderItems.id (Sequelize default PK type is signed INTEGER,
|
|
8
|
+
* not BIGINT UNSIGNED).
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
module.exports = {
|
|
12
|
+
up: async (queryInterface, Sequelize) => {
|
|
13
|
+
// ── 1. Orders.hasEverNeededVendor ──────────────────────────────────────
|
|
14
|
+
await queryInterface.sequelize.query(`
|
|
15
|
+
ALTER TABLE \`Orders\`
|
|
16
|
+
ADD COLUMN \`hasEverNeededVendor\` TINYINT(1) NOT NULL DEFAULT 0
|
|
17
|
+
COMMENT 'Set once at placement: true if any OrderItem ever started as awaiting_stock. Used by the Edit Order eligibility check (rule 5) — an awaiting_fulfillment order that ever needed vendor sourcing cannot be edited.'
|
|
18
|
+
AFTER \`refundStatus\`
|
|
19
|
+
`);
|
|
20
|
+
|
|
21
|
+
// ── 2. Refunds.source ────────────────────────────────────────────────
|
|
22
|
+
await queryInterface.sequelize.query(`
|
|
23
|
+
ALTER TABLE \`Refunds\`
|
|
24
|
+
ADD COLUMN \`source\` ENUM('cancellation', 'order_edit') NOT NULL DEFAULT 'cancellation'
|
|
25
|
+
COMMENT 'Where this refund originated. order_edit refunds are created when an edit lowers the order total (rule 3b).'
|
|
26
|
+
AFTER \`notes\`
|
|
27
|
+
`);
|
|
28
|
+
|
|
29
|
+
// ── 3. Refunds.editLogId + index ────────────────────────────────────────
|
|
30
|
+
await queryInterface.sequelize.query(`
|
|
31
|
+
ALTER TABLE \`Refunds\`
|
|
32
|
+
ADD COLUMN \`editLogId\` INT NULL
|
|
33
|
+
COMMENT 'FK -> OrderItemChangeLogs.id. Set when source = order_edit; links this refund to the specific edit that produced it.'
|
|
34
|
+
AFTER \`source\`
|
|
35
|
+
`);
|
|
36
|
+
await queryInterface.addIndex('Refunds', ['editLogId'], {
|
|
37
|
+
name: 'refunds_edit_log_id_index',
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
// ── 4. OrderItemChangeLogs — rule 2: one row per confirmed edit ──────────
|
|
41
|
+
await queryInterface.createTable('OrderItemChangeLogs', {
|
|
42
|
+
id: { type: Sequelize.INTEGER, autoIncrement: true, primaryKey: true },
|
|
43
|
+
orderId: {
|
|
44
|
+
type: Sequelize.INTEGER,
|
|
45
|
+
allowNull: false,
|
|
46
|
+
references: { model: 'Orders', key: 'id' },
|
|
47
|
+
onDelete: 'CASCADE',
|
|
48
|
+
onUpdate: 'CASCADE',
|
|
49
|
+
},
|
|
50
|
+
editNumber: {
|
|
51
|
+
type: Sequelize.INTEGER,
|
|
52
|
+
allowNull: false,
|
|
53
|
+
comment:
|
|
54
|
+
'Sequential per order: 1, 2, 3... — the Nth confirmed edit on this order.',
|
|
55
|
+
},
|
|
56
|
+
beforeItemsJson: {
|
|
57
|
+
type: Sequelize.JSON,
|
|
58
|
+
allowNull: false,
|
|
59
|
+
comment:
|
|
60
|
+
'Snapshot of the basket immediately before this edit was applied.',
|
|
61
|
+
},
|
|
62
|
+
afterItemsJson: {
|
|
63
|
+
type: Sequelize.JSON,
|
|
64
|
+
allowNull: false,
|
|
65
|
+
comment:
|
|
66
|
+
'Snapshot of the basket immediately after this edit was applied.',
|
|
67
|
+
},
|
|
68
|
+
summary: {
|
|
69
|
+
type: Sequelize.TEXT,
|
|
70
|
+
allowNull: false,
|
|
71
|
+
comment:
|
|
72
|
+
'Pre-rendered human-readable diff, e.g. "Invictus: 2 -> 1, Champion: 0 -> 1 (+1)". Computed once at confirm-time.',
|
|
73
|
+
},
|
|
74
|
+
priceDelta: {
|
|
75
|
+
type: Sequelize.DECIMAL(10, 3),
|
|
76
|
+
allowNull: false,
|
|
77
|
+
defaultValue: 0,
|
|
78
|
+
comment:
|
|
79
|
+
'newTotal - oldTotal. Positive = customer owes more, negative = refund issued, zero = no payment change.',
|
|
80
|
+
},
|
|
81
|
+
scenario: {
|
|
82
|
+
type: Sequelize.ENUM('same', 'refund', 'additional_payment'),
|
|
83
|
+
allowNull: false,
|
|
84
|
+
},
|
|
85
|
+
changedBy: {
|
|
86
|
+
type: Sequelize.STRING(64),
|
|
87
|
+
allowNull: false,
|
|
88
|
+
comment: 'e.g. admin:42',
|
|
89
|
+
},
|
|
90
|
+
createdAt: { type: Sequelize.DATE, allowNull: false },
|
|
91
|
+
updatedAt: { type: Sequelize.DATE, allowNull: false },
|
|
92
|
+
});
|
|
93
|
+
await queryInterface.addIndex('OrderItemChangeLogs', ['orderId'], {
|
|
94
|
+
name: 'order_item_change_logs_order_id_index',
|
|
95
|
+
});
|
|
96
|
+
await queryInterface.addIndex(
|
|
97
|
+
'OrderItemChangeLogs',
|
|
98
|
+
['orderId', 'editNumber'],
|
|
99
|
+
{
|
|
100
|
+
name: 'order_item_change_logs_order_id_edit_number_index',
|
|
101
|
+
},
|
|
102
|
+
);
|
|
103
|
+
|
|
104
|
+
// ── 5. Refunds.editLogId -> OrderItemChangeLogs FK (now that table exists) ──
|
|
105
|
+
await queryInterface.addConstraint('Refunds', {
|
|
106
|
+
fields: ['editLogId'],
|
|
107
|
+
type: 'foreign key',
|
|
108
|
+
name: 'refunds_edit_log_id_foreign',
|
|
109
|
+
references: { table: 'OrderItemChangeLogs', field: 'id' },
|
|
110
|
+
onDelete: 'SET NULL',
|
|
111
|
+
onUpdate: 'CASCADE',
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
// ── 6. OrderEditSessions — in-progress edit lock ─────────────────────────
|
|
115
|
+
await queryInterface.createTable('OrderEditSessions', {
|
|
116
|
+
id: { type: Sequelize.INTEGER, autoIncrement: true, primaryKey: true },
|
|
117
|
+
orderId: {
|
|
118
|
+
type: Sequelize.INTEGER,
|
|
119
|
+
allowNull: false,
|
|
120
|
+
references: { model: 'Orders', key: 'id' },
|
|
121
|
+
onDelete: 'CASCADE',
|
|
122
|
+
onUpdate: 'CASCADE',
|
|
123
|
+
},
|
|
124
|
+
createdBy: {
|
|
125
|
+
type: Sequelize.STRING(64),
|
|
126
|
+
allowNull: false,
|
|
127
|
+
comment: 'e.g. admin:42',
|
|
128
|
+
},
|
|
129
|
+
expiresAt: {
|
|
130
|
+
type: Sequelize.DATE,
|
|
131
|
+
allowNull: false,
|
|
132
|
+
comment:
|
|
133
|
+
'createdAt + 2 hours. Expired sessions are treated as gone and can be silently replaced.',
|
|
134
|
+
},
|
|
135
|
+
createdAt: { type: Sequelize.DATE, allowNull: false },
|
|
136
|
+
updatedAt: { type: Sequelize.DATE, allowNull: false },
|
|
137
|
+
});
|
|
138
|
+
await queryInterface.addIndex('OrderEditSessions', ['orderId'], {
|
|
139
|
+
name: 'order_edit_sessions_order_id_index',
|
|
140
|
+
});
|
|
141
|
+
await queryInterface.addIndex('OrderEditSessions', ['expiresAt'], {
|
|
142
|
+
name: 'order_edit_sessions_expires_at_index',
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
// ── 7. OrderEditItems — working-copy basket for a session ───────────────
|
|
146
|
+
await queryInterface.createTable('OrderEditItems', {
|
|
147
|
+
id: { type: Sequelize.INTEGER, autoIncrement: true, primaryKey: true },
|
|
148
|
+
sessionId: {
|
|
149
|
+
type: Sequelize.INTEGER,
|
|
150
|
+
allowNull: false,
|
|
151
|
+
references: { model: 'OrderEditSessions', key: 'id' },
|
|
152
|
+
onDelete: 'CASCADE',
|
|
153
|
+
onUpdate: 'CASCADE',
|
|
154
|
+
},
|
|
155
|
+
itemType: {
|
|
156
|
+
type: Sequelize.ENUM('product', 'bundle'),
|
|
157
|
+
allowNull: false,
|
|
158
|
+
defaultValue: 'product',
|
|
159
|
+
},
|
|
160
|
+
productId: { type: Sequelize.INTEGER, allowNull: true },
|
|
161
|
+
productVariantId: { type: Sequelize.INTEGER, allowNull: true },
|
|
162
|
+
bundleId: { type: Sequelize.INTEGER, allowNull: true },
|
|
163
|
+
composedSku: { type: Sequelize.STRING(255), allowNull: true },
|
|
164
|
+
quantity: { type: Sequelize.INTEGER, allowNull: false, defaultValue: 1 },
|
|
165
|
+
unitPrice: { type: Sequelize.DECIMAL(10, 3), allowNull: false },
|
|
166
|
+
lineTotal: { type: Sequelize.DECIMAL(10, 3), allowNull: false },
|
|
167
|
+
sourceOrderItemId: {
|
|
168
|
+
type: Sequelize.INTEGER,
|
|
169
|
+
allowNull: true,
|
|
170
|
+
references: { model: 'OrderItems', key: 'id' },
|
|
171
|
+
onDelete: 'SET NULL',
|
|
172
|
+
onUpdate: 'CASCADE',
|
|
173
|
+
comment:
|
|
174
|
+
'NULL if this line was newly added during this edit; set if it corresponds to an existing OrderItem (possibly with a changed quantity).',
|
|
175
|
+
},
|
|
176
|
+
bundleSelectionsJson: {
|
|
177
|
+
type: Sequelize.JSON,
|
|
178
|
+
allowNull: true,
|
|
179
|
+
comment:
|
|
180
|
+
'For itemType=bundle: array of {productId, productVariantId, productNameEn, productNameAr, variantLabelEn, variantLabelAr, sortOrder} — same shape as OrderItemBundleSelections.',
|
|
181
|
+
},
|
|
182
|
+
createdAt: { type: Sequelize.DATE, allowNull: false },
|
|
183
|
+
updatedAt: { type: Sequelize.DATE, allowNull: false },
|
|
184
|
+
});
|
|
185
|
+
await queryInterface.addIndex('OrderEditItems', ['sessionId'], {
|
|
186
|
+
name: 'order_edit_items_session_id_index',
|
|
187
|
+
});
|
|
188
|
+
await queryInterface.addIndex('OrderEditItems', ['sourceOrderItemId'], {
|
|
189
|
+
name: 'order_edit_items_source_order_item_id_index',
|
|
190
|
+
});
|
|
191
|
+
|
|
192
|
+
// ── 8. OrderPayments — additional payments, N per order ──────────────────
|
|
193
|
+
await queryInterface.createTable('OrderPayments', {
|
|
194
|
+
id: { type: Sequelize.INTEGER, autoIncrement: true, primaryKey: true },
|
|
195
|
+
orderId: {
|
|
196
|
+
type: Sequelize.INTEGER,
|
|
197
|
+
allowNull: false,
|
|
198
|
+
references: { model: 'Orders', key: 'id' },
|
|
199
|
+
onDelete: 'CASCADE',
|
|
200
|
+
onUpdate: 'CASCADE',
|
|
201
|
+
},
|
|
202
|
+
editLogId: {
|
|
203
|
+
type: Sequelize.INTEGER,
|
|
204
|
+
allowNull: true,
|
|
205
|
+
references: { model: 'OrderItemChangeLogs', key: 'id' },
|
|
206
|
+
onDelete: 'SET NULL',
|
|
207
|
+
onUpdate: 'CASCADE',
|
|
208
|
+
comment: 'The edit that created this top-up requirement.',
|
|
209
|
+
},
|
|
210
|
+
sequence: {
|
|
211
|
+
type: Sequelize.INTEGER,
|
|
212
|
+
allowNull: false,
|
|
213
|
+
comment:
|
|
214
|
+
'1, 2, 3... per order — the Nth additional payment. Used as the {sequence} path segment in the unified payment-result URL and as the -P{sequence} suffix sent to payment gateways.',
|
|
215
|
+
},
|
|
216
|
+
amount: { type: Sequelize.DECIMAL(10, 3), allowNull: false },
|
|
217
|
+
currency: { type: Sequelize.STRING(8), allowNull: false },
|
|
218
|
+
paymentMethodSlug: { type: Sequelize.STRING(64), allowNull: false },
|
|
219
|
+
type: { type: Sequelize.ENUM('online', 'offline'), allowNull: false },
|
|
220
|
+
status: {
|
|
221
|
+
type: Sequelize.ENUM('pending', 'paid', 'failed', 'cancelled'),
|
|
222
|
+
allowNull: false,
|
|
223
|
+
defaultValue: 'pending',
|
|
224
|
+
},
|
|
225
|
+
paymentReference: {
|
|
226
|
+
type: Sequelize.STRING(255),
|
|
227
|
+
allowNull: true,
|
|
228
|
+
comment:
|
|
229
|
+
'Gateway transaction ID (online) or manual reference (offline).',
|
|
230
|
+
},
|
|
231
|
+
paymentLinkToken: {
|
|
232
|
+
type: Sequelize.STRING(255),
|
|
233
|
+
allowNull: true,
|
|
234
|
+
comment:
|
|
235
|
+
'Token used in the unified payment-result URL for this top-up.',
|
|
236
|
+
},
|
|
237
|
+
paidAt: { type: Sequelize.DATE, allowNull: true },
|
|
238
|
+
createdAt: { type: Sequelize.DATE, allowNull: false },
|
|
239
|
+
updatedAt: { type: Sequelize.DATE, allowNull: false },
|
|
240
|
+
});
|
|
241
|
+
await queryInterface.addConstraint('OrderPayments', {
|
|
242
|
+
fields: ['orderId', 'sequence'],
|
|
243
|
+
type: 'unique',
|
|
244
|
+
name: 'order_payments_order_id_sequence_unique',
|
|
245
|
+
});
|
|
246
|
+
await queryInterface.addIndex('OrderPayments', ['editLogId'], {
|
|
247
|
+
name: 'order_payments_edit_log_id_index',
|
|
248
|
+
});
|
|
249
|
+
await queryInterface.addIndex('OrderPayments', ['status'], {
|
|
250
|
+
name: 'order_payments_status_index',
|
|
251
|
+
});
|
|
252
|
+
|
|
253
|
+
// ── Backfill: Orders.hasEverNeededVendor for existing orders ──────────────
|
|
254
|
+
await queryInterface.sequelize.query(`
|
|
255
|
+
UPDATE \`Orders\` o
|
|
256
|
+
SET o.\`hasEverNeededVendor\` = 1
|
|
257
|
+
WHERE EXISTS (
|
|
258
|
+
SELECT 1 FROM \`OrderStatusHistories\` h
|
|
259
|
+
WHERE h.\`orderId\` = o.\`id\`
|
|
260
|
+
AND h.\`toStatus\` = 'pending_vendor'
|
|
261
|
+
)
|
|
262
|
+
AND o.\`hasEverNeededVendor\` = 0
|
|
263
|
+
`);
|
|
264
|
+
},
|
|
265
|
+
|
|
266
|
+
down: async (queryInterface, Sequelize) => {
|
|
267
|
+
await queryInterface.dropTable('OrderPayments');
|
|
268
|
+
await queryInterface.dropTable('OrderEditItems');
|
|
269
|
+
await queryInterface.dropTable('OrderEditSessions');
|
|
270
|
+
|
|
271
|
+
await queryInterface.removeConstraint(
|
|
272
|
+
'Refunds',
|
|
273
|
+
'refunds_edit_log_id_foreign',
|
|
274
|
+
);
|
|
275
|
+
await queryInterface.removeIndex('Refunds', 'refunds_edit_log_id_index');
|
|
276
|
+
await queryInterface.removeColumn('Refunds', 'editLogId');
|
|
277
|
+
await queryInterface.removeColumn('Refunds', 'source');
|
|
278
|
+
|
|
279
|
+
await queryInterface.dropTable('OrderItemChangeLogs');
|
|
280
|
+
|
|
281
|
+
await queryInterface.removeColumn('Orders', 'hasEverNeededVendor');
|
|
282
|
+
},
|
|
283
|
+
};
|
|
Binary file
|