Skip to content

Conversation

@jeebeez
Copy link
Collaborator

@jeebeez jeebeez commented Dec 2, 2025

What does this PR do?

Visual Demo (For contributors especially)

Video Demo (if applicable):

https://www.loom.com/share/9502f7d06a7a4cbcb3093bf25aa9e444

Mandatory Tasks (DO NOT REMOVE)

  • I have self-reviewed the code (A decent-sized PR without self-review might be rejected).
  • I confirm automated tests are in place that prove my fix is effective or that my feature works.

How should this be tested?

  • Try to recreate the step in the demo (Send, Submit, Decline) for an external supplier quote.

- updated supplier quote status to support new enum values
- added new share link and UI for digital supplier quote
@vercel
Copy link

vercel bot commented Dec 2, 2025

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Preview Comments Updated (UTC)
academy Ready Ready Preview Comment Dec 9, 2025 8:49am
carbon Ready Ready Preview Comment Dec 9, 2025 8:49am
mes Ready Ready Preview Comment Dec 9, 2025 8:49am

- added error handling to services
- Replacing Active with Draft everywhere
Comment on lines +42 to +79
DROP POLICY IF EXISTS "Employees with purchasing_view can view purchasing-related external links" ON "externalLink";
CREATE POLICY "Employees with purchasing_view can view purchasing-related external links" ON "externalLink"
FOR SELECT
USING (
"documentType" = 'SupplierQuote' AND
has_role('employee', "companyId") AND
has_company_permission('purchasing_view', "companyId")
);

-- Insert
DROP POLICY IF EXISTS "Employees with purchasing_create can insert purchasing-related external links" ON "externalLink";
CREATE POLICY "Employees with purchasing_create can insert purchasing-related external links" ON "externalLink"
FOR INSERT
WITH CHECK (
"documentType" = 'SupplierQuote' AND
has_role('employee', "companyId") AND
has_company_permission('purchasing_create', "companyId")
);

-- Update
DROP POLICY IF EXISTS "Employees with purchasing_update can update purchasing-related external links" ON "externalLink";
CREATE POLICY "Employees with purchasing_update can update purchasing-related external links" ON "externalLink"
FOR UPDATE
USING (
"documentType" = 'SupplierQuote' AND
has_role('employee', "companyId") AND
has_company_permission('purchasing_update', "companyId")
);

-- Delete
DROP POLICY IF EXISTS "Employees with purchasing_delete can delete purchasing-related external links" ON "externalLink";
CREATE POLICY "Employees with purchasing_delete can delete purchasing-related external links" ON "externalLink"
FOR DELETE
USING (
"documentType" = 'SupplierQuote' AND
has_role('employee', "companyId") AND
has_company_permission('purchasing_delete', "companyId")
);
Copy link
Contributor

@barbinbrad barbinbrad Dec 8, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One thing I've learned so far is that it's very non-performant to have multiple RLS policies for a single table like this. It would be great if you could consolidate all the policies for this table into a single policy for each (SELECT, INSERT, UPDATE, DELETE)

For the select policy we can do something simple like:

CREATE POLICY "SELECT" ON "externalLink"
FOR SELECT 
USING (
  "companyId" = ANY (
    (
      SELECT
        get_companies_with_employee_role()
    )::text[]
  )
);

And then for INSERT/UPDATE/DELETE, we can add in the specific documentType/permission combo:

I'm not sure exactly what the code would be but something like:

CREATE POLICY "INSERT" ON "public"."group"
FOR INSERT WITH CHECK (
  "documentType" = 'SupplierQuote' AND "companyId" = ANY (
    (
      SELECT
        get_companies_with_employee_permission('purchasing_create')
    )::text[]
  ) OR
  "documentType" = 'Quote' AND "companyId" = ANY (
    (
      SELECT
        get_companies_with_employee_permission('sales_create')
    )::text[]
  ) 
);

exchangeRateUpdatedAt: zfd.text(z.string().optional()),
});

export const externalSupplierQuoteValidator = z.object({
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good, but let's try to keep these alphabetized

}

export async function finalizeSupplierQuote(
client: SupabaseClient<Database>,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same nit, please alphabetize

});

if (externalLink.data) {
await client
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what happens if this fails?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch, my bad will add a try catch.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it will never error, it just returns a result that is of type: { data: T; error: null; } | { data: null; error: PostgresError; } so we can just do something like:

if (externalLink.data) {
  const result = await client.suchAndSuch();
  if (result.error) {
    // handle the error either by returning it or something
  }
}

const { id } = params;
if (!id) throw new Error("Could not find supplier quote id");

const [quote] = await Promise.all([getSupplierQuote(client, id)]);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why are we doing a Promise.all on a single item?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm fine with keeping these. It's a little silly in this case, but it makes it easy to rewrite to fetch more data.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, exactly, saw the pattern everywhere else, and it makes sense. This is to ensure that subsequent loading operations are performed in a similar manner. At least, that was my interpretation 😅


// Reuse existing external link or create one if it doesn't exist
const [externalLink] = await Promise.all([
upsertExternalLink(client, {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same question here


// Reuse existing external link or create one if it doesn't exist
const [externalLink] = await Promise.all([
upsertExternalLink(client, {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same thing here

const { id } = params;
if (!id) throw new Error("Could not find supplier quote id");

const [quote] = await Promise.all([getSupplierQuote(client, id)]);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same here

Copy link
Collaborator

@sidwebworks sidwebworks left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM, just a few comments

@barbinbrad barbinbrad changed the title Feat/add digital supplier quotes External Supplier Quotes Dec 9, 2025
@barbinbrad
Copy link
Contributor

barbinbrad commented Dec 9, 2025

Looks really nice! Couple UI updates:

  • We shouldn’t be able to send a quote to no one (no contact + resend integration)
  • We can remove the Share buttons and Preview buttons
  • If there is no contact and resend integration, the send modal should just display the link
  • When it’s active:
    • the external quote should be read only (checkmarks and fields), and remove the edit icons/prompt
    • the send button should be disabled, and the preview button should be hidden
  • Finalize and send buttons should be disabled if there are no active prices
  • Update the supplier quote email to be:
    • Hey {first name}, please provide pricing and lead time(s) for the linked quote:
    • Thanks,
    • {full name of sender}
    • {company name}

@barbinbrad
Copy link
Contributor

Looks great overall. Just a few things to polish up.

@jeebeez
Copy link
Collaborator Author

jeebeez commented Dec 9, 2025

  • Finalize and send buttons should be disabled if there are no active prices

Got it done. Just confused about this one. When you usually add a line item the default price is zero right so shouldn't we only disable the Finalize button and not the Send button

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Digital Supplier Quotes

4 participants