Compare commits

...
9 changed files with 271 additions and 8 deletions
@@ -0,0 +1,57 @@
package me.iiv.agent.entity.m0;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableName;
import java.io.Serializable;
import java.util.Date;
/** 楼座(b0.building):设备安装的物理位置,含经纬度。 */
@TableName("building")
public class Building implements Serializable {
private static final long serialVersionUID = 1L;
private Long id;
private String name;
private Long propertyId;
private String address;
private String province;
private String city;
private String countyseat;
private Double longitude;
private Double latitude;
@TableField("soft_delete")
private Integer softDelete;
@TableField("create_time")
private Date createTime;
@TableField("modify_time")
private Date modifyTime;
public Long getId() { return id; }
public void setId(Long id) { this.id = id; }
public String getName() { return name; }
public void setName(String name) { this.name = name; }
public Long getPropertyId() { return propertyId; }
public void setPropertyId(Long propertyId) { this.propertyId = propertyId; }
public String getAddress() { return address; }
public void setAddress(String address) { this.address = address; }
public String getProvince() { return province; }
public void setProvince(String province) { this.province = province; }
public String getCity() { return city; }
public void setCity(String city) { this.city = city; }
public String getCountyseat() { return countyseat; }
public void setCountyseat(String countyseat) { this.countyseat = countyseat; }
public Double getLongitude() { return longitude; }
public void setLongitude(Double longitude) { this.longitude = longitude; }
public Double getLatitude() { return latitude; }
public void setLatitude(Double latitude) { this.latitude = latitude; }
public Integer getSoftDelete() { return softDelete; }
public void setSoftDelete(Integer softDelete) { this.softDelete = softDelete; }
public Date getCreateTime() { return createTime; }
public void setCreateTime(Date createTime) { this.createTime = createTime; }
public Date getModifyTime() { return modifyTime; }
public void setModifyTime(Date modifyTime) { this.modifyTime = modifyTime; }
}
@@ -0,0 +1,97 @@
package me.iiv.agent.entity.m1;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;
import java.io.Serializable;
import java.math.BigDecimal;
import java.util.Date;
/**
* 应收单主表(m1.sale_receivable_order)。
* 金额列为 legacy BigDecimal(元),公开契约层统一换算为分(*_minor)。
*/
@Getter
@Setter
@ToString
@TableName("sale_receivable_order")
public class SaleReceivableOrder implements Serializable {
private static final long serialVersionUID = 1L;
@TableId(value = "id", type = IdType.AUTO)
private Long id;
/** 创建时间 */
private Date createTime;
/** 修改时间 */
private Date modifyTime;
/** 数据状态 */
private Integer softDelete;
/** 创建人(销售人员)ID */
private Long createId;
/** 创建人(销售人员)姓名 */
private String createName;
/** 应收单编号 */
private String receivableNo;
/** 订单编号 */
private String orderNo;
/** 客户名称 */
private String custerName;
/** 代理ID */
private Long agentId;
/** 物业ID */
private Long propertyId;
/** 下单公司 */
private String agentName;
/** 物业名称 */
private String propertyName;
/** 销售公司 */
private String saleCompany;
/** 订单时间 */
private Date orderTime;
/** 应收单时间 */
private Date receivableTime;
/** 已收款金额 */
private BigDecimal receivableMoney;
/** 收款状态0:未收款;1:全部收款;2:部分收款 */
private Integer receivableState;
/** 已开发票金额 */
private BigDecimal invoiceMoney;
/** 开票状态0:未开票;1:全部开票;2:部分开票 */
private Integer invoiceState;
/** 金蝶客户名称 */
private String jindieName;
/** 开票名称 */
private String invoiceName;
/** 未开发票金额 */
private BigDecimal notInvoiceMoney;
/** 开票中金额 */
private BigDecimal invoicingProgressMoney;
}
@@ -0,0 +1,7 @@
package me.iiv.agent.mapper.m0;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import me.iiv.agent.entity.m0.Building;
public interface BuildingMapper extends BaseMapper<Building> {
}
@@ -0,0 +1,11 @@
package me.iiv.agent.mapper.m1;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import me.iiv.agent.entity.m1.SaleReceivableOrder;
/**
* 应收单 Mapper 接口
*/
public interface SaleReceivableOrderMapper extends BaseMapper<SaleReceivableOrder> {
}
@@ -0,0 +1,60 @@
package me.iiv.agent.mcp.sync;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import me.iiv.agent.entity.m0.Building;
import me.iiv.agent.entity.m0.User;
import me.iiv.agent.mapper.m0.BuildingMapper;
import org.springframework.stereotype.Component;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Map;
/** buildings 同步实体(b0.building 楼座,含经纬度)。 */
@Component
public class BuildingsSyncEntity extends AbstractSyncEntity implements SyncEntity {
public static final String NAME = "buildings";
final BuildingMapper buildingMapper;
public BuildingsSyncEntity(BuildingMapper buildingMapper) {
this.buildingMapper = buildingMapper;
}
@Override
public String name() {
return NAME;
}
@Override
public List<SyncRow> fetchPage(Long fromTs, long cutoffTs, Long cursorMt, Long cursorId,
int limit, User user) {
LambdaQueryWrapper<Building> wrapper = new LambdaQueryWrapper<>();
applyWindow(wrapper, fromTs, cutoffTs, cursorMt, cursorId);
applyOrderAndLimit(wrapper, limit);
List<Building> rows = buildingMapper.selectList(wrapper);
List<SyncRow> out = new ArrayList<>(rows.size());
for (Building row : rows) {
long mt = ChangesEngine.effectiveTs(row.getModifyTime(), row.getCreateTime());
out.add(new SyncRow(toItem(row, mt), mt, row.getId()));
}
return out;
}
private Map<String, Object> toItem(Building row, long mt) {
boolean deleted = row.getSoftDelete() != null && row.getSoftDelete() == 1;
Date effective = row.getModifyTime() != null ? row.getModifyTime() : row.getCreateTime();
return SyncChangeSchemas.toMap(new SyncChangeSchemas.BuildingChange(
stableId(row.getId()), stableId(row.getPropertyId()),
row.getName(), row.getAddress(),
row.getProvince(), row.getCity(), row.getCountyseat(),
row.getLongitude(), row.getLatitude(),
ChangesEngine.iso(effective), mt, deleted ? ChangesEngine.iso(effective) : null));
}
private static String stableId(Long id) {
return id == null ? null : String.valueOf(id);
}
}
@@ -2,8 +2,8 @@ package me.iiv.agent.mcp.sync;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import me.iiv.agent.entity.m0.User;
import me.iiv.iivframework.beans.b1.SaleReceivableOrder;
import me.iiv.iivframework.mapper.m1.SaleReceivableOrderMapper;
import me.iiv.agent.entity.m1.SaleReceivableOrder;
import me.iiv.agent.mapper.m1.SaleReceivableOrderMapper;
import org.springframework.stereotype.Component;
import java.math.BigDecimal;
@@ -85,6 +85,21 @@ final class SyncChangeSchemas {
@JsonProperty("deleted_at") String deletedAt) {
}
record BuildingChange(
@JsonProperty("building_id") String buildingId,
@JsonProperty("property_id") String propertyId,
String name,
String address,
String province,
String city,
String countyseat,
Double longitude,
Double latitude,
@JsonProperty("updated_at") String updatedAt,
long version,
@JsonProperty("deleted_at") String deletedAt) {
}
record ContractChange(
@JsonProperty("contract_id") String contractId,
@JsonProperty("contract_no") String contractNo,
@@ -5,6 +5,7 @@ import me.iiv.agent.mcp.McpErrorCode;
import me.iiv.agent.mcp.ToolCallException;
import me.iiv.agent.mcp.XqTool;
import me.iiv.agent.mcp.sync.BatteryFaultsSyncEntity;
import me.iiv.agent.mcp.sync.BuildingsSyncEntity;
import me.iiv.agent.mcp.sync.ChangesEngine;
import me.iiv.agent.mcp.sync.ContractsSyncEntity;
import me.iiv.agent.mcp.sync.DeviceLifecyclesSyncEntity;
@@ -34,9 +35,9 @@ import java.util.Map;
/**
* 增量同步工具(REQ-20260911-0013;P2 域扩展于 REQ-20260902-0002):
* users/properties/contracts/repayment-targets/repayment-details/mall-orders/
* users/properties/buildings/contracts/repayment-targets/repayment-details/mall-orders/
* device-lifecycles/battery-faults/devices/withdraw-orders/relocation-orders/
* install-orders 十二个 changes 工具,
* install-orders 十三个 changes 工具,
* 协议(snapshot/cursor/tombstone/sync_token)由 ChangesEngine 统一实现。
* 数据域硬约束:本类不声明 tenant_id/scope 入参,数据域只能从 Token 推导;
* GuardedToolCallback 对 *.changes.list 工具的原始入参做二次拦截,传 tenant_id/scope 一律拒绝。
@@ -88,6 +89,17 @@ public class ChangesTools implements ToolObject {
return changesEngine.execute(PropertiesSyncEntity.NAME, syncToken, cursor, limit);
}
@XqTool(category = "sync", kind = "read")
@Tool(name = "buildings.changes.list", description = "增量同步楼座主数据(b0.building,含经纬度)。" + PROTOCOL_DESC
+ "items 字段:building_id/property_id/name/address/province/city/countyseat/"
+ "longitude/latitude/updated_at/version/deleted_at。")
public Map<String, Object> buildingsChangesList(
@ToolParam(required = false, description = SYNC_TOKEN_DESC) String syncToken,
@ToolParam(required = false, description = CURSOR_DESC) String cursor,
@ToolParam(required = false, description = LIMIT_DESC) Integer limit) {
return changesEngine.execute(BuildingsSyncEntity.NAME, syncToken, cursor, limit);
}
@XqTool(category = "sync", kind = "read")
@Tool(name = "contracts.changes.list", description = "增量同步合同主数据(m2.contract_new)。" + PROTOCOL_DESC
+ "items 字段:contract_id/contract_no/contract_name/status(PENDING/REJECTED/ACTIVE/EXPIRED/TERMINATED)/"
@@ -23,7 +23,7 @@ import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
/**
* 十二个 changes 工具的注册与路由(REQ-20260911-0013,P2 域扩展于 REQ-20260902-0002)。
* 十三个 changes 工具的注册与路由(REQ-20260911-0013,P2 域扩展于 REQ-20260902-0002)。
*/
@ExtendWith(MockitoExtension.class)
class ChangesToolsTest {
@@ -41,12 +41,13 @@ class ChangesToolsTest {
callback -> callback.getToolDefinition().name(),
callback -> callback.getToolDefinition().inputSchema()));
assertEquals(13, schemas.size());
for (String name : new String[]{"users.changes.list", "properties.changes.list", "contracts.changes.list",
assertEquals(15, schemas.size());
for (String name : new String[]{"users.changes.list", "properties.changes.list", "buildings.changes.list",
"contracts.changes.list",
"repayment-targets.changes.list", "repayment-details.changes.list",
"mall-orders.changes.list", "device-lifecycles.changes.list", "battery-faults.changes.list",
"devices.changes.list", "withdraw-orders.changes.list", "relocation-orders.changes.list",
"install-orders.changes.list"}) {
"install-orders.changes.list", "sale-receivable-orders.changes.list"}) {
assertTrue(schemas.containsKey(name), "缺少工具 " + name);
String schema = schemas.get(name);
assertTrue(schema.contains("syncToken"));
@@ -99,6 +100,9 @@ class ChangesToolsTest {
tools.installOrdersChangesList(null, null, 80);
verify(changesEngine).execute("install-orders", null, null, 80);
tools.saleReceivableOrdersChangesList("t8", "c8", null);
verify(changesEngine).execute("sale-receivable-orders", "t8", "c8", null);
}
@Test