import { DataTypes } from 'sequelize';
import { INVOICE_INSURER_PAYMENT_STATUSES, INVOICE_PATIENT_PAYMENT_STATUSES, INVOICE_STATUSES, SYNC_DIRECTIONS, SYSTEM_USER_UUID, AUTOMATIC_INVOICE_CREATION_EXCLUDED_ENCOUNTER_TYPES } from '@tamanu/constants';
import { Model } from '../Model';
import { buildEncounterLinkedSyncFilter } from '../../sync/buildEncounterLinkedSyncFilter';
import { buildEncounterLinkedLookupFilter } from '../../sync/buildEncounterLinkedLookupFilter';
import { dateTimeType } from '../../types/model';
import { generateInvoiceDisplayId } from '@tamanu/utils/generateInvoiceDisplayId';
export class Invoice extends Model {
    static initModel({ primaryKey, ...options }) {
        super.init({
            id: primaryKey,
            displayId: {
                type: DataTypes.STRING,
                allowNull: false
            },
            date: dateTimeType('date', {
                allowNull: false
            }),
            status: {
                type: DataTypes.STRING,
                allowNull: false
            },
            patientPaymentStatus: {
                type: DataTypes.STRING,
                allowNull: false,
                defaultValue: INVOICE_PATIENT_PAYMENT_STATUSES.UNPAID
            },
            insurerPaymentStatus: {
                type: DataTypes.STRING,
                allowNull: false,
                defaultValue: INVOICE_INSURER_PAYMENT_STATUSES.UNPAID
            }
        }, {
            ...options,
            syncDirection: SYNC_DIRECTIONS.BIDIRECTIONAL
        });
    }
    static initRelations(models) {
        this.belongsTo(models.Encounter, {
            foreignKey: 'encounterId',
            as: 'encounter'
        });
        this.hasOne(models.InvoiceDiscount, {
            foreignKey: 'invoiceId',
            as: 'discount'
        });
        this.hasMany(models.InvoicesInvoiceInsurancePlan, {
            foreignKey: 'invoiceId',
            as: 'invoiceInsurancePlans'
        });
        this.belongsToMany(models.InvoiceInsurancePlan, {
            through: models.InvoicesInvoiceInsurancePlan,
            foreignKey: 'invoiceId',
            otherKey: 'invoiceInsurancePlanId',
            as: 'insurancePlans'
        });
        this.hasMany(models.InvoiceItem, {
            foreignKey: 'invoiceId',
            as: 'items'
        });
        this.hasMany(models.InvoicePayment, {
            foreignKey: 'invoiceId',
            as: 'payments'
        });
    }
    static buildPatientSyncFilter(patientCount, markedForSyncPatientsTable) {
        if (patientCount === 0) {
            return null;
        }
        return buildEncounterLinkedSyncFilter([
            this.tableName,
            'encounters'
        ], markedForSyncPatientsTable);
    }
    static async buildSyncLookupQueryDetails() {
        return buildEncounterLinkedLookupFilter(this);
    }
    static getFullReferenceAssociations(invoicePriceListId, includeRefundingPayments) {
        const { models } = this.sequelize;
        return [
            'encounter',
            {
                model: models.InvoiceDiscount,
                as: 'discount',
                include: [
                    {
                        model: models.User,
                        as: 'appliedByUser',
                        attributes: [
                            'displayName'
                        ]
                    }
                ]
            },
            {
                model: models.InvoiceItem,
                as: 'items',
                include: models.InvoiceItem.getListReferenceAssociations(models, invoicePriceListId, 'items')
            },
            {
                model: models.InvoicePayment,
                as: 'payments',
                required: false,
                include: models.InvoicePayment.getListReferenceAssociations(models),
                ...!includeRefundingPayments ? {
                    where: {
                        original_payment_id: null
                    }
                } : {}
            },
            {
                model: models.InvoiceInsurancePlan,
                as: 'insurancePlans'
            }
        ];
    }
    static async getInProgressInvoiceForEncounter(encounterId) {
        const invoices = await this.findAll({
            where: {
                encounterId,
                status: INVOICE_STATUSES.IN_PROGRESS
            }
        });
        if (invoices.length === 0) {
            return null; // No in progress invoice for encounter
        }
        if (invoices.length > 1) {
            throw new Error(`Multiple in progress invoices found for encounter: ${encounterId}`);
        }
        return invoices[0];
    }
    static normaliseInvoiceItemQuantity(quantity, fallback) {
        const n = Number(quantity);
        if (!Number.isFinite(n)) return fallback;
        return Math.max(0, Math.floor(n));
    }
    static async addItemToInvoice(newItem, encounterId, invoiceProduct, orderedByUserId = SYSTEM_USER_UUID, options) {
        const invoice = await this.getInProgressInvoiceForEncounter(encounterId);
        if (!invoice) {
            return;
        }
        const invoicePriceListId = await this.sequelize.models.InvoicePriceList.getIdForPatientEncounter(encounterId);
        if (invoicePriceListId) {
            // Confirm invoice product is not configured to be hidden for this price list
            const hiddenInvoicePriceListItem = await this.sequelize.models.InvoicePriceListItem.findOne({
                where: {
                    invoicePriceListId,
                    invoiceProductId: invoiceProduct.id,
                    isHidden: true
                }
            });
            if (hiddenInvoicePriceListItem) {
                return;
            }
        }
        const quantity = this.normaliseInvoiceItemQuantity(options?.quantity, 1);
        await this.sequelize.models.InvoiceItem.upsert({
            invoiceId: invoice.id,
            sourceRecordType: newItem.getModelName(),
            sourceRecordId: newItem.id,
            productId: invoiceProduct.id,
            orderedByUserId,
            orderDate: new Date(),
            quantity: quantity,
            note: options?.note,
            deletedAt: null
        }, {
            conflictFields: [
                'invoice_id',
                'source_record_type',
                'source_record_id'
            ]
        });
    }
    static async removeItemFromInvoice(removedItemSource, encounterId) {
        const invoice = await this.getInProgressInvoiceForEncounter(encounterId);
        if (!invoice) {
            return;
        }
        await this.sequelize.models.InvoiceItem.destroy({
            where: {
                invoiceId: invoice.id,
                sourceRecordType: removedItemSource.getModelName(),
                sourceRecordId: removedItemSource.id
            }
        });
    }
    static async automaticallyCreateForEncounter(encounterId, encounterType, date, settings, options) {
        const isInvoicingEnabled = await settings?.get('features.invoicing.enabled');
        const isValidEncounterType = !AUTOMATIC_INVOICE_CREATION_EXCLUDED_ENCOUNTER_TYPES.includes(encounterType);
        if (!isInvoicingEnabled || !isValidEncounterType) {
            return null;
        }
        return await this.create({
            displayId: generateInvoiceDisplayId(),
            status: INVOICE_STATUSES.IN_PROGRESS,
            date,
            encounterId
        }, options);
    }
}

//# sourceMappingURL=Invoice.js.map