1import * as cloudchannel from "@distilled.cloud/gcp/cloudchannel_v1";
2import * as Data from "effect/Data";
3import * as Effect from "effect/Effect";
4import { Unowned } from "../../AdoptPolicy.ts";
5import { isResolved } from "../../Diff.ts";
6import * as Provider from "../../Provider.ts";
7import { Resource } from "../../Resource.ts";
8import type { Providers } from "../Providers.ts";
9import {
10 accountOf,
11 defaultInvoiceMonth,
12 desiredRepricingConfig,
13 findChannelPartnerRepricing,
14 jsonEqual,
15 normalizeDate,
16 replaceOnIdentity,
17 toChannelPartnerLinkName,
18 toChannelPartnerRepricingAttrs,
19 toRepricingConfigName,
20} from "./internal.ts";
21
22export type ChannelPartnerLinksChannelPartnerRepricingConfigProps = {
23 /**
24 * Channel partner link that receives this repricing config. Full name
25 * `accounts/{account}/channelPartnerLinks/{channelPartner}` or the
26 * partner id (combined with `account`). Immutable — changing it
27 * replaces the config.
28 */
29 parent: string;
30 /**
31 * Reseller account used when `parent` is a bare partner id.
32 */
33 account?: string;
34 /**
35 * Config id (last segment of the resource name). Server-assigned on
36 * create. Immutable — changing it replaces the config.
37 */
38 configId?: string;
39 /**
40 * Year/month when the adjustment activates. Day must be `0`. You can
41 * only create or update configs for a future month. Immutable —
42 * changing it replaces the config.
43 */
44 effectiveInvoiceMonth?: cloudchannel.GoogleTypeDate;
45 /**
46 * Rebilling basis used for the bill.
47 * @default "COST_AT_LIST"
48 */
49 rebillingBasis?:
50 | cloudchannel.GoogleCloudChannelV1RepricingConfigRebillingBasisEnum
51 | (string & {});
52 /**
53 * Markup or markdown percentage (`"1.00"` is +1%, `"-1.00"` is -1%,
54 * `"0.00"` is pass-through). Ignored when `adjustment` is set.
55 * @default "0.00"
56 */
57 adjustmentPercentage?: string;
58 /**
59 * Full adjustment. Defaults to a percentage adjustment of
60 * `adjustmentPercentage`.
61 */
62 adjustment?: cloudchannel.GoogleCloudChannelV1RepricingAdjustment;
63 /**
64 * Entitlement this config applies to
65 * (`accounts/{account}/customers/{customer}/entitlements/{entitlement}`).
66 */
67 entitlement?: string;
68 /**
69 * Entitlement granularity. Takes precedence over `entitlement`.
70 */
71 entitlementGranularity?: cloudchannel.GoogleCloudChannelV1RepricingConfigEntitlementGranularity;
72 /**
73 * Conditional overrides applied before the default adjustment.
74 */
75 conditionalOverrides?: cloudchannel.GoogleCloudChannelV1ConditionalOverrideList;
76};
77
78export type ChannelPartnerLinksChannelPartnerRepricingConfig = Resource<
79 "GCP.CloudChannel.ChannelPartnerLinksChannelPartnerRepricingConfig",
80 ChannelPartnerLinksChannelPartnerRepricingConfigProps,
81 {
82 /** Resource name `accounts/{account}/channelPartnerLinks/{channelPartner}/channelPartnerRepricingConfigs/{id}`. */
83 name: string;
84 /** Config id (last path segment). */
85 configId: string;
86 /** Parent channel partner link. */
87 parent: string;
88 /** RFC3339 last-update timestamp. */
89 updateTime: string | undefined;
90 /** Repricing configuration. */
91 repricingConfig:
92 | cloudchannel.GoogleCloudChannelV1RepricingConfig
93 | undefined;
94 },
95 never,
96 Providers
97>;
98
99/**
100 * A Cloud Channel channel-partner repricing config.
101 *
102 * Repricing configs have no labels field. `list` / nuke returns configs
103 * under channel partner links of the configured Cloud Channel account
104 * (`GOOGLE_CLOUDCHANNEL_ACCOUNT`). Parent partner link and effective
105 * invoice month are identity — changing either replaces the config.
106 * Adjustment, rebilling basis, entitlement, and overrides update in
107 * place for a future month.
108 *
109 * Creating configs requires Cloud Channel distributor access.
110 *
111 * ### Creating a Channel Partner Repricing Config
112 * **Example:** Pass-through for a future month
113 * ```typescript
114 * const config =
115 * yield* GCP.CloudChannel.ChannelPartnerLinksChannelPartnerRepricingConfig(
116 * "PartnerBill",
117 * {
118 * parent: "accounts/C012345/channelPartnerLinks/C987654",
119 * effectiveInvoiceMonth: { year: 2099, month: 1, day: 0 },
120 * entitlement:
121 * "accounts/C012345/customers/cust-1/entitlements/ent-1",
122 * },
123 * );
124 * ```
125 *
126 * @resource
127 * @category CloudChannel
128 */
129export const ChannelPartnerLinksChannelPartnerRepricingConfig =
130 Resource<ChannelPartnerLinksChannelPartnerRepricingConfig>(
131 "GCP.CloudChannel.ChannelPartnerLinksChannelPartnerRepricingConfig",
132 );
133
134export class ChannelPartnerLinksChannelPartnerRepricingConfigNotResolved extends Data.TaggedError(
135 "GCP.CloudChannel.ChannelPartnerLinksChannelPartnerRepricingConfigNotResolved",
136)<{
137 name: string;
138}> {}
139
140const resolveParent = (newsParent: string, account?: string) =>
141 toChannelPartnerLinkName(newsParent, account ?? accountOf(newsParent));
142
143const monthOf = (
144 news: ChannelPartnerLinksChannelPartnerRepricingConfigProps,
145 outputMonth?: cloudchannel.GoogleTypeDate,
146) =>
147 Effect.gen(function* () {
148 return (
149 normalizeDate(news.effectiveInvoiceMonth) ??
150 normalizeDate(outputMonth) ??
151 (yield* defaultInvoiceMonth())
152 );
153 });
154
155export const ChannelPartnerLinksChannelPartnerRepricingConfigProvider = () =>
156 Provider.succeed(ChannelPartnerLinksChannelPartnerRepricingConfig, {
157 stables: ["name", "configId", "parent"],
158
159 diff: Effect.fn(function* ({ news, olds, output }) {
160 if (!isResolved(news)) return undefined;
161 const previousMonth = normalizeDate(
162 olds?.effectiveInvoiceMonth ??
163 output?.repricingConfig?.effectiveInvoiceMonth,
164 );
165 const nextMonth = normalizeDate(news.effectiveInvoiceMonth);
166 return replaceOnIdentity({
167 previousId: olds?.configId ?? output?.configId,
168 nextId: news.configId,
169 previousParent: olds?.parent ?? output?.parent,
170 nextParent: resolveParent(news.parent, news.account),
171 extra:
172 previousMonth !== undefined &&
173 nextMonth !== undefined &&
174 !jsonEqual(previousMonth, nextMonth),
175 });
176 }),
177
178 read: Effect.fn(function* ({ olds, output }) {
179 const parent = resolveParent(
180 olds?.parent ?? output?.parent ?? "",
181 olds?.account ?? accountOf(output?.parent ?? ""),
182 );
183 const name = toRepricingConfigName(
184 parent,
185 olds?.configId ?? output?.configId ?? output?.name,
186 "channelPartnerRepricingConfigs",
187 );
188 const existing = yield* findChannelPartnerRepricing(
189 parent,
190 output?.name ?? name,
191 olds?.effectiveInvoiceMonth ??
192 output?.repricingConfig?.effectiveInvoiceMonth,
193 );
194 if (existing === undefined) return undefined;
195 const attrs = toChannelPartnerRepricingAttrs(existing);
196 return output?.name !== undefined || olds?.configId !== undefined
197 ? attrs
198 : Unowned(attrs);
199 }),
200
201 reconcile: Effect.fn(function* ({ news, output }) {
202 const parent = resolveParent(
203 news.parent,
204 news.account ?? accountOf(output?.parent ?? news.parent),
205 );
206 const month = yield* monthOf(
207 news,
208 output?.repricingConfig?.effectiveInvoiceMonth,
209 );
210 const repricingConfig = desiredRepricingConfig({
211 effectiveInvoiceMonth: month,
212 rebillingBasis: news.rebillingBasis,
213 adjustmentPercentage: news.adjustmentPercentage,
214 adjustment: news.adjustment,
215 entitlementGranularity:
216 news.entitlementGranularity ??
217 (news.entitlement
218 ? { entitlement: news.entitlement }
219 : output?.repricingConfig?.entitlementGranularity),
220 conditionalOverrides: news.conditionalOverrides,
221 });
222 const name = toRepricingConfigName(
223 parent,
224 news.configId ?? output?.configId ?? output?.name,
225 "channelPartnerRepricingConfigs",
226 );
227
228 let current = yield* findChannelPartnerRepricing(
229 parent,
230 output?.name ?? name,
231 month,
232 );
233
234 if (current === undefined) {
235 const created = yield* cloudchannel
236 .createAccountsChannelPartnerLinksChannelPartnerRepricingConfigs({
237 parent,
238 body: { repricingConfig },
239 })
240 .pipe(
241 Effect.catchTag("Conflict", () =>
242 findChannelPartnerRepricing(parent, name, month),
243 ),
244 );
245 current = created ?? undefined;
246 }
247
248 if (current === undefined) {
249 return yield* new ChannelPartnerLinksChannelPartnerRepricingConfigNotResolved(
250 {
251 name: name || `${parent}/channelPartnerRepricingConfigs`,
252 },
253 );
254 }
255
256 const currentName = current.name ?? name;
257 const observed = current.repricingConfig;
258 const changed =
259 !jsonEqual(
260 normalizeDate(observed?.effectiveInvoiceMonth),
261 normalizeDate(repricingConfig.effectiveInvoiceMonth),
262 ) ||
263 (observed?.rebillingBasis ?? "") !==
264 (repricingConfig.rebillingBasis ?? "") ||
265 !jsonEqual(observed?.adjustment, repricingConfig.adjustment) ||
266 !jsonEqual(
267 observed?.entitlementGranularity,
268 repricingConfig.entitlementGranularity,
269 ) ||
270 !jsonEqual(
271 observed?.conditionalOverrides,
272 repricingConfig.conditionalOverrides,
273 );
274
275 if (changed && currentName.length > 0) {
276 current =
277 yield* cloudchannel.patchAccountsChannelPartnerLinksChannelPartnerRepricingConfigs(
278 {
279 name: currentName,
280 body: { name: currentName, repricingConfig },
281 },
282 );
283 }
284
285 return toChannelPartnerRepricingAttrs(current);
286 }),
287
288 delete: Effect.fn(function* ({ output }) {
289 const name = output.name;
290 if (name.length === 0) return;
291 yield* cloudchannel
292 .deleteAccountsChannelPartnerLinksChannelPartnerRepricingConfigs({
293 name,
294 })
295 .pipe(Effect.catchTag("NotFound", () => Effect.void));
296 }),
297 });