Poder indicar la etiqueta del impuesto
This commit is contained in:
parent
75de474c16
commit
1d7ed6df38
@ -70,6 +70,7 @@ export const QuotePricesResume = ({ currency }: { currency: CurrencyData }) => {
|
||||
const { formatNumber } = useLocalization();
|
||||
|
||||
const pricesWatch = useWatch({ control, name: ["subtotal_price", "discount", "tax"] });
|
||||
const taxLabel = useWatch({ control, name: "tax_label" }) || t("quotes.form_fields.tax.label");
|
||||
|
||||
const totals = calculateQuoteTotals(pricesWatch);
|
||||
|
||||
@ -124,9 +125,7 @@ export const QuotePricesResume = ({ currency }: { currency: CurrencyData }) => {
|
||||
<Separator orientation='vertical' className='w-px h-16 mx-2' />
|
||||
<div className='grid flex-1 h-16 grid-cols-2 gap-6 auto-rows-max'>
|
||||
<div className='grid gap-1 font-medium text-muted-foreground'>
|
||||
<CardDescription className='text-sm'>
|
||||
{t("quotes.form_fields.tax.label")}
|
||||
</CardDescription>
|
||||
<CardDescription className='text-sm'>{taxLabel}</CardDescription>
|
||||
|
||||
<FormPercentageField
|
||||
scale={2}
|
||||
@ -137,7 +136,7 @@ export const QuotePricesResume = ({ currency }: { currency: CurrencyData }) => {
|
||||
</div>
|
||||
<div className='grid gap-1 font-semibold text-muted-foreground'>
|
||||
<CardDescription className='text-sm text-right'>
|
||||
{t("quotes.form_fields.tax_price.label")}
|
||||
{t("quotes.form_fields.tax_price.label", { label: "" })}
|
||||
</CardDescription>
|
||||
<CardTitle className='flex items-baseline justify-end gap-1 text-2xl tabular-nums'>
|
||||
{tax_price}
|
||||
|
||||
@ -331,8 +331,11 @@ export const QuoteResume = ({ quoteId, className }: QuoteResumeProps) => {
|
||||
</li>
|
||||
|
||||
<li className='flex items-center justify-between'>
|
||||
<span className='text-muted-foreground'>
|
||||
{t("quotes.form_fields.tax_value.label", { value: totals.tax })}
|
||||
<span className='text-muted-foreground'>
|
||||
{t("quotes.form_fields.tax_value.label", {
|
||||
label: data.tax_label,
|
||||
value: totals.tax,
|
||||
})}
|
||||
</span>
|
||||
<span>{totals.tax_price}</span>
|
||||
</li>
|
||||
|
||||
@ -86,6 +86,7 @@ export const QuoteEdit = () => {
|
||||
amount: undefined,
|
||||
scale: 0,
|
||||
},
|
||||
tax_label: "% IVA",
|
||||
tax_price: {
|
||||
amount: undefined,
|
||||
scale: 2,
|
||||
|
||||
@ -379,12 +379,12 @@
|
||||
"desc": "Percentage Tax"
|
||||
},
|
||||
"tax_value": {
|
||||
"label": "Tax ({{value}}%)",
|
||||
"label": "{{label}} ({{value}}%)",
|
||||
"placeholder": "",
|
||||
"desc": "Percentage Tax"
|
||||
},
|
||||
"tax_price": {
|
||||
"label": "Tax price",
|
||||
"label": "Tax amount",
|
||||
"placeholder": "",
|
||||
"desc": "Percentage tax price"
|
||||
},
|
||||
|
||||
@ -376,24 +376,24 @@
|
||||
"desc": ""
|
||||
},
|
||||
"tax": {
|
||||
"label": "IVA (%)",
|
||||
"label": "Impuesto (%)",
|
||||
"placeholder": "",
|
||||
"desc": "Porcentaje de IVA"
|
||||
"desc": "Porcentaje de impuesto"
|
||||
},
|
||||
"tax_value": {
|
||||
"label": "IVA ({{value}}%)",
|
||||
"label": "{{label}} ({{value}}%)",
|
||||
"placeholder": "",
|
||||
"desc": "Porcentaje de IVA"
|
||||
"desc": "Porcentaje de impuesto"
|
||||
},
|
||||
"tax_price": {
|
||||
"label": "Imp. IVA",
|
||||
"label": "Imp. impuesto",
|
||||
"placeholder": "",
|
||||
"desc": "Importe del IVA"
|
||||
"desc": "Importe del impuesto"
|
||||
},
|
||||
"total_price": {
|
||||
"label": "Total price",
|
||||
"label": "Imp. total",
|
||||
"placeholder": "",
|
||||
"desc": "Quote total price"
|
||||
"desc": "Importe total"
|
||||
},
|
||||
"items": {
|
||||
"quantity": {
|
||||
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
2
dist/client/index.html
vendored
2
dist/client/index.html
vendored
@ -8,7 +8,7 @@
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<link href="https://fonts.upset.dev/css2?family=Poppins&display=swap" rel="stylesheet" />
|
||||
<title>Presupuestador Uecko</title>
|
||||
<script type="module" crossorigin src="/assets/index-DzeF1POa.js"></script>
|
||||
<script type="module" crossorigin src="/assets/index-vq9AFyzk.js"></script>
|
||||
<link rel="stylesheet" crossorigin href="/assets/index-CBtV2-Es.css">
|
||||
</head>
|
||||
|
||||
|
||||
@ -205,6 +205,13 @@ export class CreateQuoteUseCase
|
||||
return Result.fail(taxOrError.error);
|
||||
}
|
||||
|
||||
const taxLabelOrError = TextValueObject.create(
|
||||
this._dealer?.additionalInfo.get("default_tax_label")?.toString() ?? "% IVA"
|
||||
);
|
||||
if (taxLabelOrError.isFailure) {
|
||||
return Result.fail(taxLabelOrError.error);
|
||||
}
|
||||
|
||||
const dateSentOrError = UTCDateValue.create(null);
|
||||
if (dateSentOrError.isFailure) {
|
||||
return Result.fail(dateSentOrError.error);
|
||||
@ -290,6 +297,7 @@ export class CreateQuoteUseCase
|
||||
|
||||
discount: discountOrError.object,
|
||||
tax: taxOrError.object,
|
||||
taxLabel: taxLabelOrError.object,
|
||||
|
||||
items,
|
||||
|
||||
|
||||
@ -119,6 +119,7 @@ export class DuplicateQuoteUseCase
|
||||
|
||||
discount: sourceQuote.discount,
|
||||
tax: sourceQuote.tax,
|
||||
taxLabel: sourceQuote.taxLabel,
|
||||
|
||||
items,
|
||||
|
||||
|
||||
@ -91,7 +91,8 @@ export class UpdateQuoteUseCase
|
||||
quoteDTO,
|
||||
id,
|
||||
dealerId,
|
||||
existingQuote.showLineDiscountColumn
|
||||
existingQuote.showLineDiscountColumn,
|
||||
existingQuote.taxLabel
|
||||
);
|
||||
|
||||
if (quoteOrError.isFailure) {
|
||||
@ -141,7 +142,8 @@ export class UpdateQuoteUseCase
|
||||
quoteDTO: IUpdateQuote_Request_DTO,
|
||||
quoteId: UniqueID,
|
||||
dealerId: UniqueID,
|
||||
existingShowLineDiscountColumn: boolean
|
||||
existingShowLineDiscountColumn: boolean,
|
||||
existingTaxLabel: Note
|
||||
): Result<Quote, IDomainError> {
|
||||
const statusOrError = QuoteStatus.create(quoteDTO.status);
|
||||
if (statusOrError.isFailure) {
|
||||
@ -286,6 +288,7 @@ export class UpdateQuoteUseCase
|
||||
|
||||
discount: discountOrError.object,
|
||||
tax: taxOrError.object,
|
||||
taxLabel: existingTaxLabel,
|
||||
|
||||
items,
|
||||
|
||||
|
||||
@ -1,8 +0,0 @@
|
||||
|
||||
# Cambia "Quote" por "Quote" en todos los archivos y directorios de forma recursiva
|
||||
find . -depth -name "*Quote*" | while read file; do
|
||||
# Obtén el nuevo nombre con 'Reseller' reemplazado por 'Quote'
|
||||
newfile=$(echo "$file" | sed 's/Quote/Quote/g')
|
||||
# Renombra el archivo/directorio
|
||||
mv "$file" "$newfile"
|
||||
done
|
||||
@ -34,6 +34,7 @@ export interface IQuoteProps {
|
||||
//subtotalPrice: MoneyValue;
|
||||
discount: Percentage;
|
||||
tax: Percentage;
|
||||
taxLabel: Note;
|
||||
//totalPrice: MoneyValue;
|
||||
|
||||
dealerId: UniqueID;
|
||||
@ -65,6 +66,7 @@ export interface IQuote {
|
||||
|
||||
tax: Percentage;
|
||||
taxPrice: MoneyValue;
|
||||
taxLabel: Note;
|
||||
|
||||
totalPrice: MoneyValue;
|
||||
|
||||
@ -185,6 +187,10 @@ export class Quote extends AggregateRoot<IQuoteProps> implements IQuote {
|
||||
return this.props.tax;
|
||||
}
|
||||
|
||||
get taxLabel(): Note {
|
||||
return this.props.taxLabel;
|
||||
}
|
||||
|
||||
get taxPrice(): MoneyValue {
|
||||
return this.beforeTaxPrice.percentage(this.tax.toNumber());
|
||||
}
|
||||
|
||||
@ -36,6 +36,7 @@ export const CreateQuotePresenter: ICreateQuotePresenter = {
|
||||
before_tax_price: quote.beforeTaxPrice.convertScale(2).toObject(),
|
||||
|
||||
tax: quote.tax.convertScale(2).toObject(),
|
||||
tax_label: quote.taxLabel.toString(),
|
||||
tax_price: quote.taxPrice.convertScale(2).toObject(),
|
||||
|
||||
total_price: quote.totalPrice.convertScale(2).toObject(),
|
||||
|
||||
@ -36,6 +36,7 @@ export const DuplicateQuotePresenter: IDuplicateQuotePresenter = {
|
||||
before_tax_price: quote.beforeTaxPrice.convertScale(2).toObject(),
|
||||
|
||||
tax: quote.tax.convertScale(2).toObject(),
|
||||
tax_label: quote.taxLabel.toString(),
|
||||
tax_price: quote.taxPrice.convertScale(2).toObject(),
|
||||
|
||||
total_price: quote.totalPrice.convertScale(2).toObject(),
|
||||
|
||||
@ -36,6 +36,7 @@ export const GetQuotePresenter: IGetQuotePresenter = {
|
||||
before_tax_price: quote.beforeTaxPrice.convertScale(2).toObject(),
|
||||
|
||||
tax: quote.tax.convertScale(2).toObject(),
|
||||
tax_label: quote.taxLabel.toString(),
|
||||
tax_price: quote.taxPrice.convertScale(2).toObject(),
|
||||
|
||||
total_price: quote.totalPrice.convertScale(2).toObject(),
|
||||
|
||||
@ -35,6 +35,7 @@ export const ListQuotesPresenter: IListQuotesPresenter = {
|
||||
before_tax_price: quote.beforeTaxPrice.convertScale(2).toObject(),
|
||||
|
||||
tax: quote.tax.convertScale(2).toObject(),
|
||||
tax_label: quote.taxLabel.toString(),
|
||||
tax_price: quote.taxPrice.convertScale(2).toObject(),
|
||||
|
||||
total_price: quote.totalPrice.convertScale(2).toObject(),
|
||||
|
||||
@ -2,7 +2,7 @@ import { ISalesContext } from "@/contexts/sales/infrastructure/Sales.context";
|
||||
import { createQuoteMapper } from "@/contexts/sales/infrastructure/mappers/quote.mapper";
|
||||
import { ReportQuotePresenter } from "./ReportQuote.reporter";
|
||||
|
||||
const source = (showLineDiscountColumn: boolean, generalDiscount: number) =>
|
||||
const source = (showLineDiscountColumn: boolean, generalDiscount: number, taxLabel = "% IVA") =>
|
||||
({
|
||||
show_line_discount_column: showLineDiscountColumn,
|
||||
items: [],
|
||||
@ -20,6 +20,7 @@ const source = (showLineDiscountColumn: boolean, generalDiscount: number) =>
|
||||
notes: "",
|
||||
discount: generalDiscount,
|
||||
tax: 21,
|
||||
tax_label: taxLabel,
|
||||
dealer_id: "770672b9-5a87-4e8e-a8af-8b44a7432e12",
|
||||
date_sent: null,
|
||||
},
|
||||
@ -44,4 +45,10 @@ describe("ReportQuotePresenter", () => {
|
||||
expect(html).not.toContain("Descuentos");
|
||||
expect(html).not.toContain("% Descuento");
|
||||
});
|
||||
|
||||
it("renders the tax label persisted in the quote", async () => {
|
||||
const html = await ReportQuotePresenter.toHTML(mapper.mapToDomain(source(false, 0, "% IGIC")), context);
|
||||
|
||||
expect(html).toContain("% IGIC");
|
||||
});
|
||||
});
|
||||
|
||||
@ -95,6 +95,7 @@ const map = async (quote: Quote, context: ISalesContext) => {
|
||||
before_tax_price: quote.beforeTaxPrice.toFormat(),
|
||||
|
||||
tax: quote.tax.toFormat(),
|
||||
tax_label: quote.taxLabel.toString(),
|
||||
tax_price: quote.taxPrice.toFormat(),
|
||||
|
||||
total_price: quote.totalPrice.toFormat(),
|
||||
|
||||
@ -139,7 +139,7 @@
|
||||
<td class="px-4 py-2 text-right border">{{before_tax_price}}</td>
|
||||
</tr>
|
||||
<tr class="border-b">
|
||||
<td class="px-4 py-2">% IVA</td>
|
||||
<td class="px-4 py-2">{{tax_label}}</td>
|
||||
<td class="px-4 py-2 text-right border">{{tax}}</td>
|
||||
<td class="px-4 py-2 text-right border">{{tax_price}}</td>
|
||||
</tr>
|
||||
|
||||
@ -36,6 +36,7 @@ export const UpdateQuotePresenter: IUpdateQuotePresenter = {
|
||||
before_tax_price: quote.beforeTaxPrice.convertScale(2).toObject(),
|
||||
|
||||
tax: quote.tax.convertScale(2).toObject(),
|
||||
tax_label: quote.taxLabel.toString(),
|
||||
tax_price: quote.taxPrice.convertScale(2).toObject(),
|
||||
|
||||
total_price: quote.totalPrice.convertScale(2).toObject(),
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
import { ISalesContext } from "../Sales.context";
|
||||
import { createQuoteMapper } from "./quote.mapper";
|
||||
|
||||
const source = (showLineDiscountColumn: boolean | null) =>
|
||||
const source = (showLineDiscountColumn: boolean | null, taxLabel?: string | null) =>
|
||||
({
|
||||
show_line_discount_column: showLineDiscountColumn,
|
||||
items: [],
|
||||
@ -19,6 +19,7 @@ const source = (showLineDiscountColumn: boolean | null) =>
|
||||
notes: "",
|
||||
discount: 0,
|
||||
tax: 21,
|
||||
tax_label: taxLabel,
|
||||
dealer_id: "770672b9-5a87-4e8e-a8af-8b44a7432e12",
|
||||
date_sent: null,
|
||||
},
|
||||
@ -37,4 +38,15 @@ describe("QuoteMapper show_line_discount_column", () => {
|
||||
it("uses true for historical null values", () => {
|
||||
expect(mapper.mapToDomain(source(null)).showLineDiscountColumn).toBe(true);
|
||||
});
|
||||
|
||||
it("maps a tax label in both persistence directions", () => {
|
||||
const quote = mapper.mapToDomain(source(true, "% IGIC"));
|
||||
|
||||
expect(quote.taxLabel.toString()).toBe("% IGIC");
|
||||
expect(mapper.mapToPersistence(quote).tax_label).toBe("% IGIC");
|
||||
});
|
||||
|
||||
it("uses the default tax label for historical null values", () => {
|
||||
expect(mapper.mapToDomain(source(true, null)).taxLabel.toString()).toBe("% IVA");
|
||||
});
|
||||
});
|
||||
|
||||
@ -18,7 +18,7 @@ import {
|
||||
} from "../../domain";
|
||||
import { QuoteStatus } from "../../domain/entities/Quotes/QuoteStatus";
|
||||
import { ISalesContext } from "../Sales.context";
|
||||
import { QuoteCreationAttributes, Quote_Model } from "../sequelize";
|
||||
import { DEFAULT_QUOTE_TAX_LABEL, QuoteCreationAttributes, Quote_Model } from "../sequelize";
|
||||
import { IQuoteItemMapper, createQuoteItemMapper } from "./quoteItem.mapper";
|
||||
|
||||
export interface IQuoteMapper
|
||||
@ -86,6 +86,9 @@ class QuoteMapper
|
||||
scale: 2,
|
||||
})
|
||||
),
|
||||
taxLabel: this.mapsValue(source, "tax_label", (label) =>
|
||||
TextValueObject.create(label ?? DEFAULT_QUOTE_TAX_LABEL)
|
||||
),
|
||||
|
||||
/*totalPrice: this.mapsValue(source, "total_price", (total_price) =>
|
||||
MoneyValue.create({
|
||||
@ -137,6 +140,7 @@ class QuoteMapper
|
||||
before_tax_price: source.beforeTaxPrice.convertScale(2).toPrimitive(),
|
||||
|
||||
tax: source.tax.convertScale(2).toPrimitive(),
|
||||
tax_label: source.taxLabel.toPrimitive(),
|
||||
tax_price: source.taxPrice.convertScale(2).toPrimitive(),
|
||||
|
||||
total_price: source.totalPrice.convertScale(2).toPrimitive(),
|
||||
|
||||
@ -19,6 +19,8 @@ export type QuoteCreationAttributes = InferCreationAttributes<
|
||||
dealer_id: string;
|
||||
};
|
||||
|
||||
export const DEFAULT_QUOTE_TAX_LABEL = "% IVA";
|
||||
|
||||
export class Quote_Model extends Model<
|
||||
InferAttributes<Quote_Model, { omit: "items" | "dealer" }>,
|
||||
InferCreationAttributes<Quote_Model, { omit: "items" | "dealer" }>
|
||||
@ -60,6 +62,7 @@ export class Quote_Model extends Model<
|
||||
declare before_tax_price: CreationOptional<number | null>;
|
||||
|
||||
declare tax: CreationOptional<number | null>;
|
||||
declare tax_label: CreationOptional<string | null>;
|
||||
declare tax_price: CreationOptional<number | null>;
|
||||
|
||||
declare total_price: CreationOptional<number | null>;
|
||||
@ -156,6 +159,12 @@ export default (sequelize: Sequelize) => {
|
||||
allowNull: true,
|
||||
},
|
||||
|
||||
tax_label: {
|
||||
type: DataTypes.STRING(32),
|
||||
allowNull: false,
|
||||
defaultValue: DEFAULT_QUOTE_TAX_LABEL,
|
||||
},
|
||||
|
||||
tax_price: {
|
||||
type: new DataTypes.BIGINT(),
|
||||
allowNull: true,
|
||||
|
||||
@ -9,6 +9,10 @@ import {
|
||||
DEFAULT_TAX_LABEL,
|
||||
Dealer_Model,
|
||||
} from "@/contexts/sales/infrastructure/sequelize/dealer.model";
|
||||
import {
|
||||
DEFAULT_QUOTE_TAX_LABEL,
|
||||
Quote_Model,
|
||||
} from "@/contexts/sales/infrastructure/sequelize/quote.model";
|
||||
import { trace } from "console";
|
||||
import { config } from "../../config";
|
||||
import { app } from "../express/app";
|
||||
@ -115,6 +119,10 @@ try {
|
||||
{ default_tax_label: DEFAULT_TAX_LABEL },
|
||||
{ where: { default_tax_label: null } }
|
||||
);
|
||||
await Quote_Model.update(
|
||||
{ tax_label: DEFAULT_QUOTE_TAX_LABEL },
|
||||
{ where: { tax_label: null } }
|
||||
);
|
||||
|
||||
initStructure(sequelizeConn.connection);
|
||||
insertUsers();
|
||||
|
||||
@ -20,6 +20,7 @@ export interface ICreateQuote_Response_DTO {
|
||||
discount_price: IMoney_DTO;
|
||||
before_tax_price: IMoney_DTO;
|
||||
tax: IPercentage_DTO;
|
||||
tax_label: string;
|
||||
tax_price: IMoney_DTO;
|
||||
total_price: IMoney_DTO;
|
||||
|
||||
|
||||
@ -20,6 +20,7 @@ export interface IDuplicateQuote_Response_DTO {
|
||||
discount_price: IMoney_DTO;
|
||||
before_tax_price: IMoney_DTO;
|
||||
tax: IPercentage_DTO;
|
||||
tax_label: string;
|
||||
tax_price: IMoney_DTO;
|
||||
total_price: IMoney_DTO;
|
||||
|
||||
|
||||
@ -20,6 +20,7 @@ export interface IGetQuote_Response_DTO {
|
||||
discount_price: IMoney_DTO;
|
||||
before_tax_price: IMoney_DTO;
|
||||
tax: IPercentage_DTO;
|
||||
tax_label: string;
|
||||
tax_price: IMoney_DTO;
|
||||
total_price: IMoney_DTO;
|
||||
|
||||
|
||||
@ -15,6 +15,7 @@ export interface IListQuotes_Response_DTO {
|
||||
discount_price: IMoney_DTO;
|
||||
before_tax_price: IMoney_DTO;
|
||||
tax: IPercentage_DTO;
|
||||
tax_label: string;
|
||||
tax_price: IMoney_DTO;
|
||||
total_price: IMoney_DTO;
|
||||
|
||||
|
||||
@ -20,6 +20,7 @@ export interface IUpdateQuote_Response_DTO {
|
||||
discount_price: IMoney_DTO;
|
||||
before_tax_price: IMoney_DTO;
|
||||
tax: IPercentage_DTO;
|
||||
tax_label: string;
|
||||
tax_price: IMoney_DTO;
|
||||
total_price: IMoney_DTO;
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user