Corp/backend/commerce/services/moysklad_projects.py

142 lines
5.0 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

from django.db import transaction
from django.utils import timezone
from commerce.integrations.moysklad import MoySkladError
from commerce.models import ExternalProject, ProjectPayment, Sale, SourceRecord
from commerce.services.ingestion import ingest_source_record
from commerce.services.moysklad_sync import (
_document_currency,
_external_id,
_money,
_reference_external_id,
_source_updated_at,
)
def project_reference(source, row):
if not isinstance(row, dict):
return None
external_id = _reference_external_id(row)
if not external_id:
return None
project, _ = ExternalProject.objects.get_or_create(
source=source,
external_id=external_id,
defaults={"name": str(row.get("name") or external_id)[:255]},
)
# A reference must not erase information obtained from the full catalogue.
if row.get("name") and project.name != row["name"]:
project.name = str(row["name"])[:255]
project.save(update_fields=["name"])
return project
@transaction.atomic
def normalize_project(source, row):
external_id = _external_id(row)
if not external_id or not row.get("name"):
raise MoySkladError("В проекте отсутствует ID или название.")
ingest_source_record(
source=source,
entity_type="project",
external_id=external_id,
payload=row,
source_updated_at=_source_updated_at(row),
)
project, _ = ExternalProject.objects.update_or_create(
source=source,
external_id=external_id,
defaults={
"name": str(row["name"])[:255],
"code": str(row.get("code") or "")[:255],
"description": str(row.get("description") or ""),
"archived": bool(row.get("archived")),
"synced_at": timezone.now(),
},
)
return project
@transaction.atomic
def normalize_project_payment(source, row, document_type, seen_at):
if document_type not in {"paymentout", "cashout"}:
raise MoySkladError("Неподдерживаемый платёж проекта.")
external_id = _external_id(row)
occurred_at = _source_updated_at({"updated": row.get("moment")})
if not external_id or not occurred_at or row.get("sum") is None:
raise MoySkladError("В платеже отсутствует ID, дата или сумма.")
amount = _money(row["sum"])
if amount < 0:
raise MoySkladError("Отрицательная сумма исходящего платежа.")
record = ingest_source_record(
source=source,
entity_type=document_type,
external_id=external_id,
payload=row,
source_updated_at=_source_updated_at(row),
).record
payment, _ = ProjectPayment.objects.update_or_create(
source=source,
external_id=external_id,
document_type=document_type,
defaults={
"source_record": record,
"project": project_reference(source, row.get("project")),
"number": str(row.get("name") or "")[:255],
"description": str(
row.get("paymentPurpose") or row.get("description") or ""
),
"expense_category": str((row.get("expenseItem") or {}).get("name") or "")[
:255
],
"payee": str((row.get("agent") or {}).get("name") or "")[:255],
"amount": amount,
"currency": _document_currency(row),
"occurred_at": occurred_at,
"applicable": bool(row.get("applicable", False)) and not row.get("deleted"),
"seen_at": seen_at,
"missing_from_source": False,
},
)
return payment
def restore_sale_projects(source):
count = 0
records = SourceRecord.objects.filter(
source=source, entity_type__in=["demand", "salesreturn"]
)
for record in records.iterator(chunk_size=500):
project = project_reference(source, record.payload.get("project"))
if project:
count += Sale.objects.filter(
source_record=record, project__isnull=True
).update(project=project)
return count
def sync_project_catalog(source, client):
count = 0
for archived in [False, True]:
for row in client.iter_project_data("project", archived=archived):
normalize_project(source, row)
count += 1
restore_sale_projects(source)
return count
def sync_project_payments(source, client, progress=None):
count = 0
for document_type in ["paymentout", "cashout"]:
seen_at = timezone.now()
for row in client.iter_project_data(document_type):
normalize_project_payment(source, row, document_type, seen_at)
count += 1
if progress and count % 25 == 0:
progress(count)
# Only a fully successful scan may retire missing documents. Keep history.
ProjectPayment.objects.filter(
source=source, document_type=document_type, seen_at__lt=seen_at
).update(missing_from_source=True)
return count