*&---------------------------------------------------------------------* *& Report ZMIKNE_DIARIO_ENVIO *&---------------------------------------------------------------------* *& MIKNE × Higasket — Envío del libro diario al middleware México *& Layout: Interaccion_Campos_Middleware_Mexico V1.1 (27/10/2026), *& pestaña "Libro diario" *& 向墨西哥中间件发送日记账 (基于 V1.1 字段规范「Libro diario」) *& *& Flujo (igual que ZMIKNE_CATALOGO_ENVIO y ZMIKNE_SALDOS_ENVIO): *& 1. POST /api/login → obtiene token Bearer (24 h) *& 2. POST /api/diario → envía encabezado + partidas *& 3. Lee E_CODE ('S'/'E'), E_MSG y FOLIO de la respuesta *& *& El libro diario solo se entrega al SAT cuando éste lo solicita. *& 仅在墨西哥税务局(SAT)提出要求时提交日记账。 *& Tipo de solicitud / 请求类型 (FIGLMX_REQ_TYP): *& AF = acto de fiscalización → FIGLMX_ORD_NO opcional, del oficio del SAT (AAA0000000/00) *& FC = fiscalización compulsa → FIGLMX_ORD_NO opcional, del oficio del SAT (AAA0000000/00) *& DE = devolución → FIGLMX_PRO_NO obligatorio (AA000000000000) *& CO = compensación → FIGLMX_PRO_NO obligatorio (AA000000000000) *& AF/FC 审计令编号可选(按 SAT 通知填写);DE/CO 需填写手续编号。 *& Reenviar la misma solicitud reemplaza el envío anterior. *& 重新发送同一请求将替换之前的记录。 *& *& Volumen / 数据量: con P_GZIP = 'X' el cuerpo se comprime (Content-Encoding: *& gzip). Límite: 25 MB comprimido, 200 MB descomprimido. *& 勾选 P_GZIP 时压缩请求体(gzip)。上限:压缩后 25 MB,解压后 200 MB。 *& *& Origen (SAP S/4 HANA) / 数据来源: ACDOCA del ledger P_RLDNR, periodo *& P_MES (sin arrastre de saldos), una línea por partida del documento. *& FIGLMX_NUMBER = BELNR FIGLMX_TYPE = BLART *& FIGLMX_BKTXT = BKPF-BKTXT FIGLMX_DATE = BUDAT *& FIGLMX_ACCNO = RACCT (mismo formato que el catálogo) *& FIGLMX_ACCTNM = SKAT-TXT50 *& FIGLMX_CONCEPT = SGTXT; si está vacío, BKPF-BKTXT; si también, *& la descripción del tipo de documento (T003T) *& FIGLMX_DEBIT / FIGLMX_CREDIT = HSL (MXN) según DRCRK S / H *& FIGLMX_CURRENCY = RWCUR FIGLMX_ERATE = BKPF-KURSF (1 si es MXN) *& 凭证行取自 ACDOCA;摘要优先取行项目文本,其次凭证抬头文本,再次凭证类型描述。 *&---------------------------------------------------------------------* REPORT zmikne_diario_envio. CONSTANTS: gc_host TYPE string VALUE 'https://higasket-ce.mikne.com.mx', gc_usuario TYPE string VALUE 'higasket', gc_password TYPE string VALUE '*** CONFIGURAR ***'. * ---- Estructuras del layout V1.1 (Libro diario) --------------------- TYPES: BEGIN OF ty_partida, figlmx_number TYPE c LENGTH 19, " Número de documento (agrupa la póliza) figlmx_bktxt TYPE c LENGTH 50, " Texto de encabezado figlmx_type TYPE c LENGTH 4, " Tipo de documento figlmx_date TYPE d, " Fecha de contabilización figlmx_accno TYPE c LENGTH 20, " Número de cuenta (el del catálogo) figlmx_acctnm TYPE c LENGTH 50, " Descripción de la cuenta figlmx_concept TYPE c LENGTH 50, " Texto de partida figlmx_debit TYPE p LENGTH 12 DECIMALS 2, " Importe deudor (MXN) CURR 23,2 figlmx_credit TYPE p LENGTH 12 DECIMALS 2, " Importe acreedor (MXN) CURR 23,2 figlmx_currency TYPE c LENGTH 5, " Moneda del documento (c_Moneda SAT) figlmx_erate TYPE p LENGTH 5 DECIMALS 5, " Tipo de cambio DEC 9,5 END OF ty_partida. DATA: gt_partidas TYPE STANDARD TABLE OF ty_partida, gv_token TYPE string, gv_json TYPE string, gv_status TYPE i, gv_resp TYPE string. PARAMETERS: p_bukrs TYPE bukrs DEFAULT '8600', p_mes TYPE numc2 DEFAULT '06', p_anio TYPE numc4 DEFAULT '2026', p_reqtyp TYPE c LENGTH 2 DEFAULT 'AF', " AF | FC | DE | CO p_ordno TYPE c LENGTH 50, " AF / FC, opcional (no se valida) p_prono TYPE c LENGTH 14, " solo DE / CO p_gzip AS CHECKBOX DEFAULT 'X', " comprimir el envío p_rldnr TYPE fins_ledger DEFAULT '0L', " ledger p_spras TYPE spras DEFAULT 'S'. " idioma de textos AT SELECTION-SCREEN. IF p_reqtyp <> 'AF' AND p_reqtyp <> 'FC' AND p_reqtyp <> 'DE' AND p_reqtyp <> 'CO'. MESSAGE 'P_REQTYP debe ser AF, FC, DE o CO / 请求类型无效' TYPE 'E'. ENDIF. IF ( p_reqtyp = 'AF' OR p_reqtyp = 'FC' ) AND p_prono IS NOT INITIAL. MESSAGE 'AF/FC: deje vacío P_PRONO / AF/FC 时手续编号留空' TYPE 'E'. ENDIF. IF ( p_reqtyp = 'DE' OR p_reqtyp = 'CO' ) AND p_prono IS INITIAL. MESSAGE 'DE/CO: indique P_PRONO / 需填写手续编号' TYPE 'E'. ENDIF. START-OF-SELECTION. PERFORM obtener_partidas. IF gt_partidas IS INITIAL. WRITE: / '无日记账数据 / Sin partidas para el periodo'. RETURN. ENDIF. PERFORM login. IF gv_token IS INITIAL. WRITE: / '登录失败 / LOGIN FALLÓ'. RETURN. ENDIF. PERFORM enviar_diario. *&---------------------------------------------------------------------* *& Llenar gt_partidas con las líneas contables del periodo *&---------------------------------------------------------------------* FORM obtener_partidas. DATA: lv_ktopl TYPE ktopl, lv_poper TYPE poper. lv_poper = |0{ p_mes }|. SELECT SINGLE ktopl FROM t001 WHERE bukrs = @p_bukrs INTO @lv_ktopl. SELECT a~belnr, a~docln, a~budat, a~blart, a~racct, a~sgtxt, a~drcrk, a~hsl, a~rwcur, k~bktxt, k~kursf, t~txt50, y~ltext FROM acdoca AS a LEFT OUTER JOIN bkpf AS k ON k~bukrs = a~rbukrs AND k~belnr = a~belnr AND k~gjahr = a~gjahr LEFT OUTER JOIN skat AS t ON t~saknr = a~racct AND t~ktopl = @lv_ktopl AND t~spras = @p_spras LEFT OUTER JOIN t003t AS y ON y~blart = a~blart AND y~spras = @p_spras WHERE a~rldnr = @p_rldnr AND a~rbukrs = @p_bukrs AND a~gjahr = @p_anio AND a~poper = @lv_poper AND a~hsl <> 0 ORDER BY a~belnr, a~docln INTO TABLE @DATA(lt_lin). LOOP AT lt_lin INTO DATA(ls_l). DATA(ls) = VALUE ty_partida( ). ls-figlmx_number = ls_l-belnr. ls-figlmx_bktxt = ls_l-bktxt. ls-figlmx_type = ls_l-blart. ls-figlmx_date = ls_l-budat. ls-figlmx_accno = |{ ls_l-racct ALPHA = OUT }|. ls-figlmx_acctnm = COND #( WHEN ls_l-txt50 IS NOT INITIAL THEN ls_l-txt50 ELSE |Cuenta { ls-figlmx_accno }| ). ls-figlmx_concept = COND #( WHEN ls_l-sgtxt IS NOT INITIAL THEN ls_l-sgtxt WHEN ls_l-bktxt IS NOT INITIAL THEN ls_l-bktxt ELSE ls_l-ltext ). IF ls_l-drcrk = 'S'. ls-figlmx_debit = ls_l-hsl. ELSE. ls-figlmx_credit = - ls_l-hsl. ENDIF. ls-figlmx_currency = ls_l-rwcur. " KURSF negativo = cotización indirecta: el tipo de cambio es 1 / |KURSF| ls-figlmx_erate = COND #( WHEN ls_l-rwcur = 'MXN' OR ls_l-kursf IS INITIAL THEN 1 WHEN ls_l-kursf < 0 THEN 1 / abs( ls_l-kursf ) ELSE ls_l-kursf ). APPEND ls TO gt_partidas. ENDLOOP. ENDFORM. *&---------------------------------------------------------------------* *& 1) POST /api/login → token *&---------------------------------------------------------------------* FORM login. DATA(lv_body) = |\{"usuario":"{ gc_usuario }","password":"{ gc_password }"\}|. PERFORM http_post USING '/api/login' lv_body '' abap_false CHANGING gv_status gv_resp. IF gv_status = 200. " extraer "token":"..." de la respuesta FIND REGEX '"token"\s*:\s*"([^"]+)"' IN gv_resp SUBMATCHES gv_token. WRITE: / '登录成功 / Login correcto. Token obtenido.'. ELSE. WRITE: / 'HTTP', gv_status, gv_resp. ENDIF. ENDFORM. *&---------------------------------------------------------------------* *& 2) Armar JSON del layout y POST /api/diario *&---------------------------------------------------------------------* FORM enviar_diario. DATA lt_json TYPE string_table. " Importes con NUMBER = RAW: punto decimal y signo a la izquierda (-1500.00) LOOP AT gt_partidas INTO DATA(ls). APPEND |\{"FIGLMX_NUMBER":"{ escape( val = condense( ls-figlmx_number ) format = cl_abap_format=>e_json_string ) }",| && |"FIGLMX_BKTXT":"{ escape( val = ls-figlmx_bktxt format = cl_abap_format=>e_json_string ) }",| && |"FIGLMX_TYPE":"{ escape( val = condense( ls-figlmx_type ) format = cl_abap_format=>e_json_string ) }",| && |"FIGLMX_DATE":"{ ls-figlmx_date DATE = RAW }",| && |"FIGLMX_ACCNO":"{ escape( val = condense( ls-figlmx_accno ) format = cl_abap_format=>e_json_string ) }",| && |"FIGLMX_ACCTNM":"{ escape( val = ls-figlmx_acctnm format = cl_abap_format=>e_json_string ) }",| && |"FIGLMX_CONCEPT":"{ escape( val = ls-figlmx_concept format = cl_abap_format=>e_json_string ) }",| && |"FIGLMX_DEBIT":"{ ls-figlmx_debit NUMBER = RAW }",| && |"FIGLMX_CREDIT":"{ ls-figlmx_credit NUMBER = RAW }",| && |"FIGLMX_CURRENCY":"{ condense( ls-figlmx_currency ) }",| && |"FIGLMX_ERATE":"{ ls-figlmx_erate NUMBER = RAW }"\}| TO lt_json. ENDLOOP. gv_json = |\{"encabezado":\{| && |"FIGLMX_VERSION":"1.3",| && |"FIGLMX_TAXNO":"HME151105EJ3",| && |"FIGLMX_MONTH":"{ p_mes }",| && |"FIGLMX_YEAR":"{ p_anio }",| && |"FIGLMX_REQ_TYP":"{ p_reqtyp }",| && |"FIGLMX_ORD_NO":"{ escape( val = condense( p_ordno ) format = cl_abap_format=>e_json_string ) }",| && |"FIGLMX_PRO_NO":"{ condense( p_prono ) }"\},| && |"partidas":[{ concat_lines_of( table = lt_json sep = `,` ) }]\}|. PERFORM http_post USING '/api/diario' gv_json gv_token p_gzip CHANGING gv_status gv_resp. DATA: lv_ecode TYPE string, lv_emsg TYPE string, lv_folio TYPE string, lv_adv TYPE string. FIND REGEX '"E_CODE"\s*:\s*"([^"]+)"' IN gv_resp SUBMATCHES lv_ecode. FIND REGEX '"E_MSG"\s*:\s*"([^"]+)"' IN gv_resp SUBMATCHES lv_emsg. FIND REGEX '"FOLIO"\s*:\s*"([^"]+)"' IN gv_resp SUBMATCHES lv_folio. FIND REGEX '"totalAdvertencias"\s*:\s*(\d+)' IN gv_resp SUBMATCHES lv_adv. WRITE: / 'HTTP 状态码:', gv_status. WRITE: / 'E_CODE:', lv_ecode, 'E_MSG:', lv_emsg. IF lv_ecode = 'S'. WRITE: / '发送成功 / Envío correcto. Folio:', lv_folio. IF lv_adv IS NOT INITIAL AND lv_adv <> '0'. " Advertencias: no rechazan el envío (pólizas que no cuadran, cuentas fuera del catálogo) " 警告:不影响接收(凭证借贷不平、科目不在科目表中) WRITE: / '警告 / Advertencias:', lv_adv, '→', gv_resp. ENDIF. ELSE. WRITE: / '发送失败 / Envío rechazado. Revisar detalle:', gv_resp. ENDIF. ENDFORM. *&---------------------------------------------------------------------* *& Utilería HTTP (mismo patrón validado en la prueba de conectividad), *& con compresión gzip opcional del cuerpo *&---------------------------------------------------------------------* FORM http_post USING iv_ruta TYPE string iv_body TYPE string iv_token TYPE string iv_gzip TYPE abap_bool CHANGING cv_status TYPE i cv_resp TYPE string. DATA: lo_http TYPE REF TO if_http_client, lv_raw TYPE xstring, lv_gz TYPE xstring. cl_http_client=>create_by_url( EXPORTING url = |{ gc_host }{ iv_ruta }| IMPORTING client = lo_http EXCEPTIONS OTHERS = 4 ). IF sy-subrc <> 0. WRITE: / '创建 HTTP 客户端失败'. RETURN. ENDIF. lo_http->request->set_method( if_http_request=>co_request_method_post ). lo_http->request->set_header_field( name = 'Content-Type' value = 'application/json; charset=utf-8' ). IF iv_token IS NOT INITIAL. lo_http->request->set_header_field( name = 'Authorization' value = |Bearer { iv_token }| ). ENDIF. IF iv_gzip = abap_true. " UTF-8 → gzip; el servidor lo descomprime automáticamente lv_raw = cl_abap_codepage=>convert_to( source = iv_body codepage = `UTF-8` ). cl_abap_gzip=>compress_binary( EXPORTING raw_in = lv_raw IMPORTING gzip_out = lv_gz ). lo_http->request->set_header_field( name = 'Content-Encoding' value = 'gzip' ). lo_http->request->set_data( lv_gz ). WRITE: / '压缩 / gzip:', xstrlen( lv_raw ), '→', xstrlen( lv_gz ), 'bytes'. ELSE. lo_http->request->set_cdata( iv_body ). ENDIF. lo_http->send( EXCEPTIONS OTHERS = 1 ). IF sy-subrc <> 0. WRITE: / '发送请求失败'. lo_http->close( ). RETURN. ENDIF. lo_http->receive( EXCEPTIONS OTHERS = 1 ). IF sy-subrc <> 0. WRITE: / '接收响应失败'. lo_http->close( ). RETURN. ENDIF. lo_http->response->get_status( IMPORTING code = cv_status ). cv_resp = lo_http->response->get_cdata( ). lo_http->close( ). ENDFORM.