Artifact fc2f65b2576927fb077adf42de7c77c5c6dd6ae05031183f7ce16fc508f8766c:
- File
shared/ql361api/src/ql361api/orders.gleam
— part of check-in
[5bb18ed4d4]
at
2026-10-23 03:34:10
on branch trunk
— 把来肯对接和本地库落到可切换的后端。
账号从环境读取,商品由人工增量同步,订单按五分钟轮询并缓存登录会话。开发期用 SQLite,同一套结构可切到 PostgreSQL。 (user: pluto size: 8924)
import gleam/dynamic/decode import gleam/json import gleam/option.{type Option, None, Some} import gleam/result import gleam/string import ql361api/client.{type Client} import ql361api/json_codec import ql361api/models.{ type ApiError, type Page, type SalesOrder, type SalesOrderSummary, ApiFailure, AuthExpired, HttpError, JsonError, NetworkError, Page, SalesOrder, SalesOrderItem, SalesOrderSummary, UrlParseError, } pub const query_orders_method = "cc.erp.bll.bill.billbllmanager.getdatalist" pub const get_full_data_method = "cc.erp.bll.bill.billbllmanager.getfulldata" pub const page_size = 100 pub fn fetch_sales_orders( client client: Client, begin_date begin_date: String, end_date end_date: String, status status: Option(Int), ) -> Result(List(SalesOrderSummary), ApiError) { use page <- result.try(fetch_sales_order_page( client, begin_date, end_date, status, 0, page_size, )) Ok(page.items) } pub fn fetch_sales_order_page( client client: Client, begin_date begin_date: String, end_date end_date: String, status status: Option(Int), page page: Int, page_size page_size: Int, ) -> Result(Page(SalesOrderSummary), ApiError) { let condition = [ #("begindate", json.string(begin_date)), #("enddate", json.string(end_date)), #("billtype", json.int(604)), ] let condition = case status { Some(value) -> [#("status", json.int(value)), ..condition] None -> condition } let payload = json.object([ #( "reportparam", json.object([ #("orders", json.string("")), #("parid", json.int(0)), #("pagetype", json.string("erp")), #("displaytype", json.string("tree")), #("pagerow", json.int(page_size)), #("pagenumber", json.int(page)), ]), ), #("conditionparam", json.object(condition)), ]) |> json.to_string use raw_json <- result.try( client.post_rpc(client, query_orders_method, "erp", payload), ) parse_orders_body(raw_json) } pub fn fetch_pending_picking_orders( client client: Client, begin_date begin_date: String, end_date end_date: String, ) -> Result(List(SalesOrderSummary), ApiError) { fetch_sales_orders(client, begin_date, end_date, Some(20)) } pub fn fetch_order_detail( client client: Client, bill_id bill_id: Int, ) -> Result(SalesOrder, ApiError) { let payload = json.object([ #("billid", json.int(bill_id)), #("billtype", json.int(604)), ]) |> json.to_string use raw_json <- result.try( client.post_rpc(client, get_full_data_method, "erp", payload), ) parse_order_detail_body(raw_json) } pub fn parse_orders_body( body: String, ) -> Result(Page(SalesOrderSummary), ApiError) { let summary_decoder = { use bill_id <- decode.field("billid", json_codec.int()) use bill_code <- decode.optional_field("billcode", "", decode.string) use customer_name <- decode.optional_field("dfullname", "", decode.string) use contact_person <- decode.optional_field("contactor", "", decode.string) use phone <- decode.optional_field("phone", "", decode.string) use warehouse_name <- decode.optional_field("soutfullname", "", decode.string) use warehouse_id <- decode.optional_field("soutid", 0, json_codec.int()) use bill_date <- decode.optional_field("billdate", "", decode.string) use order_status <- decode.optional_field("status", 0, json_codec.int()) use status_name <- decode.optional_field("statusname", "", decode.string) use total_qty <- decode.optional_field("qty", 0.0, json_codec.number()) use total_money <- decode.optional_field("totalmoney", 0.0, json_codec.number()) use remark <- decode.optional_field("remark", "", decode.string) decode.success(SalesOrderSummary( bill_id: bill_id, bill_code: bill_code, customer_name: customer_name, contact_person: contact_person, phone: phone, warehouse_name: warehouse_name, warehouse_id: warehouse_id, bill_date: bill_date, status: order_status, status_name: status_name, total_qty: total_qty, total_money: total_money, remark: remark, )) } let data_decoder = { use items <- decode.field("datasource", decode.list(of: summary_decoder)) use total <- decode.optional_field("rowcount", 0, json_codec.int()) decode.success(Page(items: items, total: total)) } let wrapper_decoder = { use data <- decode.field("data", data_decoder) decode.success(data) } case json.parse(body, wrapper_decoder) { Ok(page) -> Ok(page) Error(_) -> Error(JsonError("销售订单列表响应无法解析")) } } pub fn parse_order_detail_body(body: String) -> Result(SalesOrder, ApiError) { let item_decoder = { use bill_detail_id <- decode.optional_field( "billdetailid", 0, json_codec.int(), ) use sku_id <- decode.optional_field("skuid", 0, json_codec.int()) use goods_code <- decode.optional_field("gcode", "", decode.string) use goods_name <- decode.optional_field("gfullname", "", decode.string) use barcode <- decode.optional_field("barcode", "", decode.string) use spec <- decode.optional_field("spec", "", decode.string) use package_qty <- decode.optional_field("mqty", 0.0, json_codec.number()) use package_unit <- decode.optional_field("mufullname", "", decode.string) use base_qty <- decode.optional_field("qty", 0.0, json_codec.number()) use base_unit <- decode.optional_field("bufullname", "", decode.string) use price <- decode.optional_field("price", 0.0, json_codec.number()) use total_money <- decode.optional_field("totalmoney", 0.0, json_codec.number()) use position_code <- decode.optional_field("gpcode", "", decode.string) use area_code <- decode.optional_field("sacode", "", decode.string) decode.success(SalesOrderItem( bill_detail_id: bill_detail_id, sku_id: sku_id, goods_code: goods_code, goods_name: goods_name, barcode: barcode, spec: spec, package_qty: package_qty, package_unit: package_unit, base_qty: base_qty, base_unit: base_unit, price: price, total_money: total_money, position_code: position_code, area_code: area_code, )) } let body0_decoder = { use datasource <- decode.field("datasource", decode.list(of: item_decoder)) decode.success(datasource) } let header_decoder = { use bill_id <- decode.field("billid", json_codec.int()) use bill_code <- decode.optional_field("billcode", "", decode.string) use customer_name <- decode.optional_field("dfullname", "", decode.string) use contact_person <- decode.optional_field("contactor", "", decode.string) use phone <- decode.optional_field("phone", "", decode.string) use warehouse_name <- decode.optional_field("sfullname", "", decode.string) use warehouse_id <- decode.optional_field("soutid", 0, json_codec.int()) use bill_date <- decode.optional_field("billdate", "", decode.string) use order_status <- decode.optional_field("status", 0, json_codec.int()) use status_name <- decode.optional_field("statusname", "", decode.string) use total_qty <- decode.optional_field("qty", 0.0, json_codec.number()) use total_money <- decode.optional_field("totalmoney", 0.0, json_codec.number()) use remark <- decode.optional_field("remark", "", decode.string) decode.success(#( bill_id, bill_code, customer_name, contact_person, phone, warehouse_name, warehouse_id, bill_date, order_status, status_name, total_qty, total_money, remark, )) } let data_decoder = { use header <- decode.field("header", header_decoder) use body0 <- decode.field("body0", body0_decoder) decode.success(SalesOrder( bill_id: header.0, bill_code: header.1, customer_name: header.2, contact_person: header.3, phone: header.4, warehouse_name: header.5, warehouse_id: header.6, bill_date: header.7, status: header.8, status_name: header.9, total_qty: header.10, total_money: header.11, remark: header.12, items: body0, )) } let wrapper_decoder = { use data <- decode.field("data", data_decoder) decode.success(data) } case json.parse(body, wrapper_decoder) { Ok(order) -> Ok(order) Error(_) -> Error(JsonError("销售订单明细响应无法解析")) } } pub fn describe_error(error: ApiError) -> String { case error { HttpError(status, _) -> "QL361 HTTP " <> string.inspect(status) NetworkError(message) -> "QL361 网络错误: " <> message JsonError(message) -> message AuthExpired(message) -> "QL361 会话失效: " <> message ApiFailure(message) -> message UrlParseError -> "QL361 地址无法解析" } }