Heartinz Essentials
heartinz_essentials is Heartinz's shared money-math and date/time toolkit for our
Flutter/Dart apps (used today in the Negobill POS app). All monetary amounts are
integers in subunits (paise/cents) to avoid floating-point rounding bugs, and every
calculation uses package:decimal internally with banker's rounding for consistent,
predictable results.
Everything is exposed through a single entry point:
final essentials = Essentials();
...with calculators grouped into sub-modules: calc, currency, date, unit,
purchase, offer, returnPrice, and discount.
Why this package exists
Sale, purchase, stock, and return screens across the app all need the same GST/MRP/tax breakdown math, the same unit conversions (e.g. Kg ↔ Gram), and the same ISO date-time handling. Instead of re-implementing (and re-debugging rounding edge cases) in every feature, that logic lives here once, is unit-tested, and is versioned independently.
Features / Modules
essentials.calc — Grand total & GST
calculateGrandTotal(...)— rolls up MRP, sale price, base price, and tax amount into aGrandTotalModel(grand total, SGST/CGST/IGST split, amount in words).gstCalculate(...)— full price breakdown for a single line item (MRP, discount, sale price, base price, tax %, SGST/CGST/IGST, line totals) as aGSTResult.findGstAmountSplit(taxAmount)— splits a tax amount into SGST/CGST halves.
essentials.currency — Currency conversion
toCurrency(amountInSubunits, currencyCode)— subunits → display currency (e.g. 15000 paise → ₹150.00). SupportsINR,USD,GBP,EUR,JPY.toSubunit(amount, currencyCode)— display currency → subunits, for storing/ computing without floats.
essentials.date — ISO date/time utilities
displayIso(isoString)— ISO string → map of common display formats (dd MMM yyyy,dd/MM/yyyy, etc).toTimeZone(isoTime, targetTimeZone)— convert an ISO time into another IANA timezone (e.g.Asia/Kolkata).getCurrentTimeByTimeZone(timeZone)— current time in a given timezone, in multiple display formats.dayBoundariesInUTC(isoDateStr)— start/end of the local day, expressed in UTC (handy for "today's sales" style date-range queries).timeDifference(isoA, isoB)— difference between two ISO timestamps.modifyIso(isoTime, duration)— add/subtract aDurationfrom an ISO time.splitIso(isoString)— break an ISO string into its date/time components.toIso({dateStr, type, timeZone})— parse a formatted date string into ISO 8601.toUtc(isoTime)— convert an ISO time to UTC.
essentials.unit — Unit conversion (primary ↔ secondary)
For products sold in two units (e.g. Kg as primary, Gram as secondary):
toPrimaryQuantity(secondaryQty, {unitConfig})/toSecondaryQuantity(primaryQty, {unitConfig})— convert quantities between units using aUnitConfig(unit names/codes + conversion ratio).toPrimaryUnitPrice(...)/toSecondaryUnitPrice(...)— convert a full MRP/sale/ base price breakdown from one unit to the other, returning aGSTResultfor the target unit.
essentials.purchase — Purchase price breakdown
getPurchaseAmount(...)— given catalog prices (MRP/sale/base per unit), quantity, and either a tax-inclusive or tax-exclusive total purchase amount, returns both the resulting sale-side breakdown (SaleAmountList) and purchase-side breakdown (PurchaseAmountList) as aPurchaseAmountResult.getPurchaseGrandTotal(...)— rolls up total purchase MRP/price/tax across line items into aPurchaseGrandTotalResult(with amount-in-words).
essentials.offer — Discounts
getOfferAmount(amount, offerPercent)— final price after applying an offer/discount percent to an amount (subunits).getOfferPercent(mrp, salePrice)— discount percent represented by selling atsalePriceagainstmrp.
essentials.returnPrice — Sales returns
getReturnAmountList(...)— price breakdown for a single return line item (MRP, sold price, returned/cancelled price, tax split).getReturnGrandTotal(...)— rolls up total return amounts (returned + cancelled) into aReturnGrandTotalResult, with amount-in-words for both the raw and rounded totals.
essentials.discount — Discount distribution
distributeDiscount(amounts, totalDiscount)— spreads a total discount across a list of amounts (subunits), proportional to each amount, using the largest-remainder method so the per-item discounts always sum exactly tototalDiscount.distributeDiscountToItems(lineItems, totalDiscount, grandTotal)— spreads a total discount acrossDiscountLineItems (proportional to each item's line total) and returns the discounted line items, reconciling any rounding drift onto a single item so line totals always sum tograndTotal - totalDiscount.
Getting started
Add the package to your pubspec.yaml:
dependencies:
heartinz_essentials: ^3.4.0
Then fetch it:
flutter pub get
Usage
import 'package:decimal/decimal.dart';
import 'package:heartinz_essentials/heartinz_essentials.dart';
final essentials = Essentials();
// GST / line-item price breakdown
final gst = essentials.calc.gstCalculate(
mrp: essentials.currency.toSubunit(200, "INR"),
salePrice: essentials.currency.toSubunit(150, "INR"),
basePrice: 0,
taxPercent: 18,
quantity: 2,
lock: "SALEPRICE", // which field (MRP/SALEPRICE/BASEPRICE) drives the calc
);
print(gst.toJson());
// Offer / discount
final finalPrice = essentials.offer.getOfferAmount(gst.mrp, 15); // 15% off
final discountPercent = essentials.offer.getOfferPercent(gst.mrp, gst.salePrice);
// Distribute a bill-level discount across line items proportionally
final discountedItems = essentials.discount.distributeDiscountToItems(
[DiscountLineItem(soldPrice: 100, quantity: 5.5, lineTotalSoldPrice: 550)],
300, // totalDiscount
550, // grandTotal (sum of lineTotalSoldPrice)
);
// Unit conversion, e.g. Kg (primary) <-> Gram (secondary)
final unitConfig = UnitConfig(
primaryUnitCode: 'kg',
primaryUnitName: 'Kilogram',
secondaryUnitCode: 'g',
secondaryUnitName: 'Gram',
conversion: Decimal.parse('1000'),
);
final inGrams = essentials.unit.toSecondaryQuantity(
Decimal.parse('1.5'), // 1.5 kg
unitConfig: unitConfig,
);
print(inGrams.secondaryQty); // 1500
// Current time in a store's timezone
final storeTime = essentials.date.getCurrentTimeByTimeZone("Asia/Kolkata");
print(storeTime['shortMonthFormat']);
See example/example.dart for more runnable snippets covering grand totals, currency
conversion, and date/time helpers, and the example/test/ directory for expected
input/output pairs for every module.
Notes
- All monetary parameters are integers in subunits (e.g. paise for INR), not
floating-point currency values — convert with
essentials.currency.toSubunit(...)first if you're starting from a display value like150.50. - Rounding uses banker's rounding (round-half-to-even) throughout for consistency
with backend calculations (this package mirrors the rounding/precision rules of the
@heartinz/financeJS package, so results match across platforms). lockparameters (e.g."MRP","SALEPRICE","BASEPRICE") tell a calculator which of the given prices is authoritative when the others need to be derived/ recomputed.