Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
07ceadc
feat: add menu item for item posting groups
barbinbrad Aug 14, 2025
66e1d02
feat: add item postign group to item forms
barbinbrad Aug 15, 2025
18472c0
feat: add item posting groups to tables
barbinbrad Aug 15, 2025
adbc607
feat: oauth for quickbooks and xero
barbinbrad Aug 25, 2025
7a6eedd
feat: add zuno interface for accounting connections
barbinbrad Aug 25, 2025
f30b905
feat: add endpoint for quickbooks webhook
barbinbrad Sep 2, 2025
157b3c5
feat: handle webhook with quickbooks
barbinbrad Nov 26, 2025
c8948b5
feat: handle webhook with quickbooks
barbinbrad Sep 3, 2025
fd80636
feat: llm stubout webhook handler for accounting sync
barbinbrad Sep 5, 2025
e221e22
feat: add the ability to edit item group
barbinbrad Sep 13, 2025
bb2dca1
feat: sync customers and vendors from quickbooks
barbinbrad Sep 14, 2025
03fcc6f
feat: add sync customers and vendors from xero
barbinbrad Sep 14, 2025
82419a3
feat: sync suppliers and customers from xero to carbon
barbinbrad Sep 14, 2025
590d118
refactor: fix typescript error for database client
barbinbrad Sep 14, 2025
0edb99f
refactor: use camelCase on accounting sync payload
barbinbrad Sep 15, 2025
7e64285
feat: support recording multi instances of each step in MES
barbinbrad Sep 16, 2025
2788f5a
feat: oauth for quickbooks and xero
barbinbrad Aug 25, 2025
f1d2497
feat: add endpoint for quickbooks webhook
barbinbrad Sep 2, 2025
26c7d63
feat: add the ability to edit item group
barbinbrad Sep 13, 2025
d021a07
feat: sync customers and vendors from quickbooks
barbinbrad Sep 14, 2025
73cb6a1
refactor: fix typescript error for database client
barbinbrad Sep 14, 2025
14fa802
refactor: generator types
barbinbrad Sep 18, 2025
acb0bdc
ci: add temporary logging for callback
barbinbrad Sep 21, 2025
fa5fa72
ci: revert temporary logging
barbinbrad Sep 21, 2025
6d4cae6
ci: add quickbooks and xero variables to sst ci job
barbinbrad Sep 26, 2025
7cadfb0
feat: add filtering to integrations page
barbinbrad Oct 2, 2025
cab9ae4
refactor: update types
barbinbrad Nov 3, 2025
de3ef73
refactor: generate types
barbinbrad Nov 26, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 13 additions & 1 deletion apps/erp/app/components/Form/ItemPostingGroup.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,19 @@ import { useMemo, useRef, useState } from "react";
import type { getItemPostingGroupsList } from "~/modules/items";
import ItemPostingGroupForm from "~/modules/items/ui/ItemPostingGroups/ItemPostingGroupForm";
import { path } from "~/utils/path";
import { Enumerable } from "../Enumerable";

type ItemPostingGroupSelectProps = Omit<ComboboxProps, "options">;
type ItemPostingGroupSelectProps = Omit<ComboboxProps, "options" | "inline"> & {
inline?: boolean;
};

const ItemPostingGroupPreview = (
value: string,
options: { value: string; label: string }[]
) => {
const itemGroup = options.find((o) => o.value === value);
return <Enumerable value={itemGroup?.label ?? null} />;
};

const ItemPostingGroup = (props: ItemPostingGroupSelectProps) => {
const newItemPostingGroupModal = useDisclosure();
Expand All @@ -22,6 +33,7 @@ const ItemPostingGroup = (props: ItemPostingGroupSelectProps) => {
ref={triggerRef}
options={options}
{...props}
inline={props.inline ? ItemPostingGroupPreview : undefined}
label={props?.label ?? "Posting Group"}
onCreateOption={(option) => {
newItemPostingGroupModal.onOpen();
Expand Down
3 changes: 2 additions & 1 deletion apps/erp/app/modules/items/items.models.ts
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,7 @@ export const itemValidator = z.object({
message: "Part type is required",
}),
}),
postingGroupId: zfd.text(z.string().optional()),
unitOfMeasureCode: z
.string()
.min(1, { message: "Unit of Measure is required" }),
Expand Down Expand Up @@ -339,7 +340,7 @@ export const methodOperationValidator = z

export const itemCostValidator = z.object({
itemId: z.string().min(1, { message: "Item ID is required" }),
// itemPostingGroupId: zfd.text(z.string().optional()),
itemPostingGroupId: zfd.text(z.string().optional()),
// costingMethod: z.enum(itemCostingMethods, {
// errorMap: (issue, ctx) => ({
// message: "Costing method is required",
Expand Down
116 changes: 83 additions & 33 deletions apps/erp/app/modules/items/items.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2001,14 +2001,27 @@ export async function upsertConsumable(
if (itemInsert.error) return itemInsert;
const itemId = itemInsert.data?.id;

const consumableInsert = await client.from("consumable").upsert({
id: consumable.id,
companyId: consumable.companyId,
createdBy: consumable.createdBy,
customFields: consumable.customFields,
});
const [consumableInsert, itemCostUpdate] = await Promise.all([
client.from("consumable").upsert({
id: consumable.id,
companyId: consumable.companyId,
createdBy: consumable.createdBy,
customFields: consumable.customFields,
}),
client
.from("itemCost")
.update(
sanitize({
itemPostingGroupId: consumable.postingGroupId,
})
)
.eq("itemId", itemId),
]);

if (consumableInsert.error) return consumableInsert;
if (itemCostUpdate.error) {
console.error(itemCostUpdate.error);
}

const costUpdate = await client
.from("itemCost")
Expand Down Expand Up @@ -2100,14 +2113,27 @@ export async function upsertPart(
if (itemInsert.error) return itemInsert;
const itemId = itemInsert.data?.id;

const partInsert = await client.from("part").upsert({
id: part.id,
companyId: part.companyId,
createdBy: part.createdBy,
customFields: part.customFields,
});
const [partInsert, itemCostUpdate] = await Promise.all([
client.from("part").upsert({
id: part.id,
companyId: part.companyId,
createdBy: part.createdBy,
customFields: part.customFields,
}),
client
.from("itemCost")
.update(
sanitize({
itemPostingGroupId: part.postingGroupId,
})
)
.eq("itemId", itemId),
]);

if (partInsert.error) return partInsert;
if (itemCostUpdate.error) {
console.error(itemCostUpdate.error);
}

if (part.replenishmentSystem !== "Make") {
const costUpdate = await client
Expand Down Expand Up @@ -2633,15 +2659,21 @@ export async function upsertMaterial(
const firstError = itemInserts.find((insert) => insert.error);
return firstError!;
}

const itemIds = itemInserts.map((insert) => insert.data!.id);

await client
.from("itemCost")
.update({
unitCost: material.unitCost,
})
.in("itemId", itemIds);
const itemCostUpdate = await Promise.all(
itemInserts.map((insert) =>
client
.from("itemCost")
.update(
sanitize({
itemPostingGroupId: material.postingGroupId,
})
)
.eq("itemId", insert.data?.id ?? "")
)
);
if (itemCostUpdate.some((update) => update.error)) {
console.error(itemCostUpdate.find((update) => update.error));
}
} else {
const itemInsert = await client
.from("item")
Expand All @@ -2660,13 +2692,18 @@ export async function upsertMaterial(
.select("id")
.single();
if (itemInsert.error) return itemInsert;

await client
const itemId = itemInsert.data?.id;
const itemCostUpdate = await client
.from("itemCost")
.update({
unitCost: material.unitCost,
})
.eq("itemId", itemInsert.data!.id);
.update(
sanitize({
itemPostingGroupId: material.postingGroupId,
})
)
.eq("itemId", itemId);
if (itemCostUpdate.error) {
console.error(itemCostUpdate.error);
}
}

const materialInsert = await client.from("material").upsert({
Expand Down Expand Up @@ -3136,14 +3173,27 @@ export async function upsertTool(
if (itemInsert.error) return itemInsert;
const itemId = itemInsert.data?.id;

const toolInsert = await client.from("tool").upsert({
id: tool.id,
companyId: tool.companyId,
createdBy: tool.createdBy,
customFields: tool.customFields,
});
const [toolInsert, itemCostUpdate] = await Promise.all([
client.from("tool").upsert({
id: tool.id,
companyId: tool.companyId,
createdBy: tool.createdBy,
customFields: tool.customFields,
}),
client
.from("itemCost")
.update(
sanitize({
itemPostingGroupId: tool.postingGroupId,
})
)
.eq("itemId", itemId),
]);

if (toolInsert.error) return toolInsert;
if (itemCostUpdate.error) {
console.error(itemCostUpdate.error);
}

const costUpdate = await client
.from("itemCost")
Expand Down
4 changes: 4 additions & 0 deletions apps/erp/app/modules/items/ui/Consumables/ConsumableForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import {
Hidden,
Input,
InputControlled,
ItemPostingGroup,
Number,
Select,
Submit,
Expand Down Expand Up @@ -156,6 +157,9 @@ const ConsumableForm = ({
name="unitOfMeasureCode"
label="Unit of Measure"
/>
{!isEditing && (
<ItemPostingGroup name="postingGroupId" label="Item Group" />
)}
{!isEditing && (
<Number
name="unitCost"
Expand Down
26 changes: 24 additions & 2 deletions apps/erp/app/modules/items/ui/Consumables/ConsumableProperties.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ import { z } from 'zod/v3';
import { zfd } from "zod-form-data";
import { MethodBadge, MethodIcon, TrackingTypeIcon } from "~/components";
import { Enumerable } from "~/components/Enumerable";
import { Boolean, Tags } from "~/components/Form";
import { Boolean, ItemPostingGroup, Tags } from "~/components/Form";
import CustomFormInlineFields from "~/components/Form/CustomFormInlineFields";
import { ItemThumbnailUpload } from "~/components/ItemThumnailUpload";
import { useRouteData } from "~/hooks";
Expand Down Expand Up @@ -83,6 +83,7 @@ const ConsumableProperties = () => {
| "replenishmentSystem"
| "defaultMethodType"
| "itemTrackingType"
| "itemPostingGroupId"
| "consumableId"
| "active",
value: string | null
Expand Down Expand Up @@ -196,7 +197,8 @@ const ConsumableProperties = () => {
<ValidatedForm
defaultValues={{
consumableId:
routeData?.consumableSummary?.readableIdWithRevision ?? undefined,
routeData?.consumableSummary?.readableIdWithRevision ??
undefined,
}}
validator={z.object({
consumableId: z.string(),
Expand Down Expand Up @@ -256,6 +258,26 @@ const ConsumableProperties = () => {
/>
</VStack> */}

<ValidatedForm
defaultValues={{
itemPostingGroupId:
routeData?.consumableSummary?.itemPostingGroupId ?? undefined,
}}
validator={z.object({
itemPostingGroupId: z.string().nullable().optional(),
})}
className="w-full"
>
<ItemPostingGroup
label="Item Group"
name="itemPostingGroupId"
inline
onChange={(value) => {
onUpdate("itemPostingGroupId", value?.value ?? null);
}}
/>
</ValidatedForm>

<VStack spacing={2}>
<h3 className="text-xs text-muted-foreground">Tracking Type</h3>
<DropdownMenu>
Expand Down
Loading