Compare commits

...

16 commits

Author SHA1 Message Date
f
ddc5f1ae3c 0.3.0 - Agregar tipos de unidades 2022-07-29 15:49:04 -03:00
2a68f4f90d 0.2.0 2022-06-16 22:21:08 +00:00
5f78c52db4 Agregar Stock 2022-06-16 21:59:47 +00:00
555b118716 Demo: filtrar por lista de precios en precios 2021-11-22 15:34:52 -03:00
cf868a92e9 demo: fijar el toggleable 2021-11-18 12:42:18 -03:00
cb3c12541b demo: mover el resultado de la consulta de clientes 2021-11-18 12:40:39 -03:00
f9c69c6812 demo: no tener resultados dobles 2021-11-18 12:08:27 -03:00
220b094b8b PriceList 2021-11-18 12:08:10 -03:00
b5ba132517 price: documentar filter 2021-11-18 12:07:14 -03:00
cc63099252 demo: Usar BusinessName como el User del pedido 2021-11-18 12:06:50 -03:00
5d79510780 .prettierignore: ignorar dist/ 2021-11-15 19:42:40 -03:00
e7ddef05d3 0.1.1 2021-11-15 19:12:37 -03:00
e55b84feba 0.1.0 2021-11-15 19:09:46 -03:00
6ecb366919 Limpiar 2021-11-15 18:58:56 -03:00
be4fbc20e0 Softcodear HOST 2021-11-15 18:57:50 -03:00
689dc24850 Orders 2021-11-15 18:48:48 -03:00
18 changed files with 460 additions and 57 deletions

2
.gitignore vendored
View file

@ -1,2 +1,2 @@
build/ dist/
node_modules/ node_modules/

0
.npmignore Normal file
View file

View file

@ -1,2 +1,2 @@
pnpm-lock.yaml pnpm-lock.yaml
build/ dist/

View file

@ -1,6 +1,3 @@
// const HOST = 'https://tiendas.axoft.com'
export const HOST = "http://sutty.vm:4001";
export interface Paging { export interface Paging {
/** /**
* Si hay más páginas disponibles * Si hay más páginas disponibles
@ -56,10 +53,10 @@ export function queryCustomerToSearchParams(
} }
export enum TipoDeDocumento { export enum TipoDeDocumento {
CUIT = 80, CUIT = "80",
CUIL = 86, CUIL = "86",
CDI = 87, CDI = "87",
LE = 89, LE = "89",
LC = 90, LC = "90",
DNI = 96, DNI = "96",
} }

View file

@ -1,6 +1,5 @@
import { import {
QueryCustomer, QueryCustomer,
HOST,
Paginacion, Paginacion,
paginacionToSearchParams, paginacionToSearchParams,
Paging, Paging,
@ -78,6 +77,7 @@ export interface CustomerResponse {
} }
export async function getCustomers( export async function getCustomers(
host: string,
token: string, token: string,
options: CustomerQuery options: CustomerQuery
): Promise<CustomerResponse> { ): Promise<CustomerResponse> {
@ -87,7 +87,7 @@ export async function getCustomers(
queryCustomerToSearchParams(options.customer, searchParams); queryCustomerToSearchParams(options.customer, searchParams);
} }
const res = await fetch( const res = await fetch(
`${HOST}/api/Aperture/Customer?${searchParams.toString()}`, `${host}/api/Aperture/Customer?${searchParams.toString()}`,
{ {
headers: { headers: {
accesstoken: token, accesstoken: token,

View file

@ -3,10 +3,21 @@ import {
getProductos, getProductos,
getPrices, getPrices,
getPricesByCustomer, getPricesByCustomer,
getStocks,
getCustomers, getCustomers,
order,
OrderDto,
TipoDeDocumento,
Customer,
Producto,
Precio,
getPublications, getPublications,
getPriceLists,
} from "../index.js"; } from "../index.js";
// TODO: hacerlo input
const HOST = "http://sutty.vm:4001";
const tokenInput = document.querySelector<HTMLInputElement>("#token-input")!; const tokenInput = document.querySelector<HTMLInputElement>("#token-input")!;
if (localStorage.token) { if (localStorage.token) {
@ -44,18 +55,22 @@ function objectToDom(object: any) {
} }
async function showResponse(statusEl: Element | null, promise: Promise<any>) { async function showResponse(statusEl: Element | null, promise: Promise<any>) {
if (statusEl) clear(statusEl);
try { try {
const response = await promise; const response = await promise;
if (statusEl) { if (statusEl) {
clear(statusEl);
statusEl.append("¡Funcionó!"); statusEl.append("¡Funcionó!");
if (response) { if (response) {
statusEl.append(" Respuesta:", objectToDom(response)); statusEl.append(" Respuesta:", objectToDom(response));
} }
} else alert(`¡Funcionó!${response ? ` Respuesta: ${response}` : ""}`); } else alert(`¡Funcionó!${response ? ` Respuesta: ${response}` : ""}`);
} catch (error) { } catch (error) {
if (statusEl) statusEl.append("Hubo un error :(", objectToDom(error)); if (statusEl) {
else alert(`Hubo un error: ${error}`); clear(statusEl);
statusEl.append("Hubo un error :(", objectToDom(error));
} else {
alert(`Hubo un error: ${error}`);
}
} }
} }
@ -73,14 +88,133 @@ function setupForm(selector: string, listener: (event: Event) => Promise<any>) {
}); });
} }
setupForm("#token", () => dummy(token())); setupForm("#token", () => dummy(HOST, token()));
setupForm("#save-token", async () => { setupForm("#save-token", async () => {
localStorage.token = token(); localStorage.token = token();
}); });
setupForm("#productos", () => getProductos(token(), {})); setupForm("#productos", () => getProductos(HOST, token(), {}));
setupForm("#publicaciones", () => getPublications(token(), {})); setupForm("#publicaciones", () => getPublications(HOST, token(), {}));
setupForm("#price", () => getPrices(token(), {})); setupForm("#price", (event) =>
setupForm("#price-by-customer", (event) => getPrices(HOST, token(), {
getPricesByCustomer(token(), { filter: (event.target! as any).filter.value }) filter: (event.target! as any).filter.value,
})
); );
setupForm("#customer", () => getCustomers(token(), {})); setupForm("#stock", (event) =>
getStocks(HOST, token(), {
filter: (event.target! as any).filter.value,
warehouseCode: (event.target! as any).warehouseCode.value,
})
);
setupForm("#price-by-customer", (event) =>
getPricesByCustomer(HOST, token(), {
filter: (event.target! as any).filter.value,
})
);
setupForm("#price-list", (event) =>
getPriceLists(HOST, token(), {
filter: (event.target! as any).filter.value,
})
);
setupForm("#customers", () => getCustomers(HOST, token(), {}));
let customer: Customer | null = null;
setupForm("#pedido-customer", async (event) => {
const cuit = (event.target! as any).cuit.value;
if (cuit.length === 0) throw new Error("No pusiste un CUIT.");
const customers = await getCustomers(HOST, token(), {
customer: {
type: TipoDeDocumento.CUIT,
number: cuit,
},
});
if (customers.Data.length !== 1)
throw new Error("Encontré más de unx cliente.");
customer = customers.Data[0];
return customers;
});
const productosEl = document.querySelector<HTMLUListElement>(
"#pedido-item > .productos"
)!;
let productos: {
producto: Producto;
precio: Precio;
}[] = [];
function renderProductosEl() {
clear(productosEl);
for (const { producto, precio } of productos) {
const itemEl = document.createElement("li");
itemEl.append(
`${producto.Description} (precio de cliente: $${precio.Price}, SKUCode: ${producto.SKUCode})`
);
productosEl.append(itemEl);
}
}
setupForm("#pedido-item", async (event) => {
if (!customer) throw new Error("No seteaste lx cliente todavía.");
const sku = (event.target! as any).sku.value;
if (sku.length === 0) throw new Error("No pusiste un SKU.");
const productosResponse = await getProductos(HOST, token(), { filter: sku });
if (productosResponse.Data.length !== 1)
throw new Error("Encontré más de un producto.");
const preciosDelClienteResponse = await getPricesByCustomer(HOST, token(), {
SKUCode: sku,
customer: {
type: customer.DocumentType,
number: customer.DocumentNumber,
},
});
if (preciosDelClienteResponse.Data.length !== 1)
throw new Error("Encontré más de un producto.");
productos.push({
producto: productosResponse.Data[0],
precio: preciosDelClienteResponse.Data[0],
});
renderProductosEl();
return productosResponse;
});
setupForm("#pedido", async () => {
if (!customer) throw new Error("No seteaste lx cliente todavía.");
// Se supone que Total es:
// >=0 ∑[(OrderItems.Quantity x OrderItems.UnitPrice) OrderItems.DiscountPorcentage)] + Shipping.ShippingCost + Principal.FinancialSurcharge Principal.TotalDiscount
// Pero no tenemos la mayoría de las variables.
const total = productos.reduce((total, curr) => total + curr.precio.Price, 0);
// Se supone que es único ¡pero lo tenemos que generar nosotrxs! wtf
const id = Math.floor(Math.random() * 100000).toString();
const orderJson: OrderDto = {
Date: new Date().toISOString(),
Total: total,
OrderID: id,
OrderNumber: id,
OrderItems: productos.map(({ producto, precio }) => ({
Description: producto.Description,
UnitPrice: precio.Price,
Quantity: 1,
ProductCode: producto.SKUCode,
})),
Customer: {
CustomerID: 1,
User: customer.BusinessName,
IVACategoryCode: customer.IvaCategoryCode,
Email: "api@axoft.com", // En prod: customer.Email, tenemos que usar esto porque el usuario de prueba no tiene Email
ProvinceCode: customer.ProvinceCode,
DocumentNumber: customer.DocumentNumber,
DocumentType: customer.DocumentType,
},
};
return await order(HOST, token(), orderJson);
});

View file

@ -5,6 +5,11 @@
form { form {
border: solid 2px black; border: solid 2px black;
padding: 0.2em; padding: 0.2em;
margin: 1em 0;
}
section {
border: solid 2px blue;
padding: 0.2em;
margin-bottom: 1em; margin-bottom: 1em;
} }
@ -18,6 +23,12 @@
pre { pre {
overflow-x: auto; overflow-x: auto;
} }
summary {
position: sticky;
top: 0px;
background: white;
}
</style> </style>
<label for="token-input">Access token: </label> <label for="token-input">Access token: </label>
@ -45,6 +56,7 @@
<form id="price"> <form id="price">
<button>Conseguir precios</button> <button>Conseguir precios</button>
<input name="filter" type="text" placeholder="Filtro por lista de precios (ID, opcional)" />
<p class="status"></p> <p class="status"></p>
</form> </form>
@ -54,9 +66,40 @@
<p class="status"></p> <p class="status"></p>
</form> </form>
<form id="customer"> <form id="price-list">
<button>Conseguir listas de precios</button>
<input name="filter" type="text" placeholder="Filtro por ID (opcional)" />
<p class="status"></p>
</form>
<form id="stock">
<button>Conseguir stock</button>
<input name="filter" type="text" placeholder="Filtro por ID (opcional)" />
<input name="warehouseCode" type="text" placeholder="Filtro por código de deposito (opcional)" />
<p class="status"></p>
</form>
<form id="customers">
<button>Conseguir clientes</button> <button>Conseguir clientes</button>
<p class="status"></p> <p class="status"></p>
</form> </form>
<script type="module" src="../build/demo/demo.js"></script> <section>
<form id="pedido-customer">
<p class="status"></p>
<input type="text" name="cuit" placeholder="CUIT" />
<button>Setear cliente</button>
</form>
<form id="pedido-item">
<p class="status"></p>
<input type="text" name="sku" placeholder="SKU" />
<button>+</button>
<ul class="productos"></ul>
</form>
<form id="pedido">
<p class="status"></p>
<button>Hacer pedido</button>
</form>
</section>
<script type="module" src="../dist/demo/demo.js"></script>

View file

@ -1,7 +1,5 @@
import { HOST } from "./common.js"; export async function dummy(host: string, token: string) {
const res = await fetch(`${host}/api/Aperture/dummy`, {
export async function dummy(token: string) {
const res = await fetch(`${HOST}/api/Aperture/dummy`, {
method: "POST", method: "POST",
headers: { headers: {
accesstoken: token, accesstoken: token,

View file

@ -4,5 +4,8 @@ export * from "./dummy.js";
export * from "./product.js"; export * from "./product.js";
export * from "./price.js"; export * from "./price.js";
export * from "./priceByCustomer.js"; export * from "./priceByCustomer.js";
export * from "./priceList.js";
export * from "./stock.js";
export * from "./customer.js"; export * from "./customer.js";
export * from "./order.js";
export * from "./publications.js"; export * from "./publications.js";

134
order.ts Normal file
View file

@ -0,0 +1,134 @@
export interface OrderDto {
SituacionOrden?: null | string;
Date: string;
Total: number;
TotalDiscount?: number;
PaidTotal?: number;
FinancialSurcharge?: number;
WarehouseCode?: null | string;
SellerCode?: null | string;
TransportCode?: null | string;
SaleConditionCode?: number;
InvoiceCounterfoil?: number;
PriceListNumber?: number;
AgreedWithSeller?: boolean;
IvaIncluded?: boolean;
InternalTaxIncluded?: boolean;
OrderID: string;
OrderNumber: string;
ValidateTotalWithPaidTotal?: boolean;
Comment?: null | string;
Customer: CustomerDto;
CancelOrder?: boolean;
OrderItems: OrderItemDto[];
Shipping?: null | ShippingDto;
CashPayment?: null | CashPaymentDto;
CashPayments?: CashPaymentDto[] | null;
Payments?: PaymentDto[] | null;
}
export interface CustomerDto {
CustomerID: number;
Code?: null | string;
DocumentType?: null | string;
DocumentNumber?: null | string;
IVACategoryCode: string;
PayInternalTax?: boolean;
User: string;
Email: string;
FirstName?: null | string;
LastName?: null | string;
BusinessName?: null | string;
Street?: null | string;
HouseNumber?: null | string;
Floor?: null | string;
Apartment?: null | string;
City?: null | string;
ProvinceCode: string;
PostalCode?: null | string;
PhoneNumber1?: null | string;
PhoneNumber2?: null | string;
Bonus?: number;
MobilePhoneNumber?: null | string;
WebPage?: null | string;
BusinessAddress?: null | string;
Comments?: null | string;
NumberListPrice?: number;
Removed?: boolean;
DateUpdate?: string;
Disable?: string;
}
export interface OrderItemDto {
ProductCode: string;
SKUCode?: null | string;
VariantCode?: null | string;
Description: string;
VariantDescription?: null | string;
Quantity: number;
UnitPrice: number;
DiscountPercentage?: number;
SelectMeasureUnit?: string;
MeasureCode?: string;
}
export interface ShippingDto {
ShippingID: number;
ShippingCode?: null | string;
Street?: null | string;
HouseNumber?: null | string;
Floor?: null | string;
Apartment?: null | string;
City?: null | string;
ProvinceCode?: null | string;
PostalCode?: null | string;
PhoneNumber1?: null | string;
PhoneNumber2?: null | string;
ShippingCost?: number;
DeliversMonday?: null | string;
DeliversTuesday?: null | string;
DeliversWednesday?: null | string;
DeliversThursday?: null | string;
DeliversFriday?: null | string;
DeliversSaturday?: null | string;
DeliversSunday?: null | string;
DeliveryHours?: null | string;
}
export interface CashPaymentDto {
PaymentID?: number;
PaymentMethod: string;
PaymentTotal?: number;
}
export interface PaymentDto {
PaymentId: number;
TransactionDate: string;
AuthorizationCode?: null | string;
TransactionNumber?: null | string;
Installments: number;
InstallmentAmount: number;
Total: number;
CardCode: string;
CardPlanCode: string;
VoucherNo: number;
CardPromotionCode?: null | string;
}
export interface OrderResponse {}
export async function order(
host: string,
token: string,
order: OrderDto
): Promise<OrderResponse> {
const res = await fetch(`${host}/api/Aperture/order`, {
method: "POST",
headers: {
accesstoken: token,
"content-type": "application/json",
},
body: JSON.stringify(order),
});
const json = await res.json();
console.debug(json);
if (!json.isOk) {
throw new Error(`Tango: ${json.Message}`);
}
return json;
}

View file

@ -1,10 +1,11 @@
{ {
"name": "hyperpop", "name": "@suttyweb/hyperpop",
"version": "0.0.0", "version": "0.3.0",
"description": "Un cliente de API de https://github.com/TangoSoftware/ApiTiendas", "description": "Un cliente de API de https://github.com/TangoSoftware/ApiTiendas",
"main": "index.js", "license": "SEE LICENSE IN LICENSE",
"scripts": { "scripts": {
"build": "tsc", "build": "tsc",
"prepublish": "tsc",
"watch-build": "tsc --watch", "watch-build": "tsc --watch",
"format": "prettier --write .", "format": "prettier --write .",
"check-format": "prettier --check .", "check-format": "prettier --check .",
@ -19,7 +20,8 @@
"api" "api"
], ],
"author": "Sutty <hi@sutty.nl>", "author": "Sutty <hi@sutty.nl>",
"license": "SEE LICENSE IN LICENSE", "main": "dist/index.js",
"types": "dist/index.d.ts",
"devDependencies": { "devDependencies": {
"prettier": "^2.4.1", "prettier": "^2.4.1",
"typescript": "^4.4.3" "typescript": "^4.4.3"

View file

@ -1,12 +1,10 @@
import { import { Paginacion, paginacionToSearchParams, Paging } from "./common.js";
HOST,
Paginacion,
paginacionToSearchParams,
Paging,
} from "./common.js";
export interface PriceQuery { export interface PriceQuery {
paginacion?: Paginacion; paginacion?: Paginacion;
/**
* Filtrar por PriceListNumber
*/
filter?: string; filter?: string;
SKUCode?: string; SKUCode?: string;
} }
@ -25,6 +23,7 @@ export interface PriceResponse {
} }
export async function getPrices( export async function getPrices(
host: string,
token: string, token: string,
options: PriceQuery options: PriceQuery
): Promise<PriceResponse> { ): Promise<PriceResponse> {
@ -37,7 +36,7 @@ export async function getPrices(
searchParams.set("SKUCode", options.SKUCode); searchParams.set("SKUCode", options.SKUCode);
} }
const res = await fetch( const res = await fetch(
`${HOST}/api/Aperture/Price?${searchParams.toString()}`, `${host}/api/Aperture/Price?${searchParams.toString()}`,
{ {
headers: { headers: {
accesstoken: token, accesstoken: token,

View file

@ -1,7 +1,6 @@
import { import {
QueryCustomer, QueryCustomer,
queryCustomerToSearchParams, queryCustomerToSearchParams,
HOST,
Paginacion, Paginacion,
paginacionToSearchParams, paginacionToSearchParams,
Paging, Paging,
@ -28,6 +27,7 @@ export interface PreciosResponse {
} }
export async function getPricesByCustomer( export async function getPricesByCustomer(
host: string,
token: string, token: string,
options: PriceByCustomerQuery options: PriceByCustomerQuery
): Promise<PreciosResponse> { ): Promise<PreciosResponse> {
@ -43,7 +43,7 @@ export async function getPricesByCustomer(
queryCustomerToSearchParams(options.customer, searchParams); queryCustomerToSearchParams(options.customer, searchParams);
} }
const res = await fetch( const res = await fetch(
`${HOST}/api/Aperture/PriceByCustomer?${searchParams.toString()}`, `${host}/api/Aperture/PriceByCustomer?${searchParams.toString()}`,
{ {
headers: { headers: {
accesstoken: token, accesstoken: token,

48
priceList.ts Normal file
View file

@ -0,0 +1,48 @@
import { Paginacion, paginacionToSearchParams, Paging } from "./common.js";
export interface PriceListQuery {
paginacion?: Paginacion;
filter?: string;
}
export interface PriceList {
PriceListNumber: number;
Description: string;
CommonCurrency: boolean;
IvaIncluded: boolean;
InternalTaxIncluded: boolean;
ValidityDateSince: string;
ValidityDateUntil: string;
Disabled: boolean;
}
export interface PriceListResponse {
Paging: Paging;
Data: PriceList[];
}
export async function getPriceLists(
host: string,
token: string,
options: PriceListQuery
): Promise<PriceListResponse> {
let searchParams = new URLSearchParams();
paginacionToSearchParams(options.paginacion, searchParams);
if (options.filter) {
searchParams.set("filter", options.filter);
}
const res = await fetch(
`${host}/api/Aperture/PriceList?${searchParams.toString()}`,
{
headers: {
accesstoken: token,
},
}
);
const json = await res.json();
console.debug(json);
if (json.Message) {
throw new Error(`Tango: ${json.Message}`);
}
return json;
}

View file

@ -1,9 +1,4 @@
import { import { Paginacion, paginacionToSearchParams, Paging } from "./common.js";
HOST,
Paginacion,
paginacionToSearchParams,
Paging,
} from "./common.js";
export interface ProductosQuery { export interface ProductosQuery {
paginacion?: Paginacion; paginacion?: Paginacion;
@ -54,6 +49,7 @@ export interface ProductosResponse {
// https://github.com/TangoSoftware/ApiTiendas#art%C3%ADculos // https://github.com/TangoSoftware/ApiTiendas#art%C3%ADculos
export async function getProductos( export async function getProductos(
host: string,
token: string, token: string,
options: ProductosQuery options: ProductosQuery
): Promise<ProductosResponse> { ): Promise<ProductosResponse> {
@ -66,7 +62,7 @@ export async function getProductos(
searchParams.set("filter", options.filter); searchParams.set("filter", options.filter);
} }
const res = await fetch( const res = await fetch(
`${HOST}/api/Aperture/Product?${searchParams.toString()}`, `${host}/api/Aperture/Product?${searchParams.toString()}`,
{ {
headers: { headers: {
accesstoken: token, accesstoken: token,

View file

@ -1,9 +1,4 @@
import { import { Paginacion, paginacionToSearchParams, Paging } from "./common.js";
HOST,
Paginacion,
paginacionToSearchParams,
Paging,
} from "./common.js";
export interface PublicationsQuery { export interface PublicationsQuery {
paginacion?: Paginacion; paginacion?: Paginacion;
@ -27,6 +22,7 @@ export interface PublicationsResponse {
// https://github.com/TangoSoftware/ApiTiendas#art%C3%ADculos // https://github.com/TangoSoftware/ApiTiendas#art%C3%ADculos
export async function getPublications( export async function getPublications(
host: string,
token: string, token: string,
options: PublicationsQuery options: PublicationsQuery
): Promise<PublicationsResponse> { ): Promise<PublicationsResponse> {
@ -42,7 +38,7 @@ export async function getPublications(
searchParams.set("variantCode", options.variantCode); searchParams.set("variantCode", options.variantCode);
} }
const res = await fetch( const res = await fetch(
`${HOST}/api/Aperture/Publications?${searchParams.toString()}`, `${host}/api/Aperture/Publications?${searchParams.toString()}`,
{ {
headers: { headers: {
accesstoken: token, accesstoken: token,

51
stock.ts Normal file
View file

@ -0,0 +1,51 @@
import { Paginacion, paginacionToSearchParams, Paging } from "./common.js";
export interface StockQuery {
paginacion?: Paginacion;
warehouseCode?: string;
filter?: string;
}
export interface Stock {
StoreNumber: number;
WarehouseCode: string;
SKUCode: string;
Quantity: number;
EngagedQuantity: number;
PendingQuantity: number;
}
export interface StockResponse {
Paging: Paging;
Data: Stock[];
}
// https://github.com/TangoSoftware/ApiTiendas#saldos-de-stock
export async function getStocks(
host: string,
token: string,
options: StockQuery
): Promise<StockResponse> {
let searchParams = new URLSearchParams();
paginacionToSearchParams(options.paginacion, searchParams);
if (options.filter) {
searchParams.set("filter", options.filter);
}
if (options.warehouseCode) {
searchParams.set("warehouseCode", options.warehouseCode);
}
const res = await fetch(
`${host}/api/Aperture/Stock?${searchParams.toString()}`,
{
headers: {
accesstoken: token,
},
}
);
const json = await res.json();
console.debug(json);
if (json.Message) {
throw new Error(`Tango: ${json.Message}`);
}
return json;
}

View file

@ -6,7 +6,7 @@
"module": "es6", "module": "es6",
"moduleResolution": "node", "moduleResolution": "node",
"outDir": "./build", "outDir": "./dist",
"allowJs": false, "allowJs": false,
@ -15,6 +15,8 @@
"strict": true, "strict": true,
"declaration": true,
"skipLibCheck": true "skipLibCheck": true
} }
} }