From cdd9203c74e294b82e109033ad5da30928f02879 Mon Sep 17 00:00:00 2001 From: RexBearIU Date: Fri, 11 Oct 2024 18:47:30 +0800 Subject: [PATCH 01/10] feat: beta schema --- package.json | 2 +- .../migrations/2_beta_migrate/migration.sql | 499 ++++++ prisma/schema.prisma | 399 +++-- prisma/seed.ts | 25 +- prisma/seed_json/asset.json | 40 + prisma/seed_json/asset_voucher.json | 16 + prisma/seed_json/counterparty.json | 24 + prisma/seed_json/line_item.json | 63 - prisma/seed_json/payment_record.json | 42 +- prisma/seed_json/plan.json | 36 +- prisma/seed_json/subscription.json | 25 +- prisma/seed_json/voucher.json | 69 +- src/interfaces/invoice.ts | 36 +- src/interfaces/journal.ts | 17 - src/interfaces/payment_record.ts | 8 +- src/interfaces/subscription.ts | 1 + src/interfaces/voucher.ts | 6 +- src/lib/utils/formatter/invoice.formatter.ts | 68 +- src/lib/utils/formatter/journal.formatter.ts | 159 +- src/lib/utils/repo/beta_transition.repo.ts | 714 +++++++++ src/lib/utils/repo/folder.repo.ts | 10 +- src/lib/utils/repo/invoice.beta.repo.ts | 697 ++++----- src/lib/utils/repo/invoice.repo.ts | 1238 +++++++-------- src/lib/utils/repo/journal.repo.ts | 1385 ++++++++++++----- src/lib/utils/repo/line_item.beta.repo.ts | 12 +- src/lib/utils/repo/line_item.repo.ts | 12 +- src/lib/utils/repo/payment_record.repo.ts | 18 +- src/lib/utils/repo/subscription.repo.ts | 1 + src/lib/utils/repo/voucher.beta.repo.ts | 639 ++++---- src/lib/utils/repo/voucher.repo.ts | 117 +- .../repo_test/payment_record.repo.test.ts | 19 +- .../report/cash_flow_statement_generator.ts | 8 +- src/lib/utils/report/report_401.ts | 86 +- src/lib/utils/report/report_401_generator.ts | 57 +- src/lib/utils/type_guard/invoice.ts | 38 +- src/lib/utils/voucher.ts | 8 +- .../[companyId]/invoice/[invoiceId]/index.ts | 14 +- .../v1/company/[companyId]/invoice/index.ts | 5 +- .../[companyId]/journal/[journalId]/index.ts | 9 +- .../v1/company/[companyId]/journal/index.ts | 12 +- .../[companyId]/ocr/[resultId]/index.ts | 8 +- .../v1/company/[companyId]/payment/index.ts | 28 +- .../[companyId]/salary/voucher/index.ts | 15 +- .../[companyId]/voucher/[voucherId]/index.ts | 8 +- .../v1/company/[companyId]/voucher/index.ts | 50 +- 45 files changed, 4411 insertions(+), 2332 deletions(-) create mode 100644 prisma/migrations/2_beta_migrate/migration.sql create mode 100644 prisma/seed_json/asset.json create mode 100644 prisma/seed_json/asset_voucher.json create mode 100644 prisma/seed_json/counterparty.json create mode 100644 src/lib/utils/repo/beta_transition.repo.ts diff --git a/package.json b/package.json index c4a624902..c08ce1cb9 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "iSunFA", - "version": "0.8.2+34", + "version": "0.8.2+35", "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..904e90e0e --- /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 + } +] \ No newline at end of file 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..25bc31466 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 } -] +] \ No newline at end of file diff --git a/prisma/seed_json/plan.json b/prisma/seed_json/plan.json index d8f3774e9..4ac3ba0d9 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": 9.99, + "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": 19.99, + "createdAt": 1627849200, + "updatedAt": 1627849200, + "deletedAt": null + }, + { + "id": 1002, + "name": "Enterprise Plan", + "description": "This is an enterprise plan.", + "billingCycle": "yearly", + "price": 199.99, + "createdAt": 1627849200, + "updatedAt": 1627849200, + "deletedAt": null } -] +] \ No newline at end of file diff --git a/prisma/seed_json/subscription.json b/prisma/seed_json/subscription.json index a215d967b..e9cf77465 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 } -] +] \ No newline at end of file diff --git a/prisma/seed_json/voucher.json b/prisma/seed_json/voucher.json index 2b9f04547..7d205c6ba 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 } -] +] \ No newline at end of file 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 cc619f839..030c236c7 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; 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 index 20ec42002..d1bb78c7b 100644 --- a/src/lib/utils/repo/invoice.beta.repo.ts +++ b/src/lib/utils/repo/invoice.beta.repo.ts @@ -1,348 +1,349 @@ -// 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; -} +// ToDo: (20241011 - Jacky) Temporarily commnet the following code for the transition +// // 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 index 9620486b2..a50a89e28 100644 --- a/src/lib/utils/repo/invoice.repo.ts +++ b/src/lib/utils/repo/invoice.repo.ts @@ -1,619 +1,619 @@ -// 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; -} +// // 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 } 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 index dcebbd117..79b49e3b7 100644 --- a/src/lib/utils/repo/journal.repo.ts +++ b/src/lib/utils/repo/journal.repo.ts @@ -1,400 +1,985 @@ -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; -} +// // ToDo: (20241011 - Jacky) Temporarily commnet the following code for the beta transition +// import prisma from '@/client'; +// import { DEFAULT_PAGE_LIMIT } from '@/constants/config'; +// import { STATUS_MESSAGE } from '@/constants/status_code'; +// import { IJournalFromPrismaIncludeProjectContractInvoiceVoucher } from '@/interfaces/journal'; +// import { IPaginatedData } from '@/interfaces/pagination'; +// import { +// Account, +// Certificate, +// File, +// Invoice, +// InvoiceVoucherJournal, +// Journal, +// LineItem, +// Ocr, +// Prisma, +// Voucher, +// } from '@prisma/client'; +// import { calculateTotalPages, timestampInSeconds } from '@/lib/utils/common'; +// import { JOURNAL_EVENT, SortBy } from '@/constants/journal'; +// import { SortOrder } from '@/constants/sort'; +// import { loggerError } from '@/lib/utils/logger_back'; +// import { IInvoice } from '@/interfaces/invoice'; +// import { EventType, ProgressStatus } from '@/constants/account'; +// import { InvoiceType } from '@/constants/invoice'; + +// // 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, +// }, +// invoiceVoucherJournals: { every: { voucher: { type: eventType } } }, +// AND: [ +// { OR: [{ deletedAt: 0 }, { deletedAt: null }] }, +// { +// OR: [ +// { +// invoiceVoucherJournals: { +// every: { vendorOrSupplier: { contains: searchQuery, mode: 'insensitive' } }, +// }, +// }, +// { +// invoiceVoucherJournals: { +// every: { description: { contains: searchQuery, mode: 'insensitive' } }, +// }, +// }, +// { +// invoiceVoucherJournals: { +// every: { 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; +// // } + +// 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; +// }) +// | null +// > { +// const where: Prisma.InvoiceVoucherJournalWhereInput = { +// invoiceId, +// voucher: { +// companyId, +// }, +// }; +// const journal = await prisma.invoiceVoucherJournal.findFirst({ +// where, +// include: { +// journal: true, +// invoice: { include: { certificate: { include: { file: true } } } }, +// voucher: { include: { lineItems: { include: { account: true } } } }, +// }, +// }); +// return journal; +// } + +// export async function getInvoiceVoucherJournalByJournalId(journalId: number): Promise< +// | (InvoiceVoucherJournal & { +// journal: Journal | null; +// invoice: Invoice | null; +// voucher: (Voucher & { lineItems: (LineItem & { account: Account })[] }) | null; +// }) +// | null +// > { +// const where: Prisma.InvoiceVoucherJournalWhereInput = { +// journalId, +// }; +// const include = { +// journal: true, +// invoice: 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 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 = { +// ...formattedInvoice, +// inputOrOutput: 'input', +// no: 'no', +// currencyAlias: 'TWD', +// priceBeforeTax: 0, +// 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 createInvoiceVoucherJournal( +// journalId: number, +// invoiceId: number, +// voucher?: number +// ) { +// const now = Date.now(); +// const nowTimestamp = timestampInSeconds(now); +// const data: Prisma.InvoiceVoucherJournalCreateInput = { +// journal: { +// connect: { +// id: journalId, +// }, +// }, +// invoice: { +// connect: { +// id: invoiceId, +// }, +// }, +// voucher: { +// connect: { +// id: voucher, +// }, +// }, +// 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 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); +// } +// await prisma.invoiceVoucherJournal.update({ +// where: { +// id: invoiceVoucherJournal.id, +// }, +// data: { +// invoice: { +// update: { +// ...formattedInvoice, +// 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 +// ); + +// await createInvoice(formattedInvoice, journalIdBeCreated, ocrIdInDB?.imageFileId); + +// createInvoiceVoucherJournal(journalIdBeCreated, formattedInvoice.journalId || 0); + +// // 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 listInvoiceVoucherJournalFor401( +// companyId: number, +// startDateInSecond: number, +// endDateInSecond: number +// ): Promise< +// (InvoiceVoucherJournal & { +// journal: Journal | null; +// invoice: Invoice | null; +// voucher: (Voucher & { lineItems: (LineItem & { account: Account })[] }) | null; +// })[] +// > { +// 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/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 index 0cdf7fe44..9e7ce12ab 100644 --- a/src/lib/utils/repo/voucher.beta.repo.ts +++ b/src/lib/utils/repo/voucher.beta.repo.ts @@ -1,319 +1,320 @@ -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: 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; -} +// ToDo: (20241011 - Jacky) Temporarily commnet the following code for the beta transition +// 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: 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 a41f47259..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,7 +304,11 @@ export async function findManyVoucherWithCashInPrisma( }, }, include: { - journal: true, + invoiceVoucherJournals: { + include: { + journal: true, + }, + }, lineItems: { include: { account: true, @@ -326,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, @@ -358,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, }, }); } @@ -390,7 +401,7 @@ export async function updateVoucherByJournalIdInPrisma( }, }, voucher: { - connect: { id: journalExists?.voucher?.id }, + connect: { id: voucherExists.id }, }, createdAt: nowInSecond, updatedAt: nowInSecond, @@ -400,7 +411,7 @@ export async function updateVoucherByJournalIdInPrisma( ); const voucherBeUpdated = await prismaClient.voucher.update({ where: { - id: journalExists.voucher.id, + id: voucherExists.id, }, data: { updatedAt: nowInSecond, @@ -409,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 60b2dfe64..258f3b5f2 100644 --- a/src/lib/utils/report/cash_flow_statement_generator.ts +++ b/src/lib/utils/report/cash_flow_statement_generator.ts @@ -54,13 +54,15 @@ export default class CashFlowStatementGenerator extends FinancialReportGenerator endDateInSecond ); this.voucherRelatedToCash = voucherRelatedToCash.filter((voucher) => { - const laterThanStartDate = voucher.journal.createdAt >= startDateInSecond; - const earlierThanEndDate = voucher.journal.createdAt <= endDateInSecond; + // ToDo: (20241011 - Jacky) To Murky, This is a temporary fix for the voucher with cash flow + const laterThanStartDate = voucher.createdAt >= startDateInSecond; + const earlierThanEndDate = voucher.createdAt <= endDateInSecond; return laterThanStartDate && earlierThanEndDate; }); this.voucherLastPeriod = voucherRelatedToCash.filter((voucher) => { - const earlierThanStartDate = voucher.journal.createdAt < startDateInSecond; + // ToDo: (20241011 - Jacky) To Murky, This is a temporary fix for the voucher with cash flow + const earlierThanStartDate = voucher.createdAt < 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 { From e23bedca7853c7a1d4732e5a420a64be80b2e90d Mon Sep 17 00:00:00 2001 From: RexBearIU Date: Fri, 11 Oct 2024 19:02:00 +0800 Subject: [PATCH 02/10] fix: merge conflict --- package.json | 2 +- src/interfaces/voucher.ts | 4 ++-- .../report/cash_flow_statement_generator.ts | 17 ++++++++++------- 3 files changed, 13 insertions(+), 10 deletions(-) diff --git a/package.json b/package.json index 8cc0589d3..ed46610d4 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "iSunFA", - "version": "0.8.2+46", + "version": "0.8.2+47", "private": false, "scripts": { "dev": "next dev", diff --git a/src/interfaces/voucher.ts b/src/interfaces/voucher.ts index e739b6652..b42bd2bb1 100644 --- a/src/interfaces/voucher.ts +++ b/src/interfaces/voucher.ts @@ -64,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/report/cash_flow_statement_generator.ts b/src/lib/utils/report/cash_flow_statement_generator.ts index 37f8c5cd6..69c96ddb0 100644 --- a/src/lib/utils/report/cash_flow_statement_generator.ts +++ b/src/lib/utils/report/cash_flow_statement_generator.ts @@ -56,15 +56,18 @@ export default class CashFlowStatementGenerator extends FinancialReportGenerator startDateInSecond, endDateInSecond ); - this.voucherRelatedToCash = voucherRelatedToCash.filter((voucher) => { - - const laterThanStartDate = voucher.date >= startDateInSecond; - const earlierThanEndDate = voucher.date <= endDateInSecond; - 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.date < startDateInSecond; return earlierThanStartDate; }); From e7c6baaa627a18f69864a1a6ac563ccb25cb582c Mon Sep 17 00:00:00 2001 From: Luphia Chang Date: Sun, 13 Oct 2024 13:36:49 +0800 Subject: [PATCH 03/10] Update package.json --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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", From 51b29ebddf05ba7ffe0d548fd94759f71c5f0f0d Mon Sep 17 00:00:00 2001 From: Luphia Chang Date: Sun, 13 Oct 2024 13:39:30 +0800 Subject: [PATCH 04/10] Update counterparty.json --- prisma/seed_json/counterparty.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/prisma/seed_json/counterparty.json b/prisma/seed_json/counterparty.json index 904e90e0e..c8dccdbd9 100644 --- a/prisma/seed_json/counterparty.json +++ b/prisma/seed_json/counterparty.json @@ -21,4 +21,4 @@ "updatedAt": 1625130800, "deletedAt": null } -] \ No newline at end of file +] From 8aa3e298dec255b9c3a8442d581c03590e97a5d5 Mon Sep 17 00:00:00 2001 From: Luphia Chang Date: Sun, 13 Oct 2024 13:41:28 +0800 Subject: [PATCH 05/10] Update payment_record.json --- prisma/seed_json/payment_record.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/prisma/seed_json/payment_record.json b/prisma/seed_json/payment_record.json index 25bc31466..08fd0695f 100644 --- a/prisma/seed_json/payment_record.json +++ b/prisma/seed_json/payment_record.json @@ -33,4 +33,4 @@ "updatedAt": 1696243200, "deletedAt": null } -] \ No newline at end of file +] From a4e20ddbcd4121e8c5bf3b858c260221bece7d04 Mon Sep 17 00:00:00 2001 From: Luphia Chang Date: Sun, 13 Oct 2024 13:41:49 +0800 Subject: [PATCH 06/10] Update plan.json --- prisma/seed_json/plan.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/prisma/seed_json/plan.json b/prisma/seed_json/plan.json index 4ac3ba0d9..898723e59 100644 --- a/prisma/seed_json/plan.json +++ b/prisma/seed_json/plan.json @@ -29,4 +29,4 @@ "updatedAt": 1627849200, "deletedAt": null } -] \ No newline at end of file +] From 515f59cfd072b2a1813bf1d45066d4524f2c1cc9 Mon Sep 17 00:00:00 2001 From: RexBearIU Date: Mon, 14 Oct 2024 10:34:32 +0800 Subject: [PATCH 07/10] fix: delete unneed repo --- package.json | 2 +- src/lib/utils/repo/invoice.beta.repo.ts | 349 --------- src/lib/utils/repo/invoice.repo.ts | 619 --------------- src/lib/utils/repo/journal.repo.ts | 985 ------------------------ src/lib/utils/repo/voucher.beta.repo.ts | 320 -------- 5 files changed, 1 insertion(+), 2274 deletions(-) delete mode 100644 src/lib/utils/repo/invoice.beta.repo.ts delete mode 100644 src/lib/utils/repo/invoice.repo.ts delete mode 100644 src/lib/utils/repo/journal.repo.ts delete mode 100644 src/lib/utils/repo/voucher.beta.repo.ts diff --git a/package.json b/package.json index cc34f731a..ea703626c 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "iSunFA", - "version": "0.8.2+48", + "version": "0.8.2+49", "private": false, "scripts": { "dev": "next dev", 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 d1bb78c7b..000000000 --- a/src/lib/utils/repo/invoice.beta.repo.ts +++ /dev/null @@ -1,349 +0,0 @@ -// ToDo: (20241011 - Jacky) Temporarily commnet the following code for the transition -// // 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 a50a89e28..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 } 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 79b49e3b7..000000000 --- a/src/lib/utils/repo/journal.repo.ts +++ /dev/null @@ -1,985 +0,0 @@ -// // ToDo: (20241011 - Jacky) Temporarily commnet the following code for the beta transition -// import prisma from '@/client'; -// import { DEFAULT_PAGE_LIMIT } from '@/constants/config'; -// import { STATUS_MESSAGE } from '@/constants/status_code'; -// import { IJournalFromPrismaIncludeProjectContractInvoiceVoucher } from '@/interfaces/journal'; -// import { IPaginatedData } from '@/interfaces/pagination'; -// import { -// Account, -// Certificate, -// File, -// Invoice, -// InvoiceVoucherJournal, -// Journal, -// LineItem, -// Ocr, -// Prisma, -// Voucher, -// } from '@prisma/client'; -// import { calculateTotalPages, timestampInSeconds } from '@/lib/utils/common'; -// import { JOURNAL_EVENT, SortBy } from '@/constants/journal'; -// import { SortOrder } from '@/constants/sort'; -// import { loggerError } from '@/lib/utils/logger_back'; -// import { IInvoice } from '@/interfaces/invoice'; -// import { EventType, ProgressStatus } from '@/constants/account'; -// import { InvoiceType } from '@/constants/invoice'; - -// // 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, -// }, -// invoiceVoucherJournals: { every: { voucher: { type: eventType } } }, -// AND: [ -// { OR: [{ deletedAt: 0 }, { deletedAt: null }] }, -// { -// OR: [ -// { -// invoiceVoucherJournals: { -// every: { vendorOrSupplier: { contains: searchQuery, mode: 'insensitive' } }, -// }, -// }, -// { -// invoiceVoucherJournals: { -// every: { description: { contains: searchQuery, mode: 'insensitive' } }, -// }, -// }, -// { -// invoiceVoucherJournals: { -// every: { 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; -// // } - -// 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; -// }) -// | null -// > { -// const where: Prisma.InvoiceVoucherJournalWhereInput = { -// invoiceId, -// voucher: { -// companyId, -// }, -// }; -// const journal = await prisma.invoiceVoucherJournal.findFirst({ -// where, -// include: { -// journal: true, -// invoice: { include: { certificate: { include: { file: true } } } }, -// voucher: { include: { lineItems: { include: { account: true } } } }, -// }, -// }); -// return journal; -// } - -// export async function getInvoiceVoucherJournalByJournalId(journalId: number): Promise< -// | (InvoiceVoucherJournal & { -// journal: Journal | null; -// invoice: Invoice | null; -// voucher: (Voucher & { lineItems: (LineItem & { account: Account })[] }) | null; -// }) -// | null -// > { -// const where: Prisma.InvoiceVoucherJournalWhereInput = { -// journalId, -// }; -// const include = { -// journal: true, -// invoice: 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 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 = { -// ...formattedInvoice, -// inputOrOutput: 'input', -// no: 'no', -// currencyAlias: 'TWD', -// priceBeforeTax: 0, -// 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 createInvoiceVoucherJournal( -// journalId: number, -// invoiceId: number, -// voucher?: number -// ) { -// const now = Date.now(); -// const nowTimestamp = timestampInSeconds(now); -// const data: Prisma.InvoiceVoucherJournalCreateInput = { -// journal: { -// connect: { -// id: journalId, -// }, -// }, -// invoice: { -// connect: { -// id: invoiceId, -// }, -// }, -// voucher: { -// connect: { -// id: voucher, -// }, -// }, -// 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 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); -// } -// await prisma.invoiceVoucherJournal.update({ -// where: { -// id: invoiceVoucherJournal.id, -// }, -// data: { -// invoice: { -// update: { -// ...formattedInvoice, -// 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 -// ); - -// await createInvoice(formattedInvoice, journalIdBeCreated, ocrIdInDB?.imageFileId); - -// createInvoiceVoucherJournal(journalIdBeCreated, formattedInvoice.journalId || 0); - -// // 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 listInvoiceVoucherJournalFor401( -// companyId: number, -// startDateInSecond: number, -// endDateInSecond: number -// ): Promise< -// (InvoiceVoucherJournal & { -// journal: Journal | null; -// invoice: Invoice | null; -// voucher: (Voucher & { lineItems: (LineItem & { account: Account })[] }) | null; -// })[] -// > { -// 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/voucher.beta.repo.ts b/src/lib/utils/repo/voucher.beta.repo.ts deleted file mode 100644 index 9e7ce12ab..000000000 --- a/src/lib/utils/repo/voucher.beta.repo.ts +++ /dev/null @@ -1,320 +0,0 @@ -// ToDo: (20241011 - Jacky) Temporarily commnet the following code for the beta transition -// 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: 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; -// } From 3ce052176b222f623b20b45b97c6941b25d45e75 Mon Sep 17 00:00:00 2001 From: RexBearIU Date: Mon, 14 Oct 2024 10:43:30 +0800 Subject: [PATCH 08/10] feat: add eol --- package.json | 2 +- prisma/seed_json/subscription.json | 2 +- prisma/seed_json/voucher.json | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package.json b/package.json index ea703626c..a528924a2 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "iSunFA", - "version": "0.8.2+49", + "version": "0.8.2+50", "private": false, "scripts": { "dev": "next dev", diff --git a/prisma/seed_json/subscription.json b/prisma/seed_json/subscription.json index e9cf77465..4c011508e 100644 --- a/prisma/seed_json/subscription.json +++ b/prisma/seed_json/subscription.json @@ -23,4 +23,4 @@ "updatedAt": 1672531200, "deletedAt": null } -] \ No newline at end of file +] diff --git a/prisma/seed_json/voucher.json b/prisma/seed_json/voucher.json index 7d205c6ba..5cbdf2304 100644 --- a/prisma/seed_json/voucher.json +++ b/prisma/seed_json/voucher.json @@ -29,4 +29,4 @@ "updatedAt": 1672617600, "deletedAt": null } -] \ No newline at end of file +] From 95ca35b6b81ebb0e50eb4fb914a5f37961cc3aa4 Mon Sep 17 00:00:00 2001 From: Luphia Chang Date: Mon, 14 Oct 2024 11:06:40 +0800 Subject: [PATCH 09/10] Update plan.json --- prisma/seed_json/plan.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/prisma/seed_json/plan.json b/prisma/seed_json/plan.json index 898723e59..0d53f0c0c 100644 --- a/prisma/seed_json/plan.json +++ b/prisma/seed_json/plan.json @@ -4,7 +4,7 @@ "name": "Basic Plan", "description": "This is a basic plan.", "billingCycle": "monthly", - "price": 9.99, + "price": 89, "createdAt": 1627849200, "updatedAt": 1627849200, "deletedAt": null @@ -14,7 +14,7 @@ "name": "Pro Plan", "description": "This is a pro plan.", "billingCycle": "monthly", - "price": 19.99, + "price": 899, "createdAt": 1627849200, "updatedAt": 1627849200, "deletedAt": null @@ -24,7 +24,7 @@ "name": "Enterprise Plan", "description": "This is an enterprise plan.", "billingCycle": "yearly", - "price": 199.99, + "price": 8990, "createdAt": 1627849200, "updatedAt": 1627849200, "deletedAt": null From f6c92a9be8d80fce45e44ea48ff951fbaa045047 Mon Sep 17 00:00:00 2001 From: Luphia Chang Date: Mon, 14 Oct 2024 11:07:14 +0800 Subject: [PATCH 10/10] Update package.json --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index a528924a2..cc34f731a 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "iSunFA", - "version": "0.8.2+50", + "version": "0.8.2+48", "private": false, "scripts": { "dev": "next dev",