210 lines
		
	
	
		
			12 KiB
		
	
	
	
		
			C#
		
	
	
	
	
	
			
		
		
	
	
			210 lines
		
	
	
		
			12 KiB
		
	
	
	
		
			C#
		
	
	
	
	
	
| using Elwig.Models.Entities;
 | |
| using Microsoft.Data.Sqlite;
 | |
| using Microsoft.EntityFrameworkCore;
 | |
| using System;
 | |
| using System.Collections.Generic;
 | |
| using System.Linq;
 | |
| using System.Threading.Tasks;
 | |
| 
 | |
| namespace Elwig.Helpers.Billing {
 | |
|     public class BillingVariant : Billing {
 | |
| 
 | |
|         protected readonly int AvNr;
 | |
|         protected readonly PaymentVar PaymentVariant;
 | |
|         protected readonly PaymentBillingData Data;
 | |
| 
 | |
|         public BillingVariant(int year, int avnr) : base(year) {
 | |
|             AvNr = avnr;
 | |
|             using var ctx = new AppDbContext();
 | |
|             PaymentVariant = ctx.PaymentVariants.Include(v => v.Season).Where(v => v.Year == Year && v.AvNr == AvNr).Single() ?? throw new ArgumentException("PaymentVar not found");
 | |
|             Data = PaymentBillingData.FromJson(PaymentVariant.Data, Utils.GetVaributes(ctx, Year, onlyDelivered: false));
 | |
|         }
 | |
| 
 | |
|         public async Task Calculate(bool? honorGebunden = null, bool? allowAttrsIntoLower = null, bool? avoidUnderDeliveries = null) {
 | |
|             using var cnx = await AppDbContext.ConnectAsync();
 | |
|             using var tx = await cnx.BeginTransactionAsync();
 | |
|             await CalculateBuckets(honorGebunden, allowAttrsIntoLower, avoidUnderDeliveries, cnx);
 | |
|             await DeleteInDb(cnx);
 | |
|             await SetCalcTime(cnx);
 | |
|             await CalculatePrices(cnx);
 | |
|             if (Data.ConsiderDelieryModifiers) {
 | |
|                 await CalculateDeliveryModifiers(cnx);
 | |
|             }
 | |
|             if (Data.ConsiderCustomModifiers) {
 | |
|                 await CalculateMemberModifiers(cnx);
 | |
|             }
 | |
|             await tx.CommitAsync();
 | |
|         }
 | |
| 
 | |
|         public async Task Commit() {
 | |
|             await Revert();
 | |
|             using var cnx = await AppDbContext.ConnectAsync();
 | |
|             await AppDbContext.ExecuteBatch(cnx, $"""
 | |
|                     INSERT INTO credit (year, tgnr, mgnr, avnr, net_amount, prev_net_amount, vat, modifiers, prev_modifiers)
 | |
|                     SELECT s.year,
 | |
|                            COALESCE(t.tgnr, 0) + ROW_NUMBER() OVER(ORDER BY m.mgnr) AS tgnr,
 | |
|                            m.mgnr,
 | |
|                            v.avnr,
 | |
|                            ROUND(p.amount / POW(10, s.precision - 2)) AS net_amount,
 | |
|                            IIF(lc.amount >= 0, ROUND(lp.amount / POW(10, s.precision - 2)), 0) AS prev_net_amount,
 | |
|                            IIF(m.buchführend, s.vat_normal, s.vat_flatrate) AS vat,
 | |
|                            ROUND(IIF({Data.ConsiderTotalPenalty}, COALESCE(b.total_penalty, 0), 0) / POW(10, s.precision - 2)) +
 | |
|                            ROUND(IIF({Data.ConsiderContractPenalties}, COALESCE(u.total_penalty, 0), 0) / POW(10, 4 - 2)) +
 | |
|                            ROUND(IIF({Data.ConsiderAutoBusinessShares}, -COALESCE(a.total_amount, 0), 0) / POW(10, s.precision - 2)) +
 | |
|                            IIF({Data.ConsiderCustomModifiers}, COALESCE(x.amount, 0), 0)
 | |
|                            AS modifiers,
 | |
|                            IIF(lc.amount >= 0, lc.modifiers, 0) AS prev_modifiers
 | |
|                     FROM season s
 | |
|                         JOIN payment_variant v ON v.year = s.year
 | |
|                         LEFT JOIN payment_variant l ON l.year = s.year
 | |
|                             AND l.avnr = (SELECT avnr
 | |
|                                           FROM payment_variant
 | |
|                                           WHERE year = s.year AND NOT test_variant
 | |
|                                           ORDER BY COALESCE(transfer_date, date) DESC, avnr DESC
 | |
|                                           LIMIT 1)
 | |
|                         LEFT JOIN (SELECT year, MAX(tgnr) AS tgnr FROM credit GROUP BY year) t ON t.year = s.year
 | |
|                         JOIN (SELECT DISTINCT year, mgnr FROM delivery) d ON d.year = s.year
 | |
|                         JOIN member m ON m.mgnr = d.mgnr
 | |
|                         LEFT JOIN payment_member lp ON (lp.year, lp.avnr, lp.mgnr) = (l.year, l.avnr, m.mgnr)
 | |
|                         LEFT JOIN payment_member p ON (p.year, p.avnr, p.mgnr) = (v.year, v.avnr, m.mgnr)
 | |
|                         LEFT JOIN credit lc ON (lc.year, lc.avnr, lc.mgnr) = (l.year, l.avnr, m.mgnr)
 | |
|                         LEFT JOIN v_penalty_business_shares b ON (b.year, b.mgnr) = (s.year, m.mgnr)
 | |
|                         LEFT JOIN v_penalty_area_commitments u ON (u.year, u.mgnr) = (s.year, m.mgnr)
 | |
|                         LEFT JOIN v_auto_business_shares a ON (a.year, a.mgnr) = (s.year, m.mgnr)
 | |
|                         LEFT JOIN payment_custom x ON (x.year, x.mgnr) = (s.year, m.mgnr)
 | |
|                     WHERE s.year = {Year} AND v.avnr = {AvNr};
 | |
|                     """);
 | |
|             await AppDbContext.ExecuteBatch(cnx, $"""
 | |
|                 UPDATE payment_variant SET test_variant = FALSE WHERE (year, avnr) = ({Year}, {AvNr});
 | |
|                 """);
 | |
|         }
 | |
| 
 | |
|         public async Task Revert() {
 | |
|             using var cnx = await AppDbContext.ConnectAsync();
 | |
|             await AppDbContext.ExecuteBatch(cnx, $"""
 | |
|                 DELETE FROM credit WHERE (year, avnr) = ({Year}, {AvNr});
 | |
|                 UPDATE payment_variant SET test_variant = TRUE WHERE (year, avnr) = ({Year}, {AvNr});
 | |
|                 """);
 | |
|         }
 | |
| 
 | |
|         protected async Task SetCalcTime(SqliteConnection cnx) {
 | |
|             await AppDbContext.ExecuteBatch(cnx, $"""
 | |
|                 UPDATE payment_variant SET calc_time = UNIXEPOCH() WHERE (year, avnr) = ({Year}, {AvNr})
 | |
|                 """);
 | |
|         }
 | |
| 
 | |
|         protected async Task DeleteInDb(SqliteConnection cnx) {
 | |
|             await AppDbContext.ExecuteBatch(cnx, $"""
 | |
|                 DELETE FROM payment_delivery_part_bucket WHERE (year, avnr) = ({Year}, {AvNr});
 | |
|                 DELETE FROM payment_delivery_part WHERE (year, avnr) = ({Year}, {AvNr});
 | |
|                 DELETE FROM payment_member WHERE (year, avnr) = ({Year}, {AvNr});
 | |
|                 UPDATE payment_variant SET calc_time = NULL WHERE (year, avnr) = ({Year}, {AvNr});
 | |
|                 """);
 | |
|         }
 | |
| 
 | |
|         protected async Task CalculateMemberModifiers(SqliteConnection cnx) {
 | |
|             if (App.Client.IsMatzen) {
 | |
|                 var lastYears = 3;
 | |
|                 var multiplier = 0.50;
 | |
|                 var includePredecessor = true;
 | |
|                 var modName = "Treue%";
 | |
|                 await AppDbContext.ExecuteBatch(cnx, $"""
 | |
|                     INSERT INTO payment_member (year, avnr, mgnr, net_amount, mod_abs, mod_rel)
 | |
|                     SELECT c.year, {AvNr}, s.mgnr, 0,
 | |
|                            ROUND(s.sum * COALESCE(m.abs, 0)),
 | |
|                            COALESCE(m.rel, 0)
 | |
|                     FROM (SELECT {Year} AS year, m.mgnr,
 | |
|                                  ROUND(AVG(COALESCE(a.sum, b.sum)) * {multiplier}) AS baseline,
 | |
|                                  COUNT(*) = {lastYears} AND MIN(COALESCE(a.sum, b.sum)) > 0 AS allowed
 | |
|                           FROM member m
 | |
|                               LEFT JOIN v_stat_member a ON a.mgnr = m.mgnr
 | |
|                               FULL OUTER JOIN v_stat_member b ON b.mgnr = m.predecessor_mgnr AND b.year = a.year AND {(includePredecessor ? "TRUE" : "FALSE")}
 | |
|                           WHERE a.year > {Year} - {lastYears}
 | |
|                           GROUP BY m.mgnr
 | |
|                           HAVING allowed) c
 | |
|                         JOIN v_stat_member s ON (s.year, s.mgnr) = (c.year, c.mgnr)
 | |
|                         LEFT JOIN modifier m ON m.year = c.year AND m.name LIKE '{modName}'
 | |
|                     WHERE sum >= baseline
 | |
|                     ON CONFLICT DO UPDATE
 | |
|                     SET mod_abs = mod_abs + excluded.mod_abs,
 | |
|                         mod_rel = mod_rel + excluded.mod_rel
 | |
|                     """);
 | |
|             }
 | |
|             await AppDbContext.ExecuteBatch(cnx, $"""
 | |
|                 INSERT INTO payment_member (year, avnr, mgnr, net_amount, mod_abs, mod_rel)
 | |
|                 SELECT x.year, {AvNr}, x.mgnr, 0, COALESCE(x.mod_abs * POW(10, s.precision - 2), 0), COALESCE(x.mod_rel, 0)
 | |
|                 FROM payment_custom x
 | |
|                     JOIN season s ON s.year = x.year
 | |
|                 WHERE x.year = {Year}
 | |
|                 ON CONFLICT DO UPDATE
 | |
|                 SET mod_abs = mod_abs + excluded.mod_abs,
 | |
|                     mod_rel = mod_rel + excluded.mod_rel
 | |
|                 """);
 | |
|         }
 | |
| 
 | |
|         protected async Task CalculatePrices(SqliteConnection cnx) {
 | |
|             var parts = new List<(int Year, int DId, int DPNr, int BktNr, string SortId, string? AttrId, string? CultId, string Discr, int Value, double Oe, double Kmw, string QualId, bool AttrAreaCom)>();
 | |
|             using (var cmd = cnx.CreateCommand()) {
 | |
|                 cmd.CommandText = $"""
 | |
|                     SELECT d.year, d.did, d.dpnr, b.bktnr, d.sortid, d.attrid, d.cultid, b.discr, b.value, d.oe, d.kmw, d.qualid, COALESCE(a.area_com, TRUE)
 | |
|                     FROM delivery_part_bucket b
 | |
|                         JOIN v_delivery d ON (d.year, d.did, d.dpnr) = (b.year, b.did, b.dpnr)
 | |
|                         LEFT JOIN v_wine_attribute a ON a.attrid = d.attrid
 | |
|                     WHERE b.year = {Year}
 | |
|                     """;
 | |
|                 using var reader = await cmd.ExecuteReaderAsync();
 | |
|                 while (await reader.ReadAsync()) {
 | |
|                     parts.Add((
 | |
|                         reader.GetInt32(0), reader.GetInt32(1), reader.GetInt32(2), reader.GetInt32(3),
 | |
|                         reader.GetString(4), reader.IsDBNull(5) ? null : reader.GetString(5),
 | |
|                         reader.IsDBNull(6) ? null : reader.GetString(6), reader.GetString(7),
 | |
|                         reader.GetInt32(8), reader.GetDouble(9), reader.GetDouble(10), reader.GetString(11),
 | |
|                         reader.GetBoolean(12)
 | |
|                     ));
 | |
|                 }
 | |
|             }
 | |
| 
 | |
|             var inserts = new List<(int Year, int DId, int DPNr, int BktNr, long Price, long Amount)>();
 | |
|             foreach (var part in parts) {
 | |
|                 if (part.Value == 0)
 | |
|                     continue;
 | |
|                 var ungeb = part.Discr == "_";
 | |
|                 var payAttrId = (part.Discr is "" or "_") ? null : part.Discr;
 | |
|                 var attrId = part.AttrAreaCom ? payAttrId : part.AttrId;
 | |
|                 var geb = !ungeb && (payAttrId == attrId || !part.AttrAreaCom);
 | |
|                 var price = Data.CalculatePrice(part.SortId, attrId, part.CultId, part.QualId, geb, part.Oe, part.Kmw);
 | |
|                 var priceL = PaymentVariant.Season.DecToDb(price);
 | |
|                 inserts.Add((part.Year, part.DId, part.DPNr, part.BktNr, priceL, priceL * part.Value));
 | |
|             }
 | |
| 
 | |
|             await AppDbContext.ExecuteBatch(cnx, $"""
 | |
|                 INSERT INTO payment_delivery_part_bucket (year, did, dpnr, bktnr, avnr, price, amount)
 | |
|                 VALUES {string.Join(",\n       ", inserts.Select(i => $"({i.Year}, {i.DId}, {i.DPNr}, {i.BktNr}, {AvNr}, {i.Price}, {i.Amount})"))};
 | |
|                 """);
 | |
|         }
 | |
| 
 | |
|         protected async Task CalculateDeliveryModifiers(SqliteConnection cnx) {
 | |
|             var netMod = Data.NetWeightModifier.ToString().Replace(',', '.');
 | |
|             var grossMod = Data.GrossWeightModifier.ToString().Replace(',', '.');
 | |
|             await AppDbContext.ExecuteBatch(cnx, $"""
 | |
|                 INSERT INTO payment_delivery_part (year, did, dpnr, avnr, net_amount, mod_abs, mod_rel)
 | |
|                 SELECT d.year, d.did, d.dpnr, {AvNr}, 0, 0, IIF(d.net_weight, {netMod}, {grossMod})
 | |
|                 FROM delivery_part d
 | |
|                 WHERE d.year = {Year}
 | |
|                 ON CONFLICT DO UPDATE
 | |
|                 SET mod_rel = mod_rel + excluded.mod_rel;
 | |
| 
 | |
|                 INSERT INTO payment_delivery_part (year, did, dpnr, avnr, net_amount, mod_abs, mod_rel)
 | |
|                 SELECT d.year, d.did, d.dpnr, {AvNr}, 0, COALESCE(m.abs, 0), COALESCE(m.rel, 0)
 | |
|                 FROM delivery_part d
 | |
|                     LEFT JOIN delivery_part_modifier p ON (p.year, p.did, p.dpnr) = (d.year, d.did, d.dpnr)
 | |
|                     LEFT JOIN modifier m ON (m.year, m.modid) = (d.year, p.modid)
 | |
|                 WHERE d.year = {Year}
 | |
|                 ON CONFLICT DO UPDATE
 | |
|                 SET mod_abs = mod_abs + excluded.mod_abs,
 | |
|                     mod_rel = mod_rel + excluded.mod_rel;
 | |
|                 """);
 | |
|         }
 | |
|     }
 | |
| }
 |