{ "version": 3, "sources": ["libs/orders/src/lib/shared/constants.ts", "libs/orders/src/lib/components/edit-order/revenue-utils.ts", "libs/orders/src/lib/shared/utils.ts", "libs/orders/src/lib/components/line-items/line-items-adapter.service.ts", "libs/orders/src/lib/app-requirements.ts", "libs/orders/src/lib/components/attachments/attachments-dialog.component.ts", "libs/orders/src/lib/components/attachments/attachments-dialog.component.html", "libs/orders/src/lib/components/attachments/admin-attachments/admin-attachments.component.ts", "libs/orders/src/lib/components/attachments/admin-attachments/admin-attachments.component.html", "libs/orders/src/lib/core/tokens.ts", "libs/orders/src/lib/components/order-details/contract-details/consts.ts", "libs/orders/src/lib/components/line-items/line-items.service.ts", "libs/orders/src/lib/core/orders.service.ts", "libs/orders/src/lib/components/order-details/company-details/company-name.component.ts", "libs/orders/src/lib/components/order-details/company-details/format-address.pipe.ts", "libs/orders/src/lib/components/order-details/company-details/company-address.component.ts", "libs/orders/src/lib/components/order-details/contract-details/contract-start.component.ts", "libs/orders/src/lib/components/order-details/contract-details/contract-duration.component.ts", "libs/orders/src/lib/components/order-details/order-details.component.ts", "libs/orders/src/lib/components/create-order-details/create-order-details.component.ts", "libs/orders/src/lib/components/create-order-details/create-order-details.component.html", "libs/orders/src/lib/components/notes/notes.component.ts", "libs/orders/src/lib/components/notes/notes.component.html", "libs/orders/src/lib/components/tags/tags.component.ts", "libs/orders/src/lib/components/tags/tags.component.html", "libs/orders/src/lib/components/line-items/utils.ts", "libs/orders/src/lib/core/features.ts", "libs/orders/src/lib/components/line-items/line-items-to-inventory-items.pipe.ts", "libs/orders/src/lib/components/line-items/line-items.component.ts", "libs/orders/src/lib/components/line-items/line-items.component.html", "libs/orders/src/lib/components/currency-selector/currency-selector.component.ts", "libs/orders/src/lib/components/currency-selector/currency-selector.component.html", "libs/orders/src/lib/components/variable-prices/variable-prices.component.ts", "libs/orders/src/lib/components/variable-prices/variable-prices.component.html", "libs/orders/src/lib/components/create-order/dialog/format-address.pipe.ts", "libs/orders/src/lib/components/create-order/dialog/create-order-dialog.component.ts", "libs/orders/src/lib/components/create-order/dialog/create-order-dialog.component.html", "libs/orders/src/lib/components/create-order/dialog/create-order-dialog.service.ts", "libs/orders/src/lib/core/permissions/actions.ts", "libs/orders/src/lib/core/permissions/features.ts", "libs/orders/src/lib/core/permissions/permissions.ts", "libs/orders/src/lib/core/permissions/permissions.service.ts", "libs/orders/src/lib/core/permissions/index.ts", "libs/orders/src/lib/components/activation-date-confirmation-dialog/activation-date-confirmation-dialog.component.ts", "libs/orders/src/lib/components/activation-date-confirmation-dialog/activation-date-confirmation-dialog.component.html", "libs/orders/src/lib/components/order-form/order-form.service.ts", "libs/orders/src/lib/components/order-form/order-form.component.ts", "libs/orders/src/lib/components/order-form/order-form.component.html", "libs/orders/src/lib/components/variable-prices/variable-prices.service.ts", "libs/orders/src/lib/components/edit-order/dialogs/cancel-order/cancel-order-dialog.component.ts", "libs/orders/src/lib/components/edit-order/dialogs/cancel-order/cancel-order-dialog.component.html", "libs/orders/src/lib/components/edit-order/dialogs/decline-cancellation/decline-cancellation-dialog.component.ts", "libs/orders/src/lib/components/edit-order/dialogs/decline-cancellation/decline-cancellation-dialog.component.html", "libs/orders/src/lib/components/edit-order/dialogs/order-decline-dialog/order-decline-dialog.component.ts", "libs/orders/src/lib/components/edit-order/dialogs/order-decline-dialog/order-decline-dialog.component.html", "libs/orders/src/lib/components/invalid-billing-terms-dialog/invalid-billing-terms-dialog.component.ts", "libs/orders/src/lib/components/invalid-billing-terms-dialog/invalid-billing-terms-dialog.component.html", "libs/orders/src/lib/components/order-line-item-validation/order-line-item-validation.service.ts", "libs/orders/src/lib/components/edit-order/edit-order-tab.service.ts", "libs/orders/src/lib/components/edit-order/edit-order-form-validation.service.ts", "libs/orders/src/lib/components/fulfillment-status-card/fulfillment-row/format-app-type.pipe.ts", "libs/orders/src/lib/components/fulfillment-status-card/fulfillment-row/get-fulfillment-badge-data.pipe.ts", "libs/orders/src/lib/components/fulfillment-status-card/fulfillment-row/fulfillment-row.component.ts", "libs/orders/src/lib/components/fulfillment-status-card/fulfillment-row/fulfillment-row.component.html", "libs/orders/src/lib/components/fulfillment-status-card/fulfillment-status.service.ts", "libs/orders/src/lib/components/fulfillment-status-card/fulfillment-status-card.component.ts", "libs/orders/src/lib/components/fulfillment-status-card/fulfillment-status-card.component.html", "libs/orders/src/lib/components/associate-user-to-order/associate-user-to-order.service.ts", "libs/orders/src/lib/components/associate-user-to-order/associate-user-to-order.component.ts", "libs/orders/src/lib/components/associate-user-to-order/associate-user-to-order.component.html", "libs/orders/src/lib/components/retail-summary/select-payment-method/orders-select-payment-method-dialog/orders-select-payment-method-dialog.component.ts", "libs/orders/src/lib/components/retail-summary/select-payment-method/orders-select-payment-method-dialog/orders-select-payment-method-dialog.component.html", "libs/orders/src/lib/components/retail-summary/select-payment-method/orders-payment-method.service.ts", "libs/orders/src/lib/components/retail-summary/submit-for-charge-dialog/submit-for-charge-dialog.component.ts", "libs/orders/src/lib/components/retail-summary/submit-for-charge-dialog/submit-for-charge-dialog.component.html", "libs/orders/src/lib/components/retail-summary/select-payment-method/orders-payment-method.component.ts", "libs/orders/src/lib/components/retail-summary/select-payment-method/orders-payment-method.component.html", "libs/orders/src/lib/components/retail-summary/retail-summary.component.ts", "libs/orders/src/lib/components/retail-summary/retail-summary.component.html", "libs/orders/src/lib/components/condensed-order-details/condensed-order-details.service.ts", "libs/orders/src/lib/components/condensed-order-details/format-duration.pipe.ts", "libs/orders/src/lib/components/condensed-order-details/condensed-order-details.component.ts", "libs/orders/src/lib/components/condensed-order-details/condensed-order-details.component.html", "libs/orders/src/lib/components/business-header/business-header.component.ts", "libs/orders/src/lib/components/business-header/business-header.component.html", "libs/orders/src/lib/components/order-line-item-validation/order-line-item-validation-banner.component.ts", "libs/orders/src/lib/components/order-line-item-validation/order-line-item-validation-banner.component.html", "libs/orders/src/lib/components/attachments/customer-attachments/customer-attachments.component.ts", "libs/orders/src/lib/components/attachments/customer-attachments/customer-attachments.component.html", "libs/orders/src/lib/components/order-activity/build-detail-name.pipe.ts", "libs/orders/src/lib/components/order-activity/build-detail-value.pipe.ts", "libs/orders/src/lib/components/order-activity/order-activity.component.ts", "libs/orders/src/lib/components/order-activity/order-activity.component.html", "libs/orders/src/lib/components/edit-order/edit-order.component.ts", "libs/orders/src/lib/components/edit-order/edit-order.component.html", "libs/orders/src/lib/components/line-items/line-items-selector.service.ts", "libs/orders/src/lib/stripe.service.ts", "libs/orders/src/lib/components/payment-element-form/payment-element-form.component.ts", "libs/orders/src/lib/components/payment-element-form/payment-element-form.component.html", "libs/orders/src/lib/components/product-requirements-dialog/product-requirements-dialog.component.ts", "libs/orders/src/lib/components/product-requirements-dialog/product-requirements-dialog.component.html", "libs/orders/src/lib/components/terms/order-terms.component.ts", "libs/orders/src/lib/components/terms/order-terms.component.html", "libs/orders/src/lib/core/status/guard.ts", "libs/orders/src/lib/core/status/util.ts", "libs/orders/src/lib/core/status/badge.ts", "libs/orders/src/lib/core/status/index.ts", "libs/orders/src/lib/utils.ts", "libs/orders/src/lib/components/history/history.service.ts", "libs/orders/src/lib/components/agreements/agreements.component.ts", "libs/orders/src/lib/components/agreements/agreements.component.html", "libs/orders/src/lib/components/history/history.component.ts", "libs/orders/src/lib/components/history/history.component.html", "libs/orders/src/lib/components/view-order/work-order-details-helper.service.ts", "libs/orders/src/lib/components/view-order/view-order.component.ts", "libs/orders/src/lib/components/view-order/view-order.component.html", "libs/orders/src/lib/components/view-or-edit-order/view-or-edit-order.component.ts", "libs/orders/src/lib/components/view-or-edit-order/view-or-edit-order.component.html", "libs/orders/src/lib/components/pricing/pricing.component.ts", "libs/orders/src/lib/components/pricing/pricing.component.html", "libs/orders/src/lib/order.component.ts", "libs/orders/src/lib/order.component.html", "libs/orders/src/lib/assets/en_devel.json", "libs/orders/src/lib/components/create-order/create-order.module.ts", "libs/orders/src/lib/orders.module.ts", "libs/orders/src/lib/index.ts", "libs/orders/src/index.ts"], "sourcesContent": ["// Used in cases where no revenue period is set on the item\n// Examples: Contact Sales and Free items\nimport { RevenueComponent, RevenuePeriod } from '@vendasta/sales-orders';\nimport { UILineItem } from '@vendasta/store';\n\nexport const defaultRevenuePeriodMonthly = RevenuePeriod.MONTHLY;\nexport const defaultCurrencyCodeUSD = 'USD';\n\n// INVALID_ITEM handles scenarios where order items are no longer available.\n// Examples: Has been disabled from the marketplace by the partner or no longer whitelisted to the partner\nexport const INVALID_ITEM: UILineItem = {\n name: 'Invalid Item',\n iconUrl: null,\n lineItem: null,\n};\n\n// defaultRevenue handles scenarios where there's no pricing info on the item (package or product)\n// Examples: Contact Sales and Free items\nexport const defaultRevenue: RevenueComponent = new RevenueComponent({\n value: 0,\n period: defaultRevenuePeriodMonthly,\n isStartingRevenue: false,\n});\n\nexport const PARTNER_ID = 'PARTNER_ID';\nexport const MARKET_ID = 'MARKET_ID';\n\n// Used for providing different styles/templates components for desktop and mobile views\nexport const mobileScreenSizeMax = 600;\n\n// Used for SSC attachments\nexport const DEMO_SSC_UPLOAD_URL = 'https://sales-orders-demo.apigateway.co/file/upload';\nexport const PROD_SSC_UPLOAD_URL = 'https://sales-orders-prod.apigateway.co/file/upload';\n", "import { LineItem, Order, RevenuePeriod } from '@vendasta/sales-orders';\n\nexport function getTaxAmountForRevenuePeriod(salesOrder: Order, revenuePeriod: RevenuePeriod): number {\n return (\n salesOrder.lineItems.reduce((totalTax, lineItem) => {\n const revenue = lineItem.currentRevenue.revenueComponents.find((component) => component.period === revenuePeriod);\n if (!revenue) {\n return totalTax;\n }\n\n let lineItemTax =\n lineItem?.taxRateIds?.reduce((taxTotal, taxRateId) => {\n const taxRate = salesOrder.taxOptions.find((taxOption) => taxOption.taxRateId === taxRateId);\n if (taxRate) {\n return taxTotal + (revenue.value * taxRate.percentageMultiplier) / 100;\n }\n return taxTotal;\n }, 0) || 0;\n if (lineItem.quantity > 0) {\n lineItemTax = lineItemTax * lineItem.quantity;\n }\n return totalTax + lineItemTax;\n }, 0) * 100\n );\n}\n\nexport function getRevenueTotalFromLineItems(lineItems: LineItem[], revenuePeriod?: RevenuePeriod): number {\n if (revenuePeriod) {\n return lineItems\n .map((lineItem) => {\n const total = lineItem.currentRevenue.revenueComponents\n .filter((component) => component.period === revenuePeriod)\n .reduce((total, rev) => total + (rev.value || 0), 0);\n return lineItem.quantity > 0 ? total * lineItem.quantity : total;\n })\n .reduce((total, rev) => total + (rev || 0), 0);\n }\n\n return lineItems\n .map((lineItem) => {\n const total = lineItem.currentRevenue.revenueComponents.reduce((total, rev) => total + (rev.value || 0), 0);\n return lineItem.quantity > 0 ? total * lineItem.quantity : total;\n })\n .reduce((total, rev) => total + (rev || 0), 0);\n}\n", "import { App } from '@galaxy/marketplace-apps';\nimport { Currency as PackageCurrency, Frequency, Package, Pricing } from '@vendasta/marketplace-packages';\nimport {\n BillingPeriod,\n DurationInterface,\n DurationPeriod,\n LineItem,\n Order,\n Revenue,\n RevenueComponent,\n RevenuePeriod,\n} from '@vendasta/sales-orders';\nimport { UILineItem } from '@vendasta/store';\nimport dayjs from 'dayjs';\nimport utc from 'dayjs/plugin/utc';\nimport { AppWithRetailPricing } from '../components/line-items/line-items.interface';\nimport { defaultRevenue, defaultRevenuePeriodMonthly } from './constants';\nimport { UILineItemWithEditionPricing } from './interface';\nimport { RetailSummary } from '../components/retail-summary/retail-summary.component';\nimport { getRevenueTotalFromLineItems, getTaxAmountForRevenuePeriod } from '../components/edit-order/revenue-utils';\n\ndayjs.extend(utc);\n\nexport function packageToUILineItem(pkg: Package, currency: string): UILineItem {\n return {\n lineItem: packageToSalesOrderLineItem(pkg, currency),\n billingId: pkg.packageId,\n name: pkg.name,\n iconUrl: pkg.icon,\n bannerUrl: pkg.headerImageUrl,\n isMultiActivatable: true,\n storeLineItem: packageToSalesOrderLineItem(pkg, currency),\n discountPercentage: 0,\n description: pkg.tagline,\n };\n}\n\nexport function packageToSalesOrderLineItem(pkg: Package, currency: string): LineItem {\n return new LineItem({\n packageId: pkg.packageId,\n currencyCode: !!pkg.pricing && !!pkg.pricing.currency ? PackageCurrency[pkg.pricing.currency] : currency,\n currentRevenue: new Revenue({ revenueComponents: buildPackageRevenueComponentsList(pkg.pricing) }),\n quantity: 1,\n });\n}\n\n// Pricing input is specific to Package type\n// The pricing prices list may contain only a single object with just the startingAt property set to true in the case of contact sales and free packages\nexport function buildPackageRevenueComponentsList(pricing: Pricing): RevenueComponent[] {\n const prices = !!pricing && !!pricing.prices ? pricing.prices : [];\n if (prices.length === 0) {\n return [defaultRevenue];\n }\n return prices.map((p) => {\n return new RevenueComponent({\n value: p.price && p.price > -1 ? p.price : 0,\n period: packageFrequencyToSalesOrderPeriod(p.frequency),\n isStartingRevenue: p.isStartingPrice,\n });\n });\n}\n\nexport function packageFrequencyToSalesOrderPeriod(f: Frequency): RevenuePeriod {\n switch (f) {\n case Frequency.DAILY:\n return RevenuePeriod.DAILY;\n case Frequency.MONTHLY:\n return RevenuePeriod.MONTHLY;\n case Frequency.ONCE:\n return RevenuePeriod.ONETIME;\n case Frequency.WEEKLY:\n return RevenuePeriod.WEEKLY;\n case Frequency.YEARLY:\n return RevenuePeriod.YEARLY;\n default:\n return defaultRevenuePeriodMonthly;\n }\n}\n\nexport function convertSelectedAppToLineItem(\n app: App | AppWithRetailPricing,\n defaultDisplayCurrency: string,\n): UILineItem {\n const uiLineItem: UILineItemWithEditionPricing = {\n lineItem: appToSalesOrderLineItem(app, defaultDisplayCurrency),\n name: app.sharedMarketingInformation.name,\n editionName: app.sharedMarketingInformation.editionName,\n iconUrl: app.sharedMarketingInformation.iconUrl,\n bannerUrl: app.sharedMarketingInformation.bannerImageUrl,\n storeLineItem: appToSalesOrderLineItem(app, defaultDisplayCurrency),\n discountPercentage: 0,\n description: app.sharedMarketingInformation.tagline,\n appType: app.appType,\n billingId: app.externalIdentifiers?.billingId,\n };\n\n if (app.price) {\n uiLineItem.usesCustomPricing = app.price.usesCustomPricing;\n }\n\n if (app.activationInformation) {\n uiLineItem.isMultiActivatable = app.activationInformation.multipleActivationsEnabled;\n }\n\n if ('retailPricing' in app) {\n // override the default revenue component if retail pricing exists\n if (app.retailPricing.revenue.revenueComponents.length > 0) {\n uiLineItem.lineItem.currentRevenue = app.retailPricing.revenue;\n uiLineItem.lineItem.currencyCode = app.retailPricing.currencyCode;\n uiLineItem.storeLineItem.currentRevenue = app.retailPricing.revenue;\n uiLineItem.storeLineItem.currencyCode = app.retailPricing.currencyCode;\n }\n }\n\n if (app.parentRequirements?.enabled) {\n uiLineItem.parentIconUrl = app.parentRequirements.parentDetails.iconUrl;\n uiLineItem.parentName = app.parentRequirements.parentDetails.name;\n uiLineItem.parentAppId = app.parentRequirements.parentDetails.key.appId;\n }\n return uiLineItem;\n}\n\nexport function appToSalesOrderLineItem(app: App, currency: string): LineItem {\n return new LineItem({\n appKey: app.key,\n currencyCode: currency,\n currentRevenue: new Revenue({ revenueComponents: [defaultRevenue] }),\n quantity: 1,\n });\n}\n\nexport function calculateDiscountPercentage(uiLineItem: UILineItem): number {\n const storeRevenueComponents = uiLineItem.storeLineItem?.currentRevenue?.revenueComponents;\n if (storeRevenueComponents === null) {\n return 0;\n }\n\n const storeYearlyTotalEstimate = storeRevenueComponents.reduce((total, storeRevenueComponent) => {\n return total + getYearlyTotal(storeRevenueComponent);\n }, 0);\n if (storeYearlyTotalEstimate <= 0) {\n return 0;\n }\n\n const orderRevenueComponents = uiLineItem.lineItem?.currentRevenue?.revenueComponents;\n if (orderRevenueComponents === null) {\n return 0;\n }\n const orderYearlyTotalEstimate = orderRevenueComponents.reduce((total, orderRevenueComponent) => {\n return total + getYearlyTotal(orderRevenueComponent);\n }, 0);\n if (orderYearlyTotalEstimate <= 0) {\n return 0;\n }\n\n const discountPercentage = ((storeYearlyTotalEstimate - orderYearlyTotalEstimate) * 100) / storeYearlyTotalEstimate;\n if (discountPercentage > 100 || discountPercentage < 0) {\n return 0;\n }\n if (0 < discountPercentage && discountPercentage < 1) {\n return 1;\n }\n return Math.floor(discountPercentage);\n}\n\nfunction getYearlyTotal(revenueComponent: RevenueComponent): number {\n switch (revenueComponent.period) {\n case RevenuePeriod.YEARLY:\n return revenueComponent.value;\n case RevenuePeriod.MONTHLY:\n return revenueComponent.value * 12;\n case RevenuePeriod.BIWEEKLY:\n return revenueComponent.value * 26;\n case RevenuePeriod.WEEKLY:\n return revenueComponent.value * 52;\n case RevenuePeriod.DAILY:\n return revenueComponent.value * 365;\n default:\n return revenueComponent.value;\n }\n}\n\nexport function applyContractTermToLineItems(\n lineItems: LineItem[],\n startDate: Date,\n contractDuration: DurationInterface,\n): LineItem[] {\n startDate = startDate ?? new Date();\n let endDate = undefined;\n if (contractDuration) {\n let duration: 'months' | 'years' = 'months';\n if (contractDuration.duration === DurationPeriod.YEAR) {\n duration = 'years';\n }\n endDate = dayjs(startDate).add(contractDuration.value, duration).toDate();\n }\n return lineItems.map((lineItem) => {\n const billingFrequency = lineItem.currentRevenue?.revenueComponents[0]?.period ?? RevenuePeriod.ONETIME;\n if (billingFrequency === RevenuePeriod.ONETIME) return lineItem;\n\n return new LineItem({\n ...lineItem,\n billingPeriod: new BillingPeriod({\n startDate: lineItem.billingPeriod?.startDate,\n endDate: endDate,\n }),\n });\n });\n}\n\nexport function updateBillingTermEndDateWithNewStartDate(lineItem: LineItem, startDate: Date): LineItem {\n const billingPeriod = lineItem.billingPeriod;\n if (!billingPeriod) return lineItem;\n\n let newEndDate = undefined;\n if (billingPeriod.endDate) {\n const billingPeriodLength = dayjs(billingPeriod.endDate).diff(billingPeriod.startDate, 'months');\n newEndDate = dayjs(startDate).add(billingPeriodLength, 'months').toDate();\n }\n\n return new LineItem({\n ...lineItem,\n billingPeriod: new BillingPeriod({\n startDate: lineItem.billingPeriod?.startDate,\n endDate: newEndDate,\n }),\n });\n}\n\n// getRetailSummaryFromOrder takes a sales order and returns a RetailSummary to pass to the RetailSummaryComponent\nexport function getRetailSummaryFromOrder(salesOrder: Order): RetailSummary {\n if (!salesOrder || !salesOrder.lineItems || salesOrder.lineItems.length === 0) {\n return null;\n }\n\n const currencyCode = salesOrder.lineItems[0]?.currencyCode;\n // Get the subtotal from the revenue of all line items\n const subTotal = getRevenueTotalFromLineItems(salesOrder.lineItems);\n // Get the total tax from the revenue of all line items\n const taxAmount = salesOrder?.taxOptions?.reduce((total, taxOption) => total + taxOption.initialAmount, 0) || 0;\n\n // The actual first payment is the revenue total + any taxes\n const firstPayment = subTotal + taxAmount;\n\n // Figure out the revenue values per frequency\n const monthlySubTotal = getRevenueTotalFromLineItems(salesOrder.lineItems, RevenuePeriod.MONTHLY);\n const yearlySubTotal = getRevenueTotalFromLineItems(salesOrder.lineItems, RevenuePeriod.YEARLY);\n\n // Also get the taxes per frequency to determine recurring charges\n const monthlyTax = getTaxAmountForRevenuePeriod(salesOrder, RevenuePeriod.MONTHLY);\n const yearlyTax = getTaxAmountForRevenuePeriod(salesOrder, RevenuePeriod.YEARLY);\n\n return {\n subtotal: subTotal,\n currencyCode: currencyCode,\n taxAmount: taxAmount,\n firstPayment: firstPayment,\n monthlyTotal: monthlySubTotal + monthlyTax,\n yearlyTotal: yearlySubTotal + yearlyTax,\n } as RetailSummary;\n}\n", "import { LineItem } from '@vendasta/sales-orders';\nimport { UILineItem } from '@vendasta/store';\nimport { combineLatest, Observable, of } from 'rxjs';\nimport { map, publishReplay, refCount, shareReplay, switchMap } from 'rxjs/operators';\nimport { Package, PackageService } from '@vendasta/marketplace-packages';\nimport { App, AppKey, AppPartnerService } from '@galaxy/marketplace-apps';\nimport { INVALID_ITEM } from '../../shared/constants';\nimport { convertSelectedAppToLineItem, packageToUILineItem } from '../../shared/utils';\nimport { Inject, Injectable } from '@angular/core';\nimport { AppSdkService, PackageApiService } from './line-items.interface';\nimport { MARKET_ID_TOKEN, PARTNER_ID_TOKEN } from '@vendasta/sales-ui';\n\n@Injectable()\nexport class LineItemsAdapterService {\n constructor(\n @Inject(PARTNER_ID_TOKEN) private readonly partnerId$: Observable,\n @Inject(MARKET_ID_TOKEN) private readonly marketId$: Observable,\n @Inject(PackageService) private readonly packageSdk: PackageApiService,\n @Inject(AppPartnerService) private readonly appSdk: AppSdkService,\n ) {}\n\n lineItemsToUILineItems(input: {\n lineItems$: Observable;\n partnerId?: string;\n marketId?: string;\n defaultCurrency?: string;\n }): Observable {\n const partnerId$ = input.partnerId ? of(input.partnerId) : this.partnerId$;\n const marketId$ = input.marketId ? of(input.marketId) : this.marketId$;\n\n // call out for package information\n const packages$ = input.lineItems$.pipe(\n switchMap((lineItems) => {\n const packageLineItems = lineItems.filter((li) => !!li.packageId);\n if (packageLineItems.length > 0) {\n return this.packageSdk.getMulti(packageLineItems.map((li) => li.packageId));\n }\n return of([] as Package[]);\n }),\n publishReplay(1),\n refCount(),\n );\n\n const lineItemsWithPartnerIdAndMarketId$ = combineLatest([partnerId$, marketId$, input.lineItems$]).pipe(\n map(([partnerId, marketId, lineItems]) => ({ partnerId, marketId, lineItems })),\n );\n\n // call out for app information\n const appsAndAddons$ = lineItemsWithPartnerIdAndMarketId$.pipe(\n switchMap(({ lineItems, partnerId, marketId }) => {\n const appsAndAddonsLineItems = lineItems.filter((li) => !!li.appKey);\n const appsAndAddonsKeys = appsAndAddonsLineItems.map((app) => new AppKey(app.appKey));\n if (appsAndAddonsKeys.length > 0) {\n return this.appSdk.getMulti(appsAndAddonsKeys, partnerId, marketId, {}, true);\n }\n return of([] as App[]);\n }),\n map((apps) => apps.filter((a) => !!a?.key?.appId)),\n publishReplay(1),\n refCount(),\n );\n\n // resolve the package/app information into the line item\n return combineLatest([input.lineItems$, packages$, appsAndAddons$]).pipe(\n map(([lineItems, packages, appsAndAddons]) => {\n return lineItems.map((li) => {\n let uiLineItem = INVALID_ITEM;\n if (li.packageId) {\n const pack = packages.find((p) => p.packageId === li.packageId);\n if (pack) {\n uiLineItem = packageToUILineItem(pack, input.defaultCurrency);\n }\n } else {\n const app = appsAndAddons.find(\n (a) => a.key.appId === li.appKey.appId && (a.key.editionId || '') === (li.appKey.editionId || ''),\n );\n if (app) {\n uiLineItem = convertSelectedAppToLineItem(app, input.defaultCurrency);\n }\n }\n uiLineItem.lineItem = li;\n return uiLineItem;\n });\n }),\n shareReplay(1),\n );\n }\n}\n", "import { AppKey } from '@galaxy/marketplace-apps';\n\nexport function isRequiredAppSatisfied(requiredAppId: string, availableApps: string[]): boolean {\n return availableApps.includes(requiredAppId);\n}\n\nexport function isRequiredEditionSatisfied(requiredAppKey: AppKey, availableAppKeys: AppKey[]): boolean {\n const requiredAppId = requiredAppKey.appId;\n const requiredEditionId = requiredAppKey.editionId;\n const exactAvailableEdition = availableAppKeys.find((a) => {\n const appIdMatch = a.appId === requiredAppId;\n const editionsAreFalsy = !a.editionId && !requiredEditionId;\n const editionsMatch = a.editionId === requiredEditionId;\n return appIdMatch && (editionsAreFalsy || editionsMatch);\n });\n const hasExactEdition = !!exactAvailableEdition;\n return hasExactEdition;\n}\n", "import { Component, Inject, ViewChild } from '@angular/core';\nimport { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog';\nimport { TranslateService } from '@ngx-translate/core';\nimport { SnackbarService } from '@vendasta/galaxy/snackbar-service';\nimport { FileInfo, FileUploadError, GalaxyUploaderComponent } from '@vendasta/galaxy/uploader';\nimport { BehaviorSubject } from 'rxjs';\n\n@Component({\n selector: 'orders-attachments-dialog',\n templateUrl: 'attachments-dialog.component.html',\n styleUrls: ['./attachments-dialog.component.scss'],\n standalone: false,\n})\nexport class AttachmentsDialogComponent {\n numberFilesUploaded = 0;\n maxNumberFiles = 100;\n files: FileInfo[] = [];\n loading$$ = new BehaviorSubject(false);\n\n @ViewChild('glxyUploader') uploader: GalaxyUploaderComponent;\n\n constructor(\n private translate: TranslateService,\n private snack: SnackbarService,\n public dialogRef: MatDialogRef,\n @Inject(MAT_DIALOG_DATA) public data: { uploadUrl: string },\n ) {}\n\n onError(event: FileUploadError): void {\n this.snack.openErrorSnack(event.error.message);\n }\n\n // eslint-disable-next-line @typescript-eslint/no-unused-vars\n onUpload(event: any): void {\n this.numberFilesUploaded++;\n if (this.files.length === this.numberFilesUploaded) {\n this.loading$$.next(false);\n const successMessage = this.translate.instant('LIB_ORDERS.COMMON.ORDER_DETAILS.SUCCESSFUL_UPLOAD', {\n numberFilesUploaded: this.numberFilesUploaded,\n });\n this.snack.openSuccessSnack(successMessage);\n this.dialogRef.close(this.files);\n }\n }\n\n onFilesChanged(files: FileInfo[]): void {\n this.files = files;\n }\n\n onUploadAndClose(): void {\n this.loading$$.next(true);\n this.uploader.uploadQueuedFiles();\n }\n}\n", "

\n {{ 'LIB_ORDERS.COMMON.ORDER_DETAILS.UPLOAD_ATTACHMENTS' | translate }}\n

\n\n \n \n\n\n \n \n {{ 'LIB_ORDERS.COMMON.ORDERS.UPLOAD' | translate }}\n \n\n", "import { Component, Input } from '@angular/core';\nimport { FileInfo } from '@vendasta/galaxy/uploader';\nimport { MatDialog } from '@angular/material/dialog';\nimport { AttachmentsDialogComponent } from '../attachments-dialog.component';\nimport { filter, take } from 'rxjs/operators';\nimport { SalesOrdersService } from '@vendasta/sales-orders';\n\n@Component({\n selector: 'orders-admin-attachments',\n templateUrl: 'admin-attachments.component.html',\n styleUrls: ['./admin-attachments.component.scss'],\n standalone: false,\n})\nexport class AdminAttachmentsComponent {\n @Input() files: FileInfo[];\n @Input() uploadUrl: string;\n @Input() businessId: string;\n @Input() orderId: string;\n @Input() createMode = false;\n @Input() editingDisabled: boolean;\n\n constructor(\n public dialog: MatDialog,\n private salesOrderService: SalesOrdersService,\n ) {\n if (!this.files) {\n this.files = [];\n }\n }\n\n updateAttachments(): void {\n if (this.createMode) {\n // skip if component is used in create order\n return;\n }\n const attachments = this.files.map((file) => {\n return {\n name: file.name,\n url: file.url,\n };\n });\n this.salesOrderService.updateAttachments(this.orderId, this.businessId, attachments).pipe(take(1)).subscribe();\n }\n\n onClickAdd(): void {\n this.dialog\n .open(AttachmentsDialogComponent, {\n width: '720px',\n data: {\n uploadUrl: this.uploadUrl,\n },\n })\n .afterClosed()\n .pipe(filter(Boolean), take(1))\n .subscribe((files) => {\n if (!this.files) {\n this.files = [];\n }\n this.files = this.files.concat(files);\n this.updateAttachments();\n });\n }\n\n getFiles(): FileInfo[] {\n return this.files;\n }\n\n onFileDeleted(file: FileInfo): void {\n const index = this.files.findIndex((f) => f.name === file.name);\n this.files.splice(index, 1);\n this.updateAttachments();\n }\n}\n", "\n \n \n
\n {{ 'LIB_ORDERS.COMMON.ORDER_DETAILS.ADMINISTRATOR_ATTACHMENTS' | translate }}\n
\n
\n \n {{ 'LIB_ORDERS.COMMON.ORDER_DETAILS.ADMIN_ATTACHMENTS_SUBTITLE' | translate }}\n \n @if (!editingDisabled) {\n
\n \n
\n }\n
\n\n \n @if (files && files.length > 0) {\n
\n \n
\n } @else {\n
\n {{ 'LIB_ORDERS.COMMON.ORDER_DETAILS.EMPTY_ATTACHMENTS' | translate }}\n
\n }\n
\n
\n", "import { InjectionToken } from '@angular/core';\nimport { AccountGroup, ProjectionFilter, ReadFilter } from '@galaxy/account-group';\nimport { Package } from '@vendasta/marketplace-packages';\nimport { User } from '@vendasta/store';\nimport { Observable } from 'rxjs';\n\n// Must be provided in the root\nexport interface CoreOrderConfig {\n automationsOrderWorkflowEnabled$: Observable;\n getMultiPackages(packageIds: string[]): Observable;\n listenToBusiness(\n businessId: string,\n mask: Partial,\n readFilter?: Partial,\n ): Observable;\n listenToBusinessUsers(partnerId: string, businessId: string): Observable;\n}\n\nexport type View = 'admin-view' | 'salesperson-view';\n\nexport interface PageOrderConfig {\n orderId$: Observable;\n businessId$?: Observable | undefined;\n previousPageUrl$?: Observable;\n view$?: Observable;\n userCanAccessRetailBilling$?: Observable;\n invoicesPageUrl$?: Observable;\n getOrderUrl?(orderId: string, businessId: string): string;\n}\n\nexport interface OrderBusinessConfig {\n getBusinessUrl?(businessId: string): string | Promise;\n}\n\nexport const CORE_ORDER_CONFIG_TOKEN = new InjectionToken(\n '[Orders]: Injection token for global config',\n);\n\nexport const PAGE_ORDER_CONFIG_TOKEN = new InjectionToken(\n '[Orders]: Injection token for a page looking at an individual order',\n);\n\nexport const ORDER_BUSINESS_CONFIG_TOKEN = new InjectionToken(\n '[Orders]: Injection token for business-specific config',\n);\n", "import { DurationInterface, DurationPeriod } from '@vendasta/sales-orders';\n\nexport const DEFAULT_CONTRACT_DURATION: DurationInterface = {\n value: 1,\n duration: DurationPeriod.YEAR,\n};\n", "import { Injectable } from '@angular/core';\nimport { AccountGroup } from '@galaxy/account-group';\nimport { MerchantService } from '@galaxy/billing';\nimport { App } from '@galaxy/marketplace-apps';\nimport { Package } from '@vendasta/marketplace-packages';\nimport { DurationInterface, LineItem } from '@vendasta/sales-orders';\nimport { UILineItem } from '@vendasta/store';\nimport { BehaviorSubject, Observable, of } from 'rxjs';\nimport { map, mergeMap } from 'rxjs/operators';\nimport { UILineItemWithEditionPricing } from '../../shared/interface';\nimport { convertSelectedAppToLineItem, packageToUILineItem } from '../../shared/utils';\nimport { DEFAULT_CONTRACT_DURATION } from '../order-details/contract-details/consts';\nimport { LineItemsAdapterService } from './line-items-adapter.service';\nimport { LineItemsSelected } from './line-items.interface';\n\nexport interface ContractTerm {\n startDate: Date;\n duration: DurationInterface;\n}\n\n@Injectable()\nexport class LineItemsService {\n partnerId: string;\n marketId: string;\n businessId: string;\n\n specifiedLineItems$$: BehaviorSubject = new BehaviorSubject([]);\n specifiedLineItems$: Observable = this.specifiedLineItems$$.asObservable();\n uiLineItems$: Observable;\n\n private contractStartDate$$ = new BehaviorSubject(null);\n public contractStartDate$ = this.contractStartDate$$.asObservable();\n\n private contractDuration$$ = new BehaviorSubject(DEFAULT_CONTRACT_DURATION);\n public contractDuration$ = this.contractDuration$$.asObservable();\n\n loading$$: BehaviorSubject = new BehaviorSubject(true);\n loading$: Observable = this.loading$$.asObservable();\n\n // Find packages and apps that have been deselected and remove them from the service instance\n public static removeUnselectedLineItems(\n preselectedPackages: Package[],\n preselectedApps: App[],\n newlySelectedLineItems: LineItemsSelected,\n lineItemsToUpdate: UILineItemWithEditionPricing[],\n ): UILineItemWithEditionPricing[] {\n // Find packages that have been deselected in the selector\n const deselectedPackageIds = [];\n preselectedPackages.map((pkg) => {\n const stillSelected = newlySelectedLineItems.packages.some((selected) => selected.packageId === pkg.packageId);\n if (!stillSelected) {\n deselectedPackageIds.push(pkg.packageId);\n }\n });\n\n // Find apps that have been deselected in the selector\n const deselectedAppIds = [];\n preselectedApps.map((app) => {\n const stillSelected = newlySelectedLineItems.apps.some((selected) => selected.key.appId === app.key.appId);\n if (!stillSelected) {\n deselectedAppIds.push(app.key.appId);\n }\n });\n\n // Remove packages and apps that have been deselected in the selector\n return lineItemsToUpdate.filter(\n (item) =>\n (!!item.lineItem.packageId && !deselectedPackageIds.includes(item.lineItem.packageId)) ||\n (!!item.lineItem.appKey?.appId && !deselectedAppIds.includes(item.lineItem.appKey.appId)),\n );\n }\n\n constructor(\n private readonly merchantService: MerchantService,\n private readonly lineItemsAdapterService: LineItemsAdapterService,\n ) {}\n\n // initializes service class variables\n initialize(business: Pick, lineItems: LineItem[]): void {\n this.marketId = business.externalIdentifiers.marketId;\n this.partnerId = business.externalIdentifiers.partnerId;\n this.specifiedLineItems$$.next(lineItems || []);\n this.buildLineItemsFromMarketplaceInfo();\n }\n\n // populates line item branding info from marketplace package and/or app APIs\n private buildLineItemsFromMarketplaceInfo(): void {\n this.uiLineItems$ = this.lineItemsAdapterService.lineItemsToUILineItems({\n lineItems$: this.specifiedLineItems$,\n partnerId: this.partnerId,\n marketId: this.marketId,\n });\n }\n\n addSpecifiedLineItems(lineItems: LineItem[]): void {\n this.specifiedLineItems$$.next(this.specifiedLineItems$$.getValue().concat(lineItems));\n }\n\n private getDefaultCurrency(): Observable {\n if (this.marketId && this.partnerId) {\n return this.merchantService.getMultiRetailConfigurations(this.partnerId, [this.marketId]).pipe(\n map((retailConfigs) => {\n return retailConfigs.get(this.marketId).currencyCode;\n }),\n );\n }\n return of('');\n }\n\n // resolves the latest line items according to what was selected\n public resolveLineItemSelectorInteractions(\n preselectedPackages: Package[],\n preselectedApps: App[],\n newlySelectedLineItems: LineItemsSelected,\n lineItemsToUpdate: UILineItemWithEditionPricing[],\n ): Observable {\n lineItemsToUpdate = LineItemsService.removeUnselectedLineItems(\n preselectedPackages,\n preselectedApps,\n newlySelectedLineItems,\n lineItemsToUpdate,\n );\n const addNewlySelectedPackages$ = this.addNewlySelectedPackages(newlySelectedLineItems, lineItemsToUpdate);\n return addNewlySelectedPackages$.pipe(\n mergeMap((lineItems) => {\n return this.addNewlySelectedApps(newlySelectedLineItems, lineItems);\n }),\n );\n }\n\n // Add only newly selected packages so that previously selected packages do not have quantity and revenue overwritten\n public addNewlySelectedPackages(\n newlySelectedLineItems: LineItemsSelected,\n lineItemsToUpdate: UILineItemWithEditionPricing[],\n ): Observable {\n return this.getDefaultCurrency().pipe(\n map((currency) => {\n const addedPackages = newlySelectedLineItems.packages.reduce((added, pkg) => {\n if (!lineItemsToUpdate.some((item) => item.lineItem.packageId === pkg.packageId)) {\n added.push(pkg);\n }\n return added;\n }, []);\n return lineItemsToUpdate.concat(addedPackages.map((pkg) => packageToUILineItem(pkg, currency)));\n }),\n );\n }\n\n // Add only newly added apps so that preselected apps that have not be deselected do not have quantity and revenue overwritten\n public addNewlySelectedApps(\n newlySelectedLineItems: LineItemsSelected,\n lineItemsToUpdate: UILineItemWithEditionPricing[],\n ): Observable {\n return this.getDefaultCurrency().pipe(\n map((currency) => {\n const addedApps = newlySelectedLineItems.apps.reduce((added, app) => {\n if (!lineItemsToUpdate.some((item) => item.lineItem.appKey?.appId === app.key.appId)) {\n added.push(app);\n } else {\n const mustUpdateIndex = lineItemsToUpdate.findIndex((u) => u.lineItem.appKey?.appId === app.key.appId);\n if (\n mustUpdateIndex > -1 &&\n lineItemsToUpdate[mustUpdateIndex].lineItem.appKey?.editionId !== app.key.editionId\n ) {\n lineItemsToUpdate[mustUpdateIndex] = convertSelectedAppToLineItem(app, currency);\n }\n }\n return added;\n }, []);\n return lineItemsToUpdate.concat(addedApps.map((app) => convertSelectedAppToLineItem(app, currency)));\n }),\n );\n }\n\n public setContractStartDate(value: Date): void {\n this.contractStartDate$$.next(value);\n }\n\n public getContractStartDate(): Date {\n return this.contractStartDate$$.getValue();\n }\n\n public setContractDuration(value: DurationInterface): void {\n this.contractDuration$$.next(value);\n }\n\n public getContractDuration(): DurationInterface {\n return this.contractDuration$$.getValue();\n }\n}\n", "import { Inject, Injectable } from '@angular/core';\nimport { AccountGroup, ProjectionFilter, ReadFilter } from '@galaxy/account-group';\nimport { filterNullAndUndefined } from '@vendasta/rx-utils';\nimport {\n CommonField,\n ConfigInterface,\n CustomField,\n Field,\n NotificationsService,\n Order,\n SalesOrdersService,\n Status,\n SubscriptionLocation,\n} from '@vendasta/sales-orders';\nimport { User } from '@vendasta/store';\nimport { BehaviorSubject, combineLatest, Observable, of, throwError } from 'rxjs';\nimport {\n catchError,\n distinctUntilChanged,\n filter,\n first,\n map,\n publishReplay,\n refCount,\n shareReplay,\n switchMap,\n switchMapTo,\n tap,\n} from 'rxjs/operators';\nimport { OrderConfirmationActionConfig } from '../components/order-actions/order-actions.component';\nimport { CORE_ORDER_CONFIG_TOKEN, CoreOrderConfig, PAGE_ORDER_CONFIG_TOKEN, PageOrderConfig } from './tokens';\nimport { SalesOrderProjectionFilterInterface } from '@vendasta/sales-orders/lib/_internal/interfaces';\n\nexport interface OrderState {\n subscriptionLocations: Partial>;\n}\n\nconst defaultOrderState: OrderState = {\n subscriptionLocations: {},\n};\n\n/**\n * This is a page service meant to track all the complicated state involved in an order.\n */\n@Injectable()\nexport class OrderStoreService {\n private readonly state$$ = new BehaviorSubject(defaultOrderState);\n\n public readonly order$: Observable;\n public readonly orderConfig$: Observable;\n public readonly orderConfirmationActionConfig$: Observable;\n public readonly productIds$: Observable;\n\n // Push values to this subject to cause the order to be re-fetched.\n private readonly reloadOrder$$ = new BehaviorSubject(undefined);\n private readonly orderId$: Observable;\n private readonly businessId$: Observable;\n public readonly business$: Observable;\n public readonly businessUsers$: Observable;\n\n loadingOrder$$ = new BehaviorSubject(true);\n loadingOrder$ = this.loadingOrder$$.asObservable();\n\n constructor(\n @Inject(CORE_ORDER_CONFIG_TOKEN) public readonly coreOrderConfig: CoreOrderConfig,\n @Inject(PAGE_ORDER_CONFIG_TOKEN) public readonly pageOrderConfig: PageOrderConfig,\n private readonly salesOrdersService: SalesOrdersService,\n protected readonly salesOrdersNotificationService: NotificationsService,\n ) {\n this.orderId$ = this.pageOrderConfig.orderId$.pipe(distinctUntilChanged());\n this.businessId$ = this.pageOrderConfig.businessId$.pipe(distinctUntilChanged());\n\n this.business$ = this.businessId$.pipe(\n switchMap((businessId) => {\n const mask = new ProjectionFilter({ napData: true, accountGroupExternalIdentifiers: true });\n const readFilter = new ReadFilter({ includeDeleted: true });\n return this.coreOrderConfig.listenToBusiness(businessId, mask, readFilter);\n }),\n shareReplay(1),\n );\n\n this.businessUsers$ = this.business$.pipe(\n switchMap((business) => {\n if (!business || business.deleted) {\n return of([]);\n }\n return this.coreOrderConfig.listenToBusinessUsers(\n business.externalIdentifiers.partnerId,\n business.accountGroupId,\n );\n }),\n catchError((err) => {\n if (err.status === 400) {\n return of([]);\n }\n return throwError(err);\n }),\n shareReplay(1),\n );\n\n this.order$ = combineLatest([this.orderId$, this.businessId$, this.reloadOrder$$]).pipe(\n filter(([orderId, businessId]) => Boolean(orderId && businessId)),\n tap(() => {\n this.loadingOrder$$.next(true);\n }),\n switchMap(([orderId, businessId]) => {\n const projectionFilter = {\n includeUserInfoInStatusHistory: true,\n includeCustomerRecipient: true,\n } as SalesOrderProjectionFilterInterface;\n return this.salesOrdersService.get(orderId, businessId, projectionFilter);\n }),\n map((order) => {\n order.status = order.status ?? Status.SUBMITTED;\n return order;\n }),\n tap(() => {\n this.loadingOrder$$.next(false);\n }),\n catchError((err) => {\n // TODO: Add RXJS retry logic - only set loading to false after last retry fails\n this.loadingOrder$$.next(false);\n return throwError(err);\n }),\n shareReplay(1),\n );\n\n this.orderConfig$ = this.order$.pipe(\n filterNullAndUndefined(),\n distinctUntilChanged(),\n switchMap((order) => this.salesOrdersService.getConfig(order.partnerId, order.marketId)),\n shareReplay(1),\n );\n\n this.productIds$ = this.setupProductIds$();\n this.orderConfirmationActionConfig$ = combineLatest([this.orderConfig$]).pipe(\n map(([salesOrdersConfig]) => ({\n allowSendDirectToAdmin: salesOrdersConfig.workflowStepOptions.allowSendDirectToAdmin,\n allowSendToCustomer: salesOrdersConfig.workflowStepOptions.allowSendToCustomer,\n customSalespersonActions: salesOrdersConfig.workflowStepOptions.customSalespersonActions,\n })),\n );\n }\n\n // user is implicitly grabbed from the context in the Sales Orders service\n isUserSubscribedToOrderAtLocation(location: SubscriptionLocation): Observable {\n return combineLatest([this.orderId$, this.businessId$]).pipe(\n switchMap(([orderId, businessId]) =>\n this.salesOrdersNotificationService.getSubscribedLocations(businessId, orderId),\n ),\n tap((locations) => {\n const subscriptionLocations: Partial> = (locations || []).reduce(\n (acc, curr) => {\n return { ...acc, [curr]: true };\n },\n {},\n );\n this.state$$.next({ ...this.state$$.getValue(), subscriptionLocations });\n }),\n switchMapTo(this.state$$.pipe(map((state) => state.subscriptionLocations[location] || false))),\n );\n }\n\n // user is implicitly grabbed from the context in the Sales Orders service\n async subscribeUserToOrderAtLocation(location: SubscriptionLocation): Promise {\n await this.salesOrdersNotificationService\n .subscribe(await this.getBusinessId(), await this.getOrderId(), location)\n .toPromise();\n this.setSubscriptionLocation(location, true);\n }\n\n // user is implicitly grabbed from the context in the Sales Orders service\n async unSubscribeUserToOrderAtLocation(location: SubscriptionLocation): Promise {\n await this.salesOrdersNotificationService\n .unsubscribe(await this.getBusinessId(), await this.getOrderId(), location)\n .toPromise();\n this.setSubscriptionLocation(location, false);\n }\n\n async sendExistingOrderToCustomerForApproval(\n userId: string,\n expiry: Date | null,\n orderIsSmbPayable?: boolean,\n ): Promise {\n return this.salesOrdersService.sendExistingOrderToCustomerForApproval(\n await this.getOrderId(),\n await this.getBusinessId(),\n userId,\n expiry,\n orderIsSmbPayable,\n );\n }\n\n async convertToDraft(): Promise {\n return this.salesOrdersService.convertToDraft(await this.getOrderId(), await this.getBusinessId()).toPromise();\n }\n\n async submitDraft({\n customFormData,\n extraFieldsData,\n commonFormData,\n userId,\n expiry,\n }: {\n customFormData: CustomField[];\n extraFieldsData: Field[];\n commonFormData: CommonField[];\n userId: string;\n expiry?: Date;\n }): Promise {\n if (!userId) {\n return this.salesOrdersService\n .submitDraftSalesOrder(\n await this.getOrderId(),\n await this.getBusinessId(),\n customFormData,\n commonFormData,\n extraFieldsData,\n )\n .toPromise();\n }\n return this.salesOrdersService\n .submitDraftForCustomerApproval(\n await this.getOrderId(),\n await this.getBusinessId(),\n userId,\n customFormData,\n commonFormData,\n extraFieldsData,\n expiry,\n )\n .toPromise();\n }\n\n // Request cancellation of an order. If order is not provided, the orderId and\n // accountGroupId from the route will be used.\n async requestCancellation(\n notes: string,\n order?: {\n orderId: string;\n businessId: string;\n },\n ): Promise {\n const orderId = order ? order.orderId : await this.getOrderId();\n const businessId = order ? order.businessId : await this.getBusinessId();\n return this.salesOrdersService.requestCancellation(orderId, businessId, notes).toPromise();\n }\n\n reloadOrder(): void {\n this.reloadOrder$$.next(undefined);\n }\n\n private getOrderId(): Promise {\n return this.orderId$.pipe(filter(Boolean), first()).toPromise();\n }\n\n private getBusinessId(): Promise {\n return this.businessId$.pipe(filter(Boolean), first()).toPromise();\n }\n\n private getPartnerId(): Promise {\n return this.order$\n .pipe(\n map((order) => order.partnerId),\n filter(Boolean),\n first(),\n )\n .toPromise();\n }\n\n private setSubscriptionLocation(location: SubscriptionLocation, subscribed: boolean): void {\n this.state$$.next({\n ...this.state$$.getValue(),\n subscriptionLocations: {\n ...this.state$$.getValue().subscriptionLocations,\n [location]: subscribed,\n },\n });\n }\n\n private setupProductIds$(): Observable {\n const packages$ = this.order$.pipe(\n switchMap((order) => {\n const packageLineItems = order.lineItems?.filter((li) => !!li.packageId) || [];\n if (packageLineItems.length > 0) {\n return this.coreOrderConfig.getMultiPackages(packageLineItems.map((li) => li.packageId));\n }\n return of([]);\n }),\n shareReplay(1),\n );\n return combineLatest([this.order$, packages$]).pipe(\n map(([order, packages]) => {\n const unpackagedApps = order.lineItems?.filter((li) => !!li.appKey) || [];\n const appIds = unpackagedApps.map((np) => np.appKey.appId);\n packages.map((p) => (p.lineItems.lineItems || []).map((li) => appIds.push(li.id)));\n return appIds;\n }),\n publishReplay(1),\n refCount(),\n );\n }\n}\n", "import { Component, Input } from '@angular/core';\nimport { AccountGroup } from '@galaxy/account-group';\nimport { TranslateModule } from '@ngx-translate/core';\n\n@Component({\n selector: 'orders-company-name',\n styleUrls: ['../order-details.component.scss'],\n template: `\n
\n \n {{ 'LIB_ORDERS.COMMON.ORDER_DETAILS.COMPANY' | translate }}\n \n \n \n {{ business?.napData?.companyName }}\n \n \n
\n `,\n imports: [TranslateModule],\n})\nexport class CompanyNameComponent {\n @Input() business: Pick;\n @Input() url?: string;\n}\n", "import { Pipe, PipeTransform } from '@angular/core';\nimport { AccountGroupLocationInterface } from '@vendasta/account-group';\n\n@Pipe({ standalone: true, name: 'formatAddress' })\nexport class FormatAddressPipe implements PipeTransform {\n transform(address: AccountGroupLocationInterface): string {\n if (!address) {\n return '';\n }\n return [\n address.address?.trim(),\n address.address2?.trim(),\n [address.city?.trim(), address.state?.trim(), address.country?.trim()].filter((v) => !!v).join(', '),\n address.zip?.trim(),\n ]\n .filter((v) => !!v)\n .join('\\n');\n }\n}\n", "import { Component, Input } from '@angular/core';\nimport { AccountGroup } from '@galaxy/account-group';\nimport { TranslateModule } from '@ngx-translate/core';\nimport { FormatAddressPipe } from './format-address.pipe';\n\n@Component({\n selector: 'orders-company-address',\n styleUrls: ['../order-details.component.scss'],\n template: `\n
\n \n {{ 'LIB_ORDERS.COMMON.ORDER_DETAILS.ADDRESS' | translate }}\n \n @if (business?.napData; as napData) {\n \n
{{ napData | formatAddress }}
\n
\n }\n
\n `,\n imports: [TranslateModule, FormatAddressPipe],\n})\nexport class CompanyAddressComponent {\n @Input() business: Pick;\n}\n", "import { Component, EventEmitter, Input, Output } from '@angular/core';\nimport { DateFnsAdapter, MAT_DATE_FNS_FORMATS } from '@angular/material-date-fns-adapter';\nimport { DateAdapter, MAT_DATE_FORMATS, MAT_DATE_LOCALE } from '@angular/material/core';\nimport { getLocale } from '@vendasta/galaxy/utility/locale';\nimport { GalaxyFormFieldModule } from '@vendasta/galaxy/form-field';\nimport { FormsModule } from '@angular/forms';\nimport { MatFormFieldModule } from '@angular/material/form-field';\nimport { MatDatepickerModule } from '@angular/material/datepicker';\nimport { MatTooltipModule } from '@angular/material/tooltip';\nimport { TranslateModule } from '@ngx-translate/core';\n\n@Component({\n selector: 'orders-contract-start',\n styleUrls: ['../order-details.component.scss', './contract-start.component.scss'],\n template: `\n \n {{ 'LIB_ORDERS.COMMON.ORDER_DETAILS.CONTRACT_START' | translate }}\n \n \n \n \n \n @if (!contractStartDate) {\n {{ 'LIB_ORDERS.COMMON.ORDERS.REQUIRED' | translate }}\n }\n \n `,\n providers: [\n { provide: MAT_DATE_LOCALE, useValue: getLocale() },\n {\n provide: DateAdapter,\n useClass: DateFnsAdapter,\n deps: [MAT_DATE_LOCALE],\n },\n { provide: MAT_DATE_FORMATS, useValue: MAT_DATE_FNS_FORMATS },\n ],\n imports: [\n GalaxyFormFieldModule,\n FormsModule,\n MatFormFieldModule,\n MatDatepickerModule,\n MatTooltipModule,\n TranslateModule,\n ],\n})\nexport class OrderContractStartComponent {\n @Input() minDate: Date;\n @Input() contractStartDate: Date;\n @Input() viewOnly = false;\n\n @Output() contractStartDateChange = new EventEmitter();\n\n handleDateChange(event: Date): void {\n this.contractStartDateChange.emit(event);\n }\n}\n", "import { Component, EventEmitter, Input, Output } from '@angular/core';\nimport { DurationInterface, DurationPeriod } from '@vendasta/sales-orders';\nimport { GalaxyFormFieldModule } from '@vendasta/galaxy/form-field';\nimport { TranslateModule } from '@ngx-translate/core';\nimport { FormsModule } from '@angular/forms';\nimport { MatSelectModule } from '@angular/material/select';\nimport { MatTooltipModule } from '@angular/material/tooltip';\nimport { CommonModule } from '@angular/common';\n\n@Component({\n selector: 'orders-contract-duration',\n styleUrls: ['../order-details.component.scss', './contract-duration.component.scss'],\n template: `\n \n {{ 'LIB_ORDERS.COMMON.ORDER_DETAILS.CONTRACT_DURATION' | translate }}\n \n \n \n \n @if (contractDuration?.value < 1) {\n {{ 'LIB_ORDERS.COMMON.ORDERS.ERROR_LESS_THAN_ONE' | translate }}\n }\n \n \n \n \n {{ option.i18nLabel | translate }}\n \n \n \n \n `,\n imports: [GalaxyFormFieldModule, FormsModule, TranslateModule, MatTooltipModule, MatSelectModule, CommonModule],\n})\nexport class OrderContractDurationComponent {\n @Input() hideTooltip = false;\n @Input() viewOnly = false;\n\n _contractDuration: DurationInterface;\n @Input()\n get contractDuration(): DurationInterface {\n return this._contractDuration;\n }\n set contractDuration(value: DurationInterface) {\n this._contractDuration = value;\n }\n\n @Output() contractDurationChange = new EventEmitter();\n\n handleContractDurationValueChange(value: number): void {\n this.contractDuration.value = value;\n this.contractDurationChange.emit(this.contractDuration);\n }\n\n handleContractDurationPeriodChange(period: DurationPeriod): void {\n this.contractDuration.duration = period;\n this.contractDurationChange.emit(this.contractDuration);\n }\n\n protected durationPeriodOptions = GetDurationPeriodsOptions();\n\n deprecatedPeriodOption(): DurationPeriodOption | undefined {\n const period = this.contractDuration.duration;\n return GetDeprecatedDurationPeriodOptions().find((option) => option.value === period);\n }\n}\n\ninterface DurationPeriodOption {\n i18nLabel: string;\n value: number;\n}\n\nfunction GetDurationPeriodsOptions(): DurationPeriodOption[] {\n return [\n {\n i18nLabel: 'LIB_ORDERS.COMMON.ORDER_DETAILS.CONTRACT_DURATIONS.MONTH',\n value: DurationPeriod.MONTH,\n },\n {\n i18nLabel: 'LIB_ORDERS.COMMON.ORDER_DETAILS.CONTRACT_DURATIONS.YEAR',\n value: DurationPeriod.YEAR,\n },\n ];\n}\n\nfunction GetDeprecatedDurationPeriodOptions(): DurationPeriodOption[] {\n return [\n {\n i18nLabel: 'LIB_ORDERS.COMMON.ORDER_DETAILS.CONTRACT_DURATIONS.DAY',\n value: DurationPeriod.DAY,\n },\n {\n i18nLabel: 'LIB_ORDERS.COMMON.ORDER_DETAILS.CONTRACT_DURATIONS.WEEK',\n value: DurationPeriod.WEEK,\n },\n ];\n}\n", "import { Component, EventEmitter, Input, Output } from '@angular/core';\nimport { AccountGroup } from '@galaxy/account-group';\nimport { DurationInterface } from '@vendasta/sales-orders';\n\nexport interface OrderDetailsComponentConfig {\n showBusinessName: boolean;\n minContractStartDate: Date;\n showContractDurationTooltip: boolean;\n}\n\n@Component({\n selector: 'orders-order-details',\n template: `\n \n \n \n {{ 'LIB_ORDERS.COMMON.ORDER_DETAILS.TITLE' | translate }}\n
\n \n
\n
\n
\n \n \n @if (config.showBusinessName) {\n \n }\n\n \n \n\n
\n \n \n \n \n \n \n
\n
\n
\n `,\n styleUrl: './order-details.component.scss',\n standalone: false,\n})\nexport class OrderDetailsComponent {\n @Input() business: Pick;\n @Input() contractStartDate: Date;\n @Input() contractDuration: DurationInterface;\n @Input() config: OrderDetailsComponentConfig;\n @Input() isLoading = false;\n @Input() viewOnly = false;\n @Input() companyLink: string;\n\n @Output() nameClicked = new EventEmitter();\n @Output() contractStartDateChanged = new EventEmitter();\n @Output() contractDurationChanged = new EventEmitter();\n}\n", "import { AfterViewInit, Component, Inject, inject, Input, OnDestroy, OnInit, Optional } from '@angular/core';\nimport { FormControl, FormGroup, UntypedFormGroup, Validators } from '@angular/forms';\nimport { DateFnsAdapter, MAT_DATE_FNS_FORMATS } from '@angular/material-date-fns-adapter';\nimport { DateAdapter, MAT_DATE_FORMATS, MAT_DATE_LOCALE } from '@angular/material/core';\nimport { AccountGroup } from '@galaxy/account-group';\nimport { SnackbarService } from '@vendasta/galaxy/snackbar-service';\nimport { getLocale } from '@vendasta/galaxy/utility/locale';\nimport { DurationInterface, SalesOrdersService, Status } from '@vendasta/sales-orders';\nimport { BehaviorSubject, lastValueFrom, Observable, Subscription } from 'rxjs';\nimport { debounceTime, filter, map, scan, take } from 'rxjs/operators';\nimport { ORDER_BUSINESS_CONFIG_TOKEN } from '../../core/tokens';\nimport { MARKET_ID, PARTNER_ID } from '../../shared/constants';\nimport { LineItemsService } from '../line-items/line-items.service';\nimport { DEFAULT_CONTRACT_DURATION } from '../order-details/contract-details/consts';\nimport { CreateOrderDetailsForm } from './create-order-details.interface';\nimport { OrderStoreService } from '../../core/orders.service';\n\n@Component({\n selector: 'orders-create-order-details',\n templateUrl: './create-order-details.component.html',\n styleUrls: ['./create-order-details.component.scss'],\n providers: [\n { provide: MAT_DATE_LOCALE, useValue: getLocale() },\n {\n provide: DateAdapter,\n useClass: DateFnsAdapter,\n deps: [MAT_DATE_LOCALE],\n },\n { provide: MAT_DATE_FORMATS, useValue: MAT_DATE_FNS_FORMATS },\n ],\n standalone: false,\n})\nexport class CreateOrderDetailsComponent implements OnInit, OnDestroy, AfterViewInit {\n readonly minDate = new Date(new Date().setHours(7, 0, 0, 0));\n private readonly orderBusinessConfig = inject(ORDER_BUSINESS_CONFIG_TOKEN, { optional: true });\n\n @Input() business: Pick;\n @Input() orderId: string;\n @Input() contractStartDate = this.minDate;\n @Input() formGroup?: UntypedFormGroup;\n @Input() createMode = false;\n @Input() viewOnly = false;\n\n _contractDuration: DurationInterface = DEFAULT_CONTRACT_DURATION;\n get contractDuration(): DurationInterface {\n return this._contractDuration;\n }\n\n @Input() set contractDuration(value: DurationInterface | null) {\n this._contractDuration = value ?? DEFAULT_CONTRACT_DURATION;\n }\n\n readonly contractDurationValueControl = new FormControl(this.contractDuration.value, [\n Validators.required,\n Validators.min(1),\n ]);\n readonly contractStartControl = new FormControl(this.contractStartDate, [Validators.required]);\n readonly contractDurationGroup = new FormGroup({\n value: this.contractDurationValueControl,\n duration: new FormControl(this.contractDuration.duration, [Validators.required]),\n });\n\n readonly headerForm = new FormGroup({\n contractStartDate: this.contractStartControl,\n contractDuration: this.contractDurationGroup,\n });\n\n private readonly updating$$ = new BehaviorSubject(0);\n public readonly isUpdating$ = this.updating$$.pipe(\n scan((acc, value) => acc + value),\n map((total) => total > 0),\n );\n private readonly addUpdateOp = () => this.updating$$.next(1);\n private readonly completeUpdateOp = () => this.updating$$.next(-1);\n\n private readonly subscriptions = new Subscription();\n\n link$: Promise;\n\n constructor(\n @Inject(PARTNER_ID) private readonly partnerId$: Observable,\n @Inject(MARKET_ID) private readonly marketId$: Observable,\n private readonly salesOrdersService: SalesOrdersService,\n private readonly lineItemsService: LineItemsService,\n private readonly snack: SnackbarService,\n @Optional() private readonly orderStoreService: OrderStoreService,\n ) {}\n\n ngOnInit(): void {\n this.initHeaderForm();\n this.link$ = this.getAccountLink$(this.business?.accountGroupId);\n }\n\n ngOnDestroy(): void {\n this.subscriptions.unsubscribe();\n }\n\n ngAfterViewInit(): void {\n if (this.orderId && this.orderStoreService) {\n this.orderStoreService.order$.pipe(take(1)).subscribe((order) => {\n if (order.status === Status.DRAFTED) {\n this.validateStartDate();\n }\n });\n }\n }\n\n private initHeaderForm(): void {\n this.formGroup?.addControl('headerForm', this.headerForm);\n const durationControl = this.headerForm.controls.contractDuration;\n\n this.contractStartControl.setValue(this.contractStartDate);\n this.setLineItemsServiceContractStartDate(this.contractStartDate);\n\n if (this.contractDuration) {\n durationControl.setValue({\n value: this.contractDuration.value,\n duration: this.contractDuration.duration,\n });\n this.setLineItemsServiceContractDuration(this.contractDuration);\n }\n\n const dueTime = 250;\n this.subscriptions.add(\n durationControl.valueChanges\n .pipe(\n filter(() => durationControl.valid),\n debounceTime(dueTime),\n )\n .subscribe(() => {\n this.updateContractDuration();\n }),\n );\n\n this.subscriptions.add(\n this.contractStartControl.valueChanges\n .pipe(\n filter(() => this.contractStartControl.valid),\n debounceTime(dueTime),\n )\n .subscribe(() => {\n this.updateContractStartDate();\n }),\n );\n }\n\n handleContractStartDateChange(date: Date): void {\n this.contractStartControl.setValue(date);\n this.contractStartDate = date;\n }\n\n handleContractDurationChange(duration: DurationInterface): void {\n this.headerForm.controls.contractDuration.setValue({\n value: duration.value,\n duration: duration.duration,\n });\n }\n\n validateStartDate(): void {\n const today = new Date(new Date().setHours(7, 0, 0, 0));\n if (this.contractStartDate < today) {\n this.handleContractStartDateChange(today);\n }\n }\n\n // used for editing existing order details (updates contract start date value specifically)\n private updateContractStartDate(): void {\n const startDate = this.contractStartControl.value;\n if (this.orderId) {\n this.addUpdateOp();\n startDate.setHours(7);\n lastValueFrom(\n this.salesOrdersService.updateRequestedActivation(this.orderId, this.business.accountGroupId, startDate),\n )\n .then(() => this.orderStoreService.reloadOrder())\n .catch(() => this.snack.openErrorSnack('LIB_ORDERS.COMMON.ORDER_DETAILS.CONTRACT_START_UPDATE_ERROR'))\n .finally(this.completeUpdateOp);\n }\n\n this.setLineItemsServiceContractStartDate(startDate);\n }\n\n private updateContractDuration(): void {\n const contractDuration = this.headerForm.value.contractDuration;\n if (this.orderId) {\n this.addUpdateOp();\n lastValueFrom(\n this.salesOrdersService.updateContractDuration(this.orderId, this.business.accountGroupId, contractDuration),\n )\n .catch(() => this.snack.openErrorSnack('LIB_ORDERS.COMMON.ORDER_DETAILS.CONTRACT_DURATION_UPDATE_ERROR'))\n .finally(this.completeUpdateOp);\n }\n\n this.setLineItemsServiceContractDuration(contractDuration);\n }\n\n public getFormData(): CreateOrderDetailsForm {\n const data = this.headerForm.value;\n data.contractStartDate.setHours(7);\n return data as CreateOrderDetailsForm;\n }\n\n private getAccountLink$(accountGroupId: string): Promise {\n const url = this.orderBusinessConfig?.getBusinessUrl?.(accountGroupId) ?? `/info/${accountGroupId}`;\n return this.makePromise(url);\n }\n\n private makePromise(value: T | Promise): Promise {\n if (value instanceof Promise) {\n return value;\n }\n return Promise.resolve(value);\n }\n\n private setLineItemsServiceContractStartDate(startDate: Date): void {\n this.lineItemsService.setContractStartDate(startDate);\n }\n\n private setLineItemsServiceContractDuration(duration: DurationInterface): void {\n this.lineItemsService.setContractDuration(duration);\n }\n}\n", "@if (\n {\n link: link$ | async,\n isLoading: isUpdating$ | async,\n };\n as state\n) {\n \n}\n", "import { ChangeDetectorRef, Component, ElementRef, Input, OnDestroy, OnInit, ViewChild } from '@angular/core';\nimport { UntypedFormControl } from '@angular/forms';\nimport { SnackbarService } from '@vendasta/galaxy/snackbar-service';\nimport { SalesOrdersService } from '@vendasta/sales-orders';\nimport { Observable, Subscription } from 'rxjs';\nimport { debounceTime, distinctUntilChanged } from 'rxjs/operators';\n\ntype NoteType = 'customer' | 'administrator';\n\ntype EditingType = 'toggleable' | 'editingonly' | 'disabled';\n\n@Component({\n selector: 'orders-notes',\n templateUrl: 'notes.component.html',\n styleUrls: ['notes.component.scss'],\n standalone: false,\n})\nexport class NotesComponent implements OnInit, OnDestroy {\n @Input() noteType: NoteType;\n @Input() existingNotes: string;\n @Input() orderId: string;\n @Input() businessId: string;\n @Input() editingType: EditingType = 'toggleable';\n @Input() createMode = false;\n\n @ViewChild('textFocus') textFocus: ElementRef;\n\n editMode = false;\n formControl: UntypedFormControl;\n notes: string;\n updatingNotes = false;\n\n subscriptions: Subscription[] = [];\n\n constructor(\n private readonly snack: SnackbarService,\n private readonly salesOrdersService: SalesOrdersService,\n private cdr: ChangeDetectorRef,\n ) {}\n\n ngOnInit(): void {\n this.notes = this.existingNotes;\n this.formControl = new UntypedFormControl(this.existingNotes, []);\n this.subscriptions.push(\n this.formControl.valueChanges.pipe(distinctUntilChanged(), debounceTime(500)).subscribe((notes) => {\n this.notes = notes;\n this.saveNotes();\n }),\n );\n this.editMode = this.editingType === 'editingonly';\n }\n\n ngOnDestroy(): void {\n this.subscriptions.forEach((sub) => sub.unsubscribe());\n }\n\n saveNotes(): void {\n if (this.existingNotes === this.notes || this.createMode) {\n // Skip if there were no changes to the notes\n return;\n }\n this.updatingNotes = true;\n this.updateNotes(this.notes).subscribe({\n next: (successful) => {\n if (successful) {\n this.updatingNotes = false;\n this.existingNotes = this.notes;\n }\n },\n error: () => {\n this.snack.openErrorSnack('LIB_ORDERS.COMMON.ORDER_DETAILS.NOTES_UPDATE_ERROR');\n this.updatingNotes = false;\n },\n });\n }\n\n onBlur(): void {\n this.editMode = this.editingType === 'editingonly';\n }\n\n onClickEdit(): void {\n this.editMode = true;\n this.cdr.detectChanges();\n this.textFocus.nativeElement.focus();\n }\n\n updateNotes(existingNotes: string): Observable {\n if (this.noteType === 'customer') {\n return this.salesOrdersService.updateCustomerNotes(this.orderId, this.businessId, existingNotes);\n } else {\n return this.salesOrdersService.updateNotes(this.orderId, this.businessId, existingNotes);\n }\n }\n\n // getNotes can be called from a ViewChild reference to get the current notes value\n getNotes(): string {\n return this.notes;\n }\n}\n", "\n \n \n
\n {{ 'LIB_ORDERS.COMMON.ORDER_DETAILS.CUSTOMER_NOTES_TITLE' | translate }}\n
\n \n {{ 'LIB_ORDERS.COMMON.ORDER_DETAILS.ADMINISTRATIVE_NOTES_TITLE' | translate }}\n \n
\n \n
\n
\n \n
\n
\n
\n\n \n \n \n @switch (noteType) {\n @case ('customer') {\n \n {{ 'LIB_ORDERS.COMMON.ORDER_DETAILS.CUSTOMER_NOTES_INFO' | translate }}\n \n }\n @case ('administrator') {\n \n {{ 'LIB_ORDERS.COMMON.ORDER_DETAILS.ADMINISTRATIVE_NOTES_INFO' | translate }}\n \n }\n }\n \n\n
\n
\n {{ existingNotes }}\n
\n \n
\n {{ 'LIB_ORDERS.COMMON.ORDER_DETAILS.EMPTY_NOTES' | translate }}\n
\n
\n
\n
\n
\n", "import { COMMA, ENTER } from '@angular/cdk/keycodes';\nimport { ChangeDetectorRef, Component, ElementRef, Input, OnDestroy, OnInit, ViewChild } from '@angular/core';\nimport { UntypedFormControl } from '@angular/forms';\nimport { MatAutocomplete } from '@angular/material/autocomplete';\nimport { SnackbarService } from '@vendasta/galaxy/snackbar-service';\nimport { Order, SalesOrdersService } from '@vendasta/sales-orders';\nimport { BehaviorSubject, Observable, ReplaySubject, Subscription, of } from 'rxjs';\nimport { debounceTime, map, switchMap } from 'rxjs/operators';\nimport { NewOrderDetails } from './tags.interface';\nimport { TranslateService } from '@ngx-translate/core';\n\nexport type EditingType = 'toggleable' | 'editingonly' | 'disabled';\n\n@Component({\n selector: 'orders-tags',\n templateUrl: 'tags.component.html',\n styleUrls: ['tags.component.scss'],\n standalone: false,\n})\nexport class TagsComponent implements OnInit, OnDestroy {\n // Exactly one of the following inputs is required:\n // newOrderDetails is to be used when there isn't an existing order to pull info from (eg. partner id and market id)\n @Input() newOrderDetails: NewOrderDetails;\n // otherwise the existingOrder can be passed in which has all the info this component needs\n @Input() existingOrder: Order;\n @Input() editingType: EditingType = 'toggleable';\n @Input() createMode = false;\n\n @ViewChild('tagsInput') tagsInput: ElementRef;\n @ViewChild('auto') matAutocomplete: MatAutocomplete;\n\n partnerId: string;\n marketId: string;\n\n editMode = false;\n editingTags: string[] = [];\n tagOptions$: Observable;\n updatingTags = false;\n\n separatorKeysCodes: number[] = [ENTER, COMMA];\n tagsCtrl = new UntypedFormControl();\n loadTags$$ = new BehaviorSubject(true);\n newTag$$ = new ReplaySubject(1);\n newTag$ = this.newTag$$.asObservable();\n filteredTags$: Observable;\n filteredTags: string[];\n tags: string[] = [];\n availableTags: string[] = [];\n subscriptions: Subscription[] = [];\n\n constructor(\n private snack: SnackbarService,\n private salesOrderService: SalesOrdersService,\n private cdr: ChangeDetectorRef,\n private translateService: TranslateService,\n ) {}\n\n ngOnInit(): void {\n this.editMode = this.editingType === 'editingonly';\n if (this.newOrderDetails) {\n this.initNewOrderTags();\n } else {\n this.initExistingOrderTags();\n }\n this.tagOptions$ = this.loadTags$$.pipe(\n switchMap(() => {\n return this.salesOrderService.listTags(this.partnerId, { marketIds: [this.marketId] });\n }),\n map((resp) => {\n return resp.tags || [];\n }),\n );\n\n this.subscriptions.push(\n // init all tags dropdown\n this.tagOptions$.subscribe((tags) => {\n // if we have pre-selected tags\n this.availableTags = tags;\n }),\n );\n\n this.filteredTags$ = this.tagsCtrl.valueChanges.pipe(\n debounceTime(300),\n map((tag: string | null) => {\n if (!tag) {\n tag = '';\n }\n const filteredTags = this._filter(tag);\n // if no existing tags found, display the new entry as an option:\n if (filteredTags.length === 0) {\n this.newTag$$.next(tag);\n return [];\n }\n this.newTag$$.next('');\n return tag ? filteredTags : this.availableTags.slice();\n }),\n );\n }\n\n ngOnDestroy(): void {\n this.subscriptions.forEach((sub) => sub.unsubscribe());\n }\n\n onClickEdit(): void {\n this.editMode = true;\n this.cdr.detectChanges();\n this.tagsInput.nativeElement.focus();\n }\n\n // initializes class variables with information provided via an existing order\n private initExistingOrderTags(): void {\n this.editingTags = this.existingOrder.tags;\n if (this.editingTags) {\n this.tags = this.editingTags;\n }\n this.partnerId = this.existingOrder.partnerId;\n this.marketId = this.existingOrder.marketId;\n }\n\n // initializes class variables with info provided via the details input\n private initNewOrderTags(): void {\n this.partnerId = this.newOrderDetails.partnerId;\n this.marketId = this.newOrderDetails.marketId;\n }\n\n getTags(): string[] {\n return this.tags || []; // the tag input may not have been toggled\n }\n\n removeTag(tag: string): void {\n const index = this.tags.findIndex((tagOption) => tagOption === tag);\n if (index >= 0) {\n this.tags.splice(index, 1);\n this.updateTags();\n }\n }\n\n onAddTag(tag: string): void {\n const index = this.tags.findIndex((tagOption) => tagOption === tag);\n if (index === -1) {\n this.tags.push(tag);\n this.updateTags();\n }\n this.tagsInput.nativeElement.value = '';\n this.tagsCtrl.setValue(null);\n }\n\n updateTags(): void {\n if (this.createMode) {\n // Skip if component is used in create order.\n return;\n }\n this.updatingTags = true;\n this.subscriptions.push(\n this.salesOrderService\n .updateTags(this.existingOrder.orderId, this.existingOrder.businessId, this.tags)\n .pipe(\n switchMap((order) => {\n return of({\n tags: order.tags || [],\n });\n }),\n )\n .subscribe({\n next: () => {\n this.updatingTags = false;\n this.loadTags$$.next(true);\n },\n error: () => {\n this.snack.openErrorSnack('LIB_ORDERS.COMMON.ORDER_DETAILS.TAGS_UPDATE_ERROR');\n this.updatingTags = false;\n },\n }),\n );\n }\n\n private _filter(option: string): string[] {\n const filterValue = option.toLowerCase();\n return this.availableTags.filter((tag) => tag.toLowerCase().indexOf(filterValue) === 0);\n }\n}\n", "\n \n \n
\n {{ 'LIB_ORDERS.COMMON.ORDER_DETAILS.TAGS' | translate }}\n
\n
\n \n
\n
\n \n
\n
\n
\n\n \n \n \n \n \n \n \n {{ tag }}\n cancel\n \n \n \n \n \n {{ tag }}\n \n \n add\n \n {{ 'LIB_ORDERS.COMMON.ORDER_DETAILS.NEW_TAG' | translate: { newTag: '`' + newTag + '`' } }}\n \n \n \n \n \n \n\n \n
\n
0; else noTags\" class=\"tags\">\n
\n \n {{ tag }}\n \n
\n
\n \n
\n {{ 'LIB_ORDERS.COMMON.ORDER_DETAILS.EMPTY_TAGS' | translate }}\n
\n
\n
\n
\n
\n", "import { InventoryItemWithPricing } from '@galaxy/inventory-ui';\nimport { AppType } from '@vendasta/marketplace-apps';\nimport { LineItem, LineItemAppKey } from '@vendasta/sales-orders';\nimport { UILineItemWithEditionPricing } from '../../shared/interface';\n\nexport function inventoryItemsToLineItems(items: InventoryItemWithPricing[]): UILineItemWithEditionPricing[] {\n return items.map((item) => {\n const lineItem = new LineItem(item.pricing);\n let editionName: string = undefined;\n let appType: AppType = undefined;\n if (item.type === 'app') {\n editionName = item.edition?.name;\n }\n if (item.type !== 'package') {\n if (item.type === 'app') {\n lineItem.appKey = new LineItemAppKey({\n appId: item.itemId,\n editionId: item.edition?.editionId,\n });\n appType = AppType.APP_TYPE_APP;\n } else {\n lineItem.appKey = new LineItemAppKey({\n appId: item.itemId,\n });\n appType = AppType.APP_TYPE_ADDON;\n }\n } else {\n lineItem.packageId = item.itemId;\n }\n lineItem.quantity = item.quantity;\n return {\n lineItem: lineItem,\n name: item.name,\n editionName: editionName,\n iconUrl: item.iconUrl,\n appType: appType,\n };\n });\n}\n", "export class Features {\n public static readonly CREATE_ORDERS_CONTRACT_TERMS = '2024_05_pcc_create_orders_contract_terms';\n public static readonly PC_UNIFIED_ORDERS_PAGE = '2024_07_pcc_unified_orders_page';\n public static readonly UNIFIED_ORDERS_SCHEDULED_ACTIVATIONS = '2024_11_07_unified_orders_page_schedule_activation';\n}\n", "import { Pipe, PipeTransform } from '@angular/core';\nimport { UILineItemWithEditionPricing } from '../../shared/interface';\nimport { InventoryItemType, InventoryItemWithPricing } from '@galaxy/inventory-ui';\nimport { AppType } from '@vendasta/marketplace-apps';\n\n@Pipe({\n name: 'lineItemsToInventoryItems',\n standalone: false,\n})\nexport class LineItemsToInventoryItemsPipe implements PipeTransform {\n transform(items: UILineItemWithEditionPricing[]): InventoryItemWithPricing[] {\n if (!items?.length) {\n return [];\n }\n\n return items.map((item) => {\n let itemType: InventoryItemType = 'package';\n if (item.appType === AppType.APP_TYPE_APP) {\n itemType = 'app';\n } else if (item.appType === AppType.APP_TYPE_ADDON) {\n itemType = 'addon';\n }\n\n const inventoryItem: InventoryItemWithPricing = {\n type: itemType,\n itemId: item.lineItem.appKey?.appId ?? item.lineItem.packageId,\n name: item.name,\n iconUrl: item.iconUrl,\n pricing: item.lineItem,\n quantity: item.lineItem.quantity,\n };\n\n if (inventoryItem.type === 'app') {\n inventoryItem.edition = {\n editionId: item.lineItem.appKey?.editionId,\n name: item.editionName,\n };\n }\n\n return inventoryItem;\n });\n }\n}\n", "import { getCurrencySymbol } from '@angular/common';\nimport { ChangeDetectorRef, Component, EventEmitter, Input, OnDestroy, OnInit, Output } from '@angular/core';\nimport {\n UntypedFormArray,\n UntypedFormBuilder,\n UntypedFormControl,\n UntypedFormGroup,\n ValidationErrors,\n Validators,\n} from '@angular/forms';\nimport { AccountGroup } from '@galaxy/account-group';\nimport { ItemPricingTableConfig, ItemsChangedEvent, ItemSelectorFilterType } from '@galaxy/inventory-ui';\nimport { FeatureFlagService, FeatureFlagStatusInterface } from '@galaxy/partner';\nimport { SnackbarService } from '@vendasta/galaxy/snackbar-service';\nimport { LineItem, Revenue, RevenueComponent, RevenuePeriod, SalesOrdersService } from '@vendasta/sales-orders';\nimport { OrderFormOptionsInterface } from '@vendasta/store';\nimport { BehaviorSubject, combineLatest, Observable, Subscription } from 'rxjs';\nimport { map, shareReplay, tap } from 'rxjs/operators';\nimport { UILineItemWithEditionPricing } from '../../shared/interface';\nimport { PeriodOptions } from './line-items.interface';\nimport { LineItemsService } from './line-items.service';\nimport { inventoryItemsToLineItems } from './utils';\nimport { Features } from '../../core/features';\n\n@Component({\n selector: 'orders-line-items',\n templateUrl: './line-items.component.html',\n styleUrls: ['./line-items.component.scss'],\n standalone: false,\n})\nexport class LineItemsComponent implements OnInit, OnDestroy {\n @Input() business: Pick;\n\n // we specify line items to populate the component when an opportunity is\n // selected, or when we are editing an existing order\n @Input() specifiedLineItems: LineItem[];\n\n // configurations\n hideSellingStandaloneProducts = false;\n // allows multiple quantities to be set for products that are not multi-activatable\n @Input() bypassQuantityRestrictions = false;\n @Input() allowNegativeValues = false;\n @Input() editable = false;\n @Input() startEditing = false;\n @Input() currencyOverride: string = null;\n @Input() allowRemovingLastLineItem: boolean;\n @Input() viewOnly = false;\n @Input() canEditInvoices = false;\n\n @Output() lineItemsUpdated: EventEmitter = new EventEmitter<\n UILineItemWithEditionPricing[]\n >();\n @Output() lineItemsInitialized: EventEmitter = new EventEmitter<\n UILineItemWithEditionPricing[]\n >();\n\n editing = false;\n loading$: Observable;\n formArrayLineItems: UntypedFormArray;\n currentLineItems$$: BehaviorSubject = new BehaviorSubject<\n UILineItemWithEditionPricing[]\n >([]);\n currentLineItems: UILineItemWithEditionPricing[] = [];\n uiLineItems$: Observable;\n orderFormOptions$: Observable;\n featureFlags$: Observable;\n\n itemPricingTableConfig$: Observable;\n\n // pricing related\n periodOptions: PeriodOptions[] = [\n { label: 'FRONTEND.STORE.PERIODS.ONE_TIME', value: RevenuePeriod.ONETIME },\n { label: 'FRONTEND.STORE.PERIODS.MONTHLY', value: RevenuePeriod.MONTHLY },\n { label: 'FRONTEND.STORE.PERIODS.YEARLY', value: RevenuePeriod.YEARLY },\n ];\n\n oneTimeOnlyOptions: PeriodOptions[] = [{ label: 'FRONTEND.STORE.PERIODS.ONE_TIME', value: RevenuePeriod.ONETIME }];\n\n private subscriptions: Subscription[] = [];\n\n constructor(\n private formbuilder: UntypedFormBuilder,\n private cdk: ChangeDetectorRef,\n private alertService: SnackbarService,\n private readonly salesOrdersService: SalesOrdersService,\n private readonly lineItemsService: LineItemsService,\n private readonly featureFlagService: FeatureFlagService,\n ) {}\n\n ngOnInit(): void {\n this.editing = !!this.editable && !!this.startEditing;\n this.lineItemsService.initialize(this.business, this.specifiedLineItems);\n\n this.uiLineItems$ = this.lineItemsService.uiLineItems$.pipe(shareReplay(1));\n this.loading$ = this.lineItemsService.loading$;\n const orderConfig$ = this.salesOrdersService\n .getConfig(this.business.externalIdentifiers.partnerId, this.business.externalIdentifiers.marketId)\n .pipe(shareReplay(1));\n const hideSellingStandaloneProducts$ = orderConfig$.pipe(\n map((config) => config.salespersonOptions?.disableSellingStandaloneProducts || false),\n );\n this.subscriptions.push(\n hideSellingStandaloneProducts$.subscribe((hideSellingStandaloneProducts) => {\n this.hideSellingStandaloneProducts = hideSellingStandaloneProducts;\n }),\n );\n this.formArrayLineItems = this.formbuilder.array([]);\n\n this.subscriptions.push(\n this.uiLineItems$\n .pipe(\n map((uiLineItems) => {\n this.setCurrentLineItems(uiLineItems);\n this.lineItemsInitialized.emit(uiLineItems);\n this.formArrayLineItems = this.buildFormArrayFromLineItems(uiLineItems);\n this.cdk.detectChanges();\n }),\n tap(() => this.lineItemsService.loading$$.next(false)),\n )\n .subscribe(),\n );\n this.orderFormOptions$ = orderConfig$.pipe(\n map((config): OrderFormOptionsInterface => {\n return {\n readOnly: false,\n showOfficeUseQuestions: true,\n bypassRequiredQuestions: !config.salespersonOptions.validateRequiredFields,\n };\n }),\n );\n\n this.featureFlags$ = this.featureFlagService\n .batchGetStatus(this.business.externalIdentifiers.partnerId, this.business.externalIdentifiers.marketId, [\n Features.PC_UNIFIED_ORDERS_PAGE,\n Features.UNIFIED_ORDERS_SCHEDULED_ACTIVATIONS,\n ])\n .pipe(shareReplay({ bufferSize: 1, refCount: true }));\n\n const unifiedOrderPageEnabled$ = this.featureFlags$.pipe(map((flags) => flags[Features.PC_UNIFIED_ORDERS_PAGE]));\n const canScheduleActivations$ = this.featureFlags$.pipe(\n map((flags) => flags[Features.UNIFIED_ORDERS_SCHEDULED_ACTIVATIONS]),\n );\n\n this.itemPricingTableConfig$ = combineLatest([\n hideSellingStandaloneProducts$,\n this.lineItemsService.contractStartDate$,\n unifiedOrderPageEnabled$,\n canScheduleActivations$,\n ]).pipe(\n map(([hideSellingStandaloneProducts, startDate, unifiedOrderPageEnabled, canScheduleActivations]) =>\n this.pricingTableConfig(\n hideSellingStandaloneProducts,\n startDate,\n unifiedOrderPageEnabled,\n canScheduleActivations,\n ),\n ),\n );\n }\n\n ngOnDestroy(): void {\n this.subscriptions.forEach((subscription) => subscription.unsubscribe());\n }\n\n deleteLineItem(index: number): void {\n if (!this.allowRemovingLastLineItem && this.currentLineItems.length === 1) {\n this.alertService.openErrorSnack('LIB_ORDERS.COMMON.ORDERS.ERROR_AT_LEAST_ONE_LINE_ITEM');\n return;\n }\n this.currentLineItems.splice(index, 1);\n this.setCurrentLineItems(this.currentLineItems);\n this.formArrayLineItems.removeAt(index);\n }\n\n duplicateLineItem(index: number): void {\n const duplicateLineItem = structuredClone(this.currentLineItems[index]);\n this.currentLineItems.splice(index + 1, 0, duplicateLineItem);\n this.setCurrentLineItems(this.currentLineItems);\n // update line items in the UI and form\n this.formArrayLineItems = this.buildFormArrayFromLineItems(this.currentLineItems);\n this.cdk.detectChanges();\n this.lineItemsUpdated.emit(this.currentLineItems);\n }\n\n // builds the form group out of the selected items\n buildFormArrayFromLineItems(lineItems: UILineItemWithEditionPricing[]): UntypedFormArray {\n const newForm = this.formbuilder.array(\n lineItems.map((li) => {\n const revenueControls = this.revenueControlFromLineItemRevenue(li.lineItem?.currentRevenue);\n const quantityEnabled = this.bypassQuantityRestrictions || (li.isMultiActivatable && !li.usesCustomPricing);\n return this.formbuilder.group({\n quantity: [\n {\n value: quantityEnabled ? li.lineItem.quantity : 1,\n disabled: !quantityEnabled,\n },\n [Validators.min(1)],\n ],\n revenueComponents: this.formbuilder.array(revenueControls),\n });\n }),\n );\n return newForm;\n }\n\n revenueControlFromLineItemRevenue(revenue: Revenue): UntypedFormGroup[] {\n return (revenue?.revenueComponents || []).map((rc) =>\n this.formbuilder.group({\n revenue: [(rc.value || 0) / 100, [Validators.required, this.validateRevenue.bind(this)]],\n period: [rc.period === undefined ? RevenuePeriod.ONETIME : rc.period, [Validators.required]],\n startingAt: [rc.isStartingRevenue],\n }),\n );\n }\n\n validateRevenue(c: UntypedFormControl): ValidationErrors | null {\n const revenueValue = c.value;\n if (isNaN(revenueValue)) {\n return { pattern: true };\n }\n if (Number(revenueValue) < 0 && this.allowNegativeValues === false) {\n return { min: true };\n }\n return null;\n }\n\n //\n // NOTE:\n // The following methods are used from page components with a ViewChild reference to this component\n //\n\n // check that the form of the component is in a valid state\n public isFormValid(): boolean {\n const valid = this.formArrayLineItems.valid;\n if (!valid) {\n this.formArrayLineItems.markAsTouched();\n this.formArrayLineItems.updateValueAndValidity();\n }\n if (this.currentLineItems.length <= 0) {\n this.alertService.openErrorSnack('FRONTEND.SALES_UI.ERRORS.ERROR_MUST_HAVE_ITEMS');\n return false;\n }\n if (this.currentLineItems.find((item) => item.lineItem?.currentRevenue?.revenueComponents.length === 0)) {\n this.alertService.openErrorSnack('FRONTEND.SALES_UI.ERRORS.ERROR_MUST_HAVE_PRICING');\n return false;\n }\n\n return valid;\n }\n\n // retrieve the line items currently stored in the component\n public getCurrentLineItems(): LineItem[] {\n return this.currentLineItems.map((li) => {\n const item = new LineItem(li.lineItem);\n if (this.currencyOverride) {\n item.currencyCode = this.currencyOverride;\n }\n return item;\n });\n }\n\n public currencySymbol(currencyCode: string): string {\n return getCurrencySymbol(currencyCode, 'wide');\n }\n\n private setCurrentLineItems(uiLineItems: UILineItemWithEditionPricing[]): void {\n // Sort the revenue components to ensure one time always appears last, initially\n uiLineItems?.forEach?.((uilineItem) => {\n uilineItem.lineItem?.currentRevenue?.revenueComponents?.sort((a: RevenueComponent, b: RevenueComponent) => {\n if (a.period === RevenuePeriod.ONETIME || !a.period) {\n return 1;\n }\n if (b.period === RevenuePeriod.ONETIME || !b.period) {\n return -1;\n }\n return 0;\n });\n });\n this.currentLineItems = uiLineItems;\n this.currentLineItems$$.next(uiLineItems);\n }\n\n updateLineItemsFromInventoryLineItems(changedEvent: ItemsChangedEvent): void {\n let updateFn: (currentLineItems: UILineItemWithEditionPricing[]) => UILineItemWithEditionPricing[];\n switch (changedEvent.type) {\n case 'add':\n updateFn = (currentLineItems) => {\n const newItems = changedEvent.items;\n const newLineItems = inventoryItemsToLineItems(newItems);\n newItems.forEach((newItem, i) => {\n currentLineItems.splice(newItem.index, 0, newLineItems[i]);\n });\n return currentLineItems;\n };\n break;\n case 'update':\n updateFn = (currentLineItems) => {\n const changedItems = changedEvent.items;\n const changedLineItems = inventoryItemsToLineItems(changedItems);\n changedItems.forEach((changedItem, i) => {\n const lineItem = changedLineItems[i];\n currentLineItems[changedItem.index] = lineItem;\n });\n return currentLineItems;\n };\n break;\n case 'overwrite':\n updateFn = () => {\n const newItems = changedEvent.items;\n const newLineItems = inventoryItemsToLineItems(newItems);\n return newLineItems;\n };\n break;\n case 'delete':\n changedEvent.items.forEach((item) => this.deleteLineItem(item.index));\n break;\n }\n if (updateFn) {\n this.setCurrentLineItems(updateFn(this.currentLineItems));\n }\n this.lineItemsUpdated.emit(this.currentLineItems);\n }\n\n private pricingTableConfig(\n hideSellingStandaloneProducts: boolean,\n billingTermsDefaultStartDate: Date,\n unifiedOrderPageEnabled: boolean,\n canScheduleActivations: boolean,\n ): ItemPricingTableConfig {\n let itemTypesAvailable: ItemSelectorFilterType[] = ['products', 'packages'];\n if (hideSellingStandaloneProducts) {\n itemTypesAvailable = itemTypesAvailable.filter((itemType) => itemType !== 'products');\n }\n\n return {\n partnerId: this.business.externalIdentifiers.partnerId,\n marketId: this.business.externalIdentifiers.marketId,\n businessId: this.business.accountGroupId,\n pageSize: 15,\n hideOwnerName: true,\n hideWholesale: true,\n hideRetail: false,\n hideQuantity: false,\n hideItemsWithOrderForms: false,\n itemTypesAvailable: itemTypesAvailable,\n enforceDependencies: true,\n quantityEditable: true,\n billingTermsConfig: {\n enabled: true,\n defaultStartDate: billingTermsDefaultStartDate,\n },\n hideColumns: ['wholesale_price'],\n hidePriceSummary: true,\n hideBorder: true,\n canAddItems: true,\n canEditInvoices: this.canEditInvoices && unifiedOrderPageEnabled,\n unifiedOrdersEnabled: unifiedOrderPageEnabled,\n canScheduleActivations: canScheduleActivations,\n };\n }\n}\n", "\n \n \n {{ 'LIB_ORDERS.COMMON.LINE_ITEMS.TITLE' | translate }}\n \n \n\n \n @if ((loading$ | async) === false) {\n \n } @else {\n \n }\n \n\n \n
\n
\n
\n
\n\n\n \n \n \n\n", "import { Component, OnInit, Output, EventEmitter, Input } from '@angular/core';\nimport { Currency } from '@galaxy/billing';\n\n@Component({\n selector: 'orders-currency-selector',\n templateUrl: './currency-selector.component.html',\n styleUrls: ['./currency-selector.component.scss'],\n standalone: false,\n})\nexport class CurrencySelectorComponent implements OnInit {\n private _selectedCurrency = '';\n\n allCurrencies: string[];\n\n get selectedCurrency(): string {\n return this._selectedCurrency;\n }\n\n set selectedCurrency(curr: string) {\n this._selectedCurrency = curr;\n this.currencySelected.emit(curr);\n }\n\n @Output() currencySelected = new EventEmitter();\n @Input() customCurrencies: string[] = [];\n @Input() initialCurrency = '';\n\n ngOnInit() {\n if (this.customCurrencies.length > 0) {\n this.allCurrencies = this.customCurrencies;\n } else {\n this.allCurrencies = Object.keys(Currency).map((key) => Currency[key]);\n }\n if (this.initialCurrency) {\n this._selectedCurrency = this.initialCurrency;\n }\n }\n}\n", "\n \n \n {{ 'LIB_ORDERS.COMMON.ORDER_CURRENCY.TITLE' | translate }}\n \n {{ 'LIB_ORDERS.COMMON.ORDER_CURRENCY.VMF_ONLY_BADGE' | translate }}\n \n \n \n {{ 'LIB_ORDERS.COMMON.ORDER_CURRENCY.DESCRIPTION' | translate }}\n \n \n\n \n \n \n \n {{ 'LIB_ORDERS.COMMON.ORDER_CURRENCY.DEFAULT_OPTION' | translate }}\n \n \n {{ currency }}\n \n \n \n \n\n", "import { Component, EventEmitter, Input, Output } from '@angular/core';\nimport { AppPrice } from '@vendasta/marketplace-apps';\n\nexport interface VariablePriceInputs {\n appPrice: AppPrice;\n name: string;\n iconUrl?: string;\n packageID?: string;\n packageName?: string;\n packageIconUrl?: string;\n customPrice?: number;\n}\n\nexport type AppVariablePriceMap = Map;\nexport type PackageVariablePriceMap = Map;\n\n@Component({\n selector: 'orders-variable-prices',\n templateUrl: './variable-prices.component.html',\n styleUrls: ['./variable-prices.component.scss'],\n standalone: false,\n})\nexport class VariablePricesComponent {\n @Input() variablePricesMap: AppVariablePriceMap;\n @Input() variablePackagePricesMap: PackageVariablePriceMap;\n @Input() partnerCurrency: string;\n\n @Output() updateData: EventEmitter<{ appId: string; customPrice: number; packageId?: string }> = new EventEmitter();\n\n changeSpendAmount(customPrice, appId, packageId?): void {\n this.updateData.emit({ appId: appId, customPrice: customPrice, packageId: packageId });\n }\n}\n", "\n \n \n \n \n \n {{ product.value?.name }}\n \n \n

Enter the amount you'll spend each billing period.

\n \n \n \n
\n
\n
\n
\n\n\n\n \n \n \n \n \n \n {{ product.value?.packageName }}\n {{ product.value?.name }}\n \n \n

Enter the amount you'll spend each billing period.

\n \n \n \n
\n
\n
\n
\n
\n\n", "import { Pipe, PipeTransform } from '@angular/core';\nimport { AccountGroupLocation } from '@vendasta/account-group';\n\ntype Address = Pick;\n\n// formatAddress formats address, city, state, zip, and country into a comma separated string\n@Pipe({\n name: 'formatAddress',\n standalone: false,\n})\nexport class FormatAddressPipe implements PipeTransform {\n transform(address: Address): string {\n return [\n address.address?.trim(),\n address.city?.trim(),\n address.state?.trim(),\n address.zip?.trim(),\n address.country?.trim(),\n ]\n .filter((v) => !!v)\n .join(', ');\n }\n}\n", "import { Component, Inject } from '@angular/core';\nimport { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog';\nimport { AccountGroup } from '@vendasta/account-group';\nimport { Order } from '@vendasta/sales-orders';\nimport { format } from 'date-fns';\n\nexport interface CreateOrderDialogData {\n business: AccountGroup;\n order: Order;\n}\n\nexport type CreateOrderDialogOutput = 'new-order' | 'latest-order' | 'cancel';\n\n@Component({\n selector: 'orders-create-order-dialog',\n templateUrl: './create-order-dialog.component.html',\n styleUrls: ['./create-order-dialog.component.scss'],\n standalone: false,\n})\nexport class CreateOrderDialogComponent {\n data: CreateOrderDialogData;\n contractStartDate = this.formatContractDate(new Date());\n\n constructor(\n private readonly dialogRef: MatDialogRef,\n @Inject(MAT_DIALOG_DATA) data: CreateOrderDialogData,\n ) {\n this.data = data;\n if (data?.order?.requestedActivation) {\n this.contractStartDate = this.formatContractDate(data.order.requestedActivation);\n }\n }\n\n createNewOrder(): void {\n this.dialogRef.close('new-order');\n }\n\n goToDraftOrder(): void {\n this.dialogRef.close('latest-order');\n }\n\n close(): void {\n this.dialogRef.close('cancel');\n }\n\n private formatContractDate(date: Date): string {\n return format(date, 'MMMM d, yyyy');\n }\n}\n", "

{{ 'LIB_ORDERS.SALES_ORDERS.CREATE_ORDER_SHORT' | translate }}

\n\n
\n {{ data.business?.napData?.companyName }}\n
\n
\n {{ data.business?.napData | formatAddress }}\n
\n\n
\n
\n {{ 'LIB_ORDERS.SALES_ORDERS.CREATE_ORDER_DIALOG.CREATE_NEW_ORDER' | translate }}\n
\n
\n\n @if (data.order; as order) {\n
\n
\n {{ 'LIB_ORDERS.SALES_ORDERS.CREATE_ORDER_DIALOG.OPEN_LAST_DRAFT' | translate }}\n
\n
\n
{{ order.orderId }}
\n
\n {{\n 'LIB_ORDERS.SALES_ORDERS.CREATE_ORDER_DIALOG.CONTRACT_START_DATE' | translate: { date: contractStartDate }\n }}\n
\n
\n
\n }\n
\n\n \n\n", "import { Injectable } from '@angular/core';\nimport { MatDialog } from '@angular/material/dialog';\nimport { Router } from '@angular/router';\nimport { AccountGroup, AccountGroupService, ProjectionFilter } from '@galaxy/account-group';\nimport { FeatureFlagService } from '@galaxy/partner';\nimport { Salesperson, SalespersonService } from '@vendasta/sales';\nimport {\n DurationPeriod,\n ListSalesOrderRequestFilters,\n ListSalesOrderRequestSortDirection,\n ListSalesOrderRequestSortField,\n ListSalesOrderRequestSortOption,\n Order,\n SalesOrdersService,\n Status,\n} from '@vendasta/sales-orders';\nimport { combineLatest, Observable, of, retry } from 'rxjs';\nimport { catchError, map, shareReplay, switchMap, take, withLatestFrom } from 'rxjs/operators';\nimport { Features } from '../../../core/features';\nimport { CreateOrderDialogComponent, CreateOrderDialogOutput } from './create-order-dialog.component';\n\nexport type CreateRedirectURLFn = (businessId: string, orderId: string) => string;\n\nconst defaultRedirectURLFn = (businessId: string) => {\n return `/order-management/${businessId}/sales-order/create`;\n};\n\n@Injectable()\nexport class CreateOrderDialogService {\n constructor(\n private router: Router,\n private dialog: MatDialog,\n private featureFlagService: FeatureFlagService,\n private accountGroupService: AccountGroupService,\n private salespersonService: SalespersonService,\n private salesOrdersService: SalesOrdersService,\n ) {}\n\n createOrderAndRedirect(\n partnerId: string,\n marketId: string,\n businessId: string,\n userId: string,\n createRedirectURLFn: CreateRedirectURLFn,\n skipDraftPrompt = false,\n ): Observable {\n const isFeatureEnabled$ = this.checkFeatureFlag(partnerId, marketId).pipe(shareReplay(1));\n const createOrderResp$ = this.onCreateOrderDialog(partnerId, marketId, businessId, userId, skipDraftPrompt).pipe(\n shareReplay(1),\n );\n return isFeatureEnabled$.pipe(\n switchMap((isFeatureEnabled) => {\n if (!isFeatureEnabled) {\n return of(null);\n }\n\n return createOrderResp$;\n }),\n withLatestFrom(isFeatureEnabled$),\n map(([orderId, isFeatureEnabled]) => {\n // If the feature is enabled and there is no order ID, the user canceled the action.\n if (isFeatureEnabled && !orderId) {\n return null;\n }\n\n let redirectURL = defaultRedirectURLFn(businessId);\n if (isFeatureEnabled) {\n redirectURL = createRedirectURLFn(businessId, orderId);\n }\n this.router.navigateByUrl(redirectURL);\n return orderId;\n }),\n take(1),\n );\n }\n\n private checkFeatureFlag(partnerId: string, marketId: string): Observable {\n return this.featureFlagService\n .batchGetStatus(partnerId, marketId, [Features.PC_UNIFIED_ORDERS_PAGE])\n .pipe(map((flags) => flags && flags[Features.PC_UNIFIED_ORDERS_PAGE]));\n }\n\n private fetchBusiness(businessId: string): Observable {\n return this.accountGroupService.get(\n businessId,\n new ProjectionFilter({\n napData: true,\n }),\n );\n }\n\n private fetchSalesperson(partnerId: string, userId: string): Observable {\n return this.salespersonService.getSalespersonByUserId(partnerId, userId).pipe(\n catchError((err) => {\n // Not all users have a salesperson, and we only want the salesperson to assign to the order.\n if (err.status === 404) {\n return of(new Salesperson());\n }\n throw err;\n }),\n );\n }\n\n private fetchLatestOrder(partnerId: string, businessId: string): Observable {\n const filters = new ListSalesOrderRequestFilters({\n businessId,\n statuses: [Status.DRAFTED],\n });\n const sort = new ListSalesOrderRequestSortOption({\n field: ListSalesOrderRequestSortField.CREATED,\n direction: ListSalesOrderRequestSortDirection.DESCENDING,\n });\n return this.salesOrdersService.list(partnerId, filters, sort, '', 1).pipe(\n map((resp) => {\n if (!resp?.results?.length) {\n return null;\n }\n return resp.results[0];\n }),\n );\n }\n\n private createDraftOrder(\n partnerId: string,\n marketId: string,\n businessId: string,\n userId: string,\n ): Observable {\n return this.fetchSalesperson(partnerId, userId).pipe(\n map((salesperson) => salesperson?.salespersonId ?? ''),\n switchMap((salespersonId) => {\n return this.salesOrdersService\n .createDraft({\n partnerId,\n marketId,\n businessId,\n salespersonId,\n requestedActivation: new Date(),\n contractDuration: {\n value: 1,\n duration: DurationPeriod.YEAR,\n },\n })\n .pipe(retry({ delay: 1000, count: 5 }));\n }),\n );\n }\n\n private onCreateOrderDialog(\n partnerId: string,\n marketId: string,\n businessId: string,\n userId: string,\n skipDraftPrompt = false,\n ): Observable {\n const business$ = this.fetchBusiness(businessId).pipe(shareReplay(1));\n const lastestOrder$ = this.fetchLatestOrder(partnerId, businessId).pipe(shareReplay(1));\n const latestOrderId$ = lastestOrder$.pipe(map((latestOrder) => latestOrder?.orderId));\n const draftOrderId$ = this.createDraftOrder(partnerId, marketId, businessId, userId).pipe(shareReplay(1));\n if (skipDraftPrompt) {\n return draftOrderId$;\n }\n\n const userResp$: Observable = combineLatest([business$, lastestOrder$]).pipe(\n switchMap(([business, latestOrder]) => {\n // If there are no draft orders, automatically create one.\n if (!latestOrder) {\n return of('new-order');\n }\n return this.dialog\n .open(CreateOrderDialogComponent, {\n width: '500px',\n autoFocus: false,\n data: {\n business: business,\n order: latestOrder,\n },\n })\n .afterClosed();\n }),\n );\n return userResp$.pipe(\n switchMap((resp) => {\n switch (resp) {\n case 'new-order':\n return draftOrderId$;\n case 'latest-order':\n return latestOrderId$;\n default:\n return of(null);\n }\n }),\n map((chosenOrderId) => {\n return chosenOrderId ?? null;\n }),\n );\n }\n}\n", "import {\n ConfigInterface,\n SalespersonOptionsInterface,\n Status,\n WorkflowStepOptionsInterface,\n} from '@vendasta/sales-orders';\nimport { RetailProvider, RetailStatus } from '@galaxy/billing';\n\ntype GetSubmitForCustomerApprovalPermissionsParams = {\n orderStatus: Status;\n workflowStepOptions: WorkflowStepOptionsInterface;\n isAdmin: boolean;\n};\n\nexport function GetSubmitForCustomerApprovalPermissions({\n orderStatus,\n workflowStepOptions,\n isAdmin,\n}: GetSubmitForCustomerApprovalPermissionsParams): boolean {\n return (\n [\n Status.DRAFTED,\n Status.SUBMITTED,\n Status.RESUBMITTED,\n Status.APPROVED,\n Status.SCHEDULED_ACTIVATION,\n Status.SUBMITTED_FOR_CUSTOMER_APPROVAL,\n ].includes(orderStatus) &&\n (isAdmin || workflowStepOptions.allowSendToCustomer)\n );\n}\n\ntype GetIgnoreAllErrorsOnOrderPermissionsParams = {\n orderStatus: Status;\n};\n\nexport function GetIgnoreAllErrorsOnOrderPermissions({\n orderStatus,\n}: GetIgnoreAllErrorsOnOrderPermissionsParams): boolean {\n return orderStatus === Status.ACTIVATION_ERRORS;\n}\n\ntype GetCreateInvoiceFromOrderPermissionsParams = {\n userCanAccessRetailBilling: boolean;\n};\n\nexport function GetCreateInvoiceFromOrderPermissions({\n userCanAccessRetailBilling,\n}: GetCreateInvoiceFromOrderPermissionsParams): boolean {\n return userCanAccessRetailBilling;\n}\n\ntype GetCanSubmitOrderParams = {\n isAdmin: boolean;\n salespersonConfig: SalespersonOptionsInterface;\n};\n\nexport function GetCanSubmitOrderWithoutRequiredFields({\n isAdmin,\n salespersonConfig,\n}: GetCanSubmitOrderParams): boolean {\n return isAdmin || !salespersonConfig.validateRequiredFields;\n}\n\ntype GetArchiveOrderPermissionsParams = {\n isAdmin: boolean;\n orderStatus: Status;\n};\n\nexport function GetArchiveOrderPermissions({ isAdmin, orderStatus }: GetArchiveOrderPermissionsParams): boolean {\n if (isAdmin) {\n return orderStatus !== Status.ARCHIVED;\n }\n return orderStatus === Status.DRAFTED;\n}\n\ntype GetSubscribeToUpdatesPermissionsParams = {\n orderStatus: Status;\n};\n\nexport function GetSubscribeToUpdatesPermissions({ orderStatus }: GetSubscribeToUpdatesPermissionsParams): boolean {\n return orderStatus !== Status.ARCHIVED;\n}\n\ntype GetDuplicateOrderPermissionsParams = {\n orderStatus: Status;\n};\n\nexport function GetDuplicateOrderPermissions({ orderStatus }: GetDuplicateOrderPermissionsParams): boolean {\n return orderStatus !== Status.DRAFTED;\n}\n\ntype GetRequestToCancelOrderPermissionsParams = {\n isAdmin: boolean;\n orderStatus: Status;\n};\n\ntype GetCancelOrderPermissionsParams = {\n isAdmin: boolean;\n orderStatus: Status;\n};\n\nexport function GetCancelOrderPermissions({ isAdmin, orderStatus }: GetCancelOrderPermissionsParams): boolean {\n const visibleStatuses = [\n Status.SUBMITTED_FOR_CUSTOMER_APPROVAL,\n Status.SCHEDULED_ACTIVATION,\n Status.APPROVED,\n Status.FULFILLED,\n Status.ACTIVATION_ERRORS,\n ];\n return isAdmin && visibleStatuses.includes(orderStatus);\n}\n\nexport function GetRequestToCancelOrderPermissions({\n isAdmin,\n orderStatus,\n}: GetRequestToCancelOrderPermissionsParams): boolean {\n const visibleStatuses = [\n Status.SUBMITTED,\n Status.RESUBMITTED,\n Status.SUBMITTED_FOR_CUSTOMER_APPROVAL,\n Status.SCHEDULED_ACTIVATION,\n Status.APPROVED,\n Status.FULFILLED,\n Status.ACTIVATION_ERRORS,\n ];\n return !isAdmin && visibleStatuses.includes(orderStatus);\n}\n\nexport function CanReviewCancellationRequest({ isAdmin, orderStatus }: { isAdmin: boolean; orderStatus: Status }) {\n return isAdmin && orderStatus === Status.CANCELLATION_REQUESTED;\n}\n\ntype GetActivateOrderPermissionsParams = {\n isAdmin: boolean;\n orderStatus: Status;\n};\n\nexport function GetActivateOrderPermissions({ isAdmin, orderStatus }: GetActivateOrderPermissionsParams): boolean {\n const activatableStatuses = [Status.APPROVED, Status.SCHEDULED_ACTIVATION];\n return isAdmin && activatableStatuses.includes(orderStatus);\n}\n\ntype GetScheduleActivationPermissionsParams = {\n isAdmin: boolean;\n orderStatus: Status;\n};\n\nexport function GetScheduleActivationPermissions({\n isAdmin,\n orderStatus,\n}: GetScheduleActivationPermissionsParams): boolean {\n return isAdmin && orderStatus === Status.APPROVED;\n}\n\nexport function CanReviewSubmittedOrder({ isAdmin, orderStatus }: { isAdmin: boolean; orderStatus: Status }) {\n if (!isAdmin) return false;\n const statuses = [Status.SUBMITTED, Status.RESUBMITTED];\n return statuses.includes(orderStatus);\n}\n\nexport function CanUpdateInvalidContractStartDatePermissions(orderStatus: Status): boolean {\n const statuses: Status[] = [\n Status.DRAFTED,\n Status.SUBMITTED,\n Status.RESUBMITTED,\n Status.APPROVED,\n Status.SCHEDULED_ACTIVATION,\n Status.SUBMITTED_FOR_CUSTOMER_APPROVAL,\n ];\n return statuses.includes(orderStatus);\n}\n\ntype GetCanChargeSMBOnOrderSubmissionPermissionsParams = {\n isAdmin: boolean;\n canAccessRetailBilling: boolean;\n orderConfig: ConfigInterface;\n retailProvider: RetailProvider;\n retailStatus: RetailStatus;\n};\n\nexport function GetCanChargeSMBOnOrderSubmissionPermissions({\n isAdmin,\n canAccessRetailBilling,\n orderConfig,\n retailProvider,\n retailStatus,\n}: GetCanChargeSMBOnOrderSubmissionPermissionsParams): boolean {\n return (\n ((isAdmin && canAccessRetailBilling) || orderConfig?.workflowStepOptions?.canChargeSmbOnOrderSubmission) &&\n !!retailProvider &&\n retailStatus?.canAcceptPayment\n );\n}\n", "import { SalespersonOptionsInterface, Status, WorkflowStepOptionsInterface } from '@vendasta/sales-orders';\nimport { FeaturePermissions } from './permissions';\n\ntype GetOrderFormPermissionsParams = {\n orderStatus: Status;\n workflowStepOptions: WorkflowStepOptionsInterface;\n isAdmin: boolean;\n};\n\nexport function GetOrderFormPermissions({\n orderStatus,\n workflowStepOptions,\n isAdmin,\n}: GetOrderFormPermissionsParams): FeaturePermissions {\n const canEditUntilActivation = workflowStepOptions.allowOrderFormEditing || isAdmin;\n\n const editableStatuses = [Status.DRAFTED];\n if (canEditUntilActivation) {\n editableStatuses.push(Status.SUBMITTED, Status.RESUBMITTED, Status.APPROVED);\n }\n const canEdit = editableStatuses.includes(orderStatus);\n\n return { view: true, edit: canEdit };\n}\n\ntype GetTagPermissionsParams = {\n salespersonOptions: SalespersonOptionsInterface;\n isAdmin: boolean;\n};\n\nexport function GetTagPermissions({ salespersonOptions, isAdmin }: GetTagPermissionsParams): FeaturePermissions {\n const canEdit = isAdmin || !salespersonOptions.disableTagging;\n\n return { view: true, edit: canEdit };\n}\n\ntype GetEditOrderContentsPermissionsParams = {\n orderStatus: Status;\n isAdmin: boolean;\n};\n\nexport function GetEditOrderContentsPermissions({\n orderStatus,\n isAdmin,\n}: GetEditOrderContentsPermissionsParams): FeaturePermissions {\n const adminEditableStatuses = [Status.SUBMITTED, Status.RESUBMITTED, Status.APPROVED, Status.SCHEDULED_ACTIVATION];\n const adminCanEdit = isAdmin && adminEditableStatuses.includes(orderStatus);\n\n const canEdit = adminCanEdit || orderStatus === Status.DRAFTED;\n\n return { view: true, edit: canEdit };\n}\n\ntype GetInvoicePermissionsParams = {\n isAdmin: boolean;\n autoGenerateRetailSubscriptions: boolean;\n};\n\nexport function GetInvoicePermissions({\n isAdmin,\n autoGenerateRetailSubscriptions,\n}: GetInvoicePermissionsParams): FeaturePermissions {\n const canViewAndEdit = isAdmin && autoGenerateRetailSubscriptions;\n return { view: canViewAndEdit, edit: canViewAndEdit };\n}\n\nexport function GetFulfillmentStatusesPermissions(orderStatus: Status): FeaturePermissions {\n const postSubmissionStatuses: Status[] = [\n Status.FULFILLED,\n Status.PROCESSING,\n Status.ACTIVATION_ERRORS,\n Status.ARCHIVED,\n Status.CANCELLATION_REQUESTED,\n Status.CANCELLED,\n Status.AWAITING_PAYMENT,\n Status.PROCESSING_PAYMENT,\n ];\n const canViewAndEdit = postSubmissionStatuses.includes(orderStatus);\n return { view: canViewAndEdit, edit: canViewAndEdit };\n}\n\nexport function GetCreateOrderDetailsPermissions(orderStatus: Status, isAdmin: boolean): FeaturePermissions {\n const isDraft = orderStatus === Status.DRAFTED;\n const canEditOrderContents = GetEditOrderContentsPermissions({ orderStatus: orderStatus, isAdmin: isAdmin }).edit;\n return { view: isDraft, edit: isDraft && canEditOrderContents };\n}\n\nexport function GetCondensedOrderDetailsPermissions(orderStatus: Status): FeaturePermissions {\n const canViewAndEdit = orderStatus !== Status.DRAFTED;\n return { view: canViewAndEdit, edit: canViewAndEdit };\n}\n\nexport function GetActiveItemsPermissions(orderStatus: Status): FeaturePermissions {\n const canViewAndEdit = orderStatus === Status.DRAFTED;\n return { view: canViewAndEdit, edit: canViewAndEdit };\n}\n\nexport function GetBusinessHeaderPermissions(orderStatus: Status): FeaturePermissions {\n const canViewAndEdit = orderStatus !== Status.DRAFTED;\n return { view: canViewAndEdit, edit: canViewAndEdit };\n}\n\ntype GetEditOrderContractStartAndDurationPermissionsParams = {\n orderStatus: Status;\n isAdmin: boolean;\n};\n\nexport function GetEditOrderContractStartAndDurationPermissions({\n orderStatus,\n isAdmin,\n}: GetEditOrderContractStartAndDurationPermissionsParams): FeaturePermissions {\n const adminEditableStatuses = [Status.SUBMITTED, Status.RESUBMITTED, Status.APPROVED];\n const adminCanEdit = isAdmin && adminEditableStatuses.includes(orderStatus);\n\n const canEdit = adminCanEdit || orderStatus === Status.DRAFTED;\n\n return { view: true, edit: canEdit };\n}\n\nexport function GetInvalidLineItemsBannerPermissions(orderStatus: Status): FeaturePermissions {\n const showBannerStatuses: Status[] = [\n Status.DRAFTED,\n Status.SUBMITTED,\n Status.RESUBMITTED,\n Status.APPROVED,\n Status.SCHEDULED_ACTIVATION,\n ];\n const canViewAndEdit = showBannerStatuses.includes(orderStatus);\n return { view: canViewAndEdit, edit: canViewAndEdit };\n}\n", "import { Observable } from 'rxjs';\n\nexport type FeaturePermissions = {\n view: boolean;\n edit: boolean;\n};\n\nexport enum OrderFeature {\n OrderForms = 'orderForms',\n Tags = 'tags',\n OrderContents = 'orderContents',\n Invoices = 'invoices',\n FulfillmentStatuses = 'fulfillmentStatuses',\n CreateOrderDetails = 'createOrderDetails',\n CondensedOrderDetails = 'condensedOrderDetails',\n ActiveItems = 'activeItems',\n BusinessHeader = 'businessHeader',\n ContractStartAndDuration = 'contractStartAndDuration',\n InvalidLineItemsBanner = 'invalidLineItemsBanner',\n}\n\nexport type OrderFeaturePermissions = {\n [key in OrderFeature]: Observable;\n};\n\nexport enum OrderAction {\n SubmitWithoutRequiredFields = 'submitWithoutRequiredFields',\n IgnoreAllErrorsOnOrder = 'ignoreAllErrorsOnOrder',\n CreateInvoiceFromOrder = 'createInvoiceFromOrder',\n SubmitForCustomerApproval = 'submitForCustomerApproval',\n DuplicateOrder = 'duplicateOrder',\n SubscribeToUpdates = 'subscribeToUpdates',\n\n CancelOrder = 'cancelOrder',\n RequestToCancelOrder = 'requestToCancelOrder',\n ReviewCancellationRequest = 'reviewCancellationRequest',\n ArchiveOrder = 'archiveOrder',\n ActivateOrder = 'activateOrder',\n ScheduleActivation = 'scheduleActivation',\n ReviewSubmittedOrder = 'reviewSubmittedOrder',\n SelectOrderCurrency = 'selectOrderCurrency',\n AccessOnlySalespersonAutomations = 'accessOnlySalespersonAutomations',\n UpdateInvalidContractStartDate = 'updateInvalidContractStartDate',\n RedirectToCompanyProfileAfterSubmitting = 'redirectToCompanyProfileAfterSubmitting',\n CollectPaymentFromCustomer = 'collectPaymentFromCustomer',\n ChargeSMBOnOrderSubmission = 'chargeSMBOnOrderSubmission',\n}\n\nexport type OrderActionPermissions = {\n [key in OrderAction]: Observable;\n};\n", "import { Inject, Injectable } from '@angular/core';\nimport {\n ConfigInterface,\n Order,\n SalespersonOptionsInterface,\n Status,\n WorkflowStepOptionsInterface,\n} from '@vendasta/sales-orders';\nimport { combineLatest, Observable, of } from 'rxjs';\nimport { catchError, defaultIfEmpty, map, shareReplay, switchMap, take } from 'rxjs/operators';\nimport { OrderStoreService } from '../orders.service';\nimport { PAGE_ORDER_CONFIG_TOKEN, PageOrderConfig } from '../tokens';\nimport {\n CanReviewCancellationRequest,\n CanReviewSubmittedOrder,\n GetActivateOrderPermissions,\n GetArchiveOrderPermissions,\n GetCancelOrderPermissions,\n GetCanSubmitOrderWithoutRequiredFields,\n GetCreateInvoiceFromOrderPermissions,\n GetDuplicateOrderPermissions,\n GetIgnoreAllErrorsOnOrderPermissions,\n GetRequestToCancelOrderPermissions,\n GetScheduleActivationPermissions,\n GetSubmitForCustomerApprovalPermissions,\n GetSubscribeToUpdatesPermissions,\n CanUpdateInvalidContractStartDatePermissions,\n GetCanChargeSMBOnOrderSubmissionPermissions,\n} from './actions';\nimport {\n GetActiveItemsPermissions,\n GetBusinessHeaderPermissions,\n GetCondensedOrderDetailsPermissions,\n GetCreateOrderDetailsPermissions,\n GetEditOrderContentsPermissions,\n GetEditOrderContractStartAndDurationPermissions,\n GetFulfillmentStatusesPermissions,\n GetInvalidLineItemsBannerPermissions,\n GetInvoicePermissions,\n GetOrderFormPermissions,\n GetTagPermissions,\n} from './features';\nimport { OrderAction, OrderActionPermissions, OrderFeature, OrderFeaturePermissions } from './permissions';\nimport { RetailCustomerConfigurationService, RetailStatus } from '@galaxy/billing';\nimport { FeatureFlagService } from '@galaxy/partner';\nimport { Features } from '../features';\nimport { PaymentService, RetailProvider } from '@galaxy/billing';\n\ninterface OrderPermissionsServiceInterface {\n CanView(feature: OrderFeature): Observable;\n\n CanEdit(feature: OrderFeature): Observable;\n\n CanDoAction(action: OrderAction): Observable;\n}\n\n@Injectable()\nexport class OrderPermissionsService implements OrderPermissionsServiceInterface {\n isAdmin$: Observable;\n\n constructor(\n @Inject(PAGE_ORDER_CONFIG_TOKEN) public readonly pageOrderConfig: PageOrderConfig,\n @Inject(OrderStoreService) readonly orderStoreService: OrderStoreService,\n private readonly retailCustomerConfigurationService: RetailCustomerConfigurationService,\n private readonly featureFlagService: FeatureFlagService,\n private readonly paymentService: PaymentService,\n ) {\n const order$ = this.orderStoreService.order$;\n const orderStatus$ = order$.pipe(map((order) => order.status));\n const orderConfig$ = this.orderStoreService.orderConfig$;\n const workflowStepOptions$ = orderConfig$.pipe(map((config) => config.workflowStepOptions));\n const salespersonConfig$ = orderConfig$.pipe(map((config) => config.salespersonOptions));\n const retailProvider$ = order$.pipe(\n switchMap((order) => this.paymentService.getRetailProvider(order.partnerId)),\n catchError(() => of(null)),\n );\n const retailStatus$: Observable = order$.pipe(\n switchMap((order) => this.paymentService.retailStatus(order.partnerId)),\n catchError(() => {\n return of(null);\n }),\n );\n // view is optional so we default to false\n this.isAdmin$ = (this.pageOrderConfig.view$ || of(null)).pipe(\n map((view) => view === 'admin-view'),\n defaultIfEmpty(false),\n shareReplay(1),\n );\n\n const autoGenerateRetailSubscriptions$ = order$.pipe(\n switchMap((order) =>\n this.retailCustomerConfigurationService.get(order.partnerId, order.businessId).pipe(\n take(1),\n map((retailConfig) => retailConfig?.autoGenerateRetailSubscriptions ?? false),\n catchError(() => of(false)),\n ),\n ),\n );\n\n const featureFlags$ = order$.pipe(\n switchMap((order) =>\n this.featureFlagService.batchGetStatus(order.partnerId, order.marketId, [\n Features.CREATE_ORDERS_CONTRACT_TERMS,\n Features.PC_UNIFIED_ORDERS_PAGE,\n ]),\n ),\n );\n\n const hasFeatureAccessToUnifiedOrders$ = featureFlags$.pipe(map((res) => res[Features.PC_UNIFIED_ORDERS_PAGE]));\n\n // features\n this[OrderFeature.OrderForms] = this.setupOrderFormPermissions(orderStatus$, orderConfig$, this.isAdmin$);\n this[OrderFeature.Tags] = this.setupTagPermissions(salespersonConfig$, this.isAdmin$);\n this[OrderFeature.OrderContents] = this.setupEditOrderContentsPermissions(orderStatus$, this.isAdmin$);\n this[OrderFeature.Invoices] = this.setupIncludeInInvoicesTogglePermissions(\n this.isAdmin$,\n autoGenerateRetailSubscriptions$,\n );\n this[OrderFeature.FulfillmentStatuses] = this.setupFulfillmentStatusesPermissions(orderStatus$);\n this[OrderFeature.CreateOrderDetails] = this.setupCreateOrderDetailsPermissions(orderStatus$, this.isAdmin$);\n this[OrderFeature.CondensedOrderDetails] = this.setupCondensedOrderDetailsPermissions(orderStatus$);\n this[OrderFeature.ActiveItems] = this.setupActiveItemsPermissions(orderStatus$);\n this[OrderFeature.BusinessHeader] = this.setupBusinessHeaderPermissions(orderStatus$);\n this[OrderFeature.ContractStartAndDuration] = this.setupEditContractStartAndDurationPermissions(\n orderStatus$,\n this.isAdmin$,\n );\n this[OrderFeature.InvalidLineItemsBanner] = this.setupInvalidLineItemsBannerPermissions(orderStatus$);\n\n // actions\n this[OrderAction.SubmitWithoutRequiredFields] = this.setupCanSubmitOrderWithoutRequiredFields(\n this.isAdmin$,\n salespersonConfig$,\n );\n this[OrderAction.IgnoreAllErrorsOnOrder] = this.setupIgnoreAllErrorsOnOrderPermissions(orderStatus$);\n this[OrderAction.CreateInvoiceFromOrder] = this.setupCreateInvoiceFromOrderPermissions(\n this.pageOrderConfig.userCanAccessRetailBilling$ ?? of(false),\n );\n this[OrderAction.SubmitForCustomerApproval] = this.setupSubmitForCustomerApprovalPermissions(\n orderStatus$,\n workflowStepOptions$,\n this.isAdmin$,\n );\n this[OrderAction.DuplicateOrder] = this.setupDuplicateOrderPermissions(orderStatus$);\n this[OrderAction.SubscribeToUpdates] = this.setupSubscribeToUpdatesPermissions(orderStatus$);\n this[OrderAction.CancelOrder] = this.setupCancelOrderPermissions(this.isAdmin$, orderStatus$);\n this[OrderAction.RequestToCancelOrder] = this.setupRequestToCancelOrderPermissions(this.isAdmin$, orderStatus$);\n this[OrderAction.ReviewCancellationRequest] = this.setupCanReviewCancellationRequest(this.isAdmin$, orderStatus$);\n this[OrderAction.ArchiveOrder] = this.setupArchiveOrderPermissions(this.isAdmin$, orderStatus$);\n this[OrderAction.CancelOrder] = this.setupCancelOrderPermissions(this.isAdmin$, orderStatus$);\n this[OrderAction.ActivateOrder] = this.setupActivateOrderPermissions(this.isAdmin$, orderStatus$);\n this[OrderAction.ScheduleActivation] = this.setupScheduleActivationPermissions(this.isAdmin$, orderStatus$);\n this[OrderAction.ReviewSubmittedOrder] = this.setupCanReviewSubmittedOrder(this.isAdmin$, orderStatus$);\n this[OrderAction.SelectOrderCurrency] = this.setupCanSelectOrderCurrency(order$);\n this[OrderAction.AccessOnlySalespersonAutomations] = this.setupCanAccessOnlySalespersonAutomations(this.isAdmin$);\n this[OrderAction.UpdateInvalidContractStartDate] =\n this.setupUpdateInvalidContractStartDatePermissions(orderStatus$);\n this[OrderAction.RedirectToCompanyProfileAfterSubmitting] =\n this.setupCanRedirectToCompanyProfileAfterSubmittingOrder(this.isAdmin$);\n this[OrderAction.CollectPaymentFromCustomer] = this.setupCanCollectPaymentFromCustomerPermissions(\n this.isAdmin$,\n retailProvider$,\n hasFeatureAccessToUnifiedOrders$,\n retailStatus$,\n orderConfig$,\n );\n this[OrderAction.ChargeSMBOnOrderSubmission] = this.setupCanChargeSMBOnOrderSubmission(\n this.isAdmin$,\n this.pageOrderConfig.userCanAccessRetailBilling$ ?? of(false),\n orderConfig$,\n retailProvider$,\n retailStatus$,\n );\n }\n\n public CanView(feature: OrderFeature): Observable {\n return this.getFeaturePermissions(feature).pipe(map((permissions) => permissions.view));\n }\n\n public CanEdit(feature: OrderFeature): Observable {\n return this.getFeaturePermissions(feature).pipe(map((permissions) => permissions.edit));\n }\n\n public CanDoAction(action: OrderAction): Observable {\n return this.getActionPermission(action);\n }\n\n private getFeaturePermissions(feature: OrderFeature): OrderFeaturePermissions[OrderFeature] {\n const permissions = this[feature];\n if (!permissions) {\n throw new Error(`Invalid feature: ${feature}`);\n }\n return permissions;\n }\n\n private getActionPermission(action: OrderAction): Observable {\n const permission = this[action];\n if (!permission) {\n throw new Error(`Invalid action: ${action}`);\n }\n return this[action];\n }\n\n private setupOrderFormPermissions(\n orderStatus$: Observable,\n orderConfig$: Observable,\n isAdmin$: Observable,\n ): OrderFeaturePermissions[OrderFeature.OrderForms] {\n return combineLatest([orderStatus$, orderConfig$, isAdmin$]).pipe(\n map(([orderStatus, config, isAdmin]) => {\n return GetOrderFormPermissions({ orderStatus, workflowStepOptions: config.workflowStepOptions, isAdmin });\n }),\n );\n }\n\n private setupCanSubmitOrderWithoutRequiredFields(\n isAdmin$: Observable,\n salesPersonConfig$: Observable,\n ): OrderActionPermissions[OrderAction.SubmitWithoutRequiredFields] {\n return combineLatest([isAdmin$, salesPersonConfig$]).pipe(\n map(([isAdmin, salespersonConfig]) => {\n return GetCanSubmitOrderWithoutRequiredFields({ isAdmin, salespersonConfig });\n }),\n );\n }\n\n private setupTagPermissions(\n salespersonOptions$: Observable,\n isAdmin$: Observable,\n ): OrderFeaturePermissions[OrderFeature.Tags] {\n return combineLatest([salespersonOptions$, isAdmin$]).pipe(\n map(([salespersonOptions, isAdmin]) => {\n return GetTagPermissions({ salespersonOptions, isAdmin });\n }),\n );\n }\n\n private setupIgnoreAllErrorsOnOrderPermissions(\n orderStatus$: Observable,\n ): OrderActionPermissions[OrderAction.CreateInvoiceFromOrder] {\n return orderStatus$.pipe(map((orderStatus) => GetIgnoreAllErrorsOnOrderPermissions({ orderStatus })));\n }\n\n private setupCreateInvoiceFromOrderPermissions(\n userCanAccessRetailBilling$: Observable,\n ): OrderActionPermissions[OrderAction.CreateInvoiceFromOrder] {\n return userCanAccessRetailBilling$.pipe(\n map((userCanAccessRetailBilling) => GetCreateInvoiceFromOrderPermissions({ userCanAccessRetailBilling })),\n );\n }\n\n private setupSubmitForCustomerApprovalPermissions(\n orderStatus$: Observable,\n workflowStepOptions$: Observable,\n isAdmin$: Observable,\n ): OrderActionPermissions[OrderAction.SubmitForCustomerApproval] {\n return combineLatest([orderStatus$, workflowStepOptions$, isAdmin$]).pipe(\n map(([orderStatus, workflowStepOptions, isAdmin]) => {\n return GetSubmitForCustomerApprovalPermissions({ orderStatus, workflowStepOptions, isAdmin });\n }),\n );\n }\n\n private setupEditOrderContentsPermissions(\n orderStatus$: Observable,\n isAdmin$: Observable,\n ): OrderFeaturePermissions[OrderFeature.OrderContents] {\n return combineLatest([orderStatus$, isAdmin$]).pipe(\n map(([orderStatus, isAdmin]) => GetEditOrderContentsPermissions({ orderStatus, isAdmin })),\n );\n }\n\n private setupDuplicateOrderPermissions(\n orderStatus$: Observable,\n ): OrderActionPermissions[OrderAction.DuplicateOrder] {\n return orderStatus$.pipe(map((orderStatus) => GetDuplicateOrderPermissions({ orderStatus })));\n }\n\n private setupSubscribeToUpdatesPermissions(\n orderStatus$: Observable,\n ): OrderActionPermissions[OrderAction.SubscribeToUpdates] {\n return orderStatus$.pipe(map((orderStatus) => GetSubscribeToUpdatesPermissions({ orderStatus })));\n }\n\n private setupCancelOrderPermissions(\n isAdmin$: Observable,\n orderStatus$: Observable,\n ): OrderActionPermissions[OrderAction.CancelOrder] {\n return combineLatest([isAdmin$, orderStatus$]).pipe(\n map(([isAdmin, orderStatus]) => GetCancelOrderPermissions({ isAdmin, orderStatus })),\n );\n }\n\n private setupRequestToCancelOrderPermissions(\n isAdmin$: Observable,\n orderStatus$: Observable,\n ): OrderActionPermissions[OrderAction.RequestToCancelOrder] {\n return combineLatest([isAdmin$, orderStatus$]).pipe(\n map(([isAdmin, orderStatus]) => GetRequestToCancelOrderPermissions({ isAdmin, orderStatus })),\n );\n }\n\n private setupCanReviewCancellationRequest(\n isAdmin$: Observable,\n orderStatus$: Observable,\n ): OrderActionPermissions[OrderAction.ReviewCancellationRequest] {\n return combineLatest([isAdmin$, orderStatus$]).pipe(\n map(([isAdmin, orderStatus]) => CanReviewCancellationRequest({ isAdmin, orderStatus })),\n );\n }\n\n private setupArchiveOrderPermissions(\n isAdmin$: Observable,\n orderStatus$: Observable,\n ): OrderActionPermissions[OrderAction.ArchiveOrder] {\n return combineLatest([isAdmin$, orderStatus$]).pipe(\n map(([isAdmin, orderStatus]) => GetArchiveOrderPermissions({ isAdmin, orderStatus })),\n );\n }\n\n private setupActivateOrderPermissions(\n isAdmin$: Observable,\n orderStatus$: Observable,\n ): OrderActionPermissions[OrderAction.ActivateOrder] {\n return combineLatest([isAdmin$, orderStatus$]).pipe(\n map(([isAdmin, orderStatus]) => GetActivateOrderPermissions({ isAdmin, orderStatus })),\n );\n }\n\n private setupScheduleActivationPermissions(\n isAdmin$: Observable,\n orderStatus$: Observable,\n ): OrderActionPermissions[OrderAction.ScheduleActivation] {\n return combineLatest([isAdmin$, orderStatus$]).pipe(\n map(([isAdmin, orderStatus]) => GetScheduleActivationPermissions({ isAdmin, orderStatus })),\n );\n }\n\n private setupCanReviewSubmittedOrder(\n isAdmin$: Observable,\n orderStatus$: Observable,\n ): OrderActionPermissions[OrderAction.ScheduleActivation] {\n return combineLatest([isAdmin$, orderStatus$]).pipe(\n map(([isAdmin, orderStatus]) => CanReviewSubmittedOrder({ isAdmin, orderStatus })),\n );\n }\n\n private setupIncludeInInvoicesTogglePermissions(\n isAdmin$: Observable,\n autoGenerateRetailSubscriptions$: Observable,\n ): OrderFeaturePermissions[OrderFeature.Invoices] {\n return combineLatest([isAdmin$, autoGenerateRetailSubscriptions$]).pipe(\n map(([isAdmin, autoGenerateRetailSubscriptions]) =>\n GetInvoicePermissions({ isAdmin, autoGenerateRetailSubscriptions }),\n ),\n );\n }\n\n private setupFulfillmentStatusesPermissions(\n orderStatus$: Observable,\n ): OrderFeaturePermissions[OrderFeature.FulfillmentStatuses] {\n return orderStatus$.pipe(map((orderStatus) => GetFulfillmentStatusesPermissions(orderStatus)));\n }\n\n private setupCreateOrderDetailsPermissions(\n orderStatus$: Observable,\n isAdmin$: Observable,\n ): OrderFeaturePermissions[OrderFeature.CreateOrderDetails] {\n return combineLatest([orderStatus$, isAdmin$]).pipe(\n map(([orderStatus, isAdmin]: [Status, boolean]) => {\n return GetCreateOrderDetailsPermissions(orderStatus, isAdmin);\n }),\n );\n }\n\n private setupCondensedOrderDetailsPermissions(\n orderStatus$: Observable,\n ): OrderFeaturePermissions[OrderFeature.CondensedOrderDetails] {\n return orderStatus$.pipe(\n map((orderStatus) => {\n return GetCondensedOrderDetailsPermissions(orderStatus);\n }),\n );\n }\n\n private setupActiveItemsPermissions(\n orderStatus$: Observable,\n ): OrderFeaturePermissions[OrderFeature.ActiveItems] {\n return orderStatus$.pipe(\n map((orderStatus) => {\n return GetActiveItemsPermissions(orderStatus);\n }),\n );\n }\n\n private setupBusinessHeaderPermissions(\n orderStatus$: Observable,\n ): OrderFeaturePermissions[OrderFeature.BusinessHeader] {\n return orderStatus$.pipe(\n map((orderStatus) => {\n return GetBusinessHeaderPermissions(orderStatus);\n }),\n );\n }\n\n private setupCanSelectOrderCurrency(\n order$: Observable,\n ): OrderActionPermissions[OrderAction.SelectOrderCurrency] {\n return order$.pipe(map((order) => order.partnerId === 'VMF'));\n }\n\n private setupEditContractStartAndDurationPermissions(\n orderStatus$: Observable,\n isAdmin$: Observable,\n ): OrderFeaturePermissions[OrderFeature.ContractStartAndDuration] {\n return combineLatest([orderStatus$, isAdmin$]).pipe(\n map(([orderStatus, isAdmin]) => GetEditOrderContractStartAndDurationPermissions({ orderStatus, isAdmin })),\n );\n }\n\n private setupCanAccessOnlySalespersonAutomations(\n isAdmin$: Observable,\n ): OrderActionPermissions[OrderAction.AccessOnlySalespersonAutomations] {\n return isAdmin$.pipe(\n map((isAdmin) => {\n return !isAdmin;\n }),\n );\n }\n private setupCanRedirectToCompanyProfileAfterSubmittingOrder(\n isAdmin$: Observable,\n ): OrderActionPermissions[OrderAction.RedirectToCompanyProfileAfterSubmitting] {\n return isAdmin$.pipe(\n map((isAdmin) => {\n return !isAdmin;\n }),\n );\n }\n\n private setupUpdateInvalidContractStartDatePermissions(\n orderStatus$: Observable,\n ): OrderActionPermissions[OrderAction.ScheduleActivation] {\n return orderStatus$.pipe(map((orderStatus) => CanUpdateInvalidContractStartDatePermissions(orderStatus)));\n }\n\n private setupInvalidLineItemsBannerPermissions(\n orderStatus$: Observable,\n ): OrderFeaturePermissions[OrderFeature.InvalidLineItemsBanner] {\n return orderStatus$.pipe(\n map((orderStatus) => {\n return GetInvalidLineItemsBannerPermissions(orderStatus);\n }),\n );\n }\n\n private setupCanCollectPaymentFromCustomerPermissions(\n isAdmin$: Observable,\n retailProvider$: Observable,\n hasAccessToUnifiedOrders$: Observable,\n retailStatus$: Observable,\n orderConfig$: Observable,\n ): OrderActionPermissions[OrderAction.CollectPaymentFromCustomer] {\n return combineLatest([isAdmin$, retailProvider$, hasAccessToUnifiedOrders$, retailStatus$, orderConfig$]).pipe(\n map(([isAdmin, retailProvider, hasAccessToUnifiedOrders, retailStatus, orderConfig]) => {\n return (\n (isAdmin || orderConfig?.workflowStepOptions?.allowCollectPaymentFromCustomer) &&\n !!retailProvider &&\n hasAccessToUnifiedOrders &&\n retailStatus?.canAcceptPayment\n );\n }),\n );\n }\n\n private setupCanChargeSMBOnOrderSubmission(\n isAdmin$: Observable,\n userCanAccessRetailBilling$: Observable,\n orderConfig$: Observable,\n retailProvider$: Observable,\n retailStatus$: Observable,\n ): OrderActionPermissions[OrderAction.ChargeSMBOnOrderSubmission] {\n return combineLatest([isAdmin$, userCanAccessRetailBilling$, orderConfig$, retailProvider$, retailStatus$]).pipe(\n map(([isAdmin, canAccessRetailBilling, orderConfig, retailProvider, retailStatus]) =>\n GetCanChargeSMBOnOrderSubmissionPermissions({\n isAdmin,\n canAccessRetailBilling,\n orderConfig,\n retailProvider,\n retailStatus,\n }),\n ),\n );\n }\n}\n", "export { OrderPermissionsService } from './permissions.service';\nexport { OrderFeature } from './permissions';\n", "import { Component, Inject, OnInit } from '@angular/core';\nimport { MatButtonModule } from '@angular/material/button';\nimport { MAT_DIALOG_DATA, MatDialogClose, MatDialogRef } from '@angular/material/dialog';\nimport { MatIconModule } from '@angular/material/icon';\nimport { take } from 'rxjs/operators';\nimport { GalaxyConfirmationModalModule } from '@vendasta/galaxy/confirmation-modal';\nimport { GalaxyButtonLoadingIndicatorModule } from '@vendasta/galaxy/button-loading-indicator';\nimport { SnackbarService } from '@vendasta/galaxy/snackbar-service';\nimport { SalesOrdersService } from '@vendasta/sales-orders';\nimport { TranslateModule } from '@ngx-translate/core';\n\nexport interface DialogInput {\n orderId: string;\n businessId: string;\n orderDate: string;\n todayDate: string;\n}\n\n@Component({\n selector: 'orders-activation-date-confirmation-dialog',\n templateUrl: './activation-date-confirmation-dialog.component.html',\n styleUrls: ['./activation-date-confirmation-dialog.component.scss'],\n imports: [\n GalaxyButtonLoadingIndicatorModule,\n GalaxyConfirmationModalModule,\n MatButtonModule,\n MatDialogClose,\n MatIconModule,\n TranslateModule,\n ],\n})\nexport class ActivationDateConfirmationDialogComponent implements OnInit {\n orderId: string;\n businessId: string;\n orderDate: string;\n todayDate: string;\n updatingDate = false;\n\n constructor(\n @Inject(MAT_DIALOG_DATA) private data: DialogInput,\n public dialogRef: MatDialogRef,\n private salesOrdersService: SalesOrdersService,\n private snackbarService: SnackbarService,\n ) {}\n\n ngOnInit(): void {\n this.orderId = this.data?.orderId;\n this.businessId = this.data?.businessId;\n this.orderDate = this.data?.orderDate;\n this.todayDate = this.data?.todayDate;\n }\n\n proceedWithPreviousDate(): void {\n this.dialogRef.close(true);\n }\n\n proceedWithNewDate(): void {\n if (this.updatingDate) return;\n\n this.updatingDate = true;\n\n // Set time to 7AM local time\n const currentDate = new Date(new Date().setHours(7, 0, 0, 0));\n\n this.salesOrdersService\n .updateRequestedActivation(this.orderId, this.businessId, currentDate)\n .pipe(take(1))\n .subscribe({\n next: () => {\n this.updatingDate = false;\n this.dialogRef.close(true);\n },\n error: (err) => {\n console.error(err);\n this.snackbarService.openErrorSnack(\n 'LIB_ORDERS.SALES_ORDERS.ACTIVATION_DATE_CONFIRMATION_DIALOG.ERROR_REQUESTED_ACTIVATION_UPDATE',\n );\n this.updatingDate = false;\n },\n });\n }\n}\n", "\n info\n \n {{ 'LIB_ORDERS.SALES_ORDERS.ACTIVATION_DATE_CONFIRMATION_DIALOG.TITLE' | translate }}\n \n\n \n

\n

{{ 'LIB_ORDERS.SALES_ORDERS.ACTIVATION_DATE_CONFIRMATION_DIALOG.MESSAGE_2' | translate }}

\n
\n
\n\n\n \n \n \n \n \n \n \n\n", "import { Injectable } from '@angular/core';\nimport {\n CommonFormData,\n CustomFieldsAnswer,\n CustomFieldsAnswers,\n IncludedCommonFormFieldsInterface,\n OrderFormFieldOptionsType,\n OrderFormInterface,\n OrderFormOptionsInterface,\n ProductInfoInterface,\n TextBoxFieldOptions,\n} from '@vendasta/store';\nimport { Observable, of, zip, combineLatest, ReplaySubject } from 'rxjs';\nimport {\n GetMultiOrderFormsResponseOrderFormContainerInterface as MPOrderFormContainerInterface,\n MarketplaceAppsApiService,\n} from '@galaxy/marketplace-apps/v1';\nimport { App, AppKey, AppPartnerService } from '@galaxy/marketplace-apps';\nimport {\n CommonField,\n CustomField,\n Field,\n LineItem,\n LineItemInterface,\n SalesOrdersService,\n Order,\n ConfigInterface,\n} from '@vendasta/sales-orders';\nimport { Package, PackageService } from '@vendasta/marketplace-packages';\nimport { filter, map, publishReplay, refCount, switchMap } from 'rxjs/operators';\nimport { TranslateService } from '@ngx-translate/core';\nimport { AccountGroup, AccountGroupService, ProjectionFilter } from '@galaxy/account-group';\nimport { AppType } from '@vendasta/marketplace-apps';\n\n@Injectable()\nexport class OrderFormService {\n private mpOrderForms$: Observable;\n private appQuantityMap$: Observable>;\n\n private order$$: ReplaySubject = new ReplaySubject(1);\n order$: Observable = this.order$$.asObservable();\n private accountGroup$: Observable;\n\n private orderFormOptions$$: ReplaySubject = new ReplaySubject(\n 1,\n );\n orderFormOptions$: Observable = this.orderFormOptions$$.asObservable();\n\n private orderConfig$$: ReplaySubject = new ReplaySubject(1);\n orderConfig$: Observable = this.orderConfig$$.asObservable();\n\n private lineItems$$: ReplaySubject = new ReplaySubject(1);\n lineItems$: Observable = this.lineItems$$.asObservable();\n packageIds$: Observable;\n packages$: Observable;\n apps$: Observable;\n standaloneAppKeys$: Observable;\n includedAppKeys$: Observable;\n uniqueApps$: Observable;\n\n private orderForms$: Observable;\n private commonData$: Observable;\n private productInfo$: Observable;\n private answers$: Observable;\n private extraQuestions$: Observable;\n private extraAnswers$: Observable;\n\n constructor(\n private translateService: TranslateService,\n private appsApiService: MarketplaceAppsApiService,\n private salesOrderSdk: SalesOrdersService,\n private packageService: PackageService,\n private appsService: AppPartnerService,\n private accountGroupService: AccountGroupService,\n ) {}\n\n initialize(\n order: Order,\n orderFormOptions: OrderFormOptionsInterface,\n orderLineItems: LineItemInterface[] = [],\n orderConfig: ConfigInterface,\n ): void {\n this.order$$.next(order);\n this.orderFormOptions$$.next(orderFormOptions);\n this.orderConfig$$.next(orderConfig);\n this.lineItems$$.next(orderLineItems);\n this.refreshOrderForms();\n }\n\n private refreshOrderForms(): void {\n this.lineItems$ = combineLatest([this.order$, this.lineItems$]).pipe(\n map(([order, orderLineItems]) => {\n return order?.lineItems || orderLineItems;\n }),\n );\n\n this.packageIds$ = this.lineItems$.pipe(\n map((lineItems) => {\n return lineItems.map((lineItem) => lineItem.packageId).filter((packageId) => !!packageId);\n }),\n publishReplay(1),\n refCount(),\n );\n\n this.standaloneAppKeys$ = this.lineItems$.pipe(\n map((lineItems) => {\n return lineItems\n .filter((lineItem) => !!lineItem.appKey)\n .map((lineItem) => {\n return new AppKey(lineItem.appKey);\n });\n }),\n publishReplay(1),\n refCount(),\n );\n\n this.packages$ = this.packageIds$.pipe(\n switchMap((packageIds) => {\n return packageIds.length > 0 ? this.packageService.getMulti(packageIds) : of([]);\n }),\n publishReplay(1),\n refCount(),\n );\n\n this.includedAppKeys$ = zip(this.packages$, this.standaloneAppKeys$).pipe(\n map(([packages, standaloneAppKeys]) => {\n const packageAppKeys = packages\n .map((pkg) => {\n const lineItems = pkg && pkg.lineItems && pkg.lineItems.lineItems ? pkg.lineItems.lineItems : [];\n return lineItems.map(\n (item) =>\n new AppKey({\n appId: item.id || '',\n editionId: item.editionId || '',\n }),\n );\n })\n .reduce((a, b) => [...a, ...b], []);\n return standaloneAppKeys.concat(packageAppKeys);\n }),\n publishReplay(1),\n refCount(),\n );\n\n this.apps$ = combineLatest([this.includedAppKeys$, this.order$]).pipe(\n switchMap(([includedAppKeys, order]) =>\n this.appsService.getMulti(includedAppKeys, order.partnerId, order.marketId, null, true),\n ),\n publishReplay(1),\n refCount(),\n );\n\n this.uniqueApps$ = this.apps$.pipe(\n filter((apps) => !!apps && apps.length > 0),\n map((appKeys) => {\n return appKeys.reduce((unique, app) => {\n const { key } = app;\n if (\n unique.find(({ key: uniqueKey }) => uniqueKey.appId === key.appId && uniqueKey.editionId === key.editionId)\n ) {\n return unique;\n }\n\n unique.push(app);\n return unique;\n }, []);\n }),\n publishReplay(1),\n refCount(),\n );\n\n this.mpOrderForms$ = combineLatest([this.apps$, this.order$]).pipe(\n switchMap(([apps, order]) => {\n return this.getMarketplaceOrderForms(order.partnerId, apps);\n }),\n publishReplay(1),\n refCount(),\n );\n\n this.appQuantityMap$ = zip(this.packages$, this.lineItems$).pipe(\n map(([packages, salesLineItems]) => {\n const quantityMap: Map = new Map();\n const allPkgLineItems = packages.reduce((items, pkg) => {\n const pkgLineItem = salesLineItems.find((lineItem) => lineItem.packageId === pkg.packageId);\n const pkgCount = (pkgLineItem ? pkgLineItem.quantity : 1) || 1;\n return items.concat(this.salesOrderLineItemsFromPackage(pkg, pkgCount));\n }, []);\n salesLineItems = salesLineItems.filter((lineItem) => !lineItem.packageId);\n salesLineItems = salesLineItems.concat(allPkgLineItems);\n salesLineItems.forEach((item) => {\n const count = quantityMap.get(item.appKey.appId) || 0;\n quantityMap.set(item.appKey.appId, count + (item.quantity || 1));\n });\n return quantityMap;\n }),\n publishReplay(1),\n refCount(),\n );\n\n this.orderForms$ = combineLatest([\n this.mpOrderForms$,\n this.appQuantityMap$,\n this.uniqueApps$,\n this.orderFormOptions$,\n ]).pipe(\n map(([orderForms, appQuantityMap, apps, options]) => {\n return apps.reduce((forms, app) => {\n let commonFormRequiredFields: IncludedCommonFormFieldsInterface = {};\n let customFields: OrderFormFieldOptionsType[] = [];\n\n if (app.activationInformation.activationSpecificUrlEnabled) {\n customFields.unshift({\n id: 'custom-entry-url-' + app.key.appId,\n label: this.translateService.instant('FRONTEND.SALES_UI.ORDER_FORM.CUSTOM_ENTRY_URL'),\n type: 'text',\n required: options.showOfficeUseQuestions,\n hidden: !options.showOfficeUseQuestions,\n regexValidator: '^(http://|https://).*',\n regexErrorMessage: this.translateService.instant(\n 'FRONTEND.SALES_UI.ORDER_FORM.CUSTOM_ENTRY_URL_REGEX_ERROR',\n ),\n textboxType: 'text',\n } as TextBoxFieldOptions);\n }\n\n const orderForm = orderForms.find((o) => o.appId === app.key.appId);\n\n if (!!orderForm || app.activationInformation.activationSpecificUrlEnabled) {\n if (!!orderForm && orderForm.orderForm && app.activationInformation.orderFormEnabled) {\n if (orderForm.orderForm.commonFormRequiredFields) {\n commonFormRequiredFields = orderForm.orderForm.commonFormRequiredFields;\n }\n if (orderForm.orderForm.orderForm) {\n customFields = customFields.concat(\n orderForm.orderForm.orderForm.map(\n (f) => ({ ...f, options: f.optionsWithLabels }) as OrderFormFieldOptionsType,\n ),\n );\n }\n }\n\n let appId = app.key.appId;\n let addonId = '';\n if (app.parentRequirements.enabled && app.appType === AppType.APP_TYPE_ADDON) {\n appId = app.parentRequirements.parentDetails.key.appId;\n addonId = app.key.appId;\n }\n const footerText = orderForm?.orderForm?.footerText ? orderForm.orderForm.footerText : null;\n const createdForm: OrderFormInterface = {\n appId: appId,\n addonId: addonId,\n customFields: customFields,\n commonFormRequiredFields: commonFormRequiredFields,\n };\n if (footerText) {\n createdForm.footerText = footerText;\n }\n forms.push(createdForm);\n\n if (\n !!addonId &&\n app.activationInformation.orderFormEnabled &&\n app.activationInformation.multipleActivationsEnabled &&\n app.activationInformation.separateOrderForms\n ) {\n const count = appQuantityMap.get(app.key.appId) || 1;\n for (let i = 1; i < count; i++) {\n forms.push(createdForm);\n }\n } else if (\n !addonId &&\n app.activationInformation.orderFormEnabled &&\n app.activationInformation.multipleActivationsEnabled\n ) {\n const count = appQuantityMap.get(app.key.appId) || 1;\n for (let i = 1; i < count; i++) {\n forms.push(createdForm);\n }\n }\n }\n\n return forms;\n }, []);\n }),\n );\n\n this.accountGroup$ = this.order$.pipe(\n switchMap((o) => this.accountGroupService.get(o.businessId, new ProjectionFilter({ napData: true }))),\n );\n this.commonData$ = combineLatest([this.order$, this.accountGroup$]).pipe(\n switchMap(([order, accountGroup]) => {\n const commonData: CommonFormData = OrderFormService.populateCommonFormData(accountGroup);\n // commonFields from the order take precedence over the auto-populated data\n if (order) {\n (order.commonFields || []).map((cf) => {\n let fieldAnswer = cf.field.answer;\n try {\n fieldAnswer = JSON.parse(fieldAnswer);\n } catch (error) {\n // do nothing, this code should be cleaned up.\n }\n const fieldId = cf.field.fieldId;\n commonData[fieldId] = fieldAnswer;\n });\n }\n return of(commonData);\n }),\n );\n\n this.productInfo$ = this.uniqueApps$.pipe(\n map((apps) => {\n const productInfoMap: Map = new Map();\n apps.map((app) => {\n let productInfo: ProductInfoInterface = {\n productId: 'temporary',\n addonInfo: [],\n };\n if (app.parentRequirements.enabled && app.appType === AppType.APP_TYPE_ADDON) {\n if (productInfoMap.has(app.parentRequirements.parentDetails.key.appId)) {\n productInfo = productInfoMap.get(app.parentRequirements.parentDetails.key.appId);\n }\n productInfo.name = app.parentRequirements.parentDetails.name;\n productInfo.tagline = app.parentRequirements.parentDetails.tagline;\n productInfo.icon = app.parentRequirements.parentDetails.iconUrl;\n productInfo.addonInfo.push({\n addonId: app.key.appId,\n name: app.sharedMarketingInformation.name,\n });\n productInfo.productId = app.parentRequirements.parentDetails.key.appId;\n productInfoMap.set(productInfo.productId, productInfo);\n } else {\n if (productInfoMap.has(app.key.appId)) {\n productInfo = productInfoMap.get(app.key.appId);\n }\n productInfo.name = app.sharedMarketingInformation.name;\n productInfo.tagline = app.sharedMarketingInformation.tagline;\n productInfo.icon = app.sharedMarketingInformation.iconUrl;\n productInfo.productId = app.key.appId;\n productInfoMap.set(productInfo.productId, productInfo);\n }\n });\n return Array.from(productInfoMap.values());\n }),\n );\n\n this.answers$ = this.order$.pipe(\n map((order) => {\n if (order) {\n const answers: CustomFieldsAnswers[] = [];\n if (order.customFields) {\n order.customFields.map((c) => {\n let fieldAnswers: CustomFieldsAnswer[] = [];\n if (c.fields) {\n fieldAnswers = c.fields.map((f) => ({ fieldId: f.fieldId, answer: f.answer }));\n }\n answers.push({\n productID: c.productId || (c.addonKey ? c.addonKey.appId : ''),\n addonKey: c.addonKey,\n customFieldsAnswers: fieldAnswers,\n });\n });\n }\n return answers;\n }\n return [];\n }),\n );\n\n this.extraQuestions$ = this.orderConfig$.pipe(\n map((config) => {\n return !!config && !!config.extraFields\n ? config.extraFields.map((f) => ({ ...f, options: f.optionsWithLabels }) as OrderFormFieldOptionsType)\n : [];\n }),\n );\n\n this.extraAnswers$ = this.order$.pipe(\n map((order) => {\n if (order) {\n return order.extraFields || [];\n }\n return [];\n }),\n );\n }\n\n private salesOrderLineItemsFromPackage(pkg: Package, pkgCount: number): LineItem[] {\n if (!pkg.lineItems || !pkg.lineItems.lineItems || !pkg.lineItems.lineItems.length) {\n return [];\n }\n return pkg.lineItems.lineItems.map(\n (lineItem) =>\n new LineItem({\n appKey: { appId: lineItem.id, editionId: lineItem.editionId },\n quantity: (lineItem.quantity || 1) * pkgCount,\n }),\n );\n }\n\n private getMarketplaceOrderForms(partnerId: string, apps: App[]): Observable {\n if (apps?.length > 0) {\n const appIds: string[] = apps\n .filter((app) => app?.activationInformation?.orderFormEnabled)\n .map((app) => app?.key?.appId);\n if (appIds?.length > 0) {\n return this.appsApiService\n .getMultiOrderForms({ partnerId: partnerId, appIds: appIds })\n .pipe(map((resp) => resp.orderFormContainer || []));\n }\n }\n return of([]);\n }\n\n get getOrderForms(): Observable {\n return this.orderForms$;\n }\n\n get getCommonData(): Observable {\n return this.commonData$;\n }\n\n get getProductInfo(): Observable {\n return this.productInfo$;\n }\n\n get getAnswers(): Observable {\n return this.answers$;\n }\n\n get getExtraQuestions(): Observable {\n return this.extraQuestions$;\n }\n\n get getExtraAnswers(): Observable {\n return this.extraAnswers$;\n }\n\n static populateCommonFormData(accountGroup?: AccountGroup): CommonFormData {\n const napData = accountGroup?.napData;\n const fullAddress = [napData?.address, napData?.city, napData?.state, napData?.zip].filter(Boolean).join(', ');\n return {\n business_account_group_id: accountGroup?.accountGroupId ?? '',\n business_name: napData?.companyName ?? '',\n business_address: fullAddress,\n business_phone_number: napData?.workNumber?.filter(Boolean)[0] ?? '',\n business_website: napData?.website ?? '',\n };\n }\n\n updateAnswers(commonFormData: CommonField[], customFormData: CustomField[], extraFields: Field[]): Observable {\n return this.order$.pipe(\n switchMap((order) => {\n return this.salesOrderSdk.updateAnswers(\n order.orderId,\n order.businessId,\n customFormData,\n commonFormData,\n extraFields,\n );\n }),\n );\n }\n\n refresh(order: Order): void {\n this.order$$.next(order);\n }\n}\n", "import {\n ChangeDetectionStrategy,\n ChangeDetectorRef,\n Component,\n EventEmitter,\n Input,\n OnDestroy,\n OnInit,\n Output,\n ViewChild,\n ViewEncapsulation,\n} from '@angular/core';\nimport { UntypedFormControl, UntypedFormGroup } from '@angular/forms';\nimport { SnackbarService } from '@vendasta/galaxy/snackbar-service';\nimport {\n CommonField,\n ConfigInterface,\n CustomField,\n Field,\n LineItemInterface,\n Order,\n Status,\n UserInterface as User,\n} from '@vendasta/sales-orders';\nimport {\n CommonFormData,\n CustomFieldsAnswer,\n CustomFieldsAnswers,\n OrderFormFieldOptionsType,\n OrderFormInterface,\n OrderFormOptionsInterface,\n ProductInfoInterface,\n OrderFormComponent as StoreOrderFormComponent,\n} from '@vendasta/store';\nimport { BehaviorSubject, Observable, Subject, Subscription, combineLatest, of } from 'rxjs';\nimport { debounceTime, distinctUntilChanged, filter, map, startWith, switchMap, take, tap } from 'rxjs/operators';\nimport { OrderFormService } from './order-form.service';\n\ninterface OrderData {\n orderForms: OrderFormInterface[];\n productInfo: ProductInfoInterface[];\n commonData: CommonFormData;\n answers: CustomFieldsAnswers[];\n extraQuestions: OrderFormFieldOptionsType[];\n extraAnswers: CustomFieldsAnswer[];\n users: User[];\n orderFormOptions: OrderFormOptionsInterface;\n}\n\n@Component({\n templateUrl: 'order-form.component.html',\n providers: [OrderFormService],\n selector: 'orders-form',\n changeDetection: ChangeDetectionStrategy.OnPush,\n encapsulation: ViewEncapsulation.None,\n styleUrls: ['../../shared/shared-styles.scss', './order-form.component.scss'],\n standalone: false,\n})\nexport class OrderFormComponent implements OnInit, OnDestroy {\n @ViewChild('vaOrderForm')\n public orderFormComponent: StoreOrderFormComponent;\n\n // The order and its configuration\n @Input() order: Order;\n @Input() orderFormOptions: OrderFormOptionsInterface;\n @Input() orderConfig: ConfigInterface;\n\n // Optionally used within the create order experience\n @Input() lineItems: LineItemInterface[];\n\n // Required attributes\n @Input() userOptions: User[];\n @Input() fileUploadUrl = '';\n @Input() showSelfSaveButton = false;\n @Input() answers: CustomFieldsAnswers[] = [];\n\n private parentForm$$ = new BehaviorSubject(null);\n @Input() set parentForm(form: UntypedFormGroup) {\n this.parentForm$$.next(form);\n }\n get parentForm(): UntypedFormGroup | null {\n return this.parentForm$$.getValue();\n }\n\n // External identifiers\n @Input() partnerId: string;\n @Input() businessId: string;\n\n @Output() loading: EventEmitter = new EventEmitter();\n\n answers$: Observable;\n orderData$: Observable;\n startOpen$: Observable;\n submitting = false;\n orderStatus: Status;\n\n loading$$: BehaviorSubject = new BehaviorSubject(true);\n loading$: Observable = this.loading$$.asObservable();\n isValid$!: Observable;\n\n subscriptions: Subscription[] = [];\n\n constructor(\n private alertService: SnackbarService,\n private salesOrderFormService: OrderFormService,\n private cdr: ChangeDetectorRef,\n ) {}\n\n ngOnInit(): void {\n this.salesOrderFormService.initialize(this.order, this.orderFormOptions, this.lineItems, this.orderConfig);\n\n this.answers$ = this.salesOrderFormService.getAnswers.pipe(\n map((answers) => {\n if (answers.length > 0) {\n return answers;\n }\n\n return this.answers;\n }),\n );\n\n this.orderData$ = combineLatest([\n this.salesOrderFormService.getOrderForms,\n this.salesOrderFormService.getProductInfo,\n this.salesOrderFormService.getCommonData,\n this.answers$,\n this.salesOrderFormService.getExtraAnswers,\n this.salesOrderFormService.getExtraQuestions,\n this.salesOrderFormService.orderFormOptions$,\n ]).pipe(\n // OrderFormService emits in a burst when the order re-renders which caused order form issues, debounce helps prevent that (https://vendasta.jira.com/browse/QOF-1472)\n debounceTime(250),\n map(([orderForms, productInfo, commonData, answers, extraAnswers, extraQuestions, orderFormOptions]) => {\n return {\n orderForms: orderForms,\n productInfo: productInfo,\n commonData: commonData,\n answers: answers,\n extraQuestions: extraQuestions,\n extraAnswers: extraAnswers,\n users: this.userOptions,\n orderFormOptions: orderFormOptions,\n };\n }),\n tap(() => this.loading$$.next(false)),\n );\n\n this.isValid$ = this.parentForm$$.asObservable().pipe(\n filter((form) => form !== null),\n switchMap((form) => {\n return form.statusChanges.pipe(\n startWith(form.status),\n debounceTime(250), // wait for form to stabilize before emitting\n map((status) => status !== 'INVALID'),\n );\n }),\n distinctUntilChanged(),\n );\n\n this.startOpen$ = this.salesOrderFormService.order$.pipe(\n take(1),\n map(\n (o) =>\n !o ||\n (o.status !== Status.PROCESSING &&\n o.status !== Status.ACTIVATION_ERRORS &&\n o.status !== Status.FULFILLED &&\n o.status !== Status.ARCHIVED),\n ),\n );\n\n this.subscriptions.push(this.loading$.pipe(tap((loading) => this.loading.emit(loading))).subscribe());\n }\n\n ngOnDestroy(): void {\n this.subscriptions.forEach((sub) => {\n sub.unsubscribe();\n });\n }\n\n /**\n * returns the data from common form fields in a format that is usable by sales orders\n */\n public getCommonFormData(): CommonField[] {\n return this.orderFormComponent.getCommonFormData();\n }\n\n /**\n * returns the data from custom forms in a format that is usable by sales orders\n */\n public getCustomFormData(): CustomField[] {\n return this.orderFormComponent.getCustomFormData();\n }\n\n /**\n * returns the data from extra fields in a format that is usable by sales orders\n */\n public getExtraFieldsData(): Field[] {\n return this.orderFormComponent.getExtraFieldsData();\n }\n\n /**\n * returns true if the order form contains fields that need to be filled out by the partner\n */\n public requiresPartnerInput(): boolean {\n return this.orderFormComponent.orderFormRequiresPartnerInput();\n }\n\n /**\n * changes the read only state of the sales order form\n */\n public changeReadOnly(readOnly: boolean): void {\n this.orderFormComponent.changeReadOnly(readOnly);\n }\n\n private setAsTouched(form: UntypedFormGroup): void {\n form.markAsTouched();\n const controls = form.controls;\n for (const i of Object.keys(controls)) {\n if (controls[i] instanceof UntypedFormControl) {\n controls[i].markAsTouched();\n } else if (controls[i] instanceof UntypedFormGroup) {\n this.setAsTouched(controls[i] as UntypedFormGroup);\n }\n controls[i].updateValueAndValidity({ emitEvent: false });\n }\n form.updateValueAndValidity({ emitEvent: false });\n }\n\n /**\n * returns whether or not the sales order form is valid and touches all controls\n */\n public validateForm(): boolean {\n if (!this.parentForm) {\n return true;\n }\n const valid = this.parentForm.valid;\n if (!valid) {\n this.setAsTouched(this.parentForm);\n }\n return valid;\n }\n\n public openDropdown(): void {\n this.orderFormComponent.openDropdown();\n }\n\n updateAnswers(): Observable {\n if (this.orderStatus !== Status.DRAFTED && !this.validateForm()) {\n this.alertService.openErrorSnack('FRONTEND.SALES_UI.ERRORS.ERROR_FORM_NOT_VALID');\n return of(false);\n }\n\n this.submitting = true;\n const customFormData = this.getCustomFormData();\n const extraFieldsData = this.getExtraFieldsData();\n const commonFormData = this.getCommonFormData();\n\n const result$$ = new Subject();\n this.salesOrderFormService\n .updateAnswers(commonFormData, customFormData, extraFieldsData)\n .pipe(take(1))\n .subscribe(\n () => {\n result$$.next(true);\n this.parentForm?.markAsPristine();\n this.submitting = false;\n this.cdr.detectChanges();\n this.alertService.openSuccessSnack('FRONTEND.SALES_UI.ORDER_FORM_ANSWERS_UPDATED');\n },\n () => {\n result$$.next(false);\n this.submitting = false;\n this.cdr.detectChanges();\n this.alertService.openErrorSnack('FRONTEND.SALES_UI.ERRORS.ERROR_UPDATING_ORDER_FORM');\n },\n );\n return result$$.asObservable();\n }\n\n refresh(o: Order): void {\n this.salesOrderFormService.refresh(o);\n this.cdr.detectChanges();\n }\n}\n", "
\n \n \n \n \n {{ 'LIB_ORDERS.COMMON.ORDERS.ORDER_FORM_LABEL' | translate }}\n \n \n
\n
\n
\n \n \n
\n
\n \n
\n \n
\n
\n
\n
\n
\n\n\n\n
\n
\n", "import { Inject, Injectable } from '@angular/core';\nimport { AppPartnerService, AppPricingService } from '@galaxy/marketplace-apps';\nimport { App, AppKey, FieldMask } from '@vendasta/marketplace-apps';\nimport { Package, PackageService } from '@vendasta/marketplace-packages';\nimport { LineItem } from '@vendasta/sales-orders';\nimport { BehaviorSubject, combineLatest, Observable, of } from 'rxjs';\nimport { map, shareReplay, switchMap, withLatestFrom } from 'rxjs/operators';\nimport { MARKET_ID, PARTNER_ID } from '../../shared/constants';\nimport {\n AppVariablePriceMap,\n PackageVariablePriceMap,\n VariablePriceInputs,\n} from '../variable-prices/variable-prices.component';\n\n@Injectable({\n providedIn: 'root',\n})\nexport class VariablePricesService {\n private lineItems$$ = new BehaviorSubject([]);\n variablePricesMap$: Observable;\n variablePackagePricesMap$: Observable;\n\n constructor(\n @Inject(PARTNER_ID) private readonly partnerId$: Observable,\n @Inject(MARKET_ID) private readonly marketId$: Observable,\n private readonly appSdk: AppPartnerService,\n private readonly packageSdk: PackageService,\n private readonly appPricingSdk: AppPricingService,\n ) {\n this.initializeService();\n }\n\n setLineItems(lineItems: LineItem[]): void {\n this.lineItems$$.next(lineItems ?? []);\n }\n\n private initializeService(): void {\n const lineItemAppIds$ = this.lineItems$$.pipe(\n map((lineItems) => {\n return lineItems.filter((li) => li.appKey?.appId).map((li) => li.appKey.appId);\n }),\n );\n const lineItemPackageIds$ = this.lineItems$$.pipe(\n map((lineItems) => {\n return lineItems.filter((li) => li.packageId).map((li) => li.packageId);\n }),\n );\n\n const packages$ = lineItemPackageIds$.pipe(\n switchMap((packageIds) => {\n if (!packageIds.length) {\n return of([]);\n }\n return this.packageSdk.getMulti(packageIds);\n }),\n shareReplay(1),\n );\n const appIdsFromPackages$ = packages$.pipe(\n map((packages) => {\n return getAppIdsFromPackages(packages);\n }),\n );\n const allApps$ = combineLatest([lineItemAppIds$, appIdsFromPackages$]).pipe(\n withLatestFrom(this.partnerId$, this.marketId$),\n switchMap(([[appIds, appIdsFromPackages], partnerId, marketId]) => {\n const allAppIDs = Array.from(new Set(appIds.concat(appIdsFromPackages)));\n const appKeys: AppKey[] = convertAppIdsIntoAppKeys(allAppIDs);\n if (!appKeys.length) {\n return of([]);\n }\n return this.appSdk.getMulti(appKeys, partnerId, marketId, { paths: ['key', 'sharedMarketingInformation'] });\n }),\n shareReplay(1),\n );\n const pricesForAllApps$ = allApps$.pipe(\n withLatestFrom(this.partnerId$, this.marketId$),\n switchMap(([allApps, partnerId, marketId]) => {\n const appIds = allApps.map((app) => app.key.appId);\n if (!appIds.length) {\n return of([]);\n }\n return this.appPricingSdk.getMultiPricing(appIds, new FieldMask({ paths: ['wholesale'] }), partnerId, marketId);\n }),\n shareReplay(1),\n );\n\n const variablePriceInputs$ = pricesForAllApps$.pipe(\n withLatestFrom(this.lineItems$$, allApps$, packages$),\n map(([appPrices, lineItems, allApps, packages]) => {\n return appPrices\n .filter((appPrice) => appPrice.pricesForContexts['wholesale'].billingConfiguration?.usesVariablePricing)\n .map((appPrice) => {\n let lineItem = findMatchingLineItem(lineItems, appPrice.appId);\n if (lineItem) {\n const app = findMatchingApp(lineItem.appKey.appId, allApps);\n return {\n appPrice: appPrice.pricesForContexts['wholesale'],\n name: app.sharedMarketingInformation.name,\n iconUrl: app.sharedMarketingInformation.iconUrl,\n customPrice: lineItem.cost?.customPrice ?? undefined,\n } as VariablePriceInputs;\n }\n\n const app = findMatchingApp(appPrice.appId, allApps);\n const pkg = findMatchingPackage(appPrice.appId, packages);\n lineItem = findMatchingLineItem(lineItems, pkg.packageId);\n const customPrice = lineItem?.customPrices?.find((cp) => cp.productId === appPrice.appId)?.customPrice;\n return {\n appPrice: appPrice.pricesForContexts['wholesale'],\n name: app.sharedMarketingInformation.name,\n iconUrl: app.sharedMarketingInformation.iconUrl,\n packageID: pkg.packageId,\n packageName: pkg.name,\n packageIconUrl: pkg.icon,\n customPrice: customPrice ?? undefined,\n } as VariablePriceInputs;\n });\n }),\n shareReplay(1),\n );\n this.variablePricesMap$ = variablePriceInputs$.pipe(\n map((variablePrices) => {\n const variablePricesMap = new Map();\n variablePrices\n .filter((vp) => !vp.packageID)\n .forEach((variablePrice: VariablePriceInputs) => {\n variablePricesMap.set(variablePrice.appPrice.appId, variablePrice);\n });\n return variablePricesMap;\n }),\n );\n this.variablePackagePricesMap$ = variablePriceInputs$.pipe(\n map((variablePrices) => {\n const variablePackagePricesMap = new Map();\n variablePrices\n .filter((vp) => vp.packageID)\n .forEach((variablePrice: VariablePriceInputs) => {\n let variablePriceMap = variablePackagePricesMap.get(variablePrice.packageID);\n if (!variablePriceMap) {\n variablePriceMap = new Map();\n variablePackagePricesMap.set(variablePrice.packageID, variablePriceMap);\n }\n variablePriceMap.set(variablePrice.appPrice.appId, variablePrice);\n });\n return variablePackagePricesMap;\n }),\n );\n }\n}\n\nfunction getAppIdsFromPackages(packages: Package[]): string[] {\n const appIDsFromPackages: string[] = [];\n packages.forEach((p) => {\n p.lineItems.lineItems.forEach((li) => {\n appIDsFromPackages.push(li.id);\n });\n });\n return appIDsFromPackages;\n}\n\nfunction convertAppIdsIntoAppKeys(appIds: string[]): AppKey[] {\n return appIds.map((ai) => {\n return new AppKey({\n appId: ai,\n });\n });\n}\n\nfunction findMatchingLineItem(lineItems: LineItem[], itemId: string): LineItem {\n return lineItems.find((li) => {\n if (li.appKey?.appId) {\n return li.appKey.appId === itemId;\n }\n return li.packageId === itemId;\n });\n}\n\nfunction findMatchingApp(appId: string, apps: App[]): App {\n return apps.find((a) => a.key.appId === appId);\n}\n\nfunction findMatchingPackage(appId: string, packages: Package[]): Package {\n return packages.find((p) => {\n return p.lineItems.lineItems.find((li) => {\n return li.id === appId;\n });\n });\n}\n", "import { CommonModule } from '@angular/common';\nimport { Component, Inject, OnInit } from '@angular/core';\nimport { FormsModule } from '@angular/forms';\nimport { MatButtonModule } from '@angular/material/button';\nimport { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog';\nimport { MatIconModule } from '@angular/material/icon';\nimport { TranslateModule } from '@ngx-translate/core';\nimport { GalaxyBadgeModule } from '@vendasta/galaxy/badge';\nimport { GalaxyConfirmationModalModule } from '@vendasta/galaxy/confirmation-modal';\nimport { GalaxyFormFieldModule } from '@vendasta/galaxy/form-field';\n\nexport interface CancelOrderDialogData {\n isAdmin: boolean;\n}\n\n@Component({\n templateUrl: './cancel-order-dialog.component.html',\n styleUrl: './cancel-order-dialog.component.scss',\n imports: [\n GalaxyConfirmationModalModule,\n CommonModule,\n MatButtonModule,\n MatIconModule,\n GalaxyFormFieldModule,\n GalaxyBadgeModule,\n FormsModule,\n TranslateModule,\n ],\n})\nexport class CancelOrderDialogComponent implements OnInit {\n private isAdmin: boolean;\n titleText: string;\n bodyText: string;\n labelText: string;\n\n cancellationReason: string;\n\n constructor(\n private dialogRef: MatDialogRef,\n @Inject(MAT_DIALOG_DATA) private data: CancelOrderDialogData,\n ) {\n this.isAdmin = this.data.isAdmin;\n }\n\n ngOnInit(): void {\n this.titleText = 'LIB_ORDERS.COMMON.ORDERS.DIALOGS.REQUEST_TO_CANCEL_ORDER.SALESPERSON.TITLE';\n if (this.isAdmin) {\n this.titleText = 'LIB_ORDERS.COMMON.ORDERS.DIALOGS.REQUEST_TO_CANCEL_ORDER.ADMIN.TITLE';\n }\n this.bodyText = 'LIB_ORDERS.COMMON.ORDERS.DIALOGS.REQUEST_TO_CANCEL_ORDER.SALESPERSON.MESSAGE';\n if (this.isAdmin) {\n this.bodyText = 'LIB_ORDERS.COMMON.ORDERS.DIALOGS.REQUEST_TO_CANCEL_ORDER.ADMIN.MESSAGE';\n }\n this.labelText = 'LIB_ORDERS.COMMON.ORDERS.DIALOGS.REQUEST_TO_CANCEL_ORDER.SALESPERSON.LABEL';\n if (this.isAdmin) {\n this.labelText = 'LIB_ORDERS.COMMON.ORDERS.DIALOGS.REQUEST_TO_CANCEL_ORDER.ADMIN.LABEL';\n }\n }\n\n onCancel(): void {\n this.dialogRef.close(this.cancellationReason);\n }\n\n onDontCancel(): void {\n this.dialogRef.close();\n }\n}\n", "\n warning\n {{ titleText | translate }}\n\n \n {{ bodyText | translate }}\n \n \n {{ labelText | translate }}\n {{\n 'LIB_ORDERS.COMMON.ORDERS.DIALOGS.REQUEST_TO_CANCEL_ORDER.REQUIRED' | translate\n }}\n \n\n\n
\n \n \n \n
\n\n\n \n \n \n \n\n", "import { CommonModule } from '@angular/common';\nimport { Component, inject } from '@angular/core';\nimport { FormsModule } from '@angular/forms';\nimport { MatButtonModule } from '@angular/material/button';\nimport { MatDialogRef } from '@angular/material/dialog';\nimport { MatIconModule } from '@angular/material/icon';\nimport { TranslateModule } from '@ngx-translate/core';\nimport { GalaxyBadgeModule } from '@vendasta/galaxy/badge';\nimport { GalaxyConfirmationModalModule } from '@vendasta/galaxy/confirmation-modal';\nimport { GalaxyFormFieldModule } from '@vendasta/galaxy/form-field';\n\nexport interface CancelOrderDialogData {\n isAdmin: boolean;\n}\n\n@Component({\n templateUrl: './decline-cancellation-dialog.component.html',\n styleUrl: './decline-cancellation-dialog.component.scss',\n imports: [\n GalaxyConfirmationModalModule,\n CommonModule,\n MatButtonModule,\n MatIconModule,\n GalaxyFormFieldModule,\n GalaxyBadgeModule,\n FormsModule,\n TranslateModule,\n ],\n})\nexport class DeclineCancellationDialogComponent {\n notes: string;\n\n private dialogRef = inject(MatDialogRef);\n\n onCancel(): void {\n this.dialogRef.close(this.notes);\n }\n\n onDontCancel(): void {\n this.dialogRef.close();\n }\n}\n", "\n info\n {{\n 'LIB_ORDERS.COMMON.ORDERS.DIALOGS.DECLINE_ORDER_CANCELLATION.TITLE' | translate\n }}\n\n \n {{ 'LIB_ORDERS.COMMON.ORDERS.DIALOGS.DECLINE_ORDER_CANCELLATION.MESSAGE' | translate }}\n \n \n {{ 'LIB_ORDERS.COMMON.ORDERS.DIALOGS.DECLINE_ORDER_CANCELLATION.LABEL' | translate }}\n {{\n 'LIB_ORDERS.COMMON.ORDERS.DIALOGS.DECLINE_ORDER_CANCELLATION.REQUIRED' | translate\n }}\n
\n \n \n \n
\n
\n {{ 'LIB_ORDERS.COMMON.ORDERS.DIALOGS.DECLINE_ORDER_CANCELLATION.SUBNOTES' | translate }}\n
\n
\n
\n\n\n \n \n \n \n\n", "import { Component, EventEmitter, Input, OnDestroy, OnInit, Output } from '@angular/core';\nimport {\n FormsModule,\n ReactiveFormsModule,\n UntypedFormBuilder,\n UntypedFormControl,\n UntypedFormGroup,\n Validators,\n} from '@angular/forms';\nimport { MatDialogModule, MatDialogRef } from '@angular/material/dialog';\nimport { Router, RouterModule } from '@angular/router';\nimport { FeatureFlagService } from '@galaxy/partner';\nimport { GalaxySnackbarModule, SnackbarService } from '@vendasta/galaxy/snackbar-service';\nimport {\n CommonField,\n CustomField,\n DeclinedReason,\n DeclinedReasonService,\n Field,\n SalesOrdersService,\n} from '@vendasta/sales-orders';\nimport { BehaviorSubject, combineLatest, Observable, ReplaySubject } from 'rxjs';\nimport { finalize, map, take, takeUntil } from 'rxjs/operators';\nimport { MatButtonModule } from '@angular/material/button';\nimport { GalaxyFormFieldModule } from '@vendasta/galaxy/form-field';\nimport { TranslateModule } from '@ngx-translate/core';\nimport { CommonModule } from '@angular/common';\nimport { MatInputModule } from '@angular/material/input';\nimport { MatProgressSpinnerModule } from '@angular/material/progress-spinner';\nimport { MatRadioModule } from '@angular/material/radio';\nimport {\n ConfirmationModalActionsDirective,\n ConfirmationModalIconDirective,\n ConfirmationModalMessageDirective,\n ConfirmationModalSecondaryActionsDirective,\n ConfirmationModalTitleDirective,\n ModalActionsComponent,\n ModalBodyComponent,\n} from '@vendasta/galaxy/confirmation-modal';\nimport { MatIcon } from '@angular/material/icon';\n\n@Component({\n templateUrl: 'order-decline-dialog.component.html',\n styleUrls: ['order-decline-dialog.component.scss'],\n imports: [\n CommonModule,\n FormsModule,\n ReactiveFormsModule,\n MatButtonModule,\n MatInputModule,\n MatDialogModule,\n MatProgressSpinnerModule,\n MatRadioModule,\n GalaxyFormFieldModule,\n TranslateModule,\n RouterModule,\n GalaxySnackbarModule,\n ModalActionsComponent,\n ConfirmationModalSecondaryActionsDirective,\n ConfirmationModalActionsDirective,\n ModalBodyComponent,\n ConfirmationModalTitleDirective,\n ConfirmationModalMessageDirective,\n ConfirmationModalIconDirective,\n MatIcon,\n ],\n})\nexport class OrderDeclineDialogComponent implements OnInit, OnDestroy {\n @Input() orderId: string;\n @Input() businessId: string;\n @Input() partnerId: string;\n @Input() title: string;\n @Input() details: string;\n\n @Input() commonFields?: CommonField[];\n @Input() customFields?: CustomField[];\n @Input() extraFields?: Field[];\n\n // eslint-disable-next-line @angular-eslint/no-output-native\n @Output() error: EventEmitter = new EventEmitter();\n\n form: UntypedFormGroup;\n\n formSubmitted$$: BehaviorSubject = new BehaviorSubject(false);\n declinedReasons$$: ReplaySubject = new ReplaySubject(1);\n loadingDeclinedReasons$$: BehaviorSubject = new BehaviorSubject(true);\n declinedReasonsApiError$$: BehaviorSubject = new BehaviorSubject(false);\n submitDisabled$: Observable;\n\n private destroyed$: ReplaySubject = new ReplaySubject(1);\n\n constructor(\n public dialogRef: MatDialogRef,\n private salesOrdersService: SalesOrdersService,\n private declinedReasonsService: DeclinedReasonService,\n private snackbarService: SnackbarService,\n private featureFlagService: FeatureFlagService,\n private fb: UntypedFormBuilder,\n private router: Router,\n ) {}\n\n ngOnInit(): void {\n this.router.events.pipe(takeUntil(this.destroyed$)).subscribe(() => {\n this.dialogRef.close();\n });\n\n this.loadDeclinedReasons();\n this.submitDisabled$ = combineLatest([\n this.formSubmitted$$,\n this.loadingDeclinedReasons$$,\n this.declinedReasonsApiError$$,\n ]).pipe(map(([formSubmitted, loadingDeclinedReasons, error]) => formSubmitted || loadingDeclinedReasons || error));\n\n this.declinedReasons$$.pipe(takeUntil(this.destroyed$)).subscribe((declinedReasons) => {\n this.form = this.fb.group({\n reasonId: new UntypedFormControl('', declinedReasons.length > 0 ? [Validators.required] : null),\n message: new UntypedFormControl('', declinedReasons.length === 0 ? [Validators.required] : null),\n });\n });\n }\n\n get formReasonId(): UntypedFormControl {\n return this.form.controls.reasonId as UntypedFormControl;\n }\n\n get formMessage(): UntypedFormControl {\n return this.form.controls.message as UntypedFormControl;\n }\n\n loadDeclinedReasons(): void {\n this.loadingDeclinedReasons$$.next(true);\n this.declinedReasonsApiError$$.next(false);\n\n this.declinedReasonsService\n .list(this.partnerId, null)\n .pipe(\n take(1),\n finalize(() => this.loadingDeclinedReasons$$.next(false)),\n )\n .subscribe(\n (declinedReasons) => this.declinedReasons$$.next(declinedReasons),\n () => this.declinedReasonsApiError$$.next(true),\n );\n }\n\n onDecline(): void {\n if (!this.form.valid) {\n return;\n }\n this.formSubmitted$$.next(true);\n this.salesOrdersService\n .decline(\n this.businessId,\n this.orderId,\n this.formMessage.value,\n this.formReasonId.value ? [this.formReasonId.value] : null,\n )\n .pipe(\n take(1),\n finalize(() => this.formSubmitted$$.next(false)),\n )\n .subscribe(\n (o) => this.dialogRef.close(o),\n (err) => {\n this.snackbarService.openErrorSnack('LIB_ORDERS.COMMON.ORDERS.DIALOGS.DECLINE_SUBMITTED_ORDER.ERROR_MESSAGE');\n this.error.emit(err);\n },\n );\n }\n\n ngOnDestroy(): void {\n this.destroyed$.next(true);\n this.destroyed$.complete();\n }\n}\n", "\n warning\n \n {{ title }}\n \n \n \n \n

{{ details }}

\n
\n \n 0\">\n \n {{\n 'LIB_ORDERS.COMMON.ORDERS.DIALOGS.DECLINE_SUBMITTED_ORDER.DECLINE_REASON.REASONS_TITLE' | translate\n }}\n \n {{ 'LIB_ORDERS.COMMON.ORDERS.DIALOGS.DECLINE_SUBMITTED_ORDER.DECLINE_REASON.REQUIRED' | translate }}\n \n \n \n {{ 'LIB_ORDERS.COMMON.ORDERS.DIALOGS.DECLINE_SUBMITTED_ORDER.DECLINE_REASON_VALIDATION' | translate }}\n \n \n \n {{ declineReason.reason }}\n \n \n \n \n \n \n {{\n 'LIB_ORDERS.COMMON.ORDERS.DIALOGS.DECLINE_SUBMITTED_ORDER.DECLINE_REASON.DECLINE_MESSAGE_TITLE'\n | translate\n }}\n \n \n \n {{ 'LIB_ORDERS.COMMON.ORDERS.DIALOGS.DECLINE_SUBMITTED_ORDER.DECLINE_MESSAGE_VALIDATION' | translate }}\n \n \n
\n
\n
\n
\n
\n\n \n \n \n \n \n \n \n\n\n
\n \n
\n
\n\n
\n
\n {{ 'LIB_ORDERS.COMMON.ORDERS.DIALOGS.DECLINE_SUBMITTED_ORDER.ERROR_MESSAGE' | translate }}\n
\n
\n \n
\n
\n
\n", "import { Component, Inject } from '@angular/core';\nimport { CommonModule } from '@angular/common';\nimport { MAT_DIALOG_DATA, MatDialogModule } from '@angular/material/dialog';\nimport { GalaxyConfirmationModalModule } from '@vendasta/galaxy/confirmation-modal';\nimport { TranslationModule } from '@galaxy/crm/static';\nimport { MatIconModule } from '@angular/material/icon';\nimport { MatButtonModule } from '@angular/material/button';\n\ninterface DialogData {\n date: Date;\n}\n\n@Component({\n selector: 'orders-invalid-billing-terms-dialog',\n standalone: true,\n imports: [\n CommonModule,\n GalaxyConfirmationModalModule,\n TranslationModule,\n MatIconModule,\n MatButtonModule,\n MatDialogModule,\n ],\n templateUrl: './invalid-billing-terms-dialog.component.html',\n styleUrl: './invalid-billing-terms-dialog.component.scss',\n})\nexport class InvalidBillingTermsDialogComponent {\n constructor(@Inject(MAT_DIALOG_DATA) public config: DialogData) {}\n}\n", "\n warning \n\n \n {{ 'LIB_ORDERS.COMMON.ORDERS.DIALOGS.INVALID_BILLING_TERMS.TITLE' | translate }}\n \n\n \n \n \n\n\n\n \n
\n \n
\n
\n
\n", "import { Injectable } from '@angular/core';\nimport { combineLatest, forkJoin, Observable, of } from 'rxjs';\nimport { LineItem, SalesOrdersService, ValidationErrorCodes } from '@vendasta/sales-orders';\nimport { distinctUntilChanged, map, shareReplay, switchMap, take } from 'rxjs/operators';\nimport { OrderStoreService } from '../../core/orders.service';\nimport { OrderPermissionsService } from '../../core/permissions/permissions.service';\nimport { OrderFeature } from '../../core/permissions';\nimport { TranslateService } from '@ngx-translate/core';\nimport { MatDialog, MatDialogConfig } from '@angular/material/dialog';\nimport { InvalidBillingTermsDialogComponent } from '../invalid-billing-terms-dialog/invalid-billing-terms-dialog.component';\nimport { ConfirmationModalMaxWidth, ConfirmationModalWidth } from '@vendasta/galaxy/confirmation-modal';\nimport * as e from '@vendasta/sales-orders/lib/_internal/enums';\nimport { AppKey, GetMultiAppRequest, PartnerApiService } from '@vendasta/marketplace-apps';\n\nconst validationErrorMessage: { [key in ValidationErrorCodes]?: string } = {\n [ValidationErrorCodes.ALREADY_ACTIVE]: 'LIB_ORDERS.SALES_ORDERS.WARNINGS.INVALID_LINE_ITEMS_BANNER_ALREADY_ACTIVE',\n [ValidationErrorCodes.INVALID_BILLING_TERMS]:\n 'LIB_ORDERS.SALES_ORDERS.WARNINGS.INVALID_LINE_ITEMS_BANNER_INVALID_BILLING_TERMS',\n};\n\ninterface LineItemValidation {\n errorCodes: ValidationErrorCodes[];\n errorCodesToLineItems?: Map;\n}\n\n@Injectable()\nexport class OrderLineItemValidationService {\n public invalidLineItemErrors$: Observable;\n public formattedLineItemErrors$: Observable;\n private areOrderBillingTermsValid$: Observable;\n\n public order$ = this.orderService.order$;\n constructor(\n private salesOrdersService: SalesOrdersService,\n private orderService: OrderStoreService,\n private orderPermissionsService: OrderPermissionsService,\n private translateService: TranslateService,\n private dialog: MatDialog,\n private readonly marketplaceAppsPartnerApiService: PartnerApiService,\n ) {\n const canValidateLineItems = this.orderPermissionsService.CanView(OrderFeature.InvalidLineItemsBanner);\n this.invalidLineItemErrors$ = combineLatest([\n this.order$.pipe(distinctUntilChanged()),\n canValidateLineItems.pipe(distinctUntilChanged()),\n ]).pipe(\n switchMap(([order, canValidate]) => {\n if (!canValidate) {\n return of({ errorCodes: [], errorCodesToLineItems: new Map() });\n }\n if (order?.orderId || order?.businessId) {\n return this.validateLineItems(order.businessId, order.orderId);\n }\n return of({ errorCodes: [], errorCodesToLineItems: new Map() });\n }),\n shareReplay({ refCount: true, bufferSize: 1 }),\n );\n\n const appNames$ = this.order$.pipe(\n switchMap((order) => {\n const appItems = order.lineItems.filter((item) => !item.packageId);\n if (!appItems.length) return of(new Map());\n\n const getMultiAppRequest = new GetMultiAppRequest({\n projectionFilter: {\n paths: ['sharedMarketingInformation'],\n },\n appKeys: appItems.map((item) => {\n return new AppKey({\n appId: item?.appKey?.appId,\n editionId: item?.appKey?.editionId ?? undefined,\n });\n }),\n partnerId: order.partnerId,\n marketId: order.marketId,\n includeNotEnabled: true,\n });\n\n return this.marketplaceAppsPartnerApiService.getMultiApp(getMultiAppRequest).pipe(\n map((response) => {\n return response.apps.reduce((map, app) => {\n if (!app.sharedMarketingInformation.editionName) {\n map.set(app.key?.appId, app.sharedMarketingInformation?.name || '');\n } else {\n map.set(\n app.key?.appId,\n `${app.sharedMarketingInformation?.name} | ${app.sharedMarketingInformation?.editionName}`,\n );\n }\n return map;\n }, new Map());\n }),\n );\n }),\n shareReplay({ refCount: true, bufferSize: 1 }),\n );\n\n this.formattedLineItemErrors$ = combineLatest([this.invalidLineItemErrors$, this.order$, appNames$]).pipe(\n switchMap(([errors, order, appNames]) => {\n return this.formatValidationErrorCodeForBanner(errors, appNames, order.requestedActivation, order.lineItems);\n }),\n );\n\n this.areOrderBillingTermsValid$ = this.invalidLineItemErrors$.pipe(\n distinctUntilChanged(),\n map((errors) => (errors ? !errors?.errorCodes?.includes(ValidationErrorCodes.INVALID_BILLING_TERMS) : true)),\n );\n }\n\n public validateLineItems(businessId: string, orderId: string): Observable {\n return this.salesOrdersService.validateLineItems(businessId, orderId).pipe(\n map((resp) => {\n const liValidation: LineItemValidation = {\n errorCodes: resp?.errorCodes || [],\n errorCodesToLineItems: new Map(),\n };\n if (resp?.lineItemErrors) {\n liValidation.errorCodesToLineItems = this.lineItemErrorsMapToMapOfErrorCodesToIndexes(resp.lineItemErrors);\n }\n return liValidation;\n }),\n );\n }\n\n /**\n * Converts a map of line item errors into a Map, where each key corresponds to an error code\n * and contains a list of line item indexes associated with that error.\n *\n * @param errorsMap - Map of line item indexes to arrays of validation error codes.\n * @returns - Map where each key represents an error code and stores corresponding line item indexes.\n */\n public lineItemErrorsMapToMapOfErrorCodesToIndexes(errorsMap: {\n [key: string]: e.ValidationErrorCodes[];\n }): Map {\n const errorsMapToArray = new Map();\n\n Object.keys(errorsMap).forEach((lineItemIndex) => {\n const errorCodes = errorsMap[lineItemIndex];\n errorCodes.forEach((errorCode) => {\n if (!errorsMapToArray.has(errorCode.toString())) {\n errorsMapToArray.set(errorCode.toString(), []);\n }\n errorsMapToArray.get(errorCode.toString()).push(lineItemIndex);\n });\n });\n\n return errorsMapToArray;\n }\n\n public formatValidationErrorCodeForBanner(\n liValidation: LineItemValidation,\n lineItemNames: Map,\n contractDate: Date,\n lineItems: LineItem[],\n ): Observable {\n if (!(liValidation?.errorCodes?.length > 0)) {\n return of([]);\n }\n return forkJoin(\n liValidation?.errorCodes.map((errorCode): Observable => {\n const names = [];\n const lineItemIndices = liValidation.errorCodesToLineItems.get(errorCode.toString()) || [];\n lineItemIndices.forEach((index) => {\n if (lineItems[index].packageId) {\n return;\n }\n names.push(lineItemNames.get(lineItems[index].appKey.appId));\n });\n const formattedLineItemNames = this.formatLineItemName(names);\n switch (errorCode) {\n case ValidationErrorCodes.ALREADY_ACTIVE:\n return this.translateService.get(validationErrorMessage[errorCode], {\n invalidLineItems: formattedLineItemNames,\n });\n case ValidationErrorCodes.INVALID_BILLING_TERMS:\n return this.translateService.get(validationErrorMessage[errorCode], {\n date: this.formatLineItemDate(contractDate),\n invalidLineItems: formattedLineItemNames,\n });\n default:\n return of('');\n }\n }),\n );\n }\n\n private formatLineItemName(lineItemNames: string[]): string {\n return lineItemNames?.length ? `: ${lineItemNames.join(', ')}` : '';\n }\n\n private formatLineItemDate(contractDate: Date): string {\n return Intl.DateTimeFormat('en', { month: 'short', day: 'numeric', year: 'numeric' }).format(contractDate);\n }\n\n public areBillingTermsValidOrOpenModal$() {\n return combineLatest([this.order$, this.areOrderBillingTermsValid$]).pipe(\n take(1),\n switchMap(([order, isValid]) => {\n if (isValid) {\n return of(true);\n }\n return this.dialog\n .open(InvalidBillingTermsDialogComponent, {\n width: ConfirmationModalWidth,\n maxWidth: ConfirmationModalMaxWidth,\n data: {\n date: this.formatLineItemDate(order.requestedActivation),\n },\n autoFocus: false,\n } as MatDialogConfig)\n .afterOpened()\n .pipe(map(() => false));\n }),\n );\n }\n}\n", "import { Injectable } from '@angular/core';\nimport { BehaviorSubject, Observable } from 'rxjs';\n\nexport const OVERVIEW_TAB_INDEX = 0;\nexport const ORDER_FORMS_TAB_INDEX = 1;\nexport const WORK_ORDERS_TAB_INDEX = 2;\n\n@Injectable({\n providedIn: 'root',\n})\nexport class OrdersEditTabService {\n private selectedTabIndex$$ = new BehaviorSubject(OVERVIEW_TAB_INDEX);\n\n get selectedTabIndex$(): Observable {\n return this.selectedTabIndex$$.asObservable();\n }\n\n setSelectedTabIndex(index: number): void {\n this.selectedTabIndex$$.next(index);\n }\n\n navigateToWorkOrdersTab(): void {\n this.selectedTabIndex$$.next(WORK_ORDERS_TAB_INDEX);\n }\n\n navigateToOrderFormsTab(): void {\n this.selectedTabIndex$$.next(ORDER_FORMS_TAB_INDEX);\n }\n}\n", "import { Injectable } from '@angular/core';\nimport { BehaviorSubject, map, Observable, take } from 'rxjs';\nimport { OpenConfirmationModalService } from '@vendasta/galaxy/confirmation-modal';\nimport { OrdersEditTabService } from './edit-order-tab.service';\n\n@Injectable({\n providedIn: 'root',\n})\nexport class OrdersEditOrderFormValidationService {\n private invalidForms$$: BehaviorSubject = new BehaviorSubject(false);\n private invalidForms$: Observable = this.invalidForms$$.asObservable();\n\n constructor(\n private confirmationModal: OpenConfirmationModalService,\n private tabService: OrdersEditTabService,\n ) {}\n\n setInvalidForms(invalidForms: boolean): void {\n this.invalidForms$$.next(invalidForms);\n }\n\n validateFormsForCharging(): Observable {\n return this.invalidForms$.pipe(\n take(1),\n map((invalidForms) => {\n if (invalidForms) {\n this.confirmationModal\n .openModal({\n title: 'LIB_ORDERS.SALES_ORDERS.INVALID_ORDER_FORMS_DIALOG.TITLE',\n message: 'LIB_ORDERS.SALES_ORDERS.INVALID_ORDER_FORMS_DIALOG.MESSAGE',\n confirmButtonText: 'LIB_ORDERS.SALES_ORDERS.INVALID_ORDER_FORMS_DIALOG.GO_TO_FORMS',\n cancelButtonText: 'LIB_ORDERS.COMMON.ACTION_LABELS.CLOSE',\n cancelOnEscapeKeyOrBackgroundClick: true,\n })\n .subscribe((goToForms) => {\n if (goToForms) {\n this.tabService.navigateToOrderFormsTab();\n }\n });\n return false;\n }\n return true;\n }),\n );\n }\n}\n", "/*\n * Format app type for display.\n *\n * Usage:\n * type | formatAppType\n * Example:\n * {{ 'APP_TYPE_APP' | formatAppType }}\n * formats to: 'Product'\n\n*/\nimport { Pipe, PipeTransform } from '@angular/core';\nimport { AppType } from '@vendasta/marketplace-apps';\n\n@Pipe({ standalone: true, name: 'formatAppType' })\nexport class FormatAppTypePipe implements PipeTransform {\n transform(appType: AppType): string {\n return getAppTypeString(appType);\n }\n}\n\nfunction getAppTypeString(appType: AppType): string {\n switch (appType) {\n case AppType.APP_TYPE_APP:\n return 'Product';\n case AppType.APP_TYPE_ADDON:\n return 'Add-on';\n default:\n return 'Not specified';\n }\n}\n", "import { Pipe, PipeTransform } from '@angular/core';\nimport { fulfillmentOrderStatusToString, WorkOrderPersona } from '@vendasta/work-order';\nimport { FulfillmentOrderStatus } from '@vendasta/order-fulfillment';\nimport { BadgeColor } from '@vendasta/galaxy/badge';\n\n@Pipe({\n standalone: true,\n name: 'getFulfillmentBadgeData',\n})\nexport class GetFulfillmentBadgeDataPipe implements PipeTransform {\n transform(\n fulfillmentStatus: FulfillmentOrderStatus,\n badgeColors: Map,\n persona: WorkOrderPersona,\n ): { color: string; status: string } {\n const statusString = fulfillmentOrderStatusToString(fulfillmentStatus, persona);\n const color: BadgeColor = badgeColors.get(fulfillmentStatus);\n return { color, status: statusString };\n }\n}\n", "import { Component, EventEmitter, Input, Output } from '@angular/core';\nimport { GalaxyAvatarModule } from '@vendasta/galaxy/avatar';\nimport { NgTemplateOutlet } from '@angular/common';\nimport { AppType } from '@vendasta/marketplace-apps';\nimport { FormatAppTypePipe } from './format-app-type.pipe';\nimport { ActivationStatus } from '@vendasta/sales-orders';\nimport { GalaxyBadgeModule } from '@vendasta/galaxy/badge';\nimport { GalaxyPopoverModule } from '@vendasta/galaxy/popover';\nimport { MatIcon } from '@angular/material/icon';\nimport { MatIconButton } from '@angular/material/button';\nimport { MatMenu, MatMenuItem, MatMenuTrigger } from '@angular/material/menu';\nimport { TranslateModule } from '@ngx-translate/core';\nimport { MenuItemClickedInterface, MenuItemInterface } from '../fulfillment-status.service';\nimport { FulfillmentOrderStatus } from '@vendasta/order-fulfillment';\nimport { statusColorMap, WorkOrderPersona } from '@vendasta/work-order';\nimport { GetFulfillmentBadgeDataPipe } from './get-fulfillment-badge-data.pipe';\n@Component({\n selector: 'orders-fulfillment-row',\n templateUrl: './fulfillment-row.component.html',\n styleUrls: ['./fulfillment-row.component.scss'],\n providers: [],\n imports: [\n GalaxyAvatarModule,\n FormatAppTypePipe,\n GalaxyBadgeModule,\n GalaxyPopoverModule,\n MatIcon,\n MatIconButton,\n MatMenu,\n MatMenuItem,\n TranslateModule,\n NgTemplateOutlet,\n MatMenuTrigger,\n GetFulfillmentBadgeDataPipe,\n ],\n})\nexport class FulfillmentRowComponent {\n @Input() name: string;\n @Input() iconUrl: string;\n @Input() type: AppType;\n @Input() parentName: string;\n @Input() activationStatus: ActivationStatus;\n @Input() activationErrorDetails: string;\n @Input() menuItems: MenuItemInterface[] = [];\n @Input() fulfillmentStatus: FulfillmentOrderStatus = 0;\n @Input() persona: WorkOrderPersona;\n @Output() menuItemClicked: EventEmitter = new EventEmitter();\n @Output() workOrdersLinkClicked: EventEmitter = new EventEmitter();\n\n public ActivationStatus = ActivationStatus;\n protected readonly badgeColors = statusColorMap.get(WorkOrderPersona.SALES);\n\n menuItemClick(item: MenuItemInterface): void {\n this.menuItemClicked.emit({ value: item.value, eventType: item.eventType });\n }\n\n handleWorkOrdersLinkClicked(): void {\n this.workOrdersLinkClicked.emit();\n }\n\n protected readonly FulfillmentOrderStatus = FulfillmentOrderStatus;\n}\n", "
\n \n
\n \n
\n @if (!!fulfillmentStatus) {\n \n {{ name }}\n \n } @else {\n
\n {{ name }}\n
\n }\n
\n @if ((type | formatAppType) === 'Add-on') {\n
Add-on to {{ parentName }}
\n } @else {\n
Product
\n }\n
\n
\n
\n\n \n
\n
\n @if (!!fulfillmentStatus) {\n @if (fulfillmentStatus | getFulfillmentBadgeData: badgeColors : persona; as badgeData) {\n \n {{ badgeData.status | translate }}\n \n }\n }\n @if (activationStatus === ActivationStatus.ACTIVATED) {\n {{ 'LIB_ORDERS.FULFILLMENT_STATUS.ACTIVATED' | translate }}\n } @else if (activationStatus === ActivationStatus.ERRORED) {\n \n {{ 'LIB_ORDERS.FULFILLMENT_STATUS.ERRORED' | translate }}\n \n } @else if (activationStatus === ActivationStatus.ALREADY_ACTIVATED) {\n {{ 'LIB_ORDERS.FULFILLMENT_STATUS.ALREADY_ACTIVATED' | translate }}\n } @else if (activationStatus === ActivationStatus.IGNORED_ERRORS) {\n {{ 'LIB_ORDERS.FULFILLMENT_STATUS.IGNORED_ERRORS' | translate }}\n } @else if (activationStatus === ActivationStatus.SCHEDULED) {\n {{ 'LIB_ORDERS.FULFILLMENT_STATUS.SCHEDULED' | translate }}\n } @else if (activationStatus === ActivationStatus.UNSCHEDULED) {\n {{ 'LIB_ORDERS.FULFILLMENT_STATUS.UNSCHEDULED' | translate }}\n } @else if (activationStatus !== ActivationStatus.CANCELED) {\n {{ 'LIB_ORDERS.FULFILLMENT_STATUS.PENDING' | translate }}\n }\n
\n\n \n
\n
\n\n\n
{{ activationErrorDetails }}
\n
\n\n\n
\n @if (menuItems && menuItems.length > 0) {\n \n \n @for (menuItem of menuItems; track menuItem) {\n \n }\n \n }\n
\n
\n", "import { Injectable } from '@angular/core';\nimport { GetMultiAppRequest } from '@galaxy/marketplace-apps';\nimport { catchError, map, take, tap } from 'rxjs/operators';\nimport { combineLatest, Observable, of } from 'rxjs';\nimport { App, AppKey, AppType, PartnerApiService } from '@vendasta/marketplace-apps';\nimport { ActivationStatus, ProductActivation, SalesOrdersService, Status } from '@vendasta/sales-orders';\nimport { ActivationFailureDetails } from '@vendasta/order-fulfillment/lib/_internal/objects';\nimport { FailureReason, FulfillmentOrder, OrderFulfillmentService } from '@vendasta/order-fulfillment';\nimport { OrderStoreService } from '../../core/orders.service';\nimport * as e from '@vendasta/order-fulfillment/lib/_internal/enums';\n\nexport interface FulfillmentRowData {\n name: string;\n iconUrl: string;\n type: AppType;\n parentName: string;\n activationStatus: ActivationStatus;\n failureDetails: string; // Only set if activation status is ERRORED\n menuItems?: MenuItemInterface[];\n fulfillmentStatus?: e.FulfillmentOrderStatus;\n}\n\nexport enum MenuItemEventType {\n IGNORE_ACTIVATION_ERRORS = 'ignore-errors',\n}\n\nexport interface MenuItemClickedInterface {\n value: string;\n eventType: MenuItemEventType;\n}\n\nexport interface MenuItemInterface {\n label: string;\n icon: string;\n value: string;\n eventType: MenuItemEventType;\n}\n\n@Injectable()\nexport class FulfillmentStatusService {\n private validationErrorMsgs = new Map([\n ['1', 'App Not Found'],\n ['2', 'No Order Form SubmissionID'],\n ['3', 'Order Form Not Found'],\n ['4', 'Order Form Already Used'],\n ['5', 'Order Form Not Supported'],\n ['6', 'Invalid Entry URL'],\n ['7', 'Entry URL Not Provided'],\n ['8', 'Invalid Dependency'],\n ['9', 'Already Active'],\n ['10', 'Edition Not Provided'],\n ['11', 'Editions Not Supported'],\n ['12', 'Invalid Edition'],\n ['13', 'Trial Not Supported'],\n ['14', 'Account Group Not Found'],\n ['15', 'No Country Information'],\n ['16', 'Unsupported Country'],\n ['17', 'Can Not Change Edition of Trial'],\n ['18', 'App Not Found'],\n ['20', 'No longer available for activation'],\n ]);\n\n constructor(\n private partnerApiService: PartnerApiService,\n private orderFulfillmentService: OrderFulfillmentService,\n private salesOrdersService: SalesOrdersService,\n private readonly orderStoreService: OrderStoreService,\n ) {}\n\n getRows(\n productActivations: ProductActivation[],\n workOrders: FulfillmentOrder[],\n partnerId: string,\n marketId: string,\n ): Observable {\n const appKeys = productActivations.map(\n (act) => new AppKey({ appId: act.productId, editionId: act.editionId || '' }),\n );\n\n const workOrdersMap = this.convertWorkOrdersArrayToMap(workOrders);\n const productActivationErrorsMap$ = this.getProductActivationErrorsMap(productActivations);\n\n const marketplaceApps$ = this.partnerApiService\n .getMultiApp(\n new GetMultiAppRequest({\n partnerId: partnerId,\n marketId: marketId,\n appKeys: appKeys,\n }),\n )\n .pipe(\n take(1),\n map((response) => response?.apps || ([] as App[])),\n catchError(() => of([] as App[])),\n );\n\n return combineLatest([productActivationErrorsMap$, marketplaceApps$, this.orderStoreService.order$]).pipe(\n take(1),\n map(([errorsMap, apps, order]) =>\n this.constructRows(productActivations, errorsMap, apps, workOrdersMap, order.status),\n ),\n );\n }\n\n ignoreErrors(orderId: string, businessId: string, productActivationUniqueId: string): Observable {\n return this.salesOrdersService\n .ignoreProductActivationError(orderId, businessId, productActivationUniqueId)\n .pipe(tap(() => this.orderStoreService.reloadOrder()));\n }\n\n private constructRows(\n productActivations: ProductActivation[],\n errorsMap: Map,\n apps: App[],\n workOrdersMap: Map,\n orderStatus: Status,\n ): FulfillmentRowData[] {\n return productActivations.map((act) => {\n const item = apps.find((app) => app?.key?.appId === act.productId);\n let failureDetails = '';\n let menuItems = [];\n let activationStatus = act.activationStatus;\n if (act.activationStatus === ActivationStatus.ERRORED && errorsMap.has(act.uniqueId)) {\n failureDetails = this.translateFailureDetails(errorsMap.get(act.uniqueId));\n menuItems = [\n {\n label: 'LIB_ORDERS.FULFILLMENT_STATUS.IGNORE_ERRORS_OPTION_LABEL',\n icon: '',\n value: act.uniqueId,\n eventType: MenuItemEventType.IGNORE_ACTIVATION_ERRORS,\n },\n ];\n }\n if (!activationStatus && (orderStatus === Status.CANCELLED || orderStatus === Status.ARCHIVED)) {\n activationStatus = ActivationStatus.CANCELED;\n }\n return {\n name: item?.sharedMarketingInformation?.name || act.productId,\n iconUrl: item?.sharedMarketingInformation?.iconUrl || '',\n type: item?.appType,\n parentName: item?.parentRequirements?.parentDetails?.name || '',\n activationStatus: activationStatus,\n failureDetails: failureDetails,\n menuItems: menuItems,\n fulfillmentStatus: workOrdersMap.get(act.productId)?.status || null,\n } as FulfillmentRowData;\n });\n }\n\n private getProductActivationErrorsMap(\n productActivations: ProductActivation[],\n ): Observable> {\n const orderItemIDs = productActivations\n .filter((act) => act.activationStatus === ActivationStatus.ERRORED)\n .map((act) => act.uniqueId);\n\n if (!orderItemIDs.length) {\n return of(new Map());\n }\n\n return this.orderFulfillmentService.getMultiActivationErrorsForOrderItems(orderItemIDs).pipe(\n take(1),\n catchError(() => of([] as ActivationFailureDetails[])),\n map((errorDetails) => this.createErrorDetailsMap(errorDetails)),\n );\n }\n\n private createErrorDetailsMap(errorDetails: ActivationFailureDetails[]): Map {\n return errorDetails.reduce((map, curr) => {\n map.set(curr.orderItemId, curr);\n return map;\n }, new Map());\n }\n\n private translateFailureDetails(activationFailureDetails: ActivationFailureDetails): string {\n switch (activationFailureDetails.failureReason) {\n case FailureReason.FAILURE_REASON_VALIDATION_ERROR:\n return this.translateValidationError(activationFailureDetails.details);\n case FailureReason.FAILURE_REASON_CHARGE_ERROR:\n return activationFailureDetails.details\n ? this.formattedChargeError(activationFailureDetails.details[0])\n : 'Payment could not be processed for this product';\n case FailureReason.FAILURE_REASON_VENDOR_REJECTED_ERROR:\n return activationFailureDetails.details ? activationFailureDetails.details[0] : 'Vendor rejected the order';\n case FailureReason.FAILURE_REASON_ACTIVATION_ERROR:\n return activationFailureDetails.details\n ? activationFailureDetails.details[0]\n : 'Product failed to activate. Contact Support for assistance.';\n }\n return activationFailureDetails.details ? activationFailureDetails.details[0] : '';\n }\n\n private translateValidationError(details: string[]): string {\n let failureMsg = '';\n details.forEach((detail) => {\n const validationMsg = this.validationErrorMsgs.has(detail) ? this.validationErrorMsgs.get(detail) : '';\n if (failureMsg === '') {\n failureMsg += validationMsg;\n } else {\n failureMsg += ' / ' + validationMsg;\n }\n });\n return failureMsg;\n }\n\n private formattedChargeError(details: string): string {\n const toCapitalize = details.split('_');\n toCapitalize.forEach((line) => {\n line.toUpperCase();\n });\n return toCapitalize.join(' ');\n }\n\n // Returns map of appID to work order.\n // There is a one to one mapping between appID and work order, unless the app is multi-activatable.\n // Multi-activatable apps in the same sales order share a single work order.\n private convertWorkOrdersArrayToMap(workOrders: FulfillmentOrder[]): Map {\n return workOrders.reduce((map, workOrder) => {\n map.set(workOrder.appId, workOrder);\n return map;\n }, new Map());\n }\n}\n", "import { Component, EventEmitter, Input, OnChanges, OnInit, Output, SimpleChanges } from '@angular/core';\nimport { GalaxyAvatarModule } from '@vendasta/galaxy/avatar';\nimport { AsyncPipe } from '@angular/common';\n\nimport { GalaxyBadgeModule } from '@vendasta/galaxy/badge';\nimport { AppType } from '@vendasta/marketplace-apps';\nimport { ActivationStatus, ProductActivation } from '@vendasta/sales-orders';\nimport { Observable } from 'rxjs';\n\nimport { FulfillmentRowComponent } from './fulfillment-row/fulfillment-row.component';\nimport { MatExpansionPanel, MatExpansionPanelHeader, MatExpansionPanelTitle } from '@angular/material/expansion';\nimport { TranslateModule } from '@ngx-translate/core';\nimport { MatDivider } from '@angular/material/divider';\nimport {\n FulfillmentRowData,\n FulfillmentStatusService,\n MenuItemClickedInterface,\n MenuItemEventType,\n} from './fulfillment-status.service';\nimport { map, take } from 'rxjs/operators';\nimport { FulfillmentOrder } from '@vendasta/order-fulfillment';\nimport { WorkOrderPersona } from '@vendasta/work-order';\n\n@Component({\n selector: 'orders-fulfillment-status-card',\n templateUrl: './fulfillment-status-card.component.html',\n styleUrls: ['./fulfillment-status-card.component.scss'],\n providers: [FulfillmentStatusService],\n imports: [\n GalaxyAvatarModule,\n GalaxyBadgeModule,\n AsyncPipe,\n FulfillmentRowComponent,\n MatExpansionPanelTitle,\n TranslateModule,\n MatExpansionPanel,\n MatExpansionPanelHeader,\n MatDivider,\n ],\n})\nexport class FulfillmentStatusCardComponent implements OnInit, OnChanges {\n @Input() productActivations: ProductActivation[];\n @Input() workOrders: FulfillmentOrder[];\n @Input() partnerId: string;\n @Input() businessId: string;\n @Input() marketId: string;\n @Input() orderId: string;\n @Input() persona: WorkOrderPersona;\n\n @Output() workOrdersLinkClicked: EventEmitter = new EventEmitter();\n\n rows$: Observable;\n shouldExpand$: Observable;\n\n protected readonly ActivationStatus = ActivationStatus;\n protected readonly AppType = AppType;\n\n constructor(private fulfillmentStatusService: FulfillmentStatusService) {}\n\n ngOnInit() {\n this.rows$ = this.fulfillmentStatusService.getRows(\n this.productActivations,\n this.workOrders,\n this.partnerId,\n this.marketId,\n );\n\n this.shouldExpand$ = this.rows$.pipe(\n map((rows) => rows.some((row) => row.activationStatus === ActivationStatus.ERRORED)),\n );\n }\n\n ngOnChanges(changes: SimpleChanges) {\n if (changes.productActivations || changes.workOrders || changes.partnerId || changes.marketId) {\n this.rows$ = this.fulfillmentStatusService.getRows(\n this.productActivations,\n this.workOrders,\n this.partnerId,\n this.marketId,\n );\n }\n }\n\n menuItemClick(event: MenuItemClickedInterface): void {\n if (event.eventType === MenuItemEventType.IGNORE_ACTIVATION_ERRORS) {\n this.fulfillmentStatusService.ignoreErrors(this.orderId, this.businessId, event.value).pipe(take(1)).subscribe();\n }\n }\n\n handleWorkOrdersLinkClicked(): void {\n this.workOrdersLinkClicked.emit();\n }\n}\n", "\n \n {{ 'LIB_ORDERS.FULFILLMENT_STATUS.TITLE' | translate }}\n \n @if (rows$ | async; as rows) {\n
\n @for (row of rows; track row; let index = $index) {\n
\n \n
\n @if (index < rows.length - 1) {\n \n }\n }\n
\n }\n
\n", "import { inject, Injectable, Injector } from '@angular/core';\nimport { combineLatest, EMPTY, merge, Observable, of, ReplaySubject, switchMap } from 'rxjs';\nimport { Customer } from '../submit-customer-dialog/submit-customer-dialog.component';\nimport {\n createCrmObjectModalProviders,\n CrmCreateCrmObjectModalComponent,\n CrmCreateCrmObjectModalResult,\n CrmFieldService,\n ListObjectTypeDialogData,\n ListObjectTypeDialogResult,\n PageAnalyticsInjectionToken,\n PlatformExtensionFieldIds,\n SelectObjectTypeModalComponent,\n StandardExternalIds,\n tableFiltersInjectionTokenGenerator,\n} from '@galaxy/crm/static';\nimport {\n Association,\n CRMApiService,\n CRMAssociationApiService,\n CrmObject,\n FilterGroupOperator,\n FilterOperator,\n ListAssociationsRequestInterface,\n} from '@vendasta/crm';\nimport { distinctUntilChanged, map, filter, take, shareReplay, catchError, tap, withLatestFrom } from 'rxjs/operators';\nimport { AccountGroupService, ProjectionFilter } from '@galaxy/account-group';\nimport { SnackbarService } from '@vendasta/galaxy/snackbar-service';\nimport { MatDialog } from '@angular/material/dialog';\nimport { VbcApiService } from '@galaxy/vbc';\nimport { TranslateService } from '@ngx-translate/core';\nimport { GalaxyFilterChipInjectionToken, GalaxyFilterOperator } from '@vendasta/galaxy/filter/chips';\nimport { FormControl, TouchedChangeEvent, Validators, ValueChangeEvent } from '@angular/forms';\nimport { OrderStoreService } from '../../core/orders.service';\nimport { Order } from '@vendasta/sales-orders';\nimport { CreateAndAssociateRequest, UserService } from '@vendasta/business-center';\n\nexport enum AssociatedUserIDType {\n subjectID,\n userID,\n}\n\n@Injectable()\nexport class AssociateUserToOrderService {\n private companyId$: Observable;\n public customers$: Observable;\n\n private readonly associateContact$$ = new ReplaySubject(1);\n private readonly associateContact$ = this.associateContact$$.asObservable();\n\n private readonly order$: Observable;\n private readonly fieldService = inject(CrmFieldService);\n\n //TODO: Check if two instances of form can be controlled by one reference the service\n readonly customersControl = new FormControl(null, [Validators.required]);\n\n public isCustomerControlInvalid$: Observable;\n\n constructor(\n private readonly accountGroupService: AccountGroupService,\n private readonly crmAPIService: CRMApiService,\n private readonly crmAssociationApiService: CRMAssociationApiService,\n private readonly snackbarService: SnackbarService,\n private readonly dialog: MatDialog,\n private readonly vbcApiService: VbcApiService,\n private readonly injector: Injector,\n private readonly translate: TranslateService,\n private readonly orderStoreService: OrderStoreService,\n private readonly userService: UserService,\n ) {\n this.order$ = orderStoreService.order$.pipe(distinctUntilChanged());\n this.isCustomerControlInvalid$ = this.customersControl.events.pipe(\n filter((event) => event instanceof TouchedChangeEvent || event instanceof ValueChangeEvent),\n map(() => {\n return this.isCustomerFormInvalid() && this.customersControl.touched;\n }),\n shareReplay({ bufferSize: 1, refCount: true }),\n );\n\n this.companyId$ = this.order$.pipe(\n switchMap((order) =>\n this.accountGroupService.get(order.businessId, new ProjectionFilter({ accountGroupExternalIdentifiers: true })),\n ),\n map((accountGroup) => accountGroup?.externalIdentifiers['platformCrmCompanyId'] ?? ''),\n shareReplay({ bufferSize: 1, refCount: true }),\n );\n\n const contacts$ = this.retrieveCRMContactsAssociatedWithAccount$();\n const contactCreated$ = combineLatest([contacts$, this.setupContactCreated$()]).pipe(\n map(([contacts, newContact]) => [...contacts, newContact]),\n );\n\n this.customers$ = merge(contacts$.pipe(distinctUntilChanged()), contactCreated$.pipe(distinctUntilChanged()));\n }\n\n retrieveCRMContactsAssociatedWithAccount$(): Observable {\n const contactAssociations$ = combineLatest([this.order$, this.companyId$]).pipe(\n switchMap(([order, id]) => {\n return this.crmAssociationApiService.listAssociation({\n namespace: order.partnerId,\n pagingOptions: {\n pageSize: 1000,\n },\n filtersV2: {\n operator: FilterGroupOperator.FILTER_GROUP_OPERATOR_AND,\n filters: [\n {\n fieldId: 'to_id',\n operator: FilterOperator.FILTER_OPERATOR_IS,\n values: [\n {\n string: id,\n },\n ],\n },\n {\n fieldId: 'association_type',\n operator: FilterOperator.FILTER_OPERATOR_IS,\n values: [\n {\n string: 'Contact-Company',\n },\n ],\n },\n ],\n },\n } as ListAssociationsRequestInterface);\n }),\n map((resp) => resp?.associations?.map((r) => r.fromId) || []),\n );\n\n const customers: Observable = combineLatest([this.order$, contactAssociations$]).pipe(\n switchMap(([order, ids]) => {\n if (ids?.length === 0) {\n return of([]);\n }\n return this.crmAPIService\n .getMultiContact({\n namespace: order.partnerId,\n crmObjectIds: ids,\n })\n .pipe(map((res) => this.convertContactsToCustomers(res.crmObjects)));\n }),\n );\n\n return combineLatest([this.order$, customers]).pipe(\n //Set the default value of the input field if default billing recipient exists\n tap(([order, customers]) => {\n if (\n order?.customerRecipient?.userId &&\n this.customersControl.value?.userId !== order?.customerRecipient?.userId\n ) {\n customers.forEach((customer) => {\n if (customer?.userId === order?.customerRecipient?.userId) {\n this.customersControl.patchValue(customer, { onlySelf: true });\n this.customersControl.markAsTouched();\n\n this.customersControl.updateValueAndValidity();\n return;\n }\n });\n }\n }),\n map(([_, customers]) => {\n return customers;\n }),\n );\n }\n\n private setupContactCreated$(): Observable {\n const contactId$ = this.associateContact$.pipe(\n distinctUntilChanged(),\n withLatestFrom(this.companyId$, this.order$),\n filter(([contactId, companyId, order]: [string, string, Order]) => !!contactId && !!companyId && !!order),\n switchMap(([contactId, companyId, order]: [string, string, Order]) => {\n const association = {\n associationType: 'Contact-Company',\n fromId: contactId,\n toId: companyId,\n tags: [],\n };\n\n return this.crmAssociationApiService\n .createAssociation({\n namespace: order.partnerId,\n association: association,\n })\n .pipe(\n map(() => contactId),\n catchError(() => {\n this.snackbarService.openErrorSnack('SALES_ORDERS.SUBMIT_ORDER_FOR_APPROVAL.ASSOCIATION_FAILED');\n return of(undefined);\n }),\n );\n }),\n filter((contactId) => !!contactId),\n );\n\n return combineLatest([this.order$, contactId$]).pipe(\n switchMap(([order, id]) =>\n this.crmAPIService\n .getMultiContact({\n namespace: order.partnerId,\n crmObjectIds: [id],\n })\n .pipe(\n map((res) => res.crmObjects),\n map((contacts) => this.convertContactsToCustomers(contacts)),\n map((customers) => customers[0]),\n ),\n ),\n tap((newCustomer) => {\n this.customersControl.patchValue(newCustomer);\n this.customersControl.updateValueAndValidity();\n }),\n );\n }\n\n /*\n createVBCUserFromCustomer$ creates a user from the customer email and name of the input field\n userIDType: identifies if the returned value is the subjectID or userID of the created user\n */\n public createVBCUserFromCustomer$(userIDType: AssociatedUserIDType): Observable {\n return this.order$.pipe(\n switchMap((order) => {\n if (this.isCustomerFormInvalid()) {\n this.customersControl.markAsTouched();\n return EMPTY;\n }\n const customer = this.customersControl.value;\n\n return this.userService\n .createAndAssociate({\n email: customer?.email?.toLowerCase(),\n accountGroupId: order.businessId,\n partnerId: order.partnerId,\n firstName: customer?.firstName,\n lastName: customer?.lastName,\n sendWelcomeEmail: true,\n failOnExistingUser: false,\n failOnExistingAssociation: false,\n } as CreateAndAssociateRequest)\n .pipe(\n filter((user) => !!user?.userId),\n switchMap((user) => {\n if (userIDType === AssociatedUserIDType.subjectID) {\n return of(user?.userId);\n } else if (userIDType === AssociatedUserIDType.userID) {\n return of(user?.unifiedUserId);\n }\n }),\n );\n }),\n map((userId) => {\n const customer = this.customersControl.value;\n const updatedCustomer: Customer = JSON.parse(JSON.stringify(customer));\n updatedCustomer.userId = userId;\n\n return updatedCustomer;\n }),\n );\n }\n\n private openAssociateContactModal(): void {\n const dialogRef = this.dialog.open(SelectObjectTypeModalComponent, {\n data: {\n baseColumnIds: [],\n modalTitle: this.translate.instant(\n 'SALES_ORDERS.SUBMIT_ORDER_FOR_APPROVAL.ASSOCIATE_RECIPIENT_WITH_OWNERSHIP_ENTITY',\n ),\n objectType: 'Contact',\n associations: [],\n hideCreateButton: false,\n enableMultiplePreSelectedRows: false,\n singleSelection: true,\n filters: [\n {\n fieldId: this.fieldService.getFieldId(StandardExternalIds.Email),\n filterId: 'filterContactByEmail',\n operator: GalaxyFilterOperator.FILTER_OPERATOR_IS_NOT_EMPTY,\n },\n ],\n } as ListObjectTypeDialogData,\n injector: Injector.create({\n providers: [\n {\n provide: PageAnalyticsInjectionToken,\n useFactory: () => {\n const deps = {\n trackEvent(): void {\n return;\n },\n };\n return deps;\n },\n },\n {\n provide: GalaxyFilterChipInjectionToken,\n useFactory: tableFiltersInjectionTokenGenerator('Contact'),\n },\n ],\n parent: this.injector,\n }),\n });\n\n dialogRef\n .afterClosed()\n .pipe(take(1))\n .subscribe((result: ListObjectTypeDialogResult) => {\n if (result?.clickedCreate) {\n return this.openCreateContactModal();\n }\n\n if (result?.selectedRows.length > 0) {\n const id = result.selectedRows[0].id;\n this.associateContact$$.next(id);\n }\n });\n }\n\n private openCreateContactModal(): void {\n const ref = this.dialog.open(CrmCreateCrmObjectModalComponent, {\n data: {\n objectType: 'Contact',\n },\n injector: createCrmObjectModalProviders('Contact', this.injector),\n width: '500px',\n });\n ref\n .afterClosed()\n .pipe(take(1))\n .subscribe({\n next: (contact: CrmCreateCrmObjectModalResult) => {\n this.associateContact$$.next(contact.crmObject.crmObjectId);\n },\n });\n }\n\n public addCRMContact(): void {\n this.openAssociateContactModal();\n }\n\n public isCustomerFormInvalid() {\n return this.customersControl.invalid || !this.customersControl.value?.email;\n }\n\n private convertContactsToCustomers(contacts: CrmObject[]): Customer[] {\n return contacts\n .filter((c) => JSON.stringify(c) !== '{}')\n .map((contact: CrmObject): Customer => {\n const userId =\n contact?.fields?.find((field) => field.fieldId === PlatformExtensionFieldIds.UserID)?.stringValue ?? '';\n const firstName =\n contact?.fields?.find((field) => field.externalId === StandardExternalIds.FirstName)?.stringValue ?? '';\n const lastName =\n contact?.fields?.find((field) => field.externalId === StandardExternalIds.LastName)?.stringValue ?? '';\n const email =\n contact?.fields?.find((field) => field.externalId === StandardExternalIds.Email)?.stringValue ?? '';\n\n return {\n userId: userId,\n firstName: firstName,\n lastName: lastName,\n email: email,\n };\n });\n }\n}\n", "import { CommonModule } from '@angular/common';\nimport { Component } from '@angular/core';\nimport { TranslateModule } from '@ngx-translate/core';\nimport { GalaxyFormFieldModule } from '@vendasta/galaxy/form-field';\nimport { MatSelectModule } from '@angular/material/select';\nimport { FormControl, ReactiveFormsModule } from '@angular/forms';\nimport { Observable } from 'rxjs';\n\nimport { Customer } from '../submit-customer-dialog/submit-customer-dialog.component';\n\nimport { MatButton } from '@angular/material/button';\n\nimport { AssociateUserToOrderService } from './associate-user-to-order.service';\n@Component({\n selector: 'orders-associate-user-to-order',\n imports: [CommonModule, GalaxyFormFieldModule, MatSelectModule, TranslateModule, ReactiveFormsModule, MatButton],\n templateUrl: './associate-user-to-order.component.html',\n styleUrl: './associate-user-to-order.component.scss',\n})\nexport class AssociateUserToOrderComponent {\n customersControl: FormControl;\n public customers$: Observable;\n protected isCustomerControlInvalid$: Observable;\n\n constructor(private readonly associateUserToOrderService: AssociateUserToOrderService) {\n this.customersControl = this.associateUserToOrderService.customersControl;\n this.customers$ = this.associateUserToOrderService.customers$;\n this.isCustomerControlInvalid$ = this.associateUserToOrderService.isCustomerControlInvalid$;\n this.customersControl.markAsUntouched();\n }\n\n protected addCRMContact() {\n this.associateUserToOrderService.addCRMContact();\n }\n protected compareCustomer(customer1: Customer, customer2: Customer) {\n return (\n customer1.email === customer2.email &&\n customer1.userId === customer2.userId &&\n customer1.firstName === customer2.firstName &&\n customer1.lastName === customer2.lastName\n );\n }\n}\n", "@if (customers$ | async; as customers) {\n
\n
\n @if (customers?.length > 0) {\n \n \n {{ 'SALES_ORDERS.SUBMIT_ORDER_FOR_APPROVAL.SELECT_FOR_ASSOCIATION' | translate }}\n \n \n @for (customer of customers; track customer.email) {\n \n \n \n }\n \n \n \n \n @if (isCustomerControlInvalid$ | async) {\n \n {{ 'SALES_ORDERS.SUBMIT_ORDER_FOR_APPROVAL.CONTACT_REQUIRES_EMAIL' | translate }}\n \n }\n \n } @else {\n \n \n {{ 'SALES_ORDERS.SUBMIT_ORDER_FOR_APPROVAL.SELECT_FOR_ASSOCIATION' | translate }}\n \n \n \n }\n
\n 0,\n 'associate-user-button-empty-spacing': customers?.length === 0,\n }\"\n >\n \n
\n \n} @else {\n
\n}\n\n\n @if (customer) {\n
\n {{ customer.firstName }} {{ customer.lastName }}\n @if (customer.email) {\n ({{ customer.email }})\n }\n
\n }\n
\n", "import {\n AfterViewInit,\n ChangeDetectorRef,\n Component,\n ElementRef,\n Inject,\n OnDestroy,\n OnInit,\n ViewChild,\n} from '@angular/core';\nimport { FormsModule, ReactiveFormsModule, UntypedFormControl, Validators } from '@angular/forms';\nimport { MAT_DIALOG_DATA, MatDialogModule, MatDialogRef } from '@angular/material/dialog';\nimport { Intent, PaymentMethod, PaymentMethodService, PaymentMethodType, PaymentService } from '@galaxy/billing';\nimport { SnackbarService } from '@vendasta/galaxy/snackbar-service';\nimport { from, Observable, of, ReplaySubject } from 'rxjs';\nimport { catchError, finalize, map, switchMap, take } from 'rxjs/operators';\nimport { SelectedPaymentMethod, SelectPaymentMethodDialogData } from '../payment-method';\nimport { MatRadioModule } from '@angular/material/radio';\nimport { AsyncPipe, NgClass, NgForOf, NgIf } from '@angular/common';\nimport { MatButtonModule } from '@angular/material/button';\nimport { GalaxyBadgeModule } from '@vendasta/galaxy/badge';\nimport { TranslateModule, TranslateService } from '@ngx-translate/core';\nimport { MatIconModule } from '@angular/material/icon';\nimport { MatProgressSpinnerModule } from '@angular/material/progress-spinner';\nimport {\n KeysPipe,\n PaymentMethodDisplayComponent,\n PaymentTypeToTitlePipe,\n ScriptLoaderService,\n ScriptModel,\n} from '@vendasta/smb-invoicing';\nimport { MatDividerModule } from '@angular/material/divider';\nimport { GalaxyFormFieldModule } from '@vendasta/galaxy/form-field';\nimport { MatInputModule } from '@angular/material/input';\nimport { MatSelectModule } from '@angular/material/select';\nimport { UserService } from '@vendasta/business-center';\nimport { CustomerRecipientInterface, Order } from '@vendasta/sales-orders';\nimport { OrderStoreService } from '../../../../core/orders.service';\nimport { IAMService, UserIdentifier } from '@vendasta/iamv2';\nimport {\n AssociatedUserIDType,\n AssociateUserToOrderService,\n} from '../../../associate-user-to-order/associate-user-to-order.service';\nimport { AssociateUserToOrderComponent } from '../../../associate-user-to-order/associate-user-to-order.component';\n\ndeclare let Stripe; // : stripe.StripeStatic;\n\ninterface UserDisplay {\n userId: string;\n displayName: string;\n}\n\n@Component({\n imports: [\n NgIf,\n NgForOf,\n NgClass,\n AsyncPipe,\n FormsModule,\n ReactiveFormsModule,\n MatDialogModule,\n MatRadioModule,\n MatButtonModule,\n MatIconModule,\n MatDividerModule,\n MatProgressSpinnerModule,\n GalaxyBadgeModule,\n PaymentMethodDisplayComponent,\n TranslateModule,\n PaymentTypeToTitlePipe,\n KeysPipe,\n MatInputModule,\n MatSelectModule,\n GalaxyFormFieldModule,\n AssociateUserToOrderComponent,\n ],\n providers: [TranslateService],\n templateUrl: './orders-select-payment-method-dialog.component.html',\n styleUrls: ['orders-select-payment-method-dialog.component.scss'],\n})\nexport class OrdersSelectPaymentMethodDialogComponent implements OnInit, AfterViewInit, OnDestroy {\n PaymentMethodType = PaymentMethodType;\n\n @ViewChild('cardElement') cardElement: ElementRef;\n\n accountId: string;\n merchantId: string;\n customerId: string;\n userId: string;\n stripeKey: string;\n submitDialogText: string;\n defaultPaymentMethod: string;\n hideExistingMethods: boolean;\n orderCustomerRecipient: CustomerRecipientInterface;\n defaultCustomerRecipientId: string;\n\n order$: Observable;\n\n paymentMethod: UntypedFormControl;\n customerRecipientForm: UntypedFormControl;\n isAddingMethod$$ = new ReplaySubject(1);\n scriptModel$: Observable;\n intent$: Observable;\n paymentMethods$$ = new ReplaySubject>();\n paymentMethods$ = this.paymentMethods$$.asObservable();\n selectedMethod$$ = new ReplaySubject();\n defaultBillingUser$: Observable;\n stripe;\n card;\n error: string;\n cardHandler = this.onChange.bind(this);\n\n constructor(\n private changeDetector: ChangeDetectorRef,\n private stripeScriptLoader: ScriptLoaderService,\n private paymentService: PaymentService,\n private paymentMethodService: PaymentMethodService,\n public dialogRef: MatDialogRef,\n private alertService: SnackbarService,\n @Inject(MAT_DIALOG_DATA) public data: SelectPaymentMethodDialogData,\n private translateService: TranslateService,\n private readonly userService: UserService,\n private readonly orderStoreService: OrderStoreService,\n private readonly iamService: IAMService,\n private readonly associateUserToOrderService: AssociateUserToOrderService,\n ) {\n this.accountId = data?.accountId;\n this.merchantId = data?.merchantId;\n this.customerId = data?.customerId;\n this.userId = data?.userId;\n this.stripeKey = data?.stripeKey;\n this.submitDialogText = data?.submitDialogText;\n this.paymentMethods$$.next(data?.paymentMethods);\n this.isAddingMethod$$.next(false);\n this.hideExistingMethods = data?.hideExistingMethods ?? false;\n this.orderCustomerRecipient = data?.orderCustomerRecipient;\n this.defaultCustomerRecipientId = data?.defaultCustomerRecipientId;\n this.defaultPaymentMethod = data?.defaultPaymentMethod;\n\n this.order$ = this.orderStoreService.order$;\n if (!this.defaultPaymentMethod) {\n // assume the first entry in the map is the default payment method\n const paymentMethodsMap = data?.paymentMethods;\n if (paymentMethodsMap && paymentMethodsMap.size > 0) {\n const firstEntry = paymentMethodsMap.values().next().value;\n if (firstEntry && firstEntry.length > 0) {\n this.defaultPaymentMethod = firstEntry[0]?.details?.id || '';\n }\n }\n }\n this.customerRecipientForm = this.associateUserToOrderService.customersControl;\n\n this.defaultBillingUser$ = of(this.defaultCustomerRecipientId).pipe(\n switchMap((defaultCustomerRecipientId) => {\n if (!defaultCustomerRecipientId) {\n return of(null);\n }\n return this.iamService.getUser(new UserIdentifier({ userId: defaultCustomerRecipientId })).pipe(\n map((user) => {\n return {\n userId: user?.userId,\n displayName: `${user?.firstName || ''} ${user?.lastName || ''} (${user?.email || ''})`.trim(),\n } as UserDisplay;\n }),\n );\n }),\n );\n }\n\n ngOnInit(): void {\n this.scriptModel$ = this.stripeScriptLoader.load();\n this.selectedMethod$$.next(this.defaultPaymentMethod);\n this.paymentMethod = new UntypedFormControl(\n this.defaultPaymentMethod ? this.defaultPaymentMethod : null,\n Validators.required,\n );\n this.paymentMethod.markAsTouched();\n this.intent$ = this.paymentService.prepareRetailPayment(this.merchantId, this.customerId);\n\n if (!this.submitDialogText) {\n this.submitDialogText = this.translateService.instant('PAYMENT_METHOD_SELECTOR.SAVE');\n }\n }\n\n ngOnDestroy(): void {\n this.card.removeEventListener('change', this.cardHandler);\n this.card.destroy();\n }\n\n ngAfterViewInit(): void {\n this.scriptModel$.pipe(take(1)).subscribe(() => {\n this.stripe = Stripe(this.stripeKey, {\n stripeAccount: this.accountId,\n });\n const elements = this.stripe.elements();\n this.card = elements.create('card');\n this.card.mount(this.cardElement.nativeElement);\n this.card.addEventListener('change', this.cardHandler);\n });\n }\n\n selectMethod(paymentMethodId: string): void {\n this.selectedMethod$$.next(paymentMethodId);\n }\n\n save(): void {\n if (!this.defaultCustomerRecipientId && this.customerRecipientForm.invalid) {\n this.customerRecipientForm.markAsTouched();\n return;\n }\n\n if (this.paymentMethod?.value) {\n if (!this.checkUserUpdated()) {\n this.dialogRef.close({\n paymentMethodId: this.paymentMethod.value,\n newMethod: false,\n userId: undefined,\n });\n } else {\n this.associateUserToOrderService\n .createVBCUserFromCustomer$(AssociatedUserIDType.userID)\n .pipe(take(1))\n .subscribe({\n next: (user) =>\n this.dialogRef.close({\n paymentMethodId: this.paymentMethod.value,\n newMethod: false,\n userId: user.userId,\n }),\n });\n }\n } else {\n this.addPaymentMethod();\n }\n }\n\n onChange({ error }: any): void {\n this.error = error?.message ? error.message : '';\n this.changeDetector.detectChanges();\n }\n\n addPaymentMethod(): void {\n this.isAddingMethod$$.next(true);\n this.intent$.pipe(take(1)).subscribe((intent) => this.confirmCard(intent.stripeClientSecret));\n }\n\n handleConfirmCardError(error): void {\n const stripeErrorCodeMessage = {\n incorrect_number: 'PAYMENT_METHOD_SELECTOR.ERROR_INCORRECT_NUMBER',\n card_declined: 'PAYMENT_METHOD_SELECTOR.ERROR_CARD_DECLINED',\n };\n const errorMessage = stripeErrorCodeMessage[error?.code] || 'PAYMENT_METHOD_SELECTOR.ERROR_ADDING_PAYMENT_METHOD';\n this.alertService.openErrorSnack(errorMessage);\n }\n\n confirmCard(clientSecret: string): void {\n const data = {\n payment_method: {\n card: this.card,\n },\n };\n if (this.userId) {\n data.payment_method['metadata'] = {\n userID: this.userId,\n };\n }\n from(this.stripe.confirmCardSetup(clientSecret, data))\n .pipe(\n map((response: any) => {\n if (response?.error) {\n throw response?.error;\n }\n return response;\n }),\n catchError((err) => {\n this.handleConfirmCardError(err);\n throw err;\n }),\n take(1),\n finalize(() => this.isAddingMethod$$.next(false)),\n )\n .subscribe((response) =>\n this.dialogRef.close({\n paymentMethodId: response?.setupIntent?.payment_method,\n newMethod: true,\n userId: this.checkUserUpdated() ? this.customerRecipientForm.value : undefined,\n }),\n );\n }\n\n // Checks if the current order recipient is different from selected on modal\n checkUserUpdated(): boolean {\n return (\n !this.defaultCustomerRecipientId &&\n !!this.customerRecipientForm.value?.email &&\n this.orderCustomerRecipient?.userId !== this.customerRecipientForm.value?.userId\n );\n }\n}\n", "

{{ 'PAYMENT_METHOD_SELECTOR.PAYMENT_METHODS' | translate }}

\n\n \n
\n \n {{ 'LIB_ORDERS.COMMON.ORDERS.DIALOGS.ORDERS_SELECT_PAYMENT_METHOD.BILLING_RECIPIENT' | translate }}\n \n {{ 'LIB_ORDERS.COMMON.ORDERS.DIALOGS.ORDERS_SELECT_PAYMENT_METHOD.BILLING_RECIPIENT_HINT' | translate }}\n \n @if (defaultBillingUser$ | async; as user) {\n \n } @else {\n \n }\n \n
\n\n \n \n 0)\">\n {{ 'PAYMENT_METHOD_SELECTOR.NO_PAYMENT_METHODS' | translate }}\n \n \n\n \n

{{ type | paymentTypeToTitle }}

\n \n \n \n
\n \n \n \n @if (paymentMethod?.expired) {\n \n {{ 'PAYMENT_METHOD_SELECTOR.EXPIRED' | translate }}\n \n } @else if (paymentMethod?.default) {\n \n {{ 'PAYMENT_METHOD_SELECTOR.DEFAULT' | translate }}\n \n }\n \n \n
\n \n
\n
\n
\n
\n
\n

{{ 'PAYMENT_METHOD_SELECTOR.NEW_PAYMENT_METHOD' | translate }}

\n \n add_card\n {{ 'PAYMENT_METHOD_SELECTOR.ADD_CARD' | translate }}\n \n
\n
\n \n
\n
 {{ error }}
\n
\n
\n
\n\n \n \n\n\n\n \n\n", "import { Injectable } from '@angular/core';\nimport { BehaviorSubject, Observable } from 'rxjs';\n\n@Injectable({\n providedIn: 'root',\n})\nexport class OrdersPaymentMethodService {\n private paymentMethodSubject$$: BehaviorSubject = new BehaviorSubject(null);\n private shouldChargeOrderSubject$$: BehaviorSubject = new BehaviorSubject(false);\n\n get paymentMethod$(): Observable {\n return this.paymentMethodSubject$$.asObservable();\n }\n\n get shouldChargeOrder$(): Observable {\n return this.shouldChargeOrderSubject$$.asObservable();\n }\n\n setPaymentMethod(paymentMethod: string | null): void {\n this.paymentMethodSubject$$.next(paymentMethod);\n }\n\n setShouldChargeOrder(shouldCharge: boolean): void {\n this.shouldChargeOrderSubject$$.next(shouldCharge);\n }\n}\n", "import { CommonModule } from '@angular/common';\nimport { Component } from '@angular/core';\nimport { MatButtonModule } from '@angular/material/button';\nimport { MatDialogModule, MatDialogRef } from '@angular/material/dialog';\nimport { MatCheckboxModule } from '@angular/material/checkbox';\nimport { TranslateModule } from '@ngx-translate/core';\nimport { FormsModule } from '@angular/forms';\nimport { MatIconModule } from '@angular/material/icon';\nimport { GalaxyConfirmationModalModule } from '@vendasta/galaxy/confirmation-modal';\n\n@Component({\n templateUrl: './submit-for-charge-dialog.component.html',\n styleUrls: ['./submit-for-charge-dialog.component.scss'],\n imports: [\n CommonModule,\n TranslateModule,\n MatDialogModule,\n MatCheckboxModule,\n MatButtonModule,\n FormsModule,\n MatIconModule,\n GalaxyConfirmationModalModule,\n ],\n})\nexport class SubmitForChargeDialogComponent {\n readonly accountGroupId: string;\n readonly partnerId: string;\n\n confirmCharge = false;\n\n constructor(public dialogRef: MatDialogRef) {}\n\n chargeAndSubmit() {\n this.dialogRef.close(true);\n }\n}\n", "\n info\n\n {{\n 'LIB_ORDERS.COMMON.EDIT_ORDERS.SUBMIT_FOR_CHARGE_DIALOG.TITLE' | translate\n }}\n \n
\n {{\n 'LIB_ORDERS.COMMON.EDIT_ORDERS.SUBMIT_FOR_CHARGE_DIALOG.CONFIRM_CHECKBOX' | translate\n }}\n
\n
\n\n\n \n \n\n \n \n\n", "import {\n ChangeDetectorRef,\n Component,\n EventEmitter,\n inject,\n Injector,\n Input,\n OnChanges,\n OnInit,\n Output,\n SimpleChanges,\n} from '@angular/core';\nimport { MatDialog } from '@angular/material/dialog';\nimport {\n PaymentMethod,\n PaymentMethodService,\n PaymentMethodType,\n RetailCustomerConfigurationService,\n RetailCustomerConfiguration,\n} from '@galaxy/billing';\nimport { firstValueFrom, Observable, of, take } from 'rxjs';\nimport { SelectPaymentMethodDialogData, SelectedPaymentMethod } from './payment-method';\nimport { OrdersSelectPaymentMethodDialogComponent } from './orders-select-payment-method-dialog/orders-select-payment-method-dialog.component';\nimport { SnackbarService } from '@vendasta/galaxy/snackbar-service';\nimport { TranslateModule, TranslateService } from '@ngx-translate/core';\nimport { AsyncPipe, NgClass } from '@angular/common';\nimport { MatButtonModule } from '@angular/material/button';\nimport { MatIconModule } from '@angular/material/icon';\nimport { MatDividerModule } from '@angular/material/divider';\nimport { MatFormFieldModule } from '@angular/material/form-field';\nimport { GalaxyBadgeModule } from '@vendasta/galaxy/badge';\nimport { PaymentMethodDisplayComponent } from '@vendasta/smb-invoicing';\nimport { CustomerRecipientInterface, SalesOrdersService } from '@vendasta/sales-orders';\nimport { MatCheckboxModule } from '@angular/material/checkbox';\nimport { OrdersPaymentMethodService } from './orders-payment-method.service';\nimport { distinctUntilChanged, shareReplay, switchMap, withLatestFrom } from 'rxjs/operators';\nimport { SubmitForChargeDialogComponent } from '../submit-for-charge-dialog/submit-for-charge-dialog.component';\nimport { OpenConfirmationModalService } from '@vendasta/galaxy/confirmation-modal';\nimport { OrderLineItemValidationService } from '../../order-line-item-validation/order-line-item-validation.service';\nimport { OrdersEditOrderFormValidationService } from '../../edit-order/edit-order-form-validation.service';\nimport { OrderStoreService } from '../../../core/orders.service';\n\n@Component({\n imports: [\n NgClass,\n MatButtonModule,\n MatIconModule,\n MatDividerModule,\n MatFormFieldModule,\n GalaxyBadgeModule,\n PaymentMethodDisplayComponent,\n TranslateModule,\n MatCheckboxModule,\n AsyncPipe,\n ],\n providers: [TranslateService],\n selector: 'orders-payment-method-selector',\n templateUrl: './orders-payment-method.component.html',\n styleUrls: ['./orders-payment-method.component.scss'],\n})\nexport class OrderPaymentMethodSelectorComponent implements OnChanges, OnInit {\n @Input() stripeConnectId: string;\n @Input() merchantId: string;\n @Input() customerId: string;\n @Input() orderId: string;\n @Input() userId: string;\n @Input() stripeKey: string;\n @Input() editButton: string;\n @Input() makeDefaultButton?: string;\n @Input() addCardButton: string;\n @Input() isEditable: boolean;\n @Input() hintText: string;\n @Input() emptyCardHintText: string;\n @Input() submitDialogText: string;\n @Input() paymentMethods: PaymentMethod[];\n @Input() defaultPaymentMethod: PaymentMethod;\n @Input() canRemoveSelectedMethod: boolean;\n @Input() orderCustomerRecipient: CustomerRecipientInterface;\n @Input() canChargeOrder: boolean;\n\n @Output() selectedPaymentMethod: EventEmitter = new EventEmitter();\n @Output() orderCharged: EventEmitter = new EventEmitter();\n\n confirmationModal = inject(OpenConfirmationModalService);\n\n chargeOrder = false;\n\n sortedMethods: Map;\n protected retailCustomerConfiguration$: Observable;\n constructor(\n public dialog: MatDialog,\n private paymentMethodService: PaymentMethodService,\n private cd: ChangeDetectorRef,\n private snackbarService: SnackbarService,\n private translateService: TranslateService,\n private ordersPaymentMethodService: OrdersPaymentMethodService,\n private readonly customerConfigService: RetailCustomerConfigurationService,\n private salesOrdersApiService: SalesOrdersService,\n private readonly lineItemValidationService: OrderLineItemValidationService,\n private readonly editOrderFormValidationService: OrdersEditOrderFormValidationService,\n private readonly orderStoreService: OrderStoreService,\n private readonly injector: Injector,\n ) {}\n\n ngOnInit(): void {\n this.ordersPaymentMethodService.setPaymentMethod(this.defaultPaymentMethod?.details?.id);\n if (!this.makeDefaultButton) {\n this.makeDefaultButton = this.translateService.instant('PAYMENT_METHOD_SELECTOR.USE_AS_DEFAULT');\n }\n\n this.retailCustomerConfiguration$ = this.customerConfigService\n .get(this.merchantId, this.customerId)\n .pipe(distinctUntilChanged(), shareReplay({ bufferSize: 1, refCount: true }));\n }\n\n ngOnChanges(changes: SimpleChanges): void {\n if (changes.paymentMethods?.previousValue !== changes.paymentMethods?.currentValue) {\n this.sortedMethods = this._groupAndSortPaymentMethods(this.paymentMethods);\n }\n }\n\n public openPaymentMethodDialog(): void {\n this.retailCustomerConfiguration$\n .pipe(\n take(1),\n switchMap((retailConfiguration) =>\n this.dialog\n .open(OrdersSelectPaymentMethodDialogComponent, {\n width: '600px',\n data: {\n accountId: this.stripeConnectId,\n merchantId: this.merchantId,\n customerId: this.customerId,\n userId: this.userId,\n stripeKey: this.stripeKey,\n paymentMethods: this.sortedMethods,\n submitDialogText: this.submitDialogText,\n defaultPaymentMethod: this.defaultPaymentMethod?.details?.id,\n orderCustomerRecipient: this.orderCustomerRecipient,\n defaultCustomerRecipientId: retailConfiguration?.contactId,\n } as SelectPaymentMethodDialogData,\n autoFocus: false,\n injector: Injector.create({\n providers: [{ provide: OrderStoreService, useValue: this.orderStoreService }],\n parent: this.injector,\n }),\n })\n .afterClosed(),\n ),\n )\n .subscribe((m) => {\n this.selectedPaymentMethod.emit(m);\n this.ordersPaymentMethodService.setPaymentMethod(m?.paymentMethodId);\n });\n }\n\n public openChargeConfirmationDialog(): void {\n this.editOrderFormValidationService\n .validateFormsForCharging()\n .pipe(\n switchMap((isFormValid) => {\n if (!isFormValid) {\n return of(null);\n }\n\n return this.lineItemValidationService.areBillingTermsValidOrOpenModal$();\n }),\n take(1),\n switchMap((areBillingTermsValid) => {\n if (!areBillingTermsValid) {\n return of(null);\n }\n\n return this.dialog\n .open(SubmitForChargeDialogComponent, {\n width: '600px',\n data: {\n accountId: this.stripeConnectId,\n partnerId: this.merchantId,\n },\n autoFocus: false,\n })\n .afterClosed()\n .pipe(take(1));\n }),\n take(1),\n withLatestFrom(this.ordersPaymentMethodService.paymentMethod$),\n switchMap(([res, paymentMethodToken]) => {\n if (res && paymentMethodToken) {\n return this.salesOrdersApiService.updatePaymentMethodToken(\n this.orderId,\n this.customerId,\n paymentMethodToken,\n );\n }\n return of(false);\n }),\n switchMap((order) => {\n if (order) {\n return this.salesOrdersApiService.chargeOrder(this.orderId, this.customerId);\n }\n return of(false);\n }),\n )\n .subscribe({\n next: (submitted) => {\n if (submitted) {\n this.snackbarService.openSuccessSnack('LIB_ORDERS.SALES_ORDERS.SUCCESS.ORDER_UPDATED');\n this.orderCharged.emit(true);\n }\n },\n error: (err) => {\n if (err?.message === 'Order has no lineItem') {\n this.snackbarService.openErrorSnack('LIB_ORDERS.COMMON.ORDERS.ERROR_AT_LEAST_ONE_LINE_ITEM');\n } else {\n this.snackbarService.openErrorSnack('LIB_ORDERS.COMMON.ERRORS.GENERIC_ERROR');\n }\n },\n });\n }\n\n public addPaymentMethodDialog(): void {\n this.dialog\n .open(OrdersSelectPaymentMethodDialogComponent, {\n width: '600px',\n data: {\n accountId: this.stripeConnectId,\n merchantId: this.merchantId,\n customerId: this.customerId,\n userId: this.userId,\n stripeKey: this.stripeKey,\n submitDialogText: this.submitDialogText,\n hideExistingMethods: true,\n } as SelectPaymentMethodDialogData,\n autoFocus: false,\n })\n .afterClosed()\n .subscribe((m) => {\n this.selectedPaymentMethod.emit(m);\n this.ordersPaymentMethodService.setPaymentMethod(m?.paymentMethodId);\n });\n }\n\n public async makePaymentMethodDefault(paymentMethod: PaymentMethod): Promise {\n this.selectedPaymentMethod.emit({\n paymentMethodId: paymentMethod.details?.id,\n newMethod: false,\n userId: undefined,\n });\n this.ordersPaymentMethodService.setPaymentMethod(paymentMethod.details?.id);\n }\n\n public async deletePaymentMethod(paymentMethod: PaymentMethod): Promise {\n try {\n await firstValueFrom(this.paymentMethodService.deletePaymentMethod(this.merchantId, paymentMethod.details?.id));\n } catch {\n this.snackbarService.openErrorSnack('PAYMENT_METHOD_SELECTOR.ERROR_REMOVING_PAYMENT_METHOD');\n return;\n }\n\n const type = paymentMethod.type;\n const methods = this.sortedMethods.get(type) || [];\n\n if (!methods) {\n return;\n }\n\n const index = methods.findIndex((m) => m.details?.id === paymentMethod.details?.id);\n if (index > -1) {\n methods.splice(index, 1);\n methods.length === 0 ? this.sortedMethods.delete(type) : this.sortedMethods.set(type, methods);\n }\n\n this.sortedMethods = new Map(this.sortedMethods);\n this.cd.detectChanges();\n return;\n }\n\n private _typeSortPriority(type: PaymentMethodType): number {\n switch (type) {\n case PaymentMethodType.CARD:\n return 1;\n case PaymentMethodType.ACH_DEBIT:\n return 2;\n case PaymentMethodType.ACSS_DEBIT:\n return 3;\n default:\n return 4;\n }\n }\n\n private _groupAndSortPaymentMethods(methods: PaymentMethod[]): Map {\n const grouped = new Map();\n\n methods.sort((a, b) => {\n const typePriorityA = this._typeSortPriority(a.type);\n const typePriorityB = this._typeSortPriority(b.type);\n if (typePriorityA !== typePriorityB) {\n return typePriorityA - typePriorityB;\n }\n const isADefault = a.details?.id === this.defaultPaymentMethod?.details?.id ? 1 : 0;\n const isBDefault = b.details?.id === this.defaultPaymentMethod?.details?.id ? 1 : 0;\n return isBDefault - isADefault;\n });\n\n methods.forEach((method) => {\n const group = grouped.get(method.type) || [];\n group.push(method);\n grouped.set(method.type, group);\n });\n\n return grouped;\n }\n}\n", "
\n \n @if (retailCustomerConfiguration$ | async; as retailCustomerConfiguration) {\n @if (defaultPaymentMethod && (orderCustomerRecipient?.userId || retailCustomerConfiguration?.contactId)) {\n \n @if (isEditable) {\n \n
\n {{ hintText }}\n @if (canChargeOrder) {\n
\n \n
\n }\n }\n } @else if (isEditable) {\n
\n \n {{ addCardButton }}\n \n
\n }\n }\n
\n
\n", "import { Component, Input, OnInit, inject, Optional, Output, EventEmitter } from '@angular/core';\n\nimport { MatExpansionPanel, MatExpansionPanelHeader, MatExpansionPanelTitle } from '@angular/material/expansion';\nimport { TranslateModule } from '@ngx-translate/core';\nimport { MatDivider } from '@angular/material/divider';\nimport { BillingUiModule } from '@vendasta/billing-ui';\nimport { Frequency } from '@vendasta/galaxy/frequency';\nimport { MatDialog } from '@angular/material/dialog';\nimport { map, shareReplay, take, switchMap } from 'rxjs/operators';\nimport { BehaviorSubject, ReplaySubject, firstValueFrom, Observable } from 'rxjs';\nimport { PaymentMethod, PaymentService, PaymentMethodService, StripeService } from '@galaxy/billing';\nimport { CommonModule } from '@angular/common';\nimport { OrderPaymentMethodSelectorComponent } from './select-payment-method/orders-payment-method.component';\nimport { SelectedPaymentMethod } from './select-payment-method/payment-method';\nimport { CustomerRecipientInterface, SalesOrdersService } from '@vendasta/sales-orders';\nimport { OrderStoreService } from '../../core/orders.service';\nimport { OrderPermissionsService } from '../../core/permissions';\nimport { OrderAction } from '../../core/permissions/permissions';\n\nexport interface RetailSummary {\n subtotal: number;\n taxAmount: number;\n firstPayment: number;\n currencyCode: string;\n taxes: number;\n monthlyTotal: number;\n yearlyTotal: number;\n}\n\n@Component({\n selector: 'orders-retail-summary',\n templateUrl: './retail-summary.component.html',\n styleUrls: ['./retail-summary.component.scss'],\n providers: [],\n imports: [\n MatExpansionPanelTitle,\n TranslateModule,\n MatExpansionPanel,\n MatExpansionPanelHeader,\n MatDivider,\n BillingUiModule,\n CommonModule,\n OrderPaymentMethodSelectorComponent,\n ],\n})\nexport class RetailSummaryComponent implements OnInit {\n @Input() summary: RetailSummary;\n\n @Input() accountGroupId: string;\n @Input() partnerId: string;\n @Input() orderId: string;\n @Input() canChargeOrder: boolean;\n @Input() orderCustomerRecipient: CustomerRecipientInterface;\n @Input() orderPaymentMethodToken: string;\n @Input() canEditPaymentMethod: boolean;\n\n @Output() orderCharged = new EventEmitter();\n\n paymentMethods$$: ReplaySubject = new ReplaySubject(1);\n loadingPaymentMethods$$: BehaviorSubject = new BehaviorSubject(true);\n defaultPaymentMethod$$ = new ReplaySubject(1);\n defaultPaymentMethodID: string;\n canCollectPaymentFromCustomer$: Observable;\n private readonly paymentService = inject(PaymentService);\n\n protected readonly stripeKey = this.stripeService.getConnectPublicKey();\n protected stripeConnectId$;\n\n protected readonly Frequency = Frequency;\n\n constructor(\n private dialog: MatDialog,\n private paymentMethodService: PaymentMethodService,\n private stripeService: StripeService,\n private salesOrderService: SalesOrdersService,\n @Optional() private orderStoreService: OrderStoreService,\n @Optional() private permissionsService: OrderPermissionsService,\n ) {}\n\n ngOnInit(): void {\n this._listPaymentMethods();\n this.stripeConnectId$ = this.paymentService.getRetailProvider(this.partnerId).pipe(\n map((retailProvider) => retailProvider.stripeConnectId),\n shareReplay({ refCount: true, bufferSize: 1 }),\n );\n this.canCollectPaymentFromCustomer$ = this.permissionsService.CanDoAction(OrderAction.CollectPaymentFromCustomer);\n }\n\n private async _listPaymentMethods(): Promise {\n this.loadingPaymentMethods$$.next(true);\n const paymentMethods = await firstValueFrom(this.paymentMethodService.list(this.partnerId, this.accountGroupId));\n this.paymentMethods$$.next(paymentMethods);\n\n let defaultPaymentMethod: PaymentMethod;\n if (this.orderPaymentMethodToken) {\n defaultPaymentMethod = (paymentMethods || []).find(\n (method) => method?.details?.id === this.orderPaymentMethodToken,\n );\n } else {\n defaultPaymentMethod = (paymentMethods || []).find((method) => method?.default);\n }\n\n this.defaultPaymentMethodID = defaultPaymentMethod?.details?.id;\n this.defaultPaymentMethod$$.next(defaultPaymentMethod);\n this.loadingPaymentMethods$$.next(false);\n }\n\n onPaymentMethodSelected(paymentMethod: SelectedPaymentMethod): void {\n if (!paymentMethod) {\n this._listPaymentMethods();\n return;\n }\n if (paymentMethod.userId?.length > 0) {\n this.salesOrderService\n .updateCustomerRecipientUserId(this.orderId, this.accountGroupId, paymentMethod.userId)\n .pipe(\n switchMap(() =>\n this.salesOrderService.updatePaymentMethodToken(\n this.orderId,\n this.accountGroupId,\n paymentMethod.paymentMethodId,\n ),\n ),\n take(1),\n )\n .subscribe();\n }\n\n this.salesOrderService\n .updatePaymentMethodToken(this.orderId, this.accountGroupId, paymentMethod.paymentMethodId)\n .pipe(take(1))\n .subscribe(() => {\n this.orderStoreService?.reloadOrder();\n });\n\n this.paymentMethodService\n .list(this.partnerId, this.accountGroupId)\n .pipe(take(1))\n .subscribe((cards) => {\n const defaultCard = cards.find((card) => card?.details?.id === paymentMethod?.paymentMethodId);\n this.defaultPaymentMethod$$.next(defaultCard);\n });\n }\n\n onOrderCharged(orderCharged: boolean): void {\n this.orderCharged.emit(orderCharged);\n }\n}\n", "\n \n {{ 'LIB_ORDERS.COMMON.EDIT_ORDERS.RETAIL_SUMMARY.TITLE' | translate }}\n \n @if (summary) {\n
\n {{ 'LIB_ORDERS.COMMON.EDIT_ORDERS.RETAIL_SUMMARY.SUBTOTAL' | translate }}\n \n \n \n
\n @if (summary.taxAmount) {\n
\n {{ 'LIB_ORDERS.COMMON.EDIT_ORDERS.RETAIL_SUMMARY.TAXES' | translate }}\n \n \n \n
\n }\n \n
\n {{ 'LIB_ORDERS.COMMON.EDIT_ORDERS.RETAIL_SUMMARY.FIRST_PAYMENT' | translate }}\n \n \n \n
\n
\n {{ 'LIB_ORDERS.COMMON.EDIT_ORDERS.RETAIL_SUMMARY.RECURRING_CHARGES' | translate }}\n \n
\n @if (summary.monthlyTotal) {\n \n }\n @if (summary.yearlyTotal) {\n \n }\n
\n
\n
\n @if (canCollectPaymentFromCustomer$ | async) {\n @if ((loadingPaymentMethods$$ | async) === false) {\n \n \n } @else {\n
\n
\n
\n }\n }\n }\n
\n", "import { Inject, inject, Injectable } from '@angular/core';\nimport { BehaviorSubject, combineLatest, from, merge, Observable, of, Subject, switchMap } from 'rxjs';\nimport { ORDER_BUSINESS_CONFIG_TOKEN } from '../../core/tokens';\nimport {\n DurationInterface,\n DurationPeriod,\n Order,\n SalesOrdersService,\n Source,\n Status,\n StatusHistoryItemInterface,\n UserRole,\n} from '@vendasta/sales-orders';\nimport { SnackbarService } from '@vendasta/galaxy/snackbar-service';\nimport { TranslateService } from '@ngx-translate/core';\nimport {\n catchError,\n distinctUntilChanged,\n filter,\n map,\n scan,\n shareReplay,\n startWith,\n withLatestFrom,\n} from 'rxjs/operators';\nimport { LineItemsService } from '../line-items/line-items.service';\nimport { PARTNER_ID } from '../../shared/constants';\nimport { OrderStoreService } from '../../core/orders.service';\nimport { Salesperson, SalespersonService } from '@vendasta/sales';\nimport { AccountGroup } from '@galaxy/account-group';\nimport { OrderFeature, OrderPermissionsService } from '../../core/permissions';\nimport { DatePipe } from '@angular/common';\nimport { OrderAction } from '../../core/permissions/permissions';\n\nexport interface OrderDetails {\n loading: boolean;\n canEditContractDetails: boolean;\n accountLink: string;\n companyLink: string;\n business: AccountGroup;\n salesperson?: Salesperson;\n orderUserDetails: OrderUserDetails;\n contractDetails: ContractDetails;\n customerAgreementData?: CustomerAgreementData;\n}\n\nexport interface ContractDetails {\n contractStartDate: Date;\n minimumContractStartDate: Date;\n contractDuration: DurationInterface;\n}\n\nexport interface OrderUserDetails {\n badgeColor: string;\n badgeTranslationKey: string;\n name: string;\n}\n\ninterface CustomerAgreementData {\n lastAcceptedName: string;\n lastAcceptedDate: string;\n agreements: CustomerAgreement[];\n}\n\ninterface CustomerAgreement {\n name: string;\n term: string;\n agreed: boolean;\n}\n\nexport const DEFAULT_CONTRACT_DURATION: DurationInterface = {\n value: 1,\n duration: DurationPeriod.YEAR,\n};\n\ntype UserInfo = { name: string; role: UserRole; source: Source };\n\nconst userRoleToBadge = {\n [UserRole.USER_ROLE_ADMIN]: { color: 'blue', translationKey: 'FRONTEND.SALES_UI.ADMIN' },\n [UserRole.USER_ROLE_SALESPERSON]: { color: 'purple', translationKey: 'FRONTEND.SALES_UI.SALESPERSON' },\n [UserRole.USER_ROLE_CLIENT]: { color: 'green', translationKey: 'FRONTEND.SALES_UI.CLIENT' },\n};\n\nconst userSourceToDisplayName = {\n [Source.SOURCE_SYSTEM]: 'LIB_ORDERS.COMMON.ORDER_DETAILS.SYSTEM',\n [Source.SOURCE_SYSTEM_ADMIN]: 'LIB_ORDERS.COMMON.ORDER_DETAILS.SYSTEM_ADMIN',\n [Source.SOURCE_INVALID]: undefined,\n};\n\nconst setContractStartDateFn =\n (input: { loading: boolean; startDate: Date }) =>\n (orderDetails: OrderDetails): OrderDetails => {\n orderDetails.loading = input.loading;\n orderDetails.contractDetails = {\n ...orderDetails?.contractDetails,\n contractStartDate: input.startDate,\n };\n return orderDetails;\n };\n\nconst setContractDurationFn =\n (input: { loading: boolean; contractDuration: DurationInterface }) =>\n (orderDetails: OrderDetails): OrderDetails => {\n orderDetails.loading = input.loading;\n orderDetails.contractDetails = {\n ...orderDetails?.contractDetails,\n contractDuration: { ...input.contractDuration },\n };\n return orderDetails;\n };\n\nconst setInitialDataFn = (input: OrderDetails) => (_: OrderDetails) => input;\n\n@Injectable()\nexport class CondensedOrderDetailsService {\n private readonly _updateContractStartDate$$ = new BehaviorSubject(null);\n private readonly _updateContractDuration$$ = new Subject();\n\n public readonly orderDetails$: Observable;\n\n private readonly orderBusinessConfig = inject(ORDER_BUSINESS_CONFIG_TOKEN, { optional: true });\n private readonly loading$$ = new BehaviorSubject(false);\n readonly loading$ = this.loading$$.asObservable();\n private readonly canEditContractDetails$ = this.permissionsService.CanEdit(OrderFeature.ContractStartAndDuration);\n private readonly canUpdateInvalidContractStartDate$ = this.permissionsService.CanDoAction(\n OrderAction.UpdateInvalidContractStartDate,\n );\n\n constructor(\n @Inject(PARTNER_ID) private readonly partnerId$: Observable,\n private readonly orderService: OrderStoreService,\n private salesOrdersService: SalesOrdersService,\n private lineItemsService: LineItemsService,\n private readonly snack: SnackbarService,\n private readonly salespersonService: SalespersonService,\n private readonly permissionsService: OrderPermissionsService,\n private readonly translateService: TranslateService,\n ) {\n const updateContractStartDate$ = this._updateContractStartDate$$.asObservable().pipe(\n filter((date): date is Date => date !== null),\n distinctUntilChanged((prev, curr) => prev.getTime() === curr.getTime()),\n withLatestFrom(this.orderService.order$),\n switchMap(([newContractStartDate, order]) =>\n this._buildUpdateContractStartDateStream(order.orderId, order.businessId, newContractStartDate),\n ),\n shareReplay({ bufferSize: 1, refCount: true }),\n );\n\n const updateContractDuration$ = this._updateContractDuration$$.asObservable().pipe(\n withLatestFrom(this.orderService.order$),\n switchMap(([newContractDuration, order]) =>\n this._buildUpdateContractDurationStream(order.orderId, order.businessId, newContractDuration),\n ),\n );\n\n const accountLink$ = this.getAccountLink();\n const companyLink$ = this.getCompanyLink();\n const business$ = this.getBusiness();\n const salesperson$: Observable> = combineLatest([\n this.partnerId$,\n this.orderService.order$,\n business$,\n ]).pipe(\n switchMap(([partnerId, order, { business }]: [string, Order, Pick]) =>\n this.getSalesperson(partnerId, order, business),\n ),\n );\n\n const orderUserDetails$ = this.orderService.order$.pipe(\n map((order) => this.getOrderCreatorUser(order?.statusHistory)),\n );\n\n const contractStartDate$ = combineLatest([this.orderService.order$, this.canUpdateInvalidContractStartDate$]).pipe(\n map(([order, canUpdateInvalidContractStartDate]) =>\n this.validateContractStartDate(order?.requestedActivation, canUpdateInvalidContractStartDate),\n ),\n );\n const contractDuration$ = this.orderService.order$.pipe(map((order) => order.contractDuration));\n\n const customerAgreementData$ = this.orderService.order$.pipe(\n map((order: Order) => {\n return { order, userMap: this.getStatusHistoryUsersMapFromOrder(order) };\n }),\n map(({ order, userMap }) => {\n const agreements = this.constructCustomerAgreements(order, userMap);\n const agreementUser = this.getLastAcceptedUser(order.statusHistory, userMap);\n return {\n lastAcceptedName: agreementUser.name,\n lastAcceptedDate: agreementUser.date,\n agreements: agreements,\n };\n }),\n );\n\n const initialData$ = combineLatest([\n accountLink$,\n companyLink$,\n business$,\n this.canEditContractDetails$,\n salesperson$,\n orderUserDetails$,\n contractStartDate$,\n contractDuration$,\n customerAgreementData$,\n ]).pipe(\n map(\n ([\n accountLinkData,\n companyLinkData,\n businessData,\n canEditContractDetails,\n salespersonData,\n orderUserDetailsData,\n contractStartDate,\n contractDuration,\n customerAgreementData,\n ]) => {\n return {\n loading: false,\n accountLink: accountLinkData?.accountLink,\n companyLink: companyLinkData?.companyLink,\n business: businessData?.business,\n canEditContractDetails: canEditContractDetails,\n salesperson: salespersonData?.salesperson,\n orderUserDetails: orderUserDetailsData?.orderUserDetails,\n contractDetails: {\n contractStartDate: contractStartDate,\n minimumContractStartDate: new Date(new Date().setHours(7, 0, 0, 0)),\n contractDuration: {\n value: contractDuration?.value || DEFAULT_CONTRACT_DURATION.value,\n duration: contractDuration?.duration || DEFAULT_CONTRACT_DURATION.duration,\n },\n },\n customerAgreementData: customerAgreementData,\n };\n },\n ),\n startWith({ loading: true }),\n );\n\n this.orderDetails$ = merge(\n initialData$.pipe(map(setInitialDataFn)),\n updateContractStartDate$.pipe(map(setContractStartDateFn)),\n updateContractDuration$.pipe(map(setContractDurationFn)),\n ).pipe(\n scan((acc, fn) => fn({ ...acc }), {\n loading: false,\n accountLink: '',\n companyLink: '',\n canEditContractDetails: false,\n business: null,\n salesperson: null,\n orderUserDetails: null,\n contractDetails: null,\n customerAgreementData: null,\n }),\n distinctUntilChanged((prev, curr) => this.areOrderDetailsEqual(prev as OrderDetails, curr as OrderDetails)),\n shareReplay(1),\n );\n }\n\n areOrderDetailsEqual(prev: OrderDetails, curr: OrderDetails): boolean {\n return (\n prev.loading === curr.loading &&\n prev.accountLink === curr.accountLink &&\n prev.companyLink === curr.companyLink &&\n prev.business === curr.business &&\n prev.salesperson === curr.salesperson &&\n prev.orderUserDetails?.name === curr.orderUserDetails?.name &&\n prev.orderUserDetails?.badgeColor === curr.orderUserDetails?.badgeColor &&\n prev.orderUserDetails?.badgeTranslationKey === curr.orderUserDetails?.badgeTranslationKey &&\n prev.contractDetails?.contractStartDate === curr.contractDetails?.contractStartDate &&\n prev.contractDetails?.contractDuration?.value === curr.contractDetails?.contractDuration?.value &&\n prev.contractDetails?.contractDuration?.duration === curr.contractDetails?.contractDuration?.duration &&\n prev.customerAgreementData?.lastAcceptedDate === curr.customerAgreementData?.lastAcceptedDate &&\n prev.customerAgreementData?.lastAcceptedName === curr.customerAgreementData?.lastAcceptedName &&\n curr.customerAgreementData?.agreements.every((agreement, index) => {\n return (\n agreement?.name === curr?.customerAgreementData?.agreements?.[index]?.name &&\n agreement?.term === curr?.customerAgreementData?.agreements?.[index]?.term &&\n agreement?.agreed === curr?.customerAgreementData?.agreements?.[index]?.agreed\n );\n })\n );\n }\n\n // Get the CRM company link\n getCompanyLink(): Observable> {\n return this.orderService.order$.pipe(\n switchMap((order: Order) => {\n const url = this.orderBusinessConfig?.getBusinessUrl?.(order.businessId) ?? `/info/${order.businessId}`;\n if (url instanceof Promise) {\n return from(url);\n }\n return of(url);\n }),\n map((url) => ({ companyLink: url })),\n );\n }\n\n // Get link to the account\n getAccountLink(): Observable> {\n return this.orderService.order$.pipe(\n switchMap((order: Order) => {\n const url = `/businesses/accounts/${order.businessId}/details`;\n return of(url);\n }),\n map((url) => ({ accountLink: url })),\n );\n }\n\n getBusiness(): Observable> {\n return this.orderService.business$.pipe(map((business) => ({ business })));\n }\n\n private _buildUpdateContractStartDateStream(\n orderId: string,\n businessId: string,\n contractStartDate: Date,\n ): Observable<{ loading: boolean; startDate: Date }> {\n contractStartDate.setHours(7);\n return this.salesOrdersService.updateRequestedActivation(orderId, businessId, contractStartDate).pipe(\n map(() => {\n this.lineItemsService.setContractStartDate(contractStartDate);\n this.orderService.reloadOrder();\n return { loading: false, startDate: contractStartDate };\n }),\n startWith({ loading: true, startDate: contractStartDate }),\n );\n }\n\n private _buildUpdateContractDurationStream(\n orderId: string,\n businessId: string,\n contractDuration: DurationInterface,\n ): Observable<{ loading: boolean; contractDuration: DurationInterface }> {\n return this.salesOrdersService.updateContractDuration(orderId, businessId, contractDuration).pipe(\n map(() => {\n this.lineItemsService.setContractDuration(contractDuration);\n return { loading: false, contractDuration: contractDuration };\n }),\n startWith({ loading: true, contractDuration: contractDuration }),\n );\n }\n\n public updateContractStartDate(newContractStartDate: Date): void {\n this._updateContractStartDate$$.next(newContractStartDate);\n }\n\n public updateContractDuration(newDuration: DurationInterface): void {\n this._updateContractDuration$$.next(newDuration);\n }\n\n private getSalesperson(\n partnerId: string,\n order: Order,\n business: AccountGroup,\n ): Observable> {\n let salespersonId = '';\n\n // Use the salespersonId on the order if there is one\n if (order?.salespersonId && order.salespersonId !== 'DEFAULT') {\n salespersonId = order.salespersonId;\n // Otherwise use the salespersonId assigned to the business\n } else if (business?.externalIdentifiers?.salesPersonId) {\n salespersonId = business.externalIdentifiers.salesPersonId;\n }\n\n if (salespersonId) {\n if (salespersonId.startsWith('U-')) {\n return this.salespersonService.getSalespersonByUserId(partnerId, salespersonId).pipe(\n map((salesperson) => ({ salesperson })),\n catchError((error) => {\n console.error('Error fetching salesperson by User ID:', error);\n return of({ salesperson: null });\n }),\n );\n } else if (salespersonId.startsWith('UID-')) {\n return this.salespersonService.getSalespersonBySubjectId(partnerId, salespersonId).pipe(\n map((salesperson) => ({ salesperson })),\n catchError((error) => {\n console.error('Error fetching salesperson by Subject ID:', error);\n return of({ salesperson: null });\n }),\n );\n }\n }\n\n return of({ salesperson: null });\n }\n\n private getUserNameOrSource(name: string, source: Source): string {\n if (name) {\n return name;\n } else if (source) {\n return this.translateService.instant(userSourceToDisplayName[source]);\n }\n return undefined;\n }\n\n private getOrderCreatorUser(orderHistory: StatusHistoryItemInterface[]) {\n if (orderHistory?.length > 0) {\n const orderCreatorUser = orderHistory[0];\n if (orderCreatorUser?.name?.length > 0 || !!orderCreatorUser?.source) {\n const userBadge = userRoleToBadge?.[orderCreatorUser.userRole];\n return {\n orderUserDetails: {\n name: this.getUserNameOrSource(orderCreatorUser?.name, orderCreatorUser?.source),\n badgeColor: userBadge?.color,\n badgeTranslationKey: userBadge?.translationKey,\n },\n };\n }\n }\n return { orderUserDetails: undefined };\n }\n\n public getStatusHistoryUsersMapFromOrder(order: Order): { [userId: string]: UserInfo } {\n const userInfoMap: { [userId: string]: UserInfo } = {};\n if (order?.statusHistory?.length > 0) {\n order.statusHistory.forEach((historyItem) => {\n if (historyItem?.userId && !userInfoMap[historyItem.userId]) {\n userInfoMap[historyItem.userId] = {\n name: historyItem.name,\n role: historyItem.userRole,\n source: historyItem.source,\n };\n }\n });\n }\n\n return userInfoMap;\n }\n\n private constructCustomerAgreements(order: Order, userMap: { [userId: string]: UserInfo }) {\n return (order?.agreements || []).map((agreement) => {\n const user = userMap[agreement?.userId];\n return {\n name: this.getUserNameOrSource(user?.name, user?.source),\n term: agreement?.term,\n agreed: agreement?.agreed,\n } as CustomerAgreement;\n });\n }\n\n public getLastAcceptedUser(orderHistory: StatusHistoryItemInterface[], userMap: { [userId: string]: UserInfo }) {\n for (let i = orderHistory?.length - 1; i > 0; i--) {\n if (\n orderHistory[i].status == Status.SUBMITTED_FOR_CUSTOMER_APPROVAL &&\n orderHistory[i + 1] &&\n (!orderHistory[i + 1]?.status || orderHistory[i + 1]?.status == Status.AWAITING_PAYMENT)\n ) {\n const user = userMap[orderHistory[i + 1].userId];\n return {\n name: this.getUserNameOrSource(user?.name, user?.source),\n date: new DatePipe('en-US').transform(orderHistory[i + 1].created, 'MMMM d, y, h:mm:ss a'),\n };\n }\n }\n return { name: '', date: '' };\n }\n\n public validateContractStartDate(contractStartDay: Date, canUpdateInvalidContractStartDate: boolean): Date {\n const earliestContractStartDay = new Date(new Date().setHours(7, 0, 0, 0));\n if (contractStartDay < earliestContractStartDay && canUpdateInvalidContractStartDate) {\n this.updateContractStartDate(earliestContractStartDay);\n contractStartDay = earliestContractStartDay;\n }\n return contractStartDay;\n }\n}\n", "import { Pipe, PipeTransform } from '@angular/core';\nimport { DurationInterface, DurationPeriod } from '@vendasta/sales-orders';\n\n@Pipe({\n standalone: true,\n name: 'formatDuration',\n})\nexport class FormatDurationPipe implements PipeTransform {\n transform(duration: DurationInterface): { value: number; period: string } {\n if (!duration) {\n return { value: 0, period: '' };\n }\n let period = 'month';\n if (duration.duration === DurationPeriod.YEAR) {\n period = 'year';\n }\n if (duration.value > 1) {\n period += 's';\n }\n return { value: duration.value, period: period };\n }\n}\n", "import { Component } from '@angular/core';\nimport { MatCard, MatCardContent, MatCardHeader, MatCardTitle } from '@angular/material/card';\nimport { MatProgressSpinner } from '@angular/material/progress-spinner';\nimport { TranslateModule } from '@ngx-translate/core';\nimport { Observable } from 'rxjs';\nimport { AsyncPipe, DatePipe } from '@angular/common';\nimport { CondensedOrderDetailsService, OrderDetails } from './condensed-order-details.service';\nimport { CompanyNameComponent } from '../order-details/company-details/company-name.component';\nimport { CompanyAddressComponent } from '../order-details/company-details/company-address.component';\nimport { DurationInterface } from '@vendasta/sales-orders';\nimport { FormatDurationPipe } from './format-duration.pipe';\nimport { GalaxyBadgeModule } from '@vendasta/galaxy/badge';\nimport { OrderContractStartComponent } from '../order-details/contract-details/contract-start.component';\nimport { OrderContractDurationComponent } from '../order-details/contract-details/contract-duration.component';\nimport { MatDivider } from '@angular/material/divider';\nimport { MatIcon } from '@angular/material/icon';\n\n@Component({\n selector: 'orders-condensed-order-details',\n templateUrl: './condensed-order-details.component.html',\n styleUrls: ['./condensed-order-details.component.scss'],\n providers: [CondensedOrderDetailsService],\n imports: [\n MatCard,\n MatCardHeader,\n MatCardTitle,\n MatProgressSpinner,\n MatCardContent,\n TranslateModule,\n AsyncPipe,\n CompanyNameComponent,\n CompanyAddressComponent,\n DatePipe,\n FormatDurationPipe,\n OrderContractStartComponent,\n GalaxyBadgeModule,\n OrderContractDurationComponent,\n MatDivider,\n MatIcon,\n ],\n})\nexport class CondensedOrderDetailsComponent {\n public readonly orderDetails$: Observable = this.condensedOrderDetailsService.orderDetails$;\n\n constructor(private condensedOrderDetailsService: CondensedOrderDetailsService) {}\n\n handleContractStartDateChange(newContractStartDate: Date): void {\n this.condensedOrderDetailsService.updateContractStartDate(newContractStartDate);\n }\n\n handleContractDurationChange(newDuration: DurationInterface): void {\n this.condensedOrderDetailsService.updateContractDuration(newDuration);\n }\n}\n", "@if (orderDetails$ | async; as orderDetails) {\n \n \n \n
\n {{ 'LIB_ORDERS.COMMON.ORDER_DETAILS.TITLE' | translate }}\n
\n @if (orderDetails.loading) {\n
\n \n
\n }\n
\n
\n \n \n @if (orderDetails.orderUserDetails; as orderCreatorUser) {\n @if (orderCreatorUser.name) {\n
\n
\n {{ 'LIB_ORDERS.COMMON.ORDER_DETAILS.CREATED_BY' | translate }}\n
\n {{ orderCreatorUser.name }}\n @if (orderCreatorUser.badgeColor && orderCreatorUser.badgeTranslationKey) {\n \n {{ orderCreatorUser.badgeTranslationKey | translate }}\n \n }\n
\n }\n }\n \n @if (!!orderDetails.salesperson) {\n
\n
\n {{ 'LIB_ORDERS.COMMON.ORDER_DETAILS.SALESPERSON' | translate }}\n
\n
{{ orderDetails.salesperson?.firstName || '' }} {{ orderDetails.salesperson?.lastName || '' }}
\n
\n }\n \n @if (!!orderDetails.companyLink) {\n \n }\n \n \n \n @if (orderDetails.contractDetails; as orderContractDetails) {\n
\n @if (orderDetails.canEditContractDetails) {\n
\n \n \n \n \n
\n } @else {\n
\n
\n
\n {{ 'LIB_ORDERS.COMMON.ORDER_DETAILS.CONTRACT_START' | translate }}\n
\n
\n {{ orderContractDetails.contractStartDate | date }}\n
\n
\n @if (orderContractDetails.contractDuration | formatDuration; as duration) {\n
\n
\n {{ 'LIB_ORDERS.COMMON.ORDER_DETAILS.CONTRACT_DURATION' | translate }}\n
\n
{{ duration.value }} {{ duration.period }}
\n
\n }\n
\n }\n
\n }\n \n @if (orderDetails.customerAgreementData; as agreementData) {\n @if (agreementData?.agreements?.length > 0) {\n
\n
\n {{ 'LIB_ORDERS.COMMON.ORDER_DETAILS.CUSTOMER_AGREEMENTS' | translate }}\n
\n
\n @if (agreementData.lastAcceptedName && agreementData.lastAcceptedDate) {\n
\n {{\n 'LIB_ORDERS.COMMON.ORDER_DETAILS.ACCEPTED_ON'\n | translate: { date: agreementData.lastAcceptedDate, name: agreementData.lastAcceptedName }\n }}\n
\n }\n @for (agreement of agreementData.agreements; track agreement; let isLast = $last) {\n
\n
\n
\n @if (agreement?.agreed) {\n check_circle_outline\n } @else {\n highlight_off\n }\n
\n
\n
\n @if (agreement?.term) {\n \n }\n
\n @if (agreement?.name) {\n \n }\n
\n
\n @if (!isLast) {\n
\n \n
\n }\n
\n }\n
\n
\n }\n }\n
\n
\n}\n", "import { Component, Input } from '@angular/core';\nimport { CondensedOrderDetailsService, OrderDetails } from '../condensed-order-details/condensed-order-details.service';\n\nimport { AsyncPipe } from '@angular/common';\n\nimport { Observable } from 'rxjs';\nimport { TranslateModule } from '@ngx-translate/core';\n\n@Component({\n selector: 'orders-business-header',\n templateUrl: './business-header.component.html',\n styleUrls: ['./business-header.component.scss'],\n providers: [CondensedOrderDetailsService],\n imports: [TranslateModule, AsyncPipe],\n})\nexport class BusinessHeaderComponent {\n public readonly orderDetails$: Observable = this.condensedOrderDetailsService.orderDetails$;\n\n @Input() orderId: string;\n\n constructor(private condensedOrderDetailsService: CondensedOrderDetailsService) {}\n}\n", "@if (orderDetails$ | async; as orderDetails) {\n \n
\n @if (orderDetails.business?.napData; as napData) {\n {{ napData.address }} \n @if (napData.address2) {\n {{ napData.address2 }}\n }\n {{ napData.city }} {{ napData.state }} {{ napData.country }} {{ napData.zip }}\n @if (orderDetails.business?.napData?.workNumber?.[0] && orderDetails.business?.napData?.workNumber?.[0] !== '') {\n | \n \n {{ orderDetails.business?.napData?.workNumber[0] }}\n \n }\n }\n
\n}\n", "import { Component } from '@angular/core';\nimport { CommonModule } from '@angular/common';\nimport { GalaxyAlertModule } from '@vendasta/galaxy/alert';\nimport { TranslateModule } from '@ngx-translate/core';\nimport { OrderLineItemValidationService } from './order-line-item-validation.service';\n@Component({\n selector: 'orders-line-item-validation-banner',\n imports: [CommonModule, GalaxyAlertModule, TranslateModule],\n templateUrl: './order-line-item-validation-banner.component.html',\n styleUrl: './order-line-item-validation-banner.component.scss',\n})\nexport class OrderLineItemValidationBannerComponent {\n protected formattedLineItemErrors$ = this.orderLineItemValidationService.formattedLineItemErrors$;\n constructor(private orderLineItemValidationService: OrderLineItemValidationService) {}\n}\n", "@if (formattedLineItemErrors$ | async; as lineItemErrors) {\n @for (errorText of lineItemErrors; track errorText) {\n \n {{ 'LIB_ORDERS.SALES_ORDERS.WARNINGS.INVALID_LINE_ITEMS_BANNER_WARNING' | translate\n }}\n \n }\n}\n", "import { Component, Input } from '@angular/core';\nimport { FileInfo } from '@vendasta/galaxy/uploader';\nimport { MatDialog } from '@angular/material/dialog';\nimport { AttachmentsDialogComponent } from '../attachments-dialog.component';\nimport { filter, take } from 'rxjs/operators';\nimport { SalesOrdersService } from '@vendasta/sales-orders';\n\n@Component({\n selector: 'orders-customer-attachments',\n templateUrl: 'customer-attachments.component.html',\n styleUrls: ['./customer-attachments.component.scss'],\n standalone: false,\n})\nexport class CustomerAttachmentsComponent {\n @Input() files: FileInfo[];\n @Input() uploadUrl: string;\n @Input() businessId: string;\n @Input() orderId: string;\n @Input() createMode = false;\n @Input() editingDisabled: boolean;\n @Input() hideSubtitle = false;\n\n constructor(\n public dialog: MatDialog,\n private salesOrderService: SalesOrdersService,\n ) {\n if (!this.files) {\n this.files = [];\n }\n }\n\n updateAttachments(): void {\n if (this.createMode) {\n // skip if component is used in create order\n return;\n }\n const attachments = this.files.map((file) => {\n return {\n name: file.name,\n url: file.url,\n };\n });\n\n this.salesOrderService\n .updateCustomerAttachments(this.orderId, this.businessId, attachments)\n .pipe(take(1))\n .subscribe();\n }\n\n onClickAdd(): void {\n this.dialog\n .open(AttachmentsDialogComponent, {\n width: '720px',\n data: {\n uploadUrl: this.uploadUrl,\n },\n })\n .afterClosed()\n .pipe(filter(Boolean), take(1))\n .subscribe((files) => {\n if (!this.files) {\n this.files = [];\n }\n this.files = this.files.concat(files);\n this.updateAttachments();\n });\n }\n\n getFiles(): FileInfo[] {\n return this.files;\n }\n\n onFileDeleted(file: FileInfo): void {\n const index = this.files.findIndex((f) => f.name === file.name);\n this.files.splice(index, 1);\n this.updateAttachments();\n }\n}\n", "\n \n \n
\n {{ 'LIB_ORDERS.COMMON.ORDER_DETAILS.CUSTOMER_ATTACHMENTS' | translate }}\n
\n
\n @if (!hideSubtitle) {\n \n {{ 'LIB_ORDERS.COMMON.ORDER_DETAILS.CUSTOMER_ATTACHMENTS_SUBTITLE' | translate }}\n \n }\n @if (!editingDisabled) {\n
\n \n
\n }\n
\n\n \n @if (files && files.length > 0) {\n
\n @if (!editingDisabled) {\n \n } @else {\n \n }\n
\n } @else {\n
\n {{ 'LIB_ORDERS.COMMON.ORDER_DETAILS.EMPTY_ATTACHMENTS' | translate }}\n
\n }\n
\n
\n", "import { Pipe, PipeTransform } from '@angular/core';\nimport { OrderDetailField, StatusToBadgeMapping } from '@vendasta/sales-ui';\nimport {\n CustomerApprovalEventBounced,\n CustomerApprovalEventClicked,\n CustomerApprovalEventDeferred,\n CustomerApprovalEventDelivered,\n CustomerApprovalEventDropped,\n CustomerApprovalEventOpened,\n CustomerApprovalEventProcessing,\n CustomerApprovalEventSending,\n CustomerApprovalEventSpamReport,\n CustomerApprovalEventUnsubscribed,\n CustomerApprovalEventUpdatedAnswers,\n DeclineCancellationEvent,\n RequestCancellationEvent,\n Status,\n UpdateContractDurationEvent,\n UpdatedCustomerNotesEvent,\n UpdatedDraftEvent,\n UpdatedLineItemsEvent,\n UpdatedNotesEvent,\n UpdatedRequestedActivationEvent,\n UpdatedSalespersonEvent,\n UpdatedTagsEvent,\n UpdateEnforceContractTermEvent,\n UpdatedCustomerAttachmentsEvent,\n UpdatedAttachmentsEvent,\n} from '@vendasta/sales-orders';\n\n@Pipe({ standalone: true, name: 'buildDetailName' })\nexport class BuildDetailNamePipe implements PipeTransform {\n transform(orderDetailField: OrderDetailField, isChild: boolean): string {\n if (isChild) {\n return this.getEventNameTranslationKeyForOrderDetailField(orderDetailField);\n }\n // Not all sales histories were created with a status\n return StatusToBadgeMapping.get(orderDetailField.status)?.text || StatusToBadgeMapping.get(Status.SUBMITTED).text;\n }\n\n getEventNameTranslationKeyForOrderDetailField(orderDetailField: OrderDetailField): string {\n if (!orderDetailField || !orderDetailField.metadata) {\n return null;\n }\n\n if (orderDetailField.metadata.event.id === UpdatedTagsEvent) {\n return 'FRONTEND.SALES_UI.UPDATED_TAGS';\n }\n\n if (orderDetailField.metadata.event.id === UpdatedNotesEvent) {\n return 'FRONTEND.SALES_UI.UPDATED_NOTES';\n }\n\n if (orderDetailField.metadata.event.id === UpdatedSalespersonEvent) {\n return 'FRONTEND.SALES_UI.UPDATED_SALESPERSON';\n }\n\n if (orderDetailField.metadata.event.id === UpdatedCustomerNotesEvent) {\n return 'FRONTEND.SALES_UI.UPDATED_CUSTOMER_NOTES';\n }\n\n if (orderDetailField.metadata.event.id === UpdatedRequestedActivationEvent) {\n return 'FRONTEND.SALES_UI.UPDATED_CONTRACT_START';\n }\n\n if (orderDetailField.metadata.event.id === UpdateContractDurationEvent) {\n return 'FRONTEND.SALES_UI.UPDATED_CONTRACT_LENGTH';\n }\n\n if (orderDetailField.metadata.event.id === UpdateEnforceContractTermEvent) {\n return 'FRONTEND.SALES_UI.UPDATED_ENFORCE_CONTRACT_TERM';\n }\n\n if (orderDetailField.metadata.event.id === RequestCancellationEvent) {\n return 'FRONTEND.SALES_UI.CANCELLATION_REQUESTED';\n }\n\n if (orderDetailField.metadata.event.id === DeclineCancellationEvent) {\n return 'FRONTEND.SALES_UI.CANCELLATION_DECLINED';\n }\n\n if (orderDetailField.metadata.event.id === UpdatedDraftEvent) {\n return 'FRONTEND.SALES_UI.UPDATED_DRAFT';\n }\n\n if (orderDetailField.metadata.event.id === UpdatedLineItemsEvent) {\n return 'FRONTEND.SALES_UI.UPDATED_CONTENTS';\n }\n\n if (orderDetailField.metadata.event.id === UpdatedCustomerAttachmentsEvent) {\n return 'FRONTEND.SALES_UI.UPDATED_CUSTOMER_ATTACHMENTS';\n }\n\n if (orderDetailField.metadata.event.id === UpdatedAttachmentsEvent) {\n return 'FRONTEND.SALES_UI.UPDATED_ADMIN_ATTACHMENTS';\n }\n\n if (orderDetailField.status !== Status.SUBMITTED_FOR_CUSTOMER_APPROVAL) {\n return null;\n }\n return this.getCustomerApprovalTranslationKeyForEventID(orderDetailField.metadata.event.id);\n }\n\n getCustomerApprovalTranslationKeyForEventID(eventID: string): string {\n switch (eventID) {\n case CustomerApprovalEventSending:\n return 'FRONTEND.SALES_UI.CUSTOMER_EVENTS.SENDING';\n case CustomerApprovalEventProcessing:\n return 'FRONTEND.SALES_UI.CUSTOMER_EVENTS.PROCESSING';\n case CustomerApprovalEventDelivered:\n return 'FRONTEND.SALES_UI.CUSTOMER_EVENTS.DELIVERED';\n case CustomerApprovalEventBounced:\n return 'FRONTEND.SALES_UI.CUSTOMER_EVENTS.BOUNCED';\n case CustomerApprovalEventDropped:\n return 'FRONTEND.SALES_UI.CUSTOMER_EVENTS.DROPPED';\n case CustomerApprovalEventUnsubscribed:\n return 'FRONTEND.SALES_UI.CUSTOMER_EVENTS.UNSUBSCRIBED';\n case CustomerApprovalEventDeferred:\n return 'FRONTEND.SALES_UI.CUSTOMER_EVENTS.DEFERRED';\n case CustomerApprovalEventSpamReport:\n return 'FRONTEND.SALES_UI.CUSTOMER_EVENTS.SPAM_REPORT';\n case CustomerApprovalEventOpened:\n return 'FRONTEND.SALES_UI.CUSTOMER_EVENTS.EMAIL_OPENED';\n case CustomerApprovalEventClicked:\n return 'FRONTEND.SALES_UI.CUSTOMER_EVENTS.EMAIL_CLICKED';\n case CustomerApprovalEventUpdatedAnswers:\n return 'FRONTEND.SALES_UI.CUSTOMER_EVENTS.UPDATED_ANSWERS';\n default:\n return 'FRONTEND.SALES_UI.CUSTOMER_EVENTS.UNKNOWN';\n }\n }\n}\n", "import { Pipe, PipeTransform } from '@angular/core';\nimport { ContractDurationEnumToKeys, OrderDetailField } from '@vendasta/sales-ui';\nimport {\n CustomerNotesMetadata,\n DeclineCancellationEvent,\n DeclinedOrderMetadata,\n HistoryMetadata,\n NotesMetadata,\n Order,\n RequestCancellationMetadata,\n SubmittedForCustomerApprovalSendingMetadata,\n UpdateContractDurationMetadata,\n UpdatedRequestedActivationMetadata,\n UpdatedTagsMetadata,\n UserInterface as SalesUser,\n} from '@vendasta/sales-orders';\nimport { DatePipe } from '@angular/common';\nimport { TranslateService } from '@ngx-translate/core';\n\n@Pipe({ standalone: true, name: 'buildDetailValue' })\nexport class BuildDetailValuePipe implements PipeTransform {\n constructor(private translateService: TranslateService) {}\n\n transform(orderDetailField: OrderDetailField, order: Order): string {\n const nameAndDateString = this.formatDateAndNamePair(orderDetailField.date, orderDetailField.userInfo);\n return this.buildValueFromMetadata(nameAndDateString, orderDetailField.metadata, order).replace(/\\n/g, '
');\n }\n\n private formatDateAndNamePair(date: Date, userInfo: string): string {\n const formattedDate = this.formatDateWithTime(date);\n if (userInfo) {\n return this.translateService.instant('FRONTEND.SALES_UI.ORDER_DETAILS.DATE_BY_USER', {\n date: formattedDate,\n user: userInfo,\n });\n }\n // If there's no user just display the date ()\n return formattedDate;\n }\n\n private getDisplayNameForSalesUser(user: SalesUser): string {\n if (!user) {\n return '';\n }\n const names = [];\n if (user.firstName) {\n names.push(user.firstName);\n }\n if (user.lastName) {\n names.push(user.lastName);\n }\n const displayName = names.join(' ');\n if (!displayName) {\n return user.email;\n }\n return displayName;\n }\n\n private formatDisplayName(firstName: string, lastName: string): string {\n const names = [];\n if (firstName) {\n names.push(firstName);\n }\n if (lastName) {\n names.push(lastName);\n }\n return names.join(' ');\n }\n\n private formatDateWithTime(date: Date): string {\n return new DatePipe('en-US').transform(date, 'MMMM d, y, h:mm:ss a');\n }\n\n private buildValueFromMetadata(nameAndDateString: string, metadata: HistoryMetadata, order: Order): string {\n if (\n metadata instanceof RequestCancellationMetadata ||\n metadata instanceof NotesMetadata ||\n metadata instanceof CustomerNotesMetadata\n ) {\n let notes = metadata.current;\n if (metadata.event.id === DeclineCancellationEvent) {\n notes = metadata.event.name + '\\n' + metadata.current;\n }\n return nameAndDateString + '\\n' + notes;\n }\n if (metadata instanceof DeclinedOrderMetadata) {\n const notes = [...metadata.declinedReasons, metadata.current].join('\\n');\n return nameAndDateString + '\\n' + notes;\n }\n if (metadata instanceof UpdatedTagsMetadata) {\n const tagsString =\n !!metadata.tags && metadata.tags.length > 0\n ? this.translateService.instant('FRONTEND.SALES_UI.ORDER_DETAILS.UPDATED_TO', {\n value: metadata.tags.join(', '),\n })\n : this.translateService.instant('FRONTEND.SALES_UI.ORDER_DETAILS.ALL_TAGS_REMOVED');\n return nameAndDateString + '\\n' + tagsString;\n }\n if (metadata instanceof UpdatedRequestedActivationMetadata) {\n if (!!metadata.previous && !!metadata.current) {\n return (\n nameAndDateString +\n '\\n' +\n this.translateService.instant('FRONTEND.SALES_UI.ORDER_DETAILS.UPDATED_FROM_TO', {\n fromValue: this.formatDate(metadata.previous),\n toValue: this.formatDate(metadata.current),\n })\n );\n }\n if (metadata.current) {\n return (\n nameAndDateString +\n '\\n' +\n this.translateService.instant('FRONTEND.SALES_UI.ORDER_DETAILS.UPDATED_TO', {\n value: this.formatDate(metadata.current),\n })\n );\n }\n if (metadata.previous) {\n return (\n nameAndDateString +\n '\\n' +\n this.translateService.instant('FRONTEND.SALES_UI.ORDER_DETAILS.UPDATED_FROM_TO', {\n fromValue: this.formatDate(metadata.previous),\n toValue: this.translateService.instant('FRONTEND.SALES_UI.ORDER_DETAILS.NO_CONTRACT_START_DATE'),\n })\n );\n }\n return (\n nameAndDateString +\n '\\n' +\n this.translateService.instant('FRONTEND.SALES_UI.ORDER_DETAILS.UPDATED_TO', {\n value: this.translateService.instant('FRONTEND.SALES_UI.ORDER_DETAILS.NO_CONTRACT_START_DATE'),\n })\n );\n }\n if (metadata instanceof UpdateContractDurationMetadata) {\n if (!!metadata.previous && !!metadata.current) {\n const previousDurationPeriod = this.translateService.instant(\n ContractDurationEnumToKeys[metadata.previous.duration],\n );\n const previousDuration = `${metadata.previous.value} ${previousDurationPeriod}`;\n const currentDurationPeriod = this.translateService.instant(\n ContractDurationEnumToKeys[metadata.current.duration],\n );\n const currentDuration = `${metadata.current.value} ${currentDurationPeriod}`;\n return (\n nameAndDateString +\n '\\n' +\n this.translateService.instant('FRONTEND.SALES_UI.ORDER_DETAILS.UPDATED_FROM_TO', {\n fromValue: previousDuration,\n toValue: currentDuration,\n })\n );\n }\n if (metadata.current) {\n const currentDurationPeriod = this.translateService.instant(\n ContractDurationEnumToKeys[metadata.current.duration],\n );\n const currentDuration = `${metadata.current.value} ${currentDurationPeriod}`;\n return (\n nameAndDateString +\n '\\n' +\n this.translateService.instant('FRONTEND.SALES_UI.ORDER_DETAILS.UPDATED_TO', {\n value: currentDuration,\n })\n );\n }\n if (metadata.previous) {\n const previousDurationPeriod = this.translateService.instant(\n ContractDurationEnumToKeys[metadata.previous.duration],\n );\n const previousDuration = `${metadata.previous.value} ${previousDurationPeriod}`;\n return (\n nameAndDateString +\n '\\n' +\n this.translateService.instant('FRONTEND.SALES_UI.ORDER_DETAILS.UPDATED_FROM_TO', {\n fromValue: previousDuration,\n toValue: this.translateService.instant('FRONTEND.SALES_UI.ORDER_DETAILS.NO_CONTRACT_LENGTH'),\n })\n );\n }\n return (\n nameAndDateString +\n '\\n' +\n this.translateService.instant('FRONTEND.SALES_UI.ORDER_DETAILS.UPDATED_TO', {\n value: this.translateService.instant('FRONTEND.SALES_UI.ORDER_DETAILS.NO_CONTRACT_LENGTH'),\n })\n );\n }\n if (metadata instanceof SubmittedForCustomerApprovalSendingMetadata) {\n let resultString = nameAndDateString;\n if (order?.customerRecipient?.firstName || order?.customerRecipient?.lastName) {\n resultString =\n resultString +\n '\\n' +\n 'Sent to ' +\n this.formatDisplayName(order.customerRecipient.firstName ?? '', order.customerRecipient.lastName ?? '');\n }\n if (metadata?.offerExpiry) {\n return resultString + '\\n' + 'Expires ' + this.formatDateWithTime(metadata.offerExpiry);\n }\n return resultString;\n }\n return nameAndDateString;\n }\n\n private formatDate(date: Date): string {\n return new DatePipe('en-US').transform(date, 'MMMM d, y');\n }\n}\n", "import { Component, Input, OnInit } from '@angular/core';\nimport { Order } from '@vendasta/sales-orders';\nimport { Observable } from 'rxjs';\nimport { map } from 'rxjs/operators';\nimport { OrderDetailField } from '@vendasta/sales-ui';\n\n@Component({\n selector: 'orders-order-activity',\n templateUrl: './order-activity.component.html',\n styleUrls: ['./order-activity.component.scss'],\n standalone: false,\n})\nexport class OrderActivityComponent implements OnInit {\n @Input() order$!: Observable;\n orderDetailFields$: Observable;\n\n ngOnInit(): void {\n this.orderDetailFields$ = this.order$.pipe(\n map((salesOrder) => OrderDetailField.createFromOrderWithUsers(salesOrder, {})),\n );\n }\n\n protected activityTrackIndex(index: number, item: OrderDetailField) {\n return `${index}-${item.status}`;\n }\n}\n", "@if (this.order$ | async; as order) {\n @if (this.orderDetailFields$ | async; as orderDetails) {\n \n \n {{ 'LIB_ORDERS.COMMON.ACTIVITY.TITLE' | translate }} \n \n @for (info of orderDetails; track activityTrackIndex($index, info)) {\n \n \n \n \n \n \n \n \n @for (child of info.childFields; track activityTrackIndex($index, child)) {\n \n \n }\n \n \n \n \n
\n
\n {{ info | buildDetailName: isChild | translate }}\n
\n
\n \n
\n \n }\n
\n }\n}\n", "import {\n Component,\n DestroyRef,\n EventEmitter,\n Inject,\n Input,\n OnDestroy,\n OnInit,\n Output,\n signal,\n ViewChild,\n} from '@angular/core';\nimport { takeUntilDestroyed } from '@angular/core/rxjs-interop';\nimport { UntypedFormBuilder, UntypedFormGroup } from '@angular/forms';\nimport { MatDialog } from '@angular/material/dialog';\nimport { AccountGroup } from '@galaxy/account-group';\nimport { BillingService, Currency } from '@galaxy/billing';\nimport { Environment, EnvironmentService } from '@galaxy/core';\nimport { FeatureFlagService } from '@galaxy/partner';\nimport { TranslateService } from '@ngx-translate/core';\nimport { ProductActivationPrereqFormComponent } from '@vendasta/businesses';\nimport {\n ConfirmationModalMaxWidth,\n ConfirmationModalWidth,\n OpenConfirmationModalService,\n} from '@vendasta/galaxy/confirmation-modal';\nimport { SnackbarService } from '@vendasta/galaxy/snackbar-service';\nimport { FulfillmentFormConfigInterface, FulfillmentOrder, FulfillmentOrderStatus } from '@vendasta/order-fulfillment';\nimport { ProductAnalyticsService } from '@vendasta/product-analytics';\nimport {\n ActivationStatus,\n CommonField,\n ConfigInterface,\n Cost,\n CustomField,\n CustomPriceMapping,\n Field,\n LineItem,\n Order,\n ProductActivation,\n SalesOrdersService,\n Status,\n UserInterface as User,\n ValidationErrorCodes,\n} from '@vendasta/sales-orders';\nimport { OrderFormOptionsInterface } from '@vendasta/store';\nimport {\n WorkOrderDetailsComponent,\n WorkOrderFormConfigService,\n WorkOrderPersona,\n WorkOrderSalesOrderSummaryService,\n} from '@vendasta/work-order';\nimport { BehaviorSubject, combineLatest, EMPTY, interval, merge, Observable, of, Subject, Subscription } from 'rxjs';\nimport {\n catchError,\n concatMap,\n debounceTime,\n delay,\n distinctUntilChanged,\n filter,\n first,\n map,\n mergeMap,\n shareReplay,\n skipWhile,\n startWith,\n switchMap,\n take,\n tap,\n withLatestFrom,\n} from 'rxjs/operators';\nimport { Features } from '../../core/features';\nimport { OrderFeature } from '../../core/permissions';\nimport { OrderAction } from '../../core/permissions/permissions';\nimport { OrderPermissionsService } from '../../core/permissions/permissions.service';\nimport { PAGE_ORDER_CONFIG_TOKEN, PageOrderConfig } from '../../core/tokens';\nimport { DEMO_SSC_UPLOAD_URL, PROD_SSC_UPLOAD_URL } from '../../shared/constants';\nimport { ActivationDateConfirmationDialogComponent } from '../activation-date-confirmation-dialog/activation-date-confirmation-dialog.component';\nimport { LineItemsComponent } from '../line-items/line-items.component';\nimport { OrderFormComponent } from '../order-form/order-form.component';\nimport { AppVariablePriceMap, PackageVariablePriceMap } from '../variable-prices/variable-prices.component';\nimport { VariablePricesService } from '../variable-prices/variable-prices.service';\nimport { CancelOrderDialogComponent } from './dialogs/cancel-order/cancel-order-dialog.component';\nimport { DeclineCancellationDialogComponent } from './dialogs/decline-cancellation/decline-cancellation-dialog.component';\nimport { format, isBefore, isSameDay } from 'date-fns';\nimport { ProjectsAndSubtasksByProductsService, ProjectTrackerInfo } from '@vendasta/project-tracker';\nimport { PersonaType } from '@vendasta/iam';\nimport { OrderDeclineDialogComponent } from './dialogs/order-decline-dialog/order-decline-dialog.component';\nimport { AppType } from '@vendasta/marketplace-apps';\nimport { RetailSummary } from '../retail-summary/retail-summary.component';\nimport { OrderLineItemValidationService } from '../order-line-item-validation/order-line-item-validation.service';\nimport { getRetailSummaryFromOrder } from '../../shared/utils';\nimport { OrdersEditOrderFormValidationService } from './edit-order-form-validation.service';\nimport { OrdersEditTabService } from './edit-order-tab.service';\n\nconst INVALID_APPS_IN_ORDER = 'Invalid apps in order';\nconst ORDER_DETAIL_DIALOG_WIDTH = '660px';\n\ninterface fulfillmentStatusCardData {\n productActivations: ProductActivation[];\n workOrders: FulfillmentOrder[];\n}\n\n@Component({\n selector: 'orders-edit-order',\n templateUrl: './edit-order.component.html',\n styleUrls: [\n '../../shared/shared-styles.scss',\n '../create-order/create/create-order.component.scss',\n './edit-order.component.scss',\n ],\n providers: [OrderPermissionsService, OrdersEditOrderFormValidationService],\n standalone: false,\n})\nexport class EditOrderComponent implements OnInit, OnDestroy {\n @Input() business?: Pick;\n @Input() users: User[] = [];\n @Input() fileUploadUrl = '';\n @Input() productIds: string[] = [];\n @Input() fulfillmentFormLink = '';\n @Input() hideNotesSections?: boolean;\n @Input() hideTagsSection?: boolean;\n @Input() hideAttachmentsSection?: boolean;\n @Input() hideOrderFormSaveButton?: boolean;\n\n @Input() set order(order: Order) {\n this.order$$.next(order);\n }\n\n // let the parent component (in the client) be responsible for refreshing the order\n // so that other child components on the page respond accordingly\n @Output() refreshOrderDetails = new EventEmitter();\n\n @ViewChild('lineItems') lineItemsComponent!: LineItemsComponent;\n\n productPrereqForm$$ = new BehaviorSubject(null);\n productPrereqForm$ = this.productPrereqForm$$.asObservable().pipe(\n filter((form) => form != null),\n // delay is needed due to lifecycle issues with ViewChild components and observable initialization\n delay(0),\n );\n\n get productPrereqForm(): ProductActivationPrereqFormComponent | null {\n return this.productPrereqForm$$.getValue();\n }\n\n @ViewChild('productPrereqForm', { static: false }) set productPrereqForm(form: ProductActivationPrereqFormComponent) {\n if (form) {\n this.productPrereqForm$$.next(form);\n }\n }\n\n salesOrderForm$$ = new BehaviorSubject(null);\n salesOrderForm$ = this.salesOrderForm$$.asObservable().pipe(\n filter((form) => form != null),\n // delay is needed due to lifecycle issues with ViewChild components and observable initialization\n delay(0),\n );\n\n get salesOrderForm(): OrderFormComponent | null {\n return this.salesOrderForm$$.getValue();\n }\n\n @ViewChild('orderForm', { static: false }) set salesOrderForm(form: OrderFormComponent) {\n if (form) {\n this.salesOrderForm$$.next(form);\n }\n }\n\n @ViewChild('workOrderDetails') workOrderDetails: WorkOrderDetailsComponent;\n\n public Scheduled: Status = Status.SCHEDULED_ACTIVATION;\n\n private readonly order$$ = new BehaviorSubject(null);\n order$: Observable = this.order$$.asObservable().pipe(filter((order) => order != null));\n workOrders$!: Observable;\n productActivations$!: Observable;\n fulfillmentStatusCardData$: Observable;\n\n orderFormOptions$!: Observable;\n\n orderDetailsFormGroup: UntypedFormGroup;\n orderFormGroup: UntypedFormGroup;\n subscriptions: Subscription[] = [];\n attachmentsUploadUrl = PROD_SSC_UPLOAD_URL;\n orderConfig$!: Observable;\n hasAtLeastOneForm$!: Observable;\n shouldShowDetailsNeededBanner$!: Observable;\n unifiedOrderPageEnabled$!: Observable;\n selectedTabIndex$: Observable;\n hasOrderForms$: Observable;\n invalidForms$!: Observable;\n showFooter$: Observable;\n workOrderPersona$!: Observable;\n protected readonly WorkOrderPersona = WorkOrderPersona;\n public Status = Status;\n\n partnerCurrency$: Observable;\n variablePricesMap$: Observable;\n variablePackagePricesMap$: Observable;\n\n canEditOrderContents$: Observable;\n canEditInvoices$: Observable;\n canViewFulfillmentStatuses$: Observable;\n canViewTags$: Observable;\n canEditTags$: Observable;\n canViewCreateOrderDetails$: Observable;\n canViewCondensedOrderDetails$: Observable;\n canViewActiveItems$: Observable;\n canViewBusinessHeader$: Observable;\n canViewInvalidLineItemsBanner$: Observable;\n\n canSubmitWithoutRequiredFields$: Observable;\n canReviewCancellationRequest$: Observable;\n canCancelOrder$: Observable;\n canArchiveOrder$: Observable;\n canActivateOrder$: Observable;\n canScheduleActivation$: Observable;\n canReviewSubmittedOrder$: Observable;\n canChargeSmbOnOrderSubmission$: Observable;\n\n projectTrackerInfo$: Observable;\n retailSummary$: Observable;\n\n confirmationCheckbox = false;\n invalidActivateAttempt = false;\n\n canEditOrderCurrency$: Observable;\n private readonly currencyOverride$$ = new BehaviorSubject('');\n currencyOverride$ = this.currencyOverride$$.asObservable();\n currencySelectorOptions: string[] = [Currency.USD, Currency.CAD, Currency.AUD];\n invalidOrderLineItems$: Observable;\n\n protected processingAction = signal(false);\n saveUpdatedLineItems$$ = new Subject();\n\n constructor(\n @Inject(PAGE_ORDER_CONFIG_TOKEN) private readonly pageOrderConfig: PageOrderConfig,\n private environmentService: EnvironmentService,\n private formBuilder: UntypedFormBuilder,\n private salesOrdersService: SalesOrdersService,\n private workOrderFormConfigService: WorkOrderFormConfigService,\n private workOrderSalesOrderSummaryService: WorkOrderSalesOrderSummaryService,\n private billingService: BillingService,\n private variablePricesService: VariablePricesService,\n private readonly permissionsService: OrderPermissionsService,\n private dialog: MatDialog,\n private confirmationModal: OpenConfirmationModalService,\n private featureFlagService: FeatureFlagService,\n private analyticsService: ProductAnalyticsService,\n private snackbarService: SnackbarService,\n private destroyRef: DestroyRef,\n private readonly translateService: TranslateService,\n private projectTrackerService: ProjectsAndSubtasksByProductsService,\n private lineItemValidationService: OrderLineItemValidationService,\n private tabService: OrdersEditTabService,\n private formValidationService: OrdersEditOrderFormValidationService,\n ) {\n this.orderDetailsFormGroup = this.formBuilder.group({});\n this.orderFormGroup = this.formBuilder.group({});\n }\n\n ngOnInit(): void {\n this.selectedTabIndex$ = this.tabService.selectedTabIndex$;\n if (this.environmentService.getEnvironment() === Environment.DEMO) {\n this.attachmentsUploadUrl = DEMO_SSC_UPLOAD_URL;\n }\n if (this.business) {\n this.unifiedOrderPageEnabled$ = this.featureFlagService\n .batchGetStatus(this.business.externalIdentifiers.partnerId, this.business.accountGroupId, [\n Features.PC_UNIFIED_ORDERS_PAGE,\n ])\n .pipe(map((res) => res[Features.PC_UNIFIED_ORDERS_PAGE]));\n\n this.orderConfig$ = this.salesOrdersService\n .getConfig(this.business.externalIdentifiers.partnerId, this.business.externalIdentifiers.marketId)\n .pipe(shareReplay(1));\n }\n\n this.orderFormOptions$ = combineLatest([\n this.orderConfig$,\n this.permissionsService.CanEdit(OrderFeature.OrderForms),\n ]).pipe(\n map(([config, canEditOrderForms]) => {\n return {\n readOnly: !canEditOrderForms,\n showOfficeUseQuestions: true,\n bypassRequiredQuestions: !config.salespersonOptions?.validateRequiredFields,\n };\n }),\n );\n\n const formConfigs$: Observable> = this.order$.pipe(\n switchMap((order) => {\n return this.salesOrdersService.previewOrderActivations(order.orderId, order.businessId).pipe(\n map((activations) => {\n const productIds: string[] = activations?.map((activation) => activation.productId) || [];\n return Array.from(new Set(productIds));\n }),\n switchMap((productIds) => {\n const formConfigs = productIds.map((productId) =>\n this.workOrderFormConfigService.getFormConfig(order.orderId, productId, order.businessId),\n );\n return combineLatest(formConfigs).pipe(\n map((configs) => {\n const productIdToConfigMap: Map = new Map();\n productIds.forEach((productId, i) => {\n productIdToConfigMap.set(productId, configs[i]);\n });\n return productIdToConfigMap;\n }),\n );\n }),\n );\n }),\n shareReplay({ bufferSize: 1, refCount: true }),\n );\n\n this.hasAtLeastOneForm$ = formConfigs$.pipe(\n map((formConfigs) => {\n return Array.from(formConfigs.values()).some((config) => {\n if (!config || !config.orderForm) {\n return false;\n }\n return config?.orderForm?.length > 0;\n });\n }),\n );\n\n this.workOrders$ = this.order$.pipe(\n switchMap((order) =>\n this.workOrderSalesOrderSummaryService.getWorkOrdersForSalesOrder(order.orderId, order.businessId, true),\n ),\n map((workOrders) => {\n if (!workOrders) {\n return [];\n }\n return workOrders.filter(\n (workOrder) => Object.hasOwnProperty.call(workOrder, 'status') && workOrder?.fulfillmentFormInfo?.hasForm,\n );\n }),\n );\n const view$ = this.pageOrderConfig.view$ ?? of('salesperson-view');\n this.workOrderPersona$ = view$.pipe(\n map((view) => {\n let persona = WorkOrderPersona.SALES;\n if (view === 'admin-view') {\n persona = WorkOrderPersona.CP;\n }\n return persona;\n }),\n );\n\n this.canEditOrderContents$ = this.permissionsService.CanEdit(OrderFeature.OrderContents);\n this.canEditInvoices$ = this.permissionsService.CanEdit(OrderFeature.Invoices);\n this.canViewFulfillmentStatuses$ = this.permissionsService.CanView(OrderFeature.FulfillmentStatuses);\n this.canViewTags$ = this.permissionsService\n .CanView(OrderFeature.Tags)\n .pipe(map((canView) => canView && !this.hideTagsSection));\n this.canEditTags$ = this.permissionsService.CanEdit(OrderFeature.Tags);\n this.canViewCreateOrderDetails$ = this.permissionsService.CanView(OrderFeature.CreateOrderDetails);\n this.canViewCondensedOrderDetails$ = this.permissionsService.CanView(OrderFeature.CondensedOrderDetails);\n this.canViewActiveItems$ = this.permissionsService.CanView(OrderFeature.ActiveItems);\n this.canViewBusinessHeader$ = this.permissionsService.CanView(OrderFeature.BusinessHeader);\n this.canViewInvalidLineItemsBanner$ = this.permissionsService.CanView(OrderFeature.InvalidLineItemsBanner);\n\n this.canSubmitWithoutRequiredFields$ = this.permissionsService.CanDoAction(OrderAction.SubmitWithoutRequiredFields);\n this.canReviewCancellationRequest$ = this.permissionsService.CanDoAction(OrderAction.ReviewCancellationRequest);\n this.canCancelOrder$ = this.permissionsService.CanDoAction(OrderAction.CancelOrder);\n this.canArchiveOrder$ = this.permissionsService.CanDoAction(OrderAction.ArchiveOrder);\n this.canActivateOrder$ = this.permissionsService.CanDoAction(OrderAction.ActivateOrder);\n this.canScheduleActivation$ = this.permissionsService.CanDoAction(OrderAction.ScheduleActivation);\n this.canReviewSubmittedOrder$ = this.permissionsService.CanDoAction(OrderAction.ReviewSubmittedOrder);\n this.canEditOrderCurrency$ = this.permissionsService.CanDoAction(OrderAction.SelectOrderCurrency);\n this.canChargeSmbOnOrderSubmission$ = this.permissionsService.CanDoAction(OrderAction.ChargeSMBOnOrderSubmission);\n this.showFooter$ = combineLatest([\n this.unifiedOrderPageEnabled$,\n this.canCancelOrder$,\n this.canArchiveOrder$,\n this.canActivateOrder$,\n this.canScheduleActivation$,\n this.canReviewSubmittedOrder$,\n ]).pipe(\n map(\n ([\n unifiedOrderPageEnabled,\n canCancelOrder,\n canArchiveOrder,\n canActivateOrder,\n canScheduleActivation,\n canReviewSubmittedOrder,\n ]) =>\n unifiedOrderPageEnabled &&\n (canCancelOrder || canArchiveOrder || canActivateOrder || canScheduleActivation || canReviewSubmittedOrder),\n ),\n );\n\n this.shouldShowDetailsNeededBanner$ = combineLatest([this.workOrders$, this.unifiedOrderPageEnabled$]).pipe(\n map(\n ([workOrders, isUnifiedOrderPageEnabled]) =>\n !isUnifiedOrderPageEnabled &&\n workOrders.some((wo) => wo.status === FulfillmentOrderStatus.FULFILLMENT_ORDER_STATUS_DETAILS_NEEDED),\n ),\n );\n\n this.invalidForms$ = combineLatest([\n this.productPrereqForm$,\n this.salesOrderForm$,\n this.unifiedOrderPageEnabled$,\n ]).pipe(\n switchMap(([prereqForm, salesOrderForm, unifiedOrderPageEnabled]) => {\n if (!prereqForm || !salesOrderForm || !unifiedOrderPageEnabled) {\n return of(false);\n }\n return combineLatest([prereqForm.isValid$, salesOrderForm.isValid$]).pipe(\n map(([prereqFormValid, orderFormValid]) => {\n return !prereqFormValid || !orderFormValid;\n }),\n );\n }),\n tap((invalidForms) => this.formValidationService.setInvalidForms(invalidForms)),\n distinctUntilChanged(),\n startWith(false),\n );\n\n this.hasOrderForms$ = combineLatest([this.productPrereqForm$, this.salesOrderForm$]).pipe(\n filter(([productPrereqForm, salesOrderForm]) => productPrereqForm !== null && salesOrderForm !== null),\n switchMap(([productPrereqForm, salesOrderForm]) => {\n return combineLatest([productPrereqForm.hasRestrictions$, salesOrderForm.orderData$]);\n }),\n withLatestFrom(this.unifiedOrderPageEnabled$),\n map(([[businessPrereqRequired, orderData], unifiedOrderPageEnabled]) => {\n if (!unifiedOrderPageEnabled) {\n return false;\n }\n return businessPrereqRequired || orderData?.orderForms?.length > 0 || orderData?.extraQuestions?.length > 0;\n }),\n );\n\n this.subscriptions.push(\n this.hasAtLeastOneForm$.pipe(take(1)).subscribe((hasFormConfig) => {\n if (hasFormConfig) {\n this.analyticsService.trackEvent('draft-order-viewed', 'page-view', 'page-view');\n }\n }),\n );\n\n this.order$\n .pipe(takeUntilDestroyed(this.destroyRef))\n .subscribe((order) => this.variablePricesService.setLineItems(order.lineItems));\n this.partnerCurrency$ = this.order$.pipe(\n switchMap((order) => {\n return this.billingService.getMerchantWholesaleCurrency(order.partnerId);\n }),\n shareReplay(1),\n );\n this.variablePricesMap$ = this.variablePricesService.variablePricesMap$;\n this.variablePackagePricesMap$ = this.variablePricesService.variablePackagePricesMap$;\n\n this.retailSummary$ = this.order$.pipe(\n map((salesOrder) => {\n return getRetailSummaryFromOrder(salesOrder);\n }),\n );\n\n const orderCurrencyInitialChange$ = combineLatest([this.order$, this.canEditOrderCurrency$]).pipe(\n take(1),\n map(([order, canEditOrderCurrency]) => {\n if (canEditOrderCurrency && order?.lineItems?.length) {\n return order.lineItems[0].currencyCode;\n }\n }),\n );\n\n this.currencyOverride$ = merge(this.currencyOverride$$.asObservable(), orderCurrencyInitialChange$);\n\n this.projectTrackerInfo$ = this.order$.pipe(\n switchMap((salesOrder) =>\n this.projectTrackerService.lookupVisibleProjectsForAccountByOrderIds(\n salesOrder.partnerId,\n salesOrder.businessId,\n salesOrder.orderId ? [salesOrder.orderId] : [],\n [PersonaType.partner, PersonaType.sales_person, PersonaType.smb],\n ),\n ),\n map((info: ProjectTrackerInfo[]) =>\n info.map((tracker) => {\n return {\n ...tracker,\n subtasks: tracker.subtasks,\n };\n }),\n ),\n );\n\n this.productActivations$ = this.order$.pipe(\n switchMap((order) => {\n if (order.productActivations) {\n return of(order.productActivations);\n }\n return this.salesOrdersService.previewOrderActivations(order.orderId, order.businessId);\n }),\n catchError(() => {\n return of([]);\n }),\n shareReplay({ bufferSize: 1, refCount: true }),\n );\n\n this.fulfillmentStatusCardData$ = combineLatest([\n this.productActivations$,\n this.canViewFulfillmentStatuses$,\n this.workOrders$,\n ]).pipe(\n takeUntilDestroyed(this.destroyRef),\n map(([productActivations, canViewFulfillmentStatuses, workOrders]) => {\n const shouldDisplay = productActivations && productActivations.length > 0 && canViewFulfillmentStatuses;\n\n if (shouldDisplay) {\n return {\n productActivations,\n workOrders,\n };\n } else {\n return null;\n }\n }),\n );\n this.invalidOrderLineItems$ = this.lineItemValidationService.invalidLineItemErrors$.pipe(\n map((errors) => errors?.errorCodes),\n );\n\n this.saveUpdatedLineItems$$\n .asObservable()\n .pipe(\n takeUntilDestroyed(this.destroyRef),\n withLatestFrom(this.order$),\n debounceTime(400),\n tap(() => this.processingAction.set(true)),\n mergeMap(([lineItems, order]) => {\n return this.salesOrdersService\n .updateLineItems(\n order.orderId,\n order.businessId,\n lineItems ?? this.lineItemsComponent.getCurrentLineItems(),\n )\n .pipe(catchError(() => EMPTY));\n }),\n )\n .subscribe({\n next: () => {\n this.processingAction.set(false);\n this.refreshOrderDetails.emit(true);\n this.workOrderDetails?.refreshWorkOrders();\n },\n });\n }\n\n ngOnDestroy(): void {\n this.subscriptions.forEach((sub) => {\n sub.unsubscribe();\n });\n }\n\n hasUnsavedChanges(): Observable {\n return this.productPrereqForm?.hasUnsavedChanges$ ?? of(false);\n }\n\n navigateToWorkOrdersTab(): void {\n this.tabService.navigateToWorkOrdersTab();\n }\n\n indexChanged(index: number): void {\n this.tabService.setSelectedTabIndex(index);\n }\n\n validateForm(): Observable {\n if (!this.productPrereqForm) {\n return of(false);\n }\n\n return combineLatest([\n this.permissionsService.CanDoAction(OrderAction.SubmitWithoutRequiredFields),\n this.productPrereqForm.isValid$,\n this.unifiedOrderPageEnabled$,\n ]).pipe(\n map(([canSubmitWithoutRequiredFields, isValid, unifiedOrderPageEnabled]) => {\n if (!isValid) {\n this.snackbarService.openErrorSnack('LIB_ORDERS.SALES_ORDERS.ERRORS.PREREQ_FORM_IS_NOT_VALID');\n return false;\n }\n if (!canSubmitWithoutRequiredFields && !this.salesOrderForm?.validateForm()) {\n if (unifiedOrderPageEnabled) {\n this.confirmationModal\n .openModal({\n title: 'LIB_ORDERS.SALES_ORDERS.INVALID_ORDER_FORMS_DIALOG.TITLE',\n message: 'LIB_ORDERS.SALES_ORDERS.INVALID_ORDER_FORMS_DIALOG.MESSAGE',\n confirmButtonText: 'LIB_ORDERS.SALES_ORDERS.INVALID_ORDER_FORMS_DIALOG.GO_TO_FORMS',\n cancelButtonText: 'LIB_ORDERS.COMMON.ACTION_LABELS.CLOSE',\n cancelOnEscapeKeyOrBackgroundClick: true,\n })\n .subscribe((goToForms) => {\n if (goToForms) {\n this.tabService.navigateToOrderFormsTab();\n }\n });\n } else {\n this.snackbarService.openErrorSnack('LIB_ORDERS.SALES_ORDERS.ERRORS.FORM_IS_NOT_VALID');\n }\n return false;\n }\n return true;\n }),\n );\n }\n\n saveOrderFormIfDirty(): Observable {\n if (this.orderFormGroup.dirty && this.salesOrderForm) {\n return this.salesOrderForm.updateAnswers();\n }\n return of(true);\n }\n\n // updates the line items on the order and triggers the parent component to update the order reference\n saveUpdatedLineItems(lineItems?: LineItem[]): void {\n this.saveUpdatedLineItems$$.next(lineItems);\n }\n\n /**\n * returns the data from common form fields in a format that is usable by sales orders\n */\n public getCommonFormData(): CommonField[] {\n return this.salesOrderForm?.getCommonFormData() ?? [];\n }\n\n /**\n * returns the data from custom forms in a format that is usable by sales orders\n */\n public getCustomFormData(): CustomField[] {\n return this.salesOrderForm?.getCustomFormData() ?? [];\n }\n\n /**\n * returns the data from extra fields in a format that is usable by sales orders\n */\n public getExtraFieldsData(): Field[] {\n return this.salesOrderForm?.getExtraFieldsData() ?? [];\n }\n\n public isValid(): Observable {\n return this.productPrereqForm?.isValid$ ?? of(false);\n }\n\n public printPage(): void {\n this.salesOrderForm?.openDropdown();\n }\n\n public openOrderFormTab(): void {\n this.tabService.navigateToOrderFormsTab;\n }\n\n variablePriceChanged(event: { appId: string; customPrice: number; packageId?: string }) {\n const lineItems = this.lineItemsComponent.getCurrentLineItems();\n applyVariablePriceChange(event, lineItems);\n this.saveUpdatedLineItems(lineItems);\n }\n\n orderCharged(orderCharged: boolean): void {\n if (orderCharged) {\n this.refreshOrderDetails.emit(true);\n }\n }\n\n approveSubmittedOrder(): void {\n this.processingAction.set(true);\n\n const dialogTitle = this.translateService.instant(\n 'LIB_ORDERS.COMMON.ORDERS.DIALOGS.APPROVE_SUBMITTED_ORDER.TITLE',\n {\n businessName: this.businessName(),\n },\n );\n\n this.lineItemValidationService\n .areBillingTermsValidOrOpenModal$()\n .pipe(\n take(1),\n switchMap((areBillingTermsValid) => {\n if (areBillingTermsValid) {\n return this.confirmationModal.openModal({\n title: dialogTitle,\n message: 'LIB_ORDERS.COMMON.ORDERS.DIALOGS.APPROVE_SUBMITTED_ORDER.MESSAGE',\n confirmButtonText: 'LIB_ORDERS.COMMON.ORDERS.DIALOGS.APPROVE_SUBMITTED_ORDER.CONFIRM',\n cancelButtonText: 'LIB_ORDERS.COMMON.ORDERS.DIALOGS.APPROVE_SUBMITTED_ORDER.CANCEL',\n type: 'confirm',\n });\n }\n return of(false);\n }),\n withLatestFrom(this.order$),\n switchMap(([confirm, order]) => {\n if (!confirm) {\n return of(null);\n }\n return this.salesOrdersService.approve(order.businessId, order.orderId);\n }),\n )\n .subscribe({\n next: (result) => {\n if (!result) {\n return;\n }\n\n this.refreshOrderDetails.emit(true);\n this.snackbarService.openSuccessSnack(\n 'LIB_ORDERS.COMMON.ORDERS.DIALOGS.APPROVE_SUBMITTED_ORDER.SUCCESS_MESSAGE',\n );\n },\n error: () => {\n this.snackbarService.openErrorSnack('LIB_ORDERS.COMMON.ORDERS.DIALOGS.APPROVE_SUBMITTED_ORDER.ERROR_MESSAGE');\n },\n complete: () => {\n this.processingAction.set(false);\n },\n });\n }\n\n declineSubmittedOrder(): void {\n this.processingAction.set(true);\n\n this.subscriptions.push(\n this.order$\n .pipe(\n take(1),\n switchMap((order) => {\n const declineDialogRef = this.dialog.open(OrderDeclineDialogComponent, {\n width: ORDER_DETAIL_DIALOG_WIDTH,\n });\n declineDialogRef.componentInstance.orderId = order.orderId;\n declineDialogRef.componentInstance.businessId = order.businessId;\n declineDialogRef.componentInstance.partnerId = order.partnerId;\n declineDialogRef.componentInstance.title = this.translateService.instant(\n 'LIB_ORDERS.COMMON.ORDERS.DIALOGS.DECLINE_SUBMITTED_ORDER.TITLE',\n {\n businessName: this.businessName(),\n },\n );\n declineDialogRef.componentInstance.details = this.translateService.instant(\n 'LIB_ORDERS.COMMON.ORDERS.DIALOGS.DECLINE_SUBMITTED_ORDER.MESSAGE',\n );\n\n return declineDialogRef.afterClosed();\n }),\n )\n .subscribe({\n next: (result) => {\n if (!result) {\n return;\n }\n this.refreshOrderDetails.emit(true);\n this.snackbarService.openSuccessSnack(\n 'LIB_ORDERS.COMMON.ORDERS.DIALOGS.DECLINE_SUBMITTED_ORDER.SUCCESS_MESSAGE',\n );\n },\n error: () => {\n this.snackbarService.openErrorSnack(\n 'LIB_ORDERS.COMMON.ORDERS.DIALOGS.DECLINE_SUBMITTED_ORDER.ERROR_MESSAGE',\n );\n },\n complete: () => {\n this.processingAction.set(false);\n },\n }),\n );\n }\n\n onRequestToCancel(): void {\n this.processingAction.set(true);\n this.dialog\n .open(CancelOrderDialogComponent, {\n width: ConfirmationModalWidth,\n maxWidth: ConfirmationModalMaxWidth,\n autoFocus: false,\n data: {\n isAdmin: true,\n },\n })\n .afterClosed()\n .pipe(\n withLatestFrom(this.order$),\n switchMap(([cancellationReason, order]) => {\n if (!cancellationReason) {\n return of(null);\n }\n\n return this.salesOrdersService.cancelOrder(order.orderId, order.businessId, cancellationReason);\n }),\n )\n .subscribe({\n next: (result) => {\n this.processingAction.set(false);\n if (!result) {\n return;\n }\n\n this.refreshOrderDetails.emit(true);\n this.snackbarService.openSuccessSnack(\n 'LIB_ORDERS.COMMON.ORDERS.DIALOGS.REQUEST_TO_CANCEL_ORDER.SUCCESS_MESSAGE',\n );\n },\n error: () => {\n this.processingAction.set(false);\n this.snackbarService.openErrorSnack('LIB_ORDERS.COMMON.ORDERS.DIALOGS.REQUEST_TO_CANCEL_ORDER.ERROR_MESSAGE');\n },\n });\n }\n\n approveCancellationRequest(): void {\n this.processingAction.set(true);\n\n const getDialogTranslationKey = (path: string) => {\n const base = 'LIB_ORDERS.COMMON.ORDERS.DIALOGS.APPROVE_CANCELLATION_REQUEST';\n return `${base}.${path}`;\n };\n\n const confirmCancellation$ = this.confirmationModal.openModal({\n title: this.translateService.instant(getDialogTranslationKey('TITLE')),\n message: this.translateService.instant(getDialogTranslationKey('MESSAGE')),\n confirmButtonText: this.translateService.instant(getDialogTranslationKey('CONFIRM')),\n cancelButtonText: this.translateService.instant(getDialogTranslationKey('CANCEL')),\n type: 'warn',\n });\n\n combineLatest([this.order$, confirmCancellation$])\n .pipe(\n switchMap(([order, confirmCancellation]) => {\n if (!confirmCancellation) {\n return of(false);\n }\n\n return this.salesOrdersService.approveCancellation(order.orderId, order.businessId);\n }),\n take(1),\n )\n .subscribe({\n next: (result) => {\n if (!result) {\n return;\n }\n\n this.refreshOrderDetails.emit(true);\n this.snackbarService.openSuccessSnack(getDialogTranslationKey('SUCCESS_MESSAGE'));\n },\n error: () => {\n this.snackbarService.openErrorSnack(getDialogTranslationKey('ERROR_MESSAGE'));\n },\n complete: () => {\n this.processingAction.set(false);\n },\n });\n }\n\n onDeclineCancellationRequest(): void {\n this.processingAction.set(true);\n this.dialog\n .open(DeclineCancellationDialogComponent, {\n width: ConfirmationModalWidth,\n maxWidth: ConfirmationModalMaxWidth,\n autoFocus: false,\n })\n .afterClosed()\n .pipe(\n withLatestFrom(this.order$),\n switchMap(([notes, order]) => {\n if (!notes) {\n return of(null);\n }\n\n return this.salesOrdersService.declineCancellation(order.orderId, order.businessId, notes);\n }),\n )\n .subscribe({\n next: (result) => {\n if (!result) {\n return;\n }\n this.refreshOrderDetails.emit(true);\n this.snackbarService.openSuccessSnack(\n 'LIB_ORDERS.COMMON.ORDERS.DIALOGS.DECLINE_ORDER_CANCELLATION.SUCCESS_MESSAGE',\n );\n },\n error: () => {\n this.snackbarService.openErrorSnack(\n 'LIB_ORDERS.COMMON.ORDERS.DIALOGS.DECLINE_ORDER_CANCELLATION.ERROR_MESSAGE',\n );\n },\n complete: () => {\n this.processingAction.set(false);\n },\n });\n }\n\n onArchive(): void {\n this.processingAction.set(true);\n this.confirmationModal\n .openModal({\n title: this.translateService.instant('LIB_ORDERS.COMMON.ORDERS.DIALOGS.ARCHIVE_ORDER.TITLE'),\n message: this.translateService.instant('LIB_ORDERS.COMMON.ORDERS.DIALOGS.ARCHIVE_ORDER.MESSAGE'),\n confirmButtonText: this.translateService.instant('LIB_ORDERS.COMMON.ORDERS.DIALOGS.ARCHIVE_ORDER.ARCHIVE'),\n cancelButtonText: this.translateService.instant('LIB_ORDERS.COMMON.ACTION_LABELS.CANCEL'),\n type: 'warn',\n })\n .pipe(\n withLatestFrom(this.order$),\n switchMap(([archive, order]) => {\n if (!archive) {\n return of(null);\n }\n\n return this.salesOrdersService.archive(order.businessId, order.orderId);\n }),\n )\n .subscribe({\n next: (archivedOrder) => {\n this.processingAction.set(false);\n if (!archivedOrder) {\n return;\n }\n\n const message = this.translateService.instant(\n 'LIB_ORDERS.COMMON.ORDERS.DIALOGS.ARCHIVE_ORDER.SUCCESS_MESSAGE',\n );\n this.snackbarService.openSuccessSnack(message);\n this.refreshOrderDetails.emit(true);\n },\n error: () => {\n this.processingAction.set(false);\n const message = this.translateService.instant('LIB_ORDERS.COMMON.ORDERS.DIALOGS.ARCHIVE_ORDER.ERROR_MESSAGE');\n this.snackbarService.openErrorSnack(message);\n },\n });\n }\n\n onActivateBtnPressed(order: Order): void {\n if (this.processingAction()) return;\n\n combineLatest([\n this.proceedWithoutUnsavedChanges$(),\n this.lineItemValidationService.areBillingTermsValidOrOpenModal$(),\n ])\n .pipe(\n switchMap(([proceed, areBillingTermsValid]) => {\n if (!proceed || !areBillingTermsValid) return EMPTY;\n\n this.processingAction.set(true);\n return combineLatest([this.salesOrderForm.isValid$, this.productPrereqForm.isValid$]);\n }),\n take(1),\n )\n .subscribe(([salesOrderFormIsValid, prereqFormIsValid]) => {\n if (!salesOrderFormIsValid || !prereqFormIsValid) {\n this.invalidActivateAttempt = true;\n this.processingAction.set(false);\n this.snackbarService.openErrorSnack('LIB_ORDERS.SALES_ORDERS.ERRORS.FORM_IS_NOT_VALID');\n } else {\n this.activateProducts(order);\n }\n });\n }\n\n onScheduleBtnPressed(order: Order): void {\n if (this.processingAction() || !this.confirmationCheckbox) return;\n\n combineLatest([\n this.proceedWithoutUnsavedChanges$(),\n this.lineItemValidationService.areBillingTermsValidOrOpenModal$(),\n ])\n .pipe(\n switchMap(([proceed, areBillingTermsValid]) => {\n if (!proceed || !areBillingTermsValid) return EMPTY;\n\n this.processingAction.set(true);\n return combineLatest([this.salesOrderForm.isValid$, this.productPrereqForm.isValid$]);\n }),\n take(1),\n )\n .subscribe(([salesOrderFormIsValid, prereqFormIsValid]) => {\n if (!salesOrderFormIsValid || !prereqFormIsValid) {\n this.invalidActivateAttempt = true;\n this.processingAction.set(false);\n this.snackbarService.openErrorSnack('LIB_ORDERS.SALES_ORDERS.ERRORS.FORM_IS_NOT_VALID');\n } else {\n this.scheduleActivation(order);\n }\n });\n }\n\n private activateProducts(order: Order): void {\n const activateSalesOrder$ = this.activateSalesOrder(order);\n\n this.checkDateForActivation(true)\n .pipe(\n switchMap((shouldContinue) => {\n if (shouldContinue) {\n return activateSalesOrder$;\n }\n\n return of(null);\n }),\n take(1),\n )\n .subscribe({\n next: (processingOrder: Order) => {\n if (processingOrder !== null) {\n this.snackbarService.openSuccessSnack('LIB_ORDERS.SALES_ORDERS.SUCCESS.ACTIVATION_IN_PROCESS');\n this.refreshOrderDetails.emit(true);\n this.refreshOrderWhileProcessing();\n }\n },\n error: (err) => {\n if (err.message === INVALID_APPS_IN_ORDER) {\n this.snackbarService.openErrorSnack('LIB_ORDERS.SALES_ORDERS.ERRORS.INVALID_PRODUCTS_ERROR');\n } else {\n console.error(err);\n this.snackbarService.openErrorSnack('LIB_ORDERS.SALES_ORDERS.ERRORS.GENERIC_ERROR_TRY_AGAIN');\n }\n },\n complete: () => {\n this.processingAction.set(false);\n },\n });\n }\n\n private scheduleActivation(order: Order): void {\n const scheduleSalesOrder$ = this.scheduleSalesOrder(order);\n\n this.checkDateForActivation(false)\n .pipe(\n switchMap((shouldContinue) => {\n if (shouldContinue) {\n return scheduleSalesOrder$;\n }\n\n return of(null);\n }),\n take(1),\n )\n .subscribe({\n next: (scheduledOrder) => {\n if (scheduledOrder !== null) {\n this.snackbarService.openSuccessSnack('LIB_ORDERS.SALES_ORDERS.SUCCESS.ORDER_SCHEDULED');\n this.refreshOrderDetails.emit(true);\n }\n },\n error: (err) => {\n if (err.message === INVALID_APPS_IN_ORDER) {\n this.snackbarService.openErrorSnack('LIB_ORDERS.SALES_ORDERS.ERRORS.INVALID_PRODUCTS_ERROR');\n } else {\n console.error(err);\n this.snackbarService.openErrorSnack('LIB_ORDERS.SALES_ORDERS.ERRORS.GENERIC_ERROR_TRY_AGAIN');\n }\n },\n complete: () => this.processingAction.set(false),\n });\n }\n\n private activateSalesOrder(order: Order): Observable {\n return this.salesOrdersService.activate(\n order.orderId,\n order.businessId,\n this.getCustomFormData(),\n this.getCommonFormData(),\n this.getExtraFieldsData(),\n );\n }\n\n private scheduleSalesOrder(order: Order): Observable {\n return this.salesOrdersService\n .scheduleActivation(\n order.orderId,\n order.businessId,\n this.getCustomFormData(),\n this.getCommonFormData(),\n this.getExtraFieldsData(),\n )\n .pipe(switchMap(() => this.salesOrdersService.get(order.orderId, order.businessId)));\n }\n\n private checkDateForActivation(activatingNow: boolean): Observable {\n return this.order$.pipe(\n take(1),\n switchMap((order) => {\n const todayDate = new Date(new Date().setHours(0, 0, 0, 0));\n const orderDate = new Date(order.requestedActivation);\n\n const dateCheck = activatingNow ? !isSameDay(orderDate, todayDate) : isBefore(orderDate, todayDate);\n\n if (dateCheck) {\n const activationDateDialog = this.dialog.open(ActivationDateConfirmationDialogComponent, {\n width: ConfirmationModalWidth,\n maxWidth: ConfirmationModalMaxWidth,\n autoFocus: false,\n data: {\n orderId: order.orderId,\n businessId: order.businessId,\n orderDate: format(orderDate, 'LLL dd, yyyy'),\n todayDate: format(todayDate, 'LLL dd, yyyy'),\n },\n });\n\n return activationDateDialog.afterClosed();\n }\n\n return of(true);\n }),\n );\n }\n\n private proceedWithoutUnsavedChanges$(): Observable {\n return this.productPrereqForm.hasUnsavedChanges$.pipe(\n take(1),\n switchMap((hasUnsavedChanges) => {\n if (!hasUnsavedChanges) {\n return of(true);\n }\n\n return this.confirmationModal.openModal({\n title: 'LIB_ORDERS.SALES_ORDERS.DISCARD_UNSAVED_CHANGES_DIALOG.TITLE',\n message: 'LIB_ORDERS.SALES_ORDERS.DISCARD_UNSAVED_CHANGES_DIALOG.MESSAGE',\n confirmButtonText: 'LIB_ORDERS.SALES_ORDERS.DISCARD_UNSAVED_CHANGES_DIALOG.PROCEED_WITHOUT_CHANGES',\n cancelButtonText: 'LIB_ORDERS.SALES_ORDERS.DISCARD_UNSAVED_CHANGES_DIALOG.REVIEW_CHANGES',\n type: 'confirm',\n });\n }),\n );\n }\n\n /**\n * Refreshes order till status is fulfilled, activated with errors or maximum calls made\n */\n private refreshOrderWhileProcessing(): void {\n const pollingInterval = 10000;\n const maxRefreshCalls = 60;\n const getSalesOrderTime$ = interval(pollingInterval).pipe(take(maxRefreshCalls));\n const newOrder$ = combineLatest([getSalesOrderTime$, this.order$]).pipe(\n concatMap(([, order]) => this.salesOrdersService.get(order.orderId, order.businessId)),\n skipWhile((order: Order) => order.status === Status.PROCESSING),\n first(),\n );\n\n this.subscriptions.push(\n newOrder$.subscribe((newOrder) => {\n if (newOrder.status === Status.FULFILLED) {\n this.snackbarService.openSuccessSnack('LIB_ORDERS.SALES_ORDERS.SUCCESS.ORDER_ACTIVATED');\n } else {\n this.snackbarService.openErrorSnack('LIB_ORDERS.SALES_ORDERS.ERRORS.ACTIVATED_WITH_ERRORS');\n }\n this.refreshOrderDetails.emit(true);\n }),\n );\n }\n\n private businessName(): string {\n return this.business?.napData?.companyName ?? this.translateService.instant('LIB_ORDERS.SALES_ORDERS.BUSINESS');\n }\n\n protected readonly AppType = AppType;\n protected readonly ActivationStatus = ActivationStatus;\n\n public selectOrderCurrency(newCurrency: string) {\n this.currencyOverride$$.next(newCurrency);\n const lineItems = this.lineItemsComponent.getCurrentLineItems().map((lineItem) => {\n lineItem.currencyCode = newCurrency;\n return lineItem;\n });\n this.saveUpdatedLineItems(lineItems);\n }\n}\n\nexport function applyVariablePriceChange(\n event: { appId: string; customPrice: number; packageId?: string },\n lineItems: LineItem[],\n): void {\n const lineItem = lineItems.find((item) => {\n if (!event.packageId) {\n return item.appKey?.appId === event.appId;\n }\n return item.packageId === event.packageId;\n });\n if (!lineItem) {\n return;\n }\n\n if (!event.packageId) {\n if (!lineItem.cost) {\n lineItem.cost = new Cost();\n }\n lineItem.cost.customPrice = event.customPrice;\n } else {\n if (!lineItem.customPrices?.length) {\n lineItem.customPrices = [];\n }\n const customPrice = lineItem.customPrices?.find((cp) => {\n return cp.productId === event.appId;\n });\n if (customPrice) {\n customPrice.customPrice = event.customPrice;\n } else {\n lineItem.customPrices.push(\n new CustomPriceMapping({\n productId: event.appId,\n customPrice: event.customPrice,\n }),\n );\n }\n }\n}\n", "@if (\n {\n order: order$ | async,\n invalidForms: invalidForms$ | async,\n unifiedOrderPageEnabled: unifiedOrderPageEnabled$ | async,\n workOrderPersona: workOrderPersona$ | async,\n partnerCurrency: partnerCurrency$ | async,\n };\n as context\n) {\n @if (context.unifiedOrderPageEnabled) {\n \n \n
\n \n
\n
\n \n \n @if (context.invalidForms) {\n warning\n }\n {{ 'LIB_ORDERS.COMMON.ORDERS.ORDER_FORMS_LABEL' | translate }}\n \n
\n \n
\n
\n \n \n @if (workOrderDetails.detailsNeeded$ | async) {\n warning\n }\n {{ 'LIB_ORDERS.COMMON.ORDERS.FULFILLMENT_FORMS_LABEL' | translate }}\n \n
\n \n
\n
\n \n } @else {\n \n }\n @if ((selectedTabIndex$ | async) === 0 && (showFooter$ | async)) {\n \n
\n @if ((hasOrderForms$ | async) && invalidActivateAttempt) {\n
\n *\n {{ 'LIB_ORDERS.SALES_ORDERS.ERRORS.FILL_OUT_REQUIRED_FORMS' | translate }}\n
\n }\n @if (canScheduleActivation$ | async) {\n
\n \n \n {{\n 'LIB_ORDERS.SALES_ORDERS.I_UNDERSTAND_I_WILL_BE_BILLED'\n | translate: { businessAppName: 'COMMON.BUSINESS_APP' | translate }\n }}\n \n
\n }\n \n
\n
\n }\n\n \n @if (this.invalidOrderLineItems$ | async; as invalidOrderLineItems) {\n @if (\n context.unifiedOrderPageEnabled &&\n (this.canViewInvalidLineItemsBanner$ | async) &&\n invalidOrderLineItems?.length > 0\n ) {\n \n }\n }\n @if ((canSubmitWithoutRequiredFields$ | async) !== true && context.invalidForms) {\n \n \n \n }\n
\n
\n \n \n {{ 'LIB_ORDERS.COMMON.EDIT_ORDERS.DETAILS_NEEDED_BANNER' | translate }}\n \n -\n {{ 'LIB_ORDERS.COMMON.EDIT_ORDERS.DETAILS_NEEDED_BANNER_DESCRIPTION' | translate }}\n \n
\n
\n @if (context.unifiedOrderPageEnabled && (canViewBusinessHeader$ | async) === true) {\n \n }\n @if ((canViewCreateOrderDetails$ | async) === true) {\n \n }\n @if (context.unifiedOrderPageEnabled) {\n @if ((canEditOrderCurrency$ | async) && (canEditOrderContents$ | async)) {\n \n }\n\n @if (order.status !== Status.DRAFTED) {\n \n @if (fulfillmentStatusCardData$ | async; as statusCardData) {\n \n }\n }\n }\n \n\n @if (context.unifiedOrderPageEnabled && orderConfig$ | async; as orderConfig) {\n \n\n
\n \n \n \n {{ 'LIB_ORDERS.COMMON.EDIT_ORDERS.FULFILLMENT.PROJECTS' | translate }}\n \n \n \n \n
\n }\n\n @if (!context.unifiedOrderPageEnabled) {\n \n }\n
\n
\n @if (context.unifiedOrderPageEnabled && (canViewCondensedOrderDetails$ | async) === true) {\n \n }\n @if (context.unifiedOrderPageEnabled && (canViewActiveItems$ | async) === true) {\n \n \n \n {{ 'LIB_ORDERS.SALES_ORDERS.ACTIVE_ITEMS' | translate }}\n \n \n \n \n }\n @if (context.unifiedOrderPageEnabled) {\n @if (\n { products: variablePricesMap$ | async, packages: variablePackagePricesMap$ | async };\n as priceMaps\n ) {\n \n }\n }\n\n @if (!hideAttachmentsSection) {\n \n \n }\n \n \n @if (\n {\n canView: canViewTags$ | async,\n canEdit: canEditTags$ | async,\n };\n as tags\n ) {\n \n }\n
\n
\n
\n
\n
\n\n \n \n \n \n \n @if (orderFormOptions$ | async; as orderFormOptions) {\n \n }\n \n \n}\n", "import { Inject, Injectable } from '@angular/core';\nimport { MatDialog } from '@angular/material/dialog';\nimport { App } from '@galaxy/marketplace-apps';\nimport { Package } from '@vendasta/marketplace-packages';\nimport { SalesOrdersService } from '@vendasta/sales-orders';\nimport {\n SalesOrderLineItemSelectorDialogComponent,\n SalesOrderLineItemSelectorDialogData,\n SalesOrderLineItemSelectorDialogResult,\n} from '@vendasta/sales-ui';\nimport { UILineItem } from '@vendasta/store';\nimport { lastValueFrom, Observable } from 'rxjs';\nimport { first, map, take, shareReplay, switchMap } from 'rxjs/operators';\nimport { LineItemsService } from './line-items.service';\nimport { AppWithRetailPricing, LineItemsSelected } from './line-items.interface';\n\nexport interface LineItemsSelectorModalInput {\n marketId?: string;\n currentLineItems: UILineItem[];\n setLineItems: (lineItems: UILineItem[]) => void;\n}\n@Injectable()\nexport class LineItemsSelectorService {\n hideSellingStandaloneProducts: boolean;\n\n constructor(\n @Inject('PARTNER_ID') private readonly partnerId$: Observable,\n private readonly dialog: MatDialog,\n private readonly lineItemsService: LineItemsService,\n private readonly salesOrdersService: SalesOrdersService,\n ) {}\n\n async openLineItemsSelector(input: LineItemsSelectorModalInput): Promise {\n this.hideSellingStandaloneProducts = await lastValueFrom(this.getHideSellingStandaloneProducts(input.marketId));\n\n const preselectedPackages = input.currentLineItems\n .filter((item) => !!item.lineItem?.packageId)\n .map((item) => new Package({ packageId: item.lineItem.packageId }));\n const preselectedApps = input.currentLineItems\n .filter((item) => !!item.lineItem?.appKey?.appId)\n .map(\n (item) =>\n new App({\n key: {\n appId: item.lineItem.appKey.appId,\n editionId: item.lineItem.appKey.editionId,\n },\n appType: item.appType,\n }),\n );\n\n const dialogRef = this.dialog.open<\n SalesOrderLineItemSelectorDialogComponent,\n SalesOrderLineItemSelectorDialogData,\n SalesOrderLineItemSelectorDialogResult\n >(SalesOrderLineItemSelectorDialogComponent, {\n data: {\n marketId: input.marketId,\n preselectedPackages: preselectedPackages,\n preselectedApps: preselectedApps,\n hideSellingStandaloneProducts: this.hideSellingStandaloneProducts,\n hideOwnerName: true,\n },\n maxWidth: '100vw',\n maxHeight: '100vh',\n minWidth: '750px',\n });\n\n const newlySelectedLineItems = await lastValueFrom(\n dialogRef.afterClosed().pipe(\n first(),\n map((result) => {\n const items: LineItemsSelected = {\n apps: result.apps as AppWithRetailPricing[],\n packages: result.packages,\n };\n return items;\n }),\n ),\n );\n\n if (!newlySelectedLineItems) return input.currentLineItems;\n return lastValueFrom(\n this.lineItemsService\n .resolveLineItemSelectorInteractions(\n preselectedPackages,\n preselectedApps,\n newlySelectedLineItems,\n input.currentLineItems,\n )\n .pipe(first()),\n );\n }\n\n getHideSellingStandaloneProducts(marketId: string): Observable {\n return this.partnerId$.pipe(\n switchMap((partnerId) => this.salesOrdersService.getConfig(partnerId, marketId)),\n map((c) => c.salespersonOptions.disableSellingStandaloneProducts),\n take(1),\n shareReplay({ bufferSize: 1, refCount: true }),\n );\n }\n}\n", "import { Injectable } from '@angular/core';\nimport { Observable, Observer } from 'rxjs';\n\nexport interface Script {\n name: string;\n src: string;\n}\n\n@Injectable()\nexport class StripeService {\n private loaded = false;\n\n public loadScript(): Observable