Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
22 changes: 19 additions & 3 deletions apps/web-app/app/components/InvoiceCard.vue
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
<UCard class="group/list">
<div class="flex flex-col gap-2.5">
<div class="flex flex-row justify-between">
<UIcon name="i-lucide-banknote-arrow-up" class="size-14 text-primary" />
<UIcon name="i-lucide-banknote-arrow-up" class="size-10 text-muted/50" />

<UButton
variant="outline"
Expand All @@ -14,6 +14,10 @@
/>
</div>

<div class="text-sm/4 text-muted">
Создан {{ format(new Date(invoice.createdAt), 'd MMMM в HH:mm', { locale: ru }) }}
</div>

<h3 class="text-xl md:text-xl/6 font-semibold">
{{ new Intl.NumberFormat().format(invoice.total) }} ₽
</h3>
Expand All @@ -22,7 +26,7 @@
{{ invoice.title }}
</p>

<p class="text-sm/4 text-muted">
<p v-if="invoice.description" class="text-sm/4 text-muted">
{{ invoice.description }}
</p>

Expand All @@ -36,7 +40,7 @@

<UBadge
:label="getInfoByStatus(invoice.status)"
:color="invoice.status === 'unpaid' ? 'error' : 'success'"
:color="invoice.status === 'unpaid' ? 'error' : 'neutral'"
size="md"
variant="soft"
/>
Expand All @@ -48,6 +52,8 @@
<script setup lang="ts">
import type { Invoice } from '@roll-stack/database'
import { ModalUpdateInvoice } from '#components'
import { format } from 'date-fns'
import { ru } from 'date-fns/locale/ru'

Comment on lines +55 to 57
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Fix date‑fns locale import to avoid runtime error

date-fns/locale/ru exports default, not a named ru. Use default import.

-import { format } from 'date-fns'
-import { ru } from 'date-fns/locale/ru'
+import { format } from 'date-fns'
+import ru from 'date-fns/locale/ru'
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
import { format } from 'date-fns'
import { ru } from 'date-fns/locale/ru'
import { format } from 'date-fns'
import ru from 'date-fns/locale/ru'
🤖 Prompt for AI Agents
In apps/web-app/app/components/InvoiceCard.vue around lines 55 to 57, the
date-fns locale is imported as a named export (`{ ru }`) but date-fns/locale/ru
provides a default export; change the import to use the default (`import ru from
'date-fns/locale/ru'`) and update any references that expect a named `ru` to use
the default `ru` variable so the locale is passed correctly to format functions
to avoid the runtime error.

defineProps<{
invoice: Invoice
Expand All @@ -59,8 +65,16 @@ function getInfoByType(type: Invoice['type']) {
return 'Пополнение'
case 'royalties':
return 'Роялти'
case 'lump_sum_fee':
return 'Паушальный взнос'
case 'marketing_fee':
return 'Маркетинговый сбор'
case 'rospatent_fee':
return 'Роспатент'
case 'other':
return 'Другое'
default:
return 'Другое'
}
}

Expand All @@ -70,6 +84,8 @@ function getInfoByStatus(status: Invoice['status']) {
return 'Не оплачен'
case 'paid':
return 'Оплачен'
default:
return 'Неизвестно'
}
}

Expand Down
7 changes: 2 additions & 5 deletions apps/web-app/app/components/form/CreateInvoice.vue
Original file line number Diff line number Diff line change
Expand Up @@ -31,11 +31,7 @@
<UFormField label="Тип" name="type">
<USelect
v-model="state.type"
:items="[
{ label: 'Оплата роялти', value: 'royalties' },
{ label: 'Пополнение', value: 'replenishment' },
{ label: 'Другое', value: 'other' },
]"
:items="getInvoiceTypeForSelect()"
:placeholder="$t('common.select')"
size="xl"
class="w-full"
Expand Down Expand Up @@ -72,6 +68,7 @@
import type { CreatePartnerInvoice } from '#shared/services/partner'
import type { FormSubmitEvent } from '@nuxt/ui'
import { createPartnerInvoiceSchema } from '#shared/services/partner'
import { getInvoiceTypeForSelect } from '#shared/utils/helpers'

const { partnerId } = defineProps<{ partnerId?: string }>()
const emit = defineEmits(['success', 'submitted'])
Expand Down
6 changes: 2 additions & 4 deletions apps/web-app/app/components/form/UpdateInvoice.vue
Original file line number Diff line number Diff line change
Expand Up @@ -48,10 +48,7 @@
<UFormField label="Тип" name="type">
<USelect
v-model="state.type"
:items="[
{ label: 'Оплата роялти', value: 'royalties' },
{ label: 'Пополнение', value: 'replenishment' },
]"
:items="getInvoiceTypeForSelect()"
:placeholder="$t('common.select')"
size="xl"
class="w-full"
Expand Down Expand Up @@ -88,6 +85,7 @@
import type { UpdatePartnerInvoice } from '#shared/services/partner'
import type { FormSubmitEvent } from '@nuxt/ui'
import { updatePartnerInvoiceSchema } from '#shared/services/partner'
import { getInvoiceTypeForSelect } from '#shared/utils/helpers'

const { invoiceId } = defineProps<{
invoiceId: string
Expand Down
5 changes: 2 additions & 3 deletions apps/web-app/app/pages/partner/[id]/invoice.vue
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
<PartnerBalanceCard :balance="partner?.balance ?? 0" />

<InvoiceCard
v-for="invoice in activeInvoices"
v-for="invoice in invoices"
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Fix potential crash and compatibility: optional chain + avoid toSorted

  • If partner is undefined, partner.value?.invoices.toSorted(...) calls toSorted on undefined → runtime error.
  • Array.prototype.toSorted is ES2023; SSR/runtime may not support it.

Use a safe fallback and compatible sort:

-const invoices = computed(() => partner.value?.invoices.toSorted((a, b) => new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime()))
+const invoices = computed(() =>
+  (partner.value?.invoices ?? [])
+    .slice() // clone to avoid mutating store
+    .sort((a, b) => new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime()),
+)

Also applies to: 31-31

🤖 Prompt for AI Agents
In apps/web-app/app/pages/partner/[id]/invoice.vue around lines 7 and 31, the
current expressions call partner.value?.invoices.toSorted(...) which can throw
if partner or invoices is undefined and also uses the ES2023
Array.prototype.toSorted API which may not be available at SSR/runtime; change
both occurrences to safely fall back to an empty array and use a compatible sort
such as [...(partner.value?.invoices ?? [])].sort((a,b) => /* comparator */) or
(partner.value?.invoices ?? []).slice().sort((a,b) => /* comparator */) so you
never call methods on undefined and avoid toSorted. Ensure the comparator
replicates the original sort logic.

:key="invoice.id"
:invoice="invoice"
/>
Expand All @@ -28,8 +28,7 @@ const { params } = useRoute('partner-id')

const partnerStore = usePartnerStore()
const partner = computed(() => partnerStore.partners.find((partner) => partner.id === params.id))

const activeInvoices = computed(() => partner.value?.invoices)
const invoices = computed(() => partner.value?.invoices.toSorted((a, b) => new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime()))

const overlay = useOverlay()
const modalCreateInvoice = overlay.create(ModalCreateInvoice)
Expand Down
16 changes: 8 additions & 8 deletions apps/web-app/server/services/invoice.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,16 +5,16 @@ export async function recountPartnerBalance(partnerId: string) {

let balance = 0
for (const invoice of partnerInvoices) {
if (invoice.type === 'replenishment' && invoice.status === 'paid') {
balance += invoice.total
}
if (invoice.type === 'replenishment') {
if (invoice.status === 'paid') {
balance += invoice.total
}

if (invoice.type === 'royalties') {
balance -= invoice.total
}
if (invoice.type === 'other') {
balance -= invoice.total
continue
}

// All other invoices
balance -= invoice.total
}

await db.partner.update(partnerId, {
Expand Down
11 changes: 11 additions & 0 deletions apps/web-app/shared/utils/helpers.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,17 @@
import type { AgreementPatentStatus, User } from '@roll-stack/database'
import type { Resolution } from '../services/task'

export function getInvoiceTypeForSelect() {
return [
{ label: 'Оплата роялти', value: 'royalties' },
{ label: 'Паушальный взнос', value: 'lump_sum_fee' },
{ label: 'Маркетинговый сбор', value: 'marketing_fee' },
{ label: 'Роспатент', value: 'rospatent_fee' },
{ label: 'Пополнение', value: 'replenishment' },
{ label: 'Другое', value: 'other' },
]
}

export function getResolutionForSelect(): { value: Resolution, label: string, icon: string }[] {
return [
{ value: 'success', label: 'Успешно выполнена', icon: 'i-lucide-circle-check' },
Expand Down
7 changes: 6 additions & 1 deletion packages/database/src/types/entities.ts
Original file line number Diff line number Diff line change
Expand Up @@ -97,5 +97,10 @@ export type ActivityScheduleTag = 'permanent'
| 'optional'
| 'advertising'

export type InvoiceType = 'replenishment' | 'royalties' | 'other'
export type InvoiceType = 'replenishment'
| 'royalties' // Роялти
| 'lump_sum_fee' // Паушальный взнос
| 'marketing_fee' // Маркетинговый сбор
| 'rospatent_fee' // Роспатент
| 'other'
export type InvoiceStatus = 'paid' | 'unpaid'
Comment on lines +100 to 106
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

Ensure validators/DB constraints accept the new InvoiceType values

With new literals added, please verify:

  • DB enum/check constraints (if any) and migrations include these values.
  • API/Zod/DTO validators and UI filters handle them.

Run to find hard‑coded checks that may need updates:


🏁 Script executed:

#!/bin/bash
# Find InvoiceType usages and literal comparisons
rg -nP -C2 "\bInvoiceType\b|invoice\.type\s*[!=]==?\s*['\"][a-z_]+['\"]|['\"](lump_sum_fee|marketing_fee|rospatent_fee)['\"]"

Length of output: 3466


🏁 Script executed:

#!/bin/bash
# Find DB migrations, enum definitions, and validators
echo "=== DB Migrations and Enum Definitions ==="
fd -t f -e sql -e ts -e js -path "*migration*" . 2>/dev/null | head -20

echo -e "\n=== Zod Validators and Schema Definitions ==="
rg -l "ZodType|z\.enum|z\.literal|validator|schema" --type=ts --type=js packages/ apps/ 2>/dev/null | grep -E "(validator|schema|zod)" | head -20

echo -e "\n=== Enum definitions in database package ==="
rg -nP "enum.*Invoice|InvoiceType.*=" packages/database/ --type=ts

echo -e "\n=== Check constraints in table definitions ==="
rg -nP -A2 -B2 "type.*varchar.*InvoiceType|\.check\(" packages/database/src/tables.ts

Length of output: 314


🏁 Script executed:

#!/bin/bash
# Find DB migrations, enum definitions, and validators
echo "=== DB Migrations and SQL Definitions ==="
find . -type f \( -name "*migration*" -o -name "*schema*" \) 2>/dev/null | head -20

echo -e "\n=== Zod Validators in packages ==="
rg -l "z\.enum|z\.literal|validator" --type=ts --type=js 2>/dev/null | grep -v node_modules | head -20

echo -e "\n=== Enum or const for InvoiceType ==="
rg -nP "(enum|const).*Invoice" --type=ts 2>/dev/null

echo -e "\n=== DB Table definition with check constraints ==="
rg -nP -A5 "invoices.*table|type.*varchar.*Invoice" packages/database/src/ --type=ts 2>/dev/null | head -30

Length of output: 3065


🏁 Script executed:

#!/bin/bash
# View the Zod schema definitions for invoices
echo "=== Zod Schema for createPartnerInvoiceSchema ==="
sed -n '55,75p' apps/web-app/shared/services/partner.ts

echo -e "\n=== Zod Schema for updatePartnerInvoiceSchema ==="
sed -n '64,80p' apps/web-app/shared/services/partner.ts

echo -e "\n=== Search for migrations in database package ==="
find packages/database -type f \( -name "*.sql" -o -name "*.ts" -o -name "*.js" \) 2>/dev/null | grep -i migrat

echo -e "\n=== Check for enum() or check constraints in tables.ts ==="
rg -nP "enum\(|\.check\(|values\(" packages/database/src/tables.ts

echo -e "\n=== Full context around invoice table definition ==="
sed -n '790,800p' packages/database/src/tables.ts

Length of output: 2324


🏁 Script executed:

#!/bin/bash
# Check if type() function has enum validation or if used with enum values
echo "=== Definition of type() helper function ==="
rg -nP "export.*const type|function type" apps/web-app/shared/ --type=ts | head -5

echo -e "\n=== Check partner.ts imports ==="
head -20 apps/web-app/shared/services/partner.ts

echo -e "\n=== Search for any InvoiceType validation in routes ==="
rg -nP "InvoiceType|invoiceType" apps/web-app/server/api/ --type=ts

echo -e "\n=== Check for database schema constraints in SQL or migrations ==="
find packages/database -type f -name "*.sql" 2>/dev/null

Length of output: 1317


🏁 Script executed:

#!/bin/bash
# Verify arktype supports literal constraints for enums
echo "=== Full createPartnerInvoiceSchema definition ==="
rg -nP -A15 "export const createPartnerInvoiceSchema" apps/web-app/shared/services/partner.ts

echo -e "\n=== Search for arktype examples with literals in codebase ==="
rg -nP "type\(['\"](.*\s*\|\s*.*)['\"]" apps/web-app/shared/services/ --type=ts | head -10

echo -e "\n=== Check if any schema uses enum patterns ==="
rg -nP "'\w+'\s*\|" apps/web-app/shared/services/partner.ts | head -10

Length of output: 2415


Update Arktype validators to enforce new InvoiceType values

The invoice schemas in apps/web-app/shared/services/partner.ts use generic string validation and will not enforce the new InvoiceType literals. The codebase already demonstrates the correct pattern in task.ts (line 3) with literal unions.

Required fixes:

  • Line 59: Change type: type('string') to type: type('replenishment' | 'royalties' | 'lump_sum_fee' | 'marketing_fee' | 'rospatent_fee' | 'other')
  • Line 60: Change status: type('string') to status: type('paid' | 'unpaid')
  • Line 68: Change type: type('string | undefined') to type: type('replenishment' | 'royalties' | 'lump_sum_fee' | 'marketing_fee' | 'rospatent_fee' | 'other' | undefined)
  • Line 69: Change status: type('string | undefined') to status: type('paid' | 'unpaid' | undefined)

Additionally, no database-level check constraints were found. Consider adding Drizzle check constraints to the invoices table definition as a secondary validation layer.

🤖 Prompt for AI Agents
packages/database/src/types/entities.ts lines 100-106: The Arktype validators in
apps/web-app/shared/services/partner.ts currently use generic 'string' types
which won't enforce the InvoiceType/InvoiceStatus literal unions; update the
validator calls as follows: change type('string') to type('replenishment' |
'royalties' | 'lump_sum_fee' | 'marketing_fee' | 'rospatent_fee' | 'other') and
type('string') for status to type('paid' | 'unpaid'); for optional fields change
type('string | undefined') to type('replenishment' | 'royalties' |
'lump_sum_fee' | 'marketing_fee' | 'rospatent_fee' | 'other' | undefined) and
type('string | undefined') for status to type('paid' | 'unpaid' | undefined);
also consider adding Drizzle check constraints on the invoices table to enforce
these enums at the DB level as a secondary validation layer.