Brett Parent

ERP and business systems specialist

I run the NetSuite, Celigo and Shopify stack for a cross-border hockey equipment retailer, on my own. Most of what I have built exists because the platform would not do it, and the business was not willing to change how it works to suit the software.

Selected work

CS Hub

Live

Node, TypeScript, Fastify, PostgreSQL, SuiteScript RESTlet, Shopify Admin GraphQL, OAuth and TBA

  • ~90,000sales orders a year in the book it edits against
  • 2 to 4 hoursof customer service time saved a day across the team

The constraint

Customer service edits orders in Shopify and those edits reach NetSuite through a managed integration app. The warehouse runs consolidated multi-order wave picking, and once an order sits on a released wave NetSuite refuses to change committed lines. The sync cannot carry the edit through, it fails, and the two systems drift apart. The integration logic sits inside a managed app, so it cannot be changed, and the wave itself cannot be unpicked by script or through the interface.

What I built

An internal web application that takes the managed integration out of the path for these edits and holds its own direct connections to both systems. An edit or an order care verdict used to take an agent five to ten minutes across two systems depending on complexity; the team now saves two to four hours a day between them. Agents get one screen to swap, add, remove, cancel, relocate and batch-edit lines, change addresses and shipping methods, add notes, and handle returns review, with both systems updated from a single action.

How it works

A NetSuite RESTlet does the work the interface will not allow: it stages and clears pick tasks in the right order, removes fulfillments, closes lines instead of deleting them, and puts everything back in a consistent state. Setting one pick task complete regenerates the wave and re-locks the others, so the whole sequence has to be staged before anything is marked done.

Shopify is edited separately through its order-edit API. The two sides run in a fixed order with one deliberate exception, because a user event script resets line locations on every save.

Every action is written to a Postgres operations log before either system is called, with a status for each system. That log is the reconciliation backstop when one side succeeds and the other does not, and it feeds the usage dashboard. Edits apply automatically, but money never moves until an agent presses the button.

Sign-in is through Google Workspace and restricted to the company domain, with per-user permissions on each action and a request queue for the operations only certain people should approve.

Matrix Item Creator

Live

SuiteScript 2.1 Suitelet, single-page HTML front end, SuiteQL

  • 200 to 300retail items created a month
  • 60 min to 10to build a matrix family, roughly 43 hours saved a month

The constraint

NetSuite computes the matrix parent and child relationship itself and only activates it through its own wizard or a CSV matrix import. Setting it from the record API does nothing at all, so there is no supported way to build a matrix item family by script. A single hockey stick family runs to dozens of variants across hand, flex, curve and length, and every one of them was being created by hand.

What I built

A tool that creates the parent and every child variant in one pass, driven by a single-page interface that purchasing staff run themselves without opening an item record. It replaced a spreadsheet and a long afternoon of data entry per product line.

How it works

A Suitelet serves an HTML front end from the file cabinet and routes every request by mode. The operator sets up one card per parent and picks the options on each dimension; the combinations become the variant list, previewed before anything is created.

The parent is created with pricing applied across both currency price levels in the same save, because price validation runs before the record commits. Nothing is inherited from the parent on a scripted child, so brand, product type, class, weight, country of manufacture, tariff code, description, cost and prices are all written explicitly to every variant.

Building a family used to take up to an hour and now takes ten minutes or less. Most of that comes from a companion script that generates the item name, SKU and barcode on every variant from the parent brand, product type and model plus the options the child carries, so nobody is typing or checking those fields.

Governance was the real problem at scale. The front end splits the work into batches of twelve variants, each its own request, so every batch draws a fresh allowance. That is what keeps a large family from dying halfway through, rather than any amount of reordering inside a single execution.

Receiving Catalog and PO Builder

Live

SuiteScript 2.1 Suitelet, user event script, shared abbreviation library, SuiteQL

  • ~560gear lots catalogued a year
  • about halfof the end-to-end time per lot removed, across ~11,000 items and 13,700 PO lines a year

The constraint

The company buys used gear lots directly from professional hockey teams. A lot arrives with no manifest. Nobody knows what is in the shipment until we open the boxes and go through them piece by piece, so the cataloguing is not a conversion of someone else's data, it is where the data first exists. Every piece then had to be turned into an item one at a time, then a purchase order built by hand, then a quote sheet, an inventory sheet, a transfer file and two sets of labels all produced separately from the same counts. Item naming came out differently depending on who did the work, which broke reporting and made items hard to find later.

What I built

One tool that captures the lot as it is counted, creates or updates the purchase order, and generates every downstream file from that single pass. The count becomes the record, and item naming was taken out of human hands entirely and moved into a script.

How it works

The operator captures the quote header, then types a parent product name and the server detects that family's dimensions and configures the columns automatically. Detecting those dimensions has to be done by projecting the child rows, because the matrix relationship columns on the item table return wrong answers under counts, null checks and aggregates.

The second tab either creates a new purchase order or loads an existing one and syncs it back against the cataloged rows, showing current quantity against updated quantity before anything is committed. The third generates the quote sheet, inventory sheet, transfer file and the two label formats, grouped so the imports do not collide.

Naming is owned by a separate user event script on inventory parts, not by the tool, and it is most of why a build that took up to an hour now takes ten minutes. On every create and edit it rebuilds the item name, display name, SKU and barcode from the parent's brand, product type and model, plus an abbreviated suffix for each matrix option the child carries, drawn from a shared abbreviation library. Staff can set a manual code on a child and the script builds around it, so the naming survives a parent rename. The result is that every item is named the same way no matter who created it.

RMA and returns automation

Live

SuiteScript 2.1 user event, Shopify GraphQL via N/https, third-party returns platform

  • ~250returns handled a month, 1,000+ since launch
  • ~100 hoursof manual crediting removed a year, with a reason now on every return against none before

The constraint

Returns come back through a third-party returns platform that creates the return authorization in NetSuite. Every return then had to be credited and refunded by hand, and the accounting had to be built to match a refund Shopify had already issued on its own side. The obvious automation does not work: the return reaches its refundable state during the receiving process rather than through a user edit, so a script attached to the return authorization record never fires on that transition, whatever event type you give it.

What I built

A logic map that reads the return reason supplied by the returns platform and produces the correct documents on its own, the moment the goods are physically received.

How it works

A custom return reason field on the return authorization form is populated by the returns platform when the customer chooses how they want to be made whole. The script hangs off item receipt creation instead of the return record, which is the one trigger point that reliably catches the transition, and loads the return from there.

From the reason it branches three ways. A refund produces a credit memo and a customer refund routed to the right payment method. An exchange produces a credit memo and a negative cart-level discount line at the pre-tax subtotal. Store credit produces a credit memo and a negative gift card redemption line at the post-tax total. In each case the general ledger impact in NetSuite mirrors what the return already did in Shopify.

Shopify tracking identifiers carry across to the refund document, and for one payment method the script calls the Shopify API directly to retrieve the authorization key that makes the refund reconcile. All three branches are live, and a one-time backfill cleared the backlog that built up before it existed.

Also built

Data quality frameworkA deployable framework with a custom exception record, a rules engine and dashboard portlets. Duplicate SKUs and barcodes, blank item codes, payment variances and open partial-payment invoices surface daily instead of at close.
Bidirectional inventory mirrorTwo storefronts kept in step through webhooks, pre-save and post-response hooks and results mapping, including a cleanup for duplicate variant mappings.
Operational portletsBack orders, open customer deposits, reorder points split by location, and unmatched payment processor deposits, each surfaced where the person who acts on it already works.
Shipping and picking KPI dashboardDaily picked and shipped figures by location, scored on the same system the warehouse already uses, refreshing on its own.
Tax and compliance scriptingProvince-based tax code correction on orders arriving from the storefront, bulk correction of historical records, and customs documentation with tariff code breakdowns for cross-border shipments.
AI connected to the ERPA Claude connection to NetSuite over the Model Context Protocol, built and permission audited, used for querying, reporting and development support.

Toolkit

NetSuite
SuiteScript 2.x across user event, map/reduce, scheduled, Suitelet, RESTlet and portlet. SuiteQL, SuiteFlow, saved searches, dashboards, SDF and the SuiteCloud CLI, sandbox refresh and release management.
Modules
OneWorld, WMS and wave picking, advanced inventory, sales order management, accounts receivable and payable, SuiteTax and AvaTax.
Integration
Celigo integrator.io, REST and RESTlets, Shopify Admin GraphQL, OAuth and token-based authentication, JSON and XML, Handlebars.
Application
Node and TypeScript, Fastify, PostgreSQL, single-page front ends served from NetSuite, deployment and environment management.
Experience
NetSuite three years, Shopify five years, ShipStation three years, Celigo eighteen months.

Background

2023 to present
ERP and business systems specialist, HockeyStickMan

Sole technical resource for NetSuite customization, Celigo integration and two Shopify storefronts.

2021 to 2022
Purchasing and operations coordinator, HockeyStickMan

End-to-end purchasing in NetSuite, item creation, vendor deliveries, bank reconciliation and financial close support. The manual bottlenecks found here are most of what later got automated.

Earlier
Shipping, retail, receiving and logistics, HockeyStickMan

Joined when the company had fewer than ten employees and ran logistics for the opening of the Belleville warehouse. The warehouse floor is where I learned what the ERP is actually for.

2015 to 2021
Brock University

MA, Applied Health Sciences (Sport Management). BA, Sport Management with honours.