import { assign, createMachine, StateMachine } from '@xstate/fsm'; import { DealState, DealStateEvent, DealStateContext } from './deal-state'; export enum DealAction { Idle = '00', // 초기 Margin = '10', // 증거금 // 계약서 재작성 가능 Contract = '20', // 계약서 ContractEnd = '21', // 계약서 작성 완료 ContractVerifing = '22', // 계약서 확인 완료 // 이 이후는 계약서 재작성 불가능 ContractSigning = '29', // 계약서 사인대기 Down = '30', // 계약금 Interim = '40', // 중도금 Balance = '50', // 잔금 BrokerageFee = "60", // 중개수수료 Complete = '9E', // 완료 Cancel = '9C', // 취소 } export type DealStateFSM = StateMachine.State< DealStateContext, DealStateEvent, DealState >; // FIXME : 이런거 노출되면 안되는 값, 차후 숨길 것 export const DealStateFSM = createMachine< DealStateContext, DealStateEvent, DealState >( { initial: DealAction.Idle, context: { buyer: false, seller: false, owner: false, iter: 0 }, states : { [DealAction.Idle]: { on: { // "CONTRACT-UPDATE": {}, "MARGIN-PAY": { target : DealAction.Margin, actions: assign((ctx, ev) => { if (ev.caller === "buyer") { return { ...ctx, buyer: true, }; } else if (ev.caller === "seller") { return { ...ctx, seller: true, }; } throw new Error(`${ev}, no caller for event`); }), }, CANCEL: { target: DealAction.Cancel, actions: "cancel" }, }, }, [DealAction.Margin]: { on: { // "CONTRACT-UPDATE": {}, "MARGIN-PAY": [ { target: DealAction.Contract, cond(ctx, ev) { return ( (ctx.buyer === true && ev.caller === "seller") || (ctx.seller === true && ev.caller === "buyer") ); }, actions: "clear", }, ], CANCEL: { target: DealAction.Cancel, actions: "cancel" }, }, }, [DealAction.Contract]: { on: { "CONTRACT-UPDATE" : {}, "CONTRACT-COMPLETE": [ { target : DealAction.ContractEnd, actions: "clear", }, ], CANCEL: { target: DealAction.Cancel, actions: "cancel" }, }, }, [DealAction.ContractEnd]: { on: { "CONTRACT-UPDATE": { target: DealAction.Contract, actions: "clear" }, "CONTRACT-VERIFY": { target : DealAction.ContractVerifing, actions: assign((ctx, ev) => { if (ev.caller === "buyer") { return { buyer : true, seller: false, owner : false, iter : 0, }; } else if (ev.caller === "owner") { return { buyer : false, seller: false, owner : true, iter : 0, }; } throw new Error(`${ev}, no caller for event`); }), }, CANCEL: { target: DealAction.Cancel, actions: "cancel" }, }, }, [DealAction.ContractVerifing]: { on: { "CONTRACT-UPDATE": { target: DealAction.Contract, actions: "clear" }, "CONTRACT-VERIFY": [ { cond(ctx, ev) { return ( (ctx.buyer === true && ev.caller === "owner") || (ctx.owner === true && ev.caller === "buyer") ); }, target : DealAction.ContractSigning, actions: "clear", }, ], CANCEL: { target: DealAction.Cancel, actions: "cancel" }, }, }, [DealAction.ContractSigning]: { on: { "CONTRACT-SIGN": [ { target: DealAction.Down, cond(ctx, ev) { return ( (ctx.buyer === true && ctx.seller === true && ev.caller === "owner") || (ctx.buyer === true && ctx.owner === true && ev.caller === "seller") || (ctx.seller === true && ctx.owner === true && ev.caller === "buyer") ); }, actions: "clear", }, { cond(ctx, ev) { switch (ev.caller) { case "buyer": return ctx.buyer === false; case "seller": return ctx.seller === false; case "owner": return ctx.owner === false; } }, actions: assign((ctx, ev) => { switch (ev.caller) { case "buyer": return { ...ctx, buyer: true, }; case "seller": return { ...ctx, seller: true, }; case "owner": return { ...ctx, owner: true, }; } }), }, ], CANCEL: { target: DealAction.Cancel, actions: "cancel" }, }, }, [DealAction.Down]: { on: { "DOWN-PAY": { cond: (ctx, ev) => { return !ctx.buyer; }, actions: assign((ctx, ev) => { return { ...ctx, buyer: true }; }), }, "DOWN-COMPLETE": [ { target: DealAction.Interim, cond : (ctx, ev) => { return ctx.buyer && ev.interim !== undefined && ev.interim >= 1; }, actions: [ "clear", assign((ctx, ev) => { return { ...ctx, iter: Math.max(Math.trunc(ev.interim ?? 0), 1), }; }), ], }, { target: DealAction.Balance, cond : (ctx, ev) => { return ( ctx.buyer && (ev.interim === undefined || ev.interim < 1) ); }, actions: "clear", }, ], CANCEL: { target: DealAction.Cancel, actions: "cancel" }, }, }, [DealAction.Interim]: { on: { "INTERIM-PAY": { cond: (ctx, ev) => { return ctx.iter >= 1 && ctx.buyer === false; }, actions: assign((ctx, ev) => { return { ...ctx, buyer: true, }; }), }, "INTERIM-COMPLETE": [ { cond: (ctx, ev) => { return ctx.iter > 1 && ctx.buyer === true; }, actions: assign((ctx, ev) => { return { ...ctx, buyer: false, iter : ctx.iter - 1, }; }), }, { target: DealAction.Balance, cond : (ctx, ev) => { return ctx.iter === 1 && ctx.buyer === true; }, actions: "clear", }, ], CANCEL: { target: DealAction.Cancel, actions: "cancel" }, }, }, [DealAction.Balance]: { on: { "BALANCE-PAY": { cond: (ctx, ev) => { return !ctx.buyer; }, actions: assign((ctx, ev) => { return { ...ctx, buyer: true }; }), }, "BALANCE-COMPLETE": { target: DealAction.BrokerageFee, cond : (ctx, ev) => { return ctx.buyer; }, actions: "clear", }, CANCEL: { target: DealAction.Cancel, actions: "cancel" }, }, }, [DealAction.BrokerageFee]: { on: { "BROKERAGE-FEE-PAY": { cond: (ctx, ev) => { return ( (ctx.owner === false && ev.caller === "owner") || (ctx.buyer === false && ev.caller === "buyer") ); }, actions: assign((ctx, ev) => { if (ev.caller === "buyer") { return { ...ctx, buyer: true, }; } else if (ev.caller === "owner") { return { ...ctx, owner: true, }; } throw new Error(`${ev}, no caller for event`); }), }, "BROKERAGE-FEE-COMPLETE": { target: DealAction.Complete, cond : (ctx, ev) => { return ctx.buyer && ctx.owner && ev.caller === "seller"; }, actions: "clear", }, CANCEL: { target: DealAction.Cancel, actions: "cancel" }, }, }, [DealAction.Complete]: {}, [DealAction.Cancel] : {}, }, }, { actions: { cancel: assign((act, ev) => { if (ev.type === "CANCEL") { switch (ev.caller) { case "buyer": return { buyer : true, seller: false, owner : false, iter : 0, }; case "seller": return { buyer : false, seller: true, owner : false, iter : 0, }; case "owner": return { buyer : false, seller: false, owner : true, iter : 0, }; default: return { buyer : false, seller: false, owner : false, iter : 0, }; } } throw new Error(`${ev}, no caller for event`); }), clear: assign(() => { return { buyer : false, seller: false, owner : false, iter : 0, }; }), }, } ); export function toFSMState( dstate: DealState, ): StateMachine.State { return { value : dstate.value, context: dstate.context, actions: [], matches: (stateValue) => { return dstate.value === stateValue; }, }; }