From 250bbca226f69b269808e425d92a02cf88fad619 Mon Sep 17 00:00:00 2001 From: HAM Date: Fri, 12 Sep 2025 15:19:51 +0700 Subject: [PATCH 1/2] feat: create product for flowAccount service --- src/services/flowaccount.ts | 184 ++++++++++++++++++++++++++++++++++++ 1 file changed, 184 insertions(+) diff --git a/src/services/flowaccount.ts b/src/services/flowaccount.ts index 80c0ca0..df20be0 100644 --- a/src/services/flowaccount.ts +++ b/src/services/flowaccount.ts @@ -3,6 +3,7 @@ import config from "../config.json"; import { CustomerType, PayCondition } from "@prisma/client"; import { convertTemplate } from "../utils/string-template"; import { htmlToText } from "html-to-text"; +import { JsonObject } from "@prisma/client/runtime/library"; if (!process.env.FLOW_ACCOUNT_URL) throw new Error("Require FLOW_ACCOUNT_URL"); if (!process.env.FLOW_ACCOUNT_CLIENT_ID) throw new Error("Require FLOW_ACCOUNT_CLIENT_ID"); @@ -388,6 +389,189 @@ const flowAccount = { } return null; }, + + // flowAccount GET Product list + async getProducts() { + const { token } = await flowAccountAPI.auth(); + + const res = await fetch(api + "/products", { + method: "GET", + headers: { + ["Content-Type"]: `application/json`, + ["Authorization"]: `Bearer ${token}`, + }, + }); + + return { + ok: res.ok, + status: res.status, + body: await res.json(), + }; + }, + + // flowAccount GET Product by id + async getProductsById(recordId: string) { + const { token } = await flowAccountAPI.auth(); + + const res = await fetch(api + `/products/${recordId}`, { + method: "GET", + headers: { + ["Content-Type"]: `application/json`, + ["Authorization"]: `Bearer ${token}`, + }, + }); + + const data = await res.json(); + + return { + ok: res.ok, + status: res.status, + list: data.data.list, + total: data.data.total, + }; + }, + + // flowAccount POST create Product + async createProducts(body: JsonObject) { + const { token } = await flowAccountAPI.auth(); + + const commonBody = { + productStructureType: null, + type: "3", + name: body.name, + sellDescription: body.detail, + sellVatType: 3, + unitName: "Unit", + categoryName: "Car", + }; // helper function สำหรับสร้าง product + + const createProduct = async (price: any, vatIncluded: boolean) => { + try { + const res = await fetch(api + "/products", { + method: "POST", + headers: { + "Content-Type": "application/json", + Authorization: `Bearer ${token}`, + }, + body: JSON.stringify({ + ...commonBody, + sellPrice: price, + sellVatType: vatIncluded ? 1 : 3, + }), + }); + + if (!res.ok) { + throw new Error(`Request failed with status ${res.status}`); + } // ป้องกัน response ที่ไม่ใช่ JSON หรือว่าง + + let json: any = null; + try { + json = await res.json(); + } catch { + throw new Error("Response is not valid JSON"); + } + + return json?.data?.list?.[0]?.id ?? null; + } catch (err) { + console.error("createProduct error:", err); + return null; + } + }; + + const [sellId, agentId] = await Promise.all([ + createProduct(body.price, /true/.test(`${body.vatIncluded}`)), + createProduct(body.agentPrice, /true/.test(`${body.agentPriceVatIncluded}`)), + ]); + + return { + ok: !!(agentId && sellId), + status: agentId && sellId ? 200 : 500, + data: { + productIdAgentPrice: agentId, + productIdSellPrice: sellId, + }, + }; + }, + + // flowAccount PUT edit Product + async editProducts(sellPriceId: String, agentPriceId: String, body: JsonObject) { + console.log("body: ", body); + const { token } = await flowAccountAPI.auth(); + + const commonBody = { + productStructureType: null, + type: "3", + name: body.name, + sellDescription: body.detail, + sellVatType: 3, + unitName: "Unit", + categoryName: "Car", + }; // helper function สำหรับสร้าง product + + const editProduct = async (id: String, price: any, vatIncluded: boolean) => { + try { + const res = await fetch(api + `/products/${id}`, { + method: "PUT", + headers: { + "Content-Type": "application/json", + Authorization: `Bearer ${token}`, + }, + body: JSON.stringify({ + ...commonBody, + sellPrice: price, + sellVatType: vatIncluded ? 1 : 3, + }), + }); + + if (!res.ok) { + throw new Error(`Request failed with status ${res.status}`); + } // ป้องกัน response ที่ไม่ใช่ JSON หรือว่าง + + let json: any = null; + try { + json = await res.json(); + } catch { + throw new Error("Response is not valid JSON"); + } + + return json?.data?.list?.[0]?.id ?? null; + } catch (err) { + console.error("createProduct error:", err); + return null; + } + }; + + const [agentId, sellId] = await Promise.all([ + editProduct(sellPriceId, body.price, /true/.test(`${body.vatIncluded}`)), + editProduct(agentPriceId, body.agentPrice, /true/.test(`${body.agentPriceVatIncluded}`)), + ]); + + return { + ok: !!(agentId && sellId), + status: agentId && sellId ? 200 : 500, + data: { + productIdAgentPrice: agentId, + productIdSellPrice: sellId, + }, + }; + }, + + // flowAccount DELETE Product + async deleteProduct(recordId: string) { + const { token } = await flowAccountAPI.auth(); + + const res = await fetch(api + `/products/${recordId}`, { + method: "DELETE", + headers: { + ["Authorization"]: `Bearer ${token}`, + }, + }); + + return { + ok: res.ok, + status: res.status, + }; + }, }; export default flowAccount; From 61825309d1224a42a9cf14c08b57278d64bdd3b1 Mon Sep 17 00:00:00 2001 From: HAM Date: Fri, 12 Sep 2025 15:20:20 +0700 Subject: [PATCH 2/2] feat: sync data from data to flowAccount --- src/controllers/04-product-controller.ts | 31 ++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/src/controllers/04-product-controller.ts b/src/controllers/04-product-controller.ts index b50462c..c1c40d8 100644 --- a/src/controllers/04-product-controller.ts +++ b/src/controllers/04-product-controller.ts @@ -30,6 +30,7 @@ import { deleteFile, deleteFolder, fileLocation, getFile, listFile, setFile } fr import { isUsedError, notFoundError, relationError } from "../utils/error"; import { queryOrNot, whereDateQuery } from "../utils/relation"; import spreadsheet from "../utils/spreadsheet"; +import flowAccount from "../services/flowaccount"; const MANAGE_ROLES = [ "system", @@ -299,6 +300,9 @@ export class ProductController extends Controller { }, update: { value: { increment: 1 } }, }); + + const listId = await flowAccount.createProducts(body); + return await prisma.product.create({ include: { createdBy: true, @@ -306,6 +310,8 @@ export class ProductController extends Controller { }, data: { ...body, + flowAccountProductIdAgentPrice: `${listId.data.productIdAgentPrice}`, + flowAccountProductIdSellPrice: `${listId.data.productIdSellPrice}`, document: body.document ? { createMany: { data: body.document.map((v) => ({ name: v })) }, @@ -379,6 +385,19 @@ export class ProductController extends Controller { await permissionCheck(req.user, productGroup.registeredBranch); } + if ( + product.flowAccountProductIdSellPrice !== null && + product.flowAccountProductIdAgentPrice !== null + ) { + await flowAccount.editProducts( + product.flowAccountProductIdSellPrice, + product.flowAccountProductIdAgentPrice, + body, + ); + } else { + throw notFoundError("FlowAccountProductId"); + } + const record = await prisma.product.update({ include: { productGroup: true, @@ -441,6 +460,18 @@ export class ProductController extends Controller { if (record.status !== Status.CREATED) throw isUsedError("Product"); + if ( + record.flowAccountProductIdSellPrice !== null && + record.flowAccountProductIdAgentPrice !== null + ) { + await Promise.all([ + flowAccount.deleteProduct(record.flowAccountProductIdSellPrice), + flowAccount.deleteProduct(record.flowAccountProductIdAgentPrice), + ]); + } else { + throw notFoundError("FlowAccountProductId"); + } + await deleteFolder(fileLocation.product.img(productId)); return await prisma.product.delete({