@leaddreamer/inventorylogic 0.0.3-beta.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.
@@ -0,0 +1,142 @@
1
+ /**
2
+ * Typedefs, constants, support functions and database operations related to Products
3
+ * @module Product
4
+ */
5
+ import { updateRecord} from "@leaddreamer/firebase-wrapper";
6
+ import { Forms, fillForm } from "@leaddreamer/staticforms";
7
+
8
+ import { PRODUCT, MARKUPS, ISBN_LENGTH } from "../boilerplate";
9
+ import { typedRecordFromChild } from "../Utilities"
10
+
11
+ //////////////////////////////////////////////////////////////////////
12
+ // Constants
13
+ /**
14
+ * a record
15
+ * @typedef {object} Product
16
+ * @property {?string} Id Id of document (redundant for convenience)
17
+ * @property {?string} refPath if from database, full path to document
18
+ * @property {string} name name for the Product
19
+ * @property {string} description
20
+ * @property {string} sku
21
+ * @property {module:Markups~Stage} markups
22
+ * @property {boolean} projectQ flag for product/project
23
+ * @property {dateTime} created
24
+ * @property {dateTime} modified
25
+ */
26
+
27
+ /**
28
+ * @readonly
29
+ * @constant
30
+ * @type {Product}
31
+ * @default
32
+ */
33
+ const ProductDefault = {
34
+ Id: null,
35
+ refPath: null,
36
+ name: `A Product`,
37
+ description: null,
38
+ sku: null,
39
+ markups: null,
40
+ projectQ: false,
41
+ created: null,
42
+ modified: null,
43
+ };
44
+
45
+ /**
46
+ * @readonly
47
+ * @constant
48
+ * @type {Product}
49
+ * @default
50
+ */
51
+ const ProductDataForm = {
52
+ Id: `string.uuid`,
53
+ refPath: null,
54
+ name: `commerce.product`,
55
+ description: `commerce.productDescription`,
56
+ sku: `commerce.isbn:${ISBN_LENGTH}`,
57
+ markups: `subrecord:${MARKUPS}`,
58
+ projectQ: `checkbox`
59
+ };
60
+
61
+ /**
62
+ * @readonly
63
+ * @constant
64
+ * @type {Product}
65
+ * @default
66
+ */
67
+ const ProductDefaultForm = {
68
+ Id: `hidden`,
69
+ refPath: `hidden`,
70
+ name: "text",
71
+ description: "textarea",
72
+ sku: "text",
73
+ markups: `subrecord:${MARKUPS}`,
74
+ projectQ: `checkbox`,
75
+ };
76
+
77
+ Forms.Base[PRODUCT] = ProductDefault;
78
+ Forms.BaseForm[PRODUCT] = ProductDefaultForm;
79
+ Forms.CreateForm[PRODUCT] = ProductDefaultForm;
80
+ Forms.Data[PRODUCT] = ProductDataForm;
81
+ Forms.Summarize[PRODUCT] = summarizeProduct;
82
+ Forms.Save[PRODUCT] = updateProduct;
83
+ Forms.SearchBy[PRODUCT] = "name";
84
+
85
+ //////////////////////////////////////////////////////////////////////
86
+ // business operations
87
+
88
+ /**
89
+ * Creates a new Blank Product
90
+ * @param {UserObject} user minimal record of user this commodity belongs to (if any)
91
+ * @returns {Promise<void>}
92
+ */
93
+
94
+ export async function createProduct(){
95
+ //get a blank Product Record
96
+ //write it to database, saving reference
97
+ //copy user stages to product stages
98
+ //We return the product information - but most applications will use database listeners
99
+ };
100
+
101
+
102
+ //////////////////////////////////////////////////////////////////////
103
+ // record operations
104
+
105
+ /**
106
+ * writes record back to database. Assumes only Id is valid
107
+ * @param {LineItem} product a partially-populated product record for updates
108
+ * @param {Product} user minimal record of user this product belongs to
109
+ * @returns {promise.LineItem} a copy of the data written
110
+ */
111
+ async function updateProduct(product, user = null) {
112
+ if ( !(product?.refPath || user?.refPath)) {
113
+ console.log("no product specified to save LineItem");
114
+ //no root products
115
+ return null;
116
+ };
117
+
118
+ return updateRecord(
119
+ fillForm(product, PRODUCT), //saves only APPROPRIATE fields -does NOT populate missing fields
120
+ user,
121
+ PRODUCT
122
+ );
123
+ }
124
+
125
+ //////////////////////////////////////////////////////////////////////
126
+ // utilities
127
+
128
+ /**
129
+ * Returns a standardized formatted name string
130
+ * @param {Unit}
131
+ * @returns {string} formatted name string
132
+ */
133
+ function summarizeProduct(product) {
134
+ return `${product.name} ${product.description}`;
135
+ }
136
+ /**
137
+ * @param {Record} child a record assumed to be a leaf/child
138
+ * @returns {Record}
139
+ */
140
+ export function makeProductFromChild(child) {
141
+ return typedRecordFromChild(child, PRODUCT);
142
+ }
@@ -0,0 +1,372 @@
1
+ /**
2
+ * Typedefs, constants, support functions and database operations related to individual
3
+ * users
4
+ * @module User
5
+ */
6
+ import cloneDeep from "lodash.clonedeep";
7
+ import {
8
+ documentId,
9
+ writeRecord,
10
+ writeBack,
11
+ fetchRecord,
12
+ collectRecords,
13
+ collectRecordsByFilter,
14
+ deleteRecord,
15
+ PaginateFetch,
16
+ typedFetchFromChild,
17
+ } from "@leaddreamer/firebase-wrapper";
18
+ import { Forms } from "@leaddreamer/staticforms";
19
+
20
+ import { USER, USERS } from "../boilerplate";
21
+ import { typedRecordFromChild } from "../Utilities"
22
+
23
+ //////////////////////////////////////////////////////////////////////
24
+ // Constants
25
+
26
+ /**
27
+ * @typedef {object} UserObject
28
+ * @property {?string} Id Id of document from storage
29
+ * @property {?string} refPath if from database, full path to document
30
+ * @property {!string} first_name could be "Anonymous"
31
+ * @property {?string} last_name
32
+ * @property {boolean} isAnonymous flag indicating auto-generated Anonymous user account
33
+ * @property {url} image URL (possibly in app storage) of User image
34
+ * @property {string} region User home region, from fixed list
35
+ * @property {?url} [social_links] array of user social account links
36
+ * @property {?string} bio user-maintained biography
37
+ * @property {?url} website URL to user user website
38
+ * @property {?string} payment_method summary string of user payment
39
+ * method. Not secure, not source-of-truth
40
+ * @property {?PaymentData} payment when "full account"
41
+ */
42
+
43
+ /**
44
+ * @readonly
45
+ * @constant
46
+ * @type {UserObject}
47
+ * @default
48
+ */
49
+ const UserDefault = {
50
+ Id: null,
51
+ refPath: null,
52
+ name: null,
53
+ email: null,
54
+ tel: null,
55
+ isAnonymous: true,
56
+ payment_method: null,
57
+ admin_status: null,
58
+ };
59
+
60
+ /**
61
+ * @readonly
62
+ * @constant
63
+ * @type {UserObject}
64
+ * @default
65
+ */
66
+ const UserData = {
67
+ Id: `string.uuid`,
68
+ refPath: null,
69
+ name: `person.fullName`,
70
+ email: `internet.email`,
71
+ tel: `phone.number`,
72
+ isAnonymous: `datatype.boolean`,
73
+ payment_method: null,
74
+ admin_status: null,
75
+ };
76
+
77
+ /**
78
+ * @readonly
79
+ * @constant
80
+ * @type {UserObject}
81
+ * @default
82
+ */
83
+ const UserDefaultForm = {
84
+ name: "text",
85
+ email: "email",
86
+ tel: "tel",
87
+ };
88
+
89
+ Forms.Base[USER] = UserDefault;
90
+ Forms.BaseForm[USER] = UserDefaultForm;
91
+ Forms.AccountForm[USER] = UserDefaultForm;
92
+ Forms.Data[USER] = UserData;
93
+ Forms.Summarize[USER] = summarizeUser;
94
+ Forms.SearchBy[USER] = "name";
95
+
96
+ //////////////////////////////////////////////////////////////////////
97
+ // record operations
98
+ /**
99
+ * @param {UserObject} user
100
+ * @returns {Promise<void>}
101
+ */
102
+ export async function deleteUser(user) {
103
+ return deleteRecord(user, USER);
104
+ }
105
+
106
+ /**
107
+ * @param {UserObject} user minimal record of user account
108
+ * @param {!string} user.Id record Id of account
109
+ * @returns {Promise<void>}
110
+ */
111
+ export async function deleteUserById(user) {
112
+ if (!user?.Id) return Promise.reject;
113
+
114
+ return deleteRecord(user, USER);
115
+ }
116
+
117
+ /**
118
+ * @param {UserObject} user minimal record of user account
119
+ * @returns {Promise.UserObject}
120
+ */
121
+ export const createUser = (user) => {
122
+ //need a "no duplicate" function - whatever that means in this context
123
+ return writeRecord(USER, user);
124
+ };
125
+
126
+ /**
127
+ * Builds a User record from the authentication "user" data
128
+ * @param {User} user
129
+ * @param {boolean} user.isAnonymous
130
+ * @param {?string} user.displayName
131
+ * @param {?string} user.photoURL
132
+ * @param {?string} user.email
133
+ * @param {?string} user.phoneNumber
134
+ * @returns {Promise<void>}
135
+ */
136
+ export async function buildUserRecords(user) {
137
+ let newUser = cloneDeep(Forms.Base[USERS]);
138
+ if (user.isAnonymous) {
139
+ //signin anonymous worked
140
+ newUser = {
141
+ ...newUser,
142
+ Id: user.uid,
143
+ isAnonymous: true,
144
+ name: "Anonymous",
145
+ };
146
+ } else {
147
+ newUser = {
148
+ ...newUser,
149
+ Id: user.uid,
150
+ isAnonymous: false,
151
+ name: user?.displayName,
152
+ email: user?.email,
153
+ tel: user?.phoneNumber,
154
+ };
155
+ }
156
+ return createUser(newUser);
157
+ }
158
+
159
+ /**
160
+ * writes record back to database. Assumes only Id is valid
161
+ * @param {UserObject} user a partially-populated user record for updates
162
+ * @returns {promise.UserObject} a copy of the data written
163
+ */
164
+ export async function updateUserByID(user) {
165
+ // will update just the fields provided
166
+ return writeBack({
167
+ ...user,
168
+ refPath: `/${USER}/${user.Id}`,
169
+ });
170
+ }
171
+
172
+ /**
173
+ * writes record back to database. Assumes only Id is valid
174
+ * @param {UserObject} user a p[artially-populated user record for updates]
175
+ * @returns {promise.UserObject} a copy of the data written
176
+ */
177
+ export async function updateUser(user) {
178
+ return writeBack({
179
+ ...user,
180
+ refPath: user?.refPath || `/${USER}/${user.Id}`,
181
+ });
182
+ }
183
+
184
+ //////////////////////////////////////////////////////////////////////
185
+ // fetches
186
+
187
+ /**
188
+ * Checks if this is a known UserObject Id
189
+ * @param {!string} Id Id value to check
190
+ * @returns {Promise.boolean} true is this Id is in database
191
+ */
192
+ export async function knownUser(Id) {
193
+ let exists = true;
194
+ try {
195
+ await fetchUser(Id);
196
+ } catch (err) {
197
+ exists = false;
198
+ }
199
+ return exists;
200
+ }
201
+
202
+ //////////////////////////////////////////////////////////////////////
203
+ // fetches
204
+
205
+ /**
206
+ * Fetches ALL user records. Not very useful
207
+ * @returns {Array.UserObject}
208
+ */
209
+ export async function fetchUsers() {
210
+ return collectRecords(USER);
211
+ }
212
+
213
+ /**
214
+ * An object to allow for paginating a table read from Firestore. REQUIRES a sorting choice
215
+ * @typedef {object} PaginatedFetch
216
+ * @property {Query} Query that forms basis for the table read
217
+ * @property {number} limit page size
218
+ * @property {QuerySnapshot} snapshot last successful snapshot/page fetched
219
+ * @property {enum} status status of pagination object
220
+ * @property {method} PageForward pages the fetch forward
221
+ * @property {method} PageBack pages the fetch backward
222
+
223
+ */
224
+
225
+ /**
226
+ * Fetchs an object to fetch pages of user data, ascending by name, "limit" at a time
227
+ * @param {number} limit
228
+ * @returns {PaginatedFetch} object with pagination methods
229
+ */
230
+
231
+ export async function fetchUsersPaginated(pageSize) {
232
+ return new PaginateFetch(
233
+ USER,
234
+ null, //filter
235
+ [{ fieldRef: "first_name", dirStr: "asc" }], //sort
236
+ null, //ref
237
+ pageSize,
238
+ );
239
+ }
240
+
241
+ /**
242
+ * Fetchs an object to fetch pages of anonymous user data, ascending by name, "limit" at a time
243
+ * @param {number} limit
244
+ * @returns {PaginatedFetch} object with pagination methods
245
+ */
246
+ export async function fetchAnonymousPaginated(pageSize) {
247
+ return new PaginateFetch(
248
+ USER,
249
+ [{ fieldRef: "first_name", opStr: "==", value: "Anonymous" }], //filter only anonymous users
250
+ [{ fieldRef: documentId, dirStr: "asc" }], //sort by uid
251
+ null, //ref
252
+ pageSize,
253
+ );
254
+ }
255
+
256
+ /**
257
+ * Fetches a single UserObject record by Id
258
+ * @param {!string} userId
259
+ * @returns {Promise.UserObject}
260
+ */
261
+ export async function fetchUser(userId) {
262
+ return fetchRecord(USER, userId);
263
+ }
264
+
265
+ /**
266
+ * Fetches and returns the summarized name from a UserObject record
267
+ * @param {!string} userId
268
+ * @returns {Promise.string} the summarized name from the record
269
+ */
270
+ export async function fetchUserName(userId) {
271
+ const user = await fetchUser(userId);
272
+ return summarizeUser(user);
273
+ }
274
+
275
+ /**
276
+ * Fetches and returns the email from a UserObject record
277
+ * @param {!string} userId
278
+ * @returns {Promise.string} the email from the record
279
+ */
280
+ export async function fetchUserEmail(userId) {
281
+ const user = await fetchUser(userFromId(userId));
282
+ return user.email;
283
+ }
284
+
285
+ /**
286
+ * Fetches the PaymentRecord for a user using just the ID
287
+ * @param {!string} userId
288
+ * @returns {Promise.PaymentData}
289
+ */
290
+ export async function fetchUserPaymentById(userID) {
291
+ const account = userFromId(userID);
292
+ return fetchPayment(account);
293
+ }
294
+
295
+ /**
296
+ * Fetch a User record by their email
297
+ * @param {!string} email
298
+ * @returns {Promise.UserObject}
299
+ */
300
+ export async function fetchUserByEmail(email) {
301
+ let them = await collectRecordsByFilter(
302
+ USER,
303
+ [{ fieldRef: "email", opStr: "==", value: email }], //filter only anonymous users
304
+ null,
305
+ null,
306
+ );
307
+ //Not at all efficient, but rarely called. If this ends up used for any bulk searches
308
+ //de-normalize the database instead.
309
+ return them;
310
+ }
311
+
312
+ /**
313
+ * fetches a full user object (address, payment, private, etc) given EITHER
314
+ * just the Id, OR a UserObject
315
+ * @param {string|UserObject} userIdentifier
316
+ * @param {?string} userIdentifier.Id
317
+ * @returns {Promise.UserObject}
318
+ */
319
+ export async function fetchFullUser(userIdentifier) {
320
+ let user, payment;
321
+ const userId = userIdentifier.Id ? userIdentifier.Id : userIdentifier;
322
+ user = await fetchUser(userId);
323
+
324
+ try {
325
+ payment = await fetchPayment(user);
326
+ } catch (err) {
327
+ payment = null; //Just a flag - not really a failure as such
328
+ }
329
+
330
+ user.payment = payment;
331
+ return user;
332
+ }
333
+
334
+ //////////////////////////////////////////////////////////////////////
335
+ // utilities
336
+ /**
337
+ * Returns a standardized formatted name string
338
+ * @param {UserObject}
339
+ * @returns {string} formatted name string
340
+ */
341
+ function summarizeUser(user) {
342
+ return `${user?.name}`;
343
+ }
344
+
345
+ /**
346
+ * Fetches a UserObject from any child record in any branch
347
+ * @param {RecordDocument} child
348
+ * @returns {Promise.UserObject}
349
+ */
350
+ export async function fetchUserFromChild(child) {
351
+ return typedFetchFromChild(child, USER);
352
+ }
353
+
354
+ /**
355
+ * builds a minimal UserObject from an Id
356
+ * @param {!string} userId
357
+ * @returns {UserObject}
358
+ */
359
+ export function userFromId(userId) {
360
+ return {
361
+ Id: userId,
362
+ refPath: `/${USER}/${userId}`,
363
+ };
364
+ }
365
+
366
+ /**
367
+ * @param {Record} child a record assumed to be a leaf/child
368
+ * @returns {Record}
369
+ */
370
+ export function makeUserFromChild(child) {
371
+ return typedRecordFromChild(child, USER);
372
+ }