Files
georgeandClaude Sonnet 5.5 901dd8763d Integración Shopify → SAP Business One (snobbycl y on-chile)
Pipeline de integración de órdenes de Shopify a SAP Service Layer:
Invoice de reserva, pago con tarjeta o transferencia por transacción,
solicitud de traslado, traslado de stock y entrega.

- Invoice igual a lo cobrado; fulfillments limitados a lo facturado
- Estados terminales integrated, timeout, ignored y cancelled
- Idempotencia contra SAP: adopta documentos existentes en vez de duplicar
- Reintentos de red en el cliente de Shopify
- Secretos fuera de los JSON de tienda (${VARIABLE} resuelta desde .env)
- Herramientas de consulta, integración puntual y carga de pruebas
- Documentación en docs/ (flujo, casos especiales, mapeo, configuración)

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-09-30 19:22:40 -05:00

10 KiB
Raw Permalink Blame History

Mapeo de campos Shopify → SAP Business One

Referencia de cómo se arma cada documento SAP a partir del JSON de una orden de Shopify. Los constructores viven en domain/*.py.

RUT del cliente (domain/business_partner.py)

Prioridad Fuente Detalle
1 order.note_attributes Se busca una entrada cuyo name contenga "rut" (sin distinguir mayúsculas). Se valida el value con el formato y el dígito verificador (módulo 11).
2 (respaldo) Metafields del customer (Shopify) Solo si no apareció en note_attributes. Se recorre cada metafield buscando un value con formato de RUT válido.
  • normalizar_rut: quita puntos/guión, mayúsculas (ej. "123456785") — uso interno para validar.
  • formatear_rut: formato que usa SAP, "NNNNNNNN-D" (ej. "12345678-5") — se usa en FederalTaxID, CardCode y el filtro de búsqueda en SAP.

Resolución de CardCode (resolver_card_code)

Caso CardCode usado
Sin RUT válido sap.generic_card_code (config de la tienda)
RUT válido, existe BusinessPartner con ese FederalTaxID El CardCode de ese BP
RUT válido, no existe Se crea un BP nuevo, CardCode = "C" + rut_sap
RUT válido, pero falla la creación del BP Cae a sap.generic_card_code

En los tres primeros casos con RUT válido, rut_sap se guarda igual (para FederalTaxID del Invoice), aunque termine usándose el CardCode genérico.


BusinessPartners (creación, domain/business_partner.py)

Campo SAP Origen Shopify Nota
CardCode — "C" + rut_sap
CardName billing_address (nombre completo), si no customer fallback "Cliente Shopify"
CardType — fijo "cCustomer"
GroupCode — fijo 100 ("Clientes", mismo grupo que el genérico)
FederalTaxID — rut_sap
Phone1 billing_address.phone, si no customer.phone
EmailAddress order.email, si no order.contact_email

BPAddresses (dos entradas)

Campo SAP Origen Shopify Nota
AddressName — "BillTo" / "ShipTo"
AddressType — bo_BillTo / bo_ShipTo
Street address1
Block address2
City city
ZipCode zip
County province texto libre, no se traduce a código de región
Country country_code solo si es "CL"
TaxCode — fijo "IVA", solo en la dirección bo_ShipTo

bo_BillTo sale de order.billing_address; bo_ShipTo de order.shipping_address (si no hay, usa billing_address).


Invoices (factura de reserva, domain/invoice.py)

Campo SAP Origen Shopify Nota
CardCode — resuelto (ver arriba)
CardName billing_address (nombre completo), si no customer fallback "Cliente Shopify"
DocDate / DocDueDate created_at (solo fecha) mismo valor en ambos
Series — sap.numbering_series.INVOICE (config tienda)
DocType — fijo "dDocument_Items"
DocCurrency — fijo "CLP"
ReserveInvoice — fijo "tYES" (factura de reserva, no releva stock al crearse)
NumAtCard order.name ej. "#7863", para ubicarla fácil
FederalTaxID rut_sap solo si hay RUT válido; sobrescribe el del CardCode genérico
U_ShopifyOrderId order.id UDF
U_ShopifyStore — store_code de la config

AddressExtension (prefijo BillTo/ShipTo)

Campo SAP Origen Shopify Nota
{p}Street address1
{p}Block address2
{p}Building phone reutilizado para guardar el teléfono
{p}City city
{p}ZipCode zip
{p}County province
{p}Country country_code solo si es "CL"

BillTo* sale de billing_address, ShipTo* de shipping_address. SAP calcula Address/Address2 (texto plano) solo a partir de estos campos — no se arman a mano.

DocumentLines (una por line_item con sku)

Campo SAP Origen Shopify Nota
ItemCode sku
Quantity quantity
WarehouseCode — sap.default_warehouse_invoice (bodega virtual, fija por tienda)
PriceAfterVAT price menos descuento, ver nota precio unitario, IVA incluido
TaxCode — fijo "IVA" (hardcode, sin lista de precios)

Cálculo de PriceAfterVAT: item.price es el precio unitario antes de descuento. El descuento real está en discount_allocations (el campo total_discount no siempre lo refleja). Fórmula:

PriceAfterVAT = (price × quantity − Σ discount_allocations.amount) / quantity

IncomingPayments (domain/payment.py)

Campo SAP Origen Nota
CardCode invoice.CardCode mismo cliente que la Invoice
CardName invoice.CardName
DocType — fijo "rCustomer"
DocDate invoice.DocDate misma fecha que la Invoice
Series — sap.numbering_series.INCOMING_PAYMENT
DocCurrency — fijo "CLP"
PaymentCreditCards[] una línea por transacción efectiva con type = credit_card CreditCard = sap.payment_methods[gateway].credit_card; CreditCardNumber fijo "1234"; CardValidUntil fijo "2030-12-31"; VoucherNum = order.name (máx. 20); CreditSum = transaction.amount; CreditCur "CLP"; CreditType "cr_Regular"; NumOfPayments y SplitPayments del config; FirstPaymentDue = invoice.DocDate
TransferAccount / TransferSum / TransferDate / TransferReference solo si hay transacciones con type = transfer (reversso) cuenta de sap.payment_methods[gateway].transfer_account; TransferSum = suma de esas transacciones; TransferReference = order.name
Remarks gateways de las transacciones efectivas join con " + ", sin repetir. No se usa payment_gateway_names (lista también los intentos fallidos)
PaymentInvoices[0].DocEntry invoice.DocEntry
PaymentInvoices[0].InvoiceType — fijo "it_Invoice"
PaymentInvoices[0].SumApplied total de la Invoice debe igualar la suma de las transacciones efectivas; si no, error y no se crea el pago. Ver docs/casos_especiales.md

InventoryTransferRequests (domain/inventory_transfer.py)

Una por cada fulfillment de la orden (tienda o CD).

Campo SAP Origen Nota
CardCode / CardName invoice.CardCode / invoice.CardName
Comments order.name
DocDate / DueDate order.created_at (solo fecha)
Series — sap.numbering_series.INVENTORY_TRANSFER_REQUEST
FromWarehouse sap.locations[location_id].warehouse_code bodega real de ese location_id
ToWarehouse — sap.default_warehouse_invoice (misma bodega virtual del Invoice)
U_ShopifyOrderId / U_ShopifyStore order.id / store_code

StockTransferLines (una por line_item del fulfillment)

Campo SAP Origen Nota
ItemCode sku del line_item del fulfillment no toda la orden, solo lo de ese fulfillment
Quantity quantity
FromWarehouseCode igual a FromWarehouse
WarehouseCode igual a ToWarehouse

location_id → {is_store, warehouse_code} sale de config/stores/<tienda>.json → sap.locations.


StockTransfers (domain/stock_transfer.py, solo si is_store: true)

Se arma a partir de la ITR ya creada en SAP (se relee completa para tener LineNum).

Campo SAP Origen Nota
CardCode / CardName / Comments copiados de la ITR
DocDate itr.DocDate
FromWarehouse / ToWarehouse copiados de la ITR mismo sentido que la solicitud
Series — sap.numbering_series.STOCK_TRANSFER
U_ShopifyOrderId / U_ShopifyStore copiados de la ITR

StockTransferLines (una por línea de la ITR)

Campo SAP Origen Nota
ItemCode / Quantity / FromWarehouseCode / WarehouseCode copiados de la línea de la ITR
BaseType — 1250000001 (DocObjectCode de InventoryTransferRequests)
BaseEntry itr.DocEntry
BaseLine line.LineNum de la ITR

DeliveryNotes (domain/delivery.py, solo si is_store: true)

Se arma a partir de la Invoice (base) y el StockTransfer ya creado (para saber qué se transfirió realmente en este fulfillment).

Campo SAP Origen Nota
CardCode / CardName invoice.CardCode / invoice.CardName
Comments invoice.NumAtCard
DocDate invoice.DocDate
Series — sap.numbering_series.DELIVERY
U_ShopifyOrderId / U_ShopifyStore copiados de la Invoice

DocumentLines (una por línea del StockTransfer, emparejada con la Invoice por ItemCode)

Campo SAP Origen Nota
ItemCode / Quantity línea del StockTransfer solo lo de este fulfillment, no toda la orden
WarehouseCode línea de la Invoice con el mismo ItemCode
BaseType — 13 (ObjType clásico de Sales Invoice, oInvoices)
BaseEntry invoice.DocEntry
BaseLine LineNum de la línea de la Invoice

Referencias cruzadas en la Invoice (agregar_referencia_documento)

Cada vez que se crea ITR, StockTransfer o Delivery, se hace PATCH Invoices agregando una entrada a DocumentReferences (sin pisar las que ya existan):

Documento creado RefObjType
InventoryTransferRequests rot_InventoryTransferRequest
StockTransfers rot_InventoryTransfer
DeliveryNotes rot_DeliveryNotes

Campos fijos / hardcode (pendientes de hacer configurables si hace falta)

Campo Valor Dónde
TaxCode (líneas de Invoice) "IVA" domain/invoice.py
TaxCode (dirección ShipTo del BP) "IVA" domain/business_partner.py
DocCurrency "CLP" Invoice, Payment
Datos de tarjeta (Payment) últimos dígitos "1234", vencimiento "2030-12-31" domain/payment.py (provisorios)
GroupCode (BusinessPartner) 100 domain/business_partner.py
BaseType ITR→StockTransfer 1250000001 domain/stock_transfer.py
BaseType Invoice→Delivery 13 domain/delivery.py