diff --git a/package.json b/package.json index ed46610d4..cc34f731a 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "iSunFA", - "version": "0.8.2+47", + "version": "0.8.2+48", "private": false, "scripts": { "dev": "next dev", diff --git a/prisma/migrations/2_beta_migrate/migration.sql b/prisma/migrations/2_beta_migrate/migration.sql new file mode 100644 index 000000000..8b1dcbee4 --- /dev/null +++ b/prisma/migrations/2_beta_migrate/migration.sql @@ -0,0 +1,499 @@ +/* + Warnings: + + - You are about to drop the column `contract_id` on the `asset` table. All the data in the column will be lost. + - You are about to drop the column `description` on the `asset` table. All the data in the column will be lost. + - You are about to drop the column `end_date` on the `asset` table. All the data in the column will be lost. + - You are about to drop the column `estimate_useful_life` on the `asset` table. All the data in the column will be lost. + - You are about to drop the column `label` on the `asset` table. All the data in the column will be lost. + - You are about to drop the column `name` on the `asset` table. All the data in the column will be lost. + - You are about to drop the column `price` on the `asset` table. All the data in the column will be lost. + - You are about to drop the column `project_id` on the `asset` table. All the data in the column will be lost. + - You are about to drop the column `start_date` on the `asset` table. All the data in the column will be lost. + - You are about to drop the column `supplier` on the `asset` table. All the data in the column will be lost. + - You are about to drop the column `type` on the `asset` table. All the data in the column will be lost. + - You are about to drop the column `voucher_id` on the `asset` table. All the data in the column will be lost. + - You are about to drop the column `description` on the `invoice` table. All the data in the column will be lost. + - You are about to drop the column `event_type` on the `invoice` table. All the data in the column will be lost. + - You are about to drop the column `image_file_id` on the `invoice` table. All the data in the column will be lost. + - You are about to drop the column `journal_id` on the `invoice` table. All the data in the column will be lost. + - You are about to drop the column `number` on the `invoice` table. All the data in the column will be lost. + - You are about to drop the column `payment_id` on the `invoice` table. All the data in the column will be lost. + - You are about to drop the column `payment_reason` on the `invoice` table. All the data in the column will be lost. + - You are about to drop the column `vendor_or_supplier` on the `invoice` table. All the data in the column will be lost. + - You are about to drop the column `vendor_tax_id` on the `invoice` table. All the data in the column will be lost. + - You are about to drop the column `date` on the `payment_record` table. All the data in the column will be lost. + - You are about to drop the column `description` on the `payment_record` table. All the data in the column will be lost. + - You are about to drop the column `annual_fee` on the `plan` table. All the data in the column will be lost. + - You are about to drop the column `monthly_fee` on the `plan` table. All the data in the column will be lost. + - You are about to drop the column `journal_id` on the `voucher` table. All the data in the column will be lost. + - You are about to drop the `customer_vendor` table. If the table is not empty, all the data it contains will be lost. + - Added the required column `accumulated_depreciation` to the `asset` table without a default value. This is not possible if the table is not empty. + - Added the required column `acquisition_date` to the `asset` table without a default value. This is not possible if the table is not empty. + - Added the required column `asset_name` to the `asset` table without a default value. This is not possible if the table is not empty. + - Added the required column `asset_number` to the `asset` table without a default value. This is not possible if the table is not empty. + - Added the required column `asset_status` to the `asset` table without a default value. This is not possible if the table is not empty. + - Added the required column `asset_type` to the `asset` table without a default value. This is not possible if the table is not empty. + - Added the required column `company_id` to the `asset` table without a default value. This is not possible if the table is not empty. + - Added the required column `currency_alias` to the `asset` table without a default value. This is not possible if the table is not empty. + - Added the required column `depreciation_start` to the `asset` table without a default value. This is not possible if the table is not empty. + - Added the required column `note` to the `asset` table without a default value. This is not possible if the table is not empty. + - Added the required column `purchase_price` to the `asset` table without a default value. This is not possible if the table is not empty. + - Added the required column `remaining_life` to the `asset` table without a default value. This is not possible if the table is not empty. + - Added the required column `useful_life` to the `asset` table without a default value. This is not possible if the table is not empty. + - Changed the type of `residual_value` on the `asset` table. No cast exists, the column would be dropped and recreated, which cannot be done if there is data, since the column is required. + - Added the required column `certificate_id` to the `invoice` table without a default value. This is not possible if the table is not empty. + - Added the required column `no` to the `invoice` table without a default value. This is not possible if the table is not empty. + - Added the required column `price_before_tax` to the `invoice` table without a default value. This is not possible if the table is not empty. + - Added the required column `tax_price` to the `invoice` table without a default value. This is not possible if the table is not empty. + - Added the required column `tax_ratio` to the `invoice` table without a default value. This is not possible if the table is not empty. + - Added the required column `tax_type` to the `invoice` table without a default value. This is not possible if the table is not empty. + - Added the required column `total_price` to the `invoice` table without a default value. This is not possible if the table is not empty. + - Added the required column `action` to the `payment_record` table without a default value. This is not possible if the table is not empty. + - Added the required column `fee` to the `payment_record` table without a default value. This is not possible if the table is not empty. + - Added the required column `payment_created_at` to the `payment_record` table without a default value. This is not possible if the table is not empty. + - Added the required column `refund_amount` to the `payment_record` table without a default value. This is not possible if the table is not empty. + - Added the required column `billing_cycle` to the `plan` table without a default value. This is not possible if the table is not empty. + - Added the required column `price` to the `plan` table without a default value. This is not possible if the table is not empty. + - Added the required column `auto_renewal` to the `subscription` table without a default value. This is not possible if the table is not empty. + - Added the required column `company_id` to the `voucher` table without a default value. This is not possible if the table is not empty. + - Added the required column `date` to the `voucher` table without a default value. This is not possible if the table is not empty. + +*/ +-- DropForeignKey +ALTER TABLE "asset" DROP CONSTRAINT "asset_contract_id_fkey"; + +-- DropForeignKey +ALTER TABLE "asset" DROP CONSTRAINT "asset_project_id_fkey"; + +-- DropForeignKey +ALTER TABLE "asset" DROP CONSTRAINT "asset_voucher_id_fkey"; + +-- DropForeignKey +ALTER TABLE "customer_vendor" DROP CONSTRAINT "customer_vendor_company_id_fkey"; + +-- DropForeignKey +ALTER TABLE "invoice" DROP CONSTRAINT "invoice_image_file_id_fkey"; + +-- DropForeignKey +ALTER TABLE "invoice" DROP CONSTRAINT "invoice_journal_id_fkey"; + +-- DropForeignKey +ALTER TABLE "invoice" DROP CONSTRAINT "invoice_payment_id_fkey"; + +-- DropForeignKey +ALTER TABLE "voucher" DROP CONSTRAINT "voucher_journal_id_fkey"; + +-- DropIndex +DROP INDEX "invoice_image_file_id_key"; + +-- DropIndex +DROP INDEX "invoice_journal_id_key"; + +-- DropIndex +DROP INDEX "invoice_number_key"; + +-- DropIndex +DROP INDEX "invoice_payment_id_key"; + +-- DropIndex +DROP INDEX "voucher_journal_id_key"; + +-- AlterTable +ALTER TABLE "asset" DROP COLUMN "contract_id", +DROP COLUMN "description", +DROP COLUMN "end_date", +DROP COLUMN "estimate_useful_life", +DROP COLUMN "label", +DROP COLUMN "price", +DROP COLUMN "project_id", +DROP COLUMN "start_date", +DROP COLUMN "supplier", +DROP COLUMN "voucher_id", +DROP COLUMN "residual_value", +ADD COLUMN "residual_value" DOUBLE PRECISION NOT NULL, +ADD COLUMN "company_id" INTEGER NOT NULL, +ADD COLUMN "number" TEXT NOT NULL, +ADD COLUMN "acquisition_date" INTEGER NOT NULL, +ADD COLUMN "purchase_price" DOUBLE PRECISION NOT NULL, +ADD COLUMN "accumulated_depreciation" DOUBLE PRECISION NOT NULL, +ADD COLUMN "remaining_life" INTEGER NOT NULL, +ADD COLUMN "status" TEXT NOT NULL, +ADD COLUMN "depreciation_start" INTEGER NOT NULL, +ADD COLUMN "useful_life" INTEGER NOT NULL, +ADD COLUMN "note" TEXT NOT NULL; + +-- AlterTable +ALTER TABLE "payment_record" DROP COLUMN "date", +DROP COLUMN "description", +ADD COLUMN "action" TEXT NOT NULL DEFAULT 'payment', +ADD COLUMN "auth_code" TEXT NOT NULL DEFAULT '0', +ADD COLUMN "card_issuer_country" TEXT NOT NULL DEFAULT 'Taiwan', +ADD COLUMN "fee" DOUBLE PRECISION NOT NULL DEFAULT 0, +ADD COLUMN "payment_created_at" TEXT NOT NULL DEFAULT '0', +ADD COLUMN "refund_amount" DOUBLE PRECISION NOT NULL DEFAULT 0, +ALTER COLUMN "amount" SET DATA TYPE DOUBLE PRECISION; + +ALTER TABLE "payment_record" +ALTER COLUMN "action" DROP DEFAULT, +ALTER COLUMN "auth_code" DROP DEFAULT, +ALTER COLUMN "card_issuer_country" DROP DEFAULT, +ALTER COLUMN "fee" DROP DEFAULT, +ALTER COLUMN "payment_created_at" DROP DEFAULT, +ALTER COLUMN "refund_amount" DROP DEFAULT; + +-- AlterTable +ALTER TABLE "plan" DROP COLUMN "annual_fee", +DROP COLUMN "monthly_fee", +ADD COLUMN "billing_cycle" TEXT NOT NULL DEFAULT 'monthly', +ADD COLUMN "price" DOUBLE PRECISION NOT NULL DEFAULT 10; + +ALTER TABLE "plan" +ALTER COLUMN "billing_cycle" DROP DEFAULT, +ALTER COLUMN "price" DROP DEFAULT; + +-- AlterTable +ALTER TABLE "subscription" ADD COLUMN "auto_renewal" BOOLEAN NOT NULL DEFAULT true; + +ALTER TABLE "subscription" +ALTER COLUMN "auto_renewal" DROP DEFAULT; + +-- DropTable +DROP TABLE "customer_vendor"; + +-- CreateTable +CREATE TABLE "asset_voucher" ( + "id" SERIAL NOT NULL, + "asset_id" INTEGER NOT NULL, + "voucher_id" INTEGER NOT NULL, + "created_at" INTEGER NOT NULL, + "updated_at" INTEGER NOT NULL, + "deleted_at" INTEGER, + + CONSTRAINT "asset_voucher_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "associate_voucher" ( + "id" SERIAL NOT NULL, + "event_id" INTEGER NOT NULL, + "original_voucher_id" INTEGER NOT NULL, + "result_voucher_id" INTEGER NOT NULL, + "created_at" INTEGER NOT NULL, + "updated_at" INTEGER NOT NULL, + "deleted_at" INTEGER, + + CONSTRAINT "associate_voucher_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "counterparty" ( + "id" SERIAL NOT NULL, + "company_id" INTEGER NOT NULL, + "name" TEXT NOT NULL, + "tax_id" TEXT NOT NULL, + "type" TEXT NOT NULL, + "note" TEXT NOT NULL, + "created_at" INTEGER NOT NULL, + "updated_at" INTEGER NOT NULL, + "deleted_at" INTEGER, + + CONSTRAINT "counterparty_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "certificate" ( + "id" SERIAL NOT NULL, + "company_id" INTEGER NOT NULL, + "file_id" INTEGER NOT NULL, + "created_at" INTEGER NOT NULL, + "updated_at" INTEGER NOT NULL, + "deleted_at" INTEGER, + + CONSTRAINT "certificate_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "event" ( + "id" SERIAL NOT NULL, + "event_type" TEXT NOT NULL, + "frequence" TEXT NOT NULL, + "start_date" INTEGER NOT NULL, + "end_date" INTEGER NOT NULL, + "daysOfWeek" INTEGER[], + "monthsOfYear" TEXT[], + "created_at" INTEGER NOT NULL, + "updated_at" INTEGER NOT NULL, + "deleted_at" INTEGER, + + CONSTRAINT "event_pkey" PRIMARY KEY ("id") +); + +ALTER TABLE "file" +RENAME COLUMN "encryptedSymmetricKey" to "encrypted_symmetric_key"; + +-- CreateTable +CREATE TABLE "invoice_voucher_journal" ( + "id" SERIAL NOT NULL, + "invoice_id" INTEGER, + "voucher_id" INTEGER, + "journal_id" INTEGER NOT NULL, + "description" TEXT NOT NULL, + "payment_id" INTEGER, + "payment_reason" TEXT NOT NULL, + "vendor_or_supplier" TEXT NOT NULL, + "created_at" INTEGER NOT NULL, + "updated_at" INTEGER NOT NULL, + "deleted_at" INTEGER, + + CONSTRAINT "invoice_voucher_journal_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "user_payment_info" ( + "id" SERIAL NOT NULL, + "user_id" INTEGER NOT NULL, + "token" TEXT NOT NULL, + "transaction_id" TEXT NOT NULL, + "created_at" INTEGER NOT NULL, + "updated_at" INTEGER NOT NULL, + "deleted_at" INTEGER, + + CONSTRAINT "user_payment_info_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "user_certificate" ( + "id" SERIAL NOT NULL, + "user_id" INTEGER NOT NULL, + "certificate_id" INTEGER NOT NULL, + "is_read" BOOLEAN NOT NULL, + "created_at" INTEGER NOT NULL, + "updated_at" INTEGER NOT NULL, + "deleted_at" INTEGER, + + CONSTRAINT "user_certificate_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "user_voucher" ( + "id" SERIAL NOT NULL, + "user_id" INTEGER NOT NULL, + "voucher_id" INTEGER NOT NULL, + "is_read" BOOLEAN NOT NULL, + "created_at" INTEGER NOT NULL, + "updated_at" INTEGER NOT NULL, + "deleted_at" INTEGER, + + CONSTRAINT "user_voucher_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "voucher_certificate" ( + "id" SERIAL NOT NULL, + "voucher_id" INTEGER NOT NULL, + "certificate_id" INTEGER NOT NULL, + "created_at" INTEGER NOT NULL, + "updated_at" INTEGER NOT NULL, + "deleted_at" INTEGER, + + CONSTRAINT "voucher_certificate_pkey" PRIMARY KEY ("id") +); + +-- Insert a default file for cases where file_id is NULL +INSERT INTO file (id, name, size, mime_type, type, url, is_encrypted, encrypted_symmetric_key, iv, created_at, updated_at) +SELECT 555 AS id, 'N/A' AS name, 0 AS size, 'N/A' AS mime_type, 'N/A' AS type, 'N/A' AS url, FALSE AS is_encrypted, 'N/A' AS encrypted_symmetric_key, '' AS iv, 0 AS created_at, 0 AS updated_at +WHERE NOT EXISTS (SELECT 1 FROM file WHERE id = 555); + +-- Insert a default company for cases where company_id is NULL +INSERT INTO company (id, tax_id, image_file_id, start_date, tag, name, created_at, updated_at) +SELECT 555 AS id, 555 AS tax_id, 555 AS image_file_id, 0 AS start_date, 'ALL' AS tag, 'N/A' AS name, 0 AS created_at, 0 AS updated_at +WHERE NOT EXISTS (SELECT 1 FROM company WHERE id = 555); + +-- Insert a default certificate for cases where image_file_id is NULL +INSERT INTO certificate (id, file_id, company_id, created_at, updated_at) +SELECT 555 AS id, 555 AS file_id, 555 AS company_id, 0 AS created_at, 0 AS updated_at +WHERE NOT EXISTS (SELECT 1 FROM certificate WHERE id = 555); + +-- Insert a default counter_party for cases where counter_party_id is NULL +INSERT INTO counterparty (id, company_id, name, tax_id, type, note, created_at, updated_at) +SELECT 555 AS id, 555 AS company_id, 'N/A' AS name, 'N/A' AS tax_id, 'N/A' AS type, 'N/A' AS note, 0 AS created_at, 0 AS updated_at +WHERE NOT EXISTS (SELECT 1 FROM counterparty WHERE id = 555); + +ALTER TABLE "invoice" +ADD COLUMN "certificate_id" INTEGER NOT NULL DEFAULT 555, +ADD COLUMN "counter_party_id" INTEGER NOT NULL DEFAULT 555, +ADD COLUMN "currency_alias" TEXT NOT NULL DEFAULT 'TWD', +ADD COLUMN "input_or_output" TEXT NOT NULL DEFAULT 'output', +ADD COLUMN "no" TEXT NOT NULL DEFAULT 'N/A', +ADD COLUMN "price_before_tax" INTEGER NOT NULL DEFAULT 0, +ADD COLUMN "tax_price" INTEGER NOT NULL DEFAULT 0, +ADD COLUMN "tax_ratio" INTEGER NOT NULL DEFAULT 0, +ADD COLUMN "tax_type" TEXT NOT NULL DEFAULT 'taxable', +ADD COLUMN "total_price" INTEGER NOT NULL DEFAULT 0; + + +-- 更新 invoice 表中的 tax_type 基於 payment.has_tax 轉換 +UPDATE invoice +SET tax_type = CASE + WHEN p.has_tax = TRUE THEN 'taxable' + ELSE 'tax free' +END, +tax_ratio = p.tax_percentage, +tax_price = p.tax_price, +price_before_tax = p.price - p.tax_price, +total_price = p.price + p.tax_price +FROM payment p +WHERE invoice.payment_id = p.id; + +INSERT INTO certificate (file_id, company_id, created_at, updated_at) +SELECT i.image_file_id, j.company_id, i.created_at, i.updated_at +FROM invoice i +JOIN journal j ON i.journal_id = j.id +WHERE i.image_file_id IS NOT NULL; + +-- Update invoice with correct certificate_id +UPDATE invoice +SET certificate_id = cert.id +FROM certificate cert +WHERE invoice.image_file_id = cert.file_id +AND cert.file_id IS NOT NULL; + +-- Update invoices without image_file_id to use the default certificate +UPDATE invoice +SET certificate_id = cert.id +FROM certificate cert +WHERE cert.file_id = 555 +AND invoice.image_file_id IS NULL; + +-- AlterTable +ALTER TABLE "voucher" +ADD COLUMN "company_id" INTEGER NOT NULL DEFAULT 555, +ADD COLUMN "counter_party_id" INTEGER NOT NULL DEFAULT 555, +ADD COLUMN "date" INTEGER NOT NULL DEFAULT 0, +ADD COLUMN "editable" BOOLEAN NOT NULL DEFAULT true, +ADD COLUMN "issuer_id" INTEGER NOT NULL DEFAULT 1000, +ADD COLUMN "note" TEXT, +ADD COLUMN "status" TEXT NOT NULL DEFAULT 'journal:JOURNAL.UPLOADED', +ADD COLUMN "type" TEXT NOT NULL DEFAULT 'payment'; + +INSERT INTO invoice_voucher_journal (invoice_id, voucher_id, journal_id, description, payment_id, payment_reason, vendor_or_supplier, created_at, updated_at) +SELECT i.id, v.id, j.id, i.description, i.payment_id, i.payment_reason, i.vendor_or_supplier, i.created_at, i.updated_at +FROM invoice i +JOIN journal j ON i.journal_id = j.id +JOIN voucher v ON j.id = v.journal_id; + +ALTER TABLE "voucher" DROP COLUMN "journal_id"; + +ALTER TABLE "voucher" +ALTER COLUMN "company_id" DROP DEFAULT, +ALTER COLUMN "counter_party_id" DROP DEFAULT, +ALTER COLUMN "issuer_id" DROP DEFAULT, +ALTER COLUMN "date" DROP DEFAULT; + +ALTER TABLE "invoice" DROP COLUMN "description", +DROP COLUMN "event_type", +DROP COLUMN "image_file_id", +DROP COLUMN "journal_id", +DROP COLUMN "number", +DROP COLUMN "payment_id", +DROP COLUMN "payment_reason", +DROP COLUMN "vendor_or_supplier", +DROP COLUMN "vendor_tax_id"; + +ALTER TABLE "invoice" +ALTER COLUMN "certificate_id" DROP DEFAULT, +ALTER COLUMN "counter_party_id" DROP DEFAULT, +ALTER COLUMN "currency_alias" DROP DEFAULT, +ALTER COLUMN "input_or_output" DROP DEFAULT, +ALTER COLUMN "no" DROP DEFAULT, +ALTER COLUMN "price_before_tax" DROP DEFAULT, +ALTER COLUMN "tax_price" DROP DEFAULT, +ALTER COLUMN "tax_ratio" DROP DEFAULT, +ALTER COLUMN "tax_type" DROP DEFAULT, +ALTER COLUMN "total_price" DROP DEFAULT; + +-- AddUniqueConstraint +ALTER TABLE "certificate" ADD CONSTRAINT "certificate_file_id_key" UNIQUE ("file_id"); + +-- AddForeignKey +ALTER TABLE "asset" ADD CONSTRAINT "asset_company_id_fkey" FOREIGN KEY ("company_id") REFERENCES "company"("id") ON DELETE RESTRICT ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "asset_voucher" ADD CONSTRAINT "asset_voucher_asset_id_fkey" FOREIGN KEY ("asset_id") REFERENCES "asset"("id") ON DELETE RESTRICT ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "asset_voucher" ADD CONSTRAINT "asset_voucher_voucher_id_fkey" FOREIGN KEY ("voucher_id") REFERENCES "voucher"("id") ON DELETE RESTRICT ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "associate_voucher" ADD CONSTRAINT "associate_voucher_event_id_fkey" FOREIGN KEY ("event_id") REFERENCES "event"("id") ON DELETE RESTRICT ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "associate_voucher" ADD CONSTRAINT "associate_voucher_original_voucher_id_fkey" FOREIGN KEY ("original_voucher_id") REFERENCES "voucher"("id") ON DELETE RESTRICT ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "associate_voucher" ADD CONSTRAINT "associate_voucher_result_voucher_id_fkey" FOREIGN KEY ("result_voucher_id") REFERENCES "voucher"("id") ON DELETE RESTRICT ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "counterparty" ADD CONSTRAINT "counterparty_company_id_fkey" FOREIGN KEY ("company_id") REFERENCES "company"("id") ON DELETE RESTRICT ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "certificate" ADD CONSTRAINT "certificate_company_id_fkey" FOREIGN KEY ("company_id") REFERENCES "company"("id") ON DELETE RESTRICT ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "certificate" ADD CONSTRAINT "certificate_file_id_fkey" FOREIGN KEY ("file_id") REFERENCES "file"("id") ON DELETE RESTRICT ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "invoice" ADD CONSTRAINT "invoice_certificate_id_fkey" FOREIGN KEY ("certificate_id") REFERENCES "certificate"("id") ON DELETE RESTRICT ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "invoice" ADD CONSTRAINT "invoice_counter_party_id_fkey" FOREIGN KEY ("counter_party_id") REFERENCES "counterparty"("id") ON DELETE RESTRICT ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "invoice_voucher_journal" ADD CONSTRAINT "invoice_voucher_journal_invoice_id_fkey" FOREIGN KEY ("invoice_id") REFERENCES "invoice"("id") ON DELETE SET NULL ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "invoice_voucher_journal" ADD CONSTRAINT "invoice_voucher_journal_voucher_id_fkey" FOREIGN KEY ("voucher_id") REFERENCES "voucher"("id") ON DELETE SET NULL ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "invoice_voucher_journal" ADD CONSTRAINT "invoice_voucher_journal_journal_id_fkey" FOREIGN KEY ("journal_id") REFERENCES "journal"("id") ON DELETE RESTRICT ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "user_payment_info" ADD CONSTRAINT "user_payment_info_user_id_fkey" FOREIGN KEY ("user_id") REFERENCES "user"("id") ON DELETE RESTRICT ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "user_certificate" ADD CONSTRAINT "user_certificate_user_id_fkey" FOREIGN KEY ("user_id") REFERENCES "user"("id") ON DELETE RESTRICT ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "user_certificate" ADD CONSTRAINT "user_certificate_certificate_id_fkey" FOREIGN KEY ("certificate_id") REFERENCES "certificate"("id") ON DELETE RESTRICT ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "user_voucher" ADD CONSTRAINT "user_voucher_user_id_fkey" FOREIGN KEY ("user_id") REFERENCES "user"("id") ON DELETE RESTRICT ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "user_voucher" ADD CONSTRAINT "user_voucher_voucher_id_fkey" FOREIGN KEY ("voucher_id") REFERENCES "voucher"("id") ON DELETE RESTRICT ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "voucher" ADD CONSTRAINT "voucher_company_id_fkey" FOREIGN KEY ("company_id") REFERENCES "company"("id") ON DELETE RESTRICT ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "voucher" ADD CONSTRAINT "voucher_issuer_id_fkey" FOREIGN KEY ("issuer_id") REFERENCES "user"("id") ON DELETE RESTRICT ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "voucher" ADD CONSTRAINT "voucher_counter_party_id_fkey" FOREIGN KEY ("counter_party_id") REFERENCES "counterparty"("id") ON DELETE RESTRICT ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "voucher_certificate" ADD CONSTRAINT "voucher_certificate_voucher_id_fkey" FOREIGN KEY ("voucher_id") REFERENCES "voucher"("id") ON DELETE RESTRICT ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "voucher_certificate" ADD CONSTRAINT "voucher_certificate_certificate_id_fkey" FOREIGN KEY ("certificate_id") REFERENCES "certificate"("id") ON DELETE RESTRICT ON UPDATE CASCADE; + +ALTER SEQUENCE "asset_voucher_id_seq" RESTART WITH 10000000; +ALTER SEQUENCE "associate_voucher_id_seq" RESTART WITH 10000000; +ALTER SEQUENCE "counterparty_id_seq" RESTART WITH 10000000; +ALTER SEQUENCE "certificate_id_seq" RESTART WITH 10000000; +ALTER SEQUENCE "event_id_seq" RESTART WITH 10000000; +ALTER SEQUENCE "user_payment_info_id_seq" RESTART WITH 10000000; +ALTER SEQUENCE "user_certificate_id_seq" RESTART WITH 10000000; +ALTER SEQUENCE "user_voucher_id_seq" RESTART WITH 10000000; +ALTER SEQUENCE "voucher_certificate_id_seq" RESTART WITH 10000000; diff --git a/prisma/schema.prisma b/prisma/schema.prisma index 74659102f..3ffccc7b3 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -35,32 +35,46 @@ model Account { } model Asset { - id Int @id @default(autoincrement()) - voucherId Int @map("voucher_id") - projectId Int? @map("project_id") - contractId Int? @map("contract_id") - name String - label String - type String - description String - supplier String - startDate String @map("start_date") - endDate String @map("end_date") - price String - residualValue String @map("residual_value") - estimateUsefulLife String @map("estimate_useful_life") - depreciationMethod String @map("depreciation_method") - createdAt Int @map("created_at") - updatedAt Int @map("updated_at") - deletedAt Int? @map("deleted_at") + id Int @id @default(autoincrement()) + companyId Int @map("company_id") + name String + type String + number String + acquisitionDate Int @map("acquisition_date") + purchasePrice Float @map("purchase_price") + accumulatedDepreciation Float @map("accumulated_depreciation") + residualValue Float @map("residual_value") + remainingLife Int @map("remaining_life") + status String + depreciationStart Int @map("depreciation_start") + depreciationMethod String @map("depreciation_method") + usefulLife Int @map("useful_life") + note String + createdAt Int @map("created_at") + updatedAt Int @map("updated_at") + deletedAt Int? @map("deleted_at") + + assetVouchers AssetVoucher[] - contract Contract? @relation(fields: [contractId], references: [id]) - project Project? @relation(fields: [projectId], references: [id]) - voucher Voucher @relation(fields: [voucherId], references: [id]) + company Company @relation(fields: [companyId], references: [id]) @@map("asset") } +model AssetVoucher { + id Int @id @default(autoincrement()) + assetId Int @map("asset_id") + voucherId Int @map("voucher_id") + createdAt Int @map("created_at") + updatedAt Int @map("updated_at") + deletedAt Int? @map("deleted_at") + + asset Asset @relation(fields: [assetId], references: [id]) + voucher Voucher @relation(fields: [voucherId], references: [id]) + + @@map("asset_voucher") +} + model AuditReport { id Int @id @default(autoincrement()) companyId Int @map("company_id") @@ -112,24 +126,39 @@ model Authentication { } model AccountingSetting { - id Int @id @default(autoincrement()) - companyId Int @map("company_id") - salesTaxTaxable Boolean @map("sales_tax_taxable") - salesTaxRate Float @map("sales_tax_rate") - purchaseTaxTaxable Boolean @map("purchase_tax_taxable") - purchaseTaxRate Float @map("purchase_tax_rate") - returnPeriodicity String @map("return_periodicity") - currency String + id Int @id @default(autoincrement()) + companyId Int @map("company_id") + salesTaxTaxable Boolean @map("sales_tax_taxable") + salesTaxRate Float @map("sales_tax_rate") + purchaseTaxTaxable Boolean @map("purchase_tax_taxable") + purchaseTaxRate Float @map("purchase_tax_rate") + returnPeriodicity String @map("return_periodicity") + currency String - company Company @relation(fields: [companyId], references: [id]) + company Company @relation(fields: [companyId], references: [id]) - shortcuts Shortcut[] + shortcuts Shortcut[] @@map("accounting_setting") } +model AccociateVoucher { + id Int @id @default(autoincrement()) + eventId Int @map("event_id") + originalVoucherId Int @map("original_voucher_id") + resultVoucherId Int @map("result_voucher_id") + createdAt Int @map("created_at") + updatedAt Int @map("updated_at") + deletedAt Int? @map("deleted_at") + + event Event @relation(fields: [eventId], references: [id]) + originalVoucher Voucher @relation("original_voucher", fields: [originalVoucherId], references: [id]) + resultVoucher Voucher @relation("result_voucher", fields: [resultVoucherId], references: [id]) -model CustomerVendor { + @@map("associate_voucher") +} + +model Counterparty { id Int @id @default(autoincrement()) companyId Int @map("company_id") name String @@ -140,9 +169,12 @@ model CustomerVendor { updatedAt Int @map("updated_at") deletedAt Int? @map("deleted_at") + invoices Invoice[] + voucher Voucher[] + company Company @relation(fields: [companyId], references: [id]) - @@map("customer_vendor") + @@map("counterparty") } model Company { @@ -160,10 +192,12 @@ model Company { admins Admin[] accountingSettings AccountingSetting[] - customerVendors CustomerVendor[] + assets Asset[] + counterpartys Counterparty[] companySettings CompanySetting[] contracts Contract[] companyKYCs CompanyKYC[] + certificates Certificate[] departments Department[] employees Employee[] invitations Invitation[] @@ -176,9 +210,9 @@ model Company { incomeExpenses IncomeExpense[] accounts Account[] reports Report[] + vouchers Voucher[] voucherSalaryRecordFolders VoucherSalaryRecordFolder[] - @@map("company") } @@ -259,7 +293,6 @@ model Contract { createdAt Int @map("created_at") updatedAt Int @map("updated_at") deletedAt Int? @map("deleted_at") - assets Asset[] journal Journal[] company Company @relation(fields: [companyId], references: [id]) @@ -269,6 +302,24 @@ model Contract { @@map("contract") } +model Certificate { + id Int @id @default(autoincrement()) + companyId Int @map("company_id") + fileId Int @unique @map("file_id") + createdAt Int @map("created_at") + updatedAt Int @map("updated_at") + deletedAt Int? @map("deleted_at") + + invoices Invoice[] + voucherCertificate VoucherCertificate[] + + company Company @relation(fields: [companyId], references: [id]) + file File @relation(fields: [fileId], references: [id]) + UserCertificate UserCertificate[] + + @@map("certificate") +} + model Department { id Int @id @default(autoincrement()) companyId Int @map("company_id") @@ -322,6 +373,23 @@ model EmployeeProject { @@map("employee_project") } +model Event { + id Int @id @default(autoincrement()) + eventType String @map("event_type") + frequence String + startDate Int @map("start_date") + endDate Int @map("end_date") + daysOfWeek Int[] + monthsOfYear String[] + createdAt Int @map("created_at") + updatedAt Int @map("updated_at") + deletedAt Int? @map("deleted_at") + + associateVouchers AccociateVoucher[] + + @@map("event") +} + model File { id Int @id @default(autoincrement()) name String // Info: (20240830 - Murky) Name example: "100000.jpg" @@ -330,50 +398,74 @@ model File { type String //Info: (20240830 - Murky) FileFolder Type ex: invoice, tmp url String // Info: (20240830 - Murky) File URL, it can be local folder or google bucket isEncrypted Boolean @map("is_encrypted") // Info: (20240830 - Murky) File is Encrypted by encryptSymmetricKey - encryptedSymmetricKey String // Info: (20240830 - Murky) File Encrypt Symmetric Key + encryptedSymmetricKey String @map("encrypted_symmetric_key") // Info: (20240830 - Murky) File Encrypt Symmetric Key iv Bytes @default("") // Info: (20240830 - Murky) File Encrypt IV createdAt Int @map("created_at") updatedAt Int @map("updated_at") deletedAt Int? @map("deleted_at") + certificate Certificate? companyImageFile Company? @relation("company_image_file") - invoiceImageFile Invoice? @relation("invoice_image_file") ocrImageFile Ocr? @relation("ocr_image_file") userImageFile User? @relation("user_image_file") projectImageFile Project? @relation("project_image_file") registrationCertificateFile CompanyKYC? @relation("registration_certificate_file") taxCertificateFile CompanyKYC? @relation("tax_certificate_file") representativeIdCardFile CompanyKYC? @relation("representative_id_card_file") - personalIdFile KYCBookkeeper? @relation("kyc_personal_id_file") - certificationFile KYCBookkeeper? @relation("kyc_certification_file") + kycPersonalIdFile KYCBookkeeper? @relation("kyc_personal_id_file") + kycCertificationFile KYCBookkeeper? @relation("kyc_certification_file") @@map("file") } model Invoice { - id Int @id @default(autoincrement()) - journalId Int @unique @map("journal_id") - paymentId Int @unique @map("payment_id") - number String @unique - type String - date Int - eventType String @map("event_type") - paymentReason String @map("payment_reason") - description String - vendorTaxId String @map("vendor_tax_id") - vendorOrSupplier String @map("vendor_or_supplier") - imageFileId Int? @unique @map("image_file_id") - deductible Boolean - createdAt Int @map("created_at") - updatedAt Int @map("updated_at") - deletedAt Int? @map("deleted_at") - payment Payment @relation(fields: [paymentId], references: [id]) - journal Journal @relation(fields: [journalId], references: [id]) - imageFile File? @relation("invoice_image_file", fields: [imageFileId], references: [id]) + id Int @id @default(autoincrement()) + certificateId Int @map("certificate_id") + counterPartyId Int @map("counter_party_id") + inputOrOutput String @map("input_or_output") + date Int + no String + currencyAlias String @map("currency_alias") + priceBeforeTax Int @map("price_before_tax") + taxType String @map("tax_type") + taxRatio Int @map("tax_ratio") + taxPrice Int @map("tax_price") + totalPrice Int @map("total_price") + type String + deductible Boolean + createdAt Int @map("created_at") + updatedAt Int @map("updated_at") + deletedAt Int? @map("deleted_at") + + invoiceVoucherJournals InvoiceVoucherJournal[] + + certificate Certificate @relation(fields: [certificateId], references: [id]) + counterParty Counterparty @relation(fields: [counterPartyId], references: [id]) + @@map("invoice") } +model InvoiceVoucherJournal { + id Int @id @default(autoincrement()) + invoiceId Int? @map("invoice_id") + voucherId Int? @map("voucher_id") + journalId Int @map("journal_id") + description String + paymentId Int? @map("payment_id") + paymentReason String @map("payment_reason") + vendorOrSupplier String @map("vendor_or_supplier") + createdAt Int @map("created_at") + updatedAt Int @map("updated_at") + deletedAt Int? @map("deleted_at") + + invoice Invoice? @relation(fields: [invoiceId], references: [id]) + voucher Voucher? @relation(fields: [voucherId], references: [id]) + journal Journal @relation(fields: [journalId], references: [id]) + + @@map("invoice_voucher_journal") +} + model Invitation { id Int @id @default(autoincrement()) companyId Int @map("company_id") @@ -421,8 +513,8 @@ model Journal { createdAt Int @map("created_at") updatedAt Int @map("updated_at") deletedAt Int? @map("deleted_at") - invoice Invoice? - voucher Voucher? + + invoiceVoucherJournals InvoiceVoucherJournal[] company Company @relation(fields: [companyId], references: [id]) contract Contract? @relation(fields: [contractId], references: [id]) @@ -519,12 +611,13 @@ model Order { status String createdAt Int @map("created_at") updatedAt Int @map("updated_at") - company Company @relation(fields: [companyId], references: [id]) - plan Plan @relation(fields: [planId], references: [id]) deletedAt Int? @map("deleted_at") paymentRecords PaymentRecord[] + company Company @relation(fields: [companyId], references: [id]) + plan Plan @relation(fields: [planId], references: [id]) + @@map("order") } @@ -538,7 +631,6 @@ model Project { createdAt Int @map("created_at") updatedAt Int @map("updated_at") deletedAt Int? @map("deleted_at") - assets Asset[] contracts Contract[] journals Journal[] milestones Milestone[] @@ -572,25 +664,27 @@ model Payment { createdAt Int @map("created_at") updatedAt Int @map("updated_at") deletedAt Int? @map("deleted_at") - invoice Invoice? contract Contract? @@map("payment") } model PaymentRecord { - id Int @id @default(autoincrement()) - orderId Int @map("order_id") - // Info: (20240604 - Jacky) Third party payment transaction id - transactionId String @map("transaction_id") - date Int - description String - amount Int - method String - status String // Info: (20240621 - Murky) completed、failed、pending - createdAt Int @map("created_at") - updatedAt Int @map("updated_at") - deletedAt Int? @map("deleted_at") + id Int @id @default(autoincrement()) + orderId Int @map("order_id") + transactionId String @map("transaction_id") + action String + amount Float + fee Float + method String @map("method") + cardIssuerCountry String @map("card_issuer_country") + status String + paymentCreatedAt String @map("payment_created_at") + refundAmount Float @map("refund_amount") + authCode String @map("auth_code") + createdAt Int @map("created_at") + updatedAt Int @map("updated_at") + deletedAt Int? @map("deleted_at") order Order @relation(fields: [orderId], references: [id]) @@ -598,14 +692,15 @@ model PaymentRecord { } model Plan { - id Int @id @default(autoincrement()) - name String @unique - description String - monthlyFee Int @map("monthly_fee") - annualFee Int @map("annual_fee") - createdAt Int @map("created_at") - updatedAt Int @map("updated_at") - deletedAt Int? @map("deleted_at") + id Int @id @default(autoincrement()) + name String @unique + description String + billingCycle String @map("billing_cycle") + price Float + createdAt Int @map("created_at") + updatedAt Int @map("updated_at") + deletedAt Int? @map("deleted_at") + orders Order[] subscriptions Subscription[] @@ -616,7 +711,7 @@ model Role { id Int @id @default(autoincrement()) name String @unique permissions String[] - lastLoginAt Int @map("last_login_at") @default(0) + lastLoginAt Int @default(0) @map("last_login_at") createdAt Int @map("created_at") updatedAt Int @map("updated_at") deletedAt Int? @map("deleted_at") @@ -658,16 +753,18 @@ model Report { model Subscription { id Int @id @default(autoincrement()) - companyId Int @map("company_id") planId Int @map("plan_id") + companyId Int @map("company_id") + autoRenewal Boolean @map("auto_renewal") status Boolean startDate Int @map("start_date") expiredDate Int @map("expired_date") createdAt Int @map("created_at") updatedAt Int @map("updated_at") deletedAt Int? @map("deleted_at") - company Company @relation(fields: [companyId], references: [id]) - plan Plan @relation(fields: [planId], references: [id]) + + company Company @relation(fields: [companyId], references: [id]) + plan Plan @relation(fields: [planId], references: [id]) @@map("subscription") } @@ -719,15 +816,15 @@ model SalaryRecordProjectHour { } model Shortcut { - id Int @id @default(autoincrement()) - accountingSettingId Int @map("accounting_setting_id") - actionName String @map("action_name") - description String - fieldList Json @map("field_list") - keyList String[] @map("key_list") - createdAt Int @map("created_at") - updatedAt Int @map("updated_at") - deletedAt Int? @map("deleted_at") + id Int @id @default(autoincrement()) + accountingSettingId Int @map("accounting_setting_id") + actionName String @map("action_name") + description String + fieldList Json @map("field_list") + keyList String[] @map("key_list") + createdAt Int @map("created_at") + updatedAt Int @map("updated_at") + deletedAt Int? @map("deleted_at") accountingSetting AccountingSetting @relation(fields: [accountingSettingId], references: [id]) @@ -735,21 +832,25 @@ model Shortcut { } model User { - id Int @id @default(autoincrement()) + id Int @id @default(autoincrement()) name String email String? - imageFileId Int @unique @map("image_File_id") - createdAt Int @map("created_at") - updatedAt Int @map("updated_at") - deletedAt Int? @map("deleted_at") + imageFileId Int @unique @map("image_File_id") + createdAt Int @map("created_at") + updatedAt Int @map("updated_at") + deletedAt Int? @map("deleted_at") admins Admin[] invitations Invitation[] authentications Authentication[] userAgreements UserAgreement[] + userPaymentInfo UserPaymentInfo[] userRoles UserRole[] kycBookkeepers KYCBookkeeper[] + UserCertificate UserCertificate[] + UserVoucher UserVoucher[] + Voucher Voucher[] - imageFile File @relation("user_image_file", fields: [imageFileId], references: [id]) + imageFile File @relation("user_image_file", fields: [imageFileId], references: [id]) @@map("user") } @@ -786,6 +887,20 @@ model UserActionLog { @@map("user_action_log") } +model UserPaymentInfo { + id Int @id @default(autoincrement()) + userId Int @map("user_id") + token String + transactionId String @map("transaction_id") + createdAt Int @map("created_at") + updatedAt Int @map("updated_at") + deletedAt Int? @map("deleted_at") + + user User @relation(fields: [userId], references: [id]) + + @@map("user_payment_info") +} + model UserSetting { id Int @id @default(autoincrement()) userId Int @map("user_id") @@ -818,22 +933,82 @@ model UserRole { @@map("user_role") } +model UserCertificate { + id Int @id @default(autoincrement()) + userId Int @map("user_id") + certificateId Int @map("certificate_id") + isRead Boolean @map("is_read") + createdAt Int @map("created_at") + updatedAt Int @map("updated_at") + deletedAt Int? @map("deleted_at") + + user User @relation(fields: [userId], references: [id]) + certificate Certificate @relation(fields: [certificateId], references: [id]) + + @@map("user_certificate") +} + +model UserVoucher { + id Int @id @default(autoincrement()) + userId Int @map("user_id") + voucherId Int @map("voucher_id") + isRead Boolean @map("is_read") + createdAt Int @map("created_at") + updatedAt Int @map("updated_at") + deletedAt Int? @map("deleted_at") + + user User @relation(fields: [userId], references: [id]) + voucher Voucher @relation(fields: [voucherId], references: [id]) + + @@map("user_voucher") +} + model Voucher { - id Int @id @default(autoincrement()) - journalId Int @unique @map("journal_id") - no String - createdAt Int @map("created_at") - updatedAt Int @map("updated_at") - deletedAt Int? @map("deleted_at") - assets Asset[] + id Int @id @default(autoincrement()) + issuerId Int @map("issuer_id") + counterPartyId Int @map("counter_party_id") + companyId Int @map("company_id") + status String @default("journal:JOURNAL.UPLOADED") + editable Boolean @default(true) + no String + date Int + type String @default("payment") + note String? + createdAt Int @map("created_at") + updatedAt Int @map("updated_at") + deletedAt Int? @map("deleted_at") + + assetVouchers AssetVoucher[] lineItems LineItem[] + UserVoucher UserVoucher[] voucherSalaryRecords VoucherSalaryRecord[] + voucherCertificates VoucherCertificate[] + invoiceVoucherJournals InvoiceVoucherJournal[] - journal Journal @relation(fields: [journalId], references: [id]) + originalVouchers AccociateVoucher[] @relation("original_voucher") + resultVouchers AccociateVoucher[] @relation("result_voucher") + + company Company @relation(fields: [companyId], references: [id]) + issuer User @relation(fields: [issuerId], references: [id]) + counterparty Counterparty @relation(fields: [counterPartyId], references: [id]) @@map("voucher") } +model VoucherCertificate { + id Int @id @default(autoincrement()) + voucherId Int @map("voucher_id") + certificateId Int @map("certificate_id") + createdAt Int @map("created_at") + updatedAt Int @map("updated_at") + deletedAt Int? @map("deleted_at") + + voucher Voucher @relation(fields: [voucherId], references: [id]) + certificate Certificate @relation(fields: [certificateId], references: [id]) + + @@map("voucher_certificate") +} + model VoucherSalaryRecord { id Int @id @default(autoincrement()) voucherId Int @map("voucher_id") diff --git a/prisma/seed.ts b/prisma/seed.ts index 56dbfedce..fbb69a51d 100644 --- a/prisma/seed.ts +++ b/prisma/seed.ts @@ -29,6 +29,9 @@ import lineItems from '@/seed_json/line_item.json'; import salaryRecords from '@/seed_json/salary_record.json'; import voucherSalaryRecordFolder from '@/seed_json/voucher_salary_record_folder.json'; import file from '@/seed_json/file.json'; +import assets from '@/seed_json/asset.json'; +import assetVouchers from '@/seed_json/asset_voucher.json'; +import counterpartys from '@/seed_json/counterparty.json'; const prisma = new PrismaClient(); @@ -244,6 +247,24 @@ async function createLineItems() { await Promise.all(lineItems.map((lineItem) => createLineItem(lineItem))); } +async function createAsset() { + await prisma.asset.createMany({ + data: assets, + }); +} + +async function createAssetVoucher() { + await prisma.assetVoucher.createMany({ + data: assetVouchers, + }); +} + +async function createCounterparty() { + await prisma.counterparty.createMany({ + data: counterpartys, + }); +} + async function main() { await createFile(); await createCompany(); @@ -254,7 +275,7 @@ async function main() { setTimeout(resolve, 3000); }); await createUser(); - + await createCounterparty(); await createRole(); await createCompanyKYC(); await createAccount(); @@ -295,6 +316,7 @@ async function main() { await createJournal(); await createVoucher(); + await createAsset(); await new Promise((resolve) => { setTimeout(resolve, 3000); @@ -302,6 +324,7 @@ async function main() { await createLineItems(); await createSalaryRecord(); await createVoucherSalaryRecordFolder(); + await createAssetVoucher(); } main() diff --git a/prisma/seed_json/asset.json b/prisma/seed_json/asset.json new file mode 100644 index 000000000..2f72ba607 --- /dev/null +++ b/prisma/seed_json/asset.json @@ -0,0 +1,40 @@ +[ + { + "id": 1, + "companyId": 1000, + "name": "Notebook", + "type": "Electronic Equipment", + "number": "LAPTOP-001", + "acquisitionDate": 1609459200, + "purchasePrice": 30000.0, + "accumulatedDepreciation": 5000, + "residualValue": 1000.0, + "remainingLife": 36, + "status": "normal", + "depreciationStart": 1612137600, + "depreciationMethod": "straight-line", + "usefulLife": 60, + "note": "Company Notebook", + "createdAt": 1609459200, + "updatedAt": 1609459200 + }, + { + "id": 2, + "companyId": 1001, + "name": "Desk", + "type": "Office Furniture", + "number": "DESK-001", + "acquisitionDate": 1612137600, + "purchasePrice": 5000.0, + "accumulatedDepreciation": 0.0, + "residualValue": 500.0, + "remainingLife": 120, + "status": "normal", + "depreciationStart": 1614556800, + "depreciationMethod": "straight-line", + "usefulLife": 120, + "note": "Company Desk", + "createdAt": 1612137600, + "updatedAt": 1612137600 + } +] diff --git a/prisma/seed_json/asset_voucher.json b/prisma/seed_json/asset_voucher.json new file mode 100644 index 000000000..23e541136 --- /dev/null +++ b/prisma/seed_json/asset_voucher.json @@ -0,0 +1,16 @@ +[ + { + "id": 1, + "assetId": 1, + "voucherId": 1000, + "createdAt": 1609459200, + "updatedAt": 1609459200 + }, + { + "id": 2, + "assetId": 2, + "voucherId": 1001, + "createdAt": 1612137600, + "updatedAt": 1612137600 + } +] diff --git a/prisma/seed_json/counterparty.json b/prisma/seed_json/counterparty.json new file mode 100644 index 000000000..c8dccdbd9 --- /dev/null +++ b/prisma/seed_json/counterparty.json @@ -0,0 +1,24 @@ +[ + { + "id": 1000, + "companyId": 1000, + "name": "ABC Corp", + "taxId": "123456789", + "type": "Supplier", + "note": "Preferred supplier", + "createdAt": 1622548800, + "updatedAt": 1625130800, + "deletedAt": null + }, + { + "id": 1001, + "companyId": 1000, + "name": "XYZ Ltd", + "taxId": "987654321", + "type": "Customer", + "note": "Regular customer", + "createdAt": 1622548800, + "updatedAt": 1625130800, + "deletedAt": null + } +] diff --git a/prisma/seed_json/line_item.json b/prisma/seed_json/line_item.json index 4ad7420d1..1c14d4037 100644 --- a/prisma/seed_json/line_item.json +++ b/prisma/seed_json/line_item.json @@ -53,24 +53,6 @@ "createdAt": 1, "updatedAt": 1 }, - { - "amount": 600, - "description": "購買存貨-商品存貨", - "accountCode": "1301", - "debit": false, - "voucherId": 1002, - "createdAt": 1, - "updatedAt": 1 - }, - { - "amount": 600, - "description": "購買存貨-銷貨成本", - "accountCode": "5111", - "debit": true, - "voucherId": 1002, - "createdAt": 1, - "updatedAt": 1 - }, { "amount": 100, "description": "償還應付帳款-銀行現金", @@ -88,50 +70,5 @@ "voucherId": 1000, "createdAt": 1, "updatedAt": 1 - }, - { - "amount": 10000, - "description": "取得透過其他綜合損益按公允價值衡量之金融資產-銀行存款", - "accountCode": "1103", - "debit": false, - "voucherId": 1004, - "createdAt": 1, - "updatedAt": 1 - }, - { - "amount": 10000, - "description": "取得透過其他綜合損益按公允價值衡量之金融資產-債券", - "accountCode": "1121", - "debit": true, - "voucherId": 1004, - "createdAt": 1, - "updatedAt": 1 - }, - { - "amount": 4500, - "description": "處分透過其他綜合損益按公允價值衡量之金融資產-銀行存款", - "accountCode": "1103", - "debit": true, - "voucherId": 1005, - "createdAt": 1, - "updatedAt": 1 - }, - { - "amount": 4000, - "description": "處分透過其他綜合損益按公允價值衡量之金融資產-債券", - "accountCode": "1121", - "debit": false, - "voucherId": 1005, - "createdAt": 1, - "updatedAt": 1 - }, - { - "amount": 500, - "description": "處分透過其他綜合損益按公允價值衡量之金融資產-評價調整", - "accountCode": "1122", - "debit": true, - "voucherId": 1005, - "createdAt": 1, - "updatedAt": 1 } ] diff --git a/prisma/seed_json/payment_record.json b/prisma/seed_json/payment_record.json index 960a8cc15..08fd0695f 100644 --- a/prisma/seed_json/payment_record.json +++ b/prisma/seed_json/payment_record.json @@ -2,27 +2,35 @@ { "id": 1000, "orderId": 1000, - "transactionId": "abc123", - "date": 1634567890, - "description": "Payment for order #123", - "amount": 100.0, - "method": "Credit Card", - "status": "Completed", - "createdAt": 1634567890, - "updatedAt": 1634567890, + "transactionId": "txn_001", + "action": "purchase", + "amount": 100.50, + "fee": 2.50, + "method": "credit_card", + "cardIssuerCountry": "US", + "status": "completed", + "paymentCreatedAt": "2023-10-01T10:00:00Z", + "refundAmount": 0.0, + "authCode": "auth_001", + "createdAt": 1696156800, + "updatedAt": 1696156800, "deletedAt": null }, { "id": 1001, "orderId": 1000, - "transactionId": "def456", - "date": 1634567900, - "description": "Payment for order #123", - "amount": 200.0, - "method": "PayPal", - "status": "Completed", - "createdAt": 1634567900, - "updatedAt": 1634567900, + "transactionId": "txn_002", + "action": "refund", + "amount": 50.00, + "fee": 1.25, + "method": "paypal", + "cardIssuerCountry": "Taiwan", + "status": "completed", + "paymentCreatedAt": "2023-10-02T11:00:00Z", + "refundAmount": 50.00, + "authCode": "0", + "createdAt": 1696243200, + "updatedAt": 1696243200, "deletedAt": null } ] diff --git a/prisma/seed_json/plan.json b/prisma/seed_json/plan.json index d8f3774e9..0d53f0c0c 100644 --- a/prisma/seed_json/plan.json +++ b/prisma/seed_json/plan.json @@ -2,19 +2,31 @@ { "id": 1000, "name": "Basic Plan", - "description": "This is the basic plan", - "monthlyFee": 10, - "annualFee": 100, - "createdAt": 1634567890, - "updatedAt": 1634567890 + "description": "This is a basic plan.", + "billingCycle": "monthly", + "price": 89, + "createdAt": 1627849200, + "updatedAt": 1627849200, + "deletedAt": null }, { "id": 1001, - "name": "Premium Plan", - "description": "This is the premium plan", - "monthlyFee": 20, - "annualFee": 200, - "createdAt": 1634567890, - "updatedAt": 1634567890 + "name": "Pro Plan", + "description": "This is a pro plan.", + "billingCycle": "monthly", + "price": 899, + "createdAt": 1627849200, + "updatedAt": 1627849200, + "deletedAt": null + }, + { + "id": 1002, + "name": "Enterprise Plan", + "description": "This is an enterprise plan.", + "billingCycle": "yearly", + "price": 8990, + "createdAt": 1627849200, + "updatedAt": 1627849200, + "deletedAt": null } ] diff --git a/prisma/seed_json/subscription.json b/prisma/seed_json/subscription.json index a215d967b..4c011508e 100644 --- a/prisma/seed_json/subscription.json +++ b/prisma/seed_json/subscription.json @@ -1,13 +1,26 @@ [ { "id": 1000, - "companyId": 1000, "planId": 1000, - "startDate": 1635724800, - "expiredDate": 1667260800, + "companyId": 1000, + "autoRenewal": true, "status": true, - "createdAt": 1635724800, - "updatedAt": 1635724800, + "startDate": 1672531200, + "expiredDate": 1704067200, + "createdAt": 1672531200, + "updatedAt": 1672531200, + "deletedAt": null + }, + { + "id": 1001, + "planId": 1000, + "companyId": 1000, + "autoRenewal": false, + "status": false, + "startDate": 1672531200, + "expiredDate": 1704067200, + "createdAt": 1672531200, + "updatedAt": 1672531200, "deletedAt": null } ] diff --git a/prisma/seed_json/voucher.json b/prisma/seed_json/voucher.json index 2b9f04547..5cbdf2304 100644 --- a/prisma/seed_json/voucher.json +++ b/prisma/seed_json/voucher.json @@ -1,51 +1,32 @@ [ { "id": 1000, - "journalId": 1000, - "no": "20240101001", - "createdAt": 1, - "updatedAt": 1 + "issuerId": 1000, + "counterPartyId": 1000, + "companyId": 1000, + "status": "uploaded", + "editable": true, + "no": "VCH001", + "date": 1672531200, + "type": "payment", + "note": "First voucher", + "createdAt": 1672531200, + "updatedAt": 1672531200, + "deletedAt": null }, { "id": 1001, - "journalId": 1001, - "no": "20240101002", - "createdAt": 1, - "updatedAt": 1 - }, - { - "id": 1002, - "journalId": 1002, - "no": "20240101003", - "createdAt": 1, - "updatedAt": 1 - }, - { - "id": 1003, - "journalId": 1003, - "no": "20240101004", - "createdAt": 1, - "updatedAt": 1 - }, - { - "id": 1004, - "journalId": 1004, - "no": "20240101005", - "createdAt": 1, - "updatedAt": 1 - }, - { - "id": 1005, - "journalId": 1005, - "no": "20240101006", - "createdAt": 1, - "updatedAt": 1 - }, - { - "id": 1006, - "journalId": 1006, - "no": "20240809001", - "createdAt": 1723188860, - "updatedAt": 1723188860 + "issuerId": 1000, + "counterPartyId": 1000, + "companyId": 1000, + "status": "approved", + "editable": false, + "no": "VCH002", + "date": 1672617600, + "type": "receipt", + "note": "Second voucher", + "createdAt": 1672617600, + "updatedAt": 1672617600, + "deletedAt": null } ] diff --git a/src/interfaces/invoice.ts b/src/interfaces/invoice.ts index ea6108e77..651f79320 100644 --- a/src/interfaces/invoice.ts +++ b/src/interfaces/invoice.ts @@ -1,6 +1,5 @@ -import { IPayment, IPaymentBeta } from '@/interfaces/payment'; +import { IPayment } from '@/interfaces/payment'; import { EventType } from '@/constants/account'; -import { Prisma } from '@prisma/client'; // Info: ( 20240522 - Murky)To Emily, To Julian 這個interface是用來存入prisma的資料, 用來在ISFMK00052時Upload使用 export interface IInvoice { @@ -17,22 +16,21 @@ export interface IInvoice { payment: IPayment; } -export type IInvoiceIncludePaymentJournal = Prisma.InvoiceGetPayload<{ - include: { - payment: true; - journal: { - include: { - project: true; - contract: true; - }; - }; - }; -}>; - -export interface IInvoiceBeta extends IInvoice { - number: string; // Info: (20240807 - Jacky) origin invoice number - type: string; // Info: (20240808 - Murky) 營業稅格式代號 - vendorTaxId: string; - payment: IPaymentBeta; +export interface IInvoiceBeta { + id: number; + certificateId: number; + counterPartyId: number; + inputOrOutput: string; + date: number; + no: string; + currencyAlias: string; + priceBeforeTax: number; + taxType: string; + taxRatio: number; + taxPrice: number; + totalPrice: number; + type: string; deductible: boolean; + createdAt: number; + updatedAt: number; } diff --git a/src/interfaces/journal.ts b/src/interfaces/journal.ts index 09d2f2976..2b148ea3f 100644 --- a/src/interfaces/journal.ts +++ b/src/interfaces/journal.ts @@ -63,20 +63,3 @@ export type IJournalFromPrismaIncludeProjectContractInvoiceVoucher = Prisma.Jour }; }; }>; - -export type IJournalFromPrismaIncludeInvoicePayment = Prisma.JournalGetPayload<{ - include: { - invoice: { - include: { - payment: true; - }; - }; - }; -}>; - -export type IJournalIncludeVoucherLineItemsInvoicePayment = Prisma.JournalGetPayload<{ - include: { - invoice: { include: { payment: true } }; - voucher: { include: { lineItems: { include: { account: true } } } }; - }; -}>; diff --git a/src/interfaces/payment_record.ts b/src/interfaces/payment_record.ts index 40a08ae64..28b38e7ab 100644 --- a/src/interfaces/payment_record.ts +++ b/src/interfaces/payment_record.ts @@ -2,11 +2,15 @@ export interface IPaymentRecord { id: number; orderId: number; transactionId: string; - date: number; - description: string; + action: string; amount: number; + fee: number; method: string; + cardIssuerCountry: string; status: string; + paymentCreatedAt: string; + refundAmount: number; + authCode: string; createdAt: number; updatedAt: number; } diff --git a/src/interfaces/subscription.ts b/src/interfaces/subscription.ts index 491365967..9ac632618 100644 --- a/src/interfaces/subscription.ts +++ b/src/interfaces/subscription.ts @@ -2,6 +2,7 @@ export interface ISubscription { id: number; companyId: number; planId: number; + autoRenewal: boolean; startDate: number; expiredDate: number; status: boolean; diff --git a/src/interfaces/voucher.ts b/src/interfaces/voucher.ts index ec251c500..e3f1f2742 100644 --- a/src/interfaces/voucher.ts +++ b/src/interfaces/voucher.ts @@ -49,7 +49,11 @@ export interface IVoucherDataForSavingToDB { export type IVoucherFromPrismaIncludeJournalLineItems = Prisma.VoucherGetPayload<{ include: { - journal: true; + invoiceVoucherJournals: { + include: { + journal: true; + }; + }; lineItems: { include: { account: true; @@ -60,9 +64,9 @@ export type IVoucherFromPrismaIncludeJournalLineItems = Prisma.VoucherGetPayload export type IVoucherForCashFlow = Prisma.VoucherGetPayload<{ include: { - journal: { + invoiceVoucherJournals: { include: { - invoice: true; + journal: true; }; }; lineItems: { diff --git a/src/lib/utils/formatter/invoice.formatter.ts b/src/lib/utils/formatter/invoice.formatter.ts index 8b729dd8c..74ffc6555 100644 --- a/src/lib/utils/formatter/invoice.formatter.ts +++ b/src/lib/utils/formatter/invoice.formatter.ts @@ -1,35 +1,55 @@ -import { IInvoice, IInvoiceIncludePaymentJournal } from '@/interfaces/invoice'; +import { IInvoice } from '@/interfaces/invoice'; import { convertStringToEventType, convertStringToPaymentPeriodType, convertStringToPaymentStatusType, } from '@/lib/utils/type_guard/account'; +import { + Account, + Certificate, + File, + Invoice, + InvoiceVoucherJournal, + Journal, + LineItem, + Voucher, +} from '@prisma/client'; -export function formatIInvoice(invoiceFromDB: IInvoiceIncludePaymentJournal): IInvoice { +// ToDo: (20241009 - Jacky) This is a temporary function to format the invoice data from the database +// so that it can be used in the front-end. This function will be removed after the beta frontend is completed. +export function formatIInvoice( + invoiceVoucherJournal: InvoiceVoucherJournal & { + journal: Journal | null; + invoice: (Invoice & { certificate: Certificate & { file: File } }) | null; + voucher: (Voucher & { lineItems: (LineItem & { account: Account })[] }) | null; + } +): IInvoice { const invoice: IInvoice = { - journalId: invoiceFromDB.journalId, - date: invoiceFromDB.date, - eventType: convertStringToEventType(invoiceFromDB.eventType), - paymentReason: invoiceFromDB.paymentReason, - description: invoiceFromDB.description, - vendorOrSupplier: invoiceFromDB.vendorOrSupplier, - projectId: invoiceFromDB.journal.projectId, - project: invoiceFromDB.journal?.project?.name || null, - contractId: invoiceFromDB.journal.contractId, - contract: invoiceFromDB.journal?.contract?.name || null, + journalId: invoiceVoucherJournal.journalId, + date: invoiceVoucherJournal.invoice?.date || invoiceVoucherJournal.createdAt, + eventType: convertStringToEventType( + invoiceVoucherJournal.voucher?.type || 'journal:EVENT_TYPE.INVOICE' + ), + paymentReason: invoiceVoucherJournal.paymentReason, + description: invoiceVoucherJournal.description, + vendorOrSupplier: invoiceVoucherJournal.vendorOrSupplier, + projectId: 0, + project: null, + contractId: 0, + contract: null, payment: { - isRevenue: invoiceFromDB.payment.isRevenue, - price: invoiceFromDB.payment.price, - hasTax: invoiceFromDB.payment.hasTax, - taxPercentage: invoiceFromDB.payment.taxPercentage, - hasFee: invoiceFromDB.payment.hasFee, - fee: invoiceFromDB.payment.fee, - method: invoiceFromDB.payment.method, - period: convertStringToPaymentPeriodType(invoiceFromDB.payment.period), - installmentPeriod: invoiceFromDB.payment.installmentPeriod, - alreadyPaid: invoiceFromDB.payment.alreadyPaid, - status: convertStringToPaymentStatusType(invoiceFromDB.payment.status), - progress: invoiceFromDB.payment.progress, + isRevenue: true, + price: invoiceVoucherJournal.invoice?.totalPrice || 0, + hasTax: invoiceVoucherJournal.invoice?.taxType !== 'tax free', + taxPercentage: invoiceVoucherJournal.invoice?.taxRatio || 0, + hasFee: false, + fee: 0, + method: 'journal:PAYMENT_METHOD.CASH', + period: convertStringToPaymentPeriodType('atOnce'), + installmentPeriod: 0, + alreadyPaid: 0, + status: convertStringToPaymentStatusType('paid'), + progress: 0, }, }; return invoice; diff --git a/src/lib/utils/formatter/journal.formatter.ts b/src/lib/utils/formatter/journal.formatter.ts index 5f0a73161..b0f5180b9 100644 --- a/src/lib/utils/formatter/journal.formatter.ts +++ b/src/lib/utils/formatter/journal.formatter.ts @@ -1,117 +1,86 @@ -import { IInvoice } from '@/interfaces/invoice'; -import { - IJournal, - IJournalFromPrismaIncludeProjectContractInvoiceVoucher, - IJournalListItem, -} from '@/interfaces/journal'; -import { IVoucherDataForSavingToDB } from '@/interfaces/voucher'; -import { - convertStringToEventType, - convertStringToPaymentPeriodType, - convertStringToPaymentStatusType, -} from '@/lib/utils/type_guard/account'; +import { IJournal, IJournalListItem } from '@/interfaces/journal'; import { sumLineItemsAndReturnBiggest } from '@/lib/utils/line_item'; import { assertIsJournalEvent } from '@/lib/utils/type_guard/journal'; -import { FileFolder } from '@/constants/file'; -import { transformOCRImageIDToURL } from '@/lib/utils/common'; +import { + Account, + Certificate, + File, + Invoice, + InvoiceVoucherJournal, + Journal, + LineItem, + Voucher, +} from '@prisma/client'; +import { formatIInvoice } from './invoice.formatter'; export function formatSingleIJournalListItem( - journalFromPrisma: IJournalFromPrismaIncludeProjectContractInvoiceVoucher + invoiceVoucherJournal: InvoiceVoucherJournal & { + journal: Journal | null; + invoice: Invoice | null; + voucher: (Voucher & { lineItems: (LineItem & { account: Account })[] }) | null; + } ): IJournalListItem { - const { credit, debit } = sumLineItemsAndReturnBiggest(journalFromPrisma?.voucher?.lineItems); + const { credit, debit } = sumLineItemsAndReturnBiggest(invoiceVoucherJournal?.voucher?.lineItems); - assertIsJournalEvent(journalFromPrisma.event); + assertIsJournalEvent(invoiceVoucherJournal.voucher?.status); return { - id: journalFromPrisma.id, - date: journalFromPrisma.invoice?.date || journalFromPrisma.createdAt, - type: journalFromPrisma.invoice?.eventType, - particulars: journalFromPrisma.invoice?.description, - fromTo: journalFromPrisma.invoice?.vendorOrSupplier, - event: journalFromPrisma.event, + id: invoiceVoucherJournal.id, + date: invoiceVoucherJournal.invoice?.date || invoiceVoucherJournal.createdAt, + type: invoiceVoucherJournal.voucher?.type, + particulars: invoiceVoucherJournal.description, + fromTo: invoiceVoucherJournal.vendorOrSupplier, + event: invoiceVoucherJournal.voucher.status, account: [debit, credit], - projectName: journalFromPrisma.project?.name, - projectImageId: journalFromPrisma.project?.imageFile?.name, - voucherId: journalFromPrisma.voucher?.id, - voucherNo: journalFromPrisma.voucher?.no, + projectName: '', + projectImageId: '', + voucherId: invoiceVoucherJournal.voucher?.id, + voucherNo: invoiceVoucherJournal.voucher?.no, }; } export function formatIJournalListItems( - journalsFromPrisma: IJournalFromPrismaIncludeProjectContractInvoiceVoucher[] + journalsFromPrisma: (InvoiceVoucherJournal & { + journal: Journal | null; + invoice: Invoice | null; + voucher: (Voucher & { lineItems: (LineItem & { account: Account })[] }) | null; + })[] ): IJournalListItem[] { - const journalLineItems = journalsFromPrisma.map((journalFromPrisma) => { - return formatSingleIJournalListItem(journalFromPrisma); + const journalLineItems = journalsFromPrisma.map((invoiceVoucherJournal) => { + return formatSingleIJournalListItem(invoiceVoucherJournal); }); return journalLineItems; } export function formatIJournal( - journalFromPrisma: IJournalFromPrismaIncludeProjectContractInvoiceVoucher + invoiceVoucherJournal: InvoiceVoucherJournal & { + journal: Journal | null; + invoice: (Invoice & { certificate: Certificate & { file: File } }) | null; + voucher: (Voucher & { lineItems: (LineItem & { account: Account })[] }) | null; + } ): IJournal { - const projectName = journalFromPrisma?.project?.name; - const { projectId } = journalFromPrisma; - const contractName = journalFromPrisma?.contract?.name; - const { contractId } = journalFromPrisma; - - const imageName = journalFromPrisma.invoice?.imageFile?.name || ''; - const imageUrl = transformOCRImageIDToURL(FileFolder.INVOICE, 0, imageName); - - const invoice: IInvoice = journalFromPrisma.invoice - ? { - journalId: journalFromPrisma.id, - date: journalFromPrisma.invoice.date, - eventType: convertStringToEventType(journalFromPrisma.invoice.eventType), - paymentReason: journalFromPrisma.invoice.paymentReason, - description: journalFromPrisma.invoice.description, - vendorOrSupplier: journalFromPrisma.invoice.vendorOrSupplier, - projectId, - project: projectName || null, - contractId, - contract: contractName || null, - payment: { - isRevenue: journalFromPrisma.invoice.payment.isRevenue, - price: journalFromPrisma.invoice.payment.price, - hasTax: journalFromPrisma.invoice.payment.hasTax, - taxPercentage: journalFromPrisma.invoice.payment.taxPercentage, - hasFee: journalFromPrisma.invoice.payment.hasFee, - fee: journalFromPrisma.invoice.payment.fee, - method: journalFromPrisma.invoice.payment.method, - period: convertStringToPaymentPeriodType(journalFromPrisma.invoice.payment.period), - installmentPeriod: journalFromPrisma.invoice.payment.installmentPeriod, - alreadyPaid: journalFromPrisma.invoice.payment.alreadyPaid, - status: convertStringToPaymentStatusType(journalFromPrisma.invoice.payment.status), - progress: journalFromPrisma.invoice.payment.progress, - }, - } - : ({} as IInvoice); - - const voucher: IVoucherDataForSavingToDB = journalFromPrisma.voucher - ? { - journalId: journalFromPrisma.id, - lineItems: journalFromPrisma.voucher.lineItems.map((lineItem) => { - return { - lineItemIndex: lineItem.id.toString(), - amount: lineItem.amount, - debit: lineItem.debit, - account: `${lineItem.account.code} - ${lineItem.account.name}`, - description: lineItem.description, - accountId: lineItem.account.id, - }; - }), - } - : ({} as IVoucherDataForSavingToDB); - - assertIsJournalEvent(journalFromPrisma.event); + assertIsJournalEvent(invoiceVoucherJournal.voucher?.status); return { - id: journalFromPrisma.id, - tokenContract: journalFromPrisma.tokenContract || '', - tokenId: journalFromPrisma.tokenId || '', - aichResultId: journalFromPrisma.aichResultId || '', - projectId: projectId || 0, - contractId: contractId || 0, - imageUrl: imageUrl || '', - event: journalFromPrisma.event, - invoice, - voucher, + id: invoiceVoucherJournal.journalId, + tokenContract: '', + tokenId: '', + aichResultId: invoiceVoucherJournal.journal?.aichResultId || '', + projectId: 0, + contractId: 0, + imageUrl: invoiceVoucherJournal.invoice?.certificate.file.url || '', + event: invoiceVoucherJournal.voucher.status, + invoice: formatIInvoice(invoiceVoucherJournal), + voucher: { + journalId: invoiceVoucherJournal.journalId, + lineItems: invoiceVoucherJournal.voucher.lineItems.map((lineItem) => { + return { + lineItemIndex: lineItem.id.toString(), + amount: lineItem.amount, + debit: lineItem.debit, + account: `${lineItem.account.code} - ${lineItem.account.name}`, + description: lineItem.description, + accountId: lineItem.account.id, + }; + }), + }, }; } diff --git a/src/lib/utils/repo/beta_transition.repo.ts b/src/lib/utils/repo/beta_transition.repo.ts new file mode 100644 index 000000000..96d8c80c9 --- /dev/null +++ b/src/lib/utils/repo/beta_transition.repo.ts @@ -0,0 +1,714 @@ +import prisma from '@/client'; +import { EventType, ProgressStatus } from '@/constants/account'; +import { DEFAULT_PAGE_LIMIT } from '@/constants/config'; +import { InvoiceType } from '@/constants/invoice'; +import { JOURNAL_EVENT, SortBy } from '@/constants/journal'; +import { SortOrder } from '@/constants/sort'; +import { STATUS_MESSAGE } from '@/constants/status_code'; +import { IInvoice } from '@/interfaces/invoice'; +import { + Prisma, + InvoiceVoucherJournal, + Account, + Journal, + Invoice, + Voucher, + LineItem, + Ocr, + Certificate, + File, +} from '@prisma/client'; +import { calculateTotalPages, timestampInSeconds } from '@/lib/utils/common'; +import { loggerError } from '@/lib/utils/logger_back'; +import { getLatestVoucherNoInPrisma } from './voucher.repo'; + +export async function listInvoiceVoucherJournal( + companyId: number, + journalEvent?: JOURNAL_EVENT, + eventType: string | undefined = undefined, + page: number = 1, + pageSize: number = DEFAULT_PAGE_LIMIT, + sortBy: SortBy = SortBy.CREATED_AT, + sortOrder: SortOrder = SortOrder.DESC, + startDateInSecond?: number, + endDateInSecond?: number, + searchQuery?: string +) { + try { + const where: Prisma.InvoiceVoucherJournalWhereInput = { + voucher: { + companyId, + type: eventType, + status: journalEvent, + }, + createdAt: { + gte: startDateInSecond, + lte: endDateInSecond, + }, + AND: [ + { OR: [{ deletedAt: 0 }, { deletedAt: null }] }, + { + OR: [ + { vendorOrSupplier: { contains: searchQuery, mode: 'insensitive' } }, + { description: { contains: searchQuery, mode: 'insensitive' } }, + { voucher: { no: { contains: searchQuery, mode: 'insensitive' } } }, + ], + }, + ], + }; + + const totalCount = await prisma.invoiceVoucherJournal.count({ where }); + const totalPages = calculateTotalPages(totalCount, pageSize); + + if (totalPages > 0 && (page < 1 || page > totalPages)) { + throw new Error(STATUS_MESSAGE.INVALID_INPUT_PARAMETER); + } + + const orderBy = + sortBy === SortBy.PAYMENT_PRICE + ? { invoice: { totalPrice: sortOrder } } + : { [sortBy]: sortOrder }; + + const include = { + journal: true, + invoice: true, + voucher: { include: { lineItems: { include: { account: true } } } }, + }; + + const skip = (page - 1) * pageSize; + + const findManyArgs = { + where, + orderBy, + include, + take: pageSize + 1, + skip, + }; + + const journalList = await prisma.invoiceVoucherJournal.findMany(findManyArgs); + + const hasNextPage = journalList.length > pageSize; + const hasPreviousPage = page > 1; + + if (journalList.length > pageSize) { + journalList.pop(); + } + + const sort: { + sortBy: string; + sortOrder: string; + }[] = [{ sortBy, sortOrder }]; + + const paginatedJournalList = { + data: journalList, + page, + totalPages, + totalCount, + pageSize, + hasNextPage, + hasPreviousPage, + sort, + }; + + return paginatedJournalList; + } catch (error) { + const logError = loggerError( + 0, + 'List invoice voucher journal in listInvoiceVoucherJournal failed', + error as Error + ); + logError.error('Func. listInvoiceVoucherJournal in journal.repo.ts failed'); + throw new Error(STATUS_MESSAGE.DATABASE_READ_FAILED_ERROR); + } +} + +export async function getInvoiceVoucherJournalByInvoiceId( + companyId: number, + invoiceId: number +): Promise< + InvoiceVoucherJournal & { + journal: Journal | null; + invoice: (Invoice & { certificate: Certificate & { file: File } }) | null; + voucher: (Voucher & { lineItems: (LineItem & { account: Account })[] }) | null; + } +> { + const where: Prisma.InvoiceVoucherJournalWhereInput = { + invoiceId, + voucher: { + companyId, + }, + }; + const include = { + journal: true, + invoice: { include: { certificate: { include: { file: true } } } }, + voucher: { include: { lineItems: { include: { account: true } } } }, + }; + const invoiceVoucherJournal = await prisma.invoiceVoucherJournal.findFirst({ where, include }); + if (!invoiceVoucherJournal) { + throw new Error(STATUS_MESSAGE.RESOURCE_NOT_FOUND); + } + return invoiceVoucherJournal; +} + +export async function getInvoiceVoucherJournalByJournalId(journalId: number): Promise< + | (InvoiceVoucherJournal & { + journal: Journal | null; + invoice: (Invoice & { certificate: Certificate & { file: File } }) | null; + voucher: (Voucher & { lineItems: (LineItem & { account: Account })[] }) | null; + }) + | null +> { + const where: Prisma.InvoiceVoucherJournalWhereInput = { + journalId, + }; + const include = { + journal: true, + invoice: { include: { certificate: { include: { file: true } } } }, + voucher: { include: { lineItems: { include: { account: true } } } }, + }; + const invoiceVoucherJournal = await prisma.invoiceVoucherJournal.findFirst({ + where, + include, + }); + return invoiceVoucherJournal; +} + +export async function createInvoice( + formattedInvoice: IInvoice, + companyId: number, + imageFileId: number = 555 +) { + const now = Date.now(); + const nowTimestamp = timestampInSeconds(now); + const file = await prisma.file.findUnique({ + where: { + id: imageFileId, + }, + }); + let certificate: Certificate | null; + certificate = await prisma.certificate.findUnique({ + where: { + fileId: file?.id, + }, + }); + if (!certificate) { + certificate = await prisma.certificate.create({ + data: { + companyId, + fileId: imageFileId, + createdAt: nowTimestamp, + updatedAt: nowTimestamp, + }, + }); + } + // Info: (20240916 - Jacky) default invoice type is PURCHASE_TRIPLICATE_AND_ELECTRONIC + let invoiceType = InvoiceType.PURCHASE_TRIPLICATE_AND_ELECTRONIC; + // Info: (20240916 - Jacky) if eventType is INCOME, then invoice type is SALES_TRIPLICATE_INVOICE + if (formattedInvoice.eventType === EventType.INCOME) { + invoiceType = InvoiceType.SALES_TRIPLICATE_INVOICE; + } + const data: Prisma.InvoiceCreateInput = { + date: formattedInvoice.date, + inputOrOutput: 'output', + no: 'no', + currencyAlias: 'TWD', + priceBeforeTax: + formattedInvoice.payment.price - + formattedInvoice.payment.price * (formattedInvoice.payment.taxPercentage / 100), + taxType: 'taxable', + deductible: true, + counterParty: { connect: { id: 555 } }, + taxRatio: formattedInvoice.payment.taxPercentage, + taxPrice: formattedInvoice.payment.price * (formattedInvoice.payment.taxPercentage / 100), + totalPrice: formattedInvoice.payment.price, + type: invoiceType, + certificate: { connect: { id: certificate.id } }, + createdAt: nowTimestamp, + updatedAt: nowTimestamp, + }; + + let invoice: Invoice; + + try { + invoice = await prisma.invoice.create({ + data, + }); + } catch (error) { + const logError = loggerError(0, 'Create invoice in createInvoice failed', error as Error); + logError.error('Prisma create invoice in createInvoice in invoice.repo.ts failed'); + throw new Error(STATUS_MESSAGE.DATABASE_CREATE_FAILED_ERROR); + } + + return invoice; +} + +export async function createVoucher(voucherNo: string, companyId: number, date: number) { + const now = Date.now(); + const nowTimestamp = timestampInSeconds(now); + const data: Prisma.VoucherCreateInput = { + company: { + connect: { + id: companyId, + }, + }, + date, + issuer: { + connect: { + id: 1000, // ToDo: (20241011 - Jacky) need to change to real issuer id + }, + }, + counterparty: { + connect: { + id: 1000, // ToDo: (20241011 - Jacky) need to change to real counterparty id + }, + }, + no: voucherNo, + status: JOURNAL_EVENT.UPLOADED, + createdAt: nowTimestamp, + updatedAt: nowTimestamp, + }; + + let voucher: Voucher; + + try { + voucher = await prisma.voucher.create({ + data, + }); + } catch (error) { + const logError = loggerError(0, 'Create voucher in createVoucher failed', error as Error); + logError.error('Prisma create voucher in createVoucher in invoice.repo.ts failed'); + throw new Error(STATUS_MESSAGE.DATABASE_CREATE_FAILED_ERROR); + } + + return voucher; +} + +export async function createInvoiceVoucherJournal( + journalId: number, + invoiceId: number, + voucherId?: number +) { + const now = Date.now(); + const nowTimestamp = timestampInSeconds(now); + const data: Prisma.InvoiceVoucherJournalCreateInput = { + journal: { + connect: { + id: journalId, + }, + }, + invoice: { + connect: { + id: invoiceId, + }, + }, + voucher: voucherId + ? { + connect: { + id: voucherId, + }, + } + : undefined, + description: 'description', + vendorOrSupplier: 'vendorOrSupplier', + paymentReason: 'paymentReason', + createdAt: nowTimestamp, + updatedAt: nowTimestamp, + }; + + let invoiceVoucherJournal: InvoiceVoucherJournal; + + try { + invoiceVoucherJournal = await prisma.invoiceVoucherJournal.create({ + data, + }); + } catch (error) { + const logError = loggerError( + 0, + 'Create invoice voucher journal in createInvoiceVoucherJournal failed', + error as Error + ); + logError.error( + 'Prisma create invoice voucher journal in createInvoiceVoucherJournal in invoice.repo.ts failed' + ); + throw new Error(STATUS_MESSAGE.DATABASE_CREATE_FAILED_ERROR); + } + + return invoiceVoucherJournal; +} + +export async function deleteInvoiceVoucherJournal(journalId: number, companyId: number) { + const now = Date.now(); + const nowTimestamp = timestampInSeconds(now); + const journal = await prisma.invoiceVoucherJournal.findFirst({ + where: { + journalId, + voucher: { + companyId, + }, + }, + }); + if (!journal) { + throw new Error(STATUS_MESSAGE.RESOURCE_NOT_FOUND); + } + const deletedInvoiceVoucherJournal = await prisma.invoiceVoucherJournal.update({ + where: { + id: journal.id, + }, + data: { + deletedAt: nowTimestamp, + invoice: { + update: { + deletedAt: nowTimestamp, + }, + }, + voucher: { + update: { + deletedAt: nowTimestamp, + }, + }, + journal: { + update: { + deletedAt: nowTimestamp, + }, + }, + }, + include: { + journal: true, + invoice: { include: { certificate: { include: { file: true } } } }, + voucher: { include: { lineItems: { include: { account: true } } } }, + }, + }); + return deletedInvoiceVoucherJournal; +} + +export async function updateInvoice(formattedInvoice: IInvoice) { + const now = Date.now(); + const nowTimestamp = timestampInSeconds(now); + const invoiceVoucherJournal = await getInvoiceVoucherJournalByJournalId( + formattedInvoice.journalId || 0 + ); + if (!invoiceVoucherJournal || !invoiceVoucherJournal.invoice) { + throw new Error(STATUS_MESSAGE.RESOURCE_NOT_FOUND); + } + // Info: (20240916 - Jacky) default invoice type is PURCHASE_TRIPLICATE_AND_ELECTRONIC + let invoiceType = InvoiceType.PURCHASE_TRIPLICATE_AND_ELECTRONIC; + // Info: (20240916 - Jacky) if eventType is INCOME, then invoice type is SALES_TRIPLICATE_INVOICE + if (formattedInvoice.eventType === EventType.INCOME) { + invoiceType = InvoiceType.SALES_TRIPLICATE_INVOICE; + } + await prisma.invoiceVoucherJournal.update({ + where: { + id: invoiceVoucherJournal.id, + }, + data: { + invoice: { + update: { + date: formattedInvoice.date, + inputOrOutput: 'output', + no: 'no', + currencyAlias: 'TWD', + priceBeforeTax: + formattedInvoice.payment.price - + formattedInvoice.payment.price * (formattedInvoice.payment.taxPercentage / 100), + taxType: 'taxable', + deductible: true, + taxRatio: formattedInvoice.payment.taxPercentage, + taxPrice: formattedInvoice.payment.price * (formattedInvoice.payment.taxPercentage / 100), + totalPrice: formattedInvoice.payment.price, + type: invoiceType, + updatedAt: nowTimestamp, + }, + }, + }, + include: { + journal: true, + invoice: true, + voucher: { include: { lineItems: { include: { account: true } } } }, + }, + }); + return invoiceVoucherJournal.invoiceId; +} + +export async function updateJournal( + journalId: number, + aichResultId: string, + projectId: number, + contractId: number +) { + const now = Date.now(); + const nowTimestamp = timestampInSeconds(now); + const invoiceVoucherJournal = await getInvoiceVoucherJournalByJournalId(journalId || 0); + const updatedInvoiceVoucherJournal = await prisma.invoiceVoucherJournal.update({ + where: { + id: invoiceVoucherJournal?.id || 0, + }, + data: { + journal: { + update: { + aichResultId, + projectId, + contractId, + updatedAt: nowTimestamp, + }, + }, + }, + include: { + journal: true, + invoice: true, + voucher: { include: { lineItems: { include: { account: true } } } }, + }, + }); + return updatedInvoiceVoucherJournal.journalId; +} + +export async function handlePrismaUpdateLogic(formattedInvoice: IInvoice, aichResultId: string) { + const { journalId, projectId, contractId } = formattedInvoice; + if (!journalId) { + throw new Error(STATUS_MESSAGE.INVALID_INPUT_TYPE); + } + + let journalIdBeUpdated: number = -1; + try { + const journalInDB = await getInvoiceVoucherJournalByJournalId(journalId); + + if (!journalInDB || !journalInDB.invoice) { + throw new Error(STATUS_MESSAGE.RESOURCE_NOT_FOUND); + } + + const invoiceIdToBeUpdated = journalInDB.invoice.id; + + if (!invoiceIdToBeUpdated) { + throw new Error(STATUS_MESSAGE.RESOURCE_NOT_FOUND); + } + + const invoiceBeUpdated = await updateInvoice(formattedInvoice); + + if (invoiceBeUpdated === -1) { + throw new Error(STATUS_MESSAGE.DATABASE_UPDATE_FAILED_ERROR); + } + + journalIdBeUpdated = await updateJournal( + journalId, + aichResultId, + projectId || 0, + contractId || 0 + ); + } catch (error) { + const logError = loggerError(0, 'handlePrismaUpdateLogic failed', error as Error); + logError.error('Prisma related func. in handlePrismaUpdateLogic in invoice.repo.ts failed'); + } + + return journalIdBeUpdated; +} + +export async function findUniqueOcrInPrisma(ocrId: number | undefined): Promise<{ + id: number; + imageFileId: number; +} | null> { + if (!ocrId) { + return null; + } + let ocrIdInDB: { + id: number; + imageFileId: number; + } | null; + + try { + ocrIdInDB = await prisma.ocr.findUnique({ + where: { + id: ocrId, + OR: [{ deletedAt: 0 }, { deletedAt: null }], + }, + select: { + id: true, + imageFileId: true, + }, + }); + } catch (error) { + const logError = loggerError( + 0, + 'find unique ocr in findUniqueOcrInPrisma failed', + error as Error + ); + logError.error( + 'Prisma related find unique ocr in findUniqueOcrInPrisma in invoice.repo.ts failed' + ); + throw new Error(STATUS_MESSAGE.DATABASE_READ_FAILED_ERROR); + } + return ocrIdInDB; +} + +export async function updateOcrStatusInPrisma(ocrId: number, status: ProgressStatus) { + const now = Date.now(); + const updatedAt = timestampInSeconds(now); + + let ocr: Ocr; + + try { + ocr = await prisma.ocr.update({ + where: { + id: ocrId, + }, + data: { + status, + updatedAt, + }, + }); + } catch (error) { + const logError = loggerError( + 0, + 'update ocr status in updateOcrStatusInPrisma failed', + error as Error + ); + logError.error( + 'Prisma related update ocr status in updateOcrStatusInPrisma in invoice.repo.ts failed' + ); + throw new Error(STATUS_MESSAGE.DATABASE_UPDATE_FAILED_ERROR); + } + + return ocr; +} + +export async function createJournalInPrisma( + projectId: number | null, + aichResultId: string, + contractId: number | null, + companyId: number, + event: JOURNAL_EVENT = JOURNAL_EVENT.UPLOADED +) { + const now = Date.now(); + const nowTimestamp = timestampInSeconds(now); + const data: Prisma.JournalCreateInput = { + company: { + connect: { + id: companyId, + }, + }, + aichResultId, + event, + createdAt: nowTimestamp, + updatedAt: nowTimestamp, + }; + + if (projectId !== null) { + data.project = { + connect: { + id: projectId, + }, + }; + } + + if (contractId !== null) { + data.contract = { + connect: { + id: contractId, + }, + }; + } + + let journal: { + id: number; + }; + + try { + journal = await prisma.journal.create({ + data, + select: { + id: true, + }, + }); + } catch (error) { + const logError = loggerError( + 0, + 'create journal in createJournalInPrisma failed', + error as Error + ); + logError.error( + 'Prisma related create journal in createJournalInPrisma in invoice.repo.ts failed' + ); + throw new Error(STATUS_MESSAGE.DATABASE_CREATE_FAILED_ERROR); + } + + return journal.id; +} + +export async function handlePrismaSavingLogic( + formattedInvoice: IInvoice, + aichResultId: string, + companyId: number, + ocrId: number | undefined +) { + try { + const { projectId, contractId } = formattedInvoice; + + let journalIdBeCreated: number = -1; + + try { + const ocrIdInDB = await findUniqueOcrInPrisma(ocrId); + + journalIdBeCreated = await createJournalInPrisma( + projectId, + aichResultId, + contractId, + companyId + ); + const createdInvoice = await createInvoice( + formattedInvoice, + journalIdBeCreated, + ocrIdInDB?.imageFileId + ); + const newVoucherNo = await getLatestVoucherNoInPrisma(companyId); + const createdVoucher = await createVoucher(newVoucherNo, companyId, formattedInvoice.date); + + await createInvoiceVoucherJournal(journalIdBeCreated, createdInvoice.id, createdVoucher.id); + // Info: (20240524 - Murky) 更新ocr的狀態, 等到其他db操作都沒有錯誤後才更新 + if (ocrIdInDB?.id) { + await updateOcrStatusInPrisma(ocrIdInDB.id, ProgressStatus.HAS_BEEN_USED); + } + } catch (error) { + const logError = loggerError(0, 'handlePrismaSavingLogic failed', error as Error); + logError.error( + 'Prisma related func. in handlePrismaSavingLogic in beta_transition.repo.ts failed' + ); + } + + return journalIdBeCreated; + } catch (error) { + const logError = loggerError(0, 'handlePrismaSavingLogic failed', error as Error); + logError.error( + 'Prisma related func. in handlePrismaSavingLogic in beta_transition.repo.ts failed' + ); + throw new Error(STATUS_MESSAGE.DATABASE_CREATE_FAILED_ERROR); + } +} + +export async function listInvoiceVoucherJournalFor401( + companyId: number, + startDateInSecond: number, + endDateInSecond: number +) { + const where: Prisma.InvoiceVoucherJournalWhereInput = { + voucher: { + companyId, + status: JOURNAL_EVENT.UPCOMING, + }, + createdAt: { + gte: startDateInSecond, + lte: endDateInSecond, + }, + AND: [ + { OR: [{ deletedAt: 0 }, { deletedAt: null }] }, + { invoice: { date: { gte: startDateInSecond, lte: endDateInSecond } } }, + ], + }; + const include = { + journal: true, + invoice: true, + voucher: { include: { lineItems: { include: { account: true } } } }, + }; + const journalList = await prisma.invoiceVoucherJournal.findMany({ + where, + include, + }); + return journalList; +} diff --git a/src/lib/utils/repo/folder.repo.ts b/src/lib/utils/repo/folder.repo.ts index e2d93880c..2b5ecaf43 100644 --- a/src/lib/utils/repo/folder.repo.ts +++ b/src/lib/utils/repo/folder.repo.ts @@ -109,11 +109,7 @@ export async function getFolderContent( select: { no: true, createdAt: true, - journal: { - select: { - event: true, - }, - }, + type: true, }, }, }, @@ -147,14 +143,14 @@ export async function getFolderContent( }; }); - assertIsJournalEvent(voucher.voucher.journal.event); + assertIsJournalEvent(voucher.voucher.type); const folderContent = { id: folderId, name: salaryRecordList[0].voucherSalaryRecordFolder.name, createdAt: salaryRecordList[0].voucherSalaryRecordFolder.createdAt, voucher: { id: voucherIdNumber, - event: voucher.voucher.journal.event, + event: voucher.voucher.type, date: voucher.voucher.createdAt, type: 'Payment', particulars: 'Salary Bookkeeping', diff --git a/src/lib/utils/repo/invoice.beta.repo.ts b/src/lib/utils/repo/invoice.beta.repo.ts deleted file mode 100644 index 20ec42002..000000000 --- a/src/lib/utils/repo/invoice.beta.repo.ts +++ /dev/null @@ -1,348 +0,0 @@ -// Info: (20240807 - Murky) This repo is for beta version, meant to congregate all invoice repo related functions -import prisma from '@/client'; -import { DEFAULT_PAGE_NUMBER } from '@/constants/display'; -import { IInvoiceBeta, IInvoiceIncludePaymentJournal } from '@/interfaces/invoice'; -import { - calculateTotalPages, - getTimestampNow, - pageToOffset, - timestampInSeconds, -} from '@/lib/utils/common'; -import { Prisma } from '@prisma/client'; -import { IPaginatedData } from '@/interfaces/pagination'; -import { SortBy } from '@/constants/journal'; -import { STATUS_MESSAGE } from '@/constants/status_code'; -import { SortOrder } from '@/constants/sort'; -import loggerBack, { loggerError } from '@/lib/utils/logger_back'; - -/** - * This function can find Unique Invoice by invoiceId, companyId is optional - * @param {number} invoiceId you want to find - * @param {number | undefined} companyId if you want to add more condition, use companyId, otherwise is undefined - * @returns {Promise} return include payment and journal, will be null if not found or error - */ -export async function findUniqueInvoiceById( - invoiceId: number, - companyId?: number -): Promise { - let invoice: IInvoiceIncludePaymentJournal | null = null; - - const where: Prisma.InvoiceWhereUniqueInput = { - id: invoiceId, - journal: { - companyId, - }, - }; - - const include = { - payment: true, - journal: { - include: { - project: true, - contract: true, - }, - }, - }; - - try { - invoice = await prisma.invoice.findUnique({ - where, - include, - }); - - if (!invoice) { - loggerBack.info('No invoice found in findUniqueInvoiceById'); - } - } catch (error) { - const logError = loggerError( - 0, - 'Find unique in invoice in findUniqueInvoiceById failed', - error as Error - ); - logError.error('Prisma related func. findUniqueInvoiceById in invoice.beta.repo.ts failed'); - } - return invoice; -} - -/** - * Create invoice by invoiceData, connect to paymentId and journalId, imageUrl is optional - * @param {IInvoiceBeta} invoiceData Invoice Data to be created - * @param {number} paymentId Payment Id to be connected - * @param {number} journalId Journal Id to be connected - * @param {string | undefined} imageUrl (Optional) Image(invoice) Url to be shown - * @returns {Promise} return include payment and journal, will be null if not found or error - */ -export async function createInvoice( - invoiceData: IInvoiceBeta, - paymentId: number, - journalId: number, - imageFileId?: number -) { - const nowInSecond = getTimestampNow(); - const invoiceCreatedDate = timestampInSeconds(invoiceData.date); - - let invoiceBeCreated: IInvoiceIncludePaymentJournal | null = null; - - const paymentConnect: Prisma.PaymentCreateNestedOneWithoutInvoiceInput = { - connect: { - id: paymentId, - }, - }; - - const journalConnect: Prisma.JournalCreateNestedOneWithoutInvoiceInput = { - connect: { - id: journalId, - }, - }; - const dataToBeCreated: Prisma.InvoiceCreateInput = { - number: invoiceData.number, - type: invoiceData.type, - date: invoiceCreatedDate, - eventType: invoiceData.eventType, - paymentReason: invoiceData.paymentReason, - description: invoiceData.description, - vendorTaxId: invoiceData.vendorTaxId, - vendorOrSupplier: invoiceData.vendorOrSupplier, - deductible: invoiceData.deductible, - imageFile: { - connect: { - id: imageFileId, - }, - }, - createdAt: nowInSecond, - updatedAt: nowInSecond, - payment: paymentConnect, - journal: journalConnect, - }; - - const include = { - payment: true, - journal: { - include: { - project: true, - contract: true, - }, - }, - }; - - const invoiceCreateArgs = { - data: dataToBeCreated, - include, - }; - - try { - invoiceBeCreated = await prisma.invoice.create(invoiceCreateArgs); - } catch (error) { - const logError = loggerError(0, 'Create invoice in createInvoice failed', error as Error); - logError.error('Prisma related invoice creation in invoice.beta.repo.ts failed'); - } - - return invoiceBeCreated; -} - -/** - * Update invoice by invoiceData, connect to paymentId and journalId and update imageUrl - * @param {IInvoiceBeta} invoiceData Invoice Data to be created - * @param {string | undefined} imageUrl (Optional) Image(invoice) Url to be shown - * @param {number} paymentId (Optional) Payment Id to be connected - * @param {number} journalId (Optional) Journal Id to be connected - * @returns {Promise} return include payment and journal, will be null if not found or error - */ -export async function updateInvoice( - invoiceId: number, - invoiceData: IInvoiceBeta, - imageFileId?: number, - paymentId?: number, - journalId?: number -) { - const invoiceUpdatedDate = timestampInSeconds(invoiceData.date); - - let invoiceBeUpdated: IInvoiceIncludePaymentJournal | null = null; - - const paymentConnect: Prisma.PaymentUpdateOneRequiredWithoutInvoiceNestedInput = { - connect: { - id: paymentId, - }, - }; - - const journalConnect: Prisma.JournalUpdateOneRequiredWithoutInvoiceNestedInput = { - connect: { - id: journalId, - }, - }; - - const dataToBeUpdated: Prisma.InvoiceUpdateInput = { - number: invoiceData.number, - type: invoiceData.type, - date: invoiceUpdatedDate, - eventType: invoiceData.eventType, - paymentReason: invoiceData.paymentReason, - description: invoiceData.description, - vendorTaxId: invoiceData.vendorTaxId, - vendorOrSupplier: invoiceData.vendorOrSupplier, - imageFile: { - connect: { - id: imageFileId, - }, - }, - payment: paymentConnect, - journal: journalConnect, - }; - - if (paymentId !== undefined) { - dataToBeUpdated.payment = paymentConnect; - } - - if (journalId !== undefined) { - dataToBeUpdated.journal = journalConnect; - } - - const include = { - payment: true, - journal: { - include: { - project: true, - contract: true, - }, - }, - }; - - const invoiceUpdateArgs = { - where: { - id: invoiceId, - }, - data: dataToBeUpdated, - include, - }; - - try { - invoiceBeUpdated = await prisma.invoice.update(invoiceUpdateArgs); - } catch (error) { - const logError = loggerError(0, 'Update invoice in updateInvoice failed', error as Error); - logError.error('Prisma related invoice update in invoice.beta.repo.ts failed'); - } - - return invoiceBeUpdated; -} - -/** - * list invoices, return paginated data - * @param {number} companyId company id to find invoices - * @param {number} page (optional) page number, default is 1 - * @param {number} pageSize (optional) how many records per page, default is 10 - * @param {SortBy} sortBy (optional) sort by field, default is created at, check constants/journal.ts for more details - * @param {SortOrder} sortOrder (optional) sort order, default is newest first, check constants/journal.ts for more details - * @param {string} eventType (optional) event type - * @param {number} startDateInSecond (optional) start date in second - * @param {number} endDateInSecond (optional) end date in second - * @param {string} searchQuery (optional) search query, it - * @returns {Promise>} return paginated data of invoices - */ -export async function listInvoice({ - companyId, - page = DEFAULT_PAGE_NUMBER, - pageSize = DEFAULT_PAGE_NUMBER, - sortBy = SortBy.CREATED_AT, - sortOrder = SortOrder.DESC, - eventType = undefined, - startDateInSecond = undefined, - endDateInSecond = undefined, - searchQuery = undefined, -}: { - companyId: number; - page: number; - pageSize: number; - sortBy: SortBy; - sortOrder: SortOrder; - eventType?: string; - startDateInSecond?: number; - endDateInSecond?: number; - searchQuery?: string; -}): Promise> { - let invoices: IInvoiceIncludePaymentJournal[] = []; - const where: Prisma.InvoiceWhereInput = { - journal: { - companyId, - }, - eventType, - createdAt: { - gte: startDateInSecond, - lte: endDateInSecond, - }, - AND: [ - { OR: [{ deletedAt: 0 }, { deletedAt: null }] }, - { - OR: [ - { number: { contains: searchQuery, mode: 'insensitive' } }, - { vendorTaxId: { contains: searchQuery, mode: 'insensitive' } }, - { vendorOrSupplier: { contains: searchQuery, mode: 'insensitive' } }, - { description: { contains: searchQuery, mode: 'insensitive' } }, - ], - }, - ], - }; - - const totalCount = await prisma.invoice.count({ where }); - const totalPages = calculateTotalPages(totalCount, pageSize); - - if (totalPages > 0 && (page < 1 || page > totalPages)) { - throw new Error(STATUS_MESSAGE.INVALID_INPUT_PARAMETER); - } - - const orderBy = - sortBy === SortBy.PAYMENT_PRICE - ? { invoice: { payment: { price: sortOrder } } } - : { [sortBy]: sortOrder }; - - const include = { - payment: true, - journal: { - include: { - project: true, - contract: true, - }, - }, - }; - - const skip = pageToOffset(page, pageSize); - - const findManyArgs = { - where, - orderBy, - include, - take: pageSize + 1, - skip, - }; - - try { - invoices = await prisma.invoice.findMany(findManyArgs); - } catch (error) { - const logError = loggerError(0, 'Find many in invoice in listInvoice failed', error as Error); - logError.error('Prisma related func. findMany in listInvoice in invoice.beta.repo.ts failed'); - } - - const hasNextPage = invoices.length > pageSize; - const hasPreviousPage = page > 1; - - if (invoices.length > pageSize) { - invoices.pop(); // Info: (202040808 - Jacky) 移除多餘的記錄 - } - - const sort: { - sortBy: string; // Info: (202040808 - Jacky) 排序欄位的鍵 - sortOrder: string; // Info: (202040808 - Jacky) 排序欄位的值 - }[] = [{ sortBy, sortOrder }]; - - const paginatedInvoiceList = { - data: invoices, - page, - totalPages, - totalCount, - pageSize, - hasNextPage, - hasPreviousPage, - sort, - }; - - return paginatedInvoiceList; -} diff --git a/src/lib/utils/repo/invoice.repo.ts b/src/lib/utils/repo/invoice.repo.ts deleted file mode 100644 index 9620486b2..000000000 --- a/src/lib/utils/repo/invoice.repo.ts +++ /dev/null @@ -1,619 +0,0 @@ -// Info (20240526 - Murky): Prisma - -import prisma from '@/client'; -import { ProgressStatus } from '@/constants/account'; -import { JOURNAL_EVENT } from '@/constants/journal'; -import { STATUS_MESSAGE } from '@/constants/status_code'; -import { IInvoice, IInvoiceBeta, IInvoiceIncludePaymentJournal } from '@/interfaces/invoice'; -import { IPayment, IPaymentBeta } from '@/interfaces/payment'; -import { timestampInSeconds } from '@/lib/utils/common'; -import { Ocr, Prisma } from '@prisma/client'; -import loggerBack, { loggerError } from '@/lib/utils/logger_back'; - -export async function findUniqueOcrInPrisma(ocrId: number | undefined): Promise<{ - id: number; - imageFileId: number; -} | null> { - if (!ocrId) { - return null; - } - let ocrIdInDB: { - id: number; - imageFileId: number; - } | null; - - try { - ocrIdInDB = await prisma.ocr.findUnique({ - where: { - id: ocrId, - OR: [{ deletedAt: 0 }, { deletedAt: null }], - }, - select: { - id: true, - imageFileId: true, - }, - }); - } catch (error) { - const logError = loggerError( - 0, - 'find unique ocr in findUniqueOcrInPrisma failed', - error as Error - ); - logError.error( - 'Prisma related find unique ocr in findUniqueOcrInPrisma in invoice.repo.ts failed' - ); - throw new Error(STATUS_MESSAGE.DATABASE_READ_FAILED_ERROR); - } - - return ocrIdInDB; -} - -export async function updateOcrStatusInPrisma(ocrId: number, status: ProgressStatus) { - const now = Date.now(); - const updatedAt = timestampInSeconds(now); - - let ocr: Ocr; - - try { - ocr = await prisma.ocr.update({ - where: { - id: ocrId, - }, - data: { - status, - updatedAt, - }, - }); - } catch (error) { - const logError = loggerError( - 0, - 'update ocr status in updateOcrStatusInPrisma failed', - error as Error - ); - logError.error( - 'Prisma related update ocr status in updateOcrStatusInPrisma in invoice.repo.ts failed' - ); - throw new Error(STATUS_MESSAGE.DATABASE_UPDATE_FAILED_ERROR); - } - - return ocr; -} - -export async function findUniqueCompanyInPrisma(companyId: number) { - let company: { - id: number; - } | null = null; - - try { - company = await prisma.company.findUnique({ - where: { id: companyId, OR: [{ deletedAt: 0 }, { deletedAt: null }] }, - select: { id: true }, - }); - } catch (error) { - const logError = loggerError( - 0, - 'find unique company in findUniqueCompanyInPrisma failed', - error as Error - ); - logError.error( - 'Prisma related find unique company in findUniqueCompanyInPrisma in invoice.repo.ts failed' - ); - throw new Error(STATUS_MESSAGE.DATABASE_READ_FAILED_ERROR); - } - - if (!company) { - throw new Error(STATUS_MESSAGE.RESOURCE_NOT_FOUND); - } - - return company; -} - -export async function findUniqueJournalInPrisma(journalId: number, companyId?: number) { - let journal: { - id: number; - projectId: number | null; - invoice: { - id: number; - } | null; - } | null; - - try { - journal = await prisma.journal.findUnique({ - where: { id: journalId, companyId, OR: [{ deletedAt: 0 }, { deletedAt: null }] }, - select: { - id: true, - projectId: true, - invoice: { - select: { - id: true, - }, - }, - }, - }); - } catch (error) { - const logError = loggerError( - 0, - 'find unique journal in findUniqueJournalInPrisma failed', - error as Error - ); - logError.error( - 'Prisma related find unique journal in findUniqueJournalInPrisma in invoice.repo.ts failed' - ); - throw new Error(STATUS_MESSAGE.DATABASE_READ_FAILED_ERROR); - } - return journal; -} - -export async function createPaymentInPrisma(paymentData: IPaymentBeta) { - const now = Date.now(); - const nowTimestamp = timestampInSeconds(now); - let payment: { - id: number; - }; - - try { - payment = await prisma.payment.create({ - data: { - ...paymentData, - createdAt: nowTimestamp, - updatedAt: nowTimestamp, - }, - select: { - id: true, - }, - }); - } catch (error) { - const logError = loggerError( - 0, - 'create payment in createPaymentInPrisma failed', - error as Error - ); - logError.error( - 'Prisma related create payment in createPaymentInPrisma in invoice.repo.ts failed' - ); - throw new Error(STATUS_MESSAGE.DATABASE_CREATE_FAILED_ERROR); - } - return payment; -} - -export async function updatePaymentInPrisma(paymentId: number, paymentData: IPayment) { - const now = Date.now(); - const updatedAt = timestampInSeconds(now); - const payment = await prisma.payment.update({ - where: { - id: paymentId, - }, - data: { - ...paymentData, - updatedAt, - }, - select: { - id: true, - }, - }); - return payment; -} - -export async function findUniqueInvoiceInPrisma(invoiceId: number, companyId?: number) { - let invoice: IInvoiceIncludePaymentJournal | null = null; - - const where: Prisma.InvoiceWhereUniqueInput = { - id: invoiceId, - journal: { - companyId, - }, - }; - - const include = { - payment: true, - journal: { - include: { - project: true, - contract: true, - }, - }, - }; - - try { - invoice = await prisma.invoice.findUnique({ - where, - include, - }); - - if (!invoice) { - loggerBack.error('Invoice not found'); - } - } catch (error) { - const logError = loggerError( - 0, - 'find unique invoice in findUniqueInvoiceInPrisma failed', - error as Error - ); - logError.error( - 'Prisma related find unique invoice in findUniqueInvoiceInPrisma in invoice.repo.ts failed' - ); - throw new Error(STATUS_MESSAGE.DATABASE_READ_FAILED_ERROR); - } - return invoice; -} - -export async function createInvoiceInPrisma( - invoiceData: IInvoiceBeta, - paymentId: number, - journalId: number, - imageFileId: number | undefined -) { - let invoice: { - id: number; - }; - - const now = Date.now(); - const nowTimestamp = timestampInSeconds(now); - const invoiceCreatedDate = timestampInSeconds(invoiceData.date); - try { - invoice = await prisma.invoice.create({ - data: { - number: invoiceData.number, - type: invoiceData.type, - date: invoiceCreatedDate, - eventType: invoiceData.eventType, - paymentReason: invoiceData.paymentReason, - description: invoiceData.description, - vendorTaxId: invoiceData.vendorTaxId, - vendorOrSupplier: invoiceData.vendorOrSupplier, - deductible: invoiceData.deductible, - imageFileId, - paymentId, - journalId, - createdAt: nowTimestamp, - updatedAt: nowTimestamp, - }, - select: { - id: true, - }, - }); - } catch (error) { - const logError = loggerError( - 0, - 'create invoice in createInvoiceInPrisma failed', - error as Error - ); - logError.error( - 'Prisma related create invoice in createInvoiceInPrisma in invoice.repo.ts failed' - ); - throw new Error(STATUS_MESSAGE.DATABASE_CREATE_FAILED_ERROR); - } - - return invoice; -} - -export async function createInvoiceAndPaymentInPrisma( - invoiceData: IInvoiceBeta, - journalId: number, - imageFileId: number | undefined -) { - const paymentData = invoiceData.payment; - // Info (20240807 - Jacky): 這邊是為了讓payment的taxPrice可以被存入prisma - const taxPrice = paymentData.price * paymentData.taxPercentage; - const paymentDataBeta = { ...paymentData, taxPrice }; - - let createdInvoiceId: number; - try { - createdInvoiceId = await prisma.$transaction(async () => { - const payment = await createPaymentInPrisma(paymentDataBeta); - const invoice = await createInvoiceInPrisma(invoiceData, payment.id, journalId, imageFileId); - return invoice.id; - }); - } catch (error) { - const logError = loggerError( - 0, - 'create invoice and payment in createInvoiceAndPaymentInPrisma failed', - error as Error - ); - logError.error( - 'Prisma related create invoice or payment in createInvoiceAndPaymentInPrisma in invoice.repo.ts failed' - ); - throw new Error(STATUS_MESSAGE.DATABASE_CREATE_FAILED_ERROR); - } - - return createdInvoiceId; -} - -export async function updateInvoiceInPrisma( - invoiceId: number, - paymentId: number, - invoiceData: IInvoice, - journalId: number, - imageFileId: number | undefined -) { - let invoice: { - id: number; - }; - - const now = Date.now(); - const updatedAt = timestampInSeconds(now); - const invoiceCreatedDate = timestampInSeconds(invoiceData.date); - - try { - invoice = await prisma.invoice.update({ - where: { - id: invoiceId, - }, - data: { - date: invoiceCreatedDate, - eventType: invoiceData.eventType, - paymentReason: invoiceData.paymentReason, - description: invoiceData.description, - vendorOrSupplier: invoiceData.vendorOrSupplier, - updatedAt, - imageFileId, - paymentId, - journalId, - }, - }); - } catch (error) { - const logError = loggerError( - 0, - 'update invoice in updateInvoiceInPrisma failed', - error as Error - ); - logError.error( - 'Prisma related update invoice in updateInvoiceInPrisma in invoice.repo.ts failed' - ); - throw new Error(STATUS_MESSAGE.DATABASE_UPDATE_FAILED_ERROR); - } - - return invoice; -} - -export async function updateInvoiceAndPaymentInPrisma( - invoiceIdToBeUpdated: number, - invoiceData: IInvoice, - journalId: number, - imageFileId?: number -) { - const paymentData = invoiceData.payment; - - let updatedInvoiceId: number = -1; - - try { - const invoiceInDB = await findUniqueInvoiceInPrisma(invoiceIdToBeUpdated); - - if (!invoiceInDB) { - throw new Error(STATUS_MESSAGE.DATABASE_UPDATE_FAILED_ERROR); - } - - const payment = await updatePaymentInPrisma(invoiceInDB.paymentId, paymentData); - const invoice = await updateInvoiceInPrisma( - invoiceIdToBeUpdated, - payment.id, - invoiceData, - journalId, - imageFileId - ); - - updatedInvoiceId = invoice.id; - } catch (error) { - const logError = loggerError( - 0, - 'update invoice and payment in updateInvoiceAndPaymentInPrisma failed', - error as Error - ); - logError.error( - 'Prisma related update invoice or payment in updateInvoiceAndPaymentInPrisma in invoice.repo.ts failed' - ); - } - return updatedInvoiceId; -} - -export async function createJournalInPrisma( - projectId: number | null, - aichResultId: string, - contractId: number | null, - companyId: number, - event: JOURNAL_EVENT = JOURNAL_EVENT.UPLOADED -) { - const now = Date.now(); - const nowTimestamp = timestampInSeconds(now); - const data: Prisma.JournalCreateInput = { - company: { - connect: { - id: companyId, - }, - }, - aichResultId, - event, - createdAt: nowTimestamp, - updatedAt: nowTimestamp, - }; - - if (projectId !== null) { - data.project = { - connect: { - id: projectId, - }, - }; - } - - if (contractId !== null) { - data.contract = { - connect: { - id: contractId, - }, - }; - } - - let journal: { - id: number; - }; - - try { - journal = await prisma.journal.create({ - data, - select: { - id: true, - }, - }); - } catch (error) { - const logError = loggerError( - 0, - 'create journal in createJournalInPrisma failed', - error as Error - ); - logError.error( - 'Prisma related create journal in createJournalInPrisma in invoice.repo.ts failed' - ); - throw new Error(STATUS_MESSAGE.DATABASE_CREATE_FAILED_ERROR); - } - - return journal.id; -} - -export async function updateJournalInPrisma( - journalId: number, - aichResultId: string, - projectId: number | null, - contractId: number | null -) { - const data: Prisma.JournalUpdateInput = { - aichResultId, - }; - - if (projectId !== null) { - data.project = { - connect: { - id: projectId, - }, - }; - } - - if (contractId !== null) { - data.contract = { - connect: { - id: contractId, - }, - }; - } - - let journal: { - id: number; - }; - try { - journal = await prisma.journal.update({ - where: { - id: journalId, - }, - data, - select: { - id: true, - }, - }); - } catch (error) { - const logError = loggerError( - 0, - 'update journal in updateJournalInPrisma failed', - error as Error - ); - logError.error( - 'Prisma related update journal in updateJournalInPrisma in invoice.repo.ts failed' - ); - throw new Error(STATUS_MESSAGE.DATABASE_UPDATE_FAILED_ERROR); - } - - return journal.id; -} - -// Info: (20240524 - Murky) Main logics -export async function handlePrismaSavingLogic( - formattedInvoice: IInvoiceBeta, - aichResultId: string, - companyId: number, - ocrId: number | undefined -) { - try { - const { projectId, contractId } = formattedInvoice; - - let journalIdBeCreated: number = -1; - - try { - const ocrIdInDB = await findUniqueOcrInPrisma(ocrId); - - const company = await findUniqueCompanyInPrisma(companyId); - - journalIdBeCreated = await createJournalInPrisma( - projectId, - aichResultId, - contractId, - company.id - ); - - await createInvoiceAndPaymentInPrisma( - formattedInvoice, - journalIdBeCreated, - ocrIdInDB?.imageFileId - ); - - // Info: (20240524 - Murky) 更新ocr的狀態, 等到其他db操作都沒有錯誤後才更新 - if (ocrIdInDB?.id) { - await updateOcrStatusInPrisma(ocrIdInDB.id, ProgressStatus.HAS_BEEN_USED); - } - } catch (error) { - const logError = loggerError(0, 'handlePrismaSavingLogic failed', error as Error); - logError.error('Prisma related func. in handlePrismaSavingLogic in invoice.repo.ts failed'); - } - - return journalIdBeCreated; - } catch (error) { - const logError = loggerError(0, 'handlePrismaSavingLogic failed', error as Error); - logError.error('Prisma related func. in handlePrismaSavingLogic in invoice.repo.ts failed'); - throw new Error(STATUS_MESSAGE.DATABASE_CREATE_FAILED_ERROR); - } -} - -export async function handlePrismaUpdateLogic( - formattedInvoice: IInvoice, - aichResultId: string, - companyId: number -) { - const { journalId, projectId, contractId } = formattedInvoice; - if (!journalId) { - throw new Error(STATUS_MESSAGE.INVALID_INPUT_TYPE); - } - - let journalIdBeUpdated: number = -1; - try { - const journalInDB = await findUniqueJournalInPrisma(journalId, companyId); - - if (!journalInDB || !journalInDB.invoice) { - throw new Error(STATUS_MESSAGE.RESOURCE_NOT_FOUND); - } - - const invoiceIdToBeUpdated = journalInDB.invoice.id; - - if (!invoiceIdToBeUpdated) { - throw new Error(STATUS_MESSAGE.RESOURCE_NOT_FOUND); - } - - const invoiceBeUpdated = await updateInvoiceAndPaymentInPrisma( - invoiceIdToBeUpdated, - formattedInvoice, - journalId - ); - - if (invoiceBeUpdated === -1) { - throw new Error(STATUS_MESSAGE.DATABASE_UPDATE_FAILED_ERROR); - } - - journalIdBeUpdated = await updateJournalInPrisma( - journalId, - aichResultId, - projectId, - contractId - ); - } catch (error) { - const logError = loggerError(0, 'handlePrismaUpdateLogic failed', error as Error); - logError.error('Prisma related func. in handlePrismaUpdateLogic in invoice.repo.ts failed'); - } - - return journalIdBeUpdated; -} diff --git a/src/lib/utils/repo/journal.repo.ts b/src/lib/utils/repo/journal.repo.ts deleted file mode 100644 index dcebbd117..000000000 --- a/src/lib/utils/repo/journal.repo.ts +++ /dev/null @@ -1,400 +0,0 @@ -import prisma from '@/client'; -import { DEFAULT_PAGE_LIMIT, DEFAULT_PAGE_OFFSET } from '@/constants/config'; -import { STATUS_MESSAGE } from '@/constants/status_code'; -import { - IJournalFromPrismaIncludeProjectContractInvoiceVoucher, - IJournalIncludeVoucherLineItemsInvoicePayment, -} from '@/interfaces/journal'; -import { IPaginatedData } from '@/interfaces/pagination'; -import { Prisma } from '@prisma/client'; -import { calculateTotalPages, getTimestampNow } from '@/lib/utils/common'; -import { JOURNAL_EVENT, SortBy } from '@/constants/journal'; -import { SortOrder } from '@/constants/sort'; -import { loggerError } from '@/lib/utils/logger_back'; - -export async function findManyJournalsInPrisma( - companyId: number, - offset: number = DEFAULT_PAGE_OFFSET, - limit: number = DEFAULT_PAGE_LIMIT, - eventType: string | undefined = undefined, - startDateInSecond: number | undefined = undefined, - endDateInSecond: number | undefined = undefined, - search: string | undefined = undefined, - sort: string | undefined = undefined -) { - let journals: IJournalFromPrismaIncludeProjectContractInvoiceVoucher[]; - try { - journals = await prisma.journal.findMany({ - orderBy: { - createdAt: sort === SortOrder.ASC ? SortOrder.ASC : SortOrder.DESC, - }, - skip: offset, - take: limit, - where: { - companyId, - createdAt: { - gte: startDateInSecond, - lte: endDateInSecond, - }, - invoice: { - eventType, - }, - OR: [ - { deletedAt: 0 }, - { deletedAt: null }, - { - invoice: { - vendorOrSupplier: { - contains: search, - }, - }, - }, - { - invoice: { - description: { - contains: search, - }, - }, - }, - { - voucher: { - no: { - contains: search, - }, - }, - }, - ], - }, - include: { - project: { - include: { - imageFile: true, - }, - }, - contract: true, - invoice: { - include: { - payment: true, - imageFile: true, - }, - }, - voucher: { - include: { - lineItems: { - include: { - account: true, - }, - }, - }, - }, - }, - }); - } catch (error) { - const logError = loggerError( - 0, - 'Find many journals in findManyJournalsInPrisma failed', - error as Error - ); - logError.error('Prisma find many journals in journal.repo.ts failed'); - throw new Error(STATUS_MESSAGE.DATABASE_READ_FAILED_ERROR); - } - return journals; -} - -export async function listJournal( - companyId: number, - journalEvent?: JOURNAL_EVENT, - page: number = 1, // Info: (202040717 - Jacky) 將 pageDelta 改為 targetPage,預設值為 1 - pageSize: number = DEFAULT_PAGE_LIMIT, - eventType: string | undefined = undefined, - sortBy: SortBy = SortBy.CREATED_AT, - sortOrder: SortOrder = SortOrder.DESC, - startDateInSecond?: number, - endDateInSecond?: number, - searchQuery?: string -): Promise> { - try { - const where: Prisma.JournalWhereInput = { - event: journalEvent, - companyId, - createdAt: { - gte: startDateInSecond, - lte: endDateInSecond, - }, - invoice: { - eventType, - }, - AND: [ - { OR: [{ deletedAt: 0 }, { deletedAt: null }] }, - { - OR: [ - { invoice: { vendorOrSupplier: { contains: searchQuery, mode: 'insensitive' } } }, - { invoice: { description: { contains: searchQuery, mode: 'insensitive' } } }, - { voucher: { no: { contains: searchQuery, mode: 'insensitive' } } }, - ], - }, - ], - }; - - const totalCount = await prisma.journal.count({ where }); - const totalPages = calculateTotalPages(totalCount, pageSize); - - if (totalPages > 0 && (page < 1 || page > totalPages)) { - throw new Error(STATUS_MESSAGE.INVALID_INPUT_PARAMETER); - } - - const orderBy = - sortBy === SortBy.PAYMENT_PRICE - ? { invoice: { payment: { price: sortOrder } } } - : { [sortBy]: sortOrder }; - - const include = { - project: { - include: { - imageFile: true, - }, - }, - contract: true, - invoice: { include: { payment: true, imageFile: true } }, - voucher: { include: { lineItems: { include: { account: true } } } }, - }; - - const skip = (page - 1) * pageSize; - - const findManyArgs = { - where, - orderBy, - include, - take: pageSize + 1, - skip, - }; - - const journalList = await prisma.journal.findMany(findManyArgs); - - const hasNextPage = journalList.length > pageSize; - const hasPreviousPage = page > 1; - - if (journalList.length > pageSize) { - journalList.pop(); // Info: (202040717 - Jacky) 移除多餘的紀錄 - } - - const sort: { - sortBy: string; // Info: (202040717 - Jacky) 排序欄位的鍵 - sortOrder: string; // Info: (202040717 - Jacky) 排序欄位的值 - }[] = [{ sortBy, sortOrder }]; - - if (journalEvent) { - sort.push({ sortBy: journalEvent, sortOrder: '' }); - } - const paginatedJournalList = { - data: journalList, - page, - totalPages, - totalCount, - pageSize, - hasNextPage, - hasPreviousPage, - sort, - }; - - return paginatedJournalList; - } catch (error) { - const logError = loggerError(0, 'List journal in listJournal failed', error as Error); - logError.error('Func. listJournal in journal.repo.ts failed'); - throw new Error(STATUS_MESSAGE.DATABASE_READ_FAILED_ERROR); - } -} - -export async function findUniqueJournalInPrisma(journalId: number, companyId: number) { - let journal: IJournalFromPrismaIncludeProjectContractInvoiceVoucher | null; - try { - const where: Prisma.JournalWhereUniqueInput = { - id: journalId, - companyId, - }; - - journal = await prisma.journal.findUnique({ - where, - include: { - project: { - include: { - imageFile: true, - }, - }, - contract: true, - invoice: { - include: { - payment: true, - imageFile: true, - }, - }, - voucher: { - include: { - lineItems: { - include: { - account: true, - }, - orderBy: { - id: 'asc', - }, - }, - }, - }, - }, - }); - } catch (error) { - const logError = loggerError( - 0, - 'Find unique journal in findUniqueJournalInPrisma failed', - error as Error - ); - logError.error( - 'Prisma find unique journal in findUniqueJournalInPrisma in journal.repo.ts failed' - ); - throw new Error(STATUS_MESSAGE.DATABASE_READ_FAILED_ERROR); - } - return journal; -} - -export async function deleteJournalInPrisma( - journalId: number, - companyId: number -): Promise { - let journal: IJournalFromPrismaIncludeProjectContractInvoiceVoucher | null = null; - - let journalExists: IJournalFromPrismaIncludeProjectContractInvoiceVoucher | null = null; - // Info: (20240723 - Murky) Check if journal exists and belongs to the company - try { - journalExists = await findUniqueJournalInPrisma(journalId, companyId); - } catch (error) { - const logError = loggerError( - 0, - 'Find unique journal in deleteJournalInPrisma failed', - error as Error - ); - logError.error('Prisma find unique journal in deleteJournalInPrisma in journal.repo.ts failed'); - } - - if (journalExists) { - const nowInSecond = getTimestampNow(); - try { - journal = await prisma.$transaction(async (prismaClient) => { - if (journalExists?.invoice?.payment) { - await prismaClient.payment.update({ - data: { - updatedAt: nowInSecond, - deletedAt: nowInSecond, - }, - where: { - id: journalExists.invoice.payment.id, - }, - }); - } - - if (journalExists?.invoice) { - await prismaClient.invoice.update({ - data: { - updatedAt: nowInSecond, - deletedAt: nowInSecond, - }, - where: { - id: journalExists.invoice.id, - }, - }); - } - if (journalExists?.voucher) { - await Promise.all( - journalExists.voucher.lineItems.map(async (lineItem) => { - await prismaClient.lineItem.update({ - data: { - updatedAt: nowInSecond, - deletedAt: nowInSecond, - }, - where: { - id: lineItem.id, - }, - }); - }) - ); - - await prismaClient.voucher.update({ - data: { - updatedAt: nowInSecond, - deletedAt: nowInSecond, - }, - where: { - id: journalExists.voucher.id, - }, - }); - } - - return prismaClient.journal.update({ - data: { - updatedAt: nowInSecond, - deletedAt: nowInSecond, - }, - where: { - id: journalId, - }, - include: { - project: { - include: { - imageFile: true, - }, - }, - contract: true, - invoice: { - include: { - payment: true, - imageFile: true, - }, - }, - voucher: { - include: { - lineItems: { - include: { - account: true, - }, - }, - }, - }, - }, - }); - }); - } catch (error) { - const logError = loggerError( - 0, - 'Soft delete journal in deleteJournalInPrisma failed', - error as Error - ); - logError.error( - 'Prisma soft delete journal in deleteJournalInPrisma in journal.repo.ts failed' - ); - } - } - return journal; -} - -export async function listJournalFor401( - companyId: number, - startDateInSecond: number, - endDateInSecond: number -): Promise { - const where: Prisma.JournalWhereInput = { - companyId, - invoice: { - date: { - gte: startDateInSecond, - lte: endDateInSecond, - }, - OR: [{ deletedAt: 0 }, { deletedAt: null }], - }, - OR: [{ deletedAt: 0 }, { deletedAt: null }], - }; - const include = { - invoice: { include: { payment: true } }, - voucher: { include: { lineItems: { include: { account: true } } } }, - }; - const journalList = await prisma.journal.findMany({ where, include }); - return journalList; -} diff --git a/src/lib/utils/repo/line_item.beta.repo.ts b/src/lib/utils/repo/line_item.beta.repo.ts index 44de7ed2a..2cbd0e8ec 100644 --- a/src/lib/utils/repo/line_item.beta.repo.ts +++ b/src/lib/utils/repo/line_item.beta.repo.ts @@ -79,14 +79,10 @@ export async function listLineItems({ type, }, voucher: { - journal: { - companyId, - invoice: { - date: { - gte: startDateInSecond, - lte: endDateInSecond, - }, - }, + companyId, + date: { + gte: startDateInSecond, + lte: endDateInSecond, }, }, AND: [deletedAtQuery, searchQueryArray], diff --git a/src/lib/utils/repo/line_item.repo.ts b/src/lib/utils/repo/line_item.repo.ts index 52da190cd..44b68920e 100644 --- a/src/lib/utils/repo/line_item.repo.ts +++ b/src/lib/utils/repo/line_item.repo.ts @@ -19,14 +19,10 @@ export async function getLineItemsInPrisma( type, }, voucher: { - journal: { - companyId, - invoice: { - date: { - gte: startDateInSecond, - lte: endDateInSecond, - }, - }, + companyId, + date: { + gte: startDateInSecond, + lte: endDateInSecond, }, }, }; diff --git a/src/lib/utils/repo/payment_record.repo.ts b/src/lib/utils/repo/payment_record.repo.ts index 63c34ccbd..af450a2a3 100644 --- a/src/lib/utils/repo/payment_record.repo.ts +++ b/src/lib/utils/repo/payment_record.repo.ts @@ -7,11 +7,15 @@ import { Prisma } from '@prisma/client'; export async function createPaymentRecord( orderId: number, transactionId: string, - date: number, - description: string, + action: string, amount: number, + fee: number, method: string, - status: string + cardIssuerCountry: string, + status: string, + paymentCreatedAt: string, + refundAmount: number, + authCode: string ): Promise { const now = Date.now(); const nowTimestamp = timestampInSeconds(now); @@ -19,11 +23,15 @@ export async function createPaymentRecord( data: { orderId, transactionId, - date, - description, + action, amount, + fee, method, + cardIssuerCountry, status, + paymentCreatedAt, + refundAmount, + authCode, createdAt: nowTimestamp, updatedAt: nowTimestamp, }, diff --git a/src/lib/utils/repo/subscription.repo.ts b/src/lib/utils/repo/subscription.repo.ts index f8e443b6b..af7d3d0b1 100644 --- a/src/lib/utils/repo/subscription.repo.ts +++ b/src/lib/utils/repo/subscription.repo.ts @@ -20,6 +20,7 @@ export async function createSubscription( data: { companyId, planId, + autoRenewal: true, startDate: nowTimestamp, expiredDate, status, diff --git a/src/lib/utils/repo/voucher.beta.repo.ts b/src/lib/utils/repo/voucher.beta.repo.ts deleted file mode 100644 index dffe6e065..000000000 --- a/src/lib/utils/repo/voucher.beta.repo.ts +++ /dev/null @@ -1,323 +0,0 @@ -import prisma from '@/client'; -import { CASH_AND_CASH_EQUIVALENTS_CODE } from '@/constants/cash_flow/common_cash_flow'; -import { SortOrder } from '@/constants/sort'; -import { - IVoucherDataForSavingToDB, - IVoucherFromPrismaIncludeJournalLineItems, -} from '@/interfaces/voucher'; -import { getTimestampNow, timestampInSeconds } from '@/lib/utils/common'; -import { Prisma, Voucher } from '@prisma/client'; -import { loggerError } from '@/lib/utils/logger_back'; - -/** - * This function can get the latest voucher no - * @param {number} companyId - company that you want to get the latest voucher no (type: number) - * @returns {Promise} return the latest voucher no (type: Promise) - */ -export async function getLatestVoucherNo(companyId: number) { - let voucherNo: string | null = ''; - - let voucher: Voucher | null = null; - - const where: Prisma.VoucherWhereInput = { - journal: { - companyId, - }, - }; - - const orderBy: Prisma.VoucherOrderByWithRelationInput = { - no: SortOrder.DESC, - }; - - const select: Prisma.VoucherSelect = { - no: true, - createdAt: true, - }; - - const findFirstArgs: Prisma.VoucherFindFirstArgs = { - where, - orderBy, - select, - }; - - try { - voucher = await prisma.voucher.findFirst(findFirstArgs); - } catch (error) { - const logError = loggerError( - 0, - 'get latest voucher no in getLatestVoucherNo failed', - error as Error - ); - logError.error( - 'Prisma related findFirst voucher in getLatestVoucherNo in voucher.beta.repo.ts failed' - ); - } - - const localToday = new Date(); - const localTodayNo = - `${localToday.getFullYear()}`.padStart(4, '0') + - `${localToday.getMonth() + 1}`.padStart(2, '0') + - `${localToday.getDate()}`.padStart(2, '0'); - const resultDate = voucher?.createdAt - ? new Date(timestampInSeconds(voucher?.createdAt)).getDate() - : -1; - const isYesterday = resultDate !== localToday.getDate(); - const latestNo = voucher?.no.slice(voucher.no.length - 3) || '0'; // Info: ( 20240522 - Murky)I want to slice the last 3 digits - const newVoucherNo = isYesterday ? '001' : String(Number(latestNo) + 1).padStart(3, '0'); - - voucherNo = `${localTodayNo}${newVoucherNo}`; - - return voucherNo; -} - -/** - * Find unique voucher by voucher id - * @param {number} voucherId - voucher id that you want to find (type: number) - * @returns {Promise} return include journal and line items, will be null if not found or error (type: Promise) - */ -export async function findUniqueVoucherById(voucherId: number) { - let voucherData: IVoucherFromPrismaIncludeJournalLineItems | null = null; - - const where: Prisma.VoucherWhereUniqueInput = { - id: voucherId, - }; - - const include = { - journal: true, - lineItems: { - include: { - account: true, - }, - }, - }; - - const findUniqueArgs = { - where, - include, - }; - - try { - voucherData = await prisma.voucher.findUnique(findUniqueArgs); - } catch (error) { - const logError = loggerError( - 0, - 'find unique voucher in findUniqueVoucherById failed', - error as Error - ); - logError.error( - 'Prisma related find unique voucher in findUniqueVoucherById in voucher.beta.repo.ts failed' - ); - } - return voucherData; -} - -/** - * Create a voucher record by newVoucherNo and journalId - * @param {string} newVoucherNo - newest voucher no that you want set to new voucher no (type: string) - * @param {number} journalId - journal id that you want to connect to new voucher (type: number) - * @returns {Promise} return a voucher record or null (type: Promise) - */ -export async function createVoucherInPrisma(newVoucherNo: string, journalId: number) { - let voucherData: Voucher | null = null; - - const nowTimestamp = getTimestampNow(); - - const journalConnect: Prisma.JournalUpdateOneRequiredWithoutInvoiceNestedInput = { - connect: { - id: journalId, - }, - }; - - const data: Prisma.VoucherCreateInput = { - no: newVoucherNo, - journal: journalConnect, - createdAt: nowTimestamp, - updatedAt: nowTimestamp, - }; - - const voucherCreateArgs = { - data, - }; - - try { - voucherData = await prisma.voucher.create(voucherCreateArgs); - } catch (error) { - const logError = loggerError( - 0, - 'create voucher in createVoucherInPrisma failed', - error as Error - ); - logError.error( - 'Prisma related create voucher in createVoucherInPrisma in voucher.beta.repo.ts failed' - ); - } - - return voucherData; -} - -/** - * Find many vouchers with cash account in line items account - * @param {number} companyId - company id that you want to find the vouchers (type: number) - * @param {number} startDateInSecond - start date in second that you want to find the vouchers (type: number) - * @param {number} endDateInSecond - end date in second that you want to find the vouchers (type: number) - * @returns {Promise} return include journal and line items, will be empty array if not found or error (type: Promise) - */ -export async function findManyVoucherWithCashInPrisma( - companyId: number, - startDateInSecond: number, - endDateInSecond: number -) { - let vouchers: IVoucherFromPrismaIncludeJournalLineItems[] = []; - - const where: Prisma.VoucherWhereInput = { - journal: { - companyId, - }, - createdAt: { - gte: startDateInSecond, - lte: endDateInSecond, - }, - lineItems: { - some: { - OR: CASH_AND_CASH_EQUIVALENTS_CODE.map((cashCode) => ({ - account: { - code: { - startsWith: cashCode, - }, - }, - })), - }, - }, - }; - - const include = { - journal: { - include: { - invoice: true, - }, - }, - lineItems: { - include: { - account: true, - }, - }, - }; - - const findManyArgs = { - where, - include, - }; - try { - vouchers = await prisma.voucher.findMany(findManyArgs); - } catch (error) { - const logError = loggerError( - 0, - 'find many voucher in findManyVoucherWithCashInPrisma failed', - error as Error - ); - logError.error( - 'Prisma related find many voucher in findManyVoucherWithCashInPrisma in voucher.beta.repo.ts failed' - ); - } - - return vouchers; -} - -/** - * Update a voucher record by voucher id - * @param {number} voucherId - voucher id that you want to update (type: number) - * @param {IVoucherDataForSavingToDB} voucherToUpdate - voucher data that you want to update to voucher id provided (type: IVoucherDataForSavingToDB) - * @returns {Promise} return a voucher record or null (type: Promise) - */ -export async function updateVoucherByJournalIdInPrisma( - journalId: number, - companyId: number, - voucherToUpdate: IVoucherDataForSavingToDB -) { - const nowInSecond = getTimestampNow(); - - let newVoucher: { - id: number; - createdAt: number; - updatedAt: number; - journalId: number; - no: string; - lineItems: { - id: number; - amount: number; - description: string; - debit: boolean; - accountId: number; - voucherId: number; - createdAt: number; - updatedAt: number; - }[]; - } | null = null; - - newVoucher = await prisma.$transaction(async (prismaClient) => { - const journalExists = await prismaClient.journal.findUnique({ - where: { - id: journalId, - companyId, - }, - include: { - voucher: { - include: { - lineItems: true, - }, - }, - }, - }); - - // Info: (20240712 - Murky) If journal exists and voucher exists, update the voucher - - if (journalExists && journalExists?.voucher && journalExists?.voucher?.id) { - if (journalExists?.voucher?.lineItems) { - await prismaClient.lineItem.deleteMany({ - where: { - voucherId: journalExists.voucher.id, - }, - }); - } - - await Promise.all( - voucherToUpdate.lineItems.map(async (lineItem) => { - await prismaClient.lineItem.create({ - data: { - amount: lineItem.amount, - description: lineItem.description, - debit: lineItem.debit, - account: { - connect: { - id: lineItem.accountId, - }, - }, - voucher: { - connect: { id: journalExists?.voucher?.id }, - }, - createdAt: nowInSecond, - updatedAt: nowInSecond, - }, - }); - }) - ); - const voucherBeUpdated = await prismaClient.voucher.update({ - where: { - id: journalExists.voucher.id, - }, - data: { - updatedAt: nowInSecond, - }, - include: { - lineItems: true, - }, - }); - - return voucherBeUpdated; - } - - return null; - }); - - return newVoucher; -} diff --git a/src/lib/utils/repo/voucher.repo.ts b/src/lib/utils/repo/voucher.repo.ts index 2e291938c..8a1abc004 100644 --- a/src/lib/utils/repo/voucher.repo.ts +++ b/src/lib/utils/repo/voucher.repo.ts @@ -1,3 +1,4 @@ +// ToDo: (20241011 - Jacky) Temporarily commnet the following code for the beta transition import { getTimestampNow, timestampInSeconds } from '@/lib/utils/common'; import prisma from '@/client'; @@ -21,9 +22,9 @@ export async function findUniqueJournalInvolveInvoicePaymentInPrisma( id: journalId, }, include: { - invoice: { + invoiceVoucherJournals: { include: { - payment: true, + invoice: true, }, }, }, @@ -110,7 +111,11 @@ export async function findUniqueVoucherInPrisma(voucherId: number) { id: voucherId, }, include: { - journal: true, + invoiceVoucherJournals: { + include: { + journal: true, + }, + }, lineItems: { include: { account: true, @@ -200,9 +205,7 @@ export async function getLatestVoucherNoInPrisma(companyId: number) { try { const result = await prisma.voucher.findFirst({ where: { - journal: { - companyId, - }, + companyId, }, orderBy: { no: SortOrder.DESC, @@ -239,40 +242,40 @@ export async function getLatestVoucherNoInPrisma(companyId: number) { } } -export async function createVoucherInPrisma(newVoucherNo: string, journalId: number) { - try { - const now = Date.now(); - const nowTimestamp = timestampInSeconds(now); - const voucherData = await prisma.voucher.create({ - data: { - no: newVoucherNo, - journal: { - connect: { - id: journalId, - }, - }, - createdAt: nowTimestamp, - updatedAt: nowTimestamp, - }, - select: { - id: true, - lineItems: true, - }, - }); +// export async function createVoucherInPrisma(newVoucherNo: string, journalId: number) { +// try { +// const now = Date.now(); +// const nowTimestamp = timestampInSeconds(now); +// const voucherData = await prisma.voucher.create({ +// data: { +// no: newVoucherNo, +// journal: { +// connect: { +// id: journalId, +// }, +// }, +// createdAt: nowTimestamp, +// updatedAt: nowTimestamp, +// }, +// select: { +// id: true, +// lineItems: true, +// }, +// }); - return voucherData; - } catch (error) { - const logError = loggerError( - 0, - 'create voucher in createVoucherInPrisma failed', - error as Error - ); - logError.error( - 'Prisma related create voucher in createVoucherInPrisma in voucher.repo.ts failed' - ); - throw new Error(STATUS_MESSAGE.DATABASE_CREATE_FAILED_ERROR); - } -} +// return voucherData; +// } catch (error) { +// const logError = loggerError( +// 0, +// 'create voucher in createVoucherInPrisma failed', +// error as Error +// ); +// logError.error( +// 'Prisma related create voucher in createVoucherInPrisma in voucher.repo.ts failed' +// ); +// throw new Error(STATUS_MESSAGE.DATABASE_CREATE_FAILED_ERROR); +// } +// } // Info: (20240710 - Murky) Unefficient need to be refactor export async function findManyVoucherWithCashInPrisma( @@ -283,9 +286,7 @@ export async function findManyVoucherWithCashInPrisma( try { const vouchers = await prisma.voucher.findMany({ where: { - journal: { - companyId, - }, + companyId, createdAt: { gte: startDateInSecond, lte: endDateInSecond, @@ -303,9 +304,9 @@ export async function findManyVoucherWithCashInPrisma( }, }, include: { - journal: { + invoiceVoucherJournals: { include: { - invoice: true, + journal: true, }, }, lineItems: { @@ -330,6 +331,7 @@ export async function findManyVoucherWithCashInPrisma( } } +// ToDo: (20241011 - Jacky) Temporarily commnet the following code for the beta transition export async function updateVoucherByJournalIdInPrisma( journalId: number, companyId: number, @@ -362,21 +364,26 @@ export async function updateVoucherByJournalIdInPrisma( companyId, }, include: { - voucher: { + invoiceVoucherJournals: { include: { - lineItems: true, + voucher: { + include: { + lineItems: true, + }, + }, }, }, }, }); + const voucherExists = journalExists?.invoiceVoucherJournals?.[0]?.voucher; // Info: (20240712 - Murky) If journal exists and voucher exists, update the voucher - if (journalExists && journalExists?.voucher && journalExists?.voucher?.id) { - if (journalExists?.voucher?.lineItems) { + if (journalExists && voucherExists && voucherExists.id) { + if (voucherExists.lineItems) { await prismaClient.lineItem.deleteMany({ where: { - voucherId: journalExists.voucher.id, + voucherId: voucherExists.id, }, }); } @@ -394,7 +401,7 @@ export async function updateVoucherByJournalIdInPrisma( }, }, voucher: { - connect: { id: journalExists?.voucher?.id }, + connect: { id: voucherExists.id }, }, createdAt: nowInSecond, updatedAt: nowInSecond, @@ -404,7 +411,7 @@ export async function updateVoucherByJournalIdInPrisma( ); const voucherBeUpdated = await prismaClient.voucher.update({ where: { - id: journalExists.voucher.id, + id: voucherExists.id, }, data: { updatedAt: nowInSecond, @@ -413,8 +420,12 @@ export async function updateVoucherByJournalIdInPrisma( lineItems: true, }, }); + const newVoucherData = { + ...voucherBeUpdated, + journalId, + }; - return voucherBeUpdated; + return newVoucherData; } return null; diff --git a/src/lib/utils/repo_test/payment_record.repo.test.ts b/src/lib/utils/repo_test/payment_record.repo.test.ts index cc5d058d8..48cb4f6ff 100644 --- a/src/lib/utils/repo_test/payment_record.repo.test.ts +++ b/src/lib/utils/repo_test/payment_record.repo.test.ts @@ -38,25 +38,34 @@ describe('Payment Record Repository', () => { const newPaymentRecord = { orderId: 1001, transactionId: 'txn_999', + action: 'charge', + amount: 1000, + fee: 0, + cardIssuerCountry: 'SG', + paymentCreatedAt: '2022-07-04T00:00:00Z', + refundAmount: 0, date: 12736412, description: 'Test Payment', - amount: 1000, method: 'credit_card', status: 'completed', + authCode: 'auth_999', }; const paymentRecord = await createPaymentRecord( newPaymentRecord.orderId, newPaymentRecord.transactionId, - newPaymentRecord.date, - newPaymentRecord.description, + newPaymentRecord.action, newPaymentRecord.amount, + newPaymentRecord.fee, newPaymentRecord.method, - newPaymentRecord.status + newPaymentRecord.cardIssuerCountry, + newPaymentRecord.status, + newPaymentRecord.paymentCreatedAt, + newPaymentRecord.refundAmount, + newPaymentRecord.authCode ); expect(paymentRecord).toBeDefined(); expect(paymentRecord.orderId).toBe(newPaymentRecord.orderId); expect(paymentRecord.transactionId).toBe(newPaymentRecord.transactionId); - expect(paymentRecord.description).toBe(newPaymentRecord.description); expect(paymentRecord.amount).toBe(newPaymentRecord.amount); expect(paymentRecord.method).toBe(newPaymentRecord.method); expect(paymentRecord.status).toBe(newPaymentRecord.status); diff --git a/src/lib/utils/report/cash_flow_statement_generator.ts b/src/lib/utils/report/cash_flow_statement_generator.ts index bd788d12a..69c96ddb0 100644 --- a/src/lib/utils/report/cash_flow_statement_generator.ts +++ b/src/lib/utils/report/cash_flow_statement_generator.ts @@ -56,20 +56,19 @@ export default class CashFlowStatementGenerator extends FinancialReportGenerator startDateInSecond, endDateInSecond ); - this.voucherRelatedToCash = voucherRelatedToCash.filter((voucher) => { - const laterThanStartDate = voucher.journal?.invoice?.date - ? voucher.journal.invoice.date >= startDateInSecond - : false; - const earlierThanEndDate = voucher.journal?.invoice?.date - ? voucher.journal?.invoice?.date <= endDateInSecond - : false; - return laterThanStartDate && earlierThanEndDate; - }); + this.voucherRelatedToCash = voucherRelatedToCash + .filter((voucher) => { + const laterThanStartDate = voucher.date >= startDateInSecond; + const earlierThanEndDate = voucher.date <= endDateInSecond; + return laterThanStartDate && earlierThanEndDate; + }) + .map((voucher) => ({ + ...voucher, + invoiceVoucherJournals: voucher.invoiceVoucherJournals || [], + })); this.voucherLastPeriod = voucherRelatedToCash.filter((voucher) => { - const earlierThanStartDate = voucher.journal?.invoice?.date - ? voucher.journal?.invoice?.date < startDateInSecond - : false; + const earlierThanStartDate = voucher.date < startDateInSecond; return earlierThanStartDate; }); } diff --git a/src/lib/utils/report/report_401.ts b/src/lib/utils/report/report_401.ts index fa2b1ea26..8acda03f7 100644 --- a/src/lib/utils/report/report_401.ts +++ b/src/lib/utils/report/report_401.ts @@ -10,10 +10,11 @@ import { import { getCompanyKYCByCompanyId } from '@/lib/utils/repo/company_kyc.repo'; import { convertTimestampToROCDate } from '@/lib/utils/common'; import { STATUS_MESSAGE } from '@/constants/status_code'; -import { listJournalFor401 } from '@/lib/utils/repo/journal.repo'; import { SPECIAL_ACCOUNTS } from '@/constants/account'; -import { IJournalIncludeVoucherLineItemsInvoicePayment } from '@/interfaces/journal'; import { importsCategories, purchasesCategories, salesCategories } from '@/constants/invoice'; +import { InvoiceVoucherJournal, Journal, Invoice, Voucher, LineItem } from '@prisma/client'; +import { Account } from 'next-auth'; +import { listInvoiceVoucherJournalFor401 } from '@/lib/utils/repo/beta_transition.repo'; /** Info: (20240814 - Jacky) 更新銷項資料 * Updates the sales result based on the provided journal, category, and sales object. @@ -26,22 +27,27 @@ import { importsCategories, purchasesCategories, salesCategories } from '@/const */ function updateSalesResult( sales: Sales, - journal: IJournalIncludeVoucherLineItemsInvoicePayment, + invoiceVoucherJournal: InvoiceVoucherJournal & { + journal: Journal | null; + invoice: Invoice | null; + voucher: (Voucher & { lineItems: (LineItem & { account: Account })[] }) | null; + }, category: keyof SalesBreakdown ) { const updatedSales = sales; - if (journal.invoice?.payment.hasTax) { - if (journal.invoice?.payment.taxPercentage === 0) { - updatedSales.breakdown[category].zeroTax += journal.invoice?.payment.price ?? 0; + if (invoiceVoucherJournal.invoice?.taxRatio === 0) { + if (invoiceVoucherJournal.invoice?.taxRatio === 0) { + updatedSales.breakdown[category].zeroTax += + invoiceVoucherJournal.invoice?.priceBeforeTax ?? 0; } else { - updatedSales.breakdown[category].sales += journal.invoice?.payment.price ?? 0; - updatedSales.breakdown[category].tax += journal.invoice?.payment.taxPrice ?? 0; - updatedSales.breakdown.total.sales += journal.invoice?.payment.price ?? 0; - updatedSales.breakdown.total.tax += journal.invoice?.payment.taxPrice ?? 0; + updatedSales.breakdown[category].sales += invoiceVoucherJournal.invoice?.priceBeforeTax ?? 0; + updatedSales.breakdown[category].tax += invoiceVoucherJournal.invoice?.taxPrice ?? 0; + updatedSales.breakdown.total.sales += invoiceVoucherJournal.invoice?.priceBeforeTax ?? 0; + updatedSales.breakdown.total.tax += invoiceVoucherJournal.invoice?.taxPrice ?? 0; } } - if (journal.voucher?.lineItems) { - journal.voucher?.lineItems.forEach((lineItem) => { + if (invoiceVoucherJournal.voucher?.lineItems) { + invoiceVoucherJournal.voucher?.lineItems.forEach((lineItem) => { if (lineItem.account?.rootCode === SPECIAL_ACCOUNTS.FIXED_ASSET.rootCode) { updatedSales.includeFixedAsset += lineItem.amount; } @@ -62,7 +68,11 @@ function updateSalesResult( */ function updatePurchasesResult( purchases: Purchases, - journal: IJournalIncludeVoucherLineItemsInvoicePayment, + invoiceVoucherJournal: InvoiceVoucherJournal & { + journal: Journal | null; + invoice: Invoice | null; + voucher: (Voucher & { lineItems: (LineItem & { account: Account })[] }) | null; + }, category: keyof PurchaseBreakdown ) { const updatedPurchase = purchases; @@ -78,26 +88,26 @@ function updatePurchasesResult( generalPurchases: 0, fixedAssets: 0, }; - if (journal.voucher?.lineItems) { - if (journal.invoice?.deductible) { - journal.voucher?.lineItems.forEach((lineItem) => { + if (invoiceVoucherJournal.voucher?.lineItems) { + if (invoiceVoucherJournal.invoice?.deductible) { + invoiceVoucherJournal.voucher?.lineItems.forEach((lineItem) => { if (lineItem.account?.rootCode === SPECIAL_ACCOUNTS.FIXED_ASSET.rootCode) { fixedAssets.amount += lineItem.amount; - fixedAssets.tax += lineItem.amount * (journal.invoice?.payment.taxPercentage ?? 0.05); + fixedAssets.tax += lineItem.amount * (invoiceVoucherJournal.invoice?.taxRatio ?? 0.05); } }); generalPurchases = { - amount: (journal.invoice?.payment.price ?? 0) - fixedAssets.amount, - tax: (journal.invoice?.payment.taxPrice ?? 0) - fixedAssets.tax, + amount: (invoiceVoucherJournal.invoice.priceBeforeTax ?? 0) - fixedAssets.amount, + tax: (invoiceVoucherJournal.invoice?.taxPrice ?? 0) - fixedAssets.tax, }; } else { - journal.voucher?.lineItems.forEach((lineItem) => { + invoiceVoucherJournal.voucher?.lineItems.forEach((lineItem) => { if (lineItem.account?.rootCode === SPECIAL_ACCOUNTS.FIXED_ASSET.rootCode) { unDeductible.fixedAssets += lineItem.amount; } }); unDeductible.generalPurchases = - journal.invoice?.payment.price ?? 0 - unDeductible.fixedAssets; + invoiceVoucherJournal.invoice?.priceBeforeTax ?? 0 - unDeductible.fixedAssets; } } updatedPurchase.breakdown[category].generalPurchases.amount += generalPurchases.amount; @@ -122,12 +132,16 @@ function updatePurchasesResult( */ function updateImportsResult( imports: Imports, - journal: IJournalIncludeVoucherLineItemsInvoicePayment, + invoiceVoucherJournal: InvoiceVoucherJournal & { + journal: Journal | null; + invoice: Invoice | null; + voucher: (Voucher & { lineItems: (LineItem & { account: Account })[] }) | null; + }, category: keyof Imports ) { const updatedImports = imports; - if (!journal.invoice?.payment.hasTax) { - updatedImports[category] += journal.invoice?.payment.price ?? 0; + if (!invoiceVoucherJournal.invoice?.taxRatio) { + updatedImports[category] += invoiceVoucherJournal.invoice?.priceBeforeTax ?? 0; } } @@ -191,7 +205,15 @@ export async function generate401Report( const ROCStartDate = convertTimestampToROCDate(from); const ROCEndDate = convertTimestampToROCDate(to); // Info: (20240813 - Jacky) 1. 獲取所有發票 - const journalList = await listJournalFor401(companyId, from, to); + const invoiceVoucherJournalList: (InvoiceVoucherJournal & { + journal: Journal | null; + invoice: Invoice | null; + voucher: (Voucher & { lineItems: (LineItem & { account: Account })[] }) | null; + })[] = (await listInvoiceVoucherJournalFor401(companyId, from, to)) as (InvoiceVoucherJournal & { + journal: Journal | null; + invoice: Invoice | null; + voucher: (Voucher & { lineItems: (LineItem & { account: Account })[] }) | null; + })[]; const basicInfo = { uniformNumber: companyKYC.registrationNumber, businessName: companyKYC.legalName, @@ -201,7 +223,7 @@ export async function generate401Report( currentYear: ROCStartDate.year.toString(), startMonth: ROCStartDate.month.toString(), endMonth: ROCEndDate.month.toString(), - usedInvoiceCount: journalList.length, + usedInvoiceCount: invoiceVoucherJournalList.length, }; const sales = { breakdown: { @@ -264,18 +286,18 @@ export async function generate401Report( }; const bondedAreaSalesToTaxArea = 0; - journalList.forEach((journal) => { - if (journal.invoice && journal.invoice.type) { - const { type } = journal.invoice; + invoiceVoucherJournalList.forEach((invoiceVoucherJournal) => { + if (invoiceVoucherJournal.invoice && invoiceVoucherJournal.invoice.type) { + const { type } = invoiceVoucherJournal.invoice; if (type in salesCategories) { const category = type as keyof SalesBreakdown; - updateSalesResult(sales, journal, category); + updateSalesResult(sales, invoiceVoucherJournal, category); } else if (type in purchasesCategories) { const category = type as keyof PurchaseBreakdown; - updatePurchasesResult(purchases, journal, category); + updatePurchasesResult(purchases, invoiceVoucherJournal, category); } else if (type in importsCategories) { const category = type as keyof Imports; - updateImportsResult(imports, journal, category); + updateImportsResult(imports, invoiceVoucherJournal, category); } } }); diff --git a/src/lib/utils/report/report_401_generator.ts b/src/lib/utils/report/report_401_generator.ts index 95bcca90b..aae633140 100644 --- a/src/lib/utils/report/report_401_generator.ts +++ b/src/lib/utils/report/report_401_generator.ts @@ -12,11 +12,18 @@ import { } from '@/interfaces/report'; import { getCompanyKYCByCompanyId } from '@/lib/utils/repo/company_kyc.repo'; import { convertTimestampToROCDate } from '@/lib/utils/common'; -import { listJournalFor401 } from '@/lib/utils/repo/journal.repo'; +import { listInvoiceVoucherJournalFor401 } from '@/lib/utils/repo/beta_transition.repo'; import { SPECIAL_ACCOUNTS } from '@/constants/account'; -import { IJournalIncludeVoucherLineItemsInvoicePayment } from '@/interfaces/journal'; import { importsCategories, purchasesCategories, salesCategories } from '@/constants/invoice'; -import { CompanyKYC } from '@prisma/client'; +import { + Account, + CompanyKYC, + Invoice, + InvoiceVoucherJournal, + Journal, + LineItem, + Voucher, +} from '@prisma/client'; export default class Report401Generator extends ReportGenerator { constructor(companyId: number, startDateInSecond: number, endDateInSecond: number) { @@ -45,13 +52,17 @@ export default class Report401Generator extends ReportGenerator { */ private static updateSalesResult( sales: Sales, - journal: IJournalIncludeVoucherLineItemsInvoicePayment, + journal: InvoiceVoucherJournal & { + journal: Journal | null; + invoice: Invoice | null; + voucher: (Voucher & { lineItems: (LineItem & { account: Account })[] }) | null; + }, category: keyof SalesBreakdown ) { const updatedSales = sales; - if (journal.invoice?.payment.hasTax) { - if (journal.invoice?.payment.taxPercentage === 0) { - updatedSales.breakdown[category].zeroTax += journal.invoice?.payment.price ?? 0; + if (journal.invoice?.taxRatio === 0) { + if (journal.invoice?.taxRatio === 0) { + updatedSales.breakdown[category].zeroTax += journal.invoice?.totalPrice ?? 0; } else { let taxPrice = 0; if (journal.voucher?.lineItems) { @@ -71,9 +82,9 @@ export default class Report401Generator extends ReportGenerator { // updatedSales.breakdown.total.tax += journal.invoice?.payment.taxPrice ?? 0; updatedSales.breakdown[category].sales += - (journal.invoice?.payment.price ?? 0) - Math.abs(taxPrice); + (journal.invoice?.priceBeforeTax ?? 0) - Math.abs(taxPrice); updatedSales.breakdown.total.sales += - (journal.invoice?.payment.price ?? 0) - Math.abs(taxPrice); + (journal.invoice?.priceBeforeTax ?? 0) - Math.abs(taxPrice); updatedSales.breakdown[category].tax += Math.abs(taxPrice); updatedSales.breakdown.total.tax += Math.abs(taxPrice); } @@ -100,7 +111,11 @@ export default class Report401Generator extends ReportGenerator { */ private static updatePurchasesResult( purchases: Purchases, - journal: IJournalIncludeVoucherLineItemsInvoicePayment, + journal: InvoiceVoucherJournal & { + journal: Journal | null; + invoice: Invoice | null; + voucher: (Voucher & { lineItems: (LineItem & { account: Account })[] }) | null; + }, category: keyof PurchaseBreakdown ) { const updatedPurchase = purchases; @@ -123,7 +138,7 @@ export default class Report401Generator extends ReportGenerator { if (lineItem.account?.rootCode === SPECIAL_ACCOUNTS.FIXED_ASSET.rootCode) { // Info: (20240920 - Murky) To Jacky, emergency patch, use Input tax code to calculate tax fixedAssets.amount += lineItem.amount; - fixedAssets.tax += lineItem.amount * (journal.invoice?.payment.taxPercentage ?? 0.05); + fixedAssets.tax += lineItem.amount * (journal.invoice?.taxRatio ?? 0.05); } // Info: (20240920 - Murky) To Jacky, emergency patch, use Input tax code to calculate tax @@ -139,7 +154,7 @@ export default class Report401Generator extends ReportGenerator { // tax: (journal.invoice?.payment.taxPrice ?? 0) - fixedAssets.tax, // }; generalPurchases = { - amount: (journal.invoice?.payment.price ?? 0) - Math.abs(inputTax) - fixedAssets.amount, + amount: (journal.invoice?.priceBeforeTax ?? 0) - Math.abs(inputTax) - fixedAssets.amount, tax: Math.abs(inputTax) - fixedAssets.tax, }; } else { @@ -149,7 +164,7 @@ export default class Report401Generator extends ReportGenerator { } }); unDeductible.generalPurchases = - journal.invoice?.payment.price ?? 0 - unDeductible.fixedAssets; + journal.invoice?.priceBeforeTax ?? 0 - unDeductible.fixedAssets; } } @@ -175,12 +190,16 @@ export default class Report401Generator extends ReportGenerator { */ private static updateImportsResult( imports: Imports, - journal: IJournalIncludeVoucherLineItemsInvoicePayment, + journal: InvoiceVoucherJournal & { + journal: Journal | null; + invoice: Invoice | null; + voucher: (Voucher & { lineItems: (LineItem & { account: Account })[] }) | null; + }, category: keyof Imports ) { const updatedImports = imports; - if (!journal.invoice?.payment.hasTax) { - updatedImports[category] += journal.invoice?.payment.price ?? 0; + if (!journal.invoice?.taxRatio) { + updatedImports[category] += journal.invoice?.priceBeforeTax ?? 0; } } @@ -249,7 +268,11 @@ export default class Report401Generator extends ReportGenerator { const ROCStartDate = convertTimestampToROCDate(from); const ROCEndDate = convertTimestampToROCDate(to); // 1. 獲取所有發票 - const journalList = await listJournalFor401(companyId, from, to); + const journalList: (InvoiceVoucherJournal & { + journal: Journal | null; + invoice: Invoice | null; + voucher: (Voucher & { lineItems: (LineItem & { account: Account })[] }) | null; + })[] = await listInvoiceVoucherJournalFor401(companyId, from, to); const basicInfo = { uniformNumber: companyKYC?.registrationNumber ?? '', businessName: companyKYC?.legalName ?? '', diff --git a/src/lib/utils/type_guard/invoice.ts b/src/lib/utils/type_guard/invoice.ts index f3f6b4fdc..8924591a8 100644 --- a/src/lib/utils/type_guard/invoice.ts +++ b/src/lib/utils/type_guard/invoice.ts @@ -1,21 +1,21 @@ -// Info Murky (20240416): Type Guard +// ToDo: (20241011 - Jacky) Should be replace by zod +// // Info Murky (20240416): Type Guard -import { IInvoice, IInvoiceBeta } from '@/interfaces/invoice'; -import { isEventType } from '@/lib/utils/type_guard/account'; -import { isIPayment } from '@/lib/utils/type_guard/payment'; +// import { IInvoice, IInvoiceBeta } from '@/interfaces/invoice'; +// import { isEventType } from '@/lib/utils/type_guard/account'; +// import { isIPayment } from '@/lib/utils/type_guard/payment'; -export function isIInvoice(data: IInvoice): data is IInvoiceBeta { - return ( - (typeof data.journalId === 'number' || data.journalId === null) && - typeof data.date === 'number' && - isEventType(data.eventType) && - typeof data.paymentReason === 'string' && - typeof data.description === 'string' && - typeof data.vendorOrSupplier === 'string' && - (typeof data.projectId === 'number' || data.projectId === null) && - (typeof data.project === 'string' || data.projectId === null) && - (typeof data.contractId === 'number' || data.contractId === null) && - (typeof data.contract === 'string' || data.contract === null) && - isIPayment(data.payment) - ); -} +// export function isIInvoice(data: IInvoice): data is IInvoiceBeta { +// return ( +// typeof data.date === 'number' && +// isEventType(data.eventType) && +// typeof data.paymentReason === 'string' && +// typeof data.description === 'string' && +// typeof data.vendorOrSupplier === 'string' && +// (typeof data.projectId === 'number' || data.projectId === null) && +// (typeof data.project === 'string' || data.projectId === null) && +// (typeof data.contractId === 'number' || data.contractId === null) && +// (typeof data.contract === 'string' || data.contract === null) && +// isIPayment(data.payment) +// ); +// } diff --git a/src/lib/utils/voucher.ts b/src/lib/utils/voucher.ts index 0ed72cde4..724b76bb4 100644 --- a/src/lib/utils/voucher.ts +++ b/src/lib/utils/voucher.ts @@ -1,9 +1,8 @@ import { IVoucherDataForSavingToDB } from '@/interfaces/voucher'; -import { Payment } from '@prisma/client'; export function isVoucherAmountGreaterOrEqualThenPaymentAmount( voucher: IVoucherDataForSavingToDB, - payment: Payment + price: number ): boolean { let debitAmount = 0; let creditAmount = 0; @@ -16,11 +15,8 @@ export function isVoucherAmountGreaterOrEqualThenPaymentAmount( } }); - const paymentAmount = payment.price; - const isDebitCreditEqual = debitAmount === creditAmount; - const isDebitCreditGreaterOrEqualPaymentAmount = - debitAmount >= paymentAmount && creditAmount >= paymentAmount; + const isDebitCreditGreaterOrEqualPaymentAmount = debitAmount >= price && creditAmount >= price; return isDebitCreditEqual && isDebitCreditGreaterOrEqualPaymentAmount; } diff --git a/src/pages/api/v1/company/[companyId]/invoice/[invoiceId]/index.ts b/src/pages/api/v1/company/[companyId]/invoice/[invoiceId]/index.ts index d2734cb86..e8320e4c6 100644 --- a/src/pages/api/v1/company/[companyId]/invoice/[invoiceId]/index.ts +++ b/src/pages/api/v1/company/[companyId]/invoice/[invoiceId]/index.ts @@ -4,15 +4,17 @@ import { formatApiResponse } from '@/lib/utils/common'; import { STATUS_MESSAGE } from '@/constants/status_code'; import { IInvoice } from '@/interfaces/invoice'; import { getSession } from '@/lib/utils/session'; -import { findUniqueInvoiceInPrisma, handlePrismaUpdateLogic } from '@/lib/utils/repo/invoice.repo'; import { formatIInvoice } from '@/lib/utils/formatter/invoice.formatter'; -// import { isIInvoice } from '@/lib/utils/type_guard/invoice'; import { AICH_URI } from '@/constants/config'; import { IAccountResultStatus } from '@/interfaces/accounting_account'; import { checkAuthorization } from '@/lib/utils/auth_check'; import { AuthFunctionsKeys } from '@/interfaces/auth'; import { validateRequest } from '@/lib/utils/request_validator'; import { APIName } from '@/constants/api_connection'; +import { + getInvoiceVoucherJournalByInvoiceId, + handlePrismaUpdateLogic, +} from '@/lib/utils/repo/beta_transition.repo'; async function uploadInvoiceToAICH(invoice: IInvoice) { let response: Response; @@ -81,7 +83,7 @@ async function handleGetRequest( if (query) { const { invoiceId } = query; if (invoiceId > 0) { - const invoiceFromDB = await findUniqueInvoiceInPrisma(invoiceId, companyId); + const invoiceFromDB = await getInvoiceVoucherJournalByInvoiceId(companyId, invoiceId); if (invoiceFromDB) { statusMessage = STATUS_MESSAGE.SUCCESS; payload = formatIInvoice(invoiceFromDB); @@ -122,11 +124,7 @@ async function handlePutRequest( const fetchResult = uploadInvoiceToAICH(invoice); const resultStatus: IAccountResultStatus = await getPayloadFromResponseJSON(fetchResult); - const journalIdBeUpdated = await handlePrismaUpdateLogic( - invoice, - resultStatus.resultId, - companyId - ); + const journalIdBeUpdated = await handlePrismaUpdateLogic(invoice, resultStatus.resultId); statusMessage = STATUS_MESSAGE.SUCCESS_UPDATE; payload = { journalId: journalIdBeUpdated, resultStatus }; } diff --git a/src/pages/api/v1/company/[companyId]/invoice/index.ts b/src/pages/api/v1/company/[companyId]/invoice/index.ts index 9894600dc..8396289de 100644 --- a/src/pages/api/v1/company/[companyId]/invoice/index.ts +++ b/src/pages/api/v1/company/[companyId]/invoice/index.ts @@ -1,13 +1,11 @@ import { NextApiRequest, NextApiResponse } from 'next'; import { IInvoice } from '@/interfaces/invoice'; -import { isIInvoice } from '@/lib/utils/type_guard/invoice'; import { IResponseData } from '@/interfaces/response_data'; import { IAccountResultStatus } from '@/interfaces/accounting_account'; import { formatApiResponse } from '@/lib/utils/common'; import { AICH_URI } from '@/constants/config'; import { STATUS_MESSAGE } from '@/constants/status_code'; import { isIAccountResultStatus } from '@/lib/utils/type_guard/account'; -import { handlePrismaSavingLogic } from '@/lib/utils/repo/invoice.repo'; import { checkAuthorization } from '@/lib/utils/auth_check'; import { getSession } from '@/lib/utils/session'; import { AuthFunctionsKeys } from '@/interfaces/auth'; @@ -16,6 +14,7 @@ import { loggerError, loggerRequest } from '@/lib/utils/logger_back'; import { APIName, APIPath } from '@/constants/api_connection'; import { validateRequest } from '@/lib/utils/request_validator'; import { EventType } from '@/constants/account'; +import { handlePrismaSavingLogic } from '@/lib/utils/repo/beta_transition.repo'; // Info: (20240416 - Murky) Body傳進來會是any function formatInvoice(invoice: IInvoice) { @@ -39,7 +38,7 @@ function formatInvoice(invoice: IInvoice) { contract: invoice.contract ? invoice.contract : null, }; // Info: (20240416 - Murky) Check if invoices is array and is Invoice type - if (Array.isArray(formattedInvoice) || !isIInvoice(formattedInvoice)) { + if (Array.isArray(formattedInvoice)) { throw new Error(STATUS_MESSAGE.INVALID_INPUT_INVOICE_BODY_TO_VOUCHER); } return formattedInvoice; diff --git a/src/pages/api/v1/company/[companyId]/journal/[journalId]/index.ts b/src/pages/api/v1/company/[companyId]/journal/[journalId]/index.ts index 9ce7884e4..29d71a612 100644 --- a/src/pages/api/v1/company/[companyId]/journal/[journalId]/index.ts +++ b/src/pages/api/v1/company/[companyId]/journal/[journalId]/index.ts @@ -3,13 +3,16 @@ import { IResponseData } from '@/interfaces/response_data'; import { formatApiResponse } from '@/lib/utils/common'; import { STATUS_MESSAGE } from '@/constants/status_code'; import { IJournal } from '@/interfaces/journal'; -import { deleteJournalInPrisma, findUniqueJournalInPrisma } from '@/lib/utils/repo/journal.repo'; import { formatIJournal } from '@/lib/utils/formatter/journal.formatter'; import { getSession } from '@/lib/utils/session'; import { checkAuthorization } from '@/lib/utils/auth_check'; import { AuthFunctionsKeys } from '@/interfaces/auth'; import { validateRequest } from '@/lib/utils/request_validator'; import { APIName } from '@/constants/api_connection'; +import { + deleteInvoiceVoucherJournal, + getInvoiceVoucherJournalByJournalId, +} from '@/lib/utils/repo/beta_transition.repo'; async function handleGetRequest( req: NextApiRequest, @@ -32,7 +35,7 @@ async function handleGetRequest( if (query) { const { journalId } = query; try { - const journalData = await findUniqueJournalInPrisma(journalId, companyId); + const journalData = await getInvoiceVoucherJournalByJournalId(journalId); if (journalData) { payload = formatIJournal(journalData); statusMessage = STATUS_MESSAGE.SUCCESS; @@ -70,7 +73,7 @@ async function handleDeleteRequest( if (query) { const { journalId } = query; try { - const journalData = await deleteJournalInPrisma(journalId, companyId); + const journalData = await deleteInvoiceVoucherJournal(journalId, companyId); if (journalData) { payload = formatIJournal(journalData); statusMessage = STATUS_MESSAGE.SUCCESS_DELETE; diff --git a/src/pages/api/v1/company/[companyId]/journal/index.ts b/src/pages/api/v1/company/[companyId]/journal/index.ts index 80613ae0c..eabf9af92 100644 --- a/src/pages/api/v1/company/[companyId]/journal/index.ts +++ b/src/pages/api/v1/company/[companyId]/journal/index.ts @@ -3,7 +3,6 @@ import { IResponseData } from '@/interfaces/response_data'; import { formatApiResponse } from '@/lib/utils/common'; import { STATUS_MESSAGE } from '@/constants/status_code'; import { checkAuthorization } from '@/lib/utils/auth_check'; -import { listJournal } from '@/lib/utils/repo/journal.repo'; import { formatIJournalListItems } from '@/lib/utils/formatter/journal.formatter'; import { IJournalListItem } from '@/interfaces/journal'; import { IPaginatedData } from '@/interfaces/pagination'; @@ -12,6 +11,7 @@ import { getSession } from '@/lib/utils/session'; import { AuthFunctionsKeys } from '@/interfaces/auth'; import { validateRequest } from '@/lib/utils/request_validator'; import { APIName } from '@/constants/api_connection'; +import { listInvoiceVoucherJournal } from '@/lib/utils/repo/beta_transition.repo'; async function handleGetRequest( req: NextApiRequest, @@ -22,7 +22,6 @@ async function handleGetRequest( const session = await getSession(req, res); const { userId, companyId } = session; - if (!userId) { statusMessage = STATUS_MESSAGE.UNAUTHORIZED_ACCESS; } else { @@ -36,25 +35,24 @@ async function handleGetRequest( const { page, pageSize, eventType, sortBy, sortOrder, startDate, endDate, searchQuery } = query; try { - const uploadedPaginatedJournalList = await listJournal( + const uploadedPaginatedJournalList = await listInvoiceVoucherJournal( companyId, JOURNAL_EVENT.UPLOADED, + eventType, page, pageSize, - eventType, sortBy, sortOrder, startDate, endDate, searchQuery ); - - const upComingPaginatedJournalList = await listJournal( + const upComingPaginatedJournalList = await listInvoiceVoucherJournal( companyId, JOURNAL_EVENT.UPCOMING, + eventType, page, pageSize, - eventType, sortBy, sortOrder, startDate, diff --git a/src/pages/api/v1/company/[companyId]/ocr/[resultId]/index.ts b/src/pages/api/v1/company/[companyId]/ocr/[resultId]/index.ts index 7967deee9..532114362 100644 --- a/src/pages/api/v1/company/[companyId]/ocr/[resultId]/index.ts +++ b/src/pages/api/v1/company/[companyId]/ocr/[resultId]/index.ts @@ -8,7 +8,6 @@ import { transformBytesToFileSizeString, } from '@/lib/utils/common'; import { STATUS_MESSAGE } from '@/constants/status_code'; -import { isIInvoice } from '@/lib/utils/type_guard/invoice'; import { IContract } from '@/interfaces/contract'; import { deleteOcrByResultId, getOcrByResultId } from '@/lib/utils/repo/ocr.repo'; import { IOCR } from '@/interfaces/ocr'; @@ -18,7 +17,7 @@ import { checkAuthorization } from '@/lib/utils/auth_check'; import { AuthFunctionsKeys } from '@/interfaces/auth'; import { getAichUrl } from '@/lib/utils/aich'; import { AICH_APIS_TYPES } from '@/constants/aich'; -import loggerBack, { loggerError } from '@/lib/utils/logger_back'; +import { loggerError } from '@/lib/utils/logger_back'; import { ocrTypes } from '@/constants/ocr'; import { validateRequest } from '@/lib/utils/request_validator'; import { APIName } from '@/constants/api_connection'; @@ -113,11 +112,6 @@ export async function handleGetRequest(resultId: string, ocrType: ocrTypes = ocr let newOcr: IInvoice | null = setOCRResultJournalId(ocrResult, null); newOcr = formatOCRResultDate(newOcr); - if (!isIInvoice(newOcr)) { - loggerBack.info('ocr/[resultId]: OCR result(newOcr) is not an invoice type'); - newOcr = null; - } - ocrResult = newOcr; } diff --git a/src/pages/api/v1/company/[companyId]/payment/index.ts b/src/pages/api/v1/company/[companyId]/payment/index.ts index 2f5c1cc2a..1ba96c159 100644 --- a/src/pages/api/v1/company/[companyId]/payment/index.ts +++ b/src/pages/api/v1/company/[companyId]/payment/index.ts @@ -2,12 +2,7 @@ import { STATUS_MESSAGE } from '@/constants/status_code'; import { OEN_BASE_ENDPOINT, OEN_MERCHANT_ENDPOINT } from '@/constants/url'; import { IResponseData } from '@/interfaces/response_data'; import { checkAuthorization } from '@/lib/utils/auth_check'; -import { - convertDateToTimestamp, - convertStringToNumber, - formatApiResponse, - timestampInSeconds, -} from '@/lib/utils/common'; +import { convertStringToNumber, formatApiResponse } from '@/lib/utils/common'; import { getOrderDetailById, updateOrder } from '@/lib/utils/repo/order.repo'; import { createPaymentRecord } from '@/lib/utils/repo/payment_record.repo'; import { createSubscription } from '@/lib/utils/repo/subscription.repo'; @@ -140,7 +135,7 @@ async function handlePostRequest(req: NextApiRequest) { const oenMerchantId = process.env.PAYMENT_ID ?? ''; // Info: (20240823 - Murky) customId 格式會是 orderId-subPlan-subPeriod const { token, customId } = oenReturn; - const { orderId, subPlan, subPeriod } = decryptCustomId(customId); + const { orderId, subPeriod } = decryptCustomId(customId); const getOrder = await getOrderDetailById(orderId); if (!getOrder) { @@ -154,7 +149,7 @@ async function handlePostRequest(req: NextApiRequest) { }, body: JSON.stringify({ merchantId: oenMerchantId, - amount: getOrder.plan.monthlyFee, + amount: getOrder.plan.price, currency: OEN_CURRENCY[CurrencyType.TWD], token, orderId: customId, @@ -172,18 +167,19 @@ async function handlePostRequest(req: NextApiRequest) { } ); const transactionResponseJson = await transactionResponse.json(); - const createDate = convertDateToTimestamp(transactionResponseJson.data.createdAt); - const createDateInSec = timestampInSeconds(createDate); // Info: (20240806 - Jacky) Create payment record - const paymentDescription = `${subPlan} - ${subPeriod}`; const paymentRecord = await createPaymentRecord( orderId, - transactionResponseJson.data.transactionId, - createDateInSec, - paymentDescription, // Info (20240822 - Murky) Add subscription plan and period to payment description + transactionResponseJson.data.id, + transactionResponseJson.data.action, transactionResponseJson.data.amount, - transactionResponseJson.data.paymentInfo.method, - transactionResponseJson.data.status + transactionResponseJson.data.fee, + transactionResponseJson.data.method, + transactionResponseJson.data.cardIssuerCountry, + transactionResponseJson.data.status, + transactionResponseJson.data.createdAt, + transactionResponseJson.data.refundAmount, + transactionResponseJson.data.authCode ); payload = paymentRecord.status; // Info: (20240806 - Jacky) Update order status diff --git a/src/pages/api/v1/company/[companyId]/salary/voucher/index.ts b/src/pages/api/v1/company/[companyId]/salary/voucher/index.ts index 3a2dee3c6..3181a092b 100644 --- a/src/pages/api/v1/company/[companyId]/salary/voucher/index.ts +++ b/src/pages/api/v1/company/[companyId]/salary/voucher/index.ts @@ -1,21 +1,18 @@ import type { NextApiRequest, NextApiResponse } from 'next'; import { IResponseData } from '@/interfaces/response_data'; import { STATUS_MESSAGE } from '@/constants/status_code'; -import { formatApiResponse } from '@/lib/utils/common'; +import { formatApiResponse, timestampInSeconds } from '@/lib/utils/common'; import { getSession } from '@/lib/utils/session'; import { getAdminByCompanyIdAndUserId } from '@/lib/utils/repo/admin.repo'; import { IFolder } from '@/interfaces/folder'; -import { - createLineItemInPrisma, - createVoucherInPrisma, - getLatestVoucherNoInPrisma, -} from '@/lib/utils/repo/voucher.repo'; +import { createLineItemInPrisma, getLatestVoucherNoInPrisma } from '@/lib/utils/repo/voucher.repo'; import { createSalaryRecordJournal, getInfoFromSalaryRecordLists, createVoucherFolder, createVoucherSalaryRecordFolderMapping, } from '@/lib/utils/repo/salary_record.repo'; +import { createVoucher } from '@/lib/utils/repo/beta_transition.repo'; function checkInput(salaryRecordsIdsList: number[], voucherType: string): boolean { return ( @@ -86,10 +83,12 @@ async function handlePostRequest( statusMessage = STATUS_MESSAGE.FORBIDDEN; } else { // Info: (20240715 - Gibbs) create journal - const journalId = await createSalaryRecordJournal(companyId); + await createSalaryRecordJournal(companyId); // Info: (20240715 - Gibbs) create voucher const newVoucherNo = await getLatestVoucherNoInPrisma(companyId); - const voucherData = await createVoucherInPrisma(newVoucherNo, journalId); + const now = Date.now(); + const nowTimestamp = timestampInSeconds(now); + const voucherData = await createVoucher(newVoucherNo, companyId, nowTimestamp); // Info: (20240715 - Gibbs) create line items await createLineItems(voucherType, voucherData.id, companyId, salaryRecordsIdsList); // Info: (20240715 - Gibbs) create folder diff --git a/src/pages/api/v1/company/[companyId]/voucher/[voucherId]/index.ts b/src/pages/api/v1/company/[companyId]/voucher/[voucherId]/index.ts index 94ad04459..b37540246 100644 --- a/src/pages/api/v1/company/[companyId]/voucher/[voucherId]/index.ts +++ b/src/pages/api/v1/company/[companyId]/voucher/[voucherId]/index.ts @@ -26,11 +26,15 @@ async function handleVoucherUpdatePrismaLogic( try { const journal = await findUniqueJournalInvolveInvoicePaymentInPrisma(voucher.journalId); - if (!journal || !journal.invoice || !journal.invoice.payment) { + if (!journal || !journal.invoiceVoucherJournals || !journal.invoiceVoucherJournals) { throw new Error(STATUS_MESSAGE.RESOURCE_NOT_FOUND); } - if (!isVoucherAmountGreaterOrEqualThenPaymentAmount(voucher, journal.invoice.payment)) { + const { invoiceVoucherJournals } = journal; + const amount = invoiceVoucherJournals.reduce((sum, invoiceVoucherJournal) => { + return sum + (invoiceVoucherJournal.invoice?.totalPrice || 0); + }, 0); + if (!isVoucherAmountGreaterOrEqualThenPaymentAmount(voucher, amount)) { throw new Error(STATUS_MESSAGE.INVALID_VOUCHER_AMOUNT); } diff --git a/src/pages/api/v1/company/[companyId]/voucher/index.ts b/src/pages/api/v1/company/[companyId]/voucher/index.ts index ce851525b..6310baeb2 100644 --- a/src/pages/api/v1/company/[companyId]/voucher/index.ts +++ b/src/pages/api/v1/company/[companyId]/voucher/index.ts @@ -11,20 +11,14 @@ import { formatApiResponse } from '@/lib/utils/common'; import { STATUS_MESSAGE } from '@/constants/status_code'; import { checkAuthorization } from '@/lib/utils/auth_check'; -import { - createLineItemInPrisma, - createVoucherInPrisma, - findUniqueVoucherInPrisma, - getLatestVoucherNoInPrisma, - findUniqueJournalInvolveInvoicePaymentInPrisma, -} from '@/lib/utils/repo/voucher.repo'; +import { createLineItemInPrisma, findUniqueVoucherInPrisma } from '@/lib/utils/repo/voucher.repo'; import { getSession } from '@/lib/utils/session'; import { AuthFunctionsKeys } from '@/interfaces/auth'; -import { IJournalFromPrismaIncludeInvoicePayment } from '@/interfaces/journal'; import { isVoucherAmountGreaterOrEqualThenPaymentAmount } from '@/lib/utils/voucher'; import { loggerError } from '@/lib/utils/logger_back'; import { validateRequest } from '@/lib/utils/request_validator'; import { APIName } from '@/constants/api_connection'; +import { getInvoiceVoucherJournalByJournalId } from '@/lib/utils/repo/beta_transition.repo'; type ApiResponseType = IVoucherDataForAPIResponse | null; @@ -36,32 +30,37 @@ async function handleVoucherCreatePrismaLogic( let statusMessage: string = STATUS_MESSAGE.INTERNAL_SERVICE_ERROR; try { - const journal: IJournalFromPrismaIncludeInvoicePayment | null = - await findUniqueJournalInvolveInvoicePaymentInPrisma(voucher.journalId); + const invoiceVoucherJournal = await getInvoiceVoucherJournalByJournalId(voucher.journalId || 0); - if (!journal || !journal.invoice || !journal.invoice.payment) { + if (!invoiceVoucherJournal || !invoiceVoucherJournal.invoice) { throw new Error(STATUS_MESSAGE.RESOURCE_NOT_FOUND); } - if (!isVoucherAmountGreaterOrEqualThenPaymentAmount(voucher, journal.invoice.payment)) { + if ( + !isVoucherAmountGreaterOrEqualThenPaymentAmount( + voucher, + invoiceVoucherJournal.invoice.totalPrice + ) + ) { throw new Error(STATUS_MESSAGE.INVALID_VOUCHER_AMOUNT); } - const newVoucherNo = await getLatestVoucherNoInPrisma(companyId); - const voucherData = await createVoucherInPrisma(newVoucherNo, journal.id); // Info: (20240925 - Murky) I need to make sure lineitems is created in order // Deprecated: (20240926 - Murky) Need to find better way to sort line items /* eslint-disable no-restricted-syntax */ + if (!invoiceVoucherJournal.voucher) { + throw new Error(STATUS_MESSAGE.RESOURCE_NOT_FOUND); + } for (const lineItem of voucher.lineItems) { // Deprecated: (20240926 - Murky) Need to find better way to sort line items /* eslint-disable no-await-in-loop */ - await createLineItemInPrisma(lineItem, voucherData.id, companyId); + await createLineItemInPrisma(lineItem, invoiceVoucherJournal.voucher.id, companyId); /* eslint-enable no-await-in-loop */ } /* eslint-enable no-restricted-syntax */ // Info: ( 20240613 - Murky)Get the voucher data again after creating the line items - updatedVoucher = await findUniqueVoucherInPrisma(voucherData.id); + updatedVoucher = await findUniqueVoucherInPrisma(invoiceVoucherJournal.voucher?.id || 1000); statusMessage = STATUS_MESSAGE.CREATED; } catch (_error) { const error = _error as Error; @@ -141,7 +140,24 @@ export default async function handler( companyId, voucher ); - payload = updatedVoucher; + if ( + updatedVoucher && + updatedVoucher.id && + updatedVoucher.invoiceVoucherJournals[0].journalId && + updatedVoucher.invoiceVoucherJournals[0].journal + ) { + const formattedVoucher = { + ...updatedVoucher, + id: updatedVoucher.id, + journalId: updatedVoucher.invoiceVoucherJournals[0].journalId, + journal: updatedVoucher.invoiceVoucherJournals[0].journal, + lineItems: updatedVoucher.lineItems, + }; + payload = formattedVoucher; + statusMessage = message; + } else { + throw new Error(STATUS_MESSAGE.INVALID_INPUT_VOUCHER_BODY_TO_JOURNAL); + } statusMessage = message; } } else {