Añadir opción para mostrar la columna de descuento por defecto en las cotizaciones
This commit is contained in:
parent
c46d2b0df7
commit
6f77eaad97
@ -13,12 +13,13 @@ import {
|
||||
Form,
|
||||
Input,
|
||||
Label,
|
||||
Switch,
|
||||
} from "@/ui";
|
||||
|
||||
import { t } from "i18next";
|
||||
import { AlertCircleIcon } from "lucide-react";
|
||||
import { useMemo, useState } from "react";
|
||||
import { SubmitHandler, useForm } from "react-hook-form";
|
||||
import { Controller, SubmitHandler, useForm } from "react-hook-form";
|
||||
import { Trans } from "react-i18next";
|
||||
|
||||
import { useUnsavedChangesNotifier } from "@/lib/hooks";
|
||||
@ -43,6 +44,7 @@ export const SettingsEditor = () => {
|
||||
default_notes: "",
|
||||
default_legal_terms: "",
|
||||
default_quote_validity: "",
|
||||
default_show_line_discount_column: true,
|
||||
default_tax: {
|
||||
amount: undefined,
|
||||
scale: 2,
|
||||
@ -251,6 +253,30 @@ export const SettingsEditor = () => {
|
||||
</Card>
|
||||
</div>
|
||||
<div className={cn("grid gap-6", activeSection === "quotes" ? "visible" : "hidden")}>
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>
|
||||
<Trans i18nKey='settings.form_fields.default_show_line_discount_column.label' />
|
||||
</CardTitle>
|
||||
<CardDescription>
|
||||
<Trans i18nKey='settings.form_fields.default_show_line_discount_column.desc' />
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<Controller
|
||||
control={form.control}
|
||||
name='default_show_line_discount_column'
|
||||
render={({ field }) => (
|
||||
<Switch checked={field.value ?? true} onCheckedChange={field.onChange} />
|
||||
)}
|
||||
/>
|
||||
</CardContent>
|
||||
<CardFooter className='px-6 py-4 border-t'>
|
||||
<Button>
|
||||
<Trans i18nKey='common.save' />
|
||||
</Button>
|
||||
</CardFooter>
|
||||
</Card>
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>
|
||||
|
||||
@ -448,6 +448,10 @@
|
||||
"placeholder": "",
|
||||
"desc": "Default tax rate for your quotes"
|
||||
},
|
||||
"default_show_line_discount_column": {
|
||||
"label": "Show discount column by default",
|
||||
"desc": "New quotes will initially show the discount column in their lines. You can change this option later for each quote."
|
||||
},
|
||||
"default_legal_terms": {
|
||||
"label": "Legal terms",
|
||||
"placeholder": "",
|
||||
|
||||
@ -456,6 +456,10 @@
|
||||
"placeholder": "",
|
||||
"desc": "Porcentaje de IVA por defecto en las cotizaciones"
|
||||
},
|
||||
"default_show_line_discount_column": {
|
||||
"label": "Mostrar columna descuento por defecto",
|
||||
"desc": "Los nuevos presupuestos mostrarán inicialmente la columna de descuento en las líneas. Podrás cambiar esta opción posteriormente en cada presupuesto."
|
||||
},
|
||||
"default_legal_terms": {
|
||||
"label": "Cláusulas legales",
|
||||
"placeholder": "",
|
||||
|
||||
@ -58,6 +58,8 @@ export class UpdateProfileUseCase
|
||||
profile.defaultLegalTerms = TextValueObject.create(profileDTO.default_legal_terms).object;
|
||||
profile.defaultNotes = TextValueObject.create(profileDTO.default_notes).object;
|
||||
profile.defaultQuoteValidity = TextValueObject.create(profileDTO.default_quote_validity).object;
|
||||
profile.defaultShowLineDiscountColumn =
|
||||
profileDTO.default_show_line_discount_column ?? profile.defaultShowLineDiscountColumn;
|
||||
|
||||
const taxOrError = Percentage.create(profileDTO.default_tax);
|
||||
if (taxOrError.isFailure) {
|
||||
|
||||
@ -24,6 +24,7 @@ export interface IProfileProps {
|
||||
defaultLegalTerms: Note;
|
||||
defaultQuoteValidity: Note;
|
||||
defaultTax: Percentage;
|
||||
defaultShowLineDiscountColumn: boolean;
|
||||
|
||||
logo: ProfileLogotype;
|
||||
}
|
||||
@ -42,6 +43,7 @@ export interface IProfile {
|
||||
|
||||
defaultQuoteValidity: Note;
|
||||
defaultTax: Percentage;
|
||||
defaultShowLineDiscountColumn: boolean;
|
||||
|
||||
logo: ProfileLogotype;
|
||||
}
|
||||
@ -116,6 +118,14 @@ export class Profile extends AggregateRoot<IProfileProps> implements IProfile {
|
||||
this.props.defaultTax = newDefaultTax;
|
||||
}
|
||||
|
||||
get defaultShowLineDiscountColumn(): boolean {
|
||||
return this.props.defaultShowLineDiscountColumn;
|
||||
}
|
||||
|
||||
set defaultShowLineDiscountColumn(value: boolean) {
|
||||
this.props.defaultShowLineDiscountColumn = value;
|
||||
}
|
||||
|
||||
get logo(): ProfileLogotype {
|
||||
return this.props.logo;
|
||||
}
|
||||
|
||||
@ -36,6 +36,7 @@ export const GetProfilePresenter: IGetProfilePresenter = {
|
||||
default_legal_terms: profile.defaultLegalTerms.toString(),
|
||||
default_quote_validity: profile.defaultQuoteValidity.toString(),
|
||||
default_tax: profile.defaultTax.convertScale(2).toObject(),
|
||||
default_show_line_discount_column: profile.defaultShowLineDiscountColumn,
|
||||
|
||||
logo: await profile.logo.toBase64(),
|
||||
},
|
||||
|
||||
@ -16,6 +16,7 @@ export const UpdateProfilePresenter: IUpdateProfilePresenter = {
|
||||
default_legal_terms: profile.defaultLegalTerms.toString(),
|
||||
default_quote_validity: profile.defaultQuoteValidity.toString(),
|
||||
default_tax: profile.defaultTax.convertScale(2).toObject(),
|
||||
default_show_line_discount_column: profile.defaultShowLineDiscountColumn,
|
||||
};
|
||||
},
|
||||
};
|
||||
|
||||
@ -0,0 +1,36 @@
|
||||
import { IProfileContext } from "../Profile.context";
|
||||
import { createProfileMapper } from "./profile.mapper";
|
||||
|
||||
const source = (defaultShowLineDiscountColumn: boolean | null) =>
|
||||
({
|
||||
default_show_line_discount_column: defaultShowLineDiscountColumn,
|
||||
dataValues: {
|
||||
id: "770672b9-5a87-4e8e-a8af-8b44a7432e12",
|
||||
name: "Dealer",
|
||||
status: "actived",
|
||||
lang_code: "es",
|
||||
currency_code: "EUR",
|
||||
logo: "",
|
||||
contact_information: "",
|
||||
default_payment_method: "",
|
||||
default_notes: "",
|
||||
default_legal_terms: "",
|
||||
default_quote_validity: "",
|
||||
default_tax: 2100,
|
||||
},
|
||||
}) as any;
|
||||
|
||||
describe("ProfileMapper default_show_line_discount_column", () => {
|
||||
const mapper = createProfileMapper({} as IProfileContext);
|
||||
|
||||
it("maps false in both persistence directions", () => {
|
||||
const profile = mapper.mapToDomain(source(false));
|
||||
|
||||
expect(profile.defaultShowLineDiscountColumn).toBe(false);
|
||||
expect(mapper.mapToPersistence(profile).default_show_line_discount_column).toBe(false);
|
||||
});
|
||||
|
||||
it("uses true for historical null values", () => {
|
||||
expect(mapper.mapToDomain(source(null)).defaultShowLineDiscountColumn).toBe(true);
|
||||
});
|
||||
});
|
||||
@ -72,6 +72,7 @@ class ProfileMapper
|
||||
defaultLegalTerms,
|
||||
defaultQuoteValidity,
|
||||
defaultTax,
|
||||
defaultShowLineDiscountColumn: source.default_show_line_discount_column ?? true,
|
||||
|
||||
logo,
|
||||
};
|
||||
@ -96,6 +97,7 @@ class ProfileMapper
|
||||
default_legal_terms: source.defaultLegalTerms.toPrimitive(),
|
||||
default_quote_validity: source.defaultQuoteValidity.toPrimitive(),
|
||||
default_tax: source.defaultTax.convertScale(2).toPrimitive(),
|
||||
default_show_line_discount_column: source.defaultShowLineDiscountColumn,
|
||||
logo: source.logo.toPrimitive(),
|
||||
};
|
||||
}
|
||||
|
||||
@ -283,7 +283,10 @@ export class CreateQuoteUseCase
|
||||
paymentMethod: paymentOrError.object,
|
||||
notes: notesOrError.object,
|
||||
validity: validityOrError.object,
|
||||
showLineDiscountColumn: quoteDTO.show_line_discount_column ?? true,
|
||||
showLineDiscountColumn:
|
||||
quoteDTO.show_line_discount_column ??
|
||||
(this._dealer.additionalInfo.get("default_show_line_discount_column")?.toString() !==
|
||||
"false"),
|
||||
|
||||
discount: discountOrError.object,
|
||||
tax: taxOrError.object,
|
||||
|
||||
@ -33,6 +33,7 @@ describe("ReportQuotePresenter", () => {
|
||||
const html = await ReportQuotePresenter.toHTML(mapper.mapToDomain(source(true, 10)), context);
|
||||
|
||||
expect(html).toContain("Dto (%)");
|
||||
expect(html).toContain("Descuentos");
|
||||
expect(html).toContain("% Descuento");
|
||||
});
|
||||
|
||||
@ -40,6 +41,7 @@ describe("ReportQuotePresenter", () => {
|
||||
const html = await ReportQuotePresenter.toHTML(mapper.mapToDomain(source(false, 0)), context);
|
||||
|
||||
expect(html).not.toContain("Dto (%)");
|
||||
expect(html).not.toContain("Descuentos");
|
||||
expect(html).not.toContain("% Descuento");
|
||||
});
|
||||
});
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import { ICollection } from "@shared/contexts";
|
||||
import { ICollection, MoneyValue } from "@shared/contexts";
|
||||
import { Quote, QuoteItem } from "../../../../../../domain";
|
||||
import { ISalesContext } from "../../../../../Sales.context";
|
||||
|
||||
@ -79,6 +79,13 @@ const map = async (quote: Quote, context: ISalesContext) => {
|
||||
notes: quote.notes.toString(),
|
||||
show_line_discount_column: quote.showLineDiscountColumn,
|
||||
has_general_discount: quote.discount.toNumber() > 0,
|
||||
line_discount_price: quote.items
|
||||
.toArray()
|
||||
.reduce(
|
||||
(total, item) => total.add(item.subtotalPrice.subtract(item.totalPrice)),
|
||||
MoneyValue.create({ amount: 0, scale: 2, currencyCode: quote.currency.code }).object
|
||||
)
|
||||
.toFormat(),
|
||||
|
||||
subtotal_price: quote.subtotalPrice.toFormat(),
|
||||
|
||||
|
||||
@ -119,6 +119,13 @@
|
||||
<td class="px-4 py-2"></td>
|
||||
<td class="px-4 py-2 text-right border">{{subtotal_price}}</td>
|
||||
</tr>
|
||||
{{#if show_line_discount_column}}
|
||||
<tr class="border-b">
|
||||
<td class="px-4 py-2">Descuentos</td>
|
||||
<td class="px-4 py-2"></td>
|
||||
<td class="px-4 py-2 text-right border">{{line_discount_price}}</td>
|
||||
</tr>
|
||||
{{/if}}
|
||||
{{#if has_general_discount}}
|
||||
<tr class="border-b">
|
||||
<td class="px-4 py-2">% Descuento</td>
|
||||
|
||||
@ -33,6 +33,7 @@ class DealerMapper
|
||||
["default_notes", source.default_notes],
|
||||
["default_legal_terms", source.default_legal_terms],
|
||||
["default_quote_validity", source.default_quote_validity],
|
||||
["default_show_line_discount_column", source.default_show_line_discount_column ?? true],
|
||||
["default_tax", source.default_tax],
|
||||
]);
|
||||
|
||||
@ -72,6 +73,8 @@ class DealerMapper
|
||||
default_notes: source.additionalInfo.get("default_notes")?.toString() ?? "",
|
||||
default_legal_terms: source.additionalInfo.get("default_legal_terms")?.toString() ?? "",
|
||||
default_quote_validity: source.additionalInfo.get("default_quote_validity")?.toString() ?? "",
|
||||
default_show_line_discount_column:
|
||||
source.additionalInfo.get("default_show_line_discount_column")?.toString() !== "false",
|
||||
default_tax: Number(source.additionalInfo.get("default_tax") ?? 0),
|
||||
logo: source.logo.toPrimitive(),
|
||||
};
|
||||
|
||||
@ -53,6 +53,7 @@ export class Dealer_Model extends Model<
|
||||
declare default_notes: CreationOptional<string>;
|
||||
declare default_legal_terms: CreationOptional<string>;
|
||||
declare default_quote_validity: CreationOptional<string>;
|
||||
declare default_show_line_discount_column: CreationOptional<boolean>;
|
||||
declare default_tax: CreationOptional<number | null>;
|
||||
declare status: CreationOptional<string>;
|
||||
declare lang_code: CreationOptional<string>;
|
||||
@ -86,6 +87,11 @@ export default (sequelize: Sequelize) => {
|
||||
default_notes: DataTypes.TEXT,
|
||||
default_legal_terms: DataTypes.TEXT,
|
||||
default_quote_validity: DataTypes.TEXT,
|
||||
default_show_line_discount_column: {
|
||||
type: DataTypes.BOOLEAN,
|
||||
allowNull: false,
|
||||
defaultValue: true,
|
||||
},
|
||||
|
||||
default_tax: {
|
||||
type: new DataTypes.SMALLINT(),
|
||||
|
||||
@ -15,6 +15,7 @@ export interface IGetProfileResponse_DTO {
|
||||
default_legal_terms: string;
|
||||
default_quote_validity: string;
|
||||
default_tax: IPercentage_DTO;
|
||||
default_show_line_discount_column: boolean;
|
||||
status: string;
|
||||
lang_code: string;
|
||||
currency_code: string;
|
||||
|
||||
@ -8,6 +8,7 @@ export interface IUpdateProfile_Request_DTO {
|
||||
default_legal_terms: string;
|
||||
default_quote_validity: string;
|
||||
default_tax: IPercentage_DTO;
|
||||
default_show_line_discount_column?: boolean;
|
||||
logo: string;
|
||||
}
|
||||
|
||||
@ -22,6 +23,7 @@ export function ensureUpdateProfile_Request_DTOIsValid(userDTO: IUpdateProfile_R
|
||||
amount: Joi.number().allow(null),
|
||||
scale: Joi.number(),
|
||||
}).optional(),
|
||||
default_show_line_discount_column: Joi.boolean(),
|
||||
logo: Joi.string().optional().allow(null).allow("").default(""),
|
||||
}).unknown(true);
|
||||
|
||||
|
||||
@ -8,4 +8,5 @@ export interface IUpdateProfileResponse_DTO {
|
||||
default_legal_terms: string;
|
||||
default_quote_validity: string;
|
||||
default_tax: IPercentage_DTO;
|
||||
default_show_line_discount_column: boolean;
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user