import {
  Address,
  ContentBinary,
  ContentService,
  Filesystem,
  Http,
  PromotionType,
  Share
} from "./chunk-GYQ34QPN.js";
import {
  Directory
} from "./chunk-BN54ET2Y.js";
import {
  AuthRefService,
  ExportedOpportunity,
  Opportunity,
  OpportunityDocument,
  OpportunityFixture,
  OpportunityPrice,
  OpportunityProperty,
  OpportunityPropertyEntity,
  OpportunityStatus,
  StorageService
} from "./chunk-M3LFBN7N.js";
import {
  EntityAttribute,
  PropertyEntityType,
  RenderDimensions,
  deepClone,
  generateId,
  hashString,
  safePrice
} from "./chunk-N36JE2BE.js";
import {
  environment
} from "./chunk-IDX2GQCZ.js";
import {
  HttpClient,
  HttpEventType,
  Injectable,
  Observable,
  OrderByPipe,
  Subject,
  filter,
  map,
  setClassMetadata,
  tap,
  ɵɵdefineInjectable,
  ɵɵinject
} from "./chunk-VAMMRICA.js";
import {
  Capacitor
} from "./chunk-IT26RCSL.js";
import {
  __async
} from "./chunk-RVA4KI76.js";

// src/app/shared/opportunities/models/ledger.ts
var OpportunityLedgerEntry = class {
  constructor(opportunityId, userId, device, parentEntityId, activeUrl, description, body, type, subType) {
    this.remoteStoredStatus = 0;
    this.processed = false;
    this.id = generateId();
    this.opportunityId = opportunityId;
    this.userId = userId;
    this.device = device;
    this.parentEntityId = parentEntityId;
    this.activeUrl = activeUrl;
    this.description = description;
    this.body = body;
    this.type = type;
    this.subType = subType;
    this.timeStamp_Utc = /* @__PURE__ */ new Date();
  }
};
var OpportunityLedgerEntryType;
(function(OpportunityLedgerEntryType2) {
  OpportunityLedgerEntryType2["Meta"] = "Meta";
  OpportunityLedgerEntryType2["Imported"] = "Imported";
  OpportunityLedgerEntryType2["Acceptance"] = "Acceptance";
  OpportunityLedgerEntryType2["Assignment"] = "Assignment";
  OpportunityLedgerEntryType2["Status"] = "Status";
  OpportunityLedgerEntryType2["Scheduling"] = "Scheduling";
  OpportunityLedgerEntryType2["Job"] = "Job";
  OpportunityLedgerEntryType2["Property"] = "Property";
  OpportunityLedgerEntryType2["Entity"] = "Entity";
  OpportunityLedgerEntryType2["Fixture"] = "Fixture";
  OpportunityLedgerEntryType2["FixturePhoto"] = "FixturePhoto";
  OpportunityLedgerEntryType2["EntityAttribute"] = "EntityAttribute";
  OpportunityLedgerEntryType2["EntityPhoto"] = "EntityPhoto";
  OpportunityLedgerEntryType2["EntityRender"] = "EntityRender";
  OpportunityLedgerEntryType2["EntityRenderDimensions"] = "EntityRenderDimensions";
  OpportunityLedgerEntryType2["EntityNote"] = "EntityNote";
  OpportunityLedgerEntryType2["Customer"] = "Customer";
  OpportunityLedgerEntryType2["CustomerNeed"] = "CustomerNeed";
  OpportunityLedgerEntryType2["PaymentSetting"] = "PaymentSetting";
  OpportunityLedgerEntryType2["Lender"] = "Lender";
  OpportunityLedgerEntryType2["Payment"] = "Payment";
  OpportunityLedgerEntryType2["Price"] = "Price";
  OpportunityLedgerEntryType2["CustomPrice"] = "CustomPrice";
  OpportunityLedgerEntryType2["Disposition"] = "Disposition";
  OpportunityLedgerEntryType2["Documents"] = "Documents";
  OpportunityLedgerEntryType2["Signer"] = "Signer";
  OpportunityLedgerEntryType2["Signature"] = "Signature";
  OpportunityLedgerEntryType2["SentForSignature"] = "SentForSignature";
  OpportunityLedgerEntryType2["Shop"] = "Shop";
  OpportunityLedgerEntryType2["InstallDate"] = "InstallDate";
  OpportunityLedgerEntryType2["QuoteSent"] = "QuoteSent";
  OpportunityLedgerEntryType2["QuoteSaved"] = "QuoteSaved";
  OpportunityLedgerEntryType2["ExecutedDocumentsSent"] = "ExecutedDocumentsSent";
  OpportunityLedgerEntryType2["ExecutedDocumentsSaved"] = "ExecutedDocumentsSaved";
  OpportunityLedgerEntryType2["RouteChange"] = "RouteChange";
  OpportunityLedgerEntryType2["Click"] = "Click";
  OpportunityLedgerEntryType2["EstimateSubmitted"] = "EstimateSubmitted";
})(OpportunityLedgerEntryType || (OpportunityLedgerEntryType = {}));
var OpportunityLedgerEntrySubType;
(function(OpportunityLedgerEntrySubType2) {
  OpportunityLedgerEntrySubType2["Added"] = "Added";
  OpportunityLedgerEntrySubType2["Removed"] = "Removed";
  OpportunityLedgerEntrySubType2["Changed"] = "Changed";
})(OpportunityLedgerEntrySubType || (OpportunityLedgerEntrySubType = {}));
var OpportunityLedgerSignature = class {
  constructor(signature, documentName) {
    this.signature = signature;
    this.documentName = documentName;
  }
};

// src/app/shared/system/services/log.service.ts
var _LogService = class _LogService {
  constructor(http) {
    this.http = http;
  }
  // #region Logging
  log(body, log) {
    const url = `{{api}}/system/logs/client/`;
    if (body.indexOf("client") < 0) {
      const headers = { "Content-Type": "application/json" };
      const trace = this.getTrace();
      body = body + `

TRACE: ${trace}`;
      const request = {
        url: location.href,
        body: body.length > 0 ? body : "",
        device: Capacitor.isNativePlatform() ? "iPad" : navigator.userAgent,
        log,
        appVersion: `${environment.APP_VERSION}`
      };
      return this.http.post(url, JSON.stringify(request), { headers });
    } else {
      return new Observable((o) => {
        o.next();
        o.complete();
      });
    }
  }
  // #endregion
  // #region Traces
  getTrace() {
    const errorForStack = new Error();
    const stackTrace = errorForStack.stack || "(stack trace unavailable)";
    const cleanStack = stackTrace.split("\n").slice(2).join("\n");
    return cleanStack;
  }
  // #endregion
  ngOnDestroy() {
  }
};
_LogService.\u0275fac = function LogService_Factory(__ngFactoryType__) {
  return new (__ngFactoryType__ || _LogService)(\u0275\u0275inject(HttpClient));
};
_LogService.\u0275prov = /* @__PURE__ */ \u0275\u0275defineInjectable({ token: _LogService, factory: _LogService.\u0275fac });
var LogService = _LogService;
(() => {
  (typeof ngDevMode === "undefined" || ngDevMode) && setClassMetadata(LogService, [{
    type: Injectable
  }], () => [{ type: HttpClient }], null);
})();

// src/app/shared/opportunities/services/opportunity.service.ts
var _OpportunityService = class _OpportunityService {
  constructor(http, contentService, authRefService, storageService, logService, orderByPipe) {
    this.http = http;
    this.contentService = contentService;
    this.authRefService = authRefService;
    this.storageService = storageService;
    this.logService = logService;
    this.orderByPipe = orderByPipe;
    this.activeUser = null;
    this.watchUser();
  }
  canModifyOpportunity(opportunity) {
    if (opportunity.adminOverride) {
      return true;
    } else {
      return this.activeUser?.id == opportunity.assignedTo?.id && opportunity.status != OpportunityStatus.Canceled && opportunity.status != OpportunityStatus.Dispositioned && opportunity.status != OpportunityStatus.PendingSubmission && opportunity.status != OpportunityStatus.Submitted;
    }
  }
  watchUser() {
    this.userSubscription = this.authRefService.user.subscribe({
      next: (user) => {
        this.activeUser = user;
      }
    });
  }
  // #endregion
  // #region Retrieval
  restoreOpportunity(opportunityId) {
    const url = `{{api}}/opportunities/${opportunityId}`;
    return new Observable((o) => {
      this.http.get(url).subscribe({
        next: (opportunity) => {
          this.processRemoteOpportunity(opportunity).then((mapped) => {
            o.next(mapped);
            o.complete();
          });
        },
        error: () => {
          o.error();
          o.complete();
        }
      });
    });
  }
  syncOpportunity(oppToSync) {
    return new Observable((o) => {
      const now = /* @__PURE__ */ new Date();
      let sinceInMs = 36096e5;
      if (oppToSync.lastProcessed_Utc != null) {
        sinceInMs = now.getTime() - oppToSync.lastProcessed_Utc.getTime();
      } else if (oppToSync.lastSynced != null) {
        sinceInMs = now.getTime() - oppToSync.lastSynced.getTime();
      }
      const url = `{{api}}/opportunities/${oppToSync.id}/sync/${sinceInMs}`;
      this.http.get(url).subscribe({
        next: (response) => __async(this, null, function* () {
          if (response.ledger.length > 0) {
            const results = yield this.processRemoteOpportunities(response.opportunities, response.ledger, [oppToSync]);
            const mappedOpportunity = results.find((x) => x.id == oppToSync.id);
            if (mappedOpportunity != null) {
              o.next(mappedOpportunity);
              o.complete();
            } else {
              o.error();
              o.complete();
            }
          } else {
            o.next(oppToSync);
            o.complete();
          }
        }),
        error: () => {
          o.error();
          o.complete();
        }
      });
    });
  }
  syncOpportunitiesForUser(userId, forceAll = false) {
    return new Observable((o) => {
      let url = `{{api}}/opportunities/${userId}/latest`;
      this.storageService.getOpportunitiesForUserId(userId, 50).then((oppsToSync) => __async(this, null, function* () {
        const opportunityRequests = [];
        const now = /* @__PURE__ */ new Date();
        for (var i = 0, len = oppsToSync.length; i < len; i++) {
          const opportunity = oppsToSync[i];
          let sinceInMs = 36096e5;
          if (opportunity.lastProcessed_Utc != null) {
            sinceInMs = now.getTime() - opportunity.lastProcessed_Utc.getTime();
          } else if (opportunity.lastSynced != null) {
            sinceInMs = now.getTime() - opportunity.lastSynced.getTime();
          }
          const oRequest = {
            opportunityId: opportunity.id,
            lastEntryId: void 0,
            sinceInMs
          };
          opportunityRequests.push(oRequest);
        }
        const request = {
          userId,
          existingOpportunities: opportunityRequests,
          sinceInMs: 12096e5
        };
        const headers = { "Content-Type": "application/json" };
        this.http.post(url, JSON.stringify(request), { headers }).subscribe({
          next: (response) => __async(this, null, function* () {
            const mapped = yield this.processRemoteOpportunities(response.opportunities, response.ledger, oppsToSync);
            o.next(mapped);
            o.complete();
          }),
          error: (e) => {
            o.error(e);
            o.complete();
          }
        });
      })).catch(() => {
        o.error();
        o.complete();
      });
    });
  }
  syncTodayOpportunitiesForUser(userId) {
    let url = `{{api}}/opportunities/${userId}/today`;
    return new Observable((o) => {
      this.storageService.getOpportunitiesForUserId(userId).then((oppsToSync) => {
        const opportunityRequests = [];
        const now = /* @__PURE__ */ new Date();
        for (var i = 0, len = oppsToSync.length; i < len; i++) {
          const opportunity = oppsToSync[i];
          let sinceInMs = 36096e5;
          if (opportunity.lastProcessed_Utc != null) {
            sinceInMs = now.getTime() - opportunity.lastProcessed_Utc.getTime();
          } else if (opportunity.lastSynced != null) {
            sinceInMs = now.getTime() - opportunity.lastSynced.getTime();
          }
          const oRequest = {
            opportunityId: opportunity.id,
            lastEntryId: void 0,
            sinceInMs
          };
          opportunityRequests.push(oRequest);
        }
        const request = {
          userId,
          existingOpportunities: opportunityRequests,
          sinceInMs: void 0
        };
        const headers = { "Content-Type": "application/json" };
        this.http.post(url, JSON.stringify(request), { headers }).subscribe({
          next: (response) => __async(this, null, function* () {
            const mapped = yield this.processRemoteOpportunities(response.opportunities, response.ledger, oppsToSync);
            o.next(mapped);
            o.complete();
          }),
          error: (e) => {
            o.error(e);
            o.complete();
          }
        });
      }).catch(() => {
        o.error();
        o.complete();
      });
    });
  }
  processRemoteOpportunity(opportunity, ledger = []) {
    return new Promise((res, rej) => __async(this, null, function* () {
      opportunity.lastSynced = /* @__PURE__ */ new Date();
      opportunity.lastProcessed_Utc = /* @__PURE__ */ new Date();
      yield this.storageService.storeOpportunity(opportunity);
      const mapped = this.mapOpportunity(opportunity);
      res(mapped);
    }));
  }
  processRemoteOpportunities(opportunities, ledger, syncedOpportunities) {
    return new Promise((res, rej) => __async(this, null, function* () {
      const mappedOpportunities = [];
      for (var i = 0, len = opportunities.length; i < len; i++) {
        const opportunity = opportunities[i];
        const mapped = yield this.processRemoteOpportunity(opportunity);
        mappedOpportunities.push(mapped);
      }
      const opportunitiesToProcess = [];
      for (var i = 0, len = ledger.length; i < len; i++) {
        const entry = ledger[i];
        entry.remoteStoredStatus = 1;
        entry.remoteStored_Utc = /* @__PURE__ */ new Date();
        if (!opportunitiesToProcess.includes(entry.opportunityId)) {
          opportunitiesToProcess.push(entry.opportunityId);
        }
        const syncedOpportunity = syncedOpportunities.find((x) => x.id == entry.opportunityId);
        if (syncedOpportunity != null) {
          syncedOpportunity.lastSynced = /* @__PURE__ */ new Date();
        }
      }
      for (var i = 0, len = opportunitiesToProcess.length; i < len; i++) {
        const id = opportunitiesToProcess[i];
        const opportunity = yield this.storageService.getOpportunity(id);
        if (opportunity != null) {
          opportunity.lastSynced = /* @__PURE__ */ new Date();
          const processed = yield this.processLedger(opportunity, void 0, true, true);
          mappedOpportunities.push(processed);
        }
      }
      for (var i = 0, len = syncedOpportunities.length; i < len; i++) {
        const syncedOpportunity = syncedOpportunities[i];
        const ledgerProcessed = opportunitiesToProcess.find((x) => x == syncedOpportunity.id);
        if (ledgerProcessed == null) {
          this.getOpportunity(syncedOpportunity.id).subscribe({
            next: (opportunity) => {
              if (opportunity != null) {
                opportunity.lastProcessed_Utc = /* @__PURE__ */ new Date();
                opportunity.lastSynced = /* @__PURE__ */ new Date();
                this.storageService.storeOpportunity(opportunity);
              }
            }
          });
        }
      }
      res(mappedOpportunities);
    }));
  }
  // #endregion
  // #region Opportunity
  getOpportunity(id) {
    return new Observable((o) => {
      this.storageService.getOpportunity(id).then((opportunity) => {
        if (opportunity != null) {
          const mappedOpportunity = this.mapOpportunity(opportunity);
          o.next(mappedOpportunity);
          o.complete();
        } else {
          o.next(null);
          o.complete();
        }
      }).catch(() => {
        o.error();
        o.complete();
      });
    });
  }
  searchOpportunities(query, userId, status = void 0) {
    return new Observable((o) => {
      this.storageService.searchOpportunities(userId, query, status).then((appointments) => {
        const mapped = [];
        appointments.forEach((opportunity) => {
          const mappedAppointment = this.mapOpportunity(opportunity);
          mapped.push(mappedAppointment);
        });
        o.next(mapped);
        o.complete();
      }).catch(() => {
        o.error();
        o.complete();
      });
    });
  }
  getOpportunitiesForDateRange(startDate, endDate, userId = void 0) {
    return new Observable((o) => {
      this.storageService.getOpportunitiesForDateRange(startDate, endDate, userId).then((appointments) => {
        const mapped = [];
        appointments.forEach((opportunity) => {
          const mappedAppointment = this.mapOpportunity(opportunity);
          mapped.push(mappedAppointment);
        });
        o.next(mapped);
        o.complete();
      }).catch(() => {
        o.error();
        o.complete();
      });
    });
  }
  getOpportunitiesForToday(userId = void 0) {
    return new Observable((o) => {
      const today = /* @__PURE__ */ new Date();
      const year = today.getFullYear();
      const month = today.getMonth() + 1;
      let monthStr = `${month}`;
      if (month < 10) {
        monthStr = `0${month}`;
      }
      const date = today.getDate();
      let dateStr = `${date}`;
      if (date < 10) {
        dateStr = `0${date}`;
      }
      this.storageService.getOpportunitiesForDate(`${year}-${monthStr}-${dateStr}`, userId).then((opportunities) => {
        const mapped = [];
        opportunities.forEach((opportunity) => {
          const mappedAppointment = this.mapOpportunity(opportunity);
          mapped.push(mappedAppointment);
        });
        o.next(mapped);
        o.complete();
      }).catch(() => {
        o.error();
        o.complete();
      });
    });
  }
  getOpportunitiesForUser(userId) {
    return new Observable((o) => {
      this.storageService.getOpportunitiesForUserId(userId).then((appointments) => {
        const mapped = [];
        appointments.forEach((opportunity) => {
          const mappedAppointment = this.mapOpportunity(opportunity);
          mapped.push(mappedAppointment);
        });
        o.next(mapped);
        o.complete();
      }).catch(() => {
        o.error();
        o.complete();
      });
    });
  }
  getOpportunitiesByStatus(status, userId) {
    return new Observable((o) => {
      this.storageService.getOpportunitiesForStatus(status, userId).then((appointments) => {
        const mapped = [];
        appointments.forEach((opportunity) => {
          const mappedAppointment = this.mapOpportunity(opportunity);
          mapped.push(mappedAppointment);
        });
        o.next(mapped);
        o.complete();
      }).catch(() => {
        o.error();
        o.complete();
      });
    });
  }
  // #endregion
  // #region Mapping
  mapOpportunity(parsedOpportunity) {
    const opportunity = Object.assign(new Opportunity(), parsedOpportunity);
    if (opportunity.scheduled_Utc != null) {
      opportunity.scheduled_Utc = new Date(opportunity.scheduled_Utc);
    }
    if (opportunity.property != null) {
      const property = Object.assign(new OpportunityProperty(), opportunity.property);
      if (property.photo != null) {
        property.photo = new ContentBinary(property.photo);
      }
      if (property.address != null) {
        const address = Object.assign(new Address(), opportunity.property.address);
        property.address = address;
      }
      if (property.entity != null) {
        property.entity = this.mapPropertyEntity(property.entity, opportunity);
      }
      opportunity.property = property;
      opportunity.priceChanged$ = new Subject();
      opportunity.customPriceChanged$ = new Subject();
    }
    if (opportunity.job != null) {
      opportunity.job = this.mapPropertyEntity(opportunity.job, opportunity);
    }
    if (opportunity.price != null) {
      opportunity.price = this.mapOpportunityPrice(opportunity.price, opportunity);
    }
    if (opportunity.customPrice != null) {
      opportunity.customPrice = this.mapOpportunityPrice(opportunity.customPrice, opportunity);
      if (opportunity.customPrice.details != null && opportunity.price?.details != null) {
        const clonedDetails = deepClone(opportunity.price.details);
        if (clonedDetails != null) {
          opportunity.customPrice.details = clonedDetails;
        }
      }
    }
    opportunity.documents = this.mapOpportunityDocuments(opportunity);
    opportunity.checkPaymentsValidity();
    opportunity.checkDocumentValidity();
    opportunity.checkProgression();
    return opportunity;
  }
  mapOpportunityDocuments(opportunity) {
    const documents = [];
    opportunity.documents.forEach((document2) => {
      const mappedDocument = new OpportunityDocument(document2.id, document2.name, document2.customerFacing, document2.shopFacing, document2.standalone, document2.dynamic, document2.alwaysShow, document2.displayInSigning, document2.mapping, document2.displayOrder, document2.content, document2.executed, document2.signers, document2.rules, document2.documents, document2.signatures, document2.userContent, document2.lastUpdated_Utc, document2.created_Utc, document2.generatedContent, document2.originalHash);
      if (mappedDocument.dynamic) {
        mappedDocument.hash = hashString(mappedDocument.content ?? "");
      } else {
        mappedDocument.hash = hashString(JSON.stringify(mappedDocument.mapping));
      }
      if (mappedDocument.name == "Scope of Work Acknowledgments") {
        mappedDocument.hash = hashString(JSON.stringify(mappedDocument.documents));
      }
      documents.push(mappedDocument);
    });
    return documents;
  }
  mapOpportunityPrice(price, opportunity) {
    const mappedPrice = new OpportunityPrice(price);
    if (opportunity != null) {
      const flattenedEntities = opportunity.flattenEntities();
      const flattenedFixtures = opportunity.flattenFixtures(flattenedEntities);
      price.details.forEach((detail) => {
        detail.lineItems.forEach((lineItem) => {
          if (lineItem.entityId != null) {
            const entity = flattenedEntities.find((x) => x.id == lineItem.entityId) ?? flattenedFixtures.find((x) => x.id == lineItem.entityId);
            lineItem.entity = entity;
          }
        });
      });
      price.customItems.forEach((lineItem) => {
        if (lineItem.entityId != null) {
          const entity = flattenedEntities.find((x) => x.id == lineItem.entityId) ?? flattenedFixtures.find((x) => x.id == lineItem.entityId);
          lineItem.entity = entity;
        }
      });
    }
    return mappedPrice;
  }
  mapPropertyEntity(entity, opportunity) {
    const mappedEntity = new OpportunityPropertyEntity(entity.id, entity.entityId, entity.type, entity.name, entity.number, entity.attributes, entity.fixtures, entity.renderDimensions, entity.metafields, entity.rules, entity.photos, entity.photos_B64, entity.render, entity.localRender, entity.notes);
    const mappedEntities = [];
    for (var i = 0, len = entity.entities.length; i < len; i++) {
      const childEntity = entity.entities[i];
      const mappedChildEntity = this.mapPropertyEntity(childEntity, opportunity);
      mappedEntities.push(mappedChildEntity);
    }
    mappedEntity.entities = mappedEntities;
    const mappedFixtures = this.mapFixtures(entity.fixtures, opportunity);
    const mappedAttributes = this.mapAttributes(entity.attributes);
    mappedEntity.fixtures = mappedFixtures;
    mappedEntity.attributes = mappedAttributes;
    const photos = [];
    mappedEntity.photos.forEach((photo) => {
      photo = new ContentBinary(photo);
      photos.push(photo);
    });
    mappedEntity.photos = photos;
    if (mappedEntity.render != null) {
      mappedEntity.render = new ContentBinary(mappedEntity.render);
    }
    return mappedEntity;
  }
  mapFixture(fixture, opportunity) {
    const mappedFixture = new OpportunityFixture(fixture.id, fixture.entityId, fixture.name, fixture.imageSrc_Base64, fixture.number, fixture.attributes, fixture.renderDimensions, fixture.photos, fixture.photos_B64, fixture.notes);
    mappedFixture.attributes = this.mapAttributes(fixture.attributes);
    const photos = [];
    mappedFixture.photos.forEach((photo) => {
      photo = new ContentBinary(photo);
      photos.push(photo);
    });
    mappedFixture.photos = photos;
    return mappedFixture;
  }
  mapFixtures(fixtures, opportunity) {
    const mappedFixtures = [];
    fixtures.forEach((fixture) => {
      const mappedFixture = this.mapFixture(fixture, opportunity);
      mappedFixtures.push(mappedFixture);
    });
    return mappedFixtures;
  }
  mapAttributes(attributes) {
    const mappedAttributes = [];
    if (attributes != null) {
      attributes.forEach((attribute) => {
        const mappedAttribute = new EntityAttribute(attribute.id, attribute.name, attribute.customerFacing, attribute.possibleValues, attribute.type, attribute.displayOrder, attribute.defaultValue, attribute.hidden, attribute.rules, attribute.values, attribute.functionName);
        mappedAttributes.push(mappedAttribute);
      });
    }
    return mappedAttributes;
  }
  // #endregion
  // #region Addresses
  getDirections(address) {
    const addressParts = [];
    if (address.streetAddress1)
      addressParts.push(address.streetAddress1);
    if (address.streetAddress2)
      addressParts.push(address.streetAddress2);
    if (address.streetAddress3)
      addressParts.push(address.streetAddress3);
    if (address.city)
      addressParts.push(address.city);
    if (address.regionIso3166)
      addressParts.push(address.regionIso3166);
    if (address.subRegion)
      addressParts.push(address.subRegion);
    if (address.countryIso3166)
      addressParts.push(address.countryIso3166);
    if (address.postalCode)
      addressParts.push(address.postalCode);
    const link = addressParts.join(", ").replace(/ /g, "+");
    return `https://www.google.com/maps/search/?api=1&query=${link}`;
  }
  sendDirections(opportunityId, phoneNumber = void 0, emailAddress = void 0) {
    const url = `{{api}}/opportunities/${opportunityId}/directions`;
    const headers = { "Content-Type": "application/json" };
    const request = {
      opportunityId,
      phoneNumber,
      emailAddress
    };
    return this.http.post(url, JSON.stringify(request), { headers });
  }
  getCoordinatesForAddress(address) {
    const encodedAddress = btoa(address);
    return new Observable((o) => {
      this.storageService.getAddressCoordinates(encodedAddress).then((coordinates) => {
        if (coordinates != null) {
          o.next(coordinates);
          o.complete();
        } else {
          const url = "https://nominatim.openstreetmap.org/search";
          this.http.get(url, {
            params: {
              q: address,
              format: "json",
              addressdetails: "1",
              limit: "1"
            }
          }).subscribe({
            next: (results) => {
              let coords = results[0];
              if (coords == null) {
                coords = {
                  encodedAddress,
                  lat: 0,
                  lon: 0,
                  place_id: "",
                  licence: "",
                  osm_type: "",
                  osm_id: "",
                  display_name: address,
                  boundingbox: ["", "", "", ""],
                  class: "",
                  type: "",
                  importance: 0
                };
              }
              coords.encodedAddress = encodedAddress;
              this.storageService.storeAddressCoordinates(coords);
              o.next(coords);
              o.complete();
            },
            error: () => {
              o.error();
              o.complete();
            }
          });
        }
      }).catch(() => {
        o.error();
        o.complete();
      });
    });
  }
  // #endregion
  // #region Ledger
  getLedgerEntries(opportunityId) {
    return new Observable((o) => {
      this.storageService.getOpportunityLedgerEntriesByOpportunityId(opportunityId).then((entries) => {
        o.next(entries);
        o.complete();
      }).catch(() => {
        o.error();
        o.complete();
      });
    });
  }
  syncLedgerEntries(opportunity, count = 30, lastEntryId) {
    let url = `{{api}}/opportunities/${opportunity.id}/ledger/latest/${count}`;
    if (lastEntryId != null) {
      url = `{{api}}/opportunities/${opportunity.id}/ledger/latest/${count}/${lastEntryId}`;
    }
    return this.http.get(url).pipe(tap((entries) => __async(this, null, function* () {
      entries = this.orderByPipe.transform(entries, "timeStamp_Utc", false);
      for (var i = 0, len = entries.length; i < len; i++) {
        const entry = entries[i];
        if (opportunity.lastProcessed_Utc != null && entry.timeStamp_Utc <= opportunity.lastProcessed_Utc) {
          entry.processed = true;
        }
        yield this.storageService.storeOpportunityLedgerEntry(entry);
      }
      yield this.processLedger(opportunity, void 0, true, true);
    })));
  }
  modifyOpportunity(opportunity, parentEntityId, body, type, subType, userId = void 0, completeProcess = false) {
    return new Promise((res, rej) => __async(this, null, function* () {
      const entry = new OpportunityLedgerEntry(opportunity.id, userId, navigator.userAgent, parentEntityId, window.location.pathname, void 0, body, type, subType);
      yield this.storageService.storeOpportunityLedgerEntry(entry);
      const entryToProcess = !completeProcess ? entry : void 0;
      this.processLedger(opportunity, entryToProcess).then((opportunity2) => {
        res();
      }).catch((e) => {
        console.error(`OPPORTUNITY MODIFICATION: ${JSON.stringify(e)}`);
        rej();
      });
    }));
  }
  processLedger(opportunity, entryToProcess = void 0, store = true, preferStorage = false) {
    return __async(this, null, function* () {
      return new Promise((res, rej) => __async(this, null, function* () {
        if (preferStorage) {
          opportunity = (yield this.storageService.getOpportunity(opportunity.id)) ?? opportunity;
          opportunity = this.mapOpportunity(opportunity);
        }
        if (opportunity != null) {
          let entriesToProcess = [];
          if (entryToProcess != null) {
            entriesToProcess.push(entryToProcess);
          } else {
            if (opportunity.lastProcessed_Utc != null) {
              entriesToProcess = yield this.storageService.getLatestOpportunityLedgerEntries(opportunity.lastProcessed_Utc, opportunity.id);
            } else {
              entriesToProcess = yield this.storageService.getOpportunityLedgerEntriesByOpportunityId(opportunity.id);
            }
          }
          entriesToProcess = this.orderByPipe.transform(entriesToProcess, "timeStamp_Utc", false);
          let lastProcessed_Utc = /* @__PURE__ */ new Date();
          for (var i = 0, len = entriesToProcess.length; i < len; i++) {
            const entry = entriesToProcess[i];
            try {
              if (!entry.processed) {
                switch (entry.type) {
                  case OpportunityLedgerEntryType.Acceptance:
                    {
                      this.modifyAcceptance(opportunity, entry);
                    }
                    break;
                  case OpportunityLedgerEntryType.Assignment:
                    {
                      this.modifyAssignment(opportunity, entry);
                    }
                    break;
                  case OpportunityLedgerEntryType.Status:
                    {
                      this.modifyStatus(opportunity, entry);
                    }
                    break;
                  case OpportunityLedgerEntryType.Scheduling:
                    {
                      this.modifyScheduling(opportunity, entry);
                    }
                    break;
                  case OpportunityLedgerEntryType.Job:
                    {
                      this.modifyJob(opportunity, entry);
                    }
                    break;
                  case OpportunityLedgerEntryType.Property:
                    {
                      this.modifyProperty(opportunity, entry);
                    }
                    break;
                  case OpportunityLedgerEntryType.Fixture:
                    {
                      this.modifyFixture(opportunity, entry);
                    }
                    break;
                  case OpportunityLedgerEntryType.FixturePhoto:
                    {
                      this.modifyFixturePhoto(opportunity, entry);
                    }
                    break;
                  case OpportunityLedgerEntryType.Entity:
                    {
                      this.modifyEntity(opportunity, entry);
                    }
                    break;
                  case OpportunityLedgerEntryType.EntityPhoto:
                    {
                      this.modifyEntityPhoto(opportunity, entry);
                    }
                    break;
                  case OpportunityLedgerEntryType.EntityAttribute:
                    {
                      this.modifyEntityAttribute(opportunity, entry);
                    }
                    break;
                  case OpportunityLedgerEntryType.EntityRender:
                    {
                      this.modifyEntityRender(opportunity, entry);
                    }
                    break;
                  case OpportunityLedgerEntryType.EntityRenderDimensions:
                    {
                      this.modifyEntityRenderDimensions(opportunity, entry);
                    }
                    break;
                  case OpportunityLedgerEntryType.EntityNote:
                    {
                      this.modifyEntityNote(opportunity, entry);
                    }
                    break;
                  case OpportunityLedgerEntryType.Customer:
                    {
                      this.modifyCustomer(opportunity, entry);
                    }
                    break;
                  case OpportunityLedgerEntryType.CustomerNeed:
                    {
                      this.modifyCustomerNeed(opportunity, entry);
                    }
                    break;
                  case OpportunityLedgerEntryType.PaymentSetting:
                    {
                      this.modifyPaymentSetting(opportunity, entry);
                    }
                    break;
                  case OpportunityLedgerEntryType.Lender:
                    {
                      this.modifyLender(opportunity, entry);
                    }
                    break;
                  case OpportunityLedgerEntryType.Payment:
                    {
                      this.modifyPayment(opportunity, entry);
                    }
                    break;
                  case OpportunityLedgerEntryType.Price:
                    {
                      this.modifyPrice(opportunity, entry);
                    }
                    break;
                  case OpportunityLedgerEntryType.CustomPrice:
                    {
                      this.modifyCustomPrice(opportunity, entry);
                    }
                    break;
                  case OpportunityLedgerEntryType.Disposition:
                    {
                      this.modifyDisposition(opportunity, entry);
                    }
                    break;
                  case OpportunityLedgerEntryType.Documents:
                    {
                      this.modifyDocuments(opportunity, entry);
                    }
                    break;
                  case OpportunityLedgerEntryType.Signer:
                    {
                      this.modifySigner(opportunity, entry);
                    }
                    break;
                  case OpportunityLedgerEntryType.Signature:
                    {
                      this.modifySignature(opportunity, entry);
                    }
                    break;
                  case OpportunityLedgerEntryType.Property:
                    {
                      this.modifyProperty(opportunity, entry);
                    }
                    break;
                  case OpportunityLedgerEntryType.Shop:
                    {
                      this.modifyShop(opportunity, entry);
                    }
                    break;
                  case OpportunityLedgerEntryType.InstallDate:
                    {
                      this.modifyInstallDate(opportunity, entry);
                    }
                    break;
                }
                entry.processed = true;
                this.storageService.storeOpportunityLedgerEntry(entry);
                lastProcessed_Utc = entry.timeStamp_Utc;
              }
            } catch (e) {
              console.error(`Failed to process entry ${entry.id} with message: ${JSON.stringify(e)}`);
            }
          }
          opportunity.lastProcessed_Utc = lastProcessed_Utc;
          if (opportunity.checkDocumentValidity != null) {
            opportunity.updateAttributes();
            opportunity.applyAttributeRules();
            opportunity.renumberRooms();
            opportunity.renumberFixtures();
            opportunity.checkProgression();
            opportunity.checkPaymentsValidity();
            opportunity.checkDocumentValidity();
          }
          if (store) {
            const mapped = this.mapOpportunity(opportunity);
            yield this.storageService.storeOpportunity(mapped);
            res(mapped);
          } else {
            res(opportunity);
          }
        } else {
          rej();
        }
      }));
    });
  }
  modifyStatus(opportunity, entry) {
    if (entry.body != null) {
      switch (entry.subType) {
        case OpportunityLedgerEntrySubType.Added:
        case OpportunityLedgerEntrySubType.Changed:
          {
            const status = OpportunityStatus[entry.body];
            opportunity.status = status;
          }
          break;
      }
    }
  }
  modifyJob(opportunity, entry) {
    if (entry.body != null) {
      switch (entry.subType) {
        case OpportunityLedgerEntrySubType.Added:
        case OpportunityLedgerEntrySubType.Changed:
          {
            const job = entry.body;
            if (opportunity.job != null) {
              opportunity.job.metafields = job.metafields;
              opportunity.job.notes = job.notes;
            } else {
              opportunity.job = job;
            }
          }
          break;
      }
    }
  }
  modifyCustomer(opportunity, entry) {
    switch (entry.subType) {
      case OpportunityLedgerEntrySubType.Added:
      case OpportunityLedgerEntrySubType.Changed:
        {
          if (entry.body != null) {
            const customer = entry.body;
            opportunity.customer = customer;
          }
        }
        break;
      case OpportunityLedgerEntrySubType.Removed:
        {
          opportunity.customer = void 0;
        }
        break;
    }
  }
  modifyPaymentSetting(opportunity, entry) {
    switch (entry.subType) {
      case OpportunityLedgerEntrySubType.Added:
      case OpportunityLedgerEntrySubType.Changed:
        {
          if (entry.body != null) {
            const paymentSetting = entry.body;
            opportunity.paymentSetting = paymentSetting;
          }
        }
        break;
      case OpportunityLedgerEntrySubType.Removed:
        {
          opportunity.paymentSetting = void 0;
        }
        break;
    }
  }
  modifyCustomerNeed(opportunity, entry) {
    if (entry.body != null) {
      if (opportunity.customerNeeds == null) {
        opportunity.customerNeeds = {};
      }
      switch (entry.subType) {
        case OpportunityLedgerEntrySubType.Added:
        case OpportunityLedgerEntrySubType.Changed:
          {
            const change = entry.body;
            if (change.key != null && change.value) {
              opportunity.customerNeeds[change.key] = [...change.value];
            }
          }
          break;
        case OpportunityLedgerEntrySubType.Removed:
          {
            const change = entry.body;
            if (change.key != null) {
              opportunity.customerNeeds[change.key] = [];
            }
          }
          break;
      }
    }
  }
  modifyScheduling(opportunity, entry) {
    switch (entry.subType) {
      case OpportunityLedgerEntrySubType.Added:
      case OpportunityLedgerEntrySubType.Changed:
        {
          if (entry.body != null) {
            const scheduled_Utc = entry.body;
            opportunity.scheduled_Utc = scheduled_Utc;
          }
        }
        break;
      case OpportunityLedgerEntrySubType.Removed:
        {
          opportunity.scheduled_Utc = void 0;
        }
        break;
    }
  }
  modifyProperty(opportunity, entry) {
    if (entry.body != null) {
      switch (entry.subType) {
        case OpportunityLedgerEntrySubType.Added:
        case OpportunityLedgerEntrySubType.Changed:
          {
            const property = entry.body;
            if (opportunity.property != null) {
              opportunity.property.yearBuilt = property.yearBuilt;
              opportunity.property.existingPipeTypes = property.existingPipeTypes;
              opportunity.property.foundationTypes = property.foundationTypes;
              opportunity.property.type = property.type;
              opportunity.property.listedBathrooms = property.listedBathrooms;
              opportunity.property.listedStories = property.listedStories;
              opportunity.property.listedSquareDimensions = property.listedSquareDimensions;
              opportunity.property.dimensionsMeasurementType = property.dimensionsMeasurementType;
              opportunity.property.photo = property.photo;
              opportunity.property.photo_B64 = property.photo_B64;
            } else {
              opportunity.property = property;
            }
          }
          break;
      }
    }
  }
  modifyFixture(opportunity, entry) {
    if (entry.body != null) {
      const flattenedEntities = opportunity.flattenEntities();
      switch (entry.subType) {
        case OpportunityLedgerEntrySubType.Added:
          {
            const fixture = entry.body;
            const parent = flattenedEntities.find((x) => x.id == entry.parentEntityId);
            if (parent != null) {
              const mapped = this.mapFixture(fixture, opportunity);
              parent.fixtures.push(mapped);
              mapped.added = true;
              setTimeout(() => {
                mapped.added = false;
              }, 500);
              if (parent.fixtureAdded$ != null) {
                parent.fixtureAdded$.next(fixture);
              }
            }
          }
          break;
        case OpportunityLedgerEntrySubType.Changed:
          {
            const fixture = entry.body;
            const parent = flattenedEntities.find((x) => x.id == entry.parentEntityId);
            if (parent != null) {
              const existingFixture = parent.fixtures.find((x) => x.id == fixture.id);
              if (existingFixture != null) {
                existingFixture.number = fixture.number;
              }
            }
          }
          break;
        case OpportunityLedgerEntrySubType.Removed:
          {
            const fixtureId = entry.body;
            const parentEntity = flattenedEntities.find((x) => x.id == entry.parentEntityId);
            if (parentEntity != null) {
              const existingIndex = parentEntity.fixtures.findIndex((x) => x.id == fixtureId);
              if (existingIndex > -1) {
                parentEntity.fixtures.splice(existingIndex, 1);
              }
            }
          }
          break;
      }
    }
  }
  modifyFixturePhoto(opportunity, entry) {
    if (entry.body != null) {
      const flattenedEntities = opportunity.flattenEntities();
      const flattenedFixtures = opportunity.flattenFixtures(flattenedEntities);
      switch (entry.subType) {
        case OpportunityLedgerEntrySubType.Added:
          {
            const photo = entry.body;
            const fixture = flattenedFixtures.find((x) => x.id == photo.entityId);
            if (fixture != null) {
              if (photo.photo != null) {
                fixture.photos.push(photo.photo);
              } else if (photo.localPhoto != null) {
                fixture.photos_B64.push(photo.localPhoto);
              }
            }
          }
          break;
        case OpportunityLedgerEntrySubType.Removed:
          {
            const photo = entry.body;
            const fixture = flattenedFixtures.find((x) => x.id == photo.entityId);
            if (fixture != null && photo.photoRef != null) {
              const ref = photo.photoRef;
              const localIndex = fixture.photos_B64.findIndex((x) => hashString(x) == ref);
              if (localIndex > -1) {
                fixture.photos_B64.splice(localIndex, 1);
              }
              const remoteIndex = fixture.photos.findIndex((x) => hashString(x.src) == ref);
              if (remoteIndex > -1) {
                fixture.photos.splice(remoteIndex, 1);
              }
            }
          }
          break;
      }
    }
  }
  modifyEntity(opportunity, entry) {
    if (entry.body != null) {
      const flattenedEntities = opportunity.flattenEntities();
      switch (entry.subType) {
        case OpportunityLedgerEntrySubType.Added:
          {
            const entity = entry.body;
            if (entity.type == PropertyEntityType.Property && opportunity.property != null) {
              opportunity.property.entity = entity;
            } else {
              let parent;
              if (entity.type == PropertyEntityType.Unit) {
                parent = opportunity.property?.entity;
              }
              if (parent == null) {
                parent = flattenedEntities.find((x) => x.id == entry.parentEntityId);
              }
              if (parent != null) {
                const mapped = this.mapPropertyEntity(entity, opportunity);
                parent.entities.push(mapped);
              }
            }
          }
          break;
        case OpportunityLedgerEntrySubType.Changed:
          {
            const entity = entry.body;
            const existingEntity = flattenedEntities.find((x) => x.id == entity.id);
            if (existingEntity != null) {
              existingEntity.name = entity.name;
              existingEntity.number = entity.number;
              existingEntity.metafields = entity.metafields;
              existingEntity.number = entity.number;
            }
          }
          break;
        case OpportunityLedgerEntrySubType.Removed:
          {
            const entityId = entry.body;
            const parentEntity = flattenedEntities.find((x) => x.id == entry.parentEntityId);
            if (parentEntity != null) {
              const existingIndex = parentEntity.entities.findIndex((x) => x.id == entityId);
              if (existingIndex > -1) {
                parentEntity.entities.splice(existingIndex, 1);
              }
            }
          }
          break;
      }
    }
  }
  modifyEntityRender(opportunity, entry) {
    if (entry.body != null) {
      const flattenedEntities = opportunity.flattenEntities();
      switch (entry.subType) {
        case OpportunityLedgerEntrySubType.Changed:
          {
            const render = entry.body;
            const entity = flattenedEntities.find((x) => x.id == render.entityId);
            if (entity != null) {
              entity.render = render.render;
              entity.localRender = render.localRender;
            }
          }
          break;
      }
    }
  }
  modifyEntityRenderDimensions(opportunity, entry) {
    if (entry.body != null) {
      const flattenedEntities = opportunity.flattenEntities();
      const flattenedFixtures = opportunity.flattenFixtures(flattenedEntities);
      switch (entry.subType) {
        case OpportunityLedgerEntrySubType.Changed:
          {
            const render = entry.body;
            const entity = flattenedEntities.find((x) => x.id == render.entityId) ?? flattenedFixtures.find((x) => x.id == render.entityId);
            if (entity != null && render.dimensions != null) {
              const mapped = new RenderDimensions(render.dimensions.x, render.dimensions.y, render.dimensions.width, render.dimensions.height, render.dimensions.rotation);
              entity.renderDimensions = mapped;
            }
          }
          break;
      }
    }
  }
  modifyEntityPhoto(opportunity, entry) {
    if (entry.body != null) {
      const flattenedEntities = opportunity.flattenEntities();
      switch (entry.subType) {
        case OpportunityLedgerEntrySubType.Added:
          {
            const photo = entry.body;
            const entity = flattenedEntities.find((x) => x.id == photo.entityId);
            if (entity != null) {
              if (photo.photo != null) {
                entity.photos.push(photo.photo);
              } else if (photo.localPhoto != null) {
                entity.photos_B64.push(photo.localPhoto);
              }
            }
          }
          break;
        case OpportunityLedgerEntrySubType.Removed:
          {
            const photo = entry.body;
            const entity = flattenedEntities.find((x) => x.id == photo.entityId);
            if (entity != null && photo.photoRef != null) {
              const ref = photo.photoRef;
              const localIndex = entity.photos_B64.findIndex((x) => hashString(x) == ref);
              if (localIndex > -1) {
                entity.photos_B64.splice(localIndex, 1);
              }
              const remoteIndex = entity.photos.findIndex((x) => hashString(x.src) == ref);
              if (remoteIndex > -1) {
                entity.photos.splice(remoteIndex, 1);
              }
            }
          }
          break;
      }
    }
  }
  modifyEntityAttribute(opportunity, entry) {
    if (entry.body != null) {
      const flattenedEntities = opportunity.flattenEntities();
      const flattenedFixtures = opportunity.flattenFixtures(flattenedEntities);
      switch (entry.subType) {
        case OpportunityLedgerEntrySubType.Changed:
          {
            const attribute = entry.body;
            const entity = flattenedEntities.find((x) => x.id == attribute.entityId) ?? flattenedFixtures.find((x) => x.id == attribute.entityId);
            if (entity != null) {
              const existingAttribute = entity.attributes.find((x) => x.id == attribute.attributeId);
              if (existingAttribute != null) {
                existingAttribute.values = attribute.values;
                existingAttribute.valueChanged$.next();
              }
            }
          }
          break;
      }
    }
  }
  modifyEntityNote(opportunity, entry) {
    if (entry.body != null) {
      const flattenedEntities = opportunity.flattenEntities();
      const flattenedFixtures = opportunity.flattenFixtures(flattenedEntities);
      switch (entry.subType) {
        case OpportunityLedgerEntrySubType.Changed:
          {
            const note = entry.body;
            const entity = flattenedEntities.find((x) => x.id == note.entityId) ?? flattenedFixtures.find((x) => x.id == note.entityId);
            if (entity != null && note.value != null) {
              entity.notes[note.key] = note.value.toString();
            }
          }
          break;
      }
    }
  }
  modifyAcceptance(opportunity, entry) {
    switch (entry.subType) {
      case OpportunityLedgerEntrySubType.Added:
      case OpportunityLedgerEntrySubType.Changed:
        {
          opportunity.accepted = true;
          opportunity.accepted_Utc = entry.timeStamp_Utc;
        }
        break;
      case OpportunityLedgerEntrySubType.Removed:
        {
          opportunity.accepted = false;
          opportunity.accepted_Utc = void 0;
        }
        break;
    }
  }
  modifyAssignment(opportunity, entry) {
    switch (entry.subType) {
      case OpportunityLedgerEntrySubType.Added:
      case OpportunityLedgerEntrySubType.Changed:
        {
          if (entry.body != null) {
            const assignedTo = entry.body;
            opportunity.assignedTo = assignedTo;
          }
        }
        break;
      case OpportunityLedgerEntrySubType.Removed:
        {
          opportunity.assignedTo = void 0;
        }
        break;
    }
  }
  modifyShop(opportunity, entry) {
    switch (entry.subType) {
      case OpportunityLedgerEntrySubType.Added:
      case OpportunityLedgerEntrySubType.Changed:
        {
          if (entry.body != null) {
            const shop = entry.body;
            opportunity.selectedShopId = shop;
          }
        }
        break;
      case OpportunityLedgerEntrySubType.Removed:
        {
          opportunity.selectedShopId = void 0;
        }
        break;
    }
  }
  modifyInstallDate(opportunity, entry) {
    switch (entry.subType) {
      case OpportunityLedgerEntrySubType.Added:
      case OpportunityLedgerEntrySubType.Changed:
        {
          if (entry.body != null) {
            const installDate = entry.body;
            opportunity.preferredInstallDate = installDate;
          }
        }
        break;
      case OpportunityLedgerEntrySubType.Removed:
        {
          opportunity.preferredInstallDate = void 0;
        }
        break;
    }
  }
  modifyLender(opportunity, entry) {
    if (entry.body != null) {
      switch (entry.subType) {
        case OpportunityLedgerEntrySubType.Added:
          {
            const lender = entry.body;
            opportunity.lenders.push(lender);
          }
          break;
        case OpportunityLedgerEntrySubType.Changed:
          {
            const lender = entry.body;
            let existingLender = opportunity.lenders.find((x) => x.name == lender.name);
            if (existingLender != null) {
              existingLender = deepClone(lender);
            }
          }
          break;
        case OpportunityLedgerEntrySubType.Removed:
          {
            const lender = entry.body;
            const existingIndex = opportunity.lenders.findIndex((x) => x.name == lender);
            if (existingIndex > -1) {
              opportunity.lenders.splice(existingIndex, 1);
            }
          }
          break;
      }
    }
  }
  modifyPayment(opportunity, entry) {
    if (entry.body != null) {
      switch (entry.subType) {
        case OpportunityLedgerEntrySubType.Added:
          {
            const payment = entry.body;
            opportunity.payments.push(payment);
          }
          break;
        case OpportunityLedgerEntrySubType.Changed:
          {
            const payment = entry.body;
            let existingPayment = opportunity.payments.find((x) => x.id == payment.id);
            if (existingPayment != null) {
              existingPayment = deepClone(payment);
            }
          }
          break;
        case OpportunityLedgerEntrySubType.Removed:
          {
            const payment = entry.body;
            const existingIndex = opportunity.payments.findIndex((x) => x.id == payment);
            if (existingIndex > -1) {
              opportunity.payments.splice(existingIndex, 1);
            }
          }
          break;
      }
    }
  }
  modifyPrice(opportunity, entry) {
    switch (entry.subType) {
      case OpportunityLedgerEntrySubType.Added:
      case OpportunityLedgerEntrySubType.Changed:
        {
          if (entry.body != null) {
            const price = entry.body;
            const mapped = this.mapOpportunityPrice(price, opportunity);
            opportunity.price = mapped;
          }
        }
        break;
    }
  }
  modifyCustomPrice(opportunity, entry) {
    switch (entry.subType) {
      case OpportunityLedgerEntrySubType.Added:
      case OpportunityLedgerEntrySubType.Changed:
        {
          if (entry.body != null) {
            const price = entry.body;
            const mapped = this.mapOpportunityPrice(price, opportunity);
            opportunity.customPrice = mapped;
          }
        }
        break;
      case OpportunityLedgerEntrySubType.Removed:
        {
          opportunity.customPrice = void 0;
        }
        break;
    }
  }
  modifyDisposition(opportunity, entry) {
    switch (entry.subType) {
      case OpportunityLedgerEntrySubType.Added:
        {
          if (entry.body != null) {
            const disposition = entry.body;
            opportunity.dispositionStatus = disposition;
          }
        }
        break;
      case OpportunityLedgerEntrySubType.Changed:
        {
          if (entry.body != null && opportunity.dispositionStatus != null) {
            const disposition = entry.body;
            opportunity.dispositionStatus = disposition;
          }
        }
        break;
      case OpportunityLedgerEntrySubType.Removed:
        {
          opportunity.dispositionStatus = void 0;
        }
        break;
    }
  }
  modifyDocuments(opportunity, entry) {
    switch (entry.subType) {
      case OpportunityLedgerEntrySubType.Added:
      case OpportunityLedgerEntrySubType.Changed:
        {
          if (entry.body != null) {
            const documents = entry.body;
            opportunity.documents = documents;
          }
        }
        break;
      case OpportunityLedgerEntrySubType.Removed:
        {
          opportunity.dispositionStatus = void 0;
        }
        break;
    }
  }
  modifySigner(opportunity, entry) {
    if (entry.body != null) {
      switch (entry.subType) {
        case OpportunityLedgerEntrySubType.Added:
          {
            const signer = entry.body;
            for (var i = 0, len = opportunity.documents.length; i < len; i++) {
              const document2 = opportunity.documents[i];
              const existingSigner = document2.signers.find((x) => x.fullName == signer.fullName);
              if (existingSigner == null) {
                document2.signers.push(signer);
              }
            }
          }
          break;
        case OpportunityLedgerEntrySubType.Changed:
          {
            const signer = entry.body;
            for (var i = 0, len = opportunity.documents.length; i < len; i++) {
              const document2 = opportunity.documents[i];
              const existingSigner = document2.signers.find((x) => x.fullName == signer.fullName);
              if (existingSigner != null) {
                existingSigner.consent = signer.consent;
              }
            }
          }
          break;
        case OpportunityLedgerEntrySubType.Removed:
          {
            const signerName = entry.body;
            for (var i = 0, len = opportunity.documents.length; i < len; i++) {
              const document2 = opportunity.documents[i];
              const signerIndex = document2.signers.findIndex((x) => x.fullName == signerName);
              if (signerIndex > -1) {
                document2.signers.splice(signerIndex, 1);
              }
            }
          }
          break;
      }
    }
  }
  modifySignature(opportunity, entry) {
    if (entry.body != null) {
      switch (entry.subType) {
        case OpportunityLedgerEntrySubType.Added:
          {
            const body = entry.body;
            const document2 = opportunity.documents.find((x) => x.id == body.signature.documentId);
            if (document2 != null) {
              document2.signatures.push(body.signature);
            }
          }
          break;
        case OpportunityLedgerEntrySubType.Changed:
          {
            const body = entry.body;
            const document2 = opportunity.documents.find((x) => x.id == body.signature.documentId);
            if (document2 != null) {
              let existingSignature = document2.signatures.find((x) => x.id == body.signature.id);
              if (existingSignature != null) {
                existingSignature = deepClone(body.signature);
              }
            }
          }
          break;
        case OpportunityLedgerEntrySubType.Removed:
          {
            const signature = entry.body;
            const document2 = opportunity.documents.find((x) => x.id == signature.documentId);
            if (document2 != null) {
              let existingIndex = document2.signatures.findIndex((x) => x.id == signature.id);
              if (existingIndex > -1) {
                document2.signatures.splice(existingIndex, 1);
              }
            }
          }
          break;
      }
    }
  }
  // #endregion
  // #region Telemetry
  storeRouteChange(opportunity, route, user) {
    return new Promise((res, rej) => __async(this, null, function* () {
      try {
        yield this.modifyOpportunity(opportunity, void 0, route, OpportunityLedgerEntryType.RouteChange, OpportunityLedgerEntrySubType.Changed, user.id);
        res();
      } catch (e) {
        console.error(`OPPORTUNITY ROUTE CHANGE: ${JSON.stringify(e)}`);
        rej();
      }
    }));
  }
  // #endregion
  // #region Status
  updateStatus(opportunity, status, user) {
    return new Promise((res, rej) => __async(this, null, function* () {
      try {
        yield this.modifyOpportunity(opportunity, void 0, status, OpportunityLedgerEntryType.Status, OpportunityLedgerEntrySubType.Changed, user.id);
        if (status != OpportunityStatus.Dispositioned) {
          this.removeDispositionStatus(opportunity, user);
        }
        res();
      } catch (e) {
        console.error(`OPPORTUNITY ACCEPTANCE: ${JSON.stringify(e)}`);
        rej();
      }
    }));
  }
  // #endregion
  // #region Customer
  // #endregion
  // #region Customer Needs
  updateCustomerNeed(opportunity, key, values, user) {
    return new Promise((res, rej) => __async(this, null, function* () {
      try {
        const entry = {
          entityId: void 0,
          entityName: void 0,
          key,
          value: values
        };
        yield this.modifyOpportunity(opportunity, void 0, entry, OpportunityLedgerEntryType.CustomerNeed, OpportunityLedgerEntrySubType.Changed, user.id);
        res();
      } catch (e) {
        console.error(`OPPORTUNITY CUSTOMER NEED CHANGE: ${JSON.stringify(e)}`);
        rej();
      }
    }));
  }
  // #endregion
  // #region Payment Setting
  // #endregion
  // #region Scheduling
  // #endregion
  // #region Property
  updateProperty(opportunity, property, user) {
    return new Promise((res, rej) => __async(this, null, function* () {
      try {
        const mapped = Object.assign(new OpportunityProperty(), property);
        const entry = mapped.toFlat();
        yield this.modifyOpportunity(opportunity, void 0, entry, OpportunityLedgerEntryType.Property, OpportunityLedgerEntrySubType.Changed, user.id);
        res(true);
      } catch (e) {
        console.error(`OPPORTUNITY PROPERTY ADDED: ${JSON.stringify(e)}`);
        rej();
      }
    }));
  }
  // #endregion
  // #region Job
  // #endregion
  // #region Entities
  addFixture(opportunity, fixture, parentEntityId, user) {
    return new Promise((res, rej) => __async(this, null, function* () {
      try {
        this.modifyOpportunity(opportunity, parentEntityId, fixture, OpportunityLedgerEntryType.Fixture, OpportunityLedgerEntrySubType.Added, user.id).then(() => {
          opportunity.updateAttributes();
          opportunity.applyAttributeRules();
          opportunity.renumberRooms();
          opportunity.renumberFixtures();
          opportunity.calcPricing(false, false);
        });
        res(true);
      } catch (e) {
        console.error(`OPPORTUNITY FIXTURE ADDED: ${JSON.stringify(e)}`);
        rej();
      }
    }));
  }
  removeFixture(opportunity, fixtureId, parentEntityId, user) {
    return new Promise((res, rej) => {
      try {
        this.modifyOpportunity(opportunity, parentEntityId, fixtureId, OpportunityLedgerEntryType.Fixture, OpportunityLedgerEntrySubType.Removed, user.id).then(() => {
          opportunity.updateAttributes();
          opportunity.applyAttributeRules();
          opportunity.renumberRooms();
          opportunity.renumberFixtures();
          opportunity.calcPricing(false, false);
        });
        res(true);
      } catch (e) {
        console.error(`OPPORTUNITY FIXTURE REMOVED: ${JSON.stringify(e)}`);
        rej();
      }
    });
  }
  addFixturePhoto(opportunity, entity, photo, localPhoto, user) {
    return new Promise((res, rej) => __async(this, null, function* () {
      try {
        const entry = {
          entityId: entity.id,
          entityName: `${entity.name} ${entity.number}`,
          photo,
          localPhoto
        };
        yield this.modifyOpportunity(opportunity, void 0, entry, OpportunityLedgerEntryType.FixturePhoto, OpportunityLedgerEntrySubType.Added, user.id);
        res(true);
      } catch (e) {
        console.error(`OPPORTUNITY FIXTURE PHOTO ADDED: ${JSON.stringify(e)}`);
        rej();
      }
    }));
  }
  removeFixturePhoto(opportunity, entity, photoRef, user) {
    return new Promise((res, rej) => __async(this, null, function* () {
      try {
        const removal = {
          entityId: entity.id,
          entityName: `${entity.name} ${entity.number}`,
          photoRef
        };
        yield this.modifyOpportunity(opportunity, void 0, removal, OpportunityLedgerEntryType.FixturePhoto, OpportunityLedgerEntrySubType.Removed, user.id);
        res(true);
      } catch (e) {
        console.error(`OPPORTUNITY FIXTURE PHOTO REMOVED: ${JSON.stringify(e)}`);
        rej();
      }
    }));
  }
  addEntity(opportunity, entity, parentEntityId, user) {
    return new Promise((res, rej) => __async(this, null, function* () {
      try {
        this.modifyOpportunity(opportunity, parentEntityId, entity, OpportunityLedgerEntryType.Entity, OpportunityLedgerEntrySubType.Added, user.id).then(() => {
          opportunity.updateAttributes();
          opportunity.applyAttributeRules();
          opportunity.renumberRooms();
          opportunity.renumberFixtures();
          opportunity.calcPricing(false, false);
        });
        res(true);
      } catch (e) {
        console.error(`OPPORTUNITY ENTITY ADDED: ${JSON.stringify(e)}`);
        rej();
      }
    }));
  }
  updateEntity(opportunity, entity, user) {
    return new Promise((res, rej) => __async(this, null, function* () {
      try {
        const flat = entity.toFlat();
        this.modifyOpportunity(opportunity, void 0, flat, OpportunityLedgerEntryType.Entity, OpportunityLedgerEntrySubType.Changed, user.id).then(() => {
          opportunity.updateAttributes();
          opportunity.applyAttributeRules();
          opportunity.renumberRooms();
          opportunity.renumberFixtures();
          opportunity.calcPricing(false, false);
        });
        res(true);
      } catch (e) {
        console.error(`OPPORTUNITY ENTITY CHANGED: ${JSON.stringify(e)}`);
        rej();
      }
    }));
  }
  removeEntity(opportunity, entityId, parentEntityId, user) {
    return new Promise((res, rej) => {
      try {
        this.modifyOpportunity(opportunity, parentEntityId, entityId, OpportunityLedgerEntryType.Entity, OpportunityLedgerEntrySubType.Removed, user.id).then(() => {
          opportunity.updateAttributes();
          opportunity.applyAttributeRules();
          opportunity.renumberRooms();
          opportunity.renumberFixtures();
          opportunity.calcPricing(false, false);
        });
        res(true);
      } catch (e) {
        console.error(`OPPORTUNITY ENTITY REMOVED: ${JSON.stringify(e)}`);
        rej();
      }
    });
  }
  updateEntityRender(opportunity, entity, render, localRender, user) {
    return new Promise((res, rej) => __async(this, null, function* () {
      try {
        const entry = {
          entityId: entity.id,
          entityName: entity.name != null ? `${entity.name} ${entity.number}` : `${entity.type} ${entity.number}`,
          render,
          localRender
        };
        yield this.modifyOpportunity(opportunity, void 0, entry, OpportunityLedgerEntryType.EntityRender, OpportunityLedgerEntrySubType.Changed, user.id);
        res(true);
      } catch (e) {
        console.error(`OPPORTUNITY ENTITY CHANGED: ${JSON.stringify(e)}`);
        rej();
      }
    }));
  }
  updateEntityRenderDimensions(opportunity, entity, renderDimensions, user) {
    return new Promise((res, rej) => __async(this, null, function* () {
      try {
        let entityName = `${entity.name} ${entity.number}`;
        if (entity.name == null && entity instanceof OpportunityPropertyEntity) {
          entityName = `${entity.type} ${entity.number}`;
        }
        const entry = {
          entityId: entity.id,
          entityName,
          dimensions: renderDimensions
        };
        yield this.modifyOpportunity(opportunity, void 0, entry, OpportunityLedgerEntryType.EntityRenderDimensions, OpportunityLedgerEntrySubType.Changed, user.id);
        res(true);
      } catch (e) {
        console.error(`OPPORTUNITY ENTITY RENDER DIMENSIONS CHANGES: ${JSON.stringify(e)}`);
        rej();
      }
    }));
  }
  addEntityPhoto(opportunity, entity, photo, localPhoto, user) {
    return new Promise((res, rej) => __async(this, null, function* () {
      try {
        const entry = {
          entityId: entity.id,
          entityName: entity.name != null ? `${entity.name} ${entity.number}` : `${entity.type} ${entity.number}`,
          photo,
          localPhoto
        };
        yield this.modifyOpportunity(opportunity, void 0, entry, OpportunityLedgerEntryType.EntityPhoto, OpportunityLedgerEntrySubType.Added, user.id);
        res(true);
      } catch (e) {
        console.error(`OPPORTUNITY ENTITY PHOTO ADDED: ${JSON.stringify(e)}`);
        rej();
      }
    }));
  }
  removeEntityPhoto(opportunity, entity, photoRef, user) {
    return new Promise((res, rej) => __async(this, null, function* () {
      try {
        const removal = {
          entityId: entity.id,
          entityName: entity.name != null ? `${entity.name} ${entity.number}` : `${entity.type} ${entity.number}`,
          photoRef
        };
        yield this.modifyOpportunity(opportunity, void 0, removal, OpportunityLedgerEntryType.EntityPhoto, OpportunityLedgerEntrySubType.Removed, user.id);
        res(true);
      } catch (e) {
        console.error(`OPPORTUNITY ENTITY PHOTO REMOVED: ${JSON.stringify(e)}`);
        rej();
      }
    }));
  }
  updateEntityAttribute(opportunity, entityId, entityName, attributeId, attributeName, values, user) {
    return new Promise((res, rej) => __async(this, null, function* () {
      try {
        const entry = {
          entityId,
          entityName,
          attributeId,
          attributeName,
          values
        };
        this.modifyOpportunity(opportunity, void 0, entry, OpportunityLedgerEntryType.EntityAttribute, OpportunityLedgerEntrySubType.Changed, user.id).then(() => {
          opportunity.updateAttributes();
          opportunity.applyAttributeRules();
          opportunity.renumberRooms();
          opportunity.renumberFixtures();
          opportunity.calcPricing(false, false);
        });
        res();
      } catch (e) {
        console.error(`OPPORTUNITY ENTITY ATTRIBUTE CHANGED: ${JSON.stringify(e)}`);
        rej();
      }
    }));
  }
  updateEntityNote(opportunity, entityId, entityName, key, value, user) {
    return new Promise((res, rej) => __async(this, null, function* () {
      try {
        const entry = {
          entityId,
          entityName,
          key,
          value
        };
        yield this.modifyOpportunity(opportunity, void 0, entry, OpportunityLedgerEntryType.EntityNote, OpportunityLedgerEntrySubType.Changed, user.id);
        res(true);
      } catch (e) {
        console.error(`OPPORTUNITY ENTITY NOTE CHANGED: ${JSON.stringify(e)}`);
        rej();
      }
    }));
  }
  // #endregion
  // #region Acceptance
  acceptOpportunity(opportunity, user) {
    return new Promise((res, rej) => __async(this, null, function* () {
      if (opportunity.assignedTo?.id == user.id) {
        try {
          yield this.modifyOpportunity(opportunity, void 0, void 0, OpportunityLedgerEntryType.Acceptance, OpportunityLedgerEntrySubType.Added, user.id);
          res(true);
        } catch (e) {
          console.error(`OPPORTUNITY ACCEPTANCE: ${JSON.stringify(e)}`);
          rej();
        }
      } else {
        res(false);
      }
    }));
  }
  // #endregion
  // #region Assignment
  // #endregion
  // #region Shop
  updateShop(opportunity, shopId, user) {
    return new Promise((res, rej) => __async(this, null, function* () {
      try {
        yield this.modifyOpportunity(opportunity, void 0, shopId, OpportunityLedgerEntryType.Shop, OpportunityLedgerEntrySubType.Changed, user.id);
        res(true);
      } catch (e) {
        console.error(`OPPORTUNITY SHOP CHANGED: ${JSON.stringify(e)}`);
        rej();
      }
    }));
  }
  removeShop(opportunity, user) {
    return new Promise((res, rej) => __async(this, null, function* () {
      try {
        yield this.modifyOpportunity(opportunity, void 0, void 0, OpportunityLedgerEntryType.Shop, OpportunityLedgerEntrySubType.Removed, user.id);
        res(true);
      } catch (e) {
        console.error(`OPPORTUNITY SHOP REMOVED: ${JSON.stringify(e)}`);
        rej();
      }
    }));
  }
  // #endregion
  // #region Install Date
  updateInstallDate(opportunity, installDate, user) {
    return new Promise((res, rej) => __async(this, null, function* () {
      try {
        yield this.modifyOpportunity(opportunity, void 0, installDate, OpportunityLedgerEntryType.InstallDate, OpportunityLedgerEntrySubType.Changed, user.id);
        res(true);
      } catch (e) {
        console.error(`OPPORTUNITY INSTALL DATE CHANGED: ${JSON.stringify(e)}`);
        rej();
      }
    }));
  }
  removeInstallDate(opportunity, user) {
    return new Promise((res, rej) => __async(this, null, function* () {
      try {
        yield this.modifyOpportunity(opportunity, void 0, void 0, OpportunityLedgerEntryType.InstallDate, OpportunityLedgerEntrySubType.Removed, user.id);
        res(true);
      } catch (e) {
        console.error(`OPPORTUNITY INSTALL DATE REMOVED: ${JSON.stringify(e)}`);
        rej();
      }
    }));
  }
  // #endregion
  // #region Lenders
  addLender(opportunity, lender, user) {
    return new Promise((res, rej) => __async(this, null, function* () {
      try {
        yield this.modifyOpportunity(opportunity, void 0, lender, OpportunityLedgerEntryType.Lender, OpportunityLedgerEntrySubType.Added, user.id);
        res(true);
      } catch (e) {
        console.error(`OPPORTUNITY LENDER ADDED: ${JSON.stringify(e)}`);
        rej();
      }
    }));
  }
  updateLender(opportunity, lender, user) {
    return new Promise((res, rej) => __async(this, null, function* () {
      try {
        yield this.modifyOpportunity(opportunity, void 0, lender, OpportunityLedgerEntryType.Lender, OpportunityLedgerEntrySubType.Changed, user.id);
        res(true);
      } catch (e) {
        console.error(`OPPORTUNITY LENDER CHANGED: ${JSON.stringify(e)}`);
        rej();
      }
    }));
  }
  removeLender(opportunity, lender, user) {
    return new Promise((res, rej) => __async(this, null, function* () {
      try {
        yield this.modifyOpportunity(opportunity, void 0, lender, OpportunityLedgerEntryType.Lender, OpportunityLedgerEntrySubType.Removed, user.id);
        res(true);
      } catch (e) {
        console.error(`OPPORTUNITY LENDER REMOVED: ${JSON.stringify(e)}`);
        rej();
      }
    }));
  }
  // #endregion
  // #region Payment
  addPayment(opportunity, payment, user) {
    return new Promise((res, rej) => __async(this, null, function* () {
      try {
        yield this.modifyOpportunity(opportunity, void 0, payment, OpportunityLedgerEntryType.Payment, OpportunityLedgerEntrySubType.Added, user.id);
        res(true);
      } catch (e) {
        console.error(`OPPORTUNITY PAYMENT ADDED: ${JSON.stringify(e)}`);
        rej();
      }
    }));
  }
  updatePayment(opportunity, payment, user) {
    return new Promise((res, rej) => __async(this, null, function* () {
      try {
        yield this.modifyOpportunity(opportunity, void 0, payment, OpportunityLedgerEntryType.Payment, OpportunityLedgerEntrySubType.Changed, user.id);
        res(true);
      } catch (e) {
        console.error(`OPPORTUNITY PAYMENT CHANGED: ${JSON.stringify(e)}`);
        rej();
      }
    }));
  }
  removePayment(opportunity, id, user) {
    return new Promise((res, rej) => __async(this, null, function* () {
      try {
        yield this.modifyOpportunity(opportunity, void 0, id, OpportunityLedgerEntryType.Payment, OpportunityLedgerEntrySubType.Removed, user.id);
        res(true);
      } catch (e) {
        console.error(`OPPORTUNITY PAYMENT REMOVED: ${JSON.stringify(e)}`);
        rej();
      }
    }));
  }
  updatePaymentSetting(opportunity, paymentSetting, user) {
    return new Promise((res, rej) => __async(this, null, function* () {
      try {
        yield this.modifyOpportunity(opportunity, void 0, paymentSetting, OpportunityLedgerEntryType.PaymentSetting, OpportunityLedgerEntrySubType.Changed, user.id);
        res(true);
      } catch (e) {
        console.error(`OPPORTUNITY ACCEPTANCE: ${JSON.stringify(e)}`);
        rej();
      }
    }));
  }
  // #endregion
  // #region Promotions
  applyPromotion(price, promotion, updatePrice = true) {
    const existingPromotion = price.appliedPromotions.find((x) => x.id == promotion.id);
    if (promotion.discount != null && existingPromotion == null) {
      const appliedDiscount = promotion.appliedDiscount ?? this.calcPromotionDiscount(promotion, price);
      promotion.active = true;
      price.appliedPromotions.push(promotion);
      if (updatePrice) {
        price.finalPrice = price.finalPrice - appliedDiscount;
      }
      price.calcTotalDiscount();
    }
  }
  removePromotion(price, promotion) {
    const existingPromotionIndex = price.appliedPromotions.findIndex((x) => x.id == promotion.id);
    if (existingPromotionIndex > -1) {
      const existingPromotion = price.appliedPromotions[existingPromotionIndex];
      if (existingPromotion.appliedDiscount != null) {
        existingPromotion.active = false;
        existingPromotion.locked = false;
        price.appliedPromotions.splice(existingPromotionIndex, 1);
        price.finalPrice = price.finalPrice + existingPromotion.appliedDiscount;
        price.calcTotalDiscount();
      }
    }
  }
  calcPromotionDiscount(promotion, price, invert = false) {
    if (promotion.discount != null) {
      switch (promotion.type) {
        case PromotionType.Percent: {
          let promotionDiscount = promotion.discount / 100;
          if (invert) {
            promotionDiscount = 1 - promotion.discount / 100;
            return safePrice(safePrice(price.finalPrice / promotionDiscount) - price.finalPrice);
          } else {
            return safePrice(price.finalPrice * (promotion.discount / 100));
          }
        }
      }
    }
    return 0;
  }
  resetPromotions(opportunity, price, invert = false) {
    price.appliedPromotions = [];
    for (var i = 0, len = opportunity.availablePromotions.length; i < len; i++) {
      const promotion = opportunity.availablePromotions[i];
      promotion.active = false;
      promotion.locked = false;
      if (promotion.discount != null) {
        const invertCalc = invert && promotion.autoApply;
        switch (promotion.type) {
          case PromotionType.Percent:
            {
              promotion.appliedDiscount = this.calcPromotionDiscount(promotion, price, invertCalc);
            }
            break;
        }
        const appliedDiscount = promotion.appliedDiscount ?? 0;
        promotion.appliedDiscount = appliedDiscount;
      }
      if (promotion.autoApply) {
        this.applyPromotion(price, promotion, false);
      }
    }
  }
  calcSpecialPromotion(price) {
    let delta = price.targetPrice - price.totalDiscount - price.finalPrice;
    delta = safePrice(delta);
    let specialPromotion = price.appliedPromotions.find((x) => x.name == "Special Discount");
    if (delta > 0) {
      if (specialPromotion == null) {
        specialPromotion = {
          id: generateId(),
          name: "Special Discount",
          discount: delta,
          autoApply: true,
          type: PromotionType.Amount,
          manual: false,
          appliedDiscount: delta,
          stackable: true,
          active: true,
          priority: 99,
          locked: true
        };
        price.appliedPromotions.push(specialPromotion);
      } else {
        specialPromotion.discount = delta;
        specialPromotion.appliedDiscount = delta;
      }
    } else if (specialPromotion != null) {
      this.removePromotion(price, specialPromotion);
    }
  }
  // #endregion
  // #region Price
  updatePrice(opportunity, price, user) {
    return new Promise((res, rej) => __async(this, null, function* () {
      try {
        const clone = deepClone(price);
        if (clone != null) {
          for (var i = 0, len = clone.details.length; i < len; i++) {
            const detail = clone.details[i];
            for (var j = 0, jlen = detail.lineItems.length; j < jlen; j++) {
              const lineItem = detail.lineItems[j];
              lineItem.entity = void 0;
            }
          }
        }
        ;
        yield this.modifyOpportunity(opportunity, void 0, clone, OpportunityLedgerEntryType.Price, OpportunityLedgerEntrySubType.Changed, user.id);
        res();
      } catch (e) {
        console.error(`OPPORTUNITY PRICE CHANGED: ${JSON.stringify(e)}`);
        rej();
      }
    }));
  }
  // #endregion
  // #region Custom Price
  resetCustomPrice(opportunity, user) {
    const customPrice = deepClone(opportunity.price);
    if (customPrice != null) {
      const mapped = this.mapOpportunityPrice(customPrice, opportunity);
      const originalFinalPrice = mapped.finalPrice;
      this.resetPromotions(opportunity, mapped, true);
      mapped.targetPrice = originalFinalPrice + mapped.totalDiscount;
      this.updateCustomPrice(opportunity, mapped, user);
    }
  }
  updateCustomPrice(opportunity, customPrice, user) {
    return new Promise((res, rej) => __async(this, null, function* () {
      try {
        this.calcSpecialPromotion(customPrice);
        const clone = deepClone(customPrice);
        if (clone != null) {
          for (var i = 0, len = clone.details.length; i < len; i++) {
            const detail = clone.details[i];
            for (var j = 0, jlen = detail.lineItems.length; j < jlen; j++) {
              const lineItem = detail.lineItems[j];
              lineItem.entity = void 0;
            }
          }
        }
        ;
        yield this.modifyOpportunity(opportunity, void 0, clone, OpportunityLedgerEntryType.CustomPrice, OpportunityLedgerEntrySubType.Changed, user.id);
        res();
      } catch (e) {
        console.error(`OPPORTUNITY CUSTOM PRICE CHANGED: ${JSON.stringify(e)}`);
        rej();
      }
    }));
  }
  // #endregion
  // #region Disposition
  addDispositionStatus(opportunity, status, user) {
    return new Promise((res, rej) => __async(this, null, function* () {
      try {
        yield this.modifyOpportunity(opportunity, void 0, status, OpportunityLedgerEntryType.Disposition, OpportunityLedgerEntrySubType.Added, user.id);
        res(true);
      } catch (e) {
        console.error(`OPPORTUNITY DISPOSITION: ${JSON.stringify(e)}`);
        rej();
      }
    }));
  }
  removeDispositionStatus(opportunity, user) {
    return new Promise((res, rej) => __async(this, null, function* () {
      try {
        yield this.modifyOpportunity(opportunity, void 0, void 0, OpportunityLedgerEntryType.Disposition, OpportunityLedgerEntrySubType.Removed, user.id);
        res(true);
      } catch (e) {
        console.error(`OPPORTUNITY DISPOSITION: ${JSON.stringify(e)}`);
        rej();
      }
    }));
  }
  // #endregion
  // #region Documents
  updateDocuments(opportunity, documents, user) {
    return new Promise((res, rej) => __async(this, null, function* () {
      try {
        for (var i = 0, len = opportunity.documents.length; i < len; i++) {
          const document2 = opportunity.documents[i];
          for (var j = 0, jlen = document2.signatures.length; j < jlen; j++) {
            const signature = document2.signatures[j];
            this.removeSignature(opportunity, signature, document2.name, user);
          }
        }
        yield this.modifyOpportunity(opportunity, void 0, documents, OpportunityLedgerEntryType.Documents, OpportunityLedgerEntrySubType.Changed, user.id);
        opportunity.checkDocumentValidity();
        res(true);
      } catch (e) {
        console.error(`OPPORTUNITY DOCUMENTS: ${JSON.stringify(e)}`);
        rej();
      }
    }));
  }
  sendDocumentsToCustomer(opportunity, content, recipientName, user, email = void 0, phone = void 0) {
    const url = `{{api}}/opportunities/${opportunity.id}/documents/customer`;
    const headers = { "Content-Type": "application/json" };
    const request = {
      content,
      recipientName,
      emailAddress: email,
      smsNumber: phone
    };
    return this.http.post(url, JSON.stringify(request), { headers }).pipe(map((response) => {
      this.modifyOpportunity(opportunity, void 0, void 0, OpportunityLedgerEntryType.SentForSignature, OpportunityLedgerEntrySubType.Added, user.id).then();
      return response;
    }));
  }
  sendFinalDocuments(opportunity, documents, recipientName, recipientEmail, user) {
    if (Capacitor.isNativePlatform()) {
      const url = `${environment.API_URL}/opportunities/${opportunity.id}/documents/final/send`;
      return new Observable((o) => {
        const base64 = this.contentService.convertBytesToBase64(documents);
        Filesystem.writeFile({
          path: "Documents.pdf",
          data: base64,
          directory: Directory.Cache
        }).then((storeResult) => {
          o.next({ progress: 100 });
          return Http.uploadFile({
            url,
            name: "documents",
            filePath: storeResult.uri,
            data: {
              recipientName,
              recipientEmail
            },
            headers: {
              Authorization: `Bearer ${this.authRefService.accessToken ?? ""}`
            }
          }).then((result) => {
            this.modifyOpportunity(opportunity, void 0, void 0, OpportunityLedgerEntryType.ExecutedDocumentsSent, OpportunityLedgerEntrySubType.Added, user.id).then();
            o.next(result.data);
            o.complete();
          }).catch((uploadErr) => {
            this.logService.log(JSON.stringify(uploadErr), `CAPACITOR: Failed to send final documents for opportunity ${opportunity.id}.`).subscribe();
            o.error(uploadErr);
            o.complete();
          }).finally(() => __async(this, null, function* () {
            yield Filesystem.deleteFile({ path: "Documents.pdf", directory: Directory.Cache });
          }));
        }).catch((e) => {
          this.logService.log(JSON.stringify(e), `CAPACITOR: Failed to prepare documents for opportunity ${opportunity.id}.`).subscribe();
          o.error("Failed to prepare documents for sending.");
          o.complete();
        });
      });
    } else {
      const url = `{{api}}/opportunities/${opportunity.id}/documents/final/send`;
      const pdfBlob = this.contentService.convertBytesToBlob(documents, "application/pdf");
      const formData = new FormData();
      formData.append("documents", pdfBlob);
      formData.append("recipientName", recipientName);
      formData.append("recipientEmail", recipientEmail);
      return this.http.post(url, formData, {
        reportProgress: true,
        observe: "events"
      }).pipe(filter((e) => e.type === HttpEventType.UploadProgress || e.type === HttpEventType.Response), map((event) => {
        if (event.type === HttpEventType.UploadProgress) {
          const percent = event.total ? Math.round(100 * event.loaded / event.total) : 0;
          return { progress: percent };
        } else if (event.type === HttpEventType.Response) {
          this.modifyOpportunity(opportunity, void 0, void 0, OpportunityLedgerEntryType.ExecutedDocumentsSent, OpportunityLedgerEntrySubType.Added, user.id).then();
        }
        return event.body;
      }));
    }
  }
  sendQuote(opportunity, quote, recipientName, recipientEmail, user) {
    if (Capacitor.isNativePlatform()) {
      const url = `${environment.API_URL}/opportunities/${opportunity.id}/documents/quote/send`;
      return new Observable((o) => {
        const base64 = this.contentService.convertBytesToBase64(quote);
        Filesystem.writeFile({
          path: "Quote.pdf",
          data: base64,
          directory: Directory.Cache
        }).then((storeResult) => __async(this, null, function* () {
          o.next({ progress: 100 });
          return Http.uploadFile({
            url,
            name: "quote",
            filePath: storeResult.uri,
            data: {
              recipientName,
              recipientEmail
            },
            headers: {
              Authorization: `Bearer ${this.authRefService.accessToken ?? ""}`
            }
          }).then((result) => {
            this.modifyOpportunity(opportunity, void 0, void 0, OpportunityLedgerEntryType.QuoteSent, OpportunityLedgerEntrySubType.Added, user.id).then();
            o.next(result.data);
            o.complete();
          }).catch((uploadErr) => {
            this.logService.log(JSON.stringify(uploadErr), `CAPACITOR: Failed to send quote for opportunity ${opportunity.id}.`).subscribe();
            o.error(uploadErr);
            o.complete();
          }).finally(() => __async(this, null, function* () {
            yield Filesystem.deleteFile({ path: "Quote.pdf", directory: Directory.Cache });
          }));
        })).catch((e) => {
          this.logService.log(JSON.stringify(e), `CAPACITOR: Failed to prepare quote for opportunity ${opportunity.id}.`).subscribe();
          o.error("Failed to prepare quote for sending.");
          o.complete();
        });
      });
    } else {
      const url = `{{api}}/opportunities/${opportunity.id}/documents/quote/send`;
      const pdfBlob = this.contentService.convertBytesToBlob(quote, "application/pdf");
      const formData = new FormData();
      formData.append("quote", pdfBlob);
      formData.append("recipientName", recipientName);
      formData.append("recipientEmail", recipientEmail);
      return this.http.post(url, formData, {
        reportProgress: true,
        observe: "events"
      }).pipe(filter((e) => e.type === HttpEventType.UploadProgress || e.type === HttpEventType.Response), map((event) => {
        if (event.type === HttpEventType.UploadProgress) {
          const percent = event.total ? Math.round(100 * event.loaded / event.total) : 0;
          return { progress: percent };
        } else if (event.type === HttpEventType.Response) {
          this.modifyOpportunity(opportunity, void 0, void 0, OpportunityLedgerEntryType.QuoteSent, OpportunityLedgerEntrySubType.Added, user.id).then();
        }
        return event.body;
      }));
    }
  }
  addSigner(opportunity, signer, user) {
    return new Promise((res, rej) => __async(this, null, function* () {
      try {
        yield this.modifyOpportunity(opportunity, void 0, signer, OpportunityLedgerEntryType.Signer, OpportunityLedgerEntrySubType.Added, user.id);
        res(true);
      } catch (e) {
        console.error(`OPPORTUNITY SIGNER ADDED: ${JSON.stringify(e)}`);
        rej();
      }
    }));
  }
  updateSigner(opportunity, signer, user) {
    return new Promise((res, rej) => __async(this, null, function* () {
      try {
        yield this.modifyOpportunity(opportunity, void 0, signer, OpportunityLedgerEntryType.Signer, OpportunityLedgerEntrySubType.Changed, user.id);
        res(true);
      } catch (e) {
        console.error(`OPPORTUNITY SIGNER CHANGED: ${JSON.stringify(e)}`);
        rej();
      }
    }));
  }
  removeSigner(opportunity, signer, user) {
    return new Promise((res, rej) => __async(this, null, function* () {
      try {
        yield this.modifyOpportunity(opportunity, void 0, signer.fullName, OpportunityLedgerEntryType.Signer, OpportunityLedgerEntrySubType.Removed, user.id);
        res(true);
      } catch (e) {
        console.error(`OPPORTUNITY SIGNER REMOVED: ${JSON.stringify(e)}`);
        rej();
      }
    }));
  }
  addSignature(opportunity, signature, documentName, user) {
    return new Promise((res, rej) => __async(this, null, function* () {
      try {
        const entry = new OpportunityLedgerSignature(signature, documentName);
        yield this.modifyOpportunity(opportunity, void 0, entry, OpportunityLedgerEntryType.Signature, OpportunityLedgerEntrySubType.Added, user.id);
        res(true);
      } catch (e) {
        console.error(`OPPORTUNITY SIGNATURE ADDED: ${JSON.stringify(e)}`);
        rej();
      }
    }));
  }
  updateSignature(opportunity, signature, documentName, user) {
    return new Promise((res, rej) => __async(this, null, function* () {
      try {
        const entry = new OpportunityLedgerSignature(signature, documentName);
        yield this.modifyOpportunity(opportunity, void 0, entry, OpportunityLedgerEntryType.Signature, OpportunityLedgerEntrySubType.Changed, user.id);
        res(true);
      } catch (e) {
        console.error(`OPPORTUNITY SIGNATURE CHANGED: ${JSON.stringify(e)}`);
        rej();
      }
    }));
  }
  removeSignature(opportunity, signature, documentName, user) {
    return new Promise((res, rej) => __async(this, null, function* () {
      try {
        const removal = {
          id: signature.id,
          signerName: signature.signerName ?? "",
          documentId: signature.documentId ?? "",
          documentName
        };
        yield this.modifyOpportunity(opportunity, void 0, removal, OpportunityLedgerEntryType.Signature, OpportunityLedgerEntrySubType.Removed, user?.id);
        res(true);
      } catch (e) {
        console.error(`OPPORTUNITY SIGNATURE REMOVED: ${JSON.stringify(e)}`);
        rej();
      }
    }));
  }
  // #endregion
  // #region Estimates
  submitEstimate(opportunity, documents, metaDocuments, operationsNote, preferredShopId, preferredInstallDate) {
    const price = opportunity.customPrice ?? opportunity.price;
    let priceToSend = deepClone(price);
    if (priceToSend != null) {
      priceToSend.details.forEach((detail) => {
        detail.lineItems.forEach((lineItem) => {
          lineItem.entity = void 0;
        });
      });
    }
    const submittedDate = (/* @__PURE__ */ new Date()).toLocaleDateString("en-CA");
    if (Capacitor.isNativePlatform()) {
      const url = `${environment.API_URL}/opportunities/estimates/submit`;
      return new Observable((o) => {
        const base64 = this.contentService.convertBytesToBase64(documents);
        Filesystem.writeFile({
          path: "Estimate Documents.pdf",
          data: base64,
          directory: Directory.Cache
        }).then((storeResult) => {
          o.next({ progress: 100 });
          return Http.uploadFile({
            url,
            name: "documents",
            filePath: storeResult.uri,
            data: {
              opportunityId: opportunity.id,
              operationsNote,
              preferredInstallDate,
              preferredShopId,
              metaDocuments: JSON.stringify(metaDocuments),
              price: JSON.stringify(priceToSend),
              paymentSetting: JSON.stringify(opportunity.paymentSetting),
              lenders: JSON.stringify(opportunity.lenders ?? []),
              payments: JSON.stringify(opportunity.payments),
              submittedDate
            },
            headers: {
              Authorization: `Bearer ${this.authRefService.accessToken ?? ""}`
            }
          }).then((result) => {
            o.next(result.data);
            o.complete();
          }).catch((uploadErr) => {
            this.logService.log(JSON.stringify(uploadErr), `CAPACITOR: Failed to send estimate for opportunity ${opportunity.id}.`).subscribe();
            o.error(uploadErr);
            o.complete();
          }).finally(() => {
            Filesystem.deleteFile({ path: "Estimate Documents.pdf", directory: Directory.Cache }).then();
          });
        }).catch((e) => {
          this.logService.log(JSON.stringify(e), `CAPACITOR: Failed to prepare estimate for opportunity ${opportunity.id}.`).subscribe();
          o.error("Failed to prepare estimate for sending.");
          o.complete();
        });
      });
    } else {
      const url = `{{api}}/opportunities/estimates/submit`;
      const formData = new FormData();
      formData.append("opportunityId", opportunity.id);
      const pdfBlob = this.contentService.convertBytesToBlob(documents, "application/pdf");
      formData.append("documents", pdfBlob);
      if (operationsNote !== void 0) {
        formData.append("operationsNote", operationsNote);
      }
      if (preferredInstallDate !== void 0) {
        formData.append("preferredInstallDate", preferredInstallDate);
      }
      if (preferredShopId !== void 0) {
        formData.append("preferredShopId", preferredShopId);
      }
      formData.append("price", JSON.stringify(priceToSend));
      formData.append("paymentSetting", JSON.stringify(opportunity.paymentSetting));
      formData.append("lenders", JSON.stringify(opportunity.lenders ?? []));
      formData.append("payments", JSON.stringify(opportunity.payments));
      formData.append("metaDocuments", JSON.stringify(metaDocuments));
      formData.append("submittedDate", submittedDate);
      return this.http.post(url, formData, {
        reportProgress: true,
        observe: "events"
      }).pipe(filter((e) => e.type === HttpEventType.UploadProgress || e.type === HttpEventType.Response), map((event) => {
        if (event.type === HttpEventType.UploadProgress) {
          const percent = event.total ? Math.round(100 * event.loaded / event.total) : 0;
          return { progress: percent };
        }
        return event.body;
      }));
    }
  }
  // #endregion
  // #region Data Export
  remoteExportOpportunity(jobId, compressed) {
    const fileName = `${jobId}-${(/* @__PURE__ */ new Date()).toISOString()}`;
    if (Capacitor.isNativePlatform()) {
      const url = `${environment.API_URL}/opportunities/export`;
      return new Observable((o) => {
        const base64 = this.contentService.convertBytesToBase64(compressed);
        const filePath = `${fileName}.zip`;
        Filesystem.writeFile({
          path: filePath,
          data: base64,
          directory: Directory.Cache
        }).then((storeResult) => __async(this, null, function* () {
          o.next({ progress: 100 });
          return Http.uploadFile({
            url,
            name: "appointment",
            filePath: storeResult.uri,
            data: {
              fileName: filePath
            },
            headers: {
              Authorization: `Bearer ${this.authRefService.accessToken ?? ""}`
            }
          }).then((result) => {
            o.next(result.data);
            o.complete();
          }).catch((uploadErr) => {
            o.error(uploadErr);
            o.complete();
          }).finally(() => __async(this, null, function* () {
            yield Filesystem.deleteFile({ path: filePath, directory: Directory.Cache });
          }));
        })).catch(() => {
          o.error("Failed to remote export appointment.");
          o.complete();
        });
      });
    } else {
      const url = `{{api}}/opportunities/export`;
      const blob = new Blob([compressed], { type: "application/zip" });
      const formData = new FormData();
      formData.append("opportunity", blob);
      formData.append("fileName", `${fileName}.zip`);
      return this.http.post(url, formData, {
        reportProgress: true,
        observe: "events"
      }).pipe(filter((e) => e.type === HttpEventType.UploadProgress || e.type === HttpEventType.Response), map((event) => {
        if (event.type === HttpEventType.UploadProgress) {
          const percent = event.total ? Math.round(100 * event.loaded / event.total) : 0;
          return { progress: percent };
        }
        return event.body;
      }));
    }
  }
  exportOppotunity(opportunity) {
    return new Promise((res, rej) => __async(this, null, function* () {
      const entries = yield this.storageService.getOpportunityLedgerEntriesByOpportunityId(opportunity.id);
      const fileName = `${opportunity.jobId}-${(/* @__PURE__ */ new Date()).toISOString()}`;
      const compressedAppointment = yield this.contentService.compressData(new ExportedOpportunity(opportunity, entries), `${fileName}.json`);
      if (Capacitor.isNativePlatform()) {
        const base64 = this.contentService.convertBytesToBase64(compressedAppointment);
        const filePath = `${fileName}.zip`;
        yield Filesystem.writeFile({
          path: filePath,
          data: base64,
          directory: Directory.Cache
        }).then((writeResult) => __async(this, null, function* () {
          yield Share.share({
            title: "Export Appointment to Device",
            url: writeResult.uri,
            dialogTitle: "Save Appointment to Files"
          });
          res();
        })).catch(() => {
          rej();
        }).finally(() => __async(this, null, function* () {
          yield Filesystem.deleteFile({ path: filePath, directory: Directory.Cache });
        }));
      } else {
        try {
          const appointmentBlob = new Blob([compressedAppointment], { type: "application/zip" });
          const url = URL.createObjectURL(appointmentBlob);
          const link = document.createElement("a");
          link.href = url;
          link.download = `${fileName}.zip`;
          document.body.appendChild(link);
          link.click();
          document.body.removeChild(link);
          URL.revokeObjectURL(url);
          res();
        } catch {
          rej();
        }
      }
    }));
  }
  // #endregion
  ngOnDestroy() {
  }
};
_OpportunityService.\u0275fac = function OpportunityService_Factory(__ngFactoryType__) {
  return new (__ngFactoryType__ || _OpportunityService)(\u0275\u0275inject(HttpClient), \u0275\u0275inject(ContentService), \u0275\u0275inject(AuthRefService), \u0275\u0275inject(StorageService), \u0275\u0275inject(LogService), \u0275\u0275inject(OrderByPipe));
};
_OpportunityService.\u0275prov = /* @__PURE__ */ \u0275\u0275defineInjectable({ token: _OpportunityService, factory: _OpportunityService.\u0275fac });
var OpportunityService = _OpportunityService;
(() => {
  (typeof ngDevMode === "undefined" || ngDevMode) && setClassMetadata(OpportunityService, [{
    type: Injectable
  }], () => [{ type: HttpClient }, { type: ContentService }, { type: AuthRefService }, { type: StorageService }, { type: LogService }, { type: OrderByPipe }], null);
})();

export {
  OpportunityLedgerEntryType,
  OpportunityLedgerEntrySubType,
  LogService,
  OpportunityService
};
//# sourceMappingURL=chunk-55OJRHBT.js.map
