468 lines
21 KiB
Python
468 lines
21 KiB
Python
from email.policy import default
|
|
from datetime import datetime, timedelta, date
|
|
|
|
from odoo import models, fields, api
|
|
from odoo.exceptions import UserError, ValidationError
|
|
from dateutil.relativedelta import relativedelta
|
|
|
|
class PropertyMeterArchives(models.Model):
|
|
_name = "property.meter.archives"
|
|
_description = "电表库"
|
|
_order = "number asc"
|
|
|
|
name = fields.Char(string="仪表编号")
|
|
unit_number = fields.Char(string="单元编号/用途")
|
|
number = fields.Integer(related="epitope_values_id.sequence", string="序号", store=True)
|
|
sequence1 = fields.Integer(string="序号")
|
|
installation_location = fields.Char(string="安装位置")
|
|
is_state = fields.Boolean(string="是否启用")
|
|
type = fields.Selection([("public", "公共"), ("fees", "收费")], string="类型")
|
|
gauge_magnification = fields.Integer(string="仪表倍率", default=1)
|
|
maximum_count = fields.Integer(string="最大位数")
|
|
lessee_id = fields.Many2one("property.tenant.information", string="承租方", store=True, compute="_compute_lessee_id",)
|
|
employee_ids = fields.Many2many("hr.employee", string="抄表人")
|
|
epitope_values = fields.Char(string="表位置")
|
|
epitope_values_id = fields.Many2one("yuthon.epitope.values", string="表位置")
|
|
other = fields.Char(string="其他")
|
|
unit = fields.Float(string="当月单价", digits=(16, 4))
|
|
archives_state = fields.Selection([("stop", "停用"), ("use", "正常"), ("rent", "出租")], string="状态", store=True, default="use",)
|
|
start_time = fields.Date(string="安装日期")
|
|
end_time = fields.Date(string="拆除日期")
|
|
standards_id = fields.Many2one("yuthon.electricity.setting", string="标准")
|
|
yuthon_property_ids = fields.Many2many("yuthon.property", string="物业信息")
|
|
line_ids = fields.One2many("meter.archives.line", "archives_id", string="明细行")
|
|
remarks = fields.Text(string="备注")
|
|
fixed_receivables = fields.Float(string="固定应收", digits=(16, 4))
|
|
is_virtual_meter = fields.Boolean(string="虚拟电表")
|
|
daily_readings = fields.Float(string="每日读数", digits=(16, 4))
|
|
moth_readings = fields.Float(string="每月读数", digits=(16, 4))
|
|
|
|
state = fields.Selection([("reading", "已抄"), ("unreading", "未抄")], string="抄表状态", default="unreading")
|
|
|
|
def update_ele_state(self):
|
|
current_date = fields.Date.today() + timedelta(hours=8)
|
|
records = self.env["property.meter.archives"].search([])
|
|
for record in records:
|
|
if record.end_time:
|
|
if current_date >= record.end_time:
|
|
record.archives_state = "stop"
|
|
|
|
def delete_data(self):
|
|
archives_ids = self.env["property.meter.archives"].browse(
|
|
self._context.get('active_ids', self._context.get('active_id')))
|
|
for archive in archives_ids:
|
|
if archive.line_ids:
|
|
lines_to_delete = archive.line_ids.filtered(
|
|
lambda l:
|
|
l.meter_date # 确保日期非空
|
|
and l.meter_date.year == 2025 # 固定2025年
|
|
and l.meter_date.month in [10, 11] # 10/11月
|
|
)
|
|
if lines_to_delete:
|
|
lines_to_delete.unlink()
|
|
|
|
def meter_tasks(self):
|
|
current_date = fields.Date.today()
|
|
current_date = current_date.replace(day=15)
|
|
month_start = current_date.replace(day=1)
|
|
if current_date.month == 12:
|
|
month_end = current_date.replace(year=current_date.year + 1, month=1, day=1) - timedelta(days=1)
|
|
else:
|
|
month_end = current_date.replace(month=current_date.month + 1, day=1) - timedelta(days=1)
|
|
meter_archives = self.search([])
|
|
for archive in meter_archives:
|
|
existing_lines = self.env["meter.archives.line"].search([
|
|
("archives_id", "=", archive.id),
|
|
("meter_date", ">=", month_start),
|
|
("meter_date", "<=", month_end),
|
|
], limit=1)
|
|
if not existing_lines:
|
|
self.env["meter.archives.line"].create({
|
|
"archives_id": archive.id,
|
|
"epitope_values_id": archive.epitope_values_id.id,
|
|
"meter_date": current_date,
|
|
"state": "unreading",
|
|
"archives_state": "use",
|
|
"unit": archive.unit or 0.0,
|
|
"gauge_magnification": archive.gauge_magnification or 1,
|
|
"line_loss": 0.02,
|
|
"type": archive.type,
|
|
"contract_id": archive.line_ids[-1].contract_id.id if archive.line_ids else None,
|
|
"unit_number": archive.unit_number or "",
|
|
"maximum_count": archive.maximum_count or 0,
|
|
})
|
|
|
|
def change_floor_address(self):
|
|
archives_ids = self.env["property.meter.archives"].browse(self._context.get("active_ids", self._context.get("active_id")))
|
|
for meter_id in archives_ids:
|
|
meter_id._constrains_property_energy()
|
|
|
|
@api.onchange("moth_readings")
|
|
def _onchange_moth_readings(self):
|
|
if self.moth_readings:
|
|
self.daily_readings = 0
|
|
|
|
@api.onchange("daily_readings")
|
|
def _onchange_daily_readings(self):
|
|
if self.daily_readings:
|
|
self.moth_readings = 0
|
|
|
|
# @api.model_create_multi 多条创建
|
|
@api.model
|
|
def create(self, vals):
|
|
record = super().create(vals)
|
|
if "name" in vals and vals["name"]:
|
|
existing_records = self.search([("name", "=", vals["name"]), ("id", "!=", record.id)])
|
|
if existing_records:
|
|
raise ValidationError(f"仪表编号 '{vals['name']}' 已存在,请使用其他编号!")
|
|
return record
|
|
|
|
|
|
@api.depends("yuthon_property_ids.lessee_id")
|
|
def _compute_lessee_id(self):
|
|
for record in self:
|
|
if record.yuthon_property_ids:
|
|
record.lessee_id = record.yuthon_property_ids[0].lessee_id
|
|
else:
|
|
record.lessee_id = False
|
|
|
|
@api.constrains("yuthon_property_ids", "epitope_values_id", "maximum_count")
|
|
def _constrains_property_energy(self):
|
|
for record in self:
|
|
selected_properties = record.yuthon_property_ids
|
|
# 1. 当没有选中物业时,删除所有关联的明细行
|
|
if not selected_properties:
|
|
meters_to_remove = self.env['yuthon.property.energy'].search([
|
|
('property_meter_archives_id', '=', record.id),
|
|
])
|
|
if meters_to_remove:
|
|
related_properties = meters_to_remove.mapped('yuthon_property_id')
|
|
meters_to_remove.unlink()
|
|
# 更新物业的电表标记
|
|
for prop in related_properties:
|
|
prop.is_electricity_meter = bool(prop.energy_meter_ids)
|
|
|
|
# 删除不在选中列表中的物业对应的明细行
|
|
all_meters = self.env['yuthon.property.energy'].search([
|
|
('property_meter_archives_id', '=', record.id),
|
|
])
|
|
meters_to_remove = all_meters.filtered(
|
|
lambda m: m.yuthon_property_id not in selected_properties
|
|
)
|
|
if meters_to_remove:
|
|
related_properties = meters_to_remove.mapped('yuthon_property_id')
|
|
meters_to_remove.unlink()
|
|
for prop in related_properties:
|
|
prop.is_electricity_meter = bool(prop.energy_meter_ids)
|
|
|
|
# 为新增的选中物业创建明细行
|
|
for prop in selected_properties:
|
|
existing_meter = self.env['yuthon.property.energy'].search([
|
|
('property_meter_archives_id', '=', record.id),
|
|
('yuthon_property_id', '=', prop.id)
|
|
], limit=1)
|
|
if not existing_meter:
|
|
self.env['yuthon.property.energy'].create({
|
|
'property_meter_archives_id': record.id,
|
|
'yuthon_property_id': prop.id,
|
|
})
|
|
# 更新物业的电表标记
|
|
prop.is_electricity_meter = bool(prop.energy_meter_ids)
|
|
|
|
if record.line_ids:
|
|
write_vals = {
|
|
'epitope_values_id': record.epitope_values_id.id,
|
|
'maximum_count': record.maximum_count,
|
|
'yuthon_property_ids': [(6, 0, record.yuthon_property_ids.ids)]
|
|
if record.yuthon_property_ids else [(5,)]
|
|
}
|
|
record.line_ids.write(write_vals)
|
|
|
|
def open_record(self):
|
|
return {
|
|
"type": "ir.actions.act_window",
|
|
"res_model": "property.meter.archives",
|
|
"res_id": self.id,
|
|
"view_mode": "form",
|
|
}
|
|
|
|
def button_archives_state(self):
|
|
archives_ids = self.env["property.meter.archives"].browse(
|
|
self._context.get("active_ids", self._context.get("active_id"))
|
|
)
|
|
for record in archives_ids:
|
|
record.archives_state = "stop"
|
|
|
|
def update_property(self):
|
|
reading_ids = self.env["property.meter.archives"].browse(
|
|
self._context.get("active_ids", self._context.get("active_id"))
|
|
)
|
|
for record in reading_ids:
|
|
record.onchange_property()
|
|
|
|
def action_property_meter_wizard(self):
|
|
return {
|
|
"type": "ir.actions.act_window",
|
|
"name": "批量导入物业电表",
|
|
"res_model": "property.meter.wizard",
|
|
"view_mode": "form",
|
|
"target": "new",
|
|
}
|
|
|
|
def reading_publish(self):
|
|
menu_id = self.env["ir.ui.menu"].search([("name", "=", "水表抄表")])
|
|
reading_ids = self.env["property.meter.archives"].browse(
|
|
self._context.get("active_ids", self._context.get("active_id"))
|
|
)
|
|
for record in reading_ids:
|
|
for employee_id in record.employee_ids:
|
|
url = f"https://oa.thtzjt.com/web#id={self.id}&cids=2&action={menu_id.action.id}&model=property.meter.archives&view_type=list&menu_id={menu_id.id}"
|
|
we_id = (
|
|
self.env["hr.employee"]
|
|
.search([("id", "=", employee_id)])
|
|
.wecom_userid
|
|
)
|
|
self.sudo().env["wecom.apps"].sync_send_message_textcard(
|
|
category="notice",
|
|
user_id=we_id,
|
|
title="通知公告",
|
|
description="您有抄表待处理,请及时处理",
|
|
url=url,
|
|
btntxt="详细信息",
|
|
)
|
|
|
|
def batch_reading_push(self):
|
|
active_ids = self._context.get("active_ids", [])
|
|
return {
|
|
"name": "电水表推送",
|
|
"type": "ir.actions.act_window",
|
|
"res_model": "electric.water.wizard",
|
|
"view_mode": "form",
|
|
"view_type": "form",
|
|
"target": "new",
|
|
"context": {"default_meter_ids": [(6, 0, active_ids)]},
|
|
}
|
|
|
|
|
|
class MeterArchivesLine(models.Model):
|
|
_name = "meter.archives.line"
|
|
_description = "电表库明细行"
|
|
_order = "number asc, sequence asc, last_meter asc"
|
|
|
|
is_acc = fields.Boolean(string="新租", default=False)
|
|
archives_id = fields.Many2one("property.meter.archives", string="电表库", required=True, ondelete="cascade")
|
|
# name = fields.Char(string='仪表编号')
|
|
number = fields.Integer(related="epitope_values_id.sequence", string="序号", store=True)
|
|
sequence = fields.Integer(related="archives_id.sequence1", string="表序号", store=True)
|
|
epitope_values_id = fields.Many2one("yuthon.epitope.values", string="表位置")
|
|
lessee_id = fields.Many2one("property.tenant.information", string="承租方", store=True)
|
|
yuthon_property_ids = fields.Many2many("yuthon.property", string="物业信息")
|
|
last_meter = fields.Date(string="上次抄表日期", compute="_compute_last", store=True)
|
|
last_dosage = fields.Float(string="上次用量", store=True, group_operator=None, compute="_compute_last")
|
|
initial_value = fields.Float(string="初始读数")
|
|
last_read = fields.Float(string="上次读数", store=True, group_operator=None, compute="_compute_last")
|
|
meter_date = fields.Date(string="任务日期")
|
|
meter_date_month = fields.Char(string="任务日期月份", index=True, copy=False)
|
|
|
|
current_meter = fields.Float(string="本次用量", compute="_compute_current_meter")
|
|
is_abnormal = fields.Boolean(string="是否异常", compute="_compute_is_abnormal", store=True)
|
|
current_read = fields.Float(string="本次读数", group_operator=None)
|
|
unit_number = fields.Char(string="单元编号/用途")
|
|
gauge_magnification = fields.Integer(string="仪表倍率", default=1, group_operator=None)
|
|
last_reading_time = fields.Date(string="最后读数时间")
|
|
receivables = fields.Float(string="应收", store=True, help="应收金额=本次用量*单价*(1+线损)")
|
|
unit = fields.Float(string="单价", group_operator=None, digits=(16, 4))
|
|
line_loss = fields.Float(string="线损", default=0.02)
|
|
type = fields.Selection([("public", "公共"), ("fees", "收费")], string="类型")
|
|
employee_ids = fields.Many2many("hr.employee", string="抄表人")
|
|
remarks = fields.Text(string="备注")
|
|
is_fix_loss = fields.Selection([('yes', '是'), ('no', '否')], default="no", string="是否固定线损")
|
|
is_fix_price = fields.Selection([('yes', '是'), ('no', '否')], default="no", string="是否固定单价")
|
|
archives_state = fields.Selection([("stop", "停用"), ("use", "正常"), ("rent", "出租")], string="状态", default="use",)
|
|
state = fields.Selection([("reading", "已抄"), ("unreading", "未抄")], string="抄表状态", default="unreading",)
|
|
maximum_count = fields.Integer(string="最大位数")
|
|
is_push = fields.Boolean(string='推送', default=False)
|
|
|
|
def delete_dec_2025_meter_data(self):
|
|
dec_2025_start = datetime(2025, 12, 1).date()
|
|
dec_2025_end = datetime(2025, 12, 31).date()
|
|
dec_2025_records = self.search([
|
|
("meter_date", ">=", dec_2025_start),
|
|
("meter_date", "<=", dec_2025_end),
|
|
])
|
|
dec_2025_records.unlink()
|
|
|
|
@api.depends('current_read', 'last_read')
|
|
def _compute_is_abnormal(self):
|
|
"""
|
|
计算是否异常:本次读数与上次读数差值超过50%则为True
|
|
"""
|
|
for rec in self:
|
|
rec.is_abnormal = False
|
|
if not rec.last_read or not rec.current_read:
|
|
continue
|
|
diff = abs(rec.current_read - rec.last_read)
|
|
diff_ratio = diff / rec.last_read
|
|
if diff_ratio > 0.5:
|
|
rec.is_abnormal = True
|
|
|
|
def update_is_abnormal(self):
|
|
archives_ids = self.env["meter.archives.line"].browse(
|
|
self._context.get('active_ids', self._context.get('active_id')))
|
|
for ins in archives_ids:
|
|
ins._compute_is_abnormal()
|
|
|
|
@api.model_create_multi
|
|
def create(self, vals_list):
|
|
records = super().create(vals_list)
|
|
for rec in records:
|
|
if rec.meter_date:
|
|
rec.meter_date_month = rec.meter_date.strftime("%Y-%m")
|
|
else:
|
|
rec.meter_date_month = fields.Date.today().strftime("%Y-%m")
|
|
rec.archives_id._constrains_property_energy()
|
|
return records
|
|
|
|
|
|
@api.depends('last_read', 'current_read')
|
|
def _compute_current_meter(self):
|
|
for record in self:
|
|
if record.current_read >= record.last_read:
|
|
record.current_meter = record.current_read - record.last_read
|
|
else:
|
|
if record.current_read:
|
|
if record.maximum_count > 0:
|
|
max_value = 10 ** record.maximum_count - 1
|
|
record.current_meter = max_value - record.last_read + record.current_read
|
|
else:
|
|
record.current_meter = 0
|
|
else:
|
|
record.current_meter = 0
|
|
|
|
@api.onchange('current_meter', 'unit', 'line_loss')
|
|
def onchange_receivables(self):
|
|
for record in self:
|
|
# 因为current_meter 取不到值所有复制一遍
|
|
if record.current_read >= record.last_read:
|
|
current_meter = record.current_read - record.last_read
|
|
else:
|
|
if record.current_read:
|
|
if record.maximum_count > 0:
|
|
max_value = 10 ** record.maximum_count - 1
|
|
current_meter = max_value - record.last_read + record.current_read
|
|
else:
|
|
current_meter = 0
|
|
else:
|
|
current_meter = 0
|
|
|
|
if current_meter and record.unit:
|
|
raw_value = current_meter * record.unit * (1 + record.line_loss) * record.gauge_magnification
|
|
record.receivables = round(raw_value, 1)
|
|
else:
|
|
record.receivables = 0.0
|
|
|
|
def update_date(self):
|
|
archives_ids = self.env["meter.archives.line"].browse(
|
|
self._context.get('active_ids', self._context.get('active_id')))
|
|
for record in archives_ids:
|
|
record.onchange_lessee_id()
|
|
|
|
def update_meter(self):
|
|
current_year = datetime.now().year
|
|
target_date = datetime(current_year, 9, 1).date()
|
|
domain = [
|
|
("state", "=", "unreading"), # 只处理未抄的记录
|
|
("meter_date", "<", target_date), # 任务日期在9月之前
|
|
]
|
|
active_ids = self._context.get("active_ids", self._context.get("active_id"))
|
|
if active_ids:
|
|
domain.append(("id", "in", active_ids))
|
|
unread_meter_lines = self.env["meter.archives.line"].search(domain)
|
|
if unread_meter_lines:
|
|
unread_meter_lines.write({"state": "reading"})
|
|
|
|
@api.depends("archives_id", "last_dosage", "last_read", "last_meter", "current_meter", "current_read","maximum_count",)
|
|
def _compute_last(self):
|
|
"""
|
|
上次用量,读数,超标日期
|
|
:return:
|
|
"""
|
|
for i in self:
|
|
last_id = (
|
|
self.env["meter.archives.line"]
|
|
.search(
|
|
[
|
|
("meter_date", "<", i.meter_date),
|
|
("archives_id", "=", i.archives_id.id),
|
|
]
|
|
)
|
|
.sorted("meter_date")
|
|
)
|
|
if last_id:
|
|
i.last_dosage = last_id[-1].current_meter
|
|
i.last_read = last_id[-1].current_read
|
|
i.last_meter = last_id[-1].meter_date
|
|
if i.current_read >= i.last_read:
|
|
i.current_meter = i.current_read - i.last_read
|
|
else:
|
|
if i.current_read != 0:
|
|
if i.maximum_count > 0:
|
|
max_value = 10**i.maximum_count - 1
|
|
i.current_meter = max_value - i.last_read + i.current_read
|
|
else:
|
|
i.current_meter = 0
|
|
else:
|
|
i.current_meter = 0
|
|
else:
|
|
i.last_dosage = 0
|
|
i.last_read = i.initial_value
|
|
i.last_meter = i.meter_date - relativedelta(months=1)
|
|
if i.current_read != 0:
|
|
if i.maximum_count > 0:
|
|
max_value = 10 ** i.maximum_count - 1
|
|
i.current_meter = max_value - i.last_read + i.current_read
|
|
else:
|
|
i.current_meter = 0
|
|
else:
|
|
i.current_meter = 0
|
|
|
|
@api.onchange('current_read', 'last_read', 'maximum_count')
|
|
def onchange_current_meter(self):
|
|
for record in self:
|
|
if record.last_read is not None and record.current_read is not None and record.last_read > 0:
|
|
if record.current_read < record.last_read:
|
|
if record.current_meter>= 10000:
|
|
raise ValidationError(
|
|
f"输入错误:仪表编号为{record.archives_id.name}当前读数 {record.current_read} 必须大于上次读数 {record.last_read} "
|
|
)
|
|
|
|
@api.onchange("yuthon_property_ids")
|
|
def onchange_lessee_id(self):
|
|
for record in self:
|
|
lessee = False
|
|
if record.yuthon_property_ids:
|
|
for prop in record.yuthon_property_ids:
|
|
if prop.lessee_id:
|
|
lessee = prop.lessee_id
|
|
break
|
|
record.lessee_id = lessee
|
|
|
|
def push_task_push_wizard(self):
|
|
meter_ids = self.env["meter.archives.line"].browse(
|
|
self._context.get("active_ids", self._context.get("active_id"))
|
|
)
|
|
for meter_id in meter_ids:
|
|
if meter_id.is_push == True:
|
|
raise UserError(f"电表名为{meter_id.archives_id}任务已推送,请勿重复推送")
|
|
return {
|
|
"name": "电水表任务",
|
|
"type": "ir.actions.act_window",
|
|
"res_model": "task.push.wizard",
|
|
"view_mode": "form",
|
|
"target": "new",
|
|
"context": {
|
|
"default_meter_ids": meter_ids.ids,
|
|
}
|
|
}
|
|
|