@infuro/cms-core 1.0.33 → 1.0.35
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 +22 -6
- package/dist/OrderTrackPage-W3J6WIG2.js +426 -0
- package/dist/OrderTrackPage-Z6FCJQYY.cjs +428 -0
- package/dist/admin.cjs +34072 -20487
- package/dist/admin.d.cts +191 -9
- package/dist/admin.d.ts +191 -9
- package/dist/admin.js +33913 -20423
- package/dist/api.cjs +123 -16508
- package/dist/api.d.cts +633 -3
- package/dist/api.d.ts +633 -3
- package/dist/api.js +14 -16519
- package/dist/auth.cjs +196 -955
- package/dist/auth.d.cts +201 -42
- package/dist/auth.d.ts +201 -42
- package/dist/auth.js +4 -888
- package/dist/chunk-2XE4X74C.js +29602 -0
- package/dist/chunk-3DZRIZOD.cjs +52 -0
- package/dist/chunk-3KPNTQBV.cjs +620 -0
- package/dist/chunk-46GKADYE.cjs +84 -0
- package/dist/chunk-4TRPKBKO.js +589 -0
- package/dist/chunk-5ELSW2UP.js +150 -0
- package/dist/chunk-6KPQWRFW.js +171 -0
- package/dist/chunk-7NMT3GBR.cjs +29 -0
- package/dist/chunk-7YIBMQDR.cjs +86 -0
- package/dist/chunk-BGJAV67S.cjs +29710 -0
- package/dist/chunk-CNTSLED2.cjs +559 -0
- package/dist/chunk-CRFV5WJK.js +16 -0
- package/dist/chunk-D5FBNKQM.cjs +104 -0
- package/dist/chunk-EF2JBPDM.js +114 -0
- package/dist/chunk-EQPITOP6.js +543 -0
- package/dist/chunk-GO7PPYNU.js +76 -0
- package/dist/chunk-HF3L2GIT.cjs +194 -0
- package/dist/chunk-J7XHLOLO.js +830 -0
- package/dist/chunk-JBRR5UYL.js +87 -0
- package/dist/chunk-JC6DLWTE.js +21 -0
- package/dist/chunk-JHF4YYRC.cjs +188 -0
- package/dist/chunk-JXF23MPG.js +25 -0
- package/dist/chunk-MQBT33IV.js +48 -0
- package/dist/chunk-N44BJZ4C.cjs +123 -0
- package/dist/chunk-OOTLNH63.cjs +18 -0
- package/dist/chunk-PAMVINEX.cjs +577 -0
- package/dist/chunk-PNOKNSG2.cjs +222 -0
- package/dist/chunk-QM733NUK.js +186 -0
- package/dist/chunk-R73BKDRO.cjs +90 -0
- package/dist/chunk-SHUYVCID.js +4 -0
- package/dist/chunk-SYBOCAWB.js +97 -0
- package/dist/chunk-TDV6EJHV.js +192 -0
- package/dist/chunk-TFUOHI37.cjs +839 -0
- package/dist/chunk-TIAEXJLI.cjs +182 -0
- package/dist/chunk-UPLVMVRX.cjs +23 -0
- package/dist/chunk-USNT2KNT.cjs +6 -0
- package/dist/chunk-WEMDMVHQ.js +216 -0
- package/dist/chunk-XDSNZUW7.js +564 -0
- package/dist/chunk-XQ3QUHWR.cjs +83 -0
- package/dist/chunk-XW5ATPRW.cjs +155 -0
- package/dist/chunk-ZQ4X6OAU.js +82 -0
- package/dist/chunk-ZSVCP6N3.js +83 -0
- package/dist/cli.cjs +142 -84
- package/dist/cli.js +101 -27
- package/dist/email-FKR4FXFI.js +5 -0
- package/dist/email-KVKVPNCL.cjs +58 -0
- package/dist/email-queue-CGZBVVAS.js +2 -0
- package/dist/email-queue-TPZWLTIV.cjs +23 -0
- package/dist/erp-config-enabled-CUMQ6N2V.cjs +11 -0
- package/dist/erp-config-enabled-T37X7T34.js +2 -0
- package/dist/erp-order-invoice-3YISSOWW.js +3 -0
- package/dist/erp-order-invoice-ZR5F2KTA.cjs +12 -0
- package/dist/event-order-defaults-6E543227.js +2 -0
- package/dist/event-order-defaults-DUGDVGJI.cjs +39 -0
- package/dist/event-order-notifications-5GSAOYVS.cjs +27 -0
- package/dist/event-order-notifications-IIIF23D5.js +10 -0
- package/dist/hooks.cjs +259 -132
- package/dist/hooks.d.cts +42 -1
- package/dist/hooks.d.ts +42 -1
- package/dist/hooks.js +234 -91
- package/dist/index.cjs +3884 -21070
- package/dist/index.d.cts +2255 -749
- package/dist/index.d.ts +2255 -749
- package/dist/index.js +1925 -19891
- package/dist/interpolate-WAKPN56R.cjs +11 -0
- package/dist/interpolate-WDUWDF3I.js +2 -0
- package/dist/layout-36UFTHC5.cjs +11 -0
- package/dist/layout-AOBN26XG.js +2 -0
- package/dist/migrations/1778660000004-CreateCustomerAndCustomerContacts.ts +99 -0
- package/dist/migrations/1778660000005-AddCustomerIdToVendorCustomers.ts +39 -0
- package/dist/migrations/1778660000006-UpdateVendorCustomersContactNullable.ts +36 -0
- package/dist/migrations/1778660000007-VendorOwnerCustomerContactsPermission.ts +39 -0
- package/dist/migrations/1779300000000-VendorOwnerContactsPermission.ts +39 -0
- package/dist/migrations/1779400000000-AddCustomerContactIdToOrders.ts +33 -0
- package/dist/migrations/1779400000000-ConvertStatusToEnum.ts +142 -0
- package/dist/migrations/1779400000000-SeedDefaultCurrencies.ts +33 -0
- package/dist/migrations/1779500000000-VendorRolesAndPermissions.ts +102 -0
- package/dist/migrations/1779600000000-VendorRegistrationProfile.ts +52 -0
- package/dist/migrations/1779700000000-CollectionIsCatalog.ts +22 -0
- package/dist/migrations/1779710000000-CollectionPlatformCatalog.ts +60 -0
- package/dist/migrations/1779720000000-ProductCategoryPlatformCatalog.ts +157 -0
- package/dist/migrations/1779800000000-EventsAndEventProducts.ts +173 -0
- package/dist/migrations/1779810000000-BackfillVendorRoleEventsPermissions.ts +38 -0
- package/dist/migrations/1779820000000-FixProductsCategoryForeignKey.ts +70 -0
- package/dist/migrations/1779830000000-ProductTitleColumn.ts +32 -0
- package/dist/migrations/1779840000000-EventAboutAndVisibilityFields.ts +34 -0
- package/dist/migrations/1779850000000-EventCityCountrySplit.ts +43 -0
- package/dist/migrations/1779860000000-CreateCombos.ts +140 -0
- package/dist/migrations/1780100000000-OrderAttendeesEventId.ts +55 -0
- package/dist/migrations/1780110000000-EventContactFormId.ts +49 -0
- package/dist/migrations/1780130000000-ProductVariants.ts +54 -0
- package/dist/migrations/1780140000000-EventManagementEnhancements.ts +44 -0
- package/dist/migrations/1780160000000-EventRegistrationGstProductContactForm.ts +79 -0
- package/dist/migrations/1780170000000-ComboCurrencyPrices.ts +20 -0
- package/dist/migrations/1781110000000-VendorOwnerCombosPermission.ts +38 -0
- package/dist/migrations/1781120000000-RefundPoliciesAndRequests.ts +225 -0
- package/dist/migrations/1781130000000-CreateAttendees.ts +44 -0
- package/dist/migrations/1781140000000-EventOrderNotifications.ts +29 -0
- package/dist/migrations/1781150000000-EventNamedLists.ts +28 -0
- package/dist/migrations/1781160000000-RefundPolicyEventTiers.ts +27 -0
- package/dist/migrations/1781170000000-EventRefundPolicy.ts +42 -0
- package/dist/migrations/1781180000000-RemoveCartItemsUniqueConstraint.ts +15 -0
- package/dist/migrations/1781190000000-RenameProductTaxesToProductConfig.ts +84 -0
- package/dist/migrations/1781200000000-VendorOwnerProductConfigPermissions.ts +51 -0
- package/dist/migrations/1781210000000-VendorRoleProductConfigPermissions.ts +46 -0
- package/dist/migrations/helpers/vendor-role-seed.ts +243 -0
- package/dist/paid-order-erp-3USEG6WL.js +3 -0
- package/dist/paid-order-erp-FSQ3GSXE.cjs +12 -0
- package/dist/pg-boss-service-4XF2TQ2E.js +2 -0
- package/dist/pg-boss-service-L6MTF4EQ.cjs +15 -0
- package/dist/razorpay-KLO2KNCB.js +49 -0
- package/dist/razorpay-U23LGY72.cjs +56 -0
- package/dist/rbac-debug-db-D7ME7QUY.cjs +35 -0
- package/dist/rbac-debug-db-MTCTVNNM.js +33 -0
- package/dist/sms-defaults-2NL24H57.js +2 -0
- package/dist/sms-defaults-KCOPKQBA.cjs +19 -0
- package/dist/stripe-4FD27QVJ.js +51 -0
- package/dist/stripe-VU6OEG2D.cjs +57 -0
- package/dist/theme.cjs +31 -59
- package/dist/theme.d.cts +78 -3
- package/dist/theme.d.ts +78 -3
- package/dist/theme.js +23 -20
- package/dist/vendor-invite-status-77O7ZQ3U.cjs +19 -0
- package/dist/vendor-invite-status-PWJ3DE76.js +2 -0
- package/dist/whatsapp-queue-3DXA4MXU.js +2 -0
- package/dist/whatsapp-queue-KG65MXD5.cjs +15 -0
- package/package.json +147 -141
- package/dist/admin.cjs.map +0 -1
- package/dist/admin.js.map +0 -1
- package/dist/api.cjs.map +0 -1
- package/dist/api.js.map +0 -1
- package/dist/auth.cjs.map +0 -1
- package/dist/auth.js.map +0 -1
- package/dist/cli.cjs.map +0 -1
- package/dist/cli.js.map +0 -1
- package/dist/config-DJ5CmQvS.d.cts +0 -8
- package/dist/config-DJ5CmQvS.d.ts +0 -8
- package/dist/hooks.cjs.map +0 -1
- package/dist/hooks.js.map +0 -1
- package/dist/index-DWd5Gjc4.d.ts +0 -582
- package/dist/index-rZTnpK7y.d.cts +0 -582
- package/dist/index.cjs.map +0 -1
- package/dist/index.js.map +0 -1
- package/dist/theme.cjs.map +0 -1
- package/dist/theme.js.map +0 -1
- package/dist/types-DCVaXlPV.d.cts +0 -78
- package/dist/types-DCVaXlPV.d.ts +0 -78
- package/dist/vendor-scope-DTYhJk_I.d.cts +0 -129
- package/dist/vendor-scope-DTYhJk_I.d.ts +0 -129
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var chunkOOTLNH63_cjs = require('./chunk-OOTLNH63.cjs');
|
|
4
|
+
require('./chunk-USNT2KNT.cjs');
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
Object.defineProperty(exports, "applyTemplateVars", {
|
|
9
|
+
enumerable: true,
|
|
10
|
+
get: function () { return chunkOOTLNH63_cjs.applyTemplateVars; }
|
|
11
|
+
});
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import { MigrationInterface, QueryRunner } from 'typeorm';
|
|
2
|
+
|
|
3
|
+
export class CreateCustomerAndCustomerContacts1778660000004
|
|
4
|
+
implements MigrationInterface
|
|
5
|
+
{
|
|
6
|
+
name = 'CreateCustomerAndCustomerContacts1778660000004';
|
|
7
|
+
|
|
8
|
+
public async up(queryRunner: QueryRunner): Promise<void> {
|
|
9
|
+
// Create customer table
|
|
10
|
+
await queryRunner.query(`
|
|
11
|
+
CREATE TABLE "customer" (
|
|
12
|
+
"id" SERIAL NOT NULL,
|
|
13
|
+
"userId" integer,
|
|
14
|
+
"name" character varying NOT NULL,
|
|
15
|
+
"email" character varying NOT NULL,
|
|
16
|
+
"phone" character varying NOT NULL,
|
|
17
|
+
"createdAt" TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
18
|
+
"updatedAt" TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
19
|
+
"deletedAt" TIMESTAMP,
|
|
20
|
+
"deleted" boolean NOT NULL DEFAULT false,
|
|
21
|
+
"createdBy" integer,
|
|
22
|
+
"updatedBy" integer,
|
|
23
|
+
"deletedBy" integer,
|
|
24
|
+
CONSTRAINT "PK_customer_id" PRIMARY KEY ("id"),
|
|
25
|
+
CONSTRAINT "UQ_customer_email" UNIQUE ("email"),
|
|
26
|
+
CONSTRAINT "UQ_customer_phone" UNIQUE ("phone")
|
|
27
|
+
)
|
|
28
|
+
`);
|
|
29
|
+
|
|
30
|
+
// Add foreign key for userId
|
|
31
|
+
await queryRunner.query(`
|
|
32
|
+
ALTER TABLE "customer"
|
|
33
|
+
ADD CONSTRAINT "FK_customer_user"
|
|
34
|
+
FOREIGN KEY ("userId")
|
|
35
|
+
REFERENCES "users"("id")
|
|
36
|
+
ON DELETE SET NULL
|
|
37
|
+
ON UPDATE NO ACTION
|
|
38
|
+
`);
|
|
39
|
+
|
|
40
|
+
// Create customer_contacts table
|
|
41
|
+
await queryRunner.query(`
|
|
42
|
+
CREATE TABLE "customer_contacts" (
|
|
43
|
+
"id" SERIAL NOT NULL,
|
|
44
|
+
"customerId" integer NOT NULL,
|
|
45
|
+
"contactId" integer NOT NULL,
|
|
46
|
+
CONSTRAINT "PK_customer_contacts_id" PRIMARY KEY ("id")
|
|
47
|
+
)
|
|
48
|
+
`);
|
|
49
|
+
|
|
50
|
+
// Add foreign key for customerId
|
|
51
|
+
await queryRunner.query(`
|
|
52
|
+
ALTER TABLE "customer_contacts"
|
|
53
|
+
ADD CONSTRAINT "FK_customer_contacts_customer"
|
|
54
|
+
FOREIGN KEY ("customerId")
|
|
55
|
+
REFERENCES "customer"("id")
|
|
56
|
+
ON DELETE CASCADE
|
|
57
|
+
ON UPDATE NO ACTION
|
|
58
|
+
`);
|
|
59
|
+
|
|
60
|
+
// Add foreign key for contactId
|
|
61
|
+
await queryRunner.query(`
|
|
62
|
+
ALTER TABLE "customer_contacts"
|
|
63
|
+
ADD CONSTRAINT "FK_customer_contacts_contact"
|
|
64
|
+
FOREIGN KEY ("contactId")
|
|
65
|
+
REFERENCES "contacts"("id")
|
|
66
|
+
ON DELETE CASCADE
|
|
67
|
+
ON UPDATE NO ACTION
|
|
68
|
+
`);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
public async down(queryRunner: QueryRunner): Promise<void> {
|
|
72
|
+
// Drop foreign keys from customer_contacts
|
|
73
|
+
await queryRunner.query(`
|
|
74
|
+
ALTER TABLE "customer_contacts"
|
|
75
|
+
DROP CONSTRAINT "FK_customer_contacts_contact"
|
|
76
|
+
`);
|
|
77
|
+
|
|
78
|
+
await queryRunner.query(`
|
|
79
|
+
ALTER TABLE "customer_contacts"
|
|
80
|
+
DROP CONSTRAINT "FK_customer_contacts_customer"
|
|
81
|
+
`);
|
|
82
|
+
|
|
83
|
+
// Drop customer_contacts table
|
|
84
|
+
await queryRunner.query(`
|
|
85
|
+
DROP TABLE "customer_contacts"
|
|
86
|
+
`);
|
|
87
|
+
|
|
88
|
+
// Drop foreign key from customer
|
|
89
|
+
await queryRunner.query(`
|
|
90
|
+
ALTER TABLE "customer"
|
|
91
|
+
DROP CONSTRAINT "FK_customer_user"
|
|
92
|
+
`);
|
|
93
|
+
|
|
94
|
+
// Drop customer table
|
|
95
|
+
await queryRunner.query(`
|
|
96
|
+
DROP TABLE "customer"
|
|
97
|
+
`);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { MigrationInterface, QueryRunner } from 'typeorm';
|
|
2
|
+
|
|
3
|
+
export class AddCustomerIdToVendorCustomers1778660000005
|
|
4
|
+
implements MigrationInterface
|
|
5
|
+
{
|
|
6
|
+
name = 'AddCustomerIdToVendorCustomers1778660000005';
|
|
7
|
+
|
|
8
|
+
public async up(queryRunner: QueryRunner): Promise<void> {
|
|
9
|
+
// Add customerId column
|
|
10
|
+
await queryRunner.query(`
|
|
11
|
+
ALTER TABLE "vendor_customers"
|
|
12
|
+
ADD COLUMN "customerId" integer
|
|
13
|
+
`);
|
|
14
|
+
|
|
15
|
+
// Add foreign key constraint
|
|
16
|
+
await queryRunner.query(`
|
|
17
|
+
ALTER TABLE "vendor_customers"
|
|
18
|
+
ADD CONSTRAINT "FK_vendor_customers_customer"
|
|
19
|
+
FOREIGN KEY ("customerId")
|
|
20
|
+
REFERENCES "customer"("id")
|
|
21
|
+
ON DELETE CASCADE
|
|
22
|
+
ON UPDATE NO ACTION
|
|
23
|
+
`);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
public async down(queryRunner: QueryRunner): Promise<void> {
|
|
27
|
+
// Drop foreign key constraint
|
|
28
|
+
await queryRunner.query(`
|
|
29
|
+
ALTER TABLE "vendor_customers"
|
|
30
|
+
DROP CONSTRAINT "FK_vendor_customers_customer"
|
|
31
|
+
`);
|
|
32
|
+
|
|
33
|
+
// Drop customerId column
|
|
34
|
+
await queryRunner.query(`
|
|
35
|
+
ALTER TABLE "vendor_customers"
|
|
36
|
+
DROP COLUMN "customerId"
|
|
37
|
+
`);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { MigrationInterface, QueryRunner } from 'typeorm';
|
|
2
|
+
|
|
3
|
+
export class UpdateVendorCustomersContactNullable1778660000006
|
|
4
|
+
implements MigrationInterface
|
|
5
|
+
{
|
|
6
|
+
name = 'UpdateVendorCustomersContactNullable1778660000006';
|
|
7
|
+
|
|
8
|
+
public async up(queryRunner: QueryRunner): Promise<void> {
|
|
9
|
+
// Drop unique constraint
|
|
10
|
+
await queryRunner.query(`
|
|
11
|
+
ALTER TABLE "vendor_customers"
|
|
12
|
+
DROP CONSTRAINT "UQ_vendor_customers_vendor_contact"
|
|
13
|
+
`);
|
|
14
|
+
|
|
15
|
+
// Make contactId nullable
|
|
16
|
+
await queryRunner.query(`
|
|
17
|
+
ALTER TABLE "vendor_customers"
|
|
18
|
+
ALTER COLUMN "contactId" DROP NOT NULL
|
|
19
|
+
`);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
public async down(queryRunner: QueryRunner): Promise<void> {
|
|
23
|
+
// Make contactId NOT NULL again
|
|
24
|
+
await queryRunner.query(`
|
|
25
|
+
ALTER TABLE "vendor_customers"
|
|
26
|
+
ALTER COLUMN "contactId" SET NOT NULL
|
|
27
|
+
`);
|
|
28
|
+
|
|
29
|
+
// Recreate unique constraint
|
|
30
|
+
await queryRunner.query(`
|
|
31
|
+
ALTER TABLE "vendor_customers"
|
|
32
|
+
ADD CONSTRAINT "UQ_vendor_customers_vendor_contact"
|
|
33
|
+
UNIQUE ("vendorId", "contactId")
|
|
34
|
+
`);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import type { MigrationInterface, QueryRunner } from 'typeorm';
|
|
2
|
+
|
|
3
|
+
/** Vendor Owner group: access customer contacts in admin. */
|
|
4
|
+
export class VendorOwnerCustomerContactsPermission1778660000007
|
|
5
|
+
implements MigrationInterface
|
|
6
|
+
{
|
|
7
|
+
name = 'VendorOwnerCustomerContactsPermission1778660000007';
|
|
8
|
+
|
|
9
|
+
public async up(queryRunner: QueryRunner): Promise<void> {
|
|
10
|
+
await queryRunner.query(`
|
|
11
|
+
INSERT INTO "permissions"
|
|
12
|
+
("groupId", "entity", "canCreate", "canRead", "canUpdate", "canDelete")
|
|
13
|
+
SELECT
|
|
14
|
+
g.id,
|
|
15
|
+
'customer_contacts',
|
|
16
|
+
true,
|
|
17
|
+
true,
|
|
18
|
+
true,
|
|
19
|
+
false
|
|
20
|
+
FROM "user_groups" g
|
|
21
|
+
WHERE g.name = 'Vendor Owner'
|
|
22
|
+
AND g.deleted = false
|
|
23
|
+
ON CONFLICT ("groupId", "entity") DO UPDATE SET
|
|
24
|
+
"canCreate" = true,
|
|
25
|
+
"canRead" = true,
|
|
26
|
+
"canUpdate" = true
|
|
27
|
+
`);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
public async down(queryRunner: QueryRunner): Promise<void> {
|
|
31
|
+
await queryRunner.query(`
|
|
32
|
+
DELETE FROM "permissions" p
|
|
33
|
+
USING "user_groups" g
|
|
34
|
+
WHERE p."groupId" = g.id
|
|
35
|
+
AND g.name = 'Vendor Owner'
|
|
36
|
+
AND p.entity = 'customer_contacts'
|
|
37
|
+
`);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import type { MigrationInterface, QueryRunner } from 'typeorm';
|
|
2
|
+
|
|
3
|
+
/** Vendor Owner group: access contacts in admin. */
|
|
4
|
+
export class VendorOwnerContactsPermission1779300000000
|
|
5
|
+
implements MigrationInterface
|
|
6
|
+
{
|
|
7
|
+
name = 'VendorOwnerContactsPermission1779300000000';
|
|
8
|
+
|
|
9
|
+
public async up(queryRunner: QueryRunner): Promise<void> {
|
|
10
|
+
await queryRunner.query(`
|
|
11
|
+
INSERT INTO "permissions"
|
|
12
|
+
("groupId", "entity", "canCreate", "canRead", "canUpdate", "canDelete")
|
|
13
|
+
SELECT
|
|
14
|
+
g.id,
|
|
15
|
+
'contacts',
|
|
16
|
+
true,
|
|
17
|
+
true,
|
|
18
|
+
true,
|
|
19
|
+
false
|
|
20
|
+
FROM "user_groups" g
|
|
21
|
+
WHERE g.name = 'Vendor Owner'
|
|
22
|
+
AND g.deleted = false
|
|
23
|
+
ON CONFLICT ("groupId", "entity") DO UPDATE SET
|
|
24
|
+
"canCreate" = true,
|
|
25
|
+
"canRead" = true,
|
|
26
|
+
"canUpdate" = true
|
|
27
|
+
`);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
public async down(queryRunner: QueryRunner): Promise<void> {
|
|
31
|
+
await queryRunner.query(`
|
|
32
|
+
DELETE FROM "permissions" p
|
|
33
|
+
USING "user_groups" g
|
|
34
|
+
WHERE p."groupId" = g.id
|
|
35
|
+
AND g.name = 'Vendor Owner'
|
|
36
|
+
AND p.entity = 'contacts'
|
|
37
|
+
`);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { MigrationInterface, QueryRunner } from 'typeorm';
|
|
2
|
+
|
|
3
|
+
export class AddCustomerContactIdToOrders1779400000000 implements MigrationInterface {
|
|
4
|
+
name = 'AddCustomerContactIdToOrders1779400000000';
|
|
5
|
+
|
|
6
|
+
public async up(queryRunner: QueryRunner): Promise<void> {
|
|
7
|
+
await queryRunner.query(`
|
|
8
|
+
ALTER TABLE "orders"
|
|
9
|
+
ADD COLUMN "customerContactId" integer
|
|
10
|
+
`);
|
|
11
|
+
|
|
12
|
+
await queryRunner.query(`
|
|
13
|
+
ALTER TABLE "orders"
|
|
14
|
+
ADD CONSTRAINT "FK_orders_customer_contact"
|
|
15
|
+
FOREIGN KEY ("customerContactId")
|
|
16
|
+
REFERENCES "customer_contacts"("id")
|
|
17
|
+
ON DELETE SET NULL
|
|
18
|
+
ON UPDATE NO ACTION
|
|
19
|
+
`);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
public async down(queryRunner: QueryRunner): Promise<void> {
|
|
23
|
+
await queryRunner.query(`
|
|
24
|
+
ALTER TABLE "orders"
|
|
25
|
+
DROP CONSTRAINT "FK_orders_customer_contact"
|
|
26
|
+
`);
|
|
27
|
+
|
|
28
|
+
await queryRunner.query(`
|
|
29
|
+
ALTER TABLE "orders"
|
|
30
|
+
DROP COLUMN "customerContactId"
|
|
31
|
+
`);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
import { MigrationInterface, QueryRunner } from 'typeorm';
|
|
2
|
+
|
|
3
|
+
export class ConvertStatusToEnum1779400000000
|
|
4
|
+
implements MigrationInterface
|
|
5
|
+
{
|
|
6
|
+
name = 'ConvertStatusToEnum1779400000000';
|
|
7
|
+
|
|
8
|
+
public async up(queryRunner: QueryRunner): Promise<void> {
|
|
9
|
+
// Create order status enum
|
|
10
|
+
await queryRunner.query(`
|
|
11
|
+
CREATE TYPE "order_status_enum" AS ENUM (
|
|
12
|
+
'pending',
|
|
13
|
+
'confirmed',
|
|
14
|
+
'processing',
|
|
15
|
+
'completed',
|
|
16
|
+
'cancelled'
|
|
17
|
+
)
|
|
18
|
+
`);
|
|
19
|
+
|
|
20
|
+
// Create payment status enum
|
|
21
|
+
await queryRunner.query(`
|
|
22
|
+
CREATE TYPE "payment_status_enum" AS ENUM (
|
|
23
|
+
'unpaid',
|
|
24
|
+
'paid',
|
|
25
|
+
'pending',
|
|
26
|
+
'failed',
|
|
27
|
+
'refunded'
|
|
28
|
+
)
|
|
29
|
+
`);
|
|
30
|
+
|
|
31
|
+
// Remove old defaults
|
|
32
|
+
await queryRunner.query(`
|
|
33
|
+
ALTER TABLE "orders"
|
|
34
|
+
ALTER COLUMN "status" DROP DEFAULT
|
|
35
|
+
`);
|
|
36
|
+
|
|
37
|
+
await queryRunner.query(`
|
|
38
|
+
ALTER TABLE "payments"
|
|
39
|
+
ALTER COLUMN "status" DROP DEFAULT
|
|
40
|
+
`);
|
|
41
|
+
|
|
42
|
+
// Fix invalid existing order statuses
|
|
43
|
+
await queryRunner.query(`
|
|
44
|
+
UPDATE "orders"
|
|
45
|
+
SET "status" = CASE
|
|
46
|
+
WHEN "status" = 'paid' THEN 'completed'
|
|
47
|
+
WHEN "status" = 'unpaid' THEN 'pending'
|
|
48
|
+
ELSE "status"
|
|
49
|
+
END
|
|
50
|
+
`);
|
|
51
|
+
|
|
52
|
+
// Fix invalid existing payment statuses if needed
|
|
53
|
+
await queryRunner.query(`
|
|
54
|
+
UPDATE "payments"
|
|
55
|
+
SET "status" = CASE
|
|
56
|
+
WHEN "status" = 'processing' THEN 'pending'
|
|
57
|
+
WHEN "status" = 'completed' THEN 'paid'
|
|
58
|
+
WHEN "status" = 'cancelled' THEN 'failed'
|
|
59
|
+
ELSE "status"
|
|
60
|
+
END
|
|
61
|
+
`);
|
|
62
|
+
|
|
63
|
+
// Convert orders.status to enum
|
|
64
|
+
await queryRunner.query(`
|
|
65
|
+
ALTER TABLE "orders"
|
|
66
|
+
ALTER COLUMN "status"
|
|
67
|
+
TYPE "order_status_enum"
|
|
68
|
+
USING "status"::text::"order_status_enum"
|
|
69
|
+
`);
|
|
70
|
+
|
|
71
|
+
// Convert payments.status to enum
|
|
72
|
+
await queryRunner.query(`
|
|
73
|
+
ALTER TABLE "payments"
|
|
74
|
+
ALTER COLUMN "status"
|
|
75
|
+
TYPE "payment_status_enum"
|
|
76
|
+
USING "status"::text::"payment_status_enum"
|
|
77
|
+
`);
|
|
78
|
+
|
|
79
|
+
// Set new defaults
|
|
80
|
+
await queryRunner.query(`
|
|
81
|
+
ALTER TABLE "orders"
|
|
82
|
+
ALTER COLUMN "status"
|
|
83
|
+
SET DEFAULT 'pending'
|
|
84
|
+
`);
|
|
85
|
+
|
|
86
|
+
await queryRunner.query(`
|
|
87
|
+
ALTER TABLE "payments"
|
|
88
|
+
ALTER COLUMN "status"
|
|
89
|
+
SET DEFAULT 'pending'
|
|
90
|
+
`);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
public async down(queryRunner: QueryRunner): Promise<void> {
|
|
94
|
+
// Remove enum defaults
|
|
95
|
+
await queryRunner.query(`
|
|
96
|
+
ALTER TABLE "orders"
|
|
97
|
+
ALTER COLUMN "status" DROP DEFAULT
|
|
98
|
+
`);
|
|
99
|
+
|
|
100
|
+
await queryRunner.query(`
|
|
101
|
+
ALTER TABLE "payments"
|
|
102
|
+
ALTER COLUMN "status" DROP DEFAULT
|
|
103
|
+
`);
|
|
104
|
+
|
|
105
|
+
// Convert enums back to varchar
|
|
106
|
+
await queryRunner.query(`
|
|
107
|
+
ALTER TABLE "orders"
|
|
108
|
+
ALTER COLUMN "status"
|
|
109
|
+
TYPE varchar
|
|
110
|
+
USING "status"::text
|
|
111
|
+
`);
|
|
112
|
+
|
|
113
|
+
await queryRunner.query(`
|
|
114
|
+
ALTER TABLE "payments"
|
|
115
|
+
ALTER COLUMN "status"
|
|
116
|
+
TYPE varchar
|
|
117
|
+
USING "status"::text
|
|
118
|
+
`);
|
|
119
|
+
|
|
120
|
+
// Restore varchar defaults
|
|
121
|
+
await queryRunner.query(`
|
|
122
|
+
ALTER TABLE "orders"
|
|
123
|
+
ALTER COLUMN "status"
|
|
124
|
+
SET DEFAULT 'pending'
|
|
125
|
+
`);
|
|
126
|
+
|
|
127
|
+
await queryRunner.query(`
|
|
128
|
+
ALTER TABLE "payments"
|
|
129
|
+
ALTER COLUMN "status"
|
|
130
|
+
SET DEFAULT 'pending'
|
|
131
|
+
`);
|
|
132
|
+
|
|
133
|
+
// Drop enum types
|
|
134
|
+
await queryRunner.query(`
|
|
135
|
+
DROP TYPE "order_status_enum"
|
|
136
|
+
`);
|
|
137
|
+
|
|
138
|
+
await queryRunner.query(`
|
|
139
|
+
DROP TYPE "payment_status_enum"
|
|
140
|
+
`);
|
|
141
|
+
}
|
|
142
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import type { MigrationInterface, QueryRunner } from 'typeorm';
|
|
2
|
+
|
|
3
|
+
/** Seed INR (base) and USD with symbols for fresh installs and existing DBs. */
|
|
4
|
+
export class SeedDefaultCurrencies1779400000000 implements MigrationInterface {
|
|
5
|
+
name = 'SeedDefaultCurrencies1779400000000';
|
|
6
|
+
|
|
7
|
+
public async up(queryRunner: QueryRunner): Promise<void> {
|
|
8
|
+
await queryRunner.query(`
|
|
9
|
+
UPDATE "currency"
|
|
10
|
+
SET "isBaseCurrency" = false
|
|
11
|
+
WHERE "code" <> 'INR'
|
|
12
|
+
`);
|
|
13
|
+
|
|
14
|
+
await queryRunner.query(`
|
|
15
|
+
INSERT INTO "currency" ("code", "name", "symbol", "isActive", "isBaseCurrency")
|
|
16
|
+
VALUES
|
|
17
|
+
('INR', 'Indian Rupee', '₹', true, true),
|
|
18
|
+
('USD', 'US Dollar', '$', true, false)
|
|
19
|
+
ON CONFLICT ("code") DO UPDATE SET
|
|
20
|
+
"name" = EXCLUDED."name",
|
|
21
|
+
"symbol" = EXCLUDED."symbol",
|
|
22
|
+
"isActive" = EXCLUDED."isActive",
|
|
23
|
+
"isBaseCurrency" = EXCLUDED."isBaseCurrency"
|
|
24
|
+
`);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
public async down(queryRunner: QueryRunner): Promise<void> {
|
|
28
|
+
await queryRunner.query(`
|
|
29
|
+
DELETE FROM "currency"
|
|
30
|
+
WHERE "code" IN ('INR', 'USD')
|
|
31
|
+
`);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import type { MigrationInterface, QueryRunner } from 'typeorm';
|
|
2
|
+
import { seedVendorRolesForMigration } from './helpers/vendor-role-seed';
|
|
3
|
+
|
|
4
|
+
export class VendorRolesAndPermissions1779500000000 implements MigrationInterface {
|
|
5
|
+
name = 'VendorRolesAndPermissions1779500000000';
|
|
6
|
+
|
|
7
|
+
public async up(queryRunner: QueryRunner): Promise<void> {
|
|
8
|
+
await queryRunner.query(`
|
|
9
|
+
CREATE TABLE IF NOT EXISTS "vendor_roles" (
|
|
10
|
+
"id" SERIAL NOT NULL,
|
|
11
|
+
"vendorId" integer NOT NULL,
|
|
12
|
+
"name" character varying NOT NULL,
|
|
13
|
+
"description" text,
|
|
14
|
+
"isSystem" boolean NOT NULL DEFAULT false,
|
|
15
|
+
"isOwnerRole" boolean NOT NULL DEFAULT false,
|
|
16
|
+
"createdAt" TIMESTAMP NOT NULL DEFAULT now(),
|
|
17
|
+
"updatedAt" TIMESTAMP NOT NULL DEFAULT now(),
|
|
18
|
+
"deletedAt" TIMESTAMP,
|
|
19
|
+
"deleted" boolean NOT NULL DEFAULT false,
|
|
20
|
+
CONSTRAINT "PK_vendor_roles" PRIMARY KEY ("id")
|
|
21
|
+
)
|
|
22
|
+
`);
|
|
23
|
+
|
|
24
|
+
await queryRunner.query(`
|
|
25
|
+
CREATE INDEX IF NOT EXISTS "IDX_vendor_roles_vendor" ON "vendor_roles" ("vendorId")
|
|
26
|
+
`);
|
|
27
|
+
|
|
28
|
+
await queryRunner.query(`
|
|
29
|
+
CREATE TABLE IF NOT EXISTS "vendor_role_permissions" (
|
|
30
|
+
"id" SERIAL NOT NULL,
|
|
31
|
+
"vendorRoleId" integer NOT NULL,
|
|
32
|
+
"entity" character varying NOT NULL,
|
|
33
|
+
"canCreate" boolean NOT NULL DEFAULT false,
|
|
34
|
+
"canRead" boolean NOT NULL DEFAULT false,
|
|
35
|
+
"canUpdate" boolean NOT NULL DEFAULT false,
|
|
36
|
+
"canDelete" boolean NOT NULL DEFAULT false,
|
|
37
|
+
"createdAt" TIMESTAMP NOT NULL DEFAULT now(),
|
|
38
|
+
"updatedAt" TIMESTAMP NOT NULL DEFAULT now(),
|
|
39
|
+
CONSTRAINT "UQ_vendor_role_permissions_role_entity" UNIQUE ("vendorRoleId", "entity"),
|
|
40
|
+
CONSTRAINT "PK_vendor_role_permissions" PRIMARY KEY ("id")
|
|
41
|
+
)
|
|
42
|
+
`);
|
|
43
|
+
|
|
44
|
+
await queryRunner.query(`
|
|
45
|
+
CREATE INDEX IF NOT EXISTS "IDX_vendor_role_permissions_role"
|
|
46
|
+
ON "vendor_role_permissions" ("vendorRoleId")
|
|
47
|
+
`);
|
|
48
|
+
|
|
49
|
+
await queryRunner.query(`
|
|
50
|
+
DO $$ BEGIN
|
|
51
|
+
ALTER TABLE "vendor_roles" ADD CONSTRAINT "FK_vendor_roles_vendorId"
|
|
52
|
+
FOREIGN KEY ("vendorId") REFERENCES "vendors"("id") ON DELETE CASCADE ON UPDATE NO ACTION;
|
|
53
|
+
EXCEPTION WHEN duplicate_object THEN NULL;
|
|
54
|
+
END $$
|
|
55
|
+
`);
|
|
56
|
+
|
|
57
|
+
await queryRunner.query(`
|
|
58
|
+
DO $$ BEGIN
|
|
59
|
+
ALTER TABLE "vendor_role_permissions" ADD CONSTRAINT "FK_vendor_role_permissions_vendorRoleId"
|
|
60
|
+
FOREIGN KEY ("vendorRoleId") REFERENCES "vendor_roles"("id") ON DELETE CASCADE ON UPDATE NO ACTION;
|
|
61
|
+
EXCEPTION WHEN duplicate_object THEN NULL;
|
|
62
|
+
END $$
|
|
63
|
+
`);
|
|
64
|
+
|
|
65
|
+
await queryRunner.query(`
|
|
66
|
+
ALTER TABLE "vendor_users" ADD COLUMN IF NOT EXISTS "vendorRoleId" integer
|
|
67
|
+
`);
|
|
68
|
+
|
|
69
|
+
await queryRunner.query(`
|
|
70
|
+
CREATE INDEX IF NOT EXISTS "IDX_vendor_users_vendor_role" ON "vendor_users" ("vendorRoleId")
|
|
71
|
+
`);
|
|
72
|
+
|
|
73
|
+
await queryRunner.query(`
|
|
74
|
+
DO $$ BEGIN
|
|
75
|
+
ALTER TABLE "vendor_users" ADD CONSTRAINT "FK_vendor_users_vendorRoleId"
|
|
76
|
+
FOREIGN KEY ("vendorRoleId") REFERENCES "vendor_roles"("id") ON DELETE SET NULL ON UPDATE NO ACTION;
|
|
77
|
+
EXCEPTION WHEN duplicate_object THEN NULL;
|
|
78
|
+
END $$
|
|
79
|
+
`);
|
|
80
|
+
|
|
81
|
+
const vendors = (await queryRunner.query(
|
|
82
|
+
`SELECT id FROM "vendors" WHERE "deleted" = false ORDER BY id ASC`
|
|
83
|
+
)) as Array<{ id: number }>;
|
|
84
|
+
|
|
85
|
+
for (const v of vendors) {
|
|
86
|
+
await seedVendorRolesForMigration(queryRunner, Number(v.id));
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
public async down(queryRunner: QueryRunner): Promise<void> {
|
|
91
|
+
await queryRunner.query(`
|
|
92
|
+
DO $$ BEGIN
|
|
93
|
+
ALTER TABLE "vendor_users" DROP CONSTRAINT "FK_vendor_users_vendorRoleId";
|
|
94
|
+
EXCEPTION WHEN undefined_object THEN NULL;
|
|
95
|
+
END $$
|
|
96
|
+
`);
|
|
97
|
+
await queryRunner.query(`DROP INDEX IF EXISTS "IDX_vendor_users_vendor_role"`);
|
|
98
|
+
await queryRunner.query(`ALTER TABLE "vendor_users" DROP COLUMN IF EXISTS "vendorRoleId"`);
|
|
99
|
+
await queryRunner.query(`DROP TABLE IF EXISTS "vendor_role_permissions"`);
|
|
100
|
+
await queryRunner.query(`DROP TABLE IF EXISTS "vendor_roles"`);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import type { MigrationInterface, QueryRunner } from 'typeorm';
|
|
2
|
+
|
|
3
|
+
export class VendorRegistrationProfile1779600000000 implements MigrationInterface {
|
|
4
|
+
name = 'VendorRegistrationProfile1779600000000';
|
|
5
|
+
|
|
6
|
+
public async up(queryRunner: QueryRunner): Promise<void> {
|
|
7
|
+
await queryRunner.query(`
|
|
8
|
+
DO $$ BEGIN
|
|
9
|
+
CREATE TYPE "vendors_registrationstatus_enum" AS ENUM ('pending', 'approved', 'rejected', 'suspended');
|
|
10
|
+
EXCEPTION WHEN duplicate_object THEN NULL;
|
|
11
|
+
END $$
|
|
12
|
+
`);
|
|
13
|
+
|
|
14
|
+
await queryRunner.query(`ALTER TABLE "vendors" ADD COLUMN IF NOT EXISTS "legalName" character varying`);
|
|
15
|
+
await queryRunner.query(`ALTER TABLE "vendors" ADD COLUMN IF NOT EXISTS "businessType" character varying`);
|
|
16
|
+
await queryRunner.query(`ALTER TABLE "vendors" ADD COLUMN IF NOT EXISTS "website" character varying`);
|
|
17
|
+
await queryRunner.query(`ALTER TABLE "vendors" ADD COLUMN IF NOT EXISTS "addressLine1" character varying`);
|
|
18
|
+
await queryRunner.query(`ALTER TABLE "vendors" ADD COLUMN IF NOT EXISTS "addressLine2" character varying`);
|
|
19
|
+
await queryRunner.query(`ALTER TABLE "vendors" ADD COLUMN IF NOT EXISTS "city" character varying`);
|
|
20
|
+
await queryRunner.query(`ALTER TABLE "vendors" ADD COLUMN IF NOT EXISTS "state" character varying`);
|
|
21
|
+
await queryRunner.query(`ALTER TABLE "vendors" ADD COLUMN IF NOT EXISTS "country" character varying`);
|
|
22
|
+
await queryRunner.query(`ALTER TABLE "vendors" ADD COLUMN IF NOT EXISTS "postalCode" character varying`);
|
|
23
|
+
await queryRunner.query(`ALTER TABLE "vendors" ADD COLUMN IF NOT EXISTS "gstin" character varying`);
|
|
24
|
+
await queryRunner.query(`ALTER TABLE "vendors" ADD COLUMN IF NOT EXISTS "pan" character varying`);
|
|
25
|
+
await queryRunner.query(`
|
|
26
|
+
ALTER TABLE "vendors"
|
|
27
|
+
ADD COLUMN IF NOT EXISTS "registrationStatus" "vendors_registrationstatus_enum" NOT NULL DEFAULT 'approved'
|
|
28
|
+
`);
|
|
29
|
+
|
|
30
|
+
await queryRunner.query(`
|
|
31
|
+
UPDATE "vendors"
|
|
32
|
+
SET "registrationStatus" = 'approved'::"vendors_registrationstatus_enum"
|
|
33
|
+
WHERE "registrationStatus" IS NULL
|
|
34
|
+
`);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
public async down(queryRunner: QueryRunner): Promise<void> {
|
|
38
|
+
await queryRunner.query(`ALTER TABLE "vendors" DROP COLUMN IF EXISTS "registrationStatus"`);
|
|
39
|
+
await queryRunner.query(`ALTER TABLE "vendors" DROP COLUMN IF EXISTS "pan"`);
|
|
40
|
+
await queryRunner.query(`ALTER TABLE "vendors" DROP COLUMN IF EXISTS "gstin"`);
|
|
41
|
+
await queryRunner.query(`ALTER TABLE "vendors" DROP COLUMN IF EXISTS "postalCode"`);
|
|
42
|
+
await queryRunner.query(`ALTER TABLE "vendors" DROP COLUMN IF EXISTS "country"`);
|
|
43
|
+
await queryRunner.query(`ALTER TABLE "vendors" DROP COLUMN IF EXISTS "state"`);
|
|
44
|
+
await queryRunner.query(`ALTER TABLE "vendors" DROP COLUMN IF EXISTS "city"`);
|
|
45
|
+
await queryRunner.query(`ALTER TABLE "vendors" DROP COLUMN IF EXISTS "addressLine2"`);
|
|
46
|
+
await queryRunner.query(`ALTER TABLE "vendors" DROP COLUMN IF EXISTS "addressLine1"`);
|
|
47
|
+
await queryRunner.query(`ALTER TABLE "vendors" DROP COLUMN IF EXISTS "website"`);
|
|
48
|
+
await queryRunner.query(`ALTER TABLE "vendors" DROP COLUMN IF EXISTS "businessType"`);
|
|
49
|
+
await queryRunner.query(`ALTER TABLE "vendors" DROP COLUMN IF EXISTS "legalName"`);
|
|
50
|
+
await queryRunner.query(`DROP TYPE IF EXISTS "vendors_registrationstatus_enum"`);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { MigrationInterface, QueryRunner } from 'typeorm';
|
|
2
|
+
|
|
3
|
+
/** Marks existing collections as platform catalog via metadata (no new column required). */
|
|
4
|
+
export class CollectionIsCatalog1779700000000 implements MigrationInterface {
|
|
5
|
+
name = 'CollectionIsCatalog1779700000000';
|
|
6
|
+
|
|
7
|
+
public async up(queryRunner: QueryRunner): Promise<void> {
|
|
8
|
+
await queryRunner.query(`
|
|
9
|
+
UPDATE "collections"
|
|
10
|
+
SET "metadata" = COALESCE("metadata", '{}'::jsonb) || '{"isCatalog":true}'::jsonb
|
|
11
|
+
WHERE "deleted" = false
|
|
12
|
+
`);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
public async down(queryRunner: QueryRunner): Promise<void> {
|
|
16
|
+
await queryRunner.query(`
|
|
17
|
+
UPDATE "collections"
|
|
18
|
+
SET "metadata" = "metadata" - 'isCatalog'
|
|
19
|
+
WHERE "metadata" ? 'isCatalog'
|
|
20
|
+
`);
|
|
21
|
+
}
|
|
22
|
+
}
|