Tran Manh Tuan

TaxPal

TaxPal is an accounting-services brokerage platform in Japan. It connects three sides: customers who need an accountant or another expert, the sales and broker team who take each request and see it through, and the accountants and experts who deliver the service. Each side has its own area of the product, and all of them share the same companies, cases and service history.

In 2022 I was the Tech Lead of the team in Vietnam and coordinated the Vietnam side of the project through its main development phase. Our scope was the sales dashboard: the internal analytics the broker team and its managers use to follow cases, KPIs, sales performance and conversion — for each staff member, each team and the whole company.

Why it is a management-system project: the hard part was never drawing charts. It was deciding what a number means — which cases count, on which working days, in which fiscal period, for staff who have since left — and then serving those numbers quickly. It was also brownfield work: new dashboards on an existing AWS codebase, with its own GraphQL resolvers, database, authentication and release process.

The platform and who uses it

Customers bring a need. The sales and broker team turn it into a case, follow it up and move it towards a contract; their managers watch individual, team and company performance. Accountants and experts take the cases that reach them and have their own dashboard.

The TaxPal ecosystemCustomers who need a service enter through the customer area, which creates company, customer and case data. Cases go to the sales and broker team, who work in the sales area; that area updates cases and leads to the sales dashboard. Cases also go to accountants and experts, who use the accountant / expert area and the expert dashboard.

Customers needing a service

Sales / broker team

Accountants / experts

Customer area

Sales / broker area

Accountant / expert area

Company / customer / case

Sales dashboard

Expert dashboard

Diagram fitted to its frame

Three user groups, one shared set of companies and cases. The sales dashboard sits on the broker side of the product.

My responsibilities

  • Tracked and controlled the team's technical progress, schedule and workload across menus and milestones.
  • Coordinated backend, frontend and the IT communicator, and met the Japanese client regularly.
  • Turned business requirements into technical designs; broke down and estimated the work.
  • Reviewed API designs and pull requests.
  • Unblocked hard problems in GraphQL, Prisma, the database and integration, and coordinated bug fixes between frontend and backend.
  • Assessed schedule risk and cleared up unclear points in the business documents with the client.
  • Managed hand-over and releases through the client's repository process, and reported status, issues, causes and fixes.

From request to contract: the case pipeline

Every customer need is a case, linked to a company record that becomes the master data for its appointments, progress and contracts. A salesperson can be assigned by hand — the system does not force an automatic assignment rule on the organisation. From there a case moves through many sales states: likely to get an appointment, blocked by an issue, difficult, contacted but not reached, appointment confirmed or cancelled, following up, contract signed, accounting contract signed, paused, cancelled, cancelled at renewal. In the version described, salespeople choose the status themselves on the case screen rather than a fully automatic state machine setting it.

How a case moves through the pipelineA customer need arrives, the system creates or finds the company, creates a case and assigns a salesperson, who contacts the customer. If they cannot connect, they retry and contact again. Once connected, they keep following up and judge whether an appointment is possible: if not, the case stops and is closed; if so, the appointment is confirmed and followed by a meeting and follow-up. If it moves to a contract, a contract and then an accounting contract or service follow; otherwise the case is cancelled or closed. This is a simplified version of the real status system.

No

Yes

No

Yes

Yes

No

A customer need arrives

Create / find the company

Create the case

Assign a salesperson

Contact the customer

Connected?

Keep following up

Not reached / retry

Appointment possible?

Confirm the appointment

Not feasible / stop

Meeting / follow-up

Moving to a contract?

Contract

Accounting contract / service

Cancel / close

Diagram fitted to its frame

A simplified view of the real status system. The case, not the customer, is the unit every count, KPI and conversion rate is calculated from.

What the sales dashboard shows

The dashboard turns cases into management views: new and in-progress cases, intake, KPIs, actual results, sales, success rates, and performance per staff member, per team and for the company. It includes a weekly table of cases per member and team, KPI results by day, month and year, a sales ranking, sales by service over the last four years, and the success rate of the pipeline over periods from two weeks to a year.

Data flow of the sales dashboardSix sources — case and company data, the staff master, the team master, KPI and sales targets, working days and holidays, and sales status — all feed a GraphQL analytics API. The API passes the data through an aggregation and business-rule layer that produces three views: by staff member, by team and for the whole company. The staff view renders as tables and a sales ranking; the team and company views render as charts.

Case / company data

Staff master

Team master

KPI / sales target

Working day / holiday

Sales status

GraphQL analytics API

Aggregation / business rules

By staff member

By team

Whole company

Tables

Charts

Sales ranking

Diagram fitted to its frame

Six sources, one analytics API, one layer of business rules, three levels of aggregation.

KPI targets against actuals

Managers enter sales targets by year, month, staff member and metric. An empty period opens ready for input; a period that already has targets opens them for editing. Targets are then compared with actual results at daily, monthly and yearly scale, so the gap and its trend are visible, not only the total.

KPI target against actualA manager or sales user enters KPI targets, which are stored as sales target data. In parallel, case, sales and contract data are used to calculate the actual figures. Targets and actuals meet in a target-against-actual comparison, shown at three time scales: daily, monthly and yearly.

Manager / sales user

Enter KPI targets

Sales target data

Case / sales / contract data

Calculate actuals

Target vs actual

Daily

Monthly

Yearly

Diagram fitted to its frame

Targets are entered once; actuals come from the same case, sales and contract data the rest of the platform uses.

Counting a customer once: the duplicate-case rule

The same customer can arrive more than once, through different contacts or at different times. The rule groups cases that share a representative, a matching email or phone number, and an intake date inside a set window. Groups can be chains: if A matches B and B matches C, all three belong together. For each group the dashboard picks one representative record and one forecast value, so mail, contact and case counts are not inflated.

The duplicate-case ruleThe system collects cases that may be related and checks three conditions in turn: the same representative, a matching email or phone number, and an intake date inside the rule's window. A case that fails any condition is handled as an independent case. Cases that pass all three go into a duplicate group; the system checks chained relations such as A-B-C, picks a representative record, picks the forecast value by rule, and counts the whole group as one case in the statistics.

No

Yes

No

Yes

No

Yes

Collect cases that may be related

Same representative?

Email / phone match?

Intake date inside the rule?

Put into a duplicate group

Check chained relations A-B-C

Pick a representative record

Pick the forecast value by rule

Count the group as one case

Handle as an independent case

Diagram fitted to its frame

A dashboard figure is only as good as its counting rule. Duplicates are resolved before anything is summed.

Numbers that respect the calendar and the organisation

  • Read-optimised analytics tables. To avoid recomputing everything from transactional tables on every page load, statistics tables store what aggregation needs: the period and its start and end dates, team order, the type of statistic, staff status and working dates. Reads get fast and queries simple; the trade-off is that a changed business rule can mean rebuilding that data.
  • Working days. Remaining working days and valid intake dates are counted against holiday data, not the plain calendar.
  • Fiscal periods. A year chosen on screen can mean a fiscal year running from October to September, so frontend and backend must agree on the date range rather than filter by calendar year.
  • Staff who have left. A retired flag lives in the statistics data, so historical reports keep former staff without treating them as active in every calculation.

The expert dashboard

The project documentation also covers a dashboard for the accountants and experts themselves: income, business performance, strategy, people, succession, IT adoption and customer satisfaction, compared on radar charts against individual, group, company and reference-average benchmarks.

Structure of the expert dashboardThe expert dashboard has seven groups of screens: income, business performance, strategy, people, succession, IT adoption and customer satisfaction. All seven feed a radar chart and benchmark for comparison across dimensions. The overview page also leads to search and reference.

Expert dashboard

Income

Business performance

Strategy

People

Succession

IT adoption

Customer satisfaction

Radar chart / benchmark

Search / reference

Diagram fitted to its frame

Analytics is not only for the internal sales team: the experts get their own view across seven dimensions.

Architecture

The frontend never touches the database. Next.js and React call AWS AppSync through Apollo; the GraphQL schema is authorised with Cognito user pools and IAM; each request goes through resolvers, use cases and repositories, and Prisma reaches PostgreSQL on AWS. The codebase also includes Elasticsearch / OpenSearch for search and indexing, and the AWS infrastructure is defined in Terraform.

TaxPal high-level technical architectureThe browser runs a Next.js, React and TypeScript application, which calls an Apollo GraphQL client, which calls AWS AppSync GraphQL. Cognito and IAM authorise AppSync. AppSync passes requests through a resolver layer, use cases or interactors, and repositories. Repositories read and write PostgreSQL on AWS through Prisma, and work with Elasticsearch or OpenSearch. Terraform defines the AWS infrastructure for AppSync, Cognito and IAM, PostgreSQL and search. This is a portfolio-level diagram based on components in the codebase and documentation, not a copy of the production infrastructure diagram.

Browser

Next.js / React / TypeScript

Apollo / GraphQL client

AWS AppSync GraphQL

Cognito / IAM

Resolver layer

Use case / interactor

Repository

Prisma

PostgreSQL on AWS

Elasticsearch / OpenSearch

Terraform / AWS infrastructure

Diagram fitted to its frame

A portfolio-level view built from the components in the codebase and documents — not a copy of the production infrastructure diagram.

Adding an API meant the same path every time: schema, code generation, resolver, use case, repository, Prisma query, then test and seed data. Pull requests were reviewed on the client's side and every GitHub check had to pass. Database migrations and seed data went in pull requests of their own, so a schema change was never merged together with business code that would be hard to review or roll back.

Building one GraphQL APIFrom a screen or business requirement, the team designs the API, writes the GraphQL schema, runs GraphQL code generation, then writes the resolver, the use case or interactor, the repository and the Prisma query against PostgreSQL. Test and seed data come next, then a pull request.

Screen / business requirement

API design

GraphQL schema

GraphQL codegen

Resolver

Use case / interactor

Repository

Prisma query

PostgreSQL

Test / seed data

Pull request

Diagram fitted to its frame

One path for every new API, from requirement to pull request.

Making the dashboard fast

A dashboard with many widgets and many statistics makes performance a daily concern. The issues found were concrete: APIs selecting far more fields than the screen used, whole relations loaded where a few fields would do, checks made with repeated requests, a screen of about 120 records taking more than a second, and some dashboards calling the API once per team. The fixes: select only what is needed, use relations instead of repeated queries, shrink responses, return several teams from one API, send fewer requests from the frontend, and lazy-load widgets the user has not reached yet — with loading states that tell "still loading" apart from "no data".

Optimising the dashboard APIA dashboard widget can fetch its data two ways. The unoptimised way sends many requests, one per team, and selects too many fields and relations, so responses are slow. The optimised design selects only the data it needs, cuts repeated queries by using relations, aggregates several teams in one API and lazy-loads widgets, giving fewer requests, smaller payloads and faster loading.

Dashboard widget

Unoptimised

Many requests / one per team

Selects too many fields / relations

Slow responses

Optimised design

Select only what is needed

Fewer repeated queries via relations

Aggregate several teams in one API

Lazy-load widgets

Fewer requests / smaller payloads / faster load

Diagram fitted to its frame

The same widget, fetched two ways. Most of the gain came from asking for less, less often.

Charts and QA for a dashboard people manage by

A dashboard people manage by has to survive awkward data. Team colours had to stay readable as the number of teams grew, with teams beyond a threshold sharing one style, and some statistics showing only teams that had members. Values above 100% — up to around 200% — had to stay inside their chart, with readable tooltips and scale rules. Testing therefore went beyond the interface: field mapping, totals, working days, duplicate cases, sorting, statuses, date ranges, filters, zero and null values, and the structure of every API response.

What the project covers

Concern in a large management systemHow it shows up in TaxPal
Sales pipeline with many statesCase statuses from first contact to contract, set by the salespeople
KPI managementTargets by year, month, staff member and metric; target against actual by day, month and year
Multi-level aggregationStaff, team and company views; rankings; sales by service over four years
Data-correctness rulesDuplicate-case groups, working days, fiscal periods, retired staff
Read performancePre-computed analytics tables, selecting only what is needed, one API for many teams, lazy loading
Brownfield deliveryNew modules on an existing AppSync, Prisma and PostgreSQL codebase; migrations in their own pull requests
Working with a client in JapanRegular meetings, requirement clarification, releases through the client's repository

The diagrams are reconstructed at component level from the project's documents. They omit internal URLs, identifiers, proprietary code and client data. The screenshots are design mock-ups and a redrawn data model; staff names, a logo and a portrait are blurred.

Image 1 of 4