feat(vat): auto-fill new Əlavə 1 tables and page totals

Fill the second Əlavə 1 tab (əlavə1_yeni) from the Purchase VAT offset
report (Cədvəl 22), whose columns map one-to-one onto the table and whose
vat_offset_line_code shares the 308/314/316 options of sətirkodu. The
report gained a posting_date key so the tarix column can be filled; its
visible columns are unchanged.

Also fill three summary targets that were left to manual entry:
  - əlavə_1_2      totals over Əlavə 1
  - əlavə1_2_yeni  totals per line code
  - sertifikat...səhifəsicəmi  page total of Əlavə 8 Tablo 2

The two summary tables already carry rows created by a client script, so
they are filled in place — fill_first_row and fill_rows_by_code never add
or remove rows. Rows are matched on the leading line code because the
stored sətirkodu carries a long description after it.

Widen the Int money fields to Float: paid VAT is proportional and was
truncating (106.78 -> 107). Invoice number becomes Data, matching the
report where it is a free-form string.

Note: the generated Formula Editor client script still writes əlavə_1_2
and əlavə1_2_yeni row 0 from report() grand totals. Those 6 formula blocks
are to be removed by hand; the Formula Editor record itself no longer
defines them, so they will not be regenerated.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Ali 2026-07-10 09:39:00 +00:00
parent 1aba387f87
commit 4e4a6517c9
6 changed files with 140 additions and 24 deletions

View File

@ -10,8 +10,8 @@ frappe.ui.form.on("Declaration of value added tax", {
},
});
// Pull Əlavə 1 / Əlavə 2 Hissə 1 / Əlavə 2 Hissə 2 from the three source
// reports once both the year (İl) and month (Ay) are selected.
// Pull the appendix tables from their source reports once both the year (İl)
// and month (Ay) are selected.
function fill_from_reports(frm) {
if (!frm.doc.il || !frm.doc.ay) {
return;
@ -31,8 +31,19 @@ function fill_from_reports(frm) {
set_table(frm, "əlavə_1", r.message.elave_1);
set_table(frm, "əlavə_2", r.message.elave_2);
set_table(frm, "əlavə_2_2", r.message.elave_2_2);
set_table(frm, "əlavə1_yeni", r.message.elave_1_yeni);
set_table(frm, "müəssisə_malın_dəyəri", r.message.elave_8_2);
// Summary tables ship with their rows already created by a client
// script, so fill them in place instead of rebuilding.
fill_first_row(frm, "əlavə_1_2", r.message.elave_1_2);
fill_rows_by_code(frm, "əlavə1_2_yeni", "sətirkodu", r.message.elave_1_2_yeni);
frm.set_value(
"sertifikathesabatdövründəmalındəyərininsəhifəsicəmi",
r.message.elave_8_total
);
frm.refresh_fields();
frappe.show_alert({ message: __("Əlavələr hesabatlardan dolduruldu"), indicator: "green" });
},
@ -44,10 +55,45 @@ function set_table(frm, fieldname, rows) {
frm.clear_table(fieldname);
(rows || []).forEach(function (data) {
const child = frm.add_child(fieldname);
Object.keys(data).forEach(function (key) {
if (data[key] !== null && data[key] !== undefined) {
child[key] = data[key];
}
});
assign(child, data);
});
frm.refresh_field(fieldname);
}
// Fill the single summary row of a table without creating or deleting rows.
function fill_first_row(frm, fieldname, values) {
const rows = frm.doc[fieldname] || [];
if (!rows.length || !values) {
return;
}
assign(rows[0], values);
frm.refresh_field(fieldname);
}
// Fill pre-created summary rows, matching each row by the leading line code of
// `codefield`. The stored value carries a long description after the code, so
// only the first whitespace-delimited token is compared. Rows are never added
// or removed — a code with no data keeps the zeroed totals from the server.
function fill_rows_by_code(frm, fieldname, codefield, values_by_code) {
const rows = frm.doc[fieldname] || [];
if (!rows.length || !values_by_code) {
return;
}
rows.forEach(function (row) {
const code = String(row[codefield] || "").trim().split(/\s+/)[0];
const values = values_by_code[code];
if (values) {
assign(row, values);
}
});
frm.refresh_field(fieldname);
}
// Copy the supplied values onto a row, skipping keys the server left empty.
function assign(row, data) {
Object.keys(data).forEach(function (key) {
if (data[key] !== null && data[key] !== undefined) {
row[key] = data[key];
}
});
}

View File

@ -373,7 +373,7 @@
},
{
"fieldname": "sertifikathesabatd\u00f6vr\u00fcnd\u0259mal\u0131nd\u0259y\u0259rinins\u0259hif\u0259sic\u0259mi",
"fieldtype": "Int",
"fieldtype": "Float",
"label": "Sertifikat \u00fczr\u0259 hesabat d\u00f6vr\u00fcnd\u0259 t\u0259qdim edilmi\u015f mal\u0131n (g\u00f6r\u00fclm\u00fc\u015f i\u015fin, g\u00f6st\u0259rilmi\u015f xidm\u0259tin) d\u0259y\u0259rinin bu s\u0259hif\u0259si \u00fczr\u0259 C\u018fM\u0130",
"precision": "2"
},
@ -604,7 +604,7 @@
],
"index_web_pages_for_search": 1,
"links": [],
"modified": "2026-07-06 21:26:56.777547",
"modified": "2026-07-10 10:00:00.000000",
"modified_by": "nihad_m@jeysoft.az",
"module": "Taxes Az",
"name": "Declaration of value added tax",

View File

@ -40,14 +40,29 @@ def _period_filters(year, month):
return {"company": company, "from_date": from_date, "to_date": to_date}
# Line codes of the new Əlavə 1 summary table (əlavə1_2_yeni). The stored
# `sətirkodu` values carry a long description after the code, so rows are
# matched on this prefix rather than on the full option string.
VAT_OFFSET_LINE_CODES = ["308", "314", "316"]
@frappe.whitelist()
def populate_vat_tables(year, month):
"""Build the VAT declaration appendix rows from the three source reports.
"""Build the VAT declaration appendix rows from the source reports.
Returns row dicts for:
- Əlavə 1 (əlavə_1) <- Purchase YGB VAT report
- Əlavə 2 Hissə 1 (əlavə_2) <- Purchase invoice info report
- Əlavə 2 Hissə 2 (əlavə_2_2) <- Agricultural goods stock report
- Əlavə 1 (əlavə_1) <- Purchase YGB VAT report
- Əlavə 1 cəmi (əlavə_1_2) <- totals over Əlavə 1
- Əlavə 1 yeni (əlavə1_yeni) <- Purchase VAT offset report
- Əlavə 1 yeni cəmi (əlavə1_2_yeni) <- totals per line code
- Əlavə 2 Hissə 1 (əlavə_2) <- Purchase invoice info report
- Əlavə 2 Hissə 2 (əlavə_2_2) <- Agricultural goods stock report
- Əlavə 8 Tablo 2 (müəssisə_malın_dəyəri) <- Certificate VAT report
- Əlavə 8 cəmi (elave_8_total) <- total of the Əlavə 8 values
The two summary tables already carry their rows (created by a client
script), so only values are returned the caller fills existing rows
instead of rebuilding them.
"""
from taxes_az.taxes_az.report.purchase_ygb_vat_report.purchase_ygb_vat_report import (
get_data as ygb_get_data,
@ -61,6 +76,9 @@ def populate_vat_tables(year, month):
from taxes_az.taxes_az.report.certificate_vat_report.certificate_vat_report import (
get_data as certificate_get_data,
)
from taxes_az.taxes_az.report.purchase_vat_offset_report.purchase_vat_offset_report import (
get_data as vat_offset_get_data,
)
if not month or not year:
return {"success": False, "error": "Year and month are required"}
@ -78,6 +96,47 @@ def populate_vat_tables(year, month):
"ygbüzrəalınmışmalınədvməbləğimanatla": r.get("vat_amount"),
})
# Əlavə 1 cəmi — the single pre-created summary row over Əlavə 1.
elave_1_2 = {
"malınədvsizümumidəyəri": sum(flt(r["ygbüzrəalınmışmalınədvsizdəyərimanatla"]) for r in elave_1),
"malınədvməbləği": sum(flt(r["ygbüzrəalınmışmalınədvməbləğimanatla"]) for r in elave_1),
}
# Əlavə 1 (yeni) — one row per VAT-offset purchase invoice.
elave_1_yeni = []
for r in (vat_offset_get_data(filters) or []):
elave_1_yeni.append({
"sətirkodu": r.get("line_code"),
"tarix": r.get("posting_date"),
"vöenadı": r.get("voen_name"),
"qaiməninseriyası": r.get("invoice_series"),
"qaiməninnömrəsi": r.get("invoice_number"),
"ümumiməbləğmanatla": r.get("total_amount"),
"ədvməbləğimanatla": r.get("vat_amount"),
"ödənilmişümumiməbləğmanatla": r.get("paid_total"),
"ödənilmişədvməbləğimanatla": r.get("paid_vat"),
})
# Əlavə 1 (yeni) cəmi — totals per line code, keyed by the bare code. Every
# code is present so a code that lost its rows is zeroed rather than stale.
elave_1_2_yeni = {
code: {
"ümumiməbləğmanatla": 0.0,
"ədvməbləğimanatla": 0.0,
"ödənilmişümumiməbləğmanatla": 0.0,
"ödənilmişədvməbləğimanatla": 0.0,
}
for code in VAT_OFFSET_LINE_CODES
}
for r in elave_1_yeni:
bucket = elave_1_2_yeni.get(str(r["sətirkodu"] or "").strip())
if not bucket:
continue
bucket["ümumiməbləğmanatla"] += flt(r["ümumiməbləğmanatla"])
bucket["ədvməbləğimanatla"] += flt(r["ədvməbləğimanatla"])
bucket["ödənilmişümumiməbləğmanatla"] += flt(r["ödənilmişümumiməbləğmanatla"])
bucket["ödənilmişədvməbləğimanatla"] += flt(r["ödənilmişədvməbləğimanatla"])
# Əlavə 2 Hissə 1 — one row per agricultural purchase document.
elave_2 = []
for r in (purchase_info_get_data(filters) or []):
@ -113,11 +172,20 @@ def populate_vat_tables(year, month):
"sertifikataltındatəqdimolunanmallarınişlərinxidmətlərindəyəri": r.get("value"),
})
# Əlavə 8 — page total of the values listed in Tablo 2.
elave_8_total = sum(
flt(r["sertifikataltındatəqdimolunanmallarınişlərinxidmətlərindəyəri"]) for r in elave_8_2
)
return {
"success": True,
"elave_1": elave_1,
"elave_1_2": elave_1_2,
"elave_1_yeni": elave_1_yeni,
"elave_1_2_yeni": elave_1_2_yeni,
"elave_2": elave_2,
"elave_2_2": elave_2_2,
"elave_8_2": elave_8_2,
"elave_8_total": elave_8_total,
"period": filters,
}

View File

@ -33,21 +33,21 @@
},
{
"fieldname": "qaim\u0259ninn\u00f6mr\u0259si",
"fieldtype": "Int",
"fieldtype": "Data",
"in_list_view": 1,
"label": "Qaim\u0259nin n\u00f6mr\u0259si",
"precision": "2"
},
{
"fieldname": "\u00fcmumim\u0259bl\u0259\u011fmanatla",
"fieldtype": "Int",
"fieldtype": "Float",
"in_list_view": 1,
"label": "\u00dcmumi m\u0259bl\u0259\u011f,manatla",
"precision": "2"
},
{
"fieldname": "\u0259dvm\u0259bl\u0259\u011fimanatla",
"fieldtype": "Int",
"fieldtype": "Float",
"in_list_view": 1,
"label": "\u018fDV m\u0259bl\u0259\u011fi,manatla",
"precision": "2"
@ -68,14 +68,14 @@
},
{
"fieldname": "\u00f6d\u0259nilmi\u015f\u00fcmumim\u0259bl\u0259\u011fmanatla",
"fieldtype": "Int",
"fieldtype": "Float",
"in_list_view": 1,
"label": "\u00d6d\u0259nilmi\u015f \u00fcmumi m\u0259bl\u0259\u011f,manatla",
"precision": "2"
},
{
"fieldname": "\u00f6d\u0259nilmi\u015f\u0259dvm\u0259bl\u0259\u011fimanatla",
"fieldtype": "Int",
"fieldtype": "Float",
"in_list_view": 1,
"label": "\u00d6d\u0259nilmi\u015f \u018fDV m\u0259bl\u0259\u011fi,manatla",
"precision": "2"
@ -84,7 +84,7 @@
"index_web_pages_for_search": 1,
"istable": 1,
"links": [],
"modified": "2026-02-04 18:48:15.462987",
"modified": "2026-07-10 10:00:00.000000",
"modified_by": "Administrator",
"module": "Taxes Az",
"name": "Declaration of value added tax yeni elave 1",

View File

@ -22,28 +22,28 @@
},
{
"fieldname": "\u00fcmumim\u0259bl\u0259\u011fmanatla",
"fieldtype": "Int",
"fieldtype": "Float",
"in_list_view": 1,
"label": "\u00dcmumi m\u0259bl\u0259\u011f,manatla",
"precision": "2"
},
{
"fieldname": "\u0259dvm\u0259bl\u0259\u011fimanatla",
"fieldtype": "Int",
"fieldtype": "Float",
"in_list_view": 1,
"label": "\u018fDV m\u0259bl\u0259\u011fi,manatla",
"precision": "2"
},
{
"fieldname": "\u00f6d\u0259nilmi\u015f\u00fcmumim\u0259bl\u0259\u011fmanatla",
"fieldtype": "Int",
"fieldtype": "Float",
"in_list_view": 1,
"label": "\u00d6d\u0259nilmi\u015f \u00fcmumi m\u0259bl\u0259\u011f,manatla",
"precision": "2"
},
{
"fieldname": "\u00f6d\u0259nilmi\u015f\u0259dvm\u0259bl\u0259\u011fimanatla",
"fieldtype": "Int",
"fieldtype": "Float",
"in_list_view": 1,
"label": "\u00d6d\u0259nilmi\u015f \u018fDV m\u0259bl\u0259\u011fi,manatla",
"precision": "2"
@ -52,7 +52,7 @@
"index_web_pages_for_search": 1,
"istable": 1,
"links": [],
"modified": "2026-02-04 18:50:57.540374",
"modified": "2026-07-10 10:00:00.000000",
"modified_by": "Administrator",
"module": "Taxes Az",
"name": "Declaration of value added tax yeni elave 1_2",

View File

@ -110,6 +110,7 @@ def get_data(filters=None):
rows = frappe.db.sql(f"""
SELECT
pi.name AS purchase_invoice,
pi.posting_date,
pi.vat_offset_line_code AS line_code,
pi.supplier_name,
sup.tax_id,
@ -145,6 +146,7 @@ def get_data(filters=None):
data.append({
"purchase_invoice": r.purchase_invoice,
"posting_date": r.posting_date,
"line_code": r.line_code,
"voen_name": f"{(r.tax_id or '').strip()} / {(r.supplier_name or '').strip()}",
"invoice_series": r.etaxes_invoice_series,