forked from erp-dev/erp
feat: added print-template(test) and sync_mdy_plate_order beat task setting
This commit is contained in:
78
printing/management/commands/render_docx_template.py
Normal file
78
printing/management/commands/render_docx_template.py
Normal file
@@ -0,0 +1,78 @@
|
||||
import json
|
||||
from pathlib import Path
|
||||
|
||||
from django.core.management.base import BaseCommand, CommandError
|
||||
|
||||
|
||||
class Command(BaseCommand):
|
||||
help = "Render a .docx template by replacing placeholders like #{...} / {...} using a JSON context mapping."
|
||||
|
||||
def add_arguments(self, parser):
|
||||
parser.add_argument(
|
||||
"template",
|
||||
type=str,
|
||||
help="模板文件路径(.docx),例如 /home/f/coding/flower/print-template.docx",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--out",
|
||||
type=str,
|
||||
default="rendered.docx",
|
||||
help="输出 docx 路径(默认 rendered.docx)",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--context-json",
|
||||
type=str,
|
||||
default="{}",
|
||||
help="JSON 字符串,键为占位符内部文本(例如 打印-销售单(8*9).客户名称)",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--context-json-file",
|
||||
type=str,
|
||||
default=None,
|
||||
help="JSON 文件路径(优先级高于 --context-json)",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--keep-unresolved",
|
||||
action="store_true",
|
||||
help="不清空未命中的占位符(默认会清空)",
|
||||
)
|
||||
|
||||
def handle(self, *args, **options):
|
||||
template = str(options["template"])
|
||||
out = str(options["out"])
|
||||
context_json = str(options["context_json"] or "{}")
|
||||
context_json_file = options.get("context_json_file")
|
||||
keep_unresolved = bool(options.get("keep_unresolved"))
|
||||
|
||||
tp = Path(template)
|
||||
if not tp.exists():
|
||||
raise CommandError(f"模板文件不存在:{template}")
|
||||
if tp.suffix.lower() != ".docx":
|
||||
raise CommandError("template 必须是 .docx 文件")
|
||||
|
||||
if context_json_file:
|
||||
jp = Path(str(context_json_file))
|
||||
if not jp.exists():
|
||||
raise CommandError(f"JSON 文件不存在:{context_json_file}")
|
||||
context = json.loads(jp.read_text("utf-8"))
|
||||
else:
|
||||
context = json.loads(context_json)
|
||||
|
||||
if not isinstance(context, dict):
|
||||
raise CommandError("context 必须是 JSON object(字典)")
|
||||
|
||||
from printing.docx_template import render_docx_template
|
||||
|
||||
stats = render_docx_template(
|
||||
template_path=str(tp),
|
||||
output_path=str(out),
|
||||
context=context,
|
||||
clear_unresolved=(not keep_unresolved),
|
||||
)
|
||||
|
||||
self.stdout.write(
|
||||
self.style.SUCCESS(
|
||||
f"渲染完成:out={out} paragraphs_touched={stats.paragraphs_touched} placeholders={stats.placeholders_replaced_or_cleared}"
|
||||
)
|
||||
)
|
||||
|
||||
96
printing/management/commands/render_mock_docx.py
Normal file
96
printing/management/commands/render_mock_docx.py
Normal file
@@ -0,0 +1,96 @@
|
||||
from pathlib import Path
|
||||
|
||||
from django.conf import settings
|
||||
from django.core.management.base import BaseCommand, CommandError
|
||||
|
||||
|
||||
class Command(BaseCommand):
|
||||
help = "Render a .docx template with realistic mock data (no database required)."
|
||||
|
||||
def add_arguments(self, parser):
|
||||
parser.add_argument(
|
||||
"--template",
|
||||
type=str,
|
||||
default=str(Path(settings.BASE_DIR) / "print-template.docx"),
|
||||
help="docx 模板路径(默认 BASE_DIR/print-template.docx)",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--out",
|
||||
type=str,
|
||||
default="",
|
||||
help="输出 docx 路径(默认 .tmp_docx/mock-rendered.docx)",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--seed",
|
||||
type=int,
|
||||
default=20260126,
|
||||
help="随机种子(默认 20260126),便于复现同一份拟真数据",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--pages",
|
||||
type=int,
|
||||
default=1,
|
||||
help="生成页数(每页固定 9 行明细),默认 1",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--printer",
|
||||
type=str,
|
||||
default="系统自动生成",
|
||||
help="填充 {打印人}",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--keep-unresolved",
|
||||
action="store_true",
|
||||
help="不清空未命中的占位符(默认会清空)",
|
||||
)
|
||||
|
||||
def handle(self, *args, **options):
|
||||
template = str(options.get("template") or "")
|
||||
out = str(options.get("out") or "")
|
||||
seed = int(options.get("seed") or 0)
|
||||
pages = int(options.get("pages") or 1)
|
||||
printer = str(options.get("printer") or "")
|
||||
keep_unresolved = bool(options.get("keep_unresolved"))
|
||||
|
||||
tp = Path(template)
|
||||
if not tp.exists():
|
||||
raise CommandError(f"模板文件不存在:{template}")
|
||||
if tp.suffix.lower() != ".docx":
|
||||
raise CommandError("template 必须是 .docx 文件")
|
||||
|
||||
if not out:
|
||||
out = str(Path(settings.BASE_DIR) / ".tmp_docx" / "mock-rendered.docx")
|
||||
Path(out).parent.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
from printing.docx_template import extract_docx_placeholders, render_docx_template_pages
|
||||
from printing.print_docx_services import build_debug_context_summary, build_mock_sales_docx_pages
|
||||
|
||||
placeholders = extract_docx_placeholders(template_path=str(tp))
|
||||
page_contexts = build_mock_sales_docx_pages(
|
||||
placeholders=placeholders,
|
||||
pages=pages,
|
||||
printer_label=printer,
|
||||
seed=seed,
|
||||
)
|
||||
# stats: aggregate all page contexts
|
||||
ctx_stats = build_debug_context_summary({k: v for d in page_contexts for k, v in d.items()})
|
||||
|
||||
stats = render_docx_template_pages(
|
||||
template_path=str(tp),
|
||||
output_path=str(out),
|
||||
pages_contexts=page_contexts,
|
||||
clear_unresolved=(not keep_unresolved),
|
||||
)
|
||||
|
||||
self.stdout.write(
|
||||
self.style.SUCCESS(
|
||||
"渲染完成:"
|
||||
f" out={out}"
|
||||
f" seed={seed}"
|
||||
f" pages={pages}"
|
||||
f" paragraphs_touched={stats.paragraphs_touched}"
|
||||
f" placeholders_replaced_or_cleared={stats.placeholders_replaced_or_cleared}"
|
||||
f" context_filled={ctx_stats['context_filled_keys']}/{ctx_stats['context_total_keys']}"
|
||||
)
|
||||
)
|
||||
|
||||
110
printing/management/commands/render_printing_order_docx.py
Normal file
110
printing/management/commands/render_printing_order_docx.py
Normal file
@@ -0,0 +1,110 @@
|
||||
from pathlib import Path
|
||||
|
||||
from django.conf import settings
|
||||
from django.core.management.base import BaseCommand, CommandError
|
||||
from django.db.models import Count
|
||||
|
||||
|
||||
class Command(BaseCommand):
|
||||
help = "Render a PrintingOrder into a .docx template using real DB data (pick the most 'full' order by default)."
|
||||
|
||||
def add_arguments(self, parser):
|
||||
parser.add_argument(
|
||||
"--order-id",
|
||||
type=int,
|
||||
default=None,
|
||||
help="指定 PrintingOrder.id;不传则自动选择 PrintingJob 明细最多的一单",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--template",
|
||||
type=str,
|
||||
default=str(Path(settings.BASE_DIR) / "print-template.docx"),
|
||||
help="docx 模板路径(默认 BASE_DIR/print-template.docx)",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--out",
|
||||
type=str,
|
||||
default="",
|
||||
help="输出 docx 路径(默认 .tmp_docx/printing-order-{id}.docx)",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--printer",
|
||||
type=str,
|
||||
default="系统自动生成",
|
||||
help="填充 {打印人}",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--keep-unresolved",
|
||||
action="store_true",
|
||||
help="不清空未命中的占位符(默认会清空)",
|
||||
)
|
||||
|
||||
def handle(self, *args, **options):
|
||||
order_id = options.get("order_id")
|
||||
template = str(options.get("template") or "")
|
||||
out = str(options.get("out") or "")
|
||||
printer = str(options.get("printer") or "")
|
||||
keep_unresolved = bool(options.get("keep_unresolved"))
|
||||
|
||||
tp = Path(template)
|
||||
if not tp.exists():
|
||||
raise CommandError(f"模板文件不存在:{template}")
|
||||
if tp.suffix.lower() != ".docx":
|
||||
raise CommandError("template 必须是 .docx 文件")
|
||||
|
||||
from printing.models import PrintingOrder
|
||||
|
||||
qs = PrintingOrder.objects.all()
|
||||
if order_id:
|
||||
order = (
|
||||
qs.select_related("customer", "merchant", "process")
|
||||
.prefetch_related("printing_jobs__product")
|
||||
.filter(id=int(order_id))
|
||||
.first()
|
||||
)
|
||||
if not order:
|
||||
raise CommandError(f"PrintingOrder 不存在:id={order_id}")
|
||||
else:
|
||||
order = (
|
||||
qs.annotate(job_count=Count("printing_jobs"))
|
||||
.filter(job_count__gt=0)
|
||||
.order_by("-job_count", "-id")
|
||||
.select_related("customer", "merchant", "process")
|
||||
.prefetch_related("printing_jobs__product")
|
||||
.first()
|
||||
)
|
||||
if not order:
|
||||
raise CommandError("未找到包含 printing_jobs 的 PrintingOrder(无法做“饱满”渲染测试)")
|
||||
|
||||
# output path default
|
||||
if not out:
|
||||
out = str(Path(settings.BASE_DIR) / ".tmp_docx" / f"printing-order-{order.id}.docx")
|
||||
Path(out).parent.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
from printing.docx_template import extract_docx_placeholders, render_docx_template
|
||||
from printing.print_docx_services import build_debug_context_summary, build_printing_order_docx_context
|
||||
|
||||
placeholders = extract_docx_placeholders(template_path=str(tp))
|
||||
ctx = build_printing_order_docx_context(order=order, placeholders=placeholders, printer_label=printer)
|
||||
ctx_stats = build_debug_context_summary(ctx)
|
||||
|
||||
stats = render_docx_template(
|
||||
template_path=str(tp),
|
||||
output_path=str(out),
|
||||
context=ctx,
|
||||
clear_unresolved=(not keep_unresolved),
|
||||
)
|
||||
|
||||
job_count = order.printing_jobs.count()
|
||||
self.stdout.write(
|
||||
self.style.SUCCESS(
|
||||
"渲染完成:"
|
||||
f" order_id={order.id}"
|
||||
f" job_count={job_count}"
|
||||
f" out={out}"
|
||||
f" paragraphs_touched={stats.paragraphs_touched}"
|
||||
f" placeholders_replaced_or_cleared={stats.placeholders_replaced_or_cleared}"
|
||||
f" context_filled={ctx_stats['context_filled_keys']}/{ctx_stats['context_total_keys']}"
|
||||
)
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user