feature/catalog-importation
This commit is contained in:
@@ -10,6 +10,7 @@ from api.v1.modules.public.reference_data.customs_sections.models import Customs
|
||||
from api.v1.modules.public.reference_data.pedimento_codes.models import PedimentoCode
|
||||
from api.v1.modules.public.reference_data.code_pedimento_regimens.models import CodePedimentoRegimen
|
||||
from api.v1.modules.public.reference_data.incoterms.models import Incoterm
|
||||
from api.v1.modules.public.reference_data.valuation_methods.models import ValuationMethod
|
||||
from api.v1.modules.public.reference_data.transport_modes.models import TransportMode
|
||||
|
||||
# Import A76 Services
|
||||
@@ -31,6 +32,7 @@ from api.v1.modules.public.reference_data.transport_types.dto import TransportTy
|
||||
from api.v1.modules.public.reference_data.customs_sections.dto import CustomsSectionDTO
|
||||
from api.v1.modules.public.reference_data.code_pedimento_regimens.dto import CodePedimentoRegimenDTO
|
||||
from api.v1.modules.public.reference_data.incoterms.dto import IncotermDTO
|
||||
from api.v1.modules.public.reference_data.valuation_methods.dto import ValuationMethodDTO
|
||||
from api.v1.modules.public.reference_data.transport_modes.dto import TransportModeDTO
|
||||
|
||||
# Additional DTOs
|
||||
@@ -72,6 +74,9 @@ class InvoiceCatalogService:
|
||||
response.incoterms = [
|
||||
IncotermDTO.model_validate(obj) for obj in db.query(Incoterm).all()
|
||||
]
|
||||
response.valuation_methods = [
|
||||
ValuationMethodDTO.model_validate(obj) for obj in db.query(ValuationMethod).all()
|
||||
]
|
||||
response.transport_modes = [
|
||||
TransportModeDTO.model_validate(obj) for obj in db.query(TransportMode).all()
|
||||
]
|
||||
|
||||
@@ -3,9 +3,10 @@ from api.v1.common.tenant_crud_routes import TenantCRUDRoutes
|
||||
from core.database import get_core_db
|
||||
from core.security import get_current_user, validate_access_to_resource
|
||||
from fastapi import APIRouter, Depends, HTTPException, Query, Path
|
||||
from sqlalchemy import func
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
from . import schemas, services
|
||||
from . import schemas, services, models
|
||||
from .catalog_service import InvoiceCatalogService
|
||||
|
||||
# Create main router
|
||||
@@ -36,6 +37,27 @@ def get_edition_data(
|
||||
return data
|
||||
|
||||
|
||||
@router.get("/invoices/remesa-suggestion", response_model=Dict[str, int])
|
||||
def get_remesa_suggestion(
|
||||
pedimento_id: int = Query(..., description="Pedimento ID"),
|
||||
company_id: int = Query(..., description="Company ID"),
|
||||
db: Session = Depends(get_core_db),
|
||||
current_user: Dict[str, Any] = Depends(get_current_user),
|
||||
):
|
||||
"""Suggest next remesa for selected pedimento (max+1)."""
|
||||
tenant_id = validate_access_to_resource(db, company_id, current_user)
|
||||
max_rem = (
|
||||
db.query(func.max(models.InvoiceComplianceMx.remesa))
|
||||
.filter(
|
||||
models.InvoiceComplianceMx.pedimento_id == pedimento_id,
|
||||
models.InvoiceComplianceMx.tenant_id == tenant_id,
|
||||
models.InvoiceComplianceMx.company_id == company_id,
|
||||
)
|
||||
.scalar()
|
||||
)
|
||||
return {"next_remesa": int((max_rem or 0) + 1)}
|
||||
|
||||
|
||||
# Create CRUD routes for Invoice Header using TenantCRUDRoutes
|
||||
invoice_crud = TenantCRUDRoutes(
|
||||
service=services.InvoiceService,
|
||||
|
||||
@@ -605,6 +605,7 @@ from api.v1.modules.public.reference_data.currency_types.dto import CurrencyType
|
||||
from api.v1.modules.public.reference_data.transport_types.dto import TransportTypeDTO
|
||||
from api.v1.modules.public.reference_data.customs_sections.dto import CustomsSectionDTO
|
||||
from api.v1.modules.public.reference_data.incoterms.dto import IncotermDTO
|
||||
from api.v1.modules.public.reference_data.valuation_methods.dto import ValuationMethodDTO
|
||||
from api.v1.modules.public.reference_data.transport_modes.dto import TransportModeDTO
|
||||
from api.v1.modules.public.reference_data.code_pedimento_regimens.dto import CodePedimentoRegimenDTO
|
||||
|
||||
@@ -634,6 +635,7 @@ class InvoiceCatalogsResponse(BaseModel):
|
||||
code_pedimento_regimens: List[CodePedimentoRegimenDTO] = []
|
||||
seals: List[dict] = [] # Placeholder, refine with actual DTO
|
||||
incoterms: List[IncotermDTO] = []
|
||||
valuation_methods: List[ValuationMethodDTO] = []
|
||||
pedimentos: List[dict] = [] # Placeholder, refine with actual DTO
|
||||
transport_modes: List[TransportModeDTO] = []
|
||||
default_settings: Optional[dict] = None
|
||||
|
||||
@@ -34,6 +34,60 @@ def _get_current_username() -> str:
|
||||
return "System"
|
||||
|
||||
|
||||
def _autofill_remesa_if_needed(db: Session, invoice_data, tenant_id: int, company_id: int) -> None:
|
||||
"""
|
||||
Autocalcula remesa cuando hay pedimento consolidado y remesa viene vacía.
|
||||
Se hace ANTES de validar para que cumpla reglas de required en validators.
|
||||
"""
|
||||
try:
|
||||
compliance = getattr(invoice_data, "compliance_mx", None)
|
||||
if not compliance:
|
||||
return
|
||||
|
||||
pedimento_id = getattr(compliance, "pedimento_id", None)
|
||||
remesa = getattr(compliance, "remesa", None)
|
||||
|
||||
if not pedimento_id or remesa:
|
||||
return
|
||||
|
||||
# No aplicar a MEX (por consistencia con CSV import donde remesa es None para MEX)
|
||||
invoice_type = getattr(invoice_data, "invoice_type", None)
|
||||
if invoice_type == "MEX":
|
||||
return
|
||||
|
||||
from api.v1.modules.a76.pedmientos.models.pedimentos import Pedimentos, PedimentoType
|
||||
|
||||
ped = (
|
||||
db.query(Pedimentos)
|
||||
.filter(
|
||||
Pedimentos.id == pedimento_id,
|
||||
Pedimentos.tenant_id == tenant_id,
|
||||
Pedimentos.company_id == company_id,
|
||||
)
|
||||
.first()
|
||||
)
|
||||
if not ped:
|
||||
return
|
||||
|
||||
if getattr(ped, "pedimento_type", None) != PedimentoType.CONSOLIDATED:
|
||||
return
|
||||
|
||||
max_rem = (
|
||||
db.query(func.max(models.InvoiceComplianceMx.remesa))
|
||||
.filter(
|
||||
models.InvoiceComplianceMx.pedimento_id == pedimento_id,
|
||||
models.InvoiceComplianceMx.tenant_id == tenant_id,
|
||||
models.InvoiceComplianceMx.company_id == company_id,
|
||||
)
|
||||
.scalar()
|
||||
)
|
||||
next_rem = (max_rem or 0) + 1
|
||||
compliance.remesa = next_rem
|
||||
except Exception:
|
||||
# No bloquear guardado por fallo de autocalculo; validación normal aplicará.
|
||||
return
|
||||
|
||||
|
||||
class InvoiceService:
|
||||
"""Service for Invoice Header operations"""
|
||||
|
||||
@@ -126,6 +180,9 @@ class InvoiceService:
|
||||
# Validaciones con ErrorCollector
|
||||
errors = ErrorCollector()
|
||||
|
||||
# Autocalculo remesa (si aplica) ANTES de validar
|
||||
_autofill_remesa_if_needed(db, invoice_data, tenant_id, company_id)
|
||||
|
||||
# Validar si la factura ya existe
|
||||
invoice_exists(db, invoice_data.invoice_number, tenant_id, company_id, errors)
|
||||
if invoice_data.operation_type == "exp":
|
||||
@@ -284,6 +341,8 @@ class InvoiceService:
|
||||
if invoice_data.operation_type == "exp":
|
||||
validate_update_export(db, invoice_data, invoice, tenant_id, company_id, errors)
|
||||
else:
|
||||
# Autocalculo remesa (si aplica) ANTES de validar
|
||||
_autofill_remesa_if_needed(db, invoice_data, tenant_id, company_id)
|
||||
validate_update_import(db, invoice_data, invoice, tenant_id, company_id, errors)
|
||||
|
||||
# Si hay errores, lanzar excepción ANTES de actualizar
|
||||
|
||||
@@ -78,7 +78,7 @@ async def delete_incoterm(
|
||||
db: Session = Depends(get_core_db),
|
||||
current_user: dict = Depends(has_role("admin")),
|
||||
):
|
||||
obj = db.query(Incoterm).filter(Incoterm.key == key).first()
|
||||
obj = db.query(Incoterm).filter(Incoterm.code == key).first()
|
||||
if not obj:
|
||||
raise HTTPException(status_code=404, detail="Not found")
|
||||
db.delete(obj)
|
||||
|
||||
@@ -11,6 +11,8 @@
|
||||
exists = $bindable(),
|
||||
seals = [],
|
||||
incoterms = [],
|
||||
valuationMethods = [],
|
||||
legends = [],
|
||||
enclosure = [],
|
||||
operationType = undefined,
|
||||
invoiceType = undefined
|
||||
@@ -20,11 +22,25 @@
|
||||
exists?: boolean;
|
||||
seals?: any[];
|
||||
incoterms?: any[];
|
||||
valuationMethods?: any[];
|
||||
legends?: any[];
|
||||
enclosure?: any[];
|
||||
operationType?: number;
|
||||
invoiceType?: string;
|
||||
} = $props();
|
||||
|
||||
let selectedLegendCode = $state<string>('');
|
||||
|
||||
function appendLegendToEs() {
|
||||
const code = selectedLegendCode;
|
||||
if (!code) return;
|
||||
const legend = legends.find((l) => String(l.code) === String(code));
|
||||
const text = legend?.description?.trim();
|
||||
if (!text) return;
|
||||
const current = (formData.observation_es || '').trim();
|
||||
formData.observation_es = current ? `${current}\n${text}` : text;
|
||||
}
|
||||
|
||||
if (!formData && invoice) {
|
||||
formData = {
|
||||
// Campos de observaciones
|
||||
@@ -39,13 +55,13 @@
|
||||
total_increments_mn: invoice.financials?.total_increments_mn || null,
|
||||
total_increments_me: invoice.financials?.total_increments_me || null,
|
||||
// Incoterm y recinto
|
||||
incoterm: invoice.logistics?.incoterm || null,
|
||||
incoterm: invoice.logistics?.incoterm || '',
|
||||
enclosure: invoice.compliance_mx?.enclosure || null,
|
||||
// Campos de esta pestaña
|
||||
num_seals: null,
|
||||
movement_type: invoice.compliance_mx?.movement_type || '',
|
||||
alternate_invoice: invoice.alternate_invoice || '',
|
||||
valuation_method: invoice.compliance_mx?.value_method || null,
|
||||
valuation_method: invoice.compliance_mx?.value_method || '',
|
||||
// New fields for Export
|
||||
other_deductibles: invoice.financials?.other_deductibles || null,
|
||||
proforma_number: invoice.proforma_number || '',
|
||||
@@ -73,13 +89,13 @@
|
||||
total_increments_mn: null,
|
||||
total_increments_me: null,
|
||||
// Incoterm y recinto
|
||||
incoterm: null,
|
||||
incoterm: '',
|
||||
enclosure: null,
|
||||
// Campos de esta pestaña
|
||||
num_seals: null,
|
||||
movement_type: '',
|
||||
alternate_invoice: '',
|
||||
valuation_method: null,
|
||||
valuation_method: '',
|
||||
// New fields for Export
|
||||
other_deductibles: null,
|
||||
proforma_number: '',
|
||||
@@ -117,6 +133,43 @@
|
||||
/>
|
||||
</div>
|
||||
|
||||
{#if legends?.length}
|
||||
<div class="grid grid-cols-1 sm:grid-cols-[1fr_auto] gap-2">
|
||||
<div class="space-y-1">
|
||||
<Label for="legend_code" class="text-xs">Leyenda fija:</Label>
|
||||
<Select.Root
|
||||
type="single"
|
||||
value={selectedLegendCode}
|
||||
onValueChange={(v) => {
|
||||
selectedLegendCode = v ?? '';
|
||||
}}
|
||||
>
|
||||
<Select.Trigger id="legend_code" class="h-7 text-xs">
|
||||
<span class="truncate">
|
||||
{selectedLegendCode ? `Clave ${selectedLegendCode}` : 'Selecciona leyenda...'}
|
||||
</span>
|
||||
</Select.Trigger>
|
||||
<Select.Content class="max-h-[300px]">
|
||||
{#each legends as l}
|
||||
<Select.Item value={String(l.code)}>
|
||||
{l.code} - {l.description || ''}
|
||||
</Select.Item>
|
||||
{/each}
|
||||
</Select.Content>
|
||||
</Select.Root>
|
||||
</div>
|
||||
<div class="flex items-end">
|
||||
<button
|
||||
type="button"
|
||||
class="h-7 px-3 rounded-md border text-xs hover:bg-muted"
|
||||
onclick={appendLegendToEs}
|
||||
>
|
||||
Agregar a observaciones
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
{#if invoiceType !== 'MEX'}
|
||||
<div class="space-y-2">
|
||||
<h4 class="text-xs font-semibold text-muted-foreground uppercase">
|
||||
@@ -410,21 +463,21 @@
|
||||
<Label for="incoterm" class="text-xs">Incoterm:</Label>
|
||||
<Select.Root
|
||||
type="single"
|
||||
value={formData.incoterm ? String(formData.incoterm) : ''}
|
||||
value={formData.incoterm || ''}
|
||||
onValueChange={(v) => {
|
||||
formData.incoterm = v ? parseInt(v) : null;
|
||||
formData.incoterm = v ?? '';
|
||||
}}
|
||||
>
|
||||
<Select.Trigger id="incoterm" class="h-7 text-xs">
|
||||
<span class="truncate"
|
||||
>{formData.incoterm
|
||||
? incoterms.find((p) => p.id === formData.incoterm)?.name || 'Selecciona...'
|
||||
: 'Selecciona...'}</span
|
||||
>{formData.incoterm || 'Selecciona...'}</span
|
||||
>
|
||||
</Select.Trigger>
|
||||
<Select.Content class="max-h-[300px]">
|
||||
{#each incoterms as inco}
|
||||
<Select.Item value={String(inco.id)}>{inco.name}</Select.Item>
|
||||
<Select.Item value={String(inco.code)}>
|
||||
{inco.code} - {inco.description_es}
|
||||
</Select.Item>
|
||||
{/each}
|
||||
</Select.Content>
|
||||
</Select.Root>
|
||||
@@ -460,18 +513,18 @@
|
||||
>
|
||||
<Select.Root
|
||||
type="single"
|
||||
value={formData.valuation_method ? String(formData.valuation_method) : ''}
|
||||
value={formData.valuation_method || ''}
|
||||
onValueChange={(v) => {
|
||||
formData.valuation_method = v;
|
||||
formData.valuation_method = v ?? '';
|
||||
}}
|
||||
>
|
||||
<Select.Trigger id="valuation_method_exp" class="h-8 text-xs">
|
||||
<span class="truncate">{formData.valuation_method || 'Selecciona...'}</span>
|
||||
</Select.Trigger>
|
||||
<Select.Content>
|
||||
<Select.Item value="general">General</Select.Item>
|
||||
<Select.Item value="devalued">Devaluado</Select.Item>
|
||||
<Select.Item value="special">Especial</Select.Item>
|
||||
{#each valuationMethods as m}
|
||||
<Select.Item value={String(m.key)}>{m.key} - {m.description}</Select.Item>
|
||||
{/each}
|
||||
</Select.Content>
|
||||
</Select.Root>
|
||||
</div>
|
||||
@@ -564,18 +617,18 @@
|
||||
<Label for="valuation_method" class="text-xs">Met. Valoracion:</Label>
|
||||
<Select.Root
|
||||
type="single"
|
||||
value={formData.valuation_method ? String(formData.valuation_method) : ''}
|
||||
value={formData.valuation_method || ''}
|
||||
onValueChange={(v) => {
|
||||
formData.valuation_method = v;
|
||||
formData.valuation_method = v ?? '';
|
||||
}}
|
||||
>
|
||||
<Select.Trigger id="valuation_method" class="h-7 text-xs">
|
||||
<span class="truncate">{formData.valuation_method || 'Selecciona...'}</span>
|
||||
</Select.Trigger>
|
||||
<Select.Content class="max-h-[300px]">
|
||||
<Select.Item value="general">General</Select.Item>
|
||||
<Select.Item value="devalued">Devaluado</Select.Item>
|
||||
<Select.Item value="special">Especial</Select.Item>
|
||||
{#each valuationMethods as m}
|
||||
<Select.Item value={String(m.key)}>{m.key} - {m.description}</Select.Item>
|
||||
{/each}
|
||||
</Select.Content>
|
||||
</Select.Root>
|
||||
</div>
|
||||
|
||||
@@ -31,6 +31,7 @@ export const load: PageServerLoad = async ({ params, cookies, fetch, url }) => {
|
||||
let catalogsData: any = {};
|
||||
let defaultSettings: any = null;
|
||||
let isCreate = false;
|
||||
let legendsData: any = { items: [] };
|
||||
|
||||
// Fetch Default Settings separately if applicable (for creation)
|
||||
const settingsPromise = (params.id === 'new' && parsedOperationType && invoiceTypeParam && companyId)
|
||||
@@ -98,6 +99,22 @@ export const load: PageServerLoad = async ({ params, cookies, fetch, url }) => {
|
||||
invoiceData = editionData.invoice;
|
||||
}
|
||||
|
||||
// Leyendas fijas (para observaciones)
|
||||
try {
|
||||
const legendsResponse = await authenticatedFetch(
|
||||
`v1/a76/legends/?company_id=${companyId}&page=1&page_size=200`,
|
||||
{ method: 'GET' },
|
||||
cookies,
|
||||
fetch
|
||||
);
|
||||
if (legendsResponse.ok) {
|
||||
legendsData = await legendsResponse.json();
|
||||
}
|
||||
} catch (e) {
|
||||
// No bloquear la carga de factura por fallo en leyendas
|
||||
legendsData = { items: [] };
|
||||
}
|
||||
|
||||
// Map snake_case response to camelCase props expected by Svelte Page
|
||||
// Providing empty arrays as defaults if something is missing
|
||||
return {
|
||||
@@ -118,8 +135,10 @@ export const load: PageServerLoad = async ({ params, cookies, fetch, url }) => {
|
||||
codePedimentoRegimens: catalogsData.code_pedimento_regimens || [],
|
||||
seals: catalogsData.seals || [],
|
||||
incoterms: catalogsData.incoterms || [],
|
||||
valuationMethods: catalogsData.valuation_methods || [],
|
||||
pedimentos: catalogsData.pedimentos || [],
|
||||
transportModes: catalogsData.transport_modes || [],
|
||||
legends: legendsData?.items || [],
|
||||
defaultSettings: defaultSettings,
|
||||
filters: {
|
||||
operation_type: parsedOperationType,
|
||||
|
||||
@@ -66,7 +66,9 @@
|
||||
providers?: ClientProvider[];
|
||||
seals?: any[];
|
||||
incoterms?: any[];
|
||||
valuationMethods?: any[];
|
||||
enclosure?: any[];
|
||||
legends?: any[];
|
||||
currencyTypes?: any[];
|
||||
transportTypes?: any[];
|
||||
transportModes?: any[];
|
||||
@@ -483,6 +485,91 @@
|
||||
return list.filter((p: { operation_type?: string }) => p.operation_type === opType);
|
||||
});
|
||||
|
||||
// Al seleccionar pedimento, autollenar Aduana y Clave de régimen (document_type)
|
||||
// Fuente de verdad: pedimento.customs_office (aduana) y pedimento.regime (régimen)
|
||||
$effect(() => {
|
||||
// Si se marca pedimento pendiente, el componente ya limpia pedimento/remesa;
|
||||
// aquí limpiamos también los campos que dependen del pedimento.
|
||||
if (InvoiceTopFieldsFormData?.is_pedimento_pending) {
|
||||
if (generalFormData) {
|
||||
generalFormData.aduana = '';
|
||||
generalFormData.document_type = '';
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
const pid = InvoiceTopFieldsFormData?.pedimento_id;
|
||||
if (!pid || !generalFormData) return;
|
||||
const id = typeof pid === 'string' ? parseInt(pid, 10) : pid;
|
||||
if (!id || Number.isNaN(id)) return;
|
||||
|
||||
const ped = (filteredPedimentos || []).find((p: any) => p?.id === id);
|
||||
if (!ped) return;
|
||||
|
||||
// Solo autollenar si están vacíos (no pisar captura manual)
|
||||
if (!generalFormData.aduana && ped.customs_office) {
|
||||
generalFormData.aduana = ped.customs_office;
|
||||
}
|
||||
if (!generalFormData.document_type && ped.regime) {
|
||||
generalFormData.document_type = ped.regime;
|
||||
}
|
||||
});
|
||||
|
||||
let remesaSuggestionReqId = 0;
|
||||
let lastRemesaPedimentoId: number | null = null;
|
||||
let lastAutoSuggestedRemesa: string | null = null;
|
||||
$effect(() => {
|
||||
if (InvoiceTopFieldsFormData?.is_pedimento_pending) return;
|
||||
|
||||
const companyId = companyStore?.activeCompany?.id;
|
||||
const pid = InvoiceTopFieldsFormData?.pedimento_id;
|
||||
if (!companyId || !pid) {
|
||||
lastRemesaPedimentoId = null;
|
||||
lastAutoSuggestedRemesa = null;
|
||||
return;
|
||||
}
|
||||
|
||||
const pedimentoId = typeof pid === 'string' ? parseInt(pid, 10) : pid;
|
||||
if (!pedimentoId || Number.isNaN(pedimentoId)) return;
|
||||
|
||||
const currentRemesa = String(InvoiceTopFieldsFormData?.remesa || '');
|
||||
const isCurrentRemesaAuto = lastAutoSuggestedRemesa != null && currentRemesa === lastAutoSuggestedRemesa;
|
||||
|
||||
// Si cambió el pedimento, solo recalcular si la remesa está vacía
|
||||
// (o si la remesa actual fue auto-sugerida previamente)
|
||||
if (lastRemesaPedimentoId !== pedimentoId) {
|
||||
lastRemesaPedimentoId = pedimentoId;
|
||||
if (!currentRemesa || isCurrentRemesaAuto) {
|
||||
InvoiceTopFieldsFormData.remesa = '';
|
||||
lastAutoSuggestedRemesa = null;
|
||||
} else {
|
||||
// Remesa ya capturada / existente: no sobrescribir
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
// Mismo pedimento: solo sugerir si está vacía
|
||||
if (currentRemesa) return;
|
||||
}
|
||||
|
||||
const myReq = ++remesaSuggestionReqId;
|
||||
(async () => {
|
||||
try {
|
||||
const res = await api.get(
|
||||
`/v1/a76/invoices/remesa-suggestion?company_id=${companyId}&pedimento_id=${pedimentoId}`
|
||||
);
|
||||
if (myReq !== remesaSuggestionReqId) return;
|
||||
const next = (res as any)?.data?.next_remesa;
|
||||
if (typeof next === 'number' && next > 0) {
|
||||
const nextStr = String(next);
|
||||
InvoiceTopFieldsFormData.remesa = nextStr;
|
||||
lastAutoSuggestedRemesa = nextStr;
|
||||
}
|
||||
} catch (e) {
|
||||
// Silencioso: no bloquear selección de pedimento por fallo de sugerencia
|
||||
}
|
||||
})();
|
||||
});
|
||||
|
||||
async function checkExchangeRate(date: string): Promise<boolean> {
|
||||
if (!date || !companyStore?.activeCompany?.id) return true;
|
||||
|
||||
@@ -915,7 +1002,9 @@
|
||||
bind:exists={observationExists}
|
||||
seals={data.seals || []}
|
||||
incoterms={data.incoterms || []}
|
||||
valuationMethods={data.valuationMethods || []}
|
||||
enclosure={data.enclosure || []}
|
||||
legends={data.legends || []}
|
||||
{invoiceType}
|
||||
operationType={InvoiceTopFieldsFormData?.operation_type === 'exp'
|
||||
? 1
|
||||
|
||||
Reference in New Issue
Block a user